소스 검색

Merge pull request #9667 from jtpio/filebrowser-optionals

Make the filebrowser plugins more reusable
Steven Silvester 4 년 전
부모
커밋
b1f4648bf7
1개의 변경된 파일329개의 추가작업 그리고 295개의 파일을 삭제
  1. 329 295
      packages/filebrowser-extension/src/index.ts

+ 329 - 295
packages/filebrowser-extension/src/index.ts

@@ -136,33 +136,271 @@ namespace CommandIDs {
   export const search = 'filebrowser:search';
 }
 
+/**
+ * The file browser namespace token.
+ */
+const namespace = 'filebrowser';
+
 /**
  * The default file browser extension.
  */
 const browser: JupyterFrontEndPlugin<void> = {
-  activate: activateBrowser,
   id: '@jupyterlab/filebrowser-extension:browser',
-  requires: [
-    IFileBrowserFactory,
-    IDocumentManager,
-    ILabShell,
+  requires: [IFileBrowserFactory, ITranslator],
+  optional: [
     ILayoutRestorer,
     ISettingRegistry,
-    ITranslator
+    ITreePathUpdater,
+    ICommandPalette,
+    IMainMenu
   ],
-  optional: [ITreePathUpdater, ICommandPalette, IMainMenu],
-  autoStart: true
+  autoStart: true,
+  activate: (
+    app: JupyterFrontEnd,
+    factory: IFileBrowserFactory,
+    translator: ITranslator,
+    restorer: ILayoutRestorer | null,
+    settingRegistry: ISettingRegistry | null,
+    treePathUpdater: ITreePathUpdater | null,
+    commandPalette: ICommandPalette | null,
+    mainMenu: IMainMenu | null
+  ): void => {
+    const trans = translator.load('jupyterlab');
+    const browser = factory.defaultBrowser;
+
+    // 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.
+    if (restorer) {
+      restorer.add(browser, namespace);
+    }
+
+    addCommands(
+      app,
+      factory,
+      translator,
+      settingRegistry,
+      commandPalette,
+      mainMenu
+    );
+
+    browser.title.icon = 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 = trans.__('File Browser (%1)', ks);
+      } else {
+        browser.title.caption = trans.__('File Browser');
+      }
+    };
+    updateBrowserTitle();
+    app.commands.keyBindingChanged.connect(() => {
+      updateBrowserTitle();
+    });
+
+    void Promise.all([app.restored, browser.model.restored]).then(() => {
+      if (treePathUpdater) {
+        browser.model.pathChanged.connect((sender, args) => {
+          treePathUpdater(args.newValue);
+        });
+      }
+
+      let navigateToCurrentDirectory: boolean = false;
+      let useFuzzyFilter: boolean = true;
+
+      if (settingRegistry) {
+        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;
+            settings.changed.connect(settings => {
+              useFuzzyFilter = settings.get('useFuzzyFilter')
+                .composite as boolean;
+              browser.useFuzzyFilter = useFuzzyFilter;
+            });
+            useFuzzyFilter = settings.get('useFuzzyFilter')
+              .composite as boolean;
+            browser.useFuzzyFilter = useFuzzyFilter;
+          });
+      }
+    });
+  }
 };
 
 /**
  * The default file browser factory provider.
  */
 const factory: JupyterFrontEndPlugin<IFileBrowserFactory> = {
-  activate: activateFactory,
   id: '@jupyterlab/filebrowser-extension:factory',
   provides: IFileBrowserFactory,
   requires: [IDocumentManager, ITranslator],
-  optional: [IStateDB, IRouter, JupyterFrontEnd.ITreeResolver]
+  optional: [IStateDB, IRouter, JupyterFrontEnd.ITreeResolver],
+  activate: async (
+    app: JupyterFrontEnd,
+    docManager: IDocumentManager,
+    translator: ITranslator,
+    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 FilterFileBrowserModel({
+        translator: translator,
+        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, translator });
+
+      // 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 };
+  }
+};
+
+/**
+ * A plugin to add the file browser widget to an ILabShell
+ */
+const browserWidget: JupyterFrontEndPlugin<void> = {
+  id: '@jupyterlab/filebrowser-extension:widget',
+  requires: [IDocumentManager, IFileBrowserFactory, ITranslator, ILabShell],
+  autoStart: true,
+  activate: (
+    app: JupyterFrontEnd,
+    docManager: IDocumentManager,
+    factory: IFileBrowserFactory,
+    translator: ITranslator,
+    labShell: ILabShell
+  ): void => {
+    const { commands } = app;
+    const { defaultBrowser: browser, tracker } = factory;
+    labShell.add(browser, 'left', { rank: 100 });
+
+    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 (const 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.hideBrowser, {
+      execute: () => {
+        const widget = tracker.currentWidget;
+        if (widget && !widget.isHidden) {
+          labShell.collapseLeft();
+        }
+      }
+    });
+
+    // If the layout is a fresh session without saved data and not in single document
+    // mode, open file browser.
+    void labShell.restored.then(layout => {
+      if (layout.fresh && labShell.mode !== 'single-document') {
+        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') &&
+          commands.hasCommand('launcher:create')
+        ) {
+          void Private.createLauncher(commands, browser);
+        }
+      }
+
+      // When layout is modified, create a launcher if there are no open items.
+      labShell.layoutModified.connect(() => {
+        maybeCreate();
+      });
+
+      // Whether to automatically navigate to a document's current directory
+      labShell.currentChanged.connect(async (_, change) => {
+        if (browser.navigateToCurrentDirectory && change.newValue) {
+          const { newValue } = change;
+          const context = docManager.contextForWidget(newValue);
+          if (context) {
+            const { path } = context;
+            try {
+              await Private.navigateToPath(path, factory, translator);
+              labShell.currentWidget?.activate();
+            } catch (reason) {
+              console.warn(
+                `${CommandIDs.goToPath} failed to open: ${path}`,
+                reason
+              );
+            }
+          }
+        }
+      });
+
+      maybeCreate();
+    });
+  }
 };
 
 /**
@@ -177,10 +415,44 @@ const factory: JupyterFrontEndPlugin<IFileBrowserFactory> = {
  * with another implementation.
  */
 const shareFile: JupyterFrontEndPlugin<void> = {
-  activate: activateShareFile,
   id: '@jupyterlab/filebrowser-extension:share-file',
   requires: [IFileBrowserFactory, ITranslator],
-  autoStart: true
+  autoStart: true,
+  activate: (
+    app: JupyterFrontEnd,
+    factory: IFileBrowserFactory,
+    translator: ITranslator
+  ): void => {
+    const trans = translator.load('jupyterlab');
+    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.normalize(
+            PageConfig.getUrl({
+              mode: 'single-document',
+              workspace: PageConfig.defaultWorkspace,
+              treePath: path
+            })
+          )
+        );
+      },
+      isVisible: () =>
+        !!tracker.currentWidget &&
+        toArray(tracker.currentWidget.selectedItems()).length === 1,
+      icon: linkIcon.bindprops({ stylesheet: 'menuItem' }),
+      label: trans.__('Copy Shareable Link')
+    });
+  }
 };
 
 /**
@@ -221,247 +493,35 @@ export const fileUploadStatus: JupyterFrontEndPlugin<void> = {
 };
 
 /**
- * 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.
+ * A plugin to add a launcher button to the file browser toolbar
  */
-async function activateFactory(
-  app: JupyterFrontEnd,
-  docManager: IDocumentManager,
-  translator: ITranslator,
-  state: IStateDB | null,
-  router: IRouter | null,
-  tree: JupyterFrontEnd.ITreeResolver | null
-): Promise<IFileBrowserFactory> {
-  const trans = translator.load('jupyterlab');
-  const { commands } = app;
-  const tracker = new WidgetTracker<FileBrowser>({ namespace });
-  const createFileBrowser = (
-    id: string,
-    options: IFileBrowserFactory.IOptions = {}
+export const launcherToolbarButton: JupyterFrontEndPlugin<void> = {
+  id: '@jupyterlab/filebrowser-extension:launcher-toolbar-button',
+  autoStart: true,
+  requires: [IFileBrowserFactory, ITranslator],
+  activate: (
+    app: JupyterFrontEnd,
+    factory: IFileBrowserFactory,
+    translator: ITranslator
   ) => {
-    const model = new FilterFileBrowserModel({
-      translator: translator,
-      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, translator });
+    const { commands } = app;
+    const trans = translator.load('jupyterlab');
+    const { defaultBrowser: browser } = factory;
 
     // Add a launcher toolbar item.
     const launcher = new ToolbarButton({
       icon: addIcon,
       onClick: () => {
         if (commands.hasCommand('launcher:create')) {
-          return Private.createLauncher(commands, widget);
+          return Private.createLauncher(commands, browser);
         }
       },
       tooltip: trans.__('New Launcher'),
       actualOnClick: true
     });
-    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,
-  translator: ITranslator,
-  treePathUpdater: ITreePathUpdater | null,
-  commandPalette: ICommandPalette | null,
-  mainMenu: IMainMenu | null
-): void {
-  const trans = translator.load('jupyterlab');
-  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,
-    translator,
-    commandPalette,
-    mainMenu
-  );
-
-  browser.title.icon = 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 = trans.__('File Browser (%1)', ks);
-    } else {
-      browser.title.caption = trans.__('File Browser');
-    }
-  };
-  updateBrowserTitle();
-  app.commands.keyBindingChanged.connect(() => {
-    updateBrowserTitle();
-  });
-  labShell.add(browser, 'left', { rank: 100 });
-
-  // If the layout is a fresh session without saved data and not in single document
-  // mode, open file browser.
-  void labShell.restored.then(layout => {
-    if (layout.fresh && labShell.mode !== 'single-document') {
-      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') && commands.hasCommand('launcher:create')) {
-        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;
-    let useFuzzyFilter: boolean = true;
-
-    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;
-        settings.changed.connect(settings => {
-          useFuzzyFilter = settings.get('useFuzzyFilter').composite as boolean;
-          browser.useFuzzyFilter = useFuzzyFilter;
-        });
-        useFuzzyFilter = settings.get('useFuzzyFilter').composite as boolean;
-        browser.useFuzzyFilter = useFuzzyFilter;
-      });
-
-    // 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, translator);
-            labShell.currentWidget?.activate();
-          } catch (reason) {
-            console.warn(
-              `${CommandIDs.goToPath} failed to open: ${path}`,
-              reason
-            );
-          }
-        }
-      }
-    });
-
-    if (treePathUpdater) {
-      browser.model.pathChanged.connect((sender, args) => {
-        treePathUpdater(args.newValue);
-      });
-    }
-
-    maybeCreate();
-  });
-}
-
-function activateShareFile(
-  app: JupyterFrontEnd,
-  factory: IFileBrowserFactory,
-  translator: ITranslator
-): void {
-  const trans = translator.load('jupyterlab');
-  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.normalize(
-          PageConfig.getUrl({
-            mode: 'single-document',
-            workspace: PageConfig.defaultWorkspace,
-            treePath: path
-          })
-        )
-      );
-    },
-    isVisible: () =>
-      !!tracker.currentWidget &&
-      toArray(tracker.currentWidget.selectedItems()).length === 1,
-    icon: linkIcon.bindprops({ stylesheet: 'menuItem' }),
-    label: trans.__('Copy Shareable Link')
-  });
-}
+    browser.toolbar.insertItem(0, 'launch', launcher);
+  }
+};
 
 /**
  * Add the main file browser commands to the application's command registry.
@@ -469,10 +529,8 @@ function activateShareFile(
 function addCommands(
   app: JupyterFrontEnd,
   factory: IFileBrowserFactory,
-  labShell: ILabShell,
-  docManager: IDocumentManager,
-  settingRegistry: ISettingRegistry,
   translator: ITranslator,
+  settingRegistry: ISettingRegistry | null,
   commandPalette: ICommandPalette | null,
   mainMenu: IMainMenu | null
 ): void {
@@ -542,15 +600,6 @@ function addCommands(
     label: trans.__('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) || '';
@@ -811,36 +860,6 @@ function addCommands(
     label: trans.__('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 (const 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;
@@ -868,19 +887,21 @@ function addCommands(
     execute: () => Private.createLauncher(commands, browser)
   });
 
-  commands.addCommand(CommandIDs.toggleNavigateToCurrentDirectory, {
-    label: trans.__('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 (settingRegistry) {
+    commands.addCommand(CommandIDs.toggleNavigateToCurrentDirectory, {
+      label: trans.__('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`);
+          });
+      }
+    });
+  }
 
   commands.addCommand(CommandIDs.toggleLastModified, {
     label: trans.__('Toggle Last Modified Column'),
@@ -1239,3 +1260,16 @@ namespace Private {
     router.routed.connect(listener);
   }
 }
+
+/**
+ * Export the plugins as default.
+ */
+const plugins: JupyterFrontEndPlugin<any>[] = [
+  factory,
+  browser,
+  shareFile,
+  fileUploadStatus,
+  browserWidget,
+  launcherToolbarButton
+];
+export default plugins;