@jxsuite/studio 2.2.2 → 2.4.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-jrvn5vwy.js} +13 -12
- package/dist/chunks/{convert-to-repeater-cnpyvj74.js → convert-to-repeater-109dwcj7.js} +12 -11
- package/dist/chunks/{extension-sections-vpcm4jvx.js → extension-sections-2zjyty05.js} +17 -16
- package/dist/chunks/{files-zeg88k2d.js → files-jdbtammm.js} +18 -19
- package/dist/chunks/{format-host-taymsw55.js → format-host-kq6963gs.js} +2 -2
- package/dist/chunks/{git-panel-qp4rrwr9.js → git-panel-2stf3ahg.js} +12 -11
- package/dist/chunks/{layers-panel-kj93w2zg.js → layers-panel-a8wjsdjj.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-2we6smjv.js} +6 -6
- package/dist/chunks/{quick-search-9wwa09hh.js → quick-search-hmpwdgys.js} +19 -18
- package/dist/chunks/right-panel-hn3y3j0j.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-0evba9z5.js} +5 -5
- package/dist/chunks/{studio-qhh8r5fz.js → studio-102jzb8r.js} +4 -4
- package/dist/chunks/{studio-22xr4q8q.js → studio-1dgqagzz.js} +18 -29
- package/dist/chunks/{studio-6f0am8f2.js → studio-2w11grav.js} +2 -2
- package/dist/chunks/{studio-pv8m0h4a.js → studio-377bbhtz.js} +28 -23
- package/dist/chunks/{studio-6zzbyx17.js → studio-53dn6e6t.js} +2 -2
- package/dist/chunks/studio-53n8bp04.js +16 -0
- package/dist/chunks/{studio-tqemhphj.js → studio-6gcrk1bq.js} +4 -4
- package/dist/chunks/{studio-3z8jjm2p.js → studio-7ejemhej.js} +3 -3
- package/dist/chunks/{studio-nqq8jk8f.js → studio-8x4399kc.js} +14 -14
- package/dist/chunks/{studio-rgwcpast.js → studio-ay8wcr2b.js} +2 -2
- package/dist/chunks/{studio-xmvbhg31.js → studio-c31yb4t6.js} +2 -2
- package/dist/chunks/{studio-5kt960pk.js → studio-ddgrc5hs.js} +133 -147
- package/dist/chunks/{studio-zqv3w24n.js → studio-eqyybj2e.js} +5 -5
- package/dist/chunks/{studio-p471gg5n.js → studio-kvfx9z2c.js} +7 -3
- package/dist/chunks/{studio-8sdyjfd6.js → studio-kwavhmdr.js} +2 -2
- package/dist/chunks/{studio-300zsz1k.js → studio-ky68mp85.js} +6 -6
- package/dist/chunks/{studio-x0et2z9g.js → studio-ng5gbxkj.js} +15 -15
- package/dist/chunks/{studio-hekaagev.js → studio-q07vj7p9.js} +17 -17
- package/dist/chunks/{studio-fys6z73y.js → studio-qd4k0h80.js} +7 -7
- package/dist/chunks/{studio-yphy4z9m.js → studio-rdsvb808.js} +5 -5
- package/dist/chunks/{studio-g8t7g0gp.js → studio-tqr02xaa.js} +3 -3
- 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-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-wxthr2x1.js} +14 -13
- package/dist/chunks/{studio-yz9s88s0.js → studio-yk7037sg.js} +40 -14
- package/dist/chunks/{studio-1kgfcv7s.js → studio-yz06mkfj.js} +12 -11
- package/dist/codicon.ttf +0 -0
- package/dist/iframe-entry.js +14 -3
- package/dist/manifest.json +82 -0
- package/dist/studio.js +247 -106
- package/index.html +0 -50
- package/package.json +16 -9
- package/src/canvas/canvas-live-render.ts +2 -4
- package/src/canvas/canvas-origin.ts +25 -0
- 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/platforms/cloud.ts +9 -0
- package/src/services/ai-models.ts +14 -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 +32 -0
- package/src/ui/ai-managed-connect.ts +18 -3
- package/src/ui/regions.ts +10 -45
- package/styles/overlays.css +35 -0
- package/styles/shell.css +7 -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
package/index.html
CHANGED
|
@@ -11,59 +11,9 @@
|
|
|
11
11
|
<link rel="stylesheet" href="./styles/panels.css" />
|
|
12
12
|
<link rel="stylesheet" href="./styles/inspector.css" />
|
|
13
13
|
<link rel="stylesheet" href="./styles/overlays.css" />
|
|
14
|
-
<!-- Last: forced colours redraws what Windows High Contrast deletes. -->
|
|
15
14
|
<link rel="stylesheet" href="./styles/forced-colors.css" />
|
|
16
15
|
</head>
|
|
17
16
|
<body>
|
|
18
|
-
<sp-theme color="dark" scale="medium" system="spectrum">
|
|
19
|
-
<div id="app">
|
|
20
|
-
<div id="toolbar"></div>
|
|
21
|
-
<!-- The PANE GRID (spec §18.1). One cell per pane, each holding that pane's own strip,
|
|
22
|
-
jump bar, chrome layer and stage — the four surfaces that used to be flat siblings of
|
|
23
|
-
THIS grid, which is to say application rows that only ever described one pane. There
|
|
24
|
-
is nothing to see here in markup: `panels/pane-grid.ts` reconciles the cells against
|
|
25
|
-
`workspace.panes`, because how many there are is a fact about the workspace and a
|
|
26
|
-
`<div id>` can only ever be one of them. -->
|
|
27
|
-
<div id="pane-grid"></div>
|
|
28
|
-
<div id="activity-bar"></div>
|
|
29
|
-
<div id="left-panel"></div>
|
|
30
|
-
<div id="resize-left" class="resize-handle"></div>
|
|
31
|
-
<!-- ⑪ The Bottom dock (⌘J). It sits in the PANE's column, under the stage and above the
|
|
32
|
-
status bar, so opening it never narrows the Navigator or the Inspector (spec §12
|
|
33
|
-
region ⑪). Its handle resizes on the other axis; `panels/bottom-dock.ts` stamps
|
|
34
|
-
`dock.bottom` on this host while it is open, and nothing while it is closed. -->
|
|
35
|
-
<div id="resize-bottom" class="resize-handle resize-handle-row"></div>
|
|
36
|
-
<div id="bottom-dock"></div>
|
|
37
|
-
<div id="resize-right" class="resize-handle"></div>
|
|
38
|
-
<!-- The Inspector dock. Four tabs — Content · Style · Logic · Assistant (spec §12 region
|
|
39
|
-
⑨) — all inside this one host. The assistant's fifth grid column is gone. -->
|
|
40
|
-
<div id="right-panel"></div>
|
|
41
|
-
<!-- The app's only status channel: announce what lands in it (spec §12 region ⑫). -->
|
|
42
|
-
<div id="statusbar" role="status" aria-live="polite"></div>
|
|
43
|
-
</div>
|
|
44
|
-
<div
|
|
45
|
-
id="layer-popover"
|
|
46
|
-
style="position: fixed; inset: 0; pointer-events: none; z-index: 1000"
|
|
47
|
-
></div>
|
|
48
|
-
<div
|
|
49
|
-
id="layer-modal"
|
|
50
|
-
style="position: fixed; inset: 0; pointer-events: none; z-index: 2000"
|
|
51
|
-
></div>
|
|
52
|
-
<div
|
|
53
|
-
id="layer-dialog"
|
|
54
|
-
style="position: fixed; inset: 0; pointer-events: none; z-index: 3000"
|
|
55
|
-
></div>
|
|
56
|
-
<!-- The toast host: the fourth overlay layer (spec §12 region ④, §7.1). Above the dialog
|
|
57
|
-
layer, because an operation started from a dialog reports its outcome to the person
|
|
58
|
-
still looking at the dialog. `role="status"` sits on the HOST, so a stack of toasts is
|
|
59
|
-
announced as one live region rather than as one region per notification. -->
|
|
60
|
-
<div
|
|
61
|
-
id="layer-toast"
|
|
62
|
-
role="status"
|
|
63
|
-
aria-live="polite"
|
|
64
|
-
style="position: fixed; inset: 0; pointer-events: none; z-index: 4000"
|
|
65
|
-
></div>
|
|
66
|
-
</sp-theme>
|
|
67
17
|
<script type="module" src="./dist/studio.js"></script>
|
|
68
18
|
</body>
|
|
69
19
|
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/studio",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Jx Studio — visual builder for Jx documents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -9,12 +9,15 @@
|
|
|
9
9
|
"directory": "packages/studio"
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
|
-
"*.js",
|
|
13
12
|
"src",
|
|
13
|
+
"data",
|
|
14
14
|
"dist/studio.js",
|
|
15
15
|
"dist/studio.css",
|
|
16
16
|
"dist/iframe-entry.js",
|
|
17
|
-
"dist/
|
|
17
|
+
"dist/codicon.ttf",
|
|
18
|
+
"dist/manifest.json",
|
|
19
|
+
"dist/chunks/*.js",
|
|
20
|
+
"dist/chunks/*.css",
|
|
18
21
|
"dist/workers",
|
|
19
22
|
"index.html",
|
|
20
23
|
"canvas.html",
|
|
@@ -30,6 +33,10 @@
|
|
|
30
33
|
"./directory-picker": "./src/services/directory-picker.ts",
|
|
31
34
|
"./import-client": "./src/services/import-client.ts",
|
|
32
35
|
"./github-auth": "./src/github/github-auth.ts",
|
|
36
|
+
"./hosting": "./src/hosting/stage.ts",
|
|
37
|
+
"./hosting/layout": "./src/hosting/layout.ts",
|
|
38
|
+
"./hosting/document": "./src/hosting/document.ts",
|
|
39
|
+
"./package.json": "./package.json",
|
|
33
40
|
"./platforms/cloud": "./src/platforms/cloud.ts",
|
|
34
41
|
"./preview-navigate": "./src/canvas/preview-navigate.ts"
|
|
35
42
|
},
|
|
@@ -38,7 +45,7 @@
|
|
|
38
45
|
},
|
|
39
46
|
"scripts": {
|
|
40
47
|
"build": "bun run scripts/build.ts",
|
|
41
|
-
"build:metafile": "bun build ./src/studio.ts --outdir dist --target browser --sourcemap=linked --metafile=metafile.json",
|
|
48
|
+
"build:metafile": "bun build ./src/studio.ts --outdir dist-metafile --target browser --sourcemap=linked --metafile=metafile.json",
|
|
42
49
|
"gen:webdata": "bun run scripts/gen-webdata.js",
|
|
43
50
|
"eval": "bun run evals/cli.ts",
|
|
44
51
|
"eval:headless": "bun run tests/harness/run-eval.ts",
|
|
@@ -50,13 +57,13 @@
|
|
|
50
57
|
"dependencies": {
|
|
51
58
|
"@atlaskit/pragmatic-drag-and-drop": "^3.0.0",
|
|
52
59
|
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^2.1.0",
|
|
53
|
-
"@jxsuite/ai": "^0.36.
|
|
60
|
+
"@jxsuite/ai": "^0.36.2",
|
|
54
61
|
"@jxsuite/collab": "^0.8.1",
|
|
55
|
-
"@jxsuite/create": "^1.3.
|
|
56
|
-
"@jxsuite/formulas": "^0.0.
|
|
62
|
+
"@jxsuite/create": "^1.3.4",
|
|
63
|
+
"@jxsuite/formulas": "^0.0.10",
|
|
57
64
|
"@jxsuite/markup": "^0.4.5",
|
|
58
|
-
"@jxsuite/protocol": "^1.
|
|
59
|
-
"@jxsuite/runtime": "^2.0.
|
|
65
|
+
"@jxsuite/protocol": "^1.2.0",
|
|
66
|
+
"@jxsuite/runtime": "^2.0.3",
|
|
60
67
|
"@jxsuite/schema": "^1.8.0",
|
|
61
68
|
"@spectrum-web-components/accordion": "^1.12.2",
|
|
62
69
|
"@spectrum-web-components/action-bar": "1.12.2",
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
getEffectiveMedia,
|
|
20
20
|
resolveLayoutDoc,
|
|
21
21
|
} from "../site-context";
|
|
22
|
-
import {
|
|
22
|
+
import { documentBase } from "./canvas-origin";
|
|
23
23
|
import { componentRegistry, computeRelativePath } from "../files/components";
|
|
24
24
|
import { prepareForEditMode } from "../utils/edit-display";
|
|
25
25
|
import {
|
|
@@ -179,10 +179,8 @@ export async function resolveCanvasDocument(
|
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
const root = projectState?.projectRoot || "";
|
|
183
|
-
const docPrefix = root ? `${root}/` : "";
|
|
184
182
|
const docBase = S.documentPath
|
|
185
|
-
?
|
|
183
|
+
? new URL(S.documentPath, documentBase(projectState?.projectRoot)).href
|
|
186
184
|
: undefined;
|
|
187
185
|
|
|
188
186
|
// Substitute chosen dynamic route params ({$ref: "#/$params/x"} → literal), inject state.$page,
|
|
@@ -25,6 +25,31 @@ export function canvasBaseOrigin(): string {
|
|
|
25
25
|
return location.origin;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* The base the canvas fetches PROJECT FILES against — component `$ref`s, `$src` modules, images.
|
|
30
|
+
*
|
|
31
|
+
* Two shapes, and the platform chooses. Most hosts serve the project tree from their web root, so
|
|
32
|
+
* `<canvas origin>/<projectRoot>/` is a real URL: the dev server serves by path (absolute
|
|
33
|
+
* filesystem paths included) and the desktop loopback does the same. Those set nothing.
|
|
34
|
+
*
|
|
35
|
+
* A host whose `projectRoot` is an IDENTIFIER rather than a served path sets `documentBaseUrl`. Jx
|
|
36
|
+
* Cloud is one — its root is `owner/repo@branch`, and the default produced a URL nothing answered,
|
|
37
|
+
* so every `$ref` fetch fell through to the SPA fallback. That fallback returns the marketing page
|
|
38
|
+
* at **HTTP 200**, so `res.ok` passed and the runtime's `res.json()` died on `Unexpected token
|
|
39
|
+
* '<'`; images failed the same way and reported nothing.
|
|
40
|
+
*
|
|
41
|
+
* @param {string | undefined} projectRoot - The active project root, for the default shape.
|
|
42
|
+
* @returns {string} A base URL ending in `/`, safe to pass to `new URL(relativePath, base)`.
|
|
43
|
+
*/
|
|
44
|
+
export function documentBase(projectRoot?: string): string {
|
|
45
|
+
const declared = hasPlatform() ? getPlatform().documentBaseUrl : undefined;
|
|
46
|
+
if (declared) {
|
|
47
|
+
return declared.endsWith("/") ? declared : `${declared}/`;
|
|
48
|
+
}
|
|
49
|
+
const root = projectRoot || "";
|
|
50
|
+
return `${canvasBaseOrigin()}/${root ? `${root}/` : ""}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
28
53
|
/**
|
|
29
54
|
* Build an absolute loopback-origin src for a PROJECT asset path referenced by a PARENT-realm
|
|
30
55
|
* (shell) preview <img>. On the views:// shell a relative "/images/foo.png" resolves to
|
|
@@ -56,6 +56,11 @@ import type {
|
|
|
56
56
|
} from "./iframe-protocol";
|
|
57
57
|
import type { JxDocument, JxMutableNode, JxStyle } from "@jxsuite/schema/types";
|
|
58
58
|
import type { IframeRenderCtx, RenderHandle } from "./iframe-render";
|
|
59
|
+
import { setBundleBase } from "../services/bundle-base";
|
|
60
|
+
|
|
61
|
+
/* Anchor shipped-asset URLs to this ENTRY's directory — see src/studio.ts for why only an
|
|
62
|
+
entry may, and tests/entry-anchors.test.ts for the guard. */
|
|
63
|
+
setBundleBase(import.meta.url);
|
|
59
64
|
|
|
60
65
|
/**
|
|
61
66
|
* Resolve the drop placement for a forwarded cursor: point hit-test → nearest `[data-jx-path]` →
|
|
@@ -67,6 +67,7 @@ import type { OverlayLayer, OverlayPlacement } from "./iframe-overlay";
|
|
|
67
67
|
import type { SlashCommand } from "../editor/inline-edit";
|
|
68
68
|
import type { Tab } from "../tabs/tab";
|
|
69
69
|
import type { JxExpressionNode, JxMutableNode } from "@jxsuite/schema/types";
|
|
70
|
+
import { bundleUrl } from "../services/bundle-base";
|
|
70
71
|
|
|
71
72
|
/** A rect in PARENT coordinates (overlay-local from {@link canvasRectToParent}, same field shape). */
|
|
72
73
|
type ParentRect = OverlayPlacement;
|
|
@@ -1473,10 +1474,27 @@ function requestStylebookSelection(host: HostState): void {
|
|
|
1473
1474
|
}
|
|
1474
1475
|
|
|
1475
1476
|
/**
|
|
1476
|
-
* The
|
|
1477
|
-
*
|
|
1477
|
+
* The canvas document, beside the bundle that asked for it.
|
|
1478
|
+
*
|
|
1479
|
+
* This was the literal `/packages/studio/canvas.html` — a repo dev-server path baked into a browser
|
|
1480
|
+
* bundle, correct on exactly one host out of four. It happened to work because every other host
|
|
1481
|
+
* overrides `canvasUrl`: the chromium launcher builds one from its token, electrobun fetches one
|
|
1482
|
+
* over RPC, and the cloud adapter hard-codes its own. So the default was never the default; it was
|
|
1483
|
+
* the dev server's URL with nothing saying so.
|
|
1484
|
+
*
|
|
1485
|
+
* `bundleUrl` resolves it against the ENTRY's directory, which is the one fact every host agrees on
|
|
1486
|
+
* (see services/bundle-base.ts), so the fallback is now correct everywhere rather than accidentally
|
|
1487
|
+
* unused.
|
|
1478
1488
|
*/
|
|
1479
|
-
|
|
1489
|
+
function defaultCanvasUrl(): string {
|
|
1490
|
+
return bundleUrl("../canvas.html");
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
/**
|
|
1494
|
+
* Loads nothing, deliberately: what a platform that resolves its canvasUrl LATER gets in the
|
|
1495
|
+
* meantime. See {@link StudioPlatform.canvasUrlDeferred}.
|
|
1496
|
+
*/
|
|
1497
|
+
const DEFERRED_CANVAS_URL = "about:blank";
|
|
1480
1498
|
|
|
1481
1499
|
/**
|
|
1482
1500
|
* Release one host: its channel, its overlay, its frame, and everything awaiting a reply from it.
|
|
@@ -1523,6 +1541,29 @@ function releaseHost(host: HostState): void {
|
|
|
1523
1541
|
liveHosts.delete(host);
|
|
1524
1542
|
}
|
|
1525
1543
|
|
|
1544
|
+
/**
|
|
1545
|
+
* Which document this pane's canvas iframe should load.
|
|
1546
|
+
*
|
|
1547
|
+
* The `canvasUrlDeferred` branch is the one that needs saying. Electrobun resolves its canvasUrl
|
|
1548
|
+
* asynchronously — it is this window's loopback port, fetched over RPC inside `activate()` — and
|
|
1549
|
+
* until the fix above, the bundle-relative default resolved to nothing servable under `views://`,
|
|
1550
|
+
* so an early frame simply failed and `ensureHost` rebuilt against the real URL when it landed. Now
|
|
1551
|
+
* that default RESOLVES: `views://studio/canvas.html` is a document electrobun really does stage.
|
|
1552
|
+
* An early frame would therefore boot the whole canvas bundle inside the SHELL's app-privileged
|
|
1553
|
+
* origin, in a CEF instance running `disable-site-isolation-trials` — and the cross-origin loopback
|
|
1554
|
+
* canvas exists precisely so that never happens.
|
|
1555
|
+
*/
|
|
1556
|
+
function resolveCanvasUrl(): string {
|
|
1557
|
+
if (!hasPlatform()) {
|
|
1558
|
+
return defaultCanvasUrl();
|
|
1559
|
+
}
|
|
1560
|
+
const platform = getPlatform();
|
|
1561
|
+
if (platform.canvasUrl) {
|
|
1562
|
+
return platform.canvasUrl;
|
|
1563
|
+
}
|
|
1564
|
+
return platform.canvasUrlDeferred ? DEFERRED_CANVAS_URL : defaultCanvasUrl();
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1526
1567
|
/**
|
|
1527
1568
|
* Release every canvas host mounted under `root`, and say how many there were.
|
|
1528
1569
|
*
|
|
@@ -1554,7 +1595,7 @@ export function releaseCanvasHosts(root: HTMLElement): number {
|
|
|
1554
1595
|
function ensureHost(canvasEl: HTMLElement): HostState {
|
|
1555
1596
|
// Read the platform's canvasUrl when one is registered; otherwise fall back to the default. The
|
|
1556
1597
|
// Dev server leaves it unset, and some tests mount without a platform registered.
|
|
1557
|
-
const canvasUrl = (
|
|
1598
|
+
const canvasUrl = resolveCanvasUrl();
|
|
1558
1599
|
const existing = hosts.get(canvasEl);
|
|
1559
1600
|
if (existing) {
|
|
1560
1601
|
if (existing.canvasUrl === canvasUrl) {
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
36
|
import { html, nothing, render as litRender } from "lit-html";
|
|
37
|
+
import { live } from "lit-html/directives/live.js";
|
|
37
38
|
import { activeRegistry } from "../commands/active-registry";
|
|
38
39
|
import { effect, effectScope } from "../reactivity";
|
|
39
40
|
import { projectState } from "../store";
|
|
@@ -182,7 +183,7 @@ function headerTpl(tab: Tab, collection: EntryCollection) {
|
|
|
182
183
|
<sp-switch
|
|
183
184
|
size="s"
|
|
184
185
|
class="entry-draft-switch"
|
|
185
|
-
|
|
186
|
+
.checked=${live(draft)}
|
|
186
187
|
title=${DRAFT_MEANING}
|
|
187
188
|
@change=${(e: Event) => setEntryDraft(tab, (e.target as HTMLInputElement).checked)}
|
|
188
189
|
>Draft</sp-switch
|
|
@@ -849,7 +849,17 @@ function itemElements(): HTMLElement[] {
|
|
|
849
849
|
: [];
|
|
850
850
|
}
|
|
851
851
|
|
|
852
|
-
/**
|
|
852
|
+
/**
|
|
853
|
+
* Move the roving tabindex (and the caret) to `index`, wrapping at both ends.
|
|
854
|
+
*
|
|
855
|
+
* Still imperative, deliberately and not happily. The menu's template is inline in the
|
|
856
|
+
* `renderPopover` call and its `ref` closure MUTATES the `x`/`y` it was rendered with to clamp the
|
|
857
|
+
* popover into the viewport, so re-rendering to move focus would also re-run the placement from
|
|
858
|
+
* stale coordinates. The template's own `tabindex`/`?focused` now read `_activeIdx` rather than
|
|
859
|
+
* hard-coding row 0, so first paint and this function at least agree on where focus starts; making
|
|
860
|
+
* the binding the only writer needs the template hoisted out and the placement state with it.
|
|
861
|
+
* Carried in check-lit-conventions' allow-list with that reason.
|
|
862
|
+
*/
|
|
853
863
|
function focusItem(index: number): void {
|
|
854
864
|
const items = itemElements();
|
|
855
865
|
if (items.length === 0) {
|
|
@@ -978,7 +988,8 @@ function rowTemplate(row: MenuRow, index: number) {
|
|
|
978
988
|
}<sp-menu-item
|
|
979
989
|
role="menuitem"
|
|
980
990
|
data-command-id=${row.command.id}
|
|
981
|
-
tabindex=${index ===
|
|
991
|
+
tabindex=${index === _activeIdx ? 0 : -1}
|
|
992
|
+
?focused=${index === _activeIdx}
|
|
982
993
|
?disabled=${!row.enabled}
|
|
983
994
|
aria-disabled=${row.enabled ? "false" : "true"}
|
|
984
995
|
style=${row.command.destructive && row.enabled ? "color: var(--danger)" : ""}
|
package/src/editor/slash-menu.ts
CHANGED
|
@@ -58,6 +58,9 @@ export interface SlashMenuAnchorRect {
|
|
|
58
58
|
|
|
59
59
|
let callbacks: SlashMenuCallbacks | null = null;
|
|
60
60
|
let activeIdx = 0;
|
|
61
|
+
|
|
62
|
+
/** What the current open() asked for, so a repaint driven by the keyboard reproduces it. */
|
|
63
|
+
let showFilter = false;
|
|
61
64
|
let filteredItems: SlashCommand[] = [];
|
|
62
65
|
let open = false;
|
|
63
66
|
let _anchorRect: SlashMenuAnchorRect | null = null;
|
|
@@ -123,7 +126,8 @@ function showAt(rect: SlashMenuAnchorRect, filter: string, cbs: SlashMenuCallbac
|
|
|
123
126
|
|
|
124
127
|
activeIdx = 0;
|
|
125
128
|
|
|
126
|
-
|
|
129
|
+
showFilter = cbs.showFilter || false;
|
|
130
|
+
render();
|
|
127
131
|
|
|
128
132
|
if (!open) {
|
|
129
133
|
open = true;
|
|
@@ -163,8 +167,9 @@ export function dismissSlashMenu() {
|
|
|
163
167
|
|
|
164
168
|
// ─── Internal ─────────────────────────────────────────────────────────────────
|
|
165
169
|
|
|
166
|
-
|
|
167
|
-
|
|
170
|
+
/* No parameter: `showFilter` is module state now, so there is one answer to what this
|
|
171
|
+
menu is, and a keyboard repaint cannot disagree with the open() that created it. */
|
|
172
|
+
function render() {
|
|
168
173
|
const rect = _anchorRect;
|
|
169
174
|
if (!rect) {
|
|
170
175
|
return;
|
|
@@ -203,7 +208,7 @@ function render(showFilter: boolean) {
|
|
|
203
208
|
? filteredItems.map(
|
|
204
209
|
(cmd, i) => html`
|
|
205
210
|
<sp-menu-item
|
|
206
|
-
?focused=${i ===
|
|
211
|
+
?focused=${i === activeIdx}
|
|
207
212
|
@click=${(e: Event) => {
|
|
208
213
|
e.preventDefault();
|
|
209
214
|
e.stopPropagation();
|
|
@@ -255,7 +260,7 @@ function onFilterInput(e: Event) {
|
|
|
255
260
|
: source;
|
|
256
261
|
|
|
257
262
|
activeIdx = 0;
|
|
258
|
-
render(
|
|
263
|
+
render();
|
|
259
264
|
|
|
260
265
|
// Re-focus input after re-render
|
|
261
266
|
requestAnimationFrame(() => {
|
|
@@ -267,6 +272,18 @@ function onFilterInput(e: Event) {
|
|
|
267
272
|
});
|
|
268
273
|
}
|
|
269
274
|
|
|
275
|
+
/**
|
|
276
|
+
* Scroll the active row into view after a repaint.
|
|
277
|
+
*
|
|
278
|
+
* The only imperative half of the keyboard model, and it stays imperative on purpose: where a row
|
|
279
|
+
* sits in a scroller is a measurement, not something a template can express. Everything else —
|
|
280
|
+
* WHICH row is active — is now a binding, because it has to be.
|
|
281
|
+
*/
|
|
282
|
+
function revealActive(): void {
|
|
283
|
+
const rows = getHost().querySelectorAll("sp-menu-item:not([disabled])");
|
|
284
|
+
rows[activeIdx]?.scrollIntoView({ block: "nearest" });
|
|
285
|
+
}
|
|
286
|
+
|
|
270
287
|
/**
|
|
271
288
|
* Drive the open menu with a navigation key. The canvas-iframe bridge calls this DIRECTLY (the key
|
|
272
289
|
* was pressed in the iframe realm — a synthetic keydown redispatch on this document would lose the
|
|
@@ -285,18 +302,16 @@ export function handleSlashMenuKey(key: string): void {
|
|
|
285
302
|
if (items.length === 0) {
|
|
286
303
|
return;
|
|
287
304
|
}
|
|
288
|
-
items[activeIdx]?.removeAttribute("focused");
|
|
289
305
|
activeIdx = (activeIdx + 1) % items.length;
|
|
290
|
-
|
|
291
|
-
|
|
306
|
+
render();
|
|
307
|
+
revealActive();
|
|
292
308
|
} else if (key === "ArrowUp") {
|
|
293
309
|
if (items.length === 0) {
|
|
294
310
|
return;
|
|
295
311
|
}
|
|
296
|
-
items[activeIdx]?.removeAttribute("focused");
|
|
297
312
|
activeIdx = (activeIdx - 1 + items.length) % items.length;
|
|
298
|
-
|
|
299
|
-
|
|
313
|
+
render();
|
|
314
|
+
revealActive();
|
|
300
315
|
} else if (key === "Enter") {
|
|
301
316
|
const cmd = filteredItems[activeIdx];
|
|
302
317
|
if (cmd) {
|
package/src/files/files.ts
CHANGED
|
@@ -488,6 +488,9 @@ export function renderFilesTemplate({
|
|
|
488
488
|
class="file-tree"
|
|
489
489
|
role="tree"
|
|
490
490
|
aria-label="Project files"
|
|
491
|
+
@keydown=${(e: KeyboardEvent) => {
|
|
492
|
+
onFileTreeKeydown(e, e.currentTarget as HTMLElement);
|
|
493
|
+
}}
|
|
491
494
|
${ref((el) => {
|
|
492
495
|
if (el) {
|
|
493
496
|
afterFileTreeRender(el as HTMLElement);
|
|
@@ -814,26 +817,15 @@ async function toggleTreeDirectory(path: string): Promise<void> {
|
|
|
814
817
|
}
|
|
815
818
|
}
|
|
816
819
|
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
/**
|
|
821
|
-
* Give the tree its keyboard, once per tree ELEMENT.
|
|
820
|
+
/*
|
|
821
|
+
* There is no setupTreeKeyboard here any more, and no WeakSet of trees that already have one.
|
|
822
822
|
*
|
|
823
|
-
* The panel's
|
|
824
|
-
* across all of them, so the unguarded `addEventListener` it
|
|
825
|
-
* render — after ten repaints a single Down keystroke walked ten rows. The WeakSet
|
|
826
|
-
*
|
|
823
|
+
* The panel's afterRender called it after every repaint and lit re-uses the `.file-tree` element
|
|
824
|
+
* across all of them, so the unguarded `addEventListener` it replaced accumulated one listener per
|
|
825
|
+
* render — after ten repaints a single Down keystroke walked ten rows. The WeakSet made that safe;
|
|
826
|
+
* `@keydown` on the element the template already renders makes it impossible, because lit owns the
|
|
827
|
+
* binding and swaps it rather than stacking it.
|
|
827
828
|
*/
|
|
828
|
-
export function setupTreeKeyboard(tree: HTMLElement) {
|
|
829
|
-
if (_keyboardTrees.has(tree)) {
|
|
830
|
-
return;
|
|
831
|
-
}
|
|
832
|
-
_keyboardTrees.add(tree);
|
|
833
|
-
tree.addEventListener("keydown", (e: KeyboardEvent) => {
|
|
834
|
-
onFileTreeKeydown(e, tree);
|
|
835
|
-
});
|
|
836
|
-
}
|
|
837
829
|
|
|
838
830
|
/**
|
|
839
831
|
* The tree's keyboard model: ↑↓ walk the rows, → expands, ← collapses, Enter opens.
|
|
@@ -1875,11 +1867,7 @@ export function registerFilesPanel(): void {
|
|
|
1875
1867
|
dock: "navigator",
|
|
1876
1868
|
icon: "sp-icon-folder",
|
|
1877
1869
|
render: (ctx) => ctx.deps.renderFilesTemplate(),
|
|
1878
|
-
afterRender: (ctx
|
|
1879
|
-
const tree = host.querySelector(".file-tree") as HTMLElement | null;
|
|
1880
|
-
if (tree) {
|
|
1881
|
-
ctx.deps.setupTreeKeyboard(tree);
|
|
1882
|
-
}
|
|
1870
|
+
afterRender: (ctx) => {
|
|
1883
1871
|
ctx.deps.registerFileTreeDnD({ renderLeftPanel: ctx.rerender });
|
|
1884
1872
|
},
|
|
1885
1873
|
});
|
package/src/grid/grid-panel.ts
CHANGED
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
* controls that each half-remember what the others did.
|
|
18
18
|
*/
|
|
19
19
|
import { html, render as litRender, nothing } from "lit-html";
|
|
20
|
+
import { guard } from "lit-html/directives/guard.js";
|
|
21
|
+
import { live } from "lit-html/directives/live.js";
|
|
20
22
|
import { ref } from "lit-html/directives/ref.js";
|
|
21
23
|
import { effect, effectScope, reactive } from "../reactivity";
|
|
22
24
|
import { renderPopover, showConfirmDialog, showPromptDialog } from "../ui/layers";
|
|
@@ -193,7 +195,7 @@ function toolbarTpl(controller: GridController, panel: ActiveGridPanel) {
|
|
|
193
195
|
size="s"
|
|
194
196
|
quiet
|
|
195
197
|
placeholder="Filter rows"
|
|
196
|
-
.value=${layout?.filter ?? ""}
|
|
198
|
+
.value=${live(layout?.filter ?? "")}
|
|
197
199
|
@input=${(e: Event) => {
|
|
198
200
|
const term = (e.target as HTMLInputElement).value;
|
|
199
201
|
// The filter is part of the view, so it persists with the rest of it rather than
|
|
@@ -575,7 +577,23 @@ function shellTpl(
|
|
|
575
577
|
return html`
|
|
576
578
|
<div class="jx-grid">
|
|
577
579
|
${toolbarTpl(controller, panel)}
|
|
578
|
-
|
|
580
|
+
${guard(
|
|
581
|
+
[controller.source.id],
|
|
582
|
+
/* Tabulator OWNS this node's children once createGridView hands it over, so a toolbar tick
|
|
583
|
+
must not re-render it. Today that holds by ACCIDENT: shellTpl returns the same template
|
|
584
|
+
shape every pass, so lit keeps the element and patches only the toolbar above it. Put
|
|
585
|
+
this div behind a conditional, or inside a map, and the next tick silently destroys the
|
|
586
|
+
whole table. The guard says the guarantee out loud.
|
|
587
|
+
Keyed on the SOURCE, not on nothing: an empty dependency list also suppresses the
|
|
588
|
+
re-commit a tab switch needs. `hostEl` lives in the panel closure, so switching tabs
|
|
589
|
+
builds a fresh one and relies on `ref` firing again with the new callback to hand the
|
|
590
|
+
node over; memoising past that leaves the new panel with a null host and no engine. */
|
|
591
|
+
() =>
|
|
592
|
+
html`<div
|
|
593
|
+
class="jx-grid-host"
|
|
594
|
+
${ref((el) => onHost(el as HTMLElement | undefined))}
|
|
595
|
+
></div>`,
|
|
596
|
+
)}
|
|
579
597
|
</div>
|
|
580
598
|
`;
|
|
581
599
|
}
|
|
@@ -610,13 +628,16 @@ export function renderGridMode(surface: CanvasSurface, tab: Tab) {
|
|
|
610
628
|
// `localStorage` is not reactive and neither is the engine, so the toolbar needs something that
|
|
611
629
|
// Is: a saved-view edit bumps this and the panel's one effect re-renders like any other change.
|
|
612
630
|
const local = reactive({ views: 0 });
|
|
613
|
-
|
|
631
|
+
/* Named `controller` everywhere else in this file; this alias exists so the closures below
|
|
632
|
+
capture it by a short name. NOT `live` — that is lit's directive, imported above and used
|
|
633
|
+
in shellTpl, and a local of the same name shadows it. */
|
|
634
|
+
const engine = controller;
|
|
614
635
|
let hostEl: HTMLElement | null = null;
|
|
615
636
|
|
|
616
637
|
const panel: ActiveGridPanel = {
|
|
617
638
|
applyLayout(layout) {
|
|
618
|
-
void
|
|
619
|
-
|
|
639
|
+
void engine.setSort(layout?.sort ?? null);
|
|
640
|
+
engine.setGrouping(layout?.groupBy ?? null);
|
|
620
641
|
panel.remount();
|
|
621
642
|
panel.view?.setSearch(layout?.filter ?? "");
|
|
622
643
|
panel.bump();
|
|
@@ -626,7 +647,7 @@ export function renderGridMode(surface: CanvasSurface, tab: Tab) {
|
|
|
626
647
|
},
|
|
627
648
|
remount() {
|
|
628
649
|
panel.view?.destroy();
|
|
629
|
-
panel.view = hostEl ? createGridView(hostEl,
|
|
650
|
+
panel.view = hostEl ? createGridView(hostEl, engine) : null;
|
|
630
651
|
},
|
|
631
652
|
paneId,
|
|
632
653
|
scope,
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two documents `@jxsuite/studio` ships, generated for whatever base a host mounts the tree at.
|
|
3
|
+
*
|
|
4
|
+
* They used to be hand-written files that every host rewrote by hand. jx-platform's asset build
|
|
5
|
+
* carried three `replaceAll` calls, an exact-string surgery on the entry's script tag, and — added
|
|
6
|
+
* after the fact — an assertion that no relative reference had survived, because studio 2.1.0 split
|
|
7
|
+
* the chrome into `./styles/*.css` and the rewrite list missed it. The cloud editor shipped seven
|
|
8
|
+
* dead stylesheet links and rendered unstyled while the build exited 0. A rewrite list is a fact
|
|
9
|
+
* about a document someone else owns; generating the document removes the category.
|
|
10
|
+
*
|
|
11
|
+
* Pure: no `node:`, no DOM, no I/O. The caller supplies the base and the boot modules.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
assetUrl,
|
|
16
|
+
STUDIO_BUNDLE_CSS,
|
|
17
|
+
STUDIO_ENTRY,
|
|
18
|
+
STUDIO_IFRAME_ENTRY,
|
|
19
|
+
STUDIO_STYLESHEETS,
|
|
20
|
+
} from "./layout";
|
|
21
|
+
import type { AssetBase } from "./layout";
|
|
22
|
+
|
|
23
|
+
/** The default base: the tree served at the document's own directory, in the package's own shape. */
|
|
24
|
+
export const IN_PLACE: AssetBase = { mode: "nested", prefix: "./" };
|
|
25
|
+
|
|
26
|
+
export interface DocumentOptions {
|
|
27
|
+
/** Where the asset tree is mounted. Defaults to {@link IN_PLACE}. */
|
|
28
|
+
readonly base?: AssetBase | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Module urls evaluated BEFORE the studio entry — the host's PAL registration, in order.
|
|
31
|
+
*
|
|
32
|
+
* This is the declared seam. Both hosts used to obtain it by string-replacing the entry's script
|
|
33
|
+
* tag, and only one of them checked that the replace had matched; the other silently shipped an
|
|
34
|
+
* app with no platform registered, which then self-registered the dev-server adapter and fetched
|
|
35
|
+
* `/__studio/*` against a `views://` origin.
|
|
36
|
+
*
|
|
37
|
+
* The runtime half of the handshake is unchanged: a boot module sets `globalThis.__jxPlatform`,
|
|
38
|
+
* or publishes the `__jxCloud` signal for the studio entry to build the adapter from. It must do
|
|
39
|
+
* so SYNCHRONOUSLY — a module script with top-level await does not block a later script tag, and
|
|
40
|
+
* the entry reads the global as it evaluates.
|
|
41
|
+
*/
|
|
42
|
+
readonly boot?: readonly string[] | undefined;
|
|
43
|
+
/** Document title. Defaults to "Jx Studio". */
|
|
44
|
+
readonly title?: string | undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function scriptTag(url: string): string {
|
|
48
|
+
return `<script type="module" src="${url}"></script>`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The editor document.
|
|
53
|
+
*
|
|
54
|
+
* The chrome stylesheets are LINKED rather than bundled, and the order is
|
|
55
|
+
* {@link STUDIO_STYLESHEETS}'s. Bundling them was tried and rejected on measurement: Bun inlines a
|
|
56
|
+
* woff2 below its size threshold as a base64 data uri, which would put roughly 85 KB into a
|
|
57
|
+
* render-blocking stylesheet, and concatenating the chrome with the vendor CSS already in
|
|
58
|
+
* `dist/studio.css` makes the cascade depend on module-graph traversal instead of on link order.
|
|
59
|
+
* Generating the links keeps the one thing that mattered — a single definition of the list.
|
|
60
|
+
*/
|
|
61
|
+
export function studioShellHtml(options: DocumentOptions = {}): string {
|
|
62
|
+
const base = options.base ?? IN_PLACE;
|
|
63
|
+
const url = (path: string) => assetUrl(base, path);
|
|
64
|
+
const links = [STUDIO_BUNDLE_CSS, ...STUDIO_STYLESHEETS]
|
|
65
|
+
.map((path) => ` <link rel="stylesheet" href="${url(path)}" />`)
|
|
66
|
+
.join("\n");
|
|
67
|
+
const boot = (options.boot ?? []).map((mod) => ` ${scriptTag(mod)}`).join("\n");
|
|
68
|
+
return `<!doctype html>
|
|
69
|
+
<html lang="en">
|
|
70
|
+
<head>
|
|
71
|
+
<meta charset="utf-8" />
|
|
72
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
73
|
+
<title>${options.title ?? "Jx Studio"}</title>
|
|
74
|
+
${links}
|
|
75
|
+
</head>
|
|
76
|
+
<body>
|
|
77
|
+
${boot ? `${boot}\n` : ""} ${scriptTag(url(STUDIO_ENTRY))}
|
|
78
|
+
</body>
|
|
79
|
+
</html>
|
|
80
|
+
`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The canvas iframe document.
|
|
85
|
+
*
|
|
86
|
+
* Its `<style>` block is hand-authored and stays that way — it establishes the fixed-size query
|
|
87
|
+
* container the runtime transposes viewport units against, and the preview-mode overrides, both of
|
|
88
|
+
* which have to apply before the iframe's first render. Only the entry url is generated.
|
|
89
|
+
*
|
|
90
|
+
* The reference it replaces is a dynamic `import()` inside a template literal with a cache-buster,
|
|
91
|
+
* which is precisely the shape jx-platform's `(?:src|href)="\./…"` residual-ref guard could not
|
|
92
|
+
* see.
|
|
93
|
+
*
|
|
94
|
+
* @param html The package's own `canvas.html`, read by the caller.
|
|
95
|
+
* @param base Where the asset tree is mounted. Defaults to {@link IN_PLACE}.
|
|
96
|
+
* @returns The document with its entry url rebased.
|
|
97
|
+
* @throws {Error} When the entry reference is absent — a silent miss here is a canvas that 404s at
|
|
98
|
+
* boot.
|
|
99
|
+
*/
|
|
100
|
+
export function canvasShellHtml(html: string, base: AssetBase = IN_PLACE): string {
|
|
101
|
+
const from = `./${STUDIO_IFRAME_ENTRY}`;
|
|
102
|
+
if (!html.includes(from)) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`canvas.html does not reference ${from}. The canvas entry's path is a contract ` +
|
|
105
|
+
`(studio.md 11.1); if it moved, STUDIO_IFRAME_ENTRY moved with it.`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
return html.replaceAll(from, assetUrl(base, STUDIO_IFRAME_ENTRY));
|
|
109
|
+
}
|