Browse Source

Merge pull request #2766 from blink1073/app-dirty

Add dirty state handling to the application
Afshin Darian 7 years ago
parent
commit
21485cb9ea

+ 1 - 3
packages/application-extension/src/index.ts

@@ -47,7 +47,6 @@ const mainPlugin: JupyterLabPlugin<void> = {
   activate: (app: JupyterLab, palette: ICommandPalette) => {
     addCommands(app, palette);
 
-    let unloadPrompt = true;
     let builder = app.serviceManager.builder;
 
     let doBuild = () => {
@@ -60,7 +59,6 @@ const mainPlugin: JupyterLabPlugin<void> = {
         });
       }).then(result => {
         if (result.button.accept) {
-          unloadPrompt = false;
           location.reload();
         }
       }).catch(err => {
@@ -105,7 +103,7 @@ const mainPlugin: JupyterLabPlugin<void> = {
     // For more information, see:
     // https://developer.mozilla.org/en/docs/Web/Events/beforeunload
     window.addEventListener('beforeunload', event => {
-      if (unloadPrompt) {
+      if (app.isDirty) {
         return (event as any).returnValue = message;
       }
     });

+ 1 - 0
packages/application/package.json

@@ -23,6 +23,7 @@
     "@phosphor/application": "^1.3.0",
     "@phosphor/commands": "^1.3.0",
     "@phosphor/coreutils": "^1.2.0",
+    "@phosphor/disposable": "^1.1.1",
     "@phosphor/messaging": "^1.2.1",
     "@phosphor/properties": "^1.1.1",
     "@phosphor/signaling": "^1.2.1",

+ 24 - 0
packages/application/src/index.ts

@@ -21,6 +21,10 @@ import {
   Application, IPlugin
 } from '@phosphor/application';
 
+import {
+  DisposableDelegate, IDisposable
+} from '@phosphor/disposable';
+
 import {
   createRendermimePlugins
 } from './mimerenderers';
@@ -105,6 +109,13 @@ class JupyterLab extends Application<ApplicationShell> {
    */
   readonly serviceManager: ServiceManager;
 
+  /**
+   * Whether the application is dirty.
+   */
+  get isDirty(): boolean {
+    return this._dirtyCount > 0;
+  }
+
   /**
    * The information about the application.
    */
@@ -122,6 +133,18 @@ class JupyterLab extends Application<ApplicationShell> {
     return this.shell.restored;
   }
 
+  /**
+   * Set the application state to dirty.
+   *
+   * @returns A disposable used to clear the dirty state for the caller.
+   */
+  setDirty(): IDisposable {
+    this._dirtyCount++;
+    return new DisposableDelegate(() => {
+      this._dirtyCount = Math.max(0, this._dirtyCount - 1);
+    });
+  }
+
   /**
    * Register plugins from a plugin module.
    *
@@ -149,6 +172,7 @@ class JupyterLab extends Application<ApplicationShell> {
   }
 
   private _info: JupyterLab.IInfo;
+  private _dirtyCount = 0;
 }
 
 

+ 4 - 1
packages/docmanager-extension/package.json

@@ -14,8 +14,11 @@
   "dependencies": {
     "@jupyterlab/application": "^0.9.0",
     "@jupyterlab/apputils": "^0.9.0",
+    "@jupyterlab/coreutils": "^0.9.0",
     "@jupyterlab/docmanager": "^0.9.0",
-    "@jupyterlab/services": "^0.48.0"
+    "@jupyterlab/docregistry": "^0.9.0",
+    "@jupyterlab/services": "^0.48.0",
+    "@phosphor/disposable": "^1.1.1"
   },
   "devDependencies": {
     "rimraf": "^2.5.2",

+ 51 - 0
packages/docmanager-extension/src/index.ts

@@ -9,14 +9,26 @@ import {
   ICommandPalette, IMainMenu
 } from '@jupyterlab/apputils';
 
+import {
+  IChangedArgs
+} from '@jupyterlab/coreutils';
+
 import {
   renameDialog, DocumentManager, IDocumentManager, showErrorMessage
 } from '@jupyterlab/docmanager';
 
+import {
+  DocumentRegistry
+} from '@jupyterlab/docregistry';
+
 import {
   Contents, Kernel
 } from '@jupyterlab/services';
 
+import {
+  IDisposable
+} from '@phosphor/disposable';
+
 
 /**
  * The command IDs used by the document manager plugin.
@@ -63,6 +75,7 @@ const plugin: JupyterLabPlugin<IDocumentManager> = {
   requires: [ICommandPalette, IMainMenu],
   activate: (app: JupyterLab, palette: ICommandPalette, mainMenu: IMainMenu): IDocumentManager => {
     const manager = app.serviceManager;
+    const contexts = new WeakSet<DocumentRegistry.Context>();
     const opener: DocumentManager.IWidgetOpener = {
       open: widget => {
         if (!widget.id) {
@@ -76,6 +89,13 @@ const plugin: JupyterLabPlugin<IDocumentManager> = {
           app.shell.addToMainArea(widget);
         }
         app.shell.activateById(widget.id);
+
+        // Handle dirty state for open documents.
+        let context = docManager.contextForWidget(widget);
+        if (!contexts.has(context)) {
+          handleContext(app, context);
+          contexts.add(context);
+        }
       }
     };
     const registry = app.docRegistry;
@@ -243,6 +263,37 @@ function addCommands(app: JupyterLab, docManager: IDocumentManager, palette: ICo
 }
 
 
+/**
+ * Handle dirty state for a context.
+ */
+function handleContext(app: JupyterLab, context: DocumentRegistry.Context): void {
+  let disposable: IDisposable | null = null;
+  let onStateChanged = (sender: any, args: IChangedArgs<any>) => {
+    if (args.name === 'dirty') {
+      if (args.newValue === true) {
+        if (!disposable) {
+          disposable = app.setDirty();
+        }
+      } else if (disposable) {
+        disposable.dispose();
+        disposable = null;
+      }
+    }
+  };
+  context.ready.then(() => {
+    context.model.stateChanged.connect(onStateChanged);
+    if (context.model.dirty) {
+      disposable = app.setDirty();
+    }
+  });
+  context.disposed.connect(() => {
+    if (disposable) {
+      disposable.dispose();
+    }
+  });
+}
+
+
 /**
  * A namespace for private module data.
  */