jupyterlab-emrys
Version:
A computational environment for Jupyter. Powered by Emrys
1,375 lines • 51.7 kB
JavaScript
// 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