forked from jupyterlab/jupyterlab
/
widgettracker.ts
360 lines (319 loc) · 9.65 KB
/
widgettracker.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { IRestorable, RestorablePool } from '@jupyterlab/statedb';
import { IDisposable } from '@lumino/disposable';
import { ISignal, Signal } from '@lumino/signaling';
import { FocusTracker, Widget } from '@lumino/widgets';
/**
* A tracker that tracks widgets.
*
* @typeparam T - The type of widget being tracked. Defaults to `Widget`.
*/
export interface IWidgetTracker<T extends Widget = Widget> extends IDisposable {
/**
* A signal emitted when a widget is added.
*/
readonly widgetAdded: ISignal<this, T>;
/**
* The current widget is the most recently focused or added widget.
*
* #### Notes
* It is the most recently focused widget, or the most recently added
* widget if no widget has taken focus.
*/
readonly currentWidget: T | null;
/**
* A signal emitted when the current instance changes.
*
* #### Notes
* If the last instance being tracked is disposed, `null` will be emitted.
*/
readonly currentChanged: ISignal<this, T | null>;
/**
* The number of instances held by the tracker.
*/
readonly size: number;
/**
* A promise that is resolved when the widget tracker has been
* restored from a serialized state.
*
* #### Notes
* Most client code will not need to use this, since they can wait
* for the whole application to restore. However, if an extension
* wants to perform actions during the application restoration, but
* after the restoration of another widget tracker, they can use
* this promise.
*/
readonly restored: Promise<void>;
/**
* A signal emitted when a widget is updated.
*/
readonly widgetUpdated: ISignal<this, T>;
/**
* Find the first instance in the tracker that satisfies a filter function.
*
* @param - fn The filter function to call on each instance.
*
* #### Notes
* If nothing is found, the value returned is `undefined`.
*/
find(fn: (obj: T) => boolean): T | undefined;
/**
* Iterate through each instance in the tracker.
*
* @param fn - The function to call on each instance.
*/
forEach(fn: (obj: T) => void): void;
/**
* Filter the instances in the tracker based on a predicate.
*
* @param fn - The function by which to filter.
*/
filter(fn: (obj: T) => boolean): T[];
/**
* Check if this tracker has the specified instance.
*
* @param obj - The object whose existence is being checked.
*/
has(obj: Widget): boolean;
/**
* Inject an instance into the widget tracker without the tracker handling
* its restoration lifecycle.
*
* @param obj - The instance to inject into the tracker.
*/
inject(obj: T): void;
}
/**
* A class that keeps track of widget instances on an Application shell.
*
* @typeparam T - The type of widget being tracked. Defaults to `Widget`.
*
* #### Notes
* The API surface area of this concrete implementation is substantially larger
* than the widget tracker interface it implements. The interface is intended
* for export by JupyterLab plugins that create widgets and have clients who may
* wish to keep track of newly created widgets. This class, however, can be used
* internally by plugins to restore state as well.
*/
export class WidgetTracker<T extends Widget = Widget>
implements IWidgetTracker<T>, IRestorable<T> {
/**
* Create a new widget tracker.
*
* @param options - The instantiation options for a widget tracker.
*/
constructor(options: WidgetTracker.IOptions) {
const focus = (this._focusTracker = new FocusTracker());
const pool = (this._pool = new RestorablePool(options));
this.namespace = options.namespace;
focus.currentChanged.connect((_, current) => {
if (current.newValue !== this.currentWidget) {
pool.current = current.newValue;
}
}, this);
pool.added.connect((_, widget) => {
this._widgetAdded.emit(widget);
}, this);
pool.currentChanged.connect((_, widget) => {
// If the pool's current reference is `null` but the focus tracker has a
// current widget, update the pool to match the focus tracker.
if (widget === null && focus.currentWidget) {
pool.current = focus.currentWidget;
return;
}
this.onCurrentChanged(widget);
this._currentChanged.emit(widget);
}, this);
pool.updated.connect((_, widget) => {
this._widgetUpdated.emit(widget);
}, this);
}
/**
* A namespace for all tracked widgets, (e.g., `notebook`).
*/
readonly namespace: string;
/**
* A signal emitted when the current widget changes.
*/
get currentChanged(): ISignal<this, T | null> {
return this._currentChanged;
}
/**
* The current widget is the most recently focused or added widget.
*
* #### Notes
* It is the most recently focused widget, or the most recently added
* widget if no widget has taken focus.
*/
get currentWidget(): T | null {
return this._pool.current || null;
}
/**
* A promise resolved when the tracker has been restored.
*/
get restored(): Promise<void> {
return this._pool.restored;
}
/**
* The number of widgets held by the tracker.
*/
get size(): number {
return this._pool.size;
}
/**
* A signal emitted when a widget is added.
*
* #### Notes
* This signal will only fire when a widget is added to the tracker. It will
* not fire if a widget is injected into the tracker.
*/
get widgetAdded(): ISignal<this, T> {
return this._widgetAdded;
}
/**
* A signal emitted when a widget is updated.
*/
get widgetUpdated(): ISignal<this, T> {
return this._widgetUpdated;
}
/**
* Add a new widget to the tracker.
*
* @param widget - The widget being added.
*
* #### Notes
* The widget passed into the tracker is added synchronously; its existence in
* the tracker can be checked with the `has()` method. The promise this method
* returns resolves after the widget has been added and saved to an underlying
* restoration connector, if one is available.
*/
async add(widget: T): Promise<void> {
this._focusTracker.add(widget);
await this._pool.add(widget);
this._pool.current = widget;
}
/**
* Test whether the tracker is disposed.
*/
get isDisposed(): boolean {
return this._isDisposed;
}
/**
* Dispose of the resources held by the tracker.
*/
dispose(): void {
if (this.isDisposed) {
return;
}
this._isDisposed = true;
this._pool.dispose();
this._focusTracker.dispose();
Signal.clearData(this);
}
/**
* Find the first widget in the tracker that satisfies a filter function.
*
* @param - fn The filter function to call on each widget.
*
* #### Notes
* If no widget is found, the value returned is `undefined`.
*/
find(fn: (widget: T) => boolean): T | undefined {
return this._pool.find(fn);
}
/**
* Iterate through each widget in the tracker.
*
* @param fn - The function to call on each widget.
*/
forEach(fn: (widget: T) => void): void {
return this._pool.forEach(fn);
}
/**
* Filter the widgets in the tracker based on a predicate.
*
* @param fn - The function by which to filter.
*/
filter(fn: (widget: T) => boolean): T[] {
return this._pool.filter(fn);
}
/**
* Inject a foreign widget into the widget tracker.
*
* @param widget - The widget to inject into the tracker.
*
* #### Notes
* Injected widgets will not have their state saved by the tracker.
*
* The primary use case for widget injection is for a plugin that offers a
* sub-class of an extant plugin to have its instances share the same commands
* as the parent plugin (since most relevant commands will use the
* `currentWidget` of the parent plugin's widget tracker). In this situation,
* the sub-class plugin may well have its own widget tracker for layout and
* state restoration in addition to injecting its widgets into the parent
* plugin's widget tracker.
*/
inject(widget: T): Promise<void> {
return this._pool.inject(widget);
}
/**
* Check if this tracker has the specified widget.
*
* @param widget - The widget whose existence is being checked.
*/
has(widget: Widget): boolean {
return this._pool.has(widget as any);
}
/**
* Restore the widgets in this tracker's namespace.
*
* @param options - The configuration options that describe restoration.
*
* @returns A promise that resolves when restoration has completed.
*
* #### Notes
* This function should not typically be invoked by client code.
* Its primary use case is to be invoked by a restorer.
*/
async restore(options: IRestorable.IOptions<T>): Promise<any> {
return this._pool.restore(options);
}
/**
* Save the restore data for a given widget.
*
* @param widget - The widget being saved.
*/
async save(widget: T): Promise<void> {
return this._pool.save(widget);
}
/**
* Handle the current change event.
*
* #### Notes
* The default implementation is a no-op.
*/
protected onCurrentChanged(value: T | null): void {
/* no-op */
}
private _currentChanged = new Signal<this, T | null>(this);
private _focusTracker: FocusTracker<T>;
private _pool: RestorablePool<T>;
private _isDisposed = false;
private _widgetAdded = new Signal<this, T>(this);
private _widgetUpdated = new Signal<this, T>(this);
}
/**
* A namespace for `WidgetTracker` statics.
*/
export namespace WidgetTracker {
/**
* The instantiation options for a widget tracker.
*/
export interface IOptions {
/**
* A namespace for all tracked widgets, (e.g., `notebook`).
*/
namespace: string;
}
}