Browse Source

Update InstanceTracker to allow for automatic save/restore behavior.

Afshin Darian 8 years ago
parent
commit
f86404540e
1 changed files with 142 additions and 0 deletions
  1. 142 0
      src/common/instancetracker.ts

+ 142 - 0
src/common/instancetracker.ts

@@ -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' });
+}