@jxsuite/studio 0.15.0 → 0.16.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.js +10211 -10122
- package/dist/studio.js.map +42 -42
- package/package.json +2 -2
- package/src/canvas/canvas-live-render.js +1 -27
- package/src/canvas/canvas-render.js +50 -40
- package/src/files/file-ops.js +4 -2
- package/src/files/files.js +70 -68
- package/src/panels/editors.js +10 -6
- package/src/panels/git-panel.js +17 -15
- package/src/panels/left-panel.js +4 -0
- package/src/panels/panel-events.js +24 -19
- package/src/panels/properties-panel.js +14 -14
- package/src/panels/signals-panel.js +63 -40
- package/src/platforms/devserver.js +10 -0
- package/src/services/code-services.js +4 -4
- package/src/studio.js +18 -4
- package/src/tabs/transact.js +6 -2
- package/src/workspace/workspace.js +44 -0
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
mutateRenameDef,
|
|
15
15
|
} from "../tabs/transact.js";
|
|
16
16
|
import { renderFieldRow } from "../ui/field-row.js";
|
|
17
|
+
import { renderMediaPicker } from "../ui/media-picker.js";
|
|
17
18
|
import { fetchPluginSchema, pluginSchemaCache } from "../services/code-services.js";
|
|
18
19
|
|
|
19
20
|
// ─── Module-local state ─────────────────────────────────────────────────────
|
|
@@ -343,6 +344,9 @@ function renderSignalEditorTemplate(
|
|
|
343
344
|
/** @type {any} */ def,
|
|
344
345
|
/** @type {any} */ ctx,
|
|
345
346
|
) {
|
|
347
|
+
if (typeof def !== "object" || def === null) {
|
|
348
|
+
def = { default: def };
|
|
349
|
+
}
|
|
346
350
|
const cat = defCategory(def);
|
|
347
351
|
|
|
348
352
|
// Helper for picker rows
|
|
@@ -470,20 +474,31 @@ function renderSignalEditorTemplate(
|
|
|
470
474
|
),
|
|
471
475
|
)
|
|
472
476
|
: nothing}
|
|
473
|
-
${
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
|
|
477
|
+
${def.format === "image"
|
|
478
|
+
? renderFieldRow({
|
|
479
|
+
prop: "Default",
|
|
480
|
+
label: "Default",
|
|
481
|
+
hasValue: false,
|
|
482
|
+
widget: renderMediaPicker("default", defaultVal, (/** @type {any} */ v) => {
|
|
483
|
+
transactDoc(activeTab.value, (t) =>
|
|
484
|
+
mutateUpdateDef(t, name, { default: v || undefined }),
|
|
485
|
+
);
|
|
486
|
+
}),
|
|
487
|
+
})
|
|
488
|
+
: signalFieldRow("Default", defaultVal, (/** @type {any} */ v) => {
|
|
489
|
+
let parsed = v;
|
|
490
|
+
if (def.type === "integer") parsed = parseInt(v, 10) || 0;
|
|
491
|
+
else if (def.type === "number") parsed = parseFloat(v) || 0;
|
|
492
|
+
else if (def.type === "boolean") parsed = v === "true";
|
|
493
|
+
else if (def.type === "array" || def.type === "object") {
|
|
494
|
+
try {
|
|
495
|
+
parsed = JSON.parse(v);
|
|
496
|
+
} catch {
|
|
497
|
+
parsed = v;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { default: parsed }));
|
|
501
|
+
})}
|
|
487
502
|
${signalFieldRow("Description", def.description || "", (/** @type {any} */ v) =>
|
|
488
503
|
transactDoc(activeTab.value, (t) =>
|
|
489
504
|
mutateUpdateDef(t, name, { description: v || undefined }),
|
|
@@ -644,7 +659,16 @@ function renderFunctionFields(
|
|
|
644
659
|
/** @type {any} */ textareaRow,
|
|
645
660
|
/** @type {any} */ ctx,
|
|
646
661
|
) {
|
|
647
|
-
const
|
|
662
|
+
const descriptionField = signalFieldRow(
|
|
663
|
+
"Description",
|
|
664
|
+
def.description || "",
|
|
665
|
+
(/** @type {any} */ v) =>
|
|
666
|
+
transactDoc(activeTab.value, (t) =>
|
|
667
|
+
mutateUpdateDef(t, name, { description: v || undefined }),
|
|
668
|
+
),
|
|
669
|
+
);
|
|
670
|
+
|
|
671
|
+
const bodyField = def.$src
|
|
648
672
|
? html`
|
|
649
673
|
${signalFieldRow("Source", def.$src || "", (/** @type {any} */ v) =>
|
|
650
674
|
transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { $src: v || undefined })),
|
|
@@ -655,36 +679,35 @@ function renderFunctionFields(
|
|
|
655
679
|
),
|
|
656
680
|
)}
|
|
657
681
|
`
|
|
658
|
-
:
|
|
659
|
-
"
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
return html`
|
|
667
|
-
${srcFields} ${renderParameterEditorTemplate(S, name, def, ctx)}
|
|
668
|
-
${isCustomElementDoc(S) ? renderEmitsEditorTemplate(S, name, def) : nothing}
|
|
669
|
-
${!def.$src
|
|
670
|
-
? html`
|
|
671
|
-
<button
|
|
672
|
-
class="kv-add"
|
|
673
|
-
style="margin-top:4px"
|
|
682
|
+
: html`
|
|
683
|
+
<div style="display:flex;align-items:center;gap:4px">
|
|
684
|
+
<span class="field-label" style="flex:1">Body</span>
|
|
685
|
+
<sp-action-button
|
|
686
|
+
size="xs"
|
|
687
|
+
quiet
|
|
688
|
+
title="Open in code editor"
|
|
674
689
|
@click=${() => {
|
|
675
690
|
ctx.updateSession({ ui: { editingFunction: { type: "def", defName: name } } });
|
|
676
691
|
ctx.renderCanvas();
|
|
677
692
|
}}
|
|
678
693
|
>
|
|
679
|
-
|
|
680
|
-
</button>
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
694
|
+
<sp-icon-code slot="icon"></sp-icon-code>
|
|
695
|
+
</sp-action-button>
|
|
696
|
+
</div>
|
|
697
|
+
<textarea
|
|
698
|
+
class="field-input"
|
|
699
|
+
style="min-height:60px;font-family:monospace;font-size:11px"
|
|
700
|
+
.value=${def.body || ""}
|
|
701
|
+
@input=${(/** @type {any} */ e) => {
|
|
702
|
+
const v = e.target.value;
|
|
703
|
+
transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { body: v }));
|
|
704
|
+
}}
|
|
705
|
+
></textarea>
|
|
706
|
+
`;
|
|
707
|
+
|
|
708
|
+
return html`
|
|
709
|
+
${descriptionField} ${renderParameterEditorTemplate(S, name, def, ctx)}
|
|
710
|
+
${isCustomElementDoc(S) ? renderEmitsEditorTemplate(S, name, def) : nothing} ${bodyField}
|
|
688
711
|
`;
|
|
689
712
|
}
|
|
690
713
|
|
|
@@ -434,6 +434,16 @@ export function createDevServerPlatform() {
|
|
|
434
434
|
return await res.json();
|
|
435
435
|
},
|
|
436
436
|
|
|
437
|
+
/** @param {{ path: string; ref?: string }} opts */
|
|
438
|
+
async gitShow(opts) {
|
|
439
|
+
const params = new URLSearchParams({ path: opts.path });
|
|
440
|
+
if (opts.ref) params.set("ref", opts.ref);
|
|
441
|
+
const res = await fetch(`/__studio/git/show?${params}`);
|
|
442
|
+
if (!res.ok) throw new Error(await res.text());
|
|
443
|
+
const data = await res.json();
|
|
444
|
+
return data.content;
|
|
445
|
+
},
|
|
446
|
+
|
|
437
447
|
/** @param {string[]} files */
|
|
438
448
|
async gitDiscard(files) {
|
|
439
449
|
const res = await fetch("/__studio/git/discard", {
|
|
@@ -85,13 +85,13 @@ export function setLintMarkers(editor, diagnostics) {
|
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
87
|
* @param {any} editing
|
|
88
|
-
* @param {any}
|
|
88
|
+
* @param {any} document
|
|
89
89
|
*/
|
|
90
|
-
export function getFunctionArgs(editing,
|
|
90
|
+
export function getFunctionArgs(editing, document) {
|
|
91
91
|
if (editing.type === "def") {
|
|
92
|
-
return
|
|
92
|
+
return document?.state?.[editing.defName]?.parameters || ["state", "event"];
|
|
93
93
|
} else if (editing.type === "event") {
|
|
94
|
-
const node = getNodeAtPath(
|
|
94
|
+
const node = getNodeAtPath(document, editing.path);
|
|
95
95
|
return node?.[editing.eventKey]?.parameters || ["state", "event"];
|
|
96
96
|
}
|
|
97
97
|
return ["state", "event"];
|
package/src/studio.js
CHANGED
|
@@ -35,7 +35,13 @@ import {
|
|
|
35
35
|
setStatusbarRenderer,
|
|
36
36
|
mountStatusbar,
|
|
37
37
|
} from "./panels/statusbar.js";
|
|
38
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
openFile,
|
|
40
|
+
loadMarkdown,
|
|
41
|
+
saveFile,
|
|
42
|
+
exportFile,
|
|
43
|
+
serializeDocument,
|
|
44
|
+
} from "./files/file-ops.js";
|
|
39
45
|
import {
|
|
40
46
|
loadProject as _loadProject,
|
|
41
47
|
openProject as _openProject,
|
|
@@ -128,6 +134,7 @@ async function navigateToComponent(componentPath) {
|
|
|
128
134
|
documentPath: tab.documentPath,
|
|
129
135
|
dirty: tab.doc.dirty,
|
|
130
136
|
mode: tab.doc.mode,
|
|
137
|
+
sourceFormat: tab.doc.sourceFormat,
|
|
131
138
|
};
|
|
132
139
|
if (!tab.session.documentStack) tab.session.documentStack = [];
|
|
133
140
|
tab.session.documentStack.push(frame);
|
|
@@ -136,6 +143,7 @@ async function navigateToComponent(componentPath) {
|
|
|
136
143
|
tab.doc.document = parsed;
|
|
137
144
|
tab.doc.dirty = false;
|
|
138
145
|
tab.doc.mode = /** @type {any} */ (null);
|
|
146
|
+
tab.doc.sourceFormat = null;
|
|
139
147
|
tab.documentPath = componentPath;
|
|
140
148
|
tab.session.selection = null;
|
|
141
149
|
view.leftTab = "layers";
|
|
@@ -156,7 +164,7 @@ async function navigateBack() {
|
|
|
156
164
|
if (tab.doc.dirty && tab.documentPath) {
|
|
157
165
|
try {
|
|
158
166
|
const platform = getPlatform();
|
|
159
|
-
await platform.writeFile(tab.documentPath,
|
|
167
|
+
await platform.writeFile(tab.documentPath, serializeDocument(tab));
|
|
160
168
|
} catch (/** @type {any} */ e) {
|
|
161
169
|
const err = /** @type {any} */ (e);
|
|
162
170
|
statusMessage(`Save error: ${err.message}`);
|
|
@@ -169,6 +177,7 @@ async function navigateBack() {
|
|
|
169
177
|
tab.doc.document = frame.document;
|
|
170
178
|
tab.doc.dirty = frame.dirty;
|
|
171
179
|
tab.doc.mode = frame.mode;
|
|
180
|
+
tab.doc.sourceFormat = frame.sourceFormat;
|
|
172
181
|
tab.documentPath = frame.documentPath;
|
|
173
182
|
tab.session.selection = frame.selection;
|
|
174
183
|
view.leftTab = "layers";
|
|
@@ -300,7 +309,9 @@ initCanvasRender({
|
|
|
300
309
|
setCanvasMode,
|
|
301
310
|
openFileFromTree,
|
|
302
311
|
exportFile,
|
|
303
|
-
gitDiffState
|
|
312
|
+
get gitDiffState() {
|
|
313
|
+
return gitDiffState;
|
|
314
|
+
},
|
|
304
315
|
setGitDiffState: (/** @type {any} */ state) => {
|
|
305
316
|
gitDiffState = state;
|
|
306
317
|
},
|
|
@@ -359,6 +370,9 @@ leftPanelMod.mount({
|
|
|
359
370
|
registerElementsDnD,
|
|
360
371
|
registerComponentsDnD,
|
|
361
372
|
setupTreeKeyboard,
|
|
373
|
+
setGitDiffState: (/** @type {any} */ state) => {
|
|
374
|
+
gitDiffState = state;
|
|
375
|
+
},
|
|
362
376
|
});
|
|
363
377
|
|
|
364
378
|
// Register all renderers with the store so render()/renderOnly() work
|
|
@@ -578,7 +592,7 @@ function scheduleAutosave() {
|
|
|
578
592
|
if (t?.fileHandle && t.doc.dirty && "createWritable" in t.fileHandle) {
|
|
579
593
|
try {
|
|
580
594
|
const writable = await t.fileHandle.createWritable();
|
|
581
|
-
await writable.write(
|
|
595
|
+
await writable.write(serializeDocument(t));
|
|
582
596
|
await writable.close();
|
|
583
597
|
t.doc.dirty = false;
|
|
584
598
|
statusMessage("Auto-saved");
|
package/src/tabs/transact.js
CHANGED
|
@@ -33,7 +33,7 @@ export function transactDoc(tab, mutationFn, { skipHistory = false } = {}) {
|
|
|
33
33
|
|
|
34
34
|
if (!skipHistory) {
|
|
35
35
|
const snapshot = {
|
|
36
|
-
document:
|
|
36
|
+
document: JSON.parse(JSON.stringify(raw)),
|
|
37
37
|
selection: tab.session.selection ? [...tab.session.selection] : null,
|
|
38
38
|
};
|
|
39
39
|
const truncated = tab.history.snapshots.slice(0, tab.history.index + 1);
|
|
@@ -319,7 +319,11 @@ export function mutateRemoveDef(tab, name) {
|
|
|
319
319
|
export function mutateUpdateDef(tab, name, updates) {
|
|
320
320
|
const doc = tab.doc.document;
|
|
321
321
|
if (!doc.state) doc.state = {};
|
|
322
|
-
if (
|
|
322
|
+
if (doc.state[name] == null) {
|
|
323
|
+
doc.state[name] = {};
|
|
324
|
+
} else if (typeof doc.state[name] !== "object") {
|
|
325
|
+
doc.state[name] = { default: doc.state[name] };
|
|
326
|
+
}
|
|
323
327
|
Object.assign(doc.state[name], updates);
|
|
324
328
|
for (const k of Object.keys(doc.state[name])) {
|
|
325
329
|
if (doc.state[name][k] === undefined || doc.state[name][k] === null) {
|
|
@@ -84,6 +84,50 @@ export function closeTab(tabId) {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
/** Close all open tabs, disposing each. Defers reactivity until fully cleared. */
|
|
88
|
+
export function closeAllTabs() {
|
|
89
|
+
const tabs = [...workspace.tabs.values()];
|
|
90
|
+
workspace.tabs.clear();
|
|
91
|
+
workspace.tabOrder = [];
|
|
92
|
+
workspace.activeTabId = null;
|
|
93
|
+
for (const tab of tabs) {
|
|
94
|
+
disposeTab(tab);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Replace all existing tabs with a new one atomically. Ensures activeTab is never null during the
|
|
100
|
+
* transition.
|
|
101
|
+
*
|
|
102
|
+
* @param {{
|
|
103
|
+
* id: string;
|
|
104
|
+
* documentPath?: string | null;
|
|
105
|
+
* document: Record<string, any>;
|
|
106
|
+
* frontmatter?: Record<string, unknown>;
|
|
107
|
+
* sourceFormat?: string | null;
|
|
108
|
+
* }} newTabOpts
|
|
109
|
+
* @returns {import("../tabs/tab.js").Tab}
|
|
110
|
+
*/
|
|
111
|
+
export function replaceAllTabs(newTabOpts) {
|
|
112
|
+
const oldIds = [...workspace.tabs.keys()];
|
|
113
|
+
const oldTabs = [...workspace.tabs.values()];
|
|
114
|
+
|
|
115
|
+
const newTab = createTab(newTabOpts);
|
|
116
|
+
workspace.tabs.set(newTab.id, newTab);
|
|
117
|
+
workspace.activeTabId = newTab.id;
|
|
118
|
+
workspace.tabOrder = [newTab.id];
|
|
119
|
+
|
|
120
|
+
for (const id of oldIds) {
|
|
121
|
+
if (id === newTab.id) continue;
|
|
122
|
+
workspace.tabs.delete(id);
|
|
123
|
+
}
|
|
124
|
+
for (const tab of oldTabs) {
|
|
125
|
+
disposeTab(tab);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return newTab;
|
|
129
|
+
}
|
|
130
|
+
|
|
87
131
|
/**
|
|
88
132
|
* Switch to an existing tab.
|
|
89
133
|
*
|