|
@@ -1,18 +1,35 @@
|
|
|
// Copyright (c) Jupyter Development Team.
|
|
|
// Distributed under the terms of the Modified BSD License.
|
|
|
|
|
|
+import {
|
|
|
+ JSONObject
|
|
|
+} from 'phosphor/lib/algorithm/json';
|
|
|
+
|
|
|
import {
|
|
|
IDisposable
|
|
|
} from 'phosphor/lib/core/disposable';
|
|
|
|
|
|
+import {
|
|
|
+ AttachedProperty
|
|
|
+} from 'phosphor/lib/core/properties';
|
|
|
+
|
|
|
import {
|
|
|
clearSignalData, defineSignal, ISignal
|
|
|
} from 'phosphor/lib/core/signaling';
|
|
|
|
|
|
+import {
|
|
|
+ CommandRegistry
|
|
|
+} from 'phosphor/lib/ui/commandregistry';
|
|
|
+
|
|
|
import {
|
|
|
Widget
|
|
|
} from 'phosphor/lib/ui/widget';
|
|
|
|
|
|
+import {
|
|
|
+ IStateDB
|
|
|
+} from '../statedb';
|
|
|
+
|
|
|
+
|
|
|
|
|
|
/**
|
|
|
* An object that tracks widget instances.
|
|
@@ -42,6 +59,24 @@ interface IInstanceTracker<T extends Widget> {
|
|
|
*/
|
|
|
export
|
|
|
class InstanceTracker<T extends Widget> implements IInstanceTracker<T>, IDisposable {
|
|
|
+ /**
|
|
|
+ * Create a new instance tracker.
|
|
|
+ */
|
|
|
+ constructor(options: InstanceTracker.IOptions<T> = {}) {
|
|
|
+ this._restore = options.restore;
|
|
|
+ if (this._restore) {
|
|
|
+ let { command, namespace, registry, state, when } = this._restore;
|
|
|
+ Promise.all([state.fetchNamespace(namespace), when]).then(([saved]) => {
|
|
|
+ saved.forEach(args => {
|
|
|
+ registry.execute(command, args.value).catch(() => {
|
|
|
+ // If the command fails, delete the state restore data.
|
|
|
+ state.remove(args.id);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* A signal emitted when the current widget changes.
|
|
|
*
|
|
@@ -73,8 +108,24 @@ class InstanceTracker<T extends Widget> implements IInstanceTracker<T>, IDisposa
|
|
|
return;
|
|
|
}
|
|
|
this._widgets.add(widget);
|
|
|
+
|
|
|
+ // Handle widget state restoration.
|
|
|
+ if (this._restore) {
|
|
|
+ let { namespace, state } = this._restore;
|
|
|
+ let id = `${namespace}:${++Private.id}`;
|
|
|
+ Private.idProperty.set(widget, id);
|
|
|
+ state.save(id, this._restore.args(widget));
|
|
|
+ }
|
|
|
+
|
|
|
+ // Handle widget disposal.
|
|
|
widget.disposed.connect(() => {
|
|
|
this._widgets.delete(widget);
|
|
|
+ // If restore data was saved, delete it from the database.
|
|
|
+ if (this._restore) {
|
|
|
+ let { state } = this._restore;
|
|
|
+ state.remove(Private.idProperty.get(widget));
|
|
|
+ }
|
|
|
+ // If this was the last widget being disposed, emit null.
|
|
|
if (!this._widgets.size) {
|
|
|
this._currentWidget = null;
|
|
|
this.onCurrentChanged();
|
|
@@ -131,6 +182,22 @@ class InstanceTracker<T extends Widget> implements IInstanceTracker<T>, IDisposa
|
|
|
return this._widgets.has(widget as any);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Save the restore data for a given widget.
|
|
|
+ */
|
|
|
+ save(widget: T): void {
|
|
|
+ if (!this._restore || !this.has(widget)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ let { state } = this._restore;
|
|
|
+ let id = Private.idProperty.get(widget);
|
|
|
+
|
|
|
+ if (id) {
|
|
|
+ state.save(id, this._restore.args(widget));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Syncs the state of the tracker with a widget known to have focus.
|
|
|
*
|
|
@@ -145,6 +212,7 @@ class InstanceTracker<T extends Widget> implements IInstanceTracker<T>, IDisposa
|
|
|
if (this.isDisposed) {
|
|
|
return;
|
|
|
}
|
|
|
+
|
|
|
if (current && this._widgets.has(current as any)) {
|
|
|
// If no state change needs to occur, just bail.
|
|
|
if (this._currentWidget === current) {
|
|
@@ -155,6 +223,7 @@ class InstanceTracker<T extends Widget> implements IInstanceTracker<T>, IDisposa
|
|
|
this.currentChanged.emit(this._currentWidget);
|
|
|
return this._currentWidget;
|
|
|
}
|
|
|
+
|
|
|
return null;
|
|
|
}
|
|
|
|
|
@@ -170,9 +239,82 @@ class InstanceTracker<T extends Widget> implements IInstanceTracker<T>, IDisposa
|
|
|
}
|
|
|
|
|
|
private _currentWidget: T = null;
|
|
|
+ private _restore: InstanceTracker.IRestoreOptions<T> = null;
|
|
|
private _widgets = new Set<T>();
|
|
|
}
|
|
|
|
|
|
|
|
|
// Define the signals for the `InstanceTracker` class.
|
|
|
defineSignal(InstanceTracker.prototype, 'currentChanged');
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * A namespace for `InstanceTracker` statics.
|
|
|
+ */
|
|
|
+export
|
|
|
+namespace InstanceTracker {
|
|
|
+ /**
|
|
|
+ * The state restoration configuration options.
|
|
|
+ */
|
|
|
+ export
|
|
|
+ interface IRestoreOptions<T extends Widget> {
|
|
|
+ /**
|
|
|
+ * The command to execute when restoring instances.
|
|
|
+ */
|
|
|
+ command: string;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * A function that returns the args needed to restore an instance.
|
|
|
+ */
|
|
|
+ args: (widget: T) => JSONObject;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The namespace to occupy in the state database for restoration data.
|
|
|
+ */
|
|
|
+ namespace: string;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The command registry which holds the restore command.
|
|
|
+ */
|
|
|
+ registry: CommandRegistry;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The state database instance.
|
|
|
+ */
|
|
|
+ state: IStateDB;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The point after which it is safe to restore state.
|
|
|
+ */
|
|
|
+ when: Promise<void>;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The instance tracker constructor options.
|
|
|
+ */
|
|
|
+ export
|
|
|
+ interface IOptions<T extends Widget> {
|
|
|
+ /**
|
|
|
+ * The optional state restoration options.
|
|
|
+ */
|
|
|
+ restore?: IRestoreOptions<T>;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/*
|
|
|
+ * A namespace for private data.
|
|
|
+ */
|
|
|
+namespace Private {
|
|
|
+ /**
|
|
|
+ * The ID counter.
|
|
|
+ */
|
|
|
+ export
|
|
|
+ let id = 0;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * An attached property for a widget's ID in the state database.
|
|
|
+ */
|
|
|
+ export
|
|
|
+ const idProperty = new AttachedProperty<Widget, string>({ name: 'id' });
|
|
|
+}
|