@jxsuite/studio 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/studio.css +98 -98
- package/dist/studio.js +13476 -12330
- package/dist/studio.js.map +152 -148
- 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 +19 -7
- package/src/browse/browse-modal.js +16 -13
- package/src/browse/browse.js +44 -35
- package/src/canvas/canvas-diff.js +24 -16
- package/src/canvas/canvas-helpers.js +24 -19
- package/src/canvas/canvas-live-render.js +113 -39
- package/src/canvas/canvas-render.js +119 -73
- package/src/canvas/canvas-utils.js +51 -21
- package/src/canvas/nested-site-style.js +50 -0
- package/src/editor/component-inline-edit.js +14 -13
- package/src/editor/content-inline-edit.js +22 -10
- package/src/editor/context-menu.js +28 -14
- package/src/editor/convert-targets.js +60 -0
- package/src/editor/convert-to-component.js +20 -19
- package/src/editor/convert-to-repeater.js +226 -0
- package/src/editor/inline-edit.js +69 -28
- package/src/editor/insertion-helper.js +16 -12
- package/src/editor/shortcuts.js +14 -4
- package/src/editor/slash-menu.js +40 -19
- package/src/files/components.js +1 -1
- package/src/files/file-ops.js +37 -16
- package/src/files/files.js +322 -68
- package/src/github/github-auth.js +122 -0
- package/src/github/github-publish.js +126 -0
- package/src/markdown/md-convert.js +172 -107
- package/src/new-project/new-project-modal.js +204 -0
- package/src/panels/activity-bar.js +27 -25
- package/src/panels/ai-panel.js +406 -0
- package/src/panels/block-action-bar.js +77 -28
- package/src/panels/canvas-dnd.js +8 -2
- package/src/panels/data-explorer.js +19 -13
- package/src/panels/dnd.js +144 -92
- package/src/panels/editors.js +55 -35
- package/src/panels/elements-panel.js +33 -19
- package/src/panels/events-panel.js +15 -10
- package/src/panels/git-panel.js +475 -99
- package/src/panels/head-panel.js +121 -81
- package/src/panels/imports-panel.js +94 -65
- package/src/panels/layers-panel.js +64 -43
- package/src/panels/left-panel.js +105 -48
- package/src/panels/overlays.js +1 -0
- package/src/panels/panel-events.js +45 -30
- package/src/panels/preview-render.js +23 -18
- package/src/panels/properties-panel.js +378 -257
- package/src/panels/pseudo-preview.js +2 -2
- package/src/panels/quick-search.js +11 -9
- package/src/panels/right-panel.js +42 -16
- package/src/panels/shared.js +4 -4
- package/src/panels/signals-panel.js +415 -166
- package/src/panels/statusbar.js +10 -6
- package/src/panels/style-inputs.js +67 -35
- package/src/panels/style-panel.js +239 -134
- package/src/panels/style-utils.js +34 -16
- package/src/panels/stylebook-layers-panel.js +12 -10
- package/src/panels/stylebook-panel.js +134 -66
- package/src/panels/tab-strip.js +5 -2
- package/src/panels/toolbar.js +333 -109
- package/src/panels/welcome-screen.js +121 -0
- package/src/platform.js +2 -4
- package/src/platforms/devserver.js +113 -7
- package/src/resize-edges.js +98 -0
- package/src/services/cem-export.js +12 -7
- package/src/services/code-services.js +30 -12
- package/src/settings/content-types-editor.js +8 -8
- package/src/settings/css-vars-editor.js +30 -24
- package/src/settings/defs-editor.js +12 -7
- package/src/settings/general-settings.js +85 -3
- package/src/settings/head-editor.js +30 -24
- package/src/settings/schema-field-ui.js +58 -39
- package/src/settings/settings-modal.js +12 -9
- package/src/site-context.js +41 -31
- package/src/state.js +70 -29
- package/src/store.js +36 -35
- package/src/studio.js +120 -90
- package/src/tabs/tab.js +67 -43
- package/src/tabs/transact.js +23 -13
- package/src/ui/button-group.js +24 -24
- package/src/ui/color-selector.js +28 -23
- package/src/ui/field-row.js +3 -3
- package/src/ui/icons.js +2 -2
- package/src/ui/layers.js +31 -1
- package/src/ui/media-picker.js +7 -7
- package/src/ui/spectrum.js +5 -1
- package/src/ui/unit-selector.js +18 -16
- package/src/ui/value-selector.js +21 -15
- package/src/ui/widgets.js +22 -19
- package/src/utils/canvas-media.js +5 -4
- package/src/utils/edit-display.js +32 -26
- package/src/utils/google-fonts.js +11 -11
- package/src/utils/inherited-style.js +9 -8
- package/src/utils/studio-utils.js +18 -18
- package/src/view.js +58 -1
- package/src/workspace/workspace.js +29 -7
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* New Project modal — guides the user through creating a new Jx project. Fields mirror the CLI
|
|
3
|
+
* scaffolder: name, description, url, adapter.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { html } from "lit-html";
|
|
7
|
+
import { openModal } from "../ui/layers.js";
|
|
8
|
+
import { getPlatform } from "../platform.js";
|
|
9
|
+
|
|
10
|
+
/** @type {ReturnType<typeof openModal> | null} */
|
|
11
|
+
let _handle = null;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @type {{
|
|
15
|
+
* name: string;
|
|
16
|
+
* description: string;
|
|
17
|
+
* url: string;
|
|
18
|
+
* adapter: string;
|
|
19
|
+
* directory: string;
|
|
20
|
+
* }}
|
|
21
|
+
*/
|
|
22
|
+
let _form = { name: "", description: "", url: "", adapter: "static", directory: "" };
|
|
23
|
+
|
|
24
|
+
/** @type {string} */
|
|
25
|
+
let _error = "";
|
|
26
|
+
|
|
27
|
+
/** @type {boolean} */
|
|
28
|
+
let _creating = false;
|
|
29
|
+
|
|
30
|
+
/** @type {((result: { root: string; config: object } | null) => void) | null} */
|
|
31
|
+
let _resolve = null;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Open the New Project modal. Returns a promise that resolves with the created project info (or
|
|
35
|
+
* null if cancelled).
|
|
36
|
+
*
|
|
37
|
+
* @returns {Promise<{ root: string; config: object } | null>}
|
|
38
|
+
*/
|
|
39
|
+
export function openNewProjectModal() {
|
|
40
|
+
if (_handle) return Promise.resolve(null);
|
|
41
|
+
_form = { name: "", description: "", url: "", adapter: "static", directory: "" };
|
|
42
|
+
_error = "";
|
|
43
|
+
_creating = false;
|
|
44
|
+
|
|
45
|
+
return new Promise((resolve) => {
|
|
46
|
+
_resolve = resolve;
|
|
47
|
+
renderModal();
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function closeNewProjectModal() {
|
|
52
|
+
if (!_handle) return;
|
|
53
|
+
_handle.close();
|
|
54
|
+
_handle = null;
|
|
55
|
+
if (_resolve) {
|
|
56
|
+
_resolve(null);
|
|
57
|
+
_resolve = null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function renderModal() {
|
|
62
|
+
const onInput =
|
|
63
|
+
(/** @type {"name" | "description" | "url" | "adapter" | "directory"} */ field) =>
|
|
64
|
+
(/** @type {Event} */ e) => {
|
|
65
|
+
_form[field] = /** @type {HTMLInputElement} */ (e.target).value;
|
|
66
|
+
if (field === "name" && !_form.directory) {
|
|
67
|
+
// Auto-derive directory slug from name while user hasn't manually typed one
|
|
68
|
+
_dirDerived = true;
|
|
69
|
+
}
|
|
70
|
+
if (_dirDerived && field === "name") {
|
|
71
|
+
_form.directory = _form.name
|
|
72
|
+
.toLowerCase()
|
|
73
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
74
|
+
.replace(/^-|-$/g, "");
|
|
75
|
+
}
|
|
76
|
+
if (field === "directory") _dirDerived = false;
|
|
77
|
+
renderModal();
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const onAdapterChange = (/** @type {Event} */ e) => {
|
|
81
|
+
_form.adapter = /** @type {HTMLInputElement} */ (e.target).value;
|
|
82
|
+
renderModal();
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const onSubmit = async () => {
|
|
86
|
+
if (!_form.name.trim()) {
|
|
87
|
+
_error = "Project name is required";
|
|
88
|
+
renderModal();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (!_form.directory.trim()) {
|
|
92
|
+
_form.directory = _form.name
|
|
93
|
+
.toLowerCase()
|
|
94
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
95
|
+
.replace(/^-|-$/g, "");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
_creating = true;
|
|
99
|
+
_error = "";
|
|
100
|
+
renderModal();
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const platform = getPlatform();
|
|
104
|
+
const result = await platform.createProject(_form);
|
|
105
|
+
_creating = false;
|
|
106
|
+
if (_handle) {
|
|
107
|
+
_handle.close();
|
|
108
|
+
_handle = null;
|
|
109
|
+
}
|
|
110
|
+
if (_resolve) {
|
|
111
|
+
_resolve(result);
|
|
112
|
+
_resolve = null;
|
|
113
|
+
}
|
|
114
|
+
} catch (/** @type {unknown} */ e) {
|
|
115
|
+
_creating = false;
|
|
116
|
+
_error = /** @type {Error} */ (e).message;
|
|
117
|
+
renderModal();
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const tpl = html`
|
|
122
|
+
<sp-underlay open @close=${closeNewProjectModal}></sp-underlay>
|
|
123
|
+
<div
|
|
124
|
+
class="new-project-modal"
|
|
125
|
+
@keydown=${(/** @type {KeyboardEvent} */ e) => {
|
|
126
|
+
if (e.key === "Escape") closeNewProjectModal();
|
|
127
|
+
}}
|
|
128
|
+
>
|
|
129
|
+
<div class="new-project-modal-header">
|
|
130
|
+
<h2 class="new-project-modal-title">New Project</h2>
|
|
131
|
+
<sp-action-button quiet size="s" @click=${closeNewProjectModal} title="Close">
|
|
132
|
+
<sp-icon-close slot="icon"></sp-icon-close>
|
|
133
|
+
</sp-action-button>
|
|
134
|
+
</div>
|
|
135
|
+
<div class="new-project-modal-body">
|
|
136
|
+
<label class="new-project-field">
|
|
137
|
+
<span class="new-project-label">Project Name *</span>
|
|
138
|
+
<sp-textfield
|
|
139
|
+
placeholder="My Site"
|
|
140
|
+
.value=${_form.name}
|
|
141
|
+
@input=${onInput("name")}
|
|
142
|
+
style="width: 100%"
|
|
143
|
+
></sp-textfield>
|
|
144
|
+
</label>
|
|
145
|
+
|
|
146
|
+
<label class="new-project-field">
|
|
147
|
+
<span class="new-project-label">Directory</span>
|
|
148
|
+
<sp-textfield
|
|
149
|
+
placeholder="my-site"
|
|
150
|
+
.value=${_form.directory}
|
|
151
|
+
@input=${onInput("directory")}
|
|
152
|
+
style="width: 100%"
|
|
153
|
+
></sp-textfield>
|
|
154
|
+
</label>
|
|
155
|
+
|
|
156
|
+
<label class="new-project-field">
|
|
157
|
+
<span class="new-project-label">Description</span>
|
|
158
|
+
<sp-textfield
|
|
159
|
+
placeholder="A short description of the site"
|
|
160
|
+
.value=${_form.description}
|
|
161
|
+
@input=${onInput("description")}
|
|
162
|
+
style="width: 100%"
|
|
163
|
+
></sp-textfield>
|
|
164
|
+
</label>
|
|
165
|
+
|
|
166
|
+
<label class="new-project-field">
|
|
167
|
+
<span class="new-project-label">Production URL</span>
|
|
168
|
+
<sp-textfield
|
|
169
|
+
placeholder="https://example.com"
|
|
170
|
+
.value=${_form.url}
|
|
171
|
+
@input=${onInput("url")}
|
|
172
|
+
style="width: 100%"
|
|
173
|
+
></sp-textfield>
|
|
174
|
+
</label>
|
|
175
|
+
|
|
176
|
+
<label class="new-project-field">
|
|
177
|
+
<span class="new-project-label">Deployment Adapter</span>
|
|
178
|
+
<sp-picker label="Adapter" .value=${_form.adapter} @change=${onAdapterChange}>
|
|
179
|
+
<sp-menu-item value="static">Static</sp-menu-item>
|
|
180
|
+
<sp-menu-item value="cloudflare-pages">Cloudflare Pages</sp-menu-item>
|
|
181
|
+
<sp-menu-item value="node">Node</sp-menu-item>
|
|
182
|
+
<sp-menu-item value="bun">Bun</sp-menu-item>
|
|
183
|
+
</sp-picker>
|
|
184
|
+
</label>
|
|
185
|
+
|
|
186
|
+
${_error ? html`<div class="new-project-error">${_error}</div>` : ""}
|
|
187
|
+
</div>
|
|
188
|
+
<div class="new-project-modal-footer">
|
|
189
|
+
<sp-button variant="secondary" @click=${closeNewProjectModal}>Cancel</sp-button>
|
|
190
|
+
<sp-button variant="accent" ?disabled=${_creating} @click=${onSubmit}>
|
|
191
|
+
${_creating ? "Creating…" : "Create Project"}
|
|
192
|
+
</sp-button>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
`;
|
|
196
|
+
|
|
197
|
+
if (!_handle) {
|
|
198
|
+
_handle = openModal(tpl);
|
|
199
|
+
} else {
|
|
200
|
+
_handle.update(tpl);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
let _dirDerived = true;
|
|
@@ -16,10 +16,11 @@ export function mount() {
|
|
|
16
16
|
_scope.run(() => {
|
|
17
17
|
effect(() => {
|
|
18
18
|
const tab = activeTab.value;
|
|
19
|
-
if (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
if (tab) {
|
|
20
|
+
const gs = tab.session.ui.gitStatus;
|
|
21
|
+
if (!gs && !tab.session.ui.gitLoading) {
|
|
22
|
+
refreshGitStatus();
|
|
23
|
+
}
|
|
23
24
|
}
|
|
24
25
|
renderActivityBar();
|
|
25
26
|
});
|
|
@@ -31,7 +32,7 @@ export function unmount() {
|
|
|
31
32
|
_scope = null;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
const gitBranchIcon = (/** @type {
|
|
35
|
+
const gitBranchIcon = (/** @type {string} */ s) => html`
|
|
35
36
|
<svg
|
|
36
37
|
slot="icon"
|
|
37
38
|
xmlns="http://www.w3.org/2000/svg"
|
|
@@ -52,35 +53,37 @@ const gitBranchIcon = (/** @type {any} */ s) => html`
|
|
|
52
53
|
`;
|
|
53
54
|
|
|
54
55
|
/**
|
|
55
|
-
* @param {
|
|
56
|
-
* @param {
|
|
56
|
+
* @param {string} tag
|
|
57
|
+
* @param {string} [size]
|
|
57
58
|
*/
|
|
58
59
|
export function tabIcon(tag, size) {
|
|
59
|
-
/** @type {Record<string,
|
|
60
|
+
/** @type {Record<string, (s: string) => import("lit-html").TemplateResult>} */
|
|
60
61
|
const m = {
|
|
61
|
-
"sp-icon-folder": (/** @type {
|
|
62
|
+
"sp-icon-folder": (/** @type {string} */ s) =>
|
|
62
63
|
html`<sp-icon-folder slot="icon" size=${s}></sp-icon-folder>`,
|
|
63
|
-
"sp-icon-layers": (/** @type {
|
|
64
|
+
"sp-icon-layers": (/** @type {string} */ s) =>
|
|
64
65
|
html`<sp-icon-layers slot="icon" size=${s}></sp-icon-layers>`,
|
|
65
|
-
"sp-icon-view-grid": (/** @type {
|
|
66
|
+
"sp-icon-view-grid": (/** @type {string} */ s) =>
|
|
66
67
|
html`<sp-icon-view-grid slot="icon" size=${s}></sp-icon-view-grid>`,
|
|
67
|
-
"sp-icon-brackets": (/** @type {
|
|
68
|
+
"sp-icon-brackets": (/** @type {string} */ s) =>
|
|
68
69
|
html`<sp-icon-brackets slot="icon" size=${s}></sp-icon-brackets>`,
|
|
69
|
-
"sp-icon-data": (/** @type {
|
|
70
|
+
"sp-icon-data": (/** @type {string} */ s) =>
|
|
70
71
|
html`<sp-icon-data slot="icon" size=${s}></sp-icon-data>`,
|
|
71
|
-
"sp-icon-properties": (/** @type {
|
|
72
|
+
"sp-icon-properties": (/** @type {string} */ s) =>
|
|
72
73
|
html`<sp-icon-properties slot="icon" size=${s}></sp-icon-properties>`,
|
|
73
|
-
"sp-icon-event": (/** @type {
|
|
74
|
+
"sp-icon-event": (/** @type {string} */ s) =>
|
|
74
75
|
html`<sp-icon-event slot="icon" size=${s}></sp-icon-event>`,
|
|
75
|
-
"sp-icon-brush": (/** @type {
|
|
76
|
+
"sp-icon-brush": (/** @type {string} */ s) =>
|
|
76
77
|
html`<sp-icon-brush slot="icon" size=${s}></sp-icon-brush>`,
|
|
77
|
-
"sp-icon-file-single-web-page": (/** @type {
|
|
78
|
+
"sp-icon-file-single-web-page": (/** @type {string} */ s) =>
|
|
78
79
|
html`<sp-icon-file-single-web-page slot="icon" size=${s}></sp-icon-file-single-web-page>`,
|
|
79
|
-
"sp-icon-view-all-tags": (/** @type {
|
|
80
|
+
"sp-icon-view-all-tags": (/** @type {string} */ s) =>
|
|
80
81
|
html`<sp-icon-view-all-tags slot="icon" size=${s}></sp-icon-view-all-tags>`,
|
|
81
|
-
"sp-icon-artboard": (/** @type {
|
|
82
|
+
"sp-icon-artboard": (/** @type {string} */ s) =>
|
|
82
83
|
html`<sp-icon-artboard slot="icon" size=${s}></sp-icon-artboard>`,
|
|
83
|
-
"sp-icon-
|
|
84
|
+
"sp-icon-chat": (/** @type {string} */ s) =>
|
|
85
|
+
html`<sp-icon-chat slot="icon" size=${s}></sp-icon-chat>`,
|
|
86
|
+
"sp-icon-box": (/** @type {string} */ s) =>
|
|
84
87
|
html`<sp-icon-box slot="icon" size=${s}></sp-icon-box>`,
|
|
85
88
|
"sp-icon-git-branch": gitBranchIcon,
|
|
86
89
|
};
|
|
@@ -90,9 +93,8 @@ export function tabIcon(tag, size) {
|
|
|
90
93
|
|
|
91
94
|
export function renderActivityBar() {
|
|
92
95
|
const tab = activeTab.value;
|
|
93
|
-
if (!tab) return;
|
|
94
96
|
const leftTab = view.leftTab;
|
|
95
|
-
const gitFileCount =
|
|
97
|
+
const gitFileCount = tab?.session.ui.gitStatus?.files?.length || 0;
|
|
96
98
|
const tabs = [
|
|
97
99
|
{ value: "files", icon: "sp-icon-folder", label: "Files" },
|
|
98
100
|
{ value: "layers", icon: "sp-icon-layers", label: "Layers" },
|
|
@@ -108,8 +110,8 @@ export function renderActivityBar() {
|
|
|
108
110
|
selected=${view.leftPanelCollapsed ? "" : leftTab}
|
|
109
111
|
direction="vertical"
|
|
110
112
|
quiet
|
|
111
|
-
@change=${(/** @type {
|
|
112
|
-
const clicked = e.target.selected;
|
|
113
|
+
@change=${(/** @type {Event} */ e) => {
|
|
114
|
+
const clicked = /** @type {HTMLElement & { selected: string }} */ (e.target).selected;
|
|
113
115
|
if (clicked === view.leftTab && !view.leftPanelCollapsed) {
|
|
114
116
|
view.leftPanelCollapsed = true;
|
|
115
117
|
applyPanelCollapse();
|
|
@@ -146,5 +148,5 @@ export function renderActivityBar() {
|
|
|
146
148
|
</sp-action-button>
|
|
147
149
|
</div>
|
|
148
150
|
`;
|
|
149
|
-
litRender(tpl,
|
|
151
|
+
litRender(tpl, activityBar);
|
|
150
152
|
}
|