|
@@ -51,6 +51,12 @@ import '../style/index.css';
|
|
*/
|
|
*/
|
|
const WORKSPACE_SAVE_DEBOUNCE_INTERVAL = 750;
|
|
const WORKSPACE_SAVE_DEBOUNCE_INTERVAL = 750;
|
|
|
|
|
|
|
|
+/**
|
|
|
|
+ * The query string parameter indicating that a workspace name should be
|
|
|
|
+ * automatically generated if the current request collides with an open session.
|
|
|
|
+ */
|
|
|
|
+const WORKSPACE_RESOLVE = 'resolve-workspace';
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* The interval in milliseconds before recover options appear during splash.
|
|
* The interval in milliseconds before recover options appear during splash.
|
|
*/
|
|
*/
|
|
@@ -239,23 +245,52 @@ const resolver: JupyterFrontEndPlugin<IWindowResolver> = {
|
|
paths: JupyterFrontEnd.IPaths,
|
|
paths: JupyterFrontEnd.IPaths,
|
|
router: IRouter
|
|
router: IRouter
|
|
) => {
|
|
) => {
|
|
- const workspacePattern = new RegExp(`^${paths.urls.workspaces}([^?\/]+)`);
|
|
|
|
|
|
+ const { hash, path, search } = router.current;
|
|
|
|
+ const query = URLExt.queryStringToObject(search || '');
|
|
const solver = new WindowResolver();
|
|
const solver = new WindowResolver();
|
|
- const match = router.current.path.match(workspacePattern);
|
|
|
|
|
|
+ const match = path.match(new RegExp(`^${paths.urls.workspaces}([^?\/]+)`));
|
|
const workspace = (match && decodeURIComponent(match[1])) || '';
|
|
const workspace = (match && decodeURIComponent(match[1])) || '';
|
|
const candidate = Private.candidate(paths, workspace);
|
|
const candidate = Private.candidate(paths, workspace);
|
|
|
|
|
|
try {
|
|
try {
|
|
await solver.resolve(candidate);
|
|
await solver.resolve(candidate);
|
|
} catch (error) {
|
|
} catch (error) {
|
|
- console.warn('Window resolution failed:', error);
|
|
|
|
-
|
|
|
|
- // Return a promise that never resolves.
|
|
|
|
|
|
+ // Window resolution has failed so the URL must change. Return a promise
|
|
|
|
+ // that never resolves to prevent the application from loading plugins
|
|
|
|
+ // that rely on `IWindowResolver`.
|
|
return new Promise<IWindowResolver>(() => {
|
|
return new Promise<IWindowResolver>(() => {
|
|
- Private.redirect(router, paths, workspacePattern);
|
|
|
|
|
|
+ // If the user has requested workspace resolution create a new one.
|
|
|
|
+ if (WORKSPACE_RESOLVE in query) {
|
|
|
|
+ const { base, workspaces } = paths.urls;
|
|
|
|
+ const pool =
|
|
|
|
+ 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
|
|
+ const random = pool[Math.floor(Math.random() * pool.length)];
|
|
|
|
+ const path = URLExt.join(base, workspaces, `auto-${random}`);
|
|
|
|
+
|
|
|
|
+ // Clone the originally requested workspace after redirecting.
|
|
|
|
+ query['clone'] = workspace;
|
|
|
|
+
|
|
|
|
+ // Change the URL and trigger a hard reload to re-route.
|
|
|
|
+ const url = path + URLExt.objectToQueryString(query) + (hash || '');
|
|
|
|
+ router.navigate(url, { hard: true, silent: true });
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Launch a dialog to ask the user for a new workspace name.
|
|
|
|
+ console.warn('Window resolution failed:', error);
|
|
|
|
+ Private.redirect(router, paths, workspace);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ // If the user has requested workspace resolution remove the query param.
|
|
|
|
+ if (WORKSPACE_RESOLVE in query) {
|
|
|
|
+ delete query[WORKSPACE_RESOLVE];
|
|
|
|
+
|
|
|
|
+ // Silently scrub the URL.
|
|
|
|
+ const url = path + URLExt.objectToQueryString(query) + (hash || '');
|
|
|
|
+ router.navigate(url, { silent: true });
|
|
|
|
+ }
|
|
|
|
+
|
|
return solver;
|
|
return solver;
|
|
}
|
|
}
|
|
};
|
|
};
|
|
@@ -451,12 +486,6 @@ const state: JupyterFrontEndPlugin<IStateDB> = {
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
|
|
- router.register({
|
|
|
|
- command: CommandIDs.loadState,
|
|
|
|
- pattern: /.?/,
|
|
|
|
- rank: 20 // Very high priority: 20:100.
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
commands.addCommand(CommandIDs.reset, {
|
|
commands.addCommand(CommandIDs.reset, {
|
|
label: 'Reset Application State',
|
|
label: 'Reset Application State',
|
|
execute: async () => {
|
|
execute: async () => {
|
|
@@ -523,10 +552,16 @@ const state: JupyterFrontEndPlugin<IStateDB> = {
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
|
|
|
|
+ router.register({
|
|
|
|
+ command: CommandIDs.loadState,
|
|
|
|
+ pattern: /.?/,
|
|
|
|
+ rank: 30 // High priority: 30:100.
|
|
|
|
+ });
|
|
|
|
+
|
|
router.register({
|
|
router.register({
|
|
command: CommandIDs.resetOnLoad,
|
|
command: CommandIDs.resetOnLoad,
|
|
pattern: /(\?reset|\&reset)($|&)/,
|
|
pattern: /(\?reset|\&reset)($|&)/,
|
|
- rank: 10 // Very high priority: 10:100.
|
|
|
|
|
|
+ rank: 20 // High priority: 20:100.
|
|
});
|
|
});
|
|
|
|
|
|
// Clean up state database when the window unloads.
|
|
// Clean up state database when the window unloads.
|
|
@@ -661,7 +696,7 @@ namespace Private {
|
|
export async function redirect(
|
|
export async function redirect(
|
|
router: IRouter,
|
|
router: IRouter,
|
|
paths: JupyterFrontEnd.IPaths,
|
|
paths: JupyterFrontEnd.IPaths,
|
|
- workspacePattern: RegExp,
|
|
|
|
|
|
+ workspace: string,
|
|
warn = false
|
|
warn = false
|
|
): Promise<void> {
|
|
): Promise<void> {
|
|
const form = createRedirectForm(warn);
|
|
const form = createRedirectForm(warn);
|
|
@@ -675,14 +710,12 @@ namespace Private {
|
|
|
|
|
|
dialog.dispose();
|
|
dialog.dispose();
|
|
if (!result.value) {
|
|
if (!result.value) {
|
|
- return redirect(router, paths, workspacePattern, true);
|
|
|
|
|
|
+ return redirect(router, paths, workspace, true);
|
|
}
|
|
}
|
|
|
|
|
|
// Navigate to a new workspace URL and abandon this session altogether.
|
|
// Navigate to a new workspace URL and abandon this session altogether.
|
|
const page = paths.urls.page;
|
|
const page = paths.urls.page;
|
|
const workspaces = paths.urls.workspaces;
|
|
const workspaces = paths.urls.workspaces;
|
|
- const match = router.current.path.match(workspacePattern);
|
|
|
|
- const workspace = (match && decodeURIComponent(match[1])) || '';
|
|
|
|
const prefix = (workspace ? workspaces : page).length + workspace.length;
|
|
const prefix = (workspace ? workspaces : page).length + workspace.length;
|
|
const rest = router.current.request.substring(prefix);
|
|
const rest = router.current.request.substring(prefix);
|
|
const url = URLExt.join(workspaces, result.value, rest);
|
|
const url = URLExt.join(workspaces, result.value, rest);
|