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>