Skip to content

Commit a4887b4

Browse files
committed
emulate classic ui in notebook 7
1 parent 6d13e74 commit a4887b4

File tree

5 files changed

+220
-48
lines changed

5 files changed

+220
-48
lines changed

labextension/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@
4444
"lint:prettier": "prettier --write \"../.github/**/*.yaml\" \"../*.{yaml,yml,md}\" \"../docs/**/*.md\" \"src/**/*.{tsx,ts}\" \"./*.{yml,json,md}\""
4545
},
4646
"dependencies": {
47-
"@jupyterlab/application": "^2.0 || ^3.0 || ^4.0",
48-
"@jupyterlab/launcher": "^2.0 || ^3.0 || ^4.0"
47+
"@jupyterlab/application": "^3.0 || ^4.0",
48+
"@jupyterlab/filebrowser": "^3.0 || ^4.0",
49+
"@jupyterlab/launcher": "^3.0 || ^4.0"
4950
},
5051
"devDependencies": {
5152
"@jupyterlab/builder": "^4.0.6",

labextension/src/index.ts

Lines changed: 67 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,23 @@ import {
44
ILayoutRestorer,
55
ILabShell,
66
} from "@jupyterlab/application";
7-
import { ReadonlyPartialJSONObject } from "@lumino/coreutils";
7+
import { IToolbarWidgetRegistry } from "@jupyterlab/apputils";
8+
89
import { ILauncher } from "@jupyterlab/launcher";
910
import { PageConfig } from "@jupyterlab/coreutils";
1011
import { IFrame, MainAreaWidget, WidgetTracker } from "@jupyterlab/apputils";
12+
import { IDefaultFileBrowser } from "@jupyterlab/filebrowser";
1113

12-
/** An interface for the arguments to the open command. */
13-
export interface IOpenArgs extends ReadonlyPartialJSONObject {
14-
id: string;
15-
title: string;
16-
url: string;
17-
newBrowserTab: boolean;
18-
}
14+
import {
15+
CommandIDs,
16+
IOpenArgs,
17+
IServerProcess,
18+
IServersInfo,
19+
NS,
20+
argSchema,
21+
} from "./tokens";
1922

20-
/** The JSON schema for the open command arguments.
21-
*
22-
* https://lumino.readthedocs.io/en/latest/api/interfaces/commands.CommandRegistry.ICommandOptions.html
23-
*/
24-
export const argSchema = {
25-
type: "object",
26-
properties: {
27-
id: { type: "string" },
28-
title: { type: "string" },
29-
url: { type: "string", format: "uri" },
30-
newBrowserTab: { type: "boolean" },
31-
},
32-
};
23+
import type { Widget, Menu } from "@lumino/widgets";
3324

3425
/** Create a new iframe widget. */
3526
function newServerProxyWidget(
@@ -68,8 +59,15 @@ async function activate(
6859
labShell: ILabShell | null,
6960
launcher: ILauncher | null,
7061
restorer: ILayoutRestorer | null,
62+
toolbarRegistry: IToolbarWidgetRegistry | null,
63+
fileBrowser: IDefaultFileBrowser | null,
7164
): Promise<void> {
7265
const baseUrl = PageConfig.getBaseUrl();
66+
// determine whether we are in the Notebook 7 tree
67+
const notebookPage = PageConfig.getOption("notebookPage");
68+
const isNotebook7 = !!notebookPage;
69+
const isTree = isNotebook7 && notebookPage === "tree";
70+
7371
// Fetch configured server processes from {base_url}/server-proxy/servers-info
7472
const response = await fetch(`${baseUrl}server-proxy/servers-info`);
7573

@@ -81,15 +79,26 @@ async function activate(
8179
return;
8280
}
8381

84-
const data = await response.json();
82+
function argsForServer(server_process: IServerProcess): IOpenArgs {
83+
const { new_browser_tab, launcher_entry, name } = server_process;
8584

86-
const namespace = "server-proxy";
87-
const tracker = new WidgetTracker<MainAreaWidget<IFrame>>({ namespace });
88-
const command = `${namespace}:open`;
85+
const suffix = new_browser_tab && !isNotebook7 ? " [↗]" : "";
86+
87+
return {
88+
url: `${baseUrl}${launcher_entry.path_info}`,
89+
title: `${launcher_entry.title}${suffix}`,
90+
newBrowserTab: new_browser_tab,
91+
id: `${NS}:${name}`,
92+
};
93+
}
94+
95+
const data: IServersInfo = await response.json();
96+
97+
const tracker = new WidgetTracker<MainAreaWidget<IFrame>>({ namespace: NS });
8998

9099
if (restorer) {
91100
void restorer.restore(tracker, {
92-
command: command,
101+
command: CommandIDs.open,
93102
args: (widget) => ({
94103
url: widget.content.url,
95104
title: widget.content.title.label,
@@ -102,7 +111,7 @@ async function activate(
102111

103112
const { commands, shell } = app;
104113

105-
commands.addCommand(command, {
114+
commands.addCommand(CommandIDs.open, {
106115
label: (args) => (args as IOpenArgs).title,
107116
describedBy: async () => {
108117
return { args: argSchema };
@@ -130,30 +139,42 @@ async function activate(
130139
});
131140

132141
if (launcher) {
133-
const baseUrl = PageConfig.getBaseUrl();
134142
for (let server_process of data.server_processes) {
135-
const { new_browser_tab, launcher_entry, name } = server_process;
143+
const { launcher_entry } = server_process;
136144

137145
if (!launcher_entry.enabled) {
138146
continue;
139147
}
140148

141149
launcher.add({
142-
command: command,
143-
args: {
144-
url: `${baseUrl}${launcher_entry.path_info}`,
145-
title: launcher_entry.title + (new_browser_tab ? " [↗]" : ""),
146-
newBrowserTab: new_browser_tab,
147-
id: `${namespace}:${name}`,
148-
},
150+
command: CommandIDs.open,
151+
args: argsForServer(server_process),
149152
category: "Notebook",
150153
kernelIconUrl: launcher_entry.icon_url || void 0,
151154
});
152155
}
153156
}
154157

155-
if (!labShell) {
156-
console.warn("TODO: handle notebook 7");
158+
if (isTree && !labShell && toolbarRegistry && fileBrowser) {
159+
const { toolbar } = fileBrowser;
160+
const widgets = ((toolbar.layout || {}) as any).widgets as Widget[];
161+
if (widgets && widgets.length) {
162+
for (const widget of widgets) {
163+
if (widget && (widget as any).menus) {
164+
const menu: Menu = (widget as any).menus[0];
165+
console.warn(menu);
166+
menu.addItem({ type: "separator" });
167+
for (const server_process of data.server_processes) {
168+
// create args, overriding all to launch in new heavyweight browser tabs
169+
let args = {
170+
...argsForServer(server_process),
171+
newBrowserTab: true,
172+
};
173+
menu.addItem({ command: CommandIDs.open, args });
174+
}
175+
}
176+
}
177+
}
157178
}
158179
}
159180

@@ -166,8 +187,14 @@ async function activate(
166187
const extension: JupyterFrontEndPlugin<void> = {
167188
id: "@jupyterhub/jupyter-server-proxy:add-launcher-entries",
168189
autoStart: true,
169-
optional: [ILabShell, ILauncher, ILayoutRestorer],
170-
activate: activate,
190+
optional: [
191+
ILabShell,
192+
ILauncher,
193+
ILayoutRestorer,
194+
IToolbarWidgetRegistry,
195+
IDefaultFileBrowser,
196+
],
197+
activate,
171198
};
172199

173200
export default extension;

labextension/src/tokens.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import type { ReadonlyJSONObject } from "@lumino/coreutils";
2+
3+
/**
4+
* The canonical name of the extension, also used in CLI commands such as
5+
* `jupyter labextension disable`.
6+
*/
7+
export const NAME = "@jupyterhub/jupyter-server-proxy";
8+
9+
/**
10+
* The short namespace for commands, etc.
11+
*/
12+
export const NS = "server-proxy";
13+
14+
/**
15+
* The identifying string names for server proxy commands.
16+
*/
17+
export namespace CommandIDs {
18+
/* Opens a new server proxy tab */
19+
export const open = `${NS}:open`;
20+
}
21+
22+
/**
23+
* An interface for the arguments to the open command.
24+
*/
25+
export interface IOpenArgs extends ReadonlyJSONObject {
26+
id: string;
27+
title: string;
28+
url: string;
29+
newBrowserTab: boolean;
30+
}
31+
32+
/**
33+
* An interface for the server response.
34+
*/
35+
export interface IServersInfo {
36+
server_processes: IServerProcess[];
37+
}
38+
39+
/**
40+
* Public description of a single server process.
41+
*/
42+
export interface IServerProcess {
43+
new_browser_tab: boolean;
44+
launcher_entry: ILauncherEntry;
45+
name: string;
46+
}
47+
48+
/**
49+
* Description of launcher information.
50+
*/
51+
export interface ILauncherEntry {
52+
enabled: boolean;
53+
title: string;
54+
path_info: string;
55+
icon_url?: string;
56+
}
57+
58+
/**
59+
* The JSON schema for the open command arguments.
60+
*
61+
* https://lumino.readthedocs.io/en/latest/api/interfaces/commands.CommandRegistry.ICommandOptions.html
62+
*/
63+
export const argSchema = Object.freeze({
64+
type: "object",
65+
properties: {
66+
id: { type: "string" },
67+
title: { type: "string" },
68+
url: { type: "string", format: "uri" },
69+
newBrowserTab: { type: "boolean" },
70+
},
71+
});

labextension/yarn.lock

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,10 @@ __metadata:
119119
version: 0.0.0-use.local
120120
resolution: "@jupyterhub/jupyter-server-proxy@workspace:."
121121
dependencies:
122-
"@jupyterlab/application": ^2.0 || ^3.0 || ^4.0
122+
"@jupyterlab/application": ^3.0 || ^4.0
123123
"@jupyterlab/builder": ^4.0.6
124-
"@jupyterlab/launcher": ^2.0 || ^3.0 || ^4.0
124+
"@jupyterlab/filebrowser": ^3.0 || ^4.0
125+
"@jupyterlab/launcher": ^3.0 || ^4.0
125126
npm-run-all: ^4.1.5
126127
prettier: ^3.0.3
127128
rimraf: ^5.0.1
@@ -130,7 +131,7 @@ __metadata:
130131
languageName: unknown
131132
linkType: soft
132133

133-
"@jupyterlab/application@npm:^2.0 || ^3.0 || ^4.0":
134+
"@jupyterlab/application@npm:^3.0 || ^4.0":
134135
version: 4.0.6
135136
resolution: "@jupyterlab/application@npm:4.0.6"
136137
dependencies:
@@ -265,6 +266,29 @@ __metadata:
265266
languageName: node
266267
linkType: hard
267268

269+
"@jupyterlab/docmanager@npm:^4.0.6":
270+
version: 4.0.6
271+
resolution: "@jupyterlab/docmanager@npm:4.0.6"
272+
dependencies:
273+
"@jupyterlab/apputils": ^4.1.6
274+
"@jupyterlab/coreutils": ^6.0.6
275+
"@jupyterlab/docregistry": ^4.0.6
276+
"@jupyterlab/services": ^7.0.6
277+
"@jupyterlab/statusbar": ^4.0.6
278+
"@jupyterlab/translation": ^4.0.6
279+
"@jupyterlab/ui-components": ^4.0.6
280+
"@lumino/algorithm": ^2.0.1
281+
"@lumino/coreutils": ^2.1.2
282+
"@lumino/disposable": ^2.1.2
283+
"@lumino/messaging": ^2.0.1
284+
"@lumino/properties": ^2.0.1
285+
"@lumino/signaling": ^2.1.2
286+
"@lumino/widgets": ^2.3.0
287+
react: ^18.2.0
288+
checksum: 25d3f694ae8664ca6c54bfcd36d8913caba9455fea68ed3df23963ce9723254c1f2c38fb6a93e267187f095392507d40cd2a4181c30173306c1c0b962e001b93
289+
languageName: node
290+
linkType: hard
291+
268292
"@jupyterlab/docregistry@npm:^4.0.6":
269293
version: 4.0.6
270294
resolution: "@jupyterlab/docregistry@npm:4.0.6"
@@ -290,7 +314,35 @@ __metadata:
290314
languageName: node
291315
linkType: hard
292316

293-
"@jupyterlab/launcher@npm:^2.0 || ^3.0 || ^4.0":
317+
"@jupyterlab/filebrowser@npm:^3.0 || ^4.0":
318+
version: 4.0.6
319+
resolution: "@jupyterlab/filebrowser@npm:4.0.6"
320+
dependencies:
321+
"@jupyterlab/apputils": ^4.1.6
322+
"@jupyterlab/coreutils": ^6.0.6
323+
"@jupyterlab/docmanager": ^4.0.6
324+
"@jupyterlab/docregistry": ^4.0.6
325+
"@jupyterlab/services": ^7.0.6
326+
"@jupyterlab/statedb": ^4.0.6
327+
"@jupyterlab/statusbar": ^4.0.6
328+
"@jupyterlab/translation": ^4.0.6
329+
"@jupyterlab/ui-components": ^4.0.6
330+
"@lumino/algorithm": ^2.0.1
331+
"@lumino/coreutils": ^2.1.2
332+
"@lumino/disposable": ^2.1.2
333+
"@lumino/domutils": ^2.0.1
334+
"@lumino/dragdrop": ^2.1.3
335+
"@lumino/messaging": ^2.0.1
336+
"@lumino/polling": ^2.1.2
337+
"@lumino/signaling": ^2.1.2
338+
"@lumino/virtualdom": ^2.0.1
339+
"@lumino/widgets": ^2.3.0
340+
react: ^18.2.0
341+
checksum: abe7eca4072a9c3d1f7a756840d0ad209403928b958fe09dfd81fbb693cb18c91c64027157babe1e7353a556b11c070716326b16ee2eb629089399906a3467be
342+
languageName: node
343+
linkType: hard
344+
345+
"@jupyterlab/launcher@npm:^3.0 || ^4.0":
294346
version: 4.0.6
295347
resolution: "@jupyterlab/launcher@npm:4.0.6"
296348
dependencies:

tests/acceptance/Notebook.robot

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,37 @@ Library JupyterLibrary
88
Suite Setup Start Notebook Tests
99
Test Tags app:notebook
1010

11+
*** Variables ***
12+
${XP_NEW_MENU} xpath://div[contains(@class, "jp-FileBrowser-toolbar")]//*[contains(text(), "New")]
13+
${XP_OPEN_COMMAND} xpath://li[@data-command = "server-proxy:open"]
14+
1115
*** Test Cases ***
1216
Notebook Loads
1317
Capture Page Screenshot 00-smoke.png
1418

19+
Launch Browser Tab
20+
Launch With Toolbar Menu foo
21+
Wait Until Keyword Succeeds 3x 0.5s Switch Window title:Hello World
22+
Location Should Contain foo
23+
Wait Until Page Contains Hello World timeout=10s
24+
Close Window
25+
26+
Launch Another Browser Tab
27+
Launch With Toolbar Menu bar
28+
Wait Until Keyword Succeeds 3x 0.5s Switch Window title:Hello World
29+
Location Should Contain bar
30+
Wait Until Page Contains Hello World timeout=10s
31+
Close Window
32+
1533
*** Keywords ***
1634
Start Notebook Tests
1735
Open Notebook
1836
Tag With JupyterLab Metadata
1937
Set Screenshot Directory ${OUTPUT DIR}${/}notebook
2038

21-
Click Launcher
39+
Launch With Toolbar Menu
2240
[Arguments] ${title}
23-
Click Element css:.jp-LauncherCard-label[title^\="${title}"]
41+
Mouse Over ${XP_NEW_MENU}
42+
${item} = Set Variable ${XP_OPEN_COMMAND}//div[text() = '${title}']
43+
Wait Until Element Is Visible ${item}
44+
Click Element ${item}

0 commit comments

Comments
 (0)