@jxsuite/studio 2.0.1 → 2.1.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/canvas.html +21 -0
- package/dist/chunks/Overlay.dev-qfjzkms8.js +23 -0
- package/dist/chunks/{Overlay.dev-cszx7yn5.js.map → Overlay.dev-qfjzkms8.js.map} +1 -1
- package/dist/chunks/{adapter-z8fkmbmv.js → adapter-qfnesn5v.js} +2 -2
- package/dist/chunks/{cf-settings-mmyv436d.js → cf-settings-73sh5z4p.js} +3 -3
- package/dist/chunks/{components-799jpvhm.js → components-2bpqp4mq.js} +5 -7
- package/dist/chunks/{components-799jpvhm.js.map → components-2bpqp4mq.js.map} +1 -1
- package/dist/chunks/content-source-34gnkbnn.js +38 -0
- package/dist/chunks/content-source-34gnkbnn.js.map +9 -0
- package/dist/chunks/convert-to-repeater-t8kxre5s.js +258 -0
- package/dist/chunks/convert-to-repeater-t8kxre5s.js.map +10 -0
- package/dist/chunks/extension-sections-6mqydz35.js +301 -0
- package/dist/chunks/extension-sections-6mqydz35.js.map +11 -0
- package/dist/chunks/files-8kn321h1.js +70 -0
- package/dist/chunks/files-8kn321h1.js.map +9 -0
- package/dist/chunks/{format-host-8jxwts2p.js → format-host-mkwsmt8d.js} +3 -3
- package/dist/chunks/git-panel-tjbr191d.js +51 -0
- package/dist/chunks/git-panel-tjbr191d.js.map +9 -0
- package/dist/chunks/{index-baeg2x3y.js → index-7pxchcce.js} +4 -3
- package/dist/chunks/{index-baeg2x3y.js.map → index-7pxchcce.js.map} +3 -3
- package/dist/chunks/{jx-validate-qx8degj7.js → jx-validate-v6qgknrs.js} +3 -3
- package/dist/chunks/layers-panel-87pb25km.js +58 -0
- package/dist/chunks/layers-panel-87pb25km.js.map +9 -0
- package/dist/chunks/{monaco-setup-zjdp9zdw.js → monaco-setup-e0yh25j4.js} +4 -75
- package/dist/chunks/{monaco-setup-zjdp9zdw.js.map → monaco-setup-e0yh25j4.js.map} +3 -4
- package/dist/chunks/publish-panel-m0fcgdrt.js +21 -0
- package/dist/chunks/publish-panel-m0fcgdrt.js.map +9 -0
- package/dist/chunks/quick-search-rhs6cbsb.js +53 -0
- package/dist/chunks/quick-search-rhs6cbsb.js.map +9 -0
- package/dist/chunks/right-panel-zpdxd98t.js +37 -0
- package/dist/chunks/right-panel-zpdxd98t.js.map +9 -0
- package/dist/chunks/section-registry-zmckaxf4.js +30 -0
- package/dist/chunks/section-registry-zmckaxf4.js.map +9 -0
- package/dist/chunks/sp-overlay.dev-9ran0sfq.js +18 -0
- package/dist/chunks/sp-overlay.dev-9ran0sfq.js.map +9 -0
- package/dist/chunks/sp-popover.dev-rebmdncr.js +9 -0
- package/dist/chunks/sp-popover.dev-rebmdncr.js.map +9 -0
- package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js +15 -0
- package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js.map +9 -0
- package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js +15 -0
- package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js.map +9 -0
- package/dist/chunks/{sp-tray.dev-pb2fsd65.js → sp-tray.dev-ey59vfh7.js} +10 -8
- package/dist/chunks/{sp-tray.dev-pb2fsd65.js.map → sp-tray.dev-ey59vfh7.js.map} +2 -2
- package/dist/chunks/{studio-9qr6fq7p.js → studio-0rqqhrdy.js} +10 -378
- package/dist/chunks/{studio-9qr6fq7p.js.map → studio-0rqqhrdy.js.map} +4 -14
- package/dist/chunks/{studio-5x6tqqec.js → studio-2sj1jj3v.js} +10 -8
- package/dist/chunks/{studio-5x6tqqec.js.map → studio-2sj1jj3v.js.map} +2 -2
- package/dist/chunks/{studio-h3fw0wn8.js → studio-3fk1ac9z.js} +6 -4
- package/dist/chunks/{studio-h3fw0wn8.js.map → studio-3fk1ac9z.js.map} +2 -2
- package/dist/chunks/studio-41exgzhq.js +6690 -0
- package/dist/chunks/studio-41exgzhq.js.map +38 -0
- package/dist/chunks/{studio-7v2bpweq.js → studio-4mqb9a0j.js} +6 -4
- package/dist/chunks/{studio-7v2bpweq.js.map → studio-4mqb9a0j.js.map} +2 -2
- package/dist/chunks/studio-50k77cca.js +1787 -0
- package/dist/chunks/studio-50k77cca.js.map +18 -0
- package/dist/chunks/{studio-56v3zwk6.js → studio-6a0h7sne.js} +10 -8
- package/dist/chunks/{studio-56v3zwk6.js.map → studio-6a0h7sne.js.map} +2 -2
- package/dist/chunks/studio-6f0am8f2.js +53 -0
- package/dist/chunks/studio-6f0am8f2.js.map +10 -0
- package/dist/chunks/studio-6nmjc5qk.js +7002 -0
- package/dist/chunks/studio-6nmjc5qk.js.map +32 -0
- package/dist/chunks/studio-7afd21nw.js +67 -0
- package/dist/chunks/studio-7afd21nw.js.map +10 -0
- package/dist/chunks/{studio-xze4zrs1.js → studio-7bmdvzne.js} +2 -2
- package/dist/chunks/studio-7bmdvzne.js.map +10 -0
- package/dist/chunks/studio-8jpzqk5s.js +128 -0
- package/dist/chunks/studio-8jpzqk5s.js.map +10 -0
- package/dist/chunks/studio-94m77tgk.js +1482 -0
- package/dist/chunks/studio-94m77tgk.js.map +13 -0
- package/dist/chunks/studio-9je6w5sq.js +1000 -0
- package/dist/chunks/studio-9je6w5sq.js.map +16 -0
- package/dist/chunks/{studio-w7c6kvk7.js → studio-a7pzveq1.js} +2 -2
- package/dist/chunks/{studio-gs6kqdt1.js → studio-adn0z1k3.js} +14 -8
- package/dist/chunks/{studio-gs6kqdt1.js.map → studio-adn0z1k3.js.map} +3 -3
- package/dist/chunks/{studio-kr88ryr7.js → studio-bsxxx3x2.js} +17 -15
- package/dist/chunks/{studio-kr88ryr7.js.map → studio-bsxxx3x2.js.map} +7 -6
- package/dist/chunks/studio-bw5dz6km.js +608 -0
- package/dist/chunks/studio-bw5dz6km.js.map +21 -0
- package/dist/chunks/studio-c6s5getc.js +4232 -0
- package/dist/chunks/studio-c6s5getc.js.map +39 -0
- package/dist/chunks/studio-cjm7fdjf.js +451 -0
- package/dist/chunks/studio-cjm7fdjf.js.map +14 -0
- package/dist/chunks/{studio-cx91mnh8.js → studio-ckwz4d3x.js} +53 -53
- package/dist/chunks/{studio-cx91mnh8.js.map → studio-ckwz4d3x.js.map} +5 -5
- package/dist/chunks/studio-dg7zyt5x.js +28 -0
- package/dist/chunks/studio-dg7zyt5x.js.map +10 -0
- package/dist/chunks/studio-dzcyvvvz.js +166 -0
- package/dist/chunks/studio-dzcyvvvz.js.map +10 -0
- package/dist/chunks/{studio-7kqe41sa.js → studio-evkr5d3b.js} +15 -7
- package/dist/chunks/{studio-7kqe41sa.js.map → studio-evkr5d3b.js.map} +5 -4
- package/dist/chunks/studio-f12h6177.js +411 -0
- package/dist/chunks/studio-f12h6177.js.map +13 -0
- package/dist/chunks/studio-hbp1s8m9.js +1817 -0
- package/dist/chunks/studio-hbp1s8m9.js.map +14 -0
- package/dist/chunks/studio-k2sxjph5.js +15295 -0
- package/dist/chunks/studio-k2sxjph5.js.map +235 -0
- package/dist/chunks/studio-myje51t3.js +385 -0
- package/dist/chunks/studio-myje51t3.js.map +12 -0
- package/dist/chunks/studio-na6rwzhw.js +353 -0
- package/dist/chunks/studio-na6rwzhw.js.map +11 -0
- package/dist/chunks/{studio-4k0m4fmp.js → studio-p471gg5n.js} +126 -3
- package/dist/chunks/studio-p471gg5n.js.map +14 -0
- package/dist/chunks/{studio-9bjwf0d4.js → studio-rgwcpast.js} +5 -46
- package/dist/chunks/{studio-9bjwf0d4.js.map → studio-rgwcpast.js.map} +4 -5
- package/dist/chunks/{studio-esrewxap.js → studio-s519zc99.js} +3 -3
- package/dist/chunks/studio-sas7mt3k.js +75 -0
- package/dist/chunks/studio-sas7mt3k.js.map +10 -0
- package/dist/chunks/studio-t5wqwzdh.js +74 -0
- package/dist/chunks/studio-t5wqwzdh.js.map +10 -0
- package/dist/chunks/{studio-9qpspwfn.js → studio-temv5bw4.js} +5 -5
- package/dist/chunks/{studio-9qpspwfn.js.map → studio-temv5bw4.js.map} +1 -1
- package/dist/chunks/{studio-qkvt37c0.js → studio-tr20m8g8.js} +669 -18
- package/dist/chunks/studio-tr20m8g8.js.map +9 -0
- package/dist/chunks/{studio-v7jkjqng.js → studio-v96qfnww.js} +5514 -4291
- package/dist/chunks/studio-v96qfnww.js.map +54 -0
- package/dist/chunks/studio-vcetc91k.js +659 -0
- package/dist/chunks/studio-vcetc91k.js.map +10 -0
- package/dist/chunks/{studio-jm0z592w.js → studio-vmymjefp.js} +2972 -4937
- package/dist/chunks/studio-vmymjefp.js.map +98 -0
- package/dist/chunks/{studio-bghqfmea.js → studio-wwseqyw0.js} +2 -2
- package/dist/chunks/studio-x9hmy5kr.js +390 -0
- package/dist/chunks/studio-x9hmy5kr.js.map +10 -0
- package/dist/chunks/studio-xa0ce0n7.js +335 -0
- package/dist/chunks/studio-xa0ce0n7.js.map +10 -0
- package/dist/chunks/studio-xm3dbxvn.js +378 -0
- package/dist/chunks/studio-xm3dbxvn.js.map +19 -0
- package/dist/chunks/{studio-nqw433kv.js → studio-xmvbhg31.js} +2 -2
- package/dist/chunks/studio-zv37yrfa.js +1054 -0
- package/dist/chunks/studio-zv37yrfa.js.map +13 -0
- package/dist/chunks/{ws-client-q7hkz3j9.js → ws-client-654ts66j.js} +6 -4
- package/dist/chunks/ws-client-654ts66j.js.map +11 -0
- package/dist/iframe-entry.js +668 -230
- package/dist/studio.js +21080 -32585
- package/index.html +35 -4685
- package/package.json +17 -15
- package/src/about/about-modal.ts +42 -11
- package/src/browse/library-commands.ts +240 -0
- package/src/browse/library-layouts.ts +369 -0
- package/src/browse/library-model.ts +396 -0
- package/src/browse/library-pane.ts +1049 -0
- package/src/browse/library-preview.ts +252 -0
- package/src/browse/library-source.ts +150 -0
- package/src/canvas/canvas-helpers.ts +33 -9
- package/src/canvas/canvas-live-render.ts +45 -29
- package/src/canvas/canvas-patcher.ts +151 -31
- package/src/canvas/canvas-perf.ts +46 -7
- package/src/canvas/canvas-render.ts +1092 -220
- package/src/canvas/canvas-surface.ts +240 -0
- package/src/canvas/canvas-utils.ts +1166 -124
- package/src/canvas/iframe-drop.ts +2 -1
- package/src/canvas/iframe-entry.ts +241 -4
- package/src/canvas/iframe-host.ts +1226 -143
- package/src/canvas/iframe-inline-edit.ts +31 -0
- package/src/canvas/iframe-interaction.ts +76 -16
- package/src/canvas/iframe-keys.ts +107 -45
- package/src/canvas/iframe-overlay.ts +38 -1
- package/src/canvas/iframe-protocol.ts +137 -2
- package/src/canvas/iframe-render.ts +70 -2
- package/src/canvas/iframe-slash.ts +6 -0
- package/src/canvas/path-mapping.ts +56 -6
- package/src/canvas/surface-registry.ts +292 -0
- package/src/collab/collab-commands.ts +244 -0
- package/src/collab/collab-session.ts +198 -32
- package/src/collab/collab-state.ts +28 -2
- package/src/collab/presence-chips.ts +100 -9
- package/src/commands/active-registry.ts +42 -0
- package/src/commands/app-commands.ts +176 -0
- package/src/commands/budget.ts +158 -0
- package/src/commands/command-args.ts +253 -0
- package/src/commands/context.ts +387 -0
- package/src/commands/defaults.ts +580 -0
- package/src/commands/keymap.ts +571 -0
- package/src/commands/levels.ts +345 -0
- package/src/commands/live-context.ts +193 -0
- package/src/commands/reference.ts +227 -0
- package/src/commands/registry.ts +293 -0
- package/src/component-props.ts +3 -2
- package/src/content/draft-state.ts +106 -0
- package/src/content/entry-commands.ts +282 -0
- package/src/content/entry-editor.ts +327 -0
- package/src/content/entry-fields.ts +116 -0
- package/src/content/entry-model.ts +175 -0
- package/src/editor/canvas-context-menu.ts +17 -11
- package/src/editor/canvas-slash-bridge.ts +11 -4
- package/src/editor/context-menu.ts +804 -204
- package/src/editor/convert-to-component.ts +31 -10
- package/src/editor/convert-to-repeater.ts +3 -2
- package/src/editor/inline-edit-apply.ts +5 -5
- package/src/editor/inline-edit.ts +86 -50
- package/src/editor/insert-zone-action.ts +1 -1
- package/src/editor/shortcuts.ts +1043 -329
- package/src/editor/slash-menu.ts +1 -0
- package/src/files/elements.ts +166 -0
- package/src/files/file-ops.ts +178 -31
- package/src/files/files.ts +898 -268
- package/src/files/fs-events.ts +21 -0
- package/src/files/media-meta.ts +203 -0
- package/src/files/media-paths.ts +141 -0
- package/src/files/media-upload.ts +7 -3
- package/src/files/media-usage.ts +189 -0
- package/src/github/github-auth.ts +235 -33
- package/src/github/github-publish.ts +80 -21
- package/src/grid/grid-controller.ts +152 -12
- package/src/grid/grid-layout.ts +225 -20
- package/src/grid/grid-open.ts +120 -0
- package/src/grid/grid-panel.ts +554 -39
- package/src/grid/grid-source.ts +27 -1
- package/src/grid/redirects-grid.ts +599 -0
- package/src/grid/redirects.ts +398 -0
- package/src/grid/sources/content-source.ts +108 -2
- package/src/i18n/i18n-commands.ts +322 -0
- package/src/new-project/add-repo-modal.ts +8 -10
- package/src/new-project/import-tab.ts +7 -0
- package/src/new-project/location-fields.ts +9 -1
- package/src/new-project/new-project-modal.ts +221 -245
- package/src/packages/jxsuite-update.ts +97 -48
- package/src/packages/pull-package-sync.ts +18 -9
- package/src/page-params.ts +2 -1
- package/src/panels/activity-bar.ts +152 -91
- package/src/panels/activity-panel.ts +481 -0
- package/src/panels/ai-chat/chat-markdown.ts +8 -3
- package/src/panels/ai-chat/chat-view.ts +245 -18
- package/src/panels/ai-chat/composer.ts +31 -5
- package/src/panels/ai-chat/sessions-view.ts +17 -10
- package/src/panels/ai-panel.ts +279 -53
- package/src/panels/block-action-bar.ts +1115 -224
- package/src/panels/bottom-dock.ts +337 -0
- package/src/panels/canvas-dnd-bridge.ts +37 -8
- package/src/panels/chat-panel.ts +29 -12
- package/src/panels/data-explorer.ts +254 -112
- package/src/panels/data-grid.ts +10 -4
- package/src/panels/dnd.ts +28 -51
- package/src/panels/editors.ts +434 -107
- package/src/panels/elements-panel.ts +66 -2
- package/src/panels/empty-state.ts +112 -0
- package/src/panels/events-panel.ts +596 -82
- package/src/panels/formula-workspace.ts +408 -218
- package/src/panels/frontmatter-fields.ts +51 -10
- package/src/panels/frontmatter-panel.ts +357 -79
- package/src/panels/git-panel.ts +375 -164
- package/src/panels/head-panel.ts +791 -85
- package/src/panels/i18n-panel.ts +592 -0
- package/src/panels/imports-panel.ts +95 -85
- package/src/panels/jump-bar.ts +584 -0
- package/src/panels/layers-panel.ts +1106 -328
- package/src/panels/left-panel.ts +109 -204
- package/src/panels/navigator-panels.ts +131 -0
- package/src/panels/overlays.ts +7 -2
- package/src/panels/pane-context.ts +1477 -0
- package/src/panels/pane-grid.ts +406 -0
- package/src/panels/panel-registry.ts +340 -0
- package/src/panels/panel-scheduler.ts +75 -0
- package/src/panels/problems-panel.ts +234 -0
- package/src/panels/properties-panel.ts +946 -1139
- package/src/panels/provenance.ts +258 -0
- package/src/panels/quick-search.ts +791 -122
- package/src/panels/right-panel.ts +228 -102
- package/src/panels/seo-modal.ts +395 -0
- package/src/panels/settings-pane.ts +203 -0
- package/src/panels/signals-panel.ts +354 -137
- package/src/panels/statement-editor.ts +41 -25
- package/src/panels/statusbar.ts +342 -106
- package/src/panels/style-panel.ts +974 -463
- package/src/panels/style-utils.ts +3 -3
- package/src/panels/stylebook-doc.ts +4 -2
- package/src/panels/stylebook-layers-panel.ts +3 -2
- package/src/panels/stylebook-panel.ts +51 -26
- package/src/panels/tab-strip.ts +1058 -78
- package/src/panels/target-line.ts +274 -0
- package/src/panels/toolbar.ts +631 -484
- package/src/panels/welcome-screen.ts +301 -198
- package/src/platform-errors.ts +26 -3
- package/src/platform.ts +96 -2
- package/src/platforms/cloud.ts +71 -8
- package/src/platforms/devserver.ts +92 -29
- package/src/publish/deploy-checklist.ts +383 -0
- package/src/publish/pages-service.ts +10 -1
- package/src/publish/publish-commands.ts +218 -0
- package/src/publish/publish-panel.ts +110 -16
- package/src/recent-projects.ts +3 -2
- package/src/services/a11y-report.ts +505 -0
- package/src/services/ai-models.ts +30 -1
- package/src/services/ai-project-tools.ts +29 -10
- package/src/services/ai-system-prompt.ts +53 -14
- package/src/services/ai-tools.ts +39 -3
- package/src/services/ai-writes.ts +154 -0
- package/src/services/announce.ts +97 -0
- package/src/services/automation.ts +424 -175
- package/src/services/cem-export.ts +9 -4
- package/src/services/clock.ts +61 -0
- package/src/services/document-assistant.ts +36 -8
- package/src/services/idle.ts +179 -0
- package/src/services/import-client.ts +26 -0
- package/src/services/monaco-buffer.ts +627 -0
- package/src/services/monaco-lazy.ts +36 -0
- package/src/services/notify.ts +271 -0
- package/src/services/profile.ts +155 -0
- package/src/services/references.ts +0 -0
- package/src/services/token-lint.ts +3 -2
- package/src/services/tool-executor.ts +53 -7
- package/src/services/trusted-types.ts +123 -0
- package/src/settings/contexts-section.ts +427 -0
- package/src/settings/contributed-section.ts +149 -11
- package/src/settings/css-vars-editor.ts +306 -349
- package/src/settings/defs-editor.ts +93 -34
- package/src/settings/dependencies-editor.ts +7 -7
- package/src/settings/extension-sections.ts +37 -5
- package/src/settings/general-settings.ts +204 -121
- package/src/settings/locales-section.ts +347 -0
- package/src/settings/preferences-accounts.ts +140 -0
- package/src/settings/preferences-dialog.ts +620 -0
- package/src/settings/preferences-keymap.ts +270 -0
- package/src/settings/project-sections.ts +260 -0
- package/src/settings/section-registry.ts +169 -0
- package/src/settings/settings-document.ts +385 -0
- package/src/shell.ts +1549 -0
- package/src/site-context.ts +45 -20
- package/src/state.ts +14 -275
- package/src/store.ts +88 -43
- package/src/studio.ts +790 -346
- package/src/style/live-preview.ts +36 -0
- package/src/style/project-styles.ts +349 -0
- package/src/style/token-ref.ts +123 -0
- package/src/tabs/patch-ops.ts +16 -4
- package/src/tabs/project-config.ts +416 -0
- package/src/tabs/selection.ts +323 -0
- package/src/tabs/tab.ts +182 -47
- package/src/tabs/transact.ts +209 -28
- package/src/types.ts +68 -13
- package/src/ui/ai-credentials-form.ts +39 -35
- package/src/ui/ai-managed-connect.ts +7 -1
- package/src/ui/dynamic-slot.ts +224 -81
- package/src/ui/expression-editor.ts +71 -63
- package/src/ui/field-input.ts +4 -6
- package/src/ui/field-row.ts +215 -38
- package/src/ui/form-controls.ts +194 -7
- package/src/ui/layers.ts +492 -69
- package/src/ui/media-picker.ts +115 -25
- package/src/ui/panel-resize.ts +109 -91
- package/src/ui/progress-modal.ts +131 -50
- package/src/ui/regions.ts +462 -0
- package/src/ui/schema-form.ts +229 -45
- package/src/ui/spectrum.ts +24 -8
- package/src/ui/timing.ts +49 -0
- package/src/ui/value-selector.ts +20 -0
- package/src/ui/value-source.ts +348 -0
- package/src/ui/virtual-window.ts +400 -0
- package/src/utils/edit-display.ts +3 -2
- package/src/utils/inherited-style.ts +67 -37
- package/src/view.ts +64 -78
- package/src/workspace/pane-derive.ts +1258 -0
- package/src/workspace/session.ts +317 -0
- package/src/workspace/workspace.ts +1345 -23
- package/styles/canvas.css +653 -0
- package/styles/forced-colors.css +106 -0
- package/styles/inspector.css +1454 -0
- package/styles/overlays.css +2704 -0
- package/styles/panels.css +1701 -0
- package/styles/shell.css +1423 -0
- package/styles/tokens.css +151 -0
- package/dist/chunks/Overlay.dev-cszx7yn5.js +0 -22
- package/dist/chunks/extension-sections-qr3drkkw.js +0 -531
- package/dist/chunks/extension-sections-qr3drkkw.js.map +0 -12
- package/dist/chunks/files-j8a1t06k.js +0 -50
- package/dist/chunks/files-j8a1t06k.js.map +0 -9
- package/dist/chunks/git-panel-xtq5z81x.js +0 -28
- package/dist/chunks/git-panel-xtq5z81x.js.map +0 -9
- package/dist/chunks/layers-panel-ancn2wpq.js +0 -26
- package/dist/chunks/layers-panel-ancn2wpq.js.map +0 -9
- package/dist/chunks/settings-modal-17ttr96g.js +0 -29
- package/dist/chunks/settings-modal-17ttr96g.js.map +0 -9
- package/dist/chunks/sp-overlay.dev-earn5as8.js +0 -17
- package/dist/chunks/sp-overlay.dev-earn5as8.js.map +0 -9
- package/dist/chunks/sp-popover.dev-frxtfejn.js +0 -8
- package/dist/chunks/sp-popover.dev-frxtfejn.js.map +0 -9
- package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js +0 -14
- package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js.map +0 -9
- package/dist/chunks/sp-tooltip.dev-a9fywesm.js +0 -21
- package/dist/chunks/sp-tooltip.dev-a9fywesm.js.map +0 -10
- package/dist/chunks/studio-0xgs7xrs.js +0 -429
- package/dist/chunks/studio-0xgs7xrs.js.map +0 -13
- package/dist/chunks/studio-4d8hzcz5.js +0 -3427
- package/dist/chunks/studio-4d8hzcz5.js.map +0 -26
- package/dist/chunks/studio-4k0m4fmp.js.map +0 -13
- package/dist/chunks/studio-9smfzqhc.js +0 -19
- package/dist/chunks/studio-9smfzqhc.js.map +0 -10
- package/dist/chunks/studio-jm0z592w.js.map +0 -115
- package/dist/chunks/studio-m2t4mdg5.js +0 -1854
- package/dist/chunks/studio-m2t4mdg5.js.map +0 -18
- package/dist/chunks/studio-n4p11dfy.js +0 -853
- package/dist/chunks/studio-n4p11dfy.js.map +0 -12
- package/dist/chunks/studio-qkvt37c0.js.map +0 -9
- package/dist/chunks/studio-sjh1wt1a.js +0 -3744
- package/dist/chunks/studio-sjh1wt1a.js.map +0 -22
- package/dist/chunks/studio-v7jkjqng.js.map +0 -46
- package/dist/chunks/studio-xq0t7n5t.js +0 -140
- package/dist/chunks/studio-xq0t7n5t.js.map +0 -10
- package/dist/chunks/studio-xze4zrs1.js.map +0 -10
- package/dist/chunks/studio-yfcstdzy.js +0 -399
- package/dist/chunks/studio-yfcstdzy.js.map +0 -12
- package/dist/chunks/studio-zp7jncs8.js +0 -469
- package/dist/chunks/studio-zp7jncs8.js.map +0 -14
- package/dist/chunks/ws-client-q7hkz3j9.js.map +0 -11
- package/src/browse/browse-modal.ts +0 -69
- package/src/browse/browse.ts +0 -904
- package/src/canvas/canvas-diff.ts +0 -216
- package/src/canvas/nested-site-style.ts +0 -58
- package/src/new-project/design-fields.ts +0 -264
- package/src/new-project/templates.ts +0 -61
- package/src/panels/tab-bar.ts +0 -548
- package/src/settings/settings-modal.ts +0 -203
- /package/dist/chunks/{adapter-z8fkmbmv.js.map → adapter-qfnesn5v.js.map} +0 -0
- /package/dist/chunks/{cf-settings-mmyv436d.js.map → cf-settings-73sh5z4p.js.map} +0 -0
- /package/dist/chunks/{format-host-8jxwts2p.js.map → format-host-mkwsmt8d.js.map} +0 -0
- /package/dist/chunks/{jx-validate-qx8degj7.js.map → jx-validate-v6qgknrs.js.map} +0 -0
- /package/dist/chunks/{studio-w7c6kvk7.js.map → studio-a7pzveq1.js.map} +0 -0
- /package/dist/chunks/{studio-esrewxap.js.map → studio-s519zc99.js.map} +0 -0
- /package/dist/chunks/{studio-bghqfmea.js.map → studio-wwseqyw0.js.map} +0 -0
- /package/dist/chunks/{studio-nqw433kv.js.map → studio-xmvbhg31.js.map} +0 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
/**
|
|
3
|
+
* Preferences-keymap.ts — the user's keyboard layer: where it is kept, and what it refuses.
|
|
4
|
+
*
|
|
5
|
+
* `specs/studio.md` §15 rule 2 says the Keyboard sheet is a PROJECTION of the command registry —
|
|
6
|
+
* the same `shortcutReference()` that generates `docs/studio/interface/shortcuts.md` — so it cannot
|
|
7
|
+
* drift from the app. Rebinding must not break that, which is why nothing here writes to a command
|
|
8
|
+
* record. A rebinding is one entry in an override map that `keymap.setOverrides` lays over the
|
|
9
|
+
* declared chords; `keymap.declaredFor(id)` keeps answering what the app ships with, and a reset is
|
|
10
|
+
* the removal of an entry rather than the restoration of a value this module remembered.
|
|
11
|
+
*
|
|
12
|
+
* Three refusals, in the order they are checked, because each one is a different mistake:
|
|
13
|
+
*
|
|
14
|
+
* 1. **Not a chord.** Modifier-only, or a string no keyboard produces.
|
|
15
|
+
* 2. **A bare printable key.** `s` bound globally fires while you are typing your name into a field:
|
|
16
|
+
* the dispatcher is one `document` keydown listener, and only some scopes are narrow enough to
|
|
17
|
+
* hold the key back. So a letter, digit or punctuation needs a modifier; ↑, Esc, F5 and their
|
|
18
|
+
* kind do not, because nothing types them.
|
|
19
|
+
* 3. **Already taken.** The chord is claimed by another command in a scope that can be live at the
|
|
20
|
+
* same instant as this one — `keymap.overlappingScopes`. In the SAME scope that is exactly the
|
|
21
|
+
* condition `keymap.add` throws on at registration (the check that caught ⌘W); across scopes it
|
|
22
|
+
* is the quieter version of the same loss, because every dispatch stack is `[engine, "global"]`
|
|
23
|
+
* and a narrow binding silently shadows a global one for as long as that engine has focus. Two
|
|
24
|
+
* engine scopes (`caret` and `grid`, say) never share a stack and may hold one chord each for
|
|
25
|
+
* ever, so that is allowed — refusing it would forbid the shadowing ladder its whole purpose.
|
|
26
|
+
* Here it must not throw and must not silently win: the result NAMES the command holding the
|
|
27
|
+
* chord AND where that command is live, so the sheet can say who has it and offer to show that
|
|
28
|
+
* row.
|
|
29
|
+
*
|
|
30
|
+
* Storage is `localStorage`, the same place the chrome theme lives, and it is read defensively at
|
|
31
|
+
* every layer: unavailable storage, invalid JSON, a non-array value and an unparseable chord all
|
|
32
|
+
* degrade to "this author has no override for that command" rather than to a keyboard that throws
|
|
33
|
+
* during boot.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { normalizeChord, overlappingScopes, parseChord } from "../commands/keymap";
|
|
37
|
+
import type { KeybindingOverrides } from "../commands/keymap";
|
|
38
|
+
import type { KeyScope } from "../commands/levels";
|
|
39
|
+
import type { AnyCommand, CommandRegistry } from "../commands/registry";
|
|
40
|
+
|
|
41
|
+
/** Where the user's layer is kept. One key, one JSON object of `id → chords`. */
|
|
42
|
+
export const KEYBINDINGS_STORAGE_KEY = "jx.keybindings";
|
|
43
|
+
|
|
44
|
+
/** Keys that type nothing, and so may be bound without a modifier. */
|
|
45
|
+
const UNMODIFIED_KEYS = new Set([
|
|
46
|
+
"escape",
|
|
47
|
+
"enter",
|
|
48
|
+
"tab",
|
|
49
|
+
"backspace",
|
|
50
|
+
"delete",
|
|
51
|
+
"arrowup",
|
|
52
|
+
"arrowdown",
|
|
53
|
+
"arrowleft",
|
|
54
|
+
"arrowright",
|
|
55
|
+
"home",
|
|
56
|
+
"end",
|
|
57
|
+
"pageup",
|
|
58
|
+
"pagedown",
|
|
59
|
+
"insert",
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
const FUNCTION_KEY = /^f([1-9]|1\d|2[0-4])$/;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Where each scope's chords are live, in words a refusal can end with.
|
|
66
|
+
*
|
|
67
|
+
* A cross-scope conflict is otherwise unreadable: "⌘S is already Save" is baffling when the sheet
|
|
68
|
+
* is showing you a canvas command and Save is a global one. The scope is the missing half of the
|
|
69
|
+
* sentence, and it is also the half that tells the author whether they care.
|
|
70
|
+
*/
|
|
71
|
+
const SCOPE_WHERE: Readonly<Record<KeyScope, string>> = {
|
|
72
|
+
global: "everywhere",
|
|
73
|
+
canvas: "on the canvas",
|
|
74
|
+
caret: "while text is being edited",
|
|
75
|
+
grid: "in the data grid",
|
|
76
|
+
code: "in the code editor",
|
|
77
|
+
dock: "in the panels",
|
|
78
|
+
palette: "in the command palette",
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/** The command that already holds a chord — everything the refusal needs to name it. */
|
|
82
|
+
export interface RebindConflict {
|
|
83
|
+
/** Canonical chord, as the user just pressed it. */
|
|
84
|
+
chord: string;
|
|
85
|
+
scope: KeyScope;
|
|
86
|
+
/** The holder. */
|
|
87
|
+
commandId: string;
|
|
88
|
+
/** The holder's title, so the refusal names an action rather than an id. */
|
|
89
|
+
title: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* What a rebind did.
|
|
94
|
+
*
|
|
95
|
+
* A refusal always carries a `reason` fit to show a person, and carries `conflict` when the reason
|
|
96
|
+
* is another command — the case the sheet turns into "show me that one".
|
|
97
|
+
*/
|
|
98
|
+
export type RebindResult = { ok: true } | { ok: false; reason: string; conflict?: RebindConflict };
|
|
99
|
+
|
|
100
|
+
/** Whether a canonical chord may be bound at all. Modifier-less printable keys may not. */
|
|
101
|
+
export function isBindableChord(chord: string): boolean {
|
|
102
|
+
let parsed;
|
|
103
|
+
try {
|
|
104
|
+
parsed = parseChord(chord);
|
|
105
|
+
} catch {
|
|
106
|
+
// Modifier-only, or empty: `parseChord` is the one place that decides what a chord is.
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
if (parsed.mod || parsed.ctrl || parsed.alt) {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
// Shift alone is not enough: ⇧A is how a capital A is typed.
|
|
113
|
+
return UNMODIFIED_KEYS.has(parsed.key) || FUNCTION_KEY.test(parsed.key);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function readRaw(): string {
|
|
117
|
+
try {
|
|
118
|
+
return globalThis.localStorage?.getItem(KEYBINDINGS_STORAGE_KEY) ?? "";
|
|
119
|
+
} catch {
|
|
120
|
+
// Storage unavailable (private mode): every command keeps the chord it declared.
|
|
121
|
+
return "";
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Every chord in one stored entry, canonicalised — or `null` if any of them is not a chord. */
|
|
126
|
+
function canonicalise(chords: unknown): string[] | null {
|
|
127
|
+
if (!Array.isArray(chords)) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
const canonical: string[] = [];
|
|
131
|
+
for (const chord of chords as unknown[]) {
|
|
132
|
+
if (typeof chord !== "string") {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
canonical.push(normalizeChord(chord));
|
|
137
|
+
} catch {
|
|
138
|
+
// A stored string that is not a chord: drop the whole entry rather than half of it.
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return canonical;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* The stored layer, validated.
|
|
147
|
+
*
|
|
148
|
+
* Nothing here trusts the store: it is a JSON blob a user can hand-edit and an older release can
|
|
149
|
+
* have written. An entry that does not survive validation is dropped, and the command keeps its
|
|
150
|
+
* declared chord — which is the only failure mode that leaves the app usable.
|
|
151
|
+
*/
|
|
152
|
+
export function loadKeybindingOverrides(): Map<string, string[]> {
|
|
153
|
+
const layer = new Map<string, string[]>();
|
|
154
|
+
const raw = readRaw();
|
|
155
|
+
if (!raw) {
|
|
156
|
+
return layer;
|
|
157
|
+
}
|
|
158
|
+
let parsed: unknown;
|
|
159
|
+
try {
|
|
160
|
+
parsed = JSON.parse(raw);
|
|
161
|
+
} catch {
|
|
162
|
+
// Corrupt store: the defaults are the whole keymap.
|
|
163
|
+
return layer;
|
|
164
|
+
}
|
|
165
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
166
|
+
return layer;
|
|
167
|
+
}
|
|
168
|
+
for (const [id, chords] of Object.entries(parsed as Record<string, unknown>)) {
|
|
169
|
+
const canonical = canonicalise(chords);
|
|
170
|
+
if (canonical) {
|
|
171
|
+
layer.set(id, canonical);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return layer;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function store(layer: KeybindingOverrides): void {
|
|
178
|
+
try {
|
|
179
|
+
if (layer.size === 0) {
|
|
180
|
+
globalThis.localStorage?.removeItem(KEYBINDINGS_STORAGE_KEY);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
globalThis.localStorage?.setItem(
|
|
184
|
+
KEYBINDINGS_STORAGE_KEY,
|
|
185
|
+
JSON.stringify(Object.fromEntries(layer)),
|
|
186
|
+
);
|
|
187
|
+
} catch {
|
|
188
|
+
// Storage unavailable — the rebinding is live in this window and is not remembered.
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Lay the stored overrides over a registry's keymap.
|
|
194
|
+
*
|
|
195
|
+
* Called from `registerPreferencesCommands`, which is the bootstrap's one contact point with
|
|
196
|
+
* Preferences. Because the layer lives INSIDE the keymap, applying it before the rest of the
|
|
197
|
+
* bootstrap has finished registering is safe: a record registered afterwards is indexed against the
|
|
198
|
+
* layer that is already there.
|
|
199
|
+
*/
|
|
200
|
+
export function applyKeybindingOverrides(registry: CommandRegistry): void {
|
|
201
|
+
registry.keymap.setOverrides(loadKeybindingOverrides());
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Bind `command` to `chord`, or say why not.
|
|
206
|
+
*
|
|
207
|
+
* Rebinding a command back to the chord it declared REMOVES the override rather than pinning the
|
|
208
|
+
* current default: the point of a layer is that a command whose default moves in a later release
|
|
209
|
+
* moves with it unless the author asked for something else.
|
|
210
|
+
*/
|
|
211
|
+
export function rebindCommand(
|
|
212
|
+
registry: CommandRegistry,
|
|
213
|
+
command: AnyCommand,
|
|
214
|
+
chord: string,
|
|
215
|
+
): RebindResult {
|
|
216
|
+
let canonical: string;
|
|
217
|
+
try {
|
|
218
|
+
canonical = normalizeChord(chord);
|
|
219
|
+
} catch {
|
|
220
|
+
return { ok: false, reason: "That is not a shortcut — hold a key as well as the modifiers." };
|
|
221
|
+
}
|
|
222
|
+
if (!isBindableChord(canonical)) {
|
|
223
|
+
return {
|
|
224
|
+
ok: false,
|
|
225
|
+
reason:
|
|
226
|
+
`${registry.keymap.format(canonical)} would fire while you type. ` +
|
|
227
|
+
"Add ⌘, Ctrl or Alt, or use a key that types nothing.",
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
const scope: KeyScope = command.keyScope ?? "global";
|
|
231
|
+
// Every scope this one can share a dispatch stack with, not just its own: a chord claimed in an
|
|
232
|
+
// Overlapping scope is one of the two commands going quiet, and which one it is depends only on
|
|
233
|
+
// Which is narrower. `resolveChord` walks the list in order, so the same scope is named first.
|
|
234
|
+
const held = registry.keymap.resolveChord(canonical, overlappingScopes(scope));
|
|
235
|
+
if (held && held.commandId !== command.id) {
|
|
236
|
+
const holder = registry.get(held.commandId);
|
|
237
|
+
const title = holder?.title ?? held.commandId;
|
|
238
|
+
const where = held.scope === scope ? "" : `, which is live ${SCOPE_WHERE[held.scope]}`;
|
|
239
|
+
return {
|
|
240
|
+
ok: false,
|
|
241
|
+
reason: `${registry.keymap.format(canonical)} is already ${title}${where}.`,
|
|
242
|
+
conflict: { chord: canonical, commandId: held.commandId, scope: held.scope, title },
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
const next = new Map(registry.keymap.overrides());
|
|
246
|
+
const declared = registry.keymap.declaredFor(command.id);
|
|
247
|
+
if (declared.length === 1 && declared[0] === canonical) {
|
|
248
|
+
next.delete(command.id);
|
|
249
|
+
} else {
|
|
250
|
+
next.set(command.id, [canonical]);
|
|
251
|
+
}
|
|
252
|
+
registry.keymap.setOverrides(next);
|
|
253
|
+
store(next);
|
|
254
|
+
return { ok: true };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Drop the override on `commandId`, restoring what the registry declares.
|
|
259
|
+
*
|
|
260
|
+
* @returns Whether there was one to drop, so a caller does not repaint over nothing.
|
|
261
|
+
*/
|
|
262
|
+
export function resetKeybinding(registry: CommandRegistry, commandId: string): boolean {
|
|
263
|
+
const next = new Map(registry.keymap.overrides());
|
|
264
|
+
if (!next.delete(commandId)) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
registry.keymap.setOverrides(next);
|
|
268
|
+
store(next);
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
/**
|
|
3
|
+
* The three Project Settings sections the document adds — Extensions, Deploy and Raw JSON.
|
|
4
|
+
*
|
|
5
|
+
* They live together because each is a small, direct view of ONE `project.json` key and none of
|
|
6
|
+
* them owns a dialog, a picker or a schema form: `extensions` (an array of package names),
|
|
7
|
+
* `build.adapter` (one enum), and the file itself.
|
|
8
|
+
*
|
|
9
|
+
* **Extensions had no UI at all** — plan §9.2's last row — despite `site-context.ts` carrying a
|
|
10
|
+
* dedicated branch to react to the array changing. Adding or removing a package here is therefore
|
|
11
|
+
* the first way to turn an extension on from inside Studio, and the chokepoint's own
|
|
12
|
+
* `refreshExtensionSurfaces()` re-reads the formats and the contributed sections afterwards, so the
|
|
13
|
+
* section list beside this one updates without a reload.
|
|
14
|
+
*
|
|
15
|
+
* **Raw JSON does not open a second editor.** It shows what is on disk and hands the author to the
|
|
16
|
+
* Code editor over the SAME tab — one document, three editors (§9.3), so switching to the text and
|
|
17
|
+
* back keeps one undo stack instead of forking two.
|
|
18
|
+
*
|
|
19
|
+
* @docs studio/projects/settings
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { html, render as litRender, nothing } from "lit-html";
|
|
23
|
+
import { errorMessage } from "@jxsuite/schema/parse";
|
|
24
|
+
import { projectState, updateUi } from "../store";
|
|
25
|
+
import { serializeProjectConfig } from "../tabs/project-config";
|
|
26
|
+
import { tabOfContainer } from "../canvas/canvas-surface";
|
|
27
|
+
import { updateSiteConfig } from "../site-context";
|
|
28
|
+
|
|
29
|
+
import type { ProjectConfig } from "@jxsuite/schema/types";
|
|
30
|
+
|
|
31
|
+
/** The mode the Code editor draws under — `tabs/tab.ts`'s `EDITOR_KIND_BY_MODE` maps it to `code`. */
|
|
32
|
+
const CODE_MODE = "source";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The last failed write per rendered section container.
|
|
36
|
+
*
|
|
37
|
+
* Keyed by container so two mounted copies (and two tests) never share a message — the same shape
|
|
38
|
+
* `general-settings.ts` uses, and for the same reason: these sections save on change, so a silent
|
|
39
|
+
* rejection would read as "my edit just vanished".
|
|
40
|
+
*/
|
|
41
|
+
const errors = new WeakMap<HTMLElement, string>();
|
|
42
|
+
|
|
43
|
+
/** Drop a parked error and re-render. */
|
|
44
|
+
function clearError(container: HTMLElement): void {
|
|
45
|
+
errors.delete(container);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The error line for a section, if its last write failed. */
|
|
49
|
+
function errorLine(container: HTMLElement) {
|
|
50
|
+
const message = errors.get(container);
|
|
51
|
+
return message === undefined
|
|
52
|
+
? nothing
|
|
53
|
+
: html`<p class="settings-field-error" role="alert">${message}</p>`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Persist a patch, surfacing the rejection instead of swallowing it.
|
|
58
|
+
*
|
|
59
|
+
* `updateSiteConfig` rejects on a failed write and the chokepoint has already filed the Problem, so
|
|
60
|
+
* all that is left here is to keep the form honest about what is on disk.
|
|
61
|
+
*
|
|
62
|
+
* @param {HTMLElement} container
|
|
63
|
+
* @param {Partial<ProjectConfig>} patch
|
|
64
|
+
* @param {() => void} rerender
|
|
65
|
+
*/
|
|
66
|
+
async function persist(
|
|
67
|
+
container: HTMLElement,
|
|
68
|
+
patch: Partial<ProjectConfig>,
|
|
69
|
+
rerender: () => void,
|
|
70
|
+
): Promise<void> {
|
|
71
|
+
try {
|
|
72
|
+
await updateSiteConfig(patch);
|
|
73
|
+
clearError(container);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
errors.set(container, `Could not save project.json — ${errorMessage(error)}`);
|
|
76
|
+
}
|
|
77
|
+
rerender();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The live project configuration, or an empty one before a project is open. */
|
|
81
|
+
function config(): ProjectConfig {
|
|
82
|
+
return (projectState?.projectConfig ?? {}) as ProjectConfig;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ─── Extensions ───────────────────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
/** Pending package name per container, so typing survives the section re-rendering. */
|
|
88
|
+
const pendingExtension = new WeakMap<HTMLElement, string>();
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The `extensions` array, as a list you can add to and remove from.
|
|
92
|
+
*
|
|
93
|
+
* @param {HTMLElement} container
|
|
94
|
+
*/
|
|
95
|
+
export function renderExtensionsSection(container: HTMLElement): void {
|
|
96
|
+
const rerender = () => renderExtensionsSection(container);
|
|
97
|
+
const extensions = config().extensions ?? [];
|
|
98
|
+
const pending = pendingExtension.get(container) ?? "";
|
|
99
|
+
|
|
100
|
+
/* Both handlers re-read rather than closing over the render's values. Typing updates the pending
|
|
101
|
+
name WITHOUT a re-render (re-rendering per keystroke would fight the field's own value), so a
|
|
102
|
+
closed-over `pending` would always be whatever it was when the section was last drawn — which
|
|
103
|
+
is "" on the first add. */
|
|
104
|
+
const add = () => {
|
|
105
|
+
const name = (pendingExtension.get(container) ?? "").trim();
|
|
106
|
+
const current = config().extensions ?? [];
|
|
107
|
+
if (!name || current.includes(name)) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
pendingExtension.delete(container);
|
|
111
|
+
void persist(container, { extensions: [...current, name] }, rerender);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const remove = (name: string) => {
|
|
115
|
+
const current = config().extensions ?? [];
|
|
116
|
+
void persist(container, { extensions: current.filter((e) => e !== name) }, rerender);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const tpl = html`
|
|
120
|
+
<div class="settings-section">
|
|
121
|
+
<h3 class="settings-section-title">Extensions</h3>
|
|
122
|
+
<p class="settings-field-desc">
|
|
123
|
+
Packages this project loads. An extension can contribute formats, elements, data connectors
|
|
124
|
+
— and settings sections of its own, which appear in the list beside this one.
|
|
125
|
+
</p>
|
|
126
|
+
${errorLine(container)}
|
|
127
|
+
<div class="settings-list-panel">
|
|
128
|
+
${
|
|
129
|
+
extensions.length === 0
|
|
130
|
+
? html`<div class="settings-empty-state">No extensions.</div>`
|
|
131
|
+
: extensions.map(
|
|
132
|
+
(name) => html`
|
|
133
|
+
<div class="settings-row">
|
|
134
|
+
<span class="settings-row-name">${name}</span>
|
|
135
|
+
<sp-action-button
|
|
136
|
+
size="xs"
|
|
137
|
+
quiet
|
|
138
|
+
title=${`Remove ${name}`}
|
|
139
|
+
@click=${() => remove(name)}
|
|
140
|
+
>
|
|
141
|
+
<sp-icon-delete slot="icon"></sp-icon-delete>
|
|
142
|
+
</sp-action-button>
|
|
143
|
+
</div>
|
|
144
|
+
`,
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
<div class="settings-inline-form">
|
|
148
|
+
<sp-textfield
|
|
149
|
+
size="s"
|
|
150
|
+
class="settings-extension-name"
|
|
151
|
+
placeholder="@scope/package"
|
|
152
|
+
.value=${pending}
|
|
153
|
+
@input=${(e: Event) => {
|
|
154
|
+
pendingExtension.set(container, (e.target as HTMLInputElement).value);
|
|
155
|
+
}}
|
|
156
|
+
@keydown=${(e: KeyboardEvent) => {
|
|
157
|
+
if (e.key === "Enter") {
|
|
158
|
+
add();
|
|
159
|
+
}
|
|
160
|
+
}}
|
|
161
|
+
></sp-textfield>
|
|
162
|
+
<sp-action-button size="s" @click=${add}>Add</sp-action-button>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
`;
|
|
167
|
+
|
|
168
|
+
litRender(tpl, container);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ─── Deploy ───────────────────────────────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
/** The build adapters `@jxsuite/compiler` ships. One enumeration, shared with New Project. */
|
|
174
|
+
export const BUILD_ADAPTERS: readonly { value: string; label: string }[] = [
|
|
175
|
+
{ label: "Static", value: "static" },
|
|
176
|
+
{ label: "Bun", value: "bun" },
|
|
177
|
+
{ label: "Node", value: "node" },
|
|
178
|
+
{ label: "Cloudflare Workers", value: "cloudflare-workers" },
|
|
179
|
+
{ label: "Cloudflare Pages", value: "cloudflare-pages" },
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Where the project is built for, and what it is built with.
|
|
184
|
+
*
|
|
185
|
+
* The adapter moved off Overview: Overview says what the site IS, and the adapter is a fact about
|
|
186
|
+
* where it SHIPS — the same split §2 principle 5 applies to Contexts.
|
|
187
|
+
*
|
|
188
|
+
* @param {HTMLElement} container
|
|
189
|
+
*/
|
|
190
|
+
export function renderDeploySection(container: HTMLElement): void {
|
|
191
|
+
const rerender = () => renderDeploySection(container);
|
|
192
|
+
const current = config().build?.adapter || "static";
|
|
193
|
+
|
|
194
|
+
const onAdapterChange = (e: Event) => {
|
|
195
|
+
void persist(
|
|
196
|
+
container,
|
|
197
|
+
{ build: { ...config().build, adapter: (e.target as HTMLInputElement).value } },
|
|
198
|
+
rerender,
|
|
199
|
+
);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const tpl = html`
|
|
203
|
+
<div class="settings-section">
|
|
204
|
+
<h3 class="settings-section-title">Deploy</h3>
|
|
205
|
+
${errorLine(container)}
|
|
206
|
+
<div class="settings-field">
|
|
207
|
+
<label class="settings-field-label">Platform Adapter</label>
|
|
208
|
+
<p class="settings-field-desc">
|
|
209
|
+
What the site is built for. Static writes plain files; the others emit a server entry for
|
|
210
|
+
that runtime.
|
|
211
|
+
</p>
|
|
212
|
+
<sp-picker
|
|
213
|
+
size="s"
|
|
214
|
+
label="Platform Adapter"
|
|
215
|
+
class="settings-build-adapter"
|
|
216
|
+
.value=${current}
|
|
217
|
+
@change=${onAdapterChange}
|
|
218
|
+
>
|
|
219
|
+
${BUILD_ADAPTERS.map(
|
|
220
|
+
(adapter) => html`<sp-menu-item value=${adapter.value}>${adapter.label}</sp-menu-item>`,
|
|
221
|
+
)}
|
|
222
|
+
</sp-picker>
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
`;
|
|
226
|
+
|
|
227
|
+
litRender(tpl, container);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ─── Raw JSON ─────────────────────────────────────────────────────────────────
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* `project.json` as it is written, and the way into editing it as text.
|
|
234
|
+
*
|
|
235
|
+
* The serialisation is the chokepoint's own (`serializeProjectConfig`), so what this shows is what
|
|
236
|
+
* a save writes — not a second pretty-printer that would disagree with the file by an indent.
|
|
237
|
+
*
|
|
238
|
+
* @param {HTMLElement} container
|
|
239
|
+
*/
|
|
240
|
+
export function renderRawJsonSection(container: HTMLElement): void {
|
|
241
|
+
const tpl = html`
|
|
242
|
+
<div class="settings-section">
|
|
243
|
+
<h3 class="settings-section-title">Raw JSON</h3>
|
|
244
|
+
<p class="settings-field-desc">
|
|
245
|
+
The whole of <code>project.json</code>, as it is saved. Editing it as code opens the same
|
|
246
|
+
document in the Code editor — one undo stack, one dirty flag.
|
|
247
|
+
</p>
|
|
248
|
+
<pre class="settings-raw-json">${serializeProjectConfig(config())}</pre>
|
|
249
|
+
<sp-action-button
|
|
250
|
+
size="s"
|
|
251
|
+
class="settings-edit-as-code"
|
|
252
|
+
@click=${() => updateUi(tabOfContainer(container), "canvasMode", CODE_MODE)}
|
|
253
|
+
>
|
|
254
|
+
Edit as code
|
|
255
|
+
</sp-action-button>
|
|
256
|
+
</div>
|
|
257
|
+
`;
|
|
258
|
+
|
|
259
|
+
litRender(tpl, container);
|
|
260
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Project Settings section registry, and which of its sections the document is showing.
|
|
3
|
+
*
|
|
4
|
+
* `registerSettingsSection` is a **preserved public contract** (specs/extensions.md §9.1): an
|
|
5
|
+
* extension's `project` class declares a `$studio.settings` block and `settings/extension-sections`
|
|
6
|
+
* turns it into one of these records. P6 moved Project Settings out of a modal and into a document
|
|
7
|
+
* in the pane; the registry did not move with it, because a section is a contribution to
|
|
8
|
+
* CONFIGURATION, not to a dialog. Splitting it out is what lets the host be replaced without the
|
|
9
|
+
* contribution point being touched.
|
|
10
|
+
*
|
|
11
|
+
* **This module holds no renderer and imports nothing.** That is load-bearing rather than tidy:
|
|
12
|
+
* `panels/settings-pane.ts` is reached from `canvas/canvas-render.ts`, so anything this module
|
|
13
|
+
* imports becomes part of the canvas's import graph. The section RENDERERS are registered by
|
|
14
|
+
* `settings/settings-document.ts`, which the app loads when it registers its commands.
|
|
15
|
+
*
|
|
16
|
+
* **What is requested and what is displayed are two variables, and unregistering touches neither.**
|
|
17
|
+
* The modal had one, and its `unregisterSettingsSection` reset it to `"general"` — which is half of
|
|
18
|
+
* the deep-link race P6 exists to close. The contribution sync unregisters stale keys before it
|
|
19
|
+
* registers fresh ones, so a section merely being REFRESHED took the caller's requested section
|
|
20
|
+
* down with it, silently, after the awaited readiness promise had already resolved. Here a request
|
|
21
|
+
* outlives its section going away and is satisfied the moment the key comes back.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/** A Project Settings section: an inner-nav entry plus a renderer for the content area. */
|
|
25
|
+
export interface SettingsSection {
|
|
26
|
+
key: string;
|
|
27
|
+
label: string;
|
|
28
|
+
/** Nav icon name (reserved for future nav treatments). */
|
|
29
|
+
icon?: string | undefined;
|
|
30
|
+
/** Sort position — lower orders render higher in the inner nav. */
|
|
31
|
+
order: number;
|
|
32
|
+
render: (container: HTMLElement) => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Where a settings document lands when nobody named a section. */
|
|
36
|
+
export const DEFAULT_SETTINGS_SECTION = "overview";
|
|
37
|
+
|
|
38
|
+
const sections = new Map<string, SettingsSection>();
|
|
39
|
+
|
|
40
|
+
const listeners = new Set<() => void>();
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Tell every subscriber something changed.
|
|
44
|
+
*
|
|
45
|
+
* A `Set` tolerates deletion during its own iteration, so a listener that unsubscribes itself (the
|
|
46
|
+
* pane host, on unmount) is safe without copying.
|
|
47
|
+
*/
|
|
48
|
+
function announce(): void {
|
|
49
|
+
for (const listener of listeners) {
|
|
50
|
+
listener();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Redraw the settings document — for a change this module cannot see, such as a command selecting
|
|
56
|
+
* an entry inside the section that is already on screen.
|
|
57
|
+
*/
|
|
58
|
+
export function notifySettingsDocument(): void {
|
|
59
|
+
announce();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Subscribe to everything that changes what the settings document draws: the section set (an
|
|
64
|
+
* extension registering or going away) and the chosen section. Returns the unsubscribe.
|
|
65
|
+
*
|
|
66
|
+
* @param {() => void} listener
|
|
67
|
+
* @returns {() => void}
|
|
68
|
+
*/
|
|
69
|
+
export function onSettingsDocumentChanged(listener: () => void): () => void {
|
|
70
|
+
listeners.add(listener);
|
|
71
|
+
return () => {
|
|
72
|
+
listeners.delete(listener);
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ─── Registration ─────────────────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Register (or replace) a settings section.
|
|
80
|
+
*
|
|
81
|
+
* @param {SettingsSection} section
|
|
82
|
+
*/
|
|
83
|
+
export function registerSettingsSection(section: SettingsSection): void {
|
|
84
|
+
sections.set(section.key, section);
|
|
85
|
+
announce();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Remove a registered section — used when a descriptor-contributed section's extension is disabled
|
|
90
|
+
* (see ./extension-sections). Built-ins are never unregistered.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} key
|
|
93
|
+
*/
|
|
94
|
+
export function unregisterSettingsSection(key: string): void {
|
|
95
|
+
if (sections.delete(key)) {
|
|
96
|
+
announce();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Registered sections sorted by order (registration order breaks ties). */
|
|
101
|
+
export function sortedSettingsSections(): SettingsSection[] {
|
|
102
|
+
return [...sections.values()].toSorted((a, b) => a.order - b.order);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Every registered section key, built-ins and contributions alike, in display order. */
|
|
106
|
+
export function settingsSectionKeys(): string[] {
|
|
107
|
+
return sortedSettingsSections().map((section) => section.key);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* One section by key, or `undefined`.
|
|
112
|
+
*
|
|
113
|
+
* @param {string} key
|
|
114
|
+
* @returns {SettingsSection | undefined}
|
|
115
|
+
*/
|
|
116
|
+
export function settingsSection(key: string): SettingsSection | undefined {
|
|
117
|
+
return sections.get(key);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ─── Which section is on screen ───────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
/** The section the last caller ASKED for, kept until it is satisfied. */
|
|
123
|
+
let _requested: string | null = null;
|
|
124
|
+
|
|
125
|
+
/** The section the document is drawing. Only a nav click and a satisfied request move it. */
|
|
126
|
+
let _displayed = DEFAULT_SETTINGS_SECTION;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The section key the document should render right now.
|
|
130
|
+
*
|
|
131
|
+
* A pending request wins as soon as its section exists. Otherwise the displayed one, and — when
|
|
132
|
+
* that has been unregistered too — the first section there is, so the content area is never blank
|
|
133
|
+
* because of bookkeeping.
|
|
134
|
+
*
|
|
135
|
+
* @returns {string}
|
|
136
|
+
*/
|
|
137
|
+
export function settingsDocumentSection(): string {
|
|
138
|
+
if (_requested !== null && sections.has(_requested)) {
|
|
139
|
+
_displayed = _requested;
|
|
140
|
+
_requested = null;
|
|
141
|
+
return _displayed;
|
|
142
|
+
}
|
|
143
|
+
if (sections.has(_displayed)) {
|
|
144
|
+
return _displayed;
|
|
145
|
+
}
|
|
146
|
+
return settingsSectionKeys()[0] ?? _displayed;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Show a section. A nav click satisfies itself immediately; a deep link to a section that has not
|
|
151
|
+
* registered yet is remembered until it does.
|
|
152
|
+
*
|
|
153
|
+
* @param {string} key
|
|
154
|
+
*/
|
|
155
|
+
export function setSettingsSection(key: string): void {
|
|
156
|
+
if (sections.has(key)) {
|
|
157
|
+
_displayed = key;
|
|
158
|
+
_requested = null;
|
|
159
|
+
} else {
|
|
160
|
+
_requested = key;
|
|
161
|
+
}
|
|
162
|
+
announce();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Forget the chosen section — the project-close and test hook. */
|
|
166
|
+
export function resetSettingsDocumentState(): void {
|
|
167
|
+
_requested = null;
|
|
168
|
+
_displayed = DEFAULT_SETTINGS_SECTION;
|
|
169
|
+
}
|