Forráskód Böngészése

Update router interface and implementation.

Afshin Darian 7 éve
szülő
commit
4fcea12de1

+ 1 - 1
packages/application-extension/src/index.ts

@@ -192,7 +192,7 @@ const router: JupyterLabPlugin<IRouter> = {
       execute: args => URLExt.join(tree, (args.path as string))
     });
     app.started.then(() => { router.route(window.location.href); });
-    router.register(/^\/tree\/.+/, CommandIDs.tree);
+    router.register({ command: CommandIDs.tree, pattern: /^\/tree\/.+/ });
 
     return router;
   },

+ 77 - 20
packages/application/src/router.ts

@@ -16,9 +16,13 @@ import {
 } from '@phosphor/coreutils';
 
 import {
-  IDisposable, DisposableDelegate
+  DisposableDelegate, IDisposable
 } from '@phosphor/disposable';
 
+import {
+  ISignal, Signal
+} from '@phosphor/signaling';
+
 
 /* tslint:disable */
 /**
@@ -44,16 +48,19 @@ interface IRouter {
    */
   readonly commands: CommandRegistry;
 
+  /**
+   * A signal emitted when the router routes a route.
+   */
+  readonly routed: ISignal<IRouter, IRouter.ICommandArgs>;
+
   /**
    * Register to route a path pattern to a command.
    *
-   * @param pattern - The regular expression that will be matched against URLs.
-   *
-   * @param command - The command string that will be invoked upon matching.
+   * @param options - The route registration options.
    *
    * @returns A disposable that removes the registered rul from the router.
    */
-  register(pattern: RegExp, command: string): IDisposable;
+  register(options: IRouter.IRegisterArgs): IDisposable;
 
   /**
    * Route a specific path to an action.
@@ -89,6 +96,28 @@ namespace IRouter {
      */
     search: string;
   }
+
+  /**
+   * The specification for registering a route with the router.
+   */
+  export
+  interface IRegisterArgs {
+    /**
+     * The command string that will be invoked upon matching.
+     */
+    command: string;
+
+    /**
+     * The regular expression that will be matched against URLs.
+     */
+    pattern: RegExp;
+
+    /**
+     * The rank order of the registered rule. A lower rank denotes a higher
+     * priority. The default rank is `100`.
+     */
+    rank?: number;
+  }
 }
 
 
@@ -115,19 +144,26 @@ class Router implements IRouter {
    */
   readonly commands: CommandRegistry;
 
+  /**
+   * A signal emitted when the router routes a route.
+   */
+  get routed(): ISignal<this, IRouter.ICommandArgs> {
+    return this._routed;
+  }
+
   /**
    * Register to route a path pattern to a command.
    *
-   * @param pattern - The regular expression that will be matched against URLs.
-   *
-   * @param command - The command string that will be invoked upon matching.
+   * @param options - The route registration options.
    *
    * @returns A disposable that removes the registered rul from the router.
    */
-  register(pattern: RegExp, command: string): IDisposable {
+  register(options: IRouter.IRegisterArgs): IDisposable {
+    const { command, pattern } = options;
+    const rank = 'rank' in options ? options.rank : 100;
     const rules = this._rules;
 
-    rules.set(pattern, command);
+    rules.set(pattern, { command, rank });
 
     return new DisposableDelegate(() => { rules.delete(pattern); });
   }
@@ -142,20 +178,29 @@ class Router implements IRouter {
    * match the `IRouter.ICommandArgs` interface.
    */
   route(url: string): void {
-    const { base } = this;
-    const parsed = URLExt.parse(url.replace(base, ''));
-    const path = parsed.pathname;
-    const search = parsed.search;
-    const rules = this._rules;
-
-    rules.forEach((command, pattern) => {
-      if (path.match(pattern)) {
-        this.commands.execute(command, { path, search });
+    const parsed = URLExt.parse(url.replace(this.base, ''));
+    const args = { path: parsed.pathname, search: parsed.search };
+    const matches: Private.Rule[] = [];
+
+    // Collect all rules that match the URL.
+    this._rules.forEach((rule, pattern) => {
+      if (parsed.pathname.match(pattern)) {
+        matches.push(rule);
       }
     });
+
+    // Order the rules by rank and collect the promises their commands return.
+    const promises = matches.sort((a, b) => a.rank - b.rank)
+      .map(rule => this.commands.execute(rule.command, args));
+
+    // After all the promises (if any) resolve, emit the routed signal.
+    Promise.all(promises)
+      .catch(reason => { console.warn(`Routing ${url} failed:`, reason); })
+      .then(() => { this._routed.emit(args); });
   }
 
-  private _rules = new Map<RegExp, string>();
+  private _routed = new Signal<this, IRouter.ICommandArgs>(this);
+  private _rules = new Map<RegExp, Private.Rule>();
 }
 
 
@@ -180,3 +225,15 @@ namespace Router {
     commands: CommandRegistry;
   }
 }
+
+
+/**
+ * A namespace for private module data.
+ */
+namespace Private {
+  /**
+   * The internal representation of a routing rule.
+   */
+  export
+  type Rule = { command: string; rank: number };
+}

+ 11 - 12
packages/apputils-extension/src/index.ts

@@ -50,11 +50,14 @@ import '../style/index.css';
  * The command IDs used by the apputils plugin.
  */
 namespace CommandIDs {
+  export
+  const changeTheme = 'apputils:change-theme';
+
   export
   const clearStateDB = 'apputils:clear-statedb';
 
   export
-  const changeTheme = 'apputils:change-theme';
+  const loadState = 'apputils:load-statedb';
 }
 
 
@@ -201,13 +204,7 @@ const splash: JupyterLabPlugin<ISplashScreen> = {
   id: '@jupyterlab/apputils-extension:splash',
   autoStart: true,
   provides: ISplashScreen,
-  activate: () => {
-    return {
-      show: () => {
-        return Private.showSplash();
-      }
-    };
-  }
+  activate: () => ({ show: () => Private.showSplash() })
 };
 
 
@@ -218,12 +215,14 @@ const state: JupyterLabPlugin<IStateDB> = {
   id: '@jupyterlab/apputils-extension:state',
   autoStart: true,
   provides: IStateDB,
-  activate: (app: JupyterLab) => {
+  requires: [IRouter],
+  activate: (app: JupyterLab, router: IRouter) => {
+    const { commands, info, restored } = app;
     const state = new StateDB({
-      namespace: app.info.namespace,
-      when: app.restored.then(() => { /* no-op */ })
+      namespace: info.namespace,
+      when: restored.then(() => { /* no-op */ })
     });
-    const version = app.info.version;
+    const version = info.version;
     const key = 'statedb:version';
     const fetch = state.fetch(key);
     const save = () => state.save(key, { version });