1 | // Copyright (c) Jupyter Development Team.
|
2 | // Distributed under the terms of the Modified BSD License.
|
3 | import { RestorablePool } from '@jupyterlab/statedb';
|
4 | import { Signal } from '@lumino/signaling';
|
5 | import { FocusTracker } from '@lumino/widgets';
|
6 | /**
|
7 | * A class that keeps track of widget instances on an Application shell.
|
8 | *
|
9 | * @typeparam T - The type of widget being tracked. Defaults to `Widget`.
|
10 | *
|
11 | * #### Notes
|
12 | * The API surface area of this concrete implementation is substantially larger
|
13 | * than the widget tracker interface it implements. The interface is intended
|
14 | * for export by JupyterLab plugins that create widgets and have clients who may
|
15 | * wish to keep track of newly created widgets. This class, however, can be used
|
16 | * internally by plugins to restore state as well.
|
17 | */
|
18 | export class WidgetTracker {
|
19 | /**
|
20 | * Create a new widget tracker.
|
21 | *
|
22 | * @param options - The instantiation options for a widget tracker.
|
23 | */
|
24 | constructor(options) {
|
25 | this._currentChanged = new Signal(this);
|
26 | this._deferred = null;
|
27 | this._isDisposed = false;
|
28 | this._widgetAdded = new Signal(this);
|
29 | this._widgetUpdated = new Signal(this);
|
30 | const focus = (this._focusTracker = new FocusTracker());
|
31 | const pool = (this._pool = new RestorablePool(options));
|
32 | this.namespace = options.namespace;
|
33 | focus.currentChanged.connect((_, current) => {
|
34 | if (current.newValue !== this.currentWidget) {
|
35 | pool.current = current.newValue;
|
36 | }
|
37 | }, this);
|
38 | pool.added.connect((_, widget) => {
|
39 | this._widgetAdded.emit(widget);
|
40 | }, this);
|
41 | pool.currentChanged.connect((_, widget) => {
|
42 | // If the pool's current reference is `null` but the focus tracker has a
|
43 | // current widget, update the pool to match the focus tracker.
|
44 | if (widget === null && focus.currentWidget) {
|
45 | pool.current = focus.currentWidget;
|
46 | return;
|
47 | }
|
48 | this.onCurrentChanged(widget);
|
49 | this._currentChanged.emit(widget);
|
50 | }, this);
|
51 | pool.updated.connect((_, widget) => {
|
52 | this._widgetUpdated.emit(widget);
|
53 | }, this);
|
54 | }
|
55 | /**
|
56 | * A signal emitted when the current widget changes.
|
57 | */
|
58 | get currentChanged() {
|
59 | return this._currentChanged;
|
60 | }
|
61 | /**
|
62 | * The current widget is the most recently focused or added widget.
|
63 | *
|
64 | * #### Notes
|
65 | * It is the most recently focused widget, or the most recently added
|
66 | * widget if no widget has taken focus.
|
67 | */
|
68 | get currentWidget() {
|
69 | return this._pool.current || null;
|
70 | }
|
71 | /**
|
72 | * A promise resolved when the tracker has been restored.
|
73 | */
|
74 | get restored() {
|
75 | if (this._deferred) {
|
76 | return Promise.resolve();
|
77 | }
|
78 | else {
|
79 | return this._pool.restored;
|
80 | }
|
81 | }
|
82 | /**
|
83 | * The number of widgets held by the tracker.
|
84 | */
|
85 | get size() {
|
86 | return this._pool.size;
|
87 | }
|
88 | /**
|
89 | * A signal emitted when a widget is added.
|
90 | *
|
91 | * #### Notes
|
92 | * This signal will only fire when a widget is added to the tracker. It will
|
93 | * not fire if a widget is injected into the tracker.
|
94 | */
|
95 | get widgetAdded() {
|
96 | return this._widgetAdded;
|
97 | }
|
98 | /**
|
99 | * A signal emitted when a widget is updated.
|
100 | */
|
101 | get widgetUpdated() {
|
102 | return this._widgetUpdated;
|
103 | }
|
104 | /**
|
105 | * Add a new widget to the tracker.
|
106 | *
|
107 | * @param widget - The widget being added.
|
108 | *
|
109 | * #### Notes
|
110 | * The widget passed into the tracker is added synchronously; its existence in
|
111 | * the tracker can be checked with the `has()` method. The promise this method
|
112 | * returns resolves after the widget has been added and saved to an underlying
|
113 | * restoration connector, if one is available.
|
114 | *
|
115 | * The newly added widget becomes the current widget unless the focus tracker
|
116 | * already had a focused widget.
|
117 | */
|
118 | async add(widget) {
|
119 | this._focusTracker.add(widget);
|
120 | await this._pool.add(widget);
|
121 | if (!this._focusTracker.activeWidget) {
|
122 | this._pool.current = widget;
|
123 | }
|
124 | }
|
125 | /**
|
126 | * Test whether the tracker is disposed.
|
127 | */
|
128 | get isDisposed() {
|
129 | return this._isDisposed;
|
130 | }
|
131 | /**
|
132 | * Dispose of the resources held by the tracker.
|
133 | */
|
134 | dispose() {
|
135 | if (this.isDisposed) {
|
136 | return;
|
137 | }
|
138 | this._isDisposed = true;
|
139 | this._pool.dispose();
|
140 | this._focusTracker.dispose();
|
141 | Signal.clearData(this);
|
142 | }
|
143 | /**
|
144 | * Find the first widget in the tracker that satisfies a filter function.
|
145 | *
|
146 | * @param fn The filter function to call on each widget.
|
147 | *
|
148 | * #### Notes
|
149 | * If no widget is found, the value returned is `undefined`.
|
150 | */
|
151 | find(fn) {
|
152 | return this._pool.find(fn);
|
153 | }
|
154 | /**
|
155 | * Iterate through each widget in the tracker.
|
156 | *
|
157 | * @param fn - The function to call on each widget.
|
158 | */
|
159 | forEach(fn) {
|
160 | return this._pool.forEach(fn);
|
161 | }
|
162 | /**
|
163 | * Filter the widgets in the tracker based on a predicate.
|
164 | *
|
165 | * @param fn - The function by which to filter.
|
166 | */
|
167 | filter(fn) {
|
168 | return this._pool.filter(fn);
|
169 | }
|
170 | /**
|
171 | * Inject a foreign widget into the widget tracker.
|
172 | *
|
173 | * @param widget - The widget to inject into the tracker.
|
174 | *
|
175 | * #### Notes
|
176 | * Injected widgets will not have their state saved by the tracker.
|
177 | *
|
178 | * The primary use case for widget injection is for a plugin that offers a
|
179 | * sub-class of an extant plugin to have its instances share the same commands
|
180 | * as the parent plugin (since most relevant commands will use the
|
181 | * `currentWidget` of the parent plugin's widget tracker). In this situation,
|
182 | * the sub-class plugin may well have its own widget tracker for layout and
|
183 | * state restoration in addition to injecting its widgets into the parent
|
184 | * plugin's widget tracker.
|
185 | */
|
186 | inject(widget) {
|
187 | return this._pool.inject(widget);
|
188 | }
|
189 | /**
|
190 | * Check if this tracker has the specified widget.
|
191 | *
|
192 | * @param widget - The widget whose existence is being checked.
|
193 | */
|
194 | has(widget) {
|
195 | return this._pool.has(widget);
|
196 | }
|
197 | /**
|
198 | * Restore the widgets in this tracker's namespace.
|
199 | *
|
200 | * @param options - The configuration options that describe restoration.
|
201 | *
|
202 | * @returns A promise that resolves when restoration has completed.
|
203 | *
|
204 | * #### Notes
|
205 | * This function should not typically be invoked by client code.
|
206 | * Its primary use case is to be invoked by a restorer.
|
207 | */
|
208 | async restore(options) {
|
209 | const deferred = this._deferred;
|
210 | if (deferred) {
|
211 | this._deferred = null;
|
212 | return this._pool.restore(deferred);
|
213 | }
|
214 | if (options) {
|
215 | return this._pool.restore(options);
|
216 | }
|
217 | console.warn('No options provided to restore the tracker.');
|
218 | }
|
219 | /**
|
220 | * Save the restore options for this tracker, but do not restore yet.
|
221 | *
|
222 | * @param options - The configuration options that describe restoration.
|
223 | *
|
224 | * ### Notes
|
225 | * This function is useful when starting the shell in 'single-document' mode,
|
226 | * to avoid restoring all useless widgets. It should not ordinarily be called
|
227 | * by client code.
|
228 | */
|
229 | defer(options) {
|
230 | this._deferred = options;
|
231 | }
|
232 | /**
|
233 | * Save the restore data for a given widget.
|
234 | *
|
235 | * @param widget - The widget being saved.
|
236 | */
|
237 | async save(widget) {
|
238 | return this._pool.save(widget);
|
239 | }
|
240 | /**
|
241 | * Handle the current change event.
|
242 | *
|
243 | * #### Notes
|
244 | * The default implementation is a no-op.
|
245 | */
|
246 | onCurrentChanged(value) {
|
247 | /* no-op */
|
248 | }
|
249 | }
|
250 | //# sourceMappingURL=widgettracker.js.map |
\ | No newline at end of file |