notebooklist.js
876 lines
| 33.3 KiB
| application/javascript
|
JavascriptLexer
|
r17189 | // Copyright (c) IPython Development Team. | ||
// Distributed under the terms of the Modified BSD License. | ||||
define([ | ||||
'base/js/namespace', | ||||
|
r17200 | 'jquery', | ||
|
r17189 | 'base/js/utils', | ||
'base/js/dialog', | ||||
|
r19031 | 'base/js/events', | ||
|
r19694 | 'base/js/keyboard', | ||
], function(IPython, $, utils, dialog, events, keyboard) { | ||||
|
r13063 | "use strict"; | ||
|
r17210 | var NotebookList = function (selector, options) { | ||
|
r19176 | /** | ||
* Constructor | ||||
* | ||||
* Parameters: | ||||
* selector: string | ||||
* options: dictionary | ||||
* Dictionary of keyword arguments. | ||||
* session_list: SessionList instance | ||||
* element_name: string | ||||
* base_url: string | ||||
* notebook_path: string | ||||
* contents: Contents instance | ||||
*/ | ||||
|
r17189 | var that = this; | ||
|
r17210 | this.session_list = options.session_list; | ||
|
r15454 | // allow code re-use by just changing element_name in kernellist.js | ||
|
r17210 | this.element_name = options.element_name || 'notebook'; | ||
|
r4488 | this.selector = selector; | ||
if (this.selector !== undefined) { | ||||
this.element = $(selector); | ||||
this.style(); | ||||
this.bind_events(); | ||||
} | ||||
|
r13063 | this.notebooks_list = []; | ||
this.sessions = {}; | ||||
|
r17198 | this.base_url = options.base_url || utils.get_body_data("baseUrl"); | ||
this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath"); | ||||
|
r18643 | this.contents = options.contents; | ||
|
r17195 | if (this.session_list && this.session_list.events) { | ||
this.session_list.events.on('sessions_loaded.Dashboard', | ||||
function(e, d) { that.sessions_loaded(d); }); | ||||
} | ||||
|
r20338 | this.selected = []; | ||
|
r4488 | }; | ||
NotebookList.prototype.style = function () { | ||||
|
r17189 | var prefix = '#' + this.element_name; | ||
|
r15518 | $(prefix + '_toolbar').addClass('list_toolbar'); | ||
$(prefix + '_list_info').addClass('toolbar_info'); | ||||
$(prefix + '_buttons').addClass('toolbar_buttons'); | ||||
$(prefix + '_list_header').addClass('list_header'); | ||||
|
r10920 | this.element.addClass("list_container"); | ||
|
r4488 | }; | ||
NotebookList.prototype.bind_events = function () { | ||||
|
r4491 | var that = this; | ||
|
r15456 | $('#refresh_' + this.element_name + '_list').click(function () { | ||
that.load_sessions(); | ||||
|
r6195 | }); | ||
|
r4491 | this.element.bind('dragover', function () { | ||
return false; | ||||
}); | ||||
|
r6838 | this.element.bind('drop', function(event){ | ||
|
r15455 | that.handleFilesUpload(event,'drop'); | ||
|
r4491 | return false; | ||
}); | ||||
|
r19694 | |||
// Bind events for singleton controls. | ||||
if (!NotebookList._bound_singletons) { | ||||
NotebookList._bound_singletons = true; | ||||
$('#new-file').click(function(e) { | ||||
|
r20971 | var w = window.open('', IPython._target); | ||
|
r19699 | that.contents.new_untitled(that.notebook_path || '', {type: 'file', ext: '.txt'}).then(function(data) { | ||
var url = utils.url_join_encode( | ||||
that.base_url, 'edit', data.path | ||||
); | ||||
w.location = url; | ||||
|
r20238 | }).catch(function (e) { | ||
w.close(); | ||||
dialog.modal({ | ||||
title: 'Creating File Failed', | ||||
body: $('<div/>') | ||||
.text("An error occurred while creating a new file.") | ||||
.append($('<div/>') | ||||
.addClass('alert alert-danger') | ||||
.text(e.message || e)), | ||||
buttons: { | ||||
OK: {'class': 'btn-primary'} | ||||
} | ||||
}); | ||||
|
r20744 | console.warn('Error durring New file creation', e); | ||
|
r19699 | }); | ||
|
r19696 | that.load_sessions(); | ||
|
r19694 | }); | ||
$('#new-folder').click(function(e) { | ||||
|
r19983 | that.contents.new_untitled(that.notebook_path || '', {type: 'directory'}) | ||
.then(function(){ | ||||
that.load_list(); | ||||
|
r20238 | }).catch(function (e) { | ||
dialog.modal({ | ||||
title: 'Creating Folder Failed', | ||||
body: $('<div/>') | ||||
.text("An error occurred while creating a new folder.") | ||||
.append($('<div/>') | ||||
.addClass('alert alert-danger') | ||||
.text(e.message || e)), | ||||
buttons: { | ||||
OK: {'class': 'btn-primary'} | ||||
} | ||||
}); | ||||
|
r20744 | console.warn('Error durring New directory creation', e); | ||
|
r19983 | }); | ||
|
r20238 | that.load_sessions(); | ||
|
r19694 | }); | ||
|
r20157 | |||
|
r20313 | // Bind events for action buttons. | ||
|
r20157 | $('.rename-button').click($.proxy(this.rename_selected, this)); | ||
$('.shutdown-button').click($.proxy(this.shutdown_selected, this)); | ||||
$('.duplicate-button').click($.proxy(this.duplicate_selected, this)); | ||||
$('.delete-button').click($.proxy(this.delete_selected, this)); | ||||
|
r20313 | |||
// Bind events for selection menu buttons. | ||||
|
r20877 | $('#selector-menu').click(function (event) { | ||
that.select($(event.target).attr('id')); | ||||
}); | ||||
var select_all = $('#select-all'); | ||||
select_all.change(function () { | ||||
if (!select_all.prop('checked') || select_all.data('indeterminate')) { | ||||
that.select('select-none'); | ||||
} else { | ||||
that.select('select-all'); | ||||
} | ||||
}); | ||||
$('#button-select-all').click(function (e) { | ||||
|
r20360 | // toggle checkbox if the click doesn't come from the checkbox already | ||
if (!$(e.target).is('input[type=checkbox]')) { | ||||
|
r20877 | if (select_all.prop('checked') || select_all.data('indeterminate')) { | ||
that.select('select-none'); | ||||
} else { | ||||
that.select('select-all'); | ||||
} | ||||
|
r20360 | } | ||
}); | ||||
|
r19694 | } | ||
|
r4488 | }; | ||
|
r15455 | NotebookList.prototype.handleFilesUpload = function(event, dropOrForm) { | ||
|
r6838 | var that = this; | ||
var files; | ||||
if(dropOrForm =='drop'){ | ||||
files = event.originalEvent.dataTransfer.files; | ||||
} else | ||||
{ | ||||
|
r13063 | files = event.originalEvent.target.files; | ||
|
r6838 | } | ||
|
r13063 | for (var i = 0; i < files.length; i++) { | ||
var f = files[i]; | ||||
|
r17198 | var name_and_ext = utils.splitext(f.name); | ||
|
r13322 | var file_ext = name_and_ext[1]; | ||
|
r17536 | |||
var reader = new FileReader(); | ||||
|
r13121 | if (file_ext === '.ipynb') { | ||
|
r17536 | reader.readAsText(f); | ||
|
r13115 | } else { | ||
|
r17536 | // read non-notebook files as binary | ||
reader.readAsArrayBuffer(f); | ||||
|
r13063 | } | ||
|
r20157 | var item = that.new_item(0, true); | ||
|
r17536 | item.addClass('new-file'); | ||
|
r17643 | that.add_name_input(f.name, item, file_ext == '.ipynb' ? 'notebook' : 'file'); | ||
|
r17536 | // Store the list item in the reader so we can use it later | ||
// to know which item it belongs to. | ||||
$(reader).data('item', item); | ||||
reader.onload = function (event) { | ||||
var item = $(event.target).data('item'); | ||||
that.add_file_data(event.target.result, item); | ||||
that.add_upload_button(item); | ||||
}; | ||||
|
r17650 | reader.onerror = function (event) { | ||
var item = $(event.target).data('item'); | ||||
|
r18752 | var name = item.data('name'); | ||
|
r17650 | item.remove(); | ||
dialog.modal({ | ||||
title : 'Failed to read file', | ||||
body : "Failed to read file '" + name + "'", | ||||
buttons : {'OK' : { 'class' : 'btn-primary' }} | ||||
}); | ||||
}; | ||||
|
r6838 | } | ||
|
r14926 | // Replace the file input form wth a clone of itself. This is required to | ||
// reset the form. Otherwise, if you upload a file, delete it and try to | ||||
// upload it again, the changed event won't fire. | ||||
var form = $('input.fileinput'); | ||||
form.replaceWith(form.clone(true)); | ||||
|
r6838 | return false; | ||
|
r13063 | }; | ||
|
r4488 | |||
|
r16213 | NotebookList.prototype.clear_list = function (remove_uploads) { | ||
|
r19176 | /** | ||
* Clears the navigation tree. | ||||
* | ||||
* Parameters | ||||
* remove_uploads: bool=False | ||||
* Should upload prompts also be removed from the tree. | ||||
*/ | ||||
|
r16213 | if (remove_uploads) { | ||
this.element.children('.list_item').remove(); | ||||
} else { | ||||
this.element.children('.list_item:not(.new-file)').remove(); | ||||
} | ||||
|
r7941 | }; | ||
|
r6195 | |||
|
r12988 | NotebookList.prototype.load_sessions = function(){ | ||
|
r17189 | this.session_list.load_sessions(); | ||
|
r12988 | }; | ||
NotebookList.prototype.sessions_loaded = function(data){ | ||||
|
r15479 | this.sessions = data; | ||
|
r12988 | this.load_list(); | ||
}; | ||||
|
r6195 | |||
|
r4488 | NotebookList.prototype.load_list = function () { | ||
|
r18752 | var that = this; | ||
|
r18827 | this.contents.list_contents(that.notebook_path).then( | ||
$.proxy(this.draw_notebook_list, this), | ||||
function(error) { | ||||
|
r18678 | that.draw_notebook_list({content: []}, "Server error: " + error.message); | ||
|
r18653 | } | ||
|
r18827 | ); | ||
|
r4488 | }; | ||
|
r18636 | /** | ||
* Draw the list of notebooks | ||||
* @method draw_notebook_list | ||||
* @param {Array} list An array of dictionaries representing files or | ||||
|
r18660 | * directories. | ||
|
r18636 | * @param {String} error_msg An error message | ||
*/ | ||||
|
r19479 | |||
var type_order = {'directory':0,'notebook':1,'file':2}; | ||||
|
r18636 | NotebookList.prototype.draw_notebook_list = function (list, error_msg) { | ||
|
r20302 | // Remember what was selected before the refresh. | ||
var selected_before = this.selected; | ||||
|
r19449 | list.content.sort(function(a, b) { | ||
|
r19479 | if (type_order[a['type']] < type_order[b['type']]) { | ||
|
r19449 | return -1; | ||
} | ||||
|
r19479 | if (type_order[a['type']] > type_order[b['type']]) { | ||
|
r19449 | return 1; | ||
} | ||||
if (a['name'] < b['name']) { | ||||
return -1; | ||||
} | ||||
if (a['name'] > b['name']) { | ||||
return 1; | ||||
} | ||||
return 0; | ||||
}); | ||||
|
r18636 | var message = error_msg || 'Notebook list empty.'; | ||
|
r15078 | var item = null; | ||
|
r18640 | var model = null; | ||
var len = list.content.length; | ||||
|
r6842 | this.clear_list(); | ||
|
r17651 | var n_uploads = this.element.children('.list_item').length; | ||
|
r13063 | if (len === 0) { | ||
|
r17526 | item = this.new_item(0); | ||
|
r15078 | var span12 = item.children().first(); | ||
span12.empty(); | ||||
|
r15094 | span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message)); | ||
|
r6857 | } | ||
|
r15234 | var path = this.notebook_path; | ||
|
r17651 | var offset = n_uploads; | ||
|
r15071 | if (path !== '') { | ||
|
r20157 | item = this.new_item(offset, false); | ||
|
r17526 | model = { | ||
type: 'directory', | ||||
name: '..', | ||||
|
r18752 | path: utils.url_path_split(path)[0], | ||
|
r17526 | }; | ||
this.add_link(model, item); | ||||
|
r17651 | offset += 1; | ||
|
r15071 | } | ||
|
r4488 | for (var i=0; i<len; i++) { | ||
|
r18640 | model = list.content[i]; | ||
|
r20157 | item = this.new_item(i+offset, true); | ||
|
r18640 | this.add_link(model, item); | ||
|
r13063 | } | ||
|
r19031 | // Trigger an event when we've finished drawing the notebook list. | ||
events.trigger('draw_notebook_list.NotebookList'); | ||||
|
r20302 | |||
// Reselect the items that were selected before. Notify listeners | ||||
// that the selected items may have changed. O(n^2) operation. | ||||
selected_before.forEach(function(item) { | ||||
var list_items = $('.list_item'); | ||||
for (var i=0; i<list_items.length; i++) { | ||||
var $list_item = $(list_items[i]); | ||||
if ($list_item.data('path') == item.path) { | ||||
$list_item.find('input[type=checkbox]').prop('checked', true); | ||||
break; | ||||
} | ||||
} | ||||
}); | ||||
this._selection_changed(); | ||||
|
r4491 | }; | ||
|
r4490 | |||
|
r4491 | |||
|
r20157 | /** | ||
* Creates a new item. | ||||
* @param {integer} index | ||||
* @param {boolean} [selectable] - tristate, undefined: don't draw checkbox, | ||||
* false: don't draw checkbox but pad | ||||
* where it should be, true: draw checkbox. | ||||
* @return {JQuery} row | ||||
*/ | ||||
NotebookList.prototype.new_item = function (index, selectable) { | ||||
var row = $('<div/>') | ||||
.addClass("list_item") | ||||
.addClass("row"); | ||||
var item = $("<div/>") | ||||
.addClass("col-md-12") | ||||
.appendTo(row); | ||||
var checkbox; | ||||
if (selectable !== undefined) { | ||||
checkbox = $('<input/>') | ||||
.attr('type', 'checkbox') | ||||
|
r20163 | .attr('title', 'Click here to rename, delete, etc.') | ||
|
r20157 | .appendTo(item); | ||
} | ||||
$('<i/>') | ||||
.addClass('item_icon') | ||||
.appendTo(item); | ||||
var link = $("<a/>") | ||||
.addClass("item_link") | ||||
.appendTo(item); | ||||
$("<span/>") | ||||
.addClass("item_name") | ||||
.appendTo(link); | ||||
if (selectable === false) { | ||||
checkbox.css('visibility', 'hidden'); | ||||
} else if (selectable === true) { | ||||
var that = this; | ||||
row.click(function(e) { | ||||
|
r20358 | // toggle checkbox only if the click doesn't come from the checkbox or the link | ||
if (!$(e.target).is('span[class=item_name]') && !$(e.target).is('input[type=checkbox]')) { | ||||
checkbox.prop('checked', !checkbox.prop('checked')); | ||||
} | ||||
|
r20157 | that._selection_changed(); | ||
}); | ||||
} | ||||
var buttons = $('<div/>') | ||||
.addClass("item_buttons pull-right") | ||||
.appendTo(item); | ||||
|
r20161 | $('<div/>') | ||
.addClass('running-indicator') | ||||
.text('Running') | ||||
.css('visibility', 'hidden') | ||||
|
r20157 | .appendTo(buttons); | ||
|
r10891 | |||
|
r4491 | if (index === -1) { | ||
|
r20157 | this.element.append(row); | ||
|
r4491 | } else { | ||
|
r20157 | this.element.children().eq(index).after(row); | ||
|
r4488 | } | ||
|
r20157 | return row; | ||
|
r4491 | }; | ||
|
r17526 | NotebookList.icons = { | ||
directory: 'folder_icon', | ||||
notebook: 'notebook_icon', | ||||
file: 'file_icon', | ||||
}; | ||||
NotebookList.uri_prefixes = { | ||||
directory: 'tree', | ||||
notebook: 'notebooks', | ||||
|
r19307 | file: 'edit', | ||
|
r17526 | }; | ||
|
r20241 | /** | ||
|
r20345 | * Select all items in the tree of specified type. | ||
|
r20651 | * selection_type : string among "select-all", "select-folders", "select-notebooks", "select-running-notebooks", "select-files" | ||
* any other string (like "select-none") deselects all items | ||||
|
r20313 | */ | ||
|
r20651 | NotebookList.prototype.select = function(selection_type) { | ||
|
r20313 | var that = this; | ||
$('.list_item').each(function(index, item) { | ||||
|
r20651 | var item_type = $(item).data('type'); | ||
var state = false; | ||||
state = state || (selection_type === "select-all"); | ||||
state = state || (selection_type === "select-folders" && item_type === 'directory'); | ||||
state = state || (selection_type === "select-notebooks" && item_type === 'notebook'); | ||||
state = state || (selection_type === "select-running-notebooks" && item_type === 'notebook' && that.sessions[$(item).data('path')] !== undefined); | ||||
state = state || (selection_type === "select-files" && item_type === 'file'); | ||||
$(item).find('input[type=checkbox]').prop('checked', state); | ||||
|
r20313 | }); | ||
this._selection_changed(); | ||||
}; | ||||
/** | ||||
|
r20241 | * Handles when any row selector checkbox is toggled. | ||
*/ | ||||
|
r20157 | NotebookList.prototype._selection_changed = function() { | ||
|
r20241 | // Use a JQuery selector to find each row with a checked checkbox. If | ||
// we decide to add more checkboxes in the future, this code will need | ||||
// to be changed to distinguish which checkbox is the row selector. | ||||
|
r20157 | var selected = []; | ||
|
r20160 | var has_running_notebook = false; | ||
|
r20157 | var has_directory = false; | ||
|
r20165 | var has_file = false; | ||
|
r20162 | var that = this; | ||
|
r20313 | var checked = 0; | ||
|
r20157 | $('.list_item :checked').each(function(index, item) { | ||
var parent = $(item).parent().parent(); | ||||
|
r20345 | // If the item doesn't have an upload button, isn't the | ||
// breadcrumbs and isn't the parent folder '..', then it can be selected. | ||||
// Breadcrumbs path == ''. | ||||
|
r20396 | if (parent.find('.upload_button').length === 0 && parent.data('path') !== '' && parent.data('path') !== utils.url_path_split(that.notebook_path)[0]) { | ||
|
r20313 | checked++; | ||
|
r20239 | selected.push({ | ||
name: parent.data('name'), | ||||
path: parent.data('path'), | ||||
type: parent.data('type') | ||||
}); | ||||
|
r20157 | |||
|
r20241 | // Set flags according to what is selected. Flags are later | ||
// used to decide which action buttons are visible. | ||||
|
r20239 | has_running_notebook = has_running_notebook || | ||
(parent.data('type') == 'notebook' && that.sessions[parent.data('path')] !== undefined); | ||||
has_file = has_file || parent.data('type') == 'file'; | ||||
has_directory = has_directory || parent.data('type') == 'directory'; | ||||
} | ||||
|
r20157 | }); | ||
this.selected = selected; | ||||
|
r20876 | // Rename is only visible when one item is selected, and it is not a running notebook | ||
if (selected.length==1 && !has_running_notebook) { | ||||
|
r20157 | $('.rename-button').css('display', 'inline-block'); | ||
} else { | ||||
$('.rename-button').css('display', 'none'); | ||||
} | ||||
|
r20241 | // Shutdown is only visible when one or more notebooks running notebooks | ||
// are selected and no non-notebook items are selected. | ||||
|
r20165 | if (has_running_notebook && !(has_file || has_directory)) { | ||
|
r20157 | $('.shutdown-button').css('display', 'inline-block'); | ||
} else { | ||||
$('.shutdown-button').css('display', 'none'); | ||||
} | ||||
|
r20241 | // Duplicate isn't visible when a directory is selected. | ||
|
r20157 | if (selected.length > 0 && !has_directory) { | ||
$('.duplicate-button').css('display', 'inline-block'); | ||||
} else { | ||||
$('.duplicate-button').css('display', 'none'); | ||||
} | ||||
// Delete is visible if one or more items are selected. | ||||
if (selected.length > 0) { | ||||
$('.delete-button').css('display', 'inline-block'); | ||||
} else { | ||||
$('.delete-button').css('display', 'none'); | ||||
} | ||||
|
r20313 | |||
// If all of the items are selected, show the selector as checked. If | ||||
// some of the items are selected, show it as checked. Otherwise, | ||||
// uncheck it. | ||||
|
r20320 | var total = 0; | ||
$('.list_item input[type=checkbox]').each(function(index, item) { | ||||
var parent = $(item).parent().parent(); | ||||
// If the item doesn't have an upload button and it's not the | ||||
// breadcrumbs, it can be selected. Breadcrumbs path == ''. | ||||
|
r20396 | if (parent.find('.upload_button').length === 0 && parent.data('path') !== '' && parent.data('path') !== utils.url_path_split(that.notebook_path)[0]) { | ||
|
r20320 | total++; | ||
} | ||||
}); | ||||
|
r20877 | |||
var select_all = $("#select-all"); | ||||
|
r20313 | if (checked === 0) { | ||
|
r20877 | select_all.prop('checked', false); | ||
select_all.prop('indeterminate', false); | ||||
select_all.data('indeterminate', false); | ||||
|
r20320 | } else if (checked === total) { | ||
|
r20877 | select_all.prop('checked', true); | ||
select_all.prop('indeterminate', false); | ||||
select_all.data('indeterminate', false); | ||||
|
r20320 | } else { | ||
|
r20877 | select_all.prop('checked', false); | ||
select_all.prop('indeterminate', true); | ||||
select_all.data('indeterminate', true); | ||||
|
r20313 | } | ||
|
r20396 | // Update total counter | ||
$('#counter-select-all').html(checked===0 ? ' ' : checked); | ||||
|
r20875 | |||
// If at aleast on item is selected, hide the selection instructions. | ||||
if (checked > 0) { | ||||
$('.dynamic-instructions').hide(); | ||||
} else { | ||||
$('.dynamic-instructions').show(); | ||||
} | ||||
|
r20157 | }; | ||
|
r17526 | |||
NotebookList.prototype.add_link = function (model, item) { | ||||
var path = model.path, | ||||
name = model.name; | ||||
|
r20235 | var running = (model.type == 'notebook' && this.sessions[path] !== undefined); | ||
|
r15071 | item.data('name', name); | ||
item.data('path', path); | ||||
|
r20157 | item.data('type', model.type); | ||
|
r15071 | item.find(".item_name").text(name); | ||
|
r17526 | var icon = NotebookList.icons[model.type]; | ||
|
r20235 | if (running) { | ||
icon = 'running_' + icon; | ||||
} | ||||
|
r17526 | var uri_prefix = NotebookList.uri_prefixes[model.type]; | ||
item.find(".item_icon").addClass(icon).addClass('icon-fixed-width'); | ||||
|
r17535 | var link = item.find("a.item_link") | ||
|
r15071 | .attr('href', | ||
|
r17198 | utils.url_join_encode( | ||
|
r15238 | this.base_url, | ||
|
r17526 | uri_prefix, | ||
|
r18752 | path | ||
|
r15071 | ) | ||
); | ||||
|
r20156 | |||
|
r20161 | item.find(".item_buttons .running-indicator").css('visibility', running ? '' : 'hidden'); | ||
|
r20156 | |||
|
r17535 | // directory nav doesn't open new tabs | ||
// files, notebooks do | ||||
if (model.type !== "directory") { | ||||
|
r20326 | link.attr('target',IPython._target); | ||
|
r17535 | } | ||
|
r15071 | }; | ||
|
r17643 | NotebookList.prototype.add_name_input = function (name, item, icon_type) { | ||
|
r17526 | item.data('name', name); | ||
|
r17643 | item.find(".item_icon").addClass(NotebookList.icons[icon_type]).addClass('icon-fixed-width'); | ||
|
r10919 | item.find(".item_name").empty().append( | ||
|
r10896 | $('<input/>') | ||
|
r17536 | .addClass("filename_input") | ||
.attr('value', name) | ||||
|
r10896 | .attr('size', '30') | ||
.attr('type', 'text') | ||||
|
r17646 | .keyup(function(event){ | ||
if(event.keyCode == 13){item.find('.upload_button').click();} | ||||
else if(event.keyCode == 27){item.remove();} | ||||
}) | ||||
|
r4491 | ); | ||
}; | ||||
|
r17536 | NotebookList.prototype.add_file_data = function (data, item) { | ||
item.data('filedata', data); | ||||
|
r4491 | }; | ||
|
r20157 | NotebookList.prototype.shutdown_selected = function() { | ||
|
r6842 | var that = this; | ||
|
r20164 | this.selected.forEach(function(item) { | ||
if (item.type == 'notebook') { | ||||
that.shutdown_notebook(item.path); | ||||
} | ||||
}); | ||||
}; | ||||
NotebookList.prototype.shutdown_notebook = function(path) { | ||||
var that = this; | ||||
|
r20157 | var settings = { | ||
processData : false, | ||||
cache : false, | ||||
type : "DELETE", | ||||
dataType : "json", | ||||
success : function () { | ||||
that.load_sessions(); | ||||
}, | ||||
error : utils.log_ajax_error, | ||||
}; | ||||
|
r20164 | var session = this.sessions[path]; | ||
if (session) { | ||||
var url = utils.url_join_encode( | ||||
this.base_url, | ||||
'api/sessions', | ||||
session | ||||
); | ||||
$.ajax(url, settings); | ||||
} | ||||
|
r20238 | }; | ||
|
r18927 | |||
|
r20157 | NotebookList.prototype.rename_selected = function() { | ||
if (this.selected.length != 1) return; | ||||
|
r20156 | |||
|
r20157 | var that = this; | ||
|
r20876 | var item_path = this.selected[0].path; | ||
var item_name = this.selected[0].name; | ||||
var item_type = this.selected[0].type; | ||||
|
r20157 | var input = $('<input/>').attr('type','text').attr('size','25').addClass('form-control') | ||
|
r20876 | .val(item_name); | ||
|
r20157 | var dialog_body = $('<div/>').append( | ||
$("<p/>").addClass("rename-message") | ||||
|
r20876 | .text('Enter a new '+ item_type + ' name:') | ||
|
r20157 | ).append( | ||
$("<br/>") | ||||
).append(input); | ||||
var d = dialog.modal({ | ||||
|
r20876 | title : "Rename "+ item_type, | ||
|
r20157 | body : dialog_body, | ||
buttons : { | ||||
OK : { | ||||
class: "btn-primary", | ||||
click: function() { | ||||
|
r20876 | that.contents.rename(item_path, utils.url_path_join(that.notebook_path, input.val())).then(function() { | ||
|
r20157 | that.load_list(); | ||
}).catch(function(e) { | ||||
dialog.modal({ | ||||
|
r20238 | title: "Rename Failed", | ||
body: $('<div/>') | ||||
|
r20876 | .text("An error occurred while renaming \"" + item_name + "\" to \"" + input.val() + "\".") | ||
|
r20238 | .append($('<div/>') | ||
.addClass('alert alert-danger') | ||||
.text(e.message || e)), | ||||
buttons: { | ||||
OK: {'class': 'btn-primary'} | ||||
|
r20157 | } | ||
}); | ||||
|
r20744 | console.warn('Error durring renaming :', e); | ||
|
r20157 | }); | ||
} | ||||
}, | ||||
Cancel : {} | ||||
}, | ||||
open : function () { | ||||
// Upon ENTER, click the OK button. | ||||
input.keydown(function (event) { | ||||
if (event.which === keyboard.keycodes.enter) { | ||||
d.find('.btn-primary').first().click(); | ||||
return false; | ||||
} | ||||
}); | ||||
input.focus().select(); | ||||
} | ||||
}); | ||||
|
r20156 | }; | ||
|
r20157 | NotebookList.prototype.delete_selected = function() { | ||
var message; | ||||
if (this.selected.length == 1) { | ||||
message = 'Are you sure you want to permanently delete: ' + this.selected[0].name + '?'; | ||||
} else { | ||||
|
r20225 | message = 'Are you sure you want to permanently delete the ' + this.selected.length + ' files/folders selected?'; | ||
|
r20157 | } | ||
var that = this; | ||||
dialog.modal({ | ||||
title : "Delete", | ||||
body : message, | ||||
buttons : { | ||||
Delete : { | ||||
class: "btn-danger", | ||||
click: function() { | ||||
|
r20159 | // Shutdown any/all selected notebooks before deleting | ||
// the files. | ||||
that.shutdown_selected(); | ||||
// Delete selected. | ||||
|
r20157 | that.selected.forEach(function(item) { | ||
that.contents.delete(item.path).then(function() { | ||||
that.notebook_deleted(item.path); | ||||
|
r20156 | }).catch(function(e) { | ||
dialog.modal({ | ||||
|
r20238 | title: "Delete Failed", | ||
body: $('<div/>') | ||||
|
r20157 | .text("An error occurred while deleting \"" + item.path + "\".") | ||
|
r20238 | .append($('<div/>') | ||
.addClass('alert alert-danger') | ||||
.text(e.message || e)), | ||||
buttons: { | ||||
OK: {'class': 'btn-primary'} | ||||
|
r20156 | } | ||
|
r18930 | }); | ||
|
r20744 | console.warn('Error durring content deletion:', e); | ||
|
r20156 | }); | ||
|
r20157 | }); | ||
} | ||||
|
r20156 | }, | ||
|
r20157 | Cancel : {} | ||
} | ||||
}); | ||||
|
r6842 | }; | ||
|
r20157 | NotebookList.prototype.duplicate_selected = function() { | ||
var message; | ||||
if (this.selected.length == 1) { | ||||
message = 'Are you sure you want to duplicate: ' + this.selected[0].name + '?'; | ||||
} else { | ||||
message = 'Are you sure you want to duplicate the ' + this.selected.length + ' files selected?'; | ||||
} | ||||
var that = this; | ||||
dialog.modal({ | ||||
|
r20733 | title : "Duplicate", | ||
|
r20157 | body : message, | ||
buttons : { | ||||
Duplicate : { | ||||
class: "btn-primary", | ||||
click: function() { | ||||
that.selected.forEach(function(item) { | ||||
that.contents.copy(item.path, that.notebook_path).then(function () { | ||||
that.load_list(); | ||||
|
r20156 | }).catch(function(e) { | ||
dialog.modal({ | ||||
|
r20733 | title: "Duplicate Failed", | ||
|
r20238 | body: $('<div/>') | ||
|
r20733 | .text("An error occurred while duplicating \"" + item.path + "\".") | ||
|
r20238 | .append($('<div/>') | ||
.addClass('alert alert-danger') | ||||
.text(e.message || e)), | ||||
buttons: { | ||||
OK: {'class': 'btn-primary'} | ||||
|
r18656 | } | ||
|
r20156 | }); | ||
|
r20744 | console.warn('Error durring content duplication', e); | ||
|
r20156 | }); | ||
|
r20157 | }); | ||
} | ||||
}, | ||||
Cancel : {} | ||||
} | ||||
}); | ||||
|
r4491 | }; | ||
|
r18752 | NotebookList.prototype.notebook_deleted = function(path) { | ||
|
r19176 | /** | ||
* Remove the deleted notebook. | ||||
*/ | ||||
|
r20184 | var that = this; | ||
|
r18772 | $( ":data(path)" ).each(function() { | ||
|
r18752 | var element = $(this); | ||
|
r20184 | if (element.data("path") === path) { | ||
|
r18655 | element.remove(); | ||
|
r19031 | events.trigger('notebook_deleted.NotebookList'); | ||
|
r20184 | that._selection_changed(); | ||
|
r18655 | } | ||
}); | ||||
|
r18752 | }; | ||
|
r18655 | |||
|
r4491 | |||
|
r18752 | NotebookList.prototype.add_upload_button = function (item) { | ||
|
r4491 | var that = this; | ||
|
r10896 | var upload_button = $('<button/>').text("Upload") | ||
|
r16914 | .addClass('btn btn-primary btn-xs upload_button') | ||
|
r10896 | .click(function (e) { | ||
|
r17536 | var filename = item.find('.item_name > input').val(); | ||
|
r18752 | var path = utils.url_path_join(that.notebook_path, filename); | ||
|
r17536 | var filedata = item.data('filedata'); | ||
var format = 'text'; | ||||
|
r17650 | if (filename.length === 0 || filename[0] === '.') { | ||
dialog.modal({ | ||||
title : 'Invalid file name', | ||||
body : "File names must be at least one character and not start with a dot", | ||||
buttons : {'OK' : { 'class' : 'btn-primary' }} | ||||
}); | ||||
return false; | ||||
} | ||||
|
r17536 | if (filedata instanceof ArrayBuffer) { | ||
// base64-encode binary file data | ||||
var bytes = ''; | ||||
var buf = new Uint8Array(filedata); | ||||
var nbytes = buf.byteLength; | ||||
for (var i=0; i<nbytes; i++) { | ||||
bytes += String.fromCharCode(buf[i]); | ||||
} | ||||
filedata = btoa(bytes); | ||||
format = 'base64'; | ||||
} | ||||
|
r18752 | var model = {}; | ||
|
r17536 | |||
var name_and_ext = utils.splitext(filename); | ||||
var file_ext = name_and_ext[1]; | ||||
var content_type; | ||||
if (file_ext === '.ipynb') { | ||||
model.type = 'notebook'; | ||||
model.format = 'json'; | ||||
|
r17537 | try { | ||
model.content = JSON.parse(filedata); | ||||
} catch (e) { | ||||
dialog.modal({ | ||||
title : 'Cannot upload invalid Notebook', | ||||
body : "The error was: " + e, | ||||
buttons : {'OK' : { | ||||
'class' : 'btn-primary', | ||||
click: function () { | ||||
item.remove(); | ||||
} | ||||
}} | ||||
}); | ||||
|
r20744 | console.warn('Error durring notebook uploading', e); | ||
|
r17650 | return false; | ||
|
r17537 | } | ||
|
r17536 | content_type = 'application/json'; | ||
} else { | ||||
model.type = 'file'; | ||||
model.format = format; | ||||
model.content = filedata; | ||||
content_type = 'application/octet-stream'; | ||||
} | ||||
|
r18752 | filedata = item.data('filedata'); | ||
|
r17536 | |||
|
r18829 | var on_success = function () { | ||
item.removeClass('new-file'); | ||||
that.add_link(model, item); | ||||
that.session_list.load_sessions(); | ||||
|
r4491 | }; | ||
|
r17648 | |||
var exists = false; | ||||
$.each(that.element.find('.list_item:not(.new-file)'), function(k,v){ | ||||
if ($(v).data('name') === filename) { exists = true; return false; } | ||||
}); | ||||
|
r18752 | |||
|
r17648 | if (exists) { | ||
dialog.modal({ | ||||
title : "Replace file", | ||||
body : 'There is already a file named ' + filename + ', do you want to replace it?', | ||||
buttons : { | ||||
Overwrite : { | ||||
class: "btn-danger", | ||||
|
r18752 | click: function () { | ||
|
r18829 | that.contents.save(path, model).then(on_success); | ||
} | ||||
|
r17648 | }, | ||
Cancel : { | ||||
click: function() { item.remove(); } | ||||
} | ||||
} | ||||
}); | ||||
} else { | ||||
|
r18829 | that.contents.save(path, model).then(on_success); | ||
|
r17648 | } | ||
|
r10891 | return false; | ||
|
r4491 | }); | ||
|
r10896 | var cancel_button = $('<button/>').text("Cancel") | ||
|
r16913 | .addClass("btn btn-default btn-xs") | ||
|
r10896 | .click(function (e) { | ||
|
r4491 | item.remove(); | ||
|
r10891 | return false; | ||
|
r4491 | }); | ||
|
r10896 | item.find(".item_buttons").empty() | ||
|
r10891 | .append(upload_button) | ||
.append(cancel_button); | ||||
|
r4488 | }; | ||
|
r18752 | // Backwards compatability. | ||
|
r4488 | IPython.NotebookList = NotebookList; | ||
|
r17201 | return {'NotebookList': NotebookList}; | ||
|
r17189 | }); | ||