flexdesk 0.17.16 → 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 +19 -0
- package/css/base.css +12 -1
- package/css/flexdesk.css +12 -1
- package/css/tokens.css +3 -0
- package/dist/flexdesk.css +12 -1
- package/dist/tokens.css +3 -0
- package/dist/widgets.js +4113 -4088
- package/dist/widgets.js.map +4 -4
- package/dist/wm.js +114 -30
- package/dist/wm.js.map +2 -2
- package/docs/components/notifications.md +23 -1
- package/docs/components/panels-and-desktops.md +22 -0
- package/docs/components/shell.md +54 -0
- package/package.json +1 -1
- package/src/tiling/kind_taxonomy.js +21 -13
- package/src/tiling/shell.js +4 -1
- package/src/tiling/wm.js +123 -28
- package/src/ui/components/notification_history.js +11 -1
- package/src/ui/notification_center.js +47 -1
package/dist/wm.js
CHANGED
|
@@ -2735,10 +2735,11 @@ function createTaxonomy({ kinds, root } = {}) {
|
|
|
2735
2735
|
const s = _kinds[kind]?.single;
|
|
2736
2736
|
return s === true || typeof s === "string" && s !== "";
|
|
2737
2737
|
};
|
|
2738
|
+
const isSinglePerDesktop = (kind) => _kinds[kind]?.single === "desktop";
|
|
2738
2739
|
const instanceKey = (kind, props) => {
|
|
2739
2740
|
if (!isSingle(kind)) return null;
|
|
2740
2741
|
const by = _kinds[kind].single;
|
|
2741
|
-
if (by === true) return String(kind);
|
|
2742
|
+
if (by === true || by === "desktop") return String(kind);
|
|
2742
2743
|
const v = props?.[by];
|
|
2743
2744
|
return `${kind}\0${v == null ? "" : String(v)}`;
|
|
2744
2745
|
};
|
|
@@ -2761,6 +2762,7 @@ function createTaxonomy({ kinds, root } = {}) {
|
|
|
2761
2762
|
panelOffer,
|
|
2762
2763
|
topNavEntries,
|
|
2763
2764
|
isSingle,
|
|
2765
|
+
isSinglePerDesktop,
|
|
2764
2766
|
instanceKey,
|
|
2765
2767
|
singleReason,
|
|
2766
2768
|
isWindowOnly,
|
|
@@ -5526,6 +5528,28 @@ var WindowManager = class _WindowManager {
|
|
|
5526
5528
|
//
|
|
5527
5529
|
// MOVE verbs (float, dock, drag a tab, move to desktop) carry the one
|
|
5528
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
|
+
}
|
|
5529
5553
|
/** The instance key of a tab spec, or null when its kind is not single. */
|
|
5530
5554
|
_instanceKey(kind, props) {
|
|
5531
5555
|
return this.taxonomy.instanceKey(kind, props);
|
|
@@ -5535,27 +5559,36 @@ var WindowManager = class _WindowManager {
|
|
|
5535
5559
|
return this.taxonomy.isSingle(kind);
|
|
5536
5560
|
}
|
|
5537
5561
|
/** Where the one instance of a single kind is open, or null — also null
|
|
5538
|
-
* 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:
|
|
5539
5564
|
* `{ where: 'window', windowId, idx, desktopIdx }`
|
|
5540
5565
|
* `{ where: 'tab', desktopIdx, leafId, idx }`
|
|
5541
5566
|
* `{ where: 'archive', desktopIdx, leafId, page, idx }` — a tab of a
|
|
5542
5567
|
* top-nav page the tile has switched away from (`pageTabs`). */
|
|
5543
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) {
|
|
5544
5575
|
const key = this._instanceKey(kind, props);
|
|
5545
5576
|
if (key == null) return null;
|
|
5546
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);
|
|
5547
5579
|
for (const [windowId, rec] of this._windowToLeaf) {
|
|
5580
|
+
if (windowId === exceptWindowId) continue;
|
|
5581
|
+
if (on != null && rec.desktopIdx !== on) continue;
|
|
5548
5582
|
const idx = _windowTabSpecs(rec).findIndex(same);
|
|
5549
5583
|
if (idx >= 0) return { where: "window", windowId, idx, desktopIdx: rec.desktopIdx };
|
|
5550
5584
|
}
|
|
5551
|
-
const
|
|
5552
|
-
for (const [desktopIdx, d] of desktops.entries()) {
|
|
5585
|
+
for (const [desktopIdx, d] of desktops) {
|
|
5553
5586
|
for (const leaf of d.tree.leaves()) {
|
|
5554
5587
|
const idx = (leaf.tabs || []).findIndex(same);
|
|
5555
5588
|
if (idx >= 0) return { where: "tab", desktopIdx, leafId: leaf.id, idx };
|
|
5556
5589
|
}
|
|
5557
5590
|
}
|
|
5558
|
-
for (const [desktopIdx, d] of desktops
|
|
5591
|
+
for (const [desktopIdx, d] of desktops) {
|
|
5559
5592
|
for (const leaf of d.tree.leaves()) {
|
|
5560
5593
|
for (const [page, arc] of this._archivedPages(leaf)) {
|
|
5561
5594
|
const idx = arc.tabs.findIndex(same);
|
|
@@ -5565,6 +5598,30 @@ var WindowManager = class _WindowManager {
|
|
|
5565
5598
|
}
|
|
5566
5599
|
return null;
|
|
5567
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
|
+
}
|
|
5568
5625
|
/** Reveal the instance of a single kind that is open: answers whether
|
|
5569
5626
|
* there was one. Every open path asks this first.
|
|
5570
5627
|
*
|
|
@@ -5578,7 +5635,11 @@ var WindowManager = class _WindowManager {
|
|
|
5578
5635
|
* open names no props at all — a top-nav button, `openInPrimary(kind)` —
|
|
5579
5636
|
* which asks to SEE the instance, not to empty it. */
|
|
5580
5637
|
revealInstance(kind, props = {}) {
|
|
5581
|
-
|
|
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) {
|
|
5582
5643
|
if (!loc || !this._revealAt(loc)) return false;
|
|
5583
5644
|
if (props && Object.keys(props).length) this._deliverProps(kind, props);
|
|
5584
5645
|
return true;
|
|
@@ -5691,7 +5752,8 @@ var WindowManager = class _WindowManager {
|
|
|
5691
5752
|
* soon as the tile leaves the page by a path other than `swapToPage`
|
|
5692
5753
|
* (`navigateActiveTab`). So a restored tab whose instance is live
|
|
5693
5754
|
* anywhere else is dropped from the tile and from the restored archive;
|
|
5694
|
-
* 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. */
|
|
5695
5757
|
_swapToPage(tree, leafId, target, currentTopNav, targetTopNav, opts) {
|
|
5696
5758
|
const done = tree.swapToPage(leafId, target, currentTopNav, targetTopNav, opts);
|
|
5697
5759
|
if (done) this._dropRestoredCopies(tree, leafId, targetTopNav, target);
|
|
@@ -5701,11 +5763,12 @@ var WindowManager = class _WindowManager {
|
|
|
5701
5763
|
const leaf = tree.get(leafId);
|
|
5702
5764
|
const tabs = Array.isArray(leaf?.tabs) ? leaf.tabs : null;
|
|
5703
5765
|
if (!tabs) return;
|
|
5766
|
+
const desktopIdx = this.desktops.desktops.findIndex((d) => d.tree === tree);
|
|
5704
5767
|
const elsewhere = this._liveInstanceKeys(tree, leafId);
|
|
5705
5768
|
const keeper = () => {
|
|
5706
5769
|
const seen = /* @__PURE__ */ new Set();
|
|
5707
5770
|
return (t) => {
|
|
5708
|
-
const k =
|
|
5771
|
+
const k = this._scopedKey(t, desktopIdx);
|
|
5709
5772
|
if (k == null) return true;
|
|
5710
5773
|
if (elsewhere.has(k) || seen.has(k)) return false;
|
|
5711
5774
|
seen.add(k);
|
|
@@ -5744,16 +5807,23 @@ var WindowManager = class _WindowManager {
|
|
|
5744
5807
|
}
|
|
5745
5808
|
tree.setActiveLeafTab(leafId, Math.max(0, Math.min(tabs.length - 1, active)));
|
|
5746
5809
|
}
|
|
5747
|
-
/** The
|
|
5748
|
-
* 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. */
|
|
5749
5814
|
_liveInstanceKeys(tree, leafId) {
|
|
5815
|
+
const here = this.desktops.desktops.findIndex((d) => d.tree === tree);
|
|
5750
5816
|
const keys = /* @__PURE__ */ new Set();
|
|
5751
|
-
const
|
|
5752
|
-
|
|
5817
|
+
const adder = (onHere) => (t) => {
|
|
5818
|
+
if (!onHere && t && this.taxonomy.isSinglePerDesktop(t.kind)) return;
|
|
5819
|
+
const k = this._scopedKey(t, here);
|
|
5753
5820
|
if (k != null) keys.add(k);
|
|
5754
5821
|
};
|
|
5755
|
-
for (const [, rec] of this._windowToLeaf)
|
|
5822
|
+
for (const [, rec] of this._windowToLeaf) {
|
|
5823
|
+
_windowTabSpecs(rec).forEach(adder(rec.desktopIdx === here));
|
|
5824
|
+
}
|
|
5756
5825
|
for (const d of this.desktops.desktops) {
|
|
5826
|
+
const add = adder(d.tree === tree);
|
|
5757
5827
|
for (const leaf of d.tree.leaves()) {
|
|
5758
5828
|
if (d.tree === tree && leaf.id === leafId) continue;
|
|
5759
5829
|
(leaf.tabs || []).forEach(add);
|
|
@@ -5788,33 +5858,35 @@ var WindowManager = class _WindowManager {
|
|
|
5788
5858
|
});
|
|
5789
5859
|
return dropped;
|
|
5790
5860
|
}
|
|
5791
|
-
/** A restored layout keeps the FIRST instance of each single kind
|
|
5792
|
-
*
|
|
5793
|
-
*
|
|
5794
|
-
*
|
|
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. */
|
|
5795
5866
|
_dropDuplicateSingles() {
|
|
5796
5867
|
const seen = /* @__PURE__ */ new Set();
|
|
5797
|
-
return this._pruneRestoredTabs((t) => {
|
|
5798
|
-
const k =
|
|
5868
|
+
return this._pruneRestoredTabs((t, desktopIdx) => {
|
|
5869
|
+
const k = this._scopedKey(t, desktopIdx);
|
|
5799
5870
|
if (k == null) return true;
|
|
5800
5871
|
if (seen.has(k)) return false;
|
|
5801
5872
|
seen.add(k);
|
|
5802
5873
|
return true;
|
|
5803
5874
|
});
|
|
5804
5875
|
}
|
|
5805
|
-
/** Drop every tab of the restored layout `keep(tab)` refuses,
|
|
5806
|
-
* tiles and in their archived pages; a tile left empty closes
|
|
5807
|
-
* content tile if none is left). Returns whether anything
|
|
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. */
|
|
5808
5880
|
_pruneRestoredTabs(keep) {
|
|
5809
5881
|
let dropped = false;
|
|
5810
|
-
for (const d of this.desktops.desktops) {
|
|
5882
|
+
for (const [desktopIdx, d] of this.desktops.desktops.entries()) {
|
|
5811
5883
|
const tree = d.tree;
|
|
5812
5884
|
for (const leaf of tree.leaves()) {
|
|
5813
5885
|
const tabs = Array.isArray(leaf.tabs) ? leaf.tabs : [];
|
|
5814
5886
|
let active = leaf.activeTabIdx || 0;
|
|
5815
5887
|
let changed = false;
|
|
5816
5888
|
for (let i = 0; i < tabs.length; ) {
|
|
5817
|
-
if (keep(tabs[i])) {
|
|
5889
|
+
if (keep(tabs[i], desktopIdx)) {
|
|
5818
5890
|
i++;
|
|
5819
5891
|
continue;
|
|
5820
5892
|
}
|
|
@@ -5836,10 +5908,10 @@ var WindowManager = class _WindowManager {
|
|
|
5836
5908
|
}
|
|
5837
5909
|
}
|
|
5838
5910
|
}
|
|
5839
|
-
for (const d of this.desktops.desktops) {
|
|
5911
|
+
for (const [desktopIdx, d] of this.desktops.desktops.entries()) {
|
|
5840
5912
|
for (const leaf of d.tree.leaves()) {
|
|
5841
5913
|
for (const [page, arc] of this._archivedPages(leaf)) {
|
|
5842
|
-
const kept = arc.tabs.filter(keep);
|
|
5914
|
+
const kept = arc.tabs.filter((t) => keep(t, desktopIdx));
|
|
5843
5915
|
if (kept.length === arc.tabs.length) continue;
|
|
5844
5916
|
dropped = true;
|
|
5845
5917
|
if (!kept.length) {
|
|
@@ -6237,12 +6309,13 @@ var WindowManager = class _WindowManager {
|
|
|
6237
6309
|
* and updates the window's title. */
|
|
6238
6310
|
openInWindow(winId, kind, props = {}) {
|
|
6239
6311
|
if (this._openWindowOnly(kind, props)) return;
|
|
6240
|
-
if (this.revealInstance(kind, props)) return;
|
|
6241
6312
|
const rec = this._windowToLeaf.get(winId);
|
|
6242
6313
|
if (!rec) {
|
|
6243
6314
|
this.openInPrimary(kind, props);
|
|
6244
6315
|
return;
|
|
6245
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;
|
|
6246
6319
|
try {
|
|
6247
6320
|
rec.mountInfo?.destroy?.();
|
|
6248
6321
|
} catch {
|
|
@@ -7793,6 +7866,7 @@ var WindowManager = class _WindowManager {
|
|
|
7793
7866
|
win.clearSnapPreview();
|
|
7794
7867
|
return true;
|
|
7795
7868
|
}
|
|
7869
|
+
if (typeof desktopIdx === "number" && desktopIdx !== record.desktopIdx && this.desktopMoveRefusal({ windowId: winId }, desktopIdx)) return false;
|
|
7796
7870
|
if (typeof desktopIdx === "number") record.desktopIdx = desktopIdx;
|
|
7797
7871
|
const docked = this.dockWindowInto(winId, {
|
|
7798
7872
|
leafId,
|
|
@@ -7866,6 +7940,7 @@ var WindowManager = class _WindowManager {
|
|
|
7866
7940
|
if (!rec) return;
|
|
7867
7941
|
if (rec.desktopIdx === targetIdx) return;
|
|
7868
7942
|
if (this.desktops.desktops[targetIdx]?.fixed) return;
|
|
7943
|
+
if (this.desktopMoveRefusal({ windowId }, targetIdx)) return;
|
|
7869
7944
|
this.desktops.ensureCount(targetIdx + 1);
|
|
7870
7945
|
rec.desktopIdx = targetIdx;
|
|
7871
7946
|
if (!rec.homeContainer && rec.homeLeafId) {
|
|
@@ -8000,10 +8075,13 @@ var WindowManager = class _WindowManager {
|
|
|
8000
8075
|
if (targets.length) {
|
|
8001
8076
|
items.push({ separator: true });
|
|
8002
8077
|
for (const [i, d] of targets) {
|
|
8078
|
+
const refusal = this.desktopMoveRefusal({ windowId: winId }, i);
|
|
8003
8079
|
items.push({
|
|
8004
8080
|
label: `Move to desktop ${d.label}`,
|
|
8005
8081
|
icon: "sweep",
|
|
8006
|
-
action: `move:${i}
|
|
8082
|
+
action: `move:${i}`,
|
|
8083
|
+
disabled: !!refusal,
|
|
8084
|
+
title: refusal || void 0
|
|
8007
8085
|
});
|
|
8008
8086
|
}
|
|
8009
8087
|
}
|
|
@@ -8502,7 +8580,9 @@ var WindowManager = class _WindowManager {
|
|
|
8502
8580
|
* ruling is that a tile operation may move a window and never destroy it,
|
|
8503
8581
|
* and removing a desktop is the largest tile operation there is: they come
|
|
8504
8582
|
* across to the desktop that ends up active, re-homed onto its ground by
|
|
8505
|
-
* `_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.
|
|
8506
8586
|
*/
|
|
8507
8587
|
removeDesktop(idx) {
|
|
8508
8588
|
const m = this.desktops;
|
|
@@ -8550,6 +8630,7 @@ var WindowManager = class _WindowManager {
|
|
|
8550
8630
|
if (closeChrome === false || closeChrome?.disabled === true) return;
|
|
8551
8631
|
if (this.desktops.desktops[idx]?.fixed) return;
|
|
8552
8632
|
if (idx === this.desktops.activeIdx) return;
|
|
8633
|
+
if (this.desktopMoveRefusal({ leafId: focused.id }, idx)) return;
|
|
8553
8634
|
this.desktops.ensureCount(idx + 1);
|
|
8554
8635
|
const target = this.desktops.desktops[idx];
|
|
8555
8636
|
const tabs = Array.isArray(focused.tabs) && focused.tabs.length ? focused.tabs : [{ kind: focused.content.kind, props: focused.content.props, title: focused.title }];
|
|
@@ -10058,10 +10139,13 @@ function _tileContextMenu(wm, leafId, x, y) {
|
|
|
10058
10139
|
if (wm.desktops.desktops.length > 1 && !isPanel) {
|
|
10059
10140
|
for (const [i, d] of wm.desktops.desktops.entries()) {
|
|
10060
10141
|
if (i === wm.desktops.activeIdx || d.fixed) continue;
|
|
10142
|
+
const refusal = wm.desktopMoveRefusal({ leafId }, i);
|
|
10061
10143
|
items.push({
|
|
10062
10144
|
label: `Move to desktop ${d.label}`,
|
|
10063
10145
|
icon: "sweep",
|
|
10064
|
-
action: `move:${i}
|
|
10146
|
+
action: `move:${i}`,
|
|
10147
|
+
disabled: !!refusal,
|
|
10148
|
+
title: refusal || void 0
|
|
10065
10149
|
});
|
|
10066
10150
|
}
|
|
10067
10151
|
}
|