UNPKG

8 kBJavaScriptView Raw
1// Copyright (c) Jupyter Development Team.
2// Distributed under the terms of the Modified BSD License.
3import { RestorablePool } from '@jupyterlab/statedb';
4import { Signal } from '@lumino/signaling';
5import { 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 */
18export 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