UNPKG

jupyterlab-emrys

Version:

A computational environment for Jupyter. Powered by Emrys

1,375 lines 51.7 kB
// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. "use strict"; var __extends = (this && this.__extends) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; var moment = require('moment'); var arrays = require('phosphor-arrays'); var phosphor_dragdrop_1 = require('phosphor-dragdrop'); var phosphor_widget_1 = require('phosphor-widget'); var dialog_1 = require('../dialog'); var utils = require('./utils'); var utils_1 = require('./utils'); /** * The class name added to DirListing widget. */ var DIR_LISTING_CLASS = 'jp-DirListing'; /** * The class name added to a dir listing header node. */ var HEADER_CLASS = 'jp-DirListing-header'; /** * The class name added to a dir listing list header cell. */ var HEADER_ITEM_CLASS = 'jp-DirListing-headerItem'; /** * The class name added to a header cell text node. */ var HEADER_ITEM_TEXT_CLASS = 'jp-DirListing-headerItemText'; /** * The class name added to a header cell icon node. */ var HEADER_ITEM_ICON_CLASS = 'jp-DirListing-headerItemIcon'; /** * The class name added to the dir listing content node. */ var CONTENT_CLASS = 'jp-DirListing-content'; /** * The class name added to dir listing content item. */ var ITEM_CLASS = 'jp-DirListing-item'; /** * The class name added to the listing item text cell. */ var ITEM_TEXT_CLASS = 'jp-DirListing-itemText'; /** * The class name added to the listing item icon cell. */ var ITEM_ICON_CLASS = 'jp-DirListing-itemIcon'; /** * The class name added to the listing item modified cell. */ var ITEM_MODIFIED_CLASS = 'jp-DirListing-itemModified'; /** * The class name added to the dir listing editor node. */ var EDITOR_CLASS = 'jp-DirListing-editor'; /** * The class name added to the name column header cell. */ var NAME_ID_CLASS = 'jp-id-name'; /** * The class name added to the modified column header cell. */ var MODIFIED_ID_CLASS = 'jp-id-modified'; /** * The class name added to a file type content item. */ var FILE_TYPE_CLASS = 'jp-type-file'; /** * The class name added to a folder type content item. */ var FOLDER_TYPE_CLASS = 'jp-type-folder'; /** * The class name added to a notebook type content item. */ var NOTEBOOK_TYPE_CLASS = 'jp-type-notebook'; /** * The class name added to the widget when there are items on the clipboard. */ var CLIPBOARD_CLASS = 'jp-mod-clipboard'; /** * The class name added to cut rows. */ var CUT_CLASS = 'jp-mod-cut'; /** * The class name added when there are more than one selected rows. */ var MULTI_SELECTED_CLASS = 'jp-mod-multiSelected'; /** * The class name added to indicate running notebook. */ var RUNNING_CLASS = 'jp-mod-running'; /** * The class name added for a decending sort. */ var DESCENDING_CLASS = 'jp-mod-descending'; /** * The minimum duration for a rename select in ms. */ var RENAME_DURATION = 500; /** * The threshold in pixels to start a drag event. */ var DRAG_THRESHOLD = 5; /** * A boolean indicating whether the platform is Mac. */ var IS_MAC = !!navigator.platform.match(/Mac/i); /** * The factory MIME type supported by phosphor dock panels. */ var FACTORY_MIME = 'application/x-phosphor-widget-factory'; /** * A widget which hosts a file list area. */ var DirListing = (function (_super) { __extends(DirListing, _super); /** * Construct a new file browser directory listing widget. * * @param model - The file browser view model. */ function DirListing(options) { _super.call(this); this._model = null; this._editNode = null; this._items = []; this._sortedModels = null; this._sortState = { direction: 'ascending', key: 'name' }; this._drag = null; this._dragData = null; this._selectTimer = -1; this._noSelectTimer = -1; this._isCut = false; this._prevPath = ''; this._clipboard = []; this._manager = null; this._opener = null; this._softSelection = ''; this._inContext = false; this._selection = Object.create(null); this._renderer = null; this.addClass(DIR_LISTING_CLASS); this._model = options.model; this._model.refreshed.connect(this._onModelRefreshed, this); this._model.pathChanged.connect(this._onPathChanged, this); this._editNode = document.createElement('input'); this._editNode.className = EDITOR_CLASS; this._manager = options.manager; this._opener = options.opener; this._renderer = options.renderer || DirListing.defaultRenderer; var headerNode = utils.findElement(this.node, HEADER_CLASS); this._renderer.populateHeaderNode(headerNode); } /** * Create the DOM node for a dir listing. */ DirListing.createNode = function () { var node = document.createElement('div'); var header = document.createElement('div'); var content = document.createElement('ul'); content.className = CONTENT_CLASS; header.className = HEADER_CLASS; node.appendChild(header); node.appendChild(content); node.tabIndex = 1; return node; }; /** * Dispose of the resources held by the directory listing. */ DirListing.prototype.dispose = function () { this._model = null; this._items = null; this._editNode = null; this._drag = null; this._dragData = null; this._manager = null; this._opener = null; _super.prototype.dispose.call(this); }; Object.defineProperty(DirListing.prototype, "model", { /** * Get the model used by the listing. * * #### Notes * This is a read-only property. */ get: function () { return this._model; }, enumerable: true, configurable: true }); Object.defineProperty(DirListing.prototype, "headerNode", { /** * Get the dir listing header node. * * #### Notes * This is the node which holds the header cells. * * Modifying this node directly can lead to undefined behavior. * * This is a read-only property. */ get: function () { return utils.findElement(this.node, HEADER_CLASS); }, enumerable: true, configurable: true }); Object.defineProperty(DirListing.prototype, "contentNode", { /** * Get the dir listing content node. * * #### Notes * This is the node which holds the item nodes. * * Modifying this node directly can lead to undefined behavior. * * This is a read-only property. */ get: function () { return utils.findElement(this.node, CONTENT_CLASS); }, enumerable: true, configurable: true }); Object.defineProperty(DirListing.prototype, "renderer", { /** * The renderer instance used by the directory listing. * * #### Notes * This is a read-only property. */ get: function () { return this._renderer; }, enumerable: true, configurable: true }); Object.defineProperty(DirListing.prototype, "sortedItems", { /** * The the sorted content items. */ get: function () { return this._sortedModels; }, enumerable: true, configurable: true }); Object.defineProperty(DirListing.prototype, "sortState", { /** * The current sort state. * * #### Notes * This is a read-only property. */ get: function () { return this._sortState; }, enumerable: true, configurable: true }); /** * Sort the items using a sort condition. */ DirListing.prototype.sort = function (state) { this._sortedModels = Private.sort(this.model.items, state); this._sortState = state; this.update(); }; /** * Rename the first currently selected item. */ DirListing.prototype.rename = function () { return this._doRename(); }; /** * Cut the selected items. */ DirListing.prototype.cut = function () { this._isCut = true; this._copy(); }; /** * Copy the selected items. */ DirListing.prototype.copy = function () { this._copy(); }; /** * Paste the items from the clipboard. */ DirListing.prototype.paste = function () { var _this = this; if (!this._clipboard.length) { return; } var promises = []; for (var _i = 0, _a = this._clipboard; _i < _a.length; _i++) { var path = _a[_i]; if (this._isCut) { var parts = path.split('/'); var name_1 = parts[parts.length - 1]; promises.push(this._model.rename(path, name_1)); } else { promises.push(this._model.copy(path, '.')); } } // Remove any cut modifiers. for (var _b = 0, _c = this._items; _b < _c.length; _b++) { var item = _c[_b]; item.classList.remove(CUT_CLASS); } this._clipboard = []; this._isCut = false; this.removeClass(CLIPBOARD_CLASS); return Promise.all(promises).then(function () { return _this._model.refresh(); }, function (error) { return utils.showErrorMessage(_this, 'Paste Error', error); }); }; /** * Delete the currently selected item(s). */ DirListing.prototype.delete = function () { var _this = this; var names = []; var items = this._model.items; for (var _i = 0, items_1 = items; _i < items_1.length; _i++) { var item = items_1[_i]; if (this._selection[item.name]) { names.push(item.name); } } var message = "Permanently delete these " + names.length + " files?"; if (names.length === 1) { message = "Permanently delete file \"" + names[0] + "\"?"; } if (names.length) { return dialog_1.showDialog({ title: 'Delete file?', body: message, okText: 'DELETE' }).then(function (result) { if (result.text === 'DELETE') { return _this._delete(names); } }); } return Promise.resolve(void 0); }; /** * Duplicate the currently selected item(s). */ DirListing.prototype.duplicate = function () { var _this = this; var promises = []; for (var _i = 0, _a = this._getSelectedItems(); _i < _a.length; _i++) { var item = _a[_i]; if (item.type !== 'directory') { promises.push(this._model.copy(item.path, this._model.path)); } } return Promise.all(promises).then(function () { return _this._model.refresh(); }, function (error) { return utils.showErrorMessage(_this, 'Duplicate file', error); }); }; /** * Download the currently selected item(s). */ DirListing.prototype.download = function () { var _this = this; for (var _i = 0, _a = this._getSelectedItems(); _i < _a.length; _i++) { var item = _a[_i]; if (item.type !== 'directory') { return this._model.download(item.name).catch(function (error) { return utils.showErrorMessage(_this, 'Download file', error); }); } } }; /** * Shut down kernels on the applicable currently selected items. */ DirListing.prototype.shutdownKernels = function () { var _this = this; var promises = []; var items = this.sortedItems; var paths = items.map(function (item) { return item.path; }); for (var _i = 0, _a = this._model.sessions; _i < _a.length; _i++) { var session = _a[_i]; var index = paths.indexOf(session.notebook.path); if (this._selection[items[index].name]) { promises.push(this._model.shutdown(session.id)); } } return Promise.all(promises).then(function () { return _this._model.refresh(); }, function (error) { return utils.showErrorMessage(_this, 'Shutdown kernel', error); }); }; /** * Select next item. * * @param keepExisting - Whether to keep the current selection and add to it. */ DirListing.prototype.selectNext = function (keepExisting) { if (keepExisting === void 0) { keepExisting = false; } var index = -1; var selected = Object.keys(this._selection); var items = this.sortedItems; if (selected.length === 1 || keepExisting) { // Select the next item. var name_2 = selected[selected.length - 1]; index = arrays.findIndex(items, function (value) { return value.name === name_2; }); index += 1; if (index === this._items.length) { index = 0; } } else if (selected.length === 0) { // Select the first item. index = 0; } else { // Select the last selected item. var name_3 = selected[selected.length - 1]; index = arrays.findIndex(items, function (value) { return value.name === name_3; }); } if (index !== -1) { this._selectItem(index, keepExisting); } }; /** * Select previous item. * * @param keepExisting - Whether to keep the current selection and add to it. */ DirListing.prototype.selectPrevious = function (keepExisting) { if (keepExisting === void 0) { keepExisting = false; } var index = -1; var selected = Object.keys(this._selection); var items = this.sortedItems; if (selected.length === 1 || keepExisting) { // Select the previous item. var name_4 = selected[0]; index = arrays.findIndex(items, function (value) { return value.name === name_4; }); index -= 1; if (index === -1) { index = this._items.length - 1; } } else if (selected.length === 0) { // Select the last item. index = this._items.length - 1; } else { // Select the first selected item. var name_5 = selected[0]; index = arrays.findIndex(items, function (value) { return value.name === name_5; }); } if (index !== -1) { this._selectItem(index, keepExisting); } }; /** * Get whether an item is selected by name. */ DirListing.prototype.isSelected = function (name) { return this._selection[name] === true; }; /** * Find a path given a click. */ DirListing.prototype.pathForClick = function (event) { var items = this.sortedItems; var index = utils.hitTestNodes(this._items, event.clientX, event.clientY); if (index !== -1) { return items[index].path; } }; /** * Handle the DOM events for the directory listing. * * @param event - The DOM event sent to the widget. * * #### Notes * This method implements the DOM `EventListener` interface and is * called in response to events on the panel's DOM node. It should * not be called directly by user code. */ DirListing.prototype.handleEvent = function (event) { switch (event.type) { case 'mousedown': this._evtMousedown(event); break; case 'mouseup': this._evtMouseup(event); break; case 'mousemove': this._evtMousemove(event); break; case 'keydown': this._evtKeydown(event); break; case 'click': this._evtClick(event); break; case 'dblclick': this._evtDblClick(event); break; case 'contextmenu': this._evtContextMenu(event); break; case 'scroll': this._evtScroll(event); break; case 'p-dragenter': this._evtDragEnter(event); break; case 'p-dragleave': this._evtDragLeave(event); break; case 'p-dragover': this._evtDragOver(event); break; case 'p-drop': this._evtDrop(event); break; } }; /** * A message handler invoked on an `'after-attach'` message. */ DirListing.prototype.onAfterAttach = function (msg) { _super.prototype.onAfterAttach.call(this, msg); var node = this.node; var content = utils.findElement(node, CONTENT_CLASS); node.addEventListener('mousedown', this); node.addEventListener('keydown', this); node.addEventListener('click', this); node.addEventListener('dblclick', this); node.addEventListener('contextmenu', this); content.addEventListener('scroll', this); content.addEventListener('p-dragenter', this); content.addEventListener('p-dragleave', this); content.addEventListener('p-dragover', this); content.addEventListener('p-drop', this); }; /** * A message handler invoked on a `'before-detach'` message. */ DirListing.prototype.onBeforeDetach = function (msg) { _super.prototype.onBeforeDetach.call(this, msg); var node = this.node; var content = utils.findElement(node, CONTENT_CLASS); node.removeEventListener('mousedown', this); node.removeEventListener('keydown', this); node.removeEventListener('click', this); node.removeEventListener('dblclick', this); node.removeEventListener('contextmenu', this); content.removeEventListener('scroll', this); content.removeEventListener('p-dragenter', this); content.removeEventListener('p-dragleave', this); content.removeEventListener('p-dragover', this); content.removeEventListener('p-drop', this); document.removeEventListener('mousemove', this, true); document.removeEventListener('mouseup', this, true); }; /** * A handler invoked on an `'update-request'` message. */ DirListing.prototype.onUpdateRequest = function (msg) { // Fetch common variables. var items = this.sortedItems; var nodes = this._items; var content = utils.findElement(this.node, CONTENT_CLASS); var renderer = this._renderer; this.removeClass(MULTI_SELECTED_CLASS); this.removeClass(utils_1.SELECTED_CLASS); // Remove any excess item nodes. while (nodes.length > items.length) { var node = nodes.pop(); content.removeChild(node); } // Add any missing item nodes. while (nodes.length < items.length) { var node = renderer.createItemNode(); node.classList.add(ITEM_CLASS); nodes.push(node); content.appendChild(node); } // Remove extra classes from the nodes. for (var i = 0, n = items.length; i < n; ++i) { nodes[i].classList.remove(utils_1.SELECTED_CLASS); nodes[i].classList.remove(RUNNING_CLASS); nodes[i].classList.remove(CUT_CLASS); } // Add extra classes to item nodes based on widget state. for (var i = 0, n = items.length; i < n; ++i) { renderer.updateItemNode(nodes[i], items[i]); if (this._selection[items[i].name]) { nodes[i].classList.add(utils_1.SELECTED_CLASS); if (this._isCut && this._model.path === this._prevPath) { nodes[i].classList.add(CUT_CLASS); } } } // Handle the selectors on the widget node. var selectedNames = Object.keys(this._selection); if (selectedNames.length > 1) { this.addClass(MULTI_SELECTED_CLASS); } if (selectedNames.length) { this.addClass(utils_1.SELECTED_CLASS); } // Handle notebook session statuses. var paths = items.map(function (item) { return item.path; }); var specs = this._model.kernelspecs; for (var _i = 0, _a = this._model.sessions; _i < _a.length; _i++) { var session = _a[_i]; var index = paths.indexOf(session.notebook.path); var node = this._items[index]; node.classList.add(RUNNING_CLASS); node.title = specs.kernelspecs[session.kernel.name].spec.display_name; } this._prevPath = this._model.path; }; /** * Handle the `'click'` event for the widget. */ DirListing.prototype._evtClick = function (event) { var target = event.target; var header = this.headerNode; if (header.contains(target)) { var state = this.renderer.handleHeaderClick(header, event); if (state) { this.sort(state); } return; } }; /** * Handle the `'scroll'` event for the widget. */ DirListing.prototype._evtScroll = function (event) { this.headerNode.scrollLeft = this.contentNode.scrollLeft; }; /** * Handle the `'contextmenu'` event for the widget. */ DirListing.prototype._evtContextMenu = function (event) { this._inContext = true; }; /** * Handle the `'mousedown'` event for the widget. */ DirListing.prototype._evtMousedown = function (event) { // Bail if clicking within the edit node if (event.target === this._editNode) { return; } // Blur the edit node if necessary. if (this._editNode.parentNode) { if (this._editNode !== event.target) { this._editNode.focus(); this._editNode.blur(); clearTimeout(this._selectTimer); } else { return; } } // Check for clearing a context menu. var newContext = (IS_MAC && event.ctrlKey) || (event.button === 2); if (this._inContext && !newContext) { this._inContext = false; return; } this._inContext = false; var index = utils.hitTestNodes(this._items, event.clientX, event.clientY); if (index === -1) { return; } this._handleFileSelect(event); // Left mouse press for drag start. if (event.button === 0) { this._dragData = { pressX: event.clientX, pressY: event.clientY, index: index }; document.addEventListener('mouseup', this, true); document.addEventListener('mousemove', this, true); } if (event.button !== 0) { clearTimeout(this._selectTimer); } }; /** * Handle the `'mouseup'` event for the widget. */ DirListing.prototype._evtMouseup = function (event) { // Handle any soft selection from the previous mouse down. if (this._softSelection) { var altered = event.metaKey || event.shiftKey || event.ctrlKey; // See if we need to clear the other selection. if (!altered && event.button === 0) { this._selection = Object.create(null); this._selection[this._softSelection] = true; this.update(); } this._softSelection = ''; } // Remove the drag listeners if necessary. if (event.button !== 0 || !this._drag) { document.removeEventListener('mousemove', this, true); document.removeEventListener('mouseup', this, true); return; } event.preventDefault(); event.stopPropagation(); }; /** * Handle the `'mousemove'` event for the widget. */ DirListing.prototype._evtMousemove = function (event) { event.preventDefault(); event.stopPropagation(); // Bail if we are the one dragging. if (this._drag) { return; } // Check for a drag initialization. var data = this._dragData; var dx = Math.abs(event.clientX - data.pressX); var dy = Math.abs(event.clientY - data.pressY); if (dx < DRAG_THRESHOLD && dy < DRAG_THRESHOLD) { return; } this._startDrag(data.index, event.clientX, event.clientY); }; /** * Handle the `'keydown'` event for the widget. */ DirListing.prototype._evtKeydown = function (event) { switch (event.keyCode) { case 38: this.selectPrevious(event.shiftKey); event.stopPropagation(); event.preventDefault(); break; case 40: this.selectNext(event.shiftKey); event.stopPropagation(); event.preventDefault(); break; } }; /** * Handle the `'dblclick'` event for the widget. */ DirListing.prototype._evtDblClick = function (event) { var _this = this; // Do nothing if it's not a left mouse press. if (event.button !== 0) { return; } // Do nothing if any modifier keys are pressed. if (event.ctrlKey || event.shiftKey || event.altKey || event.metaKey) { return; } // Stop the event propagation. event.preventDefault(); event.stopPropagation(); clearTimeout(this._selectTimer); this._noSelectTimer = setTimeout(function () { _this._noSelectTimer = -1; }, RENAME_DURATION); this._editNode.blur(); // Find a valid double click target. var target = event.target; var i = arrays.findIndex(this._items, function (node) { return node.contains(target); }); if (i === -1) { return; } var model = this._model; var item = this.sortedItems[i]; if (item.type === 'directory') { model.cd(item.name).catch(function (error) { return utils_1.showErrorMessage(_this, 'Open directory', error); }); } else { var path = item.path; var widget = this._manager.findWidget(path); if (!widget) { widget = this._manager.open(item.path); var context = this._manager.contextForWidget(widget); context.populated.connect(function () { return model.refresh(); }); context.kernelChanged.connect(function () { return model.refresh(); }); } this._opener.open(widget); } }; /** * Handle the `'p-dragenter'` event for the widget. */ DirListing.prototype._evtDragEnter = function (event) { if (event.mimeData.hasData(utils.CONTENTS_MIME)) { var index = utils.hitTestNodes(this._items, event.clientX, event.clientY); if (index === -1) { return; } var item = this.sortedItems[index]; if (item.type !== 'directory' || this._selection[item.name]) { return; } var target = event.target; target.classList.add(utils.DROP_TARGET_CLASS); event.preventDefault(); event.stopPropagation(); } }; /** * Handle the `'p-dragleave'` event for the widget. */ DirListing.prototype._evtDragLeave = function (event) { event.preventDefault(); event.stopPropagation(); var dropTarget = utils.findElement(this.node, utils.DROP_TARGET_CLASS); if (dropTarget) { dropTarget.classList.remove(utils.DROP_TARGET_CLASS); } }; /** * Handle the `'p-dragover'` event for the widget. */ DirListing.prototype._evtDragOver = function (event) { event.preventDefault(); event.stopPropagation(); event.dropAction = event.proposedAction; var dropTarget = utils.findElement(this.node, utils.DROP_TARGET_CLASS); if (dropTarget) { dropTarget.classList.remove(utils.DROP_TARGET_CLASS); } var index = utils.hitTestNodes(this._items, event.clientX, event.clientY); this._items[index].classList.add(utils.DROP_TARGET_CLASS); }; /** * Handle the `'p-drop'` event for the widget. */ DirListing.prototype._evtDrop = function (event) { var _this = this; event.preventDefault(); event.stopPropagation(); clearTimeout(this._selectTimer); if (event.proposedAction === phosphor_dragdrop_1.DropAction.None) { event.dropAction = phosphor_dragdrop_1.DropAction.None; return; } if (!event.mimeData.hasData(utils.CONTENTS_MIME)) { return; } event.dropAction = event.proposedAction; var target = event.target; while (target && target.parentElement) { if (target.classList.contains(utils.DROP_TARGET_CLASS)) { target.classList.remove(utils.DROP_TARGET_CLASS); break; } target = target.parentElement; } // Get the path based on the target node. var index = this._items.indexOf(target); var items = this.sortedItems; var path = items[index].name + '/'; // Move all of the items. var promises = []; var names = event.mimeData.getData(utils.CONTENTS_MIME); var _loop_1 = function(name_6) { var newPath = path + name_6; promises.push(this_1._model.rename(name_6, newPath).catch(function (error) { if (error.xhr) { error.message = error.xhr.statusText + " " + error.xhr.status; } if (error.message.indexOf('409') !== -1) { var options = { title: 'Overwrite file?', body: "\"" + newPath + "\" already exists, overwrite?", okText: 'OVERWRITE' }; return dialog_1.showDialog(options).then(function (button) { if (button.text === 'OVERWRITE') { return _this._model.deleteFile(newPath).then(function () { return _this._model.rename(name_6, newPath); }); } }); } })); }; var this_1 = this; for (var _i = 0, names_1 = names; _i < names_1.length; _i++) { var name_6 = names_1[_i]; _loop_1(name_6); } Promise.all(promises).then(function () { return _this._model.refresh(); }, function (error) { return utils.showErrorMessage(_this, 'Move Error', error); }); }; /** * Start a drag event. */ DirListing.prototype._startDrag = function (index, clientX, clientY) { var _this = this; var selectedNames = Object.keys(this._selection); var source = this._items[index]; var model = this._model; var items = this.sortedItems; var item = null; // If the source node is not selected, use just that node. if (!source.classList.contains(utils_1.SELECTED_CLASS)) { item = items[index]; selectedNames = [item.name]; } else if (selectedNames.length === 1) { var name_7 = selectedNames[0]; item = arrays.find(items, function (value) { return value.name === name_7; }); } // Create the drag image. var dragImage = this.renderer.createDragImage(source, selectedNames.length); // Set up the drag event. this._drag = new phosphor_dragdrop_1.Drag({ dragImage: dragImage, mimeData: new phosphor_dragdrop_1.MimeData(), supportedActions: phosphor_dragdrop_1.DropActions.Move, proposedAction: phosphor_dragdrop_1.DropAction.Move }); this._drag.mimeData.setData(utils.CONTENTS_MIME, selectedNames); if (item && item.type !== 'directory') { this._drag.mimeData.setData(FACTORY_MIME, function () { var path = item.path; var widget = _this._manager.findWidget(path); if (!widget) { widget = _this._manager.open(item.path); var context = _this._manager.contextForWidget(widget); context.populated.connect(function () { return model.refresh(); }); context.kernelChanged.connect(function () { return model.refresh(); }); } return widget; }); } // Start the drag and remove the mousemove and mouseup listeners. document.removeEventListener('mousemove', this, true); document.removeEventListener('mouseup', this, true); clearTimeout(this._selectTimer); this._drag.start(clientX, clientY).then(function (action) { _this._drag = null; clearTimeout(_this._selectTimer); }); }; /** * Handle selection on a file node. */ DirListing.prototype._handleFileSelect = function (event) { var _this = this; // Fetch common variables. var items = this.sortedItems; var index = utils.hitTestNodes(this._items, event.clientX, event.clientY); clearTimeout(this._selectTimer); if (index === -1) { return; } // Clear any existing soft selection. this._softSelection = ''; var name = items[index].name; var selected = Object.keys(this._selection); // Handle toggling. if ((IS_MAC && event.metaKey) || (!IS_MAC && event.ctrlKey)) { if (this._selection[name]) { delete this._selection[name]; } else { this._selection[name] = true; } } else if (event.shiftKey) { this._handleMultiSelect(selected, index); } else if (name in this._selection && selected.length > 1) { this._softSelection = name; } else { // Handle a rename. if (selected.length === 1 && selected[0] === name) { this._selectTimer = setTimeout(function () { if (_this._noSelectTimer === -1) { _this._doRename(); } }, RENAME_DURATION); } // Select only the given item. this._selection = Object.create(null); this._selection[name] = true; } this._isCut = false; this.update(); }; /** * Handle a multiple select on a file item node. */ DirListing.prototype._handleMultiSelect = function (selected, index) { // Find the "nearest selected". var items = this.sortedItems; var nearestIndex = -1; for (var i = 0; i < this._items.length; i++) { if (i === index) { continue; } var name_8 = items[i].name; if (selected.indexOf(name_8) !== -1) { if (nearestIndex === -1) { nearestIndex = i; } else { if (Math.abs(index - i) < Math.abs(nearestIndex - i)) { nearestIndex = i; } } } } // Default to the first element (and fill down). if (nearestIndex === -1) { nearestIndex = 0; } // Select the rows between the current and the nearest selected. for (var i = 0; i < this._items.length; i++) { if (nearestIndex >= i && index <= i || nearestIndex <= i && index >= i) { this._selection[items[i].name] = true; } } }; /** * Get the currently selected items. */ DirListing.prototype._getSelectedItems = function () { var _this = this; var items = this.sortedItems; return items.filter(function (item) { return _this._selection[item.name]; }); }; /** * Copy the selected items, and optionally cut as well. */ DirListing.prototype._copy = function () { this._clipboard = []; for (var _i = 0, _a = this._getSelectedItems(); _i < _a.length; _i++) { var item = _a[_i]; if (item.type !== 'directory') { // Store the absolute path of the item. this._clipboard.push('/' + item.path); } } this.update(); }; /** * Delete the files with the given names. */ DirListing.prototype._delete = function (names) { var _this = this; var promises = []; for (var _i = 0, names_2 = names; _i < names_2.length; _i++) { var name_9 = names_2[_i]; promises.push(this._model.deleteFile(name_9)); } return Promise.all(promises).then(function () { return _this._model.refresh(); }, function (error) { return utils.showErrorMessage(_this, 'Delete file', error); }); }; /** * Allow the user to rename item on a given row. */ DirListing.prototype._doRename = function () { var _this = this; var items = this.sortedItems; var name = Object.keys(this._selection)[0]; var index = arrays.findIndex(items, function (value) { return value.name === name; }); var row = this._items[index]; var item = items[index]; var nameNode = this.renderer.getNameNode(row); var original = item.name; this._editNode.value = original; return Private.doRename(nameNode, this._editNode).then(function (newName) { if (newName === original) { return; } return _this._model.rename(original, newName).catch(function (error) { if (error.xhr) { error.message = error.xhr.status + ": error.statusText"; } if (error.message.indexOf('409') !== -1 || error.message.indexOf('already exists') !== -1) { var options = { title: 'Overwrite file?', body: "\"" + newName + "\" already exists, overwrite?", okText: 'OVERWRITE' }; return dialog_1.showDialog(options).then(function (button) { if (button.text === 'OVERWRITE') { return _this._model.deleteFile(newName).then(function () { return _this._model.rename(original, newName).then(function () { _this._model.refresh(); }); }); } }); } }).catch(function (error) { utils.showErrorMessage(_this, 'Rename Error', error); return original; }).then(function () { _this._model.refresh(); return newName; }); }); }; /** * Select a given item. */ DirListing.prototype._selectItem = function (index, keepExisting) { // Selected the given row(s) var items = this.sortedItems; if (!keepExisting) { this._selection = Object.create(null); } var name = items[index].name; this._selection[name] = true; Private.scrollIfNeeded(this.contentNode, this._items[index]); this._isCut = false; }; /** * Handle the `refreshed` signal from the model. */ DirListing.prototype._onModelRefreshed = function () { // Update the selection. var existing = Object.keys(this._selection); this._selection = Object.create(null); for (var _i = 0, _a = this._model.items; _i < _a.length; _i++) { var item = _a[_i]; var name_10 = item.name; if (existing.indexOf(name_10) !== -1) { this._selection[name_10] = true; } } // Update the sorted items. this.sort(this.sortState); }; /** * Handle a `pathChanged` signal from the model. */ DirListing.prototype._onPathChanged = function () { // Reset the selection. this._selection = Object.create(null); // Update the sorted items. this.sort(this.sortState); }; return DirListing; }(phosphor_widget_1.Widget)); exports.DirListing = DirListing; /** * The namespace for the `DirListing` class statics. */ var DirListing; (function (DirListing) { /** * The default implementation of an `IRenderer`. */ var Renderer = (function () { function Renderer() { } /** * Populate and empty header node for a dir listing. * * @param node - The header node to populate. */ Renderer.prototype.populateHeaderNode = function (node) { var name = this._createHeaderItemNode('Name'); var modified = this._createHeaderItemNode('Last Modified'); name.classList.add(NAME_ID_CLASS); name.classList.add(utils_1.SELECTED_CLASS); modified.classList.add(MODIFIED_ID_CLASS); node.appendChild(name); node.appendChild(modified); }; /** * Handle a header click. * * @param node - A node populated by [[populateHeaderNode]]. * * @param event - A click event on the node. * * @returns The sort state of the header after the click event. */ Renderer.prototype.handleHeaderClick = function (node, event) { var name = utils.findElement(node, NAME_ID_CLASS); var modified = utils.findElement(node, MODIFIED_ID_CLASS); var state = { direction: 'ascending', key: 'name' }; var target = event.target; if (name.contains(target)) { if (name.classList.contains(utils_1.SELECTED_CLASS)) { if (!name.classList.contains(DESCENDING_CLASS)) { state.direction = 'descending'; name.classList.add(DESCENDING_CLASS); } else { name.classList.remove(DESCENDING_CLASS); } } else { name.classList.remove(DESCENDING_CLASS); } name.classList.add(utils_1.SELECTED_CLASS); modified.classList.remove(utils_1.SELECTED_CLASS); modified.classList.remove(DESCENDING_CLASS); return state; } if (modified.contains(target)) { state.key = 'last_modified'; if (modified.classList.contains(utils_1.SELECTED_CLASS)) { if (!modified.classList.contains(DESCENDING_CLASS)) { state.direction = 'descending'; modified.classList.add(DESCENDING_CLASS); } else { modified.classList.remove(DESCENDING_CLASS); } } else { modified.classList.remove(DESCENDING_CLASS); } modified.classList.add(utils_1.SELECTED_CLASS); name.classList.remove(utils_1.SELECTED_CLASS); name.classList.remove(DESCENDING_CLASS); return state; } return void 0; }; /** * Create a new item node for a dir listing. * * @returns A new DOM node to use as a content item. */ Renderer.prototype.createItemNode = function () { var node = document.createElement('li'); var icon = document.createElement('span'); var text = document.createElement('span'); var modified = document.createElement('span'); icon.className = ITEM_ICON_CLASS; text.className = ITEM_TEXT_CLASS; modified.className = ITEM_MODIFIED_CLASS; node.appendChild(icon); node.appendChild(text); node.appendChild(modified); return node; }; /** * Update an item node to reflect the current state of a model. * * @param node - A node created by [[createItemNode]]. * * @param model - The model object to use for the item state. */ Renderer.prototype.updateItemNode = function (node, model) { var icon = utils.findElement(node, ITEM_ICON_CLASS); var text = utils.findElement(node, ITEM_TEXT_CLASS); var modified = utils.findElement(node, ITEM_MODIFIED_CLASS); icon.className = ITEM_ICON_CLASS; switch (model.type) { case 'directory': icon.classList.add(FOLDER_TYPE_CLASS); break; case 'notebook': icon.classList.add(NOTEBOOK_TYPE_CLASS); break; default: icon.classList.add(FILE_TYPE_CLASS); break; } var modText = ''; var modTitle = ''; if (model.last_modified) { var time = moment(model.last_modified).fromNow(); modText = time === 'a few seconds ago' ? 'seconds ago' : time; modTitle = moment(model.last_modified).format('YYYY-MM-DD HH:mm'); } text.textContent = model.name; modified.textContent = modText; modified.title = modTitle; }; /** * Get the node containing the file name. * * @param node - A node created by [[createItemNode]]. * * @returns The node containing the file name. */ Renderer.prototype.getNameNode = function (node) { return utils.findElement(node, ITEM_TEXT_CLASS); }; /** * Create a drag image for an item. * * @param node - A node created by [[createItemNode]]. * * @param count - The number of items being dragged. * * @returns An element to use as the drag image. */ Renderer.prototype.createDragImage = function (node, count) { var dragImage = node.cloneNode(true); var modified = utils.findElement(dragImage, ITEM_MODIFIED_CLASS); dragImage.removeChild(modified); if (count > 1) { var nameNode = utils.findElement(dragImage, ITEM_TEXT_CLASS); nameNode.textContent = '(' + count + ')'; var iconNode = utils.findElement(dragImage, ITEM_ICON_CLASS); iconNode.className = ITEM_ICON_CLASS + " " + FILE_TYPE_CLASS; } return dragImage; }; /** * Create a node for a header item. */ Renderer.prototype._createHeaderItemNode = function (label) { var node = document.createElement('div'); var text = document.createElement('span'); var icon = document.createElement('span'); node.className = HEADER_ITEM_CLASS; text.className = HEADER_ITEM_TEXT_CLASS; icon.className = HEADER_ITEM_ICON_CLASS; text.textContent = label; node.appendChild(text); node.appendChild(icon); return node; }; return Renderer; }()); DirListing.Renderer = Renderer; /** * The default `IRenderer` instance. */ DirListing.defaultRenderer = new Renderer(); })(DirListing = exports.DirListing || (exports.DirListing = {})); /** * The namespace for the listing private data. */ var Private; (function (Private) { /** * Handle editing text on a node. * * @returns Boolean indicating whether the name changed. */ function doRename(text, edit) { var changed = true; var parent = text.parentElement; parent.replaceChild(edit, text); edit.focus(); var index = edit.value.lastIndexOf('.'); if (index === -1) { edit.setSelectionRange(0, edit.value.length); } else { edit.setSelectionRange(0, index); } return new Promise(function (resolve, reject) { edit.onblur = function () { parent.replaceChild(text, edit); resolve(edit.value); }; edit.onkeydown = function (event) { switch (event.keyCode) { case 13