notebooklist.js
527 lines
| 19.0 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', | ||
], function(IPython, $, utils, dialog, events) { | ||||
|
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); }); | ||||
} | ||||
|
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; | ||
}); | ||||
|
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 | } | ||
|
r17536 | var item = that.new_item(0); | ||
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) { | ||
|
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 !== '') { | ||
|
r17651 | item = this.new_item(offset); | ||
|
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]; | ||
item = this.new_item(i+offset); | ||||
this.add_link(model, item); | ||||
|
r13063 | } | ||
|
r19031 | // Trigger an event when we've finished drawing the notebook list. | ||
events.trigger('draw_notebook_list.NotebookList'); | ||||
|
r4491 | }; | ||
|
r4490 | |||
|
r4491 | |||
|
r17526 | NotebookList.prototype.new_item = function (index) { | ||
|
r16913 | var item = $('<div/>').addClass("list_item").addClass("row"); | ||
|
r10891 | // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix'); | ||
// item.css('border-top-style','none'); | ||||
|
r16923 | item.append($("<div/>").addClass("col-md-12").append( | ||
|
r15071 | $('<i/>').addClass('item_icon') | ||
).append( | ||||
|
r10919 | $("<a/>").addClass("item_link").append( | ||
|
r10891 | $("<span/>").addClass("item_name") | ||
) | ||||
|
r10919 | ).append( | ||
$('<div/>').addClass("item_buttons btn-group pull-right") | ||||
)); | ||||
|
r10891 | |||
|
r4491 | if (index === -1) { | ||
|
r4490 | this.element.append(item); | ||
|
r4491 | } else { | ||
this.element.children().eq(index).after(item); | ||||
|
r4488 | } | ||
|
r4491 | return item; | ||
}; | ||||
|
r17526 | NotebookList.icons = { | ||
directory: 'folder_icon', | ||||
notebook: 'notebook_icon', | ||||
file: 'file_icon', | ||||
}; | ||||
NotebookList.uri_prefixes = { | ||||
directory: 'tree', | ||||
notebook: 'notebooks', | ||||
|
r19307 | file: 'edit', | ||
|
r17526 | }; | ||
NotebookList.prototype.add_link = function (model, item) { | ||||
var path = model.path, | ||||
name = model.name; | ||||
|
r15071 | item.data('name', name); | ||
item.data('path', path); | ||||
item.find(".item_name").text(name); | ||||
|
r17526 | var icon = NotebookList.icons[model.type]; | ||
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 | ) | ||
); | ||||
|
r17535 | // directory nav doesn't open new tabs | ||
// files, notebooks do | ||||
if (model.type !== "directory") { | ||||
link.attr('target','_blank'); | ||||
} | ||||
|
r18928 | if (model.type !== 'directory') { | ||
this.add_duplicate_button(item); | ||||
} | ||||
|
r17526 | if (model.type == 'file') { | ||
this.add_delete_button(item); | ||||
} else if (model.type == 'notebook') { | ||||
|
r18928 | if (this.sessions[path] === undefined){ | ||
|
r17526 | this.add_delete_button(item); | ||
} else { | ||||
|
r18928 | this.add_shutdown_button(item, this.sessions[path]); | ||
|
r17526 | } | ||
} | ||||
|
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 | }; | ||
|
r12988 | NotebookList.prototype.add_shutdown_button = function (item, session) { | ||
|
r6842 | var that = this; | ||
|
r16914 | var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-xs btn-danger"). | ||
|
r6842 | click(function (e) { | ||
var settings = { | ||||
processData : false, | ||||
cache : false, | ||||
type : "DELETE", | ||||
dataType : "json", | ||||
|
r12996 | success : function () { | ||
|
r12988 | that.load_sessions(); | ||
|
r16445 | }, | ||
|
r17198 | error : utils.log_ajax_error, | ||
|
r6842 | }; | ||
|
r17198 | var url = utils.url_join_encode( | ||
|
r15238 | that.base_url, | ||
|
r13063 | 'api/sessions', | ||
session | ||||
); | ||||
|
r6842 | $.ajax(url, settings); | ||
|
r10891 | return false; | ||
|
r6842 | }); | ||
|
r18927 | item.find(".item_buttons").append(shutdown_button); | ||
}; | ||||
NotebookList.prototype.add_duplicate_button = function (item) { | ||||
var notebooklist = this; | ||||
|
r18928 | var duplicate_button = $("<button/>").text("Duplicate").addClass("btn btn-default btn-xs"). | ||
|
r18927 | click(function (e) { | ||
// $(this) is the button that was clicked. | ||||
var that = $(this); | ||||
|
r18930 | var name = item.data('name'); | ||
var path = item.data('path'); | ||||
|
r18928 | var message = 'Are you sure you want to duplicate ' + name + '?'; | ||
var copy_from = {copy_from : path}; | ||||
|
r18927 | IPython.dialog.modal({ | ||
|
r18928 | title : "Duplicate " + name, | ||
|
r18927 | body : message, | ||
buttons : { | ||||
Duplicate : { | ||||
class: "btn-primary", | ||||
click: function() { | ||||
|
r18930 | notebooklist.contents.copy(path, notebooklist.notebook_path).then(function () { | ||
notebooklist.load_list(); | ||||
}); | ||||
|
r18927 | } | ||
}, | ||||
Cancel : {} | ||||
} | ||||
}); | ||||
return false; | ||||
}); | ||||
item.find(".item_buttons").append(duplicate_button); | ||||
|
r6842 | }; | ||
|
r4491 | NotebookList.prototype.add_delete_button = function (item) { | ||
|
r9787 | var notebooklist = this; | ||
|
r16913 | var delete_button = $("<button/>").text("Delete").addClass("btn btn-default btn-xs"). | ||
|
r4491 | click(function (e) { | ||
// $(this) is the button that was clicked. | ||||
var that = $(this); | ||||
|
r17536 | // We use the filename from the parent list_item element's | ||
// data because the outer scope's values change as we iterate through the loop. | ||||
|
r6195 | var parent_item = that.parents('div.list_item'); | ||
|
r18772 | var name = parent_item.data('name'); | ||
|
r18625 | var path = parent_item.data('path'); | ||
|
r18752 | var message = 'Are you sure you want to permanently delete the file: ' + name + '?'; | ||
|
r17202 | dialog.modal({ | ||
|
r17526 | title : "Delete file", | ||
|
r10921 | body : message, | ||
|
r4491 | buttons : { | ||
|
r10921 | Delete : { | ||
class: "btn-danger", | ||||
click: function() { | ||||
|
r18829 | notebooklist.contents.delete(path).then( | ||
function() { | ||||
|
r18752 | notebooklist.notebook_deleted(path); | ||
|
r18656 | } | ||
|
r18829 | ); | ||
|
r10921 | } | ||
|
r4491 | }, | ||
|
r10921 | Cancel : {} | ||
|
r4491 | } | ||
}); | ||||
|
r10891 | return false; | ||
|
r4491 | }); | ||
|
r18927 | item.find(".item_buttons").append(delete_button); | ||
|
r4491 | }; | ||
|
r18752 | NotebookList.prototype.notebook_deleted = function(path) { | ||
|
r19176 | /** | ||
* Remove the deleted notebook. | ||||
*/ | ||||
|
r18772 | $( ":data(path)" ).each(function() { | ||
|
r18752 | var element = $(this); | ||
if (element.data("path") == path) { | ||||
|
r18655 | element.remove(); | ||
|
r19031 | events.trigger('notebook_deleted.NotebookList'); | ||
|
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(); | ||||
} | ||||
}} | ||||
}); | ||||
|
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.add_delete_button(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 | }); | ||