Browse Source

Merge pull request #2037 from afshin/single-document

Single document UI
Steven Silvester 8 years ago
parent
commit
f11e1e3e59

+ 1 - 0
examples/app/index.js

@@ -34,6 +34,7 @@ var mods = [
   require('@jupyterlab/running-extension'),
   require('@jupyterlab/services-extension'),
   require('@jupyterlab/shortcuts-extension'),
+  require('@jupyterlab/tabmanager-extension'),
   require('@jupyterlab/terminal-extension'),
   require('@jupyterlab/tooltip-extension')
 ];

+ 1 - 0
examples/app/package.json

@@ -31,6 +31,7 @@
     "@jupyterlab/running-extension": "^0.2.0",
     "@jupyterlab/services-extension": "^0.2.0",
     "@jupyterlab/shortcuts-extension": "^0.2.0",
+    "@jupyterlab/tabmanager-extension": "^0.2.0",
     "@jupyterlab/terminal-extension": "^0.2.0",
     "@jupyterlab/tooltip-extension": "^0.2.0",
     "es6-promise": "^4.1.0",

+ 1 - 0
jupyterlab/package.json

@@ -33,6 +33,7 @@
     "@jupyterlab/running-extension": "^0.2.0",
     "@jupyterlab/services-extension": "^0.2.0",
     "@jupyterlab/shortcuts-extension": "^0.2.0",
+    "@jupyterlab/tabmanager-extension": "^0.2.0",
     "@jupyterlab/terminal-extension": "^0.2.0",
     "@jupyterlab/tooltip-extension": "^0.2.0",
     "es6-promise": "^4.1.0",

+ 4 - 0
jupyterlab/src/main.ts

@@ -76,6 +76,9 @@ import * as servicesExtension
 import * as shortcutsExtension
   from '@jupyterlab/shortcuts-extension';
 
+import * as tabmanagerExtension
+  from '@jupyterlab/tabmanager-extension';
+
 import * as terminalExtension
   from '@jupyterlab/terminal-extension';
 
@@ -110,6 +113,7 @@ const mods: JupyterLab.IPluginModule[] = [
   runningExtension,
   servicesExtension,
   shortcutsExtension,
+  tabmanagerExtension,
   terminalExtension,
   tooltipExtension,
 ];

+ 48 - 12
packages/application-extension/src/index.ts

@@ -14,14 +14,20 @@ import {
  * The command IDs used by the application plugin.
  */
 namespace CommandIDs {
+  export
+  const activateNextTab: string = 'main-jupyterlab:activate-next-tab';
+
+  export
+  const activatePreviousTab: string = 'main-jupyterlab:activate-previous-tab';
+
   export
   const closeAll: string = 'main-jupyterlab:close-all';
 
   export
-  const activateNextTab: string = 'main-jupyterlab:activate-next-tab';
+  const setMode: string = 'main-jupyterlab:set-mode';
 
   export
-  const activatePreviousTab: string = 'main-jupyterlab:activate-previous-tab';
+  const toggleMode: string = 'main-jupyterlab:toggle-mode';
 };
 
 
@@ -32,27 +38,57 @@ const plugin: JupyterLabPlugin<void> = {
   id: 'jupyter.extensions.main',
   requires: [ICommandPalette],
   activate: (app: JupyterLab, palette: ICommandPalette) => {
+    const category = 'Main Area';
+    let command = CommandIDs.activateNextTab;
+    app.commands.addCommand(command, {
+      label: 'Activate Next Tab',
+      execute: () => { app.shell.activateNextTab(); }
+    });
+    palette.addItem({ command, category });
 
-    let command = CommandIDs.closeAll;
+    command = CommandIDs.activatePreviousTab;
+    app.commands.addCommand(command, {
+      label: 'Activate Previous Tab',
+      execute: () => { app.shell.activatePreviousTab(); }
+    });
+    palette.addItem({ command, category });
+
+    command = CommandIDs.closeAll;
     app.commands.addCommand(command, {
       label: 'Close All Widgets',
       execute: () => { app.shell.closeAll(); }
     });
-    palette.addItem({ command, category: 'Main Area' });
+    palette.addItem({ command, category });
 
-    command = CommandIDs.activateNextTab;
+    command = CommandIDs.setMode;
     app.commands.addCommand(command, {
-      label: 'Activate Next Tab',
-      execute: () => { app.shell.activateNextTab(); }
+      isVisible: args => {
+        const mode = args['mode'] as string;
+        return mode === 'single-document' || mode === 'multiple-document';
+      },
+      execute: args => {
+        const mode = args['mode'] as string;
+        if (mode === 'single-document' || mode === 'multiple-document') {
+          app.shell.mode = mode;
+          return;
+        }
+        throw new Error(`Unsupported application shell mode: ${mode}`);
+      }
     });
-    palette.addItem({ command, category: 'Main Area' });
 
-    command = CommandIDs.activatePreviousTab;
+    command = CommandIDs.toggleMode;
     app.commands.addCommand(command, {
-      label: 'Activate Previous Tab',
-      execute: () => { app.shell.activatePreviousTab(); }
+      label: () => {
+        return app.shell.mode === 'multiple-document' ?
+          'Enable Single-Document Mode' : 'Enable Multiple-Document Mode';
+      },
+      execute: () => {
+        const args = app.shell.mode === 'multiple-document' ?
+          { mode: 'single-document' } : { mode: 'multiple-document' };
+        return app.commands.execute(CommandIDs.setMode, args);
+      }
     });
-    palette.addItem({ command, category: 'Main Area' });
+    palette.addItem({ command, category });
 
     const message = 'Are you sure you want to exit JupyterLab?\n' +
                     'Any unsaved changes will be lost.';

+ 92 - 36
packages/application/src/shell.ts

@@ -2,7 +2,7 @@
 // Distributed under the terms of the Modified BSD License.
 
 import {
-  ArrayExt, each, find, toArray
+  ArrayExt, each, find, IIterator, iter, toArray
 } from '@phosphor/algorithm';
 
 import {
@@ -44,6 +44,12 @@ const CURRENT_CLASS = 'jp-mod-current';
 const ACTIVE_CLASS = 'jp-mod-active';
 
 
+/**
+ * The default rank of items added to a sidebar.
+ */
+const DEFAULT_RANK = 500;
+
+
 /**
  * The application shell for JupyterLab.
  */
@@ -117,21 +123,31 @@ class ApplicationShell extends Widget {
     this._tracker.activeChanged.connect(this._onActiveChanged, this);
 
     // Connect main layout change listener.
-    this._dockPanel.layoutModified.connect(this._save, this);
+    this._dockPanel.layoutModified.connect(() => {
+      this._layoutModified.emit(void 0);
+      this._save();
+    }, this);
   }
 
   /**
-   * A signal emitted when main area's current focus changes.
+   * A signal emitted when main area's active focus changes.
    */
-  get currentChanged(): ISignal<this, ApplicationShell.IChangedArgs> {
-    return this._currentChanged;
+  get activeChanged(): ISignal<this, ApplicationShell.IChangedArgs> {
+    return this._activeChanged;
   }
 
   /**
-   * A signal emitted when main area's active focus changes.
+   * The active widget in the shell's main area.
    */
-  get activeChanged(): ISignal<this, ApplicationShell.IChangedArgs> {
-    return this._activeChanged;
+  get activeWidget(): Widget | null {
+    return this._tracker.activeWidget;
+  }
+
+  /**
+   * A signal emitted when main area's current focus changes.
+   */
+  get currentChanged(): ISignal<this, ApplicationShell.IChangedArgs> {
+    return this._currentChanged;
   }
 
   /**
@@ -142,28 +158,20 @@ class ApplicationShell extends Widget {
   }
 
   /**
-   * The active widget in the shell's main area.
+   * A signal emitted when the main area's layout is modified.
    */
-  get activeWidget(): Widget | null {
-    return this._tracker.activeWidget;
+  get layoutModified(): ISignal<this, void> {
+    return this._layoutModified;
   }
 
   /**
-   * True if the given area is empty.
+   * The main dock area's user interface mode.
    */
-  isEmpty(area: ApplicationShell.Area): boolean {
-    switch (area) {
-    case 'left':
-      return this._leftHandler.stackedPanel.widgets.length === 0;
-    case 'main':
-      return this._dockPanel.isEmpty;
-    case 'top':
-      return this._topPanel.widgets.length === 0;
-    case 'right':
-      return this._rightHandler.stackedPanel.widgets.length === 0;
-    default:
-      return true;
-    }
+  get mode(): DockPanel.Mode {
+    return this._dockPanel.mode;
+  }
+  set mode(mode: DockPanel.Mode) {
+    this._dockPanel.mode = mode;
   }
 
   /**
@@ -174,7 +182,7 @@ class ApplicationShell extends Widget {
   }
 
   /**
-   * Activate a widget in it's area.
+   * Activate a widget in its area.
    */
   activateById(id: string): void {
     if (this._leftHandler.has(id)) {
@@ -260,7 +268,7 @@ class ApplicationShell extends Widget {
       console.error('widgets added to app shell must have unique id property');
       return;
     }
-    let rank = 'rank' in options ? options.rank : 100;
+    let rank = 'rank' in options ? options.rank : DEFAULT_RANK;
     this._leftHandler.addWidget(widget, rank);
     this._save();
   }
@@ -293,7 +301,7 @@ class ApplicationShell extends Widget {
       console.error('widgets added to app shell must have unique id property');
       return;
     }
-    let rank = 'rank' in options ? options.rank : 100;
+    let rank = 'rank' in options ? options.rank : DEFAULT_RANK;
     this._rightHandler.addWidget(widget, rank);
     this._save();
   }
@@ -340,6 +348,24 @@ class ApplicationShell extends Widget {
     each(toArray(this._dockPanel.widgets()), widget => { widget.close(); });
   }
 
+  /**
+   * True if the given area is empty.
+   */
+  isEmpty(area: ApplicationShell.Area): boolean {
+    switch (area) {
+    case 'left':
+      return this._leftHandler.stackedPanel.widgets.length === 0;
+    case 'main':
+      return this._dockPanel.isEmpty;
+    case 'top':
+      return this._topPanel.widgets.length === 0;
+    case 'right':
+      return this._rightHandler.stackedPanel.widgets.length === 0;
+    default:
+      return true;
+    }
+  }
+
   /**
    * Set the layout data store for the application shell.
    */
@@ -357,11 +383,16 @@ class ApplicationShell extends Widget {
 
       // Rehydrate the main area.
       if (mainArea) {
-        if (mainArea.dock) {
-          this._dockPanel.restoreLayout(mainArea.dock);
+        const { currentWidget, dock, mode } = mainArea;
+
+        if (dock) {
+          this._dockPanel.restoreLayout(dock);
         }
-        if (mainArea.currentWidget) {
-          this.activateById(mainArea.currentWidget.id);
+        if (currentWidget) {
+          this.activateById(currentWidget.id);
+        }
+        if (mode) {
+          this._dockPanel.mode = mode;
         }
       }
 
@@ -392,6 +423,24 @@ class ApplicationShell extends Widget {
     this._rightHandler.sideBar.currentChanged.connect(this._save, this);
   }
 
+  /**
+   * Returns the widgets for an application area.
+   */
+  widgets(area: ApplicationShell.Area): IIterator<Widget> {
+    switch (area) {
+      case 'main':
+        return this._dockPanel.widgets();
+      case 'left':
+        return iter(this._leftHandler.sideBar.titles.map(t => t.owner));
+      case 'right':
+        return iter(this._rightHandler.sideBar.titles.map(t => t.owner));
+      case 'top':
+        return this._topPanel.children();
+      default:
+        break;
+    }
+  }
+
   /*
    * Return the TabBar that has the currently active Widget or null.
    */
@@ -465,7 +514,8 @@ class ApplicationShell extends Widget {
     let data: ApplicationShell.ILayout = {
       mainArea: {
         currentWidget: this._tracker.currentWidget,
-        dock: this._dockPanel.saveLayout()
+        dock: this._dockPanel.saveLayout(),
+        mode: this._dockPanel.mode
       },
       leftArea: this._leftHandler.dehydrate(),
       rightArea: this._rightHandler.dehydrate()
@@ -503,18 +553,19 @@ class ApplicationShell extends Widget {
     this._activeChanged.emit(args);
   }
 
+  private _activeChanged = new Signal<this, ApplicationShell.IChangedArgs>(this);
+  private _currentChanged = new Signal<this, ApplicationShell.IChangedArgs>(this);
   private _database: ApplicationShell.ILayoutDB = null;
   private _dockPanel: DockPanel;
   private _hboxPanel: BoxPanel;
   private _hsplitPanel: SplitPanel;
   private _isRestored = false;
+  private _layoutModified = new Signal<this, void>(this);
   private _leftHandler: Private.SideBarHandler;
   private _restored = new PromiseDelegate<ApplicationShell.ILayout>();
   private _rightHandler: Private.SideBarHandler;
-  private _topPanel: Panel;
   private _tracker = new FocusTracker<Widget>();
-  private _currentChanged = new Signal<this, ApplicationShell.IChangedArgs>(this);
-  private _activeChanged = new Signal<this, ApplicationShell.IChangedArgs>(this);
+  private _topPanel: Panel;
 }
 
 
@@ -607,6 +658,11 @@ namespace ApplicationShell {
      * The contents of the main application dock panel.
      */
     readonly dock: DockLayout.ILayoutConfig | null;
+
+    /**
+     * The document mode (i.e., multiple/single) of the main dock panel.
+     */
+    readonly mode: DockPanel.Mode | null;
   };
 
   /**

+ 17 - 6
packages/apputils/src/layoutrestorer.ts

@@ -16,7 +16,7 @@ import {
 } from '@phosphor/properties';
 
 import {
-  Widget
+  DockPanel, Widget
 } from '@phosphor/widgets';
 
 import {
@@ -445,6 +445,11 @@ namespace Private {
      * The main application dock panel.
      */
     dock?: ISplitArea | ITabArea | null;
+
+    /**
+     * The document mode (i.e., multiple/single) of the main dock panel.
+     */
+    mode?: DockPanel.Mode | null;
   }
 
   /**
@@ -558,10 +563,13 @@ namespace Private {
     let dehydrated: IMainArea = {
       dock: area && area.dock && serializeArea(area.dock.main) || null
     };
-    if (area && area.currentWidget) {
-      let current = Private.nameProperty.get(area.currentWidget);
-      if (current) {
-        dehydrated.current = current;
+    if (area) {
+      dehydrated.mode = area.mode;
+      if (area.currentWidget) {
+        let current = Private.nameProperty.get(area.currentWidget);
+        if (current) {
+          dehydrated.current = current;
+        }
       }
     }
     return dehydrated;
@@ -637,10 +645,13 @@ namespace Private {
 
     const name = (area as any).current || null;
     const dock = (area as any).dock || null;
+    const mode = (area as any).mode || null;
 
     return {
       currentWidget: name && names.has(name) && names.get(name) || null,
-      dock: dock ? { main: deserializeArea(dock, names) } : null
+      dock: dock ? { main: deserializeArea(dock, names) } : null,
+      mode: mode === 'multiple-document' || mode === 'single-document' ? mode
+        : null
     };
   }
 }

+ 1 - 0
packages/default-theme/package.json

@@ -38,6 +38,7 @@
     "@jupyterlab/outputarea": "^0.2.0",
     "@jupyterlab/rendermime": "^0.2.0",
     "@jupyterlab/running": "^0.2.0",
+    "@jupyterlab/tabmanager-extension": "^0.2.0",
     "@jupyterlab/terminal": "^0.2.0",
     "@jupyterlab/tooltip": "^0.2.0"
   },

+ 1 - 0
packages/default-theme/style/index.css

@@ -32,6 +32,7 @@
 @import url('~@jupyterlab/outputarea/style/index.css');
 @import url('~@jupyterlab/rendermime/style/index.css');
 @import url('~@jupyterlab/running/style/index.css');
+@import url('~@jupyterlab/tabmanager-extension/style/index.css');
 @import url('~@jupyterlab/terminal/style/index.css');
 @import url('~@jupyterlab/tooltip/style/index.css');
 

+ 1 - 1
packages/filebrowser-extension/src/index.ts

@@ -191,7 +191,7 @@ function activateFileBrowser(app: JupyterLab, factory: IFileBrowserFactory, mana
   mainMenu.addMenu(menu, { rank: 1 });
 
   fbWidget.title.label = 'Files';
-  app.shell.addToLeftArea(fbWidget, { rank: 40 });
+  app.shell.addToLeftArea(fbWidget, { rank: 100 });
 
   // If the layout is a fresh session without saved data, open file browser.
   app.restored.then(layout => {

+ 2 - 1
packages/notebook-extension/src/index.ts

@@ -270,8 +270,9 @@ function activateCellTools(app: JupyterLab, tracker: INotebookTracker, editorSer
       case Widget.Msg.ActivateRequest:
         state.save(id, { open: true });
         break;
+      case Widget.Msg.AfterHide:
       case Widget.Msg.CloseRequest:
-        state.remove(celltools.id);
+        state.remove(id);
         break;
       default:
         break;

+ 1 - 1
packages/running-extension/src/index.ts

@@ -65,5 +65,5 @@ function activate(app: JupyterLab, services: IServiceManager, restorer: ILayoutR
 
   // Rank has been chosen somewhat arbitrarily to give priority to the running
   // sessions widget in the sidebar.
-  app.shell.addToLeftArea(running, { rank: 50 });
+  app.shell.addToLeftArea(running, { rank: 200 });
 }

+ 5 - 0
packages/shortcuts-extension/src/index.ts

@@ -40,6 +40,11 @@ const SHORTCUTS = [
     selector: 'body',
     keys: ['Ctrl Shift [']
   },
+  {
+    command: 'main-jupyterlab:toggle-mode',
+    selector: 'body',
+    keys: ['Accel Shift Enter']
+  },
   {
     command: 'command-palette:activate',
     selector: 'body',

+ 38 - 0
packages/tabmanager-extension/package.json

@@ -0,0 +1,38 @@
+{
+  "name": "@jupyterlab/tabmanager-extension",
+  "version": "0.2.0",
+  "description": "JupyterLab - Tab Manager Extension",
+  "main": "lib/index.js",
+  "types": "lib/index.d.ts",
+  "files": [
+    "lib/*.d.ts",
+    "lib/*.js",
+    "style/*.css"
+  ],
+  "directories": {
+    "lib": "lib/"
+  },
+  "dependencies": {
+    "@jupyterlab/application": "^0.2.0",
+    "@jupyterlab/apputils": "^0.2.0"
+  },
+  "devDependencies": {
+    "rimraf": "^2.5.2",
+    "typescript": "^2.2.1"
+  },
+  "scripts": {
+    "build": "tsc",
+    "clean": "rimraf lib",
+    "watch": "tsc -w"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/jupyterlab/jupyterlab.git"
+  },
+  "author": "Project Jupyter",
+  "license": "BSD-3-Clause",
+  "bugs": {
+    "url": "https://github.com/jupyterlab/jupyterlab/issues"
+  },
+  "homepage": "https://github.com/jupyterlab/jupyterlab"
+}

+ 65 - 0
packages/tabmanager-extension/src/index.ts

@@ -0,0 +1,65 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import {
+  JupyterLab, JupyterLabPlugin
+} from '@jupyterlab/application';
+
+import {
+  ILayoutRestorer
+} from '@jupyterlab/apputils';
+
+import {
+  each
+} from '@phosphor/algorithm';
+
+import {
+  TabBar, Widget
+} from '@phosphor/widgets';
+
+
+/**
+ * The default tab manager extension.
+ */
+const plugin: JupyterLabPlugin<void> = {
+  id: 'jupyter.extensions.tab-manager',
+  activate: (app: JupyterLab, restorer: ILayoutRestorer): void => {
+    const { shell } = app;
+    const tabs = new TabBar<Widget>({ orientation: 'vertical' });
+    const header = document.createElement('header');
+
+    restorer.add(tabs, 'tab-manager');
+    tabs.id = 'tab-manager';
+    tabs.title.label = 'Tabs';
+    header.textContent = 'Open Tabs';
+    tabs.node.insertBefore(header, tabs.contentNode);
+    shell.addToLeftArea(tabs, { rank: 600 });
+
+    app.restored.then(() => {
+      const populate = () => {
+        tabs.clearTabs();
+        each(shell.widgets('main'), widget => { tabs.addTab(widget.title); });
+      };
+
+      // Connect signal handlers.
+      shell.layoutModified.connect(() => { populate(); });
+      tabs.tabActivateRequested.connect((sender, tab) => {
+        shell.activateById(tab.title.owner.id);
+      });
+      tabs.tabCloseRequested.connect((sender, tab) => {
+        tab.title.owner.close();
+      });
+
+      // Populate the tab manager.
+      populate();
+    });
+  },
+  autoStart: true,
+  requires: [ILayoutRestorer]
+};
+
+
+/**
+ * Export the plugin as default.
+ */
+export default plugin;

+ 112 - 0
packages/tabmanager-extension/style/index.css

@@ -0,0 +1,112 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+/*-----------------------------------------------------------------------------
+| Variables
+|----------------------------------------------------------------------------*/
+
+:root {
+  --jp-private-tab-manager-active-top-border: 2px;
+  --jp-private-tab-manager-tab-height: 32px;
+  --jp-private-tab-manager-tab-padding-top: 8px;
+}
+
+
+/*-----------------------------------------------------------------------------
+| Tabs in the dock panel
+|----------------------------------------------------------------------------*/
+
+#tab-manager {
+  background: var(--jp-layout-color0);
+  overflow: visible;
+  color: var(--jp-ui-font-color1);
+  font-size: var(--jp-ui-font-size1);
+}
+
+
+#tab-manager header {
+  border-bottom: var(--jp-border-width) solid var(--jp-border-color2);
+  flex: 0 0 auto;
+  font-size: var(--jp-ui-font-size0);
+  font-weight: 600;
+  letter-spacing: 1px;
+  margin: 4px 0px;
+  padding: 4px 12px;
+  text-transform: uppercase;
+}
+
+
+#tab-manager .p-TabBar-tab {
+  margin-left: calc(-1*var(--jp-border-width));
+  height: var(--jp-private-tab-manager-tab-height);
+  padding: 0px 8px;
+  border-top: var(--jp-border-width) solid transparent;
+  border-bottom: var(--jp-border-width) solid transparent;
+  position: relative;
+  overflow: visible;
+}
+
+
+#tab-manager .p-TabBar-tab:hover:not(.jp-mod-active) {
+  background: var(--jp-layout-color2);
+}
+
+
+#tab-manager .p-TabBar-tab:first-child {
+  margin-left: 0;
+}
+
+
+#tab-manager .p-TabBar-tab.jp-mod-active {
+  border-top: var(--jp-border-width) solid var(--jp-brand-color1);
+  border-bottom: var(--jp-border-width) solid var(--jp-brand-color1);
+  color: var(--jp-ui-font-color0);
+}
+
+
+#tab-manager .p-TabBar-tabIcon,
+#tab-manager .p-TabBar-tabLabel,
+#tab-manager .p-TabBar-tabCloseIcon {
+  display: inline-block;
+}
+
+
+#tab-manager .p-TabBar-tabLabel {
+  padding-top: var(--jp-private-tab-manager-tab-padding-top);
+}
+
+
+#tab-manager .p-TabBar-tab .p-TabBar-tabIcon {
+  width: 14px;
+  background-position: left center;
+  background-repeat: no-repeat;
+  margin-right: 2px;
+}
+
+#tab-manager .p-TabBar-tab.p-mod-current .p-TabBar-tabIcon {
+  margin-bottom: var(--jp-border-width);
+}
+
+#tab-manager .p-TabBar-tab.p-mod-closable > .p-TabBar-tabCloseIcon {
+  margin-left: 4px;
+  padding-top: 12px;
+  background-size: 16px;
+  height: 16px;
+  width: 16px;
+  background-image: var(--jp-icon-close);
+  background-position: center;
+  background-repeat: no-repeat;
+}
+
+
+#tab-manager .p-TabBar-tab.p-mod-closable.jp-mod-dirty > .p-TabBar-tabCloseIcon {
+  background-size: 10px;
+  background-image: var(--jp-icon-circle);
+}
+
+#tab-manager .p-TabBar-tab.p-mod-closable > .p-TabBar-tabCloseIcon:hover {
+  background-size: 16px;
+  background-image: var(--jp-icon-close-black);
+}

+ 15 - 0
packages/tabmanager-extension/tsconfig.json

@@ -0,0 +1,15 @@
+{
+  "compilerOptions": {
+    "declaration": true,
+    "noImplicitAny": true,
+    "noEmitOnError": true,
+    "noUnusedLocals": true,
+    "module": "commonjs",
+    "moduleResolution": "node",
+    "target": "ES5",
+    "outDir": "./lib",
+    "lib": ["ES5", "ES2015.Promise", "DOM", "ES2015.Collection"],
+    "types": []
+  },
+  "include": ["src/*"]
+}

+ 7 - 5
test/src/apputils/layoutrestorer.spec.ts

@@ -12,7 +12,7 @@ import {
 } from '@phosphor/commands';
 
 import {
-  Widget
+  DockPanel, Widget
 } from '@phosphor/widgets';
 
 import {
@@ -82,8 +82,9 @@ describe('apputils', () => {
           state: new StateDB({ namespace: NAMESPACE })
         });
         let currentWidget = new Widget();
+        let mode: DockPanel.Mode = 'single-document';
         let dehydrated: ApplicationShell.ILayout = {
-          mainArea: { currentWidget, dock: null },
+          mainArea: { currentWidget, dock: null, mode },
           leftArea: { collapsed: true, currentWidget: null, widgets: null },
           rightArea: { collapsed: true, currentWidget: null, widgets: null }
         };
@@ -93,6 +94,7 @@ describe('apputils', () => {
           .then(() => restorer.fetch())
           .then(layout => {
             expect(layout.mainArea.currentWidget).to.be(currentWidget);
+            expect(layout.mainArea.mode).to.be(mode);
             done();
           }).catch(done);
       });
@@ -124,7 +126,7 @@ describe('apputils', () => {
         // The `fresh` attribute is only here to check against the return value.
         let dehydrated: ApplicationShell.ILayout = {
           fresh: false,
-          mainArea: { currentWidget: null, dock: null },
+          mainArea: { currentWidget: null, dock: null, mode: null },
           leftArea: {
             currentWidget,
             collapsed: true,
@@ -188,7 +190,7 @@ describe('apputils', () => {
           state: new StateDB({ namespace: NAMESPACE })
         });
         let dehydrated: ApplicationShell.ILayout = {
-          mainArea: { currentWidget: null, dock: null },
+          mainArea: { currentWidget: null, dock: null, mode: null },
           leftArea: { currentWidget: null, collapsed: true, widgets: null },
           rightArea: { collapsed: true, currentWidget: null, widgets: null }
         };
@@ -208,7 +210,7 @@ describe('apputils', () => {
         // The `fresh` attribute is only here to check against the return value.
         let dehydrated: ApplicationShell.ILayout = {
           fresh: false,
-          mainArea: { currentWidget: null, dock: null },
+          mainArea: { currentWidget: null, dock: null, mode: null },
           leftArea: {
             currentWidget,
             collapsed: true,