@ai-matrx/canvas 0.1.1 → 0.2.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/CHANGELOG.md +26 -0
- package/README.md +114 -4
- package/dist/controller-Dv6J6XZm.d.cts +576 -0
- package/dist/controller-Dv6J6XZm.d.ts +576 -0
- package/dist/index.cjs +743 -104
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +118 -11
- package/dist/index.d.ts +118 -11
- package/dist/index.js +743 -104
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +1476 -406
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +242 -22
- package/dist/react.d.ts +242 -22
- package/dist/react.js +1486 -404
- package/dist/react.js.map +1 -1
- package/dist/styles.css +346 -51
- package/dist/tokens.css +58 -36
- package/package.json +1 -1
- package/dist/controller-oY84BN2c.d.cts +0 -345
- package/dist/controller-oY84BN2c.d.ts +0 -345
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/core/types.ts
|
|
2
|
+
var CANVAS_MAIN_WINDOW = "main";
|
|
2
3
|
var CANVAS_MIN_WIDTH = 360;
|
|
3
4
|
var CANVAS_DEFAULT_WIDTH = 640;
|
|
4
5
|
var CANVAS_MIN_SPLIT_FRACTION = 0.12;
|
|
@@ -50,12 +51,19 @@ function findNonJson(value, path = "data") {
|
|
|
50
51
|
function isCanvasJson(value) {
|
|
51
52
|
return findNonJson(value) === null;
|
|
52
53
|
}
|
|
54
|
+
function canvasWindowId(id) {
|
|
55
|
+
return id;
|
|
56
|
+
}
|
|
53
57
|
|
|
54
58
|
// src/core/layout.ts
|
|
55
59
|
function listPaneIds(node) {
|
|
56
60
|
if (node.type === "pane") return [node.paneId];
|
|
57
61
|
return node.children.flatMap(listPaneIds);
|
|
58
62
|
}
|
|
63
|
+
function containsPane(node, paneId) {
|
|
64
|
+
if (node.type === "pane") return node.paneId === paneId;
|
|
65
|
+
return node.children.some((child) => containsPane(child, paneId));
|
|
66
|
+
}
|
|
59
67
|
function evenSizes(count) {
|
|
60
68
|
return Array.from({ length: count }, () => 1 / count);
|
|
61
69
|
}
|
|
@@ -66,7 +74,8 @@ function normalizeSizes(sizes, count) {
|
|
|
66
74
|
const total = sizes.reduce((sum, s) => sum + s, 0);
|
|
67
75
|
return sizes.map((s) => s / total);
|
|
68
76
|
}
|
|
69
|
-
function splitPane(node, paneId, newPaneId, orientation, newSplitId) {
|
|
77
|
+
function splitPane(node, paneId, newPaneId, orientation, newSplitId, firstShare = 0.5) {
|
|
78
|
+
const first = Number.isFinite(firstShare) ? Math.min(1 - CANVAS_MIN_SPLIT_FRACTION, Math.max(CANVAS_MIN_SPLIT_FRACTION, firstShare)) : 0.5;
|
|
70
79
|
if (node.type === "pane") {
|
|
71
80
|
if (node.paneId !== paneId) return node;
|
|
72
81
|
return {
|
|
@@ -74,7 +83,7 @@ function splitPane(node, paneId, newPaneId, orientation, newSplitId) {
|
|
|
74
83
|
id: newSplitId,
|
|
75
84
|
orientation,
|
|
76
85
|
children: [node, { type: "pane", paneId: newPaneId }],
|
|
77
|
-
sizes: [
|
|
86
|
+
sizes: [first, 1 - first]
|
|
78
87
|
};
|
|
79
88
|
}
|
|
80
89
|
const index = node.children.findIndex(
|
|
@@ -85,13 +94,13 @@ function splitPane(node, paneId, newPaneId, orientation, newSplitId) {
|
|
|
85
94
|
const children = [...node.children];
|
|
86
95
|
children.splice(index + 1, 0, { type: "pane", paneId: newPaneId });
|
|
87
96
|
const sizes = [...node.sizes];
|
|
88
|
-
sizes.splice(index, 1, share
|
|
97
|
+
sizes.splice(index, 1, share * first, share * (1 - first));
|
|
89
98
|
return { ...node, children, sizes: normalizeSizes(sizes, children.length) };
|
|
90
99
|
}
|
|
91
100
|
return {
|
|
92
101
|
...node,
|
|
93
102
|
children: node.children.map(
|
|
94
|
-
(child) => splitPane(child, paneId, newPaneId, orientation, newSplitId)
|
|
103
|
+
(child) => splitPane(child, paneId, newPaneId, orientation, newSplitId, first)
|
|
95
104
|
)
|
|
96
105
|
};
|
|
97
106
|
}
|
|
@@ -110,6 +119,10 @@ function removePane(node, paneId) {
|
|
|
110
119
|
if (children.length === 1) return children[0] ?? null;
|
|
111
120
|
return { ...node, children, sizes: normalizeSizes(sizes, children.length) };
|
|
112
121
|
}
|
|
122
|
+
function containsSplit(node, splitId) {
|
|
123
|
+
if (node.type === "pane") return false;
|
|
124
|
+
return node.id === splitId || node.children.some((child) => containsSplit(child, splitId));
|
|
125
|
+
}
|
|
113
126
|
function resizeSplit(node, splitId, sizes) {
|
|
114
127
|
if (node.type === "pane") return node;
|
|
115
128
|
if (node.id === splitId) {
|
|
@@ -140,8 +153,12 @@ function isValidLayout(node, knownPanes, seen = /* @__PURE__ */ new Set()) {
|
|
|
140
153
|
|
|
141
154
|
// src/core/reducer.ts
|
|
142
155
|
var P = "matrxCanvas/";
|
|
156
|
+
var win = (windowId) => windowId && windowId !== CANVAS_MAIN_WINDOW ? { windowId } : {};
|
|
143
157
|
var canvasActions = {
|
|
144
|
-
open: (input) => ({
|
|
158
|
+
open: (input, options) => ({
|
|
159
|
+
type: `${P}open`,
|
|
160
|
+
payload: { ...input, now: Date.now(), ...options?.splitShare !== void 0 ? { splitShare: options.splitShare } : {} }
|
|
161
|
+
}),
|
|
145
162
|
update: (itemId, patch) => ({
|
|
146
163
|
type: `${P}update`,
|
|
147
164
|
payload: { itemId, ...patch, now: Date.now() }
|
|
@@ -156,19 +173,32 @@ var canvasActions = {
|
|
|
156
173
|
type: `${P}moveItem`,
|
|
157
174
|
payload: { itemId, toPaneId, index }
|
|
158
175
|
}),
|
|
159
|
-
|
|
176
|
+
/** `share` = the existing pane's fraction of the space (layout rules' stackSplit / sideSplit). */
|
|
177
|
+
splitPane: (paneId, orientation, moveItemId, share) => ({
|
|
160
178
|
type: `${P}splitPane`,
|
|
161
|
-
payload: { paneId, orientation, moveItemId }
|
|
179
|
+
payload: { paneId, orientation, moveItemId, ...share !== void 0 ? { share } : {} }
|
|
162
180
|
}),
|
|
163
181
|
closePane: (paneId) => ({ type: `${P}closePane`, payload: { paneId } }),
|
|
164
182
|
resizeSplit: (splitId, sizes) => ({
|
|
165
183
|
type: `${P}resizeSplit`,
|
|
166
184
|
payload: { splitId, sizes }
|
|
167
185
|
}),
|
|
168
|
-
setOpen: (open) => ({ type: `${P}setOpen`, payload: { open } }),
|
|
169
|
-
toggle: () =>
|
|
170
|
-
setFullscreen: (fullscreen) => ({
|
|
171
|
-
|
|
186
|
+
setOpen: (open, windowId) => ({ type: `${P}setOpen`, payload: { open, ...win(windowId) } }),
|
|
187
|
+
toggle: (windowId) => windowId && windowId !== CANVAS_MAIN_WINDOW ? { type: `${P}toggle`, payload: { windowId } } : { type: `${P}toggle` },
|
|
188
|
+
setFullscreen: (fullscreen, windowId) => ({
|
|
189
|
+
type: `${P}setFullscreen`,
|
|
190
|
+
payload: { fullscreen, ...win(windowId) }
|
|
191
|
+
}),
|
|
192
|
+
setWidth: (width, windowId) => ({ type: `${P}setWidth`, payload: { width, ...win(windowId) } }),
|
|
193
|
+
/** Moves a tab into its own pop-out window `windowId` (made by the host's popOut port). */
|
|
194
|
+
popOut: (itemId, windowId) => ({ type: `${P}popOut`, payload: { itemId, windowId } }),
|
|
195
|
+
/** Returns a popped-out tab to the window it came from. */
|
|
196
|
+
dockBack: (itemId) => ({ type: `${P}dockBack`, payload: { itemId } }),
|
|
197
|
+
/** A host window closed: its tabs dock home (default) or close with it. The main window never closes. */
|
|
198
|
+
closeWindow: (windowId, mode = "dock") => ({
|
|
199
|
+
type: `${P}closeWindow`,
|
|
200
|
+
payload: { windowId, mode }
|
|
201
|
+
}),
|
|
172
202
|
hydrate: (snapshot) => ({ type: `${P}hydrate`, payload: { snapshot } }),
|
|
173
203
|
reset: () => ({ type: `${P}reset` })
|
|
174
204
|
};
|
|
@@ -181,15 +211,80 @@ function createInitialCanvasState() {
|
|
|
181
211
|
version: 1,
|
|
182
212
|
isOpen: false,
|
|
183
213
|
isFullscreen: false,
|
|
184
|
-
width:
|
|
214
|
+
width: null,
|
|
185
215
|
layout: { type: "pane", paneId },
|
|
216
|
+
focusedPaneId: paneId,
|
|
217
|
+
windows: {},
|
|
186
218
|
panes: { [paneId]: { id: paneId, itemIds: [], activeItemId: null } },
|
|
187
219
|
items: {},
|
|
188
|
-
focusedPaneId: paneId,
|
|
189
220
|
seq: 1,
|
|
190
221
|
hydrated: false
|
|
191
222
|
};
|
|
192
223
|
}
|
|
224
|
+
function listCanvasWindowIds(state) {
|
|
225
|
+
return [CANVAS_MAIN_WINDOW, ...Object.keys(state.windows ?? {})];
|
|
226
|
+
}
|
|
227
|
+
function canvasWindowFrame(state, windowId = CANVAS_MAIN_WINDOW) {
|
|
228
|
+
return windowId === CANVAS_MAIN_WINDOW ? state : state.windows?.[windowId] ?? null;
|
|
229
|
+
}
|
|
230
|
+
function canvasWindowOfPane(state, paneId) {
|
|
231
|
+
for (const id of listCanvasWindowIds(state)) {
|
|
232
|
+
const frame2 = canvasWindowFrame(state, id);
|
|
233
|
+
if (frame2 && containsPane(frame2.layout, paneId)) return id;
|
|
234
|
+
}
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
function canvasWindowOfItem(state, itemId) {
|
|
238
|
+
const pane = paneOf(state, itemId);
|
|
239
|
+
return pane ? canvasWindowOfPane(state, pane.id) : null;
|
|
240
|
+
}
|
|
241
|
+
function withFrame(state, windowId, patch) {
|
|
242
|
+
if (windowId === CANVAS_MAIN_WINDOW) return { ...state, ...patch };
|
|
243
|
+
const current = state.windows[windowId];
|
|
244
|
+
if (!current) return state;
|
|
245
|
+
return { ...state, windows: { ...state.windows, [windowId]: { ...current, ...patch } } };
|
|
246
|
+
}
|
|
247
|
+
function ensureWindow(state, windowId, role = "window", home = CANVAS_MAIN_WINDOW) {
|
|
248
|
+
if (windowId === CANVAS_MAIN_WINDOW || state.windows[windowId]) return state;
|
|
249
|
+
const seq = state.seq + 1;
|
|
250
|
+
const paneId = paneIdFromSeq(seq);
|
|
251
|
+
const frame2 = {
|
|
252
|
+
id: windowId,
|
|
253
|
+
role,
|
|
254
|
+
home,
|
|
255
|
+
isOpen: false,
|
|
256
|
+
isFullscreen: false,
|
|
257
|
+
width: null,
|
|
258
|
+
layout: { type: "pane", paneId },
|
|
259
|
+
focusedPaneId: paneId
|
|
260
|
+
};
|
|
261
|
+
return {
|
|
262
|
+
...state,
|
|
263
|
+
seq,
|
|
264
|
+
panes: { ...state.panes, [paneId]: { id: paneId, itemIds: [], activeItemId: null } },
|
|
265
|
+
windows: { ...state.windows, [windowId]: frame2 }
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function removeWindow(state, windowId) {
|
|
269
|
+
const frame2 = state.windows[windowId];
|
|
270
|
+
if (!frame2) return state;
|
|
271
|
+
const panes = { ...state.panes };
|
|
272
|
+
for (const paneId of listPaneIds(frame2.layout)) delete panes[paneId];
|
|
273
|
+
const windows = { ...state.windows };
|
|
274
|
+
delete windows[windowId];
|
|
275
|
+
return { ...state, panes, windows };
|
|
276
|
+
}
|
|
277
|
+
function windowItemIds(state, windowId) {
|
|
278
|
+
const frame2 = canvasWindowFrame(state, windowId);
|
|
279
|
+
if (!frame2) return [];
|
|
280
|
+
return listPaneIds(frame2.layout).flatMap((paneId) => state.panes[paneId]?.itemIds ?? []);
|
|
281
|
+
}
|
|
282
|
+
function dropEmptyPopOut(state, windowId) {
|
|
283
|
+
if (!windowId || windowId === CANVAS_MAIN_WINDOW) return state;
|
|
284
|
+
const frame2 = state.windows[windowId];
|
|
285
|
+
if (!frame2 || frame2.role !== "pop-out" || windowItemIds(state, windowId).length > 0) return state;
|
|
286
|
+
return removeWindow(state, windowId);
|
|
287
|
+
}
|
|
193
288
|
function paneOf(state, itemId) {
|
|
194
289
|
for (const pane of Object.values(state.panes)) {
|
|
195
290
|
if (pane.itemIds.includes(itemId)) return pane;
|
|
@@ -199,6 +294,10 @@ function paneOf(state, itemId) {
|
|
|
199
294
|
function withPane(state, pane) {
|
|
200
295
|
return { ...state, panes: { ...state.panes, [pane.id]: pane } };
|
|
201
296
|
}
|
|
297
|
+
function focus(state, paneId) {
|
|
298
|
+
const windowId = canvasWindowOfPane(state, paneId);
|
|
299
|
+
return windowId ? withFrame(state, windowId, { focusedPaneId: paneId }) : state;
|
|
300
|
+
}
|
|
202
301
|
function nextActive(itemIds, removed, current) {
|
|
203
302
|
if (current !== removed) return current;
|
|
204
303
|
const index = itemIds.indexOf(removed);
|
|
@@ -215,27 +314,29 @@ function detachItem(state, itemId) {
|
|
|
215
314
|
});
|
|
216
315
|
}
|
|
217
316
|
function dropPane(state, paneId) {
|
|
218
|
-
const
|
|
317
|
+
const windowId = canvasWindowOfPane(state, paneId);
|
|
318
|
+
const frame2 = windowId ? canvasWindowFrame(state, windowId) : null;
|
|
319
|
+
if (!windowId || !frame2) return state;
|
|
320
|
+
const layout = removePane(frame2.layout, paneId);
|
|
219
321
|
if (!layout) return state;
|
|
220
322
|
const panes = { ...state.panes };
|
|
221
323
|
delete panes[paneId];
|
|
222
|
-
const focusedPaneId =
|
|
223
|
-
return { ...state,
|
|
324
|
+
const focusedPaneId = frame2.focusedPaneId === paneId ? listPaneIds(layout)[0] ?? frame2.focusedPaneId : frame2.focusedPaneId;
|
|
325
|
+
return withFrame({ ...state, panes }, windowId, { layout, focusedPaneId });
|
|
224
326
|
}
|
|
225
|
-
function addSplit(state, paneId, orientation) {
|
|
327
|
+
function addSplit(state, paneId, orientation, share) {
|
|
328
|
+
const windowId = canvasWindowOfPane(state, paneId);
|
|
329
|
+
const frame2 = windowId ? canvasWindowFrame(state, windowId) : null;
|
|
330
|
+
if (!windowId || !frame2) return null;
|
|
226
331
|
const seq = state.seq + 1;
|
|
227
332
|
const newPaneId = paneIdFromSeq(seq);
|
|
228
|
-
const layout = splitPane(
|
|
229
|
-
|
|
230
|
-
newPaneId,
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
panes: { ...state.panes, [newPaneId]: { id: newPaneId, itemIds: [], activeItemId: null } },
|
|
236
|
-
focusedPaneId: newPaneId
|
|
237
|
-
}
|
|
238
|
-
};
|
|
333
|
+
const layout = splitPane(frame2.layout, paneId, newPaneId, orientation, splitIdFromSeq(seq), share ?? 0.5);
|
|
334
|
+
const next = withFrame(
|
|
335
|
+
{ ...state, seq, panes: { ...state.panes, [newPaneId]: { id: newPaneId, itemIds: [], activeItemId: null } } },
|
|
336
|
+
windowId,
|
|
337
|
+
{ layout, focusedPaneId: newPaneId }
|
|
338
|
+
);
|
|
339
|
+
return { newPaneId, state: next };
|
|
239
340
|
}
|
|
240
341
|
function insertInto(state, paneId, itemId, activate, index) {
|
|
241
342
|
const pane = state.panes[paneId];
|
|
@@ -253,46 +354,96 @@ function afterActive(itemIds, active) {
|
|
|
253
354
|
const index = active ? itemIds.indexOf(active) : -1;
|
|
254
355
|
return index < 0 ? itemIds.length : index + 1;
|
|
255
356
|
}
|
|
357
|
+
function sendHome(state, itemId, home, activate) {
|
|
358
|
+
const homeFrame = canvasWindowFrame(state, home) ?? state;
|
|
359
|
+
const target = canvasWindowFrame(state, home) ? home : CANVAS_MAIN_WINDOW;
|
|
360
|
+
let next = detachItem(state, itemId);
|
|
361
|
+
next = insertInto(next, homeFrame.focusedPaneId, itemId, activate);
|
|
362
|
+
return activate ? withFrame(next, target, { focusedPaneId: homeFrame.focusedPaneId }) : next;
|
|
363
|
+
}
|
|
256
364
|
function clampWidth(width) {
|
|
257
|
-
return Number.isFinite(width) ? Math.max(CANVAS_MIN_WIDTH, Math.round(width)) :
|
|
365
|
+
return Number.isFinite(width) ? Math.max(CANVAS_MIN_WIDTH, Math.round(width)) : null;
|
|
366
|
+
}
|
|
367
|
+
function foldCanvasWindows(state) {
|
|
368
|
+
const extra = Object.keys(state.windows ?? {});
|
|
369
|
+
if (extra.length === 0) return state.windows ? state : { ...state, windows: {} };
|
|
370
|
+
let next = state;
|
|
371
|
+
for (const windowId of extra) {
|
|
372
|
+
for (const itemId of windowItemIds(next, windowId)) next = sendHome(next, itemId, CANVAS_MAIN_WINDOW, false);
|
|
373
|
+
next = removeWindow(next, windowId);
|
|
374
|
+
}
|
|
375
|
+
return next;
|
|
258
376
|
}
|
|
259
377
|
function sanitizeCanvasSnapshot(raw) {
|
|
260
378
|
if (!raw || typeof raw !== "object") return null;
|
|
261
379
|
const s = raw;
|
|
262
|
-
if (s.version !== 1 || !s.panes || !s.items || !s.layout) return null;
|
|
380
|
+
if (s.version !== 1 || !s.panes || typeof s.panes !== "object" || !s.items || typeof s.items !== "object" || !s.layout) return null;
|
|
263
381
|
const paneIds = new Set(Object.keys(s.panes));
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
382
|
+
const seen = /* @__PURE__ */ new Set();
|
|
383
|
+
if (!isValidLayout(s.layout, paneIds, seen)) return null;
|
|
384
|
+
const windows = {};
|
|
385
|
+
if (s.windows && typeof s.windows === "object") {
|
|
386
|
+
for (const [id, value] of Object.entries(s.windows)) {
|
|
387
|
+
if (!value || typeof value !== "object" || id === CANVAS_MAIN_WINDOW) continue;
|
|
388
|
+
const w = value;
|
|
389
|
+
const claimed = new Set(seen);
|
|
390
|
+
if (!isValidLayout(w.layout, paneIds, claimed)) continue;
|
|
391
|
+
for (const pane of claimed) seen.add(pane);
|
|
392
|
+
const layoutPanes = listPaneIds(w.layout);
|
|
393
|
+
const focused2 = w.focusedPaneId && layoutPanes.includes(w.focusedPaneId) ? w.focusedPaneId : layoutPanes[0];
|
|
394
|
+
if (!focused2) continue;
|
|
395
|
+
windows[id] = {
|
|
396
|
+
id,
|
|
397
|
+
role: w.role === "pop-out" ? "pop-out" : "window",
|
|
398
|
+
home: typeof w.home === "string" ? w.home : CANVAS_MAIN_WINDOW,
|
|
399
|
+
isOpen: w.isOpen === true,
|
|
400
|
+
isFullscreen: false,
|
|
401
|
+
width: typeof w.width === "number" ? clampWidth(w.width) : null,
|
|
402
|
+
layout: normalizeLayout(w.layout),
|
|
403
|
+
focusedPaneId: focused2
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (seen.size !== paneIds.size) return null;
|
|
267
408
|
const items = {};
|
|
268
409
|
for (const [id, item] of Object.entries(s.items)) {
|
|
269
410
|
if (item && typeof item.kind === "string" && typeof item.key === "string" && canvasItemId(item.kind, item.key) === id) {
|
|
270
411
|
items[id] = item;
|
|
271
412
|
}
|
|
272
413
|
}
|
|
414
|
+
const placed = /* @__PURE__ */ new Set();
|
|
273
415
|
const panes = {};
|
|
274
416
|
for (const [id, pane] of Object.entries(s.panes)) {
|
|
275
|
-
const itemIds = (Array.isArray(pane.itemIds) ? pane.itemIds : []).filter((itemId) => itemId in items);
|
|
417
|
+
const itemIds = (Array.isArray(pane.itemIds) ? pane.itemIds : []).filter((itemId) => itemId in items && !placed.has(itemId));
|
|
418
|
+
for (const itemId of itemIds) placed.add(itemId);
|
|
276
419
|
const activeItemId = pane.activeItemId && itemIds.includes(pane.activeItemId) ? pane.activeItemId : itemIds[0] ?? null;
|
|
277
420
|
panes[id] = { id, itemIds, activeItemId };
|
|
278
421
|
}
|
|
279
|
-
const
|
|
422
|
+
const mainPanes = listPaneIds(s.layout);
|
|
423
|
+
const focused = s.focusedPaneId && mainPanes.includes(s.focusedPaneId) ? s.focusedPaneId : mainPanes[0];
|
|
280
424
|
if (!focused) return null;
|
|
281
425
|
const layout = normalizeLayout(s.layout);
|
|
282
|
-
const
|
|
283
|
-
|
|
426
|
+
const highest = Math.max(highestIdNumber(layout), ...Object.values(windows).map((w) => highestIdNumber(w.layout)));
|
|
427
|
+
const seq = Math.max(typeof s.seq === "number" && Number.isFinite(s.seq) ? s.seq : 0, highest);
|
|
428
|
+
let state = {
|
|
284
429
|
version: 1,
|
|
285
430
|
isOpen: s.isOpen === true,
|
|
286
431
|
// Full screen is a moment, not a preference: a reload never starts there.
|
|
287
432
|
isFullscreen: false,
|
|
288
|
-
width:
|
|
433
|
+
width: typeof s.width === "number" ? clampWidth(s.width) : null,
|
|
289
434
|
layout,
|
|
435
|
+
focusedPaneId: focused,
|
|
436
|
+
windows,
|
|
290
437
|
panes,
|
|
291
438
|
items,
|
|
292
|
-
focusedPaneId: focused,
|
|
293
439
|
seq,
|
|
294
440
|
hydrated: true
|
|
295
441
|
};
|
|
442
|
+
state = foldCanvasWindows(state);
|
|
443
|
+
for (const id of Object.keys(items)) {
|
|
444
|
+
if (!placed.has(id)) state = insertInto(state, state.focusedPaneId, id, false);
|
|
445
|
+
}
|
|
446
|
+
return state;
|
|
296
447
|
}
|
|
297
448
|
function normalizeLayout(node) {
|
|
298
449
|
if (node.type === "pane") return node;
|
|
@@ -305,12 +456,14 @@ function highestIdNumber(node) {
|
|
|
305
456
|
}
|
|
306
457
|
function canvasReducer(state = createInitialCanvasState(), action) {
|
|
307
458
|
if (!isCanvasAction(action)) return state;
|
|
459
|
+
if (!state.windows) state = { ...state, windows: {} };
|
|
308
460
|
switch (action.type) {
|
|
309
461
|
case `${P}open`: {
|
|
310
|
-
const { kind, key, title, data, target = "focused", reveal = true, activate = true, now } = action.payload;
|
|
462
|
+
const { kind, key, title, data, target = "focused", reveal = true, activate = true, now, splitShare } = action.payload;
|
|
311
463
|
const itemId = canvasItemId(kind, key);
|
|
312
464
|
const existing = state.items[itemId];
|
|
313
465
|
let next = state;
|
|
466
|
+
let revealIn = action.payload.windowId ?? CANVAS_MAIN_WINDOW;
|
|
314
467
|
if (existing) {
|
|
315
468
|
const item = {
|
|
316
469
|
...existing,
|
|
@@ -320,23 +473,33 @@ function canvasReducer(state = createInitialCanvasState(), action) {
|
|
|
320
473
|
};
|
|
321
474
|
next = { ...next, items: { ...next.items, [itemId]: item } };
|
|
322
475
|
const pane = paneOf(next, itemId);
|
|
323
|
-
if (pane
|
|
324
|
-
|
|
476
|
+
if (pane) {
|
|
477
|
+
revealIn = canvasWindowOfPane(next, pane.id) ?? CANVAS_MAIN_WINDOW;
|
|
478
|
+
if (activate) next = focus(withPane(next, { ...pane, activeItemId: itemId }), pane.id);
|
|
479
|
+
} else {
|
|
480
|
+
next = ensureWindow(next, revealIn);
|
|
481
|
+
const frame2 = canvasWindowFrame(next, revealIn) ?? next;
|
|
482
|
+
next = insertInto(next, frame2.focusedPaneId, itemId, true);
|
|
483
|
+
}
|
|
325
484
|
} else {
|
|
326
485
|
const item = { id: itemId, kind, key, title: title ?? null, data: data ?? null, openedAt: now, updatedAt: now };
|
|
327
|
-
next = { ...next, items: { ...next.items, [itemId]: item } };
|
|
328
|
-
|
|
486
|
+
next = ensureWindow({ ...next, items: { ...next.items, [itemId]: item } }, revealIn);
|
|
487
|
+
const frame2 = canvasWindowFrame(next, revealIn) ?? next;
|
|
488
|
+
let paneId = frame2.focusedPaneId;
|
|
329
489
|
if (typeof target === "object" && next.panes[target.paneId]) {
|
|
330
490
|
paneId = target.paneId;
|
|
491
|
+
revealIn = canvasWindowOfPane(next, paneId) ?? revealIn;
|
|
331
492
|
} else if (target === "split-right" || target === "split-down") {
|
|
332
|
-
const split = addSplit(next,
|
|
333
|
-
|
|
334
|
-
|
|
493
|
+
const split = addSplit(next, frame2.focusedPaneId, target === "split-right" ? "horizontal" : "vertical", splitShare);
|
|
494
|
+
if (split) {
|
|
495
|
+
next = split.state;
|
|
496
|
+
paneId = split.newPaneId;
|
|
497
|
+
}
|
|
335
498
|
}
|
|
336
499
|
next = insertInto(next, paneId, itemId, activate);
|
|
337
|
-
if (activate) next =
|
|
500
|
+
if (activate) next = focus(next, paneId);
|
|
338
501
|
}
|
|
339
|
-
return reveal ?
|
|
502
|
+
return reveal ? withFrame(next, revealIn, { isOpen: true }) : next;
|
|
340
503
|
}
|
|
341
504
|
case `${P}update`: {
|
|
342
505
|
const { itemId, data, title, now } = action.payload;
|
|
@@ -372,12 +535,13 @@ function canvasReducer(state = createInitialCanvasState(), action) {
|
|
|
372
535
|
const { itemId } = action.payload;
|
|
373
536
|
if (!state.items[itemId]) return state;
|
|
374
537
|
const pane = paneOf(state, itemId);
|
|
538
|
+
const windowId = pane ? canvasWindowOfPane(state, pane.id) : null;
|
|
375
539
|
let next = detachItem(state, itemId);
|
|
376
540
|
const items = { ...next.items };
|
|
377
541
|
delete items[itemId];
|
|
378
542
|
next = { ...next, items };
|
|
379
543
|
if (pane && next.panes[pane.id]?.itemIds.length === 0) next = dropPane(next, pane.id);
|
|
380
|
-
return next;
|
|
544
|
+
return dropEmptyPopOut(next, windowId);
|
|
381
545
|
}
|
|
382
546
|
case `${P}closeOthers`: {
|
|
383
547
|
const { itemId } = action.payload;
|
|
@@ -390,26 +554,31 @@ function canvasReducer(state = createInitialCanvasState(), action) {
|
|
|
390
554
|
case `${P}activate`: {
|
|
391
555
|
const pane = paneOf(state, action.payload.itemId);
|
|
392
556
|
if (!pane) return state;
|
|
393
|
-
return
|
|
557
|
+
return focus(withPane(state, { ...pane, activeItemId: action.payload.itemId }), pane.id);
|
|
558
|
+
}
|
|
559
|
+
case `${P}focusPane`: {
|
|
560
|
+
const windowId = canvasWindowOfPane(state, action.payload.paneId);
|
|
561
|
+
const frame2 = windowId ? canvasWindowFrame(state, windowId) : null;
|
|
562
|
+
if (!windowId || !frame2 || frame2.focusedPaneId === action.payload.paneId) return state;
|
|
563
|
+
return withFrame(state, windowId, { focusedPaneId: action.payload.paneId });
|
|
394
564
|
}
|
|
395
|
-
case `${P}focusPane`:
|
|
396
|
-
return state.panes[action.payload.paneId] ? { ...state, focusedPaneId: action.payload.paneId } : state;
|
|
397
565
|
case `${P}moveItem`: {
|
|
398
566
|
const { itemId, toPaneId, index } = action.payload;
|
|
399
567
|
const from = paneOf(state, itemId);
|
|
400
568
|
if (!from || !state.panes[toPaneId]) return state;
|
|
401
|
-
if (from.id === toPaneId && index === void 0) return state
|
|
569
|
+
if (from.id === toPaneId && index === void 0) return canvasReducer(state, canvasActions.focusPane(toPaneId));
|
|
570
|
+
const fromWindow = canvasWindowOfPane(state, from.id);
|
|
402
571
|
let next = from.id === toPaneId ? state : detachItem(state, itemId);
|
|
403
|
-
next = insertInto(next, toPaneId, itemId, true, index);
|
|
404
|
-
next = { ...next, focusedPaneId: toPaneId };
|
|
572
|
+
next = focus(insertInto(next, toPaneId, itemId, true, index), toPaneId);
|
|
405
573
|
if (from.id !== toPaneId && next.panes[from.id]?.itemIds.length === 0) next = dropPane(next, from.id);
|
|
406
|
-
return next;
|
|
574
|
+
return dropEmptyPopOut(next, fromWindow);
|
|
407
575
|
}
|
|
408
576
|
case `${P}splitPane`: {
|
|
409
|
-
const { paneId, orientation, moveItemId } = action.payload;
|
|
577
|
+
const { paneId, orientation, moveItemId, share } = action.payload;
|
|
410
578
|
const pane = state.panes[paneId];
|
|
411
579
|
if (!pane) return state;
|
|
412
|
-
const split = addSplit(state, paneId, orientation);
|
|
580
|
+
const split = addSplit(state, paneId, orientation, share);
|
|
581
|
+
if (!split) return state;
|
|
413
582
|
let next = split.state;
|
|
414
583
|
if (moveItemId && pane.itemIds.includes(moveItemId) && pane.itemIds.length > 1) {
|
|
415
584
|
next = detachItem(next, moveItemId);
|
|
@@ -419,23 +588,106 @@ function canvasReducer(state = createInitialCanvasState(), action) {
|
|
|
419
588
|
}
|
|
420
589
|
case `${P}closePane`: {
|
|
421
590
|
const pane = state.panes[action.payload.paneId];
|
|
422
|
-
|
|
423
|
-
const
|
|
424
|
-
if (
|
|
591
|
+
const windowId = pane ? canvasWindowOfPane(state, pane.id) : null;
|
|
592
|
+
const frame2 = windowId ? canvasWindowFrame(state, windowId) : null;
|
|
593
|
+
if (!pane || !windowId || !frame2) return state;
|
|
594
|
+
const onlyPane = listPaneIds(frame2.layout).length === 1;
|
|
595
|
+
if (onlyPane) {
|
|
596
|
+
if (windowId !== CANVAS_MAIN_WINDOW && state.windows[windowId]?.role === "pop-out") {
|
|
597
|
+
return canvasReducer(state, canvasActions.closeWindow(windowId, "dock"));
|
|
598
|
+
}
|
|
599
|
+
return withFrame(state, windowId, { isOpen: false, isFullscreen: false });
|
|
600
|
+
}
|
|
425
601
|
const items = { ...state.items };
|
|
426
602
|
for (const id of pane.itemIds) delete items[id];
|
|
427
603
|
return dropPane({ ...state, items }, pane.id);
|
|
428
604
|
}
|
|
429
|
-
case `${P}resizeSplit`:
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
605
|
+
case `${P}resizeSplit`: {
|
|
606
|
+
const { splitId, sizes } = action.payload;
|
|
607
|
+
for (const windowId of listCanvasWindowIds(state)) {
|
|
608
|
+
const frame2 = canvasWindowFrame(state, windowId);
|
|
609
|
+
if (frame2 && containsSplit(frame2.layout, splitId)) {
|
|
610
|
+
return withFrame(state, windowId, { layout: resizeSplit(frame2.layout, splitId, sizes) });
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return state;
|
|
614
|
+
}
|
|
615
|
+
case `${P}setOpen`: {
|
|
616
|
+
const { open, windowId = CANVAS_MAIN_WINDOW } = action.payload;
|
|
617
|
+
const next = open ? ensureWindow(state, windowId) : state;
|
|
618
|
+
const frame2 = canvasWindowFrame(next, windowId);
|
|
619
|
+
if (!frame2 || frame2.isOpen === open) return next;
|
|
620
|
+
return withFrame(next, windowId, { isOpen: open, isFullscreen: open ? frame2.isFullscreen : false });
|
|
621
|
+
}
|
|
622
|
+
case `${P}toggle`: {
|
|
623
|
+
const windowId = action.payload?.windowId ?? CANVAS_MAIN_WINDOW;
|
|
624
|
+
const next = ensureWindow(state, windowId);
|
|
625
|
+
const frame2 = canvasWindowFrame(next, windowId);
|
|
626
|
+
if (!frame2) return state;
|
|
627
|
+
return withFrame(next, windowId, { isOpen: !frame2.isOpen, isFullscreen: frame2.isOpen ? false : frame2.isFullscreen });
|
|
628
|
+
}
|
|
629
|
+
case `${P}setFullscreen`: {
|
|
630
|
+
const { fullscreen, windowId = CANVAS_MAIN_WINDOW } = action.payload;
|
|
631
|
+
const next = fullscreen ? ensureWindow(state, windowId) : state;
|
|
632
|
+
const frame2 = canvasWindowFrame(next, windowId);
|
|
633
|
+
if (!frame2) return state;
|
|
634
|
+
return withFrame(next, windowId, { isFullscreen: fullscreen, isOpen: fullscreen ? true : frame2.isOpen });
|
|
635
|
+
}
|
|
636
|
+
case `${P}setWidth`: {
|
|
637
|
+
const { width, windowId = CANVAS_MAIN_WINDOW } = action.payload;
|
|
638
|
+
const next = ensureWindow(state, windowId);
|
|
639
|
+
return withFrame(next, windowId, { width: clampWidth(width) });
|
|
640
|
+
}
|
|
641
|
+
case `${P}popOut`: {
|
|
642
|
+
const { itemId, windowId } = action.payload;
|
|
643
|
+
const from = paneOf(state, itemId);
|
|
644
|
+
if (!from || windowId === CANVAS_MAIN_WINDOW) return state;
|
|
645
|
+
const fromWindow = canvasWindowOfPane(state, from.id) ?? CANVAS_MAIN_WINDOW;
|
|
646
|
+
if (fromWindow === windowId) return state;
|
|
647
|
+
let next = ensureWindow(state, windowId, "pop-out", fromWindow);
|
|
648
|
+
const target = canvasWindowFrame(next, windowId);
|
|
649
|
+
if (!target) return state;
|
|
650
|
+
next = detachItem(next, itemId);
|
|
651
|
+
next = insertInto(next, target.focusedPaneId, itemId, true);
|
|
652
|
+
next = withFrame(next, windowId, { isOpen: true, focusedPaneId: target.focusedPaneId });
|
|
653
|
+
if (next.panes[from.id]?.itemIds.length === 0) next = dropPane(next, from.id);
|
|
654
|
+
next = dropEmptyPopOut(next, fromWindow);
|
|
655
|
+
if (canvasWindowFrame(next, fromWindow) && windowItemIds(next, fromWindow).length === 0) {
|
|
656
|
+
next = withFrame(next, fromWindow, { isOpen: false, isFullscreen: false });
|
|
657
|
+
}
|
|
658
|
+
return next;
|
|
659
|
+
}
|
|
660
|
+
case `${P}dockBack`: {
|
|
661
|
+
const { itemId } = action.payload;
|
|
662
|
+
const windowId = canvasWindowOfItem(state, itemId);
|
|
663
|
+
if (!windowId || windowId === CANVAS_MAIN_WINDOW) return state;
|
|
664
|
+
const frame2 = state.windows[windowId];
|
|
665
|
+
if (!frame2) return state;
|
|
666
|
+
const home = canvasWindowFrame(state, frame2.home) ? frame2.home : CANVAS_MAIN_WINDOW;
|
|
667
|
+
let next = sendHome(state, itemId, home, true);
|
|
668
|
+
next = withFrame(next, home, { isOpen: true });
|
|
669
|
+
const left = paneOf(state, itemId);
|
|
670
|
+
if (left && next.panes[left.id]?.itemIds.length === 0) next = dropPane(next, left.id);
|
|
671
|
+
return dropEmptyPopOut(next, windowId);
|
|
672
|
+
}
|
|
673
|
+
case `${P}closeWindow`: {
|
|
674
|
+
const { windowId, mode } = action.payload;
|
|
675
|
+
const frame2 = state.windows[windowId];
|
|
676
|
+
if (!frame2) return state;
|
|
677
|
+
const itemIds = windowItemIds(state, windowId);
|
|
678
|
+
let next = state;
|
|
679
|
+
if (mode === "close") {
|
|
680
|
+
const items = { ...next.items };
|
|
681
|
+
for (const id of itemIds) delete items[id];
|
|
682
|
+
next = { ...next, items };
|
|
683
|
+
} else if (itemIds.length > 0) {
|
|
684
|
+
const home = canvasWindowFrame(state, frame2.home) ? frame2.home : CANVAS_MAIN_WINDOW;
|
|
685
|
+
const active = state.panes[frame2.focusedPaneId]?.activeItemId ?? null;
|
|
686
|
+
for (const id of itemIds) next = sendHome(next, id, home, id === active);
|
|
687
|
+
next = withFrame(next, home, { isOpen: true });
|
|
688
|
+
}
|
|
689
|
+
return removeWindow(next, windowId);
|
|
690
|
+
}
|
|
439
691
|
case `${P}hydrate`: {
|
|
440
692
|
const snapshot = action.payload.snapshot ? sanitizeCanvasSnapshot(action.payload.snapshot) : null;
|
|
441
693
|
if (!snapshot) return { ...state, hydrated: true };
|
|
@@ -482,6 +734,64 @@ function bindCanvasToReduxStore(store, select) {
|
|
|
482
734
|
};
|
|
483
735
|
}
|
|
484
736
|
|
|
737
|
+
// src/core/remote.ts
|
|
738
|
+
function createRemoteCanvasStore(options) {
|
|
739
|
+
let state = options.initialSnapshot;
|
|
740
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
741
|
+
const report = options.onError ?? ((error, action) => console.error(`[@ai-matrx/canvas] remote dispatch of ${action.type} failed`, error));
|
|
742
|
+
const stop = options.subscribe((snapshot) => {
|
|
743
|
+
const next = shareStructure(state, snapshot);
|
|
744
|
+
if (next === state) return;
|
|
745
|
+
state = next;
|
|
746
|
+
for (const listener of [...listeners]) listener();
|
|
747
|
+
});
|
|
748
|
+
return {
|
|
749
|
+
authority: "remote",
|
|
750
|
+
getState: () => state,
|
|
751
|
+
dispatch(action) {
|
|
752
|
+
if (!isCanvasAction(action)) return;
|
|
753
|
+
try {
|
|
754
|
+
void Promise.resolve(options.dispatch(action)).catch((error) => report(error, action));
|
|
755
|
+
} catch (error) {
|
|
756
|
+
report(error, action);
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
subscribe(listener) {
|
|
760
|
+
listeners.add(listener);
|
|
761
|
+
return () => listeners.delete(listener);
|
|
762
|
+
},
|
|
763
|
+
dispose() {
|
|
764
|
+
stop();
|
|
765
|
+
listeners.clear();
|
|
766
|
+
}
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
function shareStructure(prev, next) {
|
|
770
|
+
if (Object.is(prev, next)) return prev;
|
|
771
|
+
if (typeof prev !== "object" || typeof next !== "object" || prev === null || next === null) return next;
|
|
772
|
+
if (Array.isArray(prev) !== Array.isArray(next)) return next;
|
|
773
|
+
if (Array.isArray(prev) && Array.isArray(next)) {
|
|
774
|
+
let same2 = prev.length === next.length;
|
|
775
|
+
const out2 = next.map((value, i) => {
|
|
776
|
+
const shared = i < prev.length ? shareStructure(prev[i], value) : value;
|
|
777
|
+
if (shared !== prev[i]) same2 = false;
|
|
778
|
+
return shared;
|
|
779
|
+
});
|
|
780
|
+
return same2 ? prev : out2;
|
|
781
|
+
}
|
|
782
|
+
const a = prev;
|
|
783
|
+
const b = next;
|
|
784
|
+
const keys = Object.keys(b);
|
|
785
|
+
let same = keys.length === Object.keys(a).length;
|
|
786
|
+
const out = {};
|
|
787
|
+
for (const key of keys) {
|
|
788
|
+
const shared = key in a ? shareStructure(a[key], b[key]) : b[key];
|
|
789
|
+
if (shared !== a[key] || !(key in a)) same = false;
|
|
790
|
+
out[key] = shared;
|
|
791
|
+
}
|
|
792
|
+
return same ? prev : out;
|
|
793
|
+
}
|
|
794
|
+
|
|
485
795
|
// src/core/persistence.ts
|
|
486
796
|
var CANVAS_STORAGE_KEY = "ai-matrx.canvas.v1";
|
|
487
797
|
function createLocalStorageCanvasPersistence(key = CANVAS_STORAGE_KEY, storage = typeof window === "undefined" ? null : safeLocalStorage()) {
|
|
@@ -498,6 +808,16 @@ function createLocalStorageCanvasPersistence(key = CANVAS_STORAGE_KEY, storage =
|
|
|
498
808
|
}
|
|
499
809
|
};
|
|
500
810
|
}
|
|
811
|
+
function createKeyValueCanvasPersistence(kv, key = CANVAS_STORAGE_KEY) {
|
|
812
|
+
return {
|
|
813
|
+
async load() {
|
|
814
|
+
const raw = await kv.get(key);
|
|
815
|
+
if (raw === void 0 || raw === null) return null;
|
|
816
|
+
return typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
817
|
+
},
|
|
818
|
+
save: (snapshot) => kv.set(key, JSON.parse(JSON.stringify(snapshot)))
|
|
819
|
+
};
|
|
820
|
+
}
|
|
501
821
|
function safeLocalStorage() {
|
|
502
822
|
try {
|
|
503
823
|
const probe = "__mxc_probe__";
|
|
@@ -509,6 +829,7 @@ function safeLocalStorage() {
|
|
|
509
829
|
}
|
|
510
830
|
}
|
|
511
831
|
function toPersistableSnapshot(state, isRestorable) {
|
|
832
|
+
state = foldCanvasWindows(state);
|
|
512
833
|
const keep = new Set(
|
|
513
834
|
Object.values(state.items).filter((item) => isRestorable(item.kind)).map((item) => item.id)
|
|
514
835
|
);
|
|
@@ -535,22 +856,164 @@ function toPersistableSnapshot(state, isRestorable) {
|
|
|
535
856
|
return { ...state, items, panes, layout, focusedPaneId };
|
|
536
857
|
}
|
|
537
858
|
|
|
859
|
+
// src/core/rules.ts
|
|
860
|
+
var CANVAS_WEB_LAYOUT_RULES = {
|
|
861
|
+
defaultWidth: CANVAS_DEFAULT_WIDTH,
|
|
862
|
+
minWidth: CANVAS_MIN_WIDTH,
|
|
863
|
+
centreMinWidth: 420,
|
|
864
|
+
stackSplit: 0.5,
|
|
865
|
+
sideSplit: 0.5,
|
|
866
|
+
paneMinWidth: 0
|
|
867
|
+
};
|
|
868
|
+
var CANVAS_DESKTOP_LAYOUT_RULES = {
|
|
869
|
+
defaultWidth: { fraction: 0.36 },
|
|
870
|
+
minWidth: CANVAS_MIN_WIDTH,
|
|
871
|
+
centreMinWidth: 520,
|
|
872
|
+
stackSplit: 0.7,
|
|
873
|
+
sideSplit: 0.5,
|
|
874
|
+
paneMinWidth: 360
|
|
875
|
+
};
|
|
876
|
+
var clampFraction = (value, fallback) => Number.isFinite(value) ? Math.min(0.88, Math.max(0.12, value)) : fallback;
|
|
877
|
+
function resolveCanvasLayoutRules(rules) {
|
|
878
|
+
const merged = { ...CANVAS_WEB_LAYOUT_RULES, ...rules ?? {} };
|
|
879
|
+
const positive = (value, fallback) => Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
880
|
+
return {
|
|
881
|
+
defaultWidth: typeof merged.defaultWidth === "number" ? positive(merged.defaultWidth, CANVAS_DEFAULT_WIDTH) : { fraction: Math.min(1, Math.max(0.05, Number.isFinite(merged.defaultWidth.fraction) ? merged.defaultWidth.fraction : 0.36)) },
|
|
882
|
+
minWidth: positive(merged.minWidth, CANVAS_MIN_WIDTH),
|
|
883
|
+
centreMinWidth: positive(merged.centreMinWidth, CANVAS_WEB_LAYOUT_RULES.centreMinWidth),
|
|
884
|
+
stackSplit: clampFraction(merged.stackSplit, 0.5),
|
|
885
|
+
sideSplit: clampFraction(merged.sideSplit, 0.5),
|
|
886
|
+
paneMinWidth: positive(merged.paneMinWidth, 0)
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
function defaultCanvasWidth(rules, region) {
|
|
890
|
+
if (typeof rules.defaultWidth === "number") return rules.defaultWidth;
|
|
891
|
+
return region > 0 ? Math.round(region * rules.defaultWidth.fraction) : CANVAS_DEFAULT_WIDTH;
|
|
892
|
+
}
|
|
893
|
+
function fitCanvasWidth(stored, region, rules) {
|
|
894
|
+
const wanted = stored ?? defaultCanvasWidth(rules, region);
|
|
895
|
+
if (region <= 0) return Math.max(rules.minWidth, wanted);
|
|
896
|
+
return Math.max(rules.minWidth, Math.min(wanted, region - rules.centreMinWidth));
|
|
897
|
+
}
|
|
898
|
+
function clampDraggedCanvasWidth(next, region, rules) {
|
|
899
|
+
const max = Math.max(rules.minWidth, region - rules.centreMinWidth);
|
|
900
|
+
return Math.min(max, Math.max(rules.minWidth, Math.round(next)));
|
|
901
|
+
}
|
|
902
|
+
function shouldStackSplit(width, children, rules) {
|
|
903
|
+
return rules.paneMinWidth > 0 && width > 0 && width < rules.paneMinWidth * children;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// src/core/commands.ts
|
|
907
|
+
var CANVAS_SHORTCUTS = [
|
|
908
|
+
{ id: "canvas.toggle", accelerator: "CmdOrCtrl+\\", when: "presented", label: "Show or hide canvas", web: true },
|
|
909
|
+
{ id: "canvas.exitFullscreen", accelerator: "Escape", when: "fullscreen", label: "Restore canvas", web: true },
|
|
910
|
+
{ id: "canvas.toggleFullscreen", accelerator: "CmdOrCtrl+Shift+Enter", when: "canvas-focus", label: "Expand canvas", web: true },
|
|
911
|
+
{ id: "canvas.splitRight", accelerator: "CmdOrCtrl+Alt+\\", when: "canvas-focus", label: "Split right", web: true },
|
|
912
|
+
{ id: "canvas.splitDown", accelerator: "CmdOrCtrl+Alt+Shift+\\", when: "canvas-focus", label: "Split down", web: true },
|
|
913
|
+
{ id: "canvas.closeTab", accelerator: "CmdOrCtrl+W", when: "canvas-focus", label: "Close tab", web: false },
|
|
914
|
+
{ id: "canvas.nextTab", accelerator: "Ctrl+Tab", when: "canvas-focus", label: "Next tab", web: false },
|
|
915
|
+
{ id: "canvas.previousTab", accelerator: "Ctrl+Shift+Tab", when: "canvas-focus", label: "Previous tab", web: false }
|
|
916
|
+
];
|
|
917
|
+
function canvasShortcutHint(id, isMac) {
|
|
918
|
+
const shortcut = CANVAS_SHORTCUTS.find((entry) => entry.id === id);
|
|
919
|
+
if (!shortcut) return null;
|
|
920
|
+
return shortcut.accelerator.split("+").map((part) => {
|
|
921
|
+
if (part === "CmdOrCtrl") return isMac ? "\u2318" : "Ctrl+";
|
|
922
|
+
if (part === "Ctrl") return isMac ? "\u2303" : "Ctrl+";
|
|
923
|
+
if (part === "Alt") return isMac ? "\u2325" : "Alt+";
|
|
924
|
+
if (part === "Shift") return isMac ? "\u21E7" : "Shift+";
|
|
925
|
+
if (part === "Escape") return "Esc";
|
|
926
|
+
return part;
|
|
927
|
+
}).join("");
|
|
928
|
+
}
|
|
929
|
+
function runCanvasCommand(controller, id, windowId = controller.windowId) {
|
|
930
|
+
const state = controller.getState();
|
|
931
|
+
const frame2 = canvasWindowFrame(state, windowId);
|
|
932
|
+
const pane = frame2 ? state.panes[frame2.focusedPaneId] : void 0;
|
|
933
|
+
switch (id) {
|
|
934
|
+
case "canvas.toggle":
|
|
935
|
+
controller.toggle(windowId);
|
|
936
|
+
return true;
|
|
937
|
+
case "canvas.toggleFullscreen":
|
|
938
|
+
controller.setFullscreen(!(frame2?.isFullscreen ?? false), windowId);
|
|
939
|
+
return true;
|
|
940
|
+
case "canvas.exitFullscreen":
|
|
941
|
+
if (!frame2?.isFullscreen) return false;
|
|
942
|
+
controller.setFullscreen(false, windowId);
|
|
943
|
+
return true;
|
|
944
|
+
case "canvas.closeTab":
|
|
945
|
+
if (!frame2?.isOpen || !pane?.activeItemId) return false;
|
|
946
|
+
controller.close(pane.activeItemId);
|
|
947
|
+
return true;
|
|
948
|
+
case "canvas.nextTab":
|
|
949
|
+
case "canvas.previousTab": {
|
|
950
|
+
if (!frame2?.isOpen || !pane?.activeItemId || pane.itemIds.length < 2) return false;
|
|
951
|
+
const at = pane.itemIds.indexOf(pane.activeItemId);
|
|
952
|
+
const step = id === "canvas.nextTab" ? 1 : -1;
|
|
953
|
+
const next = pane.itemIds[(at + step + pane.itemIds.length) % pane.itemIds.length];
|
|
954
|
+
if (next) controller.activate(next);
|
|
955
|
+
return true;
|
|
956
|
+
}
|
|
957
|
+
case "canvas.splitRight":
|
|
958
|
+
case "canvas.splitDown":
|
|
959
|
+
if (!frame2?.isOpen || !pane) return false;
|
|
960
|
+
controller.splitPane(pane.id, id === "canvas.splitRight" ? "horizontal" : "vertical", pane.itemIds.length > 1 ? pane.activeItemId ?? void 0 : void 0);
|
|
961
|
+
return true;
|
|
962
|
+
default:
|
|
963
|
+
return false;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
var CODE_FOR = { "\\": "Backslash", Tab: "Tab", Escape: "Escape", Enter: "Enter" };
|
|
967
|
+
function matchesCanvasAccelerator(input, accelerator, isMac) {
|
|
968
|
+
const parts = accelerator.split("+");
|
|
969
|
+
const key = parts.pop() ?? "";
|
|
970
|
+
const want = { meta: false, ctrl: false, alt: false, shift: false };
|
|
971
|
+
for (const part of parts) {
|
|
972
|
+
if (part === "CmdOrCtrl") want[isMac ? "meta" : "ctrl"] = true;
|
|
973
|
+
else if (part === "Cmd" || part === "Meta") want.meta = true;
|
|
974
|
+
else if (part === "Ctrl") want.ctrl = true;
|
|
975
|
+
else if (part === "Alt") want.alt = true;
|
|
976
|
+
else if (part === "Shift") want.shift = true;
|
|
977
|
+
}
|
|
978
|
+
if (input.metaKey !== want.meta || input.ctrlKey !== want.ctrl || input.altKey !== want.alt || input.shiftKey !== want.shift) return false;
|
|
979
|
+
const code = CODE_FOR[key] ?? (/^[A-Z]$/i.test(key) ? `Key${key.toUpperCase()}` : void 0);
|
|
980
|
+
if (code && input.code === code) return true;
|
|
981
|
+
return input.key.toLowerCase() === key.toLowerCase();
|
|
982
|
+
}
|
|
983
|
+
|
|
538
984
|
// src/core/controller.ts
|
|
539
985
|
var consoleCanvasErrorSink = (report) => {
|
|
540
986
|
console.error(`[@ai-matrx/canvas] ${report.code}: ${report.message}`, report.detail ?? "");
|
|
541
987
|
};
|
|
542
988
|
function createCanvasController(options) {
|
|
989
|
+
const shared = {
|
|
990
|
+
options,
|
|
991
|
+
onError: options.onError ?? consoleCanvasErrorSink,
|
|
992
|
+
rules: resolveCanvasLayoutRules(options.layoutRules),
|
|
993
|
+
presentations: /* @__PURE__ */ new Map(),
|
|
994
|
+
presentationListeners: /* @__PURE__ */ new Set(),
|
|
995
|
+
views: /* @__PURE__ */ new Map(),
|
|
996
|
+
started: /* @__PURE__ */ new Map(),
|
|
997
|
+
sessionStop: null
|
|
998
|
+
};
|
|
999
|
+
return view(shared, canvasWindowId(options.windowId ?? CANVAS_MAIN_WINDOW));
|
|
1000
|
+
}
|
|
1001
|
+
function view(shared, windowId) {
|
|
1002
|
+
const cached = shared.views.get(windowId);
|
|
1003
|
+
if (cached) return cached;
|
|
1004
|
+
const { options, onError, rules } = shared;
|
|
543
1005
|
const { store, persistence = null } = options;
|
|
544
|
-
const onError = options.onError ?? consoleCanvasErrorSink;
|
|
545
1006
|
const isRestorable = options.isRestorable ?? (() => true);
|
|
546
1007
|
const dispatch = (action) => store.dispatch(action);
|
|
547
|
-
let presentations = 0;
|
|
548
|
-
const presentationListeners = /* @__PURE__ */ new Set();
|
|
549
1008
|
const notifyPresentation = () => {
|
|
550
|
-
for (const listener of [...presentationListeners]) listener();
|
|
1009
|
+
for (const listener of [...shared.presentationListeners]) listener();
|
|
551
1010
|
};
|
|
1011
|
+
const shareFor = (orientation) => orientation === "vertical" ? rules.stackSplit : rules.sideSplit;
|
|
552
1012
|
const controller = {
|
|
553
1013
|
store,
|
|
1014
|
+
windowId,
|
|
1015
|
+
rules,
|
|
1016
|
+
forWindow: (id) => view(shared, canvasWindowId(id)),
|
|
554
1017
|
getState: () => store.getState(),
|
|
555
1018
|
open(input) {
|
|
556
1019
|
if (options.isKnownKind && !options.isKnownKind(input.kind)) {
|
|
@@ -568,7 +1031,10 @@ function createCanvasController(options) {
|
|
|
568
1031
|
return null;
|
|
569
1032
|
}
|
|
570
1033
|
}
|
|
571
|
-
|
|
1034
|
+
const target = input.target ?? "focused";
|
|
1035
|
+
const splitShare = target === "split-down" ? rules.stackSplit : target === "split-right" ? rules.sideSplit : void 0;
|
|
1036
|
+
const resolved = input.windowId || windowId === CANVAS_MAIN_WINDOW ? input : { ...input, windowId };
|
|
1037
|
+
dispatch(canvasActions.open(resolved, splitShare === void 0 ? void 0 : { splitShare }));
|
|
572
1038
|
return canvasItemId(input.kind, input.key);
|
|
573
1039
|
},
|
|
574
1040
|
update(itemId, patch) {
|
|
@@ -594,42 +1060,76 @@ function createCanvasController(options) {
|
|
|
594
1060
|
activate: (itemId) => dispatch(canvasActions.activate(itemId)),
|
|
595
1061
|
focusPane: (paneId) => dispatch(canvasActions.focusPane(paneId)),
|
|
596
1062
|
moveItem: (itemId, toPaneId, index) => dispatch(canvasActions.moveItem(itemId, toPaneId, index)),
|
|
597
|
-
splitPane: (paneId, orientation, moveItemId) => dispatch(canvasActions.splitPane(paneId, orientation, moveItemId)),
|
|
1063
|
+
splitPane: (paneId, orientation, moveItemId) => dispatch(canvasActions.splitPane(paneId, orientation, moveItemId, shareFor(orientation))),
|
|
598
1064
|
closePane: (paneId) => dispatch(canvasActions.closePane(paneId)),
|
|
599
1065
|
resizeSplit: (splitId, sizes) => dispatch(canvasActions.resizeSplit(splitId, sizes)),
|
|
600
|
-
show: () => dispatch(canvasActions.setOpen(true)),
|
|
601
|
-
hide: () => dispatch(canvasActions.setOpen(false)),
|
|
602
|
-
toggle: () => dispatch(canvasActions.toggle()),
|
|
603
|
-
setFullscreen: (fullscreen) => dispatch(canvasActions.setFullscreen(fullscreen)),
|
|
604
|
-
setWidth: (width) => dispatch(canvasActions.setWidth(width)),
|
|
1066
|
+
show: (w = windowId) => dispatch(canvasActions.setOpen(true, w)),
|
|
1067
|
+
hide: (w = windowId) => dispatch(canvasActions.setOpen(false, w)),
|
|
1068
|
+
toggle: (w = windowId) => dispatch(canvasActions.toggle(w)),
|
|
1069
|
+
setFullscreen: (fullscreen, w = windowId) => dispatch(canvasActions.setFullscreen(fullscreen, w)),
|
|
1070
|
+
setWidth: (width, w = windowId) => dispatch(canvasActions.setWidth(width, w)),
|
|
1071
|
+
async popOut(itemId) {
|
|
1072
|
+
const item = store.getState().items[itemId];
|
|
1073
|
+
if (!item) return null;
|
|
1074
|
+
const port = options.windowPorts?.().popOut;
|
|
1075
|
+
if (!port) {
|
|
1076
|
+
onError({ code: "pop-out-unavailable", message: "This app cannot pop canvas tabs out into their own window.", detail: { itemId } });
|
|
1077
|
+
return null;
|
|
1078
|
+
}
|
|
1079
|
+
let result;
|
|
1080
|
+
try {
|
|
1081
|
+
result = await port(item);
|
|
1082
|
+
} catch (error) {
|
|
1083
|
+
onError({ code: "pop-out-failed", message: "The window for this tab could not be opened.", detail: error });
|
|
1084
|
+
return null;
|
|
1085
|
+
}
|
|
1086
|
+
if (!result) {
|
|
1087
|
+
onError({ code: "pop-out-refused", message: "The app declined to pop this tab out.", detail: { itemId } });
|
|
1088
|
+
return null;
|
|
1089
|
+
}
|
|
1090
|
+
const target = canvasWindowId(result.windowId);
|
|
1091
|
+
dispatch(canvasActions.popOut(itemId, target));
|
|
1092
|
+
return target;
|
|
1093
|
+
},
|
|
1094
|
+
dockBack(itemId) {
|
|
1095
|
+
const state = store.getState();
|
|
1096
|
+
const from = canvasWindowOfItem(state, itemId);
|
|
1097
|
+
if (!from || from === CANVAS_MAIN_WINDOW || state.windows[from]?.role !== "pop-out") return false;
|
|
1098
|
+
dispatch(canvasActions.dockBack(itemId));
|
|
1099
|
+
options.windowPorts?.().dockBack?.(itemId, from);
|
|
1100
|
+
return true;
|
|
1101
|
+
},
|
|
1102
|
+
closeWindow: (w, mode = "dock") => dispatch(canvasActions.closeWindow(w, mode)),
|
|
605
1103
|
has: (kind, key) => canvasItemId(kind, key) in store.getState().items,
|
|
606
|
-
isPresented: () => presentations > 0,
|
|
607
|
-
registerPresentation() {
|
|
608
|
-
presentations
|
|
1104
|
+
isPresented: (w = windowId) => (shared.presentations.get(w) ?? 0) > 0,
|
|
1105
|
+
registerPresentation(w = windowId) {
|
|
1106
|
+
shared.presentations.set(w, (shared.presentations.get(w) ?? 0) + 1);
|
|
609
1107
|
notifyPresentation();
|
|
610
1108
|
let done = false;
|
|
611
1109
|
return () => {
|
|
612
1110
|
if (done) return;
|
|
613
1111
|
done = true;
|
|
614
|
-
presentations
|
|
1112
|
+
shared.presentations.set(w, Math.max(0, (shared.presentations.get(w) ?? 1) - 1));
|
|
615
1113
|
notifyPresentation();
|
|
616
1114
|
};
|
|
617
1115
|
},
|
|
618
1116
|
subscribePresentation(listener) {
|
|
619
|
-
presentationListeners.add(listener);
|
|
620
|
-
return () => presentationListeners.delete(listener);
|
|
1117
|
+
shared.presentationListeners.add(listener);
|
|
1118
|
+
return () => shared.presentationListeners.delete(listener);
|
|
621
1119
|
},
|
|
622
1120
|
start() {
|
|
623
1121
|
let disposed = false;
|
|
624
1122
|
let timer = null;
|
|
625
1123
|
let lastSaved = null;
|
|
1124
|
+
const owns = store.authority !== "remote";
|
|
1125
|
+
const memory = owns ? persistence : null;
|
|
626
1126
|
const save = () => {
|
|
627
1127
|
timer = null;
|
|
628
1128
|
const state = store.getState();
|
|
629
|
-
if (!
|
|
1129
|
+
if (!memory || !state.hydrated || state === lastSaved) return;
|
|
630
1130
|
lastSaved = state;
|
|
631
1131
|
try {
|
|
632
|
-
void Promise.resolve(
|
|
1132
|
+
void Promise.resolve(memory.save(toPersistableSnapshot(state, isRestorable))).catch(
|
|
633
1133
|
(error) => onError({ code: "persistence-save", message: "Saving the canvas layout failed.", detail: error })
|
|
634
1134
|
);
|
|
635
1135
|
} catch (error) {
|
|
@@ -638,18 +1138,18 @@ function createCanvasController(options) {
|
|
|
638
1138
|
};
|
|
639
1139
|
let seen = store.getState();
|
|
640
1140
|
const unsubscribe = store.subscribe(() => {
|
|
641
|
-
if (!
|
|
1141
|
+
if (!memory || disposed) return;
|
|
642
1142
|
const current = store.getState();
|
|
643
1143
|
if (current === seen) return;
|
|
644
1144
|
seen = current;
|
|
645
1145
|
if (timer) clearTimeout(timer);
|
|
646
1146
|
timer = setTimeout(save, options.saveDelayMs ?? 250);
|
|
647
1147
|
});
|
|
648
|
-
if (store.getState().hydrated) {
|
|
649
|
-
} else if (!
|
|
1148
|
+
if (!owns || store.getState().hydrated) {
|
|
1149
|
+
} else if (!memory) {
|
|
650
1150
|
dispatch(canvasActions.hydrate(null));
|
|
651
1151
|
} else {
|
|
652
|
-
Promise.resolve().then(() =>
|
|
1152
|
+
Promise.resolve().then(() => memory.load()).then(
|
|
653
1153
|
(snapshot) => {
|
|
654
1154
|
if (disposed) return;
|
|
655
1155
|
const openedThisSession = store.getState().isOpen;
|
|
@@ -668,9 +1168,11 @@ function createCanvasController(options) {
|
|
|
668
1168
|
}
|
|
669
1169
|
);
|
|
670
1170
|
}
|
|
1171
|
+
const stopSessions = startSessions(shared, windowId);
|
|
671
1172
|
return () => {
|
|
672
1173
|
disposed = true;
|
|
673
1174
|
unsubscribe();
|
|
1175
|
+
stopSessions();
|
|
674
1176
|
if (timer) {
|
|
675
1177
|
clearTimeout(timer);
|
|
676
1178
|
save();
|
|
@@ -678,48 +1180,180 @@ function createCanvasController(options) {
|
|
|
678
1180
|
};
|
|
679
1181
|
}
|
|
680
1182
|
};
|
|
1183
|
+
shared.views.set(windowId, controller);
|
|
681
1184
|
return controller;
|
|
682
1185
|
}
|
|
1186
|
+
function startSessions(shared, windowId) {
|
|
1187
|
+
const { options, onError } = shared;
|
|
1188
|
+
const sessions = options.sessions;
|
|
1189
|
+
if (!sessions) return () => void 0;
|
|
1190
|
+
shared.started.set(windowId, (shared.started.get(windowId) ?? 0) + 1);
|
|
1191
|
+
let stopped = false;
|
|
1192
|
+
if (!shared.sessionStop) {
|
|
1193
|
+
const store = options.store;
|
|
1194
|
+
const local = store.authority !== "remote";
|
|
1195
|
+
let tracked = null;
|
|
1196
|
+
const call = (fn) => {
|
|
1197
|
+
try {
|
|
1198
|
+
fn();
|
|
1199
|
+
} catch (error) {
|
|
1200
|
+
onError({ code: "session-hook", message: "A canvas session hook threw.", detail: error });
|
|
1201
|
+
}
|
|
1202
|
+
};
|
|
1203
|
+
const read = (fn) => {
|
|
1204
|
+
try {
|
|
1205
|
+
return fn();
|
|
1206
|
+
} catch (error) {
|
|
1207
|
+
onError({ code: "session-hook", message: "A canvas kind's sessionKey threw.", detail: error });
|
|
1208
|
+
return null;
|
|
1209
|
+
}
|
|
1210
|
+
};
|
|
1211
|
+
let prevItems = /* @__PURE__ */ new Set();
|
|
1212
|
+
const snapshot = (state) => {
|
|
1213
|
+
const out = /* @__PURE__ */ new Map();
|
|
1214
|
+
for (const w of listCanvasWindowIds(state)) {
|
|
1215
|
+
if (!local && !shared.started.has(w)) continue;
|
|
1216
|
+
const frame2 = canvasWindowFrame(state, w);
|
|
1217
|
+
if (!frame2) continue;
|
|
1218
|
+
for (const paneId of listPaneIds(frame2.layout)) {
|
|
1219
|
+
for (const itemId of state.panes[paneId]?.itemIds ?? []) {
|
|
1220
|
+
const item = state.items[itemId];
|
|
1221
|
+
const hooks = item ? sessions(item.kind) : void 0;
|
|
1222
|
+
if (!item || !hooks) continue;
|
|
1223
|
+
const key = read(() => hooks.sessionKey(item));
|
|
1224
|
+
if (key) out.set(itemId, { item, key, windowId: w });
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
return out;
|
|
1229
|
+
};
|
|
1230
|
+
const sync = () => {
|
|
1231
|
+
const state = options.store.getState();
|
|
1232
|
+
if (!state.hydrated) return;
|
|
1233
|
+
const next = snapshot(state);
|
|
1234
|
+
const first = tracked === null;
|
|
1235
|
+
const prev = tracked ?? /* @__PURE__ */ new Map();
|
|
1236
|
+
for (const [id, entry] of prev) {
|
|
1237
|
+
const now = next.get(id);
|
|
1238
|
+
if (now && now.key === entry.key) continue;
|
|
1239
|
+
const hooks = sessions(entry.item.kind);
|
|
1240
|
+
const reason = state.items[id] && !now ? "leave" : "close";
|
|
1241
|
+
call(() => hooks?.onDetach?.({ item: entry.item, sessionKey: entry.key, windowId: entry.windowId, reason }));
|
|
1242
|
+
}
|
|
1243
|
+
for (const [id, entry] of next) {
|
|
1244
|
+
const before = prev.get(id);
|
|
1245
|
+
if (before && before.key === entry.key) continue;
|
|
1246
|
+
const hooks = sessions(entry.item.kind);
|
|
1247
|
+
const reason = first ? "restore" : prevItems.has(id) ? "arrive" : "open";
|
|
1248
|
+
call(() => hooks?.onAttach?.({ item: entry.item, sessionKey: entry.key, windowId: entry.windowId, reason }));
|
|
1249
|
+
}
|
|
1250
|
+
tracked = next;
|
|
1251
|
+
prevItems = new Set(Object.keys(state.items));
|
|
1252
|
+
};
|
|
1253
|
+
let lastState = null;
|
|
1254
|
+
const unsubscribe = store.subscribe(() => {
|
|
1255
|
+
const state = store.getState();
|
|
1256
|
+
if (state === lastState) return;
|
|
1257
|
+
lastState = state;
|
|
1258
|
+
sync();
|
|
1259
|
+
});
|
|
1260
|
+
sync();
|
|
1261
|
+
shared.sessionStop = () => {
|
|
1262
|
+
unsubscribe();
|
|
1263
|
+
for (const entry of tracked?.values() ?? []) {
|
|
1264
|
+
const hooks = sessions(entry.item.kind);
|
|
1265
|
+
call(() => hooks?.onDetach?.({ item: entry.item, sessionKey: entry.key, windowId: entry.windowId, reason: "unmount" }));
|
|
1266
|
+
}
|
|
1267
|
+
tracked = null;
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
return () => {
|
|
1271
|
+
if (stopped) return;
|
|
1272
|
+
stopped = true;
|
|
1273
|
+
const left = (shared.started.get(windowId) ?? 1) - 1;
|
|
1274
|
+
if (left > 0) shared.started.set(windowId, left);
|
|
1275
|
+
else shared.started.delete(windowId);
|
|
1276
|
+
if (shared.started.size === 0 && shared.sessionStop) {
|
|
1277
|
+
const stop = shared.sessionStop;
|
|
1278
|
+
shared.sessionStop = null;
|
|
1279
|
+
stop();
|
|
1280
|
+
}
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
683
1283
|
|
|
684
1284
|
// src/core/selectors.ts
|
|
685
|
-
var
|
|
686
|
-
var
|
|
687
|
-
var
|
|
688
|
-
var
|
|
689
|
-
var
|
|
1285
|
+
var frame = (s, w) => canvasWindowFrame(s, w ?? CANVAS_MAIN_WINDOW);
|
|
1286
|
+
var selectCanvasIsOpen = (s, windowId) => frame(s, windowId)?.isOpen ?? false;
|
|
1287
|
+
var selectCanvasIsFullscreen = (s, windowId) => frame(s, windowId)?.isFullscreen ?? false;
|
|
1288
|
+
var selectCanvasWidth = (s, windowId) => frame(s, windowId)?.width ?? null;
|
|
1289
|
+
var selectCanvasLayout = (s, windowId) => frame(s, windowId)?.layout ?? null;
|
|
1290
|
+
var selectCanvasFocusedPaneId = (s, windowId) => frame(s, windowId)?.focusedPaneId ?? null;
|
|
690
1291
|
var selectCanvasIsHydrated = (s) => s.hydrated;
|
|
691
1292
|
var selectCanvasItemCount = (s) => Object.keys(s.items).length;
|
|
692
1293
|
var selectCanvasPane = (s, paneId) => s.panes[paneId];
|
|
693
1294
|
var selectCanvasItem = (s, itemId) => s.items[itemId];
|
|
694
|
-
function selectCanvasPaneCount(s) {
|
|
695
|
-
|
|
1295
|
+
function selectCanvasPaneCount(s, windowId) {
|
|
1296
|
+
const layout = frame(s, windowId)?.layout;
|
|
1297
|
+
return layout ? listPaneIds(layout).length : 0;
|
|
696
1298
|
}
|
|
697
|
-
function
|
|
698
|
-
const
|
|
1299
|
+
function selectCanvasWindowItemCount(s, windowId) {
|
|
1300
|
+
const layout = frame(s, windowId)?.layout;
|
|
1301
|
+
if (!layout) return 0;
|
|
1302
|
+
return listPaneIds(layout).reduce((sum, paneId) => sum + (s.panes[paneId]?.itemIds.length ?? 0), 0);
|
|
1303
|
+
}
|
|
1304
|
+
function selectCanvasActiveItem(s, windowId) {
|
|
1305
|
+
const f = frame(s, windowId);
|
|
1306
|
+
const pane = f ? s.panes[f.focusedPaneId] : void 0;
|
|
699
1307
|
return pane?.activeItemId ? s.items[pane.activeItemId] ?? null : null;
|
|
700
1308
|
}
|
|
1309
|
+
var NO_WINDOWS = Object.freeze({});
|
|
1310
|
+
function selectCanvasWindows(s) {
|
|
1311
|
+
return s.windows ?? NO_WINDOWS;
|
|
1312
|
+
}
|
|
1313
|
+
var selectCanvasExtraWindow = (s, windowId) => s.windows?.[windowId];
|
|
1314
|
+
var selectCanvasWindowOfItem = canvasWindowOfItem;
|
|
701
1315
|
export {
|
|
702
1316
|
CANVAS_DEFAULT_WIDTH,
|
|
1317
|
+
CANVAS_DESKTOP_LAYOUT_RULES,
|
|
1318
|
+
CANVAS_MAIN_WINDOW,
|
|
703
1319
|
CANVAS_MIN_SPLIT_FRACTION,
|
|
704
1320
|
CANVAS_MIN_WIDTH,
|
|
1321
|
+
CANVAS_SHORTCUTS,
|
|
705
1322
|
CANVAS_STORAGE_KEY,
|
|
1323
|
+
CANVAS_WEB_LAYOUT_RULES,
|
|
706
1324
|
bindCanvasToReduxStore,
|
|
707
1325
|
canvasActions,
|
|
708
1326
|
canvasItemId,
|
|
709
1327
|
canvasReducer,
|
|
1328
|
+
canvasShortcutHint,
|
|
1329
|
+
canvasWindowFrame,
|
|
1330
|
+
canvasWindowId,
|
|
1331
|
+
canvasWindowOfItem,
|
|
1332
|
+
canvasWindowOfPane,
|
|
1333
|
+
clampDraggedCanvasWidth,
|
|
710
1334
|
consoleCanvasErrorSink,
|
|
711
1335
|
createCanvasController,
|
|
712
1336
|
createCanvasStore,
|
|
713
1337
|
createInitialCanvasState,
|
|
1338
|
+
createKeyValueCanvasPersistence,
|
|
714
1339
|
createLocalStorageCanvasPersistence,
|
|
1340
|
+
createRemoteCanvasStore,
|
|
1341
|
+
defaultCanvasWidth,
|
|
715
1342
|
findNonJson,
|
|
1343
|
+
fitCanvasWidth,
|
|
1344
|
+
foldCanvasWindows,
|
|
716
1345
|
isCanvasAction,
|
|
717
1346
|
isCanvasJson,
|
|
1347
|
+
listCanvasWindowIds,
|
|
718
1348
|
listPaneIds,
|
|
1349
|
+
matchesCanvasAccelerator,
|
|
719
1350
|
normalizeSizes,
|
|
720
1351
|
parseCanvasItemId,
|
|
1352
|
+
resolveCanvasLayoutRules,
|
|
1353
|
+
runCanvasCommand,
|
|
721
1354
|
sanitizeCanvasSnapshot,
|
|
722
1355
|
selectCanvasActiveItem,
|
|
1356
|
+
selectCanvasExtraWindow,
|
|
723
1357
|
selectCanvasFocusedPaneId,
|
|
724
1358
|
selectCanvasIsFullscreen,
|
|
725
1359
|
selectCanvasIsHydrated,
|
|
@@ -730,6 +1364,11 @@ export {
|
|
|
730
1364
|
selectCanvasPane,
|
|
731
1365
|
selectCanvasPaneCount,
|
|
732
1366
|
selectCanvasWidth,
|
|
1367
|
+
selectCanvasWindowItemCount,
|
|
1368
|
+
selectCanvasWindowOfItem,
|
|
1369
|
+
selectCanvasWindows,
|
|
1370
|
+
shareStructure,
|
|
1371
|
+
shouldStackSplit,
|
|
733
1372
|
toPersistableSnapshot
|
|
734
1373
|
};
|
|
735
1374
|
//# sourceMappingURL=index.js.map
|