@jxsuite/studio 0.11.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/studio.js +6517 -5870
- package/dist/studio.js.map +57 -51
- package/package.json +5 -3
- package/src/canvas/canvas-diff.js +184 -0
- package/src/canvas/canvas-helpers.js +10 -14
- package/src/canvas/canvas-live-render.js +28 -2
- package/src/canvas/canvas-render.js +170 -20
- package/src/canvas/canvas-utils.js +22 -25
- package/src/editor/component-inline-edit.js +55 -44
- package/src/editor/content-inline-edit.js +47 -50
- package/src/editor/context-menu.js +78 -53
- package/src/editor/convert-to-component.js +11 -14
- package/src/editor/insertion-helper.js +8 -11
- package/src/editor/shortcuts.js +88 -64
- package/src/files/components.js +15 -4
- package/src/files/file-ops.js +57 -108
- package/src/files/files.js +81 -28
- package/src/panels/activity-bar.js +31 -7
- package/src/panels/block-action-bar.js +104 -80
- package/src/panels/canvas-dnd.js +4 -10
- package/src/panels/dnd.js +77 -35
- package/src/panels/editors.js +17 -26
- package/src/panels/elements-panel.js +17 -11
- package/src/panels/events-panel.js +44 -39
- package/src/panels/git-panel.js +47 -4
- package/src/panels/layers-panel.js +25 -21
- package/src/panels/left-panel.js +109 -44
- package/src/panels/overlays.js +91 -41
- package/src/panels/panel-events.js +28 -37
- package/src/panels/preview-render.js +7 -3
- package/src/panels/properties-panel.js +179 -104
- package/src/panels/pseudo-preview.js +9 -8
- package/src/panels/right-panel.js +85 -37
- package/src/panels/shared.js +0 -22
- package/src/panels/signals-panel.js +125 -54
- package/src/panels/statusbar.js +26 -19
- package/src/panels/style-inputs.js +5 -4
- package/src/panels/style-panel.js +128 -105
- package/src/panels/style-utils.js +8 -6
- package/src/panels/stylebook-layers-panel.js +5 -5
- package/src/panels/stylebook-panel.js +27 -31
- package/src/panels/tab-strip.js +124 -0
- package/src/panels/toolbar.js +40 -10
- package/src/reactivity.js +28 -0
- package/src/settings/content-types-editor.js +56 -7
- package/src/settings/defs-editor.js +56 -7
- package/src/settings/schema-field-ui.js +60 -25
- package/src/state.js +2 -459
- package/src/store.js +61 -219
- package/src/studio.js +163 -300
- package/src/tabs/tab.js +168 -0
- package/src/tabs/transact.js +406 -0
- package/src/ui/color-selector.js +4 -5
- package/src/view.js +3 -0
- package/src/workspace/workspace.js +90 -0
|
@@ -8,9 +8,6 @@ import { ref } from "lit-html/directives/ref.js";
|
|
|
8
8
|
import { styleMap } from "lit-html/directives/style-map.js";
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
|
-
getState,
|
|
12
|
-
update,
|
|
13
|
-
updateStyle,
|
|
14
11
|
updateSession,
|
|
15
12
|
updateUi,
|
|
16
13
|
canvasWrap,
|
|
@@ -18,6 +15,8 @@ import {
|
|
|
18
15
|
elToPath,
|
|
19
16
|
projectState,
|
|
20
17
|
} from "../store.js";
|
|
18
|
+
import { activeTab } from "../workspace/workspace.js";
|
|
19
|
+
import { transactDoc, mutateUpdateStyle } from "../tabs/transact.js";
|
|
21
20
|
import { view } from "../view.js";
|
|
22
21
|
import { defineElement, setSkipServerFunctions } from "@jxsuite/runtime";
|
|
23
22
|
import { componentRegistry } from "../files/components.js";
|
|
@@ -49,9 +48,8 @@ let _ctx = null;
|
|
|
49
48
|
*/
|
|
50
49
|
export function renderStylebookMode(ctx) {
|
|
51
50
|
_ctx = ctx;
|
|
52
|
-
const S = getState();
|
|
53
51
|
|
|
54
|
-
const settingsTab =
|
|
52
|
+
const settingsTab = activeTab.value?.session.ui.settingsTab || "stylebook";
|
|
55
53
|
|
|
56
54
|
const settingsChromeBarTpl = html`
|
|
57
55
|
<div
|
|
@@ -94,11 +92,14 @@ export function renderStylebookMode(ctx) {
|
|
|
94
92
|
|
|
95
93
|
// Stylebook tab — element catalog / variables
|
|
96
94
|
view.stylebookElToTag = new WeakMap();
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
const
|
|
95
|
+
const tab = activeTab.value;
|
|
96
|
+
const rootStyle = getEffectiveStyle(tab?.doc.document?.style);
|
|
97
|
+
const filter = (tab?.session.ui.stylebookFilter || "").toLowerCase();
|
|
98
|
+
const customizedOnly = tab?.session.ui.stylebookCustomizedOnly;
|
|
100
99
|
|
|
101
|
-
const { sizeBreakpoints, baseWidth } = parseMediaEntries(
|
|
100
|
+
const { sizeBreakpoints, baseWidth } = parseMediaEntries(
|
|
101
|
+
getEffectiveMedia(tab?.doc.document?.$media),
|
|
102
|
+
);
|
|
102
103
|
const hasMedia = sizeBreakpoints.length > 0;
|
|
103
104
|
|
|
104
105
|
const onTabClick = (/** @type {string} */ t) => {
|
|
@@ -110,7 +111,7 @@ export function renderStylebookMode(ctx) {
|
|
|
110
111
|
};
|
|
111
112
|
|
|
112
113
|
const onCustomizedToggle = () => {
|
|
113
|
-
updateUi("stylebookCustomizedOnly", !
|
|
114
|
+
updateUi("stylebookCustomizedOnly", !tab?.session.ui.stylebookCustomizedOnly);
|
|
114
115
|
};
|
|
115
116
|
|
|
116
117
|
const chromeBarTpl = html`
|
|
@@ -121,7 +122,7 @@ export function renderStylebookMode(ctx) {
|
|
|
121
122
|
>
|
|
122
123
|
<sp-tabs
|
|
123
124
|
size="s"
|
|
124
|
-
selected=${
|
|
125
|
+
selected=${tab?.session.ui.stylebookTab || "elements"}
|
|
125
126
|
@change=${(/** @type {any} */ e) => {
|
|
126
127
|
onTabClick(e.target.selected);
|
|
127
128
|
}}
|
|
@@ -132,17 +133,17 @@ export function renderStylebookMode(ctx) {
|
|
|
132
133
|
`,
|
|
133
134
|
)}
|
|
134
135
|
</sp-tabs>
|
|
135
|
-
${
|
|
136
|
+
${tab?.session.ui.stylebookTab === "elements"
|
|
136
137
|
? html`
|
|
137
138
|
<input
|
|
138
139
|
class="field-input"
|
|
139
140
|
style="flex:1;max-width:200px;margin-left:8px"
|
|
140
141
|
placeholder="Filter…"
|
|
141
|
-
.value=${
|
|
142
|
+
.value=${tab?.session.ui.stylebookFilter}
|
|
142
143
|
@input=${onFilterInput}
|
|
143
144
|
/>
|
|
144
145
|
<button
|
|
145
|
-
class="tb-toggle${
|
|
146
|
+
class="tb-toggle${tab?.session.ui.stylebookCustomizedOnly ? " active" : ""}"
|
|
146
147
|
style="margin-left:4px"
|
|
147
148
|
@click=${onCustomizedToggle}
|
|
148
149
|
>
|
|
@@ -176,7 +177,7 @@ export function renderStylebookMode(ctx) {
|
|
|
176
177
|
|
|
177
178
|
const renderIntoPanel = (/** @type {any} */ panel, /** @type {any} */ activeBreakpoints) => {
|
|
178
179
|
panel.canvas.classList.add("sb-canvas");
|
|
179
|
-
if (
|
|
180
|
+
if (tab?.session.ui.stylebookTab === "elements") {
|
|
180
181
|
renderStylebookElementsIntoCanvas(
|
|
181
182
|
panel.canvas,
|
|
182
183
|
rootStyle,
|
|
@@ -197,7 +198,7 @@ export function renderStylebookMode(ctx) {
|
|
|
197
198
|
/** @type {{ tpl: any; panel: any; activeSet: any }[]} */
|
|
198
199
|
let panelEntries;
|
|
199
200
|
if (!hasMedia) {
|
|
200
|
-
const effectiveMedia = getEffectiveMedia(
|
|
201
|
+
const effectiveMedia = getEffectiveMedia(tab?.doc.document?.$media);
|
|
201
202
|
const hasBaseWidth = effectiveMedia && effectiveMedia["--"];
|
|
202
203
|
const label = hasBaseWidth ? `${mediaDisplayName("--")} (${baseWidth}px)` : null;
|
|
203
204
|
const entry = ctx.canvasPanelTemplate(
|
|
@@ -268,11 +269,10 @@ export function renderStylebookOverlays() {
|
|
|
268
269
|
if (!_ctx) return;
|
|
269
270
|
if (canvasPanels.length === 0) return;
|
|
270
271
|
|
|
271
|
-
const
|
|
272
|
-
const selectedTag = S.ui.stylebookSelection;
|
|
272
|
+
const selectedTag = activeTab.value?.session.ui.stylebookSelection;
|
|
273
273
|
|
|
274
274
|
for (const panel of canvasPanels) {
|
|
275
|
-
const hoverTag = panel._lastHoverTag;
|
|
275
|
+
const hoverTag = /** @type {any} */ (panel)._lastHoverTag;
|
|
276
276
|
/**
|
|
277
277
|
* @type {{
|
|
278
278
|
* cls: string;
|
|
@@ -691,8 +691,9 @@ function renderVarRow(catKey, catMeta, varName, varVal, isNew) {
|
|
|
691
691
|
const swatch = /** @type {any} */ (row.querySelector(".sb-var-swatch"));
|
|
692
692
|
if (swatch) swatch.style.backgroundColor = colorPicker.value;
|
|
693
693
|
if (!isNew && varName) {
|
|
694
|
-
|
|
695
|
-
|
|
694
|
+
transactDoc(activeTab.value, (t) =>
|
|
695
|
+
mutateUpdateStyle(t, [], varName, colorPicker.value),
|
|
696
|
+
);
|
|
696
697
|
}
|
|
697
698
|
}}
|
|
698
699
|
/>
|
|
@@ -751,8 +752,7 @@ function renderVarRow(catKey, catMeta, varName, varVal, isNew) {
|
|
|
751
752
|
const swatch = /** @type {any} */ (row.querySelector(".sb-var-swatch"));
|
|
752
753
|
if (swatch) swatch.style.backgroundColor = v;
|
|
753
754
|
if (!isNew && varName) {
|
|
754
|
-
|
|
755
|
-
update(updateStyle(S, [], varName, v));
|
|
755
|
+
transactDoc(activeTab.value, (t) => mutateUpdateStyle(t, [], varName, v));
|
|
756
756
|
}
|
|
757
757
|
}, 400);
|
|
758
758
|
}}
|
|
@@ -765,8 +765,7 @@ function renderVarRow(catKey, catMeta, varName, varVal, isNew) {
|
|
|
765
765
|
const bar = /** @type {any} */ (row.querySelector(".sb-var-size-bar"));
|
|
766
766
|
if (bar) bar.style.width = newVal;
|
|
767
767
|
if (!isNew && varName) {
|
|
768
|
-
|
|
769
|
-
update(updateStyle(S, [], varName, newVal));
|
|
768
|
+
transactDoc(activeTab.value, (t) => mutateUpdateStyle(t, [], varName, newVal));
|
|
770
769
|
}
|
|
771
770
|
},
|
|
772
771
|
});
|
|
@@ -798,8 +797,7 @@ function renderVarRow(catKey, catMeta, varName, varVal, isNew) {
|
|
|
798
797
|
const v = textFieldEl.value;
|
|
799
798
|
const fontPrev = /** @type {any} */ (row.querySelector(".sb-var-font-preview"));
|
|
800
799
|
if (fontPrev) fontPrev.style.fontFamily = v;
|
|
801
|
-
|
|
802
|
-
update(updateStyle(S, [], varName, v));
|
|
800
|
+
transactDoc(activeTab.value, (t) => mutateUpdateStyle(t, [], varName, v));
|
|
803
801
|
}, 400);
|
|
804
802
|
}}
|
|
805
803
|
></sp-textfield>
|
|
@@ -824,8 +822,7 @@ function renderVarRow(catKey, catMeta, varName, varVal, isNew) {
|
|
|
824
822
|
const val = getValueFn();
|
|
825
823
|
const generatedVar = friendlyNameToVar(name, catMeta.prefix);
|
|
826
824
|
if (!generatedVar || !val) return;
|
|
827
|
-
|
|
828
|
-
update(updateStyle(S, [], generatedVar, val));
|
|
825
|
+
transactDoc(activeTab.value, (t) => mutateUpdateStyle(t, [], generatedVar, val));
|
|
829
826
|
}}
|
|
830
827
|
>Add</sp-action-button
|
|
831
828
|
>
|
|
@@ -858,8 +855,7 @@ function renderVarRow(catKey, catMeta, varName, varVal, isNew) {
|
|
|
858
855
|
class="sb-var-del"
|
|
859
856
|
style="pointer-events:auto"
|
|
860
857
|
@click=${() => {
|
|
861
|
-
|
|
862
|
-
update(updateStyle(S, [], varName, undefined));
|
|
858
|
+
transactDoc(activeTab.value, (t) => mutateUpdateStyle(t, [], varName, undefined));
|
|
863
859
|
}}
|
|
864
860
|
>
|
|
865
861
|
<sp-icon-delete slot="icon"></sp-icon-delete>
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tab strip — renders open tabs above the canvas area.
|
|
3
|
+
*
|
|
4
|
+
* Uses the reactive workspace model: reads from workspace.tabOrder, workspace.tabs,
|
|
5
|
+
* workspace.activeTabId. Clicks call activateTab/closeTab from workspace.js.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { html, render as litRender, nothing } from "lit-html";
|
|
9
|
+
import { effect, effectScope } from "../reactivity.js";
|
|
10
|
+
import { workspace, activateTab, closeTab } from "../workspace/workspace.js";
|
|
11
|
+
|
|
12
|
+
/** @typedef {import("../tabs/tab.js").Tab} Tab */
|
|
13
|
+
|
|
14
|
+
/** @type {HTMLElement | null} */
|
|
15
|
+
let _host = null;
|
|
16
|
+
|
|
17
|
+
/** @type {import("@vue/reactivity").EffectScope | null} */
|
|
18
|
+
let _scope = null;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Mount the tab strip into the given host element.
|
|
22
|
+
*
|
|
23
|
+
* @param {HTMLElement} host
|
|
24
|
+
*/
|
|
25
|
+
export function mount(host) {
|
|
26
|
+
_host = host;
|
|
27
|
+
_scope = effectScope();
|
|
28
|
+
_scope.run(() => {
|
|
29
|
+
effect(() => {
|
|
30
|
+
void workspace.tabOrder;
|
|
31
|
+
void workspace.activeTabId;
|
|
32
|
+
for (const tab of workspace.tabs.values()) {
|
|
33
|
+
void tab.doc.dirty;
|
|
34
|
+
void tab.documentPath;
|
|
35
|
+
}
|
|
36
|
+
render();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function unmount() {
|
|
42
|
+
_scope?.stop();
|
|
43
|
+
_scope = null;
|
|
44
|
+
_host = null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function render() {
|
|
48
|
+
if (!_host) return;
|
|
49
|
+
|
|
50
|
+
if (workspace.tabOrder.length <= 1) {
|
|
51
|
+
litRender(nothing, _host);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
litRender(
|
|
56
|
+
html`
|
|
57
|
+
<div class="tab-strip">
|
|
58
|
+
${workspace.tabOrder.map((id) => {
|
|
59
|
+
const tab = workspace.tabs.get(id);
|
|
60
|
+
if (!tab) return nothing;
|
|
61
|
+
const isActive = id === workspace.activeTabId;
|
|
62
|
+
const isDirty = tab.doc.dirty;
|
|
63
|
+
const label = tabLabel(tab);
|
|
64
|
+
return html`
|
|
65
|
+
<div
|
|
66
|
+
class="tab-strip-tab ${isActive ? "active" : ""}"
|
|
67
|
+
@click=${() => activateTab(id)}
|
|
68
|
+
@auxclick=${(/** @type {MouseEvent} */ e) => {
|
|
69
|
+
if (e.button === 1) {
|
|
70
|
+
e.preventDefault();
|
|
71
|
+
requestClose(id);
|
|
72
|
+
}
|
|
73
|
+
}}
|
|
74
|
+
title=${tab.documentPath || "Untitled"}
|
|
75
|
+
>
|
|
76
|
+
<span class="tab-strip-label">${label}</span>
|
|
77
|
+
${isDirty ? html`<span class="tab-strip-dirty">●</span>` : nothing}
|
|
78
|
+
<button
|
|
79
|
+
class="tab-strip-close"
|
|
80
|
+
@click=${(/** @type {Event} */ e) => {
|
|
81
|
+
e.stopPropagation();
|
|
82
|
+
requestClose(id);
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
85
|
+
×
|
|
86
|
+
</button>
|
|
87
|
+
</div>
|
|
88
|
+
`;
|
|
89
|
+
})}
|
|
90
|
+
</div>
|
|
91
|
+
`,
|
|
92
|
+
_host,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Derive a short label from the tab's documentPath.
|
|
98
|
+
*
|
|
99
|
+
* @param {Tab} tab
|
|
100
|
+
* @returns {string}
|
|
101
|
+
*/
|
|
102
|
+
function tabLabel(tab) {
|
|
103
|
+
const path = tab.documentPath;
|
|
104
|
+
if (!path) return "Untitled";
|
|
105
|
+
const parts = path.split("/");
|
|
106
|
+
return parts[parts.length - 1];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Close a tab, prompting if dirty.
|
|
111
|
+
*
|
|
112
|
+
* @param {string} id
|
|
113
|
+
*/
|
|
114
|
+
function requestClose(id) {
|
|
115
|
+
const tab = workspace.tabs.get(id);
|
|
116
|
+
if (!tab) return;
|
|
117
|
+
if (tab.doc.dirty) {
|
|
118
|
+
const confirmed = window.confirm(
|
|
119
|
+
`"${tabLabel(tab)}" has unsaved changes. Close without saving?`,
|
|
120
|
+
);
|
|
121
|
+
if (!confirmed) return;
|
|
122
|
+
}
|
|
123
|
+
closeTab(id);
|
|
124
|
+
}
|
package/src/panels/toolbar.js
CHANGED
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { html, render as litRender, nothing } from "lit-html";
|
|
7
|
-
import {
|
|
7
|
+
import { updateSession, updateUi } from "../store.js";
|
|
8
|
+
import { undo as tabUndo, redo as tabRedo } from "../tabs/transact.js";
|
|
9
|
+
import { effect, effectScope } from "../reactivity.js";
|
|
10
|
+
import { activeTab } from "../workspace/workspace.js";
|
|
8
11
|
import { getEffectiveMedia } from "../site-context.js";
|
|
9
12
|
import { mediaDisplayName } from "./shared.js";
|
|
10
13
|
import { view } from "../view.js";
|
|
@@ -15,8 +18,8 @@ let _rootEl = null;
|
|
|
15
18
|
/** @type {any} */
|
|
16
19
|
let _ctx = null;
|
|
17
20
|
|
|
18
|
-
/** @type {(
|
|
19
|
-
let
|
|
21
|
+
/** @type {import("@vue/reactivity").EffectScope | null} */
|
|
22
|
+
let _scope = null;
|
|
20
23
|
|
|
21
24
|
const toolbarIconMap = /** @type {Record<string, any>} */ ({
|
|
22
25
|
"sp-icon-folder-open": html`<sp-icon-folder-open slot="icon"></sp-icon-folder-open>`,
|
|
@@ -59,12 +62,28 @@ function tbBtnTpl(label, onClick, iconTag) {
|
|
|
59
62
|
export function mount(rootEl, ctx) {
|
|
60
63
|
_rootEl = rootEl;
|
|
61
64
|
_ctx = ctx;
|
|
62
|
-
|
|
65
|
+
_scope = effectScope();
|
|
66
|
+
_scope.run(() => {
|
|
67
|
+
effect(() => {
|
|
68
|
+
const tab = activeTab.value;
|
|
69
|
+
if (!tab) return;
|
|
70
|
+
// Read reactive properties to establish tracking
|
|
71
|
+
void tab.doc.document;
|
|
72
|
+
void tab.doc.dirty;
|
|
73
|
+
void tab.doc.mode;
|
|
74
|
+
void tab.session.selection;
|
|
75
|
+
void tab.session.ui.canvasMode;
|
|
76
|
+
void tab.session.ui.editingFunction;
|
|
77
|
+
void tab.session.ui.featureToggles;
|
|
78
|
+
void tab.session.ui.rightTab;
|
|
79
|
+
render();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
63
82
|
}
|
|
64
83
|
|
|
65
84
|
export function unmount() {
|
|
66
|
-
|
|
67
|
-
|
|
85
|
+
_scope?.stop();
|
|
86
|
+
_scope = null;
|
|
68
87
|
_rootEl = null;
|
|
69
88
|
_ctx = null;
|
|
70
89
|
}
|
|
@@ -79,7 +98,18 @@ export function render() {
|
|
|
79
98
|
}
|
|
80
99
|
|
|
81
100
|
function toolbarTemplate() {
|
|
82
|
-
const
|
|
101
|
+
const tab = activeTab.value;
|
|
102
|
+
if (!tab) return html``;
|
|
103
|
+
const S = /** @type {any} */ ({
|
|
104
|
+
document: tab.doc.document,
|
|
105
|
+
ui: tab.session.ui,
|
|
106
|
+
mode: tab.doc.mode,
|
|
107
|
+
selection: tab.session.selection,
|
|
108
|
+
dirty: tab.doc.dirty,
|
|
109
|
+
documentPath: tab.documentPath,
|
|
110
|
+
fileHandle: tab.fileHandle,
|
|
111
|
+
documentStack: tab.session.documentStack,
|
|
112
|
+
});
|
|
83
113
|
const canvasMode = _ctx.getCanvasMode();
|
|
84
114
|
const hasStack = S.documentStack && S.documentStack.length > 0;
|
|
85
115
|
const hasFunc = !!S.ui.editingFunction;
|
|
@@ -183,7 +213,7 @@ function toolbarTemplate() {
|
|
|
183
213
|
/** @type {Record<string, any>} */
|
|
184
214
|
const uiPatch = { editingFunction: null };
|
|
185
215
|
if (m.key === "settings") uiPatch.rightTab = "style";
|
|
186
|
-
if (m.key === "manage")
|
|
216
|
+
if (m.key === "manage") view.leftTab = "files";
|
|
187
217
|
updateSession({ ui: uiPatch });
|
|
188
218
|
_ctx.renderCanvas();
|
|
189
219
|
_ctx.safeRenderRightPanel();
|
|
@@ -203,8 +233,8 @@ function toolbarTemplate() {
|
|
|
203
233
|
${tbBtnTpl("Save", _ctx.saveFile, "sp-icon-save-floppy")}
|
|
204
234
|
</sp-action-group>
|
|
205
235
|
<sp-action-group compact size="s">
|
|
206
|
-
${tbBtnTpl("Undo", () =>
|
|
207
|
-
${tbBtnTpl("Redo", () =>
|
|
236
|
+
${tbBtnTpl("Undo", () => tabUndo(activeTab.value), "sp-icon-undo")}
|
|
237
|
+
${tbBtnTpl("Redo", () => tabRedo(activeTab.value), "sp-icon-redo")}
|
|
208
238
|
</sp-action-group>
|
|
209
239
|
<div class="tb-spacer"></div>
|
|
210
240
|
${S.documentPath
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-exports from @vue/reactivity. All studio code imports reactivity primitives from here (not
|
|
3
|
+
* directly from the package) for grep-ability and potential future wrapping.
|
|
4
|
+
*
|
|
5
|
+
* Version is pinned to match @jxsuite/runtime — reactive proxies from one version don't track in
|
|
6
|
+
* effects from another. See migrate-reactivity-workspace.md for rationale.
|
|
7
|
+
*
|
|
8
|
+
* Batching note: Vue's reactivity batches synchronous effect re-runs within a microtask. Use
|
|
9
|
+
* queueMicrotask() or Promise.resolve().then() to wait for the flush if needed (@vue/reactivity
|
|
10
|
+
* standalone doesn't ship nextTick).
|
|
11
|
+
*/
|
|
12
|
+
export {
|
|
13
|
+
reactive,
|
|
14
|
+
ref,
|
|
15
|
+
computed,
|
|
16
|
+
readonly,
|
|
17
|
+
shallowReactive,
|
|
18
|
+
shallowRef,
|
|
19
|
+
effect,
|
|
20
|
+
effectScope,
|
|
21
|
+
getCurrentScope,
|
|
22
|
+
onScopeDispose,
|
|
23
|
+
pauseTracking,
|
|
24
|
+
resetTracking,
|
|
25
|
+
toRaw,
|
|
26
|
+
isReactive,
|
|
27
|
+
isRef,
|
|
28
|
+
} from "@vue/reactivity";
|
|
@@ -8,14 +8,19 @@
|
|
|
8
8
|
import { html, render as litRender } from "lit-html";
|
|
9
9
|
import { getPlatform } from "../platform.js";
|
|
10
10
|
import { projectState } from "../store.js";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
fieldCardTpl,
|
|
13
|
+
addFieldFormTpl,
|
|
14
|
+
schemaForType,
|
|
15
|
+
detectFieldFormat,
|
|
16
|
+
} from "./schema-field-ui.js";
|
|
12
17
|
|
|
13
18
|
// ─── Module state ─────────────────────────────────────────────────────────────
|
|
14
19
|
|
|
15
20
|
/** @type {string | null} */
|
|
16
21
|
let selectedContentType = null;
|
|
17
22
|
let showAddField = false;
|
|
18
|
-
let newFieldState = { name: "", type: "string", required: false };
|
|
23
|
+
let newFieldState = { name: "", type: "string", format: "", required: false };
|
|
19
24
|
let showNewContentType = false;
|
|
20
25
|
let newContentTypeName = "";
|
|
21
26
|
|
|
@@ -76,7 +81,7 @@ function handleAddField(rerender) {
|
|
|
76
81
|
if (!schema) return;
|
|
77
82
|
|
|
78
83
|
if (!schema.properties) schema.properties = {};
|
|
79
|
-
schema.properties[name] = schemaForType(newFieldState.type);
|
|
84
|
+
schema.properties[name] = schemaForType(newFieldState.type, newFieldState.format || undefined);
|
|
80
85
|
|
|
81
86
|
if (newFieldState.required) {
|
|
82
87
|
if (!schema.required) schema.required = [];
|
|
@@ -84,7 +89,7 @@ function handleAddField(rerender) {
|
|
|
84
89
|
}
|
|
85
90
|
|
|
86
91
|
showAddField = false;
|
|
87
|
-
newFieldState = { name: "", type: "string", required: false };
|
|
92
|
+
newFieldState = { name: "", type: "string", format: "", required: false };
|
|
88
93
|
rerender();
|
|
89
94
|
saveProjectConfig();
|
|
90
95
|
}
|
|
@@ -158,7 +163,27 @@ function handleChangeType(fieldName, newType, rerender) {
|
|
|
158
163
|
const schema = getSelectedSchema();
|
|
159
164
|
if (!schema?.properties?.[fieldName]) return;
|
|
160
165
|
|
|
161
|
-
|
|
166
|
+
const oldFormat =
|
|
167
|
+
newType === "string" || newType === "array"
|
|
168
|
+
? detectFieldFormat(schema.properties[fieldName])
|
|
169
|
+
: undefined;
|
|
170
|
+
schema.properties[fieldName] = schemaForType(newType, oldFormat || undefined);
|
|
171
|
+
rerender();
|
|
172
|
+
saveProjectConfig();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* @param {string} fieldName
|
|
177
|
+
* @param {string} format
|
|
178
|
+
* @param {() => void} rerender
|
|
179
|
+
*/
|
|
180
|
+
function handleChangeFormat(fieldName, format, rerender) {
|
|
181
|
+
const schema = getSelectedSchema();
|
|
182
|
+
if (!schema?.properties?.[fieldName]) return;
|
|
183
|
+
|
|
184
|
+
const prop = schema.properties[fieldName];
|
|
185
|
+
const type = prop.type || "string";
|
|
186
|
+
schema.properties[fieldName] = schemaForType(type, format || undefined);
|
|
162
187
|
rerender();
|
|
163
188
|
saveProjectConfig();
|
|
164
189
|
}
|
|
@@ -278,7 +303,29 @@ function handleChangeNestedType(parentName, childName, newType, rerender) {
|
|
|
278
303
|
const parent = schema?.properties?.[parentName];
|
|
279
304
|
if (!parent?.properties?.[childName]) return;
|
|
280
305
|
|
|
281
|
-
|
|
306
|
+
const oldFormat =
|
|
307
|
+
newType === "string" || newType === "array"
|
|
308
|
+
? detectFieldFormat(parent.properties[childName])
|
|
309
|
+
: undefined;
|
|
310
|
+
parent.properties[childName] = schemaForType(newType, oldFormat || undefined);
|
|
311
|
+
rerender();
|
|
312
|
+
saveProjectConfig();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* @param {string} parentName
|
|
317
|
+
* @param {string} childName
|
|
318
|
+
* @param {string} format
|
|
319
|
+
* @param {() => void} rerender
|
|
320
|
+
*/
|
|
321
|
+
function handleChangeNestedFormat(parentName, childName, format, rerender) {
|
|
322
|
+
const schema = getSelectedSchema();
|
|
323
|
+
const parent = schema?.properties?.[parentName];
|
|
324
|
+
if (!parent?.properties?.[childName]) return;
|
|
325
|
+
|
|
326
|
+
const prop = parent.properties[childName];
|
|
327
|
+
const type = prop.type || "string";
|
|
328
|
+
parent.properties[childName] = schemaForType(type, format || undefined);
|
|
282
329
|
rerender();
|
|
283
330
|
saveProjectConfig();
|
|
284
331
|
}
|
|
@@ -381,12 +428,14 @@ export function renderContentTypesEditor(container) {
|
|
|
381
428
|
onToggleRequired: (n) => handleToggleRequired(n, rerender),
|
|
382
429
|
onRename: (oldN, newN) => handleRenameField(oldN, newN, rerender),
|
|
383
430
|
onChangeType: (n, t) => handleChangeType(n, t, rerender),
|
|
431
|
+
onChangeFormat: (n, f) => handleChangeFormat(n, f, rerender),
|
|
384
432
|
onChangeRefTarget: (n, target) => handleChangeRefTarget(n, target, rerender),
|
|
385
433
|
onAddNestedField: (p, s) => handleAddNestedField(p, s, rerender),
|
|
386
434
|
onDeleteNested: (p, c) => handleDeleteNested(p, c, rerender),
|
|
387
435
|
onToggleNestedRequired: (p, c) => handleToggleNestedRequired(p, c, rerender),
|
|
388
436
|
onRenameNested: (p, o, n) => handleRenameNested(p, o, n, rerender),
|
|
389
437
|
onChangeNestedType: (p, c, t) => handleChangeNestedType(p, c, t, rerender),
|
|
438
|
+
onChangeNestedFormat: (p, c, f) => handleChangeNestedFormat(p, c, f, rerender),
|
|
390
439
|
};
|
|
391
440
|
|
|
392
441
|
const fieldCards = Object.entries(properties).map(([name, def]) =>
|
|
@@ -423,7 +472,7 @@ export function renderContentTypesEditor(container) {
|
|
|
423
472
|
onConfirm: () => handleAddField(rerender),
|
|
424
473
|
onCancel: () => {
|
|
425
474
|
showAddField = false;
|
|
426
|
-
newFieldState = { name: "", type: "string", required: false };
|
|
475
|
+
newFieldState = { name: "", type: "string", format: "", required: false };
|
|
427
476
|
rerender();
|
|
428
477
|
},
|
|
429
478
|
})
|
|
@@ -9,14 +9,19 @@
|
|
|
9
9
|
import { html, render as litRender } from "lit-html";
|
|
10
10
|
import { getPlatform } from "../platform.js";
|
|
11
11
|
import { projectState } from "../store.js";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
fieldCardTpl,
|
|
14
|
+
addFieldFormTpl,
|
|
15
|
+
schemaForType,
|
|
16
|
+
detectFieldFormat,
|
|
17
|
+
} from "./schema-field-ui.js";
|
|
13
18
|
|
|
14
19
|
// ─── Module state ─────────────────────────────────────────────────────────────
|
|
15
20
|
|
|
16
21
|
/** @type {string | null} */
|
|
17
22
|
let selectedDef = null;
|
|
18
23
|
let showAddField = false;
|
|
19
|
-
let newFieldState = { name: "", type: "string", required: false };
|
|
24
|
+
let newFieldState = { name: "", type: "string", format: "", required: false };
|
|
20
25
|
let showNewDef = false;
|
|
21
26
|
let newDefName = "";
|
|
22
27
|
|
|
@@ -70,7 +75,7 @@ function handleAddField(rerender) {
|
|
|
70
75
|
if (!def) return;
|
|
71
76
|
|
|
72
77
|
if (!def.properties) def.properties = {};
|
|
73
|
-
def.properties[name] = schemaForType(newFieldState.type);
|
|
78
|
+
def.properties[name] = schemaForType(newFieldState.type, newFieldState.format || undefined);
|
|
74
79
|
|
|
75
80
|
if (newFieldState.required) {
|
|
76
81
|
if (!def.required) def.required = [];
|
|
@@ -78,7 +83,7 @@ function handleAddField(rerender) {
|
|
|
78
83
|
}
|
|
79
84
|
|
|
80
85
|
showAddField = false;
|
|
81
|
-
newFieldState = { name: "", type: "string", required: false };
|
|
86
|
+
newFieldState = { name: "", type: "string", format: "", required: false };
|
|
82
87
|
rerender();
|
|
83
88
|
saveProjectConfig();
|
|
84
89
|
}
|
|
@@ -150,7 +155,27 @@ function handleChangeType(fieldName, newType, rerender) {
|
|
|
150
155
|
const def = getSelectedDef();
|
|
151
156
|
if (!def?.properties?.[fieldName]) return;
|
|
152
157
|
|
|
153
|
-
|
|
158
|
+
const oldFormat =
|
|
159
|
+
newType === "string" || newType === "array"
|
|
160
|
+
? detectFieldFormat(def.properties[fieldName])
|
|
161
|
+
: undefined;
|
|
162
|
+
def.properties[fieldName] = schemaForType(newType, oldFormat || undefined);
|
|
163
|
+
rerender();
|
|
164
|
+
saveProjectConfig();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* @param {string} fieldName
|
|
169
|
+
* @param {string} format
|
|
170
|
+
* @param {() => void} rerender
|
|
171
|
+
*/
|
|
172
|
+
function handleChangeFormat(fieldName, format, rerender) {
|
|
173
|
+
const def = getSelectedDef();
|
|
174
|
+
if (!def?.properties?.[fieldName]) return;
|
|
175
|
+
|
|
176
|
+
const prop = def.properties[fieldName];
|
|
177
|
+
const type = prop.type || "string";
|
|
178
|
+
def.properties[fieldName] = schemaForType(type, format || undefined);
|
|
154
179
|
rerender();
|
|
155
180
|
saveProjectConfig();
|
|
156
181
|
}
|
|
@@ -256,7 +281,29 @@ function handleChangeNestedType(parentName, childName, newType, rerender) {
|
|
|
256
281
|
const parent = def?.properties?.[parentName];
|
|
257
282
|
if (!parent?.properties?.[childName]) return;
|
|
258
283
|
|
|
259
|
-
|
|
284
|
+
const oldFormat =
|
|
285
|
+
newType === "string" || newType === "array"
|
|
286
|
+
? detectFieldFormat(parent.properties[childName])
|
|
287
|
+
: undefined;
|
|
288
|
+
parent.properties[childName] = schemaForType(newType, oldFormat || undefined);
|
|
289
|
+
rerender();
|
|
290
|
+
saveProjectConfig();
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* @param {string} parentName
|
|
295
|
+
* @param {string} childName
|
|
296
|
+
* @param {string} format
|
|
297
|
+
* @param {() => void} rerender
|
|
298
|
+
*/
|
|
299
|
+
function handleChangeNestedFormat(parentName, childName, format, rerender) {
|
|
300
|
+
const def = getSelectedDef();
|
|
301
|
+
const parent = def?.properties?.[parentName];
|
|
302
|
+
if (!parent?.properties?.[childName]) return;
|
|
303
|
+
|
|
304
|
+
const prop = parent.properties[childName];
|
|
305
|
+
const type = prop.type || "string";
|
|
306
|
+
parent.properties[childName] = schemaForType(type, format || undefined);
|
|
260
307
|
rerender();
|
|
261
308
|
saveProjectConfig();
|
|
262
309
|
}
|
|
@@ -358,11 +405,13 @@ export function renderDefsEditor(container) {
|
|
|
358
405
|
onToggleRequired: (n) => handleToggleRequired(n, rerender),
|
|
359
406
|
onRename: (oldN, newN) => handleRenameField(oldN, newN, rerender),
|
|
360
407
|
onChangeType: (n, t) => handleChangeType(n, t, rerender),
|
|
408
|
+
onChangeFormat: (n, f) => handleChangeFormat(n, f, rerender),
|
|
361
409
|
onAddNestedField: (p, s) => handleAddNestedField(p, s, rerender),
|
|
362
410
|
onDeleteNested: (p, c) => handleDeleteNested(p, c, rerender),
|
|
363
411
|
onToggleNestedRequired: (p, c) => handleToggleNestedRequired(p, c, rerender),
|
|
364
412
|
onRenameNested: (p, o, n) => handleRenameNested(p, o, n, rerender),
|
|
365
413
|
onChangeNestedType: (p, c, t) => handleChangeNestedType(p, c, t, rerender),
|
|
414
|
+
onChangeNestedFormat: (p, c, f) => handleChangeNestedFormat(p, c, f, rerender),
|
|
366
415
|
};
|
|
367
416
|
|
|
368
417
|
const fieldCards = Object.entries(properties).map(([name, fieldDef]) =>
|
|
@@ -392,7 +441,7 @@ export function renderDefsEditor(container) {
|
|
|
392
441
|
onConfirm: () => handleAddField(rerender),
|
|
393
442
|
onCancel: () => {
|
|
394
443
|
showAddField = false;
|
|
395
|
-
newFieldState = { name: "", type: "string", required: false };
|
|
444
|
+
newFieldState = { name: "", type: "string", format: "", required: false };
|
|
396
445
|
rerender();
|
|
397
446
|
},
|
|
398
447
|
})
|