@jxsuite/studio 0.28.5 → 0.30.1
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 +9368 -6586
- package/dist/studio.js.map +109 -83
- 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 +11 -10
- package/src/browse/browse-modal.ts +2 -2
- package/src/browse/browse.ts +20 -19
- package/src/canvas/canvas-diff.ts +3 -3
- package/src/canvas/canvas-helpers.ts +15 -2
- package/src/canvas/canvas-live-render.ts +168 -86
- package/src/canvas/canvas-patcher.ts +656 -0
- package/src/canvas/canvas-perf.ts +68 -0
- package/src/canvas/canvas-render.ts +135 -23
- package/src/canvas/canvas-subtree-render.ts +113 -0
- package/src/canvas/canvas-utils.ts +4 -0
- package/src/editor/component-inline-edit.ts +4 -35
- package/src/editor/context-menu.ts +96 -76
- package/src/editor/convert-to-component.ts +11 -2
- package/src/editor/convert-to-repeater.ts +4 -5
- package/src/editor/inline-edit.ts +9 -9
- package/src/editor/inline-format.ts +11 -11
- package/src/editor/merge-tags.ts +120 -0
- package/src/editor/shortcuts.ts +4 -4
- package/src/files/components.ts +37 -0
- package/src/files/file-ops.ts +28 -7
- package/src/files/files.ts +68 -24
- package/src/files/fs-events.ts +162 -0
- package/src/github/github-auth.ts +14 -2
- package/src/github/github-publish.ts +12 -2
- package/src/panels/activity-bar.ts +1 -1
- package/src/panels/ai-panel.ts +67 -39
- package/src/panels/block-action-bar.ts +72 -1
- package/src/panels/canvas-dnd.ts +55 -26
- package/src/panels/data-explorer.ts +5 -3
- package/src/panels/dnd.ts +14 -17
- package/src/panels/editors.ts +5 -23
- package/src/panels/elements-panel.ts +2 -12
- package/src/panels/events-panel.ts +1 -1
- package/src/panels/git-panel.ts +6 -6
- package/src/panels/head-panel.ts +1 -1
- package/src/panels/layers-panel.ts +177 -147
- package/src/panels/preview-render.ts +15 -0
- package/src/panels/properties-panel.ts +16 -27
- package/src/panels/quick-search.ts +2 -2
- package/src/panels/right-panel.ts +2 -6
- package/src/panels/shared.ts +3 -0
- package/src/panels/signals-panel.ts +43 -33
- package/src/panels/statusbar.ts +15 -6
- package/src/panels/style-panel.ts +3 -3
- package/src/panels/style-utils.ts +3 -3
- package/src/panels/stylebook-panel.ts +4 -4
- package/src/panels/tab-bar.ts +198 -0
- package/src/panels/tab-strip.ts +2 -2
- package/src/panels/toolbar.ts +6 -75
- package/src/platforms/devserver.ts +113 -35
- package/src/reactivity.ts +2 -0
- package/src/services/cem-export.ts +23 -2
- package/src/services/code-services.ts +16 -2
- package/src/services/monaco-setup.ts +2 -1
- package/src/settings/content-types-editor.ts +16 -16
- package/src/settings/css-vars-editor.ts +2 -2
- package/src/settings/defs-editor.ts +14 -14
- package/src/settings/general-settings.ts +6 -6
- package/src/settings/head-editor.ts +2 -2
- package/src/site-context.ts +1 -1
- package/src/state.ts +66 -12
- package/src/store.ts +15 -3
- package/src/studio.ts +88 -35
- package/src/tabs/patch-ops.ts +143 -0
- package/src/tabs/tab.ts +15 -1
- package/src/tabs/transact.ts +454 -27
- package/src/types.ts +71 -1
- package/src/ui/color-selector.ts +7 -7
- package/src/ui/icons.ts +0 -30
- package/src/ui/media-picker.ts +2 -2
- package/src/ui/panel-resize.ts +14 -8
- package/src/ui/unit-selector.ts +5 -2
- package/src/ui/value-selector.ts +3 -3
- package/src/utils/canvas-media.ts +6 -6
- package/src/utils/edit-display.ts +125 -83
- package/src/utils/google-fonts.ts +1 -1
- package/src/utils/inherited-style.ts +3 -2
- package/src/utils/studio-utils.ts +1 -1
- package/src/view.ts +4 -1
package/src/panels/toolbar.ts
CHANGED
|
@@ -5,13 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { html, render as litRender, nothing } from "lit-html";
|
|
8
|
-
import { updateSession
|
|
8
|
+
import { updateSession } from "../store";
|
|
9
9
|
import { redo as tabRedo, undo as tabUndo } from "../tabs/transact";
|
|
10
10
|
import { effect, effectScope } from "../reactivity";
|
|
11
11
|
import { activeTab } from "../workspace/workspace";
|
|
12
|
-
import type { DocumentStackEntry } from "../types";
|
|
13
|
-
import { getEffectiveMedia } from "../site-context";
|
|
14
|
-
import { mediaDisplayName } from "./shared";
|
|
15
12
|
import { applyPanelCollapse, view } from "../view";
|
|
16
13
|
import { getRecentProjects } from "../recent-projects";
|
|
17
14
|
import { openQuickSearch } from "./quick-search";
|
|
@@ -23,21 +20,9 @@ import type { EffectScope } from "@vue/reactivity";
|
|
|
23
20
|
import type { TemplateResult } from "lit-html";
|
|
24
21
|
|
|
25
22
|
interface ToolbarCtx {
|
|
26
|
-
navigateBack: () => void;
|
|
27
|
-
navigateToLevel: (level: number) => void;
|
|
28
23
|
openProject: () => void;
|
|
29
24
|
openFile?: (path: string) => void;
|
|
30
25
|
saveFile: () => void;
|
|
31
|
-
parseMediaEntries: (media: Record<string, string> | null | undefined) => {
|
|
32
|
-
sizeBreakpoints: {
|
|
33
|
-
name: string;
|
|
34
|
-
query: string;
|
|
35
|
-
width: number;
|
|
36
|
-
type: string;
|
|
37
|
-
}[];
|
|
38
|
-
featureQueries: { name: string; query: string }[];
|
|
39
|
-
baseWidth: number;
|
|
40
|
-
};
|
|
41
26
|
getCanvasMode: () => string;
|
|
42
27
|
setCanvasMode: (mode: string) => void;
|
|
43
28
|
renderCanvas: () => void;
|
|
@@ -54,7 +39,6 @@ let _scope: EffectScope | null = null;
|
|
|
54
39
|
|
|
55
40
|
const toolbarIconMap = {
|
|
56
41
|
"sp-icon-artboard": html`<sp-icon-artboard slot="icon"></sp-icon-artboard>`,
|
|
57
|
-
"sp-icon-back": html`<sp-icon-back slot="icon"></sp-icon-back>`,
|
|
58
42
|
"sp-icon-brush": html`<sp-icon-brush slot="icon"></sp-icon-brush>`,
|
|
59
43
|
"sp-icon-code": html`<sp-icon-code slot="icon"></sp-icon-code>`,
|
|
60
44
|
"sp-icon-delete": html`<sp-icon-delete slot="icon"></sp-icon-delete>`,
|
|
@@ -160,9 +144,9 @@ function minimalToolbarTemplate(ctx: ToolbarCtx) {
|
|
|
160
144
|
@change=${(e: Event) => {
|
|
161
145
|
const val = (e.target as unknown as HTMLInputElement).value;
|
|
162
146
|
if (val === "__new__") {
|
|
163
|
-
handleNewProject();
|
|
147
|
+
void handleNewProject();
|
|
164
148
|
} else {
|
|
165
|
-
ctx.openRecentProject(val);
|
|
149
|
+
void ctx.openRecentProject(val);
|
|
166
150
|
}
|
|
167
151
|
}}
|
|
168
152
|
>
|
|
@@ -298,65 +282,12 @@ function toolbarTemplate() {
|
|
|
298
282
|
|
|
299
283
|
const S = {
|
|
300
284
|
dirty: tab.doc.dirty,
|
|
301
|
-
document: tab.doc.document,
|
|
302
|
-
documentPath: tab.documentPath,
|
|
303
|
-
documentStack: tab.session.documentStack,
|
|
304
285
|
fileHandle: tab.fileHandle,
|
|
305
286
|
mode: tab.doc.mode,
|
|
306
287
|
selection: tab.session.selection,
|
|
307
288
|
ui: tab.session.ui,
|
|
308
289
|
};
|
|
309
290
|
const canvasMode = ctx.getCanvasMode();
|
|
310
|
-
const hasStack = S.documentStack && S.documentStack.length > 0;
|
|
311
|
-
|
|
312
|
-
const breadcrumbTpl = hasStack
|
|
313
|
-
? html`
|
|
314
|
-
<div class="breadcrumb">
|
|
315
|
-
<sp-action-button size="s" title="Return to parent document" @click=${ctx.navigateBack}>
|
|
316
|
-
${toolbarIconMap["sp-icon-back"]}Back
|
|
317
|
-
</sp-action-button>
|
|
318
|
-
${S.documentStack.map(
|
|
319
|
-
(frame: DocumentStackEntry, i: number) => html`
|
|
320
|
-
<span class="breadcrumb-item clickable" @click=${() => ctx.navigateToLevel(i)}
|
|
321
|
-
>${frame.documentPath?.split("/").pop() || "untitled"}</span
|
|
322
|
-
>
|
|
323
|
-
<span class="breadcrumb-sep"> › </span>
|
|
324
|
-
`,
|
|
325
|
-
)}
|
|
326
|
-
<span class="breadcrumb-item current">
|
|
327
|
-
${S.documentPath?.split("/").pop() || S.document.tagName || "document"}
|
|
328
|
-
</span>
|
|
329
|
-
</div>
|
|
330
|
-
`
|
|
331
|
-
: nothing;
|
|
332
|
-
|
|
333
|
-
const { featureQueries } = ctx.parseMediaEntries(getEffectiveMedia(S.document.$media));
|
|
334
|
-
const togglesTpl =
|
|
335
|
-
featureQueries.length > 0
|
|
336
|
-
? html`
|
|
337
|
-
<sp-action-group compact size="s">
|
|
338
|
-
${featureQueries.map(
|
|
339
|
-
(/** @type {{ name: string; query: string }} */ { name, query }) => html`
|
|
340
|
-
<sp-action-button
|
|
341
|
-
toggles
|
|
342
|
-
size="s"
|
|
343
|
-
title=${query}
|
|
344
|
-
?selected=${Boolean(S.ui.featureToggles[name])}
|
|
345
|
-
@click=${() => {
|
|
346
|
-
const newToggles = {
|
|
347
|
-
...S.ui.featureToggles,
|
|
348
|
-
[name]: !S.ui.featureToggles[name],
|
|
349
|
-
};
|
|
350
|
-
updateUi("featureToggles", newToggles);
|
|
351
|
-
}}
|
|
352
|
-
>
|
|
353
|
-
${mediaDisplayName(name)}
|
|
354
|
-
</sp-action-button>
|
|
355
|
-
`,
|
|
356
|
-
)}
|
|
357
|
-
</sp-action-group>
|
|
358
|
-
`
|
|
359
|
-
: nothing;
|
|
360
291
|
|
|
361
292
|
const modeSwitcherTpl = html`
|
|
362
293
|
<sp-action-group selects="single" size="s" compact>
|
|
@@ -488,9 +419,9 @@ function toolbarTemplate() {
|
|
|
488
419
|
@change=${(e: Event) => {
|
|
489
420
|
const val = (e.target as unknown as HTMLInputElement).value;
|
|
490
421
|
if (val === "__new__") {
|
|
491
|
-
handleNewProject();
|
|
422
|
+
void handleNewProject();
|
|
492
423
|
} else {
|
|
493
|
-
ctx.openRecentProject(val);
|
|
424
|
+
void ctx.openRecentProject(val);
|
|
494
425
|
}
|
|
495
426
|
}}
|
|
496
427
|
>
|
|
@@ -543,7 +474,7 @@ function toolbarTemplate() {
|
|
|
543
474
|
</sp-action-button>`
|
|
544
475
|
: nothing}
|
|
545
476
|
<div class="tb-spacer"></div>
|
|
546
|
-
${
|
|
477
|
+
${modeSwitcherTpl}
|
|
547
478
|
<sp-action-button
|
|
548
479
|
quiet
|
|
549
480
|
size="s"
|
|
@@ -8,6 +8,26 @@
|
|
|
8
8
|
* See spec/desktop.md §8 for the full specification.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import type { ProjectConfig } from "@jxsuite/schema/types";
|
|
12
|
+
import type { DirEntry, FsEvent, RenameResult } from "../types";
|
|
13
|
+
|
|
14
|
+
/** A directory entry from the server, tolerating extra wire fields. */
|
|
15
|
+
type WireDirEntry = DirEntry & Record<string, unknown>;
|
|
16
|
+
|
|
17
|
+
/** Parse a fetch Response body as JSON, asserting the expected shape at the boundary. */
|
|
18
|
+
async function readJson<T>(res: Response): Promise<T> {
|
|
19
|
+
return (await res.json()) as T;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface ErrorBody {
|
|
23
|
+
error?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface SiteEntry {
|
|
27
|
+
config: unknown;
|
|
28
|
+
path: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
11
31
|
/**
|
|
12
32
|
* Create a DevServerPlatform instance.
|
|
13
33
|
*
|
|
@@ -58,7 +78,7 @@ export function createDevServerPlatform() {
|
|
|
58
78
|
set projectRoot(v) {
|
|
59
79
|
_projectRoot = v || "";
|
|
60
80
|
if (_projectRoot) {
|
|
61
|
-
this.activate(_projectRoot);
|
|
81
|
+
void this.activate(_projectRoot);
|
|
62
82
|
}
|
|
63
83
|
},
|
|
64
84
|
|
|
@@ -109,19 +129,16 @@ export function createDevServerPlatform() {
|
|
|
109
129
|
}
|
|
110
130
|
|
|
111
131
|
const file = await siteHandle.getFile();
|
|
112
|
-
const config = JSON.parse(await file.text());
|
|
132
|
+
const config = JSON.parse(await file.text()) as ProjectConfig;
|
|
113
133
|
|
|
114
134
|
// Resolve server-relative path by matching against known sites
|
|
115
135
|
const sitesRes = await fetch("/__studio/sites");
|
|
116
136
|
if (!sitesRes.ok) {
|
|
117
137
|
throw new Error("Failed to fetch site list from server");
|
|
118
138
|
}
|
|
119
|
-
const sites = await sitesRes
|
|
139
|
+
const sites = await readJson<SiteEntry[]>(sitesRes);
|
|
120
140
|
const match = sites.find(
|
|
121
|
-
|
|
122
|
-
config: unknown;
|
|
123
|
-
path: string;
|
|
124
|
-
}) => JSON.stringify(s.config) === JSON.stringify(config),
|
|
141
|
+
(s: SiteEntry) => JSON.stringify(s.config) === JSON.stringify(config),
|
|
125
142
|
);
|
|
126
143
|
|
|
127
144
|
if (!match) {
|
|
@@ -132,7 +149,7 @@ export function createDevServerPlatform() {
|
|
|
132
149
|
if (!findRes.ok) {
|
|
133
150
|
throw new Error("Could not locate project on disk");
|
|
134
151
|
}
|
|
135
|
-
const found = await findRes
|
|
152
|
+
const found = await readJson<{ path?: string }>(findRes);
|
|
136
153
|
if (!found.path) {
|
|
137
154
|
throw new Error(`Could not find project directory "${dirHandle.name}"`);
|
|
138
155
|
}
|
|
@@ -147,7 +164,7 @@ export function createDevServerPlatform() {
|
|
|
147
164
|
return {
|
|
148
165
|
config,
|
|
149
166
|
handle: {
|
|
150
|
-
name: config.name || _projectRoot.split("/").pop()
|
|
167
|
+
name: config.name || _projectRoot.split("/").pop()!,
|
|
151
168
|
projectConfig: config,
|
|
152
169
|
root: _projectRoot,
|
|
153
170
|
},
|
|
@@ -164,8 +181,17 @@ export function createDevServerPlatform() {
|
|
|
164
181
|
fetch("/__studio/project"),
|
|
165
182
|
fetch("/__studio/project-info?dir=."),
|
|
166
183
|
]);
|
|
167
|
-
const meta = projectRes.ok
|
|
168
|
-
|
|
184
|
+
const meta = projectRes.ok
|
|
185
|
+
? await readJson<{ name: string; root: string }>(projectRes)
|
|
186
|
+
: { name: "project", root: "." };
|
|
187
|
+
const info = infoRes.ok
|
|
188
|
+
? await readJson<{
|
|
189
|
+
isSiteProject: boolean;
|
|
190
|
+
projectConfig?: ProjectConfig | null;
|
|
191
|
+
directories?: string[];
|
|
192
|
+
[key: string]: unknown;
|
|
193
|
+
}>(infoRes)
|
|
194
|
+
: { isSiteProject: false };
|
|
169
195
|
return { info, meta };
|
|
170
196
|
} catch {
|
|
171
197
|
return null;
|
|
@@ -196,7 +222,7 @@ export function createDevServerPlatform() {
|
|
|
196
222
|
method: "POST",
|
|
197
223
|
});
|
|
198
224
|
if (!res.ok) {
|
|
199
|
-
const data = await res
|
|
225
|
+
const data = await readJson<ErrorBody>(res);
|
|
200
226
|
throw new Error(data.error || "Failed to create project");
|
|
201
227
|
}
|
|
202
228
|
return await res.json();
|
|
@@ -210,7 +236,7 @@ export function createDevServerPlatform() {
|
|
|
210
236
|
if (!res.ok) {
|
|
211
237
|
throw new Error(`Failed to list directory: ${dir}`);
|
|
212
238
|
}
|
|
213
|
-
const entries = await res
|
|
239
|
+
const entries = await readJson<WireDirEntry[]>(res);
|
|
214
240
|
for (const e of entries) {
|
|
215
241
|
e.path = stripRoot(e.path);
|
|
216
242
|
}
|
|
@@ -223,7 +249,7 @@ export function createDevServerPlatform() {
|
|
|
223
249
|
if (!res.ok) {
|
|
224
250
|
throw new Error(`Failed to read file: ${path}`);
|
|
225
251
|
}
|
|
226
|
-
const data = await res
|
|
252
|
+
const data = await readJson<{ content: string }>(res);
|
|
227
253
|
return data.content;
|
|
228
254
|
},
|
|
229
255
|
|
|
@@ -272,7 +298,7 @@ export function createDevServerPlatform() {
|
|
|
272
298
|
* @param {string} from
|
|
273
299
|
* @param {string} to
|
|
274
300
|
*/
|
|
275
|
-
async renameFile(from: string, to: string) {
|
|
301
|
+
async renameFile(from: string, to: string): Promise<RenameResult> {
|
|
276
302
|
const res = await fetch("/__studio/file/rename", {
|
|
277
303
|
body: JSON.stringify({ from: serverPath(from), to: serverPath(to) }),
|
|
278
304
|
headers: { "Content-Type": "application/json" },
|
|
@@ -281,6 +307,58 @@ export function createDevServerPlatform() {
|
|
|
281
307
|
if (!res.ok) {
|
|
282
308
|
throw new Error(`Failed to rename: ${from} → ${to}`);
|
|
283
309
|
}
|
|
310
|
+
const report = await readJson<RenameResult>(res);
|
|
311
|
+
// Map server-root-relative report paths back to project-relative for the studio.
|
|
312
|
+
if (typeof report.from === "string") {
|
|
313
|
+
report.from = stripRoot(report.from);
|
|
314
|
+
}
|
|
315
|
+
if (typeof report.to === "string") {
|
|
316
|
+
report.to = stripRoot(report.to);
|
|
317
|
+
}
|
|
318
|
+
for (const f of report.references?.files ?? []) {
|
|
319
|
+
f.path = stripRoot(f.path);
|
|
320
|
+
}
|
|
321
|
+
for (const e of report.errors ?? []) {
|
|
322
|
+
e.path = stripRoot(e.path);
|
|
323
|
+
}
|
|
324
|
+
return report;
|
|
325
|
+
},
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Subscribe to filesystem change events over the dev server's SSE stream. Listens for the named
|
|
329
|
+
* "fs" event (the preview iframe's default `onmessage` ignores it), strips paths to
|
|
330
|
+
* project-relative, and drops events for sibling projects outside the active root.
|
|
331
|
+
*/
|
|
332
|
+
subscribeFileEvents(handler: (events: FsEvent[]) => void) {
|
|
333
|
+
if (typeof EventSource === "undefined") {
|
|
334
|
+
return () => {};
|
|
335
|
+
}
|
|
336
|
+
const es = new EventSource("/__reload");
|
|
337
|
+
es.addEventListener("fs", (ev: MessageEvent) => {
|
|
338
|
+
let payload: { events?: FsEvent[] };
|
|
339
|
+
try {
|
|
340
|
+
payload = JSON.parse(ev.data as string) as { events?: FsEvent[] };
|
|
341
|
+
} catch {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const events: FsEvent[] = [];
|
|
345
|
+
for (const e of payload.events ?? []) {
|
|
346
|
+
const raw = e.path.replaceAll("\\", "/");
|
|
347
|
+
if (_projectRoot && raw !== _projectRoot && !raw.startsWith(`${_projectRoot}/`)) {
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
const path = stripRoot(raw);
|
|
351
|
+
if (path && !path.startsWith("..")) {
|
|
352
|
+
events.push({ isDir: e.isDir, path, type: e.type });
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (events.length > 0) {
|
|
356
|
+
handler(events);
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
return () => {
|
|
360
|
+
es.close();
|
|
361
|
+
};
|
|
284
362
|
},
|
|
285
363
|
|
|
286
364
|
/** @param {string} _path */
|
|
@@ -392,7 +470,7 @@ export function createDevServerPlatform() {
|
|
|
392
470
|
method: "POST",
|
|
393
471
|
});
|
|
394
472
|
if (res.ok) {
|
|
395
|
-
const body = await res
|
|
473
|
+
const body = await readJson<{ path?: string }>(res);
|
|
396
474
|
return body.path || null;
|
|
397
475
|
}
|
|
398
476
|
} catch {}
|
|
@@ -412,7 +490,7 @@ export function createDevServerPlatform() {
|
|
|
412
490
|
if (!res.ok) {
|
|
413
491
|
return [];
|
|
414
492
|
}
|
|
415
|
-
const entries = await res
|
|
493
|
+
const entries = await readJson<WireDirEntry[]>(res);
|
|
416
494
|
for (const e of entries) {
|
|
417
495
|
e.path = stripRoot(e.path);
|
|
418
496
|
}
|
|
@@ -427,7 +505,7 @@ export function createDevServerPlatform() {
|
|
|
427
505
|
if (!res.ok) {
|
|
428
506
|
return [];
|
|
429
507
|
}
|
|
430
|
-
const body = await res
|
|
508
|
+
const body = await readJson<{ formats?: unknown[] }>(res);
|
|
431
509
|
return body.formats ?? [];
|
|
432
510
|
},
|
|
433
511
|
|
|
@@ -442,7 +520,7 @@ export function createDevServerPlatform() {
|
|
|
442
520
|
headers: { "Content-Type": "application/json" },
|
|
443
521
|
method: "POST",
|
|
444
522
|
});
|
|
445
|
-
const data = await res
|
|
523
|
+
const data = await readJson<{ error?: string; result?: unknown }>(res);
|
|
446
524
|
if (!res.ok) {
|
|
447
525
|
throw new Error(data.error || "Format action failed");
|
|
448
526
|
}
|
|
@@ -468,7 +546,7 @@ export function createDevServerPlatform() {
|
|
|
468
546
|
if (!res.ok) {
|
|
469
547
|
return null;
|
|
470
548
|
}
|
|
471
|
-
const { schema } = await res
|
|
549
|
+
const { schema } = await readJson<{ schema: unknown }>(res);
|
|
472
550
|
return schema;
|
|
473
551
|
},
|
|
474
552
|
|
|
@@ -508,7 +586,7 @@ export function createDevServerPlatform() {
|
|
|
508
586
|
method: "POST",
|
|
509
587
|
});
|
|
510
588
|
if (!res.ok) {
|
|
511
|
-
const body = await res
|
|
589
|
+
const body = await readJson<ErrorBody>(res);
|
|
512
590
|
throw new Error(body.error);
|
|
513
591
|
}
|
|
514
592
|
return await res.json();
|
|
@@ -522,7 +600,7 @@ export function createDevServerPlatform() {
|
|
|
522
600
|
method: "POST",
|
|
523
601
|
});
|
|
524
602
|
if (!res.ok) {
|
|
525
|
-
const body = await res
|
|
603
|
+
const body = await readJson<ErrorBody>(res);
|
|
526
604
|
throw new Error(body.error);
|
|
527
605
|
}
|
|
528
606
|
return await res.json();
|
|
@@ -536,7 +614,7 @@ export function createDevServerPlatform() {
|
|
|
536
614
|
method: "POST",
|
|
537
615
|
});
|
|
538
616
|
if (!res.ok) {
|
|
539
|
-
const body = await res
|
|
617
|
+
const body = await readJson<ErrorBody>(res);
|
|
540
618
|
throw new Error(body.error);
|
|
541
619
|
}
|
|
542
620
|
return await res.json();
|
|
@@ -550,7 +628,7 @@ export function createDevServerPlatform() {
|
|
|
550
628
|
method: "POST",
|
|
551
629
|
});
|
|
552
630
|
if (!res.ok) {
|
|
553
|
-
const body = await res
|
|
631
|
+
const body = await readJson<ErrorBody>(res);
|
|
554
632
|
throw new Error(body.error);
|
|
555
633
|
}
|
|
556
634
|
return await res.json();
|
|
@@ -559,7 +637,7 @@ export function createDevServerPlatform() {
|
|
|
559
637
|
async gitPull() {
|
|
560
638
|
const res = await fetch("/__studio/git/pull", { method: "POST" });
|
|
561
639
|
if (!res.ok) {
|
|
562
|
-
const body = await res
|
|
640
|
+
const body = await readJson<ErrorBody>(res);
|
|
563
641
|
throw new Error(body.error);
|
|
564
642
|
}
|
|
565
643
|
return await res.json();
|
|
@@ -568,7 +646,7 @@ export function createDevServerPlatform() {
|
|
|
568
646
|
async gitFetch() {
|
|
569
647
|
const res = await fetch("/__studio/git/fetch", { method: "POST" });
|
|
570
648
|
if (!res.ok) {
|
|
571
|
-
const body = await res
|
|
649
|
+
const body = await readJson<ErrorBody>(res);
|
|
572
650
|
throw new Error(body.error);
|
|
573
651
|
}
|
|
574
652
|
return await res.json();
|
|
@@ -582,7 +660,7 @@ export function createDevServerPlatform() {
|
|
|
582
660
|
method: "POST",
|
|
583
661
|
});
|
|
584
662
|
if (!res.ok) {
|
|
585
|
-
const body = await res
|
|
663
|
+
const body = await readJson<ErrorBody>(res);
|
|
586
664
|
throw new Error(body.error);
|
|
587
665
|
}
|
|
588
666
|
return await res.json();
|
|
@@ -596,7 +674,7 @@ export function createDevServerPlatform() {
|
|
|
596
674
|
method: "POST",
|
|
597
675
|
});
|
|
598
676
|
if (!res.ok) {
|
|
599
|
-
const body = await res
|
|
677
|
+
const body = await readJson<ErrorBody>(res);
|
|
600
678
|
throw new Error(body.error);
|
|
601
679
|
}
|
|
602
680
|
return await res.json();
|
|
@@ -621,7 +699,7 @@ export function createDevServerPlatform() {
|
|
|
621
699
|
if (!res.ok) {
|
|
622
700
|
throw new Error(await res.text());
|
|
623
701
|
}
|
|
624
|
-
const data = await res
|
|
702
|
+
const data = await readJson<{ content: string }>(res);
|
|
625
703
|
return data.content;
|
|
626
704
|
},
|
|
627
705
|
|
|
@@ -633,7 +711,7 @@ export function createDevServerPlatform() {
|
|
|
633
711
|
method: "POST",
|
|
634
712
|
});
|
|
635
713
|
if (!res.ok) {
|
|
636
|
-
const body = await res
|
|
714
|
+
const body = await readJson<ErrorBody>(res);
|
|
637
715
|
throw new Error(body.error);
|
|
638
716
|
}
|
|
639
717
|
return await res.json();
|
|
@@ -647,7 +725,7 @@ export function createDevServerPlatform() {
|
|
|
647
725
|
method: "POST",
|
|
648
726
|
});
|
|
649
727
|
if (!res.ok) {
|
|
650
|
-
const body = await res
|
|
728
|
+
const body = await readJson<ErrorBody>(res);
|
|
651
729
|
throw new Error(body.error);
|
|
652
730
|
}
|
|
653
731
|
return await res.json();
|
|
@@ -656,7 +734,7 @@ export function createDevServerPlatform() {
|
|
|
656
734
|
async gitInit() {
|
|
657
735
|
const res = await fetch("/__studio/git/init", { method: "POST" });
|
|
658
736
|
if (!res.ok) {
|
|
659
|
-
const body = await res
|
|
737
|
+
const body = await readJson<ErrorBody>(res);
|
|
660
738
|
throw new Error(body.error);
|
|
661
739
|
}
|
|
662
740
|
},
|
|
@@ -672,7 +750,7 @@ export function createDevServerPlatform() {
|
|
|
672
750
|
method: "POST",
|
|
673
751
|
});
|
|
674
752
|
if (!res.ok) {
|
|
675
|
-
const body = await res
|
|
753
|
+
const body = await readJson<ErrorBody>(res);
|
|
676
754
|
throw new Error(body.error);
|
|
677
755
|
}
|
|
678
756
|
},
|
|
@@ -692,7 +770,7 @@ export function createDevServerPlatform() {
|
|
|
692
770
|
method: "POST",
|
|
693
771
|
});
|
|
694
772
|
if (!res.ok) {
|
|
695
|
-
const body = await res
|
|
773
|
+
const body = await readJson<ErrorBody>(res);
|
|
696
774
|
throw new Error(body.error);
|
|
697
775
|
}
|
|
698
776
|
return await res.json();
|
|
@@ -706,7 +784,7 @@ export function createDevServerPlatform() {
|
|
|
706
784
|
method: "POST",
|
|
707
785
|
});
|
|
708
786
|
if (!res.ok) {
|
|
709
|
-
const body = await res
|
|
787
|
+
const body = await readJson<ErrorBody>(res);
|
|
710
788
|
throw new Error(body.error);
|
|
711
789
|
}
|
|
712
790
|
return await res.json();
|
package/src/reactivity.ts
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import type { CemParameter, JxMutableNode, JxStateObject } from "@jxsuite/schema/types";
|
|
3
3
|
import { isFunctionDef, isJsonObject } from "@jxsuite/schema/guards";
|
|
4
4
|
|
|
5
|
-
/** Collect slot elements from the document tree. */
|
|
5
|
+
/** Collect slot elements from the document tree. Whitespace-only names count as unnamed. */
|
|
6
6
|
export function collectSlots(node?: JxMutableNode | null, slots: string[] = []) {
|
|
7
7
|
if (node?.tagName === "slot") {
|
|
8
8
|
const name = node.attributes?.name;
|
|
9
|
-
slots.push(typeof name === "string" ? name : "");
|
|
9
|
+
slots.push(typeof name === "string" ? name.trim() : "");
|
|
10
10
|
}
|
|
11
11
|
if (Array.isArray(node?.children)) {
|
|
12
12
|
for (const c of node.children) {
|
|
@@ -16,6 +16,27 @@ export function collectSlots(node?: JxMutableNode | null, slots: string[] = [])
|
|
|
16
16
|
return slots;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Validate slot usage in a component definition. Returns a warning message or null when valid. Only
|
|
21
|
+
* static children are walked — slots inside $switch cases are mutually exclusive branches and are
|
|
22
|
+
* not counted (v1 limitation).
|
|
23
|
+
*/
|
|
24
|
+
export function validateComponentSlots(doc: JxMutableNode): string | null {
|
|
25
|
+
const names = collectSlots(doc);
|
|
26
|
+
const unnamed = names.filter((n) => n === "").length;
|
|
27
|
+
if (unnamed > 1) {
|
|
28
|
+
return `Component has ${unnamed} unnamed <slot> elements — only one default slot is allowed. Give extra slots a name attribute.`;
|
|
29
|
+
}
|
|
30
|
+
const seen = new Set<string>();
|
|
31
|
+
for (const n of names) {
|
|
32
|
+
if (n && seen.has(n)) {
|
|
33
|
+
return `Duplicate slot name "${n}" — slot names must be unique within a component.`;
|
|
34
|
+
}
|
|
35
|
+
seen.add(n);
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
19
40
|
/**
|
|
20
41
|
* Generate and download a CEM 2.1.0 manifest for the current document.
|
|
21
42
|
*
|
|
@@ -43,8 +43,18 @@ export async function locateDocument(name: string) {
|
|
|
43
43
|
return platform.locateFile(name);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/** Schema returned by a plugin's `$prototype` module (loose superset used across panels). */
|
|
47
|
+
export interface PluginSchema {
|
|
48
|
+
type?: string;
|
|
49
|
+
description?: string;
|
|
50
|
+
properties?: Record<string, unknown>;
|
|
51
|
+
required?: string[];
|
|
52
|
+
returns?: { type?: string };
|
|
53
|
+
[key: string]: unknown;
|
|
54
|
+
}
|
|
55
|
+
|
|
46
56
|
/** Cache of plugin schemas keyed by "$src::$prototype". */
|
|
47
|
-
export const pluginSchemaCache = new Map();
|
|
57
|
+
export const pluginSchemaCache = new Map<string, PluginSchema | null>();
|
|
48
58
|
|
|
49
59
|
/**
|
|
50
60
|
* Fetch and cache the schema for a $prototype module via the server. Works for both external
|
|
@@ -77,7 +87,11 @@ export async function fetchPluginSchema(
|
|
|
77
87
|
}
|
|
78
88
|
const base =
|
|
79
89
|
!importedPath && state.documentPath ? `${location.origin}/${state.documentPath}` : undefined;
|
|
80
|
-
const schema = await platform.fetchPluginSchema(
|
|
90
|
+
const schema = (await platform.fetchPluginSchema(
|
|
91
|
+
src,
|
|
92
|
+
def.$prototype,
|
|
93
|
+
base,
|
|
94
|
+
)) as PluginSchema | null;
|
|
81
95
|
pluginSchemaCache.set(cacheKey, schema);
|
|
82
96
|
return schema;
|
|
83
97
|
} catch {
|
|
@@ -18,11 +18,12 @@ const WORKER_PATHS: Record<string, string> = {
|
|
|
18
18
|
|
|
19
19
|
self.MonacoEnvironment = {
|
|
20
20
|
getWorker(_, label: string) {
|
|
21
|
-
const path = WORKER_PATHS[label] || WORKER_PATHS.editorWorkerService
|
|
21
|
+
const path = WORKER_PATHS[label] || WORKER_PATHS.editorWorkerService!;
|
|
22
22
|
return new Worker(path, { type: "module" });
|
|
23
23
|
},
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
+
// oxlint-disable-next-line typescript/no-unsafe-call, typescript/no-unsafe-member-access -- jsonDefaults is imported from Monaco's untyped ESM contribution (see @ts-expect-error above); no type declarations exist for this named export
|
|
26
27
|
jsonDefaults.setDiagnosticsOptions({
|
|
27
28
|
allowComments: false,
|
|
28
29
|
schemas: [
|