UNPKG

@jupyterlab/filebrowser

Version:
396 lines 14.7 kB
// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. import { DOMUtils, showErrorMessage } from '@jupyterlab/apputils'; import { PageConfig, PathExt } from '@jupyterlab/coreutils'; import { renameFile } from '@jupyterlab/docmanager'; import { nullTranslator } from '@jupyterlab/translation'; import { ellipsesIcon, homeIcon as preferredIcon, folderIcon as rootIcon } from '@jupyterlab/ui-components'; import { ArrayExt } from '@lumino/algorithm'; import { JSONExt } from '@lumino/coreutils'; import { ElementExt } from '@lumino/domutils'; import { Widget } from '@lumino/widgets'; /** * The class name added to the breadcrumb node. */ const BREADCRUMB_CLASS = 'jp-BreadCrumbs'; /** * The class name for the breadcrumbs home node */ const BREADCRUMB_ROOT_CLASS = 'jp-BreadCrumbs-home'; /** * The class name for the breadcrumbs preferred node */ const BREADCRUMB_PREFERRED_CLASS = 'jp-BreadCrumbs-preferred'; /** * The class name added to the breadcrumb node. */ const BREADCRUMB_ITEM_CLASS = 'jp-BreadCrumbs-item'; /** * Bread crumb paths. */ const BREAD_CRUMB_PATHS = ['/', '../../', '../', '']; /** * The mime type for a contents drag object. */ const CONTENTS_MIME = 'application/x-jupyter-icontents'; /** * The class name added to drop targets. */ const DROP_TARGET_CLASS = 'jp-mod-dropTarget'; /** * A class which hosts folder breadcrumbs. */ export class BreadCrumbs extends Widget { /** * Construct a new file browser crumb widget. * * @param options Constructor options. */ constructor(options) { super(); this._previousState = null; this.translator = options.translator || nullTranslator; this._trans = this.translator.load('jupyterlab'); this._model = options.model; this._fullPath = options.fullPath || false; this.addClass(BREADCRUMB_CLASS); this._crumbs = Private.createCrumbs(); this._crumbSeps = Private.createCrumbSeparators(); const hasPreferred = PageConfig.getOption('preferredPath'); this._hasPreferred = hasPreferred && hasPreferred !== '/' ? true : false; if (this._hasPreferred) { this.node.appendChild(this._crumbs[Private.Crumb.Preferred]); } this.node.appendChild(this._crumbs[Private.Crumb.Home]); this._model.refreshed.connect(this.update, this); } /** * Handle the DOM events for the bread crumbs. * * @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. */ handleEvent(event) { switch (event.type) { case 'click': this._evtClick(event); break; case 'lm-dragenter': this._evtDragEnter(event); break; case 'lm-dragleave': this._evtDragLeave(event); break; case 'lm-dragover': this._evtDragOver(event); break; case 'lm-drop': this._evtDrop(event); break; default: return; } } /** * Whether to show the full path in the breadcrumbs */ get fullPath() { return this._fullPath; } set fullPath(value) { this._fullPath = value; } /** * A message handler invoked on an `'after-attach'` message. */ onAfterAttach(msg) { super.onAfterAttach(msg); this.update(); const node = this.node; node.addEventListener('click', this); node.addEventListener('lm-dragenter', this); node.addEventListener('lm-dragleave', this); node.addEventListener('lm-dragover', this); node.addEventListener('lm-drop', this); } /** * A message handler invoked on a `'before-detach'` message. */ onBeforeDetach(msg) { super.onBeforeDetach(msg); const node = this.node; node.removeEventListener('click', this); node.removeEventListener('lm-dragenter', this); node.removeEventListener('lm-dragleave', this); node.removeEventListener('lm-dragover', this); node.removeEventListener('lm-drop', this); } /** * A handler invoked on an `'update-request'` message. */ onUpdateRequest(msg) { // Update the breadcrumb list. const contents = this._model.manager.services.contents; const localPath = contents.localPath(this._model.path); const state = { path: localPath, hasPreferred: this._hasPreferred, fullPath: this._fullPath }; if (this._previousState && JSONExt.deepEqual(state, this._previousState)) { return; } this._previousState = state; Private.updateCrumbs(this._crumbs, this._crumbSeps, state); } /** * Handle the `'click'` event for the widget. */ _evtClick(event) { // Do nothing if it's not a left mouse press. if (event.button !== 0) { return; } // Find a valid click target. let node = event.target; while (node && node !== this.node) { if (node.classList.contains(BREADCRUMB_PREFERRED_CLASS)) { const preferredPath = PageConfig.getOption('preferredPath'); const path = preferredPath ? '/' + preferredPath : preferredPath; this._model .cd(path) .catch(error => showErrorMessage(this._trans.__('Open Error'), error)); // Stop the event propagation. event.preventDefault(); event.stopPropagation(); return; } if (node.classList.contains(BREADCRUMB_ITEM_CLASS) || node.classList.contains(BREADCRUMB_ROOT_CLASS)) { let index = ArrayExt.findFirstIndex(this._crumbs, value => value === node); let destination = BREAD_CRUMB_PATHS[index]; if (this._fullPath && index < 0 && !node.classList.contains(BREADCRUMB_ROOT_CLASS)) { destination = node.title; } this._model .cd(destination) .catch(error => showErrorMessage(this._trans.__('Open Error'), error)); // Stop the event propagation. event.preventDefault(); event.stopPropagation(); return; } node = node.parentElement; } } /** * Handle the `'lm-dragenter'` event for the widget. */ _evtDragEnter(event) { if (event.mimeData.hasData(CONTENTS_MIME)) { const index = ArrayExt.findFirstIndex(this._crumbs, node => ElementExt.hitTest(node, event.clientX, event.clientY)); if (index !== -1) { if (index !== Private.Crumb.Current) { this._crumbs[index].classList.add(DROP_TARGET_CLASS); event.preventDefault(); event.stopPropagation(); } } } } /** * Handle the `'lm-dragleave'` event for the widget. */ _evtDragLeave(event) { event.preventDefault(); event.stopPropagation(); const dropTarget = DOMUtils.findElement(this.node, DROP_TARGET_CLASS); if (dropTarget) { dropTarget.classList.remove(DROP_TARGET_CLASS); } } /** * Handle the `'lm-dragover'` event for the widget. */ _evtDragOver(event) { event.preventDefault(); event.stopPropagation(); event.dropAction = event.proposedAction; const dropTarget = DOMUtils.findElement(this.node, DROP_TARGET_CLASS); if (dropTarget) { dropTarget.classList.remove(DROP_TARGET_CLASS); } const index = ArrayExt.findFirstIndex(this._crumbs, node => ElementExt.hitTest(node, event.clientX, event.clientY)); if (index !== -1) { this._crumbs[index].classList.add(DROP_TARGET_CLASS); } } /** * Handle the `'lm-drop'` event for the widget. */ _evtDrop(event) { event.preventDefault(); event.stopPropagation(); if (event.proposedAction === 'none') { event.dropAction = 'none'; return; } if (!event.mimeData.hasData(CONTENTS_MIME)) { return; } event.dropAction = event.proposedAction; let target = event.target; while (target && target.parentElement) { if (target.classList.contains(DROP_TARGET_CLASS)) { target.classList.remove(DROP_TARGET_CLASS); break; } target = target.parentElement; } // Get the path based on the target node. const index = ArrayExt.findFirstIndex(this._crumbs, node => node === target); if (index === -1) { return; } const model = this._model; const path = PathExt.resolve(model.path, BREAD_CRUMB_PATHS[index]); const manager = model.manager; // Move all of the items. const promises = []; const oldPaths = event.mimeData.getData(CONTENTS_MIME); for (const oldPath of oldPaths) { const localOldPath = manager.services.contents.localPath(oldPath); const name = PathExt.basename(localOldPath); const newPath = PathExt.join(path, name); promises.push(renameFile(manager, oldPath, newPath)); } void Promise.all(promises).catch(err => { return showErrorMessage(this._trans.__('Move Error'), err); }); } } /** * The namespace for the crumbs private data. */ var Private; (function (Private) { /** * Breadcrumb item list enum. */ let Crumb; (function (Crumb) { Crumb[Crumb["Home"] = 0] = "Home"; Crumb[Crumb["Ellipsis"] = 1] = "Ellipsis"; Crumb[Crumb["Parent"] = 2] = "Parent"; Crumb[Crumb["Current"] = 3] = "Current"; Crumb[Crumb["Preferred"] = 4] = "Preferred"; })(Crumb = Private.Crumb || (Private.Crumb = {})); /** * Populate the breadcrumb node. */ function updateCrumbs(breadcrumbs, separators, state) { const node = breadcrumbs[0].parentNode; // Remove all but the home or preferred node. const firstChild = node.firstChild; while (firstChild && firstChild.nextSibling) { node.removeChild(firstChild.nextSibling); } if (state.hasPreferred) { node.appendChild(breadcrumbs[Crumb.Home]); node.appendChild(separators[0]); } else { node.appendChild(separators[0]); } const parts = state.path.split('/'); if (!state.fullPath && parts.length > 2) { node.appendChild(breadcrumbs[Crumb.Ellipsis]); const grandParent = parts.slice(0, parts.length - 2).join('/'); breadcrumbs[Crumb.Ellipsis].title = grandParent; node.appendChild(separators[1]); } if (state.path) { if (!state.fullPath) { if (parts.length >= 2) { breadcrumbs[Crumb.Parent].textContent = parts[parts.length - 2]; node.appendChild(breadcrumbs[Crumb.Parent]); const parent = parts.slice(0, parts.length - 1).join('/'); breadcrumbs[Crumb.Parent].title = parent; node.appendChild(separators[2]); } breadcrumbs[Crumb.Current].textContent = parts[parts.length - 1]; node.appendChild(breadcrumbs[Crumb.Current]); breadcrumbs[Crumb.Current].title = state.path; node.appendChild(separators[3]); } else { for (let i = 0; i < parts.length; i++) { const elem = document.createElement('span'); elem.className = BREADCRUMB_ITEM_CLASS; elem.textContent = parts[i]; const elemPath = `/${parts.slice(0, i + 1).join('/')}`; elem.title = elemPath; node.appendChild(elem); const separator = document.createElement('span'); separator.textContent = '/'; node.appendChild(separator); } } } } Private.updateCrumbs = updateCrumbs; /** * Create the breadcrumb nodes. */ function createCrumbs() { const home = rootIcon.element({ className: BREADCRUMB_ROOT_CLASS, tag: 'span', title: PageConfig.getOption('serverRoot') || 'Jupyter Server Root', stylesheet: 'breadCrumb' }); const ellipsis = ellipsesIcon.element({ className: BREADCRUMB_ITEM_CLASS, tag: 'span', stylesheet: 'breadCrumb' }); const parent = document.createElement('span'); parent.className = BREADCRUMB_ITEM_CLASS; const current = document.createElement('span'); current.className = BREADCRUMB_ITEM_CLASS; const preferredPath = PageConfig.getOption('preferredPath'); const path = preferredPath ? '/' + preferredPath : preferredPath; const preferred = preferredIcon.element({ className: BREADCRUMB_PREFERRED_CLASS, tag: 'span', title: path || 'Jupyter Preferred Path', stylesheet: 'breadCrumb' }); return [home, ellipsis, parent, current, preferred]; } Private.createCrumbs = createCrumbs; /** * Create the breadcrumb separator nodes. */ function createCrumbSeparators() { const items = []; // The maximum number of directories that will be shown in the crumbs const MAX_DIRECTORIES = 2; // Make separators for after each directory, one at the beginning, and one // after a possible ellipsis. for (let i = 0; i < MAX_DIRECTORIES + 2; i++) { const item = document.createElement('span'); item.textContent = '/'; items.push(item); } return items; } Private.createCrumbSeparators = createCrumbSeparators; })(Private || (Private = {})); //# sourceMappingURL=crumbs.js.map