@jxsuite/studio 0.19.0 → 0.20.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.css +98 -98
- package/dist/studio.js +12577 -12183
- package/dist/studio.js.map +146 -144
- package/dist/workers/editor.worker.js +79 -79
- package/dist/workers/json.worker.js +109 -109
- package/dist/workers/ts.worker.js +82 -82
- package/package.json +17 -6
- package/src/browse/browse.js +25 -19
- package/src/canvas/canvas-diff.js +24 -16
- package/src/canvas/canvas-helpers.js +24 -19
- package/src/canvas/canvas-live-render.js +94 -39
- package/src/canvas/canvas-render.js +119 -73
- package/src/canvas/canvas-utils.js +38 -15
- package/src/canvas/nested-site-style.js +50 -0
- package/src/editor/component-inline-edit.js +14 -13
- package/src/editor/content-inline-edit.js +22 -10
- package/src/editor/context-menu.js +1 -1
- package/src/editor/convert-to-component.js +12 -10
- package/src/editor/inline-edit.js +66 -28
- package/src/editor/insertion-helper.js +16 -12
- package/src/editor/shortcuts.js +4 -2
- package/src/editor/slash-menu.js +6 -4
- package/src/files/components.js +1 -1
- package/src/files/file-ops.js +37 -16
- package/src/files/files.js +78 -52
- package/src/github/github-auth.js +122 -0
- package/src/github/github-publish.js +115 -0
- package/src/markdown/md-convert.js +172 -107
- package/src/new-project/new-project-modal.js +204 -0
- package/src/panels/activity-bar.js +22 -20
- package/src/panels/ai-panel.js +399 -0
- package/src/panels/block-action-bar.js +5 -5
- package/src/panels/canvas-dnd.js +8 -2
- package/src/panels/data-explorer.js +19 -13
- package/src/panels/dnd.js +144 -92
- package/src/panels/editors.js +55 -35
- package/src/panels/elements-panel.js +33 -19
- package/src/panels/events-panel.js +15 -10
- package/src/panels/git-panel.js +469 -98
- package/src/panels/head-panel.js +121 -81
- package/src/panels/imports-panel.js +88 -64
- package/src/panels/layers-panel.js +64 -43
- package/src/panels/left-panel.js +85 -47
- package/src/panels/overlays.js +1 -0
- package/src/panels/panel-events.js +45 -30
- package/src/panels/preview-render.js +23 -18
- package/src/panels/properties-panel.js +365 -249
- package/src/panels/pseudo-preview.js +2 -2
- package/src/panels/quick-search.js +7 -5
- package/src/panels/right-panel.js +26 -2
- package/src/panels/shared.js +4 -4
- package/src/panels/signals-panel.js +295 -166
- package/src/panels/statusbar.js +4 -4
- package/src/panels/style-inputs.js +67 -35
- package/src/panels/style-panel.js +233 -132
- package/src/panels/style-utils.js +34 -16
- package/src/panels/stylebook-layers-panel.js +12 -10
- package/src/panels/stylebook-panel.js +134 -66
- package/src/panels/toolbar.js +333 -109
- package/src/panels/welcome-screen.js +121 -0
- package/src/platform.js +2 -4
- package/src/platforms/devserver.js +113 -7
- package/src/resize-edges.js +98 -0
- package/src/services/cem-export.js +12 -7
- package/src/services/code-services.js +30 -12
- package/src/settings/content-types-editor.js +7 -7
- package/src/settings/css-vars-editor.js +30 -24
- package/src/settings/defs-editor.js +12 -7
- package/src/settings/general-settings.js +85 -3
- package/src/settings/head-editor.js +30 -24
- package/src/settings/schema-field-ui.js +58 -39
- package/src/site-context.js +41 -31
- package/src/state.js +70 -29
- package/src/store.js +21 -26
- package/src/studio.js +103 -81
- package/src/tabs/tab.js +67 -43
- package/src/tabs/transact.js +23 -13
- package/src/ui/button-group.js +24 -24
- package/src/ui/color-selector.js +28 -23
- package/src/ui/field-row.js +3 -3
- package/src/ui/icons.js +2 -2
- package/src/ui/media-picker.js +7 -7
- package/src/ui/spectrum.js +5 -1
- package/src/ui/unit-selector.js +18 -16
- package/src/ui/value-selector.js +21 -15
- package/src/ui/widgets.js +22 -19
- package/src/utils/canvas-media.js +5 -4
- package/src/utils/edit-display.js +31 -20
- package/src/utils/google-fonts.js +11 -11
- package/src/utils/inherited-style.js +9 -8
- package/src/utils/studio-utils.js +7 -7
- package/src/view.js +58 -1
- package/src/workspace/workspace.js +10 -7
package/src/store.js
CHANGED
|
@@ -22,6 +22,7 @@ export {
|
|
|
22
22
|
isAncestor,
|
|
23
23
|
projectState,
|
|
24
24
|
setProjectState,
|
|
25
|
+
requireProjectState,
|
|
25
26
|
updateFrontmatter,
|
|
26
27
|
} from "./state.js";
|
|
27
28
|
|
|
@@ -39,25 +40,10 @@ export const statusbarEl = /** @type {HTMLElement} */ (document.querySelector("#
|
|
|
39
40
|
|
|
40
41
|
// ─── Shared containers (mutated in place by owner modules) ───────────────────
|
|
41
42
|
|
|
42
|
-
/** WeakMap<
|
|
43
|
+
/** @type {WeakMap<Element, JxPath>} */
|
|
43
44
|
export const elToPath = new WeakMap();
|
|
44
45
|
|
|
45
|
-
/**
|
|
46
|
-
* Canvas panels: Array<{ mediaName, canvas, overlay, overlayClk, viewport, dropLine, element,
|
|
47
|
-
* _width }>
|
|
48
|
-
*
|
|
49
|
-
* @type {{
|
|
50
|
-
* mediaName: string;
|
|
51
|
-
* canvas: HTMLElement;
|
|
52
|
-
* overlay: HTMLElement;
|
|
53
|
-
* overlayClk: HTMLElement;
|
|
54
|
-
* viewport: HTMLElement;
|
|
55
|
-
* scrollContainer?: HTMLElement | null;
|
|
56
|
-
* dropLine: HTMLElement;
|
|
57
|
-
* element?: HTMLElement | null;
|
|
58
|
-
* _width?: number | null;
|
|
59
|
-
* }[]}
|
|
60
|
-
*/
|
|
46
|
+
/** @type {import("./panels/canvas-dnd.js").CanvasPanel[]} */
|
|
61
47
|
export const canvasPanels = [];
|
|
62
48
|
|
|
63
49
|
// ─── Shared constants ────────────────────────────────────────────────────────
|
|
@@ -130,8 +116,8 @@ export function cancelStyleDebounce(/** @type {string} */ prop) {
|
|
|
130
116
|
/**
|
|
131
117
|
* Strip all on* event handler properties from a Jx document tree (deep clone).
|
|
132
118
|
*
|
|
133
|
-
* @param {
|
|
134
|
-
* @returns {
|
|
119
|
+
* @param {JxMutableNode} node
|
|
120
|
+
* @returns {JxMutableNode}
|
|
135
121
|
*/
|
|
136
122
|
export function stripEventHandlers(node) {
|
|
137
123
|
if (!node || typeof node !== "object") return node;
|
|
@@ -218,18 +204,27 @@ export function renderOnly(...names) {
|
|
|
218
204
|
export function updateSession(patch) {
|
|
219
205
|
const tab = activeTab.value;
|
|
220
206
|
if (tab) {
|
|
221
|
-
const p = /**
|
|
222
|
-
|
|
223
|
-
|
|
207
|
+
const p = /**
|
|
208
|
+
* @type {{
|
|
209
|
+
* selection?: unknown;
|
|
210
|
+
* hover?: unknown;
|
|
211
|
+
* clipboard?: unknown;
|
|
212
|
+
* ui?: Record<string, unknown>;
|
|
213
|
+
* canvas?: Record<string, unknown>;
|
|
214
|
+
* }}
|
|
215
|
+
*/ (patch);
|
|
216
|
+
if (p.selection !== undefined)
|
|
217
|
+
tab.session.selection = /** @type {JxPath | null} */ (p.selection);
|
|
218
|
+
if (p.hover !== undefined) tab.session.hover = /** @type {JxPath | null} */ (p.hover);
|
|
224
219
|
if (p.clipboard !== undefined) tab.session.clipboard = p.clipboard;
|
|
225
220
|
if (p.ui) {
|
|
226
221
|
for (const [k, v] of Object.entries(p.ui)) {
|
|
227
|
-
/** @type {
|
|
222
|
+
/** @type {Record<string, unknown>} */ (tab.session.ui)[k] = v;
|
|
228
223
|
}
|
|
229
224
|
}
|
|
230
225
|
if (p.canvas) {
|
|
231
226
|
for (const [k, v] of Object.entries(p.canvas)) {
|
|
232
|
-
/** @type {
|
|
227
|
+
/** @type {Record<string, unknown>} */ (tab.session.canvas)[k] = v;
|
|
233
228
|
}
|
|
234
229
|
}
|
|
235
230
|
}
|
|
@@ -244,7 +239,7 @@ export function updateSession(patch) {
|
|
|
244
239
|
export function updateUi(field, value) {
|
|
245
240
|
const tab = activeTab.value;
|
|
246
241
|
if (tab) {
|
|
247
|
-
/** @type {
|
|
242
|
+
/** @type {Record<string, unknown>} */ (tab.session.ui)[field] = value;
|
|
248
243
|
}
|
|
249
244
|
}
|
|
250
245
|
|
|
@@ -257,7 +252,7 @@ export function updateCanvas(patch) {
|
|
|
257
252
|
const tab = activeTab.value;
|
|
258
253
|
if (tab) {
|
|
259
254
|
for (const [k, v] of Object.entries(patch)) {
|
|
260
|
-
/** @type {
|
|
255
|
+
/** @type {Record<string, unknown>} */ (tab.session.canvas)[k] = v;
|
|
261
256
|
}
|
|
262
257
|
}
|
|
263
258
|
}
|
package/src/studio.js
CHANGED
|
@@ -15,10 +15,11 @@ import {
|
|
|
15
15
|
render,
|
|
16
16
|
projectState,
|
|
17
17
|
setProjectState,
|
|
18
|
+
requireProjectState,
|
|
18
19
|
updateUi,
|
|
19
20
|
} from "./store.js";
|
|
20
21
|
|
|
21
|
-
import { activeTab, openTab,
|
|
22
|
+
import { activeTab, openTab, closeAllTabs } from "./workspace/workspace.js";
|
|
22
23
|
import { transactDoc, mutateUpdateDef, mutateUpdateProperty } from "./tabs/transact.js";
|
|
23
24
|
import { effect } from "./reactivity.js";
|
|
24
25
|
|
|
@@ -54,6 +55,7 @@ import { exportCemManifest as _exportCemManifest } from "./services/cem-export.j
|
|
|
54
55
|
import { registerPlatform, getPlatform, hasPlatform } from "./platform.js";
|
|
55
56
|
import { parseMediaEntries } from "./utils/canvas-media.js";
|
|
56
57
|
import { createDevServerPlatform } from "./platforms/devserver.js";
|
|
58
|
+
import { mountResizeEdges } from "./resize-edges.js";
|
|
57
59
|
import { codeService } from "./services/code-services.js";
|
|
58
60
|
import { defCategory, defBadgeLabel, renderSignalsTemplate } from "./panels/signals-panel.js";
|
|
59
61
|
import { loadComponentRegistry } from "./files/components.js";
|
|
@@ -87,6 +89,9 @@ import { updateForcedPseudoPreview } from "./panels/pseudo-preview.js";
|
|
|
87
89
|
import { initPanelEvents } from "./panels/panel-events.js";
|
|
88
90
|
import { initQuickSearch } from "./panels/quick-search.js";
|
|
89
91
|
import { addRecentProject } from "./recent-projects.js";
|
|
92
|
+
import { initWelcome } from "./panels/welcome-screen.js";
|
|
93
|
+
import { openNewProjectModal } from "./new-project/new-project-modal.js";
|
|
94
|
+
import { cloneRepository } from "./panels/git-panel.js";
|
|
90
95
|
|
|
91
96
|
// ─── Globals ──────────────────────────────────────────────────────────────────
|
|
92
97
|
// These mutable variables are local to studio.js for now. As sections are extracted
|
|
@@ -105,12 +110,12 @@ function setCanvasMode(mode) {
|
|
|
105
110
|
if (tab) tab.session.ui.canvasMode = mode;
|
|
106
111
|
}
|
|
107
112
|
|
|
108
|
-
/** @type {
|
|
113
|
+
/** @type {import("./canvas/canvas-render.js").GitDiffState | null} */
|
|
109
114
|
let gitDiffState = null;
|
|
110
115
|
|
|
111
116
|
// ─── Component registry ───────────────────────────────────────────────────────
|
|
112
117
|
|
|
113
|
-
/** @param {
|
|
118
|
+
/** @param {string} componentPath */
|
|
114
119
|
async function navigateToComponent(componentPath) {
|
|
115
120
|
try {
|
|
116
121
|
const platform = getPlatform();
|
|
@@ -135,7 +140,7 @@ async function navigateToComponent(componentPath) {
|
|
|
135
140
|
// Load the component
|
|
136
141
|
tab.doc.document = parsed;
|
|
137
142
|
tab.doc.dirty = false;
|
|
138
|
-
tab.doc.mode = /** @type {
|
|
143
|
+
tab.doc.mode = /** @type {string} */ (/** @type {unknown} */ (null));
|
|
139
144
|
tab.doc.sourceFormat = null;
|
|
140
145
|
tab.documentPath = componentPath;
|
|
141
146
|
tab.session.selection = null;
|
|
@@ -145,8 +150,8 @@ async function navigateToComponent(componentPath) {
|
|
|
145
150
|
|
|
146
151
|
render();
|
|
147
152
|
statusMessage(`Editing component: ${parsed.tagName || componentPath}`);
|
|
148
|
-
} catch (/** @type {
|
|
149
|
-
const err = /** @type {
|
|
153
|
+
} catch (/** @type {unknown} */ e) {
|
|
154
|
+
const err = /** @type {Error} */ (e);
|
|
150
155
|
statusMessage(`Error: ${err.message}`);
|
|
151
156
|
}
|
|
152
157
|
}
|
|
@@ -158,21 +163,23 @@ async function navigateBack() {
|
|
|
158
163
|
try {
|
|
159
164
|
const platform = getPlatform();
|
|
160
165
|
await platform.writeFile(tab.documentPath, serializeDocument(tab));
|
|
161
|
-
} catch (/** @type {
|
|
162
|
-
const err = /** @type {
|
|
166
|
+
} catch (/** @type {unknown} */ e) {
|
|
167
|
+
const err = /** @type {Error} */ (e);
|
|
163
168
|
statusMessage(`Save error: ${err.message}`);
|
|
164
169
|
}
|
|
165
170
|
}
|
|
166
171
|
|
|
167
172
|
// Pop the stack
|
|
168
|
-
const frame = /** @type {
|
|
173
|
+
const frame = /** @type {Record<string, unknown> | undefined} */ (
|
|
174
|
+
tab.session.documentStack.pop()
|
|
175
|
+
);
|
|
169
176
|
if (!frame) return;
|
|
170
|
-
tab.doc.document = frame.document;
|
|
171
|
-
tab.doc.dirty = frame.dirty;
|
|
172
|
-
tab.doc.mode = frame.mode;
|
|
173
|
-
tab.doc.sourceFormat = frame.sourceFormat;
|
|
174
|
-
tab.documentPath = frame.documentPath;
|
|
175
|
-
tab.session.selection = frame.selection;
|
|
177
|
+
tab.doc.document = /** @type {JxMutableNode} */ (frame.document);
|
|
178
|
+
tab.doc.dirty = /** @type {boolean} */ (frame.dirty);
|
|
179
|
+
tab.doc.mode = /** @type {string} */ (frame.mode);
|
|
180
|
+
tab.doc.sourceFormat = /** @type {string | null} */ (frame.sourceFormat);
|
|
181
|
+
tab.documentPath = /** @type {string | null} */ (frame.documentPath);
|
|
182
|
+
tab.session.selection = /** @type {JxPath | null} */ (frame.selection);
|
|
176
183
|
view.leftTab = "layers";
|
|
177
184
|
|
|
178
185
|
render();
|
|
@@ -188,20 +195,20 @@ async function navigateToLevel(targetIndex) {
|
|
|
188
195
|
try {
|
|
189
196
|
const platform = getPlatform();
|
|
190
197
|
await platform.writeFile(tab.documentPath, serializeDocument(tab));
|
|
191
|
-
} catch (/** @type {
|
|
192
|
-
const err = /** @type {
|
|
198
|
+
} catch (/** @type {unknown} */ e) {
|
|
199
|
+
const err = /** @type {Error} */ (e);
|
|
193
200
|
statusMessage(`Save error: ${err.message}`);
|
|
194
201
|
}
|
|
195
202
|
}
|
|
196
203
|
|
|
197
|
-
const frame = /** @type {
|
|
204
|
+
const frame = /** @type {DocumentStackEntry} */ (stack[targetIndex]);
|
|
198
205
|
tab.session.documentStack = stack.slice(0, targetIndex);
|
|
199
|
-
tab.doc.document = frame.document;
|
|
200
|
-
tab.doc.dirty = frame.dirty;
|
|
201
|
-
tab.doc.mode = frame.mode;
|
|
202
|
-
tab.doc.sourceFormat = frame.sourceFormat;
|
|
203
|
-
tab.documentPath = frame.documentPath;
|
|
204
|
-
tab.session.selection = frame.selection;
|
|
206
|
+
tab.doc.document = /** @type {JxMutableNode} */ (frame.document);
|
|
207
|
+
tab.doc.dirty = /** @type {boolean} */ (frame.dirty);
|
|
208
|
+
tab.doc.mode = /** @type {string} */ (frame.mode);
|
|
209
|
+
tab.doc.sourceFormat = /** @type {string | null} */ (frame.sourceFormat);
|
|
210
|
+
tab.documentPath = /** @type {string | null} */ (frame.documentPath);
|
|
211
|
+
tab.session.selection = /** @type {JxPath | null} */ (frame.selection);
|
|
205
212
|
view.leftTab = "layers";
|
|
206
213
|
|
|
207
214
|
render();
|
|
@@ -210,23 +217,33 @@ async function navigateToLevel(targetIndex) {
|
|
|
210
217
|
|
|
211
218
|
async function closeFunctionEditor() {
|
|
212
219
|
const tab = activeTab.value;
|
|
213
|
-
const editing =
|
|
214
|
-
|
|
220
|
+
const editing =
|
|
221
|
+
/** @type {{ type: string; defName?: string; path?: JxPath; eventKey?: string } | null} */ (
|
|
222
|
+
tab?.session.ui.editingFunction
|
|
223
|
+
);
|
|
224
|
+
if (!editing || !tab) return;
|
|
215
225
|
if (view.functionEditor) {
|
|
216
226
|
const currentCode = view.functionEditor.getValue();
|
|
217
227
|
const minResult = await codeService("minify", { code: currentCode });
|
|
218
228
|
const bodyToStore = minResult?.code ?? currentCode;
|
|
219
229
|
if (editing.type === "def") {
|
|
220
|
-
transactDoc(tab, (t) =>
|
|
230
|
+
transactDoc(tab, (t) =>
|
|
231
|
+
mutateUpdateDef(t, /** @type {string} */ (editing.defName), { body: bodyToStore }),
|
|
232
|
+
);
|
|
221
233
|
} else if (editing.type === "event") {
|
|
222
|
-
const node = getNodeAtPath(tab.doc.document, editing.path);
|
|
223
|
-
const current = node?.[editing.eventKey] || {};
|
|
234
|
+
const node = getNodeAtPath(tab.doc.document, /** @type {JxPath} */ (editing.path));
|
|
235
|
+
const current = node?.[/** @type {string} */ (editing.eventKey)] || {};
|
|
224
236
|
transactDoc(tab, (t) =>
|
|
225
|
-
mutateUpdateProperty(
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
237
|
+
mutateUpdateProperty(
|
|
238
|
+
t,
|
|
239
|
+
/** @type {JxPath} */ (editing.path),
|
|
240
|
+
/** @type {string} */ (editing.eventKey),
|
|
241
|
+
{
|
|
242
|
+
.../** @type {object} */ (current),
|
|
243
|
+
$prototype: "Function",
|
|
244
|
+
body: bodyToStore,
|
|
245
|
+
},
|
|
246
|
+
),
|
|
230
247
|
);
|
|
231
248
|
}
|
|
232
249
|
view.functionEditor.dispose();
|
|
@@ -243,10 +260,12 @@ document.body.appendChild(datalistHost);
|
|
|
243
260
|
litRender(
|
|
244
261
|
html`
|
|
245
262
|
<datalist id="tag-names">
|
|
246
|
-
${webdata.allTags.map((/** @type {
|
|
263
|
+
${webdata.allTags.map((/** @type {string} */ tag) => html`<option value=${tag}></option>`)}
|
|
247
264
|
</datalist>
|
|
248
265
|
<datalist id="css-props">
|
|
249
|
-
${webdata.cssProps.map(
|
|
266
|
+
${webdata.cssProps.map(
|
|
267
|
+
(/** @type {string[]} */ [name]) => html`<option value=${name}></option>`,
|
|
268
|
+
)}
|
|
250
269
|
</datalist>
|
|
251
270
|
`,
|
|
252
271
|
datalistHost,
|
|
@@ -263,17 +282,7 @@ if (!hasPlatform()) {
|
|
|
263
282
|
registerPlatform(createDevServerPlatform());
|
|
264
283
|
}
|
|
265
284
|
|
|
266
|
-
|
|
267
|
-
tagName: "div",
|
|
268
|
-
style: { padding: "2rem", fontFamily: "system-ui, sans-serif" },
|
|
269
|
-
children: [
|
|
270
|
-
{ tagName: "h1", textContent: "New Component" },
|
|
271
|
-
{ tagName: "p", textContent: "Open a Jx file or start editing." },
|
|
272
|
-
],
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
// Create the initial reactive tab — the canonical state container.
|
|
276
|
-
openTab({ id: "initial", document: structuredClone(EMPTY_DOC) });
|
|
285
|
+
mountResizeEdges();
|
|
277
286
|
|
|
278
287
|
// ─── Render loop ──────────────────────────────────────────────────────────────
|
|
279
288
|
|
|
@@ -338,25 +347,38 @@ initCanvasRender({
|
|
|
338
347
|
get gitDiffState() {
|
|
339
348
|
return gitDiffState;
|
|
340
349
|
},
|
|
341
|
-
setGitDiffState: (
|
|
350
|
+
setGitDiffState: (
|
|
351
|
+
/** @type {import("./canvas/canvas-render.js").GitDiffState | null} */ state,
|
|
352
|
+
) => {
|
|
342
353
|
gitDiffState = state;
|
|
343
354
|
},
|
|
344
355
|
});
|
|
345
356
|
|
|
357
|
+
initWelcome({
|
|
358
|
+
openProject: () => openProject(),
|
|
359
|
+
openRecentProject: (/** @type {string} */ root) => openRecentProject(root),
|
|
360
|
+
openNewProject: async () => {
|
|
361
|
+
const result = await openNewProjectModal();
|
|
362
|
+
if (result) openRecentProject(result.root);
|
|
363
|
+
},
|
|
364
|
+
cloneRepository: () => cloneRepository({ openRecentProject }),
|
|
365
|
+
});
|
|
366
|
+
|
|
346
367
|
// Effect-driven canvas rendering: auto-triggers renderCanvas when reactive deps change
|
|
347
368
|
let _canvasRenderScheduled = false;
|
|
348
369
|
effect(() => {
|
|
349
370
|
const tab = activeTab.value;
|
|
350
|
-
if (
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
371
|
+
if (tab) {
|
|
372
|
+
void tab.doc.document;
|
|
373
|
+
void tab.doc.mode;
|
|
374
|
+
void tab.session.ui.canvasMode;
|
|
375
|
+
void tab.session.ui.editingFunction;
|
|
376
|
+
void tab.session.ui.featureToggles;
|
|
377
|
+
void tab.session.ui.settingsTab;
|
|
378
|
+
void tab.session.ui.stylebookTab;
|
|
379
|
+
void tab.session.ui.stylebookFilter;
|
|
380
|
+
void tab.session.ui.stylebookCustomizedOnly;
|
|
381
|
+
}
|
|
360
382
|
if (!_canvasRenderScheduled) {
|
|
361
383
|
_canvasRenderScheduled = true;
|
|
362
384
|
queueMicrotask(() => {
|
|
@@ -396,7 +418,10 @@ leftPanelMod.mount({
|
|
|
396
418
|
registerElementsDnD,
|
|
397
419
|
registerComponentsDnD,
|
|
398
420
|
setupTreeKeyboard,
|
|
399
|
-
|
|
421
|
+
cloneRepository: () => cloneRepository({ openRecentProject }),
|
|
422
|
+
setGitDiffState: (
|
|
423
|
+
/** @type {import("./canvas/canvas-render.js").GitDiffState | null} */ state,
|
|
424
|
+
) => {
|
|
400
425
|
gitDiffState = state;
|
|
401
426
|
},
|
|
402
427
|
});
|
|
@@ -412,7 +437,7 @@ mountStatusbar();
|
|
|
412
437
|
mountActivityBar();
|
|
413
438
|
|
|
414
439
|
// Clicking on the canvas-wrap background (outside any canvas panel) deselects the current element
|
|
415
|
-
canvasWrap.addEventListener("click", (/** @type {
|
|
440
|
+
canvasWrap.addEventListener("click", (/** @type {MouseEvent} */ e) => {
|
|
416
441
|
if (e.target !== canvasWrap && e.target !== view.panzoomWrap) return;
|
|
417
442
|
if (!activeTab.value?.session.selection) return;
|
|
418
443
|
activeTab.value.session.selection = null;
|
|
@@ -456,7 +481,7 @@ if (_openParam) {
|
|
|
456
481
|
name: siteCtx.projectConfig?.name || "Project",
|
|
457
482
|
projectRoot: siteCtx.sitePath,
|
|
458
483
|
isSiteProject: true,
|
|
459
|
-
projectConfig: siteCtx.projectConfig,
|
|
484
|
+
projectConfig: siteCtx.projectConfig || null,
|
|
460
485
|
projectDirs: [],
|
|
461
486
|
dirs: new Map(),
|
|
462
487
|
expanded: new Set(),
|
|
@@ -477,17 +502,17 @@ if (_openParam) {
|
|
|
477
502
|
"styles",
|
|
478
503
|
];
|
|
479
504
|
const dirEntries = await platform.listDirectory(".");
|
|
480
|
-
|
|
505
|
+
requireProjectState().dirs.set(".", dirEntries);
|
|
481
506
|
const foundDirs = [];
|
|
482
507
|
for (const e of dirEntries) {
|
|
483
508
|
if (e.type === "directory" && conventionalDirs.includes(e.name)) {
|
|
484
509
|
foundDirs.push(e.name);
|
|
485
|
-
|
|
510
|
+
requireProjectState().expanded.add(e.path || e.name);
|
|
486
511
|
const sub = await platform.listDirectory(e.path || e.name);
|
|
487
|
-
|
|
512
|
+
requireProjectState().dirs.set(e.path || e.name, sub);
|
|
488
513
|
}
|
|
489
514
|
}
|
|
490
|
-
|
|
515
|
+
requireProjectState().projectDirs = foundDirs;
|
|
491
516
|
}
|
|
492
517
|
|
|
493
518
|
// Read and open the file
|
|
@@ -520,9 +545,7 @@ if (_openParam) {
|
|
|
520
545
|
parsedDoc = JSON.parse(content);
|
|
521
546
|
}
|
|
522
547
|
|
|
523
|
-
// Open in a tab
|
|
524
|
-
const { closeTab } = await import("./workspace/workspace.js");
|
|
525
|
-
closeTab("initial");
|
|
548
|
+
// Open in a tab
|
|
526
549
|
openTab({
|
|
527
550
|
id: fileRelPath,
|
|
528
551
|
documentPath: fileRelPath,
|
|
@@ -535,13 +558,12 @@ if (_openParam) {
|
|
|
535
558
|
if (fileRelPath === "project.json" && activeTab.value) {
|
|
536
559
|
activeTab.value.session.ui.canvasMode = "stylebook";
|
|
537
560
|
}
|
|
538
|
-
view.leftTab = "layers";
|
|
539
561
|
|
|
540
562
|
render();
|
|
541
563
|
statusMessage(`Opened ${_openParam}`);
|
|
542
564
|
}
|
|
543
|
-
} catch (/** @type {
|
|
544
|
-
statusMessage(`Error: ${e.message}`);
|
|
565
|
+
} catch (/** @type {unknown} */ e) {
|
|
566
|
+
statusMessage(`Error: ${/** @type {Error} */ (e).message}`);
|
|
545
567
|
}
|
|
546
568
|
})();
|
|
547
569
|
}
|
|
@@ -594,7 +616,7 @@ async function openRecentProject(/** @type {string} */ root) {
|
|
|
594
616
|
const content = await platform.readFile("project.json");
|
|
595
617
|
const config = JSON.parse(content);
|
|
596
618
|
|
|
597
|
-
|
|
619
|
+
closeAllTabs();
|
|
598
620
|
|
|
599
621
|
setProjectState({
|
|
600
622
|
...projectState,
|
|
@@ -620,29 +642,29 @@ async function openRecentProject(/** @type {string} */ root) {
|
|
|
620
642
|
"public",
|
|
621
643
|
"styles",
|
|
622
644
|
];
|
|
623
|
-
const entries =
|
|
645
|
+
const entries = requireProjectState().dirs.get(".") || [];
|
|
624
646
|
for (const e of entries) {
|
|
625
647
|
if (e.type === "directory" && conventionalDirs.includes(e.name)) {
|
|
626
|
-
|
|
648
|
+
requireProjectState().expanded.add(e.path || e.name);
|
|
627
649
|
await loadDirectory(e.path || e.name);
|
|
628
650
|
}
|
|
629
651
|
}
|
|
630
652
|
|
|
631
|
-
addRecentProject(
|
|
653
|
+
addRecentProject(requireProjectState().name, root);
|
|
632
654
|
view.leftTab = "files";
|
|
633
655
|
renderActivityBar();
|
|
634
656
|
renderLeftPanel();
|
|
635
|
-
statusMessage(`Opened project: ${
|
|
657
|
+
statusMessage(`Opened project: ${requireProjectState().name}`);
|
|
636
658
|
|
|
637
659
|
await openHomePage();
|
|
638
|
-
} catch (/** @type {
|
|
639
|
-
statusMessage(`Error: ${e.message}`);
|
|
660
|
+
} catch (/** @type {unknown} */ e) {
|
|
661
|
+
statusMessage(`Error: ${/** @type {Error} */ (e).message}`);
|
|
640
662
|
}
|
|
641
663
|
}
|
|
642
664
|
function renderFilesTemplate() {
|
|
643
665
|
return _renderFilesTemplate({ openProject, openFileFromTree, renderLeftPanel });
|
|
644
666
|
}
|
|
645
|
-
function openFileFromTree(/** @type {
|
|
667
|
+
function openFileFromTree(/** @type {string} */ path) {
|
|
646
668
|
return openFileInTab(path);
|
|
647
669
|
}
|
|
648
670
|
|
|
@@ -676,13 +698,13 @@ initShortcuts(() => ({
|
|
|
676
698
|
|
|
677
699
|
// ─── Autosave (registered as update middleware) ──────────────────────────────
|
|
678
700
|
|
|
679
|
-
/** @type {
|
|
701
|
+
/** @type {number} */
|
|
680
702
|
const AUTO_SAVE_DELAY = 2000;
|
|
681
703
|
|
|
682
704
|
function scheduleAutosave() {
|
|
683
705
|
const tab = activeTab.value;
|
|
684
706
|
if (!tab?.fileHandle || !tab.doc.dirty) return;
|
|
685
|
-
clearTimeout(view.autosaveTimer);
|
|
707
|
+
if (view.autosaveTimer) clearTimeout(view.autosaveTimer);
|
|
686
708
|
view.autosaveTimer = setTimeout(async () => {
|
|
687
709
|
const t = activeTab.value;
|
|
688
710
|
if (t?.fileHandle && t.doc.dirty && "createWritable" in t.fileHandle) {
|
package/src/tabs/tab.js
CHANGED
|
@@ -7,7 +7,7 @@ import { reactive, effectScope } from "../reactivity.js";
|
|
|
7
7
|
* zoom: number;
|
|
8
8
|
* activeMedia: string | null;
|
|
9
9
|
* activeSelector: string | null;
|
|
10
|
-
* editingFunction:
|
|
10
|
+
* editingFunction: FunctionEditDef | null;
|
|
11
11
|
* featureToggles: Record<string, boolean>;
|
|
12
12
|
* styleSections: Record<string, boolean>;
|
|
13
13
|
* inspectorSections: Record<string, boolean>;
|
|
@@ -19,18 +19,19 @@ import { reactive, effectScope } from "../reactivity.js";
|
|
|
19
19
|
* stylebookFilter: string;
|
|
20
20
|
* stylebookCustomizedOnly: boolean;
|
|
21
21
|
* settingsTab: string;
|
|
22
|
-
* gitStatus:
|
|
23
|
-
* gitBranches:
|
|
22
|
+
* gitStatus: GitStatusResult | null;
|
|
23
|
+
* gitBranches: GitBranchesResult | null;
|
|
24
24
|
* gitCommitMessage: string;
|
|
25
25
|
* gitLoading: boolean;
|
|
26
26
|
* gitError: string | null;
|
|
27
|
-
*
|
|
27
|
+
* gitDiffState: GitDiffState | null;
|
|
28
|
+
* pendingInlineEdit: InlineEditDef | null;
|
|
28
29
|
* }} TabUi
|
|
29
30
|
*/
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
33
|
* @typedef {{
|
|
33
|
-
* document: Record<string,
|
|
34
|
+
* document: Record<string, unknown>;
|
|
34
35
|
* selection: (string | number)[] | null;
|
|
35
36
|
* }} HistorySnapshot
|
|
36
37
|
*/
|
|
@@ -40,9 +41,10 @@ import { reactive, effectScope } from "../reactivity.js";
|
|
|
40
41
|
* id: string;
|
|
41
42
|
* documentPath: string | null;
|
|
42
43
|
* fileHandle: FileSystemFileHandle | null;
|
|
43
|
-
*
|
|
44
|
+
* capabilities: { modes: string[] };
|
|
45
|
+
* scope: { stop(): void; run<T>(fn: () => T): T | undefined; [k: string]: unknown };
|
|
44
46
|
* doc: {
|
|
45
|
-
* document:
|
|
47
|
+
* document: JxMutableNode;
|
|
46
48
|
* content: { frontmatter: Record<string, unknown> };
|
|
47
49
|
* mode: string;
|
|
48
50
|
* sourceFormat: string | null;
|
|
@@ -52,14 +54,14 @@ import { reactive, effectScope } from "../reactivity.js";
|
|
|
52
54
|
* session: {
|
|
53
55
|
* selection: (string | number)[] | null;
|
|
54
56
|
* hover: (string | number)[] | null;
|
|
55
|
-
* clipboard:
|
|
56
|
-
* documentStack:
|
|
57
|
+
* clipboard: JxMutableNode | null;
|
|
58
|
+
* documentStack: DocumentStackEntry[];
|
|
57
59
|
* ui: TabUi;
|
|
58
60
|
* canvas: {
|
|
59
61
|
* status: string;
|
|
60
|
-
* scope: { stop(): void; [k: string]:
|
|
62
|
+
* scope: { stop(): void; [k: string]: unknown } | null;
|
|
61
63
|
* error: string | null;
|
|
62
|
-
* pendingInlineEdit:
|
|
64
|
+
* pendingInlineEdit: InlineEditDef | null;
|
|
63
65
|
* };
|
|
64
66
|
* };
|
|
65
67
|
* history: {
|
|
@@ -73,7 +75,7 @@ import { reactive, effectScope } from "../reactivity.js";
|
|
|
73
75
|
function createDefaultUi() {
|
|
74
76
|
return {
|
|
75
77
|
rightTab: "properties",
|
|
76
|
-
canvasMode: "
|
|
78
|
+
canvasMode: "edit",
|
|
77
79
|
zoom: 1,
|
|
78
80
|
activeMedia: null,
|
|
79
81
|
activeSelector: null,
|
|
@@ -94,10 +96,13 @@ function createDefaultUi() {
|
|
|
94
96
|
gitCommitMessage: "",
|
|
95
97
|
gitLoading: false,
|
|
96
98
|
gitError: null,
|
|
99
|
+
gitDiffState: null,
|
|
97
100
|
pendingInlineEdit: null,
|
|
98
101
|
};
|
|
99
102
|
}
|
|
100
103
|
|
|
104
|
+
const ALL_MODES = ["edit", "design", "preview", "source", "stylebook"];
|
|
105
|
+
|
|
101
106
|
/**
|
|
102
107
|
* Create a new tab with reactive doc/session/history trees, owned by an effectScope.
|
|
103
108
|
*
|
|
@@ -105,9 +110,10 @@ function createDefaultUi() {
|
|
|
105
110
|
* id: string;
|
|
106
111
|
* documentPath?: string | null;
|
|
107
112
|
* fileHandle?: FileSystemFileHandle | null;
|
|
108
|
-
* document: Record<string,
|
|
113
|
+
* document: Record<string, unknown>;
|
|
109
114
|
* frontmatter?: Record<string, unknown>;
|
|
110
115
|
* sourceFormat?: string | null;
|
|
116
|
+
* capabilities?: { modes?: string[] };
|
|
111
117
|
* }} opts
|
|
112
118
|
* @returns {Tab}
|
|
113
119
|
*/
|
|
@@ -118,46 +124,64 @@ export function createTab({
|
|
|
118
124
|
document,
|
|
119
125
|
frontmatter,
|
|
120
126
|
sourceFormat = null,
|
|
127
|
+
capabilities,
|
|
121
128
|
}) {
|
|
122
129
|
const scope = effectScope();
|
|
123
130
|
|
|
131
|
+
const resolvedModes = capabilities?.modes ?? inferModes(documentPath, sourceFormat);
|
|
132
|
+
|
|
124
133
|
const tab = /** @type {Tab} */ (
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
134
|
+
/** @type {unknown} */ (
|
|
135
|
+
scope.run(() => ({
|
|
136
|
+
id,
|
|
137
|
+
documentPath,
|
|
138
|
+
fileHandle,
|
|
139
|
+
capabilities: { modes: resolvedModes },
|
|
140
|
+
scope,
|
|
141
|
+
doc: reactive({
|
|
142
|
+
document,
|
|
143
|
+
sourceFormat,
|
|
144
|
+
content: { frontmatter: frontmatter || {} },
|
|
145
|
+
mode:
|
|
146
|
+
sourceFormat === "md"
|
|
138
147
|
? "content"
|
|
139
|
-
: "
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
148
|
+
: documentPath?.endsWith(".md")
|
|
149
|
+
? "content"
|
|
150
|
+
: "component",
|
|
151
|
+
handlersSource: null,
|
|
152
|
+
dirty: false,
|
|
153
|
+
}),
|
|
154
|
+
session: reactive({
|
|
155
|
+
selection: null,
|
|
156
|
+
hover: null,
|
|
157
|
+
clipboard: null,
|
|
158
|
+
documentStack: [],
|
|
159
|
+
ui: createDefaultUi(),
|
|
160
|
+
canvas: { status: "idle", scope: null, error: null, pendingInlineEdit: null },
|
|
161
|
+
}),
|
|
162
|
+
history: reactive({
|
|
163
|
+
snapshots: [{ document: structuredClone(document), selection: null }],
|
|
164
|
+
index: 0,
|
|
165
|
+
}),
|
|
166
|
+
}))
|
|
167
|
+
)
|
|
156
168
|
);
|
|
157
169
|
|
|
158
170
|
return tab;
|
|
159
171
|
}
|
|
160
172
|
|
|
173
|
+
/**
|
|
174
|
+
* @param {string | null | undefined} documentPath
|
|
175
|
+
* @param {string | null} sourceFormat
|
|
176
|
+
* @returns {string[]}
|
|
177
|
+
*/
|
|
178
|
+
function inferModes(documentPath, sourceFormat) {
|
|
179
|
+
if (documentPath === "project.json") return ["stylebook", "source"];
|
|
180
|
+
if (sourceFormat === "md" || documentPath?.endsWith(".md"))
|
|
181
|
+
return ["edit", "design", "preview", "source"];
|
|
182
|
+
return ALL_MODES;
|
|
183
|
+
}
|
|
184
|
+
|
|
161
185
|
/**
|
|
162
186
|
* Dispose a tab — stops its effectScope, killing all effects created within it.
|
|
163
187
|
*
|