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/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. One of:
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 desktops = this.desktops.desktops;
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.entries()) {
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
- const loc = this.findInstance(kind, props);
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 = t ? this._instanceKey(t.kind, t.props) : null;
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 instance keys of every LIVE tab — windows, and every leaf of every
5748
- * desktop but `leafId` of `tree`. Archived pages are not live. */
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 add = (t) => {
5752
- const k = t ? this._instanceKey(t.kind, t.props) : null;
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) _windowTabSpecs(rec).forEach(add);
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: later
5792
- * live tabs are dropped (the tile shows the tab that took the dropped
5793
- * one's place, or closes when it held nothing else), and so are later
5794
- * archived ones. Answers whether anything was dropped. */
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 = t ? this._instanceKey(t.kind, t.props) : null;
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, 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. */
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
  }