12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121 |
- // Copyright (c) Jupyter Development Team.
- // Distributed under the terms of the Modified BSD License.
- import {
- ILabShell,
- ILayoutRestorer,
- IRouter,
- JupyterFrontEnd,
- JupyterFrontEndPlugin
- } from '@jupyterlab/application';
- import {
- Clipboard,
- MainAreaWidget,
- ToolbarButton,
- WidgetTracker,
- ICommandPalette,
- InputDialog,
- showErrorMessage
- } from '@jupyterlab/apputils';
- import { PageConfig, PathExt, URLExt } from '@jupyterlab/coreutils';
- import { IDocumentManager } from '@jupyterlab/docmanager';
- import {
- FileBrowserModel,
- FileBrowser,
- FileUploadStatus,
- IFileBrowserFactory
- } from '@jupyterlab/filebrowser';
- import { Launcher } from '@jupyterlab/launcher';
- import { IMainMenu } from '@jupyterlab/mainmenu';
- import { Contents } from '@jupyterlab/services';
- import { ISettingRegistry } from '@jupyterlab/settingregistry';
- import { IStateDB } from '@jupyterlab/statedb';
- import { IStatusBar } from '@jupyterlab/statusbar';
- import { addIcon, folderIcon } from '@jupyterlab/ui-components';
- import { IIterator, map, reduce, toArray, find } from '@lumino/algorithm';
- import { CommandRegistry } from '@lumino/commands';
- import { Message } from '@lumino/messaging';
- import { Menu } from '@lumino/widgets';
- /**
- * The command IDs used by the file browser plugin.
- */
- namespace CommandIDs {
- export const copy = 'filebrowser:copy';
- export const copyDownloadLink = 'filebrowser:copy-download-link';
- // For main browser only.
- export const createLauncher = 'filebrowser:create-main-launcher';
- export const cut = 'filebrowser:cut';
- export const del = 'filebrowser:delete';
- export const download = 'filebrowser:download';
- export const duplicate = 'filebrowser:duplicate';
- // For main browser only.
- export const hideBrowser = 'filebrowser:hide-main';
- export const goToPath = 'filebrowser:go-to-path';
- export const openPath = 'filebrowser:open-path';
- export const open = 'filebrowser:open';
- export const openBrowserTab = 'filebrowser:open-browser-tab';
- export const paste = 'filebrowser:paste';
- export const createNewDirectory = 'filebrowser:create-new-directory';
- export const createNewFile = 'filebrowser:create-new-file';
- export const createNewMarkdownFile = 'filebrowser:create-new-markdown-file';
- export const rename = 'filebrowser:rename';
- // For main browser only.
- export const share = 'filebrowser:share-main';
- // For main browser only.
- export const copyPath = 'filebrowser:copy-path';
- export const showBrowser = 'filebrowser:activate';
- export const shutdown = 'filebrowser:shutdown';
- // For main browser only.
- export const toggleBrowser = 'filebrowser:toggle-main';
- export const toggleNavigateToCurrentDirectory =
- 'filebrowser:toggle-navigate-to-current-directory';
- }
- /**
- * The default file browser extension.
- */
- const browser: JupyterFrontEndPlugin<void> = {
- activate: activateBrowser,
- id: '@jupyterlab/filebrowser-extension:browser',
- requires: [
- IFileBrowserFactory,
- IDocumentManager,
- ILabShell,
- ILayoutRestorer,
- ISettingRegistry
- ],
- optional: [ICommandPalette, IMainMenu],
- autoStart: true
- };
- /**
- * The default file browser factory provider.
- */
- const factory: JupyterFrontEndPlugin<IFileBrowserFactory> = {
- activate: activateFactory,
- id: '@jupyterlab/filebrowser-extension:factory',
- provides: IFileBrowserFactory,
- requires: [IDocumentManager],
- optional: [IStateDB, IRouter, JupyterFrontEnd.ITreeResolver]
- };
- /**
- * The default file browser share-file plugin
- *
- * This extension adds a "Copy Shareable Link" command that generates a copy-
- * pastable URL. This url can be used to open a particular file in JupyterLab,
- * handy for emailing links or bookmarking for reference.
- *
- * If you need to change how this link is generated (for instance, to copy a
- * /user-redirect URL for JupyterHub), disable this plugin and replace it
- * with another implementation.
- */
- const shareFile: JupyterFrontEndPlugin<void> = {
- activate: activateShareFile,
- id: '@jupyterlab/filebrowser-extension:share-file',
- requires: [IFileBrowserFactory],
- autoStart: true
- };
- /**
- * A plugin providing file upload status.
- */
- export const fileUploadStatus: JupyterFrontEndPlugin<void> = {
- id: '@jupyterlab/filebrowser-extension:file-upload-status',
- autoStart: true,
- requires: [IFileBrowserFactory],
- optional: [IStatusBar],
- activate: (
- app: JupyterFrontEnd,
- browser: IFileBrowserFactory,
- statusBar: IStatusBar | null
- ) => {
- if (!statusBar) {
- // Automatically disable if statusbar missing
- return;
- }
- const item = new FileUploadStatus({
- tracker: browser.tracker
- });
- statusBar.registerStatusItem(
- '@jupyterlab/filebrowser-extension:file-upload-status',
- {
- item,
- align: 'middle',
- isActive: () => {
- return !!item.model && item.model.items.length > 0;
- },
- activeStateChanged: item.model.stateChanged
- }
- );
- }
- };
- /**
- * The file browser namespace token.
- */
- const namespace = 'filebrowser';
- /**
- * Export the plugins as default.
- */
- const plugins: JupyterFrontEndPlugin<any>[] = [
- factory,
- browser,
- shareFile,
- fileUploadStatus
- ];
- export default plugins;
- /**
- * Activate the file browser factory provider.
- */
- async function activateFactory(
- app: JupyterFrontEnd,
- docManager: IDocumentManager,
- state: IStateDB | null,
- router: IRouter | null,
- tree: JupyterFrontEnd.ITreeResolver | null
- ): Promise<IFileBrowserFactory> {
- const { commands } = app;
- const tracker = new WidgetTracker<FileBrowser>({ namespace });
- const createFileBrowser = (
- id: string,
- options: IFileBrowserFactory.IOptions = {}
- ) => {
- const model = new FileBrowserModel({
- auto: options.auto ?? true,
- manager: docManager,
- driveName: options.driveName || '',
- refreshInterval: options.refreshInterval,
- state:
- options.state === null ? undefined : options.state || state || undefined
- });
- const restore = options.restore;
- const widget = new FileBrowser({ id, model, restore });
- // Add a launcher toolbar item.
- let launcher = new ToolbarButton({
- icon: addIcon,
- onClick: () => {
- return Private.createLauncher(commands, widget);
- },
- tooltip: 'New Launcher'
- });
- widget.toolbar.insertItem(0, 'launch', launcher);
- // Track the newly created file browser.
- void tracker.add(widget);
- return widget;
- };
- // Manually restore and load the default file browser.
- const defaultBrowser = createFileBrowser('filebrowser', {
- auto: false,
- restore: false
- });
- void Private.restoreBrowser(defaultBrowser, commands, router, tree);
- return { createFileBrowser, defaultBrowser, tracker };
- }
- /**
- * Activate the default file browser in the sidebar.
- */
- function activateBrowser(
- app: JupyterFrontEnd,
- factory: IFileBrowserFactory,
- docManager: IDocumentManager,
- labShell: ILabShell,
- restorer: ILayoutRestorer,
- settingRegistry: ISettingRegistry,
- commandPalette: ICommandPalette | null,
- mainMenu: IMainMenu | null
- ): void {
- const browser = factory.defaultBrowser;
- const { commands } = app;
- // Let the application restorer track the primary file browser (that is
- // automatically created) for restoration of application state (e.g. setting
- // the file browser as the current side bar widget).
- //
- // All other file browsers created by using the factory function are
- // responsible for their own restoration behavior, if any.
- restorer.add(browser, namespace);
- addCommands(
- app,
- factory,
- labShell,
- docManager,
- settingRegistry,
- commandPalette,
- mainMenu
- );
- browser.title.iconRenderer = folderIcon;
- // Show the current file browser shortcut in its title.
- const updateBrowserTitle = () => {
- const binding = find(
- app.commands.keyBindings,
- b => b.command === CommandIDs.toggleBrowser
- );
- if (binding) {
- const ks = CommandRegistry.formatKeystroke(binding.keys.join(' '));
- browser.title.caption = `File Browser (${ks})`;
- } else {
- browser.title.caption = 'File Browser';
- }
- };
- updateBrowserTitle();
- app.commands.keyBindingChanged.connect(() => {
- updateBrowserTitle();
- });
- labShell.add(browser, 'left', { rank: 100 });
- // If the layout is a fresh session without saved data, open file browser.
- void labShell.restored.then(layout => {
- if (layout.fresh) {
- void commands.execute(CommandIDs.showBrowser, void 0);
- }
- });
- void Promise.all([app.restored, browser.model.restored]).then(() => {
- function maybeCreate() {
- // Create a launcher if there are no open items.
- if (labShell.isEmpty('main')) {
- void Private.createLauncher(commands, browser);
- }
- }
- // When layout is modified, create a launcher if there are no open items.
- labShell.layoutModified.connect(() => {
- maybeCreate();
- });
- let navigateToCurrentDirectory: boolean = false;
- void settingRegistry
- .load('@jupyterlab/filebrowser-extension:browser')
- .then(settings => {
- settings.changed.connect(settings => {
- navigateToCurrentDirectory = settings.get(
- 'navigateToCurrentDirectory'
- ).composite as boolean;
- browser.navigateToCurrentDirectory = navigateToCurrentDirectory;
- });
- navigateToCurrentDirectory = settings.get('navigateToCurrentDirectory')
- .composite as boolean;
- browser.navigateToCurrentDirectory = navigateToCurrentDirectory;
- });
- // Whether to automatically navigate to a document's current directory
- labShell.currentChanged.connect(async (_, change) => {
- if (navigateToCurrentDirectory && change.newValue) {
- const { newValue } = change;
- const context = docManager.contextForWidget(newValue);
- if (context) {
- const { path } = context;
- try {
- await Private.navigateToPath(path, factory);
- labShell.currentWidget?.activate();
- } catch (reason) {
- console.warn(
- `${CommandIDs.goToPath} failed to open: ${path}`,
- reason
- );
- }
- }
- }
- });
- maybeCreate();
- });
- }
- function activateShareFile(
- app: JupyterFrontEnd,
- factory: IFileBrowserFactory
- ): void {
- const { commands } = app;
- const { tracker } = factory;
- commands.addCommand(CommandIDs.share, {
- execute: () => {
- const widget = tracker.currentWidget;
- const model = widget?.selectedItems().next();
- if (!model) {
- return;
- }
- const path = encodeURI(model.path);
- Clipboard.copyToSystem(URLExt.join(PageConfig.getTreeUrl(), path));
- },
- isVisible: () =>
- !!tracker.currentWidget &&
- toArray(tracker.currentWidget.selectedItems()).length === 1,
- iconClass: 'jp-MaterialIcon jp-LinkIcon',
- label: 'Copy Shareable Link'
- });
- }
- /**
- * Add the main file browser commands to the application's command registry.
- */
- function addCommands(
- app: JupyterFrontEnd,
- factory: IFileBrowserFactory,
- labShell: ILabShell,
- docManager: IDocumentManager,
- settingRegistry: ISettingRegistry,
- commandPalette: ICommandPalette | null,
- mainMenu: IMainMenu | null
- ): void {
- const { docRegistry: registry, commands } = app;
- const { defaultBrowser: browser, tracker } = factory;
- commands.addCommand(CommandIDs.del, {
- execute: () => {
- const widget = tracker.currentWidget;
- if (widget) {
- return widget.delete();
- }
- },
- iconClass: 'jp-MaterialIcon jp-CloseIcon',
- label: 'Delete',
- mnemonic: 0
- });
- commands.addCommand(CommandIDs.copy, {
- execute: () => {
- const widget = tracker.currentWidget;
- if (widget) {
- return widget.copy();
- }
- },
- iconClass: 'jp-MaterialIcon jp-CopyIcon',
- label: 'Copy',
- mnemonic: 0
- });
- commands.addCommand(CommandIDs.cut, {
- execute: () => {
- const widget = tracker.currentWidget;
- if (widget) {
- return widget.cut();
- }
- },
- iconClass: 'jp-MaterialIcon jp-CutIcon',
- label: 'Cut'
- });
- commands.addCommand(CommandIDs.download, {
- execute: () => {
- const widget = tracker.currentWidget;
- if (widget) {
- return widget.download();
- }
- },
- iconClass: 'jp-MaterialIcon jp-DownloadIcon',
- label: 'Download'
- });
- commands.addCommand(CommandIDs.duplicate, {
- execute: () => {
- const widget = tracker.currentWidget;
- if (widget) {
- return widget.duplicate();
- }
- },
- iconClass: 'jp-MaterialIcon jp-CopyIcon',
- label: 'Duplicate'
- });
- commands.addCommand(CommandIDs.hideBrowser, {
- execute: () => {
- const widget = tracker.currentWidget;
- if (widget && !widget.isHidden) {
- labShell.collapseLeft();
- }
- }
- });
- commands.addCommand(CommandIDs.goToPath, {
- execute: async args => {
- const path = (args.path as string) || '';
- try {
- const item = await Private.navigateToPath(path, factory);
- if (item.type !== 'directory') {
- const browserForPath = Private.getBrowserForPath(path, factory);
- if (browserForPath) {
- browserForPath.clearSelectedItems();
- const parts = path.split('/');
- const name = parts[parts.length - 1];
- if (name) {
- await browserForPath.selectItemByName(name);
- }
- }
- }
- } catch (reason) {
- console.warn(`${CommandIDs.goToPath} failed to go to: ${path}`, reason);
- }
- return commands.execute(CommandIDs.showBrowser, { path });
- }
- });
- commands.addCommand(CommandIDs.openPath, {
- label: args => (args.path ? `Open ${args.path}` : 'Open from Path…'),
- caption: args => (args.path ? `Open ${args.path}` : 'Open from path'),
- execute: async ({ path }: { path?: string }) => {
- if (!path) {
- path =
- (
- await InputDialog.getText({
- label: 'Path',
- placeholder: '/path/relative/to/jlab/root',
- title: 'Open Path',
- okLabel: 'Open'
- })
- ).value ?? undefined;
- }
- if (!path) {
- return;
- }
- try {
- let trailingSlash = path !== '/' && path.endsWith('/');
- if (trailingSlash) {
- // The normal contents service errors on paths ending in slash
- path = path.slice(0, path.length - 1);
- }
- const browserForPath = Private.getBrowserForPath(path, factory)!;
- const { services } = browserForPath.model.manager;
- const item = await services.contents.get(path, {
- content: false
- });
- if (trailingSlash && item.type !== 'directory') {
- throw new Error(`Path ${path}/ is not a directory`);
- }
- await commands.execute(CommandIDs.goToPath, { path });
- if (item.type === 'directory') {
- return;
- }
- return commands.execute('docmanager:open', { path });
- } catch (reason) {
- if (reason.response && reason.response.status === 404) {
- reason.message = `Could not find path: ${path}`;
- }
- return showErrorMessage('Cannot open', reason);
- }
- }
- });
- // Add the openPath command to the command palette
- if (commandPalette) {
- commandPalette.addItem({
- command: CommandIDs.openPath,
- category: 'File Operations'
- });
- }
- commands.addCommand(CommandIDs.open, {
- execute: args => {
- const factory = (args['factory'] as string) || void 0;
- const widget = tracker.currentWidget;
- if (!widget) {
- return;
- }
- const { contents } = widget.model.manager.services;
- return Promise.all(
- toArray(
- map(widget.selectedItems(), item => {
- if (item.type === 'directory') {
- const localPath = contents.localPath(item.path);
- return widget.model.cd(`/${localPath}`);
- }
- return commands.execute('docmanager:open', {
- factory: factory,
- path: item.path
- });
- })
- )
- );
- },
- iconClass: args => {
- const factory = (args['factory'] as string) || void 0;
- if (factory) {
- // if an explicit factory is passed...
- const ft = registry.getFileType(factory);
- // ...set an icon if the factory name corresponds to a file type name...
- // ...or leave the icon blank
- return ft?.iconClass ?? '';
- } else {
- return 'jp-MaterialIcon jp-FolderIcon';
- }
- },
- label: args => (args['label'] || args['factory'] || 'Open') as string,
- mnemonic: 0
- });
- commands.addCommand(CommandIDs.openBrowserTab, {
- execute: () => {
- const widget = tracker.currentWidget;
- if (!widget) {
- return;
- }
- return Promise.all(
- toArray(
- map(widget.selectedItems(), item => {
- return commands.execute('docmanager:open-browser-tab', {
- path: item.path
- });
- })
- )
- );
- },
- iconClass: 'jp-MaterialIcon jp-AddIcon',
- label: 'Open in New Browser Tab',
- mnemonic: 0
- });
- commands.addCommand(CommandIDs.copyDownloadLink, {
- execute: () => {
- const widget = tracker.currentWidget;
- if (!widget) {
- return;
- }
- return widget.model.manager.services.contents
- .getDownloadUrl(widget.selectedItems().next()!.path)
- .then(url => {
- Clipboard.copyToSystem(url);
- });
- },
- iconClass: 'jp-MaterialIcon jp-CopyIcon',
- label: 'Copy Download Link',
- mnemonic: 0
- });
- commands.addCommand(CommandIDs.paste, {
- execute: () => {
- const widget = tracker.currentWidget;
- if (widget) {
- return widget.paste();
- }
- },
- iconClass: 'jp-MaterialIcon jp-PasteIcon',
- label: 'Paste',
- mnemonic: 0
- });
- commands.addCommand(CommandIDs.createNewDirectory, {
- execute: () => {
- const widget = tracker.currentWidget;
- if (widget) {
- return widget.createNewDirectory();
- }
- },
- iconClass: 'jp-MaterialIcon jp-NewFolderIcon',
- label: 'New Folder'
- });
- commands.addCommand(CommandIDs.createNewFile, {
- execute: () => {
- const {
- model: { path }
- } = browser;
- void commands.execute('docmanager:new-untitled', {
- path,
- type: 'file',
- ext: 'txt'
- });
- },
- iconClass: 'jp-MaterialIcon jp-TextEditorIcon',
- label: 'New File'
- });
- commands.addCommand(CommandIDs.createNewMarkdownFile, {
- execute: () => {
- const {
- model: { path }
- } = browser;
- void commands.execute('docmanager:new-untitled', {
- path,
- type: 'file',
- ext: 'md'
- });
- },
- iconClass: 'jp-MaterialIcon jp-MarkdownIcon',
- label: 'New Markdown File'
- });
- commands.addCommand(CommandIDs.rename, {
- execute: args => {
- const widget = tracker.currentWidget;
- if (widget) {
- return widget.rename();
- }
- },
- iconClass: 'jp-MaterialIcon jp-EditIcon',
- label: 'Rename',
- mnemonic: 0
- });
- commands.addCommand(CommandIDs.copyPath, {
- execute: () => {
- const widget = tracker.currentWidget;
- if (!widget) {
- return;
- }
- const item = widget.selectedItems().next();
- if (!item) {
- return;
- }
- Clipboard.copyToSystem(item.path);
- },
- isVisible: () =>
- !!tracker.currentWidget &&
- tracker.currentWidget.selectedItems().next !== undefined,
- iconClass: 'jp-MaterialIcon jp-FileIcon',
- label: 'Copy Path'
- });
- commands.addCommand(CommandIDs.showBrowser, {
- execute: args => {
- const path = (args.path as string) || '';
- const browserForPath = Private.getBrowserForPath(path, factory);
- // Check for browser not found
- if (!browserForPath) {
- return;
- }
- // Shortcut if we are using the main file browser
- if (browser === browserForPath) {
- labShell.activateById(browser.id);
- return;
- } else {
- const areas: ILabShell.Area[] = ['left', 'right'];
- for (let area of areas) {
- const it = labShell.widgets(area);
- let widget = it.next();
- while (widget) {
- if (widget.contains(browserForPath)) {
- labShell.activateById(widget.id);
- return;
- }
- widget = it.next();
- }
- }
- }
- }
- });
- commands.addCommand(CommandIDs.shutdown, {
- execute: () => {
- const widget = tracker.currentWidget;
- if (widget) {
- return widget.shutdownKernels();
- }
- },
- iconClass: 'jp-MaterialIcon jp-StopIcon',
- label: 'Shut Down Kernel'
- });
- commands.addCommand(CommandIDs.toggleBrowser, {
- execute: () => {
- if (browser.isHidden) {
- return commands.execute(CommandIDs.showBrowser, void 0);
- }
- return commands.execute(CommandIDs.hideBrowser, void 0);
- }
- });
- commands.addCommand(CommandIDs.createLauncher, {
- label: 'New Launcher',
- execute: () => Private.createLauncher(commands, browser)
- });
- commands.addCommand(CommandIDs.toggleNavigateToCurrentDirectory, {
- label: 'Show Active File in File Browser',
- isToggled: () => browser.navigateToCurrentDirectory,
- execute: () => {
- const value = !browser.navigateToCurrentDirectory;
- const key = 'navigateToCurrentDirectory';
- return settingRegistry
- .set('@jupyterlab/filebrowser-extension:browser', key, value)
- .catch((reason: Error) => {
- console.error(`Failed to set navigateToCurrentDirectory setting`);
- });
- }
- });
- if (mainMenu) {
- mainMenu.settingsMenu.addGroup(
- [{ command: CommandIDs.toggleNavigateToCurrentDirectory }],
- 5
- );
- }
- if (commandPalette) {
- commandPalette.addItem({
- command: CommandIDs.toggleNavigateToCurrentDirectory,
- category: 'File Operations'
- });
- }
- /**
- * A menu widget that dynamically populates with different widget factories
- * based on current filebrowser selection.
- */
- class OpenWithMenu extends Menu {
- protected onBeforeAttach(msg: Message): void {
- // clear the current menu items
- this.clearItems();
- // get the widget factories that could be used to open all of the items
- // in the current filebrowser selection
- let factories = tracker.currentWidget
- ? OpenWithMenu._intersection(
- map(tracker.currentWidget.selectedItems(), i => {
- return OpenWithMenu._getFactories(i);
- })
- )
- : undefined;
- if (factories) {
- // make new menu items from the widget factories
- factories.forEach(factory => {
- this.addItem({
- args: { factory: factory },
- command: CommandIDs.open
- });
- });
- }
- super.onBeforeAttach(msg);
- }
- static _getFactories(item: Contents.IModel): Array<string> {
- let factories = registry
- .preferredWidgetFactories(item.path)
- .map(f => f.name);
- const notebookFactory = registry.getWidgetFactory('notebook')?.name;
- if (
- notebookFactory &&
- item.type === 'notebook' &&
- factories.indexOf(notebookFactory) === -1
- ) {
- factories.unshift(notebookFactory);
- }
- return factories;
- }
- static _intersection<T>(iter: IIterator<Array<T>>): Set<T> | void {
- // pop the first element of iter
- let first = iter.next();
- // first will be undefined if iter is empty
- if (!first) {
- return;
- }
- // "initialize" the intersection from first
- let isect = new Set(first);
- // reduce over the remaining elements of iter
- return reduce(
- iter,
- (isect, subarr) => {
- // filter out all elements not present in both isect and subarr,
- // accumulate result in new set
- return new Set(subarr.filter(x => isect.has(x)));
- },
- isect
- );
- }
- }
- // matches anywhere on filebrowser
- const selectorContent = '.jp-DirListing-content';
- // matches all filebrowser items
- const selectorItem = '.jp-DirListing-item[data-isdir]';
- // matches only non-directory items
- const selectorNotDir = '.jp-DirListing-item[data-isdir="false"]';
- // If the user did not click on any file, we still want to show paste and new folder,
- // so target the content rather than an item.
- app.contextMenu.addItem({
- command: CommandIDs.createNewDirectory,
- selector: selectorContent,
- rank: 1
- });
- app.contextMenu.addItem({
- command: CommandIDs.createNewFile,
- selector: selectorContent,
- rank: 2
- });
- app.contextMenu.addItem({
- command: CommandIDs.createNewMarkdownFile,
- selector: selectorContent,
- rank: 3
- });
- app.contextMenu.addItem({
- command: CommandIDs.paste,
- selector: selectorContent,
- rank: 4
- });
- app.contextMenu.addItem({
- command: CommandIDs.open,
- selector: selectorItem,
- rank: 1
- });
- const openWith = new OpenWithMenu({ commands });
- openWith.title.label = 'Open With';
- app.contextMenu.addItem({
- type: 'submenu',
- submenu: openWith,
- selector: selectorNotDir,
- rank: 2
- });
- app.contextMenu.addItem({
- command: CommandIDs.openBrowserTab,
- selector: selectorNotDir,
- rank: 3
- });
- app.contextMenu.addItem({
- command: CommandIDs.rename,
- selector: selectorItem,
- rank: 4
- });
- app.contextMenu.addItem({
- command: CommandIDs.del,
- selector: selectorItem,
- rank: 5
- });
- app.contextMenu.addItem({
- command: CommandIDs.cut,
- selector: selectorItem,
- rank: 6
- });
- app.contextMenu.addItem({
- command: CommandIDs.copy,
- selector: selectorNotDir,
- rank: 7
- });
- app.contextMenu.addItem({
- command: CommandIDs.duplicate,
- selector: selectorNotDir,
- rank: 8
- });
- app.contextMenu.addItem({
- command: CommandIDs.download,
- selector: selectorNotDir,
- rank: 9
- });
- app.contextMenu.addItem({
- command: CommandIDs.shutdown,
- selector: selectorNotDir,
- rank: 10
- });
- app.contextMenu.addItem({
- command: CommandIDs.share,
- selector: selectorItem,
- rank: 11
- });
- app.contextMenu.addItem({
- command: CommandIDs.copyPath,
- selector: selectorItem,
- rank: 12
- });
- app.contextMenu.addItem({
- command: CommandIDs.copyDownloadLink,
- selector: selectorNotDir,
- rank: 13
- });
- }
- /**
- * A namespace for private module data.
- */
- namespace Private {
- /**
- * Create a launcher for a given filebrowser widget.
- */
- export function createLauncher(
- commands: CommandRegistry,
- browser: FileBrowser
- ): Promise<MainAreaWidget<Launcher>> {
- const { model } = browser;
- return commands
- .execute('launcher:create', { cwd: model.path })
- .then((launcher: MainAreaWidget<Launcher>) => {
- model.pathChanged.connect(() => {
- if (launcher.content) {
- launcher.content.cwd = model.path;
- }
- }, launcher);
- return launcher;
- });
- }
- /**
- * Get browser object given file path.
- */
- export function getBrowserForPath(
- path: string,
- factory: IFileBrowserFactory
- ): FileBrowser | undefined {
- const { defaultBrowser: browser, tracker } = factory;
- const driveName = browser.model.manager.services.contents.driveName(path);
- if (driveName) {
- let browserForPath = tracker.find(
- _path => _path.model.driveName === driveName
- );
- if (!browserForPath) {
- // warn that no filebrowser could be found for this driveName
- console.warn(
- `${CommandIDs.goToPath} failed to find filebrowser for path: ${path}`
- );
- return;
- }
- return browserForPath;
- }
- // if driveName is empty, assume the main filebrowser
- return browser;
- }
- /**
- * Navigate to a path or the path containing a file.
- */
- export async function navigateToPath(
- path: string,
- factory: IFileBrowserFactory
- ): Promise<Contents.IModel> {
- const browserForPath = Private.getBrowserForPath(path, factory);
- if (!browserForPath) {
- throw new Error('No browser for path');
- }
- const { services } = browserForPath.model.manager;
- const localPath = services.contents.localPath(path);
- await services.ready;
- let item = await services.contents.get(path, { content: false });
- const { model } = browserForPath;
- await model.restored;
- if (item.type === 'directory') {
- await model.cd(`/${localPath}`);
- } else {
- await model.cd(`/${PathExt.dirname(localPath)}`);
- }
- return item;
- }
- /**
- * Restores file browser state and overrides state if tree resolver resolves.
- */
- export async function restoreBrowser(
- browser: FileBrowser,
- commands: CommandRegistry,
- router: IRouter | null,
- tree: JupyterFrontEnd.ITreeResolver | null
- ): Promise<void> {
- const restoring = 'jp-mod-restoring';
- browser.addClass(restoring);
- if (!router) {
- await browser.model.restore(browser.id);
- await browser.model.refresh();
- browser.removeClass(restoring);
- return;
- }
- const listener = async () => {
- router.routed.disconnect(listener);
- const paths = await tree?.paths;
- if (paths?.file || paths?.browser) {
- // Restore the model without populating it.
- await browser.model.restore(browser.id, false);
- if (paths.file) {
- await commands.execute(CommandIDs.openPath, { path: paths.file });
- }
- if (paths.browser) {
- await commands.execute(CommandIDs.openPath, { path: paths.browser });
- }
- } else {
- await browser.model.restore(browser.id);
- await browser.model.refresh();
- }
- browser.removeClass(restoring);
- };
- router.routed.connect(listener);
- }
- }
|