@jupyterlab/application 3.4.1 → 4.0.0-alpha.10

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
@@ -4,7 +4,7 @@ import { DocumentWidget } from '@jupyterlab/docregistry';
4
4
  import { nullTranslator } from '@jupyterlab/translation';
5
5
  import { classes, DockPanelSvg, LabIcon, TabPanelSvg } from '@jupyterlab/ui-components';
6
6
  import { ArrayExt, find, iter, toArray } from '@lumino/algorithm';
7
- import { PromiseDelegate, Token } from '@lumino/coreutils';
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';
@@ -74,11 +74,24 @@ export class LabShell extends Widget {
74
74
  }, 0);
75
75
  this._restored = new PromiseDelegate();
76
76
  this._tracker = new FocusTracker();
77
+ this._topHandlerHiddenByUser = false;
78
+ this._idTypeMap = new Map();
77
79
  this._mainOptionsCache = new Map();
78
80
  this._sideOptionsCache = new Map();
81
+ this._delayedWidget = new Array();
79
82
  this.addClass(APPLICATION_SHELL_CLASS);
80
83
  this.id = 'main';
84
+ if ((options === null || options === void 0 ? void 0 : options.waitForRestore) === false) {
85
+ this._userLayout = { 'multiple-document': {}, 'single-document': {} };
86
+ }
81
87
  const trans = ((options && options.translator) || nullTranslator).load('jupyterlab');
88
+ // Skip Links
89
+ const skipLinkWidget = (this._skipLinkWidget = new Private.SkipLinkWidget(this));
90
+ this._skipLinkWidget.show();
91
+ // Wrap the skip widget to customize its position and size
92
+ const skipLinkWrapper = new Panel();
93
+ skipLinkWrapper.addClass('jp-skiplink-wrapper');
94
+ skipLinkWrapper.addWidget(skipLinkWidget);
82
95
  const headerPanel = (this._headerPanel = new BoxPanel());
83
96
  const menuHandler = (this._menuHandler = new Private.PanelHandler());
84
97
  menuHandler.panel.node.setAttribute('role', 'navigation');
@@ -88,13 +101,15 @@ export class LabShell extends Widget {
88
101
  const bottomPanel = (this._bottomPanel = new BoxPanel());
89
102
  bottomPanel.node.setAttribute('role', 'contentinfo');
90
103
  const hboxPanel = new BoxPanel();
91
- const vsplitPanel = (this._vsplitPanel = new Private.RestorableSplitPanel());
104
+ const vsplitPanel = (this._vsplitPanel =
105
+ new Private.RestorableSplitPanel());
92
106
  const dockPanel = (this._dockPanel = new DockPanelSvg({
93
107
  hiddenMode: Widget.HiddenMode.Scale,
94
108
  translator: options === null || options === void 0 ? void 0 : options.translator
95
109
  }));
96
110
  MessageLoop.installMessageHook(dockPanel, this._dockChildHook);
97
- const hsplitPanel = (this._hsplitPanel = new Private.RestorableSplitPanel());
111
+ const hsplitPanel = (this._hsplitPanel =
112
+ new Private.RestorableSplitPanel());
98
113
  const downPanel = (this._downPanel = new TabPanelSvg({
99
114
  tabsMovable: true
100
115
  }));
@@ -160,6 +175,7 @@ export class LabShell extends Widget {
160
175
  BoxLayout.setStretch(topHandler.panel, 0);
161
176
  BoxLayout.setStretch(hboxPanel, 1);
162
177
  BoxLayout.setStretch(bottomPanel, 0);
178
+ rootLayout.addWidget(skipLinkWrapper);
163
179
  rootLayout.addWidget(headerPanel);
164
180
  rootLayout.addWidget(topHandler.panel);
165
181
  rootLayout.addWidget(hboxPanel);
@@ -181,8 +197,8 @@ export class LabShell extends Widget {
181
197
  this._downPanel.tabBar.tabMoved.connect(this._onTabPanelChanged, this);
182
198
  this._downPanel.stackedPanel.widgetRemoved.connect(this._onTabPanelChanged, this);
183
199
  // Catch current changed events on the side handlers.
184
- this._leftHandler.sideBar.currentChanged.connect(this._onLayoutModified, this);
185
- this._rightHandler.sideBar.currentChanged.connect(this._onLayoutModified, this);
200
+ this._leftHandler.updated.connect(this._onLayoutModified, this);
201
+ this._rightHandler.updated.connect(this._onLayoutModified, this);
186
202
  // Catch update events on the horizontal split panel
187
203
  this._hsplitPanel.updated.connect(this._onLayoutModified, this);
188
204
  // Setup single-document-mode title bar
@@ -193,12 +209,8 @@ export class LabShell extends Widget {
193
209
  titleHandler.hide();
194
210
  }
195
211
  else {
196
- rootLayout.insertWidget(2, this._menuHandler.panel);
212
+ rootLayout.insertWidget(3, this._menuHandler.panel);
197
213
  }
198
- // Skip Links
199
- const skipLinkWidget = (this._skipLinkWidget = new Private.SkipLinkWidget(this));
200
- this.add(skipLinkWidget, 'top', { rank: 0 });
201
- this._skipLinkWidget.show();
202
214
  // Wire up signals to update the title panel of the simple interface mode to
203
215
  // follow the title of this.currentWidget
204
216
  this.currentChanged.connect((sender, args) => {
@@ -232,16 +244,25 @@ export class LabShell extends Widget {
232
244
  return this._tracker.activeWidget;
233
245
  }
234
246
  /**
235
- * A signal emitted when main area's current focus changes.
247
+ * Whether the add buttons for each main area tab bar are enabled.
236
248
  */
237
- get currentChanged() {
238
- return this._currentChanged;
249
+ get addButtonEnabled() {
250
+ return this._dockPanel.addButtonEnabled;
251
+ }
252
+ set addButtonEnabled(value) {
253
+ this._dockPanel.addButtonEnabled = value;
239
254
  }
240
255
  /**
241
- * A signal emitted when the shell/dock panel change modes (single/multiple document).
256
+ * A signal emitted when the add button on a main area tab bar is clicked.
242
257
  */
243
- get modeChanged() {
244
- return this._modeChanged;
258
+ get addRequested() {
259
+ return this._dockPanel.addRequested;
260
+ }
261
+ /**
262
+ * A signal emitted when main area's current focus changes.
263
+ */
264
+ get currentChanged() {
265
+ return this._currentChanged;
245
266
  }
246
267
  /**
247
268
  * A signal emitted when the path of the current document changes.
@@ -282,10 +303,6 @@ export class LabShell extends Widget {
282
303
  get presentationMode() {
283
304
  return this.hasClass('jp-mod-presentationMode');
284
305
  }
285
- /**
286
- * Enable/disable presentation mode (`jp-mod-presentationMode` CSS class) with
287
- * a boolean.
288
- */
289
306
  set presentationMode(value) {
290
307
  this.toggleClass('jp-mod-presentationMode', value);
291
308
  }
@@ -311,9 +328,12 @@ export class LabShell extends Widget {
311
328
  dock.activateWidget(this.currentWidget);
312
329
  }
313
330
  // Adjust menu and title
314
- this.layout.insertWidget(2, this._menuHandler.panel);
331
+ this.layout.insertWidget(3, this._menuHandler.panel);
315
332
  this._titleHandler.show();
316
333
  this._updateTitlePanelTitle();
334
+ if (this._topHandlerHiddenByUser) {
335
+ this._topHandler.panel.hide();
336
+ }
317
337
  }
318
338
  else {
319
339
  // Cache a reference to every widget currently in the dock panel before
@@ -345,7 +365,6 @@ export class LabShell extends Widget {
345
365
  }
346
366
  // Adjust menu and title
347
367
  this.add(this._menuHandler.panel, 'top', { rank: 100 });
348
- // this._topHandler.addWidget(this._menuHandler.panel, 100)
349
368
  this._titleHandler.hide();
350
369
  }
351
370
  // Set the mode data attribute on the applications shell node.
@@ -354,6 +373,12 @@ export class LabShell extends Widget {
354
373
  // Emit the mode changed signal
355
374
  this._modeChanged.emit(mode);
356
375
  }
376
+ /**
377
+ * A signal emitted when the shell/dock panel change modes (single/multiple document).
378
+ */
379
+ get modeChanged() {
380
+ return this._modeChanged;
381
+ }
357
382
  /**
358
383
  * Promise that resolves when state is first restored, returning layout
359
384
  * description.
@@ -361,6 +386,12 @@ export class LabShell extends Widget {
361
386
  get restored() {
362
387
  return this._restored.promise;
363
388
  }
389
+ /**
390
+ * User customized shell layout.
391
+ */
392
+ get userLayout() {
393
+ return JSONExt.deepCopy(this._userLayout);
394
+ }
364
395
  /**
365
396
  * Activate a widget in its area.
366
397
  */
@@ -384,7 +415,7 @@ export class LabShell extends Widget {
384
415
  dock.activateWidget(widget);
385
416
  }
386
417
  }
387
- /*
418
+ /**
388
419
  * Activate the next Tab in the active TabBar.
389
420
  */
390
421
  activateNextTab() {
@@ -413,22 +444,7 @@ export class LabShell extends Widget {
413
444
  }
414
445
  }
415
446
  }
416
- /*
417
- * Whether the add buttons for each main area tab bar are enabled.
418
- */
419
- get addButtonEnabled() {
420
- return this._dockPanel.addButtonEnabled;
421
- }
422
- set addButtonEnabled(value) {
423
- this._dockPanel.addButtonEnabled = value;
424
- }
425
- /*
426
- * A signal emitted when the add button on a main area tab bar is clicked.
427
- */
428
- get addRequested() {
429
- return this._dockPanel.addRequested;
430
- }
431
- /*
447
+ /**
432
448
  * Activate the previous Tab in the active TabBar.
433
449
  */
434
450
  activatePreviousTab() {
@@ -458,7 +474,7 @@ export class LabShell extends Widget {
458
474
  }
459
475
  }
460
476
  }
461
- /*
477
+ /**
462
478
  * Activate the next TabBar.
463
479
  */
464
480
  activateNextTabBar() {
@@ -469,7 +485,7 @@ export class LabShell extends Widget {
469
485
  }
470
486
  }
471
487
  }
472
- /*
488
+ /**
473
489
  * Activate the next TabBar.
474
490
  */
475
491
  activatePreviousTabBar() {
@@ -480,7 +496,34 @@ export class LabShell extends Widget {
480
496
  }
481
497
  }
482
498
  }
499
+ /**
500
+ * Add a widget to the JupyterLab shell
501
+ *
502
+ * @param widget Widget
503
+ * @param area Area
504
+ * @param options Options
505
+ */
483
506
  add(widget, area = 'main', options) {
507
+ var _a;
508
+ if (!this._userLayout) {
509
+ this._delayedWidget.push({ widget, area, options });
510
+ return;
511
+ }
512
+ let userPosition;
513
+ if ((options === null || options === void 0 ? void 0 : options.type) && this._userLayout[this.mode][options.type]) {
514
+ userPosition = this._userLayout[this.mode][options.type];
515
+ this._idTypeMap.set(widget.id, options.type);
516
+ }
517
+ else {
518
+ userPosition = this._userLayout[this.mode][widget.id];
519
+ }
520
+ if (options === null || options === void 0 ? void 0 : options.type) {
521
+ this._idTypeMap.set(widget.id, options.type);
522
+ }
523
+ area = (_a = userPosition === null || userPosition === void 0 ? void 0 : userPosition.area) !== null && _a !== void 0 ? _a : area;
524
+ options =
525
+ options || (userPosition === null || userPosition === void 0 ? void 0 : userPosition.options)
526
+ ? Object.assign(Object.assign({}, options), userPosition === null || userPosition === void 0 ? void 0 : userPosition.options) : undefined;
484
527
  switch (area || 'main') {
485
528
  case 'bottom':
486
529
  return this._addToBottomArea(widget, options);
@@ -502,6 +545,29 @@ export class LabShell extends Widget {
502
545
  throw new Error(`Invalid area: ${area}`);
503
546
  }
504
547
  }
548
+ /**
549
+ * Move a widget type to a new area.
550
+ *
551
+ * The type is determined from the `widget.id` and fallback to `widget.id`.
552
+ *
553
+ * #### Notes
554
+ * If `mode` is undefined, both mode are updated.
555
+ * The new layout is now persisted.
556
+ *
557
+ * @param widget Widget to move
558
+ * @param area New area
559
+ * @param mode Mode to change
560
+ * @returns The new user layout
561
+ */
562
+ move(widget, area, mode) {
563
+ var _a;
564
+ const type = (_a = this._idTypeMap.get(widget.id)) !== null && _a !== void 0 ? _a : widget.id;
565
+ for (const m of ['single-document', 'multiple-document'].filter(c => !mode || c === mode)) {
566
+ this._userLayout[m][type] = Object.assign(Object.assign({}, this._userLayout[m][type]), { area });
567
+ }
568
+ this.add(widget, area);
569
+ return this._userLayout;
570
+ }
505
571
  /**
506
572
  * Collapse the left area.
507
573
  */
@@ -558,6 +624,28 @@ export class LabShell extends Widget {
558
624
  toArray(this._dockPanel.widgets()).forEach(widget => widget.close());
559
625
  this._downPanel.stackedPanel.widgets.forEach(widget => widget.close());
560
626
  }
627
+ /**
628
+ * Whether an side tab bar is visible or not.
629
+ *
630
+ * @param side Sidebar of interest
631
+ * @returns Side tab bar visibility
632
+ */
633
+ isSideTabBarVisible(side) {
634
+ switch (side) {
635
+ case 'left':
636
+ return this._leftHandler.isVisible;
637
+ case 'right':
638
+ return this._rightHandler.isVisible;
639
+ }
640
+ }
641
+ /**
642
+ * Whether the top bar in simple mode is visible or not.
643
+ *
644
+ * @returns Top bar visibility
645
+ */
646
+ isTopInSimpleModeVisible() {
647
+ return !this._topHandlerHiddenByUser;
648
+ }
561
649
  /**
562
650
  * True if the given area is empty.
563
651
  */
@@ -584,11 +672,24 @@ export class LabShell extends Widget {
584
672
  }
585
673
  }
586
674
  /**
587
- * Restore the layout state for the application shell.
675
+ * Restore the layout state and configuration for the application shell.
676
+ *
677
+ * #### Notes
678
+ * This should only be called once.
588
679
  */
589
- restoreLayout(mode, layout) {
590
- var _a, _b;
591
- const { mainArea, downArea, leftArea, rightArea, relativeSizes } = layout;
680
+ restoreLayout(mode, layout, configuration = {}) {
681
+ var _a, _b, _c, _d;
682
+ // Set the configuration
683
+ this._userLayout = {
684
+ 'single-document': (_a = configuration['single-document']) !== null && _a !== void 0 ? _a : {},
685
+ 'multiple-document': (_b = configuration['multiple-document']) !== null && _b !== void 0 ? _b : {}
686
+ };
687
+ this._delayedWidget.forEach(({ widget, area, options }) => {
688
+ this.add(widget, area, options);
689
+ });
690
+ this._delayedWidget.length = 0;
691
+ // Reset the layout
692
+ const { mainArea, downArea, leftArea, rightArea, topArea, relativeSizes } = layout;
592
693
  // Rehydrate the main area.
593
694
  if (mainArea) {
594
695
  const { currentWidget, dock } = mainArea;
@@ -608,10 +709,16 @@ export class LabShell extends Widget {
608
709
  this.mode = mode;
609
710
  }
610
711
  }
712
+ if ((topArea === null || topArea === void 0 ? void 0 : topArea.simpleVisibility) !== undefined) {
713
+ this._topHandlerHiddenByUser = !topArea.simpleVisibility;
714
+ if (this.mode === 'single-document') {
715
+ this._topHandler.panel.setHidden(this._topHandlerHiddenByUser);
716
+ }
717
+ }
611
718
  // Rehydrate the down area
612
719
  if (downArea) {
613
720
  const { currentWidget, widgets, size } = downArea;
614
- const widgetIds = (_a = widgets === null || widgets === void 0 ? void 0 : widgets.map(widget => widget.id)) !== null && _a !== void 0 ? _a : [];
721
+ const widgetIds = (_c = widgets === null || widgets === void 0 ? void 0 : widgets.map(widget => widget.id)) !== null && _c !== void 0 ? _c : [];
615
722
  // Remove absent widgets
616
723
  this._downPanel.tabBar.titles
617
724
  .filter(title => !widgetIds.includes(title.owner.id))
@@ -632,7 +739,7 @@ export class LabShell extends Widget {
632
739
  const index = this._downPanel.stackedPanel.widgets.findIndex(widget => widget.id === currentWidget.id);
633
740
  if (index) {
634
741
  this._downPanel.currentIndex = index;
635
- (_b = this._downPanel.currentWidget) === null || _b === void 0 ? void 0 : _b.activate();
742
+ (_d = this._downPanel.currentWidget) === null || _d === void 0 ? void 0 : _d.activate();
636
743
  }
637
744
  }
638
745
  if (size && size > 0.0) {
@@ -694,10 +801,53 @@ export class LabShell extends Widget {
694
801
  },
695
802
  leftArea: this._leftHandler.dehydrate(),
696
803
  rightArea: this._rightHandler.dehydrate(),
804
+ topArea: { simpleVisibility: !this._topHandlerHiddenByUser },
697
805
  relativeSizes: this._hsplitPanel.relativeSizes()
698
806
  };
699
807
  return layout;
700
808
  }
809
+ /**
810
+ * Toggle top header visibility in simple mode
811
+ *
812
+ * Note: Does nothing in multi-document mode
813
+ */
814
+ toggleTopInSimpleModeVisibility() {
815
+ if (this.mode === 'single-document') {
816
+ if (this._topHandler.panel.isVisible) {
817
+ this._topHandlerHiddenByUser = true;
818
+ this._topHandler.panel.hide();
819
+ }
820
+ else {
821
+ this._topHandlerHiddenByUser = false;
822
+ this._topHandler.panel.show();
823
+ this._updateTitlePanelTitle();
824
+ }
825
+ this._onLayoutModified();
826
+ }
827
+ }
828
+ /**
829
+ * Toggle side tab bar visibility
830
+ *
831
+ * @param side Sidebar of interest
832
+ */
833
+ toggleSideTabBarVisibility(side) {
834
+ if (side === 'right') {
835
+ if (this._rightHandler.isVisible) {
836
+ this._rightHandler.hide();
837
+ }
838
+ else {
839
+ this._rightHandler.show();
840
+ }
841
+ }
842
+ else {
843
+ if (this._leftHandler.isVisible) {
844
+ this._leftHandler.hide();
845
+ }
846
+ else {
847
+ this._leftHandler.show();
848
+ }
849
+ }
850
+ }
701
851
  /**
702
852
  * Update the shell configuration.
703
853
  *
@@ -1113,7 +1263,9 @@ var Private;
1113
1263
  * Construct a new side bar handler.
1114
1264
  */
1115
1265
  constructor() {
1266
+ this._isHiddenByUser = false;
1116
1267
  this._items = new Array();
1268
+ this._updated = new Signal(this);
1117
1269
  this._sideBar = new TabBar({
1118
1270
  insertBehavior: 'none',
1119
1271
  removeBehavior: 'none',
@@ -1128,6 +1280,12 @@ var Private;
1128
1280
  this._sideBar.tabActivateRequested.connect(this._onTabActivateRequested, this);
1129
1281
  this._stackedPanel.widgetRemoved.connect(this._onWidgetRemoved, this);
1130
1282
  }
1283
+ /**
1284
+ * Whether the side bar is visible
1285
+ */
1286
+ get isVisible() {
1287
+ return this._sideBar.isVisible;
1288
+ }
1131
1289
  /**
1132
1290
  * Get the tab bar managed by the handler.
1133
1291
  */
@@ -1140,6 +1298,12 @@ var Private;
1140
1298
  get stackedPanel() {
1141
1299
  return this._stackedPanel;
1142
1300
  }
1301
+ /**
1302
+ * Signal fires when the stack panel or the sidebar changes
1303
+ */
1304
+ get updated() {
1305
+ return this._updated;
1306
+ }
1143
1307
  /**
1144
1308
  * Expand the sidebar.
1145
1309
  *
@@ -1212,7 +1376,12 @@ var Private;
1212
1376
  const collapsed = this._sideBar.currentTitle === null;
1213
1377
  const widgets = toArray(this._stackedPanel.widgets);
1214
1378
  const currentWidget = widgets[this._sideBar.currentIndex];
1215
- return { collapsed, currentWidget, widgets };
1379
+ return {
1380
+ collapsed,
1381
+ currentWidget,
1382
+ visible: !this._isHiddenByUser,
1383
+ widgets
1384
+ };
1216
1385
  }
1217
1386
  /**
1218
1387
  * Rehydrate the side bar.
@@ -1224,6 +1393,23 @@ var Private;
1224
1393
  if (data.collapsed) {
1225
1394
  this.collapse();
1226
1395
  }
1396
+ if (!data.visible) {
1397
+ this.hide();
1398
+ }
1399
+ }
1400
+ /**
1401
+ * Hide the side bar even if it contains widgets
1402
+ */
1403
+ hide() {
1404
+ this._isHiddenByUser = true;
1405
+ this._refreshVisibility();
1406
+ }
1407
+ /**
1408
+ * Show the side bar if it contains widgets
1409
+ */
1410
+ show() {
1411
+ this._isHiddenByUser = false;
1412
+ this._refreshVisibility();
1227
1413
  }
1228
1414
  /**
1229
1415
  * Find the insertion index for a rank item.
@@ -1255,8 +1441,9 @@ var Private;
1255
1441
  * Refresh the visibility of the side bar and stacked panel.
1256
1442
  */
1257
1443
  _refreshVisibility() {
1258
- this._sideBar.setHidden(this._sideBar.titles.length === 0);
1259
1444
  this._stackedPanel.setHidden(this._sideBar.currentTitle === null);
1445
+ this._sideBar.setHidden(this._isHiddenByUser || this._sideBar.titles.length === 0);
1446
+ this._updated.emit();
1260
1447
  }
1261
1448
  /**
1262
1449
  * Handle the `currentChanged` signal from the sidebar.