You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zeppelin.apache.org by mo...@apache.org on 2015/12/27 20:40:53 UTC
incubator-zeppelin git commit: [ZEPPELIN-391] Keyboard shortcut
Repository: incubator-zeppelin
Updated Branches:
refs/heads/master 3bfd97e23 -> 0a68c0b11
[ZEPPELIN-391] Keyboard shortcut
### What is this PR for?
This PR implements keyboard shortcuts for paragraph control.
### What type of PR is it?
Feature
### Is there a relevant Jira issue?
https://issues.apache.org/jira/browse/ZEPPELIN-391
### How should this be tested?
Try implemented shortcuts
Ctrl + Alt + c : Cancel run
Ctrl + Alt + d : Remove paragraph
Ctrl + Alt + k : Move paragraph Up
Ctrl + Alt + j : Move paragraph Down
Ctrl + Alt + b : Insert new paragraph below
Ctrl + Alt + o : Toggle output
Ctrl + Alt + e : Toggle editor
Ctrl + Alt + m : Toggle line numbers
Ctrl + Alt + t : Toggle title
Ctrl + Alt + 1~0,-,+ : Paragraph width from 1~12
### Questions:
* Does the licenses files need update? no
* Is there breaking changes for older versions? no
* Does this needs documentation? no
Author: Lee moon soo <mo...@apache.org>
Closes #569 from Leemoonsoo/keyboard_shortcut and squashes the following commits:
1ffdb97 [Lee moon soo] Change ctrl-alt-n to ctrl-alt-m to reserve ctrl-alt-p,n
20ffcf3 [Lee moon soo] Ctrl+c -> Ctrl+Alt+c
8f610c5 [Lee moon soo] fix accident change
6aa88d7 [Lee moon soo] Prevent keyboard shortcut on report mode
8b2d23b [Lee moon soo] Add more shortcuts
050fde7 [Lee moon soo] Keep focus after paragraph move
e0adb09 [Lee moon soo] Fix style
42516dc [Lee moon soo] Move focus correctly
07792d0 [Lee moon soo] Make focus and shortcut work when editor is hidden
9d998d2 [Lee moon soo] Double quote -> single quote
bf8a0b0 [Lee moon soo] Add keyboard shortcuts
42654c4 [Lee moon soo] Focus paragraph on click
Project: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/commit/0a68c0b1
Tree: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/tree/0a68c0b1
Diff: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/diff/0a68c0b1
Branch: refs/heads/master
Commit: 0a68c0b11f9bb17a63abd1b75796f09d15b5f082
Parents: 3bfd97e
Author: Lee moon soo <mo...@apache.org>
Authored: Sat Dec 26 10:24:45 2015 -0800
Committer: Lee moon soo <mo...@apache.org>
Committed: Sun Dec 27 11:42:36 2015 -0800
----------------------------------------------------------------------
.../src/app/notebook/notebook.controller.js | 105 +++++++++++----
.../notebook/paragraph/paragraph.controller.js | 135 +++++++++++++++----
.../modal-shortcut/modal-shortcut.html | 111 ++++++++++++++-
3 files changed, 293 insertions(+), 58 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/0a68c0b1/zeppelin-web/src/app/notebook/notebook.controller.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/notebook.controller.js b/zeppelin-web/src/app/notebook/notebook.controller.js
index 55384ff..89d7d7d 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) {
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/0a68c0b1/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
index 8eafa6f..68dc36f 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);
}
});
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/0a68c0b1/zeppelin-web/src/components/modal-shortcut/modal-shortcut.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/components/modal-shortcut/modal-shortcut.html b/zeppelin-web/src/components/modal-shortcut/modal-shortcut.html
index fe18589..9532039 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 @@ limitations under the License.
</div>
</div>
<div class="col-md-8">
- Run the note
+ Run paragraph
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="col-md-4">
+ <div class="keys">
+ <kbd class="kbd-dark">Ctrl</kbd> + <kbd class="kbd-dark">Alt</kbd> + <kbd class="kbd-dark">c</kbd>
+ </div>
+ </div>
+ <div class="col-md-8">
+ Cancel
</div>
</div>
@@ -56,6 +67,104 @@ limitations under the License.
</div>
</div>
+ <div class="row">
+ <div class="col-md-4">
+ <div class="keys">
+ <kbd class="kbd-dark">Ctrl</kbd> + <kbd class="kbd-dark">Alt</kbd> + <kbd class="kbd-dark">d</kbd>
+ </div>
+ </div>
+ <div class="col-md-8">
+ Remove paragraph
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="col-md-4">
+ <div class="keys">
+ <kbd class="kbd-dark">Ctrl</kbd> + <kbd class="kbd-dark">Alt</kbd> + <kbd class="kbd-dark">b</kbd>
+ </div>
+ </div>
+ <div class="col-md-8">
+ Insert new paragraph below
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="col-md-4">
+ <div class="keys">
+ <kbd class="kbd-dark">Ctrl</kbd> + <kbd class="kbd-dark">Alt</kbd> + <kbd class="kbd-dark">k</kbd>
+ </div>
+ </div>
+ <div class="col-md-8">
+ Move paragraph Up
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="col-md-4">
+ <div class="keys">
+ <kbd class="kbd-dark">Ctrl</kbd> + <kbd class="kbd-dark">Alt</kbd> + <kbd class="kbd-dark">j</kbd>
+ </div>
+ </div>
+ <div class="col-md-8">
+ Move paragraph Down
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="col-md-4">
+ <div class="keys">
+ <kbd class="kbd-dark">Ctrl</kbd> + <kbd class="kbd-dark">Alt</kbd> + <kbd class="kbd-dark">o</kbd>
+ </div>
+ </div>
+ <div class="col-md-8">
+ Toggle output
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="col-md-4">
+ <div class="keys">
+ <kbd class="kbd-dark">Ctrl</kbd> + <kbd class="kbd-dark">Alt</kbd> + <kbd class="kbd-dark">e</kbd>
+ </div>
+ </div>
+ <div class="col-md-8">
+ Toggle editor
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="col-md-4">
+ <div class="keys">
+ <kbd class="kbd-dark">Ctrl</kbd> + <kbd class="kbd-dark">Alt</kbd> + <kbd class="kbd-dark">m</kbd>
+ </div>
+ </div>
+ <div class="col-md-8">
+ Toggle line number
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="col-md-4">
+ <div class="keys">
+ <kbd class="kbd-dark">Ctrl</kbd> + <kbd class="kbd-dark">Alt</kbd> + <kbd class="kbd-dark">t</kbd>
+ </div>
+ </div>
+ <div class="col-md-8">
+ Toggle title
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="col-md-4">
+ <div class="keys">
+ <kbd class="kbd-dark">Ctrl</kbd> + <kbd class="kbd-dark">Alt</kbd> + <kbd class="kbd-dark">1</kbd>~<kbd class="kbd-dark">0</kbd>,<kbd class="kbd-dark">-</kbd>,<kbd class="kbd-dark">+</kbd>
+ </div>
+ </div>
+ <div class="col-md-8">
+ Set paragraph width from 1 to 12
+ </div>
+ </div>
<h4>Control in Note Editor</h4>