@jxsuite/studio 2.0.0 → 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-0hzentwq.js → components-2bpqp4mq.js} +5 -7
- package/dist/chunks/{components-0hzentwq.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-2c188n41.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-97qcgvdn.js → studio-adn0z1k3.js} +14 -8
- package/dist/chunks/{studio-97qcgvdn.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-hhj92gcr.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-ygzav7z5.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-n9zmpxjg.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 +23711 -35185
- 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 +34 -9
- package/src/new-project/location-fields.ts +9 -1
- package/src/new-project/new-project-modal.ts +270 -262
- 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 -120
- 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 +75 -2
- 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 +113 -0
- 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-qct0gt8f.js +0 -531
- package/dist/chunks/extension-sections-qct0gt8f.js.map +0 -12
- package/dist/chunks/files-02zn5y24.js +0 -50
- package/dist/chunks/files-02zn5y24.js.map +0 -9
- package/dist/chunks/git-panel-mbb0y0hz.js +0 -28
- package/dist/chunks/git-panel-mbb0y0hz.js.map +0 -9
- package/dist/chunks/layers-panel-9qekmqk1.js +0 -26
- package/dist/chunks/layers-panel-9qekmqk1.js.map +0 -9
- package/dist/chunks/settings-modal-gj0hrah4.js +0 -29
- package/dist/chunks/settings-modal-gj0hrah4.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-4k0m4fmp.js.map +0 -13
- package/dist/chunks/studio-568nx4t5.js +0 -469
- package/dist/chunks/studio-568nx4t5.js.map +0 -14
- package/dist/chunks/studio-9smfzqhc.js +0 -19
- package/dist/chunks/studio-9smfzqhc.js.map +0 -10
- package/dist/chunks/studio-asvf0j9e.js +0 -429
- package/dist/chunks/studio-asvf0j9e.js.map +0 -13
- package/dist/chunks/studio-d77qm3pk.js +0 -3427
- package/dist/chunks/studio-d77qm3pk.js.map +0 -26
- package/dist/chunks/studio-j6gqyjf4.js +0 -1854
- package/dist/chunks/studio-j6gqyjf4.js.map +0 -18
- package/dist/chunks/studio-n9zmpxjg.js.map +0 -115
- package/dist/chunks/studio-qkvt37c0.js.map +0 -9
- package/dist/chunks/studio-rq0vxb19.js +0 -3744
- package/dist/chunks/studio-rq0vxb19.js.map +0 -22
- package/dist/chunks/studio-wd6ewdvd.js +0 -853
- package/dist/chunks/studio-wd6ewdvd.js.map +0 -12
- 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-ygzav7z5.js.map +0 -46
- package/dist/chunks/studio-zmy7241e.js +0 -399
- package/dist/chunks/studio-zmy7241e.js.map +0 -12
- 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-2c188n41.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-hhj92gcr.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
package/src/panels/toolbar.ts
CHANGED
|
@@ -1,48 +1,78 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* The Command Bar — region ① of UX-REDESIGN-PLAN §3.2, rendered FROM the registry.
|
|
4
|
+
*
|
|
5
|
+
* Every control in this band used to be hand-authored twice: once in `toolbarTemplate` and once in
|
|
6
|
+
* `minimalToolbarTemplate`, a retyped copy of the same buttons with `disabled` hard-coded on each
|
|
7
|
+
* one for the no-project case. That second copy is the canonical example of the defect §2 principle
|
|
8
|
+
* 1 names — a hand-maintained list of actions beside the definition site — and §2 principle 4 says
|
|
9
|
+
* enablement is a PREDICATE with a sentence, never a duplicated template. It is deleted. With no
|
|
10
|
+
* project open the same bar renders; the records' own `when` clauses empty it.
|
|
11
|
+
*
|
|
12
|
+
* What is left is four things, none of which decides what an action is called:
|
|
13
|
+
*
|
|
14
|
+
* - {@link tbCmd} renders one command: title, icon, `title="Save (⌘S)"` with the chord formatted for
|
|
15
|
+
* THIS platform by `keymap.format` (the predecessor hardcoded `⌘P` and showed it to Windows and
|
|
16
|
+
* Linux users), disabled state from `enablement`, and the `requires` sentence in the tooltip when
|
|
17
|
+
* it is off — so no control is ever permanently dead with no explanation.
|
|
18
|
+
* - The primary verb cluster is `forPlacement("commandbar/primary")`, capped at five by
|
|
19
|
+
* `scripts/check-chrome-budget.ts`; the ⬢ menu is `forPlacement("commandbar/overflow")`.
|
|
20
|
+
* - The **Command Center pill** (①a) is the app's address bar: `◈ project › document › selection`,
|
|
21
|
+
* right-aligned ⌘K, each segment opening the palette pre-scoped. It replaces the Open Project
|
|
22
|
+
* split button, the recents dropdown and the `Search files… ⌘P` trigger, and it gives Studio a
|
|
23
|
+
* persistent project name for the first time — today it renders only in the Files panel header,
|
|
24
|
+
* and the desktop titlebar is `titleBarStyle:"hidden"`.
|
|
25
|
+
* - The window controls, which are the one thing in here that is not an action.
|
|
26
|
+
*
|
|
27
|
+
* **Retired here, with a name, a chord and a residue** (§2 principle 9): Open Project + New Project
|
|
28
|
+
* + recents → the pill and `Project: Open Recent…`; `Manage` → `File: Browse Library`; `Publish` →
|
|
29
|
+
* the `Publish:` family; `Sync Project` → Source Control. The five-mode switcher leaves for the
|
|
30
|
+
* pane context bar (region ⑦) and is reachable meanwhile as `View: Set Canvas Mode` in the palette,
|
|
31
|
+
* which prompts for the mode from the record's own `args` enum.
|
|
5
32
|
*/
|
|
6
33
|
|
|
7
34
|
import { html, render as litRender, nothing } from "lit-html";
|
|
8
|
-
import { openPublishPanel } from "../publish/publish-panel";
|
|
9
|
-
import { updateSession } from "../store";
|
|
10
|
-
import {
|
|
11
|
-
canRedo as tabCanRedo,
|
|
12
|
-
canUndo as tabCanUndo,
|
|
13
|
-
redo as tabRedo,
|
|
14
|
-
undo as tabUndo,
|
|
15
|
-
} from "../tabs/transact";
|
|
16
|
-
import { collabState } from "../collab/collab-state";
|
|
17
35
|
import { presenceChipsTemplate } from "../collab/presence-chips";
|
|
18
36
|
import { effect, effectScope } from "../reactivity";
|
|
19
37
|
import { activeTab } from "../workspace/workspace";
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
38
|
+
import { primarySelection } from "../tabs/selection";
|
|
39
|
+
import { shell } from "../shell";
|
|
22
40
|
import { openQuickSearch } from "./quick-search";
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
41
|
+
import { showPromptDialog } from "../ui/layers";
|
|
42
|
+
import { getPlatform, hasPlatform } from "../platform";
|
|
43
|
+
import { getPreviewNavigateHandler } from "../canvas/preview-navigate";
|
|
44
|
+
import { documentUrlPattern, dynamicRouteParams } from "../page-params";
|
|
45
|
+
import { getNodeAtPath, nodeLabel, projectState } from "../store";
|
|
46
|
+
import { activeRegistry } from "../commands/active-registry";
|
|
47
|
+
import { notify } from "../services/notify";
|
|
48
|
+
import type { Tab } from "../tabs/tab";
|
|
49
|
+
import type { SiteBuildResult } from "../types";
|
|
50
|
+
import type { CommandRegistry } from "../commands/registry";
|
|
27
51
|
import type { EffectScope } from "@vue/reactivity";
|
|
28
52
|
import type { TemplateResult } from "lit-html";
|
|
29
53
|
|
|
30
|
-
|
|
31
|
-
|
|
54
|
+
/**
|
|
55
|
+
* What the Command Bar is handed at mount.
|
|
56
|
+
*
|
|
57
|
+
* HANDOFF: **nothing here is read any more** — every control in the band is a command, so the bar
|
|
58
|
+
* asks the registry rather than the bootstrap. The fields stay declared, and optional, so
|
|
59
|
+
* `studio.ts`'s `toolbarPanel.mount(toolbarEl, { … })` object literal keeps type-checking; deleting
|
|
60
|
+
* them is one edit in that file, which is another workstream's this wave.
|
|
61
|
+
*/
|
|
62
|
+
export interface ToolbarCtx {
|
|
63
|
+
openProject?: () => void;
|
|
32
64
|
openFile?: (path: string) => void;
|
|
33
|
-
saveFile
|
|
34
|
-
getCanvasMode
|
|
35
|
-
setCanvasMode:
|
|
36
|
-
renderCanvas
|
|
37
|
-
safeRenderRightPanel
|
|
38
|
-
openRecentProject
|
|
39
|
-
closeFunctionEditor
|
|
65
|
+
saveFile?: () => void;
|
|
66
|
+
getCanvasMode?: () => string;
|
|
67
|
+
setCanvasMode?: (tab: Tab | null, mode: string) => void;
|
|
68
|
+
renderCanvas?: () => void;
|
|
69
|
+
safeRenderRightPanel?: () => void;
|
|
70
|
+
openRecentProject?: (root: string) => Promise<void>;
|
|
71
|
+
closeFunctionEditor?: () => void;
|
|
40
72
|
}
|
|
41
73
|
|
|
42
74
|
let _rootEl: HTMLElement | null = null;
|
|
43
75
|
|
|
44
|
-
let _ctx: ToolbarCtx | null = null;
|
|
45
|
-
|
|
46
76
|
/** Test override for the mac CSD layout — happy-dom forbids redefining navigator.platform. */
|
|
47
77
|
let _isMacOverride: boolean | null = null;
|
|
48
78
|
|
|
@@ -58,535 +88,652 @@ function isMacPlatform(): boolean {
|
|
|
58
88
|
|
|
59
89
|
let _scope: EffectScope | null = null;
|
|
60
90
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Icon key → Spectrum icon, for the `icon` a command record declares.
|
|
93
|
+
*
|
|
94
|
+
* The RECORD names the icon; this map only knows how to draw one. Keys are the record's vocabulary
|
|
95
|
+
* ("save", "undo"), not Spectrum tag names, so swapping the icon set is a change here and nowhere
|
|
96
|
+
* else — and a record that names an icon this bar cannot draw renders as a labelled button rather
|
|
97
|
+
* than as an empty one.
|
|
98
|
+
*/
|
|
99
|
+
const COMMAND_ICONS: Readonly<Record<string, TemplateResult>> = {
|
|
100
|
+
browser: html`<sp-icon-export slot="icon"></sp-icon-export>`,
|
|
101
|
+
redo: html`<sp-icon-redo slot="icon"></sp-icon-redo>`,
|
|
102
|
+
save: html`<sp-icon-save-floppy slot="icon"></sp-icon-save-floppy>`,
|
|
103
|
+
undo: html`<sp-icon-undo slot="icon"></sp-icon-undo>`,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// ─── One command, one control ─────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
/** How {@link tbCmd} draws a record. Presentation only — never what the record means. */
|
|
109
|
+
export interface TbCmdOptions {
|
|
110
|
+
/** Icon-only, with the title as the accessible name. Used by the dock toggles. */
|
|
111
|
+
compact?: boolean;
|
|
112
|
+
/** Rendered pressed. The dock toggles are the only controls with an on-state. */
|
|
113
|
+
selected?: boolean;
|
|
114
|
+
/** Override the record's icon — a dock toggle's glyph depends on which way the dock is. */
|
|
115
|
+
icon?: TemplateResult;
|
|
116
|
+
}
|
|
78
117
|
|
|
79
118
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
119
|
+
* The tooltip a control shows: the action's name, plus its chord, or plus WHY it is off.
|
|
120
|
+
*
|
|
121
|
+
* One string, three sources, all from the record: `title`, `keymap.formatBinding` and `requires`.
|
|
122
|
+
* `tbBtnTpl(label, onClick, icon)` — the predecessor — could express none of them, which is why
|
|
123
|
+
* "Open in Browser" had to hand-build its own disabled variant with a bespoke reason string.
|
|
83
124
|
*/
|
|
84
|
-
function
|
|
125
|
+
export function commandTooltip(registry: CommandRegistry, id: string): string {
|
|
126
|
+
const command = registry.get(id);
|
|
127
|
+
if (!command) {
|
|
128
|
+
return "";
|
|
129
|
+
}
|
|
130
|
+
const reason = registry.disabledReason(id);
|
|
131
|
+
if (reason) {
|
|
132
|
+
return `${command.title} — requires ${reason}`;
|
|
133
|
+
}
|
|
134
|
+
const chord = registry.keymap.formatBinding(id);
|
|
135
|
+
return chord ? `${command.title} (${chord})` : command.title;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Render one command as a Command Bar button, or `nothing` when its `when` hides it.
|
|
140
|
+
*
|
|
141
|
+
* This is the whole of §5.5's first row: label, icon, tooltip, chord, disabled state and disabled
|
|
142
|
+
* reason all come off the record, so the bar cannot disagree with the palette, the keymap or the
|
|
143
|
+
* agent about any of them.
|
|
144
|
+
*/
|
|
145
|
+
export function tbCmd(registry: CommandRegistry, id: string, options: TbCmdOptions = {}) {
|
|
146
|
+
const command = registry.get(id);
|
|
147
|
+
if (!command || !registry.isVisible(id)) {
|
|
148
|
+
return nothing;
|
|
149
|
+
}
|
|
150
|
+
const enabled = registry.isEnabled(id);
|
|
151
|
+
const icon = options.icon ?? (command.icon ? COMMAND_ICONS[command.icon] : undefined);
|
|
85
152
|
return html`
|
|
86
|
-
<sp-action-button
|
|
87
|
-
|
|
153
|
+
<sp-action-button
|
|
154
|
+
size="s"
|
|
155
|
+
?quiet=${options.compact === true}
|
|
156
|
+
?selected=${options.selected === true}
|
|
157
|
+
title=${commandTooltip(registry, id)}
|
|
158
|
+
aria-label=${command.title}
|
|
159
|
+
?disabled=${!enabled}
|
|
160
|
+
@click=${() => {
|
|
161
|
+
void registry.run(id);
|
|
162
|
+
}}
|
|
163
|
+
>
|
|
164
|
+
${icon ?? nothing}
|
|
165
|
+
${options.compact ? nothing : html`<span class="tb-label">${command.title}</span>`}
|
|
88
166
|
</sp-action-button>
|
|
89
167
|
`;
|
|
90
168
|
}
|
|
91
169
|
|
|
170
|
+
// ─── View: Open in Browser ───────────────────────────────────────────────────
|
|
171
|
+
|
|
92
172
|
/**
|
|
93
|
-
*
|
|
173
|
+
* Where `View: Open in Browser` would go — the site-relative PATH, not a URL — or the sentence
|
|
174
|
+
* explaining why it cannot go anywhere.
|
|
94
175
|
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* parseMediaEntries, getCanvasMode, setCanvasMode, renderCanvas, safeRenderRightPanel }
|
|
176
|
+
* A path rather than a URL because the origin is not this command's to know: the built site is
|
|
177
|
+
* served by the backend, on a port of its own, and the build call is what names it.
|
|
98
178
|
*/
|
|
99
|
-
export
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
179
|
+
export type BrowserTarget = { path: string } | { reason: string };
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Resolve the active document to the ROUTE its built page will be published at.
|
|
183
|
+
*
|
|
184
|
+
* The path mirrors the compiler's own `documentUrlPattern` plus the project's `trailingSlash`
|
|
185
|
+
* setting, so it is the URL every link inside the built site already points at — which is exactly
|
|
186
|
+
* why the reader can then browse from it.
|
|
187
|
+
*
|
|
188
|
+
* Everything that is not a page resolves to a REASON rather than to nothing: a disabled control the
|
|
189
|
+
* user can hover is discoverable, an absent one is not.
|
|
190
|
+
*/
|
|
191
|
+
export function openInBrowserTarget(tab: Tab | null): BrowserTarget {
|
|
192
|
+
const documentPath = tab?.documentPath?.replace(/^\.\//, "");
|
|
193
|
+
if (!documentPath) {
|
|
194
|
+
return { reason: "Open a page to view it in a browser." };
|
|
107
195
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
196
|
+
if (!projectState?.isSiteProject) {
|
|
197
|
+
return { reason: "This project does not build a site." };
|
|
198
|
+
}
|
|
199
|
+
if (!documentPath.startsWith("pages/")) {
|
|
200
|
+
return { reason: `Only pages have a route — ${documentPath} is not under pages/.` };
|
|
201
|
+
}
|
|
202
|
+
let route = documentUrlPattern(documentPath);
|
|
203
|
+
if (route.includes("*")) {
|
|
204
|
+
return { reason: "Catch-all routes match many pages — open a generated one instead." };
|
|
205
|
+
}
|
|
206
|
+
const params = dynamicRouteParams(documentPath);
|
|
207
|
+
if (params.length > 0) {
|
|
208
|
+
const chosen = (tab?.session.ui.previewParams ?? {}) as Record<string, string>;
|
|
209
|
+
const missing = params.filter((name) => !chosen[name]);
|
|
210
|
+
if (missing.length > 0) {
|
|
211
|
+
const names = missing.map((name) => `:${name}`).join(", ");
|
|
212
|
+
return { reason: `Pick a value for ${names} to open one of this route's pages.` };
|
|
213
|
+
}
|
|
214
|
+
route = route.replaceAll(/:(\w+)/g, (_m, name: string) => encodeURIComponent(chosen[name]!));
|
|
215
|
+
}
|
|
216
|
+
/* The page's URL, not its file's path.
|
|
217
|
+
This used to answer `${origin}/dist${route}/index.html` — the compiler's OUTPUT PATH — and the
|
|
218
|
+
browser then did exactly what a built page's own markup tells it to: a stylesheet at
|
|
219
|
+
`/components/demo.css` and a link to `/basics/counter` are ROOT-absolute, so from a `/dist/…`
|
|
220
|
+
URL the first 404s against the server root and the second leaves the site. Measured before the
|
|
221
|
+
fix: the page 200, its CSS 404, its first link 404. */
|
|
222
|
+
const trailingSlash = projectState.projectConfig?.build?.trailingSlash ?? "always";
|
|
223
|
+
return { path: route === "/" ? "/" : trailingSlash === "always" ? `${route}/` : route };
|
|
131
224
|
}
|
|
132
225
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
226
|
+
/**
|
|
227
|
+
* Hand a URL to the user's real browser.
|
|
228
|
+
*
|
|
229
|
+
* Reuses the seam the desktop launchers already register for Preview link clicks
|
|
230
|
+
* (`canvas/preview-navigate.ts`), which routes through the OS rather than navigating a webview with
|
|
231
|
+
* no address bar; the browser build falls back to a new tab.
|
|
232
|
+
*/
|
|
233
|
+
function openUrlExternally(url: string) {
|
|
234
|
+
const override = getPreviewNavigateHandler();
|
|
235
|
+
if (override) {
|
|
236
|
+
override(url);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
window.open(url, "_blank", "noopener,noreferrer");
|
|
138
240
|
}
|
|
139
241
|
|
|
140
|
-
|
|
141
|
-
|
|
242
|
+
/**
|
|
243
|
+
* Run `View: Open in Browser`, reporting the blocking reason when there is one.
|
|
244
|
+
*
|
|
245
|
+
* Exported as the implementation the bootstrap hands to the record — the ⌘⇧O chord is the record's
|
|
246
|
+
* `keybinding` now, so the bespoke `document.addEventListener("keydown", …)` this file used to
|
|
247
|
+
* install (with its own `isModalOpen()` guard, its own shift test and no way to be rebound) is
|
|
248
|
+
* deleted.
|
|
249
|
+
*/
|
|
250
|
+
export async function runOpenInBrowser() {
|
|
251
|
+
const target = openInBrowserTarget(activeTab.value ?? null);
|
|
252
|
+
if (!("path" in target)) {
|
|
253
|
+
notify.warn(target.reason, { key: OPEN_IN_BROWSER, source: "Preview" });
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
/* Build first, because the reader is about to see the OUTPUT and the author is looking at the
|
|
257
|
+
DOCUMENT. Without this they differ by however long it has been since anyone ran a build —
|
|
258
|
+
which for most projects is "always", since nothing in Studio had ever written the output at
|
|
259
|
+
all. A preview that quietly shows last week's page is worse than one that says it cannot open.
|
|
260
|
+
|
|
261
|
+
The build is also what NAMES the origin. The built site is served on a port of its own, because
|
|
262
|
+
the editor's own paths mean the project's sources while a built page means its output by the
|
|
263
|
+
same paths — so a backend that cannot build cannot preview either, and says so instead of
|
|
264
|
+
sending the reader to an address where half the site is the wrong file. */
|
|
265
|
+
const platform = hasPlatform() ? getPlatform() : null;
|
|
266
|
+
if (!platform?.buildSite) {
|
|
267
|
+
notify.warn("This backend cannot build a preview of the site.", {
|
|
268
|
+
key: OPEN_IN_BROWSER,
|
|
269
|
+
source: "Preview",
|
|
270
|
+
});
|
|
142
271
|
return;
|
|
143
272
|
}
|
|
273
|
+
notify.info("Building the site…", { key: OPEN_IN_BROWSER, source: "Preview" });
|
|
274
|
+
let result: SiteBuildResult;
|
|
144
275
|
try {
|
|
145
|
-
|
|
276
|
+
result = await platform.buildSite();
|
|
146
277
|
} catch (error) {
|
|
147
|
-
|
|
278
|
+
notify.error(`The site could not be built: ${errorText(error)}`, {
|
|
279
|
+
key: OPEN_IN_BROWSER,
|
|
280
|
+
source: "Preview",
|
|
281
|
+
});
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (!result.url) {
|
|
285
|
+
notify.warn("The site was built, but this backend serves no preview of it.", {
|
|
286
|
+
key: OPEN_IN_BROWSER,
|
|
287
|
+
source: "Preview",
|
|
288
|
+
});
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (result.errors.length > 0) {
|
|
292
|
+
/* Named, and the page still opens. A partial build produced pages, and the author can see the
|
|
293
|
+
one they asked for while reading what failed — which is the whole difference between a
|
|
294
|
+
preview and a build log. */
|
|
295
|
+
notify.warn(`The site built with ${result.errors.length} error(s): ${result.errors[0]}`, {
|
|
296
|
+
key: OPEN_IN_BROWSER,
|
|
297
|
+
source: "Preview",
|
|
298
|
+
});
|
|
299
|
+
} else {
|
|
300
|
+
notify.success(`Built ${result.routes} page(s).`, { key: OPEN_IN_BROWSER, source: "Preview" });
|
|
301
|
+
}
|
|
302
|
+
openUrlExternally(`${result.url}${target.path}`);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** One notification key for the whole flow, so building → built → failed replaces rather than piles. */
|
|
306
|
+
const OPEN_IN_BROWSER = "view.openInBrowser";
|
|
307
|
+
|
|
308
|
+
/** The sentence in an unknown thrown value. */
|
|
309
|
+
function errorText(error: unknown): string {
|
|
310
|
+
return error instanceof Error ? error.message : String(error);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// ─── ①a The Command Center pill ──────────────────────────────────────────────
|
|
314
|
+
|
|
315
|
+
/** One segment of the address, and the palette mode it opens. */
|
|
316
|
+
interface PillSegment {
|
|
317
|
+
key: string;
|
|
318
|
+
label: string;
|
|
319
|
+
title: string;
|
|
320
|
+
onClick: () => void;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** The document's label: its path without the project root, which is already segment one. */
|
|
324
|
+
export function documentSegmentLabel(tab: Tab | null): string {
|
|
325
|
+
const path = tab?.documentPath;
|
|
326
|
+
if (!path) {
|
|
327
|
+
return "No document";
|
|
328
|
+
}
|
|
329
|
+
const root = projectState?.projectRoot;
|
|
330
|
+
const trimmed = path.replace(/^\.\//, "");
|
|
331
|
+
return root && trimmed.startsWith(`${root}/`) ? trimmed.slice(root.length + 1) : trimmed;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** The selection's label — the Outline's own `nodeLabel`, so the two never disagree. */
|
|
335
|
+
export function selectionSegmentLabel(tab: Tab | null): string {
|
|
336
|
+
if (shell.layoutSelection) {
|
|
337
|
+
return "layout";
|
|
338
|
+
}
|
|
339
|
+
const paths = tab?.session.selection ?? [];
|
|
340
|
+
const selection = primarySelection(paths);
|
|
341
|
+
if (!tab || !selection) {
|
|
342
|
+
return "";
|
|
343
|
+
}
|
|
344
|
+
// A batch is not a place, so the address bar names its SIZE rather than one of its members —
|
|
345
|
+
// Printing the primary's tag would say `section` while five other elements were also selected.
|
|
346
|
+
if (paths.length > 1) {
|
|
347
|
+
return `${paths.length} elements`;
|
|
348
|
+
}
|
|
349
|
+
return nodeLabel(getNodeAtPath(tab.doc.document, selection));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* `◈ project › document › selection`, right-aligned ⌘K.
|
|
354
|
+
*
|
|
355
|
+
* Four facts about where you are, in non-collapsible chrome (§4.4), each one click-through to the
|
|
356
|
+
* surface that owns it. The empty space between the segments opens the mode picker, which is what
|
|
357
|
+
* makes the pill an address bar rather than three buttons.
|
|
358
|
+
*/
|
|
359
|
+
function commandCenterTpl(registry: CommandRegistry | null) {
|
|
360
|
+
const tab = activeTab.value ?? null;
|
|
361
|
+
const chord = registry?.keymap.formatBinding("palette.open") ?? "";
|
|
362
|
+
const selection = selectionSegmentLabel(tab);
|
|
363
|
+
const segments: PillSegment[] = [
|
|
364
|
+
{
|
|
365
|
+
key: "project",
|
|
366
|
+
label: projectState?.name ?? "No project",
|
|
367
|
+
title: "Switch project — opens Project: Open Recent…",
|
|
368
|
+
onClick: () => openQuickSearch("projects"),
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
key: "document",
|
|
372
|
+
label: documentSegmentLabel(tab),
|
|
373
|
+
title: "Go to a file",
|
|
374
|
+
onClick: () => openQuickSearch("files"),
|
|
375
|
+
},
|
|
376
|
+
];
|
|
377
|
+
if (selection) {
|
|
378
|
+
segments.push({
|
|
379
|
+
key: "selection",
|
|
380
|
+
label: selection,
|
|
381
|
+
title: "Go to an element in this document",
|
|
382
|
+
onClick: () => openQuickSearch("nodes"),
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
return html`
|
|
386
|
+
<div
|
|
387
|
+
class="tb-center"
|
|
388
|
+
role="group"
|
|
389
|
+
aria-label="Command Center"
|
|
390
|
+
@click=${() => openQuickSearch("picker")}
|
|
391
|
+
>
|
|
392
|
+
<span class="tb-center-mark" aria-hidden="true">◈</span>
|
|
393
|
+
${segments.map(
|
|
394
|
+
(segment, index) => html`
|
|
395
|
+
${index > 0 ? html`<span class="tb-center-sep" aria-hidden="true">›</span>` : nothing}
|
|
396
|
+
<button
|
|
397
|
+
class="tb-center-seg"
|
|
398
|
+
type="button"
|
|
399
|
+
title=${segment.title}
|
|
400
|
+
@click=${(e: Event) => {
|
|
401
|
+
e.stopPropagation();
|
|
402
|
+
segment.onClick();
|
|
403
|
+
}}
|
|
404
|
+
>
|
|
405
|
+
${segment.label}
|
|
406
|
+
</button>
|
|
407
|
+
`,
|
|
408
|
+
)}
|
|
409
|
+
${chord ? html`<kbd class="tb-center-chord">${chord}</kbd>` : nothing}
|
|
410
|
+
</div>
|
|
411
|
+
`;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// ─── ①b Layout tabs ──────────────────────────────────────────────────────────
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* `Write · Design · Build · Ship · +` — named arrangements, as plain-text tabs.
|
|
418
|
+
*
|
|
419
|
+
* The 80% of a workspace switcher that costs a segmented control and none of the re-architecture
|
|
420
|
+
* (§3.2 ①b). Each tab RUNS `view.setLayout`, so the click, the palette row and an agent all take
|
|
421
|
+
* the same path; double-clicking one renames it, and `+` saves whatever is on screen now.
|
|
422
|
+
*
|
|
423
|
+
* **A layout reconfigures; it never removes.** Nothing here hides a panel: every one stays on the
|
|
424
|
+
* rail, on its chord and in the palette after any layout is applied — §13 is explicit that
|
|
425
|
+
* workspaces which gate features hand the non-technical user the affordance they are least likely
|
|
426
|
+
* to reach for.
|
|
427
|
+
*/
|
|
428
|
+
function layoutTabsTpl(registry: CommandRegistry) {
|
|
429
|
+
// `get` first, like {@link tbCmd}: a registry that has not been handed the shell's records yet
|
|
430
|
+
// (the skeleton the bar paints before the bootstrap composes them) has no verb to ask about.
|
|
431
|
+
if (!registry.get("view.setLayout") || !registry.isEnabled("view.setLayout")) {
|
|
432
|
+
return nothing;
|
|
433
|
+
}
|
|
434
|
+
return html`
|
|
435
|
+
<div class="tb-layouts" role="tablist" aria-label="Layouts">
|
|
436
|
+
${shell.layouts.map(
|
|
437
|
+
(preset) => html`
|
|
438
|
+
<button
|
|
439
|
+
class=${preset.id === shell.layout ? "tb-layout active" : "tb-layout"}
|
|
440
|
+
type="button"
|
|
441
|
+
role="tab"
|
|
442
|
+
aria-selected=${preset.id === shell.layout ? "true" : "false"}
|
|
443
|
+
title=${`${preset.name} layout — double-click to rename`}
|
|
444
|
+
@click=${() => {
|
|
445
|
+
void registry.run("view.setLayout", { layout: preset.id });
|
|
446
|
+
}}
|
|
447
|
+
@dblclick=${() => {
|
|
448
|
+
void renameLayoutPrompt(registry, preset.id, preset.name);
|
|
449
|
+
}}
|
|
450
|
+
>
|
|
451
|
+
${preset.name}
|
|
452
|
+
</button>
|
|
453
|
+
`,
|
|
454
|
+
)}
|
|
455
|
+
<button
|
|
456
|
+
class="tb-layout-add"
|
|
457
|
+
type="button"
|
|
458
|
+
title="Save the current arrangement as a layout"
|
|
459
|
+
aria-label="Save layout"
|
|
460
|
+
@click=${() => {
|
|
461
|
+
void saveLayoutPrompt(registry);
|
|
462
|
+
}}
|
|
463
|
+
>
|
|
464
|
+
+
|
|
465
|
+
</button>
|
|
466
|
+
</div>
|
|
467
|
+
`;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/** Ask for a name, then run the command. Exported for the same reason `runOpenInBrowser` is. */
|
|
471
|
+
export async function saveLayoutPrompt(registry: CommandRegistry): Promise<void> {
|
|
472
|
+
const name = await showPromptDialog("Save layout", {
|
|
473
|
+
confirmLabel: "Save",
|
|
474
|
+
message: "Remembers this project's Navigator panel, dock widths and Inspector tab.",
|
|
475
|
+
placeholder: "Layout name",
|
|
476
|
+
});
|
|
477
|
+
if (name) {
|
|
478
|
+
await registry.run("view.saveLayout", { name });
|
|
148
479
|
}
|
|
149
480
|
}
|
|
150
481
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
482
|
+
/** The double-click gesture, routed through the command so the rename has one implementation. */
|
|
483
|
+
export async function renameLayoutPrompt(
|
|
484
|
+
registry: CommandRegistry,
|
|
485
|
+
layout: string,
|
|
486
|
+
current: string,
|
|
487
|
+
): Promise<void> {
|
|
488
|
+
const name = await showPromptDialog("Rename layout", { confirmLabel: "Rename", value: current });
|
|
489
|
+
if (name) {
|
|
490
|
+
await registry.run("view.renameLayout", { layout, name });
|
|
155
491
|
}
|
|
156
492
|
}
|
|
157
493
|
|
|
494
|
+
// ─── The ⬢ app menu (commandbar/overflow) ────────────────────────────────────
|
|
495
|
+
|
|
158
496
|
/**
|
|
159
|
-
*
|
|
160
|
-
* remove affordance), and a clear-all action. Shared by both the minimal and full toolbars.
|
|
497
|
+
* Everything that declared `commandbar/overflow` — the chrome's residue for retired controls.
|
|
161
498
|
*
|
|
162
|
-
*
|
|
499
|
+
* Rendered as a menu of the records themselves, so a command that moves from the primary cluster to
|
|
500
|
+
* the overflow keeps its name, its chord and its gate, and the move is one edit to its `menus`.
|
|
163
501
|
*/
|
|
164
|
-
function
|
|
165
|
-
const
|
|
502
|
+
function appMenuTpl(registry: CommandRegistry) {
|
|
503
|
+
const commands = registry.forPlacement("commandbar/overflow");
|
|
166
504
|
return html`
|
|
167
505
|
<overlay-trigger placement="bottom-start" triggered-by="click">
|
|
168
|
-
<sp-action-button size="s" slot="trigger" title="
|
|
169
|
-
<sp-icon-
|
|
506
|
+
<sp-action-button size="s" quiet slot="trigger" title="Studio menu" aria-label="Studio menu">
|
|
507
|
+
<sp-icon-show-menu slot="icon"></sp-icon-show-menu>
|
|
170
508
|
</sp-action-button>
|
|
171
509
|
<sp-popover slot="click-content" tip>
|
|
172
510
|
<sp-menu
|
|
173
511
|
@change=${(e: Event) => {
|
|
174
|
-
const
|
|
175
|
-
if (
|
|
176
|
-
void
|
|
177
|
-
} else if (val === "__clear__") {
|
|
178
|
-
clearRecentProjects();
|
|
179
|
-
render();
|
|
180
|
-
} else {
|
|
181
|
-
void ctx.openRecentProject(val);
|
|
512
|
+
const id = (e.target as unknown as HTMLInputElement).value;
|
|
513
|
+
if (registry.isEnabled(id)) {
|
|
514
|
+
void registry.run(id);
|
|
182
515
|
}
|
|
183
516
|
}}
|
|
184
517
|
>
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
size="s"
|
|
198
|
-
title="Remove from recent"
|
|
199
|
-
@click=${(e: Event) => {
|
|
200
|
-
e.stopPropagation();
|
|
201
|
-
removeRecentProject(p.root);
|
|
202
|
-
render();
|
|
203
|
-
}}
|
|
204
|
-
>
|
|
205
|
-
<sp-icon-close slot="icon"></sp-icon-close>
|
|
206
|
-
</sp-action-button>
|
|
207
|
-
</sp-menu-item>
|
|
208
|
-
`,
|
|
209
|
-
)}
|
|
210
|
-
<sp-menu-divider></sp-menu-divider>
|
|
211
|
-
<sp-menu-item value="__clear__">Clear recent projects</sp-menu-item>
|
|
212
|
-
`
|
|
213
|
-
: nothing
|
|
214
|
-
}
|
|
518
|
+
${commands.map(
|
|
519
|
+
(command) => html`
|
|
520
|
+
<sp-menu-item
|
|
521
|
+
value=${command.id}
|
|
522
|
+
?disabled=${!registry.isEnabled(command.id)}
|
|
523
|
+
title=${commandTooltip(registry, command.id)}
|
|
524
|
+
>
|
|
525
|
+
${command.title}
|
|
526
|
+
<span slot="value">${registry.keymap.formatBinding(command.id) ?? ""}</span>
|
|
527
|
+
</sp-menu-item>
|
|
528
|
+
`,
|
|
529
|
+
)}
|
|
215
530
|
</sp-menu>
|
|
216
531
|
</sp-popover>
|
|
217
532
|
</overlay-trigger>
|
|
218
533
|
`;
|
|
219
534
|
}
|
|
220
535
|
|
|
221
|
-
|
|
222
|
-
function minimalToolbarTemplate(ctx: ToolbarCtx) {
|
|
223
|
-
const recentProjectsTpl = recentMenuTpl(ctx);
|
|
224
|
-
|
|
225
|
-
const windowControls = (
|
|
226
|
-
globalThis as unknown as {
|
|
227
|
-
__jxPlatform?: {
|
|
228
|
-
windowControls?: {
|
|
229
|
-
minimize: () => void;
|
|
230
|
-
maximize: () => void;
|
|
231
|
-
close: () => void;
|
|
232
|
-
};
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
).__jxPlatform?.windowControls;
|
|
236
|
-
const csdTpl = windowControls
|
|
237
|
-
? html`
|
|
238
|
-
<sp-action-group class="window-controls" size="s">
|
|
239
|
-
<sp-action-button
|
|
240
|
-
quiet
|
|
241
|
-
size="s"
|
|
242
|
-
title="Minimize"
|
|
243
|
-
@click=${() => windowControls.minimize()}
|
|
244
|
-
>
|
|
245
|
-
<sp-icon-remove slot="icon"></sp-icon-remove>
|
|
246
|
-
</sp-action-button>
|
|
247
|
-
<sp-action-button
|
|
248
|
-
quiet
|
|
249
|
-
size="s"
|
|
250
|
-
title="Maximize"
|
|
251
|
-
@click=${() => windowControls.maximize()}
|
|
252
|
-
>
|
|
253
|
-
<sp-icon-rectangle slot="icon"></sp-icon-rectangle>
|
|
254
|
-
</sp-action-button>
|
|
255
|
-
<sp-action-button
|
|
256
|
-
quiet
|
|
257
|
-
size="s"
|
|
258
|
-
title="Close"
|
|
259
|
-
class="csd-close"
|
|
260
|
-
@click=${() => windowControls.close()}
|
|
261
|
-
>
|
|
262
|
-
<sp-icon-close slot="icon"></sp-icon-close>
|
|
263
|
-
</sp-action-button>
|
|
264
|
-
</sp-action-group>
|
|
265
|
-
`
|
|
266
|
-
: nothing;
|
|
536
|
+
// ─── Window controls ──────────────────────────────────────────────────────────
|
|
267
537
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
${toolbarIconMap["sp-icon-save-floppy"]}<span class="tb-label">Save</span>
|
|
283
|
-
</sp-action-button>
|
|
284
|
-
<sp-action-group compact size="s">
|
|
285
|
-
<sp-action-button size="s" title="Undo" disabled>
|
|
286
|
-
${toolbarIconMap["sp-icon-undo"]}<span class="tb-label">Undo</span>
|
|
287
|
-
</sp-action-button>
|
|
288
|
-
<sp-action-button size="s" title="Redo" disabled>
|
|
289
|
-
${toolbarIconMap["sp-icon-redo"]}<span class="tb-label">Redo</span>
|
|
290
|
-
</sp-action-button>
|
|
291
|
-
</sp-action-group>
|
|
292
|
-
<div class="tb-spacer"></div>
|
|
538
|
+
interface WindowControls {
|
|
539
|
+
minimize: () => void;
|
|
540
|
+
maximize: () => void;
|
|
541
|
+
close: () => void;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function windowControls(): WindowControls | undefined {
|
|
545
|
+
return (globalThis as unknown as { __jxPlatform?: { windowControls?: WindowControls } })
|
|
546
|
+
.__jxPlatform?.windowControls;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/** Client-side decorations. Mac puts them leading and close-first; everything else trailing. */
|
|
550
|
+
function csdTpl(controls: WindowControls, mac: boolean) {
|
|
551
|
+
const minimize = html`
|
|
293
552
|
<sp-action-button
|
|
294
|
-
class="tb-search-trigger"
|
|
295
|
-
size="s"
|
|
296
553
|
quiet
|
|
297
|
-
|
|
298
|
-
|
|
554
|
+
size="s"
|
|
555
|
+
title="Minimize"
|
|
556
|
+
class="csd-minimize"
|
|
557
|
+
@click=${() => controls.minimize()}
|
|
299
558
|
>
|
|
300
|
-
<sp-icon-
|
|
301
|
-
<span class="tb-search-label">Search files… <kbd>⌘P</kbd></span>
|
|
559
|
+
<sp-icon-remove slot="icon"></sp-icon-remove>
|
|
302
560
|
</sp-action-button>
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
${modes.map(
|
|
306
|
-
(m) => html`
|
|
307
|
-
<sp-action-button size="s" title=${m.label} disabled ?selected=${m.key === "design"}>
|
|
308
|
-
${toolbarIconMap[m.iconTag]}<span class="tb-label">${m.label}</span>
|
|
309
|
-
</sp-action-button>
|
|
310
|
-
`,
|
|
311
|
-
)}
|
|
312
|
-
</sp-action-group>
|
|
561
|
+
`;
|
|
562
|
+
const maximize = html`
|
|
313
563
|
<sp-action-button
|
|
314
564
|
quiet
|
|
315
565
|
size="s"
|
|
316
|
-
title="
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
applyPanelCollapse();
|
|
320
|
-
render();
|
|
321
|
-
}}
|
|
566
|
+
title="Maximize"
|
|
567
|
+
class="csd-maximize"
|
|
568
|
+
@click=${() => controls.maximize()}
|
|
322
569
|
>
|
|
323
|
-
|
|
324
|
-
view.rightPanelCollapsed
|
|
325
|
-
? html`<sp-icon-rail-right-open slot="icon"></sp-icon-rail-right-open>`
|
|
326
|
-
: html`<sp-icon-rail-right-close slot="icon"></sp-icon-rail-right-close>`
|
|
327
|
-
}
|
|
570
|
+
<sp-icon-rectangle slot="icon"></sp-icon-rectangle>
|
|
328
571
|
</sp-action-button>
|
|
329
|
-
${chatToggleTpl()} ${csdTpl}
|
|
330
572
|
`;
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
/** Toggle for the persistent AI chat sidebar (selected = open). Shared by both layouts. */
|
|
334
|
-
function chatToggleTpl() {
|
|
335
|
-
return html`
|
|
573
|
+
const close = html`
|
|
336
574
|
<sp-action-button
|
|
337
575
|
quiet
|
|
338
576
|
size="s"
|
|
339
|
-
title="
|
|
340
|
-
|
|
341
|
-
@click=${() =>
|
|
342
|
-
view.chatPanelCollapsed = !view.chatPanelCollapsed;
|
|
343
|
-
applyPanelCollapse();
|
|
344
|
-
render();
|
|
345
|
-
}}
|
|
577
|
+
title="Close"
|
|
578
|
+
class="csd-close"
|
|
579
|
+
@click=${() => controls.close()}
|
|
346
580
|
>
|
|
347
|
-
<sp-icon-
|
|
581
|
+
<sp-icon-close slot="icon"></sp-icon-close>
|
|
348
582
|
</sp-action-button>
|
|
349
583
|
`;
|
|
584
|
+
return mac
|
|
585
|
+
? html`<sp-action-group class="window-controls mac" size="s">
|
|
586
|
+
${close}${minimize}${maximize}
|
|
587
|
+
</sp-action-group>`
|
|
588
|
+
: html`<sp-action-group class="window-controls" size="s">
|
|
589
|
+
${minimize}${maximize}${close}
|
|
590
|
+
</sp-action-group>`;
|
|
350
591
|
}
|
|
351
592
|
|
|
352
|
-
|
|
353
|
-
{ iconTag: "sp-icon-edit", key: "edit", label: "Edit" },
|
|
354
|
-
{ iconTag: "sp-icon-artboard", key: "design", label: "Design" },
|
|
355
|
-
{ iconTag: "sp-icon-view-grid", key: "grid", label: "Grid" },
|
|
356
|
-
{ iconTag: "sp-icon-code", key: "source", label: "Code" },
|
|
357
|
-
{ iconTag: "sp-icon-brush", key: "stylebook", label: "Stylebook" },
|
|
358
|
-
];
|
|
593
|
+
// ─── The three dock toggles ───────────────────────────────────────────────────
|
|
359
594
|
|
|
360
|
-
|
|
361
|
-
const tab = activeTab.value;
|
|
362
|
-
if (!_ctx) {
|
|
363
|
-
return html``;
|
|
364
|
-
}
|
|
365
|
-
const ctx = _ctx;
|
|
595
|
+
/* The window, with THIS dock's rail marked — one shipped glyph per region.
|
|
366
596
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
597
|
+
The pair these replace was `rail-right-open`/`close` under `scaleX(-1)`, because the left-hand
|
|
598
|
+
pair looked absent. It is not: `rail-left`, `rail-right` and `rail-bottom` are all in the
|
|
599
|
+
workflow set, and they are a better vocabulary than an arrow besides. An arrow says which way
|
|
600
|
+
something will move and nothing about what or where, which is why the Bottom dock could never
|
|
601
|
+
join the set and carried a static `align-bottom` instead.
|
|
370
602
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
ctx.setCanvasMode(m.key);
|
|
408
|
-
view.panX = 0;
|
|
409
|
-
view.panY = 0;
|
|
410
|
-
/** @type {{ editingFunction: null; rightTab?: string }} */
|
|
411
|
-
const uiPatch: { editingFunction: null; rightTab?: string } = {
|
|
412
|
-
editingFunction: null,
|
|
413
|
-
};
|
|
414
|
-
if (m.key === "stylebook") {
|
|
415
|
-
uiPatch.rightTab = "style";
|
|
416
|
-
}
|
|
417
|
-
updateSession({ ui: uiPatch });
|
|
418
|
-
ctx.renderCanvas();
|
|
419
|
-
ctx.safeRenderRightPanel();
|
|
420
|
-
}}
|
|
421
|
-
>
|
|
422
|
-
${toolbarIconMap[m.iconTag]}<span class="tb-label">${m.label}</span>
|
|
423
|
-
</sp-action-button>
|
|
424
|
-
`,
|
|
425
|
-
)}
|
|
426
|
-
</sp-action-group>
|
|
603
|
+
The mirror was also worth deleting on its own terms. Spectrum's two right-hand glyphs are exact
|
|
604
|
+
mirror images OF EACH OTHER, so flipping the wrong member of the pair lands on the other
|
|
605
|
+
member's appearance — a real, crisp arrow pointing the wrong way, looking entirely deliberate.
|
|
606
|
+
It shipped crossed and nothing could see it. Three distinct glyphs cannot fail that way.
|
|
607
|
+
|
|
608
|
+
State stays where it already was and where it is already asserted: `?selected` on the button. */
|
|
609
|
+
const DOCK_ICONS = {
|
|
610
|
+
bottom: html`<sp-icon-rail-bottom slot="icon"></sp-icon-rail-bottom>`,
|
|
611
|
+
left: html`<sp-icon-rail-left slot="icon"></sp-icon-rail-left>`,
|
|
612
|
+
right: html`<sp-icon-rail-right slot="icon"></sp-icon-rail-right>`,
|
|
613
|
+
} as const;
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* ▤▥▦ — the three docks, each rendered from its own record.
|
|
617
|
+
*
|
|
618
|
+
* The glyph flips with the dock's state and `?selected` reports it, so the control says which way
|
|
619
|
+
* it will go; the NAME and the chord still come from the record, which is why ⌘B and this button
|
|
620
|
+
* cannot drift apart the way ⌘W and the tab strip's × did.
|
|
621
|
+
*/
|
|
622
|
+
function dockTogglesTpl(registry: CommandRegistry) {
|
|
623
|
+
return html`
|
|
624
|
+
${tbCmd(registry, "view.toggleNavigator", {
|
|
625
|
+
compact: true,
|
|
626
|
+
icon: DOCK_ICONS.left,
|
|
627
|
+
selected: !shell.docks.left.collapsed,
|
|
628
|
+
})}
|
|
629
|
+
${tbCmd(registry, "view.toggleInspector", {
|
|
630
|
+
compact: true,
|
|
631
|
+
icon: DOCK_ICONS.right,
|
|
632
|
+
selected: !shell.docks.right.collapsed,
|
|
633
|
+
})}
|
|
634
|
+
${tbCmd(registry, "view.toggleBottomDock", {
|
|
635
|
+
compact: true,
|
|
636
|
+
icon: DOCK_ICONS.bottom,
|
|
637
|
+
selected: !shell.docks.bottom.collapsed,
|
|
638
|
+
})}
|
|
427
639
|
`;
|
|
640
|
+
}
|
|
428
641
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
)
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
quiet
|
|
496
|
-
size="s"
|
|
497
|
-
title="Close"
|
|
498
|
-
class="csd-close"
|
|
499
|
-
@click=${() => windowControls.close()}
|
|
500
|
-
>
|
|
501
|
-
<sp-icon-close slot="icon"></sp-icon-close>
|
|
502
|
-
</sp-action-button>
|
|
503
|
-
</sp-action-group>
|
|
504
|
-
`
|
|
505
|
-
: nothing;
|
|
506
|
-
|
|
507
|
-
const recentProjectsTpl = recentMenuTpl(ctx);
|
|
642
|
+
// ─── Mount ────────────────────────────────────────────────────────────────────
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Mount the Command Bar.
|
|
646
|
+
*
|
|
647
|
+
* @param rootEl The `#toolbar` host, stamped `commandbar` by `stampShellRegions()`.
|
|
648
|
+
* @param _ctx Ignored — see {@link ToolbarCtx}.
|
|
649
|
+
*/
|
|
650
|
+
export function mount(rootEl: HTMLElement, _ctx: ToolbarCtx = {}) {
|
|
651
|
+
_rootEl = rootEl;
|
|
652
|
+
if (windowControls()) {
|
|
653
|
+
rootEl.classList.add("electrobun-webkit-app-region-drag");
|
|
654
|
+
}
|
|
655
|
+
_scope = effectScope();
|
|
656
|
+
_scope.run(() => {
|
|
657
|
+
effect(() => {
|
|
658
|
+
// Dock visibility, source control and the project are shell state, tracked here so the band
|
|
659
|
+
// Follows a flip made from anywhere — the automation runner, the New Project agent hand-off,
|
|
660
|
+
// The boot-time restore — not just this module's click handlers.
|
|
661
|
+
void shell.docks.left.collapsed;
|
|
662
|
+
void shell.docks.right.collapsed;
|
|
663
|
+
void shell.docks.bottom.collapsed;
|
|
664
|
+
void shell.git.status;
|
|
665
|
+
void shell.layoutSelection;
|
|
666
|
+
// The layout tabs are a rendering of the project's own record (§3.2 ①b), so the band
|
|
667
|
+
// Repaints when a layout is saved, renamed or deleted from anywhere.
|
|
668
|
+
void shell.layout;
|
|
669
|
+
void shell.layouts;
|
|
670
|
+
// The registry itself is reactive state: it is composed AFTER this mount runs, and reading it
|
|
671
|
+
// Here is what repaints the band from a skeleton into the real bar.
|
|
672
|
+
void activeRegistry();
|
|
673
|
+
const tab = activeTab.value;
|
|
674
|
+
if (tab) {
|
|
675
|
+
void tab.doc.document;
|
|
676
|
+
void tab.doc.dirty;
|
|
677
|
+
void tab.doc.mode;
|
|
678
|
+
// The whole SET, joined — a bare property read would not re-trigger when the selection
|
|
679
|
+
// Changes WITHIN the array, and §6.5's helpers always replace it but nothing enforces that.
|
|
680
|
+
void tab.session.selection.map((path) => path.join("/")).join("|");
|
|
681
|
+
void tab.session.ui.canvasMode;
|
|
682
|
+
// Open in Browser needs a value for every route param before it can resolve a page.
|
|
683
|
+
void tab.session.ui.previewParams;
|
|
684
|
+
void tab.history.index;
|
|
685
|
+
void tab.history.snapshots.length;
|
|
686
|
+
}
|
|
687
|
+
render();
|
|
688
|
+
});
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
export function unmount() {
|
|
693
|
+
_scope?.stop();
|
|
694
|
+
_scope = null;
|
|
695
|
+
_rootEl = null;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
export function render() {
|
|
699
|
+
if (!_rootEl) {
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
try {
|
|
703
|
+
litRender(toolbarTemplate(), _rootEl);
|
|
704
|
+
} catch (error) {
|
|
705
|
+
console.error("toolbar render error:", error);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
508
708
|
|
|
709
|
+
/**
|
|
710
|
+
* The band.
|
|
711
|
+
*
|
|
712
|
+
* ONE template for every state. With no project open the records' `when` clauses empty the primary
|
|
713
|
+
* cluster and the pill reads "No project"; there is no second variant to keep in step.
|
|
714
|
+
*/
|
|
715
|
+
function toolbarTemplate() {
|
|
716
|
+
const registry = activeRegistry();
|
|
717
|
+
const controls = windowControls();
|
|
718
|
+
const mac = isMacPlatform();
|
|
719
|
+
const csd = controls ? csdTpl(controls, mac) : nothing;
|
|
720
|
+
const tab = activeTab.value ?? null;
|
|
509
721
|
return html`
|
|
510
|
-
${
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
class="tb-split-main"
|
|
515
|
-
title="Open Project"
|
|
516
|
-
@click=${ctx.openProject}
|
|
517
|
-
>
|
|
518
|
-
${toolbarIconMap["sp-icon-folder-open"]}<span class="tb-label">Open Project</span>
|
|
519
|
-
</sp-action-button>
|
|
520
|
-
${recentProjectsTpl}
|
|
521
|
-
</div>
|
|
522
|
-
${tbBtnTpl("Manage", openBrowseModal, "sp-icon-view-list")}
|
|
523
|
-
${tbBtnTpl("Publish", openPublishPanel)}
|
|
524
|
-
<sp-action-button size="s" title="Save" ?disabled=${!canSave} @click=${ctx.saveFile}>
|
|
525
|
-
${toolbarIconMap["sp-icon-save-floppy"]}<span class="tb-label">Save</span>
|
|
526
|
-
</sp-action-button>
|
|
527
|
-
<sp-action-group compact size="s">
|
|
528
|
-
<sp-action-button
|
|
529
|
-
size="s"
|
|
530
|
-
title="Undo"
|
|
531
|
-
?disabled=${!canUndo}
|
|
532
|
-
@click=${() => tabUndo(activeTab.value!)}
|
|
533
|
-
>
|
|
534
|
-
${toolbarIconMap["sp-icon-undo"]}<span class="tb-label">Undo</span>
|
|
535
|
-
</sp-action-button>
|
|
536
|
-
<sp-action-button
|
|
537
|
-
size="s"
|
|
538
|
-
title="Redo"
|
|
539
|
-
?disabled=${!canRedo}
|
|
540
|
-
@click=${() => tabRedo(activeTab.value!)}
|
|
541
|
-
>
|
|
542
|
-
${toolbarIconMap["sp-icon-redo"]}<span class="tb-label">Redo</span>
|
|
543
|
-
</sp-action-button>
|
|
544
|
-
</sp-action-group>
|
|
545
|
-
${presenceChipsTemplate(tab)}
|
|
722
|
+
${mac ? csd : nothing} ${registry ? appMenuTpl(registry) : nothing}
|
|
723
|
+
${registry ? layoutTabsTpl(registry) : nothing}
|
|
724
|
+
<div class="tb-spacer"></div>
|
|
725
|
+
${commandCenterTpl(registry)}
|
|
546
726
|
<div class="tb-spacer"></div>
|
|
547
|
-
<sp-action-button
|
|
548
|
-
class="tb-search-trigger"
|
|
549
|
-
size="s"
|
|
550
|
-
quiet
|
|
551
|
-
title="Search files (⌘P)"
|
|
552
|
-
@click=${openQuickSearch}
|
|
553
|
-
>
|
|
554
|
-
<sp-icon-search slot="icon"></sp-icon-search>
|
|
555
|
-
<span class="tb-search-label">Search files… <kbd>⌘P</kbd></span>
|
|
556
|
-
</sp-action-button>
|
|
557
727
|
${
|
|
558
|
-
|
|
559
|
-
? html`<sp-action-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
await refreshGitStatus();
|
|
565
|
-
}}
|
|
566
|
-
>
|
|
567
|
-
<sp-icon-download slot="icon"></sp-icon-download>
|
|
568
|
-
<span class="tb-label">Sync Project</span>
|
|
569
|
-
</sp-action-button>`
|
|
728
|
+
registry
|
|
729
|
+
? html`<sp-action-group compact size="s">
|
|
730
|
+
${registry
|
|
731
|
+
.forPlacement("commandbar/primary")
|
|
732
|
+
.map((command) => tbCmd(registry, command.id))}
|
|
733
|
+
</sp-action-group>`
|
|
570
734
|
: nothing
|
|
571
735
|
}
|
|
572
|
-
|
|
573
|
-
${
|
|
574
|
-
<sp-action-button
|
|
575
|
-
quiet
|
|
576
|
-
size="s"
|
|
577
|
-
title="Toggle Right Panel"
|
|
578
|
-
@click=${() => {
|
|
579
|
-
view.rightPanelCollapsed = !view.rightPanelCollapsed;
|
|
580
|
-
applyPanelCollapse();
|
|
581
|
-
render();
|
|
582
|
-
}}
|
|
583
|
-
>
|
|
584
|
-
${
|
|
585
|
-
view.rightPanelCollapsed
|
|
586
|
-
? html`<sp-icon-rail-right-open slot="icon"></sp-icon-rail-right-open>`
|
|
587
|
-
: html`<sp-icon-rail-right-close slot="icon"></sp-icon-rail-right-close>`
|
|
588
|
-
}
|
|
589
|
-
</sp-action-button>
|
|
590
|
-
${chatToggleTpl()} ${isMac ? nothing : csdTpl}
|
|
736
|
+
${tab ? presenceChipsTemplate(tab) : nothing} ${registry ? dockTogglesTpl(registry) : nothing}
|
|
737
|
+
${mac ? nothing : csd}
|
|
591
738
|
`;
|
|
592
739
|
}
|