Skip to content
105 changes: 76 additions & 29 deletions zeppelin-web/src/app/notebook/notebook.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl',
var currentRoute = $route.current;

if (currentRoute) {

setTimeout(
function() {
var routeParams = currentRoute.params;
Expand All @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand All @@ -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++) {
Expand Down Expand Up @@ -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;
}
}
});
Expand All @@ -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;
}
}
});
Expand All @@ -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]});
Expand All @@ -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;
}
}
}

Expand All @@ -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) {
Expand Down
135 changes: 107 additions & 28 deletions zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {};
Expand Down Expand Up @@ -244,6 +247,7 @@ angular.module('zeppelinWebApp')
}, 500);
}
}

}

});
Expand Down Expand Up @@ -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);
};
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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 '.'
/*
Expand All @@ -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);
Expand All @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
}
});

Expand Down
Loading