xtralab 0.14.0 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -9,6 +9,7 @@ import editorIndentPlugin from './editorIndent';
9
9
  import gitPlugins from './git';
10
10
  import highlightPlugin from './highlight';
11
11
  import launcherPlugins from './launcher';
12
+ import menuBarPlugin from './menuBar';
12
13
  import menusPlugin from './menus';
13
14
  import omniboxPlugin from './omnibox';
14
15
  import searchReplacePlugin from './searchReplace';
@@ -39,6 +40,7 @@ const plugins = [
39
40
  ...gitPlugins,
40
41
  highlightPlugin,
41
42
  ...launcherPlugins,
43
+ menuBarPlugin,
42
44
  menusPlugin,
43
45
  omniboxPlugin,
44
46
  searchReplacePlugin,
@@ -0,0 +1,6 @@
1
+ import { LabIcon } from '@jupyterlab/ui-components';
2
+ /**
3
+ * Hamburger mark for the collapsed main-menu button, matching the sidebar
4
+ * toggles in ../topBar/icons.ts.
5
+ */
6
+ export declare const mainMenuIcon: LabIcon;
@@ -0,0 +1,13 @@
1
+ import { LabIcon } from '@jupyterlab/ui-components';
2
+ /**
3
+ * Hamburger mark for the collapsed main-menu button, matching the sidebar
4
+ * toggles in ../topBar/icons.ts.
5
+ */
6
+ export const mainMenuIcon = new LabIcon({
7
+ name: 'xtralab:main-menu',
8
+ svgstr: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
9
+ <line x1="4.5" y1="7" x2="19.5" y2="7" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/>
10
+ <line x1="4.5" y1="12" x2="19.5" y2="12" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/>
11
+ <line x1="4.5" y1="17" x2="19.5" y2="17" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/>
12
+ </svg>`
13
+ });
@@ -0,0 +1,15 @@
1
+ import { JupyterFrontEndPlugin } from '@jupyterlab/application';
2
+ /**
3
+ * Collapse the main menu bar into a compact menu button.
4
+ *
5
+ * The menu bar is hidden and a hamburger button in the top bar opens the same
6
+ * menus as a vertical popup. The popup reuses the live `RankedMenu` instances
7
+ * owned by the `MainMenu`, so menus added or removed at runtime are reflected
8
+ * without duplication.
9
+ *
10
+ * Hiding covers the `jp-menu-panel` container too, which has its own
11
+ * `min-height` and would otherwise leave an empty strip. The "Show Menu Bar"
12
+ * toggle restores the classic bar.
13
+ */
14
+ declare const plugin: JupyterFrontEndPlugin<void>;
15
+ export default plugin;
@@ -0,0 +1,164 @@
1
+ import { ILabShell } from '@jupyterlab/application';
2
+ import { ICommandPalette } from '@jupyterlab/apputils';
3
+ import { IMainMenu, MainMenu } from '@jupyterlab/mainmenu';
4
+ import { ISettingRegistry } from '@jupyterlab/settingregistry';
5
+ import { ITranslator, nullTranslator } from '@jupyterlab/translation';
6
+ import { CommandToolbarButton, MenuSvg } from '@jupyterlab/ui-components';
7
+ import { Menu } from '@lumino/widgets';
8
+ import { mainMenuIcon } from './icons';
9
+ const PLUGIN_ID = 'xtralab:menu-bar';
10
+ const OPEN_COMMAND = 'xtralab:open-main-menu';
11
+ const TOGGLE_COMMAND = 'xtralab:toggle-menu-bar';
12
+ /**
13
+ * How long after the popup closes a button press still counts as "close
14
+ * only". An open Lumino menu closes itself on any outside press from a
15
+ * document `pointerdown` listener in the capture phase, which runs before
16
+ * the button's own mousedown handler — so without this window, pressing the
17
+ * button while the popup is open would close it and instantly reopen it.
18
+ */
19
+ const REOPEN_GUARD_MS = 250;
20
+ /**
21
+ * Collapse the main menu bar into a compact menu button.
22
+ *
23
+ * The menu bar is hidden and a hamburger button in the top bar opens the same
24
+ * menus as a vertical popup. The popup reuses the live `RankedMenu` instances
25
+ * owned by the `MainMenu`, so menus added or removed at runtime are reflected
26
+ * without duplication.
27
+ *
28
+ * Hiding covers the `jp-menu-panel` container too, which has its own
29
+ * `min-height` and would otherwise leave an empty strip. The "Show Menu Bar"
30
+ * toggle restores the classic bar.
31
+ */
32
+ const plugin = {
33
+ id: PLUGIN_ID,
34
+ description: 'Collapse the main menu bar into a compact menu button.',
35
+ autoStart: true,
36
+ requires: [IMainMenu, ILabShell, ISettingRegistry],
37
+ optional: [ICommandPalette, ITranslator],
38
+ activate: (app, mainMenu, labShell, settingRegistry, palette, translator) => {
39
+ var _a;
40
+ const { commands } = app;
41
+ const trans = (translator !== null && translator !== void 0 ? translator : nullTranslator).load('jupyterlab');
42
+ if (!(mainMenu instanceof MainMenu)) {
43
+ return;
44
+ }
45
+ const menuPanel = () => {
46
+ var _a, _b;
47
+ return (_b = (_a = mainMenu.parent) !== null && _a !== void 0 ? _a : Array.from(labShell.widgets('top')).find(w => w.id === 'jp-menu-panel')) !== null && _b !== void 0 ? _b : null;
48
+ };
49
+ let visible = false;
50
+ let button = null;
51
+ let settings = null;
52
+ const applyVisibility = (value) => {
53
+ var _a;
54
+ visible = value;
55
+ mainMenu.setHidden(!value);
56
+ (_a = menuPanel()) === null || _a === void 0 ? void 0 : _a.setHidden(!value);
57
+ button === null || button === void 0 ? void 0 : button.setHidden(value);
58
+ commands.notifyCommandChanged(TOGGLE_COMMAND);
59
+ };
60
+ mainMenu.hide();
61
+ (_a = menuPanel()) === null || _a === void 0 ? void 0 : _a.hide();
62
+ let popup = null;
63
+ let popupClosedAt = 0;
64
+ const openMenu = () => {
65
+ var _a, _b;
66
+ if (visible) {
67
+ // The classic bar is on screen; behave like keyboard menu
68
+ // activation instead of opening a redundant popup that would
69
+ // compete with the bar for the same Menu instances.
70
+ mainMenu.activeIndex = 0;
71
+ mainMenu.openActiveMenu();
72
+ return;
73
+ }
74
+ if (Date.now() - popupClosedAt < REOPEN_GUARD_MS) {
75
+ return;
76
+ }
77
+ // One popup instance, rebuilt from the bar's current menus on every
78
+ // open so runtime menu changes stay reflected. It is a plain Lumino
79
+ // Menu rather than a MenuSvg: MenuSvg.insertItem re-wraps each
80
+ // submenu's renderer and insertItem on every call, which would pile
81
+ // wrappers onto the shared menus across reopens. The svg renderer and
82
+ // themed-container class are applied directly instead, matching how
83
+ // the real menus are constructed in MainMenu.
84
+ if (popup === null) {
85
+ popup = new Menu({ commands, renderer: MenuSvg.defaultRenderer });
86
+ popup.addClass('jp-ThemedContainer');
87
+ popup.addClass('jp-xtralab-MainMenuPopup');
88
+ popup.aboutToClose.connect(() => {
89
+ popupClosedAt = Date.now();
90
+ });
91
+ }
92
+ else {
93
+ popup.clearItems();
94
+ }
95
+ // Skip empty menus: MenuBar disables their headings, but submenu items
96
+ // have no such affordance and would open a blank panel.
97
+ for (const menu of mainMenu.menus) {
98
+ if (menu.items.length > 0) {
99
+ popup.addItem({ type: 'submenu', submenu: menu });
100
+ }
101
+ }
102
+ const anchor = button === null || button === void 0 ? void 0 : button.node.getBoundingClientRect();
103
+ // The 3px gap matches the detached-dropdown offset chrome.css gives
104
+ // the real menu bar's dropdowns (.lm-Menu.lm-MenuBar-menu).
105
+ popup.open((_a = anchor === null || anchor === void 0 ? void 0 : anchor.left) !== null && _a !== void 0 ? _a : 0, ((_b = anchor === null || anchor === void 0 ? void 0 : anchor.bottom) !== null && _b !== void 0 ? _b : 0) + 3);
106
+ };
107
+ commands.addCommand(OPEN_COMMAND, {
108
+ label: trans.__('Open Menu'),
109
+ caption: trans.__('Open the main menu'),
110
+ icon: mainMenuIcon,
111
+ execute: openMenu
112
+ });
113
+ commands.addCommand(TOGGLE_COMMAND, {
114
+ label: trans.__('Show Menu Bar'),
115
+ caption: trans.__('Toggle the visibility of the main menu bar'),
116
+ isToggled: () => visible,
117
+ execute: async () => {
118
+ applyVisibility(!visible);
119
+ if (settings !== null) {
120
+ try {
121
+ await settings.set('visible', visible);
122
+ }
123
+ catch (reason) {
124
+ console.error('xtralab: failed to persist menu bar visibility', reason);
125
+ }
126
+ }
127
+ }
128
+ });
129
+ if (palette) {
130
+ const category = trans.__('Other');
131
+ palette.addItem({ command: OPEN_COMMAND, category });
132
+ palette.addItem({ command: TOGGLE_COMMAND, category });
133
+ }
134
+ Promise.all([settingRegistry.load(PLUGIN_ID), app.restored])
135
+ .then(([loaded]) => {
136
+ settings = loaded;
137
+ button = new CommandToolbarButton({
138
+ commands,
139
+ id: OPEN_COMMAND,
140
+ icon: mainMenuIcon,
141
+ label: '',
142
+ caption: trans.__('Menu'),
143
+ noFocusOnClick: true,
144
+ 'aria-haspopup': 'menu'
145
+ });
146
+ button.id = 'xtralab-main-menu-button';
147
+ button.addClass('jp-xtralab-TopBarButton');
148
+ labShell.add(button, 'top', { rank: 0 });
149
+ const update = () => {
150
+ applyVisibility(loaded.get('visible').composite);
151
+ };
152
+ update();
153
+ loaded.changed.connect(update);
154
+ })
155
+ .catch(reason => {
156
+ // Without settings the collapsed state cannot be managed (and the
157
+ // button was never added) — reveal the stock bar rather than leave
158
+ // the app with no reachable menus.
159
+ applyVisibility(true);
160
+ console.error(`Failed to load settings for ${PLUGIN_ID}`, reason);
161
+ });
162
+ }
163
+ };
164
+ export default plugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xtralab",
3
- "version": "0.14.0",
3
+ "version": "0.14.1",
4
4
  "description": "An opinionated JupyterLab meta-package that bundles a curated set of extensions, ships a path-first file browser, renders jupyterlab-git's text, notebook and image diffs with its own viewers, and applies a quieter default workspace configuration.",
5
5
  "keywords": [
6
6
  "jupyter",
@@ -0,0 +1,37 @@
1
+ {
2
+ "title": "xtralab menu bar",
3
+ "description": "Visibility of the main menu bar.",
4
+ "jupyter.lab.shortcuts": [],
5
+ "jupyter.lab.menus": {
6
+ "main": [
7
+ {
8
+ "id": "jp-mainmenu-view",
9
+ "items": [
10
+ {
11
+ "type": "submenu",
12
+ "rank": 1,
13
+ "submenu": {
14
+ "id": "jp-mainmenu-view-appearance",
15
+ "items": [
16
+ {
17
+ "command": "xtralab:toggle-menu-bar",
18
+ "rank": 14
19
+ }
20
+ ]
21
+ }
22
+ }
23
+ ]
24
+ }
25
+ ]
26
+ },
27
+ "type": "object",
28
+ "properties": {
29
+ "visible": {
30
+ "type": "boolean",
31
+ "title": "Show the menu bar",
32
+ "description": "When false, hides the main menu bar. Its menus stay available from the menu button at the leading edge of the top bar and the bar can be restored from View → Appearance → Show Menu Bar.",
33
+ "default": false
34
+ }
35
+ },
36
+ "additionalProperties": false
37
+ }
package/src/index.ts CHANGED
@@ -11,6 +11,7 @@ import editorIndentPlugin from './editorIndent';
11
11
  import gitPlugins from './git';
12
12
  import highlightPlugin from './highlight';
13
13
  import launcherPlugins from './launcher';
14
+ import menuBarPlugin from './menuBar';
14
15
  import menusPlugin from './menus';
15
16
  import omniboxPlugin from './omnibox';
16
17
  import searchReplacePlugin from './searchReplace';
@@ -42,6 +43,7 @@ const plugins: JupyterFrontEndPlugin<unknown>[] = [
42
43
  ...gitPlugins,
43
44
  highlightPlugin,
44
45
  ...launcherPlugins,
46
+ menuBarPlugin,
45
47
  menusPlugin,
46
48
  omniboxPlugin,
47
49
  searchReplacePlugin,
@@ -0,0 +1,14 @@
1
+ import { LabIcon } from '@jupyterlab/ui-components';
2
+
3
+ /**
4
+ * Hamburger mark for the collapsed main-menu button, matching the sidebar
5
+ * toggles in ../topBar/icons.ts.
6
+ */
7
+ export const mainMenuIcon = new LabIcon({
8
+ name: 'xtralab:main-menu',
9
+ svgstr: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
10
+ <line x1="4.5" y1="7" x2="19.5" y2="7" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/>
11
+ <line x1="4.5" y1="12" x2="19.5" y2="12" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/>
12
+ <line x1="4.5" y1="17" x2="19.5" y2="17" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/>
13
+ </svg>`
14
+ });
@@ -0,0 +1,195 @@
1
+ import {
2
+ ILabShell,
3
+ JupyterFrontEnd,
4
+ JupyterFrontEndPlugin
5
+ } from '@jupyterlab/application';
6
+ import { ICommandPalette } from '@jupyterlab/apputils';
7
+ import { IMainMenu, MainMenu } from '@jupyterlab/mainmenu';
8
+ import { ISettingRegistry } from '@jupyterlab/settingregistry';
9
+ import { ITranslator, nullTranslator } from '@jupyterlab/translation';
10
+ import { CommandToolbarButton, MenuSvg } from '@jupyterlab/ui-components';
11
+ import { Menu, Widget } from '@lumino/widgets';
12
+
13
+ import { mainMenuIcon } from './icons';
14
+
15
+ const PLUGIN_ID = 'xtralab:menu-bar';
16
+
17
+ const OPEN_COMMAND = 'xtralab:open-main-menu';
18
+ const TOGGLE_COMMAND = 'xtralab:toggle-menu-bar';
19
+
20
+ /**
21
+ * How long after the popup closes a button press still counts as "close
22
+ * only". An open Lumino menu closes itself on any outside press from a
23
+ * document `pointerdown` listener in the capture phase, which runs before
24
+ * the button's own mousedown handler — so without this window, pressing the
25
+ * button while the popup is open would close it and instantly reopen it.
26
+ */
27
+ const REOPEN_GUARD_MS = 250;
28
+
29
+ /**
30
+ * Collapse the main menu bar into a compact menu button.
31
+ *
32
+ * The menu bar is hidden and a hamburger button in the top bar opens the same
33
+ * menus as a vertical popup. The popup reuses the live `RankedMenu` instances
34
+ * owned by the `MainMenu`, so menus added or removed at runtime are reflected
35
+ * without duplication.
36
+ *
37
+ * Hiding covers the `jp-menu-panel` container too, which has its own
38
+ * `min-height` and would otherwise leave an empty strip. The "Show Menu Bar"
39
+ * toggle restores the classic bar.
40
+ */
41
+ const plugin: JupyterFrontEndPlugin<void> = {
42
+ id: PLUGIN_ID,
43
+ description: 'Collapse the main menu bar into a compact menu button.',
44
+ autoStart: true,
45
+ requires: [IMainMenu, ILabShell, ISettingRegistry],
46
+ optional: [ICommandPalette, ITranslator],
47
+ activate: (
48
+ app: JupyterFrontEnd,
49
+ mainMenu: IMainMenu,
50
+ labShell: ILabShell,
51
+ settingRegistry: ISettingRegistry,
52
+ palette: ICommandPalette | null,
53
+ translator: ITranslator | null
54
+ ): void => {
55
+ const { commands } = app;
56
+ const trans = (translator ?? nullTranslator).load('jupyterlab');
57
+
58
+ if (!(mainMenu instanceof MainMenu)) {
59
+ return;
60
+ }
61
+
62
+ const menuPanel = (): Widget | null =>
63
+ mainMenu.parent ??
64
+ Array.from(labShell.widgets('top')).find(w => w.id === 'jp-menu-panel') ??
65
+ null;
66
+
67
+ let visible = false;
68
+ let button: CommandToolbarButton | null = null;
69
+ let settings: ISettingRegistry.ISettings | null = null;
70
+
71
+ const applyVisibility = (value: boolean): void => {
72
+ visible = value;
73
+ mainMenu.setHidden(!value);
74
+ menuPanel()?.setHidden(!value);
75
+ button?.setHidden(value);
76
+ commands.notifyCommandChanged(TOGGLE_COMMAND);
77
+ };
78
+
79
+ mainMenu.hide();
80
+ menuPanel()?.hide();
81
+
82
+ let popup: Menu | null = null;
83
+ let popupClosedAt = 0;
84
+
85
+ const openMenu = (): void => {
86
+ if (visible) {
87
+ // The classic bar is on screen; behave like keyboard menu
88
+ // activation instead of opening a redundant popup that would
89
+ // compete with the bar for the same Menu instances.
90
+ mainMenu.activeIndex = 0;
91
+ mainMenu.openActiveMenu();
92
+ return;
93
+ }
94
+ if (Date.now() - popupClosedAt < REOPEN_GUARD_MS) {
95
+ return;
96
+ }
97
+
98
+ // One popup instance, rebuilt from the bar's current menus on every
99
+ // open so runtime menu changes stay reflected. It is a plain Lumino
100
+ // Menu rather than a MenuSvg: MenuSvg.insertItem re-wraps each
101
+ // submenu's renderer and insertItem on every call, which would pile
102
+ // wrappers onto the shared menus across reopens. The svg renderer and
103
+ // themed-container class are applied directly instead, matching how
104
+ // the real menus are constructed in MainMenu.
105
+ if (popup === null) {
106
+ popup = new Menu({ commands, renderer: MenuSvg.defaultRenderer });
107
+ popup.addClass('jp-ThemedContainer');
108
+ popup.addClass('jp-xtralab-MainMenuPopup');
109
+ popup.aboutToClose.connect(() => {
110
+ popupClosedAt = Date.now();
111
+ });
112
+ } else {
113
+ popup.clearItems();
114
+ }
115
+
116
+ // Skip empty menus: MenuBar disables their headings, but submenu items
117
+ // have no such affordance and would open a blank panel.
118
+ for (const menu of mainMenu.menus) {
119
+ if (menu.items.length > 0) {
120
+ popup.addItem({ type: 'submenu', submenu: menu });
121
+ }
122
+ }
123
+
124
+ const anchor = button?.node.getBoundingClientRect();
125
+ // The 3px gap matches the detached-dropdown offset chrome.css gives
126
+ // the real menu bar's dropdowns (.lm-Menu.lm-MenuBar-menu).
127
+ popup.open(anchor?.left ?? 0, (anchor?.bottom ?? 0) + 3);
128
+ };
129
+
130
+ commands.addCommand(OPEN_COMMAND, {
131
+ label: trans.__('Open Menu'),
132
+ caption: trans.__('Open the main menu'),
133
+ icon: mainMenuIcon,
134
+ execute: openMenu
135
+ });
136
+
137
+ commands.addCommand(TOGGLE_COMMAND, {
138
+ label: trans.__('Show Menu Bar'),
139
+ caption: trans.__('Toggle the visibility of the main menu bar'),
140
+ isToggled: () => visible,
141
+ execute: async () => {
142
+ applyVisibility(!visible);
143
+ if (settings !== null) {
144
+ try {
145
+ await settings.set('visible', visible);
146
+ } catch (reason) {
147
+ console.error(
148
+ 'xtralab: failed to persist menu bar visibility',
149
+ reason
150
+ );
151
+ }
152
+ }
153
+ }
154
+ });
155
+
156
+ if (palette) {
157
+ const category = trans.__('Other');
158
+ palette.addItem({ command: OPEN_COMMAND, category });
159
+ palette.addItem({ command: TOGGLE_COMMAND, category });
160
+ }
161
+
162
+ Promise.all([settingRegistry.load(PLUGIN_ID), app.restored])
163
+ .then(([loaded]) => {
164
+ settings = loaded;
165
+
166
+ button = new CommandToolbarButton({
167
+ commands,
168
+ id: OPEN_COMMAND,
169
+ icon: mainMenuIcon,
170
+ label: '',
171
+ caption: trans.__('Menu'),
172
+ noFocusOnClick: true,
173
+ 'aria-haspopup': 'menu'
174
+ });
175
+ button.id = 'xtralab-main-menu-button';
176
+ button.addClass('jp-xtralab-TopBarButton');
177
+ labShell.add(button, 'top', { rank: 0 });
178
+
179
+ const update = (): void => {
180
+ applyVisibility(loaded.get('visible').composite as boolean);
181
+ };
182
+ update();
183
+ loaded.changed.connect(update);
184
+ })
185
+ .catch(reason => {
186
+ // Without settings the collapsed state cannot be managed (and the
187
+ // button was never added) — reveal the stock bar rather than leave
188
+ // the app with no reachable menus.
189
+ applyVisibility(true);
190
+ console.error(`Failed to load settings for ${PLUGIN_ID}`, reason);
191
+ });
192
+ }
193
+ };
194
+
195
+ export default plugin;