@jxsuite/studio 0.30.1 → 0.31.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 +807 -134
- package/dist/studio.js.map +30 -23
- package/package.json +8 -6
- package/src/about/about-modal.ts +156 -0
- package/src/files/files.ts +7 -15
- 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/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 +10 -7
- 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 +38 -0
- 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 +6 -0
- package/src/tabs/tab.ts +10 -4
- package/src/types.ts +52 -0
- 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/progress-modal.ts +94 -0
- package/src/ui/spectrum.ts +4 -0
- package/src/version.ts +34 -0
|
@@ -943,7 +943,7 @@ function renderFunctionFields(
|
|
|
943
943
|
</div>
|
|
944
944
|
<textarea
|
|
945
945
|
class="field-input"
|
|
946
|
-
style="min-height:60px;font-family:
|
|
946
|
+
style="min-height:60px;font-family:var(--font-mono);font-size:var(--spectrum-font-size-50, 11px)"
|
|
947
947
|
.value=${def.body || ""}
|
|
948
948
|
@input=${(e: Event) => {
|
|
949
949
|
const v = (e.target as HTMLInputElement).value;
|
|
@@ -981,7 +981,7 @@ function renderParameterEditorTemplate(
|
|
|
981
981
|
${params.map(
|
|
982
982
|
(p: CemParameter, i: number) => html`
|
|
983
983
|
<span
|
|
984
|
-
style="display:inline-flex;align-items:center;gap:2px;padding:1px 6px;border-radius:
|
|
984
|
+
style="display:inline-flex;align-items:center;gap:2px;padding:1px 6px;border-radius:var(--radius);background:var(--hover-bg);font-size:var(--spectrum-font-size-50, 11px);font-family:var(--font-mono)"
|
|
985
985
|
>
|
|
986
986
|
${p.name || "?"}
|
|
987
987
|
<span
|
|
@@ -1002,7 +1002,7 @@ function renderParameterEditorTemplate(
|
|
|
1002
1002
|
)}
|
|
1003
1003
|
<input
|
|
1004
1004
|
class="field-input"
|
|
1005
|
-
style="width:60px;flex:0 0 auto;font-size:11px"
|
|
1005
|
+
style="width:60px;flex:0 0 auto;font-size:var(--spectrum-font-size-50, 11px)"
|
|
1006
1006
|
placeholder="+"
|
|
1007
1007
|
@keydown=${(e: KeyboardEvent) => {
|
|
1008
1008
|
if (e.key === "Enter" && (e.target as HTMLInputElement).value.trim()) {
|
|
@@ -1145,7 +1145,7 @@ function renderEmitsEditorTemplate(S: SignalsPanelState, name: string, def: Sign
|
|
|
1145
1145
|
|
|
1146
1146
|
return html`
|
|
1147
1147
|
<div
|
|
1148
|
-
style="font-size:11px;font-weight:600;color:var(--fg-dim);margin:8px 0 4px;text-transform:uppercase;letter-spacing:0.05em"
|
|
1148
|
+
style="font-size:var(--spectrum-font-size-50, 11px);font-weight:600;color:var(--fg-dim);margin:8px 0 4px;text-transform:uppercase;letter-spacing:0.05em"
|
|
1149
1149
|
>
|
|
1150
1150
|
Emits
|
|
1151
1151
|
</div>
|
|
@@ -1474,7 +1474,7 @@ export function renderSchemaFieldsTemplate(
|
|
|
1474
1474
|
${Object.entries(cv.properties as Record<string, Record<string, unknown>>).map(
|
|
1475
1475
|
([k, v]) => html`
|
|
1476
1476
|
<span
|
|
1477
|
-
style="background:var(--bg
|
|
1477
|
+
style="background:var(--bg);padding:1px 6px;border-radius:var(--radius);font-size:10px;color:var(--fg-dim)"
|
|
1478
1478
|
>${k}: ${v.type ?? "any"}</span
|
|
1479
1479
|
>
|
|
1480
1480
|
`,
|
|
@@ -1642,7 +1642,7 @@ export function renderExternalPrototypeEditorTemplate(
|
|
|
1642
1642
|
} else {
|
|
1643
1643
|
// Trigger async load — will re-render when cached
|
|
1644
1644
|
schemaContent = html`<div
|
|
1645
|
-
style="padding:4px 0;font-size:11px;color:var(--fg-dim);font-style:italic"
|
|
1645
|
+
style="padding:4px 0;font-size:var(--spectrum-font-size-50, 11px);color:var(--fg-dim);font-style:italic"
|
|
1646
1646
|
>
|
|
1647
1647
|
Loading schema…
|
|
1648
1648
|
</div>`;
|
|
@@ -1658,7 +1658,10 @@ export function renderExternalPrototypeEditorTemplate(
|
|
|
1658
1658
|
|
|
1659
1659
|
return html`
|
|
1660
1660
|
${importedPath
|
|
1661
|
-
? html`<div
|
|
1661
|
+
? html`<div
|
|
1662
|
+
class="signal-hint"
|
|
1663
|
+
style="padding:4px 0 2px;font-size:var(--spectrum-font-size-50, 11px);color:var(--fg-dim)"
|
|
1664
|
+
>
|
|
1662
1665
|
${def.$prototype}
|
|
1663
1666
|
</div>`
|
|
1664
1667
|
: html`
|
|
@@ -804,7 +804,7 @@ function styleSidebarTemplate(
|
|
|
804
804
|
(rule) => html`
|
|
805
805
|
<div style="display:flex;align-items:center;gap:4px">
|
|
806
806
|
<button
|
|
807
|
-
style="flex:1;text-align:left;padding:6px 10px;background:var(--spectrum-gray-200, #1a1a1a);border:none;border-radius:
|
|
807
|
+
style="flex:1;text-align:left;padding:6px 10px;background:var(--spectrum-gray-200, #1a1a1a);border:none;border-radius:var(--radius);color:var(--spectrum-gray-900, #fafafa);font-size:var(--spectrum-font-size-75, 12px);cursor:pointer"
|
|
808
808
|
@click=${() => {
|
|
809
809
|
const newSelector = activeSelector ? `${activeSelector} ${rule}` : rule;
|
|
810
810
|
selectStylebookTag(newSelector, undefined, {
|
|
@@ -821,7 +821,7 @@ function styleSidebarTemplate(
|
|
|
821
821
|
`,
|
|
822
822
|
)}
|
|
823
823
|
<button
|
|
824
|
-
style="padding:6px 10px;background:none;border:1px dashed var(--spectrum-gray-400, #333);border-radius:
|
|
824
|
+
style="padding:6px 10px;background:none;border:1px dashed var(--spectrum-gray-400, #333);border-radius:var(--radius);color:var(--spectrum-gray-700, #a1a1aa);font-size:var(--spectrum-font-size-75, 12px);cursor:pointer"
|
|
825
825
|
@click=${() => {
|
|
826
826
|
// oxlint-disable-next-line no-alert -- native prompt is the intended quick-input UX here
|
|
827
827
|
const name = prompt("Selector name (e.g. th, :hover, .active):");
|
|
@@ -111,19 +111,19 @@ export function renderStylebookLayersTemplate(ctx: {
|
|
|
111
111
|
const style = rootStyle;
|
|
112
112
|
const vars = Object.entries(style).filter(([k]) => k.startsWith("--"));
|
|
113
113
|
if (vars.length === 0) {
|
|
114
|
-
return html`<div
|
|
114
|
+
return html`<div
|
|
115
|
+
style="padding:16px;text-align:center;color:var(--fg-dim);font-size:var(--spectrum-font-size-75, 12px)"
|
|
116
|
+
>
|
|
115
117
|
No variables defined
|
|
116
118
|
</div>`;
|
|
117
119
|
}
|
|
118
120
|
return html`${vars.map(
|
|
119
121
|
([k, v]) => html`
|
|
120
122
|
<div class="layer-row">
|
|
121
|
-
<span class="layer-tag" style="font-size:10px;font-family:
|
|
122
|
-
>var</span
|
|
123
|
-
>
|
|
123
|
+
<span class="layer-tag" style="font-size:10px;font-family:var(--font-mono)">var</span>
|
|
124
124
|
<span class="layer-label">${k}</span>
|
|
125
125
|
<span
|
|
126
|
-
style="font-size:11px;color:var(--fg-dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:80px"
|
|
126
|
+
style="font-size:var(--spectrum-font-size-50, 11px);color:var(--fg-dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:80px"
|
|
127
127
|
>${String(v)}</span
|
|
128
128
|
>
|
|
129
129
|
</div>
|
|
@@ -592,7 +592,7 @@ export async function renderComponentPreview(comp: ComponentEntry) {
|
|
|
592
592
|
function _componentFallback(tagName: string) {
|
|
593
593
|
const fallback = document.createElement("div");
|
|
594
594
|
fallback.style.cssText =
|
|
595
|
-
"padding:12px;border:1px dashed var(--border);border-radius:
|
|
595
|
+
"padding:12px;border:1px dashed var(--border);border-radius:var(--radius);color:var(--fg-dim)";
|
|
596
596
|
fallback.textContent = `<${tagName}>`;
|
|
597
597
|
return fallback;
|
|
598
598
|
}
|
|
@@ -421,6 +421,44 @@ export function createDevServerPlatform() {
|
|
|
421
421
|
return await res.json();
|
|
422
422
|
},
|
|
423
423
|
|
|
424
|
+
async installDependencies() {
|
|
425
|
+
const res = await fetch("/__studio/packages/install", { method: "POST" });
|
|
426
|
+
if (!res.ok) {
|
|
427
|
+
return { log: await res.text(), ok: false };
|
|
428
|
+
}
|
|
429
|
+
return await res.json();
|
|
430
|
+
},
|
|
431
|
+
|
|
432
|
+
async dependenciesNeedInstall() {
|
|
433
|
+
const res = await fetch("/__studio/packages/needs-install");
|
|
434
|
+
if (!res.ok) {
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
const data = (await res.json()) as { needsInstall?: boolean };
|
|
438
|
+
return Boolean(data.needsInstall);
|
|
439
|
+
},
|
|
440
|
+
|
|
441
|
+
async outdatedPackages() {
|
|
442
|
+
const res = await fetch("/__studio/packages/outdated");
|
|
443
|
+
if (!res.ok) {
|
|
444
|
+
return [];
|
|
445
|
+
}
|
|
446
|
+
return await res.json();
|
|
447
|
+
},
|
|
448
|
+
|
|
449
|
+
/** @param {{ name: string; version: string; dev?: boolean }[]} updates */
|
|
450
|
+
async setPackageVersions(updates: { name: string; version: string; dev?: boolean }[]) {
|
|
451
|
+
const res = await fetch("/__studio/packages/set-versions", {
|
|
452
|
+
body: JSON.stringify({ updates }),
|
|
453
|
+
headers: { "Content-Type": "application/json" },
|
|
454
|
+
method: "POST",
|
|
455
|
+
});
|
|
456
|
+
if (!res.ok) {
|
|
457
|
+
return { log: await res.text(), ok: false };
|
|
458
|
+
}
|
|
459
|
+
return await res.json();
|
|
460
|
+
},
|
|
461
|
+
|
|
424
462
|
// ─── Code services (optional) ─────────────────────────────────────────
|
|
425
463
|
|
|
426
464
|
/**
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
/**
|
|
3
|
+
* Dependencies settings section — an outdated-aware table of the project's npm dependencies. Shows
|
|
4
|
+
* current vs latest version, with per-row update/remove, an add-package field, "update all", and
|
|
5
|
+
* reinstall. @jxsuite/* rows target the version this Studio build embeds; other packages target the
|
|
6
|
+
* newest published version reported by the backend.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { html, render as litRender } from "lit-html";
|
|
10
|
+
import { getPlatform } from "../platform";
|
|
11
|
+
import { VERSION } from "../version";
|
|
12
|
+
import { statusMessage } from "../panels/statusbar";
|
|
13
|
+
import { showProgressModal } from "../ui/progress-modal";
|
|
14
|
+
import { isComparable, isUpgrade, stripRange } from "../packages/semver";
|
|
15
|
+
import type { PackageInfo } from "../types";
|
|
16
|
+
|
|
17
|
+
const JXSUITE_PREFIX = "@jxsuite/";
|
|
18
|
+
|
|
19
|
+
interface Update {
|
|
20
|
+
name: string;
|
|
21
|
+
version: string;
|
|
22
|
+
dev: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let _container: HTMLElement | null = null;
|
|
26
|
+
let _packages: PackageInfo[] | null = null;
|
|
27
|
+
let _latest = new Map<string, string>();
|
|
28
|
+
let _busy = false;
|
|
29
|
+
let _addName = "";
|
|
30
|
+
|
|
31
|
+
/** The version a package can be updated to, or null if it is already current. */
|
|
32
|
+
function latestFor(p: PackageInfo): string | null {
|
|
33
|
+
if (p.name.startsWith(JXSUITE_PREFIX) && isComparable(VERSION) && isUpgrade(p.version, VERSION)) {
|
|
34
|
+
return VERSION;
|
|
35
|
+
}
|
|
36
|
+
const registry = _latest.get(p.name);
|
|
37
|
+
return registry && isUpgrade(p.version, registry) ? registry : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function load() {
|
|
41
|
+
const platform = getPlatform();
|
|
42
|
+
try {
|
|
43
|
+
_packages = await platform.listPackages();
|
|
44
|
+
} catch {
|
|
45
|
+
_packages = [];
|
|
46
|
+
}
|
|
47
|
+
_latest = new Map();
|
|
48
|
+
if (platform.outdatedPackages) {
|
|
49
|
+
try {
|
|
50
|
+
for (const o of await platform.outdatedPackages()) {
|
|
51
|
+
_latest.set(o.name, o.latest);
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
/* Registry lookups are best-effort */
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
render();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function withBusy(fn: () => Promise<void>) {
|
|
61
|
+
if (_busy) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
_busy = true;
|
|
65
|
+
render();
|
|
66
|
+
const progress = showProgressModal({ status: "Running bun…", title: "Updating dependencies" });
|
|
67
|
+
try {
|
|
68
|
+
await fn();
|
|
69
|
+
progress.done();
|
|
70
|
+
} catch (error) {
|
|
71
|
+
progress.fail(error instanceof Error ? error.message : String(error));
|
|
72
|
+
} finally {
|
|
73
|
+
_busy = false;
|
|
74
|
+
await load();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function onAdd() {
|
|
79
|
+
const name = _addName.trim();
|
|
80
|
+
if (!name) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
await withBusy(async () => {
|
|
84
|
+
await getPlatform().addPackage(name);
|
|
85
|
+
_addName = "";
|
|
86
|
+
statusMessage(`Added ${name}`);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function onRemove(p: PackageInfo) {
|
|
91
|
+
await withBusy(async () => {
|
|
92
|
+
await getPlatform().removePackage(p.name);
|
|
93
|
+
statusMessage(`Removed ${p.name}`);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function onUpdate(p: PackageInfo, latest: string) {
|
|
98
|
+
const platform = getPlatform();
|
|
99
|
+
if (!platform.setPackageVersions) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
await withBusy(async () => {
|
|
103
|
+
const target = stripRange(latest);
|
|
104
|
+
const res = await platform.setPackageVersions!([
|
|
105
|
+
{ dev: Boolean(p.dev), name: p.name, version: `^${target}` },
|
|
106
|
+
]);
|
|
107
|
+
if (!res.ok) {
|
|
108
|
+
throw new Error(res.log ?? "Update failed");
|
|
109
|
+
}
|
|
110
|
+
statusMessage(`Updated ${p.name} to ${target}`);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function onUpdateAll() {
|
|
115
|
+
const platform = getPlatform();
|
|
116
|
+
if (!platform.setPackageVersions) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const updates = (_packages ?? [])
|
|
120
|
+
.map((p): Update | null => {
|
|
121
|
+
const latest = latestFor(p);
|
|
122
|
+
return latest
|
|
123
|
+
? { dev: Boolean(p.dev), name: p.name, version: `^${stripRange(latest)}` }
|
|
124
|
+
: null;
|
|
125
|
+
})
|
|
126
|
+
.filter((u): u is Update => u !== null);
|
|
127
|
+
if (updates.length === 0) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
await withBusy(async () => {
|
|
131
|
+
const res = await platform.setPackageVersions!(updates);
|
|
132
|
+
if (!res.ok) {
|
|
133
|
+
throw new Error(res.log ?? "Update failed");
|
|
134
|
+
}
|
|
135
|
+
statusMessage(`Updated ${updates.length} package(s)`);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function onReinstall() {
|
|
140
|
+
const platform = getPlatform();
|
|
141
|
+
if (!platform.installDependencies) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
await withBusy(async () => {
|
|
145
|
+
const res = await platform.installDependencies!();
|
|
146
|
+
if (!res.ok) {
|
|
147
|
+
throw new Error(res.log ?? "Install failed");
|
|
148
|
+
}
|
|
149
|
+
statusMessage("Dependencies reinstalled");
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function row(p: PackageInfo) {
|
|
154
|
+
const latest = latestFor(p);
|
|
155
|
+
return html`
|
|
156
|
+
<sp-table-row>
|
|
157
|
+
<sp-table-cell>
|
|
158
|
+
${p.name}${p.dev
|
|
159
|
+
? html`<span style="color:var(--fg-dim);font-size:10px"> · dev</span>`
|
|
160
|
+
: ""}
|
|
161
|
+
</sp-table-cell>
|
|
162
|
+
<sp-table-cell>${p.version}</sp-table-cell>
|
|
163
|
+
<sp-table-cell>${latest ?? "—"}</sp-table-cell>
|
|
164
|
+
<sp-table-cell>
|
|
165
|
+
${latest
|
|
166
|
+
? html`<sp-action-button
|
|
167
|
+
size="s"
|
|
168
|
+
quiet
|
|
169
|
+
?disabled=${_busy}
|
|
170
|
+
title="Update to ${latest}"
|
|
171
|
+
@click=${() => onUpdate(p, latest)}
|
|
172
|
+
>
|
|
173
|
+
<sp-icon-refresh slot="icon"></sp-icon-refresh>
|
|
174
|
+
</sp-action-button>`
|
|
175
|
+
: ""}
|
|
176
|
+
<sp-action-button
|
|
177
|
+
size="s"
|
|
178
|
+
quiet
|
|
179
|
+
?disabled=${_busy}
|
|
180
|
+
title="Remove"
|
|
181
|
+
@click=${() => onRemove(p)}
|
|
182
|
+
>
|
|
183
|
+
<sp-icon-delete slot="icon"></sp-icon-delete>
|
|
184
|
+
</sp-action-button>
|
|
185
|
+
</sp-table-cell>
|
|
186
|
+
</sp-table-row>
|
|
187
|
+
`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function render() {
|
|
191
|
+
if (!_container) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const pkgs = _packages ?? [];
|
|
195
|
+
const hasUpdates = pkgs.some((p) => latestFor(p) !== null);
|
|
196
|
+
|
|
197
|
+
const tpl = html`
|
|
198
|
+
<div class="settings-section">
|
|
199
|
+
<h3 class="settings-section-title">Dependencies</h3>
|
|
200
|
+
<p class="settings-field-desc">Manage this project's npm dependencies.</p>
|
|
201
|
+
|
|
202
|
+
<div style="display:flex;gap:8px;margin-bottom:12px;align-items:center">
|
|
203
|
+
<sp-textfield
|
|
204
|
+
size="s"
|
|
205
|
+
placeholder="package-name"
|
|
206
|
+
.value=${_addName}
|
|
207
|
+
?disabled=${_busy}
|
|
208
|
+
@input=${(e: Event) => {
|
|
209
|
+
_addName = (e.target as HTMLInputElement).value;
|
|
210
|
+
}}
|
|
211
|
+
></sp-textfield>
|
|
212
|
+
<sp-action-button size="s" ?disabled=${_busy} @click=${onAdd}>
|
|
213
|
+
<sp-icon-add slot="icon"></sp-icon-add>
|
|
214
|
+
Add
|
|
215
|
+
</sp-action-button>
|
|
216
|
+
<span style="flex:1"></span>
|
|
217
|
+
${hasUpdates
|
|
218
|
+
? html`<sp-action-button size="s" ?disabled=${_busy} @click=${onUpdateAll}>
|
|
219
|
+
Update all
|
|
220
|
+
</sp-action-button>`
|
|
221
|
+
: ""}
|
|
222
|
+
<sp-action-button
|
|
223
|
+
size="s"
|
|
224
|
+
quiet
|
|
225
|
+
?disabled=${_busy}
|
|
226
|
+
title="Reinstall (bun install)"
|
|
227
|
+
@click=${onReinstall}
|
|
228
|
+
>
|
|
229
|
+
<sp-icon-refresh slot="icon"></sp-icon-refresh>
|
|
230
|
+
Reinstall
|
|
231
|
+
</sp-action-button>
|
|
232
|
+
</div>
|
|
233
|
+
|
|
234
|
+
${_packages === null
|
|
235
|
+
? html`<p class="about-muted">Loading…</p>`
|
|
236
|
+
: pkgs.length === 0
|
|
237
|
+
? html`<p class="about-muted">No dependencies.</p>`
|
|
238
|
+
: html`
|
|
239
|
+
<sp-table size="s">
|
|
240
|
+
<sp-table-head>
|
|
241
|
+
<sp-table-head-cell>Package</sp-table-head-cell>
|
|
242
|
+
<sp-table-head-cell>Current</sp-table-head-cell>
|
|
243
|
+
<sp-table-head-cell>Latest</sp-table-head-cell>
|
|
244
|
+
<sp-table-head-cell></sp-table-head-cell>
|
|
245
|
+
</sp-table-head>
|
|
246
|
+
<sp-table-body> ${pkgs.map((p) => row(p))} </sp-table-body>
|
|
247
|
+
</sp-table>
|
|
248
|
+
`}
|
|
249
|
+
</div>
|
|
250
|
+
`;
|
|
251
|
+
|
|
252
|
+
litRender(tpl, _container);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** @param {HTMLElement} container */
|
|
256
|
+
export function renderDependenciesEditor(container: HTMLElement) {
|
|
257
|
+
_container = container;
|
|
258
|
+
_packages = null;
|
|
259
|
+
_addName = "";
|
|
260
|
+
render();
|
|
261
|
+
void load();
|
|
262
|
+
}
|
|
@@ -94,16 +94,18 @@ export function renderGeneralSettings(container: HTMLElement) {
|
|
|
94
94
|
? html`<img
|
|
95
95
|
src=${currentFavicon}
|
|
96
96
|
alt="Current favicon"
|
|
97
|
-
style="width:32px;height:32px;object-fit:contain;border:1px solid var(--border);border-radius:
|
|
97
|
+
style="width:32px;height:32px;object-fit:contain;border:1px solid var(--border);border-radius:var(--radius);padding:2px"
|
|
98
98
|
/>`
|
|
99
99
|
: html`<div
|
|
100
|
-
style="width:32px;height:32px;border:1px dashed var(--border);border-radius:
|
|
100
|
+
style="width:32px;height:32px;border:1px dashed var(--border);border-radius:var(--radius);display:flex;align-items:center;justify-content:center;color:var(--fg-dim);font-size:var(--spectrum-font-size-50, 11px)"
|
|
101
101
|
>
|
|
102
102
|
—
|
|
103
103
|
</div>`}
|
|
104
104
|
<sp-action-button size="s" @click=${onFaviconUpload}> Upload Favicon </sp-action-button>
|
|
105
105
|
${currentFavicon
|
|
106
|
-
? html`<span style="font-size:11px;color:var(--fg-dim)"
|
|
106
|
+
? html`<span style="font-size:var(--spectrum-font-size-50, 11px);color:var(--fg-dim)"
|
|
107
|
+
>${currentFavicon}</span
|
|
108
|
+
>`
|
|
107
109
|
: nothing}
|
|
108
110
|
</div>
|
|
109
111
|
</div>
|
|
@@ -13,6 +13,7 @@ import { renderContentTypesEditor } from "./content-types-editor";
|
|
|
13
13
|
import { renderCssVarsEditor } from "./css-vars-editor";
|
|
14
14
|
import { renderHeadEditor } from "./head-editor";
|
|
15
15
|
import { renderGeneralSettings } from "./general-settings";
|
|
16
|
+
import { renderDependenciesEditor } from "./dependencies-editor";
|
|
16
17
|
import { openModal } from "../ui/layers";
|
|
17
18
|
|
|
18
19
|
let _handle: ReturnType<typeof openModal> | null = null;
|
|
@@ -27,6 +28,7 @@ const sections = [
|
|
|
27
28
|
{ icon: "sp-icon-brush", key: "cssVars", label: "CSS Variables" },
|
|
28
29
|
{ icon: "sp-icon-data", key: "definitions", label: "Definitions" },
|
|
29
30
|
{ icon: "sp-icon-view-grid", key: "contentTypes", label: "Content Types" },
|
|
31
|
+
{ icon: "sp-icon-box", key: "dependencies", label: "Dependencies" },
|
|
30
32
|
];
|
|
31
33
|
|
|
32
34
|
export function openSettingsModal() {
|
|
@@ -131,6 +133,10 @@ function renderActiveSection() {
|
|
|
131
133
|
renderContentTypesEditor(_contentEl);
|
|
132
134
|
break;
|
|
133
135
|
}
|
|
136
|
+
case "dependencies": {
|
|
137
|
+
renderDependenciesEditor(_contentEl);
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
134
140
|
default: {
|
|
135
141
|
break;
|
|
136
142
|
}
|
package/src/studio.ts
CHANGED
|
@@ -74,6 +74,8 @@ import { mountResizeEdges } from "./resize-edges";
|
|
|
74
74
|
import { codeService } from "./services/code-services";
|
|
75
75
|
import { defBadgeLabel, defCategory, renderSignalsTemplate } from "./panels/signals-panel";
|
|
76
76
|
import { loadComponentRegistry } from "./files/components";
|
|
77
|
+
import { ensureDependenciesInstalled } from "./packages/ensure-deps";
|
|
78
|
+
import { maybePromptJxsuiteUpdate } from "./packages/jxsuite-update";
|
|
77
79
|
|
|
78
80
|
import { html, render as litRender } from "lit-html";
|
|
79
81
|
|
|
@@ -560,6 +562,7 @@ if (_projectParam) {
|
|
|
560
562
|
selectedPath: siteCtx.fileRelPath || null,
|
|
561
563
|
});
|
|
562
564
|
|
|
565
|
+
await ensureDependenciesInstalled();
|
|
563
566
|
await loadComponentRegistry();
|
|
564
567
|
|
|
565
568
|
// Load directory tree and populate projectDirs from conventional dirs found
|
|
@@ -584,6 +587,7 @@ if (_projectParam) {
|
|
|
584
587
|
}
|
|
585
588
|
}
|
|
586
589
|
requireProjectState().projectDirs = foundDirs;
|
|
590
|
+
void maybePromptJxsuiteUpdate(siteCtx.sitePath);
|
|
587
591
|
}
|
|
588
592
|
|
|
589
593
|
// Read and open the file
|
|
@@ -713,6 +717,7 @@ async function openRecentProject(root: string) {
|
|
|
713
717
|
selectedPath: null,
|
|
714
718
|
});
|
|
715
719
|
|
|
720
|
+
await ensureDependenciesInstalled();
|
|
716
721
|
await loadDirectory(".");
|
|
717
722
|
await loadComponentRegistry();
|
|
718
723
|
|
|
@@ -741,6 +746,7 @@ async function openRecentProject(root: string) {
|
|
|
741
746
|
|
|
742
747
|
await openHomePage();
|
|
743
748
|
ensureFsSync();
|
|
749
|
+
void maybePromptJxsuiteUpdate(root);
|
|
744
750
|
} catch (error) {
|
|
745
751
|
statusMessage(`Error: ${errorMessage(error)}`);
|
|
746
752
|
}
|
package/src/tabs/tab.ts
CHANGED
|
@@ -90,12 +90,15 @@ export interface Tab {
|
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
/**
|
|
94
|
-
|
|
93
|
+
/**
|
|
94
|
+
* @param {string} canvasMode — initial canvas mode (the tab's first allowed mode)
|
|
95
|
+
* @returns {TabUi}
|
|
96
|
+
*/
|
|
97
|
+
function createDefaultUi(canvasMode: string) {
|
|
95
98
|
return {
|
|
96
99
|
activeMedia: null,
|
|
97
100
|
activeSelector: null,
|
|
98
|
-
canvasMode
|
|
101
|
+
canvasMode,
|
|
99
102
|
editingFunction: null,
|
|
100
103
|
featureToggles: {},
|
|
101
104
|
gitBranches: null,
|
|
@@ -160,6 +163,9 @@ export function createTab({
|
|
|
160
163
|
normalizeArrayChildren(document);
|
|
161
164
|
|
|
162
165
|
const resolvedModes = capabilities?.modes ?? inferModes(documentPath, sourceFormat);
|
|
166
|
+
// A tab opens in its first allowed mode — never one the toolbar would disable.
|
|
167
|
+
// Formats author mode order so the default comes first (edit, stylebook, etc.).
|
|
168
|
+
const initialCanvasMode = resolvedModes[0] ?? "edit";
|
|
163
169
|
|
|
164
170
|
const tab = scope.run(() => ({
|
|
165
171
|
capabilities: { modes: resolvedModes },
|
|
@@ -190,7 +196,7 @@ export function createTab({
|
|
|
190
196
|
documentStack: [],
|
|
191
197
|
hover: null,
|
|
192
198
|
selection: null,
|
|
193
|
-
ui: createDefaultUi(),
|
|
199
|
+
ui: createDefaultUi(initialCanvasMode),
|
|
194
200
|
}),
|
|
195
201
|
})) as unknown as Tab;
|
|
196
202
|
|
package/src/types.ts
CHANGED
|
@@ -53,6 +53,36 @@ export interface ComponentMeta {
|
|
|
53
53
|
export interface PackageInfo {
|
|
54
54
|
name: string;
|
|
55
55
|
version: string;
|
|
56
|
+
/** True when the dependency lives in `devDependencies` rather than `dependencies`. */
|
|
57
|
+
dev?: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** A dependency with a newer version available, as reported by `bun outdated` / the npm registry. */
|
|
61
|
+
export interface OutdatedInfo {
|
|
62
|
+
name: string;
|
|
63
|
+
/** The version range pinned in package.json (e.g. "^0.19.0"). */
|
|
64
|
+
current: string;
|
|
65
|
+
/** The newest published version. */
|
|
66
|
+
latest: string;
|
|
67
|
+
/** The newest version satisfying the current range, if known. */
|
|
68
|
+
wanted?: string;
|
|
69
|
+
dev?: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Result of a package mutation that runs `bun install` (install / set-versions). */
|
|
73
|
+
export interface PackageOpResult {
|
|
74
|
+
ok: boolean;
|
|
75
|
+
/** Combined stdout/stderr from the bun invocation, surfaced to the user on failure. */
|
|
76
|
+
log?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Desktop app/build info surfaced in the About screen. */
|
|
80
|
+
export interface AppInfo {
|
|
81
|
+
version: string;
|
|
82
|
+
channel: string;
|
|
83
|
+
hash: string;
|
|
84
|
+
/** Human-readable update status (e.g. "Up to date", "Update available"), if known. */
|
|
85
|
+
updateStatus?: string;
|
|
56
86
|
}
|
|
57
87
|
|
|
58
88
|
export interface CodeServiceResult {
|
|
@@ -125,6 +155,28 @@ export interface StudioPlatform {
|
|
|
125
155
|
addPackage: (name: string) => Promise<unknown>;
|
|
126
156
|
removePackage: (name: string) => Promise<unknown>;
|
|
127
157
|
listPackages: () => Promise<PackageInfo[]>;
|
|
158
|
+
/**
|
|
159
|
+
* Run `bun install` in the project root. Optional: platforms without a Bun-capable backend omit
|
|
160
|
+
* it and the install-on-open / reinstall affordances are skipped.
|
|
161
|
+
*/
|
|
162
|
+
installDependencies?: () => Promise<PackageOpResult>;
|
|
163
|
+
/** Whether the project has uninstalled dependencies (node_modules missing). */
|
|
164
|
+
dependenciesNeedInstall?: () => Promise<boolean>;
|
|
165
|
+
/** List dependencies that have a newer version available. */
|
|
166
|
+
outdatedPackages?: () => Promise<OutdatedInfo[]>;
|
|
167
|
+
/**
|
|
168
|
+
* Rewrite the version range of each named package (preserving its dependencies/devDependencies
|
|
169
|
+
* placement) and run `bun install`. Used by the @jxsuite bump and per-dependency updates.
|
|
170
|
+
*/
|
|
171
|
+
setPackageVersions?: (
|
|
172
|
+
updates: { name: string; version: string; dev?: boolean }[],
|
|
173
|
+
) => Promise<PackageOpResult>;
|
|
174
|
+
/**
|
|
175
|
+
* Desktop-only app/build info (release channel, commit hash, update status). Platforms without a
|
|
176
|
+
* native shell (e.g. the dev server) omit it, and the About screen hides the corresponding
|
|
177
|
+
* section.
|
|
178
|
+
*/
|
|
179
|
+
getAppInfo?: () => Promise<AppInfo>;
|
|
128
180
|
codeService: (action: string, payload: unknown) => Promise<CodeServiceResult | null>;
|
|
129
181
|
resolveSiteContext: (filePath: string) => Promise<{
|
|
130
182
|
sitePath: string | null;
|
|
@@ -332,7 +332,8 @@ function renderLiteralEditor(operand: unknown, onChange: (newVal: JxExpressionOp
|
|
|
332
332
|
@change=${(e: Event) => onChange((e.target as HTMLInputElement).checked)}
|
|
333
333
|
>true</sp-checkbox
|
|
334
334
|
>`
|
|
335
|
-
: html`<span
|
|
335
|
+
: html`<span
|
|
336
|
+
style="font-size:var(--spectrum-font-size-75, 12px);color:var(--spectrum-gray-600, #808080)"
|
|
336
337
|
>null</span
|
|
337
338
|
>`}
|
|
338
339
|
</div>
|
|
@@ -425,7 +426,7 @@ function renderSpliceArgsEditor(
|
|
|
425
426
|
class="array-object-row"
|
|
426
427
|
style="display:flex;gap:4px;align-items:center;margin-bottom:4px"
|
|
427
428
|
>
|
|
428
|
-
<span style="font-size:10px;color:var(--spectrum-
|
|
429
|
+
<span style="font-size:10px;color:var(--spectrum-gray-600, #808080);min-width:30px">
|
|
429
430
|
${labels[idx] ?? "item"}
|
|
430
431
|
</span>
|
|
431
432
|
${renderOperandEditor(
|
|
@@ -496,7 +497,7 @@ export function renderExpressionEditor(
|
|
|
496
497
|
|
|
497
498
|
const nestStyle =
|
|
498
499
|
depth > 0
|
|
499
|
-
? "border-left:2px solid var(--spectrum-
|
|
500
|
+
? "border-left:2px solid var(--spectrum-gray-300, #3c3c3c);margin-left:8px;padding-left:8px;"
|
|
500
501
|
: "";
|
|
501
502
|
|
|
502
503
|
return html`
|
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}
|