@jxsuite/studio 0.29.0 → 0.31.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 +4628 -2543
- package/dist/studio.js.map +58 -32
- 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 +13 -11
- package/src/about/about-modal.ts +156 -0
- package/src/editor/context-menu.ts +4 -2
- package/src/files/files.ts +50 -18
- package/src/files/fs-events.ts +162 -0
- package/src/packages/ensure-deps.ts +39 -0
- package/src/packages/jxsuite-update.ts +131 -0
- package/src/packages/semver.ts +54 -0
- package/src/panels/activity-bar.ts +13 -1
- package/src/panels/ai-panel.ts +3 -1
- package/src/panels/dnd.ts +2 -2
- package/src/panels/elements-panel.ts +2 -1
- package/src/panels/layers-panel.ts +5 -1
- package/src/panels/preview-render.ts +1 -1
- package/src/panels/properties-panel.ts +20 -14
- package/src/panels/signals-panel.ts +29 -18
- package/src/panels/style-panel.ts +2 -2
- package/src/panels/stylebook-layers-panel.ts +5 -5
- package/src/panels/stylebook-panel.ts +1 -1
- package/src/platforms/devserver.ts +92 -2
- package/src/settings/dependencies-editor.ts +262 -0
- package/src/settings/general-settings.ts +5 -3
- package/src/settings/settings-modal.ts +6 -0
- package/src/studio.ts +21 -2
- package/src/tabs/tab.ts +10 -4
- package/src/types.ts +82 -1
- package/src/ui/expression-editor.ts +4 -3
- package/src/ui/field-input.ts +1 -1
- package/src/ui/media-picker.ts +1 -1
- package/src/ui/panel-resize.ts +8 -7
- package/src/ui/progress-modal.ts +94 -0
- package/src/ui/spectrum.ts +4 -0
- package/src/ui/unit-selector.ts +3 -0
- package/src/utils/canvas-media.ts +3 -3
- package/src/version.ts +34 -0
package/src/ui/field-input.ts
CHANGED
|
@@ -223,7 +223,7 @@ export function rawTextArea(
|
|
|
223
223
|
const { onInput, onCommit } = makeHandlers(key, ms, commit, opts.commitMode);
|
|
224
224
|
const style = [
|
|
225
225
|
`min-height:${opts.minHeight ?? "40px"}`,
|
|
226
|
-
opts.mono ? "font-family:
|
|
226
|
+
opts.mono ? "font-family:var(--font-mono);font-size:var(--spectrum-font-size-50,11px)" : "",
|
|
227
227
|
opts.style ?? "",
|
|
228
228
|
]
|
|
229
229
|
.filter(Boolean)
|
package/src/ui/media-picker.ts
CHANGED
|
@@ -208,7 +208,7 @@ function renderMediaPickerPopover() {
|
|
|
208
208
|
slot="icon"
|
|
209
209
|
src=${m.path}
|
|
210
210
|
alt=""
|
|
211
|
-
style="width:24px;height:24px;object-fit:cover;border-radius:2px"
|
|
211
|
+
style="width:24px;height:24px;object-fit:cover;border-radius:var(--spectrum-corner-radius-75, 2px)"
|
|
212
212
|
/>`
|
|
213
213
|
: nothing}
|
|
214
214
|
${m.name}
|
package/src/ui/panel-resize.ts
CHANGED
|
@@ -16,6 +16,11 @@ const DEFAULT_RIGHT = 280;
|
|
|
16
16
|
|
|
17
17
|
const root = document.documentElement;
|
|
18
18
|
|
|
19
|
+
/** Read a px-valued CSS custom property as a number (e.g. "320px" → 320). */
|
|
20
|
+
function readPxVar(cssVar: string): number {
|
|
21
|
+
return Number(getComputedStyle(root).getPropertyValue(cssVar).replace(/px$/, ""));
|
|
22
|
+
}
|
|
23
|
+
|
|
19
24
|
// ─── Restore saved widths & collapse state ──────────────────────────────────
|
|
20
25
|
|
|
21
26
|
try {
|
|
@@ -68,8 +73,7 @@ function setupHandle(
|
|
|
68
73
|
handle.classList.add("dragging");
|
|
69
74
|
document.body.style.userSelect = "none";
|
|
70
75
|
|
|
71
|
-
const current =
|
|
72
|
-
Number.parseInt(getComputedStyle(root).getPropertyValue(cssVar), 10) || defaultWidth;
|
|
76
|
+
const current = readPxVar(cssVar) || defaultWidth;
|
|
73
77
|
drag = { startWidth: current, startX: e.clientX };
|
|
74
78
|
});
|
|
75
79
|
|
|
@@ -105,11 +109,8 @@ function setupHandle(
|
|
|
105
109
|
}
|
|
106
110
|
|
|
107
111
|
function persistWidths() {
|
|
108
|
-
const left =
|
|
109
|
-
|
|
110
|
-
const right =
|
|
111
|
-
Number.parseInt(getComputedStyle(root).getPropertyValue("--panel-w-right"), 10) ||
|
|
112
|
-
DEFAULT_RIGHT;
|
|
112
|
+
const left = readPxVar("--panel-w-left") || DEFAULT_LEFT;
|
|
113
|
+
const right = readPxVar("--panel-w-right") || DEFAULT_RIGHT;
|
|
113
114
|
try {
|
|
114
115
|
localStorage.setItem(STORAGE_KEY, JSON.stringify({ left, right }));
|
|
115
116
|
} catch {
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
/**
|
|
3
|
+
* A blocking progress modal for long-running package operations (bun install / dependency updates).
|
|
4
|
+
* Shows an indeterminate spinner with a status line; on failure it swaps to an error view with the
|
|
5
|
+
* captured log and a Close button. Built on the persistent-modal layer helper.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { html } from "lit-html";
|
|
9
|
+
import type { TemplateResult } from "lit-html";
|
|
10
|
+
import { openModal } from "./layers";
|
|
11
|
+
|
|
12
|
+
export interface ProgressModalHandle {
|
|
13
|
+
/** Update the status line shown under the title. */
|
|
14
|
+
setStatus: (text: string) => void;
|
|
15
|
+
/** Replace the spinner with an error view (log shown) that the user dismisses. */
|
|
16
|
+
fail: (message: string) => void;
|
|
17
|
+
/** Close the modal (success path). */
|
|
18
|
+
done: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function card(body: TemplateResult): TemplateResult {
|
|
22
|
+
return html`
|
|
23
|
+
<sp-underlay open></sp-underlay>
|
|
24
|
+
<div
|
|
25
|
+
class="progress-modal"
|
|
26
|
+
role="dialog"
|
|
27
|
+
aria-live="polite"
|
|
28
|
+
style="position:fixed;inset:0;margin:auto;width:min(420px,calc(100vw - 48px));max-height:calc(100vh - 96px);height:max-content;background:var(--bg-panel,#1e1e1e);border:1px solid var(--border,#3a3a3a);border-radius:var(--spectrum-corner-radius-200,8px);padding:24px;display:flex;flex-direction:column;gap:16px;box-shadow:0 8px 32px rgba(0,0,0,.4)"
|
|
29
|
+
>
|
|
30
|
+
${body}
|
|
31
|
+
</div>
|
|
32
|
+
`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function runningView(title: string, status: string): TemplateResult {
|
|
36
|
+
return card(html`
|
|
37
|
+
<div style="display:flex;align-items:center;gap:14px">
|
|
38
|
+
<sp-progress-circle indeterminate size="m" aria-label=${title}></sp-progress-circle>
|
|
39
|
+
<div style="display:flex;flex-direction:column;gap:2px">
|
|
40
|
+
<strong style="font-size:var(--spectrum-font-size-100, 14px)">${title}</strong>
|
|
41
|
+
${status
|
|
42
|
+
? html`<span style="font-size:var(--spectrum-font-size-75, 12px);color:var(--fg-dim,#aaa)"
|
|
43
|
+
>${status}</span
|
|
44
|
+
>`
|
|
45
|
+
: ""}
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function errorView(title: string, message: string, onClose: () => void): TemplateResult {
|
|
52
|
+
return card(html`
|
|
53
|
+
<div style="display:flex;flex-direction:column;gap:8px">
|
|
54
|
+
<strong style="font-size:var(--spectrum-font-size-100, 14px);color:var(--danger,#e34850)"
|
|
55
|
+
>${title} failed</strong
|
|
56
|
+
>
|
|
57
|
+
<pre
|
|
58
|
+
style="font-size:var(--spectrum-font-size-50, 11px);white-space:pre-wrap;overflow:auto;max-height:240px;margin:0;color:var(--fg-dim,#aaa)"
|
|
59
|
+
>
|
|
60
|
+
${message}</pre
|
|
61
|
+
>
|
|
62
|
+
</div>
|
|
63
|
+
<div style="display:flex;justify-content:flex-end">
|
|
64
|
+
<sp-button size="s" @click=${onClose}>Close</sp-button>
|
|
65
|
+
</div>
|
|
66
|
+
`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function showProgressModal(opts: { title: string; status?: string }): ProgressModalHandle {
|
|
70
|
+
const modal = openModal(runningView(opts.title, opts.status ?? ""));
|
|
71
|
+
let closed = false;
|
|
72
|
+
|
|
73
|
+
const close = () => {
|
|
74
|
+
if (closed) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
closed = true;
|
|
78
|
+
modal.close();
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
done: close,
|
|
83
|
+
fail(message: string) {
|
|
84
|
+
if (!closed) {
|
|
85
|
+
modal.update(errorView(opts.title, message || "Unknown error", close));
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
setStatus(text: string) {
|
|
89
|
+
if (!closed) {
|
|
90
|
+
modal.update(runningView(opts.title, text));
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
package/src/ui/spectrum.ts
CHANGED
|
@@ -48,6 +48,7 @@ import { Accordion } from "@spectrum-web-components/accordion/src/Accordion.js";
|
|
|
48
48
|
import { AccordionItem } from "@spectrum-web-components/accordion/src/AccordionItem.js";
|
|
49
49
|
import { ActionBar } from "@spectrum-web-components/action-bar/src/ActionBar.js";
|
|
50
50
|
import { Toast } from "@spectrum-web-components/toast/src/Toast.js";
|
|
51
|
+
import { ProgressCircle } from "@spectrum-web-components/progress-circle/src/ProgressCircle.js";
|
|
51
52
|
import { Table } from "@spectrum-web-components/table/src/Table.js";
|
|
52
53
|
import { TableHead } from "@spectrum-web-components/table/src/TableHead.js";
|
|
53
54
|
import { TableHeadCell } from "@spectrum-web-components/table/src/TableHeadCell.js";
|
|
@@ -87,6 +88,7 @@ import { IconCode } from "@spectrum-web-components/icons-workflow/src/elements/I
|
|
|
87
88
|
import { IconBrush } from "@spectrum-web-components/icons-workflow/src/elements/IconBrush.js";
|
|
88
89
|
import { IconGears } from "@spectrum-web-components/icons-workflow/src/elements/IconGears.js";
|
|
89
90
|
import { IconSettings } from "@spectrum-web-components/icons-workflow/src/elements/IconSettings.js";
|
|
91
|
+
import { IconInfo } from "@spectrum-web-components/icons-workflow/src/elements/IconInfo.js";
|
|
90
92
|
import { IconBack } from "@spectrum-web-components/icons-workflow/src/elements/IconBack.js";
|
|
91
93
|
import { IconProperties } from "@spectrum-web-components/icons-workflow/src/elements/IconProperties.js";
|
|
92
94
|
import { IconEvent } from "@spectrum-web-components/icons-workflow/src/elements/IconEvent.js";
|
|
@@ -190,6 +192,7 @@ const components = [
|
|
|
190
192
|
["sp-accordion-item", AccordionItem],
|
|
191
193
|
["sp-action-bar", ActionBar],
|
|
192
194
|
["sp-toast", Toast],
|
|
195
|
+
["sp-progress-circle", ProgressCircle],
|
|
193
196
|
["sp-table", Table],
|
|
194
197
|
["sp-table-head", TableHead],
|
|
195
198
|
["sp-table-head-cell", TableHeadCell],
|
|
@@ -227,6 +230,7 @@ const components = [
|
|
|
227
230
|
["sp-icon-brush", IconBrush],
|
|
228
231
|
["sp-icon-gears", IconGears],
|
|
229
232
|
["sp-icon-settings", IconSettings],
|
|
233
|
+
["sp-icon-info", IconInfo],
|
|
230
234
|
["sp-icon-back", IconBack],
|
|
231
235
|
["sp-icon-properties", IconProperties],
|
|
232
236
|
["sp-icon-event", IconEvent],
|
package/src/ui/unit-selector.ts
CHANGED
|
@@ -44,6 +44,9 @@ export function renderUnitSelector(
|
|
|
44
44
|
} else if (match) {
|
|
45
45
|
displayValue = match[1]!;
|
|
46
46
|
} else if (strVal !== "") {
|
|
47
|
+
// Intentional partial parse: shorthand like "10px 20px" displays its leading number.
|
|
48
|
+
// Number() yields NaN for those, so Number.parseFloat stays (see unit-selector tests).
|
|
49
|
+
// oxlint-disable-next-line unicorn/prefer-number-coercion
|
|
47
50
|
const num = Number.parseFloat(strVal);
|
|
48
51
|
displayValue = Number.isNaN(num) ? strVal : String(num);
|
|
49
52
|
} else {
|
|
@@ -25,15 +25,15 @@ export function parseMediaEntries(mediaDef?: Record<string, string> | null) {
|
|
|
25
25
|
for (const [name, query] of Object.entries(mediaDef)) {
|
|
26
26
|
if (name === "--") {
|
|
27
27
|
const wm = String(query).match(/^(\d+)\s*px$/);
|
|
28
|
-
baseWidth = wm ? Number
|
|
28
|
+
baseWidth = wm ? Number(wm[1]!) : 320;
|
|
29
29
|
continue;
|
|
30
30
|
}
|
|
31
31
|
const minMatch = query.match(/min-width:\s*([\d.]+)px/);
|
|
32
32
|
const maxMatch = query.match(/max-width:\s*([\d.]+)px/);
|
|
33
33
|
if (minMatch) {
|
|
34
|
-
sizes.push({ name, query, type: "min", width: Number
|
|
34
|
+
sizes.push({ name, query, type: "min", width: Number(minMatch[1]!) });
|
|
35
35
|
} else if (maxMatch) {
|
|
36
|
-
sizes.push({ name, query, type: "max", width: Number
|
|
36
|
+
sizes.push({ name, query, type: "max", width: Number(maxMatch[1]!) });
|
|
37
37
|
} else {
|
|
38
38
|
features.push({ name, query });
|
|
39
39
|
}
|
package/src/version.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version.ts — build-time metadata for the About screen.
|
|
3
|
+
*
|
|
4
|
+
* The constants below are replaced at bundle time by scripts/build.ts via Bun's `define`. They're
|
|
5
|
+
* read through `typeof` guards so the module stays safe under `bun test`, where the defines are
|
|
6
|
+
* absent and the fallbacks apply.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
declare const __JX_VERSION__: string;
|
|
10
|
+
declare const __JX_BUILD_DATE__: string;
|
|
11
|
+
declare const __JX_GIT_COMMIT__: string;
|
|
12
|
+
|
|
13
|
+
export const APP_NAME = "Jx Studio";
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* The `typeof` guards are deliberate: the identifiers are `declare const` placeholders that
|
|
17
|
+
* only exist after Bun's build-time `define` substitution. Under `bun test` they're never
|
|
18
|
+
* bound, so a direct `=== undefined` comparison would throw a ReferenceError.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
// oxlint-disable-next-line unicorn/no-typeof-undefined -- guards undeclared build-time define
|
|
22
|
+
export const VERSION = typeof __JX_VERSION__ === "undefined" ? "dev" : __JX_VERSION__;
|
|
23
|
+
|
|
24
|
+
// oxlint-disable-next-line unicorn/no-typeof-undefined -- guards undeclared build-time define
|
|
25
|
+
export const BUILD_DATE = typeof __JX_BUILD_DATE__ === "undefined" ? "" : __JX_BUILD_DATE__;
|
|
26
|
+
|
|
27
|
+
// oxlint-disable-next-line unicorn/no-typeof-undefined -- guards undeclared build-time define
|
|
28
|
+
export const GIT_COMMIT = typeof __JX_GIT_COMMIT__ === "undefined" ? "unknown" : __JX_GIT_COMMIT__;
|
|
29
|
+
|
|
30
|
+
export const LINKS = {
|
|
31
|
+
github: "https://github.com/jxsuite/jx",
|
|
32
|
+
docs: "https://github.com/jxsuite/jx#readme",
|
|
33
|
+
license: "https://github.com/jxsuite/jx/blob/main/LICENSE",
|
|
34
|
+
} as const;
|