flexdesk 0.9.1 → 0.10.0
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/README.md +29 -0
- package/dist/wm.js +172 -33
- package/dist/wm.js.map +3 -3
- package/package.json +1 -1
- package/src/tiling/desktops.js +71 -2
- package/src/tiling/shell.js +62 -16
- package/src/tiling/wm.js +52 -12
package/README.md
CHANGED
|
@@ -130,6 +130,35 @@ await createShell({
|
|
|
130
130
|
});
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
+
### Your desktops beside the app's (0.10.0)
|
|
134
|
+
|
|
135
|
+
Pass `chrome.desktops` and the shell paints a desktop bar: a chip per desktop
|
|
136
|
+
and a `+`. An app that keeps desktops of its own (one per page, say) marks them
|
|
137
|
+
on the desktop objects themselves:
|
|
138
|
+
|
|
139
|
+
```js
|
|
140
|
+
const shell = await createShell({ /* … */ eventBus,
|
|
141
|
+
chrome: { desktops: barEl }, desktopBar: { switchOnAdd: true } });
|
|
142
|
+
shell.wm.desktops.ensureCount(pages.length);
|
|
143
|
+
shell.wm.desktops.desktops.slice(0, pages.length)
|
|
144
|
+
.forEach((d) => { d.group = 'Workbench'; d.fixed = true; });
|
|
145
|
+
eventBus.emit('wm:changed', {}); // repaint the bar
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Desktops that share a **`group`** are one chip, labelled by the group, and one
|
|
149
|
+
stop for Ctrl+Left/Right; the chip goes back to the member that was on screen
|
|
150
|
+
last (`wm.desktops.chips()` says what the bar draws). A **`fixed`** desktop is
|
|
151
|
+
the app's: the bar offers no rename and no delete, no menu offers it as a
|
|
152
|
+
*Move to desktop* target, and `removeDesktop`, `moveFocusedToDesktop` and
|
|
153
|
+
`moveWindowToDesktop` refuse it. Both are persisted. A desktop the user adds is
|
|
154
|
+
numbered among the ungrouped ones, and `desktopBar.switchOnAdd` puts it on
|
|
155
|
+
screen. With neither set anywhere, the bar is what it always was.
|
|
156
|
+
|
|
157
|
+
*Move to desktop* now carries every tab of the pane and adds them to the target's
|
|
158
|
+
main pane; it used to replace that pane with the active tab alone. A target still
|
|
159
|
+
holding only its seed is replaced, as before. `dispose()` removes the bar, so a
|
|
160
|
+
rebuilt shell paints a fresh one.
|
|
161
|
+
|
|
133
162
|
### The palette that finds anything (0.9.0; matching tightened in 0.9.1)
|
|
134
163
|
|
|
135
164
|
Ctrl+K's grouped layout. Every option is off unless you pass it, so a shell that
|
package/dist/wm.js
CHANGED
|
@@ -1659,7 +1659,10 @@ function _makeDesktop(label, seed, panelDefaults = DEFAULT_PANEL_STATE) {
|
|
|
1659
1659
|
// Boot default: whatever the embedder asked for, left + right + bottom
|
|
1660
1660
|
// when it asked for nothing. Names are assigned by wm._canonicalize via
|
|
1661
1661
|
// PANEL_TITLES.
|
|
1662
|
-
panels: { ...panelDefaults }
|
|
1662
|
+
panels: { ...panelDefaults },
|
|
1663
|
+
// C40. Both unset is a desktop exactly as it always was.
|
|
1664
|
+
group: null,
|
|
1665
|
+
fixed: false
|
|
1663
1666
|
};
|
|
1664
1667
|
}
|
|
1665
1668
|
var DesktopManager = class _DesktopManager {
|
|
@@ -1678,6 +1681,7 @@ var DesktopManager = class _DesktopManager {
|
|
|
1678
1681
|
this.panelDefaults = { ...DEFAULT_PANEL_STATE, ...panelDefaults || {} };
|
|
1679
1682
|
this.desktops = [_makeDesktop("1", seed, this.panelDefaults)];
|
|
1680
1683
|
this.activeIdx = 0;
|
|
1684
|
+
this.lastInGroup = /* @__PURE__ */ new Map();
|
|
1681
1685
|
}
|
|
1682
1686
|
active() {
|
|
1683
1687
|
return this.desktops[this.activeIdx];
|
|
@@ -1686,8 +1690,76 @@ var DesktopManager = class _DesktopManager {
|
|
|
1686
1690
|
if (idx < 0 || idx >= this.desktops.length) return false;
|
|
1687
1691
|
if (idx === this.activeIdx) return false;
|
|
1688
1692
|
this.activeIdx = idx;
|
|
1693
|
+
const d = this.desktops[idx];
|
|
1694
|
+
if (d.group) this.lastInGroup.set(d.group, d.id);
|
|
1689
1695
|
return true;
|
|
1690
1696
|
}
|
|
1697
|
+
/**
|
|
1698
|
+
* ══ C40. WHAT THE DESKTOP BAR DRAWS — ONE CHIP PER GROUP ═══════════════
|
|
1699
|
+
*
|
|
1700
|
+
* An embedder may give desktops a `group` and mark them `fixed`. Desktops
|
|
1701
|
+
* that share a group are ONE stop for the user — one chip in the bar, one
|
|
1702
|
+
* step of Ctrl+Left/Right — and the chip goes back to whichever member was
|
|
1703
|
+
* last on screen; which member that is, is the embedder's business (Tables
|
|
1704
|
+
* keeps one desktop per page under a "Workbench" group, and the rail picks
|
|
1705
|
+
* among them). A `fixed` desktop is the embedder's own: the bar offers no
|
|
1706
|
+
* rename and no delete for it, and no menu offers it as a "Move to"
|
|
1707
|
+
* destination.
|
|
1708
|
+
*
|
|
1709
|
+
* With no group set anywhere this is one chip per desktop, in order — the
|
|
1710
|
+
* bar every existing embedder has.
|
|
1711
|
+
*
|
|
1712
|
+
* @returns {Array<{ idx: number, label: string, group: string|null,
|
|
1713
|
+
* fixed: boolean, active: boolean, members: number[] }>} `idx` is the
|
|
1714
|
+
* desktop the chip switches to.
|
|
1715
|
+
*/
|
|
1716
|
+
chips() {
|
|
1717
|
+
const out = [];
|
|
1718
|
+
const byGroup = /* @__PURE__ */ new Map();
|
|
1719
|
+
this.desktops.forEach((d, i) => {
|
|
1720
|
+
if (!d.group) {
|
|
1721
|
+
out.push({
|
|
1722
|
+
idx: i,
|
|
1723
|
+
label: d.label,
|
|
1724
|
+
group: null,
|
|
1725
|
+
fixed: !!d.fixed,
|
|
1726
|
+
active: i === this.activeIdx,
|
|
1727
|
+
members: [i]
|
|
1728
|
+
});
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
let chip = byGroup.get(d.group);
|
|
1732
|
+
if (!chip) {
|
|
1733
|
+
chip = {
|
|
1734
|
+
idx: i,
|
|
1735
|
+
label: d.group,
|
|
1736
|
+
group: d.group,
|
|
1737
|
+
fixed: true,
|
|
1738
|
+
active: false,
|
|
1739
|
+
members: []
|
|
1740
|
+
};
|
|
1741
|
+
byGroup.set(d.group, chip);
|
|
1742
|
+
out.push(chip);
|
|
1743
|
+
}
|
|
1744
|
+
chip.members.push(i);
|
|
1745
|
+
if (i === this.activeIdx) chip.active = true;
|
|
1746
|
+
});
|
|
1747
|
+
for (const chip of byGroup.values()) {
|
|
1748
|
+
if (chip.active) {
|
|
1749
|
+
chip.idx = this.activeIdx;
|
|
1750
|
+
continue;
|
|
1751
|
+
}
|
|
1752
|
+
const last = this.lastInGroup.get(chip.group);
|
|
1753
|
+
const at = last ? this.desktops.findIndex((d) => d.id === last) : -1;
|
|
1754
|
+
if (at >= 0 && this.desktops[at].group === chip.group) chip.idx = at;
|
|
1755
|
+
}
|
|
1756
|
+
return out;
|
|
1757
|
+
}
|
|
1758
|
+
/** C40. Whether any desktop belongs to a group — the bar's cue to stop
|
|
1759
|
+
* promising "Alt+N" on its chips, which then no longer count desktops. */
|
|
1760
|
+
hasGroups() {
|
|
1761
|
+
return this.desktops.some((d) => !!d.group);
|
|
1762
|
+
}
|
|
1691
1763
|
ensureCount(n) {
|
|
1692
1764
|
while (this.desktops.length < n) {
|
|
1693
1765
|
this.desktops.push(_makeDesktop(
|
|
@@ -1698,11 +1770,8 @@ var DesktopManager = class _DesktopManager {
|
|
|
1698
1770
|
}
|
|
1699
1771
|
}
|
|
1700
1772
|
addDesktop(label = null) {
|
|
1701
|
-
const
|
|
1702
|
-
|
|
1703
|
-
this.seed,
|
|
1704
|
-
this.panelDefaults
|
|
1705
|
-
);
|
|
1773
|
+
const n = this.desktops.filter((d2) => !d2.group).length + 1;
|
|
1774
|
+
const d = _makeDesktop(label || String(n), this.seed, this.panelDefaults);
|
|
1706
1775
|
this.desktops.push(d);
|
|
1707
1776
|
return d;
|
|
1708
1777
|
}
|
|
@@ -1713,7 +1782,11 @@ var DesktopManager = class _DesktopManager {
|
|
|
1713
1782
|
id: d.id,
|
|
1714
1783
|
label: d.label,
|
|
1715
1784
|
tree: d.tree.serialize(),
|
|
1716
|
-
panels: { ...d.panels }
|
|
1785
|
+
panels: { ...d.panels },
|
|
1786
|
+
// C40. Written only when set, so a blob without them is
|
|
1787
|
+
// byte-for-byte the blob it always was.
|
|
1788
|
+
...d.group ? { group: d.group } : {},
|
|
1789
|
+
...d.fixed ? { fixed: true } : {}
|
|
1717
1790
|
// windows serialized at WM level since we don't own
|
|
1718
1791
|
// managed-window state in this module.
|
|
1719
1792
|
}))
|
|
@@ -1731,12 +1804,16 @@ var DesktopManager = class _DesktopManager {
|
|
|
1731
1804
|
// closed that panel, and re-opening it on every reload is the bug
|
|
1732
1805
|
// this merge order avoids. The default only fills a key the stored
|
|
1733
1806
|
// blob predates.
|
|
1734
|
-
panels: { ...m.panelDefaults, ...raw.panels || {} }
|
|
1807
|
+
panels: { ...m.panelDefaults, ...raw.panels || {} },
|
|
1808
|
+
group: raw.group || null,
|
|
1809
|
+
fixed: !!raw.fixed
|
|
1735
1810
|
}));
|
|
1736
1811
|
for (const d of m.desktops) {
|
|
1737
1812
|
if (!d.tree.rootId) d.tree.setRoot(makeLeaf(seed()));
|
|
1738
1813
|
}
|
|
1739
1814
|
m.activeIdx = Math.min(Math.max(0, blob.activeIdx | 0), m.desktops.length - 1);
|
|
1815
|
+
const active = m.desktops[m.activeIdx];
|
|
1816
|
+
if (active.group) m.lastInGroup.set(active.group, active.id);
|
|
1740
1817
|
return m;
|
|
1741
1818
|
}
|
|
1742
1819
|
};
|
|
@@ -6686,6 +6763,7 @@ var WindowManager = class _WindowManager {
|
|
|
6686
6763
|
const rec = this._windowToLeaf.get(windowId);
|
|
6687
6764
|
if (!rec) return;
|
|
6688
6765
|
if (rec.desktopIdx === targetIdx) return;
|
|
6766
|
+
if (this.desktops.desktops[targetIdx]?.fixed) return;
|
|
6689
6767
|
this.desktops.ensureCount(targetIdx + 1);
|
|
6690
6768
|
rec.desktopIdx = targetIdx;
|
|
6691
6769
|
if (!rec.homeContainer && rec.homeLeafId) {
|
|
@@ -6816,10 +6894,10 @@ var WindowManager = class _WindowManager {
|
|
|
6816
6894
|
const items = [
|
|
6817
6895
|
{ label: "Back to tile", icon: "close_fullscreen", action: "back" }
|
|
6818
6896
|
];
|
|
6819
|
-
|
|
6897
|
+
const targets = [...this.desktops.desktops.entries()].filter(([i, d]) => i !== rec.desktopIdx && !d.fixed);
|
|
6898
|
+
if (targets.length) {
|
|
6820
6899
|
items.push({ separator: true });
|
|
6821
|
-
for (const [i, d] of
|
|
6822
|
-
if (i === rec.desktopIdx) continue;
|
|
6900
|
+
for (const [i, d] of targets) {
|
|
6823
6901
|
items.push({
|
|
6824
6902
|
label: `Move to desktop ${d.label}`,
|
|
6825
6903
|
icon: "sweep",
|
|
@@ -7113,20 +7191,34 @@ var WindowManager = class _WindowManager {
|
|
|
7113
7191
|
this._persist();
|
|
7114
7192
|
this._notifyChange();
|
|
7115
7193
|
}
|
|
7116
|
-
|
|
7194
|
+
/** Add a desktop. C40: `{ switchTo: true }` also puts it on screen, which
|
|
7195
|
+
* is what the bar's `+` does under `createShell({ desktopBar: {
|
|
7196
|
+
* switchOnAdd: true } })`. Answers the new desktop's index. */
|
|
7197
|
+
addDesktop({ switchTo = false } = {}) {
|
|
7117
7198
|
this.desktops.addDesktop();
|
|
7199
|
+
const idx = this.desktops.desktops.length - 1;
|
|
7200
|
+
if (switchTo) {
|
|
7201
|
+
this.switchDesktop(idx);
|
|
7202
|
+
return idx;
|
|
7203
|
+
}
|
|
7118
7204
|
this._notifyChange();
|
|
7119
7205
|
this._persist();
|
|
7206
|
+
return idx;
|
|
7120
7207
|
}
|
|
7121
7208
|
/** Step to the previous (-1) or next (+1) existing desktop, wrapping
|
|
7122
7209
|
* around. No-op with a single desktop. Iterates only desktops that
|
|
7123
|
-
* already exist — use switchDesktop(idx) to create-on-demand.
|
|
7210
|
+
* already exist — use switchDesktop(idx) to create-on-demand.
|
|
7211
|
+
*
|
|
7212
|
+
* C40. It steps between the bar's CHIPS, so a group is one stop and
|
|
7213
|
+
* lands on its member last on screen. Ungrouped, a chip is a desktop
|
|
7214
|
+
* and this is the step it always was. */
|
|
7124
7215
|
cycleDesktop(dir) {
|
|
7125
|
-
const
|
|
7126
|
-
const n =
|
|
7216
|
+
const chips = this.desktops.chips();
|
|
7217
|
+
const n = chips.length;
|
|
7127
7218
|
if (n <= 1) return;
|
|
7128
|
-
const
|
|
7129
|
-
|
|
7219
|
+
const at = chips.findIndex((c) => c.active);
|
|
7220
|
+
const next = chips[(at + (dir < 0 ? -1 : 1) + n) % n];
|
|
7221
|
+
this.switchDesktop(next.idx);
|
|
7130
7222
|
}
|
|
7131
7223
|
/** Cycle the focused tile's main-panel tabs (Ctrl+Tab / Ctrl+Shift+Tab).
|
|
7132
7224
|
* Wraps; no-op when the focused leaf has 0 or 1 tabs. */
|
|
@@ -7163,6 +7255,7 @@ var WindowManager = class _WindowManager {
|
|
|
7163
7255
|
const m = this.desktops;
|
|
7164
7256
|
if (m.desktops.length <= 1) return false;
|
|
7165
7257
|
if (idx < 0 || idx >= m.desktops.length) return false;
|
|
7258
|
+
if (m.desktops[idx].fixed) return false;
|
|
7166
7259
|
m.desktops.splice(idx, 1);
|
|
7167
7260
|
if (idx < m.activeIdx) m.activeIdx -= 1;
|
|
7168
7261
|
if (m.activeIdx >= m.desktops.length) m.activeIdx = m.desktops.length - 1;
|
|
@@ -7188,12 +7281,31 @@ var WindowManager = class _WindowManager {
|
|
|
7188
7281
|
if (!focused || !focused.content) return;
|
|
7189
7282
|
const closeChrome = this.renderer?.leafChrome?.(focused.id)?.close;
|
|
7190
7283
|
if (closeChrome === false || closeChrome?.disabled === true) return;
|
|
7191
|
-
|
|
7284
|
+
if (this.desktops.desktops[idx]?.fixed) return;
|
|
7192
7285
|
this.desktops.ensureCount(idx + 1);
|
|
7193
7286
|
const target = this.desktops.desktops[idx];
|
|
7287
|
+
const tabs = Array.isArray(focused.tabs) && focused.tabs.length ? focused.tabs : [{ kind: focused.content.kind, props: focused.content.props, title: focused.title }];
|
|
7288
|
+
const activeTab = Math.max(0, Math.min(tabs.length - 1, focused.activeTabIdx || 0));
|
|
7194
7289
|
const targetTree = target.tree;
|
|
7195
7290
|
const primary = targetTree.primaryLeafId();
|
|
7196
|
-
if (primary)
|
|
7291
|
+
if (primary) {
|
|
7292
|
+
const dest = targetTree.get(primary);
|
|
7293
|
+
const seedKind = this._rootLeaf()?.content?.kind;
|
|
7294
|
+
const blank = (dest.tabs || []).length <= 1 && dest.content?.kind === seedKind;
|
|
7295
|
+
let first = -1;
|
|
7296
|
+
tabs.forEach((tab, i) => {
|
|
7297
|
+
const content = { kind: tab.kind, props: tab.props || {} };
|
|
7298
|
+
const title = tab.title || tab.kind || "";
|
|
7299
|
+
if (i === 0 && blank) {
|
|
7300
|
+
targetTree.setLeafContent(primary, content, title);
|
|
7301
|
+
first = 0;
|
|
7302
|
+
return;
|
|
7303
|
+
}
|
|
7304
|
+
const at = targetTree.appendLeafTab(primary, content, title);
|
|
7305
|
+
if (first < 0) first = at;
|
|
7306
|
+
});
|
|
7307
|
+
if (first >= 0) targetTree.setActiveLeafTab(primary, first + activeTab);
|
|
7308
|
+
}
|
|
7197
7309
|
tree.close(focused.id);
|
|
7198
7310
|
if (!tree.rootId) tree.setRoot(makeLeaf(this._rootLeaf()));
|
|
7199
7311
|
if (!tree.leaves().some((l) => !String(l.content?.kind || "").startsWith("panel:"))) {
|
|
@@ -8163,6 +8275,7 @@ async function createShell({
|
|
|
8163
8275
|
floatActiveTab = false,
|
|
8164
8276
|
tabLayout = null,
|
|
8165
8277
|
newTabButton = true,
|
|
8278
|
+
desktopBar = {},
|
|
8166
8279
|
chrome = {},
|
|
8167
8280
|
// An embedder that moved its sections out of the top bar — into an icon
|
|
8168
8281
|
// rail, say — passes the selector its own buttons match, and F1..F8 keep
|
|
@@ -8234,7 +8347,7 @@ async function createShell({
|
|
|
8234
8347
|
const disposeKeymap = installKeymap({ wm, palette, navSelector });
|
|
8235
8348
|
const paletteBtn = mountPaletteButton(chrome.paletteButton, palette);
|
|
8236
8349
|
topNavEl = mountTopNav(chrome.topNav, taxonomy, wm);
|
|
8237
|
-
desktopsEl = mountDesktopBar(chrome.desktops, wm);
|
|
8350
|
+
desktopsEl = mountDesktopBar(chrome.desktops, wm, desktopBar || {});
|
|
8238
8351
|
const zoom = mountZoomControl(chrome.zoom, { root, host });
|
|
8239
8352
|
if (zoom) await zoom.ready;
|
|
8240
8353
|
toggles = bindPanelToggles(chrome.panelToggles, wm);
|
|
@@ -8285,6 +8398,10 @@ async function createShell({
|
|
|
8285
8398
|
zoom?.dispose();
|
|
8286
8399
|
} catch {
|
|
8287
8400
|
}
|
|
8401
|
+
try {
|
|
8402
|
+
desktopsEl?.remove();
|
|
8403
|
+
} catch {
|
|
8404
|
+
}
|
|
8288
8405
|
}
|
|
8289
8406
|
});
|
|
8290
8407
|
}
|
|
@@ -8359,7 +8476,7 @@ function syncPanelToggles(map, wm) {
|
|
|
8359
8476
|
btn.classList.toggle("twm-panel-toggle-btn--on", wm.isPanelOpen(side));
|
|
8360
8477
|
}
|
|
8361
8478
|
}
|
|
8362
|
-
function mountDesktopBar(hostEl, wm) {
|
|
8479
|
+
function mountDesktopBar(hostEl, wm, { switchOnAdd = false } = {}) {
|
|
8363
8480
|
if (!hostEl) return null;
|
|
8364
8481
|
let el = hostEl.querySelector("#twm-desktops");
|
|
8365
8482
|
if (!el) {
|
|
@@ -8373,15 +8490,16 @@ function mountDesktopBar(hostEl, wm) {
|
|
|
8373
8490
|
if (!btn) return;
|
|
8374
8491
|
const v = btn.dataset.desktop;
|
|
8375
8492
|
if (v === "+") {
|
|
8376
|
-
wm.addDesktop();
|
|
8493
|
+
wm.addDesktop({ switchTo: switchOnAdd });
|
|
8377
8494
|
syncDesktopBar(el, wm);
|
|
8378
8495
|
return;
|
|
8379
8496
|
}
|
|
8380
8497
|
wm.switchDesktop(Number(v));
|
|
8381
8498
|
});
|
|
8499
|
+
const isOwn = (btn) => btn.dataset.desktopFixed !== "1";
|
|
8382
8500
|
el.addEventListener("dblclick", async (e) => {
|
|
8383
8501
|
const btn = e.target.closest("[data-desktop]");
|
|
8384
|
-
if (!btn || btn.dataset.desktop === "+") return;
|
|
8502
|
+
if (!btn || btn.dataset.desktop === "+" || !isOwn(btn)) return;
|
|
8385
8503
|
e.preventDefault();
|
|
8386
8504
|
const idx = Number(btn.dataset.desktop);
|
|
8387
8505
|
const d = wm.desktops.desktops[idx];
|
|
@@ -8410,25 +8528,28 @@ function mountDesktopBar(hostEl, wm) {
|
|
|
8410
8528
|
const idx = Number(btn.dataset.desktop);
|
|
8411
8529
|
const m = wm.desktops;
|
|
8412
8530
|
const d = m.desktops[idx];
|
|
8413
|
-
const
|
|
8414
|
-
|
|
8531
|
+
const own = isOwn(btn);
|
|
8532
|
+
const canDelete = own && m.desktops.length > 1;
|
|
8533
|
+
const name = btn.dataset.desktopLabel || d.label;
|
|
8534
|
+
const items = [
|
|
8415
8535
|
{
|
|
8416
|
-
label: `Switch to ${
|
|
8536
|
+
label: `Switch to ${name}`,
|
|
8417
8537
|
icon: "desktop_windows",
|
|
8418
8538
|
action: "switch",
|
|
8419
8539
|
disabled: idx === m.activeIdx
|
|
8420
8540
|
},
|
|
8421
|
-
{ label: "Rename\u2026", icon: "edit", action: "rename" },
|
|
8541
|
+
...own ? [{ label: "Rename\u2026", icon: "edit", action: "rename" }] : [],
|
|
8422
8542
|
{ separator: true },
|
|
8423
8543
|
{ label: "New desktop", icon: "add", action: "add" },
|
|
8424
|
-
{
|
|
8544
|
+
...own ? [{
|
|
8425
8545
|
label: "Delete desktop",
|
|
8426
8546
|
icon: "delete",
|
|
8427
8547
|
action: "delete",
|
|
8428
8548
|
danger: true,
|
|
8429
8549
|
disabled: !canDelete
|
|
8430
|
-
}
|
|
8431
|
-
]
|
|
8550
|
+
}] : []
|
|
8551
|
+
];
|
|
8552
|
+
showContextMenu(e.clientX, e.clientY, items, async (action) => {
|
|
8432
8553
|
if (action === "switch") wm.switchDesktop(idx);
|
|
8433
8554
|
else if (action === "rename") {
|
|
8434
8555
|
const result = await openForm({
|
|
@@ -8442,7 +8563,7 @@ function mountDesktopBar(hostEl, wm) {
|
|
|
8442
8563
|
syncDesktopBar(el, wm);
|
|
8443
8564
|
wm._persist();
|
|
8444
8565
|
} else if (action === "add") {
|
|
8445
|
-
wm.addDesktop();
|
|
8566
|
+
wm.addDesktop({ switchTo: switchOnAdd });
|
|
8446
8567
|
syncDesktopBar(el, wm);
|
|
8447
8568
|
} else if (action === "delete") {
|
|
8448
8569
|
wm.removeDesktop(idx);
|
|
@@ -8455,13 +8576,31 @@ function mountDesktopBar(hostEl, wm) {
|
|
|
8455
8576
|
function syncDesktopBar(el, wm) {
|
|
8456
8577
|
if (!el) return;
|
|
8457
8578
|
const m = wm.desktops;
|
|
8458
|
-
|
|
8579
|
+
if (!m.hasGroups()) {
|
|
8580
|
+
el.innerHTML = m.desktops.map((d, i) => `
|
|
8459
8581
|
<button class="twm-desk__btn${i === m.activeIdx ? " twm-desk__btn--on" : ""}"
|
|
8460
8582
|
data-desktop="${i}"
|
|
8461
8583
|
title="Desktop ${d.label} (Alt+${i + 1}) \xB7 right-click to delete">
|
|
8462
8584
|
${d.label}
|
|
8463
8585
|
</button>`).join("") + `<button class="twm-desk__btn twm-desk__btn--add"
|
|
8464
8586
|
data-desktop="+" title="Add desktop">+</button>`;
|
|
8587
|
+
return;
|
|
8588
|
+
}
|
|
8589
|
+
el.innerHTML = m.chips().map((c) => {
|
|
8590
|
+
const label = _escapeHtml(c.label);
|
|
8591
|
+
const title = c.fixed ? label : `${label} \xB7 double-click to rename \xB7 right-click for more`;
|
|
8592
|
+
return `
|
|
8593
|
+
<button class="twm-desk__btn${c.active ? " twm-desk__btn--on" : ""}"
|
|
8594
|
+
data-desktop="${c.idx}"
|
|
8595
|
+
data-desktop-label="${label}"${c.fixed ? ' data-desktop-fixed="1"' : ""}
|
|
8596
|
+
title="${title}">
|
|
8597
|
+
${label}
|
|
8598
|
+
</button>`;
|
|
8599
|
+
}).join("") + `<button class="twm-desk__btn twm-desk__btn--add"
|
|
8600
|
+
data-desktop="+" title="New desktop">+</button>`;
|
|
8601
|
+
}
|
|
8602
|
+
function _escapeHtml(s) {
|
|
8603
|
+
return String(s ?? "").replace(/[&<>"']/g, (ch) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[ch]);
|
|
8465
8604
|
}
|
|
8466
8605
|
function _tileTabMenu(wm, leafId, x, y) {
|
|
8467
8606
|
const tree = wm.desktops.active().tree;
|
|
@@ -8539,7 +8678,7 @@ function _tileContextMenu(wm, leafId, x, y) {
|
|
|
8539
8678
|
];
|
|
8540
8679
|
if (wm.desktops.desktops.length > 1 && !isPanel) {
|
|
8541
8680
|
for (const [i, d] of wm.desktops.desktops.entries()) {
|
|
8542
|
-
if (i === wm.desktops.activeIdx) continue;
|
|
8681
|
+
if (i === wm.desktops.activeIdx || d.fixed) continue;
|
|
8543
8682
|
items.push({
|
|
8544
8683
|
label: `Move to desktop ${d.label}`,
|
|
8545
8684
|
icon: "sweep",
|