UNPKG

@jupyterlab/application

Version:
144 lines 5.08 kB
/* ----------------------------------------------------------------------------- | Copyright (c) Jupyter Development Team. | Distributed under the terms of the Modified BSD License. |----------------------------------------------------------------------------*/ import { URLExt } from '@jupyterlab/coreutils'; import { PromiseDelegate, Token } from '@lumino/coreutils'; import { DisposableDelegate } from '@lumino/disposable'; import { Signal } from '@lumino/signaling'; /** * A static class that routes URLs within the application. */ export class Router { /** * Create a URL router. */ constructor(options) { /** * If a matching rule's command resolves with the `stop` token during routing, * no further matches will execute. */ this.stop = new Token('@jupyterlab/application:Router#stop'); this._routed = new Signal(this); this._rules = new Map(); this.base = options.base; this.commands = options.commands; } /** * Returns the parsed current URL of the application. */ get current() { var _a, _b; const { base } = this; const parsed = URLExt.parse(window.location.href); const { search, hash } = parsed; const path = (_b = (_a = parsed.pathname) === null || _a === void 0 ? void 0 : _a.replace(base, '/')) !== null && _b !== void 0 ? _b : ''; const request = path + search + hash; return { hash, path, request, search }; } /** * A signal emitted when the router routes a route. */ get routed() { return this._routed; } /** * Navigate to a new path within the application. * * @param path - The new path or empty string if redirecting to root. * * @param options - The navigation options. */ navigate(path, options = {}) { const { base } = this; const { history } = window; const { hard } = options; const old = document.location.href; const url = path && path.indexOf(base) === 0 ? path : URLExt.join(base, path); if (url === old) { return hard ? this.reload() : undefined; } history.pushState({}, '', url); if (hard) { return this.reload(); } if (!options.skipRouting) { // Because a `route()` call may still be in the stack after having received // a `stop` token, wait for the next stack frame before calling `route()`. requestAnimationFrame(() => { void this.route(); }); } } /** * Register to route a path pattern to a command. * * @param options - The route registration options. * * @returns A disposable that removes the registered rule from the router. */ register(options) { var _a; const { command, pattern } = options; const rank = (_a = options.rank) !== null && _a !== void 0 ? _a : 100; const rules = this._rules; rules.set(pattern, { command, rank }); return new DisposableDelegate(() => { rules.delete(pattern); }); } /** * Cause a hard reload of the document. */ reload() { window.location.reload(); } /** * Route a specific path to an action. * * #### Notes * If a pattern is matched, its command will be invoked with arguments that * match the `IRouter.ILocation` interface. */ route() { const { commands, current, stop } = this; const { request } = current; const routed = this._routed; const rules = this._rules; const matches = []; // Collect all rules that match the URL. rules.forEach((rule, pattern) => { if (request === null || request === void 0 ? void 0 : request.match(pattern)) { matches.push(rule); } }); // Order the matching rules by rank and enqueue them. const queue = matches.sort((a, b) => b.rank - a.rank); const done = new PromiseDelegate(); // Process each enqueued command sequentially and short-circuit if a promise // resolves with the `stop` token. const next = async () => { if (!queue.length) { routed.emit(current); done.resolve(undefined); return; } const { command } = queue.pop(); try { const request = this.current.request; const result = await commands.execute(command, current); if (result === stop) { queue.length = 0; console.debug(`Routing ${request} was short-circuited by ${command}`); } } catch (reason) { console.warn(`Routing ${request} to ${command} failed`, reason); } void next(); }; void next(); return done.promise; } } //# sourceMappingURL=router.js.map