@jupyterlab/application 4.0.0-alpha.19 → 4.0.0-alpha.20

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/src/status.ts ADDED
@@ -0,0 +1,93 @@
1
+ // Copyright (c) Jupyter Development Team.
2
+ // Distributed under the terms of the Modified BSD License.
3
+
4
+ import { DisposableDelegate, IDisposable } from '@lumino/disposable';
5
+ import { ISignal, Signal } from '@lumino/signaling';
6
+ import { JupyterFrontEnd } from './frontend';
7
+ import { ILabStatus } from './tokens';
8
+
9
+ /**
10
+ * The application status signals and flags class.
11
+ */
12
+ export class LabStatus implements ILabStatus {
13
+ /**
14
+ * Construct a new status object.
15
+ */
16
+ constructor(app: JupyterFrontEnd<any, any>) {
17
+ this._busySignal = new Signal(app);
18
+ this._dirtySignal = new Signal(app);
19
+ }
20
+
21
+ /**
22
+ * Returns a signal for when application changes its busy status.
23
+ */
24
+ get busySignal(): ISignal<JupyterFrontEnd, boolean> {
25
+ return this._busySignal;
26
+ }
27
+
28
+ /**
29
+ * Returns a signal for when application changes its dirty status.
30
+ */
31
+ get dirtySignal(): ISignal<JupyterFrontEnd, boolean> {
32
+ return this._dirtySignal;
33
+ }
34
+
35
+ /**
36
+ * Whether the application is busy.
37
+ */
38
+ get isBusy(): boolean {
39
+ return this._busyCount > 0;
40
+ }
41
+
42
+ /**
43
+ * Whether the application is dirty.
44
+ */
45
+ get isDirty(): boolean {
46
+ return this._dirtyCount > 0;
47
+ }
48
+
49
+ /**
50
+ * Set the application state to dirty.
51
+ *
52
+ * @returns A disposable used to clear the dirty state for the caller.
53
+ */
54
+ setDirty(): IDisposable {
55
+ const oldDirty = this.isDirty;
56
+ this._dirtyCount++;
57
+ if (this.isDirty !== oldDirty) {
58
+ this._dirtySignal.emit(this.isDirty);
59
+ }
60
+ return new DisposableDelegate(() => {
61
+ const oldDirty = this.isDirty;
62
+ this._dirtyCount = Math.max(0, this._dirtyCount - 1);
63
+ if (this.isDirty !== oldDirty) {
64
+ this._dirtySignal.emit(this.isDirty);
65
+ }
66
+ });
67
+ }
68
+
69
+ /**
70
+ * Set the application state to busy.
71
+ *
72
+ * @returns A disposable used to clear the busy state for the caller.
73
+ */
74
+ setBusy(): IDisposable {
75
+ const oldBusy = this.isBusy;
76
+ this._busyCount++;
77
+ if (this.isBusy !== oldBusy) {
78
+ this._busySignal.emit(this.isBusy);
79
+ }
80
+ return new DisposableDelegate(() => {
81
+ const oldBusy = this.isBusy;
82
+ this._busyCount--;
83
+ if (this.isBusy !== oldBusy) {
84
+ this._busySignal.emit(this.isBusy);
85
+ }
86
+ });
87
+ }
88
+
89
+ private _busyCount = 0;
90
+ private _busySignal: Signal<JupyterFrontEnd, boolean>;
91
+ private _dirtyCount = 0;
92
+ private _dirtySignal: Signal<JupyterFrontEnd, boolean>;
93
+ }
package/src/tokens.ts ADDED
@@ -0,0 +1,220 @@
1
+ // Copyright (c) Jupyter Development Team.
2
+ // Distributed under the terms of the Modified BSD License.
3
+
4
+ import { ServerConnection, ServiceManager } from '@jupyterlab/services';
5
+ import { ITranslator } from '@jupyterlab/translation';
6
+ import { CommandRegistry } from '@lumino/commands';
7
+ import { ReadonlyPartialJSONObject, Token } from '@lumino/coreutils';
8
+ import { IDisposable } from '@lumino/disposable';
9
+ import { ISignal } from '@lumino/signaling';
10
+ import { JupyterFrontEnd } from '.';
11
+
12
+ /**
13
+ * A token for which a plugin can provide to respond to connection failures
14
+ * to the application server.
15
+ */
16
+ export const IConnectionLost = new Token<IConnectionLost>(
17
+ '@jupyterlab/apputils:IConnectionLost'
18
+ );
19
+
20
+ /**
21
+ * A function that handles a connection to the server being lost.
22
+ *
23
+ * #### Notes
24
+ * The default implementation shows a simple dialog upon connection
25
+ * failures, but it may be overridden by extensions to perform other
26
+ * actions.
27
+ */
28
+ export type IConnectionLost = (
29
+ manager: ServiceManager.IManager,
30
+ err: ServerConnection.NetworkError,
31
+ translator?: ITranslator
32
+ ) => Promise<void>;
33
+
34
+ /**
35
+ * The application status token.
36
+ */
37
+ export const ILabStatus = new Token<ILabStatus>(
38
+ '@jupyterlab/application:ILabStatus'
39
+ );
40
+
41
+ /**
42
+ * An interface for JupyterLab-like application status functionality.
43
+ */
44
+ export interface ILabStatus {
45
+ /**
46
+ * A signal for when application changes its busy status.
47
+ */
48
+ readonly busySignal: ISignal<JupyterFrontEnd<any, any>, boolean>;
49
+
50
+ /**
51
+ * A signal for when application changes its dirty status.
52
+ */
53
+ readonly dirtySignal: ISignal<JupyterFrontEnd<any, any>, boolean>;
54
+
55
+ /**
56
+ * Whether the application is busy.
57
+ */
58
+ readonly isBusy: boolean;
59
+
60
+ /**
61
+ * Whether the application is dirty.
62
+ */
63
+ readonly isDirty: boolean;
64
+
65
+ /**
66
+ * Set the application state to busy.
67
+ *
68
+ * @returns A disposable used to clear the busy state for the caller.
69
+ */
70
+ setBusy(): IDisposable;
71
+
72
+ /**
73
+ * Set the application state to dirty.
74
+ *
75
+ * @returns A disposable used to clear the dirty state for the caller.
76
+ */
77
+ setDirty(): IDisposable;
78
+ }
79
+
80
+ /**
81
+ * The URL Router token.
82
+ */
83
+ export const IRouter = new Token<IRouter>('@jupyterlab/application:IRouter');
84
+
85
+ /**
86
+ * A static class that routes URLs within the application.
87
+ */
88
+ export interface IRouter {
89
+ /**
90
+ * The base URL for the router.
91
+ */
92
+ readonly base: string;
93
+
94
+ /**
95
+ * The command registry used by the router.
96
+ */
97
+ readonly commands: CommandRegistry;
98
+
99
+ /**
100
+ * The parsed current URL of the application.
101
+ */
102
+ readonly current: IRouter.ILocation;
103
+
104
+ /**
105
+ * A signal emitted when the router routes a route.
106
+ */
107
+ readonly routed: ISignal<IRouter, IRouter.ILocation>;
108
+
109
+ /**
110
+ * If a matching rule's command resolves with the `stop` token during routing,
111
+ * no further matches will execute.
112
+ */
113
+ readonly stop: Token<void>;
114
+
115
+ /**
116
+ * Navigate to a new path within the application.
117
+ *
118
+ * @param path - The new path or empty string if redirecting to root.
119
+ *
120
+ * @param options - The navigation options.
121
+ */
122
+ navigate(path: string, options?: IRouter.INavOptions): void;
123
+
124
+ /**
125
+ * Register a rule that maps a path pattern to a command.
126
+ *
127
+ * @param options - The route registration options.
128
+ *
129
+ * @returns A disposable that removes the registered rule from the router.
130
+ */
131
+ register(options: IRouter.IRegisterOptions): IDisposable;
132
+
133
+ /**
134
+ * Cause a hard reload of the document.
135
+ */
136
+ reload(): void;
137
+
138
+ /**
139
+ * Route a specific path to an action.
140
+ *
141
+ * @param url - The URL string that will be routed.
142
+ *
143
+ * #### Notes
144
+ * If a pattern is matched, its command will be invoked with arguments that
145
+ * match the `IRouter.ILocation` interface.
146
+ */
147
+ route(url: string): void;
148
+ }
149
+
150
+ /**
151
+ * A namespace for the `IRouter` specification.
152
+ */
153
+ export namespace IRouter {
154
+ /**
155
+ * The parsed location currently being routed.
156
+ */
157
+ export interface ILocation extends ReadonlyPartialJSONObject {
158
+ /**
159
+ * The location hash.
160
+ */
161
+ hash: string;
162
+
163
+ /**
164
+ * The path that matched a routing pattern.
165
+ */
166
+ path: string;
167
+
168
+ /**
169
+ * The request being routed with the router `base` omitted.
170
+ *
171
+ * #### Notes
172
+ * This field includes the query string and hash, if they exist.
173
+ */
174
+ request: string;
175
+
176
+ /**
177
+ * The search element, including leading question mark (`'?'`), if any,
178
+ * of the path.
179
+ */
180
+ search?: string;
181
+ }
182
+
183
+ /**
184
+ * The options passed into a navigation request.
185
+ */
186
+ export interface INavOptions {
187
+ /**
188
+ * Whether the navigation should be hard URL change instead of an HTML
189
+ * history API change.
190
+ */
191
+ hard?: boolean;
192
+
193
+ /**
194
+ * Should the routing stage be skipped when navigating? This will simply rewrite the URL
195
+ * and push the new state to the history API, no routing commands will be triggered.
196
+ */
197
+ skipRouting?: boolean;
198
+ }
199
+
200
+ /**
201
+ * The specification for registering a route with the router.
202
+ */
203
+ export interface IRegisterOptions {
204
+ /**
205
+ * The command string that will be invoked upon matching.
206
+ */
207
+ command: string;
208
+
209
+ /**
210
+ * The regular expression that will be matched against URLs.
211
+ */
212
+ pattern: RegExp;
213
+
214
+ /**
215
+ * The rank order of the registered rule. A lower rank denotes a higher
216
+ * priority. The default rank is `100`.
217
+ */
218
+ rank?: number;
219
+ }
220
+ }
@@ -0,0 +1,20 @@
1
+ /* -----------------------------------------------------------------------------
2
+ | Copyright (c) Jupyter Development Team.
3
+ | Distributed under the terms of the Modified BSD License.
4
+ |----------------------------------------------------------------------------*/
5
+
6
+ import { Token } from '@lumino/coreutils';
7
+
8
+ /**
9
+ * The tree path updater token.
10
+ */
11
+ export const ITreePathUpdater = new Token<ITreePathUpdater>(
12
+ '@jupyterlab/application:ITreePathUpdater'
13
+ );
14
+
15
+ /**
16
+ * A function to call to update the tree path.
17
+ */
18
+ export interface ITreePathUpdater {
19
+ (treePath: string): void;
20
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,153 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+
6
+ import { SemanticCommand } from '@jupyterlab/apputils';
7
+ import { TranslationBundle } from '@jupyterlab/translation';
8
+ import { CommandRegistry } from '@lumino/commands';
9
+ import { JupyterFrontEnd } from './frontend';
10
+
11
+ export interface ISemanticCommandDefault {
12
+ /**
13
+ * Default command to execute if no command is enabled
14
+ */
15
+ execute?: string;
16
+ /**
17
+ * Default command label
18
+ */
19
+ label?: string;
20
+ /**
21
+ * Default command caption
22
+ */
23
+ caption?: string;
24
+ /**
25
+ * Whether the default command is enabled.
26
+ */
27
+ isEnabled?: boolean;
28
+ /**
29
+ * Whether the default command is toggled.
30
+ */
31
+ isToggled?: boolean;
32
+ /**
33
+ * Whether the default command is visible.
34
+ */
35
+ isVisible?: boolean;
36
+ }
37
+
38
+ /**
39
+ * Create the command options from the given semantic commands list
40
+ * and the given default values.
41
+ *
42
+ * @param app Jupyter Application
43
+ * @param semanticCommands Single semantic command or a list of commands
44
+ * @param defaultValues Default values
45
+ * @param trans Translation bundle
46
+ * @returns Command options
47
+ */
48
+ export function createSemanticCommand(
49
+ app: JupyterFrontEnd,
50
+ semanticCommands: SemanticCommand | SemanticCommand[],
51
+ defaultValues: ISemanticCommandDefault,
52
+ trans: TranslationBundle
53
+ ): CommandRegistry.ICommandOptions {
54
+ const { commands, shell } = app;
55
+ const commandList = Array.isArray(semanticCommands)
56
+ ? semanticCommands
57
+ : [semanticCommands];
58
+
59
+ return {
60
+ label: concatenateTexts('label'),
61
+ caption: concatenateTexts('caption'),
62
+ isEnabled: () => {
63
+ const isEnabled = reduceAttribute('isEnabled');
64
+ return (
65
+ (isEnabled.length > 0 &&
66
+ !isEnabled.some(enabled => enabled === false)) ||
67
+ (defaultValues.isEnabled ?? false)
68
+ );
69
+ },
70
+ isToggled: () => {
71
+ const isToggled = reduceAttribute('isToggled');
72
+ return (
73
+ isToggled.some(enabled => enabled === true) ||
74
+ (defaultValues.isToggled ?? false)
75
+ );
76
+ },
77
+ isVisible: () => {
78
+ const isVisible = reduceAttribute('isVisible');
79
+ return (
80
+ (isVisible.length > 0 &&
81
+ !isVisible.some(visible => visible === false)) ||
82
+ (defaultValues.isVisible ?? true)
83
+ );
84
+ },
85
+ execute: async () => {
86
+ const widget = shell.currentWidget;
87
+ const commandIds = commandList.map(cmd =>
88
+ widget !== null ? cmd.getActiveCommandId(widget) : null
89
+ );
90
+ const toExecute = commandIds.filter(
91
+ commandId => commandId !== null && commands.isEnabled(commandId)
92
+ );
93
+
94
+ let result: any = null;
95
+ if (toExecute.length > 0) {
96
+ for (const commandId of toExecute) {
97
+ result = await commands.execute(commandId!);
98
+ if (typeof result === 'boolean' && result === false) {
99
+ // If a command returns a boolean, assume it is the execution success status
100
+ // So break if it is false.
101
+ break;
102
+ }
103
+ }
104
+ } else if (defaultValues.execute) {
105
+ result = await commands.execute(defaultValues.execute);
106
+ }
107
+ return result;
108
+ }
109
+ };
110
+
111
+ function reduceAttribute(
112
+ attribute: keyof CommandRegistry.ICommandOptions
113
+ ): any[] {
114
+ const widget = shell.currentWidget;
115
+ const commandIds = commandList.map(cmd =>
116
+ widget !== null ? cmd.getActiveCommandId(widget) : null
117
+ );
118
+ const attributes = commandIds
119
+ .filter(commandId => commandId !== null)
120
+ .map(commandId => commands[attribute](commandId!));
121
+ return attributes;
122
+ }
123
+
124
+ function concatenateTexts(
125
+ attribute: 'label' | 'caption'
126
+ ): string | CommandRegistry.CommandFunc<string> | undefined {
127
+ return () => {
128
+ const texts = (reduceAttribute(attribute) as string[]).map(
129
+ (text, textIndex) =>
130
+ attribute == 'caption' && textIndex > 0
131
+ ? text.toLocaleLowerCase()
132
+ : text
133
+ );
134
+
135
+ switch (texts.length) {
136
+ case 0:
137
+ return defaultValues[attribute] ?? '';
138
+ case 1:
139
+ return texts[0];
140
+ default: {
141
+ const hasEllipsis = texts.some(l => /…$/.test(l));
142
+ const main = texts
143
+ .slice(undefined, -1)
144
+ .map(l => l.replace(/…$/, ''))
145
+ .join(', ');
146
+ const end =
147
+ texts.slice(-1)[0].replace(/…$/, '') + (hasEllipsis ? '…' : '');
148
+ return trans.__('%1 and %2', main, end);
149
+ }
150
+ }
151
+ };
152
+ }
153
+ }