// Copyright (c) IPython Development Team. // Distributed under the terms of the Modified BSD License. define([ 'base/js/namespace', 'jquery', 'base/js/utils', 'base/js/dialog', 'base/js/events', 'base/js/keyboard', ], function(IPython, $, utils, dialog, events, keyboard) { "use strict"; var NotebookList = function (selector, options) { /** * 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 */ var that = this; this.session_list = options.session_list; // allow code re-use by just changing element_name in kernellist.js this.element_name = options.element_name || 'notebook'; this.selector = selector; if (this.selector !== undefined) { this.element = $(selector); this.style(); this.bind_events(); } this.notebooks_list = []; this.sessions = {}; this.base_url = options.base_url || utils.get_body_data("baseUrl"); this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath"); this.contents = options.contents; if (this.session_list && this.session_list.events) { this.session_list.events.on('sessions_loaded.Dashboard', function(e, d) { that.sessions_loaded(d); }); } }; NotebookList.prototype.style = function () { var prefix = '#' + this.element_name; $(prefix + '_toolbar').addClass('list_toolbar'); $(prefix + '_list_info').addClass('toolbar_info'); $(prefix + '_buttons').addClass('toolbar_buttons'); $(prefix + '_list_header').addClass('list_header'); this.element.addClass("list_container"); }; NotebookList.prototype.bind_events = function () { var that = this; $('#refresh_' + this.element_name + '_list').click(function () { that.load_sessions(); }); this.element.bind('dragover', function () { return false; }); this.element.bind('drop', function(event){ that.handleFilesUpload(event,'drop'); return false; }); // Bind events for singleton controls. if (!NotebookList._bound_singletons) { NotebookList._bound_singletons = true; $('#new-file').click(function(e) { var w = window.open(); 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; }); that.load_sessions(); }); $('#new-folder').click(function(e) { that.contents.new_untitled(that.notebook_path || '', {type: 'directory'}) .then(function(){ that.load_list(); }); }); $('.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)); } }; NotebookList.prototype.handleFilesUpload = function(event, dropOrForm) { var that = this; var files; if(dropOrForm =='drop'){ files = event.originalEvent.dataTransfer.files; } else { files = event.originalEvent.target.files; } for (var i = 0; i < files.length; i++) { var f = files[i]; var name_and_ext = utils.splitext(f.name); var file_ext = name_and_ext[1]; var reader = new FileReader(); if (file_ext === '.ipynb') { reader.readAsText(f); } else { // read non-notebook files as binary reader.readAsArrayBuffer(f); } var item = that.new_item(0, true); item.addClass('new-file'); that.add_name_input(f.name, item, file_ext == '.ipynb' ? 'notebook' : 'file'); // 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); }; reader.onerror = function (event) { var item = $(event.target).data('item'); var name = item.data('name'); item.remove(); dialog.modal({ title : 'Failed to read file', body : "Failed to read file '" + name + "'", buttons : {'OK' : { 'class' : 'btn-primary' }} }); }; } // 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)); return false; }; NotebookList.prototype.clear_list = function (remove_uploads) { /** * Clears the navigation tree. * * Parameters * remove_uploads: bool=False * Should upload prompts also be removed from the tree. */ if (remove_uploads) { this.element.children('.list_item').remove(); } else { this.element.children('.list_item:not(.new-file)').remove(); } }; NotebookList.prototype.load_sessions = function(){ this.session_list.load_sessions(); }; NotebookList.prototype.sessions_loaded = function(data){ this.sessions = data; this.load_list(); }; NotebookList.prototype.load_list = function () { var that = this; this.contents.list_contents(that.notebook_path).then( $.proxy(this.draw_notebook_list, this), function(error) { that.draw_notebook_list({content: []}, "Server error: " + error.message); } ); }; /** * Draw the list of notebooks * @method draw_notebook_list * @param {Array} list An array of dictionaries representing files or * directories. * @param {String} error_msg An error message */ var type_order = {'directory':0,'notebook':1,'file':2}; NotebookList.prototype.draw_notebook_list = function (list, error_msg) { list.content.sort(function(a, b) { if (type_order[a['type']] < type_order[b['type']]) { return -1; } if (type_order[a['type']] > type_order[b['type']]) { return 1; } if (a['name'] < b['name']) { return -1; } if (a['name'] > b['name']) { return 1; } return 0; }); var message = error_msg || 'Notebook list empty.'; var item = null; var model = null; var len = list.content.length; this.clear_list(); var n_uploads = this.element.children('.list_item').length; if (len === 0) { item = this.new_item(0); var span12 = item.children().first(); span12.empty(); span12.append($('
').text(message)); } var path = this.notebook_path; var offset = n_uploads; if (path !== '') { item = this.new_item(offset, false); model = { type: 'directory', name: '..', path: utils.url_path_split(path)[0], }; this.add_link(model, item); offset += 1; } for (var i=0; i') .addClass("list_item") .addClass("row"); var item = $("
") .addClass("col-md-12") .appendTo(row); var checkbox; if (selectable !== undefined) { checkbox = $('') .attr('type', 'checkbox') .attr('title', 'Click here to rename, delete, etc.') .appendTo(item); } $('') .addClass('item_icon') .appendTo(item); var link = $("") .addClass("item_link") .appendTo(item); $("") .addClass("item_name") .appendTo(link); if (selectable === false) { checkbox.css('visibility', 'hidden'); } else if (selectable === true) { var that = this; link.click(function(e) { e.stopPropagation(); }); checkbox.click(function(e) { e.stopPropagation(); that._selection_changed(); }); row.click(function(e) { e.stopPropagation(); checkbox.prop('checked', !checkbox.prop('checked')); that._selection_changed(); }); } var buttons = $('
') .addClass("item_buttons pull-right") .appendTo(item); $('
') .addClass('running-indicator') .text('Running') .css('visibility', 'hidden') .appendTo(buttons); if (index === -1) { this.element.append(row); } else { this.element.children().eq(index).after(row); } return row; }; NotebookList.icons = { directory: 'folder_icon', notebook: 'notebook_icon', file: 'file_icon', }; NotebookList.uri_prefixes = { directory: 'tree', notebook: 'notebooks', file: 'edit', }; NotebookList.prototype._selection_changed = function() { var selected = []; var has_running_notebook = false; var has_directory = false; var that = this; $('.list_item :checked').each(function(index, item) { var parent = $(item).parent().parent(); selected.push({ name: parent.data('name'), path: parent.data('path'), type: parent.data('type') }); has_running_notebook = has_running_notebook || (parent.data('type') == 'notebook' && that.sessions[parent.data('path')] !== undefined); has_directory = has_directory || parent.data('type') == 'directory'; }); this.selected = selected; // Rename is only visible when one item is selected. if (selected.length==1) { $('.rename-button').css('display', 'inline-block'); } else { $('.rename-button').css('display', 'none'); } // Shutdown is only visible when one or more notebooks are visible. if (has_running_notebook) { $('.shutdown-button').css('display', 'inline-block'); } else { $('.shutdown-button').css('display', 'none'); } // Duplicate isn't visible if a directory is selected. 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'); } }; NotebookList.prototype.add_link = function (model, item) { var path = model.path, name = model.name; item.data('name', name); item.data('path', path); item.data('type', model.type); item.find(".item_name").text(name); var icon = NotebookList.icons[model.type]; var uri_prefix = NotebookList.uri_prefixes[model.type]; item.find(".item_icon").addClass(icon).addClass('icon-fixed-width'); var link = item.find("a.item_link") .attr('href', utils.url_join_encode( this.base_url, uri_prefix, path ) ); var running = (model.type == 'notebook' && this.sessions[path] !== undefined); item.find(".item_buttons .running-indicator").css('visibility', running ? '' : 'hidden'); // directory nav doesn't open new tabs // files, notebooks do if (model.type !== "directory") { link.attr('target','_blank'); } }; NotebookList.prototype.add_name_input = function (name, item, icon_type) { item.data('name', name); item.find(".item_icon").addClass(NotebookList.icons[icon_type]).addClass('icon-fixed-width'); item.find(".item_name").empty().append( $('') .addClass("filename_input") .attr('value', name) .attr('size', '30') .attr('type', 'text') .keyup(function(event){ if(event.keyCode == 13){item.find('.upload_button').click();} else if(event.keyCode == 27){item.remove();} }) ); }; NotebookList.prototype.add_file_data = function (data, item) { item.data('filedata', data); }; NotebookList.prototype.shutdown_selected = function() { var that = this; this.selected.forEach(function(item) { if (item.type == 'notebook') { that.shutdown_notebook(item.path); } }); }; NotebookList.prototype.shutdown_notebook = function(path) { var that = this; var settings = { processData : false, cache : false, type : "DELETE", dataType : "json", success : function () { that.load_sessions(); }, error : utils.log_ajax_error, }; var session = this.sessions[path]; if (session) { var url = utils.url_join_encode( this.base_url, 'api/sessions', session ); $.ajax(url, settings); } } NotebookList.prototype.rename_selected = function() { if (this.selected.length != 1) return; var that = this; var path = this.selected[0].path; var input = $('').attr('type','text').attr('size','25').addClass('form-control') .val(path); var dialog_body = $('
').append( $("

").addClass("rename-message") .text('Enter a new directory name:') ).append( $("
") ).append(input); var d = dialog.modal({ title : "Rename directory", body : dialog_body, buttons : { OK : { class: "btn-primary", click: function() { that.contents.rename(path, input.val()).then(function() { that.load_list(); }).catch(function(e) { dialog.modal({ title : "Error", body : $('

') .text("An error occurred while renaming \"" + path + "\" to \"" + input.val() + "\".") .append($('
').addClass('alert alert-danger').text(String(e))), buttons : { OK : {} } }); }); } }, 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(); } }); }; 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 { message = 'Are you sure you want to permanently delete the ' + this.selected.length + ' files selected?'; } var that = this; dialog.modal({ title : "Delete", body : message, buttons : { Delete : { class: "btn-danger", click: function() { // Shutdown any/all selected notebooks before deleting // the files. that.shutdown_selected(); // Delete selected. that.selected.forEach(function(item) { that.contents.delete(item.path).then(function() { that.notebook_deleted(item.path); }).catch(function(e) { dialog.modal({ title : "Error", body : $('
') .text("An error occurred while deleting \"" + item.path + "\".") .append($('
').addClass('alert alert-danger').text(String(e))), buttons : { OK : {} } }); }); }); } }, Cancel : {} } }); }; 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({ title : "Delete", 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(); }).catch(function(e) { dialog.modal({ title : "Error", body : $('
') .text("An error occurred while copying \"" + item.path + "\".") .append($('
').addClass('alert alert-danger').text(String(e))), buttons : { OK : {} } }); }); }); } }, Cancel : {} } }); }; NotebookList.prototype.notebook_deleted = function(path) { /** * Remove the deleted notebook. */ $( ":data(path)" ).each(function() { var element = $(this); if (element.data("path") == path) { element.remove(); events.trigger('notebook_deleted.NotebookList'); this._selection_changed(); } }); }; NotebookList.prototype.add_upload_button = function (item) { var that = this; var upload_button = $('