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/package.json
CHANGED
package/src/tiling/desktops.js
CHANGED
|
@@ -47,6 +47,9 @@ function _makeDesktop(label, seed, panelDefaults = DEFAULT_PANEL_STATE) {
|
|
|
47
47
|
// when it asked for nothing. Names are assigned by wm._canonicalize via
|
|
48
48
|
// PANEL_TITLES.
|
|
49
49
|
panels: { ...panelDefaults },
|
|
50
|
+
// C40. Both unset is a desktop exactly as it always was.
|
|
51
|
+
group: null,
|
|
52
|
+
fixed: false,
|
|
50
53
|
};
|
|
51
54
|
}
|
|
52
55
|
|
|
@@ -66,6 +69,9 @@ export class DesktopManager {
|
|
|
66
69
|
this.panelDefaults = { ...DEFAULT_PANEL_STATE, ...(panelDefaults || {}) };
|
|
67
70
|
this.desktops = [_makeDesktop('1', seed, this.panelDefaults)];
|
|
68
71
|
this.activeIdx = 0;
|
|
72
|
+
// C40. group → the id of the member last on screen, which is where the
|
|
73
|
+
// group's chip goes back to.
|
|
74
|
+
this.lastInGroup = new Map();
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
active() { return this.desktops[this.activeIdx]; }
|
|
@@ -74,9 +80,62 @@ export class DesktopManager {
|
|
|
74
80
|
if (idx < 0 || idx >= this.desktops.length) return false;
|
|
75
81
|
if (idx === this.activeIdx) return false;
|
|
76
82
|
this.activeIdx = idx;
|
|
83
|
+
const d = this.desktops[idx];
|
|
84
|
+
if (d.group) this.lastInGroup.set(d.group, d.id);
|
|
77
85
|
return true;
|
|
78
86
|
}
|
|
79
87
|
|
|
88
|
+
/**
|
|
89
|
+
* ══ C40. WHAT THE DESKTOP BAR DRAWS — ONE CHIP PER GROUP ═══════════════
|
|
90
|
+
*
|
|
91
|
+
* An embedder may give desktops a `group` and mark them `fixed`. Desktops
|
|
92
|
+
* that share a group are ONE stop for the user — one chip in the bar, one
|
|
93
|
+
* step of Ctrl+Left/Right — and the chip goes back to whichever member was
|
|
94
|
+
* last on screen; which member that is, is the embedder's business (Tables
|
|
95
|
+
* keeps one desktop per page under a "Workbench" group, and the rail picks
|
|
96
|
+
* among them). A `fixed` desktop is the embedder's own: the bar offers no
|
|
97
|
+
* rename and no delete for it, and no menu offers it as a "Move to"
|
|
98
|
+
* destination.
|
|
99
|
+
*
|
|
100
|
+
* With no group set anywhere this is one chip per desktop, in order — the
|
|
101
|
+
* bar every existing embedder has.
|
|
102
|
+
*
|
|
103
|
+
* @returns {Array<{ idx: number, label: string, group: string|null,
|
|
104
|
+
* fixed: boolean, active: boolean, members: number[] }>} `idx` is the
|
|
105
|
+
* desktop the chip switches to.
|
|
106
|
+
*/
|
|
107
|
+
chips() {
|
|
108
|
+
const out = [];
|
|
109
|
+
const byGroup = new Map();
|
|
110
|
+
this.desktops.forEach((d, i) => {
|
|
111
|
+
if (!d.group) {
|
|
112
|
+
out.push({ idx: i, label: d.label, group: null, fixed: !!d.fixed,
|
|
113
|
+
active: i === this.activeIdx, members: [i] });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
let chip = byGroup.get(d.group);
|
|
117
|
+
if (!chip) {
|
|
118
|
+
chip = { idx: i, label: d.group, group: d.group, fixed: true,
|
|
119
|
+
active: false, members: [] };
|
|
120
|
+
byGroup.set(d.group, chip);
|
|
121
|
+
out.push(chip);
|
|
122
|
+
}
|
|
123
|
+
chip.members.push(i);
|
|
124
|
+
if (i === this.activeIdx) chip.active = true;
|
|
125
|
+
});
|
|
126
|
+
for (const chip of byGroup.values()) {
|
|
127
|
+
if (chip.active) { chip.idx = this.activeIdx; continue; }
|
|
128
|
+
const last = this.lastInGroup.get(chip.group);
|
|
129
|
+
const at = last ? this.desktops.findIndex((d) => d.id === last) : -1;
|
|
130
|
+
if (at >= 0 && this.desktops[at].group === chip.group) chip.idx = at;
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** C40. Whether any desktop belongs to a group — the bar's cue to stop
|
|
136
|
+
* promising "Alt+N" on its chips, which then no longer count desktops. */
|
|
137
|
+
hasGroups() { return this.desktops.some((d) => !!d.group); }
|
|
138
|
+
|
|
80
139
|
ensureCount(n) {
|
|
81
140
|
while (this.desktops.length < n) {
|
|
82
141
|
this.desktops.push(_makeDesktop(
|
|
@@ -85,8 +144,10 @@ export class DesktopManager {
|
|
|
85
144
|
}
|
|
86
145
|
|
|
87
146
|
addDesktop(label = null) {
|
|
88
|
-
|
|
89
|
-
|
|
147
|
+
// C40. Numbered among the desktops a user makes, so the first one added
|
|
148
|
+
// beside an embedder's grouped desktops is "1" rather than "13".
|
|
149
|
+
const n = this.desktops.filter((d) => !d.group).length + 1;
|
|
150
|
+
const d = _makeDesktop(label || String(n), this.seed, this.panelDefaults);
|
|
90
151
|
this.desktops.push(d);
|
|
91
152
|
return d;
|
|
92
153
|
}
|
|
@@ -98,6 +159,10 @@ export class DesktopManager {
|
|
|
98
159
|
id: d.id, label: d.label,
|
|
99
160
|
tree: d.tree.serialize(),
|
|
100
161
|
panels: { ...d.panels },
|
|
162
|
+
// C40. Written only when set, so a blob without them is
|
|
163
|
+
// byte-for-byte the blob it always was.
|
|
164
|
+
...(d.group ? { group: d.group } : {}),
|
|
165
|
+
...(d.fixed ? { fixed: true } : {}),
|
|
101
166
|
// windows serialized at WM level since we don't own
|
|
102
167
|
// managed-window state in this module.
|
|
103
168
|
})),
|
|
@@ -117,12 +182,16 @@ export class DesktopManager {
|
|
|
117
182
|
// this merge order avoids. The default only fills a key the stored
|
|
118
183
|
// blob predates.
|
|
119
184
|
panels: { ...m.panelDefaults, ...(raw.panels || {}) },
|
|
185
|
+
group: raw.group || null,
|
|
186
|
+
fixed: !!raw.fixed,
|
|
120
187
|
}));
|
|
121
188
|
// Empty tree → seed with the taxonomy root so the desktop is usable.
|
|
122
189
|
for (const d of m.desktops) {
|
|
123
190
|
if (!d.tree.rootId) d.tree.setRoot(makeLeaf(seed()));
|
|
124
191
|
}
|
|
125
192
|
m.activeIdx = Math.min(Math.max(0, blob.activeIdx | 0), m.desktops.length - 1);
|
|
193
|
+
const active = m.desktops[m.activeIdx];
|
|
194
|
+
if (active.group) m.lastInGroup.set(active.group, active.id);
|
|
126
195
|
return m;
|
|
127
196
|
}
|
|
128
197
|
}
|
package/src/tiling/shell.js
CHANGED
|
@@ -118,6 +118,13 @@ import { openForm } from '../ui/components/modal.js';
|
|
|
118
118
|
* whose new tab has nowhere useful to go
|
|
119
119
|
* passes `false`. `wm.newTab()` is there
|
|
120
120
|
* either way.
|
|
121
|
+
* @param {object} [cfg.desktopBar] C40. `{ switchOnAdd?: boolean }` — the
|
|
122
|
+
* bar's `+` also puts the new desktop on
|
|
123
|
+
* screen. Default off: `+` only adds, as
|
|
124
|
+
* it always has. Grouping and fixing
|
|
125
|
+
* desktops is said on the desktops
|
|
126
|
+
* themselves (`group`, `fixed` — see
|
|
127
|
+
* `DesktopManager.chips`).
|
|
121
128
|
* @param {object} [cfg.chrome] { topNav?, paletteButton?, desktops?,
|
|
122
129
|
* panelToggles?: { left?, right?, bottom? },
|
|
123
130
|
* zoom? }
|
|
@@ -147,6 +154,7 @@ export async function createShell({
|
|
|
147
154
|
floatActiveTab = false,
|
|
148
155
|
tabLayout = null,
|
|
149
156
|
newTabButton = true,
|
|
157
|
+
desktopBar = {},
|
|
150
158
|
chrome = {},
|
|
151
159
|
// An embedder that moved its sections out of the top bar — into an icon
|
|
152
160
|
// rail, say — passes the selector its own buttons match, and F1..F8 keep
|
|
@@ -217,7 +225,7 @@ export async function createShell({
|
|
|
217
225
|
// key means absent chrome, not a crash.
|
|
218
226
|
const paletteBtn = mountPaletteButton(chrome.paletteButton, palette);
|
|
219
227
|
topNavEl = mountTopNav(chrome.topNav, taxonomy, wm);
|
|
220
|
-
desktopsEl = mountDesktopBar(chrome.desktops, wm);
|
|
228
|
+
desktopsEl = mountDesktopBar(chrome.desktops, wm, desktopBar || {});
|
|
221
229
|
// C31. The content zoom. Painted with the rest of the chrome and, unlike the
|
|
222
230
|
// rest, AWAITED before the first mount: its saved value is applied to `root`
|
|
223
231
|
// as a CSS variable, and a shell that mounted first would paint every tile at
|
|
@@ -276,6 +284,12 @@ export async function createShell({
|
|
|
276
284
|
// the same root must not inherit the old one's scale with no control on
|
|
277
285
|
// screen to change it, so dispose puts the root back to 100%.
|
|
278
286
|
try { zoom?.dispose(); } catch { /* ignore */ }
|
|
287
|
+
// C40. The bar's listeners close over THIS wm, and `mountDesktopBar`
|
|
288
|
+
// reuses an existing `#twm-desktops` — so a rebuilt shell would
|
|
289
|
+
// inherit them, and a `+` would add a desktop to the dead wm and
|
|
290
|
+
// persist it under the host the old shell was given. The element
|
|
291
|
+
// goes; the next shell paints its own.
|
|
292
|
+
try { desktopsEl?.remove(); } catch { /* ignore */ }
|
|
279
293
|
},
|
|
280
294
|
});
|
|
281
295
|
}
|
|
@@ -390,7 +404,7 @@ function syncPanelToggles(map, wm) {
|
|
|
390
404
|
}
|
|
391
405
|
}
|
|
392
406
|
|
|
393
|
-
function mountDesktopBar(hostEl, wm) {
|
|
407
|
+
function mountDesktopBar(hostEl, wm, { switchOnAdd = false } = {}) {
|
|
394
408
|
if (!hostEl) return null;
|
|
395
409
|
let el = hostEl.querySelector('#twm-desktops');
|
|
396
410
|
if (!el) {
|
|
@@ -403,15 +417,18 @@ function mountDesktopBar(hostEl, wm) {
|
|
|
403
417
|
const btn = e.target.closest('[data-desktop]');
|
|
404
418
|
if (!btn) return;
|
|
405
419
|
const v = btn.dataset.desktop;
|
|
406
|
-
if (v === '+') { wm.addDesktop(); syncDesktopBar(el, wm); return; }
|
|
420
|
+
if (v === '+') { wm.addDesktop({ switchTo: switchOnAdd }); syncDesktopBar(el, wm); return; }
|
|
407
421
|
wm.switchDesktop(Number(v));
|
|
408
422
|
});
|
|
423
|
+
// C40. A fixed desktop, or a group's chip, is the embedder's: no rename,
|
|
424
|
+
// no delete. Asked of the button, which says which it is.
|
|
425
|
+
const isOwn = (btn) => btn.dataset.desktopFixed !== '1';
|
|
409
426
|
// Double-click a desktop chip → inline rename. Faster than the context
|
|
410
427
|
// menu's Rename… for the common case. Persists via wm._persist so the new
|
|
411
428
|
// label survives a restart.
|
|
412
429
|
el.addEventListener('dblclick', async (e) => {
|
|
413
430
|
const btn = e.target.closest('[data-desktop]');
|
|
414
|
-
if (!btn || btn.dataset.desktop === '+') return;
|
|
431
|
+
if (!btn || btn.dataset.desktop === '+' || !isOwn(btn)) return;
|
|
415
432
|
e.preventDefault();
|
|
416
433
|
const idx = Number(btn.dataset.desktop);
|
|
417
434
|
const d = wm.desktops.desktops[idx];
|
|
@@ -435,16 +452,19 @@ function mountDesktopBar(hostEl, wm) {
|
|
|
435
452
|
const idx = Number(btn.dataset.desktop);
|
|
436
453
|
const m = wm.desktops;
|
|
437
454
|
const d = m.desktops[idx];
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
|
|
455
|
+
const own = isOwn(btn);
|
|
456
|
+
const canDelete = own && m.desktops.length > 1;
|
|
457
|
+
const name = btn.dataset.desktopLabel || d.label;
|
|
458
|
+
const items = [
|
|
459
|
+
{ label: `Switch to ${name}`, icon: 'desktop_windows',
|
|
441
460
|
action: 'switch', disabled: idx === m.activeIdx },
|
|
442
|
-
{ label: 'Rename…',
|
|
461
|
+
...(own ? [{ label: 'Rename…', icon: 'edit', action: 'rename' }] : []),
|
|
443
462
|
{ separator: true },
|
|
444
463
|
{ label: 'New desktop', icon: 'add', action: 'add' },
|
|
445
|
-
{ label: 'Delete desktop', icon: 'delete', action: 'delete',
|
|
446
|
-
|
|
447
|
-
]
|
|
464
|
+
...(own ? [{ label: 'Delete desktop', icon: 'delete', action: 'delete',
|
|
465
|
+
danger: true, disabled: !canDelete }] : []),
|
|
466
|
+
];
|
|
467
|
+
showContextMenu(e.clientX, e.clientY, items, async (action) => {
|
|
448
468
|
if (action === 'switch') wm.switchDesktop(idx);
|
|
449
469
|
else if (action === 'rename') {
|
|
450
470
|
const result = await openForm({
|
|
@@ -458,7 +478,7 @@ function mountDesktopBar(hostEl, wm) {
|
|
|
458
478
|
syncDesktopBar(el, wm);
|
|
459
479
|
wm._persist();
|
|
460
480
|
}
|
|
461
|
-
else if (action === 'add') { wm.addDesktop(); syncDesktopBar(el, wm); }
|
|
481
|
+
else if (action === 'add') { wm.addDesktop({ switchTo: switchOnAdd }); syncDesktopBar(el, wm); }
|
|
462
482
|
else if (action === 'delete') { wm.removeDesktop(idx); syncDesktopBar(el, wm); }
|
|
463
483
|
});
|
|
464
484
|
});
|
|
@@ -468,15 +488,40 @@ function mountDesktopBar(hostEl, wm) {
|
|
|
468
488
|
function syncDesktopBar(el, wm) {
|
|
469
489
|
if (!el) return;
|
|
470
490
|
const m = wm.desktops;
|
|
471
|
-
|
|
472
|
-
|
|
491
|
+
// C40. Ungrouped, this is the markup it always was, byte for byte. Once a
|
|
492
|
+
// group exists the chips no longer count desktops, so the Alt+N promise
|
|
493
|
+
// goes, and a label is escaped because a group's comes from the embedder.
|
|
494
|
+
if (!m.hasGroups()) {
|
|
495
|
+
el.innerHTML =
|
|
496
|
+
m.desktops.map((d, i) => `
|
|
473
497
|
<button class="twm-desk__btn${i === m.activeIdx ? ' twm-desk__btn--on' : ''}"
|
|
474
498
|
data-desktop="${i}"
|
|
475
499
|
title="Desktop ${d.label} (Alt+${i + 1}) · right-click to delete">
|
|
476
500
|
${d.label}
|
|
477
501
|
</button>`).join('')
|
|
478
|
-
|
|
502
|
+
+ `<button class="twm-desk__btn twm-desk__btn--add"
|
|
479
503
|
data-desktop="+" title="Add desktop">+</button>`;
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
el.innerHTML =
|
|
507
|
+
m.chips().map((c) => {
|
|
508
|
+
const label = _escapeHtml(c.label);
|
|
509
|
+
const title = c.fixed ? label : `${label} · double-click to rename · right-click for more`;
|
|
510
|
+
return `
|
|
511
|
+
<button class="twm-desk__btn${c.active ? ' twm-desk__btn--on' : ''}"
|
|
512
|
+
data-desktop="${c.idx}"
|
|
513
|
+
data-desktop-label="${label}"${c.fixed ? ' data-desktop-fixed="1"' : ''}
|
|
514
|
+
title="${title}">
|
|
515
|
+
${label}
|
|
516
|
+
</button>`;
|
|
517
|
+
}).join('')
|
|
518
|
+
+ `<button class="twm-desk__btn twm-desk__btn--add"
|
|
519
|
+
data-desktop="+" title="New desktop">+</button>`;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function _escapeHtml(s) {
|
|
523
|
+
return String(s ?? '').replace(/[&<>"']/g, (ch) => (
|
|
524
|
+
{ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[ch]));
|
|
480
525
|
}
|
|
481
526
|
|
|
482
527
|
|
|
@@ -556,7 +601,8 @@ function _tileContextMenu(wm, leafId, x, y) {
|
|
|
556
601
|
];
|
|
557
602
|
if (wm.desktops.desktops.length > 1 && !isPanel) {
|
|
558
603
|
for (const [i, d] of wm.desktops.desktops.entries()) {
|
|
559
|
-
|
|
604
|
+
// C40. Never onto a fixed desktop — `moveFocusedToDesktop` refuses.
|
|
605
|
+
if (i === wm.desktops.activeIdx || d.fixed) continue;
|
|
560
606
|
items.push({ label: `Move to desktop ${d.label}`, icon: 'sweep',
|
|
561
607
|
action: `move:${i}` });
|
|
562
608
|
}
|
package/src/tiling/wm.js
CHANGED
|
@@ -2789,6 +2789,8 @@ export class WindowManager {
|
|
|
2789
2789
|
const rec = this._windowToLeaf.get(windowId);
|
|
2790
2790
|
if (!rec) return;
|
|
2791
2791
|
if (rec.desktopIdx === targetIdx) return;
|
|
2792
|
+
// C40. A fixed desktop is the embedder's, and nothing is moved onto it.
|
|
2793
|
+
if (this.desktops.desktops[targetIdx]?.fixed) return;
|
|
2792
2794
|
this.desktops.ensureCount(targetIdx + 1);
|
|
2793
2795
|
rec.desktopIdx = targetIdx;
|
|
2794
2796
|
if (!rec.homeContainer && rec.homeLeafId) {
|
|
@@ -2957,10 +2959,12 @@ export class WindowManager {
|
|
|
2957
2959
|
const items = [
|
|
2958
2960
|
{ label: 'Back to tile', icon: 'close_fullscreen', action: 'back' },
|
|
2959
2961
|
];
|
|
2960
|
-
|
|
2962
|
+
// C40. Never onto a fixed desktop — `moveWindowToDesktop` refuses.
|
|
2963
|
+
const targets = [...this.desktops.desktops.entries()]
|
|
2964
|
+
.filter(([i, d]) => i !== rec.desktopIdx && !d.fixed);
|
|
2965
|
+
if (targets.length) {
|
|
2961
2966
|
items.push({ separator: true });
|
|
2962
|
-
for (const [i, d] of
|
|
2963
|
-
if (i === rec.desktopIdx) continue;
|
|
2967
|
+
for (const [i, d] of targets) {
|
|
2964
2968
|
items.push({
|
|
2965
2969
|
label: `Move to desktop ${d.label}`, icon: 'sweep',
|
|
2966
2970
|
action: `move:${i}`,
|
|
@@ -3297,21 +3301,32 @@ export class WindowManager {
|
|
|
3297
3301
|
this._notifyChange();
|
|
3298
3302
|
}
|
|
3299
3303
|
|
|
3300
|
-
|
|
3304
|
+
/** Add a desktop. C40: `{ switchTo: true }` also puts it on screen, which
|
|
3305
|
+
* is what the bar's `+` does under `createShell({ desktopBar: {
|
|
3306
|
+
* switchOnAdd: true } })`. Answers the new desktop's index. */
|
|
3307
|
+
addDesktop({ switchTo = false } = {}) {
|
|
3301
3308
|
this.desktops.addDesktop();
|
|
3309
|
+
const idx = this.desktops.desktops.length - 1;
|
|
3310
|
+
if (switchTo) { this.switchDesktop(idx); return idx; }
|
|
3302
3311
|
this._notifyChange();
|
|
3303
3312
|
this._persist();
|
|
3313
|
+
return idx;
|
|
3304
3314
|
}
|
|
3305
3315
|
|
|
3306
3316
|
/** Step to the previous (-1) or next (+1) existing desktop, wrapping
|
|
3307
3317
|
* around. No-op with a single desktop. Iterates only desktops that
|
|
3308
|
-
* already exist — use switchDesktop(idx) to create-on-demand.
|
|
3318
|
+
* already exist — use switchDesktop(idx) to create-on-demand.
|
|
3319
|
+
*
|
|
3320
|
+
* C40. It steps between the bar's CHIPS, so a group is one stop and
|
|
3321
|
+
* lands on its member last on screen. Ungrouped, a chip is a desktop
|
|
3322
|
+
* and this is the step it always was. */
|
|
3309
3323
|
cycleDesktop(dir) {
|
|
3310
|
-
const
|
|
3311
|
-
const n =
|
|
3324
|
+
const chips = this.desktops.chips();
|
|
3325
|
+
const n = chips.length;
|
|
3312
3326
|
if (n <= 1) return;
|
|
3313
|
-
const
|
|
3314
|
-
|
|
3327
|
+
const at = chips.findIndex((c) => c.active);
|
|
3328
|
+
const next = chips[(at + (dir < 0 ? -1 : 1) + n) % n];
|
|
3329
|
+
this.switchDesktop(next.idx);
|
|
3315
3330
|
}
|
|
3316
3331
|
|
|
3317
3332
|
/** Cycle the focused tile's main-panel tabs (Ctrl+Tab / Ctrl+Shift+Tab).
|
|
@@ -3350,6 +3365,8 @@ export class WindowManager {
|
|
|
3350
3365
|
const m = this.desktops;
|
|
3351
3366
|
if (m.desktops.length <= 1) return false;
|
|
3352
3367
|
if (idx < 0 || idx >= m.desktops.length) return false;
|
|
3368
|
+
// C40. A fixed desktop is the embedder's to keep.
|
|
3369
|
+
if (m.desktops[idx].fixed) return false;
|
|
3353
3370
|
m.desktops.splice(idx, 1);
|
|
3354
3371
|
// THE SPLICE MOVES THE ACTIVE DESKTOP TOO, and the clamp below never
|
|
3355
3372
|
// said so. Removing a desktop BELOW the one you are on shifts it down
|
|
@@ -3399,13 +3416,36 @@ export class WindowManager {
|
|
|
3399
3416
|
// says "Move".
|
|
3400
3417
|
const closeChrome = this.renderer?.leafChrome?.(focused.id)?.close;
|
|
3401
3418
|
if (closeChrome === false || closeChrome?.disabled === true) return;
|
|
3402
|
-
|
|
3419
|
+
// C40. A fixed desktop is the embedder's, and nothing is moved onto it.
|
|
3420
|
+
if (this.desktops.desktops[idx]?.fixed) return;
|
|
3403
3421
|
this.desktops.ensureCount(idx + 1);
|
|
3404
3422
|
const target = this.desktops.desktops[idx];
|
|
3405
|
-
//
|
|
3423
|
+
// C40. THE PANE'S TABS JOIN THE TARGET'S PRIMARY LEAF, ALL OF THEM. It
|
|
3424
|
+
// used to `setLeafContent` the ACTIVE tab over that leaf — which threw
|
|
3425
|
+
// away every other tab of the pane being moved AND whatever the target
|
|
3426
|
+
// was showing, so moving a pane onto a desktop you had worked in wiped
|
|
3427
|
+
// that desktop's main pane. Only a target still holding nothing but the
|
|
3428
|
+
// seed it was born with is replaced, because that is a blank page.
|
|
3429
|
+
const tabs = (Array.isArray(focused.tabs) && focused.tabs.length)
|
|
3430
|
+
? focused.tabs
|
|
3431
|
+
: [{ kind: focused.content.kind, props: focused.content.props, title: focused.title }];
|
|
3432
|
+
const activeTab = Math.max(0, Math.min(tabs.length - 1, focused.activeTabIdx || 0));
|
|
3406
3433
|
const targetTree = target.tree;
|
|
3407
3434
|
const primary = targetTree.primaryLeafId();
|
|
3408
|
-
if (primary)
|
|
3435
|
+
if (primary) {
|
|
3436
|
+
const dest = targetTree.get(primary);
|
|
3437
|
+
const seedKind = this._rootLeaf()?.content?.kind;
|
|
3438
|
+
const blank = (dest.tabs || []).length <= 1 && dest.content?.kind === seedKind;
|
|
3439
|
+
let first = -1;
|
|
3440
|
+
tabs.forEach((tab, i) => {
|
|
3441
|
+
const content = { kind: tab.kind, props: tab.props || {} };
|
|
3442
|
+
const title = tab.title || tab.kind || '';
|
|
3443
|
+
if (i === 0 && blank) { targetTree.setLeafContent(primary, content, title); first = 0; return; }
|
|
3444
|
+
const at = targetTree.appendLeafTab(primary, content, title);
|
|
3445
|
+
if (first < 0) first = at;
|
|
3446
|
+
});
|
|
3447
|
+
if (first >= 0) targetTree.setActiveLeafTab(primary, first + activeTab);
|
|
3448
|
+
}
|
|
3409
3449
|
// Remove from current desktop.
|
|
3410
3450
|
tree.close(focused.id);
|
|
3411
3451
|
if (!tree.rootId) tree.setRoot(makeLeaf(this._rootLeaf()));
|