diff --git a/zeppelin-web/src/app/notebook/notebook.controller.js b/zeppelin-web/src/app/notebook/notebook.controller.js index 55384ff3b0a..89d7d7d8654 100644 --- a/zeppelin-web/src/app/notebook/notebook.controller.js +++ b/zeppelin-web/src/app/notebook/notebook.controller.js @@ -71,7 +71,6 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', var currentRoute = $route.current; if (currentRoute) { - setTimeout( function() { var routeParams = currentRoute.params; @@ -91,6 +90,35 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', initNotebook(); + + $scope.focusParagraphOnClick = function(clickEvent) { + if (!$scope.note) { + return; + } + for (var i=0; i<$scope.note.paragraphs.length; i++) { + var paragraphId = $scope.note.paragraphs[i].id; + if (jQuery.contains(angular.element('#' + paragraphId + '_container')[0], clickEvent.target)) { + $scope.$broadcast('focusParagraph', paragraphId, 0, true); + break; + } + } + }; + + // register mouseevent handler for focus paragraph + document.addEventListener('click', $scope.focusParagraphOnClick); + + + $scope.keyboardShortcut = function(keyEvent) { + // handle keyevent + if (!$scope.viewOnly) { + $scope.$broadcast('keyEvent', keyEvent); + } + }; + + // register mouseevent handler for focus paragraph + document.addEventListener('keydown', $scope.keyboardShortcut); + + /** Remove the note and go back tot he main page */ /** TODO(anthony): In the nearly future, go back to the main page and telle to the dude that the note have been remove */ $scope.removeNote = function(noteId) { @@ -238,6 +266,9 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', angular.element(window).off('beforeunload'); $scope.killSaveTimer(); $scope.saveNote(); + + document.removeEventListener('click', $scope.focusParagraphOnClick); + document.removeEventListener('keydown', $scope.keyboardShortcut); }); $scope.setLookAndFeel = function(looknfeel) { @@ -316,24 +347,6 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', return noteCopy; }; - $scope.$on('moveParagraphUp', function(event, paragraphId) { - var newIndex = -1; - for (var i=0; i<$scope.note.paragraphs.length; i++) { - if ($scope.note.paragraphs[i].id === paragraphId) { - newIndex = i-1; - break; - } - } - if (newIndex<0 || newIndex>=$scope.note.paragraphs.length) { - return; - } - // save dirtyText of moving paragraphs. - var prevParagraphId = $scope.note.paragraphs[newIndex].id; - angular.element('#' + paragraphId + '_paragraphColumn_main').scope().saveParagraph(); - angular.element('#' + prevParagraphId + '_paragraphColumn_main').scope().saveParagraph(); - websocketMsgSrv.moveParagraph(paragraphId, newIndex); - }); - // create new paragraph on current position $scope.$on('insertParagraph', function(event, paragraphId, position) { var newIndex = -1; @@ -355,6 +368,24 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', websocketMsgSrv.insertParagraph(newIndex); }); + $scope.$on('moveParagraphUp', function(event, paragraphId) { + var newIndex = -1; + for (var i=0; i<$scope.note.paragraphs.length; i++) { + if ($scope.note.paragraphs[i].id === paragraphId) { + newIndex = i-1; + break; + } + } + if (newIndex<0 || newIndex>=$scope.note.paragraphs.length) { + return; + } + // save dirtyText of moving paragraphs. + var prevParagraphId = $scope.note.paragraphs[newIndex].id; + angular.element('#' + paragraphId + '_paragraphColumn_main').scope().saveParagraph(); + angular.element('#' + prevParagraphId + '_paragraphColumn_main').scope().saveParagraph(); + websocketMsgSrv.moveParagraph(paragraphId, newIndex); + }); + $scope.$on('moveParagraphDown', function(event, paragraphId) { var newIndex = -1; for (var i=0; i<$scope.note.paragraphs.length; i++) { @@ -383,11 +414,8 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', continue; } } else { - var p = $scope.note.paragraphs[i]; - if (!p.config.hide && !p.config.editorHide) { - $scope.$broadcast('focusParagraph', $scope.note.paragraphs[i].id, -1); - break; - } + $scope.$broadcast('focusParagraph', $scope.note.paragraphs[i].id, -1); + break; } } }); @@ -401,11 +429,8 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', continue; } } else { - var p = $scope.note.paragraphs[i]; - if (!p.config.hide && !p.config.editorHide) { - $scope.$broadcast('focusParagraph', $scope.note.paragraphs[i].id, 0); - break; - } + $scope.$broadcast('focusParagraph', $scope.note.paragraphs[i].id, 0); + break; } } }); @@ -426,11 +451,22 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', var numNewParagraphs = newParagraphIds.length; var numOldParagraphs = oldParagraphIds.length; + var paragraphToBeFocused; + var focusedParagraph; + for (var i=0; i<$scope.note.paragraphs.length; i++) { + var paragraphId = $scope.note.paragraphs[i].id; + if (angular.element('#' + paragraphId + '_paragraphColumn_main').scope().paragraphFocused) { + focusedParagraph = paragraphId; + break; + } + } + /** add a new paragraph */ if (numNewParagraphs > numOldParagraphs) { for (var index in newParagraphIds) { if (oldParagraphIds[index] !== newParagraphIds[index]) { $scope.note.paragraphs.splice(index, 0, note.paragraphs[index]); + paragraphToBeFocused = note.paragraphs[index].id; break; } $scope.$broadcast('updateParagraph', {paragraph: note.paragraphs[index]}); @@ -451,6 +487,10 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', // rebuild id list since paragraph has moved. oldParagraphIds = $scope.note.paragraphs.map(function(x) {return x.id;}); } + + if (focusedParagraph === newParagraphIds[idx]) { + paragraphToBeFocused = focusedParagraph; + } } } @@ -463,6 +503,13 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', } } } + + // restore focus of paragraph + for (var f=0; f<$scope.note.paragraphs.length; f++) { + if (paragraphToBeFocused === $scope.note.paragraphs[f].id) { + $scope.note.paragraphs[f].focus = true; + } + } }; var getInterpreterBindings = function(callback) { diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js index 8eafa6fb4dd..68dc36fb223 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js @@ -37,6 +37,9 @@ angular.module('zeppelinWebApp') $scope.colWidthOption = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ]; $scope.showTitleEditor = false; $scope.paragraphFocused = false; + if (newParagraph.focus) { + $scope.paragraphFocused = true; + } if (!$scope.paragraph.config) { $scope.paragraph.config = {}; @@ -244,6 +247,7 @@ angular.module('zeppelinWebApp') }, 500); } } + } }); @@ -284,6 +288,15 @@ angular.module('zeppelinWebApp') commitParagraph($scope.paragraph.title, $scope.paragraph.text, newConfig, newParams); }; + $scope.run = function() { + var editorValue = $scope.editor.getValue(); + if (editorValue) { + if (!($scope.paragraph.status === 'RUNNING' || $scope.paragraph.status === 'PENDING')) { + $scope.runParagraph(editorValue); + } + } + }; + $scope.moveUp = function() { $scope.$emit('moveParagraphUp', $scope.paragraph.id); }; @@ -491,7 +504,9 @@ angular.module('zeppelinWebApp') $scope.editor.setHighlightGutterLine(false); $scope.editor.getSession().setUseWrapMode(true); $scope.editor.setTheme('ace/theme/chrome'); - $scope.editor.focus(); + if ($scope.paragraphFocused) { + $scope.editor.focus(); + } autoAdjustEditorHeight(_editor.container.id); angular.element(window).resize(function() { @@ -591,19 +606,6 @@ angular.module('zeppelinWebApp') $scope.setParagraphMode($scope.editor.getSession(), $scope.editor.getSession().getValue()); - $scope.editor.commands.addCommand({ - name: 'run', - bindKey: {win: 'Shift-Enter', mac: 'Shift-Enter'}, - exec: function(editor) { - var editorValue = editor.getValue(); - if (editorValue) { - if (!($scope.paragraph.status === 'RUNNING' || $scope.paragraph.status === 'PENDING')) { - $scope.runParagraph(editorValue); - } - } - }, - readOnly: false - }); // autocomplete on '.' /* @@ -617,6 +619,10 @@ angular.module('zeppelinWebApp') }); */ + // remove binding + $scope.editor.commands.bindKey('ctrl-alt-n.', null); + + // autocomplete on 'ctrl+.' $scope.editor.commands.bindKey('ctrl-.', 'startAutocomplete'); $scope.editor.commands.bindKey('ctrl-space', null); @@ -636,7 +642,7 @@ angular.module('zeppelinWebApp') var numRows; var currentRow; - if (keyCode === 38 || (keyCode === 80 && e.ctrlKey)) { // UP + if (keyCode === 38 || (keyCode === 80 && e.ctrlKey && !e.altKey)) { // UP numRows = $scope.editor.getSession().getLength(); currentRow = $scope.editor.getCursorPosition().row; if (currentRow === 0) { @@ -645,7 +651,7 @@ angular.module('zeppelinWebApp') } else { $scope.scrollToCursor($scope.paragraph.id, -1); } - } else if (keyCode === 40 || (keyCode === 78 && e.ctrlKey)) { // DOWN + } else if (keyCode === 40 || (keyCode === 78 && e.ctrlKey && !e.altKey)) { // DOWN numRows = $scope.editor.getSession().getLength(); currentRow = $scope.editor.getCursorPosition().row; if (currentRow === numRows-1) { @@ -766,21 +772,94 @@ angular.module('zeppelinWebApp') } }); - $scope.$on('focusParagraph', function(event, paragraphId, cursorPos) { + $scope.$on('keyEvent', function(event, keyEvent) { + if ($scope.paragraphFocused) { + + var paragraphId = $scope.paragraph.id; + var keyCode = keyEvent.keyCode; + var noShortcutDefined = false; + var editorHide = $scope.paragraph.config.editorHide; + + if (editorHide && (keyCode === 38 || (keyCode === 80 && keyEvent.ctrlKey && !keyEvent.altKey))) { // up + // move focus to previous paragraph + $scope.$emit('moveFocusToPreviousParagraph', paragraphId); + } else if (editorHide && (keyCode === 40 || (keyCode === 78 && keyEvent.ctrlKey && !keyEvent.altKey))) { // down + // move focus to next paragraph + $scope.$emit('moveFocusToNextParagraph', paragraphId); + } else if (keyEvent.shiftKey && keyCode === 13) { // Shift + Enter + $scope.run(); + } else if (keyEvent.ctrlKey && keyEvent.altKey && keyCode === 67) { // Ctrl + Alt + c + $scope.cancelParagraph(); + } else if (keyEvent.ctrlKey && keyEvent.altKey && keyCode === 68) { // Ctrl + Alt + d + $scope.removeParagraph(); + } else if (keyEvent.ctrlKey && keyEvent.altKey && keyCode === 75) { // Ctrl + Alt + k + $scope.moveUp(); + } else if (keyEvent.ctrlKey && keyEvent.altKey && keyCode === 74) { // Ctrl + Alt + j + $scope.moveDown(); + } else if (keyEvent.ctrlKey && keyEvent.altKey && keyCode === 66) { // Ctrl + Alt + b + $scope.insertNew(); + } else if (keyEvent.ctrlKey && keyEvent.altKey && keyCode === 79) { // Ctrl + Alt + o + $scope.toggleOutput(); + } else if (keyEvent.ctrlKey && keyEvent.altKey && keyCode === 69) { // Ctrl + Alt + e + $scope.toggleEditor(); + } else if (keyEvent.ctrlKey && keyEvent.altKey && keyCode === 77) { // Ctrl + Alt + m + if ($scope.paragraph.config.lineNumbers) { + $scope.hideLineNumbers(); + } else { + $scope.showLineNumbers(); + } + } else if (keyEvent.ctrlKey && keyEvent.altKey && ((keyCode >= 48 && keyCode <=57) || keyCode === 189 || keyCode === 187)) { // Ctrl + Alt + [1~9,0,-,=] + var colWidth = 12; + if (keyCode === 48) { + colWidth = 10; + } else if (keyCode === 189) { + colWidth = 11; + } else if (keyCode === 187) { + colWidth = 12; + } else { + colWidth = keyCode - 48; + } + $scope.paragraph.config.colWidth = colWidth; + $scope.changeColWidth(); + } else if (keyEvent.ctrlKey && keyEvent.altKey && keyCode === 84) { // Ctrl + Alt + t + if ($scope.paragraph.config.title) { + $scope.hideTitle(); + } else { + $scope.showTitle(); + } + } else { + noShortcutDefined = true; + } + + if (!noShortcutDefined) { + keyEvent.preventDefault(); + } + } + }); + + $scope.$on('focusParagraph', function(event, paragraphId, cursorPos, mouseEvent) { if ($scope.paragraph.id === paragraphId) { // focus editor - $scope.editor.focus(); - - // move cursor to the first row (or the last row) - var row; - if (cursorPos >= 0) { - row = cursorPos; - $scope.editor.gotoLine(row, 0); - } else { - row = $scope.editor.session.getLength(); - $scope.editor.gotoLine(row, 0); + if (!$scope.paragraph.config.editorHide) { + $scope.editor.focus(); + + if (!mouseEvent) { + // move cursor to the first row (or the last row) + var row; + if (cursorPos >= 0) { + row = cursorPos; + $scope.editor.gotoLine(row, 0); + } else { + row = $scope.editor.session.getLength(); + $scope.editor.gotoLine(row, 0); + } + $scope.scrollToCursor($scope.paragraph.id, 0); + } } - $scope.scrollToCursor($scope.paragraph.id, 0); + $scope.handleFocus(true); + } else { + $scope.editor.blur(); + $scope.handleFocus(false); } }); diff --git a/zeppelin-web/src/components/modal-shortcut/modal-shortcut.html b/zeppelin-web/src/components/modal-shortcut/modal-shortcut.html index fe18589a1af..95320399e6f 100644 --- a/zeppelin-web/src/components/modal-shortcut/modal-shortcut.html +++ b/zeppelin-web/src/components/modal-shortcut/modal-shortcut.html @@ -30,7 +30,18 @@

Control in Note

- Run the note + Run paragraph +
+ + +
+
+
+ Ctrl + Alt + c +
+
+
+ Cancel
@@ -56,6 +67,104 @@

Control in Note

+
+
+
+ Ctrl + Alt + d +
+
+
+ Remove paragraph +
+
+ +
+
+
+ Ctrl + Alt + b +
+
+
+ Insert new paragraph below +
+
+ +
+
+
+ Ctrl + Alt + k +
+
+
+ Move paragraph Up +
+
+ +
+
+
+ Ctrl + Alt + j +
+
+
+ Move paragraph Down +
+
+ +
+
+
+ Ctrl + Alt + o +
+
+
+ Toggle output +
+
+ +
+
+
+ Ctrl + Alt + e +
+
+
+ Toggle editor +
+
+ +
+
+
+ Ctrl + Alt + m +
+
+
+ Toggle line number +
+
+ +
+
+
+ Ctrl + Alt + t +
+
+
+ Toggle title +
+
+ +
+
+
+ Ctrl + Alt + 1~0,-,+ +
+
+
+ Set paragraph width from 1 to 12 +
+

Control in Note Editor