flexdesk 0.17.15 → 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 +23 -0
- package/css/base.css +224 -4
- package/css/flexdesk.css +314 -12
- 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-AD7FIA3Z.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-OEG6NPAN.js → chunk-XJNADRGR.js} +2 -2
- package/dist/flexdesk.css +314 -12
- 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 +102 -14
- 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 +61 -0
- package/docs/components/menus.md +85 -1
- package/docs/components/shell.md +37 -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/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/wizard.js +4 -1
- package/widgets.js +2 -1
- package/dist/chunk-MADMGRX2.js.map +0 -7
- /package/dist/{chunk-2PM6ZMNS.js.map → chunk-2Z2ENHR3.js.map} +0 -0
- /package/dist/{chunk-AD7FIA3Z.js.map → chunk-7JI7HHSY.js.map} +0 -0
- /package/dist/{chunk-TLIV6QSU.js.map → chunk-7OL6F2AM.js.map} +0 -0
- /package/dist/{chunk-OEG6NPAN.js.map → chunk-XJNADRGR.js.map} +0 -0
package/dist/wm.js
CHANGED
|
@@ -8,14 +8,14 @@ import {
|
|
|
8
8
|
} from "./chunk-REJ5HLIM.js";
|
|
9
9
|
import {
|
|
10
10
|
showContextMenu
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-7JI7HHSY.js";
|
|
12
12
|
import {
|
|
13
13
|
openForm
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-XJNADRGR.js";
|
|
15
15
|
import {
|
|
16
16
|
ManagedWindow,
|
|
17
17
|
isRowActivation
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-PGZ4EHSE.js";
|
|
19
19
|
import "./chunk-HNU6MM2B.js";
|
|
20
20
|
import {
|
|
21
21
|
NotebookTabBar
|
|
@@ -2642,6 +2642,28 @@ function createTaxonomy({ kinds, root } = {}) {
|
|
|
2642
2642
|
`createTaxonomy: '${kind}'.single must be true or a prop name`
|
|
2643
2643
|
);
|
|
2644
2644
|
}
|
|
2645
|
+
if (def.window != null) {
|
|
2646
|
+
const w = def.window;
|
|
2647
|
+
const bad = (v) => v != null && !(typeof v === "number" && Number.isFinite(v) && v > 0);
|
|
2648
|
+
if (typeof w !== "object" || Array.isArray(w) || bad(w.width) || bad(w.height)) {
|
|
2649
|
+
throw new TypeError(
|
|
2650
|
+
`createTaxonomy: '${kind}'.window must be { width?, height? } in positive px`
|
|
2651
|
+
);
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
if (def.windowOnly != null && typeof def.windowOnly !== "boolean") {
|
|
2655
|
+
throw new TypeError(`createTaxonomy: '${kind}'.windowOnly must be true or false`);
|
|
2656
|
+
}
|
|
2657
|
+
if (def.windowOnly && def.isTopNav) {
|
|
2658
|
+
throw new Error(
|
|
2659
|
+
`createTaxonomy: '${kind}' is a top-nav page and cannot be windowOnly \u2014 a page lives in tiles`
|
|
2660
|
+
);
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
if (kinds[root].windowOnly) {
|
|
2664
|
+
throw new Error(
|
|
2665
|
+
`createTaxonomy: the root kind '${root}' cannot be windowOnly \u2014 every new pane, tab and desktop is seeded with it`
|
|
2666
|
+
);
|
|
2645
2667
|
}
|
|
2646
2668
|
if (kinds[root].single) {
|
|
2647
2669
|
throw new Error(
|
|
@@ -2720,6 +2742,11 @@ function createTaxonomy({ kinds, root } = {}) {
|
|
|
2720
2742
|
const v = props?.[by];
|
|
2721
2743
|
return `${kind}\0${v == null ? "" : String(v)}`;
|
|
2722
2744
|
};
|
|
2745
|
+
const isWindowOnly = (kind) => _kinds[kind]?.windowOnly === true;
|
|
2746
|
+
const windowSize = (kind) => {
|
|
2747
|
+
const w = _kinds[kind]?.window;
|
|
2748
|
+
return w ? { width: w.width ?? null, height: w.height ?? null } : null;
|
|
2749
|
+
};
|
|
2723
2750
|
const singleReason = (kind) => `${_kinds[kind]?.label || kind} is open once: use the one that is open`;
|
|
2724
2751
|
return Object.freeze({
|
|
2725
2752
|
root,
|
|
@@ -2735,7 +2762,9 @@ function createTaxonomy({ kinds, root } = {}) {
|
|
|
2735
2762
|
topNavEntries,
|
|
2736
2763
|
isSingle,
|
|
2737
2764
|
instanceKey,
|
|
2738
|
-
singleReason
|
|
2765
|
+
singleReason,
|
|
2766
|
+
isWindowOnly,
|
|
2767
|
+
windowSize
|
|
2739
2768
|
});
|
|
2740
2769
|
}
|
|
2741
2770
|
|
|
@@ -5461,7 +5490,11 @@ var WindowManager = class _WindowManager {
|
|
|
5461
5490
|
}
|
|
5462
5491
|
}
|
|
5463
5492
|
}
|
|
5464
|
-
const
|
|
5493
|
+
const windowOnlyDropped = this._dropWindowOnlyTabs();
|
|
5494
|
+
if (windowOnlyDropped.length) {
|
|
5495
|
+
(this.logger ?? console).warn?.(`[wm] the saved layout held ${windowOnlyDropped.length} tile tab(s) of window-only kind(s) ${[...new Set(windowOnlyDropped)].join(", ")}; they open in a window when asked for`);
|
|
5496
|
+
}
|
|
5497
|
+
const deduped = this._dropDuplicateSingles() || windowOnlyDropped.length > 0;
|
|
5465
5498
|
for (const d of this.desktops.desktops) this._canonicalize(d.tree, d);
|
|
5466
5499
|
this.renderer.tree = this.desktops.active().tree;
|
|
5467
5500
|
this.renderer.render();
|
|
@@ -5728,20 +5761,52 @@ var WindowManager = class _WindowManager {
|
|
|
5728
5761
|
}
|
|
5729
5762
|
return keys;
|
|
5730
5763
|
}
|
|
5764
|
+
/** Open a window-only kind (KindDef `windowOnly`) in its window — the one
|
|
5765
|
+
* that is open, or a new one — and say so; any other kind: false, and the
|
|
5766
|
+
* caller opens it where it asked (STORY-0127). */
|
|
5767
|
+
_openWindowOnly(kind, props) {
|
|
5768
|
+
if (!this.taxonomy.isWindowOnly?.(kind)) return false;
|
|
5769
|
+
this._navigateWindow(kind, props);
|
|
5770
|
+
return true;
|
|
5771
|
+
}
|
|
5772
|
+
/** Whether a window record holds a window-only kind: such a window never
|
|
5773
|
+
* docks into a tile (Back to tile, maximize, drag-to-dock). */
|
|
5774
|
+
_holdsWindowOnly(rec) {
|
|
5775
|
+
return !!this.taxonomy.isWindowOnly && _windowTabSpecs(rec).some((t) => this.taxonomy.isWindowOnly(t?.kind));
|
|
5776
|
+
}
|
|
5777
|
+
/** A restored layout never holds a window-only kind in a tile: its tabs,
|
|
5778
|
+
* and its tabs archived under a page, go. Windows are not restored, so
|
|
5779
|
+
* the content simply opens in a window the next time it is asked for.
|
|
5780
|
+
* Returns the kinds of the tabs that went, one entry per tab. */
|
|
5781
|
+
_dropWindowOnlyTabs() {
|
|
5782
|
+
if (!this.taxonomy.isWindowOnly) return [];
|
|
5783
|
+
const dropped = [];
|
|
5784
|
+
this._pruneRestoredTabs((t) => {
|
|
5785
|
+
if (!t || !this.taxonomy.isWindowOnly(t.kind)) return true;
|
|
5786
|
+
dropped.push(t.kind);
|
|
5787
|
+
return false;
|
|
5788
|
+
});
|
|
5789
|
+
return dropped;
|
|
5790
|
+
}
|
|
5731
5791
|
/** A restored layout keeps the FIRST instance of each single kind: later
|
|
5732
5792
|
* live tabs are dropped (the tile shows the tab that took the dropped
|
|
5733
5793
|
* one's place, or closes when it held nothing else), and so are later
|
|
5734
5794
|
* archived ones. Answers whether anything was dropped. */
|
|
5735
5795
|
_dropDuplicateSingles() {
|
|
5736
5796
|
const seen = /* @__PURE__ */ new Set();
|
|
5737
|
-
|
|
5738
|
-
const keep = (t) => {
|
|
5797
|
+
return this._pruneRestoredTabs((t) => {
|
|
5739
5798
|
const k = t ? this._instanceKey(t.kind, t.props) : null;
|
|
5740
5799
|
if (k == null) return true;
|
|
5741
5800
|
if (seen.has(k)) return false;
|
|
5742
5801
|
seen.add(k);
|
|
5743
5802
|
return true;
|
|
5744
|
-
};
|
|
5803
|
+
});
|
|
5804
|
+
}
|
|
5805
|
+
/** Drop every tab of the restored layout `keep(tab)` refuses, in the
|
|
5806
|
+
* tiles and in their archived pages; a tile left empty closes (seeding a
|
|
5807
|
+
* content tile if none is left). Returns whether anything went. */
|
|
5808
|
+
_pruneRestoredTabs(keep) {
|
|
5809
|
+
let dropped = false;
|
|
5745
5810
|
for (const d of this.desktops.desktops) {
|
|
5746
5811
|
const tree = d.tree;
|
|
5747
5812
|
for (const leaf of tree.leaves()) {
|
|
@@ -5981,6 +6046,7 @@ var WindowManager = class _WindowManager {
|
|
|
5981
6046
|
const tree = this._tree();
|
|
5982
6047
|
const prior = tree.popActiveTabHistory?.(id);
|
|
5983
6048
|
if (!prior || !prior.kind) return false;
|
|
6049
|
+
if (this._openWindowOnly(prior.kind, prior.props || {})) return true;
|
|
5984
6050
|
if (this.revealInstance(prior.kind, prior.props || {})) return true;
|
|
5985
6051
|
const curKind = tree.get(id)?.content?.kind;
|
|
5986
6052
|
const curTopNav = curKind ? this.taxonomy.topNavFor(curKind) : null;
|
|
@@ -6097,6 +6163,7 @@ var WindowManager = class _WindowManager {
|
|
|
6097
6163
|
* the leaf. Used by Backspace + breadcrumb segments + anything
|
|
6098
6164
|
* else that should walk WITHIN the tile rather than reset it. */
|
|
6099
6165
|
navigateActiveTab(leafId, kind, props = {}) {
|
|
6166
|
+
if (this._openWindowOnly(kind, props)) return;
|
|
6100
6167
|
if (this.revealInstance(kind, props)) return;
|
|
6101
6168
|
const tree = this._tree();
|
|
6102
6169
|
const leaf = tree.get(leafId);
|
|
@@ -6151,6 +6218,7 @@ var WindowManager = class _WindowManager {
|
|
|
6151
6218
|
* tab feature shipped), the behavior is identical to the old
|
|
6152
6219
|
* setLeafContent: one tab in, one tab out. */
|
|
6153
6220
|
openInLeaf(leafId, kind, props = {}) {
|
|
6221
|
+
if (this._openWindowOnly(kind, props)) return;
|
|
6154
6222
|
if (this.revealInstance(kind, props)) return;
|
|
6155
6223
|
const tree = this._tree();
|
|
6156
6224
|
const leaf = tree.get(leafId);
|
|
@@ -6168,6 +6236,7 @@ var WindowManager = class _WindowManager {
|
|
|
6168
6236
|
* previous mount, mounts the new kind into the same body element,
|
|
6169
6237
|
* and updates the window's title. */
|
|
6170
6238
|
openInWindow(winId, kind, props = {}) {
|
|
6239
|
+
if (this._openWindowOnly(kind, props)) return;
|
|
6171
6240
|
if (this.revealInstance(kind, props)) return;
|
|
6172
6241
|
const rec = this._windowToLeaf.get(winId);
|
|
6173
6242
|
if (!rec) {
|
|
@@ -6207,6 +6276,7 @@ var WindowManager = class _WindowManager {
|
|
|
6207
6276
|
* If only panel tiles exist (or the tree is empty), spawn a new
|
|
6208
6277
|
* content leaf and canonicalize so the panels wrap it. */
|
|
6209
6278
|
openInPrimary(kind, props = {}) {
|
|
6279
|
+
if (this._openWindowOnly(kind, props)) return;
|
|
6210
6280
|
if (this.revealInstance(kind, props)) return;
|
|
6211
6281
|
const tree = this._tree();
|
|
6212
6282
|
let leafId = tree.primaryLeafId();
|
|
@@ -6267,6 +6337,7 @@ var WindowManager = class _WindowManager {
|
|
|
6267
6337
|
* pane in a split never dismounts the original). Returns the new
|
|
6268
6338
|
* leaf id, or null if the leaf can't be split. */
|
|
6269
6339
|
splitLeafWith(leafId, dir, kind, props = {}, title = "") {
|
|
6340
|
+
if (this._openWindowOnly(kind, props)) return null;
|
|
6270
6341
|
if (this.revealInstance(kind, props)) return null;
|
|
6271
6342
|
const tree = this._tree();
|
|
6272
6343
|
leafId = this._contentLeafId(leafId);
|
|
@@ -6676,6 +6747,9 @@ var WindowManager = class _WindowManager {
|
|
|
6676
6747
|
canMaximize: true,
|
|
6677
6748
|
canResize: true,
|
|
6678
6749
|
modal: false,
|
|
6750
|
+
// BUG-0053. The id is minted per window and never opens again, so
|
|
6751
|
+
// geometry saved under it could never be restored.
|
|
6752
|
+
persistGeometry: false,
|
|
6679
6753
|
// C15. Dropping a promoted window on a tile PUTS IT BACK — as that
|
|
6680
6754
|
// tile's content, as a split of it, or as one of its tabs. Off
|
|
6681
6755
|
// unless the embedder asked, because it changes what a drag to an
|
|
@@ -7130,6 +7204,7 @@ var WindowManager = class _WindowManager {
|
|
|
7130
7204
|
bringBackWindow(windowId) {
|
|
7131
7205
|
const rec = this._windowToLeaf.get(windowId);
|
|
7132
7206
|
if (!rec) return false;
|
|
7207
|
+
if (this._holdsWindowOnly(rec)) return false;
|
|
7133
7208
|
const tree = this.desktops.desktops[rec.desktopIdx]?.tree;
|
|
7134
7209
|
const home = rec.homeLeafId ? tree?.get(rec.homeLeafId) : null;
|
|
7135
7210
|
if (home && home.kind === "leaf") {
|
|
@@ -7748,6 +7823,7 @@ var WindowManager = class _WindowManager {
|
|
|
7748
7823
|
dockWindowInto(windowId, target) {
|
|
7749
7824
|
const rec = this._windowToLeaf.get(windowId);
|
|
7750
7825
|
if (!rec || !target?.leafId) return false;
|
|
7826
|
+
if (this._holdsWindowOnly(rec)) return false;
|
|
7751
7827
|
rec._dock = { ...target };
|
|
7752
7828
|
rec._demoting = true;
|
|
7753
7829
|
try {
|
|
@@ -7917,7 +7993,7 @@ var WindowManager = class _WindowManager {
|
|
|
7917
7993
|
e.preventDefault();
|
|
7918
7994
|
const rec = this._windowToLeaf.get(winId);
|
|
7919
7995
|
if (!rec) return;
|
|
7920
|
-
const items = [
|
|
7996
|
+
const items = this._holdsWindowOnly(rec) ? [] : [
|
|
7921
7997
|
{ label: "Back to tile", icon: "close_fullscreen", action: "back" }
|
|
7922
7998
|
];
|
|
7923
7999
|
const targets = [...this.desktops.desktops.entries()].filter(([i, d]) => i !== rec.desktopIdx && !d.fixed);
|
|
@@ -8657,6 +8733,7 @@ var WindowManager = class _WindowManager {
|
|
|
8657
8733
|
}
|
|
8658
8734
|
/** The routing behind `navigate`, for a kind known to be registered. */
|
|
8659
8735
|
_route(kind, props, opts) {
|
|
8736
|
+
if (this._openWindowOnly(kind, props)) return;
|
|
8660
8737
|
const { ctx = null, transient = false } = opts;
|
|
8661
8738
|
const background = !!opts.background;
|
|
8662
8739
|
let { dest = "main", newTab = false } = opts;
|
|
@@ -8725,6 +8802,8 @@ var WindowManager = class _WindowManager {
|
|
|
8725
8802
|
* source leaf — closing the window just disposes the content. */
|
|
8726
8803
|
_navigateWindow(kind, props) {
|
|
8727
8804
|
if (this.revealInstance(kind, props)) return;
|
|
8805
|
+
const size = this.taxonomy.windowSize?.(kind);
|
|
8806
|
+
const dockable = !this.taxonomy.isWindowOnly?.(kind);
|
|
8728
8807
|
const winId = `twm-mw-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 5)}`;
|
|
8729
8808
|
const contentEl = document.createElement("div");
|
|
8730
8809
|
contentEl.className = "twm-window-content";
|
|
@@ -8741,17 +8820,22 @@ var WindowManager = class _WindowManager {
|
|
|
8741
8820
|
title: mountInfo?.title || title,
|
|
8742
8821
|
icon: "web_asset",
|
|
8743
8822
|
content: contentEl,
|
|
8823
|
+
...size?.width ? { defaultWidth: size.width } : {},
|
|
8824
|
+
...size?.height ? { defaultHeight: size.height } : {},
|
|
8744
8825
|
canMinimize: true,
|
|
8745
8826
|
canMaximize: true,
|
|
8746
8827
|
canResize: true,
|
|
8747
8828
|
modal: false,
|
|
8829
|
+
// BUG-0053. The id is minted per window and never opens again, so
|
|
8830
|
+
// geometry saved under it could never be restored.
|
|
8831
|
+
persistGeometry: false,
|
|
8748
8832
|
// C15, and this is the SECOND of the two places the WM builds a
|
|
8749
8833
|
// window. Alt+N and "Open in new window" produce a window that is
|
|
8750
8834
|
// every bit as dockable as a promoted one, and a window that can be
|
|
8751
8835
|
// dragged onto a tile in one case and not the other is a rule
|
|
8752
8836
|
// nobody can learn.
|
|
8753
|
-
snap: this.snapPromotion,
|
|
8754
|
-
snapController: this.snapPromotion ? this._snapController() : null,
|
|
8837
|
+
snap: dockable && this.snapPromotion,
|
|
8838
|
+
snapController: dockable && this.snapPromotion ? this._snapController() : null,
|
|
8755
8839
|
// R1. THE PANE IS A BOX WITH `overflow: hidden`. A window contained
|
|
8756
8840
|
// to one (C21) cannot be dragged a single pixel outside it, so
|
|
8757
8841
|
// "drag a window from one tile to another" — the gesture all three
|
|
@@ -8769,9 +8853,11 @@ var WindowManager = class _WindowManager {
|
|
|
8769
8853
|
// harder. So the maximize button docks — and the separate demote
|
|
8770
8854
|
// button the WM used to inject beside it is gone, because two
|
|
8771
8855
|
// buttons for one verb is how you get a chrome nobody reads.
|
|
8772
|
-
|
|
8773
|
-
|
|
8774
|
-
|
|
8856
|
+
...dockable ? {
|
|
8857
|
+
onMaximize: () => this.bringBackWindow(winId),
|
|
8858
|
+
maximizeIcon: "close_fullscreen",
|
|
8859
|
+
maximizeTitle: "Back to tile"
|
|
8860
|
+
} : {},
|
|
8775
8861
|
onClose: () => this._onManagedWindowClosed(winId, mountInfo)
|
|
8776
8862
|
});
|
|
8777
8863
|
this._windowToLeaf.set(winId, {
|
|
@@ -8842,6 +8928,7 @@ var WindowManager = class _WindowManager {
|
|
|
8842
8928
|
* routing) but uses `appendLeafTab` so the existing content
|
|
8843
8929
|
* stays in place as a tab. */
|
|
8844
8930
|
openInTabFromContext(ctx, kind, props = {}, transient = false, background = false) {
|
|
8931
|
+
if (this._openWindowOnly(kind, props)) return;
|
|
8845
8932
|
if (this.revealInstance(kind, props)) return;
|
|
8846
8933
|
if (ctx?.windowId && this._windowToLeaf.has(ctx.windowId)) {
|
|
8847
8934
|
this.openInWindow(ctx.windowId, kind, props);
|
|
@@ -8875,6 +8962,7 @@ var WindowManager = class _WindowManager {
|
|
|
8875
8962
|
* "Add row" button, which passes no ctx) reliably lands as a sibling
|
|
8876
8963
|
* tab in the main tile rather than swapping its content. */
|
|
8877
8964
|
openInTabInPrimary(kind, props = {}, transient = false, background = false) {
|
|
8965
|
+
if (this._openWindowOnly(kind, props)) return;
|
|
8878
8966
|
if (this.revealInstance(kind, props)) return;
|
|
8879
8967
|
const tree = this._tree();
|
|
8880
8968
|
const leafId = tree.primaryLeafId();
|