@jupyterlab/apputils-extension 4.0.0-alpha.2 → 4.0.0-alpha.21

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.
Files changed (43) hide show
  1. package/lib/announcements.d.ts +2 -0
  2. package/lib/announcements.js +227 -0
  3. package/lib/announcements.js.map +1 -0
  4. package/lib/index.js +41 -8
  5. package/lib/index.js.map +1 -1
  6. package/lib/notificationplugin.d.ts +5 -0
  7. package/lib/notificationplugin.js +504 -0
  8. package/lib/notificationplugin.js.map +1 -0
  9. package/lib/palette.js +3 -3
  10. package/lib/palette.js.map +1 -1
  11. package/lib/settingconnector.js +4 -0
  12. package/lib/settingconnector.js.map +1 -1
  13. package/lib/settingsplugin.js +8 -2
  14. package/lib/settingsplugin.js.map +1 -1
  15. package/lib/statusbarplugin.d.ts +7 -0
  16. package/lib/statusbarplugin.js +96 -0
  17. package/lib/statusbarplugin.js.map +1 -0
  18. package/lib/themesplugins.js +3 -0
  19. package/lib/themesplugins.js.map +1 -1
  20. package/lib/toolbarregistryplugin.js +5 -1
  21. package/lib/toolbarregistryplugin.js.map +1 -1
  22. package/lib/workspacesplugin.js +6 -6
  23. package/lib/workspacesplugin.js.map +1 -1
  24. package/package.json +27 -22
  25. package/schema/notification.json +48 -0
  26. package/schema/palette.json +2 -0
  27. package/schema/sanitizer.json +25 -0
  28. package/src/announcements.ts +297 -0
  29. package/src/index.ts +684 -0
  30. package/src/notificationplugin.tsx +902 -0
  31. package/src/palette.ts +213 -0
  32. package/src/settingconnector.ts +63 -0
  33. package/src/settingsplugin.ts +65 -0
  34. package/src/statusbarplugin.ts +145 -0
  35. package/src/themesplugins.ts +266 -0
  36. package/src/toolbarregistryplugin.ts +29 -0
  37. package/src/workspacesplugin.ts +306 -0
  38. package/style/base.css +1 -1
  39. package/style/index.css +1 -0
  40. package/style/index.js +1 -0
  41. package/style/notification.css +227 -0
  42. package/style/splash.css +4 -0
  43. package/style/redirect.css +0 -15
package/src/palette.ts ADDED
@@ -0,0 +1,213 @@
1
+ /* -----------------------------------------------------------------------------
2
+ | Copyright (c) Jupyter Development Team.
3
+ | Distributed under the terms of the Modified BSD License.
4
+ |----------------------------------------------------------------------------*/
5
+
6
+ import { ILayoutRestorer, JupyterFrontEnd } from '@jupyterlab/application';
7
+ import {
8
+ ICommandPalette,
9
+ IPaletteItem,
10
+ ModalCommandPalette
11
+ } from '@jupyterlab/apputils';
12
+ import { ISettingRegistry } from '@jupyterlab/settingregistry';
13
+ import { ITranslator, nullTranslator } from '@jupyterlab/translation';
14
+ import { CommandPaletteSvg, paletteIcon } from '@jupyterlab/ui-components';
15
+ import { find } from '@lumino/algorithm';
16
+ import { CommandRegistry } from '@lumino/commands';
17
+ import { DisposableDelegate, IDisposable } from '@lumino/disposable';
18
+ import { CommandPalette } from '@lumino/widgets';
19
+
20
+ /**
21
+ * The command IDs used by the apputils extension.
22
+ */
23
+ namespace CommandIDs {
24
+ export const activate = 'apputils:activate-command-palette';
25
+ }
26
+
27
+ const PALETTE_PLUGIN_ID = '@jupyterlab/apputils-extension:palette';
28
+
29
+ /**
30
+ * A thin wrapper around the `CommandPalette` class to conform with the
31
+ * JupyterLab interface for the application-wide command palette.
32
+ */
33
+ export class Palette implements ICommandPalette {
34
+ /**
35
+ * Create a palette instance.
36
+ */
37
+ constructor(palette: CommandPalette, translator?: ITranslator) {
38
+ this.translator = translator || nullTranslator;
39
+ const trans = this.translator.load('jupyterlab');
40
+ this._palette = palette;
41
+ this._palette.title.label = '';
42
+ this._palette.title.caption = trans.__('Command Palette');
43
+ }
44
+
45
+ /**
46
+ * The placeholder text of the command palette's search input.
47
+ */
48
+ set placeholder(placeholder: string) {
49
+ this._palette.inputNode.placeholder = placeholder;
50
+ }
51
+ get placeholder(): string {
52
+ return this._palette.inputNode.placeholder;
53
+ }
54
+
55
+ /**
56
+ * Activate the command palette for user input.
57
+ */
58
+ activate(): void {
59
+ this._palette.activate();
60
+ }
61
+
62
+ /**
63
+ * Add a command item to the command palette.
64
+ *
65
+ * @param options - The options for creating the command item.
66
+ *
67
+ * @returns A disposable that will remove the item from the palette.
68
+ */
69
+ addItem(options: IPaletteItem): IDisposable {
70
+ const item = this._palette.addItem(options as CommandPalette.IItemOptions);
71
+ return new DisposableDelegate(() => {
72
+ this._palette.removeItem(item);
73
+ });
74
+ }
75
+
76
+ protected translator: ITranslator;
77
+ private _palette: CommandPalette;
78
+ }
79
+
80
+ /**
81
+ * A namespace for `Palette` statics.
82
+ */
83
+ export namespace Palette {
84
+ /**
85
+ * Activate the command palette.
86
+ */
87
+ export function activate(
88
+ app: JupyterFrontEnd,
89
+ translator: ITranslator,
90
+ settingRegistry: ISettingRegistry | null
91
+ ): ICommandPalette {
92
+ const { commands, shell } = app;
93
+ const trans = translator.load('jupyterlab');
94
+ const palette = Private.createPalette(app, translator);
95
+ const modalPalette = new ModalCommandPalette({ commandPalette: palette });
96
+ let modal = false;
97
+
98
+ palette.node.setAttribute('role', 'region');
99
+ palette.node.setAttribute(
100
+ 'aria-label',
101
+ trans.__('Command Palette Section')
102
+ );
103
+ shell.add(palette, 'left', { rank: 300, type: 'Command Palette' });
104
+ if (settingRegistry) {
105
+ const loadSettings = settingRegistry.load(PALETTE_PLUGIN_ID);
106
+ const updateSettings = (settings: ISettingRegistry.ISettings): void => {
107
+ const newModal = settings.get('modal').composite as boolean;
108
+ if (modal && !newModal) {
109
+ palette.parent = null;
110
+ modalPalette.detach();
111
+ shell.add(palette, 'left', { rank: 300, type: 'Command Palette' });
112
+ } else if (!modal && newModal) {
113
+ palette.parent = null;
114
+ modalPalette.palette = palette;
115
+ palette.show();
116
+ modalPalette.attach();
117
+ }
118
+ modal = newModal;
119
+ };
120
+
121
+ Promise.all([loadSettings, app.restored])
122
+ .then(([settings]) => {
123
+ updateSettings(settings);
124
+ settings.changed.connect(settings => {
125
+ updateSettings(settings);
126
+ });
127
+ })
128
+ .catch((reason: Error) => {
129
+ console.error(reason.message);
130
+ });
131
+ }
132
+
133
+ // Show the current palette shortcut in its title.
134
+ const updatePaletteTitle = () => {
135
+ const binding = find(
136
+ app.commands.keyBindings,
137
+ b => b.command === CommandIDs.activate
138
+ );
139
+ if (binding) {
140
+ const ks = binding.keys.map(CommandRegistry.formatKeystroke).join(', ');
141
+ palette.title.caption = trans.__('Commands (%1)', ks);
142
+ } else {
143
+ palette.title.caption = trans.__('Commands');
144
+ }
145
+ };
146
+ updatePaletteTitle();
147
+ app.commands.keyBindingChanged.connect(() => {
148
+ updatePaletteTitle();
149
+ });
150
+
151
+ commands.addCommand(CommandIDs.activate, {
152
+ execute: () => {
153
+ if (modal) {
154
+ modalPalette.activate();
155
+ } else {
156
+ shell.activateById(palette.id);
157
+ }
158
+ },
159
+ label: trans.__('Activate Command Palette')
160
+ });
161
+
162
+ palette.inputNode.placeholder = trans.__('SEARCH');
163
+
164
+ return new Palette(palette, translator);
165
+ }
166
+
167
+ /**
168
+ * Restore the command palette.
169
+ */
170
+ export function restore(
171
+ app: JupyterFrontEnd,
172
+ restorer: ILayoutRestorer,
173
+ translator: ITranslator
174
+ ): void {
175
+ const palette = Private.createPalette(app, translator);
176
+ // Let the application restorer track the command palette for restoration of
177
+ // application state (e.g. setting the command palette as the current side bar
178
+ // widget).
179
+ restorer.add(palette, 'command-palette');
180
+ }
181
+ }
182
+
183
+ /**
184
+ * The namespace for module private data.
185
+ */
186
+ namespace Private {
187
+ /**
188
+ * The private command palette instance.
189
+ */
190
+ let palette: CommandPalette;
191
+
192
+ /**
193
+ * Create the application-wide command palette.
194
+ */
195
+ export function createPalette(
196
+ app: JupyterFrontEnd,
197
+ translator: ITranslator
198
+ ): CommandPalette {
199
+ if (!palette) {
200
+ // use a renderer tweaked to use inline svg icons
201
+ palette = new CommandPalette({
202
+ commands: app.commands,
203
+ renderer: CommandPaletteSvg.defaultRenderer
204
+ });
205
+ palette.id = 'command-palette';
206
+ palette.title.icon = paletteIcon;
207
+ const trans = translator.load('jupyterlab');
208
+ palette.title.label = trans.__('Commands');
209
+ }
210
+
211
+ return palette;
212
+ }
213
+ }
@@ -0,0 +1,63 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+
6
+ import { PageConfig } from '@jupyterlab/coreutils';
7
+ import { ISettingRegistry } from '@jupyterlab/settingregistry';
8
+ import { DataConnector, IDataConnector } from '@jupyterlab/statedb';
9
+ import { Throttler } from '@lumino/polling';
10
+
11
+ /**
12
+ * A data connector for fetching settings.
13
+ *
14
+ * #### Notes
15
+ * This connector adds a query parameter to the base services setting manager.
16
+ */
17
+ export class SettingConnector extends DataConnector<
18
+ ISettingRegistry.IPlugin,
19
+ string
20
+ > {
21
+ constructor(connector: IDataConnector<ISettingRegistry.IPlugin, string>) {
22
+ super();
23
+ this._connector = connector;
24
+ }
25
+
26
+ /**
27
+ * Fetch settings for a plugin.
28
+ * @param id - The plugin ID
29
+ *
30
+ * #### Notes
31
+ * The REST API requests are throttled at one request per plugin per 100ms.
32
+ */
33
+ fetch(id: string): Promise<ISettingRegistry.IPlugin | undefined> {
34
+ const throttlers = this._throttlers;
35
+ if (!(id in throttlers)) {
36
+ throttlers[id] = new Throttler(() => this._connector.fetch(id), 100);
37
+ }
38
+ return throttlers[id].invoke();
39
+ }
40
+
41
+ async list(
42
+ query: 'active' | 'all' = 'all'
43
+ ): Promise<{ ids: string[]; values: ISettingRegistry.IPlugin[] }> {
44
+ const { isDeferred, isDisabled } = PageConfig.Extension;
45
+ const { ids, values } = await this._connector.list();
46
+
47
+ if (query === 'all') {
48
+ return { ids, values };
49
+ }
50
+
51
+ return {
52
+ ids: ids.filter(id => !isDeferred(id) && !isDisabled(id)),
53
+ values: values.filter(({ id }) => !isDeferred(id) && !isDisabled(id))
54
+ };
55
+ }
56
+
57
+ async save(id: string, raw: string): Promise<void> {
58
+ await this._connector.save(id, raw);
59
+ }
60
+
61
+ private _connector: IDataConnector<ISettingRegistry.IPlugin, string>;
62
+ private _throttlers: { [key: string]: Throttler } = Object.create(null);
63
+ }
@@ -0,0 +1,65 @@
1
+ /* -----------------------------------------------------------------------------
2
+ | Copyright (c) Jupyter Development Team.
3
+ | Distributed under the terms of the Modified BSD License.
4
+ |----------------------------------------------------------------------------*/
5
+
6
+ import {
7
+ JupyterFrontEnd,
8
+ JupyterFrontEndPlugin
9
+ } from '@jupyterlab/application';
10
+ import { PageConfig } from '@jupyterlab/coreutils';
11
+ import { ISettingRegistry, SettingRegistry } from '@jupyterlab/settingregistry';
12
+ import { SettingConnector } from './settingconnector';
13
+
14
+ /**
15
+ * The default setting registry provider.
16
+ */
17
+ export const settingsPlugin: JupyterFrontEndPlugin<ISettingRegistry> = {
18
+ id: '@jupyterlab/apputils-extension:settings',
19
+ activate: async (app: JupyterFrontEnd): Promise<ISettingRegistry> => {
20
+ const { isDisabled } = PageConfig.Extension;
21
+ const connector = new SettingConnector(app.serviceManager.settings);
22
+
23
+ // On startup, check if a plugin is available in the application.
24
+ // This helps avoid loading plugin files from other lab-based applications
25
+ // that have placed their schemas next to the JupyterLab schemas. Different lab-based
26
+ // applications might not have the same set of plugins loaded on the page.
27
+ // As an example this helps prevent having new toolbar items added by another application
28
+ // appear in JupyterLab as a side-effect when they are defined via the settings system.
29
+ const registry = new SettingRegistry({
30
+ connector,
31
+ plugins: (await connector.list('active')).values.filter(value =>
32
+ app.hasPlugin(value.id)
33
+ )
34
+ });
35
+
36
+ // If there are plugins that have schemas that are not in the setting
37
+ // registry after the application has restored, try to load them manually
38
+ // because otherwise, its settings will never become available in the
39
+ // setting registry.
40
+ void app.restored.then(async () => {
41
+ const plugins = await connector.list('all');
42
+ plugins.ids.forEach(async (id, index) => {
43
+ if (!app.hasPlugin(id) || isDisabled(id) || id in registry.plugins) {
44
+ return;
45
+ }
46
+
47
+ try {
48
+ await registry.load(id);
49
+ } catch (error) {
50
+ console.warn(`Settings failed to load for (${id})`, error);
51
+ if (plugins.values[index].schema['jupyter.lab.transform']) {
52
+ console.warn(
53
+ `This may happen if {autoStart: false} in (${id}) ` +
54
+ `or if it is one of the deferredExtensions in page config.`
55
+ );
56
+ }
57
+ }
58
+ });
59
+ });
60
+
61
+ return registry;
62
+ },
63
+ autoStart: true,
64
+ provides: ISettingRegistry
65
+ };
@@ -0,0 +1,145 @@
1
+ /* -----------------------------------------------------------------------------
2
+ | Copyright (c) Jupyter Development Team.
3
+ | Distributed under the terms of the Modified BSD License.
4
+ |----------------------------------------------------------------------------*/
5
+
6
+ import {
7
+ ILabShell,
8
+ JupyterFrontEnd,
9
+ JupyterFrontEndPlugin
10
+ } from '@jupyterlab/application';
11
+ import {
12
+ IKernelStatusModel,
13
+ ISessionContext,
14
+ ISessionContextDialogs,
15
+ KernelStatus,
16
+ RunningSessions,
17
+ sessionContextDialogs
18
+ } from '@jupyterlab/apputils';
19
+ import { IStatusBar } from '@jupyterlab/statusbar';
20
+ import { ITranslator } from '@jupyterlab/translation';
21
+ import { Title, Widget } from '@lumino/widgets';
22
+
23
+ /**
24
+ * A plugin that provides a kernel status item to the status bar.
25
+ */
26
+ export const kernelStatus: JupyterFrontEndPlugin<IKernelStatusModel> = {
27
+ id: '@jupyterlab/apputils-extension:kernel-status',
28
+ autoStart: true,
29
+ requires: [IStatusBar, ITranslator],
30
+ provides: IKernelStatusModel,
31
+ optional: [ISessionContextDialogs, ILabShell],
32
+ activate: (
33
+ app: JupyterFrontEnd,
34
+ statusBar: IStatusBar,
35
+ translator: ITranslator,
36
+ sessionDialogs: ISessionContextDialogs | null,
37
+ labShell: ILabShell | null
38
+ ): IKernelStatusModel => {
39
+ // When the status item is clicked, launch the kernel
40
+ // selection dialog for the current session.
41
+ const changeKernel = async () => {
42
+ if (!item.model.sessionContext) {
43
+ return;
44
+ }
45
+ await (sessionDialogs ?? sessionContextDialogs).selectKernel(
46
+ item.model.sessionContext,
47
+ translator
48
+ );
49
+ };
50
+
51
+ // Create the status item.
52
+ const item = new KernelStatus({ onClick: changeKernel }, translator);
53
+
54
+ const providers = new Set<(w: Widget | null) => ISessionContext | null>();
55
+
56
+ const addSessionProvider = (
57
+ provider: (w: Widget | null) => ISessionContext | null
58
+ ): void => {
59
+ providers.add(provider);
60
+
61
+ if (app.shell.currentWidget) {
62
+ updateSession(app.shell, {
63
+ newValue: app.shell.currentWidget,
64
+ oldValue: null
65
+ });
66
+ }
67
+ };
68
+
69
+ function updateSession(
70
+ shell: JupyterFrontEnd.IShell,
71
+ changes: ILabShell.IChangedArgs
72
+ ) {
73
+ const { oldValue, newValue } = changes;
74
+
75
+ // Clean up after the old value if it exists,
76
+ // listen for changes to the title of the activity
77
+ if (oldValue) {
78
+ oldValue.title.changed.disconnect(onTitleChanged);
79
+ }
80
+
81
+ item.model.sessionContext =
82
+ [...providers]
83
+ .map(provider => provider(changes.newValue))
84
+ .filter(session => session !== null)[0] ?? null;
85
+
86
+ if (newValue && item.model.sessionContext) {
87
+ onTitleChanged(newValue.title);
88
+ newValue.title.changed.connect(onTitleChanged);
89
+ }
90
+ }
91
+
92
+ // When the title of the active widget changes, update the label
93
+ // of the hover text.
94
+ const onTitleChanged = (title: Title<Widget>) => {
95
+ item.model!.activityName = title.label;
96
+ };
97
+
98
+ if (labShell) {
99
+ labShell.currentChanged.connect(updateSession);
100
+ }
101
+
102
+ statusBar.registerStatusItem(kernelStatus.id, {
103
+ item,
104
+ align: 'left',
105
+ rank: 1,
106
+ isActive: () => item.model!.sessionContext !== null
107
+ });
108
+
109
+ return { addSessionProvider };
110
+ }
111
+ };
112
+
113
+ /*
114
+ * A plugin providing running terminals and sessions information
115
+ * to the status bar.
116
+ */
117
+ export const runningSessionsStatus: JupyterFrontEndPlugin<void> = {
118
+ id: '@jupyterlab/apputils-extension:running-sessions-status',
119
+ autoStart: true,
120
+ requires: [IStatusBar, ITranslator],
121
+ activate: (
122
+ app: JupyterFrontEnd,
123
+ statusBar: IStatusBar,
124
+ translator: ITranslator
125
+ ) => {
126
+ const item = new RunningSessions({
127
+ onClick: () => app.shell.activateById('jp-running-sessions'),
128
+ serviceManager: app.serviceManager,
129
+ translator
130
+ });
131
+
132
+ item.model.sessions = Array.from(
133
+ app.serviceManager.sessions.running()
134
+ ).length;
135
+ item.model.terminals = Array.from(
136
+ app.serviceManager.terminals.running()
137
+ ).length;
138
+
139
+ statusBar.registerStatusItem(runningSessionsStatus.id, {
140
+ item,
141
+ align: 'left',
142
+ rank: 0
143
+ });
144
+ }
145
+ };