@jupyterlab/application 4.0.0-alpha.2 → 4.0.0-alpha.21

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,16 +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());
100
- const dockPanel = (this._dockPanel = new DockPanelSvg());
102
+ const vsplitPanel = (this._vsplitPanel =
103
+ new Private.RestorableSplitPanel());
104
+ const dockPanel = (this._dockPanel = new DockPanelSvg({
105
+ hiddenMode: Widget.HiddenMode.Display
106
+ }));
101
107
  MessageLoop.installMessageHook(dockPanel, this._dockChildHook);
102
- const hsplitPanel = (this._hsplitPanel = new Private.RestorableSplitPanel());
108
+ const hsplitPanel = (this._hsplitPanel =
109
+ new Private.RestorableSplitPanel());
103
110
  const downPanel = (this._downPanel = new TabPanelSvg({
104
111
  tabsMovable: true
105
112
  }));
@@ -117,14 +124,10 @@ export class LabShell extends Widget {
117
124
  downPanel.id = 'jp-down-stack';
118
125
  leftHandler.sideBar.addClass(SIDEBAR_CLASS);
119
126
  leftHandler.sideBar.addClass('jp-mod-left');
120
- leftHandler.sideBar.node.setAttribute('aria-label', trans.__('main sidebar'));
121
- leftHandler.sideBar.contentNode.setAttribute('aria-label', trans.__('main sidebar'));
122
127
  leftHandler.sideBar.node.setAttribute('role', 'complementary');
123
128
  leftHandler.stackedPanel.id = 'jp-left-stack';
124
129
  rightHandler.sideBar.addClass(SIDEBAR_CLASS);
125
130
  rightHandler.sideBar.addClass('jp-mod-right');
126
- rightHandler.sideBar.node.setAttribute('aria-label', trans.__('alternate sidebar'));
127
- rightHandler.sideBar.contentNode.setAttribute('aria-label', trans.__('alternate sidebar'));
128
131
  rightHandler.sideBar.node.setAttribute('role', 'complementary');
129
132
  rightHandler.stackedPanel.id = 'jp-right-stack';
130
133
  dockPanel.node.setAttribute('role', 'main');
@@ -201,6 +204,7 @@ export class LabShell extends Widget {
201
204
  else {
202
205
  rootLayout.insertWidget(3, this._menuHandler.panel);
203
206
  }
207
+ this.translator = nullTranslator;
204
208
  // Wire up signals to update the title panel of the simple interface mode to
205
209
  // follow the title of this.currentWidget
206
210
  this.currentChanged.connect((sender, args) => {
@@ -234,16 +238,25 @@ export class LabShell extends Widget {
234
238
  return this._tracker.activeWidget;
235
239
  }
236
240
  /**
237
- * A signal emitted when main area's current focus changes.
241
+ * Whether the add buttons for each main area tab bar are enabled.
238
242
  */
239
- get currentChanged() {
240
- return this._currentChanged;
243
+ get addButtonEnabled() {
244
+ return this._dockPanel.addButtonEnabled;
245
+ }
246
+ set addButtonEnabled(value) {
247
+ this._dockPanel.addButtonEnabled = value;
241
248
  }
242
249
  /**
243
- * 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.
244
251
  */
245
- get modeChanged() {
246
- 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;
247
260
  }
248
261
  /**
249
262
  * A signal emitted when the path of the current document changes.
@@ -284,10 +297,6 @@ export class LabShell extends Widget {
284
297
  get presentationMode() {
285
298
  return this.hasClass('jp-mod-presentationMode');
286
299
  }
287
- /**
288
- * Enable/disable presentation mode (`jp-mod-presentationMode` CSS class) with
289
- * a boolean.
290
- */
291
300
  set presentationMode(value) {
292
301
  this.toggleClass('jp-mod-presentationMode', value);
293
302
  }
@@ -323,15 +332,35 @@ export class LabShell extends Widget {
323
332
  else {
324
333
  // Cache a reference to every widget currently in the dock panel before
325
334
  // changing its mode.
326
- const widgets = toArray(dock.widgets());
335
+ const widgets = Array.from(dock.widgets());
327
336
  dock.mode = mode;
328
- // Restore the original layout.
337
+ // Restore cached layout if possible.
329
338
  if (this._cachedLayout) {
330
339
  // Remove any disposed widgets in the cached layout and restore.
331
340
  Private.normalizeAreaConfig(dock, this._cachedLayout.main);
332
341
  dock.restoreLayout(this._cachedLayout);
333
342
  this._cachedLayout = null;
334
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
+ }
335
364
  // Add any widgets created during single document mode, which have
336
365
  // subsequently been removed from the dock panel after the multiple document
337
366
  // layout has been restored. If the widget has add options cached for
@@ -339,7 +368,10 @@ export class LabShell extends Widget {
339
368
  // then take that into account.
340
369
  widgets.forEach(widget => {
341
370
  if (!widget.parent) {
342
- 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
+ });
343
375
  }
344
376
  });
345
377
  this._mainOptionsCache.clear();
@@ -350,7 +382,6 @@ export class LabShell extends Widget {
350
382
  }
351
383
  // Adjust menu and title
352
384
  this.add(this._menuHandler.panel, 'top', { rank: 100 });
353
- // this._topHandler.addWidget(this._menuHandler.panel, 100)
354
385
  this._titleHandler.hide();
355
386
  }
356
387
  // Set the mode data attribute on the applications shell node.
@@ -359,6 +390,12 @@ export class LabShell extends Widget {
359
390
  // Emit the mode changed signal
360
391
  this._modeChanged.emit(mode);
361
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
+ }
362
399
  /**
363
400
  * Promise that resolves when state is first restored, returning layout
364
401
  * description.
@@ -366,6 +403,29 @@ export class LabShell extends Widget {
366
403
  get restored() {
367
404
  return this._restored.promise;
368
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
+ }
369
429
  /**
370
430
  * Activate a widget in its area.
371
431
  */
@@ -389,7 +449,7 @@ export class LabShell extends Widget {
389
449
  dock.activateWidget(widget);
390
450
  }
391
451
  }
392
- /*
452
+ /**
393
453
  * Activate the next Tab in the active TabBar.
394
454
  */
395
455
  activateNextTab() {
@@ -418,7 +478,7 @@ export class LabShell extends Widget {
418
478
  }
419
479
  }
420
480
  }
421
- /*
481
+ /**
422
482
  * Activate the previous Tab in the active TabBar.
423
483
  */
424
484
  activatePreviousTab() {
@@ -448,7 +508,7 @@ export class LabShell extends Widget {
448
508
  }
449
509
  }
450
510
  }
451
- /*
511
+ /**
452
512
  * Activate the next TabBar.
453
513
  */
454
514
  activateNextTabBar() {
@@ -459,7 +519,7 @@ export class LabShell extends Widget {
459
519
  }
460
520
  }
461
521
  }
462
- /*
522
+ /**
463
523
  * Activate the next TabBar.
464
524
  */
465
525
  activatePreviousTabBar() {
@@ -470,7 +530,41 @@ export class LabShell extends Widget {
470
530
  }
471
531
  }
472
532
  }
533
+ /**
534
+ * Add a widget to the JupyterLab shell
535
+ *
536
+ * @param widget Widget
537
+ * @param area Area
538
+ * @param options Options
539
+ */
473
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;
474
568
  switch (area || 'main') {
475
569
  case 'bottom':
476
570
  return this._addToBottomArea(widget, options);
@@ -492,6 +586,32 @@ export class LabShell extends Widget {
492
586
  throw new Error(`Invalid area: ${area}`);
493
587
  }
494
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
+ }
495
615
  /**
496
616
  * Collapse the left area.
497
617
  */
@@ -542,10 +662,10 @@ export class LabShell extends Widget {
542
662
  * Close all widgets in the main and down area.
543
663
  */
544
664
  closeAll() {
545
- // 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()`)
546
666
  // before removing them because removing them while iterating through them
547
667
  // modifies the underlying data of the iterator.
548
- toArray(this._dockPanel.widgets()).forEach(widget => widget.close());
668
+ Array.from(this._dockPanel.widgets()).forEach(widget => widget.close());
549
669
  this._downPanel.stackedPanel.widgets.forEach(widget => widget.close());
550
670
  }
551
671
  /**
@@ -596,15 +716,31 @@ export class LabShell extends Widget {
596
716
  }
597
717
  }
598
718
  /**
599
- * 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.
600
723
  */
601
- restoreLayout(mode, layout) {
602
- 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
603
739
  const { mainArea, downArea, leftArea, rightArea, topArea, relativeSizes } = layout;
604
740
  // Rehydrate the main area.
605
741
  if (mainArea) {
606
742
  const { currentWidget, dock } = mainArea;
607
- if (dock) {
743
+ if (dock && mode === 'multiple-document') {
608
744
  this._dockPanel.restoreLayout(dock);
609
745
  }
610
746
  if (mode) {
@@ -629,7 +765,7 @@ export class LabShell extends Widget {
629
765
  // Rehydrate the down area
630
766
  if (downArea) {
631
767
  const { currentWidget, widgets, size } = downArea;
632
- 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 : [];
633
769
  // Remove absent widgets
634
770
  this._downPanel.tabBar.titles
635
771
  .filter(title => !widgetIds.includes(title.owner.id))
@@ -650,7 +786,7 @@ export class LabShell extends Widget {
650
786
  const index = this._downPanel.stackedPanel.widgets.findIndex(widget => widget.id === currentWidget.id);
651
787
  if (index) {
652
788
  this._downPanel.currentIndex = index;
653
- (_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();
654
790
  }
655
791
  }
656
792
  if (size && size > 0.0) {
@@ -707,7 +843,7 @@ export class LabShell extends Widget {
707
843
  },
708
844
  downArea: {
709
845
  currentWidget: this._downPanel.currentWidget,
710
- widgets: toArray(this._downPanel.stackedPanel.widgets),
846
+ widgets: Array.from(this._downPanel.stackedPanel.widgets),
711
847
  size: this._vsplitPanel.relativeSizes()[1]
712
848
  },
713
849
  leftArea: this._leftHandler.dehydrate(),
@@ -759,6 +895,26 @@ export class LabShell extends Widget {
759
895
  }
760
896
  }
761
897
  }
898
+ /**
899
+ * Update the shell configuration.
900
+ *
901
+ * @param config Shell configuration
902
+ */
903
+ updateConfig(config) {
904
+ if (config.hiddenMode) {
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
+ }
916
+ }
917
+ }
762
918
  /**
763
919
  * Returns the widgets for an application area.
764
920
  */
@@ -767,9 +923,9 @@ export class LabShell extends Widget {
767
923
  case 'main':
768
924
  return this._dockPanel.widgets();
769
925
  case 'left':
770
- return iter(this._leftHandler.sideBar.titles.map(t => t.owner));
926
+ return map(this._leftHandler.sideBar.titles, t => t.owner);
771
927
  case 'right':
772
- return iter(this._rightHandler.sideBar.titles.map(t => t.owner));
928
+ return map(this._rightHandler.sideBar.titles, t => t.owner);
773
929
  case 'header':
774
930
  return this._headerPanel.children();
775
931
  case 'top':
@@ -855,7 +1011,7 @@ export class LabShell extends Widget {
855
1011
  const { title } = widget;
856
1012
  // Add widget ID to tab so that we can get a handle on the tab's widget
857
1013
  // (for context menu support)
858
- title.dataset = Object.assign(Object.assign({}, title.dataset), { id: widget.id });
1014
+ title.dataset = { ...title.dataset, id: widget.id };
859
1015
  if (title.icon instanceof LabIcon) {
860
1016
  // bind an appropriate style to the icon
861
1017
  title.icon = title.icon.bindprops({
@@ -980,7 +1136,7 @@ export class LabShell extends Widget {
980
1136
  const { title } = widget;
981
1137
  // Add widget ID to tab so that we can get a handle on the tab's widget
982
1138
  // (for context menu support)
983
- title.dataset = Object.assign(Object.assign({}, title.dataset), { id: widget.id });
1139
+ title.dataset = { ...title.dataset, id: widget.id };
984
1140
  if (title.icon instanceof LabIcon) {
985
1141
  // bind an appropriate style to the icon
986
1142
  title.icon = title.icon.bindprops({
@@ -1005,7 +1161,7 @@ export class LabShell extends Widget {
1005
1161
  if (!current) {
1006
1162
  return null;
1007
1163
  }
1008
- const bars = toArray(this._dockPanel.tabBars());
1164
+ const bars = Array.from(this._dockPanel.tabBars());
1009
1165
  const len = bars.length;
1010
1166
  const index = bars.indexOf(current);
1011
1167
  if (direction === 'previous') {
@@ -1261,10 +1417,16 @@ var Private;
1261
1417
  stylesheet: 'sideBar'
1262
1418
  });
1263
1419
  }
1264
- else if (typeof title.icon === 'string' || !title.icon) {
1420
+ else if (typeof title.icon === 'string' && title.icon != '') {
1265
1421
  // add some classes to help with displaying css background imgs
1266
1422
  title.iconClass = classes(title.iconClass, 'jp-Icon', 'jp-Icon-20');
1267
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
+ }
1268
1430
  this._refreshVisibility();
1269
1431
  }
1270
1432
  /**
@@ -1272,7 +1434,7 @@ var Private;
1272
1434
  */
1273
1435
  dehydrate() {
1274
1436
  const collapsed = this._sideBar.currentTitle === null;
1275
- const widgets = toArray(this._stackedPanel.widgets);
1437
+ const widgets = Array.from(this._stackedPanel.widgets);
1276
1438
  const currentWidget = widgets[this._sideBar.currentIndex];
1277
1439
  return {
1278
1440
  collapsed,