@jxsuite/studio 0.11.0 → 0.14.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/dist/studio.js +6517 -5870
- package/dist/studio.js.map +57 -51
- package/package.json +5 -3
- package/src/canvas/canvas-diff.js +184 -0
- package/src/canvas/canvas-helpers.js +10 -14
- package/src/canvas/canvas-live-render.js +28 -2
- package/src/canvas/canvas-render.js +170 -20
- package/src/canvas/canvas-utils.js +22 -25
- package/src/editor/component-inline-edit.js +55 -44
- package/src/editor/content-inline-edit.js +47 -50
- package/src/editor/context-menu.js +78 -53
- package/src/editor/convert-to-component.js +11 -14
- package/src/editor/insertion-helper.js +8 -11
- package/src/editor/shortcuts.js +88 -64
- package/src/files/components.js +15 -4
- package/src/files/file-ops.js +57 -108
- package/src/files/files.js +81 -28
- package/src/panels/activity-bar.js +31 -7
- package/src/panels/block-action-bar.js +104 -80
- package/src/panels/canvas-dnd.js +4 -10
- package/src/panels/dnd.js +77 -35
- package/src/panels/editors.js +17 -26
- package/src/panels/elements-panel.js +17 -11
- package/src/panels/events-panel.js +44 -39
- package/src/panels/git-panel.js +47 -4
- package/src/panels/layers-panel.js +25 -21
- package/src/panels/left-panel.js +109 -44
- package/src/panels/overlays.js +91 -41
- package/src/panels/panel-events.js +28 -37
- package/src/panels/preview-render.js +7 -3
- package/src/panels/properties-panel.js +179 -104
- package/src/panels/pseudo-preview.js +9 -8
- 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 +26 -19
- package/src/panels/style-inputs.js +5 -4
- package/src/panels/style-panel.js +128 -105
- package/src/panels/style-utils.js +8 -6
- package/src/panels/stylebook-layers-panel.js +5 -5
- package/src/panels/stylebook-panel.js +27 -31
- package/src/panels/tab-strip.js +124 -0
- package/src/panels/toolbar.js +40 -10
- package/src/reactivity.js +28 -0
- package/src/settings/content-types-editor.js +56 -7
- package/src/settings/defs-editor.js +56 -7
- package/src/settings/schema-field-ui.js +60 -25
- package/src/state.js +2 -459
- package/src/store.js +61 -219
- package/src/studio.js +163 -300
- package/src/tabs/tab.js +168 -0
- package/src/tabs/transact.js +406 -0
- package/src/ui/color-selector.js +4 -5
- package/src/view.js +3 -0
- package/src/workspace/workspace.js +90 -0
package/src/studio.js
CHANGED
|
@@ -6,51 +6,26 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
-
createState,
|
|
10
|
-
selectNode,
|
|
11
|
-
updateProperty,
|
|
12
|
-
updateDef,
|
|
13
|
-
pushDocument,
|
|
14
|
-
popDocument,
|
|
15
9
|
getNodeAtPath,
|
|
16
|
-
pathsEqual,
|
|
17
10
|
canvasWrap,
|
|
18
11
|
toolbarEl,
|
|
19
|
-
canvasPanels,
|
|
20
12
|
registerRenderer,
|
|
21
13
|
render,
|
|
22
|
-
update,
|
|
23
|
-
setUpdateFn,
|
|
24
|
-
setGetStateFn,
|
|
25
|
-
addUpdateMiddleware,
|
|
26
|
-
runUpdateMiddleware,
|
|
27
|
-
addPostRenderHook,
|
|
28
|
-
runPostRenderHooks,
|
|
29
|
-
notify,
|
|
30
14
|
projectState,
|
|
31
15
|
setProjectState,
|
|
32
16
|
updateUi,
|
|
33
|
-
setUpdateSessionFn,
|
|
34
|
-
setGetDocFn,
|
|
35
|
-
setGetSessionFn,
|
|
36
|
-
toFlat,
|
|
37
|
-
fromFlat,
|
|
38
17
|
} from "./store.js";
|
|
39
18
|
|
|
19
|
+
import { activeTab, openTab } from "./workspace/workspace.js";
|
|
20
|
+
import { transactDoc, mutateUpdateDef, mutateUpdateProperty } from "./tabs/transact.js";
|
|
21
|
+
import { effect } from "./reactivity.js";
|
|
22
|
+
|
|
40
23
|
import { view } from "./view.js";
|
|
41
24
|
|
|
42
25
|
import { isEditing, isEditableBlock } from "./editor/inline-edit.js";
|
|
43
|
-
import {
|
|
44
|
-
enterComponentInlineEdit,
|
|
45
|
-
initComponentInlineEdit,
|
|
46
|
-
} from "./editor/component-inline-edit.js";
|
|
26
|
+
import { initComponentInlineEdit } from "./editor/component-inline-edit.js";
|
|
47
27
|
import { enterInlineEdit } from "./editor/content-inline-edit.js";
|
|
48
|
-
import {
|
|
49
|
-
initCanvasUtils,
|
|
50
|
-
applyTransform,
|
|
51
|
-
positionZoomIndicator,
|
|
52
|
-
updateActivePanelHeaders,
|
|
53
|
-
} from "./canvas/canvas-utils.js";
|
|
28
|
+
import { initCanvasUtils, applyTransform, positionZoomIndicator } from "./canvas/canvas-utils.js";
|
|
54
29
|
import { initCanvasHelpers, getActivePanel, findCanvasElement } from "./canvas/canvas-helpers.js";
|
|
55
30
|
import { initCanvasRender, renderCanvas } from "./canvas/canvas-render.js";
|
|
56
31
|
import { initCanvasLiveRender } from "./canvas/canvas-live-render.js";
|
|
@@ -60,20 +35,14 @@ import {
|
|
|
60
35
|
setStatusbarRenderer,
|
|
61
36
|
mountStatusbar,
|
|
62
37
|
} from "./panels/statusbar.js";
|
|
63
|
-
import {
|
|
64
|
-
openFile as _openFile,
|
|
65
|
-
loadMarkdown as _loadMarkdown,
|
|
66
|
-
saveFile as _saveFile,
|
|
67
|
-
exportFile as _exportFile,
|
|
68
|
-
} from "./files/file-ops.js";
|
|
38
|
+
import { openFile, loadMarkdown, saveFile, exportFile } from "./files/file-ops.js";
|
|
69
39
|
import {
|
|
70
40
|
loadProject as _loadProject,
|
|
71
41
|
openProject as _openProject,
|
|
72
42
|
renderFilesTemplate as _renderFilesTemplate,
|
|
73
|
-
|
|
43
|
+
openFileInTab,
|
|
74
44
|
setupTreeKeyboard,
|
|
75
45
|
} from "./files/files.js";
|
|
76
|
-
import { eventsSidebarTemplate as _eventsSidebarTemplate } from "./panels/events-panel.js";
|
|
77
46
|
import { renderImportsTemplate } from "./panels/imports-panel.js";
|
|
78
47
|
import { renderHeadTemplate } from "./panels/head-panel.js";
|
|
79
48
|
import { exportCemManifest as _exportCemManifest } from "./services/cem-export.js";
|
|
@@ -97,11 +66,12 @@ import { renderGitPanel } from "./panels/git-panel.js";
|
|
|
97
66
|
import { components as _swc } from "./ui/spectrum.js"; // eslint-disable-line no-unused-vars
|
|
98
67
|
import "./ui/panel-resize.js";
|
|
99
68
|
import { initShortcuts } from "./editor/shortcuts.js";
|
|
100
|
-
import { renderActivityBar } from "./panels/activity-bar.js";
|
|
69
|
+
import { renderActivityBar, mount as mountActivityBar } from "./panels/activity-bar.js";
|
|
101
70
|
import * as toolbarPanel from "./panels/toolbar.js";
|
|
102
71
|
import * as overlaysPanel from "./panels/overlays.js";
|
|
103
72
|
import * as rightPanelMod from "./panels/right-panel.js";
|
|
104
73
|
import * as leftPanelMod from "./panels/left-panel.js";
|
|
74
|
+
import * as tabStrip from "./panels/tab-strip.js";
|
|
105
75
|
import { renderStylebookOverlays } from "./panels/stylebook-panel.js";
|
|
106
76
|
import { registerLayersDnD, registerComponentsDnD, registerElementsDnD } from "./panels/dnd.js";
|
|
107
77
|
import { defaultDef } from "./panels/shared.js";
|
|
@@ -115,13 +85,6 @@ import { initPanelEvents } from "./panels/panel-events.js";
|
|
|
115
85
|
// These mutable variables are local to studio.js for now. As sections are extracted
|
|
116
86
|
// into their own modules, they will migrate to ctx in store.js.
|
|
117
87
|
|
|
118
|
-
/** @type {any} */
|
|
119
|
-
let S; // current state (flat compatibility view)
|
|
120
|
-
/** @type {any} */
|
|
121
|
-
let doc = null; // doc slice (persisted, history, autosave)
|
|
122
|
-
/** @type {any} */
|
|
123
|
-
let session = null; // session slice (selection, hover, ui)
|
|
124
|
-
|
|
125
88
|
/** Creates a display:contents container appended to sp-theme or body, for floating popovers/menus. */
|
|
126
89
|
function createFloatingContainer() {
|
|
127
90
|
const el = document.createElement("div");
|
|
@@ -130,7 +93,21 @@ function createFloatingContainer() {
|
|
|
130
93
|
return el;
|
|
131
94
|
}
|
|
132
95
|
|
|
133
|
-
|
|
96
|
+
function getCanvasMode() {
|
|
97
|
+
return activeTab.value?.session.ui.canvasMode ?? "design";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** @param {string} mode */
|
|
101
|
+
function setCanvasMode(mode) {
|
|
102
|
+
if (getCanvasMode() === "git-diff" && mode !== "git-diff") {
|
|
103
|
+
gitDiffState = null;
|
|
104
|
+
}
|
|
105
|
+
const tab = activeTab.value;
|
|
106
|
+
if (tab) tab.session.ui.canvasMode = mode;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** @type {any} */
|
|
110
|
+
let gitDiffState = null;
|
|
134
111
|
|
|
135
112
|
// ─── Component registry ───────────────────────────────────────────────────────
|
|
136
113
|
|
|
@@ -141,9 +118,30 @@ async function navigateToComponent(componentPath) {
|
|
|
141
118
|
const content = await platform.readFile(componentPath);
|
|
142
119
|
if (!content) return;
|
|
143
120
|
const parsed = JSON.parse(content);
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
121
|
+
const tab = activeTab.value;
|
|
122
|
+
if (!tab) return;
|
|
123
|
+
|
|
124
|
+
// Push current state onto the document stack
|
|
125
|
+
const frame = {
|
|
126
|
+
document: tab.doc.document,
|
|
127
|
+
selection: tab.session.selection,
|
|
128
|
+
documentPath: tab.documentPath,
|
|
129
|
+
dirty: tab.doc.dirty,
|
|
130
|
+
mode: tab.doc.mode,
|
|
131
|
+
};
|
|
132
|
+
if (!tab.session.documentStack) tab.session.documentStack = [];
|
|
133
|
+
tab.session.documentStack.push(frame);
|
|
134
|
+
|
|
135
|
+
// Load the component
|
|
136
|
+
tab.doc.document = parsed;
|
|
137
|
+
tab.doc.dirty = false;
|
|
138
|
+
tab.doc.mode = /** @type {any} */ (null);
|
|
139
|
+
tab.documentPath = componentPath;
|
|
140
|
+
tab.session.selection = null;
|
|
141
|
+
view.leftTab = "layers";
|
|
142
|
+
tab.session.ui.activeMedia = null;
|
|
143
|
+
tab.session.ui.activeSelector = null;
|
|
144
|
+
|
|
147
145
|
render();
|
|
148
146
|
statusMessage(`Editing component: ${parsed.tagName || componentPath}`);
|
|
149
147
|
} catch (/** @type {any} */ e) {
|
|
@@ -153,36 +151,47 @@ async function navigateToComponent(componentPath) {
|
|
|
153
151
|
}
|
|
154
152
|
|
|
155
153
|
async function navigateBack() {
|
|
156
|
-
|
|
157
|
-
if (
|
|
154
|
+
const tab = activeTab.value;
|
|
155
|
+
if (!tab?.session.documentStack || tab.session.documentStack.length === 0) return;
|
|
156
|
+
if (tab.doc.dirty && tab.documentPath) {
|
|
158
157
|
try {
|
|
159
158
|
const platform = getPlatform();
|
|
160
|
-
await platform.writeFile(
|
|
159
|
+
await platform.writeFile(tab.documentPath, JSON.stringify(tab.doc.document, null, 2));
|
|
161
160
|
} catch (/** @type {any} */ e) {
|
|
162
161
|
const err = /** @type {any} */ (e);
|
|
163
162
|
statusMessage(`Save error: ${err.message}`);
|
|
164
163
|
}
|
|
165
164
|
}
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
|
|
166
|
+
// Pop the stack
|
|
167
|
+
const frame = /** @type {any} */ (tab.session.documentStack.pop());
|
|
168
|
+
if (!frame) return;
|
|
169
|
+
tab.doc.document = frame.document;
|
|
170
|
+
tab.doc.dirty = frame.dirty;
|
|
171
|
+
tab.doc.mode = frame.mode;
|
|
172
|
+
tab.documentPath = frame.documentPath;
|
|
173
|
+
tab.session.selection = frame.selection;
|
|
174
|
+
view.leftTab = "layers";
|
|
175
|
+
|
|
168
176
|
render();
|
|
169
177
|
statusMessage("Returned to parent document");
|
|
170
178
|
}
|
|
171
179
|
|
|
172
180
|
async function closeFunctionEditor() {
|
|
173
|
-
const
|
|
181
|
+
const tab = activeTab.value;
|
|
182
|
+
const editing = /** @type {any} */ (tab?.session.ui.editingFunction);
|
|
174
183
|
if (!editing) return;
|
|
175
184
|
if (view.functionEditor) {
|
|
176
185
|
const currentCode = view.functionEditor.getValue();
|
|
177
186
|
const minResult = await codeService("minify", { code: currentCode });
|
|
178
187
|
const bodyToStore = minResult?.code ?? currentCode;
|
|
179
188
|
if (editing.type === "def") {
|
|
180
|
-
|
|
189
|
+
transactDoc(tab, (t) => mutateUpdateDef(t, editing.defName, { body: bodyToStore }));
|
|
181
190
|
} else if (editing.type === "event") {
|
|
182
|
-
const node = getNodeAtPath(
|
|
191
|
+
const node = getNodeAtPath(tab.doc.document, editing.path);
|
|
183
192
|
const current = node?.[editing.eventKey] || {};
|
|
184
|
-
|
|
185
|
-
|
|
193
|
+
transactDoc(tab, (t) =>
|
|
194
|
+
mutateUpdateProperty(t, editing.path, editing.eventKey, {
|
|
186
195
|
...current,
|
|
187
196
|
$prototype: "Function",
|
|
188
197
|
body: bodyToStore,
|
|
@@ -232,8 +241,8 @@ const EMPTY_DOC = {
|
|
|
232
241
|
],
|
|
233
242
|
};
|
|
234
243
|
|
|
235
|
-
|
|
236
|
-
({
|
|
244
|
+
// Create the initial reactive tab — the canonical state container.
|
|
245
|
+
openTab({ id: "initial", document: structuredClone(EMPTY_DOC) });
|
|
237
246
|
|
|
238
247
|
// ─── Render loop ──────────────────────────────────────────────────────────────
|
|
239
248
|
|
|
@@ -245,71 +254,95 @@ toolbarPanel.mount(toolbarEl, {
|
|
|
245
254
|
openFile: () => openFile(),
|
|
246
255
|
saveFile: () => saveFile(),
|
|
247
256
|
parseMediaEntries,
|
|
248
|
-
getCanvasMode
|
|
249
|
-
setCanvasMode
|
|
250
|
-
canvasMode = m;
|
|
251
|
-
},
|
|
257
|
+
getCanvasMode,
|
|
258
|
+
setCanvasMode,
|
|
252
259
|
renderCanvas: () => renderCanvas(),
|
|
253
260
|
safeRenderRightPanel: () => safeRenderRightPanel(),
|
|
254
261
|
});
|
|
255
262
|
|
|
263
|
+
tabStrip.mount(/** @type {HTMLElement} */ (document.querySelector("#tab-strip")));
|
|
264
|
+
|
|
256
265
|
overlaysPanel.mount({
|
|
257
|
-
getCanvasMode
|
|
266
|
+
getCanvasMode,
|
|
258
267
|
isEditing,
|
|
259
268
|
renderBlockActionBar,
|
|
260
269
|
});
|
|
261
270
|
|
|
262
271
|
initBlockActionBar({
|
|
263
|
-
getCanvasMode
|
|
272
|
+
getCanvasMode,
|
|
264
273
|
navigateToComponent,
|
|
265
274
|
createFloatingContainer,
|
|
266
275
|
});
|
|
267
276
|
|
|
268
277
|
initComponentInlineEdit({ findCanvasElement });
|
|
269
|
-
initCanvasHelpers({
|
|
278
|
+
initCanvasHelpers({
|
|
279
|
+
getCanvasMode,
|
|
280
|
+
getZoom: () => activeTab.value?.session.ui.zoom ?? 1,
|
|
281
|
+
});
|
|
270
282
|
initCanvasUtils({
|
|
271
|
-
getCanvasMode
|
|
272
|
-
getZoom: () =>
|
|
283
|
+
getCanvasMode,
|
|
284
|
+
getZoom: () => activeTab.value?.session.ui.zoom ?? 1,
|
|
273
285
|
setZoomDirect: (zoom) => {
|
|
274
|
-
|
|
275
|
-
S = toFlat(doc, session);
|
|
286
|
+
if (activeTab.value) activeTab.value.session.ui.zoom = zoom;
|
|
276
287
|
},
|
|
277
288
|
renderStylebookOverlays,
|
|
278
289
|
});
|
|
279
290
|
initPanelEvents({
|
|
280
|
-
|
|
281
|
-
setState: (s) => {
|
|
282
|
-
S = s;
|
|
283
|
-
({ doc, session } = fromFlat(S));
|
|
284
|
-
},
|
|
285
|
-
getCanvasMode: () => canvasMode,
|
|
291
|
+
getCanvasMode,
|
|
286
292
|
enterInlineEdit,
|
|
287
293
|
navigateToComponent,
|
|
288
294
|
});
|
|
289
295
|
initCanvasLiveRender({
|
|
290
|
-
|
|
291
|
-
getCanvasMode: () => canvasMode,
|
|
296
|
+
getCanvasMode,
|
|
292
297
|
});
|
|
293
298
|
initCanvasRender({
|
|
294
|
-
getCanvasMode
|
|
295
|
-
setCanvasMode
|
|
296
|
-
canvasMode = mode;
|
|
297
|
-
},
|
|
298
|
-
getState: () => S,
|
|
299
|
-
update,
|
|
299
|
+
getCanvasMode,
|
|
300
|
+
setCanvasMode,
|
|
300
301
|
openFileFromTree,
|
|
301
302
|
exportFile,
|
|
303
|
+
gitDiffState,
|
|
304
|
+
setGitDiffState: (/** @type {any} */ state) => {
|
|
305
|
+
gitDiffState = state;
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// Effect-driven canvas rendering: auto-triggers renderCanvas when reactive deps change
|
|
310
|
+
let _canvasRenderScheduled = false;
|
|
311
|
+
effect(() => {
|
|
312
|
+
const tab = activeTab.value;
|
|
313
|
+
if (!tab) return;
|
|
314
|
+
void tab.doc.document;
|
|
315
|
+
void tab.doc.mode;
|
|
316
|
+
void tab.session.ui.canvasMode;
|
|
317
|
+
void tab.session.ui.editingFunction;
|
|
318
|
+
void tab.session.ui.featureToggles;
|
|
319
|
+
void tab.session.ui.settingsTab;
|
|
320
|
+
void tab.session.ui.stylebookTab;
|
|
321
|
+
void tab.session.ui.stylebookFilter;
|
|
322
|
+
void tab.session.ui.stylebookCustomizedOnly;
|
|
323
|
+
if (!_canvasRenderScheduled) {
|
|
324
|
+
_canvasRenderScheduled = true;
|
|
325
|
+
queueMicrotask(() => {
|
|
326
|
+
_canvasRenderScheduled = false;
|
|
327
|
+
try {
|
|
328
|
+
renderCanvas();
|
|
329
|
+
} catch (e) {
|
|
330
|
+
console.error("renderCanvas error:", e);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
}
|
|
302
334
|
});
|
|
303
335
|
|
|
304
336
|
rightPanelMod.mount({
|
|
305
337
|
navigateToComponent,
|
|
306
|
-
getCanvasMode
|
|
338
|
+
getCanvasMode,
|
|
307
339
|
renderCanvas: () => renderCanvas(),
|
|
308
340
|
updateForcedPseudoPreview,
|
|
309
341
|
});
|
|
310
342
|
|
|
311
343
|
leftPanelMod.mount({
|
|
312
|
-
getCanvasMode
|
|
344
|
+
getCanvasMode,
|
|
345
|
+
setCanvasMode,
|
|
313
346
|
renderImportsTemplate,
|
|
314
347
|
renderFilesTemplate,
|
|
315
348
|
renderSignalsTemplate,
|
|
@@ -329,152 +362,27 @@ leftPanelMod.mount({
|
|
|
329
362
|
});
|
|
330
363
|
|
|
331
364
|
// Register all renderers with the store so render()/renderOnly() work
|
|
332
|
-
|
|
333
|
-
registerRenderer("activityBar", () => renderActivityBar(S));
|
|
365
|
+
// Register remaining renderers for render()/renderOnly() compat during migration
|
|
334
366
|
registerRenderer("leftPanel", () => leftPanelMod.render());
|
|
335
367
|
registerRenderer("canvas", () => renderCanvas());
|
|
336
368
|
registerRenderer("rightPanel", () => rightPanelMod.render());
|
|
337
369
|
registerRenderer("overlays", () => overlaysPanel.render());
|
|
338
|
-
|
|
339
|
-
setStatusbarRenderer(() => renderStatusbar(S));
|
|
370
|
+
setStatusbarRenderer(() => renderStatusbar());
|
|
340
371
|
mountStatusbar();
|
|
372
|
+
mountActivityBar();
|
|
341
373
|
|
|
342
374
|
// Clicking on the canvas-wrap background (outside any canvas panel) deselects the current element
|
|
343
375
|
canvasWrap.addEventListener("click", (/** @type {any} */ e) => {
|
|
344
376
|
if (e.target !== canvasWrap && e.target !== view.panzoomWrap) return;
|
|
345
|
-
if (!
|
|
346
|
-
|
|
377
|
+
if (!activeTab.value?.session.selection) return;
|
|
378
|
+
activeTab.value.session.selection = null;
|
|
347
379
|
});
|
|
348
380
|
|
|
349
|
-
function safeRenderLeftPanel() {
|
|
350
|
-
leftPanelMod.render();
|
|
351
|
-
}
|
|
352
|
-
|
|
353
381
|
function safeRenderRightPanel() {
|
|
354
382
|
rightPanelMod.render();
|
|
355
383
|
}
|
|
356
384
|
|
|
357
|
-
//
|
|
358
|
-
setGetStateFn(() => S);
|
|
359
|
-
setUpdateFn(function _update(/** @type {any} */ newState) {
|
|
360
|
-
const prev = S;
|
|
361
|
-
const prevDoc = S.document;
|
|
362
|
-
const prevSel = S.selection;
|
|
363
|
-
S = newState;
|
|
364
|
-
|
|
365
|
-
// Keep doc/session slices in sync with flat S
|
|
366
|
-
({ doc, session } = fromFlat(S));
|
|
367
|
-
|
|
368
|
-
const docChanged = prevDoc !== S.document;
|
|
369
|
-
const selChanged = !pathsEqual(prevSel, S.selection);
|
|
370
|
-
const modeChanged = prev.mode !== S.mode;
|
|
371
|
-
const uiChanged = prev.ui !== S.ui;
|
|
372
|
-
|
|
373
|
-
const canvasUiChanged =
|
|
374
|
-
uiChanged &&
|
|
375
|
-
(prev.ui?.editingFunction !== S.ui?.editingFunction ||
|
|
376
|
-
prev.ui?.settingsTab !== S.ui?.settingsTab ||
|
|
377
|
-
prev.ui?.stylebookTab !== S.ui?.stylebookTab ||
|
|
378
|
-
prev.ui?.stylebookFilter !== S.ui?.stylebookFilter ||
|
|
379
|
-
prev.ui?.stylebookCustomizedOnly !== S.ui?.stylebookCustomizedOnly ||
|
|
380
|
-
prev.ui?.featureToggles !== S.ui?.featureToggles);
|
|
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);
|
|
389
|
-
}
|
|
390
|
-
safeRenderLeftPanel();
|
|
391
|
-
} else if (selChanged || leftUiChanged) {
|
|
392
|
-
safeRenderLeftPanel();
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
if (uiChanged && prev.ui?.activeMedia !== S.ui?.activeMedia) {
|
|
396
|
-
updateActivePanelHeaders();
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
runPostRenderHooks(prevDoc, prevSel);
|
|
400
|
-
runUpdateMiddleware(S);
|
|
401
|
-
|
|
402
|
-
notify({
|
|
403
|
-
doc: docChanged,
|
|
404
|
-
selection: selChanged,
|
|
405
|
-
hover: false,
|
|
406
|
-
ui: uiChanged,
|
|
407
|
-
mode: modeChanged,
|
|
408
|
-
});
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
// Register session dispatch — lightweight path for selection/hover/ui changes
|
|
412
|
-
setGetDocFn(() => doc);
|
|
413
|
-
setGetSessionFn(() => session);
|
|
414
|
-
setUpdateSessionFn(function _updateSession(/** @type {any} */ patch) {
|
|
415
|
-
const prev = session;
|
|
416
|
-
session = { ...session, ...patch };
|
|
417
|
-
if (patch.ui) {
|
|
418
|
-
session.ui = { ...prev.ui, ...patch.ui };
|
|
419
|
-
}
|
|
420
|
-
if (patch.canvas) {
|
|
421
|
-
session.canvas = { ...prev.canvas, ...patch.canvas };
|
|
422
|
-
}
|
|
423
|
-
S = toFlat(doc, session);
|
|
424
|
-
|
|
425
|
-
const selChanged = !pathsEqual(prev.selection, session.selection);
|
|
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) {
|
|
453
|
-
updateActivePanelHeaders();
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// Process pending inline edit when canvas becomes ready
|
|
457
|
-
if (canvasChanged && session.canvas.status === "ready" && session.ui?.pendingInlineEdit) {
|
|
458
|
-
const { path, mediaName: mn } = session.ui.pendingInlineEdit;
|
|
459
|
-
updateUi("pendingInlineEdit", null);
|
|
460
|
-
const targetPanel =
|
|
461
|
-
canvasPanels.find((/** @type {any} */ p) => p.mediaName === mn) || canvasPanels[0];
|
|
462
|
-
if (targetPanel) {
|
|
463
|
-
const el = findCanvasElement(path, targetPanel.canvas);
|
|
464
|
-
if (el) enterComponentInlineEdit(el, path);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
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
|
-
});
|
|
473
|
-
|
|
474
|
-
// Register post-render hook for pseudo-state preview
|
|
475
|
-
addPostRenderHook(() => updateForcedPseudoPreview());
|
|
476
|
-
|
|
477
|
-
// Now that renderers and update are registered, bootstrap
|
|
385
|
+
// Now that renderers are registered, bootstrap
|
|
478
386
|
registerFunctionCompletions();
|
|
479
387
|
|
|
480
388
|
const _openParam = new URLSearchParams(location.search).get("open");
|
|
@@ -547,17 +455,30 @@ if (_openParam) {
|
|
|
547
455
|
const fileRelPath = _fileParam || siteCtx.fileRelPath || _openParam;
|
|
548
456
|
const content = await platform.readFile(fileRelPath);
|
|
549
457
|
if (content) {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
458
|
+
let parsedDoc, frontmatter;
|
|
459
|
+
const isMd = fileRelPath.endsWith(".md");
|
|
460
|
+
if (isMd) {
|
|
461
|
+
const result = await loadMarkdown(content);
|
|
462
|
+
parsedDoc = result.document;
|
|
463
|
+
frontmatter = result.frontmatter;
|
|
553
464
|
} else {
|
|
554
|
-
|
|
555
|
-
S = createState(parsed);
|
|
556
|
-
S.documentPath = fileRelPath;
|
|
465
|
+
parsedDoc = JSON.parse(content);
|
|
557
466
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
467
|
+
|
|
468
|
+
// Open in a tab (replaces initial tab)
|
|
469
|
+
const { closeTab } = await import("./workspace/workspace.js");
|
|
470
|
+
closeTab("initial");
|
|
471
|
+
openTab({
|
|
472
|
+
id: fileRelPath,
|
|
473
|
+
documentPath: fileRelPath,
|
|
474
|
+
document: parsedDoc,
|
|
475
|
+
frontmatter,
|
|
476
|
+
sourceFormat: isMd ? "md" : null,
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
if (isMd && activeTab.value) activeTab.value.doc.mode = "content";
|
|
480
|
+
view.leftTab = "files";
|
|
481
|
+
|
|
561
482
|
render();
|
|
562
483
|
statusMessage(`Opened ${_openParam}`);
|
|
563
484
|
}
|
|
@@ -597,40 +518,6 @@ function renderLeftPanel() {
|
|
|
597
518
|
|
|
598
519
|
// ─── Source/Function editors: delegated to panels/editors.js ─────────────────
|
|
599
520
|
|
|
600
|
-
// ─── Toolbar (delegated to panels/toolbar.js) ────────────────────────────────
|
|
601
|
-
|
|
602
|
-
function renderToolbar() {
|
|
603
|
-
toolbarPanel.render();
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
// ─── File Operations (delegated to file-ops.js) ─────────────────────────────
|
|
607
|
-
|
|
608
|
-
function fileOpsCtx() {
|
|
609
|
-
return {
|
|
610
|
-
S,
|
|
611
|
-
commit: (/** @type {any} */ ns) => {
|
|
612
|
-
S = ns;
|
|
613
|
-
({ doc, session } = fromFlat(S));
|
|
614
|
-
render();
|
|
615
|
-
},
|
|
616
|
-
renderToolbar,
|
|
617
|
-
};
|
|
618
|
-
}
|
|
619
|
-
function openFile() {
|
|
620
|
-
return _openFile(fileOpsCtx());
|
|
621
|
-
}
|
|
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
|
-
function saveFile() {
|
|
628
|
-
return _saveFile(fileOpsCtx());
|
|
629
|
-
}
|
|
630
|
-
function exportFile() {
|
|
631
|
-
return _exportFile(fileOpsCtx());
|
|
632
|
-
}
|
|
633
|
-
|
|
634
521
|
// ─── File tree (delegated to files.js) ───────────────────────────────────────
|
|
635
522
|
|
|
636
523
|
function loadProject() {
|
|
@@ -638,12 +525,7 @@ function loadProject() {
|
|
|
638
525
|
}
|
|
639
526
|
function openProject() {
|
|
640
527
|
return _openProject({
|
|
641
|
-
|
|
642
|
-
commit: (/** @type {any} */ ns) => {
|
|
643
|
-
S = ns;
|
|
644
|
-
({ doc, session } = fromFlat(S));
|
|
645
|
-
},
|
|
646
|
-
renderActivityBar: () => renderActivityBar(S),
|
|
528
|
+
renderActivityBar: () => renderActivityBar(),
|
|
647
529
|
renderLeftPanel,
|
|
648
530
|
});
|
|
649
531
|
}
|
|
@@ -651,33 +533,12 @@ function renderFilesTemplate() {
|
|
|
651
533
|
return _renderFilesTemplate({ openProject, openFileFromTree, renderLeftPanel });
|
|
652
534
|
}
|
|
653
535
|
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
|
-
);
|
|
536
|
+
return openFileInTab(path);
|
|
671
537
|
}
|
|
672
538
|
|
|
673
539
|
// ─── Keyboard shortcuts ───────────────────────────────────────────────────────
|
|
674
540
|
initShortcuts(() => ({
|
|
675
|
-
|
|
676
|
-
setS: (ns) => {
|
|
677
|
-
S = ns;
|
|
678
|
-
({ doc, session } = fromFlat(S));
|
|
679
|
-
},
|
|
680
|
-
canvasMode,
|
|
541
|
+
canvasMode: getCanvasMode(),
|
|
681
542
|
panX: view.panX,
|
|
682
543
|
panY: view.panY,
|
|
683
544
|
setPan: (x, y) => {
|
|
@@ -709,21 +570,23 @@ initShortcuts(() => ({
|
|
|
709
570
|
const AUTO_SAVE_DELAY = 2000;
|
|
710
571
|
|
|
711
572
|
function scheduleAutosave() {
|
|
712
|
-
|
|
573
|
+
const tab = activeTab.value;
|
|
574
|
+
if (!tab?.fileHandle || !tab.doc.dirty) return;
|
|
713
575
|
clearTimeout(view.autosaveTimer);
|
|
714
576
|
view.autosaveTimer = setTimeout(async () => {
|
|
715
|
-
|
|
577
|
+
const t = activeTab.value;
|
|
578
|
+
if (t?.fileHandle && t.doc.dirty && "createWritable" in t.fileHandle) {
|
|
716
579
|
try {
|
|
717
|
-
const writable = await
|
|
718
|
-
await writable.write(JSON.stringify(
|
|
580
|
+
const writable = await t.fileHandle.createWritable();
|
|
581
|
+
await writable.write(JSON.stringify(t.doc.document, null, 2));
|
|
719
582
|
await writable.close();
|
|
720
|
-
|
|
583
|
+
t.doc.dirty = false;
|
|
721
584
|
statusMessage("Auto-saved");
|
|
722
585
|
} catch {}
|
|
723
586
|
}
|
|
724
587
|
}, AUTO_SAVE_DELAY);
|
|
725
588
|
}
|
|
726
589
|
|
|
727
|
-
|
|
728
|
-
if (
|
|
590
|
+
effect(() => {
|
|
591
|
+
if (activeTab.value?.doc.dirty) scheduleAutosave();
|
|
729
592
|
});
|