flexdesk 0.17.15 → 0.17.17
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 +42 -0
- package/css/base.css +236 -5
- package/css/flexdesk.css +326 -13
- package/css/tokens.css +3 -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-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 +326 -13
- package/dist/flow.js +3 -3
- package/dist/tiles.js +84 -9
- package/dist/tiles.js.map +2 -2
- package/dist/tokens.css +3 -0
- package/dist/widgets.js +4559 -4091
- package/dist/widgets.js.map +4 -4
- package/dist/wm.js +212 -40
- 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/notifications.md +23 -1
- package/docs/components/panels-and-desktops.md +22 -0
- package/docs/components/shell.md +91 -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 +66 -13
- package/src/tiling/shell.js +4 -1
- package/src/tiling/wm.js +205 -35
- 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/notification_history.js +11 -1
- package/src/ui/components/wizard.js +4 -1
- package/src/ui/notification_center.js +47 -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(
|
|
@@ -2713,13 +2735,19 @@ function createTaxonomy({ kinds, root } = {}) {
|
|
|
2713
2735
|
const s = _kinds[kind]?.single;
|
|
2714
2736
|
return s === true || typeof s === "string" && s !== "";
|
|
2715
2737
|
};
|
|
2738
|
+
const isSinglePerDesktop = (kind) => _kinds[kind]?.single === "desktop";
|
|
2716
2739
|
const instanceKey = (kind, props) => {
|
|
2717
2740
|
if (!isSingle(kind)) return null;
|
|
2718
2741
|
const by = _kinds[kind].single;
|
|
2719
|
-
if (by === true) return String(kind);
|
|
2742
|
+
if (by === true || by === "desktop") return String(kind);
|
|
2720
2743
|
const v = props?.[by];
|
|
2721
2744
|
return `${kind}\0${v == null ? "" : String(v)}`;
|
|
2722
2745
|
};
|
|
2746
|
+
const isWindowOnly = (kind) => _kinds[kind]?.windowOnly === true;
|
|
2747
|
+
const windowSize = (kind) => {
|
|
2748
|
+
const w = _kinds[kind]?.window;
|
|
2749
|
+
return w ? { width: w.width ?? null, height: w.height ?? null } : null;
|
|
2750
|
+
};
|
|
2723
2751
|
const singleReason = (kind) => `${_kinds[kind]?.label || kind} is open once: use the one that is open`;
|
|
2724
2752
|
return Object.freeze({
|
|
2725
2753
|
root,
|
|
@@ -2734,8 +2762,11 @@ function createTaxonomy({ kinds, root } = {}) {
|
|
|
2734
2762
|
panelOffer,
|
|
2735
2763
|
topNavEntries,
|
|
2736
2764
|
isSingle,
|
|
2765
|
+
isSinglePerDesktop,
|
|
2737
2766
|
instanceKey,
|
|
2738
|
-
singleReason
|
|
2767
|
+
singleReason,
|
|
2768
|
+
isWindowOnly,
|
|
2769
|
+
windowSize
|
|
2739
2770
|
});
|
|
2740
2771
|
}
|
|
2741
2772
|
|
|
@@ -5461,7 +5492,11 @@ var WindowManager = class _WindowManager {
|
|
|
5461
5492
|
}
|
|
5462
5493
|
}
|
|
5463
5494
|
}
|
|
5464
|
-
const
|
|
5495
|
+
const windowOnlyDropped = this._dropWindowOnlyTabs();
|
|
5496
|
+
if (windowOnlyDropped.length) {
|
|
5497
|
+
(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`);
|
|
5498
|
+
}
|
|
5499
|
+
const deduped = this._dropDuplicateSingles() || windowOnlyDropped.length > 0;
|
|
5465
5500
|
for (const d of this.desktops.desktops) this._canonicalize(d.tree, d);
|
|
5466
5501
|
this.renderer.tree = this.desktops.active().tree;
|
|
5467
5502
|
this.renderer.render();
|
|
@@ -5493,6 +5528,28 @@ var WindowManager = class _WindowManager {
|
|
|
5493
5528
|
//
|
|
5494
5529
|
// MOVE verbs (float, dock, drag a tab, move to desktop) carry the one
|
|
5495
5530
|
// instance somewhere else and are untouched.
|
|
5531
|
+
//
|
|
5532
|
+
// `single: 'desktop'` keeps the same promises per virtual desktop: a
|
|
5533
|
+
// desktop is the tiles and panels the user arranged on it, so opening the
|
|
5534
|
+
// kind looks only at the ACTIVE desktop — its tiles, their archived pages
|
|
5535
|
+
// and the windows on it — and opens it there when that desktop has none.
|
|
5536
|
+
// A window is on the desktop it was opened on or moved to (`desktopIdx`):
|
|
5537
|
+
// a free-floating one is drawn over every desktop, but it is that one
|
|
5538
|
+
// desktop's instance, so it never stands in for another desktop's. The
|
|
5539
|
+
// move verbs refuse to carry the kind onto a desktop that already holds
|
|
5540
|
+
// it (`desktopMoveRefusal`). Navigating inside a window looks on the
|
|
5541
|
+
// window's own desktop (`openInWindow`). Removing a desktop brings its
|
|
5542
|
+
// windows across, a window never being destroyed by a tile operation, so
|
|
5543
|
+
// the active desktop can then hold the kind twice until the next reload
|
|
5544
|
+
// keeps the first.
|
|
5545
|
+
/** The key a tab's instance is unique under on desktop `desktopIdx`: its
|
|
5546
|
+
* instance key, scoped to that desktop for a `single: 'desktop'` kind;
|
|
5547
|
+
* null for a tab that is not single. */
|
|
5548
|
+
_scopedKey(t, desktopIdx) {
|
|
5549
|
+
const k = t ? this._instanceKey(t.kind, t.props) : null;
|
|
5550
|
+
if (k == null || !this.taxonomy.isSinglePerDesktop(t.kind)) return k;
|
|
5551
|
+
return `${desktopIdx}\0${k}`;
|
|
5552
|
+
}
|
|
5496
5553
|
/** The instance key of a tab spec, or null when its kind is not single. */
|
|
5497
5554
|
_instanceKey(kind, props) {
|
|
5498
5555
|
return this.taxonomy.instanceKey(kind, props);
|
|
@@ -5502,27 +5559,36 @@ var WindowManager = class _WindowManager {
|
|
|
5502
5559
|
return this.taxonomy.isSingle(kind);
|
|
5503
5560
|
}
|
|
5504
5561
|
/** Where the one instance of a single kind is open, or null — also null
|
|
5505
|
-
* for a kind that is not single.
|
|
5562
|
+
* for a kind that is not single. A `single: 'desktop'` kind is looked for
|
|
5563
|
+
* on the active desktop only. One of:
|
|
5506
5564
|
* `{ where: 'window', windowId, idx, desktopIdx }`
|
|
5507
5565
|
* `{ where: 'tab', desktopIdx, leafId, idx }`
|
|
5508
5566
|
* `{ where: 'archive', desktopIdx, leafId, page, idx }` — a tab of a
|
|
5509
5567
|
* top-nav page the tile has switched away from (`pageTabs`). */
|
|
5510
5568
|
findInstance(kind, props = {}) {
|
|
5569
|
+
const on = this.taxonomy.isSinglePerDesktop(kind) ? this.desktops.activeIdx : null;
|
|
5570
|
+
return this._findInstance(kind, props, on);
|
|
5571
|
+
}
|
|
5572
|
+
/** `findInstance`, looking only at desktop `on` when it is a number, and
|
|
5573
|
+
* past the window `exceptWindowId`. */
|
|
5574
|
+
_findInstance(kind, props, on, exceptWindowId = null) {
|
|
5511
5575
|
const key = this._instanceKey(kind, props);
|
|
5512
5576
|
if (key == null) return null;
|
|
5513
5577
|
const same = (t) => !!t && this._instanceKey(t.kind, t.props) === key;
|
|
5578
|
+
const desktops = [...this.desktops.desktops.entries()].filter(([desktopIdx]) => on == null || desktopIdx === on);
|
|
5514
5579
|
for (const [windowId, rec] of this._windowToLeaf) {
|
|
5580
|
+
if (windowId === exceptWindowId) continue;
|
|
5581
|
+
if (on != null && rec.desktopIdx !== on) continue;
|
|
5515
5582
|
const idx = _windowTabSpecs(rec).findIndex(same);
|
|
5516
5583
|
if (idx >= 0) return { where: "window", windowId, idx, desktopIdx: rec.desktopIdx };
|
|
5517
5584
|
}
|
|
5518
|
-
const
|
|
5519
|
-
for (const [desktopIdx, d] of desktops.entries()) {
|
|
5585
|
+
for (const [desktopIdx, d] of desktops) {
|
|
5520
5586
|
for (const leaf of d.tree.leaves()) {
|
|
5521
5587
|
const idx = (leaf.tabs || []).findIndex(same);
|
|
5522
5588
|
if (idx >= 0) return { where: "tab", desktopIdx, leafId: leaf.id, idx };
|
|
5523
5589
|
}
|
|
5524
5590
|
}
|
|
5525
|
-
for (const [desktopIdx, d] of desktops
|
|
5591
|
+
for (const [desktopIdx, d] of desktops) {
|
|
5526
5592
|
for (const leaf of d.tree.leaves()) {
|
|
5527
5593
|
for (const [page, arc] of this._archivedPages(leaf)) {
|
|
5528
5594
|
const idx = arc.tabs.findIndex(same);
|
|
@@ -5532,6 +5598,30 @@ var WindowManager = class _WindowManager {
|
|
|
5532
5598
|
}
|
|
5533
5599
|
return null;
|
|
5534
5600
|
}
|
|
5601
|
+
/** Why moving a pane's or a window's tabs onto desktop `idx` is refused,
|
|
5602
|
+
* or null: that desktop already holds one of their `single: 'desktop'`
|
|
5603
|
+
* kinds (in a tile, an archived page or a window), and a desktop holds
|
|
5604
|
+
* such a kind once. `source` is `{ leafId }` (a tile of the active
|
|
5605
|
+
* desktop) or `{ windowId }`. The move verbs ask it, and their menu rows
|
|
5606
|
+
* are greyed with its sentence. */
|
|
5607
|
+
desktopMoveRefusal(source, idx) {
|
|
5608
|
+
let specs = [];
|
|
5609
|
+
if (source?.windowId) {
|
|
5610
|
+
const rec = this._windowToLeaf.get(source.windowId);
|
|
5611
|
+
specs = rec ? _windowTabSpecs(rec) : [];
|
|
5612
|
+
} else {
|
|
5613
|
+
const leaf = this._tree().get(source?.leafId);
|
|
5614
|
+
specs = leaf?.tabs?.length ? leaf.tabs : leaf?.content ? [leaf.content] : [];
|
|
5615
|
+
}
|
|
5616
|
+
const d = this.desktops.desktops[idx];
|
|
5617
|
+
if (!d) return null;
|
|
5618
|
+
for (const t of specs) {
|
|
5619
|
+
if (!t || !this.taxonomy.isSinglePerDesktop(t.kind)) continue;
|
|
5620
|
+
if (!this._findInstance(t.kind, t.props, idx, source.windowId ?? null)) continue;
|
|
5621
|
+
return `${this.taxonomy.kinds[t.kind]?.label || t.kind} is open on desktop ${d.label} already`;
|
|
5622
|
+
}
|
|
5623
|
+
return null;
|
|
5624
|
+
}
|
|
5535
5625
|
/** Reveal the instance of a single kind that is open: answers whether
|
|
5536
5626
|
* there was one. Every open path asks this first.
|
|
5537
5627
|
*
|
|
@@ -5545,7 +5635,11 @@ var WindowManager = class _WindowManager {
|
|
|
5545
5635
|
* open names no props at all — a top-nav button, `openInPrimary(kind)` —
|
|
5546
5636
|
* which asks to SEE the instance, not to empty it. */
|
|
5547
5637
|
revealInstance(kind, props = {}) {
|
|
5548
|
-
|
|
5638
|
+
return this._revealFound(this.findInstance(kind, props), kind, props);
|
|
5639
|
+
}
|
|
5640
|
+
/** Reveal the instance at `loc` (a `findInstance` result, or null) and
|
|
5641
|
+
* hand it `props`. Answers whether there was one to reveal. */
|
|
5642
|
+
_revealFound(loc, kind, props) {
|
|
5549
5643
|
if (!loc || !this._revealAt(loc)) return false;
|
|
5550
5644
|
if (props && Object.keys(props).length) this._deliverProps(kind, props);
|
|
5551
5645
|
return true;
|
|
@@ -5658,7 +5752,8 @@ var WindowManager = class _WindowManager {
|
|
|
5658
5752
|
* soon as the tile leaves the page by a path other than `swapToPage`
|
|
5659
5753
|
* (`navigateActiveTab`). So a restored tab whose instance is live
|
|
5660
5754
|
* anywhere else is dropped from the tile and from the restored archive;
|
|
5661
|
-
* the instance stays where it is.
|
|
5755
|
+
* the instance stays where it is. For a `single: 'desktop'` kind,
|
|
5756
|
+
* anywhere else on the tile's desktop. */
|
|
5662
5757
|
_swapToPage(tree, leafId, target, currentTopNav, targetTopNav, opts) {
|
|
5663
5758
|
const done = tree.swapToPage(leafId, target, currentTopNav, targetTopNav, opts);
|
|
5664
5759
|
if (done) this._dropRestoredCopies(tree, leafId, targetTopNav, target);
|
|
@@ -5668,11 +5763,12 @@ var WindowManager = class _WindowManager {
|
|
|
5668
5763
|
const leaf = tree.get(leafId);
|
|
5669
5764
|
const tabs = Array.isArray(leaf?.tabs) ? leaf.tabs : null;
|
|
5670
5765
|
if (!tabs) return;
|
|
5766
|
+
const desktopIdx = this.desktops.desktops.findIndex((d) => d.tree === tree);
|
|
5671
5767
|
const elsewhere = this._liveInstanceKeys(tree, leafId);
|
|
5672
5768
|
const keeper = () => {
|
|
5673
5769
|
const seen = /* @__PURE__ */ new Set();
|
|
5674
5770
|
return (t) => {
|
|
5675
|
-
const k =
|
|
5771
|
+
const k = this._scopedKey(t, desktopIdx);
|
|
5676
5772
|
if (k == null) return true;
|
|
5677
5773
|
if (elsewhere.has(k) || seen.has(k)) return false;
|
|
5678
5774
|
seen.add(k);
|
|
@@ -5711,16 +5807,23 @@ var WindowManager = class _WindowManager {
|
|
|
5711
5807
|
}
|
|
5712
5808
|
tree.setActiveLeafTab(leafId, Math.max(0, Math.min(tabs.length - 1, active)));
|
|
5713
5809
|
}
|
|
5714
|
-
/** The
|
|
5715
|
-
* desktop but `leafId` of `tree
|
|
5810
|
+
/** The scoped keys (`_scopedKey`, for `tree`'s desktop) of every LIVE
|
|
5811
|
+
* tab — windows, and every leaf of every desktop but `leafId` of `tree`;
|
|
5812
|
+
* a `single: 'desktop'` tab counts only when it is on `tree`'s desktop.
|
|
5813
|
+
* Archived pages are not live. */
|
|
5716
5814
|
_liveInstanceKeys(tree, leafId) {
|
|
5815
|
+
const here = this.desktops.desktops.findIndex((d) => d.tree === tree);
|
|
5717
5816
|
const keys = /* @__PURE__ */ new Set();
|
|
5718
|
-
const
|
|
5719
|
-
|
|
5817
|
+
const adder = (onHere) => (t) => {
|
|
5818
|
+
if (!onHere && t && this.taxonomy.isSinglePerDesktop(t.kind)) return;
|
|
5819
|
+
const k = this._scopedKey(t, here);
|
|
5720
5820
|
if (k != null) keys.add(k);
|
|
5721
5821
|
};
|
|
5722
|
-
for (const [, rec] of this._windowToLeaf)
|
|
5822
|
+
for (const [, rec] of this._windowToLeaf) {
|
|
5823
|
+
_windowTabSpecs(rec).forEach(adder(rec.desktopIdx === here));
|
|
5824
|
+
}
|
|
5723
5825
|
for (const d of this.desktops.desktops) {
|
|
5826
|
+
const add = adder(d.tree === tree);
|
|
5724
5827
|
for (const leaf of d.tree.leaves()) {
|
|
5725
5828
|
if (d.tree === tree && leaf.id === leafId) continue;
|
|
5726
5829
|
(leaf.tabs || []).forEach(add);
|
|
@@ -5728,28 +5831,62 @@ var WindowManager = class _WindowManager {
|
|
|
5728
5831
|
}
|
|
5729
5832
|
return keys;
|
|
5730
5833
|
}
|
|
5731
|
-
/**
|
|
5732
|
-
*
|
|
5733
|
-
*
|
|
5734
|
-
|
|
5834
|
+
/** Open a window-only kind (KindDef `windowOnly`) in its window — the one
|
|
5835
|
+
* that is open, or a new one — and say so; any other kind: false, and the
|
|
5836
|
+
* caller opens it where it asked (STORY-0127). */
|
|
5837
|
+
_openWindowOnly(kind, props) {
|
|
5838
|
+
if (!this.taxonomy.isWindowOnly?.(kind)) return false;
|
|
5839
|
+
this._navigateWindow(kind, props);
|
|
5840
|
+
return true;
|
|
5841
|
+
}
|
|
5842
|
+
/** Whether a window record holds a window-only kind: such a window never
|
|
5843
|
+
* docks into a tile (Back to tile, maximize, drag-to-dock). */
|
|
5844
|
+
_holdsWindowOnly(rec) {
|
|
5845
|
+
return !!this.taxonomy.isWindowOnly && _windowTabSpecs(rec).some((t) => this.taxonomy.isWindowOnly(t?.kind));
|
|
5846
|
+
}
|
|
5847
|
+
/** A restored layout never holds a window-only kind in a tile: its tabs,
|
|
5848
|
+
* and its tabs archived under a page, go. Windows are not restored, so
|
|
5849
|
+
* the content simply opens in a window the next time it is asked for.
|
|
5850
|
+
* Returns the kinds of the tabs that went, one entry per tab. */
|
|
5851
|
+
_dropWindowOnlyTabs() {
|
|
5852
|
+
if (!this.taxonomy.isWindowOnly) return [];
|
|
5853
|
+
const dropped = [];
|
|
5854
|
+
this._pruneRestoredTabs((t) => {
|
|
5855
|
+
if (!t || !this.taxonomy.isWindowOnly(t.kind)) return true;
|
|
5856
|
+
dropped.push(t.kind);
|
|
5857
|
+
return false;
|
|
5858
|
+
});
|
|
5859
|
+
return dropped;
|
|
5860
|
+
}
|
|
5861
|
+
/** A restored layout keeps the FIRST instance of each single kind (of a
|
|
5862
|
+
* `single: 'desktop'` kind, on each desktop): later live tabs are
|
|
5863
|
+
* dropped (the tile shows the tab that took the dropped one's place, or
|
|
5864
|
+
* closes when it held nothing else), and so are later archived ones.
|
|
5865
|
+
* Answers whether anything was dropped. */
|
|
5735
5866
|
_dropDuplicateSingles() {
|
|
5736
5867
|
const seen = /* @__PURE__ */ new Set();
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
const k = t ? this._instanceKey(t.kind, t.props) : null;
|
|
5868
|
+
return this._pruneRestoredTabs((t, desktopIdx) => {
|
|
5869
|
+
const k = this._scopedKey(t, desktopIdx);
|
|
5740
5870
|
if (k == null) return true;
|
|
5741
5871
|
if (seen.has(k)) return false;
|
|
5742
5872
|
seen.add(k);
|
|
5743
5873
|
return true;
|
|
5744
|
-
};
|
|
5745
|
-
|
|
5874
|
+
});
|
|
5875
|
+
}
|
|
5876
|
+
/** Drop every tab of the restored layout `keep(tab, desktopIdx)` refuses,
|
|
5877
|
+
* in the tiles and in their archived pages; a tile left empty closes
|
|
5878
|
+
* (seeding a content tile if none is left). Returns whether anything
|
|
5879
|
+
* went. */
|
|
5880
|
+
_pruneRestoredTabs(keep) {
|
|
5881
|
+
let dropped = false;
|
|
5882
|
+
for (const [desktopIdx, d] of this.desktops.desktops.entries()) {
|
|
5746
5883
|
const tree = d.tree;
|
|
5747
5884
|
for (const leaf of tree.leaves()) {
|
|
5748
5885
|
const tabs = Array.isArray(leaf.tabs) ? leaf.tabs : [];
|
|
5749
5886
|
let active = leaf.activeTabIdx || 0;
|
|
5750
5887
|
let changed = false;
|
|
5751
5888
|
for (let i = 0; i < tabs.length; ) {
|
|
5752
|
-
if (keep(tabs[i])) {
|
|
5889
|
+
if (keep(tabs[i], desktopIdx)) {
|
|
5753
5890
|
i++;
|
|
5754
5891
|
continue;
|
|
5755
5892
|
}
|
|
@@ -5771,10 +5908,10 @@ var WindowManager = class _WindowManager {
|
|
|
5771
5908
|
}
|
|
5772
5909
|
}
|
|
5773
5910
|
}
|
|
5774
|
-
for (const d of this.desktops.desktops) {
|
|
5911
|
+
for (const [desktopIdx, d] of this.desktops.desktops.entries()) {
|
|
5775
5912
|
for (const leaf of d.tree.leaves()) {
|
|
5776
5913
|
for (const [page, arc] of this._archivedPages(leaf)) {
|
|
5777
|
-
const kept = arc.tabs.filter(keep);
|
|
5914
|
+
const kept = arc.tabs.filter((t) => keep(t, desktopIdx));
|
|
5778
5915
|
if (kept.length === arc.tabs.length) continue;
|
|
5779
5916
|
dropped = true;
|
|
5780
5917
|
if (!kept.length) {
|
|
@@ -5981,6 +6118,7 @@ var WindowManager = class _WindowManager {
|
|
|
5981
6118
|
const tree = this._tree();
|
|
5982
6119
|
const prior = tree.popActiveTabHistory?.(id);
|
|
5983
6120
|
if (!prior || !prior.kind) return false;
|
|
6121
|
+
if (this._openWindowOnly(prior.kind, prior.props || {})) return true;
|
|
5984
6122
|
if (this.revealInstance(prior.kind, prior.props || {})) return true;
|
|
5985
6123
|
const curKind = tree.get(id)?.content?.kind;
|
|
5986
6124
|
const curTopNav = curKind ? this.taxonomy.topNavFor(curKind) : null;
|
|
@@ -6097,6 +6235,7 @@ var WindowManager = class _WindowManager {
|
|
|
6097
6235
|
* the leaf. Used by Backspace + breadcrumb segments + anything
|
|
6098
6236
|
* else that should walk WITHIN the tile rather than reset it. */
|
|
6099
6237
|
navigateActiveTab(leafId, kind, props = {}) {
|
|
6238
|
+
if (this._openWindowOnly(kind, props)) return;
|
|
6100
6239
|
if (this.revealInstance(kind, props)) return;
|
|
6101
6240
|
const tree = this._tree();
|
|
6102
6241
|
const leaf = tree.get(leafId);
|
|
@@ -6151,6 +6290,7 @@ var WindowManager = class _WindowManager {
|
|
|
6151
6290
|
* tab feature shipped), the behavior is identical to the old
|
|
6152
6291
|
* setLeafContent: one tab in, one tab out. */
|
|
6153
6292
|
openInLeaf(leafId, kind, props = {}) {
|
|
6293
|
+
if (this._openWindowOnly(kind, props)) return;
|
|
6154
6294
|
if (this.revealInstance(kind, props)) return;
|
|
6155
6295
|
const tree = this._tree();
|
|
6156
6296
|
const leaf = tree.get(leafId);
|
|
@@ -6168,12 +6308,14 @@ var WindowManager = class _WindowManager {
|
|
|
6168
6308
|
* previous mount, mounts the new kind into the same body element,
|
|
6169
6309
|
* and updates the window's title. */
|
|
6170
6310
|
openInWindow(winId, kind, props = {}) {
|
|
6171
|
-
if (this.
|
|
6311
|
+
if (this._openWindowOnly(kind, props)) return;
|
|
6172
6312
|
const rec = this._windowToLeaf.get(winId);
|
|
6173
6313
|
if (!rec) {
|
|
6174
6314
|
this.openInPrimary(kind, props);
|
|
6175
6315
|
return;
|
|
6176
6316
|
}
|
|
6317
|
+
const loc = this.taxonomy.isSinglePerDesktop(kind) ? this._findInstance(kind, props, rec.desktopIdx) : this.findInstance(kind, props);
|
|
6318
|
+
if (this._revealFound(loc, kind, props)) return;
|
|
6177
6319
|
try {
|
|
6178
6320
|
rec.mountInfo?.destroy?.();
|
|
6179
6321
|
} catch {
|
|
@@ -6207,6 +6349,7 @@ var WindowManager = class _WindowManager {
|
|
|
6207
6349
|
* If only panel tiles exist (or the tree is empty), spawn a new
|
|
6208
6350
|
* content leaf and canonicalize so the panels wrap it. */
|
|
6209
6351
|
openInPrimary(kind, props = {}) {
|
|
6352
|
+
if (this._openWindowOnly(kind, props)) return;
|
|
6210
6353
|
if (this.revealInstance(kind, props)) return;
|
|
6211
6354
|
const tree = this._tree();
|
|
6212
6355
|
let leafId = tree.primaryLeafId();
|
|
@@ -6267,6 +6410,7 @@ var WindowManager = class _WindowManager {
|
|
|
6267
6410
|
* pane in a split never dismounts the original). Returns the new
|
|
6268
6411
|
* leaf id, or null if the leaf can't be split. */
|
|
6269
6412
|
splitLeafWith(leafId, dir, kind, props = {}, title = "") {
|
|
6413
|
+
if (this._openWindowOnly(kind, props)) return null;
|
|
6270
6414
|
if (this.revealInstance(kind, props)) return null;
|
|
6271
6415
|
const tree = this._tree();
|
|
6272
6416
|
leafId = this._contentLeafId(leafId);
|
|
@@ -6676,6 +6820,9 @@ var WindowManager = class _WindowManager {
|
|
|
6676
6820
|
canMaximize: true,
|
|
6677
6821
|
canResize: true,
|
|
6678
6822
|
modal: false,
|
|
6823
|
+
// BUG-0053. The id is minted per window and never opens again, so
|
|
6824
|
+
// geometry saved under it could never be restored.
|
|
6825
|
+
persistGeometry: false,
|
|
6679
6826
|
// C15. Dropping a promoted window on a tile PUTS IT BACK — as that
|
|
6680
6827
|
// tile's content, as a split of it, or as one of its tabs. Off
|
|
6681
6828
|
// unless the embedder asked, because it changes what a drag to an
|
|
@@ -7130,6 +7277,7 @@ var WindowManager = class _WindowManager {
|
|
|
7130
7277
|
bringBackWindow(windowId) {
|
|
7131
7278
|
const rec = this._windowToLeaf.get(windowId);
|
|
7132
7279
|
if (!rec) return false;
|
|
7280
|
+
if (this._holdsWindowOnly(rec)) return false;
|
|
7133
7281
|
const tree = this.desktops.desktops[rec.desktopIdx]?.tree;
|
|
7134
7282
|
const home = rec.homeLeafId ? tree?.get(rec.homeLeafId) : null;
|
|
7135
7283
|
if (home && home.kind === "leaf") {
|
|
@@ -7718,6 +7866,7 @@ var WindowManager = class _WindowManager {
|
|
|
7718
7866
|
win.clearSnapPreview();
|
|
7719
7867
|
return true;
|
|
7720
7868
|
}
|
|
7869
|
+
if (typeof desktopIdx === "number" && desktopIdx !== record.desktopIdx && this.desktopMoveRefusal({ windowId: winId }, desktopIdx)) return false;
|
|
7721
7870
|
if (typeof desktopIdx === "number") record.desktopIdx = desktopIdx;
|
|
7722
7871
|
const docked = this.dockWindowInto(winId, {
|
|
7723
7872
|
leafId,
|
|
@@ -7748,6 +7897,7 @@ var WindowManager = class _WindowManager {
|
|
|
7748
7897
|
dockWindowInto(windowId, target) {
|
|
7749
7898
|
const rec = this._windowToLeaf.get(windowId);
|
|
7750
7899
|
if (!rec || !target?.leafId) return false;
|
|
7900
|
+
if (this._holdsWindowOnly(rec)) return false;
|
|
7751
7901
|
rec._dock = { ...target };
|
|
7752
7902
|
rec._demoting = true;
|
|
7753
7903
|
try {
|
|
@@ -7790,6 +7940,7 @@ var WindowManager = class _WindowManager {
|
|
|
7790
7940
|
if (!rec) return;
|
|
7791
7941
|
if (rec.desktopIdx === targetIdx) return;
|
|
7792
7942
|
if (this.desktops.desktops[targetIdx]?.fixed) return;
|
|
7943
|
+
if (this.desktopMoveRefusal({ windowId }, targetIdx)) return;
|
|
7793
7944
|
this.desktops.ensureCount(targetIdx + 1);
|
|
7794
7945
|
rec.desktopIdx = targetIdx;
|
|
7795
7946
|
if (!rec.homeContainer && rec.homeLeafId) {
|
|
@@ -7917,17 +8068,20 @@ var WindowManager = class _WindowManager {
|
|
|
7917
8068
|
e.preventDefault();
|
|
7918
8069
|
const rec = this._windowToLeaf.get(winId);
|
|
7919
8070
|
if (!rec) return;
|
|
7920
|
-
const items = [
|
|
8071
|
+
const items = this._holdsWindowOnly(rec) ? [] : [
|
|
7921
8072
|
{ label: "Back to tile", icon: "close_fullscreen", action: "back" }
|
|
7922
8073
|
];
|
|
7923
8074
|
const targets = [...this.desktops.desktops.entries()].filter(([i, d]) => i !== rec.desktopIdx && !d.fixed);
|
|
7924
8075
|
if (targets.length) {
|
|
7925
8076
|
items.push({ separator: true });
|
|
7926
8077
|
for (const [i, d] of targets) {
|
|
8078
|
+
const refusal = this.desktopMoveRefusal({ windowId: winId }, i);
|
|
7927
8079
|
items.push({
|
|
7928
8080
|
label: `Move to desktop ${d.label}`,
|
|
7929
8081
|
icon: "sweep",
|
|
7930
|
-
action: `move:${i}
|
|
8082
|
+
action: `move:${i}`,
|
|
8083
|
+
disabled: !!refusal,
|
|
8084
|
+
title: refusal || void 0
|
|
7931
8085
|
});
|
|
7932
8086
|
}
|
|
7933
8087
|
}
|
|
@@ -8426,7 +8580,9 @@ var WindowManager = class _WindowManager {
|
|
|
8426
8580
|
* ruling is that a tile operation may move a window and never destroy it,
|
|
8427
8581
|
* and removing a desktop is the largest tile operation there is: they come
|
|
8428
8582
|
* across to the desktop that ends up active, re-homed onto its ground by
|
|
8429
|
-
* `_rehomeContainedWindows` on the render below.
|
|
8583
|
+
* `_rehomeContainedWindows` on the render below. One holding a
|
|
8584
|
+
* `single: 'desktop'` kind the active desktop already has makes it hold
|
|
8585
|
+
* the kind twice, until a reload keeps the first.
|
|
8430
8586
|
*/
|
|
8431
8587
|
removeDesktop(idx) {
|
|
8432
8588
|
const m = this.desktops;
|
|
@@ -8474,6 +8630,7 @@ var WindowManager = class _WindowManager {
|
|
|
8474
8630
|
if (closeChrome === false || closeChrome?.disabled === true) return;
|
|
8475
8631
|
if (this.desktops.desktops[idx]?.fixed) return;
|
|
8476
8632
|
if (idx === this.desktops.activeIdx) return;
|
|
8633
|
+
if (this.desktopMoveRefusal({ leafId: focused.id }, idx)) return;
|
|
8477
8634
|
this.desktops.ensureCount(idx + 1);
|
|
8478
8635
|
const target = this.desktops.desktops[idx];
|
|
8479
8636
|
const tabs = Array.isArray(focused.tabs) && focused.tabs.length ? focused.tabs : [{ kind: focused.content.kind, props: focused.content.props, title: focused.title }];
|
|
@@ -8657,6 +8814,7 @@ var WindowManager = class _WindowManager {
|
|
|
8657
8814
|
}
|
|
8658
8815
|
/** The routing behind `navigate`, for a kind known to be registered. */
|
|
8659
8816
|
_route(kind, props, opts) {
|
|
8817
|
+
if (this._openWindowOnly(kind, props)) return;
|
|
8660
8818
|
const { ctx = null, transient = false } = opts;
|
|
8661
8819
|
const background = !!opts.background;
|
|
8662
8820
|
let { dest = "main", newTab = false } = opts;
|
|
@@ -8725,6 +8883,8 @@ var WindowManager = class _WindowManager {
|
|
|
8725
8883
|
* source leaf — closing the window just disposes the content. */
|
|
8726
8884
|
_navigateWindow(kind, props) {
|
|
8727
8885
|
if (this.revealInstance(kind, props)) return;
|
|
8886
|
+
const size = this.taxonomy.windowSize?.(kind);
|
|
8887
|
+
const dockable = !this.taxonomy.isWindowOnly?.(kind);
|
|
8728
8888
|
const winId = `twm-mw-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 5)}`;
|
|
8729
8889
|
const contentEl = document.createElement("div");
|
|
8730
8890
|
contentEl.className = "twm-window-content";
|
|
@@ -8741,17 +8901,22 @@ var WindowManager = class _WindowManager {
|
|
|
8741
8901
|
title: mountInfo?.title || title,
|
|
8742
8902
|
icon: "web_asset",
|
|
8743
8903
|
content: contentEl,
|
|
8904
|
+
...size?.width ? { defaultWidth: size.width } : {},
|
|
8905
|
+
...size?.height ? { defaultHeight: size.height } : {},
|
|
8744
8906
|
canMinimize: true,
|
|
8745
8907
|
canMaximize: true,
|
|
8746
8908
|
canResize: true,
|
|
8747
8909
|
modal: false,
|
|
8910
|
+
// BUG-0053. The id is minted per window and never opens again, so
|
|
8911
|
+
// geometry saved under it could never be restored.
|
|
8912
|
+
persistGeometry: false,
|
|
8748
8913
|
// C15, and this is the SECOND of the two places the WM builds a
|
|
8749
8914
|
// window. Alt+N and "Open in new window" produce a window that is
|
|
8750
8915
|
// every bit as dockable as a promoted one, and a window that can be
|
|
8751
8916
|
// dragged onto a tile in one case and not the other is a rule
|
|
8752
8917
|
// nobody can learn.
|
|
8753
|
-
snap: this.snapPromotion,
|
|
8754
|
-
snapController: this.snapPromotion ? this._snapController() : null,
|
|
8918
|
+
snap: dockable && this.snapPromotion,
|
|
8919
|
+
snapController: dockable && this.snapPromotion ? this._snapController() : null,
|
|
8755
8920
|
// R1. THE PANE IS A BOX WITH `overflow: hidden`. A window contained
|
|
8756
8921
|
// to one (C21) cannot be dragged a single pixel outside it, so
|
|
8757
8922
|
// "drag a window from one tile to another" — the gesture all three
|
|
@@ -8769,9 +8934,11 @@ var WindowManager = class _WindowManager {
|
|
|
8769
8934
|
// harder. So the maximize button docks — and the separate demote
|
|
8770
8935
|
// button the WM used to inject beside it is gone, because two
|
|
8771
8936
|
// buttons for one verb is how you get a chrome nobody reads.
|
|
8772
|
-
|
|
8773
|
-
|
|
8774
|
-
|
|
8937
|
+
...dockable ? {
|
|
8938
|
+
onMaximize: () => this.bringBackWindow(winId),
|
|
8939
|
+
maximizeIcon: "close_fullscreen",
|
|
8940
|
+
maximizeTitle: "Back to tile"
|
|
8941
|
+
} : {},
|
|
8775
8942
|
onClose: () => this._onManagedWindowClosed(winId, mountInfo)
|
|
8776
8943
|
});
|
|
8777
8944
|
this._windowToLeaf.set(winId, {
|
|
@@ -8842,6 +9009,7 @@ var WindowManager = class _WindowManager {
|
|
|
8842
9009
|
* routing) but uses `appendLeafTab` so the existing content
|
|
8843
9010
|
* stays in place as a tab. */
|
|
8844
9011
|
openInTabFromContext(ctx, kind, props = {}, transient = false, background = false) {
|
|
9012
|
+
if (this._openWindowOnly(kind, props)) return;
|
|
8845
9013
|
if (this.revealInstance(kind, props)) return;
|
|
8846
9014
|
if (ctx?.windowId && this._windowToLeaf.has(ctx.windowId)) {
|
|
8847
9015
|
this.openInWindow(ctx.windowId, kind, props);
|
|
@@ -8875,6 +9043,7 @@ var WindowManager = class _WindowManager {
|
|
|
8875
9043
|
* "Add row" button, which passes no ctx) reliably lands as a sibling
|
|
8876
9044
|
* tab in the main tile rather than swapping its content. */
|
|
8877
9045
|
openInTabInPrimary(kind, props = {}, transient = false, background = false) {
|
|
9046
|
+
if (this._openWindowOnly(kind, props)) return;
|
|
8878
9047
|
if (this.revealInstance(kind, props)) return;
|
|
8879
9048
|
const tree = this._tree();
|
|
8880
9049
|
const leafId = tree.primaryLeafId();
|
|
@@ -9970,10 +10139,13 @@ function _tileContextMenu(wm, leafId, x, y) {
|
|
|
9970
10139
|
if (wm.desktops.desktops.length > 1 && !isPanel) {
|
|
9971
10140
|
for (const [i, d] of wm.desktops.desktops.entries()) {
|
|
9972
10141
|
if (i === wm.desktops.activeIdx || d.fixed) continue;
|
|
10142
|
+
const refusal = wm.desktopMoveRefusal({ leafId }, i);
|
|
9973
10143
|
items.push({
|
|
9974
10144
|
label: `Move to desktop ${d.label}`,
|
|
9975
10145
|
icon: "sweep",
|
|
9976
|
-
action: `move:${i}
|
|
10146
|
+
action: `move:${i}`,
|
|
10147
|
+
disabled: !!refusal,
|
|
10148
|
+
title: refusal || void 0
|
|
9977
10149
|
});
|
|
9978
10150
|
}
|
|
9979
10151
|
}
|