flexdesk 0.17.14 → 0.17.16
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/CHANGELOG.md +39 -0
- package/css/base.css +237 -8
- package/css/flexdesk.css +344 -17
- package/css/overrides.css +6 -0
- package/dist/charts.js +104 -34
- package/dist/charts.js.map +3 -3
- package/dist/{chunk-2PM6ZMNS.js → chunk-2Z2ENHR3.js} +2 -2
- package/dist/{chunk-IG74S3ZH.js → chunk-7JI7HHSY.js} +2 -2
- package/dist/{chunk-TLIV6QSU.js → chunk-7OL6F2AM.js} +3 -3
- package/dist/{chunk-MADMGRX2.js → chunk-PGZ4EHSE.js} +156 -9
- package/dist/chunk-PGZ4EHSE.js.map +7 -0
- package/dist/{chunk-ZMH3OC7H.js → chunk-XJNADRGR.js} +4 -2
- package/dist/chunk-XJNADRGR.js.map +7 -0
- package/dist/flexdesk.css +344 -17
- package/dist/flow.js +3 -3
- package/dist/tiles.js +84 -9
- package/dist/tiles.js.map +2 -2
- package/dist/widgets.js +448 -5
- package/dist/widgets.js.map +4 -4
- package/dist/wm.js +110 -19
- package/dist/wm.js.map +2 -2
- package/docs/README.md +1 -1
- package/docs/components/charts.md +70 -3
- package/docs/components/forms-and-dialogs.md +80 -2
- package/docs/components/menus.md +85 -1
- package/docs/components/shell.md +52 -0
- package/docs/components/slide-out-panel.md +15 -0
- package/docs/components/tiles.md +37 -0
- package/package.json +1 -1
- package/src/charts/plot_config_editor.js +125 -29
- package/src/tiles/tile_base.js +48 -2
- package/src/tiles/tile_grid.js +42 -1
- package/src/tiling/kind_taxonomy.js +45 -0
- package/src/tiling/tile_breadcrumb.js +12 -4
- package/src/tiling/wm.js +86 -11
- package/src/ui/components/category_picker.js +556 -0
- package/src/ui/components/computing_status_window.js +3 -0
- package/src/ui/components/confirm_dialog.js +3 -0
- package/src/ui/components/managed_window.js +204 -9
- package/src/ui/components/modal.js +11 -2
- package/src/ui/components/wizard.js +4 -1
- package/widgets.js +2 -1
- package/dist/chunk-MADMGRX2.js.map +0 -7
- package/dist/chunk-ZMH3OC7H.js.map +0 -7
- /package/dist/{chunk-2PM6ZMNS.js.map → chunk-2Z2ENHR3.js.map} +0 -0
- /package/dist/{chunk-IG74S3ZH.js.map → chunk-7JI7HHSY.js.map} +0 -0
- /package/dist/{chunk-TLIV6QSU.js.map → chunk-7OL6F2AM.js.map} +0 -0
package/src/tiling/wm.js
CHANGED
|
@@ -303,8 +303,14 @@ export class WindowManager {
|
|
|
303
303
|
}
|
|
304
304
|
}
|
|
305
305
|
}
|
|
306
|
-
// A
|
|
307
|
-
|
|
306
|
+
// A window-only kind is never in a tile, and a single kind is open
|
|
307
|
+
// once, whatever the saved layout says.
|
|
308
|
+
const windowOnlyDropped = this._dropWindowOnlyTabs();
|
|
309
|
+
if (windowOnlyDropped.length) {
|
|
310
|
+
(this.logger ?? console).warn?.(`[wm] the saved layout held ${windowOnlyDropped.length} tile tab(s) `
|
|
311
|
+
+ `of window-only kind(s) ${[...new Set(windowOnlyDropped)].join(', ')}; they open in a window when asked for`);
|
|
312
|
+
}
|
|
313
|
+
const deduped = this._dropDuplicateSingles() || windowOnlyDropped.length > 0;
|
|
308
314
|
for (const d of this.desktops.desktops) this._canonicalize(d.tree, d);
|
|
309
315
|
this.renderer.tree = this.desktops.active().tree;
|
|
310
316
|
this.renderer.render();
|
|
@@ -581,20 +587,57 @@ export class WindowManager {
|
|
|
581
587
|
return keys;
|
|
582
588
|
}
|
|
583
589
|
|
|
590
|
+
/** Open a window-only kind (KindDef `windowOnly`) in its window — the one
|
|
591
|
+
* that is open, or a new one — and say so; any other kind: false, and the
|
|
592
|
+
* caller opens it where it asked (STORY-0127). */
|
|
593
|
+
_openWindowOnly(kind, props) {
|
|
594
|
+
if (!this.taxonomy.isWindowOnly?.(kind)) return false;
|
|
595
|
+
this._navigateWindow(kind, props);
|
|
596
|
+
return true;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/** Whether a window record holds a window-only kind: such a window never
|
|
600
|
+
* docks into a tile (Back to tile, maximize, drag-to-dock). */
|
|
601
|
+
_holdsWindowOnly(rec) {
|
|
602
|
+
return !!this.taxonomy.isWindowOnly
|
|
603
|
+
&& _windowTabSpecs(rec).some((t) => this.taxonomy.isWindowOnly(t?.kind));
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/** A restored layout never holds a window-only kind in a tile: its tabs,
|
|
607
|
+
* and its tabs archived under a page, go. Windows are not restored, so
|
|
608
|
+
* the content simply opens in a window the next time it is asked for.
|
|
609
|
+
* Returns the kinds of the tabs that went, one entry per tab. */
|
|
610
|
+
_dropWindowOnlyTabs() {
|
|
611
|
+
if (!this.taxonomy.isWindowOnly) return [];
|
|
612
|
+
const dropped = [];
|
|
613
|
+
this._pruneRestoredTabs((t) => {
|
|
614
|
+
if (!t || !this.taxonomy.isWindowOnly(t.kind)) return true;
|
|
615
|
+
dropped.push(t.kind);
|
|
616
|
+
return false;
|
|
617
|
+
});
|
|
618
|
+
return dropped;
|
|
619
|
+
}
|
|
620
|
+
|
|
584
621
|
/** A restored layout keeps the FIRST instance of each single kind: later
|
|
585
622
|
* live tabs are dropped (the tile shows the tab that took the dropped
|
|
586
623
|
* one's place, or closes when it held nothing else), and so are later
|
|
587
624
|
* archived ones. Answers whether anything was dropped. */
|
|
588
625
|
_dropDuplicateSingles() {
|
|
589
626
|
const seen = new Set();
|
|
590
|
-
|
|
591
|
-
const keep = (t) => {
|
|
627
|
+
return this._pruneRestoredTabs((t) => {
|
|
592
628
|
const k = t ? this._instanceKey(t.kind, t.props) : null;
|
|
593
629
|
if (k == null) return true;
|
|
594
630
|
if (seen.has(k)) return false;
|
|
595
631
|
seen.add(k);
|
|
596
632
|
return true;
|
|
597
|
-
};
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/** Drop every tab of the restored layout `keep(tab)` refuses, in the
|
|
637
|
+
* tiles and in their archived pages; a tile left empty closes (seeding a
|
|
638
|
+
* content tile if none is left). Returns whether anything went. */
|
|
639
|
+
_pruneRestoredTabs(keep) {
|
|
640
|
+
let dropped = false;
|
|
598
641
|
for (const d of this.desktops.desktops) {
|
|
599
642
|
const tree = d.tree;
|
|
600
643
|
for (const leaf of tree.leaves()) {
|
|
@@ -862,6 +905,9 @@ export class WindowManager {
|
|
|
862
905
|
const tree = this._tree();
|
|
863
906
|
const prior = tree.popActiveTabHistory?.(id);
|
|
864
907
|
if (!prior || !prior.kind) return false;
|
|
908
|
+
// A window-only kind in a tab's history (a layout saved before the kind
|
|
909
|
+
// was one) goes back to its window, never into this tile.
|
|
910
|
+
if (this._openWindowOnly(prior.kind, prior.props || {})) return true;
|
|
865
911
|
// A single kind opened elsewhere since this entry was pushed is
|
|
866
912
|
// revealed there, not put back here as a second copy.
|
|
867
913
|
if (this.revealInstance(prior.kind, prior.props || {})) return true;
|
|
@@ -973,6 +1019,7 @@ export class WindowManager {
|
|
|
973
1019
|
* the leaf. Used by Backspace + breadcrumb segments + anything
|
|
974
1020
|
* else that should walk WITHIN the tile rather than reset it. */
|
|
975
1021
|
navigateActiveTab(leafId, kind, props = {}) {
|
|
1022
|
+
if (this._openWindowOnly(kind, props)) return;
|
|
976
1023
|
if (this.revealInstance(kind, props)) return;
|
|
977
1024
|
const tree = this._tree();
|
|
978
1025
|
const leaf = tree.get(leafId);
|
|
@@ -1029,6 +1076,7 @@ export class WindowManager {
|
|
|
1029
1076
|
* tab feature shipped), the behavior is identical to the old
|
|
1030
1077
|
* setLeafContent: one tab in, one tab out. */
|
|
1031
1078
|
openInLeaf(leafId, kind, props = {}) {
|
|
1079
|
+
if (this._openWindowOnly(kind, props)) return;
|
|
1032
1080
|
if (this.revealInstance(kind, props)) return;
|
|
1033
1081
|
const tree = this._tree();
|
|
1034
1082
|
const leaf = tree.get(leafId);
|
|
@@ -1047,6 +1095,7 @@ export class WindowManager {
|
|
|
1047
1095
|
* previous mount, mounts the new kind into the same body element,
|
|
1048
1096
|
* and updates the window's title. */
|
|
1049
1097
|
openInWindow(winId, kind, props = {}) {
|
|
1098
|
+
if (this._openWindowOnly(kind, props)) return;
|
|
1050
1099
|
if (this.revealInstance(kind, props)) return;
|
|
1051
1100
|
const rec = this._windowToLeaf.get(winId);
|
|
1052
1101
|
if (!rec) { this.openInPrimary(kind, props); return; }
|
|
@@ -1089,6 +1138,7 @@ export class WindowManager {
|
|
|
1089
1138
|
* If only panel tiles exist (or the tree is empty), spawn a new
|
|
1090
1139
|
* content leaf and canonicalize so the panels wrap it. */
|
|
1091
1140
|
openInPrimary(kind, props = {}) {
|
|
1141
|
+
if (this._openWindowOnly(kind, props)) return;
|
|
1092
1142
|
if (this.revealInstance(kind, props)) return;
|
|
1093
1143
|
const tree = this._tree();
|
|
1094
1144
|
let leafId = tree.primaryLeafId();
|
|
@@ -1172,6 +1222,7 @@ export class WindowManager {
|
|
|
1172
1222
|
* pane in a split never dismounts the original). Returns the new
|
|
1173
1223
|
* leaf id, or null if the leaf can't be split. */
|
|
1174
1224
|
splitLeafWith(leafId, dir, kind, props = {}, title = '') {
|
|
1225
|
+
if (this._openWindowOnly(kind, props)) return null;
|
|
1175
1226
|
if (this.revealInstance(kind, props)) return null;
|
|
1176
1227
|
const tree = this._tree();
|
|
1177
1228
|
leafId = this._contentLeafId(leafId);
|
|
@@ -1661,6 +1712,9 @@ export class WindowManager {
|
|
|
1661
1712
|
canMaximize: true,
|
|
1662
1713
|
canResize: true,
|
|
1663
1714
|
modal: false,
|
|
1715
|
+
// BUG-0053. The id is minted per window and never opens again, so
|
|
1716
|
+
// geometry saved under it could never be restored.
|
|
1717
|
+
persistGeometry: false,
|
|
1664
1718
|
// C15. Dropping a promoted window on a tile PUTS IT BACK — as that
|
|
1665
1719
|
// tile's content, as a split of it, or as one of its tabs. Off
|
|
1666
1720
|
// unless the embedder asked, because it changes what a drag to an
|
|
@@ -2273,6 +2327,9 @@ export class WindowManager {
|
|
|
2273
2327
|
// false lets the geometric maximise happen instead — the answer every
|
|
2274
2328
|
// other window manager gives a window with nowhere to go back to.
|
|
2275
2329
|
if (!rec) return false;
|
|
2330
|
+
// A window-only kind has no tile to go back to (STORY-0127): the
|
|
2331
|
+
// geometric maximise happens instead.
|
|
2332
|
+
if (this._holdsWindowOnly(rec)) return false;
|
|
2276
2333
|
const tree = this.desktops.desktops[rec.desktopIdx]?.tree;
|
|
2277
2334
|
const home = rec.homeLeafId ? tree?.get(rec.homeLeafId) : null;
|
|
2278
2335
|
if (home && home.kind === 'leaf') {
|
|
@@ -3138,6 +3195,7 @@ export class WindowManager {
|
|
|
3138
3195
|
dockWindowInto(windowId, target) {
|
|
3139
3196
|
const rec = this._windowToLeaf.get(windowId);
|
|
3140
3197
|
if (!rec || !target?.leafId) return false;
|
|
3198
|
+
if (this._holdsWindowOnly(rec)) return false; // never into a tile (STORY-0127)
|
|
3141
3199
|
rec._dock = { ...target };
|
|
3142
3200
|
rec._demoting = true;
|
|
3143
3201
|
try { rec.window.close({ force: true }); }
|
|
@@ -3343,7 +3401,8 @@ export class WindowManager {
|
|
|
3343
3401
|
e.preventDefault();
|
|
3344
3402
|
const rec = this._windowToLeaf.get(winId);
|
|
3345
3403
|
if (!rec) return;
|
|
3346
|
-
|
|
3404
|
+
// A window-only kind's window has no tile to go back to
|
|
3405
|
+
const items = this._holdsWindowOnly(rec) ? [] : [
|
|
3347
3406
|
{ label: 'Back to tile', icon: 'close_fullscreen', action: 'back' },
|
|
3348
3407
|
];
|
|
3349
3408
|
// C40. Never onto a fixed desktop — `moveWindowToDesktop` refuses.
|
|
@@ -4223,6 +4282,9 @@ export class WindowManager {
|
|
|
4223
4282
|
|
|
4224
4283
|
/** The routing behind `navigate`, for a kind known to be registered. */
|
|
4225
4284
|
_route(kind, props, opts) {
|
|
4285
|
+
// A window-only kind (KindDef `windowOnly`) opens in a window, whatever
|
|
4286
|
+
// the destination asked for.
|
|
4287
|
+
if (this._openWindowOnly(kind, props)) return;
|
|
4226
4288
|
const { ctx = null, transient = false } = opts;
|
|
4227
4289
|
// Append the tab but stay where you are. Only meaningful with `newTab`.
|
|
4228
4290
|
const background = !!opts.background;
|
|
@@ -4297,6 +4359,10 @@ export class WindowManager {
|
|
|
4297
4359
|
* source leaf — closing the window just disposes the content. */
|
|
4298
4360
|
_navigateWindow(kind, props) {
|
|
4299
4361
|
if (this.revealInstance(kind, props)) return;
|
|
4362
|
+
const size = this.taxonomy.windowSize?.(kind);
|
|
4363
|
+
// A window-only kind's window maximises as any window does and never
|
|
4364
|
+
// snaps into a tile (STORY-0127)
|
|
4365
|
+
const dockable = !this.taxonomy.isWindowOnly?.(kind);
|
|
4300
4366
|
const winId = `twm-mw-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 5)}`;
|
|
4301
4367
|
const contentEl = document.createElement('div');
|
|
4302
4368
|
contentEl.className = 'twm-window-content';
|
|
@@ -4309,17 +4375,22 @@ export class WindowManager {
|
|
|
4309
4375
|
title: mountInfo?.title || title,
|
|
4310
4376
|
icon: 'web_asset',
|
|
4311
4377
|
content: contentEl,
|
|
4378
|
+
...(size?.width ? { defaultWidth: size.width } : {}),
|
|
4379
|
+
...(size?.height ? { defaultHeight: size.height } : {}),
|
|
4312
4380
|
canMinimize: true,
|
|
4313
4381
|
canMaximize: true,
|
|
4314
4382
|
canResize: true,
|
|
4315
4383
|
modal: false,
|
|
4384
|
+
// BUG-0053. The id is minted per window and never opens again, so
|
|
4385
|
+
// geometry saved under it could never be restored.
|
|
4386
|
+
persistGeometry: false,
|
|
4316
4387
|
// C15, and this is the SECOND of the two places the WM builds a
|
|
4317
4388
|
// window. Alt+N and "Open in new window" produce a window that is
|
|
4318
4389
|
// every bit as dockable as a promoted one, and a window that can be
|
|
4319
4390
|
// dragged onto a tile in one case and not the other is a rule
|
|
4320
4391
|
// nobody can learn.
|
|
4321
|
-
snap: this.snapPromotion,
|
|
4322
|
-
snapController: this.snapPromotion ? this._snapController() : null,
|
|
4392
|
+
snap: dockable && this.snapPromotion,
|
|
4393
|
+
snapController: dockable && this.snapPromotion ? this._snapController() : null,
|
|
4323
4394
|
// R1. THE PANE IS A BOX WITH `overflow: hidden`. A window contained
|
|
4324
4395
|
// to one (C21) cannot be dragged a single pixel outside it, so
|
|
4325
4396
|
// "drag a window from one tile to another" — the gesture all three
|
|
@@ -4337,9 +4408,11 @@ export class WindowManager {
|
|
|
4337
4408
|
// harder. So the maximize button docks — and the separate demote
|
|
4338
4409
|
// button the WM used to inject beside it is gone, because two
|
|
4339
4410
|
// buttons for one verb is how you get a chrome nobody reads.
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4411
|
+
...(dockable ? {
|
|
4412
|
+
onMaximize: () => this.bringBackWindow(winId),
|
|
4413
|
+
maximizeIcon: 'close_fullscreen',
|
|
4414
|
+
maximizeTitle: 'Back to tile',
|
|
4415
|
+
} : {}),
|
|
4343
4416
|
onClose: () => this._onManagedWindowClosed(winId, mountInfo),
|
|
4344
4417
|
});
|
|
4345
4418
|
// DECORATED ONCE, below, after the record exists. It was called here as
|
|
@@ -4425,6 +4498,7 @@ export class WindowManager {
|
|
|
4425
4498
|
* routing) but uses `appendLeafTab` so the existing content
|
|
4426
4499
|
* stays in place as a tab. */
|
|
4427
4500
|
openInTabFromContext(ctx, kind, props = {}, transient = false, background = false) {
|
|
4501
|
+
if (this._openWindowOnly(kind, props)) return;
|
|
4428
4502
|
if (this.revealInstance(kind, props)) return;
|
|
4429
4503
|
// Managed-window content: just open in the window — managed
|
|
4430
4504
|
// windows aren't tabbed (one window = one content).
|
|
@@ -4461,6 +4535,7 @@ export class WindowManager {
|
|
|
4461
4535
|
* "Add row" button, which passes no ctx) reliably lands as a sibling
|
|
4462
4536
|
* tab in the main tile rather than swapping its content. */
|
|
4463
4537
|
openInTabInPrimary(kind, props = {}, transient = false, background = false) {
|
|
4538
|
+
if (this._openWindowOnly(kind, props)) return;
|
|
4464
4539
|
if (this.revealInstance(kind, props)) return;
|
|
4465
4540
|
const tree = this._tree();
|
|
4466
4541
|
const leafId = tree.primaryLeafId();
|