in zeppelin-web/src/app/notebook/notebook.controller.js [21:1650]
function NotebookCtrl($scope, $route, $routeParams, $location, $rootScope,
$http, websocketMsgSrv, baseUrlSrv, $timeout, saveAsService,
ngToast, noteActionService, noteVarShareService, TRASH_FOLDER_ID,
heliumService) {
'ngInject';
ngToast.dismiss();
$scope.note = null;
$scope.actionOnFormSelectionChange = true;
$scope.hideForms = false;
$scope.disableForms = false;
$scope.editorToggled = false;
$scope.tableToggled = false;
$scope.viewOnly = false;
$scope.showSetting = false;
$scope.showRevisionsComparator = false;
$scope.collaborativeMode = false;
$scope.collaborativeModeUsers = [];
$scope.looknfeelOption = ['default', 'simple', 'report'];
$scope.noteFormTitle = null;
$scope.cronOption = [
{name: 'None', value: undefined},
{name: '1m', value: '0 0/1 * * * ?'},
{name: '5m', value: '0 0/5 * * * ?'},
{name: '1h', value: '0 0 0/1 * * ?'},
{name: '3h', value: '0 0 0/3 * * ?'},
{name: '6h', value: '0 0 0/6 * * ?'},
{name: '12h', value: '0 0 0/12 * * ?'},
{name: '1d', value: '0 0 0 * * ?'},
];
$scope.formatRevisionDate = function(date) {
return moment.unix(date).format('MMMM Do YYYY, h:mm a');
};
// reorder this array without Head revision, put Head revision in the first
$scope.revisionSort = function(array) {
let orderArr = array.slice(1).sort((a, b) => {
return b.time-a.time;
});
orderArr.unshift(array[0]);
return orderArr;
};
$scope.interpreterSettings = [];
$scope.interpreterBindings = [];
$scope.isNoteDirty = null;
$scope.saveTimer = null;
$scope.paragraphWarningDialog = {};
let connectedOnce = false;
let isRevisionPath = function(path) {
let pattern = new RegExp('^.*\/notebook\/[a-zA-Z0-9_]*\/revision\/[a-zA-Z0-9_]*');
return pattern.test(path);
};
$scope.noteRevisions = [];
$scope.currentRevision = 'Head';
$scope.revisionView = isRevisionPath($location.path());
$scope.search = {
searchText: '',
occurrencesExists: false,
needHighlightFirst: false,
occurrencesHidden: false,
replaceText: '',
needToSendNextOccurrenceAfterReplace: false,
occurrencesCount: 0,
currentOccurrence: 0,
searchBoxOpened: false,
searchBoxWidth: 350,
left: '0px',
};
let currentSearchParagraph = 0;
$scope.$watch('note', function(value) {
let title;
if (value) {
title = value.name.substr(value.name.lastIndexOf('/') + 1, value.name.length);
title += ' - Zeppelin';
} else {
title = 'Zeppelin';
}
$rootScope.pageTitle = title;
}, true);
$scope.$on('setConnectedStatus', function(event, param) {
if (connectedOnce && param) {
initNotebook();
}
connectedOnce = true;
});
$scope.addEvent = function(config) {
let removeEventByID = function(id) {
let events = jQuery._data(config.element, 'events')[config.eventType];
if (!events) {
return;
}
for (let i=0; i < events.length; i++) {
if (events[i].data && events[i].data.eventID === id) {
events.splice(i, 1);
i--;
}
}
};
removeEventByID(config.eventID);
angular.element(config.element).bind(config.eventType, {eventID: config.eventID}, config.handler);
angular.element(config.onDestroyElement).scope().$on('$destroy', () => {
removeEventByID(config.eventID);
});
};
$scope.getCronOptionNameFromValue = function(value) {
if (!value) {
return '';
}
for (let o in $scope.cronOption) {
if ($scope.cronOption[o].value === value) {
return $scope.cronOption[o].name;
}
}
return value;
};
$scope.blockAnonUsers = function() {
let zeppelinVersion = $rootScope.zeppelinVersion;
let url = 'https://zeppelin.apache.org/docs/' + zeppelinVersion + '/security/notebook_authorization.html';
let content = 'Only authenticated user can set the permission.' +
'<a data-toggle="tooltip" data-placement="top" title="Learn more" target="_blank" ' +
'rel="noopener noreferrer" href=' + url + '>' +
'<i class="icon-question" />' +
'</a>';
BootstrapDialog.show({
closable: false,
closeByBackdrop: false,
closeByKeyboard: false,
title: 'No permission',
message: content,
buttons: [{
label: 'Close',
action: function(dialog) {
dialog.close();
},
}],
});
};
/** Init the new controller */
const initNotebook = function() {
noteVarShareService.clear();
if ($routeParams.revisionId) {
websocketMsgSrv.getNoteByRevision($routeParams.noteId, $routeParams.revisionId);
} else {
websocketMsgSrv.getNote($routeParams.noteId);
}
websocketMsgSrv.listRevisionHistory($routeParams.noteId);
let currentRoute = $route.current;
if (currentRoute) {
setTimeout(
function() {
let routeParams = currentRoute.params;
let $id = angular.element('#' + routeParams.paragraph + '_container');
if ($id.length > 0) {
// adjust for navbar
let top = $id.offset().top - 103;
angular.element('html, body').scrollTo({top: top, left: 0});
}
},
1000
);
}
};
initNotebook();
$scope.focusParagraphOnClick = function(clickEvent) {
if (!$scope.note) {
return;
}
for (let i = 0; i < $scope.note.paragraphs.length; i++) {
let paragraphId = $scope.note.paragraphs[i].id;
if (jQuery.contains(angular.element('#' + paragraphId + '_container')[0], clickEvent.target)) {
$scope.$broadcast('focusParagraph', paragraphId, 0, null, true);
break;
}
}
};
// register mouseevent handler for focus paragraph
document.addEventListener('click', $scope.focusParagraphOnClick);
let keyboardShortcut = function(keyEvent) {
// handle keyevent
if (!$scope.viewOnly && !$scope.revisionView) {
$scope.$broadcast('keyEvent', keyEvent);
}
};
$scope.keydownEvent = function(keyEvent) {
if ((keyEvent.ctrlKey || keyEvent.metaKey) && String.fromCharCode(keyEvent.which).toLowerCase() === 's') {
keyEvent.preventDefault();
}
keyboardShortcut(keyEvent);
};
// register mouseevent handler for focus paragraph
document.addEventListener('keydown', $scope.keydownEvent);
$scope.paragraphOnDoubleClick = function(paragraphId) {
$scope.$broadcast('doubleClickParagraph', paragraphId);
};
// Move the note to trash and go back to the main page
$scope.moveNoteToTrash = function(noteId) {
noteActionService.moveNoteToTrash(noteId, true);
};
// Remove the note permanently if it's in the trash
$scope.removeNote = function(noteId) {
noteActionService.removeNote(noteId, true);
};
$scope.isTrash = function(note) {
return note && note.path ? note.path.split('/')[1] === TRASH_FOLDER_ID : false;
};
// Export notebook
let limit = 0;
websocketMsgSrv.listConfigurations();
$scope.$on('configurationsInfo', function(scope, event) {
limit = event.configurations['zeppelin.websocket.max.text.message.size'];
});
$scope.exportNote = function() {
let jsonContent = JSON.stringify($scope.note, null, 2);
if (jsonContent.length > limit) {
BootstrapDialog.confirm({
closable: true,
title: 'Note size exceeds importable limit (' + limit + ')',
message: 'Do you still want to export this note?',
callback: function(result) {
if (result) {
saveAsService.saveAs(jsonContent, $scope.note.name + '_' + $scope.note.id, 'zpln');
}
},
});
} else {
saveAsService.saveAs(jsonContent, $scope.note.name + '_' + $scope.note.id, 'zpln');
}
};
// Export nbformat
$scope.exportNbformat = function() {
websocketMsgSrv.convertNote($scope.note.id, $scope.note.name);
};
// Export nbformat
$scope.reloadNote = function() {
websocketMsgSrv.reloadNote($scope.note.id);
};
// Clone note
$scope.cloneNote = function(noteId) {
BootstrapDialog.confirm({
closable: true,
title: '',
message: 'Do you want to clone this note?',
callback: function(result) {
if (result) {
websocketMsgSrv.cloneNote(noteId);
$location.path('/');
}
},
});
};
// checkpoint/commit notebook
$scope.checkpointNote = function(commitMessage) {
BootstrapDialog.confirm({
closable: true,
title: '',
message: 'Commit note to current repository?',
callback: function(result) {
if (result) {
websocketMsgSrv.checkpointNote($routeParams.noteId, commitMessage);
}
},
});
document.getElementById('note.checkpoint.message').value = '';
};
// set notebook head to given revision
$scope.setNoteRevision = function() {
BootstrapDialog.confirm({
closable: true,
title: '',
message: 'Set notebook head to current revision?',
callback: function(result) {
if (result) {
websocketMsgSrv.setNoteRevision($routeParams.noteId, $routeParams.revisionId);
}
},
});
};
$scope.preVisibleRevisionsComparator = function() {
$scope.mergeNoteRevisionsForCompare = null;
$scope.firstNoteRevisionForCompare = null;
$scope.secondNoteRevisionForCompare = null;
$scope.currentFirstRevisionForCompare = 'Choose...';
$scope.currentSecondRevisionForCompare = 'Choose...';
$scope.$apply();
};
$scope.$on('listRevisionHistory', function(event, data) {
console.debug('received list of revisions %o', data);
$scope.noteRevisions = data.revisionList;
if ($scope.noteRevisions) {
if ($scope.noteRevisions.length === 0 || $scope.noteRevisions[0].id !== 'Head') {
$scope.noteRevisions.splice(0, 0, {
id: 'Head',
message: 'Head',
time: $scope.noteRevisions[0].time,
});
}
if ($routeParams.revisionId) {
let index = _.findIndex($scope.noteRevisions,
{'id': $routeParams.revisionId});
if (index > -1) {
$scope.currentRevision = $scope.noteRevisions[index].message;
}
}
}
});
$scope.$on('noteRevision', function(event, data) {
console.log('received note revision %o', data);
if (data.note) {
$scope.note = data.note;
initializeLookAndFeel();
} else {
$location.path('/');
}
});
$scope.$on('setNoteRevisionResult', function(event, data) {
console.log('received set note revision result %o', data);
if (data.status) {
$location.path('/notebook/' + $routeParams.noteId);
}
});
$scope.visitRevision = function(revision) {
if (revision.id) {
if (revision.id === 'Head') {
$location.path('/notebook/' + $routeParams.noteId);
} else {
$location.path('/notebook/' + $routeParams.noteId + '/revision/' + revision.id);
}
} else {
ngToast.danger({content: 'There is a problem with this Revision',
verticalPosition: 'top',
dismissOnTimeout: false,
});
}
};
$scope.runAllParagraphs = function(noteId) {
BootstrapDialog.confirm({
closable: true,
title: '',
message: 'Run all paragraphs?',
callback: function(result) {
if (result) {
const paragraphs = $scope.note.paragraphs.map((p) => {
return {
id: p.id,
title: p.title,
paragraph: p.text,
config: p.config,
params: p.settings.params,
};
});
websocketMsgSrv.runAllParagraphs(noteId, paragraphs);
}
},
});
};
$scope.saveNote = function() {
if ($scope.note && $scope.note.paragraphs) {
_.forEach($scope.note.paragraphs, function(par) {
angular
.element('#' + par.id + '_paragraphColumn_main')
.scope()
.saveParagraph(par);
});
$scope.isNoteDirty = null;
}
};
$scope.clearAllParagraphOutput = function(noteId) {
noteActionService.clearAllParagraphOutput(noteId);
};
$scope.toggleAllEditor = function() {
if ($scope.editorToggled) {
$scope.$broadcast('openEditor');
} else {
$scope.$broadcast('closeEditor');
}
$scope.editorToggled = !$scope.editorToggled;
};
$scope.showAllEditor = function() {
$scope.$broadcast('openEditor');
};
$scope.hideAllEditor = function() {
$scope.$broadcast('closeEditor');
};
$scope.toggleAllTable = function() {
if ($scope.tableToggled) {
$scope.$broadcast('openTable');
} else {
$scope.$broadcast('closeTable');
}
$scope.tableToggled = !$scope.tableToggled;
};
$scope.showAllTable = function() {
$scope.$broadcast('openTable');
};
$scope.hideAllTable = function() {
$scope.$broadcast('closeTable');
};
$scope.toggleAllNumbering = function() {
if ($scope.note.config.numberingToggled) {
$scope.$broadcast('setNumbering', false);
} else {
$scope.$broadcast('setNumbering', true);
}
$scope.note.config.numberingToggled = !$scope.note.config.numberingToggled;
$scope.setConfig();
};
$scope.updateParagraphNumbering = function() {
for (let i = 0; i < $scope.note.paragraphs.length; i++) {
$scope.note.paragraphs[i].number = i + 1;
}
};
/**
* @returns {boolean} true if one more paragraphs are running. otherwise return false.
*/
$scope.isNoteRunning = function() {
if (!$scope.note) {
return false;
}
for (let i = 0; i < $scope.note.paragraphs.length; i++) {
if (isParagraphRunning($scope.note.paragraphs[i])) {
return true;
}
}
return false;
};
$scope.killSaveTimer = function() {
if ($scope.saveTimer) {
$timeout.cancel($scope.saveTimer);
$scope.saveTimer = null;
}
};
$scope.startSaveTimer = function() {
$scope.killSaveTimer();
$scope.isNoteDirty = true;
// console.log('startSaveTimer called ' + $scope.note.id);
$scope.saveTimer = $timeout(function() {
$scope.saveNote();
}, 10000);
};
$scope.setLookAndFeel = function(looknfeel) {
$scope.note.config.looknfeel = looknfeel;
if ($scope.revisionView === true) {
$rootScope.$broadcast('setLookAndFeel', $scope.note.config.looknfeel);
} else {
$scope.setConfig();
}
};
$scope.setNoteFormTitle = function(noteFormTitle) {
$scope.note.config.noteFormTitle = noteFormTitle;
$scope.setConfig();
};
/** Set cron expression for this note **/
$scope.setCronScheduler = function(cronExpr) {
if (cronExpr) {
if (!$scope.note.config.cronExecutingUser) {
$scope.note.config.cronExecutingUser = $rootScope.ticket.principal;
}
if (!$scope.note.config.cronExecutingRoles) {
$scope.note.config.cronExecutingRoles = $rootScope.ticket.roles;
}
} else {
$scope.note.config.cronExecutingUser = '';
$scope.note.config.cronExecutingRoles = '';
}
$scope.note.config.cron = cronExpr;
$scope.setConfig();
};
/** Update note config **/
$scope.setConfig = function(config) {
if (config) {
$scope.note.config = config;
}
websocketMsgSrv.updateNote($scope.note.id, $scope.note.name, $scope.note.config);
};
/** Update the note name */
$scope.updateNoteName = function(newName) {
const trimmedNewName = newName.trim();
if (trimmedNewName.length > 0 && $scope.note.name !== trimmedNewName) {
$scope.note.name = trimmedNewName;
websocketMsgSrv.renameNote($scope.note.id, $scope.note.name, true);
}
};
const initializeLookAndFeel = function() {
if (!$scope.note.config.looknfeel) {
$scope.note.config.looknfeel = 'default';
} else {
$scope.viewOnly = $scope.note.config.looknfeel === 'report' ? true : false;
}
if ($scope.note.paragraphs && $scope.note.paragraphs[0]) {
$scope.note.paragraphs[0].focus = true;
}
$rootScope.$broadcast('setLookAndFeel', $scope.note.config.looknfeel);
};
let cleanParagraphExcept = function(paragraphId, note) {
let noteCopy = {};
noteCopy.id = note.id;
noteCopy.name = note.name;
noteCopy.config = note.config;
noteCopy.info = note.info;
noteCopy.paragraphs = [];
for (let i = 0; i < note.paragraphs.length; i++) {
if (note.paragraphs[i].id === paragraphId) {
noteCopy.paragraphs[0] = note.paragraphs[i];
if (!noteCopy.paragraphs[0].config) {
noteCopy.paragraphs[0].config = {};
}
noteCopy.paragraphs[0].config.editorHide = true;
noteCopy.paragraphs[0].config.tableHide = false;
break;
}
}
return noteCopy;
};
let addPara = function(paragraph, index) {
$scope.note.paragraphs.splice(index, 0, paragraph);
$scope.note.paragraphs.map((para) => {
if (para.id === paragraph.id) {
para.focus = true;
// we need `$timeout` since angular DOM might not be initialized
$timeout(() => {
$scope.$broadcast('focusParagraph', para.id, 0, null, false);
});
}
});
};
let removePara = function(paragraphId) {
let removeIdx;
_.each($scope.note.paragraphs, function(para, idx) {
if (para.id === paragraphId) {
removeIdx = idx;
}
});
return $scope.note.paragraphs.splice(removeIdx, 1);
};
$scope.$on('addParagraph', function(event, paragraph, index) {
if ($scope.paragraphUrl || $scope.revisionView === true) {
return;
}
addPara(paragraph, index);
$scope.updateParagraphNumbering();
});
$scope.$on('removeParagraph', function(event, paragraphId) {
if ($scope.paragraphUrl || $scope.revisionView === true) {
return;
}
removePara(paragraphId);
$scope.updateParagraphNumbering();
});
$scope.$on('moveParagraph', function(event, paragraphId, newIdx) {
if ($scope.revisionView === true) {
return;
}
let removedPara = removePara(paragraphId);
if (removedPara && removedPara.length === 1) {
addPara(removedPara[0], newIdx);
}
$scope.updateParagraphNumbering();
});
$scope.$on('updateNote', function(event, name, config, info) {
/** update Note name */
if (name !== $scope.note.name) {
console.log('change note name to : %o', $scope.note.name);
$scope.note.name = name;
}
$scope.note.config = config;
$scope.note.info = info;
initializeLookAndFeel();
});
let getInterpreterBindings = function() {
websocketMsgSrv.getInterpreterBindings($scope.note.id);
};
$scope.$on('interpreterBindings', function(event, data) {
$scope.interpreterBindings = data.interpreterBindings;
$scope.interpreterBindingsOrig = angular.copy($scope.interpreterBindings); // to check dirty
let selected = false;
let key;
let setting;
for (key in $scope.interpreterBindings) {
if($scope.interpreterBindings.hasOwnProperty(key)) {
setting = $scope.interpreterBindings[key];
if (setting.selected) {
selected = true;
break;
}
}
}
if (!selected) {
// make default selection
let selectedIntp = {};
for (key in $scope.interpreterBindings) {
if ($scope.interpreterBindings.hasOwnProperty(key)) {
setting = $scope.interpreterBindings[key];
if (!selectedIntp[setting.name]) {
setting.selected = true;
selectedIntp[setting.name] = true;
}
}
}
$scope.showSetting = true;
}
});
$scope.interpreterSelectionListeners = {
accept: function(sourceItemHandleScope, destSortableScope) {
return true;
},
itemMoved: function(event) {},
orderChanged: function(event) {},
};
$scope.closeAdditionalBoards = function() {
$scope.closeSetting();
$scope.closePermissions();
$scope.closeRevisionsComparator();
};
$scope.openSetting = function() {
$scope.showSetting = true;
getInterpreterBindings();
};
$scope.closeSetting = function() {
if (isSettingDirty()) {
BootstrapDialog.confirm({
closable: true,
title: '',
message: 'Interpreter setting changes will be discarded.',
callback: function(result) {
if (result) {
$scope.$apply(function() {
$scope.showSetting = false;
});
}
},
});
} else {
$scope.showSetting = false;
}
};
$scope.saveSetting = function() {
let selectedSettingIds = [];
for (let no in $scope.interpreterBindings) {
if ($scope.interpreterBindings.hasOwnProperty(no)) {
let setting = $scope.interpreterBindings[no];
if (setting.selected) {
selectedSettingIds.push(setting.id);
}
}
}
websocketMsgSrv.saveInterpreterBindings($scope.note.id, selectedSettingIds);
console.log('Interpreter bindings %o saved', selectedSettingIds);
_.forEach($scope.note.paragraphs, function(n, key) {
let regExp = /^\s*%/g;
if (n.text && !regExp.exec(n.text)) {
$scope.$broadcast('saveInterpreterBindings', n.id);
}
});
$scope.showSetting = false;
};
$scope.toggleSetting = function() {
if ($scope.showSetting) {
$scope.closeSetting();
} else {
$scope.closeAdditionalBoards();
$scope.openSetting();
angular.element('html, body').animate({scrollTop: 0}, 'slow');
}
};
$scope.openRevisionsComparator = function() {
$scope.showRevisionsComparator = true;
};
$scope.closeRevisionsComparator = function() {
$scope.showRevisionsComparator = false;
};
$scope.toggleRevisionsComparator = function() {
if ($scope.showRevisionsComparator) {
$scope.closeRevisionsComparator();
} else {
$scope.closeAdditionalBoards();
$scope.openRevisionsComparator();
angular.element('html, body').animate({scrollTop: 0}, 'slow');
}
};
let getPermissions = function(callback) {
$http.get(baseUrlSrv.getRestApiBase() + '/notebook/' + $scope.note.id + '/permissions')
.success(function(data, status, headers, config) {
$scope.permissions = data.body;
$scope.permissionsOrig = angular.copy($scope.permissions); // to check dirty
let selectJson = {
tokenSeparators: [',', ' '],
ajax: {
url: function(params) {
if (!params.term) {
return false;
}
return baseUrlSrv.getRestApiBase() + '/security/userlist/' + params.term;
},
delay: 250,
processResults: function(data, params) {
let results = [];
if (data.body.users.length !== 0) {
let users = [];
for (let len = 0; len < data.body.users.length; len++) {
users.push({
'id': data.body.users[len],
'text': data.body.users[len],
});
}
results.push({
'text': 'Users :',
'children': users,
});
}
if (data.body.roles.length !== 0) {
let roles = [];
for (let len = 0; len < data.body.roles.length; len++) {
roles.push({
'id': data.body.roles[len],
'text': data.body.roles[len],
});
}
results.push({
'text': 'Roles :',
'children': roles,
});
}
return {
results: results,
pagination: {
more: false,
},
};
},
cache: false,
},
width: ' ',
tags: true,
minimumInputLength: 3,
};
$scope.setMyPermissions();
angular.element('#selectOwners').select2(selectJson);
angular.element('#selectReaders').select2(selectJson);
angular.element('#selectRunners').select2(selectJson);
angular.element('#selectWriters').select2(selectJson);
if (callback) {
callback();
}
})
.error(function(data, status, headers, config) {
if (status !== 0) {
console.log('Error %o %o', status, data.message);
}
});
};
$scope.openPermissions = function() {
$scope.showPermissions = true;
getPermissions();
};
$scope.closePermissions = function() {
if (isPermissionsDirty()) {
BootstrapDialog.confirm({
closable: true,
title: '',
message: 'Changes will be discarded.',
callback: function(result) {
if (result) {
$scope.$apply(function() {
$scope.showPermissions = false;
});
}
},
});
} else {
$scope.showPermissions = false;
}
};
function convertPermissionsToArray() {
$scope.permissions.owners = angular.element('#selectOwners').val();
$scope.permissions.readers = angular.element('#selectReaders').val();
$scope.permissions.runners = angular.element('#selectRunners').val();
$scope.permissions.writers = angular.element('#selectWriters').val();
angular.element('.permissionsForm select').find('option:not([is-select2="false"])').remove();
}
$scope.hasMatches = function() {
return $scope.search.occurrencesCount > 0;
};
const markAllOccurrences = function() {
$scope.search.occurrencesCount = 0;
$scope.search.occurrencesHidden = false;
currentSearchParagraph = 0;
$scope.$broadcast('markAllOccurrences', $scope.search.searchText);
$scope.search.currentOccurrence = $scope.search.occurrencesCount > 0 ? 1 : 0;
};
$scope.markAllOccurrencesAndHighlightFirst = function() {
$scope.search.needHighlightFirst = true;
markAllOccurrences();
};
const increaseCurrentOccurence = function() {
++$scope.search.currentOccurrence;
if ($scope.search.currentOccurrence > $scope.search.occurrencesCount) {
$scope.search.currentOccurrence = 1;
}
};
const decreaseCurrentOccurence = function() {
--$scope.search.currentOccurrence;
if ($scope.search.currentOccurrence === 0) {
$scope.search.currentOccurrence = $scope.search.occurrencesCount;
}
};
const sendNextOccurrenceMessage = function() {
if ($scope.search.occurrencesCount === 0) {
markAllOccurrences();
if ($scope.search.occurrencesCount === 0) {
return;
}
}
if ($scope.search.occurrencesHidden) {
markAllOccurrences();
}
$scope.$broadcast('nextOccurrence', $scope.note.paragraphs[currentSearchParagraph].id);
};
const sendPrevOccurrenceMessage = function() {
if ($scope.search.occurrencesCount === 0) {
markAllOccurrences();
if ($scope.search.occurrencesCount === 0) {
return;
}
}
if ($scope.search.occurrencesHidden) {
markAllOccurrences();
currentSearchParagraph = $scope.note.paragraphs.length - 1;
}
$scope.$broadcast('prevOccurrence', $scope.note.paragraphs[currentSearchParagraph].id);
};
const increaseCurrentSearchParagraph = function() {
++currentSearchParagraph;
if (currentSearchParagraph >= $scope.note.paragraphs.length) {
currentSearchParagraph = 0;
}
};
const decreaseCurrentSearchParagraph = function() {
--currentSearchParagraph;
if (currentSearchParagraph === -1) {
currentSearchParagraph = $scope.note.paragraphs.length - 1;
}
};
$scope.$on('occurrencesExists', function(event, count) {
$scope.search.occurrencesCount += count;
if ($scope.search.needHighlightFirst) {
sendNextOccurrenceMessage();
$scope.search.needHighlightFirst = false;
}
});
$scope.nextOccurrence = function() {
sendNextOccurrenceMessage();
increaseCurrentOccurence();
};
$scope.$on('noNextOccurrence', function(event) {
increaseCurrentSearchParagraph();
sendNextOccurrenceMessage();
});
$scope.prevOccurrence = function() {
sendPrevOccurrenceMessage();
decreaseCurrentOccurence();
};
$scope.$on('noPrevOccurrence', function(event) {
decreaseCurrentSearchParagraph();
sendPrevOccurrenceMessage();
});
$scope.$on('editorClicked', function() {
$scope.search.occurrencesHidden = true;
$scope.$broadcast('unmarkAll');
});
$scope.replace = function() {
if ($scope.search.occurrencesCount === 0) {
$scope.markAllOccurrencesAndHighlightFirst();
if ($scope.search.occurrencesCount === 0) {
return;
}
}
if ($scope.search.occurrencesHidden) {
$scope.markAllOccurrencesAndHighlightFirst();
return;
}
$scope.$broadcast('replaceCurrent', $scope.search.searchText, $scope.search.replaceText);
if ($scope.search.needToSendNextOccurrenceAfterReplace) {
sendNextOccurrenceMessage();
$scope.search.needToSendNextOccurrenceAfterReplace = false;
}
};
$scope.$on('occurrencesCountChanged', function(event, cnt) {
$scope.search.occurrencesCount += cnt;
if ($scope.search.occurrencesCount === 0) {
$scope.search.currentOccurrence = 0;
} else {
$scope.search.currentOccurrence += cnt + 1;
if ($scope.search.currentOccurrence > $scope.search.occurrencesCount) {
$scope.search.currentOccurrence = 1;
}
}
});
$scope.replaceAll = function() {
if ($scope.search.occurrencesCount === 0) {
return;
}
if ($scope.search.occurrencesHidden) {
$scope.markAllOccurrencesAndHighlightFirst();
}
$scope.$broadcast('replaceAll', $scope.search.searchText, $scope.search.replaceText);
$scope.markAllOccurrencesAndHighlightFirst();
};
$scope.$on('noNextOccurrenceAfterReplace', function() {
$scope.search.occurrencesCount = 0;
$scope.search.needHighlightFirst = false;
$scope.search.needToSendNextOccurrenceAfterReplace = false;
$scope.$broadcast('checkOccurrences');
increaseCurrentSearchParagraph();
if ($scope.search.occurrencesCount > 0) {
$scope.search.needToSendNextOccurrenceAfterReplace = true;
}
});
$scope.onPressOnFindInput = function(event) {
if (event.keyCode === 13) {
$scope.nextOccurrence();
}
};
let makeSearchBoxVisible = function() {
if ($scope.search.searchBoxOpened) {
$scope.search.searchBoxOpened = false;
console.log('make 0');
$scope.search.left = '0px';
} else {
$scope.search.searchBoxOpened = true;
let searchGroupRect = angular.element('#searchGroup')[0].getBoundingClientRect();
console.log('make visible');
let dropdownRight = searchGroupRect.left + $scope.search.searchBoxWidth;
console.log(dropdownRight + ' ' + window.innerWidth);
if (dropdownRight + 5 > window.innerWidth) {
$scope.search.left = window.innerWidth - dropdownRight - 15 + 'px';
}
}
};
$scope.searchClicked = function() {
makeSearchBoxVisible();
};
$scope.$on('toggleSearchBox', function() {
let elem = angular.element('#searchGroup');
if ($scope.search.searchBoxOpened) {
elem.removeClass('open');
} else {
elem.addClass('open');
}
$timeout(makeSearchBoxVisible());
});
$scope.restartInterpreter = function(interpreter) {
const thisConfirm = BootstrapDialog.confirm({
closable: false,
closeByBackdrop: false,
closeByKeyboard: false,
title: '',
message: 'Do you want to restart ' + _.escape(interpreter.name) + ' interpreter?',
callback: function(result) {
if (result) {
let payload = {
'noteId': $scope.note.id,
};
thisConfirm.$modalFooter.find('button').addClass('disabled');
thisConfirm.$modalFooter.find('button:contains("OK")')
.html('<i class="fa fa-circle-o-notch fa-spin"></i> Saving Setting');
$http.put(baseUrlSrv.getRestApiBase() + '/interpreter/setting/restart/' + interpreter.id, payload)
.success(function(data, status, headers, config) {
let index = _.findIndex($scope.interpreterSettings, {'id': interpreter.id});
$scope.interpreterSettings[index] = data.body;
thisConfirm.close();
}).error(function(data, status, headers, config) {
thisConfirm.close();
console.log('Error %o %o', status, data.message);
BootstrapDialog.show({
title: 'Error restart interpreter.',
message: _.escape(data.message),
});
});
return false;
}
},
});
};
$scope.savePermissions = function() {
if ($scope.isAnonymous || $rootScope.ticket.principal.trim().length === 0) {
$scope.blockAnonUsers();
}
convertPermissionsToArray();
if ($scope.isOwnerEmpty()) {
BootstrapDialog.show({
closable: false,
title: 'Setting Owners Permissions',
message: 'Please fill the [Owners] field. If not, it will set as current user.\n\n' +
'Current user : [ ' + _.escape($rootScope.ticket.principal) + ']',
buttons: [
{
label: 'Set',
action: function(dialog) {
dialog.close();
$scope.permissions.owners = [$rootScope.ticket.principal];
$scope.setPermissions();
},
},
{
label: 'Cancel',
action: function(dialog) {
dialog.close();
$scope.openPermissions();
},
},
],
});
} else {
$scope.setPermissions();
}
};
$scope.setPermissions = function() {
$http.put(baseUrlSrv.getRestApiBase() + '/notebook/' + $scope.note.id + '/permissions',
$scope.permissions, {withCredentials: true})
.success(function(data, status, headers, config) {
getPermissions(function() {
console.log('Note permissions %o saved', $scope.permissions);
BootstrapDialog.alert({
closable: true,
title: 'Permissions Saved Successfully',
message: 'Owners : ' + _.escape($scope.permissions.owners)
+ '\n\n' +
'Readers : ' + _.escape($scope.permissions.readers) +
'\n\n' +
'Runners : ' + _.escape($scope.permissions.runners) +
'\n\n' +
'Writers : ' + _.escape($scope.permissions.writers),
});
$scope.showPermissions = false;
});
})
.error(function(data, status, headers, config) {
console.log('Error %o %o', status, data.message);
BootstrapDialog.show({
closable: false,
closeByBackdrop: false,
closeByKeyboard: false,
title: 'Insufficient privileges',
message: _.escape(data.message),
buttons: [
{
label: 'Login',
action: function(dialog) {
dialog.close();
angular.element('#loginModal').modal({
show: 'true',
});
},
},
{
label: 'Cancel',
action: function(dialog) {
dialog.close();
$location.path('/');
},
},
],
});
});
};
$scope.togglePermissions = function() {
let principal = $rootScope.ticket.principal;
$scope.isAnonymous = principal === 'anonymous' ? true : false;
if (!!principal && $scope.isAnonymous) {
$scope.blockAnonUsers();
} else {
if ($scope.showPermissions) {
$scope.closePermissions();
angular.element('#selectOwners').select2({});
angular.element('#selectReaders').select2({});
angular.element('#selectRunners').select2({});
angular.element('#selectWriters').select2({});
} else {
$scope.closeAdditionalBoards();
$scope.openPermissions();
}
}
};
const arrayIntersection = function(arrayFirst, arraySecond) {
return arrayFirst.filter(function(x) {
if(arraySecond.indexOf(x) !== -1) {
return true;
} else {
return false;
}
});
};
$scope.setMyPermissions = function() {
let myPermissions;
try {
myPermissions = JSON.parse($rootScope.ticket.roles);
} catch(err) {
myPermissions = [];
}
myPermissions.push($rootScope.ticket.principal);
$scope.isOwner = !($scope.permissions.owners.length > 0 &&
arrayIntersection(myPermissions, $scope.permissions.owners).length === 0);
$scope.isWriter = !($scope.permissions.writers.length > 0 &&
arrayIntersection(myPermissions, $scope.permissions.writers).length === 0);
if (!$scope.isOwner && !$scope.isWriter) {
$scope.viewOnly = true;
$scope.note.config.looknfeel = 'report';
initializeLookAndFeel();
}
};
$scope.toggleNotePersonalizedMode = function() {
let personalizedMode = $scope.note.config.personalizedMode;
if ($scope.isOwner) {
BootstrapDialog.confirm({
closable: true,
title: 'Setting the result display',
message: function(dialog) {
let modeText = $scope.note.config.personalizedMode === 'true' ? 'collaborate' : 'personalize';
return 'Do you want to <span class="text-info">' + modeText + '</span> your analysis?';
},
callback: function(result) {
if (result) {
if ($scope.note.config.personalizedMode === undefined) {
$scope.note.config.personalizedMode = 'false';
}
$scope.note.config.personalizedMode = personalizedMode === 'true' ? 'false' : 'true';
websocketMsgSrv.updatePersonalizedMode($scope.note.id, $scope.note.config.personalizedMode);
}
},
});
}
};
const isSettingDirty = function() {
if (angular.equals($scope.interpreterBindings, $scope.interpreterBindingsOrig)) {
return false;
} else {
return true;
}
};
const isPermissionsDirty = function() {
if (angular.equals($scope.permissions, $scope.permissionsOrig)) {
return false;
} else {
return true;
}
};
angular.element(document).click(function() {
angular.element('.ace_autocomplete').hide();
});
$scope.isOwnerEmpty = function() {
if ($scope.permissions.owners.length > 0) {
for (let i = 0; i < $scope.permissions.owners.length; i++) {
if ($scope.permissions.owners[i].trim().length > 0) {
return false;
} else if (i === $scope.permissions.owners.length - 1) {
return true;
}
}
} else {
return true;
}
};
/*
** $scope.$on functions below
*/
$scope.$on('runAllAbove', function(event, paragraph, isNeedConfirm) {
let allParagraphs = $scope.note.paragraphs;
let toRunParagraphs = [];
for (let i = 0; allParagraphs[i] !== paragraph; i++) {
if (i === allParagraphs.length - 1) {
return;
} // if paragraph not in array of all paragraphs
toRunParagraphs.push(allParagraphs[i]);
}
const paragraphs = toRunParagraphs.map((p) => {
return {
id: p.id,
title: p.title,
paragraph: p.text,
config: p.config,
params: p.settings.params,
};
});
if (!isNeedConfirm) {
websocketMsgSrv.runAllParagraphs($scope.note.id, paragraphs);
} else {
BootstrapDialog.confirm({
closable: true,
title: '',
message: 'Run all above?',
callback: function(result) {
if (result) {
websocketMsgSrv.runAllParagraphs($scope.note.id, paragraphs);
}
},
});
}
$scope.saveCursorPosition(paragraph);
});
$scope.$on('collaborativeModeStatus', function(event, data) {
$scope.collaborativeMode = Boolean(data.status);
$scope.collaborativeModeUsers = data.users;
});
$scope.$on('patchReceived', function(event, data) {
$scope.collaborativeMode = true;
});
$scope.$on('runAllBelowAndCurrent', function(event, paragraph, isNeedConfirm) {
let allParagraphs = $scope.note.paragraphs;
let toRunParagraphs = [];
for (let i = allParagraphs.length - 1; allParagraphs[i] !== paragraph; i--) {
if (i < 0) {
return;
} // if paragraph not in array of all paragraphs
toRunParagraphs.push(allParagraphs[i]);
}
toRunParagraphs.push(paragraph);
toRunParagraphs.reverse();
const paragraphs = toRunParagraphs.map((p) => {
return {
id: p.id,
title: p.title,
paragraph: p.text,
config: p.config,
params: p.settings.params,
};
});
if (!isNeedConfirm) {
websocketMsgSrv.runAllParagraphs($scope.note.id, paragraphs);
} else {
BootstrapDialog.confirm({
closable: true,
title: '',
message: 'Run current and all below?',
callback: function(result) {
if (result) {
websocketMsgSrv.runAllParagraphs($scope.note.id, paragraphs);
}
},
});
}
$scope.saveCursorPosition(paragraph);
});
$scope.saveCursorPosition = function(paragraph) {
let angParagEditor = angular
.element('#' + paragraph.id + '_paragraphColumn_main')
.scope().editor;
let col = angParagEditor.selection.lead.column;
let row = angParagEditor.selection.lead.row;
$scope.$broadcast('focusParagraph', paragraph.id, row + 1, col);
};
$scope.$on('moveParagraphUp', function(event, paragraph) {
let newIndex = -1;
for (let i = 0; i < $scope.note.paragraphs.length; i++) {
if ($scope.note.paragraphs[i].id === paragraph.id) {
newIndex = i - 1;
break;
}
}
if (newIndex < 0 || newIndex >= $scope.note.paragraphs.length) {
return;
}
// save dirtyText of moving paragraphs.
let prevParagraph = $scope.note.paragraphs[newIndex];
angular
.element('#' + paragraph.id + '_paragraphColumn_main')
.scope()
.saveParagraph(paragraph);
angular
.element('#' + prevParagraph.id + '_paragraphColumn_main')
.scope()
.saveParagraph(prevParagraph);
websocketMsgSrv.moveParagraph(paragraph.id, newIndex);
});
$scope.$on('moveParagraphDown', function(event, paragraph) {
let newIndex = -1;
for (let i = 0; i < $scope.note.paragraphs.length; i++) {
if ($scope.note.paragraphs[i].id === paragraph.id) {
newIndex = i + 1;
break;
}
}
if (newIndex < 0 || newIndex >= $scope.note.paragraphs.length) {
return;
}
// save dirtyText of moving paragraphs.
let nextParagraph = $scope.note.paragraphs[newIndex];
angular
.element('#' + paragraph.id + '_paragraphColumn_main')
.scope()
.saveParagraph(paragraph);
angular
.element('#' + nextParagraph.id + '_paragraphColumn_main')
.scope()
.saveParagraph(nextParagraph);
websocketMsgSrv.moveParagraph(paragraph.id, newIndex);
});
$scope.$on('moveFocusToPreviousParagraph', function(event, currentParagraphId) {
let focus = false;
for (let i = $scope.note.paragraphs.length - 1; i >= 0; i--) {
if (focus === false) {
if ($scope.note.paragraphs[i].id === currentParagraphId) {
focus = true;
continue;
}
} else {
$scope.$broadcast('focusParagraph', $scope.note.paragraphs[i].id, -1);
break;
}
}
});
$scope.$on('moveFocusToNextParagraph', function(event, currentParagraphId) {
let focus = false;
for (let i = 0; i < $scope.note.paragraphs.length; i++) {
if (focus === false) {
if ($scope.note.paragraphs[i].id === currentParagraphId) {
focus = true;
continue;
}
} else {
$scope.$broadcast('focusParagraph', $scope.note.paragraphs[i].id, 0);
break;
}
}
});
$scope.$on('insertParagraph', function(event, paragraphId, position) {
if ($scope.revisionView === true) {
return;
}
let newIndex = -1;
for (let i = 0; i < $scope.note.paragraphs.length; i++) {
if ($scope.note.paragraphs[i].id === paragraphId) {
// determine position of where to add new paragraph; default is below
if (position === 'above') {
newIndex = i;
} else {
newIndex = i + 1;
}
break;
}
}
if (newIndex < 0 || newIndex > $scope.note.paragraphs.length) {
return;
}
websocketMsgSrv.insertParagraph(newIndex);
});
$scope.$on('setNoteContent', function(event, note) {
if (note === undefined) {
$location.path('/');
}
$scope.note = note;
$scope.paragraphUrl = $routeParams.paragraphId;
$scope.asIframe = $routeParams.asIframe;
if ($scope.paragraphUrl) {
$scope.note = cleanParagraphExcept($scope.paragraphUrl, $scope.note);
$scope.$broadcast('$unBindKeyEvent', $scope.$unBindKeyEvent);
$rootScope.$broadcast('setIframe', $scope.asIframe);
initializeLookAndFeel();
return;
}
initializeLookAndFeel();
// open interpreter binding setting when there're none selected
getInterpreterBindings();
getPermissions();
let isPersonalized = $scope.note.config.personalizedMode;
isPersonalized = isPersonalized === undefined ? 'false' : isPersonalized;
$scope.note.config.personalizedMode = isPersonalized;
});
$scope.$on('$routeChangeStart', function(event, next, current) {
if (!$scope.note || !$scope.note.paragraphs) {
return;
}
if ($scope.note && $scope.note.paragraphs) {
$scope.note.paragraphs.map((par) => {
if ($scope.allowLeave === true) {
return;
}
let thisScope = angular.element(
'#' + par.id + '_paragraphColumn_main').scope();
if (thisScope.dirtyText === undefined ||
thisScope.originalText === undefined ||
thisScope.dirtyText === thisScope.originalText) {
return true;
} else {
event.preventDefault();
$scope.showParagraphWarning(next);
}
});
}
});
$scope.showParagraphWarning = function(next) {
if ($scope.paragraphWarningDialog.opened !== true) {
$scope.paragraphWarningDialog = BootstrapDialog.show({
closable: false,
closeByBackdrop: false,
closeByKeyboard: false,
title: 'Do you want to leave this site?',
message: 'Changes that you have made will not be saved.',
buttons: [{
label: 'Stay',
action: function(dialog) {
dialog.close();
},
}, {
label: 'Leave',
action: function(dialog) {
dialog.close();
let locationToRedirect = next['$$route']['originalPath'];
Object.keys(next.pathParams).map((key) => {
locationToRedirect = locationToRedirect.replace(':' + key,
next.pathParams[key]);
});
$scope.allowLeave = true;
$location.path(locationToRedirect);
},
}],
});
}
};
$scope.$on('saveNoteForms', function(event, data) {
$scope.note.noteForms = data.formsData.forms;
$scope.note.noteParams = data.formsData.params;
});
$scope.isShowNoteForms = function() {
if ($scope.note && !_.isEmpty($scope.note.noteForms) && !$scope.paragraphUrl) {
return true;
}
return false;
};
$scope.saveNoteForms = function() {
websocketMsgSrv.saveNoteForms($scope.note);
};
$scope.removeNoteForms = function(formName) {
websocketMsgSrv.removeNoteForms($scope.note, formName);
};
$scope.$on('$destroy', function() {
angular.element(window).off('beforeunload');
$scope.killSaveTimer();
$scope.saveNote();
document.removeEventListener('click', $scope.focusParagraphOnClick);
document.removeEventListener('keydown', $scope.keyboardShortcut);
});
$scope.$on('$unBindKeyEvent', function() {
document.removeEventListener('click', $scope.focusParagraphOnClick);
document.removeEventListener('keydown', $scope.keyboardShortcut);
});
let content = document.getElementById('content');
if (content && content.id) {
$scope.addEvent({
eventID: content.id,
eventType: 'resize',
element: window,
onDestroyElement: content,
handler: () => {
const actionbarHeight = document.getElementById('actionbar').lastElementChild.clientHeight;
angular.element(document.getElementById('content')).css('padding-top', actionbarHeight - 20);
},
});
}
}