@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/studio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.1",
|
|
4
4
|
"description": "Jx Studio — visual builder for Jx documents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -23,19 +23,20 @@
|
|
|
23
23
|
"provenance": true
|
|
24
24
|
},
|
|
25
25
|
"scripts": {
|
|
26
|
-
"build": "bun
|
|
26
|
+
"build": "bun run scripts/build.ts",
|
|
27
27
|
"build:metafile": "bun build ./src/studio.ts --outdir dist --target browser --sourcemap=linked --metafile=metafile.json",
|
|
28
28
|
"gen:webdata": "bun run scripts/gen-webdata.js",
|
|
29
|
-
"
|
|
29
|
+
"lint:styles": "bun run scripts/check-styles.ts",
|
|
30
|
+
"test": "bun run lint:styles && bun test --isolate",
|
|
30
31
|
"test:coverage": "bun test --isolate --coverage",
|
|
31
32
|
"upgrade": "bunx npm-check-updates -u && bun install"
|
|
32
33
|
},
|
|
33
34
|
"dependencies": {
|
|
34
35
|
"@atlaskit/pragmatic-drag-and-drop": "^2.0.1",
|
|
35
36
|
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^2.0.0",
|
|
36
|
-
"@jxsuite/parser": "^0.
|
|
37
|
-
"@jxsuite/runtime": "^0.
|
|
38
|
-
"@jxsuite/schema": "^0.
|
|
37
|
+
"@jxsuite/parser": "^0.31.1",
|
|
38
|
+
"@jxsuite/runtime": "^0.31.1",
|
|
39
|
+
"@jxsuite/schema": "^0.31.1",
|
|
39
40
|
"@spectrum-web-components/accordion": "^1.12.1",
|
|
40
41
|
"@spectrum-web-components/action-bar": "1.12.1",
|
|
41
42
|
"@spectrum-web-components/action-button": "^1.12.1",
|
|
@@ -61,6 +62,7 @@
|
|
|
61
62
|
"@spectrum-web-components/picker": "^1.12.1",
|
|
62
63
|
"@spectrum-web-components/picker-button": "^1.12.1",
|
|
63
64
|
"@spectrum-web-components/popover": "^1.12.1",
|
|
65
|
+
"@spectrum-web-components/progress-circle": "^1.12.1",
|
|
64
66
|
"@spectrum-web-components/search": "^1.12.1",
|
|
65
67
|
"@spectrum-web-components/swatch": "^1.12.1",
|
|
66
68
|
"@spectrum-web-components/switch": "^1.12.1",
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
/**
|
|
3
|
+
* About modal — shows app version, build metadata, external links, the resolved `@jxsuite/*`
|
|
4
|
+
* package versions, and (on desktop) the release channel / update status.
|
|
5
|
+
*
|
|
6
|
+
* Build metadata comes from src/version.ts (injected at bundle time). Package versions are fetched
|
|
7
|
+
* lazily via the platform; desktop update info is shown only when the active platform implements
|
|
8
|
+
* the optional `getAppInfo` method.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { html } from "lit-html";
|
|
12
|
+
import type { TemplateResult } from "lit-html";
|
|
13
|
+
import { openModal } from "../ui/layers";
|
|
14
|
+
import { getPlatform } from "../platform";
|
|
15
|
+
import { APP_NAME, BUILD_DATE, GIT_COMMIT, LINKS, VERSION } from "../version";
|
|
16
|
+
import type { AppInfo, PackageInfo } from "../types";
|
|
17
|
+
|
|
18
|
+
let _handle: ReturnType<typeof openModal> | null = null;
|
|
19
|
+
|
|
20
|
+
let _packages: PackageInfo[] | null = null;
|
|
21
|
+
let _appInfo: AppInfo | null = null;
|
|
22
|
+
|
|
23
|
+
export function openAboutModal() {
|
|
24
|
+
if (_handle) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
_packages = null;
|
|
28
|
+
_appInfo = null;
|
|
29
|
+
renderModal();
|
|
30
|
+
void loadDetails();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function closeAboutModal() {
|
|
34
|
+
if (!_handle) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
_handle.close();
|
|
38
|
+
_handle = null;
|
|
39
|
+
_packages = null;
|
|
40
|
+
_appInfo = null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function loadDetails() {
|
|
44
|
+
const platform = getPlatform();
|
|
45
|
+
try {
|
|
46
|
+
_packages = await platform.listPackages();
|
|
47
|
+
} catch {
|
|
48
|
+
_packages = [];
|
|
49
|
+
}
|
|
50
|
+
if (platform.getAppInfo) {
|
|
51
|
+
try {
|
|
52
|
+
_appInfo = await platform.getAppInfo();
|
|
53
|
+
} catch {
|
|
54
|
+
_appInfo = null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (_handle) {
|
|
58
|
+
renderModal();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function formatBuildDate(iso: string) {
|
|
63
|
+
if (!iso) {
|
|
64
|
+
return "—";
|
|
65
|
+
}
|
|
66
|
+
const date = new Date(iso);
|
|
67
|
+
return Number.isNaN(date.getTime()) ? iso : date.toLocaleString();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function metaRows() {
|
|
71
|
+
const rows: [string, string][] = [
|
|
72
|
+
["Version", VERSION],
|
|
73
|
+
["Build date", formatBuildDate(BUILD_DATE)],
|
|
74
|
+
["Commit", GIT_COMMIT],
|
|
75
|
+
];
|
|
76
|
+
if (_appInfo) {
|
|
77
|
+
rows.push(["Channel", _appInfo.channel]);
|
|
78
|
+
if (_appInfo.updateStatus) {
|
|
79
|
+
rows.push(["Updates", _appInfo.updateStatus]);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return rows;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function renderPackages(): TemplateResult {
|
|
86
|
+
if (_packages === null) {
|
|
87
|
+
return html`<p class="about-muted">Loading packages…</p>`;
|
|
88
|
+
}
|
|
89
|
+
if (_packages.length === 0) {
|
|
90
|
+
return html`<p class="about-muted">No packages reported.</p>`;
|
|
91
|
+
}
|
|
92
|
+
return html`
|
|
93
|
+
<ul class="about-packages">
|
|
94
|
+
${_packages.map(
|
|
95
|
+
(p) => html`
|
|
96
|
+
<li class="about-package-row">
|
|
97
|
+
<span class="about-package-name">${p.name}</span>
|
|
98
|
+
<span class="about-package-version">${p.version}</span>
|
|
99
|
+
</li>
|
|
100
|
+
`,
|
|
101
|
+
)}
|
|
102
|
+
</ul>
|
|
103
|
+
`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function renderModal() {
|
|
107
|
+
const tpl = html`
|
|
108
|
+
<sp-underlay open @close=${closeAboutModal}></sp-underlay>
|
|
109
|
+
<div
|
|
110
|
+
class="about-modal"
|
|
111
|
+
role="dialog"
|
|
112
|
+
aria-label="About ${APP_NAME}"
|
|
113
|
+
@keydown=${(e: KeyboardEvent) => {
|
|
114
|
+
if (e.key === "Escape") {
|
|
115
|
+
closeAboutModal();
|
|
116
|
+
}
|
|
117
|
+
}}
|
|
118
|
+
>
|
|
119
|
+
<div class="settings-modal-header">
|
|
120
|
+
<h2 class="settings-modal-title">About ${APP_NAME}</h2>
|
|
121
|
+
<sp-action-button quiet size="s" @click=${closeAboutModal} title="Close">
|
|
122
|
+
<sp-icon-close slot="icon"></sp-icon-close>
|
|
123
|
+
</sp-action-button>
|
|
124
|
+
</div>
|
|
125
|
+
<div class="about-modal-body">
|
|
126
|
+
<dl class="about-meta">
|
|
127
|
+
${metaRows().map(
|
|
128
|
+
([label, value]) => html`
|
|
129
|
+
<div class="about-meta-row">
|
|
130
|
+
<dt class="about-meta-label">${label}</dt>
|
|
131
|
+
<dd class="about-meta-value">${value}</dd>
|
|
132
|
+
</div>
|
|
133
|
+
`,
|
|
134
|
+
)}
|
|
135
|
+
</dl>
|
|
136
|
+
|
|
137
|
+
<div class="about-links">
|
|
138
|
+
<a href=${LINKS.github} target="_blank" rel="noreferrer noopener">GitHub</a>
|
|
139
|
+
<a href=${LINKS.docs} target="_blank" rel="noreferrer noopener">Documentation</a>
|
|
140
|
+
<a href=${LINKS.license} target="_blank" rel="noreferrer noopener">License</a>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<section class="about-section">
|
|
144
|
+
<h3 class="about-section-title">Packages</h3>
|
|
145
|
+
${renderPackages()}
|
|
146
|
+
</section>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
`;
|
|
150
|
+
|
|
151
|
+
if (_handle) {
|
|
152
|
+
_handle.update(tpl);
|
|
153
|
+
} else {
|
|
154
|
+
_handle = openModal(tpl);
|
|
155
|
+
}
|
|
156
|
+
}
|
package/src/files/files.ts
CHANGED
|
@@ -16,6 +16,8 @@ import { createState, projectState, requireProjectState, setProjectState } from
|
|
|
16
16
|
import { getPlatform } from "../platform";
|
|
17
17
|
import { statusMessage } from "../panels/statusbar";
|
|
18
18
|
import { loadComponentRegistry } from "./components";
|
|
19
|
+
import { ensureDependenciesInstalled } from "../packages/ensure-deps";
|
|
20
|
+
import { maybePromptJxsuiteUpdate } from "../packages/jxsuite-update";
|
|
19
21
|
import { markLocalMutation } from "./fs-events";
|
|
20
22
|
import {
|
|
21
23
|
draggable,
|
|
@@ -23,14 +25,7 @@ import {
|
|
|
23
25
|
monitorForElements,
|
|
24
26
|
} from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
|
25
27
|
import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
|
|
26
|
-
import {
|
|
27
|
-
activateTab,
|
|
28
|
-
activeTab,
|
|
29
|
-
openTab,
|
|
30
|
-
renameTab,
|
|
31
|
-
replaceAllTabs,
|
|
32
|
-
workspace,
|
|
33
|
-
} from "../workspace/workspace";
|
|
28
|
+
import { activateTab, openTab, renameTab, replaceAllTabs, workspace } from "../workspace/workspace";
|
|
34
29
|
import { parseSourceForPath, serializeDocument } from "./file-ops";
|
|
35
30
|
import {
|
|
36
31
|
documentExtensions,
|
|
@@ -100,9 +95,11 @@ export async function loadProject() {
|
|
|
100
95
|
});
|
|
101
96
|
|
|
102
97
|
if (info.isSiteProject) {
|
|
98
|
+
await ensureDependenciesInstalled();
|
|
103
99
|
await loadDirectory(".");
|
|
104
100
|
await loadComponentRegistry();
|
|
105
101
|
await openHomePage();
|
|
102
|
+
void maybePromptJxsuiteUpdate(meta.root);
|
|
106
103
|
}
|
|
107
104
|
// If not a site project (monorepo) — show welcome prompt, don't load tree
|
|
108
105
|
} catch {
|
|
@@ -156,6 +153,7 @@ export async function openProject({
|
|
|
156
153
|
selectedPath: null,
|
|
157
154
|
});
|
|
158
155
|
|
|
156
|
+
await ensureDependenciesInstalled();
|
|
159
157
|
await loadDirectory(".");
|
|
160
158
|
await loadComponentRegistry();
|
|
161
159
|
|
|
@@ -187,6 +185,7 @@ export async function openProject({
|
|
|
187
185
|
statusMessage(`Opened project: ${requireProjectState().name}`);
|
|
188
186
|
|
|
189
187
|
await openHomePage();
|
|
188
|
+
void maybePromptJxsuiteUpdate(requireProjectState().projectRoot);
|
|
190
189
|
} catch (error) {
|
|
191
190
|
statusMessage(`Error: ${errorMessage(error)}`);
|
|
192
191
|
}
|
|
@@ -1056,13 +1055,6 @@ export async function openFileInTab(path: string) {
|
|
|
1056
1055
|
requireProjectState().selectedPath = path;
|
|
1057
1056
|
trackRecentFile({ name: path.split("/").pop() || path, path });
|
|
1058
1057
|
|
|
1059
|
-
if (path === "project.json") {
|
|
1060
|
-
const tab = activeTab.value;
|
|
1061
|
-
if (tab) {
|
|
1062
|
-
tab.session.ui.canvasMode = "stylebook";
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
1058
|
statusMessage(`Opened ${path.split("/").pop()}`);
|
|
1067
1059
|
} catch (error) {
|
|
1068
1060
|
statusMessage(`Error: ${errorMessage(error)}`);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
/**
|
|
3
|
+
* Ensure the active project's dependencies are installed before the editor loads. When the backend
|
|
4
|
+
* reports node_modules is missing, run `bun install` behind a blocking progress modal so component
|
|
5
|
+
* and format resolution see the real dependency tree. No-op on platforms without package support.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getPlatform } from "../platform";
|
|
9
|
+
import { showProgressModal } from "../ui/progress-modal";
|
|
10
|
+
|
|
11
|
+
export async function ensureDependenciesInstalled(): Promise<void> {
|
|
12
|
+
const platform = getPlatform();
|
|
13
|
+
if (!platform.dependenciesNeedInstall || !platform.installDependencies) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
let needs = false;
|
|
17
|
+
try {
|
|
18
|
+
needs = await platform.dependenciesNeedInstall();
|
|
19
|
+
} catch {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (!needs) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const progress = showProgressModal({
|
|
26
|
+
status: "Running bun install…",
|
|
27
|
+
title: "Installing dependencies",
|
|
28
|
+
});
|
|
29
|
+
try {
|
|
30
|
+
const result = await platform.installDependencies();
|
|
31
|
+
if (result.ok) {
|
|
32
|
+
progress.done();
|
|
33
|
+
} else {
|
|
34
|
+
progress.fail(result.log ?? "bun install failed");
|
|
35
|
+
}
|
|
36
|
+
} catch (error) {
|
|
37
|
+
progress.fail(error instanceof Error ? error.message : String(error));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
/**
|
|
3
|
+
* On project open, compare the project's `@jxsuite/*` dependency ranges to the version this Studio
|
|
4
|
+
* build embeds (`VERSION`). If the project is behind, prompt the user to bump them to match and —
|
|
5
|
+
* on confirm — rewrite the ranges and reinstall. The target is the embedded version (not npm
|
|
6
|
+
* latest) so a project always lines up with the runtime/compiler the running app actually uses.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { html } from "lit-html";
|
|
10
|
+
import { getPlatform } from "../platform";
|
|
11
|
+
import { VERSION } from "../version";
|
|
12
|
+
import { showConfirmDialog } from "../ui/layers";
|
|
13
|
+
import { showProgressModal } from "../ui/progress-modal";
|
|
14
|
+
import { statusMessage } from "../panels/statusbar";
|
|
15
|
+
import { isComparable, isUpgrade } from "./semver";
|
|
16
|
+
import type { PackageInfo } from "../types";
|
|
17
|
+
|
|
18
|
+
const JXSUITE_PREFIX = "@jxsuite/";
|
|
19
|
+
|
|
20
|
+
export interface JxsuiteUpdate {
|
|
21
|
+
name: string;
|
|
22
|
+
current: string;
|
|
23
|
+
dev: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Compare the project's @jxsuite/* deps to the embedded version. Returns the packages behind the
|
|
28
|
+
* target plus the target version, or null when there's nothing to do (dev build, no project, or all
|
|
29
|
+
* already current/ahead).
|
|
30
|
+
*/
|
|
31
|
+
export async function checkJxsuiteUpdate(): Promise<{
|
|
32
|
+
target: string;
|
|
33
|
+
outdated: JxsuiteUpdate[];
|
|
34
|
+
} | null> {
|
|
35
|
+
const target = VERSION;
|
|
36
|
+
if (!isComparable(target)) {
|
|
37
|
+
return null; // "dev" / non-semver build — no reliable target
|
|
38
|
+
}
|
|
39
|
+
const platform = getPlatform();
|
|
40
|
+
let pkgs: PackageInfo[];
|
|
41
|
+
try {
|
|
42
|
+
pkgs = await platform.listPackages();
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const outdated: JxsuiteUpdate[] = [];
|
|
47
|
+
for (const p of pkgs) {
|
|
48
|
+
if (!p.name.startsWith(JXSUITE_PREFIX) || !isComparable(p.version)) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (isUpgrade(p.version, target)) {
|
|
52
|
+
outdated.push({ current: p.version, dev: Boolean(p.dev), name: p.name });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return outdated.length > 0 ? { outdated, target } : null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function dismissKey(root: string, target: string): string {
|
|
59
|
+
return `jx:jxsuite-update-dismissed:${root}:${target}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isDismissed(root: string, target: string): boolean {
|
|
63
|
+
try {
|
|
64
|
+
return localStorage.getItem(dismissKey(root, target)) === "1";
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function setDismissed(root: string, target: string): void {
|
|
71
|
+
try {
|
|
72
|
+
localStorage.setItem(dismissKey(root, target), "1");
|
|
73
|
+
} catch {
|
|
74
|
+
/* Ignore storage errors */
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Apply a set of @jxsuite bumps to `^target` and reinstall, behind a progress modal. */
|
|
79
|
+
export async function applyJxsuiteUpdate(outdated: JxsuiteUpdate[], target: string): Promise<void> {
|
|
80
|
+
const platform = getPlatform();
|
|
81
|
+
if (!platform.setPackageVersions) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const progress = showProgressModal({
|
|
85
|
+
status: "Updating @jxsuite packages…",
|
|
86
|
+
title: "Updating dependencies",
|
|
87
|
+
});
|
|
88
|
+
try {
|
|
89
|
+
const result = await platform.setPackageVersions(
|
|
90
|
+
outdated.map((p) => ({ dev: p.dev, name: p.name, version: `^${target}` })),
|
|
91
|
+
);
|
|
92
|
+
if (result.ok) {
|
|
93
|
+
progress.done();
|
|
94
|
+
statusMessage(`Updated ${outdated.length} @jxsuite package(s) to ${target}`);
|
|
95
|
+
} else {
|
|
96
|
+
progress.fail(result.log ?? "Update failed");
|
|
97
|
+
}
|
|
98
|
+
} catch (error) {
|
|
99
|
+
progress.fail(error instanceof Error ? error.message : String(error));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Prompt to update @jxsuite packages on open. Skips when there's nothing to do or the user already
|
|
105
|
+
* declined this exact target for this project (remembered in localStorage).
|
|
106
|
+
*/
|
|
107
|
+
export async function maybePromptJxsuiteUpdate(projectRoot: string): Promise<void> {
|
|
108
|
+
const platform = getPlatform();
|
|
109
|
+
if (!platform.setPackageVersions) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const check = await checkJxsuiteUpdate();
|
|
113
|
+
if (!check || isDismissed(projectRoot, check.target)) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const list = check.outdated.map((p) => `${p.name} ${p.current} → ^${check.target}`).join("\n");
|
|
117
|
+
const confirmed = await showConfirmDialog(
|
|
118
|
+
"Update @jxsuite packages?",
|
|
119
|
+
html`This project uses older @jxsuite packages. Update them to match Studio ${check.target}?
|
|
120
|
+
<br /><br /><span
|
|
121
|
+
style="font-size:var(--spectrum-font-size-75, 12px);color:var(--fg-dim);white-space:pre-line"
|
|
122
|
+
>${list}</span
|
|
123
|
+
>`,
|
|
124
|
+
{ cancelLabel: "Not now", confirmLabel: "Update" },
|
|
125
|
+
);
|
|
126
|
+
if (!confirmed) {
|
|
127
|
+
setDismissed(projectRoot, check.target);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
await applyJxsuiteUpdate(check.outdated, check.target);
|
|
131
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal semver helpers for comparing dependency versions in the UI. Deliberately tiny — we only
|
|
3
|
+
* need range stripping and ordering of `x.y.z` triples, not full semver range satisfaction, so we
|
|
4
|
+
* avoid pulling in a dependency.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Strip a range prefix to its base version: "^0.19.0" → "0.19.0", "~1.2.3" → "1.2.3". */
|
|
8
|
+
export function stripRange(range: string): string {
|
|
9
|
+
const base = range.replace(/^[\^~>=<v\s]+/, "").trim();
|
|
10
|
+
return base.split(/\s+/)[0] ?? "";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Whether a spec is a comparable registry semver (a plain `x.y.z` range), as opposed to
|
|
15
|
+
* `workspace:`, `file:`, `link:`, a git/URL spec, or a wildcard like `*`/`latest`.
|
|
16
|
+
*/
|
|
17
|
+
export function isComparable(spec: string): boolean {
|
|
18
|
+
if (spec.includes(":")) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
return /^\d+\.\d+/.test(stripRange(spec));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Parse a version into numeric [major, minor, patch], ignoring any pre-release/build suffix. */
|
|
25
|
+
function parts(version: string): [number, number, number] {
|
|
26
|
+
const core = stripRange(version).split(/[-+]/)[0] ?? "";
|
|
27
|
+
const [major = 0, minor = 0, patch = 0] = core.split(".").map((n) => Math.trunc(Number(n)) || 0);
|
|
28
|
+
return [major, minor, patch];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Compare two versions (range prefixes tolerated). Returns -1 if a < b, 1 if a > b, 0 if equal.
|
|
33
|
+
* Pre-release/build metadata is ignored.
|
|
34
|
+
*/
|
|
35
|
+
export function compareSemver(a: string, b: string): number {
|
|
36
|
+
const pa = parts(a);
|
|
37
|
+
const pb = parts(b);
|
|
38
|
+
for (let i = 0; i < 3; i++) {
|
|
39
|
+
const da = pa[i] ?? 0;
|
|
40
|
+
const db = pb[i] ?? 0;
|
|
41
|
+
if (da !== db) {
|
|
42
|
+
return da < db ? -1 : 1;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Whether `latest` is strictly newer than the base version of `current` (both comparable). */
|
|
49
|
+
export function isUpgrade(current: string, latest: string): boolean {
|
|
50
|
+
if (!isComparable(current) || !isComparable(latest)) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
return compareSemver(latest, current) > 0;
|
|
54
|
+
}
|
|
@@ -7,6 +7,7 @@ import { effect, effectScope } from "../reactivity";
|
|
|
7
7
|
import { activeTab } from "../workspace/workspace";
|
|
8
8
|
import { applyPanelCollapse, view } from "../view";
|
|
9
9
|
import { openSettingsModal } from "../settings/settings-modal";
|
|
10
|
+
import { openAboutModal } from "../about/about-modal";
|
|
10
11
|
import { refreshGitStatus } from "./git-panel";
|
|
11
12
|
import type { EffectScope } from "@vue/reactivity";
|
|
12
13
|
import type { TemplateResult } from "lit-html";
|
|
@@ -129,7 +130,18 @@ export function renderActivityBar() {
|
|
|
129
130
|
`,
|
|
130
131
|
)}
|
|
131
132
|
</sp-tabs>
|
|
132
|
-
<div
|
|
133
|
+
<div
|
|
134
|
+
style="margin-top:auto;padding:8px 0;display:flex;flex-direction:column;align-items:center;gap:4px"
|
|
135
|
+
>
|
|
136
|
+
<sp-action-button
|
|
137
|
+
quiet
|
|
138
|
+
size="m"
|
|
139
|
+
title="About"
|
|
140
|
+
aria-label="About"
|
|
141
|
+
@click=${() => openAboutModal()}
|
|
142
|
+
>
|
|
143
|
+
<sp-icon-info slot="icon"></sp-icon-info>
|
|
144
|
+
</sp-action-button>
|
|
133
145
|
<sp-action-button
|
|
134
146
|
quiet
|
|
135
147
|
size="m"
|
package/src/panels/ai-panel.ts
CHANGED
|
@@ -457,7 +457,9 @@ export function renderAiPanelTemplate() {
|
|
|
457
457
|
<div class="ai-status-center">
|
|
458
458
|
<sp-icon-artboard style="font-size:32px"></sp-icon-artboard>
|
|
459
459
|
<div>Claude authentication required</div>
|
|
460
|
-
<div
|
|
460
|
+
<div
|
|
461
|
+
style="font-size:var(--spectrum-font-size-50, 11px);color:var(--spectrum-gray-600, #808080)"
|
|
462
|
+
>
|
|
461
463
|
Run the following in your terminal:
|
|
462
464
|
</div>
|
|
463
465
|
<code class="ai-code-snippet">npx @anthropic-ai/claude-code login</code>
|
|
@@ -141,7 +141,8 @@ export function renderElementsTemplate(ctx: {
|
|
|
141
141
|
}}
|
|
142
142
|
>
|
|
143
143
|
<div class="element-card-preview">
|
|
144
|
-
<span
|
|
144
|
+
<span
|
|
145
|
+
style="color:var(--fg-dim);font-size:var(--spectrum-font-size-50, 11px);font-style:italic"
|
|
145
146
|
><${comp.tagName}></span
|
|
146
147
|
>
|
|
147
148
|
</div>
|
|
@@ -151,7 +151,11 @@ export function renderLayersTemplate(ctx: {
|
|
|
151
151
|
class="layer-row"
|
|
152
152
|
style="padding-left:${depth * 16 + 8}px; opacity: 0.6; font-style: italic;"
|
|
153
153
|
>
|
|
154
|
-
<span
|
|
154
|
+
<span
|
|
155
|
+
class="layer-tag"
|
|
156
|
+
style="background: var(--spectrum-gray-500, #64748b); font-size: 0.65rem;"
|
|
157
|
+
>text</span
|
|
158
|
+
>
|
|
155
159
|
<span class="layer-label">${textPreview}</span>
|
|
156
160
|
</div>
|
|
157
161
|
`,
|
|
@@ -122,7 +122,7 @@ export function renderCanvasNode(
|
|
|
122
122
|
const placeholder = document.createElement("div");
|
|
123
123
|
placeholder.textContent = `[$switch: ${keys.join(" | ")}]`;
|
|
124
124
|
placeholder.style.cssText =
|
|
125
|
-
"font-family:
|
|
125
|
+
"font-family:var(--font-mono);font-size:var(--spectrum-font-size-50,11px);padding:6px 10px;background:color-mix(in srgb, var(--danger) 8%, transparent);border:1px dashed color-mix(in srgb, var(--danger) 40%, transparent);border-radius:var(--radius);color:var(--danger);font-style:italic";
|
|
126
126
|
el.append(placeholder);
|
|
127
127
|
}
|
|
128
128
|
|
|
@@ -331,7 +331,7 @@ function renderSwitchFieldsTemplate(
|
|
|
331
331
|
mapSignals,
|
|
332
332
|
)}
|
|
333
333
|
<div
|
|
334
|
-
style="font-size:11px;font-weight:600;color:var(--fg-dim);margin:8px 0 4px;text-transform:uppercase;letter-spacing:0.05em"
|
|
334
|
+
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"
|
|
335
335
|
>
|
|
336
336
|
Cases
|
|
337
337
|
</div>
|
|
@@ -369,7 +369,7 @@ function renderSwitchFieldsTemplate(
|
|
|
369
369
|
>→</span
|
|
370
370
|
>
|
|
371
371
|
<span
|
|
372
|
-
style="cursor:pointer;color:var(--danger);font-size:11px"
|
|
372
|
+
style="cursor:pointer;color:var(--danger);font-size:var(--spectrum-font-size-50, 11px)"
|
|
373
373
|
@click=${(e: Event) => {
|
|
374
374
|
e.stopPropagation();
|
|
375
375
|
transactDoc(activeTab.value!, (t) => mutateRemoveSwitchCase(t, path, caseName));
|
|
@@ -657,7 +657,7 @@ function renderMediaFieldsTemplate(node: JxMutableNode) {
|
|
|
657
657
|
}}
|
|
658
658
|
/>
|
|
659
659
|
<span
|
|
660
|
-
style="font-size:10px;color:var(--fg-dim);font-family:
|
|
660
|
+
style="font-size:10px;color:var(--fg-dim);font-family:var(--font-mono);white-space:nowrap"
|
|
661
661
|
>${view.addBreakpointPreview}</span
|
|
662
662
|
>
|
|
663
663
|
</div>
|
|
@@ -714,7 +714,7 @@ function mediaBreakpointRowTemplate(name: string, query: string) {
|
|
|
714
714
|
<input
|
|
715
715
|
class="field-input"
|
|
716
716
|
.value=${live(mediaDisplayName(name))}
|
|
717
|
-
style="flex:1;font-weight:600;font-size:12px"
|
|
717
|
+
style="flex:1;font-weight:600;font-size:var(--spectrum-font-size-75, 12px)"
|
|
718
718
|
@input=${(e: Event) => {
|
|
719
719
|
const newKey = friendlyNameToMedia((e.target as HTMLInputElement).value);
|
|
720
720
|
currentRawLabel = newKey || "";
|
|
@@ -738,7 +738,7 @@ function mediaBreakpointRowTemplate(name: string, query: string) {
|
|
|
738
738
|
/>
|
|
739
739
|
<span
|
|
740
740
|
class="bp-raw-label"
|
|
741
|
-
style="font-size:10px;color:var(--fg-dim);font-family:
|
|
741
|
+
style="font-size:10px;color:var(--fg-dim);font-family:var(--font-mono);white-space:nowrap"
|
|
742
742
|
>${name}</span
|
|
743
743
|
>
|
|
744
744
|
<span
|
|
@@ -892,14 +892,18 @@ function renderLayoutSelectionPanel(ctx: { navigateToComponent: (path: string) =
|
|
|
892
892
|
style="font-size:9px;padding:2px 6px;background:var(--spectrum-purple-600);color:white;border-radius:3px;text-transform:uppercase;letter-spacing:0.5px"
|
|
893
893
|
>Layout</span
|
|
894
894
|
>
|
|
895
|
-
<code
|
|
895
|
+
<code
|
|
896
|
+
style="font-size:var(--spectrum-font-size-75, 12px);font-family:var(--font-mono)"
|
|
897
|
+
><${tagName}></code
|
|
898
|
+
>
|
|
896
899
|
</div>
|
|
897
900
|
${className
|
|
898
901
|
? html`<div class="style-row">
|
|
899
902
|
<div class="style-row-label">
|
|
900
903
|
<sp-field-label size="s">Class</sp-field-label>
|
|
901
904
|
</div>
|
|
902
|
-
<span
|
|
905
|
+
<span
|
|
906
|
+
style="font-size:var(--spectrum-font-size-50, 11px);color:var(--fg-dim);word-break:break-all"
|
|
903
907
|
>${className}</span
|
|
904
908
|
>
|
|
905
909
|
</div>`
|
|
@@ -1234,9 +1238,9 @@ export function renderPropertiesPanelTemplate(ctx: {
|
|
|
1234
1238
|
const def = d as Record<string, unknown>;
|
|
1235
1239
|
return html`
|
|
1236
1240
|
<div
|
|
1237
|
-
style="display:flex;gap:6px;align-items:center;padding:2px 0;font-size:11px"
|
|
1241
|
+
style="display:flex;gap:6px;align-items:center;padding:2px 0;font-size:var(--spectrum-font-size-50, 11px)"
|
|
1238
1242
|
>
|
|
1239
|
-
<code style="font-family:
|
|
1243
|
+
<code style="font-family:var(--font-mono);color:var(--accent)"
|
|
1240
1244
|
>${def.attribute}</code
|
|
1241
1245
|
>
|
|
1242
1246
|
<span style="color:var(--fg-dim)"> → </span>
|
|
@@ -1248,7 +1252,7 @@ export function renderPropertiesPanelTemplate(ctx: {
|
|
|
1248
1252
|
: nothing}
|
|
1249
1253
|
${def.reflects
|
|
1250
1254
|
? html`<span
|
|
1251
|
-
style="font-size:9px;background:var(--bg
|
|
1255
|
+
style="font-size:9px;background:var(--hover-bg);padding:1px 4px;border-radius:var(--radius)"
|
|
1252
1256
|
>reflects</span
|
|
1253
1257
|
>`
|
|
1254
1258
|
: nothing}
|
|
@@ -1344,9 +1348,9 @@ export function renderPropertiesPanelTemplate(ctx: {
|
|
|
1344
1348
|
${cssProps.map(
|
|
1345
1349
|
([prop, val]) => html`
|
|
1346
1350
|
<div
|
|
1347
|
-
style="display:flex;gap:6px;align-items:center;padding:2px 0;font-size:11px"
|
|
1351
|
+
style="display:flex;gap:6px;align-items:center;padding:2px 0;font-size:var(--spectrum-font-size-50, 11px)"
|
|
1348
1352
|
>
|
|
1349
|
-
<code style="font-family:
|
|
1353
|
+
<code style="font-family:var(--font-mono);color:var(--accent)">${prop}</code>
|
|
1350
1354
|
<span style="margin-left:auto;color:var(--fg-dim)">${String(val)}</span>
|
|
1351
1355
|
</div>
|
|
1352
1356
|
`,
|
|
@@ -1374,9 +1378,11 @@ export function renderPropertiesPanelTemplate(ctx: {
|
|
|
1374
1378
|
${parts.map(
|
|
1375
1379
|
(p) => html`
|
|
1376
1380
|
<div
|
|
1377
|
-
style="display:flex;gap:6px;align-items:center;padding:2px 0;font-size:11px"
|
|
1381
|
+
style="display:flex;gap:6px;align-items:center;padding:2px 0;font-size:var(--spectrum-font-size-50, 11px)"
|
|
1378
1382
|
>
|
|
1379
|
-
<code style="font-family:
|
|
1383
|
+
<code style="font-family:var(--font-mono);color:var(--accent)"
|
|
1384
|
+
>${p.name}</code
|
|
1385
|
+
>
|
|
1380
1386
|
<span style="color:var(--fg-dim)"><${p.tag}></span>
|
|
1381
1387
|
</div>
|
|
1382
1388
|
`,
|