Browse Source

Merge pull request #90 from blink1073/notebook-saving

Notebook Saving
Dave Willmer 9 years ago
parent
commit
e62e8f3a63
4 changed files with 85 additions and 64 deletions
  1. 3 3
      package.json
  2. 1 1
      src/filebrowser/plugin.ts
  3. 16 15
      src/imagehandler/plugin.ts
  4. 65 45
      src/notebook/plugin.ts

+ 3 - 3
package.json

@@ -8,9 +8,9 @@
     "codemirror": "^5.10.0",
     "jquery": "^2.2.0",
     "jquery-ui": "^1.10.5",
-    "jupyter-js-docmanager": "^0.2.1",
-    "jupyter-js-filebrowser": "^0.9.0",
-    "jupyter-js-notebook": "^0.5.17",
+    "jupyter-js-docmanager": "^0.4.1",
+    "jupyter-js-filebrowser": "^0.10.0",
+    "jupyter-js-notebook": "^0.7.1",
     "jupyter-js-services": "^0.5.0",
     "jupyter-js-terminal": "^0.1.15",
     "jupyter-js-utils": "^0.3.0",

+ 1 - 1
src/filebrowser/plugin.ts

@@ -217,7 +217,7 @@ function activateFileBrowser(app: Application, manager: DocumentManager, provide
   }
 
   let id = 0;
-  widget.openRequested.connect((browser, model) => onOpenRequested(model));
+  model.openRequested.connect((bModel, model) => onOpenRequested(model));
 
   widget.title.text = 'Files';
   widget.id = 'file-browser';

+ 16 - 15
src/imagehandler/plugin.ts

@@ -7,7 +7,7 @@ import {
 } from 'jupyter-js-docmanager';
 
 import {
-  IContentsModel
+  IContentsModel, IContentsOpts
 } from 'jupyter-js-services';
 
 import {
@@ -39,7 +39,7 @@ const imageHandlerExtension = {
 
 
 export
-class ImageHandler extends AbstractFileHandler {
+class ImageHandler extends AbstractFileHandler<Widget> {
   /**
    * Get the list of file extensions explicitly supported by the handler.
    */
@@ -49,10 +49,17 @@ class ImageHandler extends AbstractFileHandler {
   }
 
   /**
-   * Get file contents given a path.
+   * Get the options used to save the widget content.
    */
-  protected getContents(model: IContentsModel): Promise<IContentsModel> {
-    return this.manager.get(model.path, { type: 'file' });
+  protected getFetchOptions(model: IContentsModel): IContentsOpts {
+    return { type: 'file' };
+  }
+
+  /**
+   * Get the options used to save the widget content.
+   */
+  protected getSaveOptions(widget: Widget, model: IContentsModel): Promise<IContentsModel> {
+    return Promise.resolve(model);
   }
 
   /**
@@ -73,8 +80,8 @@ class ImageHandler extends AbstractFileHandler {
  /**
   * Populate a widget from `IContentsModel`.
   */
-  protected setState(widget: Widget, model: IContentsModel): Promise<void> {
-    return new Promise<void>((resolve, reject) => {
+  protected populateWidget(widget: Widget, model: IContentsModel): Promise<IContentsModel> {
+    return new Promise<IContentsModel>((resolve, reject) => {
       let img = widget.node.firstChild as HTMLImageElement;
       img.addEventListener('load', () => {
         resolve(void 0);
@@ -82,14 +89,8 @@ class ImageHandler extends AbstractFileHandler {
       img.addEventListener('error', error => {
         reject(error);
       });
-      img.src = `data:${model.mimetype};${model.format},${model.content}`;;
+      img.src = `data:${model.mimetype};${model.format},${model.content}`;
+      return model;
     });
   }
-
-  /**
-   * Get the state of the Widget, returns `undefined`.
-   */
-  protected getState(widget: Widget, model: IContentsModel): Promise<IContentsModel> {
-    return Promise.resolve(void 0);
-  }
 }

+ 65 - 45
src/notebook/plugin.ts

@@ -7,7 +7,7 @@ import {
 } from 'jupyter-js-docmanager';
 
 import {
-  NotebookWidget, NotebookModel, NBData, populateNotebookModel, buildOutputModel, Output, INotebookModel
+  NotebookWidget, NotebookModel, populateNotebookModel, buildOutputModel, Output, INotebookModel, getNotebookContent
 } from 'jupyter-js-notebook';
 
 import {
@@ -15,7 +15,7 @@ import {
 } from 'jupyter-js-notebook/lib/cells';
 
 import {
-  IContentsModel, IContentsManager,
+  IContentsModel, IContentsManager, IContentsOpts,
   NotebookSessionManager, INotebookSessionManager,
   INotebookSession, IKernelMessage, IComm
 } from 'jupyter-js-services';
@@ -52,12 +52,6 @@ let selectPreviousCellCommandId = 'notebook:select-previous-cell';
 let notebookContainerClass = 'jp-NotebookContainer';
 
 
-/**
- * The class name added to a dirty documents.
- */
-const DIRTY_CLASS = 'jp-mod-dirty';
-
-
 /**
  * The notebook file handler provider.
  */
@@ -119,7 +113,6 @@ class NotebookContainer extends Panel {
   constructor() {
     super();
     this._model = new NotebookModel();
-    this._model.stateChanged.connect(this._onModelChanged, this);
     let widgetarea = new Widget();
     this._manager = new WidgetManager(widgetarea.node);
     let widget = new NotebookWidget(this._model);
@@ -174,16 +167,6 @@ class NotebookContainer extends Panel {
     this._session.kernel.registerCommTarget('jupyter.widget', commHandler);
   }
 
-  private _onModelChanged(model: INotebookModel, args: IChangedArgs<INotebookModel>): void {
-    if (args.name === 'dirty') {
-      if (args.newValue) {
-        this.addClass(DIRTY_CLASS);
-      } else {
-        this.removeClass(DIRTY_CLASS);
-      }
-    }
-  }
-
   private _model: INotebookModel = null;
   private _session: INotebookSession = null;
   private _manager: WidgetManager = null;
@@ -193,8 +176,7 @@ class NotebookContainer extends Panel {
 /**
  * An implementation of a file handler.
  */
-export
-class NotebookFileHandler extends AbstractFileHandler {
+class NotebookFileHandler extends AbstractFileHandler<NotebookContainer> {
 
   constructor(contents: IContentsManager, session: INotebookSessionManager) {
     super(contents);
@@ -212,7 +194,7 @@ class NotebookFileHandler extends AbstractFileHandler {
    * Run the selected cell on the active widget.
    */
   runSelectedCell(): void {
-    let w = this.activeWidget as NotebookContainer;
+    let w = this.activeWidget;
     if (w) w.model.runSelectedCell();
   }
 
@@ -220,7 +202,7 @@ class NotebookFileHandler extends AbstractFileHandler {
    * Select the next cell on the active widget.
    */
   selectNextCell(): void {
-    let w = this.activeWidget as NotebookContainer;
+    let w = this.activeWidget;
     if (w) w.model.selectNextCell();
   }
 
@@ -228,28 +210,59 @@ class NotebookFileHandler extends AbstractFileHandler {
    * Select the previous cell on the active widget.
    */
   selectPreviousCell(): void {
-    let w = this.activeWidget as NotebookContainer;
+    let w = this.activeWidget;
     if (w) w.model.selectPreviousCell();
   }
 
   /**
-   * Get file contents given a contents model.
+   * Set the dirty state of a widget (defaults to current active widget).
    */
-  protected getContents(model: IContentsModel): Promise<IContentsModel> {
-    return this.manager.get(model.path, { type: 'notebook' });
+  setDirty(widget?: NotebookContainer): void {
+    super.setDirty(widget);
+    widget = this.resolveWidget(widget);
+    if (widget) {
+      widget.model.dirty = true;
+    }
+  }
+
+  /**
+   * Clear the dirty state of a widget (defaults to current active widget).
+   */
+  clearDirty(widget?: NotebookContainer): void {
+    super.clearDirty(widget);
+    widget = this.resolveWidget(widget);
+    if (widget) {
+      widget.model.dirty = false;
+    }
+  }
+
+  /**
+   * Get options use to fetch the model contents from disk.
+   */
+  protected getFetchOptions(model: IContentsModel): IContentsOpts {
+    return { type: 'notebook' };
+  }
+
+  /**
+   * Get the options used to save the widget content.
+   */
+  protected getSaveOptions(widget: NotebookContainer, model: IContentsModel): Promise<IContentsOpts> {
+      let content = getNotebookContent(widget.model);
+      return Promise.resolve({ type: 'notebook', content });
   }
 
   /**
    * Create the widget from an `IContentsModel`.
    */
-  protected createWidget(contents: IContentsModel): Widget {
+  protected createWidget(contents: IContentsModel): NotebookContainer {
     let panel = new NotebookContainer();
+    panel.model.stateChanged.connect(this._onModelChanged, this);
     panel.title.text = contents.name;
     panel.addClass(notebookContainerClass);
 
     this.session.startNew({notebookPath: contents.path}).then(s => {
       panel.setSession(s);
-    })
+    });
 
     return panel;
   }
@@ -257,28 +270,35 @@ class NotebookFileHandler extends AbstractFileHandler {
   /**
    * Populate the notebook widget with the contents of the notebook.
    */
-  protected setState(widget: Widget, model: IContentsModel): Promise<void> {
-    let nbData: NBData = {
-      content: model.content,
-      name: model.name,
-      path: model.path
+  protected populateWidget(widget: NotebookContainer, model: IContentsModel): Promise<IContentsModel> {
+    populateNotebookModel(widget.model, model.content);
+    if (widget.model.cells.length === 0) {
+      let cell = widget.model.createCodeCell();
+      widget.model.cells.add(cell);
     }
-    let nbWidget: NotebookWidget = ((widget as Panel).childAt(1)) as NotebookWidget;
-    populateNotebookModel(nbWidget.model, nbData);
-    if (nbWidget.model.cells.length === 0) {
-      let cell = nbWidget.model.createCodeCell();
-      nbWidget.model.cells.add(cell);
-    }
-    nbWidget.model.selectedCellIndex = 0;
+    widget.model.selectedCellIndex = 0;
 
-    return Promise.resolve();
+    return Promise.resolve(model);
   }
 
   /**
-   * Get the current state of the notebook.
+   * Handle changes to the model state of a widget.
    */
-  protected getState(widget: Widget, model: IContentsModel): Promise<IContentsModel> {
-    return Promise.resolve(void 0);
+  private _onModelChanged(model: INotebookModel, args: IChangedArgs<INotebookModel>): void {
+    if (args.name !== 'dirty') {
+      return;
+    }
+    for (let i = 0; i < this.widgetCount(); i++) {
+      let widget = this.widgetAt(i);
+      if (widget.model === model) {
+        if (args.newValue) {
+          this.setDirty(widget);
+        } else {
+          this.clearDirty(widget);
+        }
+        return;
+      }
+    }
   }
 
   session: INotebookSessionManager;