Skip to content
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
d8c6d16
Adding doc mode to IPaths and testing for doc mode.
ellisonbg Jun 14, 2020
e5c61a4
Adding doc mode to IPaths from PageConfig.
ellisonbg Jun 14, 2020
dfaf34b
Updating tree resolver to handle doc mode in the URL.
ellisonbg Jun 14, 2020
4b4e539
Work on apputils :resolver command.
ellisonbg Jun 27, 2020
eba3f6c
Remove mode from layout state.
ellisonbg Jun 27, 2020
3087e8d
Remove mode from layout restorer.
ellisonbg Jun 27, 2020
278fe3c
Work on :layout and :tree-resolver
ellisonbg Jun 27, 2020
8ba2828
Add skipRouting option to router navigation.
ellisonbg Jun 27, 2020
1e8b8ce
Add getUrl to PageConfig to generate master URLs given mode and treeP…
ellisonbg Jun 27, 2020
089274b
Add workspace to IInfo
ellisonbg Jun 27, 2020
7855767
Get treePath and mode from PageConfig as it can mutate at runtime.
ellisonbg Jun 27, 2020
f529204
Watch mode and current path and udpate page URL.
ellisonbg Jun 27, 2020
b6e75ae
Add mode and tree path signals and handling.
ellisonbg Jun 27, 2020
4aecc53
Deal with chagne to urls.
ellisonbg Jun 28, 2020
0fec1bf
Clean up and simplify ITreeResolver logic.
ellisonbg Jun 28, 2020
f49c198
Remove obsolete IPaths attributes.
ellisonbg Jun 28, 2020
adbee3e
Add ITreePathUpdater
ellisonbg Jul 25, 2020
99f1aa1
Let state layout handle L panel logic.
ellisonbg Jul 25, 2020
12e7cef
Wire up ITreePathUpdater.
ellisonbg Jul 25, 2020
b2df708
Fixes to resolver logic.
ellisonbg Jul 25, 2020
7f28037
Add workspace to getUrl
ellisonbg Jul 25, 2020
195b39f
Fix documentation of getUrl workspace.
ellisonbg Jul 25, 2020
f151ddd
More work on single document mode
ellisonbg Jul 25, 2020
638a6a8
Add URI encoding and fix bug in go-to-path.
ellisonbg Jul 25, 2020
8e95bea
Linting
ellisonbg Jul 26, 2020
f2ac946
Fixing application tests.
ellisonbg Jul 26, 2020
2268883
Attempting to get browser test working.
ellisonbg Jul 26, 2020
2e85013
More work on browser tests.
ellisonbg Jul 26, 2020
0e1f5eb
Fix Shareable link.
ellisonbg Aug 4, 2020
861cec6
Remove extra ILabShell argument
ellisonbg Aug 4, 2020
46e7ec8
Add actualOnClick to ToolbarButtonComponent props.
ellisonbg Aug 5, 2020
efa75e7
Remove note about popups - this is fixed.
ellisonbg Aug 5, 2020
296d58c
Fixing browser_check
ellisonbg Aug 5, 2020
4cb316d
Clean up browser_test experiments.
ellisonbg Aug 8, 2020
daa7afe
Adding PageConfig.defaultWorkspace as chr code 0.
ellisonbg Aug 8, 2020
02e3bcb
Adding mode to launcher.
ellisonbg Aug 8, 2020
6731b61
Go back to using the string 'default' for the default workspace.
ellisonbg Aug 11, 2020
50f3666
Remove mode from launcher.
ellisonbg Aug 11, 2020
1530dd8
Remove workspace from IInfo in favor of PageConfig.
ellisonbg Aug 11, 2020
a1850c9
Removing extra JupyterLab
ellisonbg Aug 11, 2020
ca2ed9c
Remove commented out line from shell.
ellisonbg Aug 11, 2020
ec81938
Addressing minor review comments.
ellisonbg Aug 11, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions jupyterlab/browser_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ def initialize_settings(self):
self.settings.setdefault('page_config_data', dict())
self.settings['page_config_data']['browserTest'] = True
self.settings['page_config_data']['buildAvailable'] = False
self.settings['page_config_data']['exposeAppInBrowser'] = True
super().initialize_settings()

def initialize_handlers(self):
Expand Down
5 changes: 4 additions & 1 deletion jupyterlab/chrome-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ async function main() {
/* eslint-disable no-console */
console.info('Starting Chrome Headless');

const browser = await puppeteer.launch({ args: ['--no-sandbox'] });
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox']
});
const page = await browser.newPage();

console.info('Navigating to page:', URL);
Expand Down
92 changes: 61 additions & 31 deletions packages/application-extension/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
ILabStatus,
ILayoutRestorer,
IRouter,
ITreePathUpdater,
ConnectionLost,
JupyterFrontEnd,
JupyterFrontEndPlugin,
Expand All @@ -24,7 +25,7 @@ import {
showErrorMessage
} from '@jupyterlab/apputils';

import { PathExt, URLExt } from '@jupyterlab/coreutils';
import { URLExt, PageConfig } from '@jupyterlab/coreutils';

import {
IPropertyInspectorProvider,
Expand All @@ -43,7 +44,7 @@ import { PromiseDelegate } from '@lumino/coreutils';

import { DisposableDelegate, DisposableSet } from '@lumino/disposable';

import { Widget, DockLayout } from '@lumino/widgets';
import { Widget, DockLayout, DockPanel } from '@lumino/widgets';

import * as React from 'react';

Expand Down Expand Up @@ -88,9 +89,10 @@ namespace CommandIDs {
/**
* The main extension.
*/
const main: JupyterFrontEndPlugin<void> = {
const main: JupyterFrontEndPlugin<ITreePathUpdater> = {
id: '@jupyterlab/application-extension:main',
requires: [IRouter, IWindowResolver],
provides: ITreePathUpdater,
optional: [ICommandPalette, IConnectionLost],
activate: (
app: JupyterFrontEnd,
Expand All @@ -103,6 +105,22 @@ const main: JupyterFrontEndPlugin<void> = {
throw new Error(`${main.id} must be activated in JupyterLab.`);
}

// These two internal state variables are used to manage the two source
// of the tree part of the URL being updated: 1) path of the active document,
// 2) path of the default browser if the active main area widget isn't a document.
let _docTreePath = '';
let _defaultBrowserTreePath = '';

function updateTreePath(treePath: string) {
_defaultBrowserTreePath = treePath;
if (!_docTreePath) {
const path = PageConfig.getUrl({ treePath });
router.navigate(path, { skipRouting: true });
// Persist the new tree path to PageConfig as it is used elsewhere at runtime.
PageConfig.setOption('treePath', treePath);
}
}

// Requiring the window resolver guarantees that the application extension
// only loads if there is a viable window name. Otherwise, the application
// will short-circuit and ask the user to navigate away.
Expand All @@ -127,6 +145,27 @@ const main: JupyterFrontEndPlugin<void> = {
app.commands.notifyCommandChanged();
});

// Watch the mode and update the page URL to /lab or /doc to reflect the
// change.
app.shell.modeChanged.connect((_, args: DockPanel.Mode) => {
const path = PageConfig.getUrl({ mode: args as string });
router.navigate(path, { skipRouting: true });
// Persist this mode change to PageConfig as it is used elsewhere at runtime.
PageConfig.setOption('mode', args as string);
});

// Watch the path of the current widget in the main area and update the page
// URL to reflect the change.
app.shell.currentPathChanged.connect((_, args) => {
const maybeTreePath = args.newValue as string;
const treePath = maybeTreePath || _defaultBrowserTreePath;
const path = PageConfig.getUrl({ treePath: treePath });
router.navigate(path, { skipRouting: true });
// Persist the new tree path to PageConfig as it is used elsewhere at runtime.
PageConfig.setOption('treePath', treePath);
_docTreePath = maybeTreePath;
});

// If the connection to the server is lost, handle it with the
// connection lost handler.
connectionLost = connectionLost || ConnectionLost;
Expand Down Expand Up @@ -219,6 +258,7 @@ const main: JupyterFrontEndPlugin<void> = {
return ((event as any).returnValue = message);
}
});
return updateTreePath;
},
autoStart: true
};
Expand All @@ -229,13 +269,21 @@ const main: JupyterFrontEndPlugin<void> = {
const layout: JupyterFrontEndPlugin<ILayoutRestorer> = {
id: '@jupyterlab/application-extension:layout',
requires: [IStateDB, ILabShell],
activate: (app: JupyterFrontEnd, state: IStateDB, labShell: ILabShell) => {
activate: (
app: JupyterFrontEnd,
state: IStateDB,
labShell: ILabShell,
info: JupyterLab.IInfo
) => {
const first = app.started;
const registry = app.commands;
const restorer = new LayoutRestorer({ connector: state, first, registry });

void restorer.fetch().then(saved => {
labShell.restoreLayout(saved);
labShell.restoreLayout(
PageConfig.getOption('mode') as DockPanel.Mode,
saved
);
labShell.layoutModified.connect(() => {
void restorer.save(labShell.saveLayout());
});
Expand Down Expand Up @@ -289,55 +337,36 @@ const tree: JupyterFrontEndPlugin<JupyterFrontEnd.ITreeResolver> = {
resolver: IWindowResolver
): JupyterFrontEnd.ITreeResolver => {
const { commands } = app;
const treePattern = new RegExp(`^${paths.urls.tree}([^?]+)`);
const workspacePattern = new RegExp(
`^${paths.urls.workspaces}/[^?/]+/tree/([^?]+)`
);
const set = new DisposableSet();
const delegate = new PromiseDelegate<JupyterFrontEnd.ITreeResolver.Paths>();

const treePattern = new RegExp(
'/(lab|doc)(/workspaces/[a-zA-Z0-9-_]+)?(/tree/.*)?'
);

set.add(
commands.addCommand(CommandIDs.tree, {
execute: async (args: IRouter.ILocation) => {
if (set.isDisposed) {
return;
}

const treeMatch = args.path.match(treePattern);
const workspaceMatch = args.path.match(workspacePattern);
const match = treeMatch || workspaceMatch;
const file = match ? decodeURI(match[1]) : '';
const workspace = PathExt.basename(resolver.name);
const query = URLExt.queryStringToObject(args.search ?? '');
const browser = query['file-browser-path'] || '';

// Remove the file browser path from the query string.
delete query['file-browser-path'];

// Remove the tree portion of the URL.
const url =
(workspaceMatch
? URLExt.join(paths.urls.workspaces, workspace)
: paths.urls.app) +
URLExt.objectToQueryString(query) +
args.hash;

// Route to the cleaned URL.
router.navigate(url);

// Clean up artifacts immediately upon routing.
set.dispose();

delegate.resolve({ browser, file });
delegate.resolve({ browser, file: PageConfig.getOption('treePath') });
}
})
);
set.add(
router.register({ command: CommandIDs.tree, pattern: treePattern })
);
set.add(
router.register({ command: CommandIDs.tree, pattern: workspacePattern })
);

// If a route is handled by the router without the tree command being
// invoked, resolve to `null` and clean up artifacts.
Expand Down Expand Up @@ -433,7 +462,8 @@ const sidebar: JupyterFrontEndPlugin<void> = {
activate: (
app: JupyterFrontEnd,
settingRegistry: ISettingRegistry,
labShell: ILabShell
labShell: ILabShell,
info: JupyterLab.IInfo
) => {
type overrideMap = { [id: string]: 'left' | 'right' };
let overrides: overrideMap = {};
Expand Down Expand Up @@ -567,7 +597,7 @@ function addCommands(app: JupyterLab, palette: ICommandPalette | null): void {
// Find the tab area for a widget within the main dock area.
const tabAreaFor = (widget: Widget): DockLayout.ITabAreaConfig | null => {
const { mainArea } = shell.saveLayout();
if (!mainArea || mainArea.mode !== 'multiple-document') {
if (!mainArea || PageConfig.getOption('mode') !== 'multiple-document') {
return null;
}
const area = mainArea.dock?.main;
Expand Down
19 changes: 17 additions & 2 deletions packages/application/src/frontend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,22 @@ export namespace JupyterFrontEnd {
widgets(area?: string): IIterator<Widget>;
}

/**
* Is JupyterLab in document mode?
*
* @param path - Full URL of JupyterLab
* @param paths - The current IPaths object hydrated from PageConfig.
*/
export function inDocMode(path: string, paths: IPaths) {
const docPattern = new RegExp(`^${paths.urls.doc}`);
const match = path.match(docPattern);
if (match) {
return true;
} else {
return false;
}
}

/**
* The application paths dictionary token.
*/
Expand All @@ -319,11 +335,10 @@ export namespace JupyterFrontEnd {
readonly base: string;
readonly notFound?: string;
readonly app: string;
readonly doc: string;
readonly static: string;
readonly settings: string;
readonly themes: string;
readonly tree: string;
readonly workspaces: string;
readonly hubPrefix?: string;
readonly hubHost?: string;
readonly hubUser?: string;
Expand Down
2 changes: 2 additions & 0 deletions packages/application/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ export { ILabShell, LabShell } from './shell';

export { ILabStatus } from './status';

export { ITreePathUpdater } from './treepathupdater';

export * from './tokens';
8 changes: 1 addition & 7 deletions packages/application/src/lab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,6 @@ export class JupyterLab extends JupyterFrontEnd<ILabShell> {
this.shell.collapseRight();
return;
}
if (this.shell.mode === 'single-document') {
this.shell.collapseLeft();
} else {
this.shell.expandLeft();
}
}, this);
Private.setFormat(this);
});
Expand Down Expand Up @@ -269,11 +264,10 @@ export namespace JupyterLab {
base: PageConfig.getOption('baseUrl'),
notFound: PageConfig.getOption('notFoundUrl'),
app: PageConfig.getOption('appUrl'),
doc: PageConfig.getOption('docUrl'),
static: PageConfig.getOption('staticUrl'),
settings: PageConfig.getOption('settingsUrl'),
themes: PageConfig.getOption('themesUrl'),
tree: PageConfig.getOption('treeUrl'),
workspaces: PageConfig.getOption('workspacesUrl'),
hubHost: PageConfig.getOption('hubHost') || undefined,
hubPrefix: PageConfig.getOption('hubPrefix') || undefined,
hubUser: PageConfig.getOption('hubUser') || undefined,
Expand Down
6 changes: 1 addition & 5 deletions packages/application/src/layoutrestorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,6 @@ namespace Private {
dock: (area && area.dock && serializeArea(area.dock.main)) || null
};
if (area) {
dehydrated.mode = area.mode;
if (area.currentWidget) {
const current = Private.nameProperty.get(area.currentWidget);
if (current) {
Expand Down Expand Up @@ -675,13 +674,10 @@ namespace Private {

const name = (area as any).current || null;
const dock = (area as any).dock || null;
const mode = (area as any).mode || null;

return {
currentWidget: (name && names.has(name) && names.get(name)) || null,
dock: dock ? { main: deserializeArea(dock, names) } : null,
mode:
mode === 'multiple-document' || mode === 'single-document' ? mode : null
dock: dock ? { main: deserializeArea(dock, names) } : null
};
}
}
12 changes: 7 additions & 5 deletions packages/application/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,13 @@ export class Router implements IRouter {
return this.reload();
}

// 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();
});
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();
});
}
}

/**
Expand Down
Loading