@jxsuite/studio 0.27.0 → 0.28.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 +33900 -56705
- package/dist/studio.js.map +128 -396
- package/package.json +4 -10
- package/src/browse/browse.ts +62 -22
- package/src/canvas/canvas-live-render.ts +57 -55
- package/src/canvas/canvas-render.ts +50 -21
- package/src/editor/context-menu.ts +4 -1
- package/src/editor/convert-to-component.ts +8 -2
- package/src/editor/inline-edit.ts +7 -2
- package/src/editor/insertion-helper.ts +12 -3
- package/src/editor/slash-menu.ts +5 -1
- package/src/files/file-ops.ts +121 -102
- package/src/files/files.ts +53 -35
- package/src/format/constraints.ts +55 -0
- package/src/format/format-host.ts +212 -0
- package/src/new-project/new-project-modal.ts +18 -3
- package/src/panels/ai-panel.ts +3 -1
- package/src/panels/block-action-bar.ts +4 -2
- package/src/panels/canvas-dnd.ts +37 -8
- package/src/panels/data-explorer.ts +9 -2
- package/src/panels/editors.ts +1 -1
- package/src/panels/git-panel.ts +18 -8
- package/src/panels/head-panel.ts +12 -5
- package/src/panels/left-panel.ts +7 -4
- package/src/panels/panel-events.ts +3 -1
- package/src/panels/properties-panel.ts +9 -3
- package/src/panels/quick-search.ts +10 -10
- package/src/panels/right-panel.ts +6 -2
- package/src/panels/shared.ts +6 -1
- package/src/panels/signals-panel.ts +35 -10
- package/src/panels/style-inputs.ts +5 -1
- package/src/panels/style-panel.ts +6 -2
- package/src/panels/stylebook-layers-panel.ts +12 -3
- package/src/panels/stylebook-panel.ts +24 -8
- package/src/panels/toolbar.ts +16 -3
- package/src/platforms/devserver.ts +32 -3
- package/src/resize-edges.ts +12 -2
- package/src/services/cem-export.ts +9 -3
- package/src/settings/content-types-editor.ts +7 -2
- package/src/settings/defs-editor.ts +6 -1
- package/src/settings/general-settings.ts +3 -1
- package/src/settings/schema-field-ui.ts +11 -3
- package/src/settings/settings-modal.ts +4 -1
- package/src/state.ts +5 -1
- package/src/studio.ts +27 -16
- package/src/tabs/tab.ts +29 -6
- package/src/tabs/transact.ts +4 -1
- package/src/types.ts +15 -6
- package/src/ui/button-group.ts +6 -1
- package/src/ui/expression-editor.ts +14 -3
- package/src/ui/layers.ts +5 -1
- package/src/ui/unit-selector.ts +6 -1
- package/src/utils/canvas-media.ts +1 -1
- package/src/utils/google-fonts.ts +4 -1
- package/src/utils/studio-utils.ts +7 -1
- package/src/view.ts +8 -2
- package/src/markdown/md-allowlist.ts +0 -104
- package/src/markdown/md-convert.ts +0 -846
package/src/panels/shared.ts
CHANGED
|
@@ -79,7 +79,12 @@ export function defaultDef(tag: string) {
|
|
|
79
79
|
def.children = [
|
|
80
80
|
{
|
|
81
81
|
tagName: "thead",
|
|
82
|
-
children: [
|
|
82
|
+
children: [
|
|
83
|
+
{
|
|
84
|
+
tagName: "tr",
|
|
85
|
+
children: [{ tagName: "th", textContent: "Header" }],
|
|
86
|
+
},
|
|
87
|
+
],
|
|
83
88
|
},
|
|
84
89
|
{
|
|
85
90
|
tagName: "tbody",
|
|
@@ -420,14 +420,20 @@ export function renderSignalsTemplate(S: SignalsPanelState, ctx: SignalsPanelCtx
|
|
|
420
420
|
mutateAddDef(
|
|
421
421
|
t,
|
|
422
422
|
n,
|
|
423
|
-
/** @type {Record<string, JsonValue>} */
|
|
423
|
+
/** @type {Record<string, JsonValue>} */ {
|
|
424
|
+
$prototype: protoName,
|
|
425
|
+
},
|
|
424
426
|
),
|
|
425
427
|
);
|
|
426
428
|
expandedSignal = n;
|
|
427
429
|
if (src) {
|
|
428
430
|
fetchPluginSchema(
|
|
429
431
|
{ $prototype: protoName, $src: src },
|
|
430
|
-
{
|
|
432
|
+
{
|
|
433
|
+
...(S.documentPath != null && {
|
|
434
|
+
documentPath: S.documentPath,
|
|
435
|
+
}),
|
|
436
|
+
},
|
|
431
437
|
).then(() => ctx.renderLeftPanel());
|
|
432
438
|
} else {
|
|
433
439
|
ctx.renderLeftPanel();
|
|
@@ -683,7 +689,10 @@ function renderSignalEditorTemplate(
|
|
|
683
689
|
exprNode,
|
|
684
690
|
(newNode: any) =>
|
|
685
691
|
transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { $expression: newNode })),
|
|
686
|
-
{
|
|
692
|
+
{
|
|
693
|
+
stateDefs: Object.keys(S.document.state || {}),
|
|
694
|
+
allowEventRef: false,
|
|
695
|
+
},
|
|
687
696
|
)}
|
|
688
697
|
`;
|
|
689
698
|
}
|
|
@@ -830,7 +839,9 @@ function renderFunctionFields(
|
|
|
830
839
|
quiet
|
|
831
840
|
title="Open in code editor"
|
|
832
841
|
@click=${() => {
|
|
833
|
-
ctx.updateSession({
|
|
842
|
+
ctx.updateSession({
|
|
843
|
+
ui: { editingFunction: { type: "def", defName: name } },
|
|
844
|
+
});
|
|
834
845
|
ctx.renderCanvas();
|
|
835
846
|
}}
|
|
836
847
|
>
|
|
@@ -940,7 +951,10 @@ function renderParameterEditorTemplate(
|
|
|
940
951
|
style="flex:1"
|
|
941
952
|
@change=${(e: Event) => {
|
|
942
953
|
const next = [...params];
|
|
943
|
-
next[i] = {
|
|
954
|
+
next[i] = {
|
|
955
|
+
...next[i],
|
|
956
|
+
name: (e.target as HTMLInputElement).value,
|
|
957
|
+
};
|
|
944
958
|
transactDoc(activeTab.value, (t) =>
|
|
945
959
|
mutateUpdateDef(t, name, { parameters: next }),
|
|
946
960
|
);
|
|
@@ -995,7 +1009,9 @@ function renderParameterEditorTemplate(
|
|
|
995
1009
|
@click=${() => {
|
|
996
1010
|
const next = params.filter((_: unknown, j: number) => j !== i);
|
|
997
1011
|
transactDoc(activeTab.value, (t) =>
|
|
998
|
-
mutateUpdateDef(t, name, {
|
|
1012
|
+
mutateUpdateDef(t, name, {
|
|
1013
|
+
parameters: next.length ? next : undefined,
|
|
1014
|
+
}),
|
|
999
1015
|
);
|
|
1000
1016
|
}}
|
|
1001
1017
|
>×</span
|
|
@@ -1007,7 +1023,9 @@ function renderParameterEditorTemplate(
|
|
|
1007
1023
|
class="kv-add"
|
|
1008
1024
|
@click=${() =>
|
|
1009
1025
|
transactDoc(activeTab.value, (t) =>
|
|
1010
|
-
mutateUpdateDef(t, name, {
|
|
1026
|
+
mutateUpdateDef(t, name, {
|
|
1027
|
+
parameters: [...params, { name: "" }],
|
|
1028
|
+
}),
|
|
1011
1029
|
)}
|
|
1012
1030
|
>
|
|
1013
1031
|
+ Add parameter
|
|
@@ -1046,7 +1064,10 @@ function renderEmitsEditorTemplate(S: SignalsPanelState, name: string, def: Sign
|
|
|
1046
1064
|
style="flex:1"
|
|
1047
1065
|
@change=${(e: Event) => {
|
|
1048
1066
|
const next = [...emits];
|
|
1049
|
-
next[i] = {
|
|
1067
|
+
next[i] = {
|
|
1068
|
+
...next[i],
|
|
1069
|
+
name: (e.target as HTMLInputElement).value,
|
|
1070
|
+
};
|
|
1050
1071
|
transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { emits: next }));
|
|
1051
1072
|
}}
|
|
1052
1073
|
/>
|
|
@@ -1289,7 +1310,9 @@ export function renderSchemaFieldsTemplate(
|
|
|
1289
1310
|
? parseInt((e.target as HTMLInputElement).value, 10)
|
|
1290
1311
|
: parseFloat((e.target as HTMLInputElement).value);
|
|
1291
1312
|
transactDoc(activeTab.value, (t) =>
|
|
1292
|
-
mutateUpdateDef(t, name, {
|
|
1313
|
+
mutateUpdateDef(t, name, {
|
|
1314
|
+
[prop]: isNaN(parsed) ? undefined : parsed,
|
|
1315
|
+
}),
|
|
1293
1316
|
);
|
|
1294
1317
|
}, 400);
|
|
1295
1318
|
}}
|
|
@@ -1375,7 +1398,9 @@ export function renderSchemaFieldsTemplate(
|
|
|
1375
1398
|
@click=${() => {
|
|
1376
1399
|
const updated = rows.filter((_: unknown, i: number) => i !== idx);
|
|
1377
1400
|
transactDoc(activeTab.value, (t) =>
|
|
1378
|
-
mutateUpdateDef(t, name, {
|
|
1401
|
+
mutateUpdateDef(t, name, {
|
|
1402
|
+
[prop]: updated.length ? updated : undefined,
|
|
1403
|
+
}),
|
|
1379
1404
|
);
|
|
1380
1405
|
ctx?.renderLeftPanel();
|
|
1381
1406
|
}}
|
|
@@ -163,7 +163,11 @@ export function buildFontOptions(
|
|
|
163
163
|
* @param {string} prop @param {string} value @param {(value: string) => void} onChange
|
|
164
164
|
*/
|
|
165
165
|
export function renderComboboxInput(
|
|
166
|
-
entry: {
|
|
166
|
+
entry: {
|
|
167
|
+
enum?: string[];
|
|
168
|
+
examples?: string[];
|
|
169
|
+
presets?: { title: string; value: string }[];
|
|
170
|
+
},
|
|
167
171
|
prop: string,
|
|
168
172
|
value: string,
|
|
169
173
|
onChange: (value: string) => void,
|
|
@@ -246,7 +246,9 @@ function renderShorthandRow(
|
|
|
246
246
|
mutateFn(t, shortProp, compress(vals as string[]));
|
|
247
247
|
});
|
|
248
248
|
},
|
|
249
|
-
{
|
|
249
|
+
{
|
|
250
|
+
placeholder: !lVal && inherited[name] ? String(inherited[name]) : "",
|
|
251
|
+
},
|
|
250
252
|
)}
|
|
251
253
|
</div>
|
|
252
254
|
`;
|
|
@@ -773,7 +775,9 @@ function styleSidebarTemplate(
|
|
|
773
775
|
style="flex:1;text-align:left;padding:6px 10px;background:var(--spectrum-gray-200, #1a1a1a);border:none;border-radius:4px;color:var(--spectrum-gray-900, #fafafa);font-size:12px;cursor:pointer"
|
|
774
776
|
@click=${() => {
|
|
775
777
|
const newSelector = activeSelector ? `${activeSelector} ${rule}` : rule;
|
|
776
|
-
selectStylebookTag(newSelector, undefined, {
|
|
778
|
+
selectStylebookTag(newSelector, undefined, {
|
|
779
|
+
panCanvas: true,
|
|
780
|
+
});
|
|
777
781
|
}}
|
|
778
782
|
>
|
|
779
783
|
${rule}
|
|
@@ -57,7 +57,10 @@ export function renderStylebookLayersTemplate(ctx: {
|
|
|
57
57
|
: [];
|
|
58
58
|
return html`
|
|
59
59
|
<div
|
|
60
|
-
class=${classMap({
|
|
60
|
+
class=${classMap({
|
|
61
|
+
"layer-row": true,
|
|
62
|
+
selected: tag === selectedLeaf,
|
|
63
|
+
})}
|
|
61
64
|
style="padding-left:${8 + depth * 16}px"
|
|
62
65
|
@click=${(e: MouseEvent) => {
|
|
63
66
|
e.stopPropagation();
|
|
@@ -91,8 +94,14 @@ export function renderStylebookLayersTemplate(ctx: {
|
|
|
91
94
|
comp: import("../files/components.js").ComponentEntry,
|
|
92
95
|
) => html`
|
|
93
96
|
<div
|
|
94
|
-
class=${classMap({
|
|
95
|
-
|
|
97
|
+
class=${classMap({
|
|
98
|
+
"layer-row": true,
|
|
99
|
+
selected: comp.tagName === selectedTag,
|
|
100
|
+
})}
|
|
101
|
+
@click=${() =>
|
|
102
|
+
ctx.selectStylebookTag(comp.tagName, undefined, {
|
|
103
|
+
panCanvas: true,
|
|
104
|
+
})}
|
|
96
105
|
>
|
|
97
106
|
<span class="layer-tag component-tag" style="background:var(--accent)">⬡</span>
|
|
98
107
|
<span class="layer-label">${comp.tagName}</span>
|
|
@@ -65,7 +65,13 @@ interface StylebookCtx {
|
|
|
65
65
|
el: Element,
|
|
66
66
|
type: string,
|
|
67
67
|
panel: any,
|
|
68
|
-
) => {
|
|
68
|
+
) => {
|
|
69
|
+
cls: string;
|
|
70
|
+
top: string;
|
|
71
|
+
left: string;
|
|
72
|
+
width: string;
|
|
73
|
+
height: string;
|
|
74
|
+
};
|
|
69
75
|
effectiveZoom: () => number;
|
|
70
76
|
}
|
|
71
77
|
|
|
@@ -235,11 +241,11 @@ export function renderStylebookMode(ctx: StylebookCtx) {
|
|
|
235
241
|
${panelEntries.map((e) => e.tpl)}
|
|
236
242
|
</div>
|
|
237
243
|
`,
|
|
238
|
-
/** @type {HTMLElement} */
|
|
244
|
+
/** @type {HTMLElement} */ canvasWrap,
|
|
239
245
|
);
|
|
240
246
|
|
|
241
247
|
for (const { panel, activeSet } of panelEntries) {
|
|
242
|
-
canvasPanels.push(/** @type {import("./canvas-dnd.js").CanvasPanel} */
|
|
248
|
+
canvasPanels.push(/** @type {import("./canvas-dnd.js").CanvasPanel} */ panel);
|
|
243
249
|
renderIntoPanel(panel, activeSet);
|
|
244
250
|
}
|
|
245
251
|
if (hasMedia) {
|
|
@@ -321,7 +327,7 @@ export function refreshStylebookStyles() {
|
|
|
321
327
|
if (mediaName === "--") continue;
|
|
322
328
|
if (activeBreakpoints.has(mediaName)) {
|
|
323
329
|
const mediaTagStyle = _resolveNestedStyle(
|
|
324
|
-
/** @type {Record<string, unknown>} */
|
|
330
|
+
/** @type {Record<string, unknown>} */ val,
|
|
325
331
|
tag,
|
|
326
332
|
);
|
|
327
333
|
if (mediaTagStyle && typeof mediaTagStyle === "object") {
|
|
@@ -389,7 +395,11 @@ export function renderStylebookOverlays() {
|
|
|
389
395
|
|
|
390
396
|
if (hoverTag && hoverTag !== selectedTag) {
|
|
391
397
|
const el = findStylebookEl(panel.canvas, hoverTag);
|
|
392
|
-
if (el)
|
|
398
|
+
if (el)
|
|
399
|
+
boxes.push({
|
|
400
|
+
..._ctx.overlayBoxDescriptor(el, "hover", panel),
|
|
401
|
+
label: undefined,
|
|
402
|
+
});
|
|
393
403
|
}
|
|
394
404
|
|
|
395
405
|
if (selectedTag) {
|
|
@@ -408,7 +418,12 @@ export function renderStylebookOverlays() {
|
|
|
408
418
|
(b) => html`
|
|
409
419
|
<div
|
|
410
420
|
class=${b.cls}
|
|
411
|
-
style=${styleMap({
|
|
421
|
+
style=${styleMap({
|
|
422
|
+
top: b.top,
|
|
423
|
+
left: b.left,
|
|
424
|
+
width: b.width,
|
|
425
|
+
height: b.height,
|
|
426
|
+
})}
|
|
412
427
|
>
|
|
413
428
|
${b.label ? html`<div class="overlay-label">${b.label}</div>` : nothing}
|
|
414
429
|
</div>
|
|
@@ -441,7 +456,7 @@ export function buildStylebookElement(
|
|
|
441
456
|
if (entry.attributes) {
|
|
442
457
|
for (const [k, v] of Object.entries(entry.attributes)) {
|
|
443
458
|
try {
|
|
444
|
-
el.setAttribute(k, /** @type {string} */
|
|
459
|
+
el.setAttribute(k, /** @type {string} */ v);
|
|
445
460
|
} catch {}
|
|
446
461
|
}
|
|
447
462
|
}
|
|
@@ -519,7 +534,8 @@ export async function renderComponentPreview(comp: ComponentEntry) {
|
|
|
519
534
|
return _componentFallback(comp.tagName);
|
|
520
535
|
}
|
|
521
536
|
} else {
|
|
522
|
-
if (comp.path &&
|
|
537
|
+
if (comp.path && !comp.path.endsWith(".json")) {
|
|
538
|
+
// Format-class component sources (e.g. markdown) can't be imported as modules
|
|
523
539
|
return _componentFallback(comp.tagName);
|
|
524
540
|
}
|
|
525
541
|
const root = projectState?.projectRoot;
|
package/src/panels/toolbar.ts
CHANGED
|
@@ -27,7 +27,12 @@ interface ToolbarCtx {
|
|
|
27
27
|
openFile?: (path: string) => void;
|
|
28
28
|
saveFile: () => void;
|
|
29
29
|
parseMediaEntries: (media: Record<string, string> | null | undefined) => {
|
|
30
|
-
sizeBreakpoints: {
|
|
30
|
+
sizeBreakpoints: {
|
|
31
|
+
name: string;
|
|
32
|
+
query: string;
|
|
33
|
+
width: number;
|
|
34
|
+
type: string;
|
|
35
|
+
}[];
|
|
31
36
|
featureQueries: { name: string; query: string }[];
|
|
32
37
|
baseWidth: number;
|
|
33
38
|
};
|
|
@@ -171,7 +176,11 @@ function minimalToolbarTemplate(ctx: ToolbarCtx) {
|
|
|
171
176
|
const windowControls = (
|
|
172
177
|
globalThis as unknown as {
|
|
173
178
|
__jxPlatform?: {
|
|
174
|
-
windowControls?: {
|
|
179
|
+
windowControls?: {
|
|
180
|
+
minimize: () => void;
|
|
181
|
+
maximize: () => void;
|
|
182
|
+
close: () => void;
|
|
183
|
+
};
|
|
175
184
|
};
|
|
176
185
|
}
|
|
177
186
|
).__jxPlatform?.windowControls;
|
|
@@ -381,7 +390,11 @@ function toolbarTemplate() {
|
|
|
381
390
|
const windowControls = (
|
|
382
391
|
globalThis as unknown as {
|
|
383
392
|
__jxPlatform?: {
|
|
384
|
-
windowControls?: {
|
|
393
|
+
windowControls?: {
|
|
394
|
+
minimize: () => void;
|
|
395
|
+
maximize: () => void;
|
|
396
|
+
close: () => void;
|
|
397
|
+
};
|
|
385
398
|
};
|
|
386
399
|
}
|
|
387
400
|
).__jxPlatform?.windowControls;
|
|
@@ -352,9 +352,13 @@ export function createDevServerPlatform() {
|
|
|
352
352
|
return null;
|
|
353
353
|
},
|
|
354
354
|
|
|
355
|
-
/**
|
|
356
|
-
|
|
357
|
-
|
|
355
|
+
/**
|
|
356
|
+
* @param {string} query
|
|
357
|
+
* @param {string[]} [extensions] — extra extensions beyond .json (from the format registry)
|
|
358
|
+
*/
|
|
359
|
+
async searchFiles(query: string, extensions: string[] = []) {
|
|
360
|
+
const exts = ["json", ...extensions.map((e: string) => e.replace(/^\./, ""))];
|
|
361
|
+
const glob = `**/*${query}*.{${exts.join(",")}}`;
|
|
358
362
|
const res = await fetch(
|
|
359
363
|
`/__studio/files?dir=${encodeURIComponent(serverPath("."))}&glob=${encodeURIComponent(glob)}`,
|
|
360
364
|
);
|
|
@@ -364,6 +368,31 @@ export function createDevServerPlatform() {
|
|
|
364
368
|
return entries;
|
|
365
369
|
},
|
|
366
370
|
|
|
371
|
+
// ─── Format registry ──────────────────────────────────────────────────
|
|
372
|
+
|
|
373
|
+
/** List the project's registered format classes (auto-discovered from imports). */
|
|
374
|
+
async listFormats() {
|
|
375
|
+
const res = await fetch(`/__studio/formats?dir=${encodeURIComponent(serverPath("."))}`);
|
|
376
|
+
if (!res.ok) return [];
|
|
377
|
+
return (await res.json()).formats ?? [];
|
|
378
|
+
},
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Invoke a format capability (parse/serialize) server-side.
|
|
382
|
+
*
|
|
383
|
+
* @param {Record<string, unknown>} payload — { format, action, source?, doc?, options? }
|
|
384
|
+
*/
|
|
385
|
+
async formatAction(payload: Record<string, unknown>) {
|
|
386
|
+
const res = await fetch("/__studio/format", {
|
|
387
|
+
method: "POST",
|
|
388
|
+
headers: { "Content-Type": "application/json" },
|
|
389
|
+
body: JSON.stringify({ ...payload, dir: serverPath(".") }),
|
|
390
|
+
});
|
|
391
|
+
const data = await res.json();
|
|
392
|
+
if (!res.ok) throw new Error(data.error || "Format action failed");
|
|
393
|
+
return data.result;
|
|
394
|
+
},
|
|
395
|
+
|
|
367
396
|
// ─── Plugin schema ────────────────────────────────────────────────────
|
|
368
397
|
|
|
369
398
|
/**
|
package/src/resize-edges.ts
CHANGED
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
const g = globalThis as unknown as {
|
|
3
3
|
__jxPlatform?: {
|
|
4
4
|
windowControls?: {
|
|
5
|
-
getFrame: () => Promise<{
|
|
5
|
+
getFrame: () => Promise<{
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
}>;
|
|
6
11
|
setFrame: (x: number, y: number, w: number, h: number) => void;
|
|
7
12
|
};
|
|
8
13
|
};
|
|
@@ -54,7 +59,12 @@ async function startResize(
|
|
|
54
59
|
e: MouseEvent,
|
|
55
60
|
edge: string,
|
|
56
61
|
wc: {
|
|
57
|
-
getFrame: () => Promise<{
|
|
62
|
+
getFrame: () => Promise<{
|
|
63
|
+
x: number;
|
|
64
|
+
y: number;
|
|
65
|
+
width: number;
|
|
66
|
+
height: number;
|
|
67
|
+
}>;
|
|
58
68
|
setFrame: (x: number, y: number, w: number, h: number) => void;
|
|
59
69
|
},
|
|
60
70
|
) {
|
|
@@ -54,7 +54,9 @@ export function exportCemManifest(
|
|
|
54
54
|
...(d.description ? { description: d.description } : {}),
|
|
55
55
|
...(d.parameters ? { parameters: d.parameters.map(normParam) } : {}),
|
|
56
56
|
...(d.deprecated
|
|
57
|
-
? {
|
|
57
|
+
? {
|
|
58
|
+
deprecated: typeof d.deprecated === "string" ? d.deprecated : true,
|
|
59
|
+
}
|
|
58
60
|
: {}),
|
|
59
61
|
});
|
|
60
62
|
// Collect emits
|
|
@@ -80,7 +82,9 @@ export function exportCemManifest(
|
|
|
80
82
|
...(d.attribute ? { attribute: d.attribute } : {}),
|
|
81
83
|
...(d.reflects ? { reflects: true } : {}),
|
|
82
84
|
...(d.deprecated
|
|
83
|
-
? {
|
|
85
|
+
? {
|
|
86
|
+
deprecated: typeof d.deprecated === "string" ? d.deprecated : true,
|
|
87
|
+
}
|
|
84
88
|
: {}),
|
|
85
89
|
});
|
|
86
90
|
if (d.attribute) {
|
|
@@ -132,7 +136,9 @@ export function exportCemManifest(
|
|
|
132
136
|
],
|
|
133
137
|
};
|
|
134
138
|
|
|
135
|
-
const blob = new Blob([JSON.stringify(manifest, null, 2)], {
|
|
139
|
+
const blob = new Blob([JSON.stringify(manifest, null, 2)], {
|
|
140
|
+
type: "application/json",
|
|
141
|
+
});
|
|
136
142
|
const url = URL.createObjectURL(blob);
|
|
137
143
|
const a = document.createElement("a");
|
|
138
144
|
a.href = url;
|
|
@@ -466,7 +466,7 @@ export function renderContentTypesEditor(container: HTMLElement) {
|
|
|
466
466
|
([name, def]) =>
|
|
467
467
|
fieldCardTpl(
|
|
468
468
|
name,
|
|
469
|
-
/** @type {import("./schema-field-ui.js").SchemaProperty} */
|
|
469
|
+
/** @type {import("./schema-field-ui.js").SchemaProperty} */ def,
|
|
470
470
|
required.includes(name),
|
|
471
471
|
handlers,
|
|
472
472
|
contentTypeNames,
|
|
@@ -497,7 +497,12 @@ export function renderContentTypesEditor(container: HTMLElement) {
|
|
|
497
497
|
onConfirm: () => handleAddField(rerender),
|
|
498
498
|
onCancel: () => {
|
|
499
499
|
showAddField = false;
|
|
500
|
-
newFieldState = {
|
|
500
|
+
newFieldState = {
|
|
501
|
+
name: "",
|
|
502
|
+
type: "string",
|
|
503
|
+
format: "",
|
|
504
|
+
required: false,
|
|
505
|
+
};
|
|
501
506
|
rerender();
|
|
502
507
|
},
|
|
503
508
|
})
|
|
@@ -460,7 +460,12 @@ export function renderDefsEditor(container: HTMLElement) {
|
|
|
460
460
|
onConfirm: () => handleAddField(rerender),
|
|
461
461
|
onCancel: () => {
|
|
462
462
|
showAddField = false;
|
|
463
|
-
newFieldState = {
|
|
463
|
+
newFieldState = {
|
|
464
|
+
name: "",
|
|
465
|
+
type: "string",
|
|
466
|
+
format: "",
|
|
467
|
+
required: false,
|
|
468
|
+
};
|
|
464
469
|
rerender();
|
|
465
470
|
},
|
|
466
471
|
})
|
|
@@ -30,7 +30,9 @@ export function renderGeneralSettings(container: HTMLElement) {
|
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
const onAdapterChange = (e: Event) => {
|
|
33
|
-
updateSiteConfig({
|
|
33
|
+
updateSiteConfig({
|
|
34
|
+
build: { ...config.build, adapter: (e.target as HTMLInputElement).value },
|
|
35
|
+
});
|
|
34
36
|
};
|
|
35
37
|
|
|
36
38
|
const onEditGlobalStyles = () => {
|
|
@@ -157,7 +157,7 @@ export function fieldCardTpl(
|
|
|
157
157
|
nestedFieldCardTpl(
|
|
158
158
|
fieldName,
|
|
159
159
|
name,
|
|
160
|
-
/** @type {SchemaProperty} */
|
|
160
|
+
/** @type {SchemaProperty} */ sub,
|
|
161
161
|
nestedRequired.includes(name),
|
|
162
162
|
handlers,
|
|
163
163
|
),
|
|
@@ -274,7 +274,11 @@ function nestedAddFieldTpl(parentName: string, handlers: FieldHandlers) {
|
|
|
274
274
|
const typePicker = row?.querySelector("sp-picker") as HTMLInputElement | null;
|
|
275
275
|
const type = typePicker?.value || "string";
|
|
276
276
|
if (name && handlers.onAddNestedField) {
|
|
277
|
-
handlers.onAddNestedField(parentName, {
|
|
277
|
+
handlers.onAddNestedField(parentName, {
|
|
278
|
+
name,
|
|
279
|
+
type,
|
|
280
|
+
required: false,
|
|
281
|
+
});
|
|
278
282
|
target.value = "";
|
|
279
283
|
}
|
|
280
284
|
}
|
|
@@ -295,7 +299,11 @@ function nestedAddFieldTpl(parentName: string, handlers: FieldHandlers) {
|
|
|
295
299
|
const name = nameInput?.value?.trim();
|
|
296
300
|
const type = typePicker?.value || "string";
|
|
297
301
|
if (name && handlers.onAddNestedField) {
|
|
298
|
-
handlers.onAddNestedField(parentName, {
|
|
302
|
+
handlers.onAddNestedField(parentName, {
|
|
303
|
+
name,
|
|
304
|
+
type,
|
|
305
|
+
required: false,
|
|
306
|
+
});
|
|
299
307
|
if (nameInput) nameInput.value = "";
|
|
300
308
|
}
|
|
301
309
|
}}
|
|
@@ -68,7 +68,10 @@ function renderModal() {
|
|
|
68
68
|
${sections.map(
|
|
69
69
|
(s) => html`
|
|
70
70
|
<button
|
|
71
|
-
class=${classMap({
|
|
71
|
+
class=${classMap({
|
|
72
|
+
"settings-nav-item": true,
|
|
73
|
+
active: _activeSection === s.key,
|
|
74
|
+
})}
|
|
72
75
|
@click=${() => onNavClick(s.key)}
|
|
73
76
|
>
|
|
74
77
|
${s.label}
|
package/src/state.ts
CHANGED
|
@@ -33,7 +33,11 @@ export interface StudioState {
|
|
|
33
33
|
mode: string;
|
|
34
34
|
content: { frontmatter: Record<string, unknown> };
|
|
35
35
|
ui: Record<string, unknown>;
|
|
36
|
-
canvas: {
|
|
36
|
+
canvas: {
|
|
37
|
+
status: string;
|
|
38
|
+
scope: Record<string, unknown> | null;
|
|
39
|
+
error: string | null;
|
|
40
|
+
};
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
interface StudioStackFrame {
|
package/src/studio.ts
CHANGED
|
@@ -40,7 +40,8 @@ import {
|
|
|
40
40
|
setStatusbarRenderer,
|
|
41
41
|
mountStatusbar,
|
|
42
42
|
} from "./panels/statusbar";
|
|
43
|
-
import {
|
|
43
|
+
import { parseSourceForPath, saveFile, exportFile, serializeDocument } from "./files/file-ops";
|
|
44
|
+
import { loadFormats, formatForPath, documentExtensions } from "./format/format-host";
|
|
44
45
|
import {
|
|
45
46
|
loadProject as _loadProject,
|
|
46
47
|
openProject as _openProject,
|
|
@@ -168,7 +169,7 @@ async function navigateBack() {
|
|
|
168
169
|
if (tab.doc.dirty && tab.documentPath) {
|
|
169
170
|
try {
|
|
170
171
|
const platform = getPlatform();
|
|
171
|
-
await platform.writeFile(tab.documentPath, serializeDocument(tab));
|
|
172
|
+
await platform.writeFile(tab.documentPath, await serializeDocument(tab));
|
|
172
173
|
} catch (e) {
|
|
173
174
|
const err = e as Error;
|
|
174
175
|
statusMessage(`Save error: ${err.message}`);
|
|
@@ -198,7 +199,7 @@ async function navigateToLevel(targetIndex: number) {
|
|
|
198
199
|
if (tab.doc.dirty && tab.documentPath) {
|
|
199
200
|
try {
|
|
200
201
|
const platform = getPlatform();
|
|
201
|
-
await platform.writeFile(tab.documentPath, serializeDocument(tab));
|
|
202
|
+
await platform.writeFile(tab.documentPath, await serializeDocument(tab));
|
|
202
203
|
} catch (e) {
|
|
203
204
|
const err = e as Error;
|
|
204
205
|
statusMessage(`Save error: ${err.message}`);
|
|
@@ -222,9 +223,8 @@ async function navigateToLevel(targetIndex: number) {
|
|
|
222
223
|
async function closeFunctionEditor() {
|
|
223
224
|
const tab = activeTab.value;
|
|
224
225
|
const editing =
|
|
225
|
-
/** @type {{ type: string; defName?: string; path?: JxPath; eventKey?: string } | null} */
|
|
226
|
-
|
|
227
|
-
);
|
|
226
|
+
/** @type {{ type: string; defName?: string; path?: JxPath; eventKey?: string } | null} */ tab
|
|
227
|
+
?.session.ui.editingFunction;
|
|
228
228
|
if (!editing || !tab) return;
|
|
229
229
|
if (view.functionEditor) {
|
|
230
230
|
const currentCode = view.functionEditor.getValue();
|
|
@@ -237,7 +237,7 @@ async function closeFunctionEditor() {
|
|
|
237
237
|
const current = node?.[editing.eventKey as string] || {};
|
|
238
238
|
transactDoc(tab, (t) =>
|
|
239
239
|
mutateUpdateProperty(t, editing.path as JxPath, editing.eventKey as string, {
|
|
240
|
-
.../** @type {object} */
|
|
240
|
+
.../** @type {object} */ current,
|
|
241
241
|
$prototype: "Function",
|
|
242
242
|
body: bodyToStore,
|
|
243
243
|
}),
|
|
@@ -531,7 +531,12 @@ if (_projectParam) {
|
|
|
531
531
|
// When opening project.json, default to home page instead
|
|
532
532
|
if (fileRelPath === "project.json" || fileRelPath.endsWith("/project.json")) {
|
|
533
533
|
let opened = false;
|
|
534
|
-
|
|
534
|
+
await loadFormats();
|
|
535
|
+
const homeCandidates = [
|
|
536
|
+
...documentExtensions("page").map((ext) => `pages/index${ext}`),
|
|
537
|
+
"pages/index.json",
|
|
538
|
+
];
|
|
539
|
+
for (const candidate of homeCandidates) {
|
|
535
540
|
try {
|
|
536
541
|
await platform.readFile(candidate);
|
|
537
542
|
fileRelPath = candidate;
|
|
@@ -544,12 +549,14 @@ if (_projectParam) {
|
|
|
544
549
|
|
|
545
550
|
const content = await platform.readFile(fileRelPath);
|
|
546
551
|
if (content) {
|
|
547
|
-
let parsedDoc, frontmatter;
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
552
|
+
let parsedDoc, frontmatter, parsedMode;
|
|
553
|
+
await loadFormats();
|
|
554
|
+
const fileFormat = formatForPath(fileRelPath);
|
|
555
|
+
if (fileFormat) {
|
|
556
|
+
const result = await parseSourceForPath(fileRelPath, content);
|
|
551
557
|
parsedDoc = result.document;
|
|
552
558
|
frontmatter = result.frontmatter;
|
|
559
|
+
parsedMode = result.mode;
|
|
553
560
|
} else {
|
|
554
561
|
parsedDoc = JSON.parse(content);
|
|
555
562
|
}
|
|
@@ -560,10 +567,10 @@ if (_projectParam) {
|
|
|
560
567
|
documentPath: fileRelPath,
|
|
561
568
|
document: parsedDoc,
|
|
562
569
|
...(frontmatter != null && { frontmatter }),
|
|
563
|
-
sourceFormat:
|
|
570
|
+
sourceFormat: fileFormat?.name ?? null,
|
|
564
571
|
});
|
|
565
572
|
|
|
566
|
-
if (
|
|
573
|
+
if (parsedMode === "content" && activeTab.value) activeTab.value.doc.mode = "content";
|
|
567
574
|
if (fileRelPath === "project.json" && activeTab.value) {
|
|
568
575
|
activeTab.value.session.ui.canvasMode = "stylebook";
|
|
569
576
|
}
|
|
@@ -671,7 +678,11 @@ async function openRecentProject(root: string) {
|
|
|
671
678
|
}
|
|
672
679
|
}
|
|
673
680
|
function renderFilesTemplate() {
|
|
674
|
-
return _renderFilesTemplate({
|
|
681
|
+
return _renderFilesTemplate({
|
|
682
|
+
openProject,
|
|
683
|
+
openFileFromTree,
|
|
684
|
+
renderLeftPanel,
|
|
685
|
+
});
|
|
675
686
|
}
|
|
676
687
|
function openFileFromTree(path: string) {
|
|
677
688
|
return openFileInTab(path);
|
|
@@ -718,7 +729,7 @@ function scheduleAutosave() {
|
|
|
718
729
|
if (t?.fileHandle && t.doc.dirty && "createWritable" in t.fileHandle) {
|
|
719
730
|
try {
|
|
720
731
|
const writable = await t.fileHandle.createWritable();
|
|
721
|
-
await writable.write(serializeDocument(t));
|
|
732
|
+
await writable.write(await serializeDocument(t));
|
|
722
733
|
await writable.close();
|
|
723
734
|
t.doc.dirty = false;
|
|
724
735
|
statusMessage("Auto-saved");
|