@jupyterlab/application 4.0.0-alpha.9 → 4.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/shell.js CHANGED
@@ -2,9 +2,9 @@
2
2
  // Distributed under the terms of the Modified BSD License.
3
3
  import { DocumentWidget } from '@jupyterlab/docregistry';
4
4
  import { nullTranslator } from '@jupyterlab/translation';
5
- import { classes, DockPanelSvg, LabIcon, TabPanelSvg } from '@jupyterlab/ui-components';
6
- import { ArrayExt, find, iter, toArray } from '@lumino/algorithm';
7
- import { PromiseDelegate, Token } from '@lumino/coreutils';
5
+ import { classes, DockPanelSvg, LabIcon, TabBarSvg, tabIcon, TabPanelSvg } from '@jupyterlab/ui-components';
6
+ import { ArrayExt, find, map } from '@lumino/algorithm';
7
+ import { JSONExt, PromiseDelegate, Token } from '@lumino/coreutils';
8
8
  import { MessageLoop } from '@lumino/messaging';
9
9
  import { Debouncer } from '@lumino/polling';
10
10
  import { Signal } from '@lumino/signaling';
@@ -75,11 +75,15 @@ export class LabShell extends Widget {
75
75
  this._restored = new PromiseDelegate();
76
76
  this._tracker = new FocusTracker();
77
77
  this._topHandlerHiddenByUser = false;
78
+ this._idTypeMap = new Map();
78
79
  this._mainOptionsCache = new Map();
79
80
  this._sideOptionsCache = new Map();
81
+ this._delayedWidget = new Array();
80
82
  this.addClass(APPLICATION_SHELL_CLASS);
81
83
  this.id = 'main';
82
- const trans = ((options && options.translator) || nullTranslator).load('jupyterlab');
84
+ if ((options === null || options === void 0 ? void 0 : options.waitForRestore) === false) {
85
+ this._userLayout = { 'multiple-document': {}, 'single-document': {} };
86
+ }
83
87
  // Skip Links
84
88
  const skipLinkWidget = (this._skipLinkWidget = new Private.SkipLinkWidget(this));
85
89
  this._skipLinkWidget.show();
@@ -90,19 +94,19 @@ export class LabShell extends Widget {
90
94
  const headerPanel = (this._headerPanel = new BoxPanel());
91
95
  const menuHandler = (this._menuHandler = new Private.PanelHandler());
92
96
  menuHandler.panel.node.setAttribute('role', 'navigation');
93
- menuHandler.panel.node.setAttribute('aria-label', trans.__('main'));
94
97
  const topHandler = (this._topHandler = new Private.PanelHandler());
95
98
  topHandler.panel.node.setAttribute('role', 'banner');
96
99
  const bottomPanel = (this._bottomPanel = new BoxPanel());
97
100
  bottomPanel.node.setAttribute('role', 'contentinfo');
98
101
  const hboxPanel = new BoxPanel();
99
- const vsplitPanel = (this._vsplitPanel = new Private.RestorableSplitPanel());
102
+ const vsplitPanel = (this._vsplitPanel =
103
+ new Private.RestorableSplitPanel());
100
104
  const dockPanel = (this._dockPanel = new DockPanelSvg({
101
- hiddenMode: Widget.HiddenMode.Scale,
102
- translator: options === null || options === void 0 ? void 0 : options.translator
105
+ hiddenMode: Widget.HiddenMode.Display
103
106
  }));
104
107
  MessageLoop.installMessageHook(dockPanel, this._dockChildHook);
105
- const hsplitPanel = (this._hsplitPanel = new Private.RestorableSplitPanel());
108
+ const hsplitPanel = (this._hsplitPanel =
109
+ new Private.RestorableSplitPanel());
106
110
  const downPanel = (this._downPanel = new TabPanelSvg({
107
111
  tabsMovable: true
108
112
  }));
@@ -120,14 +124,10 @@ export class LabShell extends Widget {
120
124
  downPanel.id = 'jp-down-stack';
121
125
  leftHandler.sideBar.addClass(SIDEBAR_CLASS);
122
126
  leftHandler.sideBar.addClass('jp-mod-left');
123
- leftHandler.sideBar.node.setAttribute('aria-label', trans.__('main sidebar'));
124
- leftHandler.sideBar.contentNode.setAttribute('aria-label', trans.__('main sidebar'));
125
127
  leftHandler.sideBar.node.setAttribute('role', 'complementary');
126
128
  leftHandler.stackedPanel.id = 'jp-left-stack';
127
129
  rightHandler.sideBar.addClass(SIDEBAR_CLASS);
128
130
  rightHandler.sideBar.addClass('jp-mod-right');
129
- rightHandler.sideBar.node.setAttribute('aria-label', trans.__('alternate sidebar'));
130
- rightHandler.sideBar.contentNode.setAttribute('aria-label', trans.__('alternate sidebar'));
131
131
  rightHandler.sideBar.node.setAttribute('role', 'complementary');
132
132
  rightHandler.stackedPanel.id = 'jp-right-stack';
133
133
  dockPanel.node.setAttribute('role', 'main');
@@ -204,6 +204,7 @@ export class LabShell extends Widget {
204
204
  else {
205
205
  rootLayout.insertWidget(3, this._menuHandler.panel);
206
206
  }
207
+ this.translator = nullTranslator;
207
208
  // Wire up signals to update the title panel of the simple interface mode to
208
209
  // follow the title of this.currentWidget
209
210
  this.currentChanged.connect((sender, args) => {
@@ -237,16 +238,25 @@ export class LabShell extends Widget {
237
238
  return this._tracker.activeWidget;
238
239
  }
239
240
  /**
240
- * A signal emitted when main area's current focus changes.
241
+ * Whether the add buttons for each main area tab bar are enabled.
241
242
  */
242
- get currentChanged() {
243
- return this._currentChanged;
243
+ get addButtonEnabled() {
244
+ return this._dockPanel.addButtonEnabled;
245
+ }
246
+ set addButtonEnabled(value) {
247
+ this._dockPanel.addButtonEnabled = value;
244
248
  }
245
249
  /**
246
- * A signal emitted when the shell/dock panel change modes (single/multiple document).
250
+ * A signal emitted when the add button on a main area tab bar is clicked.
247
251
  */
248
- get modeChanged() {
249
- return this._modeChanged;
252
+ get addRequested() {
253
+ return this._dockPanel.addRequested;
254
+ }
255
+ /**
256
+ * A signal emitted when main area's current focus changes.
257
+ */
258
+ get currentChanged() {
259
+ return this._currentChanged;
250
260
  }
251
261
  /**
252
262
  * A signal emitted when the path of the current document changes.
@@ -287,10 +297,6 @@ export class LabShell extends Widget {
287
297
  get presentationMode() {
288
298
  return this.hasClass('jp-mod-presentationMode');
289
299
  }
290
- /**
291
- * Enable/disable presentation mode (`jp-mod-presentationMode` CSS class) with
292
- * a boolean.
293
- */
294
300
  set presentationMode(value) {
295
301
  this.toggleClass('jp-mod-presentationMode', value);
296
302
  }
@@ -326,15 +332,35 @@ export class LabShell extends Widget {
326
332
  else {
327
333
  // Cache a reference to every widget currently in the dock panel before
328
334
  // changing its mode.
329
- const widgets = toArray(dock.widgets());
335
+ const widgets = Array.from(dock.widgets());
330
336
  dock.mode = mode;
331
- // Restore the original layout.
337
+ // Restore cached layout if possible.
332
338
  if (this._cachedLayout) {
333
339
  // Remove any disposed widgets in the cached layout and restore.
334
340
  Private.normalizeAreaConfig(dock, this._cachedLayout.main);
335
341
  dock.restoreLayout(this._cachedLayout);
336
342
  this._cachedLayout = null;
337
343
  }
344
+ // If layout restoration has been deferred, restore layout now.
345
+ if (this._layoutRestorer.isDeferred) {
346
+ this._layoutRestorer
347
+ .restoreDeferred()
348
+ .then(mainArea => {
349
+ if (mainArea) {
350
+ const { currentWidget, dock } = mainArea;
351
+ if (dock) {
352
+ this._dockPanel.restoreLayout(dock);
353
+ }
354
+ if (currentWidget) {
355
+ this.activateById(currentWidget.id);
356
+ }
357
+ }
358
+ })
359
+ .catch(reason => {
360
+ console.error('Failed to restore the deferred layout.');
361
+ console.error(reason);
362
+ });
363
+ }
338
364
  // Add any widgets created during single document mode, which have
339
365
  // subsequently been removed from the dock panel after the multiple document
340
366
  // layout has been restored. If the widget has add options cached for
@@ -342,7 +368,10 @@ export class LabShell extends Widget {
342
368
  // then take that into account.
343
369
  widgets.forEach(widget => {
344
370
  if (!widget.parent) {
345
- this._addToMainArea(widget, Object.assign(Object.assign({}, this._mainOptionsCache.get(widget)), { activate: false }));
371
+ this._addToMainArea(widget, {
372
+ ...this._mainOptionsCache.get(widget),
373
+ activate: false
374
+ });
346
375
  }
347
376
  });
348
377
  this._mainOptionsCache.clear();
@@ -353,7 +382,6 @@ export class LabShell extends Widget {
353
382
  }
354
383
  // Adjust menu and title
355
384
  this.add(this._menuHandler.panel, 'top', { rank: 100 });
356
- // this._topHandler.addWidget(this._menuHandler.panel, 100)
357
385
  this._titleHandler.hide();
358
386
  }
359
387
  // Set the mode data attribute on the applications shell node.
@@ -362,6 +390,12 @@ export class LabShell extends Widget {
362
390
  // Emit the mode changed signal
363
391
  this._modeChanged.emit(mode);
364
392
  }
393
+ /**
394
+ * A signal emitted when the shell/dock panel change modes (single/multiple document).
395
+ */
396
+ get modeChanged() {
397
+ return this._modeChanged;
398
+ }
365
399
  /**
366
400
  * Promise that resolves when state is first restored, returning layout
367
401
  * description.
@@ -369,6 +403,29 @@ export class LabShell extends Widget {
369
403
  get restored() {
370
404
  return this._restored.promise;
371
405
  }
406
+ get translator() {
407
+ var _a;
408
+ return (_a = this._translator) !== null && _a !== void 0 ? _a : nullTranslator;
409
+ }
410
+ set translator(value) {
411
+ if (value !== this._translator) {
412
+ this._translator = value;
413
+ // Set translator for tab bars
414
+ TabBarSvg.translator = value;
415
+ const trans = value.load('jupyterlab');
416
+ this._menuHandler.panel.node.setAttribute('aria-label', trans.__('main'));
417
+ this._leftHandler.sideBar.node.setAttribute('aria-label', trans.__('main sidebar'));
418
+ this._leftHandler.sideBar.contentNode.setAttribute('aria-label', trans.__('main sidebar'));
419
+ this._rightHandler.sideBar.node.setAttribute('aria-label', trans.__('alternate sidebar'));
420
+ this._rightHandler.sideBar.contentNode.setAttribute('aria-label', trans.__('alternate sidebar'));
421
+ }
422
+ }
423
+ /**
424
+ * User customized shell layout.
425
+ */
426
+ get userLayout() {
427
+ return JSONExt.deepCopy(this._userLayout);
428
+ }
372
429
  /**
373
430
  * Activate a widget in its area.
374
431
  */
@@ -392,7 +449,7 @@ export class LabShell extends Widget {
392
449
  dock.activateWidget(widget);
393
450
  }
394
451
  }
395
- /*
452
+ /**
396
453
  * Activate the next Tab in the active TabBar.
397
454
  */
398
455
  activateNextTab() {
@@ -421,22 +478,7 @@ export class LabShell extends Widget {
421
478
  }
422
479
  }
423
480
  }
424
- /*
425
- * Whether the add buttons for each main area tab bar are enabled.
426
- */
427
- get addButtonEnabled() {
428
- return this._dockPanel.addButtonEnabled;
429
- }
430
- set addButtonEnabled(value) {
431
- this._dockPanel.addButtonEnabled = value;
432
- }
433
- /*
434
- * A signal emitted when the add button on a main area tab bar is clicked.
435
- */
436
- get addRequested() {
437
- return this._dockPanel.addRequested;
438
- }
439
- /*
481
+ /**
440
482
  * Activate the previous Tab in the active TabBar.
441
483
  */
442
484
  activatePreviousTab() {
@@ -466,7 +508,7 @@ export class LabShell extends Widget {
466
508
  }
467
509
  }
468
510
  }
469
- /*
511
+ /**
470
512
  * Activate the next TabBar.
471
513
  */
472
514
  activateNextTabBar() {
@@ -477,7 +519,7 @@ export class LabShell extends Widget {
477
519
  }
478
520
  }
479
521
  }
480
- /*
522
+ /**
481
523
  * Activate the next TabBar.
482
524
  */
483
525
  activatePreviousTabBar() {
@@ -488,7 +530,41 @@ export class LabShell extends Widget {
488
530
  }
489
531
  }
490
532
  }
533
+ /**
534
+ * Add a widget to the JupyterLab shell
535
+ *
536
+ * @param widget Widget
537
+ * @param area Area
538
+ * @param options Options
539
+ */
491
540
  add(widget, area = 'main', options) {
541
+ var _a;
542
+ if (!this._userLayout) {
543
+ this._delayedWidget.push({ widget, area, options });
544
+ return;
545
+ }
546
+ let userPosition;
547
+ if ((options === null || options === void 0 ? void 0 : options.type) && this._userLayout[this.mode][options.type]) {
548
+ userPosition = this._userLayout[this.mode][options.type];
549
+ this._idTypeMap.set(widget.id, options.type);
550
+ }
551
+ else {
552
+ userPosition = this._userLayout[this.mode][widget.id];
553
+ }
554
+ if (options === null || options === void 0 ? void 0 : options.type) {
555
+ this._idTypeMap.set(widget.id, options.type);
556
+ widget.disposed.connect(() => {
557
+ this._idTypeMap.delete(widget.id);
558
+ });
559
+ }
560
+ area = (_a = userPosition === null || userPosition === void 0 ? void 0 : userPosition.area) !== null && _a !== void 0 ? _a : area;
561
+ options =
562
+ options || (userPosition === null || userPosition === void 0 ? void 0 : userPosition.options)
563
+ ? {
564
+ ...options,
565
+ ...userPosition === null || userPosition === void 0 ? void 0 : userPosition.options
566
+ }
567
+ : undefined;
492
568
  switch (area || 'main') {
493
569
  case 'bottom':
494
570
  return this._addToBottomArea(widget, options);
@@ -510,6 +586,32 @@ export class LabShell extends Widget {
510
586
  throw new Error(`Invalid area: ${area}`);
511
587
  }
512
588
  }
589
+ /**
590
+ * Move a widget type to a new area.
591
+ *
592
+ * The type is determined from the `widget.id` and fallback to `widget.id`.
593
+ *
594
+ * #### Notes
595
+ * If `mode` is undefined, both mode are updated.
596
+ * The new layout is now persisted.
597
+ *
598
+ * @param widget Widget to move
599
+ * @param area New area
600
+ * @param mode Mode to change
601
+ * @returns The new user layout
602
+ */
603
+ move(widget, area, mode) {
604
+ var _a;
605
+ const type = (_a = this._idTypeMap.get(widget.id)) !== null && _a !== void 0 ? _a : widget.id;
606
+ for (const m of ['single-document', 'multiple-document'].filter(c => !mode || c === mode)) {
607
+ this._userLayout[m][type] = {
608
+ ...this._userLayout[m][type],
609
+ area
610
+ };
611
+ }
612
+ this.add(widget, area);
613
+ return this._userLayout;
614
+ }
513
615
  /**
514
616
  * Collapse the left area.
515
617
  */
@@ -560,10 +662,10 @@ export class LabShell extends Widget {
560
662
  * Close all widgets in the main and down area.
561
663
  */
562
664
  closeAll() {
563
- // Make a copy of all the widget in the dock panel (using `toArray()`)
665
+ // Make a copy of all the widget in the dock panel (using `Array.from()`)
564
666
  // before removing them because removing them while iterating through them
565
667
  // modifies the underlying data of the iterator.
566
- toArray(this._dockPanel.widgets()).forEach(widget => widget.close());
668
+ Array.from(this._dockPanel.widgets()).forEach(widget => widget.close());
567
669
  this._downPanel.stackedPanel.widgets.forEach(widget => widget.close());
568
670
  }
569
671
  /**
@@ -614,15 +716,31 @@ export class LabShell extends Widget {
614
716
  }
615
717
  }
616
718
  /**
617
- * Restore the layout state for the application shell.
719
+ * Restore the layout state and configuration for the application shell.
720
+ *
721
+ * #### Notes
722
+ * This should only be called once.
618
723
  */
619
- restoreLayout(mode, layout) {
620
- var _a, _b;
724
+ async restoreLayout(mode, layoutRestorer, configuration = {}) {
725
+ var _a, _b, _c, _d;
726
+ // Set the configuration and add widgets added before the shell was ready.
727
+ this._userLayout = {
728
+ 'single-document': (_a = configuration['single-document']) !== null && _a !== void 0 ? _a : {},
729
+ 'multiple-document': (_b = configuration['multiple-document']) !== null && _b !== void 0 ? _b : {}
730
+ };
731
+ this._delayedWidget.forEach(({ widget, area, options }) => {
732
+ this.add(widget, area, options);
733
+ });
734
+ this._delayedWidget.length = 0;
735
+ this._layoutRestorer = layoutRestorer;
736
+ // Get the layout from the restorer
737
+ const layout = await layoutRestorer.fetch();
738
+ // Reset the layout
621
739
  const { mainArea, downArea, leftArea, rightArea, topArea, relativeSizes } = layout;
622
740
  // Rehydrate the main area.
623
741
  if (mainArea) {
624
742
  const { currentWidget, dock } = mainArea;
625
- if (dock) {
743
+ if (dock && mode === 'multiple-document') {
626
744
  this._dockPanel.restoreLayout(dock);
627
745
  }
628
746
  if (mode) {
@@ -647,7 +765,7 @@ export class LabShell extends Widget {
647
765
  // Rehydrate the down area
648
766
  if (downArea) {
649
767
  const { currentWidget, widgets, size } = downArea;
650
- const widgetIds = (_a = widgets === null || widgets === void 0 ? void 0 : widgets.map(widget => widget.id)) !== null && _a !== void 0 ? _a : [];
768
+ const widgetIds = (_c = widgets === null || widgets === void 0 ? void 0 : widgets.map(widget => widget.id)) !== null && _c !== void 0 ? _c : [];
651
769
  // Remove absent widgets
652
770
  this._downPanel.tabBar.titles
653
771
  .filter(title => !widgetIds.includes(title.owner.id))
@@ -668,7 +786,7 @@ export class LabShell extends Widget {
668
786
  const index = this._downPanel.stackedPanel.widgets.findIndex(widget => widget.id === currentWidget.id);
669
787
  if (index) {
670
788
  this._downPanel.currentIndex = index;
671
- (_b = this._downPanel.currentWidget) === null || _b === void 0 ? void 0 : _b.activate();
789
+ (_d = this._downPanel.currentWidget) === null || _d === void 0 ? void 0 : _d.activate();
672
790
  }
673
791
  }
674
792
  if (size && size > 0.0) {
@@ -725,7 +843,7 @@ export class LabShell extends Widget {
725
843
  },
726
844
  downArea: {
727
845
  currentWidget: this._downPanel.currentWidget,
728
- widgets: toArray(this._downPanel.stackedPanel.widgets),
846
+ widgets: Array.from(this._downPanel.stackedPanel.widgets),
729
847
  size: this._vsplitPanel.relativeSizes()[1]
730
848
  },
731
849
  leftArea: this._leftHandler.dehydrate(),
@@ -784,10 +902,17 @@ export class LabShell extends Widget {
784
902
  */
785
903
  updateConfig(config) {
786
904
  if (config.hiddenMode) {
787
- this._dockPanel.hiddenMode =
788
- config.hiddenMode === 'display'
789
- ? Widget.HiddenMode.Display
790
- : Widget.HiddenMode.Scale;
905
+ switch (config.hiddenMode) {
906
+ case 'display':
907
+ this._dockPanel.hiddenMode = Widget.HiddenMode.Display;
908
+ break;
909
+ case 'scale':
910
+ this._dockPanel.hiddenMode = Widget.HiddenMode.Scale;
911
+ break;
912
+ case 'contentVisibility':
913
+ this._dockPanel.hiddenMode = Widget.HiddenMode.ContentVisibility;
914
+ break;
915
+ }
791
916
  }
792
917
  }
793
918
  /**
@@ -798,9 +923,9 @@ export class LabShell extends Widget {
798
923
  case 'main':
799
924
  return this._dockPanel.widgets();
800
925
  case 'left':
801
- return iter(this._leftHandler.sideBar.titles.map(t => t.owner));
926
+ return map(this._leftHandler.sideBar.titles, t => t.owner);
802
927
  case 'right':
803
- return iter(this._rightHandler.sideBar.titles.map(t => t.owner));
928
+ return map(this._rightHandler.sideBar.titles, t => t.owner);
804
929
  case 'header':
805
930
  return this._headerPanel.children();
806
931
  case 'top':
@@ -886,7 +1011,7 @@ export class LabShell extends Widget {
886
1011
  const { title } = widget;
887
1012
  // Add widget ID to tab so that we can get a handle on the tab's widget
888
1013
  // (for context menu support)
889
- title.dataset = Object.assign(Object.assign({}, title.dataset), { id: widget.id });
1014
+ title.dataset = { ...title.dataset, id: widget.id };
890
1015
  if (title.icon instanceof LabIcon) {
891
1016
  // bind an appropriate style to the icon
892
1017
  title.icon = title.icon.bindprops({
@@ -1011,7 +1136,7 @@ export class LabShell extends Widget {
1011
1136
  const { title } = widget;
1012
1137
  // Add widget ID to tab so that we can get a handle on the tab's widget
1013
1138
  // (for context menu support)
1014
- title.dataset = Object.assign(Object.assign({}, title.dataset), { id: widget.id });
1139
+ title.dataset = { ...title.dataset, id: widget.id };
1015
1140
  if (title.icon instanceof LabIcon) {
1016
1141
  // bind an appropriate style to the icon
1017
1142
  title.icon = title.icon.bindprops({
@@ -1036,7 +1161,7 @@ export class LabShell extends Widget {
1036
1161
  if (!current) {
1037
1162
  return null;
1038
1163
  }
1039
- const bars = toArray(this._dockPanel.tabBars());
1164
+ const bars = Array.from(this._dockPanel.tabBars());
1040
1165
  const len = bars.length;
1041
1166
  const index = bars.indexOf(current);
1042
1167
  if (direction === 'previous') {
@@ -1292,10 +1417,16 @@ var Private;
1292
1417
  stylesheet: 'sideBar'
1293
1418
  });
1294
1419
  }
1295
- else if (typeof title.icon === 'string' || !title.icon) {
1420
+ else if (typeof title.icon === 'string' && title.icon != '') {
1296
1421
  // add some classes to help with displaying css background imgs
1297
1422
  title.iconClass = classes(title.iconClass, 'jp-Icon', 'jp-Icon-20');
1298
1423
  }
1424
+ else if (!title.icon && !title.label) {
1425
+ // add a fallback icon if there is no title label nor icon
1426
+ title.icon = tabIcon.bindprops({
1427
+ stylesheet: 'sideBar'
1428
+ });
1429
+ }
1299
1430
  this._refreshVisibility();
1300
1431
  }
1301
1432
  /**
@@ -1303,7 +1434,7 @@ var Private;
1303
1434
  */
1304
1435
  dehydrate() {
1305
1436
  const collapsed = this._sideBar.currentTitle === null;
1306
- const widgets = toArray(this._stackedPanel.widgets);
1437
+ const widgets = Array.from(this._stackedPanel.widgets);
1307
1438
  const currentWidget = widgets[this._sideBar.currentIndex];
1308
1439
  return {
1309
1440
  collapsed,