@jxsuite/studio 2.2.1 → 2.3.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/data/css-meta.json +1487 -0
- package/data/elements-meta.json +370 -0
- package/data/html-meta.json +366 -0
- package/data/stylebook-meta.json +200 -0
- package/data/webdata.json +1621 -0
- package/dist/chunks/{Overlay.dev-ymy3et3y.js → Overlay.dev-d2ajy6sj.js} +7 -7
- package/dist/chunks/{content-source-r2n7v682.js → content-source-56xpvx83.js} +13 -12
- package/dist/chunks/{convert-to-repeater-cnpyvj74.js → convert-to-repeater-0xh9wwek.js} +12 -11
- package/dist/chunks/{extension-sections-vpcm4jvx.js → extension-sections-g34239vm.js} +17 -16
- package/dist/chunks/{files-zeg88k2d.js → files-gkz202dp.js} +18 -19
- package/dist/chunks/{format-host-taymsw55.js → format-host-pej3srnw.js} +2 -2
- package/dist/chunks/{git-panel-qp4rrwr9.js → git-panel-4j6xbh8g.js} +12 -11
- package/dist/chunks/{layers-panel-kj93w2zg.js → layers-panel-8mvn8sgd.js} +15 -14
- package/dist/chunks/{monaco-setup-xy3bs05d.js → monaco-setup-a5wkmbgh.js} +6 -3
- package/dist/chunks/{publish-panel-6qz6kp41.js → publish-panel-ptddc3kg.js} +6 -6
- package/dist/chunks/{quick-search-9wwa09hh.js → quick-search-j88azfs2.js} +19 -18
- package/dist/chunks/right-panel-egkaxy89.js +38 -0
- package/dist/chunks/{sp-overlay.dev-h6kdx076.js → sp-overlay.dev-s18hymn9.js} +8 -8
- package/dist/chunks/sp-progress-circle.dev-0zxcyz3r.js +15 -0
- package/dist/chunks/sp-tooltip.dev-5c6d1kjz.js +15 -0
- package/dist/chunks/{studio-753znj4c.js → studio-0e4h32g6.js} +5 -5
- package/dist/chunks/{studio-yz9s88s0.js → studio-10s469da.js} +25 -9
- package/dist/chunks/{studio-6f0am8f2.js → studio-2w11grav.js} +2 -2
- package/dist/chunks/{studio-zqv3w24n.js → studio-3q65xvzv.js} +5 -5
- package/dist/chunks/{studio-qhh8r5fz.js → studio-4058pg09.js} +4 -4
- package/dist/chunks/{studio-hekaagev.js → studio-4enps87v.js} +17 -17
- package/dist/chunks/{studio-tqemhphj.js → studio-50eed4c9.js} +4 -4
- package/dist/chunks/studio-53n8bp04.js +16 -0
- package/dist/chunks/{studio-nqq8jk8f.js → studio-5amdrjm4.js} +14 -14
- package/dist/chunks/{studio-6zzbyx17.js → studio-5ne8mdx9.js} +2 -2
- package/dist/chunks/{studio-3z8jjm2p.js → studio-7ejemhej.js} +3 -3
- package/dist/chunks/{studio-1kgfcv7s.js → studio-a11d3a1b.js} +12 -11
- package/dist/chunks/{studio-rgwcpast.js → studio-ay8wcr2b.js} +2 -2
- package/dist/chunks/{studio-fys6z73y.js → studio-bak2cyjr.js} +7 -7
- package/dist/chunks/{studio-xmvbhg31.js → studio-c31yb4t6.js} +2 -2
- package/dist/chunks/{studio-8sdyjfd6.js → studio-cf4rk7kv.js} +2 -2
- package/dist/chunks/{studio-yphy4z9m.js → studio-cv606rs1.js} +5 -5
- package/dist/chunks/{studio-5kt960pk.js → studio-hk523hby.js} +133 -147
- package/dist/chunks/{studio-x0et2z9g.js → studio-hs8x23fv.js} +15 -15
- package/dist/chunks/{studio-p471gg5n.js → studio-kvfx9z2c.js} +7 -3
- package/dist/chunks/{studio-22xr4q8q.js → studio-ncnet0ba.js} +18 -29
- package/dist/chunks/{studio-ckwz4d3x.js → studio-twsafr1e.js} +2 -2
- package/dist/chunks/{studio-qrsfh1w7.js → studio-tzx3xmvx.js} +5 -5
- package/dist/chunks/{studio-g8t7g0gp.js → studio-vdpcpada.js} +3 -3
- package/dist/chunks/{studio-6a0h7sne.js → studio-vhk5fmfv.js} +2 -2
- package/dist/chunks/{studio-hrajjyt2.js → studio-wdt9y92a.js} +2 -2
- package/dist/chunks/{studio-r8vx7bkz.js → studio-x3etxs0s.js} +14 -13
- package/dist/chunks/{studio-pv8m0h4a.js → studio-yxzgjq8q.js} +28 -23
- package/dist/chunks/{studio-300zsz1k.js → studio-zdkgwhkv.js} +6 -6
- package/dist/codicon.ttf +0 -0
- package/dist/iframe-entry.js +8 -1
- package/dist/manifest.json +82 -0
- package/dist/studio.js +232 -103
- package/index.html +0 -50
- package/package.json +12 -5
- package/src/canvas/iframe-entry.ts +5 -0
- package/src/canvas/iframe-host.ts +45 -4
- package/src/content/entry-editor.ts +2 -1
- package/src/editor/context-menu.ts +13 -2
- package/src/editor/slash-menu.ts +26 -11
- package/src/files/files.ts +11 -23
- package/src/grid/grid-panel.ts +27 -6
- package/src/hosting/document.ts +109 -0
- package/src/hosting/layout.ts +211 -0
- package/src/hosting/stage.ts +166 -0
- package/src/hosting/tsconfig.json +18 -0
- package/src/panels/ai-chat/chat-view.ts +25 -14
- package/src/panels/git-panel.ts +17 -14
- package/src/panels/head-panel.ts +32 -14
- package/src/panels/imports-panel.ts +32 -13
- package/src/panels/layers-panel.ts +9 -1
- package/src/panels/panel-registry.ts +0 -1
- package/src/panels/properties-panel.ts +3 -3
- package/src/panels/settings-pane.ts +1 -1
- package/src/panels/style-panel.ts +3 -3
- package/src/panels/stylebook-panel.ts +10 -1
- package/src/panels/toolbar.ts +1 -1
- package/src/services/bundle-base.ts +69 -0
- package/src/services/monaco-setup.ts +19 -13
- package/src/shell/tree.ts +155 -0
- package/src/shell.ts +0 -2
- package/src/studio.ts +22 -2
- package/src/types.ts +15 -0
- package/src/ui/regions.ts +10 -45
- package/styles/overlays.css +35 -0
- package/dist/chunks/2020-xp8vnwma.js.map +0 -24
- package/dist/chunks/Overlay.dev-ymy3et3y.js.map +0 -9
- package/dist/chunks/adapter-jkcyhgtr.js.map +0 -9
- package/dist/chunks/cf-settings-g5fbf2zp.js.map +0 -9
- package/dist/chunks/components-z6cnkyy1.js.map +0 -9
- package/dist/chunks/content-source-r2n7v682.js.map +0 -9
- package/dist/chunks/convert-to-repeater-cnpyvj74.js.map +0 -10
- package/dist/chunks/editor-cptq5jp1.js.map +0 -9
- package/dist/chunks/extension-sections-vpcm4jvx.js.map +0 -11
- package/dist/chunks/files-zeg88k2d.js.map +0 -9
- package/dist/chunks/flow-qbfgrpeh.js.map +0 -12
- package/dist/chunks/focus-trap.esm-ggawf1hy.js.map +0 -11
- package/dist/chunks/focus-visible-6eg70z18.js.map +0 -10
- package/dist/chunks/format-host-taymsw55.js.map +0 -9
- package/dist/chunks/git-panel-qp4rrwr9.js.map +0 -9
- package/dist/chunks/index-nj44jpzj.js.map +0 -14
- package/dist/chunks/index-zqxmqxmh.js.map +0 -9
- package/dist/chunks/javascript-q8kpmfah.js.map +0 -11
- package/dist/chunks/jsonMode-dkr040ga.js.map +0 -18
- package/dist/chunks/jx-validate-jj9rnt4n.js.map +0 -9
- package/dist/chunks/layers-panel-kj93w2zg.js.map +0 -9
- package/dist/chunks/monaco-binding-rj6xpx9y.js.map +0 -10
- package/dist/chunks/monaco-setup-xy3bs05d.js.map +0 -15
- package/dist/chunks/publish-panel-6qz6kp41.js.map +0 -9
- package/dist/chunks/quick-search-9wwa09hh.js.map +0 -9
- package/dist/chunks/right-panel-faf816ch.js +0 -37
- package/dist/chunks/right-panel-faf816ch.js.map +0 -9
- package/dist/chunks/section-registry-sx1ykvkw.js.map +0 -9
- package/dist/chunks/sp-overlay.dev-h6kdx076.js.map +0 -9
- package/dist/chunks/sp-popover.dev-dj9x7e7a.js.map +0 -9
- package/dist/chunks/sp-progress-circle.dev-0fpvhm52.js +0 -15
- package/dist/chunks/sp-progress-circle.dev-0fpvhm52.js.map +0 -9
- package/dist/chunks/sp-tooltip.dev-rvepkw4r.js +0 -15
- package/dist/chunks/sp-tooltip.dev-rvepkw4r.js.map +0 -9
- package/dist/chunks/sp-tray.dev-64v4pxw8.js.map +0 -12
- package/dist/chunks/studio-0a5qrh01.js.map +0 -9
- package/dist/chunks/studio-0rqqhrdy.js.map +0 -12
- package/dist/chunks/studio-18j3bmcz.js.map +0 -10
- package/dist/chunks/studio-1kgfcv7s.js.map +0 -13
- package/dist/chunks/studio-22xr4q8q.js.map +0 -13
- package/dist/chunks/studio-300zsz1k.js.map +0 -39
- package/dist/chunks/studio-3fk1ac9z.js.map +0 -12
- package/dist/chunks/studio-3n4ndrzh.js.map +0 -41
- package/dist/chunks/studio-3z8jjm2p.js.map +0 -13
- package/dist/chunks/studio-4mqb9a0j.js.map +0 -14
- package/dist/chunks/studio-4q0wznzf.js.map +0 -73
- package/dist/chunks/studio-5ehhackm.js.map +0 -10
- package/dist/chunks/studio-5kt960pk.js.map +0 -38
- package/dist/chunks/studio-5x5774ak.js.map +0 -10
- package/dist/chunks/studio-685s0ytq.js.map +0 -76
- package/dist/chunks/studio-6a0h7sne.js.map +0 -15
- package/dist/chunks/studio-6f0am8f2.js.map +0 -10
- package/dist/chunks/studio-6kv3apa9.js.map +0 -10
- package/dist/chunks/studio-6zzbyx17.js.map +0 -12
- package/dist/chunks/studio-753znj4c.js.map +0 -16
- package/dist/chunks/studio-7afd21nw.js.map +0 -10
- package/dist/chunks/studio-8jpzqk5s.js.map +0 -10
- package/dist/chunks/studio-8q517dfr.js.map +0 -10
- package/dist/chunks/studio-8sdyjfd6.js.map +0 -10
- package/dist/chunks/studio-a0tswy0w.js.map +0 -9
- package/dist/chunks/studio-a7pzveq1.js.map +0 -11
- package/dist/chunks/studio-ahnzd9rg.js.map +0 -10
- package/dist/chunks/studio-b9b6t1dg.js.map +0 -651
- package/dist/chunks/studio-bw5dz6km.js.map +0 -21
- package/dist/chunks/studio-cjm7fdjf.js.map +0 -14
- package/dist/chunks/studio-ckwz4d3x.js.map +0 -12
- package/dist/chunks/studio-d1ks410h.js.map +0 -10
- package/dist/chunks/studio-dg7zyt5x.js.map +0 -10
- package/dist/chunks/studio-fys6z73y.js.map +0 -10
- package/dist/chunks/studio-g1wdche0.js.map +0 -56
- package/dist/chunks/studio-g8t7g0gp.js.map +0 -10
- package/dist/chunks/studio-h4rxscfc.js.map +0 -10
- package/dist/chunks/studio-hekaagev.js.map +0 -18
- package/dist/chunks/studio-hrajjyt2.js.map +0 -10
- package/dist/chunks/studio-nqq8jk8f.js.map +0 -14
- package/dist/chunks/studio-p471gg5n.js.map +0 -14
- package/dist/chunks/studio-pv8m0h4a.js.map +0 -98
- package/dist/chunks/studio-qhh8r5fz.js.map +0 -13
- package/dist/chunks/studio-qrsfh1w7.js.map +0 -41
- package/dist/chunks/studio-r8vx7bkz.js.map +0 -235
- package/dist/chunks/studio-rgwcpast.js.map +0 -12
- package/dist/chunks/studio-s519zc99.js.map +0 -10
- package/dist/chunks/studio-sas7mt3k.js.map +0 -10
- package/dist/chunks/studio-t5wqwzdh.js.map +0 -10
- package/dist/chunks/studio-tqemhphj.js.map +0 -11
- package/dist/chunks/studio-tr20m8g8.js.map +0 -9
- package/dist/chunks/studio-tvgyw9xw.js.map +0 -14
- package/dist/chunks/studio-x0et2z9g.js.map +0 -32
- package/dist/chunks/studio-xm3dbxvn.js.map +0 -19
- package/dist/chunks/studio-xmvbhg31.js.map +0 -10
- package/dist/chunks/studio-ynxwkrzd.js.map +0 -246
- package/dist/chunks/studio-yphy4z9m.js.map +0 -10
- package/dist/chunks/studio-yz9s88s0.js.map +0 -54
- package/dist/chunks/studio-z6nw2ek2.js.map +0 -10
- package/dist/chunks/studio-zqv3w24n.js.map +0 -11
- package/dist/chunks/tsMode-76kr53q0.js.map +0 -13
- package/dist/chunks/ws-client-6shc5rze.js.map +0 -12
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `@jxsuite/studio` ships, where it goes, and how a host addresses it.
|
|
3
|
+
*
|
|
4
|
+
* The package's internal cross-references are already correct and self-locating — `dist/studio.css`
|
|
5
|
+
* reaches `./codicon.ttf`, `styles/tokens.css` reaches `../fonts/*.woff2`, the entry reaches
|
|
6
|
+
* `./chunks/*` and `./workers/*`. What it never shipped was a STATEMENT that they are, so four
|
|
7
|
+
* hosts re-derived the file list by hand, two of them flattened the tree, that broke the
|
|
8
|
+
* references, and they rewrote the shipped HTML to compensate. This module is the statement.
|
|
9
|
+
*
|
|
10
|
+
* **Pure by contract.** No `node:`, no DOM, nothing beyond the language — so a wrangler build, a
|
|
11
|
+
* Vite plugin, a Deno host or a Nix derivation can read it. `./hosting` (stage.ts) is the `node:fs`
|
|
12
|
+
* convenience the two Bun consumers share, and it is the only entry that binds a runtime. The build
|
|
13
|
+
* also emits `dist/manifest.json` from {@link STUDIO_ASSETS} for a host that cannot import
|
|
14
|
+
* TypeScript at all.
|
|
15
|
+
*
|
|
16
|
+
* **It names no adapter, no backend and no transport.** `@jxsuite/studio` may contain PAL adapters
|
|
17
|
+
* — `platforms/devserver.ts` and `platforms/cloud.ts` both ship, the latter because it owns the
|
|
18
|
+
* collab client's `Y.Doc` and a second bundled `yjs` breaks cross-module `instanceof` — but it must
|
|
19
|
+
* not depend on a backend PACKAGE. `scripts/check-dep-rules.ts` cannot catch a violation (it
|
|
20
|
+
* forbids only core-to-extension edges, and server and studio are both core), so
|
|
21
|
+
* `scripts/check-studio-package.ts` does.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/** What a manifest entry is, which decides how a host must treat it. */
|
|
25
|
+
export type StudioAssetKind =
|
|
26
|
+
| "document"
|
|
27
|
+
| "entry"
|
|
28
|
+
| "style"
|
|
29
|
+
| "chunk"
|
|
30
|
+
| "worker"
|
|
31
|
+
| "font"
|
|
32
|
+
| "asset";
|
|
33
|
+
|
|
34
|
+
export interface StudioAsset {
|
|
35
|
+
/** POSIX, relative to the package root. */
|
|
36
|
+
readonly path: string;
|
|
37
|
+
readonly kind: StudioAssetKind;
|
|
38
|
+
/** A DIRECTORY copied wholesale; the names inside it are part of the contract. */
|
|
39
|
+
readonly dir: boolean;
|
|
40
|
+
/** Absence is a boot failure, not a degradation. */
|
|
41
|
+
readonly required: boolean;
|
|
42
|
+
/** Printed verbatim when it is missing, so the error explains itself. */
|
|
43
|
+
readonly why: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Everything a host serves.
|
|
48
|
+
*
|
|
49
|
+
* `required: false` means a host may leave it out and get a working editor with something switched
|
|
50
|
+
* off; everything else is load-bearing. The `why` is not decoration — it is what a staging error
|
|
51
|
+
* prints, and the difference between "a file is missing" and "the code view will silently have no
|
|
52
|
+
* schema validation".
|
|
53
|
+
*/
|
|
54
|
+
export const STUDIO_ASSETS: readonly StudioAsset[] = [
|
|
55
|
+
{
|
|
56
|
+
dir: false,
|
|
57
|
+
kind: "document",
|
|
58
|
+
path: "index.html",
|
|
59
|
+
required: true,
|
|
60
|
+
why: "the editor document. Generated from studioShellHtml(); a host serving the shell at its own path should render its own rather than copy this one.",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
dir: false,
|
|
64
|
+
kind: "document",
|
|
65
|
+
path: "canvas.html",
|
|
66
|
+
required: true,
|
|
67
|
+
why: "the canvas iframe document. Without it every canvas frame 404s at boot.",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
dir: false,
|
|
71
|
+
kind: "entry",
|
|
72
|
+
path: "dist/studio.js",
|
|
73
|
+
required: true,
|
|
74
|
+
why: "the editor bundle. Its path is a contract (studio.md 11.1) and is never hashed, because it anchors every other url the bundle resolves.",
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
dir: false,
|
|
78
|
+
kind: "entry",
|
|
79
|
+
path: "dist/iframe-entry.js",
|
|
80
|
+
required: true,
|
|
81
|
+
why: "the canvas bundle, loaded by canvas.html. Same unhashed-path contract.",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
dir: false,
|
|
85
|
+
kind: "style",
|
|
86
|
+
path: "dist/studio.css",
|
|
87
|
+
required: true,
|
|
88
|
+
why: "the bundle's own stylesheet — Monaco's tokens and Tabulator's theme. Distinct from styles/, which is hand-authored.",
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
dir: false,
|
|
92
|
+
kind: "asset",
|
|
93
|
+
path: "dist/codicon.ttf",
|
|
94
|
+
required: true,
|
|
95
|
+
why: "Monaco's icon font, referenced by dist/studio.css and by three chunk stylesheets. It shipped in no tarball and no copy list for months, so every host drew tofu where Monaco draws icons.",
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
dir: false,
|
|
99
|
+
kind: "asset",
|
|
100
|
+
path: "dist/manifest.json",
|
|
101
|
+
required: false,
|
|
102
|
+
why: "this manifest as data, for a host that cannot import TypeScript — a Nix derivation, a shell script, a build in another language. Not required: a host that CAN import it has the same facts with types on them.",
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
dir: true,
|
|
106
|
+
kind: "chunk",
|
|
107
|
+
path: "dist/chunks",
|
|
108
|
+
required: true,
|
|
109
|
+
why: "the split chunks — Monaco, yjs, ajv and every on-demand import. The entry reaches them by url relative to itself, so the directory ships wholesale with its emitted names intact. A bundle that cannot find them dies at boot with a bare module-resolution error.",
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
dir: true,
|
|
113
|
+
kind: "worker",
|
|
114
|
+
path: "dist/workers",
|
|
115
|
+
required: true,
|
|
116
|
+
why: "Monaco's pre-bundled web workers. Missing, they do not 404 loudly: the code view simply has no JSON language service, so no schema validation, no completion and no hover, with nothing in the console.",
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
dir: true,
|
|
120
|
+
kind: "style",
|
|
121
|
+
path: "styles",
|
|
122
|
+
required: true,
|
|
123
|
+
why: "the hand-authored chrome stylesheet, linked by the document in cascade order. No bundler sees it. Missing, the editor boots with no tokens, no grid and no panel chrome.",
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
dir: true,
|
|
127
|
+
kind: "font",
|
|
128
|
+
path: "fonts",
|
|
129
|
+
required: true,
|
|
130
|
+
why: "the vendored JetBrains Mono faces, reached from styles/tokens.css by ../fonts/. They stay a directory rather than being bundled: Bun inlines a woff2 under its size threshold as base64, which would put about 85 KB into a render-blocking stylesheet.",
|
|
131
|
+
},
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
/** Never staged and never published, whatever the entries above would otherwise match. */
|
|
135
|
+
export const STUDIO_ASSET_EXCLUDE: readonly string[] = ["**/*.map"];
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Published beyond the served tree: the source graph the `exports` map points at, and the data it
|
|
139
|
+
* reads. `data/` is here because six modules import it — `src/studio.ts` among them — so without it
|
|
140
|
+
* the package's own `.` export does not resolve from a tarball.
|
|
141
|
+
*/
|
|
142
|
+
export const PUBLISHED_EXTRAS: readonly string[] = ["src", "data"];
|
|
143
|
+
|
|
144
|
+
export const STUDIO_SHELL = "index.html";
|
|
145
|
+
export const STUDIO_CANVAS = "canvas.html";
|
|
146
|
+
export const STUDIO_ENTRY = "dist/studio.js";
|
|
147
|
+
export const STUDIO_IFRAME_ENTRY = "dist/iframe-entry.js";
|
|
148
|
+
export const STUDIO_BUNDLE_CSS = "dist/studio.css";
|
|
149
|
+
|
|
150
|
+
/** Monaco's workers, by the names scripts/build-workers.ts emits and monaco-setup.ts asks for. */
|
|
151
|
+
export const STUDIO_WORKERS: readonly string[] = [
|
|
152
|
+
"editor.worker.js",
|
|
153
|
+
"json.worker.js",
|
|
154
|
+
"ts.worker.js",
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The chrome stylesheets, IN CASCADE ORDER.
|
|
159
|
+
*
|
|
160
|
+
* Order is behaviour, not presentation: `forced-colors.css` is last because it redraws what Windows
|
|
161
|
+
* High Contrast deletes. This list is what the document's link tags are generated from, so a
|
|
162
|
+
* stylesheet added to `styles/` and not added here is simply never loaded — which is the 2.1.0
|
|
163
|
+
* outage exactly, where the cloud shipped seven dead link tags and the build exited 0.
|
|
164
|
+
* `scripts/check-studio-package.ts` holds it equal to `styles/*.css` on disk.
|
|
165
|
+
*/
|
|
166
|
+
export const STUDIO_STYLESHEETS: readonly string[] = [
|
|
167
|
+
"styles/tokens.css",
|
|
168
|
+
"styles/shell.css",
|
|
169
|
+
"styles/canvas.css",
|
|
170
|
+
"styles/panels.css",
|
|
171
|
+
"styles/inspector.css",
|
|
172
|
+
"styles/overlays.css",
|
|
173
|
+
"styles/forced-colors.css",
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
/** Bump when a host-visible export changes shape. A consumer may assert against it. */
|
|
177
|
+
export const STUDIO_HOST_API = 1;
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* How a host lays the tree out.
|
|
181
|
+
*
|
|
182
|
+
* `nested` keeps the package's own shape. `flat` hoists the contents of `dist/` up one level, which
|
|
183
|
+
* is what jx-platform's `public/` has always done.
|
|
184
|
+
*/
|
|
185
|
+
export type StudioLayoutMode = "nested" | "flat";
|
|
186
|
+
|
|
187
|
+
export interface AssetBase {
|
|
188
|
+
/** URL prefix the tree is served under, as the BROWSER sees it. Non-empty values end in "/". */
|
|
189
|
+
readonly prefix: string;
|
|
190
|
+
readonly mode: StudioLayoutMode;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Map a package-relative path to the url a host serving under `base` answers.
|
|
195
|
+
*
|
|
196
|
+
* `flat` strips exactly ONE leading `dist/` segment and nothing else, and that single rule is what
|
|
197
|
+
* makes flattening a contract rather than a rewrite. Every reference that lives inside `dist/` is
|
|
198
|
+
* dist-relative — the entry reaches `./chunks/`, `dist/studio.css` reaches `./codicon.ttf`, a chunk
|
|
199
|
+
* stylesheet reaches `../codicon.ttf` — so stripping one segment uniformly moves all of them
|
|
200
|
+
* together and none of them breaks. `styles/` and `fonts/` are untouched in both modes, which is
|
|
201
|
+
* why `tokens.css`'s `url("../fonts/…")` holds either way.
|
|
202
|
+
*
|
|
203
|
+
* @param base Where the tree is mounted.
|
|
204
|
+
* @param packagePath A path from {@link STUDIO_ASSETS}, or a file inside one of its directories.
|
|
205
|
+
* @returns The url, with `base.prefix` applied.
|
|
206
|
+
*/
|
|
207
|
+
export function assetUrl(base: AssetBase, packagePath: string): string {
|
|
208
|
+
const path = packagePath.replace(/^\.?\//, "");
|
|
209
|
+
const laid = base.mode === "flat" ? path.replace(/^dist\//, "") : path;
|
|
210
|
+
return `${base.prefix}${laid}`;
|
|
211
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copy the studio tree somewhere a host can serve it.
|
|
3
|
+
*
|
|
4
|
+
* CONVENIENCE, not contract. Everything a subscriber strictly needs is in `./layout` (the manifest
|
|
5
|
+
* and {@link assetUrl}) and `./document` (the two generators), both of which are pure and run
|
|
6
|
+
* anywhere. This module exists so the two Bun consumers — the desktop's asset staging and
|
|
7
|
+
* jx-platform's `public/` build — do not each write the same copier, and it is the ONLY module
|
|
8
|
+
* under `src/` that imports `node:`. A host in another runtime reads the manifest and moves the
|
|
9
|
+
* bytes however it likes.
|
|
10
|
+
*
|
|
11
|
+
* @docs extending/embedding/hosting
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { cp, mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
15
|
+
import { existsSync } from "node:fs";
|
|
16
|
+
import { dirname, join, resolve } from "node:path";
|
|
17
|
+
import { Glob } from "bun";
|
|
18
|
+
import { STUDIO_ASSETS, STUDIO_CANVAS, STUDIO_SHELL } from "./layout";
|
|
19
|
+
import type { AssetBase, StudioAsset, StudioAssetKind, StudioLayoutMode } from "./layout";
|
|
20
|
+
import { canvasShellHtml } from "./document";
|
|
21
|
+
import type { DocumentOptions } from "./document";
|
|
22
|
+
|
|
23
|
+
/** The installed package root, resolved off this module rather than off a caller's cwd. */
|
|
24
|
+
export const STUDIO_PACKAGE_DIR = resolve(import.meta.dir, "..", "..");
|
|
25
|
+
|
|
26
|
+
export interface StageOptions {
|
|
27
|
+
/** Package root to read from — an installed copy, or a `--link` checkout. */
|
|
28
|
+
readonly from?: string | undefined;
|
|
29
|
+
/** Kinds to leave out. The cloud passes `["document"]`; see the note on {@link stageStudioAssets}. */
|
|
30
|
+
readonly exclude?: readonly StudioAssetKind[] | undefined;
|
|
31
|
+
/** Copy `*.map` too. Default false: the chunk maps alone are about 24 MB. */
|
|
32
|
+
readonly sourceMaps?: boolean | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Remove the manifest's own paths under `destDir` first. Default true.
|
|
35
|
+
*
|
|
36
|
+
* Only those paths, never the whole directory. `packages/desktop/scripts/pre-build.ts` writes its
|
|
37
|
+
* launcher's PAL-init bundle to `assets/studio/dist/init.js` BEFORE staging, so a blanket wipe
|
|
38
|
+
* deletes it and the packaged app boots with no platform registered. Not cleaning at all is
|
|
39
|
+
* equally wrong: content-hashed chunks would accumulate in a staged tree forever.
|
|
40
|
+
*/
|
|
41
|
+
readonly clean?: boolean | undefined;
|
|
42
|
+
/** Layout to report in {@link StageResult.base}. Default `"nested"`. */
|
|
43
|
+
readonly layout?: StudioLayoutMode | undefined;
|
|
44
|
+
/** Url prefix to report in {@link StageResult.base}. Default `"./"`. */
|
|
45
|
+
readonly prefix?: string | undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface StageResult {
|
|
49
|
+
/**
|
|
50
|
+
* The base the tree was staged at — hand it straight to {@link studioShellHtml}, so the stager and
|
|
51
|
+
* the document cannot disagree about where the files went.
|
|
52
|
+
*/
|
|
53
|
+
readonly base: AssetBase;
|
|
54
|
+
/** Package-relative paths written, in manifest order. */
|
|
55
|
+
readonly written: readonly string[];
|
|
56
|
+
readonly bytes: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Manifest entries absent from `root`. Empty means the tree is complete. */
|
|
60
|
+
export function missingStudioAssets(root: string = STUDIO_PACKAGE_DIR): readonly StudioAsset[] {
|
|
61
|
+
return STUDIO_ASSETS.filter((a) => a.required && !existsSync(join(root, a.path)));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Where a package-relative path lands under `destDir`, honouring the layout. */
|
|
65
|
+
function destOf(destDir: string, path: string, layout: StudioLayoutMode): string {
|
|
66
|
+
return join(destDir, layout === "flat" ? path.replace(/^dist\//, "") : path);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Copy the tree into `destDir`.
|
|
71
|
+
*
|
|
72
|
+
* @throws {Error} Naming the entry AND its `why` when a required one is missing — a staging failure
|
|
73
|
+
* should say what the reader will lose, not just which file was absent.
|
|
74
|
+
*/
|
|
75
|
+
export async function stageStudioAssets(
|
|
76
|
+
destDir: string,
|
|
77
|
+
options: StageOptions = {},
|
|
78
|
+
): Promise<StageResult> {
|
|
79
|
+
const from = options.from ?? STUDIO_PACKAGE_DIR;
|
|
80
|
+
const layout = options.layout ?? "nested";
|
|
81
|
+
const exclude = new Set(options.exclude);
|
|
82
|
+
const entries = STUDIO_ASSETS.filter((a) => !exclude.has(a.kind));
|
|
83
|
+
|
|
84
|
+
const absent = entries.filter((a) => a.required && !existsSync(join(from, a.path)));
|
|
85
|
+
if (absent.length > 0) {
|
|
86
|
+
const listed = absent.map((a) => ` ${a.path} — ${a.why}`).join("\n");
|
|
87
|
+
throw new Error(
|
|
88
|
+
`@jxsuite/studio is missing ${absent.length} required asset(s) at ${from}:\n${listed}\n` +
|
|
89
|
+
`Run \`bun run build\` in packages/studio, or check the package's published files list.`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (options.clean !== false) {
|
|
94
|
+
for (const a of entries) {
|
|
95
|
+
await rm(destOf(destDir, a.path, layout), { force: true, recursive: true });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const written: string[] = [];
|
|
100
|
+
let bytes = 0;
|
|
101
|
+
const skipMap = (p: string) => !options.sourceMaps && p.endsWith(".map");
|
|
102
|
+
|
|
103
|
+
for (const a of entries) {
|
|
104
|
+
const src = join(from, a.path);
|
|
105
|
+
if (!existsSync(src)) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const dest = destOf(destDir, a.path, layout);
|
|
109
|
+
if (a.dir) {
|
|
110
|
+
await mkdir(dest, { recursive: true });
|
|
111
|
+
for (const rel of new Glob("**/*").scanSync(src)) {
|
|
112
|
+
const posix = rel.replaceAll("\\", "/");
|
|
113
|
+
if (skipMap(posix)) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const file = join(src, rel);
|
|
117
|
+
const to = join(dest, rel);
|
|
118
|
+
await mkdir(dirname(to), { recursive: true });
|
|
119
|
+
await cp(file, to);
|
|
120
|
+
written.push(`${a.path}/${posix}`);
|
|
121
|
+
const info = await stat(file);
|
|
122
|
+
bytes += info.size;
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
if (skipMap(a.path)) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
129
|
+
await cp(src, dest);
|
|
130
|
+
written.push(a.path);
|
|
131
|
+
const info = await stat(src);
|
|
132
|
+
bytes += info.size;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return { base: { mode: layout, prefix: options.prefix ?? "./" }, bytes, written };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface ReadDocumentOptions extends DocumentOptions {
|
|
140
|
+
/** Package root to read `canvas.html` from. */
|
|
141
|
+
readonly from?: string | undefined;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* There is no shellDocument() here. The editor document needs no file read, so it would be a
|
|
145
|
+
re-export of studioShellHtml under a second name — a host imports `./hosting/document` for it,
|
|
146
|
+
which is also the entry that stays pure. */
|
|
147
|
+
|
|
148
|
+
/** The canvas document, read from the package and rebased. */
|
|
149
|
+
export async function canvasDocument(options: ReadDocumentOptions = {}): Promise<string> {
|
|
150
|
+
const from = options.from ?? STUDIO_PACKAGE_DIR;
|
|
151
|
+
const html = await readFile(join(from, STUDIO_CANVAS), "utf8");
|
|
152
|
+
return canvasShellHtml(html, options.base);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Write `dist/manifest.json` — {@link STUDIO_ASSETS} as data, for a host that cannot import
|
|
157
|
+
* TypeScript. Called by the build; gated against the manifest by check-studio-dist.ts.
|
|
158
|
+
*/
|
|
159
|
+
export async function writeAssetManifest(root: string = STUDIO_PACKAGE_DIR): Promise<void> {
|
|
160
|
+
const out = join(root, "dist", "manifest.json");
|
|
161
|
+
await mkdir(dirname(out), { recursive: true });
|
|
162
|
+
await writeFile(
|
|
163
|
+
out,
|
|
164
|
+
`${JSON.stringify({ assets: STUDIO_ASSETS, shell: STUDIO_SHELL }, null, 2)}\n`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"//": "The hosting contract is compiled under a CONSUMER's flags, not this repo's.",
|
|
3
|
+
"//why": "These three modules are published for other repositories to import, and jx-platform — the first real subscriber — sets exactOptionalPropertyTypes, verbatimModuleSyntax and isolatedModules, none of which the jx root tsconfig sets. Two type errors were found by consuming the API rather than by building it: `from: string | undefined` did not satisfy `from?: string` under exactOptionalPropertyTypes. Compiling this directory under the stricter set means the next one is found here instead of after a release. `types: []` and a bare lib are the other half: layout.ts and document.ts must not depend on node or DOM globals, because a Worker build, a Vite plugin or a Deno host has to be able to read them.",
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"strict": true,
|
|
6
|
+
"exactOptionalPropertyTypes": true,
|
|
7
|
+
"verbatimModuleSyntax": true,
|
|
8
|
+
"isolatedModules": true,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"module": "preserve",
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"target": "ES2023",
|
|
14
|
+
"lib": ["ES2023"],
|
|
15
|
+
"types": ["bun"]
|
|
16
|
+
},
|
|
17
|
+
"include": ["layout.ts", "document.ts"]
|
|
18
|
+
}
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
34
|
import { html, nothing } from "lit-html";
|
|
35
|
+
import { repeat } from "lit-html/directives/repeat.js";
|
|
35
36
|
import type { TemplateResult } from "lit-html";
|
|
36
37
|
import { ref } from "lit-html/directives/ref.js";
|
|
37
38
|
import type { Message, ToolCallRecord } from "@jxsuite/ai/chat-state";
|
|
@@ -443,21 +444,31 @@ export function renderMessageList(opts: MessageListOptions): TemplateResult {
|
|
|
443
444
|
`
|
|
444
445
|
: nothing
|
|
445
446
|
}
|
|
446
|
-
${
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
447
|
+
${repeat(
|
|
448
|
+
messages,
|
|
449
|
+
/* Keyed on msg.id, for two reasons a reader can see. The last assistant row swaps between
|
|
450
|
+
renderStreamingTail and renderAssistantMessage the moment a stream completes, so an
|
|
451
|
+
unkeyed list tears down and rebuilds the longest node in the transcript every time one
|
|
452
|
+
finishes. And an assistant row holds the reader's OWN open/closed state on its
|
|
453
|
+
ai-msg-changes details element, which position-based reuse hands to a different
|
|
454
|
+
message. */
|
|
455
|
+
(msg) => msg.id,
|
|
456
|
+
(msg, i) => {
|
|
457
|
+
if (msg.role === "user") {
|
|
458
|
+
return renderUserMessage(msg);
|
|
456
459
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
460
|
+
if (msg.role === "tool") {
|
|
461
|
+
return renderToolMessage(msg);
|
|
462
|
+
}
|
|
463
|
+
if (msg.role === "assistant") {
|
|
464
|
+
if (status === "streaming" && i === lastIdx) {
|
|
465
|
+
return renderStreamingTail(msg);
|
|
466
|
+
}
|
|
467
|
+
return renderAssistantMessage(msg, opts.onRestore);
|
|
468
|
+
}
|
|
469
|
+
return nothing;
|
|
470
|
+
},
|
|
471
|
+
)}
|
|
461
472
|
${
|
|
462
473
|
status !== "streaming" && opts.error
|
|
463
474
|
? html`
|
package/src/panels/git-panel.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { repeat } from "lit-html/directives/repeat.js";
|
|
|
13
13
|
import { getPlatform } from "../platform";
|
|
14
14
|
import { now } from "../services/clock";
|
|
15
15
|
import { formatForPath } from "../format/format-host";
|
|
16
|
-
import { projectState } from "../store";
|
|
16
|
+
import { projectState, renderOnly } from "../store";
|
|
17
17
|
import { activeTab } from "../workspace/workspace";
|
|
18
18
|
import type { Tab } from "../tabs/tab";
|
|
19
19
|
import { shell } from "../shell";
|
|
@@ -245,6 +245,16 @@ async function doPull() {
|
|
|
245
245
|
*/
|
|
246
246
|
let _pollTimer = null as ReturnType<typeof setInterval> | null;
|
|
247
247
|
|
|
248
|
+
/**
|
|
249
|
+
* Whether the commit button's split menu is showing.
|
|
250
|
+
*
|
|
251
|
+
* State rather than a `hidden` attribute toggled on a node found by selector. The two were fighting
|
|
252
|
+
* by construction: the template declared the menu `hidden` unconditionally, so lit had committed
|
|
253
|
+
* that attribute, and every repaint of the panel — the 30-second git poll among them — would leave
|
|
254
|
+
* whatever the handler had last done to it standing, with no way for the template to take it back.
|
|
255
|
+
*/
|
|
256
|
+
let _splitMenuOpen = false;
|
|
257
|
+
|
|
248
258
|
async function fetchGitLog() {
|
|
249
259
|
const plat = getPlatform();
|
|
250
260
|
try {
|
|
@@ -583,25 +593,18 @@ export function renderGitPanel(ctx: {
|
|
|
583
593
|
<sp-action-button
|
|
584
594
|
class="git-split-trigger"
|
|
585
595
|
size="s"
|
|
586
|
-
@click=${(
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
).querySelector(".git-split-menu");
|
|
590
|
-
if (menu) {
|
|
591
|
-
menu.toggleAttribute("hidden");
|
|
592
|
-
}
|
|
596
|
+
@click=${() => {
|
|
597
|
+
_splitMenuOpen = !_splitMenuOpen;
|
|
598
|
+
renderOnly("leftPanel");
|
|
593
599
|
}}
|
|
594
600
|
>
|
|
595
601
|
<sp-icon-chevron-down slot="icon" size="xs"></sp-icon-chevron-down>
|
|
596
602
|
</sp-action-button>
|
|
597
|
-
<div class="git-split-menu" hidden>
|
|
603
|
+
<div class="git-split-menu" ?hidden=${!_splitMenuOpen}>
|
|
598
604
|
<button
|
|
599
605
|
class="git-split-menu-item"
|
|
600
|
-
@click=${(
|
|
601
|
-
|
|
602
|
-
"hidden",
|
|
603
|
-
"",
|
|
604
|
-
);
|
|
606
|
+
@click=${() => {
|
|
607
|
+
_splitMenuOpen = false;
|
|
605
608
|
void doCommit();
|
|
606
609
|
}}
|
|
607
610
|
>
|
package/src/panels/head-panel.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { html, nothing } from "lit-html";
|
|
10
|
+
import { createRef, ref } from "lit-html/directives/ref.js";
|
|
10
11
|
import { renderFieldRow } from "../ui/field-row";
|
|
11
12
|
import { spTextArea, spTextField } from "../ui/field-input";
|
|
12
13
|
import { renderMediaPicker } from "../ui/media-picker";
|
|
@@ -28,6 +29,18 @@ import type { JxHeadEntry, JxMutableNode } from "@jxsuite/schema/types";
|
|
|
28
29
|
import type { Tab } from "../tabs/tab";
|
|
29
30
|
import type { TemplateResult } from "lit-html";
|
|
30
31
|
|
|
32
|
+
/**
|
|
33
|
+
* The add-a-tag draft fields.
|
|
34
|
+
*
|
|
35
|
+
* Uncontrolled by design — the draft is not state anything else reads, and rendering it back on
|
|
36
|
+
* every keystroke would fight the typist. Held as refs rather than re-found by selector: the row is
|
|
37
|
+
* rebuilt on every Head repaint, so a node resolved once is detached by the time it is used, and
|
|
38
|
+
* with a second pane open a document-wide query can return the other pane's field.
|
|
39
|
+
*/
|
|
40
|
+
const _addTag = createRef<HTMLInputElement>();
|
|
41
|
+
const _addAttr = createRef<HTMLInputElement>();
|
|
42
|
+
const _addVal = createRef<HTMLInputElement>();
|
|
43
|
+
|
|
31
44
|
interface MetaField {
|
|
32
45
|
label: string;
|
|
33
46
|
attr: "name" | "property";
|
|
@@ -982,7 +995,7 @@ export function renderHeadTemplate({
|
|
|
982
995
|
|
|
983
996
|
<!-- Add custom tag form -->
|
|
984
997
|
<div class="head-add-form">
|
|
985
|
-
<sp-picker size="s" label="Tag" class="head-add-tag"
|
|
998
|
+
<sp-picker size="s" label="Tag" class="head-add-tag" ${ref(_addTag)}>
|
|
986
999
|
<sp-menu-item value="meta">meta</sp-menu-item>
|
|
987
1000
|
<sp-menu-item value="link">link</sp-menu-item>
|
|
988
1001
|
<sp-menu-item value="script">script</sp-menu-item>
|
|
@@ -991,28 +1004,33 @@ export function renderHeadTemplate({
|
|
|
991
1004
|
placeholder="Attribute (e.g. name)"
|
|
992
1005
|
size="s"
|
|
993
1006
|
class="head-add-attr"
|
|
1007
|
+
${ref(_addAttr)}
|
|
1008
|
+
></sp-textfield>
|
|
1009
|
+
<sp-textfield
|
|
1010
|
+
placeholder="Value"
|
|
1011
|
+
size="s"
|
|
1012
|
+
class="head-add-val"
|
|
1013
|
+
${ref(_addVal)}
|
|
994
1014
|
></sp-textfield>
|
|
995
|
-
<sp-textfield placeholder="Value" size="s" class="head-add-val"></sp-textfield>
|
|
996
1015
|
<sp-action-button
|
|
997
1016
|
quiet
|
|
998
1017
|
size="xs"
|
|
999
1018
|
title="Add tag"
|
|
1000
|
-
@click=${(
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
const
|
|
1005
|
-
const
|
|
1006
|
-
const
|
|
1007
|
-
const attrVal = valField?.value?.trim();
|
|
1019
|
+
@click=${() => {
|
|
1020
|
+
/* Handles, not a closest() walk back up to a form this same template drew three lines
|
|
1021
|
+
above. The fields are uncontrolled on purpose — the draft is not state anything
|
|
1022
|
+
else reads — so a ref is exactly what reading and clearing them needs. */
|
|
1023
|
+
const tagName = _addTag.value?.value || "meta";
|
|
1024
|
+
const attrKey = _addAttr.value?.value?.trim();
|
|
1025
|
+
const attrVal = _addVal.value?.value?.trim();
|
|
1008
1026
|
if (!attrKey || !attrVal) {
|
|
1009
1027
|
return;
|
|
1010
1028
|
}
|
|
1011
|
-
if (
|
|
1012
|
-
|
|
1029
|
+
if (_addAttr.value) {
|
|
1030
|
+
_addAttr.value.value = "";
|
|
1013
1031
|
}
|
|
1014
|
-
if (
|
|
1015
|
-
|
|
1032
|
+
if (_addVal.value) {
|
|
1033
|
+
_addVal.value.value = "";
|
|
1016
1034
|
}
|
|
1017
1035
|
|
|
1018
1036
|
const entry: JxHeadEntry = { attributes: {}, tagName };
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { html, nothing } from "lit-html";
|
|
12
|
+
import { createRef, ref } from "lit-html/directives/ref.js";
|
|
12
13
|
import { componentRegistry } from "../files/components";
|
|
13
14
|
import {
|
|
14
15
|
disableElement,
|
|
@@ -30,6 +31,10 @@ import type { ComponentEntry } from "../files/components";
|
|
|
30
31
|
import type { ElementsEntry } from "../files/elements";
|
|
31
32
|
import type { JxElement, JxMutableNode } from "@jxsuite/schema/types";
|
|
32
33
|
|
|
34
|
+
/** The add-an-import draft fields — uncontrolled, held rather than re-found. See head-panel.ts. */
|
|
35
|
+
const _addName = createRef<HTMLInputElement>();
|
|
36
|
+
const _addPath = createRef<HTMLInputElement>();
|
|
37
|
+
|
|
33
38
|
interface ImportsContext {
|
|
34
39
|
renderLeftPanel: () => void;
|
|
35
40
|
documentPath: string | null;
|
|
@@ -142,23 +147,37 @@ function renderSiteLevelImports(renderLeftPanel: () => void) {
|
|
|
142
147
|
})
|
|
143
148
|
}
|
|
144
149
|
<div class="import-add-form">
|
|
145
|
-
<sp-textfield
|
|
146
|
-
|
|
150
|
+
<sp-textfield
|
|
151
|
+
placeholder="Name"
|
|
152
|
+
size="s"
|
|
153
|
+
class="import-add-name"
|
|
154
|
+
${ref(_addName)}
|
|
155
|
+
></sp-textfield>
|
|
156
|
+
<sp-textfield
|
|
157
|
+
placeholder="Path"
|
|
158
|
+
size="s"
|
|
159
|
+
class="import-add-path"
|
|
160
|
+
${ref(_addPath)}
|
|
161
|
+
></sp-textfield>
|
|
147
162
|
<sp-action-button
|
|
148
163
|
quiet
|
|
149
164
|
size="xs"
|
|
150
165
|
title="Add import"
|
|
151
|
-
@click=${async (
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const name =
|
|
156
|
-
const path =
|
|
166
|
+
@click=${async () => {
|
|
167
|
+
/* Handles rather than a closest() walk back to a form drawn six lines above. The
|
|
168
|
+
fields are an uncontrolled draft, so a ref is what reading and clearing them
|
|
169
|
+
wants — and the panel is rebuilt on every repaint, so a node found once is gone. */
|
|
170
|
+
const name = _addName.value?.value?.trim();
|
|
171
|
+
const path = _addPath.value?.value?.trim();
|
|
157
172
|
if (!name || !path) {
|
|
158
173
|
return;
|
|
159
174
|
}
|
|
160
|
-
|
|
161
|
-
|
|
175
|
+
if (_addName.value) {
|
|
176
|
+
_addName.value.value = "";
|
|
177
|
+
}
|
|
178
|
+
if (_addPath.value) {
|
|
179
|
+
_addPath.value.value = "";
|
|
180
|
+
}
|
|
162
181
|
const updated = { ...siteImports, [name]: path };
|
|
163
182
|
await updateSiteConfig({ imports: updated });
|
|
164
183
|
renderLeftPanel();
|
|
@@ -326,10 +345,10 @@ function renderDocumentLevelImports({
|
|
|
326
345
|
|
|
327
346
|
const packageGroups = groupByPackage();
|
|
328
347
|
|
|
329
|
-
/** @param {string} ref */
|
|
330
|
-
const removeRef = (
|
|
348
|
+
/** @param {string} elementRef The element reference to drop. Not `ref` — that is lit's directive. */
|
|
349
|
+
const removeRef = (elementRef: string) => {
|
|
331
350
|
applyMutation((doc: JxMutableNode) => {
|
|
332
|
-
doc.$elements = removeElementRef((doc.$elements ?? []) as ElementsEntry[],
|
|
351
|
+
doc.$elements = removeElementRef((doc.$elements ?? []) as ElementsEntry[], elementRef) as (
|
|
333
352
|
| string
|
|
334
353
|
| JxElement
|
|
335
354
|
)[];
|