@jxsuite/studio 0.10.2 → 0.13.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 +82 -0
- package/dist/studio.js +5114 -3424
- package/dist/studio.js.map +65 -49
- package/package.json +6 -3
- package/src/browse/browse.js +27 -3
- package/src/canvas/canvas-diff.js +184 -0
- package/src/canvas/canvas-helpers.js +10 -14
- package/src/canvas/canvas-live-render.js +136 -17
- package/src/canvas/canvas-render.js +154 -21
- package/src/canvas/canvas-utils.js +21 -23
- package/src/editor/component-inline-edit.js +54 -41
- package/src/editor/content-inline-edit.js +46 -47
- package/src/editor/context-menu.js +63 -39
- package/src/editor/convert-to-component.js +11 -14
- package/src/editor/insertion-helper.js +8 -10
- package/src/editor/shortcuts.js +69 -39
- package/src/files/components.js +15 -4
- package/src/files/files.js +72 -24
- package/src/panels/activity-bar.js +29 -7
- package/src/panels/block-action-bar.js +104 -80
- package/src/panels/canvas-dnd.js +132 -50
- package/src/panels/dnd.js +32 -28
- package/src/panels/editors.js +7 -14
- package/src/panels/elements-panel.js +9 -3
- package/src/panels/events-panel.js +44 -39
- package/src/panels/git-panel.js +45 -3
- package/src/panels/layers-panel.js +16 -11
- package/src/panels/left-panel.js +108 -43
- package/src/panels/overlays.js +97 -35
- package/src/panels/panel-events.js +18 -11
- package/src/panels/properties-panel.js +467 -98
- package/src/panels/right-panel.js +85 -37
- package/src/panels/shared.js +0 -22
- package/src/panels/signals-panel.js +125 -54
- package/src/panels/statusbar.js +23 -8
- package/src/panels/style-inputs.js +4 -2
- package/src/panels/style-panel.js +128 -105
- package/src/panels/stylebook-panel.js +42 -17
- package/src/panels/tab-strip.js +124 -0
- package/src/panels/toolbar.js +39 -9
- package/src/reactivity.js +28 -0
- package/src/settings/content-types-editor.js +78 -8
- package/src/settings/defs-editor.js +56 -7
- package/src/settings/schema-field-ui.js +99 -11
- package/src/site-context.js +105 -0
- package/src/state.js +0 -456
- package/src/store.js +105 -124
- package/src/studio.js +112 -121
- package/src/tabs/tab.js +153 -0
- package/src/tabs/transact.js +406 -0
- package/src/ui/spectrum.js +2 -0
- package/src/view.js +3 -0
- package/src/workspace/workspace.js +89 -0
package/src/studio.js
CHANGED
|
@@ -7,36 +7,32 @@
|
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
createState,
|
|
10
|
-
selectNode,
|
|
11
|
-
updateProperty,
|
|
12
|
-
updateDef,
|
|
13
10
|
pushDocument,
|
|
14
11
|
popDocument,
|
|
15
12
|
getNodeAtPath,
|
|
16
|
-
pathsEqual,
|
|
17
13
|
canvasWrap,
|
|
18
14
|
toolbarEl,
|
|
19
15
|
canvasPanels,
|
|
20
16
|
registerRenderer,
|
|
21
17
|
render,
|
|
22
|
-
update,
|
|
23
18
|
setUpdateFn,
|
|
24
19
|
setGetStateFn,
|
|
25
20
|
addUpdateMiddleware,
|
|
26
21
|
runUpdateMiddleware,
|
|
27
22
|
addPostRenderHook,
|
|
28
23
|
runPostRenderHooks,
|
|
29
|
-
notify,
|
|
30
24
|
projectState,
|
|
31
25
|
setProjectState,
|
|
32
26
|
updateUi,
|
|
33
27
|
setUpdateSessionFn,
|
|
34
|
-
setGetDocFn,
|
|
35
|
-
setGetSessionFn,
|
|
36
28
|
toFlat,
|
|
37
29
|
fromFlat,
|
|
38
30
|
} from "./store.js";
|
|
39
31
|
|
|
32
|
+
import { activeTab, openTab } from "./workspace/workspace.js";
|
|
33
|
+
import { transactDoc, mutateUpdateDef, mutateUpdateProperty } from "./tabs/transact.js";
|
|
34
|
+
import { effect } from "./reactivity.js";
|
|
35
|
+
|
|
40
36
|
import { view } from "./view.js";
|
|
41
37
|
|
|
42
38
|
import { isEditing, isEditableBlock } from "./editor/inline-edit.js";
|
|
@@ -71,9 +67,9 @@ import {
|
|
|
71
67
|
openProject as _openProject,
|
|
72
68
|
renderFilesTemplate as _renderFilesTemplate,
|
|
73
69
|
openFileFromTree as _openFileFromTree,
|
|
70
|
+
openFileInTab,
|
|
74
71
|
setupTreeKeyboard,
|
|
75
72
|
} from "./files/files.js";
|
|
76
|
-
import { eventsSidebarTemplate as _eventsSidebarTemplate } from "./panels/events-panel.js";
|
|
77
73
|
import { renderImportsTemplate } from "./panels/imports-panel.js";
|
|
78
74
|
import { renderHeadTemplate } from "./panels/head-panel.js";
|
|
79
75
|
import { exportCemManifest as _exportCemManifest } from "./services/cem-export.js";
|
|
@@ -97,11 +93,12 @@ import { renderGitPanel } from "./panels/git-panel.js";
|
|
|
97
93
|
import { components as _swc } from "./ui/spectrum.js"; // eslint-disable-line no-unused-vars
|
|
98
94
|
import "./ui/panel-resize.js";
|
|
99
95
|
import { initShortcuts } from "./editor/shortcuts.js";
|
|
100
|
-
import { renderActivityBar } from "./panels/activity-bar.js";
|
|
96
|
+
import { renderActivityBar, mount as mountActivityBar } from "./panels/activity-bar.js";
|
|
101
97
|
import * as toolbarPanel from "./panels/toolbar.js";
|
|
102
98
|
import * as overlaysPanel from "./panels/overlays.js";
|
|
103
99
|
import * as rightPanelMod from "./panels/right-panel.js";
|
|
104
100
|
import * as leftPanelMod from "./panels/left-panel.js";
|
|
101
|
+
import * as tabStrip from "./panels/tab-strip.js";
|
|
105
102
|
import { renderStylebookOverlays } from "./panels/stylebook-panel.js";
|
|
106
103
|
import { registerLayersDnD, registerComponentsDnD, registerElementsDnD } from "./panels/dnd.js";
|
|
107
104
|
import { defaultDef } from "./panels/shared.js";
|
|
@@ -132,6 +129,9 @@ function createFloatingContainer() {
|
|
|
132
129
|
|
|
133
130
|
let canvasMode = "design";
|
|
134
131
|
|
|
132
|
+
/** @type {any} */
|
|
133
|
+
let gitDiffState = null;
|
|
134
|
+
|
|
135
135
|
// ─── Component registry ───────────────────────────────────────────────────────
|
|
136
136
|
|
|
137
137
|
/** @param {any} componentPath */
|
|
@@ -177,12 +177,14 @@ async function closeFunctionEditor() {
|
|
|
177
177
|
const minResult = await codeService("minify", { code: currentCode });
|
|
178
178
|
const bodyToStore = minResult?.code ?? currentCode;
|
|
179
179
|
if (editing.type === "def") {
|
|
180
|
-
|
|
180
|
+
transactDoc(activeTab.value, (t) =>
|
|
181
|
+
mutateUpdateDef(t, editing.defName, { body: bodyToStore }),
|
|
182
|
+
);
|
|
181
183
|
} else if (editing.type === "event") {
|
|
182
184
|
const node = getNodeAtPath(S.document, editing.path);
|
|
183
185
|
const current = node?.[editing.eventKey] || {};
|
|
184
|
-
|
|
185
|
-
|
|
186
|
+
transactDoc(activeTab.value, (t) =>
|
|
187
|
+
mutateUpdateProperty(t, editing.path, editing.eventKey, {
|
|
186
188
|
...current,
|
|
187
189
|
$prototype: "Function",
|
|
188
190
|
body: bodyToStore,
|
|
@@ -234,6 +236,12 @@ const EMPTY_DOC = {
|
|
|
234
236
|
|
|
235
237
|
S = createState(structuredClone(EMPTY_DOC));
|
|
236
238
|
({ doc, session } = fromFlat(S));
|
|
239
|
+
setGetStateFn(() => S);
|
|
240
|
+
|
|
241
|
+
// Create the initial reactive tab — this is the canonical state container.
|
|
242
|
+
// The flat `S` object and `_update`/`_updateSession` are kept as a compatibility layer
|
|
243
|
+
// while call sites are progressively migrated to direct reactive mutations.
|
|
244
|
+
openTab({ id: "initial", document: structuredClone(EMPTY_DOC) });
|
|
237
245
|
|
|
238
246
|
// ─── Render loop ──────────────────────────────────────────────────────────────
|
|
239
247
|
|
|
@@ -246,13 +254,19 @@ toolbarPanel.mount(toolbarEl, {
|
|
|
246
254
|
saveFile: () => saveFile(),
|
|
247
255
|
parseMediaEntries,
|
|
248
256
|
getCanvasMode: () => canvasMode,
|
|
249
|
-
setCanvasMode: (/** @type {
|
|
257
|
+
setCanvasMode: (/** @type {string} */ m) => {
|
|
258
|
+
// Clear gitDiffState when exiting diff mode via toolbar
|
|
259
|
+
if (canvasMode === "git-diff" && m !== "git-diff") {
|
|
260
|
+
gitDiffState = null;
|
|
261
|
+
}
|
|
250
262
|
canvasMode = m;
|
|
251
263
|
},
|
|
252
264
|
renderCanvas: () => renderCanvas(),
|
|
253
265
|
safeRenderRightPanel: () => safeRenderRightPanel(),
|
|
254
266
|
});
|
|
255
267
|
|
|
268
|
+
tabStrip.mount(/** @type {HTMLElement} */ (document.querySelector("#tab-strip")));
|
|
269
|
+
|
|
256
270
|
overlaysPanel.mount({
|
|
257
271
|
getCanvasMode: () => canvasMode,
|
|
258
272
|
isEditing,
|
|
@@ -266,13 +280,14 @@ initBlockActionBar({
|
|
|
266
280
|
});
|
|
267
281
|
|
|
268
282
|
initComponentInlineEdit({ findCanvasElement });
|
|
269
|
-
initCanvasHelpers({ getCanvasMode: () => canvasMode });
|
|
283
|
+
initCanvasHelpers({ getCanvasMode: () => canvasMode, getZoom: () => S.ui.zoom });
|
|
270
284
|
initCanvasUtils({
|
|
271
285
|
getCanvasMode: () => canvasMode,
|
|
272
286
|
getZoom: () => S.ui.zoom,
|
|
273
287
|
setZoomDirect: (zoom) => {
|
|
274
288
|
session = { ...session, ui: { ...session.ui, zoom } };
|
|
275
289
|
S = toFlat(doc, session);
|
|
290
|
+
if (activeTab.value) activeTab.value.session.ui.zoom = zoom;
|
|
276
291
|
},
|
|
277
292
|
renderStylebookOverlays,
|
|
278
293
|
});
|
|
@@ -287,18 +302,49 @@ initPanelEvents({
|
|
|
287
302
|
navigateToComponent,
|
|
288
303
|
});
|
|
289
304
|
initCanvasLiveRender({
|
|
290
|
-
getState: () => S,
|
|
291
305
|
getCanvasMode: () => canvasMode,
|
|
292
306
|
});
|
|
293
307
|
initCanvasRender({
|
|
294
308
|
getCanvasMode: () => canvasMode,
|
|
295
|
-
setCanvasMode: (mode) => {
|
|
309
|
+
setCanvasMode: (/** @type {string} */ mode) => {
|
|
310
|
+
// Clear gitDiffState when exiting diff mode
|
|
311
|
+
if (canvasMode === "git-diff" && mode !== "git-diff") {
|
|
312
|
+
gitDiffState = null;
|
|
313
|
+
}
|
|
296
314
|
canvasMode = mode;
|
|
297
315
|
},
|
|
298
|
-
getState: () => S,
|
|
299
|
-
update,
|
|
300
316
|
openFileFromTree,
|
|
301
317
|
exportFile,
|
|
318
|
+
gitDiffState,
|
|
319
|
+
setGitDiffState: (/** @type {any} */ state) => {
|
|
320
|
+
gitDiffState = state;
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// Effect-driven canvas rendering: auto-triggers renderCanvas when reactive deps change
|
|
325
|
+
let _canvasRenderScheduled = false;
|
|
326
|
+
effect(() => {
|
|
327
|
+
const tab = activeTab.value;
|
|
328
|
+
if (!tab) return;
|
|
329
|
+
void tab.doc.document;
|
|
330
|
+
void tab.doc.mode;
|
|
331
|
+
void tab.session.ui.editingFunction;
|
|
332
|
+
void tab.session.ui.featureToggles;
|
|
333
|
+
void tab.session.ui.settingsTab;
|
|
334
|
+
void tab.session.ui.stylebookTab;
|
|
335
|
+
void tab.session.ui.stylebookFilter;
|
|
336
|
+
void tab.session.ui.stylebookCustomizedOnly;
|
|
337
|
+
if (!_canvasRenderScheduled) {
|
|
338
|
+
_canvasRenderScheduled = true;
|
|
339
|
+
queueMicrotask(() => {
|
|
340
|
+
_canvasRenderScheduled = false;
|
|
341
|
+
try {
|
|
342
|
+
renderCanvas();
|
|
343
|
+
} catch (e) {
|
|
344
|
+
console.error("renderCanvas error:", e);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
}
|
|
302
348
|
});
|
|
303
349
|
|
|
304
350
|
rightPanelMod.mount({
|
|
@@ -310,6 +356,9 @@ rightPanelMod.mount({
|
|
|
310
356
|
|
|
311
357
|
leftPanelMod.mount({
|
|
312
358
|
getCanvasMode: () => canvasMode,
|
|
359
|
+
setCanvasMode: (/** @type {string} */ mode) => {
|
|
360
|
+
canvasMode = mode;
|
|
361
|
+
},
|
|
313
362
|
renderImportsTemplate,
|
|
314
363
|
renderFilesTemplate,
|
|
315
364
|
renderSignalsTemplate,
|
|
@@ -329,27 +378,22 @@ leftPanelMod.mount({
|
|
|
329
378
|
});
|
|
330
379
|
|
|
331
380
|
// Register all renderers with the store so render()/renderOnly() work
|
|
332
|
-
|
|
333
|
-
registerRenderer("activityBar", () => renderActivityBar(S));
|
|
381
|
+
// Register remaining renderers for render()/renderOnly() compat during migration
|
|
334
382
|
registerRenderer("leftPanel", () => leftPanelMod.render());
|
|
335
383
|
registerRenderer("canvas", () => renderCanvas());
|
|
336
384
|
registerRenderer("rightPanel", () => rightPanelMod.render());
|
|
337
385
|
registerRenderer("overlays", () => overlaysPanel.render());
|
|
338
|
-
registerRenderer("statusbar", () => renderStatusbar(S));
|
|
339
386
|
setStatusbarRenderer(() => renderStatusbar(S));
|
|
340
387
|
mountStatusbar();
|
|
388
|
+
mountActivityBar();
|
|
341
389
|
|
|
342
390
|
// Clicking on the canvas-wrap background (outside any canvas panel) deselects the current element
|
|
343
391
|
canvasWrap.addEventListener("click", (/** @type {any} */ e) => {
|
|
344
392
|
if (e.target !== canvasWrap && e.target !== view.panzoomWrap) return;
|
|
345
393
|
if (!S.selection) return;
|
|
346
|
-
|
|
394
|
+
activeTab.value.session.selection = null;
|
|
347
395
|
});
|
|
348
396
|
|
|
349
|
-
function safeRenderLeftPanel() {
|
|
350
|
-
leftPanelMod.render();
|
|
351
|
-
}
|
|
352
|
-
|
|
353
397
|
function safeRenderRightPanel() {
|
|
354
398
|
rightPanelMod.render();
|
|
355
399
|
}
|
|
@@ -357,60 +401,42 @@ function safeRenderRightPanel() {
|
|
|
357
401
|
// Register the update implementation with the store
|
|
358
402
|
setGetStateFn(() => S);
|
|
359
403
|
setUpdateFn(function _update(/** @type {any} */ newState) {
|
|
360
|
-
const prev = S;
|
|
361
404
|
const prevDoc = S.document;
|
|
362
405
|
const prevSel = S.selection;
|
|
406
|
+
const prevUi = S.ui;
|
|
363
407
|
S = newState;
|
|
364
408
|
|
|
365
409
|
// Keep doc/session slices in sync with flat S
|
|
366
410
|
({ doc, session } = fromFlat(S));
|
|
367
411
|
|
|
368
|
-
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
const leftUiChanged =
|
|
382
|
-
uiChanged && (prev.ui?.leftTab !== S.ui?.leftTab || prev.ui?.settingsTab !== S.ui?.settingsTab);
|
|
383
|
-
|
|
384
|
-
if (docChanged || modeChanged || canvasUiChanged) {
|
|
385
|
-
try {
|
|
386
|
-
renderCanvas();
|
|
387
|
-
} catch (e) {
|
|
388
|
-
console.error("renderCanvas error:", e);
|
|
412
|
+
// Sync into reactive tab so effects fire
|
|
413
|
+
const tab = activeTab.value;
|
|
414
|
+
if (tab) {
|
|
415
|
+
tab.doc.document = S.document;
|
|
416
|
+
tab.doc.dirty = S.dirty;
|
|
417
|
+
tab.doc.mode = S.mode;
|
|
418
|
+
tab.doc.handlersSource = S.handlersSource;
|
|
419
|
+
tab.doc.content.frontmatter = S.content?.frontmatter ?? {};
|
|
420
|
+
tab.session.selection = S.selection;
|
|
421
|
+
tab.session.hover = S.hover;
|
|
422
|
+
tab.session.clipboard = S.clipboard ?? null;
|
|
423
|
+
for (const [k, v] of Object.entries(S.ui || {})) {
|
|
424
|
+
/** @type {any} */ (tab.session.ui)[k] = v;
|
|
389
425
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
426
|
+
tab.session.canvas.status = S.canvas?.status ?? "idle";
|
|
427
|
+
tab.session.canvas.scope = S.canvas?.scope ?? null;
|
|
428
|
+
tab.session.canvas.error = S.canvas?.error ?? null;
|
|
393
429
|
}
|
|
394
430
|
|
|
395
|
-
if (
|
|
431
|
+
if (prevUi?.activeMedia !== S.ui?.activeMedia) {
|
|
396
432
|
updateActivePanelHeaders();
|
|
397
433
|
}
|
|
398
434
|
|
|
399
435
|
runPostRenderHooks(prevDoc, prevSel);
|
|
400
436
|
runUpdateMiddleware(S);
|
|
401
|
-
|
|
402
|
-
notify({
|
|
403
|
-
doc: docChanged,
|
|
404
|
-
selection: selChanged,
|
|
405
|
-
hover: false,
|
|
406
|
-
ui: uiChanged,
|
|
407
|
-
mode: modeChanged,
|
|
408
|
-
});
|
|
409
437
|
});
|
|
410
438
|
|
|
411
439
|
// Register session dispatch — lightweight path for selection/hover/ui changes
|
|
412
|
-
setGetDocFn(() => doc);
|
|
413
|
-
setGetSessionFn(() => session);
|
|
414
440
|
setUpdateSessionFn(function _updateSession(/** @type {any} */ patch) {
|
|
415
441
|
const prev = session;
|
|
416
442
|
session = { ...session, ...patch };
|
|
@@ -422,38 +448,12 @@ setUpdateSessionFn(function _updateSession(/** @type {any} */ patch) {
|
|
|
422
448
|
}
|
|
423
449
|
S = toFlat(doc, session);
|
|
424
450
|
|
|
425
|
-
|
|
426
|
-
const uiChanged = prev.ui !== session.ui;
|
|
427
|
-
const canvasChanged = prev.canvas !== session.canvas;
|
|
428
|
-
|
|
429
|
-
const canvasUiChanged =
|
|
430
|
-
uiChanged &&
|
|
431
|
-
(prev.ui?.editingFunction !== session.ui?.editingFunction ||
|
|
432
|
-
prev.ui?.settingsTab !== session.ui?.settingsTab ||
|
|
433
|
-
prev.ui?.stylebookTab !== session.ui?.stylebookTab ||
|
|
434
|
-
prev.ui?.stylebookFilter !== session.ui?.stylebookFilter ||
|
|
435
|
-
prev.ui?.stylebookCustomizedOnly !== session.ui?.stylebookCustomizedOnly ||
|
|
436
|
-
prev.ui?.featureToggles !== session.ui?.featureToggles);
|
|
437
|
-
const leftUiChanged =
|
|
438
|
-
uiChanged &&
|
|
439
|
-
(prev.ui?.leftTab !== session.ui?.leftTab || prev.ui?.settingsTab !== session.ui?.settingsTab);
|
|
440
|
-
|
|
441
|
-
if (canvasUiChanged) {
|
|
442
|
-
try {
|
|
443
|
-
renderCanvas();
|
|
444
|
-
} catch (e) {
|
|
445
|
-
console.error("renderCanvas error:", e);
|
|
446
|
-
}
|
|
447
|
-
safeRenderLeftPanel();
|
|
448
|
-
} else if (selChanged || leftUiChanged || canvasChanged) {
|
|
449
|
-
safeRenderLeftPanel();
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
if (uiChanged && prev.ui?.activeMedia !== session.ui?.activeMedia) {
|
|
451
|
+
if (prev.ui?.activeMedia !== session.ui?.activeMedia) {
|
|
453
452
|
updateActivePanelHeaders();
|
|
454
453
|
}
|
|
455
454
|
|
|
456
455
|
// Process pending inline edit when canvas becomes ready
|
|
456
|
+
const canvasChanged = prev.canvas !== session.canvas;
|
|
457
457
|
if (canvasChanged && session.canvas.status === "ready" && session.ui?.pendingInlineEdit) {
|
|
458
458
|
const { path, mediaName: mn } = session.ui.pendingInlineEdit;
|
|
459
459
|
updateUi("pendingInlineEdit", null);
|
|
@@ -466,9 +466,6 @@ setUpdateSessionFn(function _updateSession(/** @type {any} */ patch) {
|
|
|
466
466
|
}
|
|
467
467
|
|
|
468
468
|
runPostRenderHooks(doc.document, prev.selection);
|
|
469
|
-
|
|
470
|
-
const hoverChanged = prev.hover !== session.hover;
|
|
471
|
-
notify({ doc: false, selection: selChanged, hover: hoverChanged, ui: uiChanged, mode: false });
|
|
472
469
|
});
|
|
473
470
|
|
|
474
471
|
// Register post-render hook for pseudo-state preview
|
|
@@ -547,17 +544,32 @@ if (_openParam) {
|
|
|
547
544
|
const fileRelPath = _fileParam || siteCtx.fileRelPath || _openParam;
|
|
548
545
|
const content = await platform.readFile(fileRelPath);
|
|
549
546
|
if (content) {
|
|
547
|
+
let parsedDoc;
|
|
550
548
|
if (fileRelPath.endsWith(".md")) {
|
|
551
|
-
await
|
|
549
|
+
const ns = await _loadMarkdown(content, null);
|
|
550
|
+
S = ns;
|
|
552
551
|
S.documentPath = fileRelPath;
|
|
552
|
+
parsedDoc = S.document;
|
|
553
553
|
} else {
|
|
554
|
-
|
|
555
|
-
S = createState(
|
|
554
|
+
parsedDoc = JSON.parse(content);
|
|
555
|
+
S = createState(parsedDoc);
|
|
556
556
|
S.documentPath = fileRelPath;
|
|
557
557
|
}
|
|
558
558
|
S.dirty = false;
|
|
559
559
|
S.ui = { ...S.ui, leftTab: "files" };
|
|
560
560
|
({ doc, session } = fromFlat(S));
|
|
561
|
+
|
|
562
|
+
// Sync into reactive tab
|
|
563
|
+
const tab = activeTab.value;
|
|
564
|
+
if (tab) {
|
|
565
|
+
tab.doc.document = S.document;
|
|
566
|
+
tab.doc.mode = S.mode;
|
|
567
|
+
tab.doc.dirty = false;
|
|
568
|
+
tab.doc.content = S.content || { frontmatter: {} };
|
|
569
|
+
/** @type {any} */ (tab).documentPath = fileRelPath;
|
|
570
|
+
tab.session.ui.leftTab = "files";
|
|
571
|
+
}
|
|
572
|
+
|
|
561
573
|
render();
|
|
562
574
|
statusMessage(`Opened ${_openParam}`);
|
|
563
575
|
}
|
|
@@ -619,11 +631,6 @@ function fileOpsCtx() {
|
|
|
619
631
|
function openFile() {
|
|
620
632
|
return _openFile(fileOpsCtx());
|
|
621
633
|
}
|
|
622
|
-
async function loadMarkdown(/** @type {any} */ source, /** @type {any} */ fileHandle) {
|
|
623
|
-
const ns = await _loadMarkdown(source, fileHandle);
|
|
624
|
-
S = ns;
|
|
625
|
-
({ doc, session } = fromFlat(S));
|
|
626
|
-
}
|
|
627
634
|
function saveFile() {
|
|
628
635
|
return _saveFile(fileOpsCtx());
|
|
629
636
|
}
|
|
@@ -643,7 +650,7 @@ function openProject() {
|
|
|
643
650
|
S = ns;
|
|
644
651
|
({ doc, session } = fromFlat(S));
|
|
645
652
|
},
|
|
646
|
-
renderActivityBar: () => renderActivityBar(
|
|
653
|
+
renderActivityBar: () => renderActivityBar(),
|
|
647
654
|
renderLeftPanel,
|
|
648
655
|
});
|
|
649
656
|
}
|
|
@@ -651,23 +658,7 @@ function renderFilesTemplate() {
|
|
|
651
658
|
return _renderFilesTemplate({ openProject, openFileFromTree, renderLeftPanel });
|
|
652
659
|
}
|
|
653
660
|
function openFileFromTree(/** @type {any} */ path) {
|
|
654
|
-
return
|
|
655
|
-
{
|
|
656
|
-
get S() {
|
|
657
|
-
return S;
|
|
658
|
-
},
|
|
659
|
-
set S(v) {
|
|
660
|
-
S = v;
|
|
661
|
-
},
|
|
662
|
-
commit: (/** @type {any} */ ns) => {
|
|
663
|
-
S = ns;
|
|
664
|
-
({ doc, session } = fromFlat(S));
|
|
665
|
-
},
|
|
666
|
-
render,
|
|
667
|
-
loadMarkdown,
|
|
668
|
-
},
|
|
669
|
-
path,
|
|
670
|
-
);
|
|
661
|
+
return openFileInTab(path);
|
|
671
662
|
}
|
|
672
663
|
|
|
673
664
|
// ─── Keyboard shortcuts ───────────────────────────────────────────────────────
|
|
@@ -717,7 +708,7 @@ function scheduleAutosave() {
|
|
|
717
708
|
const writable = await S.fileHandle.createWritable();
|
|
718
709
|
await writable.write(JSON.stringify(S.document, null, 2));
|
|
719
710
|
await writable.close();
|
|
720
|
-
|
|
711
|
+
activeTab.value.doc.dirty = false;
|
|
721
712
|
statusMessage("Auto-saved");
|
|
722
713
|
} catch {}
|
|
723
714
|
}
|
package/src/tabs/tab.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { reactive, effectScope } from "../reactivity.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {{
|
|
5
|
+
* leftTab: string;
|
|
6
|
+
* rightTab: string;
|
|
7
|
+
* zoom: number;
|
|
8
|
+
* activeMedia: string | null;
|
|
9
|
+
* activeSelector: string | null;
|
|
10
|
+
* editingFunction: object | null;
|
|
11
|
+
* featureToggles: Record<string, boolean>;
|
|
12
|
+
* styleSections: Record<string, boolean>;
|
|
13
|
+
* inspectorSections: Record<string, boolean>;
|
|
14
|
+
* styleShorthands: Record<string, boolean>;
|
|
15
|
+
* styleFilter: string;
|
|
16
|
+
* styleFilterActive: boolean;
|
|
17
|
+
* stylebookSelection: string | null;
|
|
18
|
+
* stylebookTab: string;
|
|
19
|
+
* stylebookFilter: string;
|
|
20
|
+
* stylebookCustomizedOnly: boolean;
|
|
21
|
+
* settingsTab: string;
|
|
22
|
+
* gitStatus: object | null;
|
|
23
|
+
* gitBranches: object | null;
|
|
24
|
+
* gitCommitMessage: string;
|
|
25
|
+
* gitLoading: boolean;
|
|
26
|
+
* gitError: string | null;
|
|
27
|
+
* pendingInlineEdit: object | null;
|
|
28
|
+
* }} TabUi
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {{
|
|
33
|
+
* document: Record<string, any>;
|
|
34
|
+
* selection: (string | number)[] | null;
|
|
35
|
+
* }} HistorySnapshot
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @typedef {{
|
|
40
|
+
* id: string;
|
|
41
|
+
* documentPath: string | null;
|
|
42
|
+
* fileHandle: FileSystemFileHandle | null;
|
|
43
|
+
* scope: { stop(): void; run<T>(fn: () => T): T | undefined; [k: string]: any };
|
|
44
|
+
* doc: {
|
|
45
|
+
* document: Record<string, any>;
|
|
46
|
+
* content: { frontmatter: Record<string, unknown> };
|
|
47
|
+
* mode: string;
|
|
48
|
+
* handlersSource: string | null;
|
|
49
|
+
* dirty: boolean;
|
|
50
|
+
* };
|
|
51
|
+
* session: {
|
|
52
|
+
* selection: (string | number)[] | null;
|
|
53
|
+
* hover: (string | number)[] | null;
|
|
54
|
+
* clipboard: object | null;
|
|
55
|
+
* documentStack: object[];
|
|
56
|
+
* ui: TabUi;
|
|
57
|
+
* canvas: {
|
|
58
|
+
* status: string;
|
|
59
|
+
* scope: { stop(): void; [k: string]: any } | null;
|
|
60
|
+
* error: string | null;
|
|
61
|
+
* pendingInlineEdit: object | null;
|
|
62
|
+
* };
|
|
63
|
+
* };
|
|
64
|
+
* history: {
|
|
65
|
+
* snapshots: HistorySnapshot[];
|
|
66
|
+
* index: number;
|
|
67
|
+
* };
|
|
68
|
+
* }} Tab
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
/** @returns {TabUi} */
|
|
72
|
+
function createDefaultUi() {
|
|
73
|
+
return {
|
|
74
|
+
leftTab: "layers",
|
|
75
|
+
rightTab: "properties",
|
|
76
|
+
zoom: 1,
|
|
77
|
+
activeMedia: null,
|
|
78
|
+
activeSelector: null,
|
|
79
|
+
editingFunction: null,
|
|
80
|
+
featureToggles: {},
|
|
81
|
+
styleSections: {},
|
|
82
|
+
inspectorSections: {},
|
|
83
|
+
styleShorthands: {},
|
|
84
|
+
styleFilter: "",
|
|
85
|
+
styleFilterActive: false,
|
|
86
|
+
stylebookSelection: null,
|
|
87
|
+
stylebookTab: "elements",
|
|
88
|
+
stylebookFilter: "",
|
|
89
|
+
stylebookCustomizedOnly: false,
|
|
90
|
+
settingsTab: "stylebook",
|
|
91
|
+
gitStatus: null,
|
|
92
|
+
gitBranches: null,
|
|
93
|
+
gitCommitMessage: "",
|
|
94
|
+
gitLoading: false,
|
|
95
|
+
gitError: null,
|
|
96
|
+
pendingInlineEdit: null,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Create a new tab with reactive doc/session/history trees, owned by an effectScope.
|
|
102
|
+
*
|
|
103
|
+
* @param {{
|
|
104
|
+
* id: string;
|
|
105
|
+
* documentPath?: string | null;
|
|
106
|
+
* fileHandle?: FileSystemFileHandle | null;
|
|
107
|
+
* document: Record<string, any>;
|
|
108
|
+
* frontmatter?: Record<string, unknown>;
|
|
109
|
+
* }} opts
|
|
110
|
+
* @returns {Tab}
|
|
111
|
+
*/
|
|
112
|
+
export function createTab({ id, documentPath = null, fileHandle = null, document, frontmatter }) {
|
|
113
|
+
const scope = effectScope();
|
|
114
|
+
|
|
115
|
+
const tab = /** @type {Tab} */ (
|
|
116
|
+
scope.run(() => ({
|
|
117
|
+
id,
|
|
118
|
+
documentPath,
|
|
119
|
+
fileHandle,
|
|
120
|
+
scope,
|
|
121
|
+
doc: reactive({
|
|
122
|
+
document,
|
|
123
|
+
content: { frontmatter: frontmatter || {} },
|
|
124
|
+
mode: documentPath?.endsWith(".md") ? "content" : "component",
|
|
125
|
+
handlersSource: null,
|
|
126
|
+
dirty: false,
|
|
127
|
+
}),
|
|
128
|
+
session: reactive({
|
|
129
|
+
selection: null,
|
|
130
|
+
hover: null,
|
|
131
|
+
clipboard: null,
|
|
132
|
+
documentStack: [],
|
|
133
|
+
ui: createDefaultUi(),
|
|
134
|
+
canvas: { status: "idle", scope: null, error: null, pendingInlineEdit: null },
|
|
135
|
+
}),
|
|
136
|
+
history: reactive({
|
|
137
|
+
snapshots: [{ document: structuredClone(document), selection: null }],
|
|
138
|
+
index: 0,
|
|
139
|
+
}),
|
|
140
|
+
}))
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
return tab;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Dispose a tab — stops its effectScope, killing all effects created within it.
|
|
148
|
+
*
|
|
149
|
+
* @param {Tab} tab
|
|
150
|
+
*/
|
|
151
|
+
export function disposeTab(tab) {
|
|
152
|
+
tab.scope.stop();
|
|
153
|
+
}
|