@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/studio.ts
CHANGED
|
@@ -12,11 +12,9 @@
|
|
|
12
12
|
* registration) on first use by source mode, the function editor, or the formula workspace.
|
|
13
13
|
*/
|
|
14
14
|
import { errorMessage } from "@jxsuite/schema/parse";
|
|
15
|
+
import { getPanel } from "./panels/panel-registry";
|
|
15
16
|
|
|
16
17
|
import {
|
|
17
|
-
canvasWrap,
|
|
18
|
-
chatPanelEl,
|
|
19
|
-
getNodeAtPath,
|
|
20
18
|
initShellRefs,
|
|
21
19
|
projectState,
|
|
22
20
|
registerRenderer,
|
|
@@ -25,36 +23,62 @@ import {
|
|
|
25
23
|
setProjectState,
|
|
26
24
|
toolbarEl,
|
|
27
25
|
updateSession,
|
|
28
|
-
updateUi,
|
|
29
26
|
} from "./store";
|
|
30
27
|
|
|
31
28
|
import {
|
|
29
|
+
PRIMARY_PANE,
|
|
32
30
|
activeTab,
|
|
33
31
|
closeAllTabs,
|
|
34
32
|
openTab,
|
|
33
|
+
paneById,
|
|
34
|
+
receivingPane,
|
|
35
|
+
registerTabCommands,
|
|
35
36
|
setWorkspaceProject,
|
|
36
37
|
workspace,
|
|
37
38
|
} from "./workspace/workspace";
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
39
|
+
import { derivationCommands, installDerivationEffects } from "./workspace/pane-derive";
|
|
40
|
+
import type { DerivationDeps } from "./workspace/pane-derive";
|
|
41
|
+
import { effect, effectScope } from "./reactivity";
|
|
42
|
+
import type { EffectScope } from "@vue/reactivity";
|
|
40
43
|
|
|
41
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
flushSession,
|
|
46
|
+
markSessionRestored,
|
|
47
|
+
mountShell,
|
|
48
|
+
registerShellViewCommands,
|
|
49
|
+
resetProjectShell,
|
|
50
|
+
setActivityTab,
|
|
51
|
+
shell,
|
|
52
|
+
} from "./shell";
|
|
42
53
|
|
|
43
54
|
import { isEditing } from "./editor/inline-edit";
|
|
44
|
-
import { applyTransform,
|
|
55
|
+
import { applyTransform, registerCanvasViewCommands } from "./canvas/canvas-utils";
|
|
56
|
+
import type { CanvasSurface } from "./canvas/canvas-surface";
|
|
45
57
|
import {
|
|
46
58
|
initCanvasRender,
|
|
59
|
+
registerSelectionSetCommand,
|
|
47
60
|
renderCanvas,
|
|
48
61
|
renderOverlays,
|
|
49
62
|
scheduleCanvasRender,
|
|
50
63
|
} from "./canvas/canvas-render";
|
|
64
|
+
import {
|
|
65
|
+
canvasModeOfPane,
|
|
66
|
+
canvasModeOfTab,
|
|
67
|
+
stageContaining,
|
|
68
|
+
surfaceForPane,
|
|
69
|
+
tabOfPane,
|
|
70
|
+
} from "./canvas/canvas-surface";
|
|
51
71
|
import { consumePatchedDocument, initCanvasPatcher } from "./canvas/canvas-patcher";
|
|
52
72
|
import {
|
|
73
|
+
setKeymapSource,
|
|
53
74
|
commitActiveEditSession,
|
|
54
75
|
allowAutoRequestsOnNextRender,
|
|
55
76
|
getEditSnapshot,
|
|
77
|
+
isCaretActive,
|
|
56
78
|
postColorSchemeToLiveHosts,
|
|
79
|
+
postLocaleToLiveHosts,
|
|
57
80
|
setCanvasContextMenuHandler,
|
|
81
|
+
setCanvasPointerDownHandler,
|
|
58
82
|
setCanvasSlashHandler,
|
|
59
83
|
setIframePatchEscalation,
|
|
60
84
|
setFileDropHandler,
|
|
@@ -65,13 +89,11 @@ import {
|
|
|
65
89
|
import { runInsertZoneAction } from "./editor/insert-zone-action";
|
|
66
90
|
import { canvasSlashHandler } from "./editor/canvas-slash-bridge";
|
|
67
91
|
import { makeCanvasContextMenuHandler } from "./editor/canvas-context-menu";
|
|
68
|
-
import {
|
|
69
|
-
import {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
statusMessage,
|
|
74
|
-
} from "./panels/statusbar";
|
|
92
|
+
import { mountStatusbar, renderStatusbar } from "./panels/statusbar";
|
|
93
|
+
import { mountJumpBar } from "./panels/jump-bar";
|
|
94
|
+
import { cellForPane } from "./panels/pane-grid";
|
|
95
|
+
import { notify } from "./services/notify";
|
|
96
|
+
import { beginActivity } from "./panels/activity-panel";
|
|
75
97
|
import { exportFile, parseSourceForPath, saveFile, serializeDocument } from "./files/file-ops";
|
|
76
98
|
import {
|
|
77
99
|
formatForPath,
|
|
@@ -85,37 +107,41 @@ import {
|
|
|
85
107
|
renderFilesTemplate as _renderFilesTemplate,
|
|
86
108
|
findHomePage,
|
|
87
109
|
loadDirectory,
|
|
110
|
+
openFileInPane,
|
|
88
111
|
openFileInTab,
|
|
89
|
-
|
|
112
|
+
openLastSessionOrHome,
|
|
90
113
|
registerFileTreeDnD,
|
|
91
114
|
reloadCleanTab,
|
|
92
115
|
setupTreeKeyboard,
|
|
93
116
|
} from "./files/files";
|
|
94
117
|
import { startFsSync } from "./files/fs-events";
|
|
118
|
+
import { invalidateParamValues } from "./page-params";
|
|
95
119
|
import {
|
|
96
120
|
configureCollabNotifier,
|
|
97
121
|
configureCollabParser,
|
|
98
122
|
configureCollabSerializer,
|
|
99
123
|
} from "./collab/collab-session";
|
|
100
124
|
import { renderImportsTemplate } from "./panels/imports-panel";
|
|
101
|
-
import { renderHeadTemplate } from "./panels/head-panel";
|
|
125
|
+
import { invalidateLayoutPickerCache, renderHeadTemplate } from "./panels/head-panel";
|
|
102
126
|
import { exportCemManifest as _exportCemManifest } from "./services/cem-export";
|
|
103
127
|
import { installAutomationHook } from "./services/automation";
|
|
104
|
-
import {
|
|
105
|
-
import { invalidateBrowseCache } from "./browse/browse";
|
|
128
|
+
import { invalidateLibrary } from "./browse/library-pane";
|
|
106
129
|
import { invalidateMediaCache } from "./ui/media-picker";
|
|
107
130
|
import { setMediaChangedHandler } from "./files/media-upload";
|
|
108
131
|
import { applyFileDrop } from "./editor/file-drop-action";
|
|
109
|
-
import { seedAssistantMessages } from "./panels/ai-panel";
|
|
132
|
+
import { assistantCommands, isAssistantStreaming, seedAssistantMessages } from "./panels/ai-panel";
|
|
110
133
|
import { seedPublishConnected } from "./publish/publish-panel";
|
|
111
|
-
import { openConnectorGrid } from "./grid/grid-open";
|
|
112
134
|
|
|
113
135
|
import { getPlatform, hasPlatform, registerPlatform } from "./platform";
|
|
114
136
|
import { parseMediaEntries } from "./utils/canvas-media";
|
|
115
137
|
import { resolveDefaultPlatform } from "./platforms/default-platform";
|
|
116
138
|
import { mountResizeEdges } from "./resize-edges";
|
|
117
|
-
import {
|
|
118
|
-
|
|
139
|
+
import {
|
|
140
|
+
defBadgeLabel,
|
|
141
|
+
defCategory,
|
|
142
|
+
registerSignalsCommands,
|
|
143
|
+
renderSignalsTemplate,
|
|
144
|
+
} from "./panels/signals-panel";
|
|
119
145
|
import { loadComponentRegistry } from "./files/components";
|
|
120
146
|
import { ensureDependenciesInstalled } from "./packages/ensure-deps";
|
|
121
147
|
import { maybePromptJxsuiteUpdate } from "./packages/jxsuite-update";
|
|
@@ -124,8 +150,14 @@ import { autoSyncProjectOnOpen } from "./packages/pull-package-sync";
|
|
|
124
150
|
import { html, render as litRender } from "lit-html";
|
|
125
151
|
|
|
126
152
|
import webdata from "../data/webdata.json";
|
|
127
|
-
import {
|
|
128
|
-
import {
|
|
153
|
+
import { registerDataExplorerCommands } from "./panels/data-explorer";
|
|
154
|
+
import {
|
|
155
|
+
cleanupGitPanel,
|
|
156
|
+
cloneRepository,
|
|
157
|
+
loadDiffForLens,
|
|
158
|
+
registerSourceControlCommands,
|
|
159
|
+
renderGitPanel,
|
|
160
|
+
} from "./panels/git-panel";
|
|
129
161
|
|
|
130
162
|
// ─── Spectrum Web Components ──────────────────────────────────────────────────
|
|
131
163
|
// Explicit class imports + registration — bare side-effect imports are tree-shaken
|
|
@@ -134,31 +166,42 @@ import { components as _swc } from "./ui/spectrum";
|
|
|
134
166
|
import "./ui/panel-resize.js";
|
|
135
167
|
// Built-in schema-form controls (schema-builder, secret) register on import
|
|
136
168
|
import "./ui/form-controls.js";
|
|
137
|
-
import { initLayers,
|
|
138
|
-
import { initShortcuts } from "./editor/shortcuts";
|
|
139
|
-
import {
|
|
169
|
+
import { initLayers, isModalOpen } from "./ui/layers";
|
|
170
|
+
import { initShortcuts, registerStudioCommands } from "./editor/shortcuts";
|
|
171
|
+
import type { ProjectOpenOutcome, ProjectOpenTarget } from "./editor/shortcuts";
|
|
172
|
+
import { createCommandRegistry } from "./commands/registry";
|
|
173
|
+
import { chordsInScopes } from "./commands/keymap";
|
|
174
|
+
import { FRAME_KEY_SCOPES } from "./canvas/iframe-keys";
|
|
175
|
+
import { createLiveContext } from "./commands/live-context";
|
|
176
|
+
import { hasAiCredentials } from "./services/ai-models";
|
|
177
|
+
import { mount as mountActivityBar } from "./panels/activity-bar";
|
|
140
178
|
import * as toolbarPanel from "./panels/toolbar";
|
|
141
179
|
import * as overlaysPanel from "./panels/overlays";
|
|
142
180
|
import * as frontmatterPanelMod from "./panels/frontmatter-panel";
|
|
143
181
|
import * as rightPanelMod from "./panels/right-panel";
|
|
144
182
|
import * as chatPanelMod from "./panels/chat-panel";
|
|
145
183
|
import { setProjectAdopter } from "./services/project-adoption";
|
|
184
|
+
import { tabBufferUnsaved } from "./services/monaco-buffer";
|
|
146
185
|
import * as leftPanelMod from "./panels/left-panel";
|
|
147
186
|
import * as tabStrip from "./panels/tab-strip";
|
|
148
|
-
import * as
|
|
187
|
+
import * as paneContext from "./panels/pane-context";
|
|
149
188
|
import { selectStylebookTag } from "./panels/stylebook-panel";
|
|
150
189
|
import { registerLayersDnD, registerComponentsDnD, registerElementsDnD } from "./panels/dnd";
|
|
151
190
|
import { registerCanvasDndBridge } from "./panels/canvas-dnd-bridge";
|
|
152
191
|
import { defaultDef } from "./panels/shared";
|
|
153
|
-
import {
|
|
192
|
+
import { registerFormulaEditorCommands } from "./panels/formula-workspace";
|
|
193
|
+
import { closeFunctionEditor } from "./panels/editors";
|
|
154
194
|
import {
|
|
195
|
+
formatCommands,
|
|
155
196
|
initBlockActionBar,
|
|
156
197
|
isEditChromeTarget,
|
|
198
|
+
registerSelectionCommands,
|
|
199
|
+
releaseBlockActionBar,
|
|
157
200
|
renderBlockActionBar,
|
|
201
|
+
suppressBlockActionBar,
|
|
158
202
|
} from "./panels/block-action-bar";
|
|
159
203
|
import { initCssData } from "./panels/style-utils";
|
|
160
|
-
import { initQuickSearch
|
|
161
|
-
import { openSettingsModal } from "./settings/settings-modal";
|
|
204
|
+
import { initQuickSearch } from "./panels/quick-search";
|
|
162
205
|
import { hydrateAccountStatus } from "./account-status";
|
|
163
206
|
import { hydrateProjectList } from "./project-list";
|
|
164
207
|
import { addRecentProject, hydrateRecentProjects, removeRecentProject } from "./recent-projects";
|
|
@@ -169,199 +212,166 @@ import {
|
|
|
169
212
|
openProjectPickerModal,
|
|
170
213
|
platformUsesRepoPicker,
|
|
171
214
|
} from "./new-project/add-repo-modal";
|
|
172
|
-
import { openNewProjectModal } from "./new-project/new-project-modal";
|
|
173
|
-
import
|
|
215
|
+
import { openNewProjectModal, registerNewProjectCommands } from "./new-project/new-project-modal";
|
|
216
|
+
import { invalidatePageRouteCache, registerInspectorCommands } from "./panels/properties-panel";
|
|
217
|
+
import { liveElementCommands, setContextMenuNavigate } from "./editor/context-menu";
|
|
218
|
+
import { registerSeoCommands, renderSeoModal } from "./panels/seo-modal";
|
|
219
|
+
import { registerA11yCommands } from "./services/a11y-report";
|
|
220
|
+
import { registerStyleCommands } from "./panels/style-panel";
|
|
221
|
+
import { registerGridCommands } from "./grid/grid-open";
|
|
222
|
+
import { registerSettingsCommands } from "./settings/settings-document";
|
|
223
|
+
import { registerPreferencesCommands } from "./settings/preferences-dialog";
|
|
224
|
+
import { registerAboutCommands } from "./about/about-modal";
|
|
225
|
+
import { registerCollabCommands } from "./collab/collab-commands";
|
|
226
|
+
import { registerLibraryCommands } from "./browse/library-commands";
|
|
227
|
+
import { registerPublishCommands } from "./publish/publish-commands";
|
|
228
|
+
import { registerGridViewCommands } from "./grid/grid-panel";
|
|
229
|
+
import { registerRedirectsCommands } from "./grid/redirects-grid";
|
|
230
|
+
import { registerContentCommands } from "./content/entry-commands";
|
|
231
|
+
import { registerI18nCommands } from "./i18n/i18n-commands";
|
|
232
|
+
import { convertToComponent } from "./editor/convert-to-component";
|
|
233
|
+
import type { GitDiffState } from "./types";
|
|
174
234
|
import type { Tab } from "./tabs/tab";
|
|
175
|
-
import type { JxPath } from "./state";
|
|
176
235
|
import type { JxMutableNode, ProjectConfig } from "@jxsuite/schema/types";
|
|
177
236
|
|
|
178
237
|
void _swc;
|
|
179
238
|
|
|
239
|
+
/**
|
|
240
|
+
* What the derivation's commands and follows need from the rest of Studio: an opener, and a reader
|
|
241
|
+
* for the one preset whose subject is not a document at all.
|
|
242
|
+
*
|
|
243
|
+
* `loadDiff` is here rather than in `workspace/pane-derive.ts` because that module owns no I/O —
|
|
244
|
+
* which is what lets its whole decision-making half be tested with no platform. It is the same pair
|
|
245
|
+
* of reads `panels/git-panel.ts` makes for a row click; an added file has no `HEAD` copy, so its
|
|
246
|
+
* "original" is the empty string rather than a `gitShow` that would throw.
|
|
247
|
+
*/
|
|
248
|
+
const derivationDeps: DerivationDeps = {
|
|
249
|
+
/*
|
|
250
|
+
* `fileExists` answers the locale companion's one question: the resolver computes WHERE this
|
|
251
|
+
* document's copy in another language would live, and only the disk can say whether anybody has
|
|
252
|
+
* written it. A read that throws is the platform's own answer for "no such file" — every backend
|
|
253
|
+
* rejects rather than returning empty — so the catch is the negative case, not an error being
|
|
254
|
+
* swallowed. Memoised per wanted path by `probeTranslationFor`, so it costs one read per locale a
|
|
255
|
+
* pane is actually pointed at.
|
|
256
|
+
*/
|
|
257
|
+
fileExists: async (path: string) => {
|
|
258
|
+
try {
|
|
259
|
+
await getPlatform().readFile(path);
|
|
260
|
+
return true;
|
|
261
|
+
} catch {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
loadDiff: loadDiffForLens,
|
|
266
|
+
openFileInPane,
|
|
267
|
+
};
|
|
268
|
+
|
|
180
269
|
// ─── Globals ──────────────────────────────────────────────────────────────────
|
|
181
270
|
// These mutable variables are local to studio.js for now. As sections are extracted
|
|
182
271
|
// Into their own modules, they will migrate to ctx in store.js.
|
|
183
272
|
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
//
|
|
273
|
+
// The FOCUSED pane's effective canvas mode. The composition itself (the per-tab preview toggle over
|
|
274
|
+
// An edit/design base) lives in `canvas/canvas-surface.ts`, beside the panes, because the answer is
|
|
275
|
+
// A property of a pane's tab and every other pane has its own — this is just the focused one, which
|
|
276
|
+
// Is what a panel drawn once for the whole shell means by "the canvas mode".
|
|
277
|
+
//
|
|
278
|
+
// It is a READ, and it is injected only into surfaces the shell draws once: the toolbar, the
|
|
279
|
+
// Inspector, the block bar, the overlays, the live-render context and the canvas view commands.
|
|
280
|
+
// `panels/pane-context.ts` used to take it too and is the reason this comment exists — a bar drawn
|
|
281
|
+
// Once per pane asking "what mode is the canvas in" got the answer for a pane it was not drawing,
|
|
282
|
+
// So the Export button appeared in BOTH bars the moment either pane entered Code. A pane-scoped
|
|
283
|
+
// Caller asks `canvasModeOfPane(paneId)` or `canvasModeOfTab(tab)`, and
|
|
284
|
+
// `scripts/check-pane-singletons.ts`'s fourth rule is what says so mechanically.
|
|
188
285
|
function getCanvasMode() {
|
|
189
|
-
|
|
190
|
-
const base = ui?.canvasMode ?? "design";
|
|
191
|
-
return ui?.preview && (base === "edit" || base === "design") ? "preview" : base;
|
|
286
|
+
return canvasModeOfPane(workspace.activePaneId);
|
|
192
287
|
}
|
|
193
288
|
|
|
194
|
-
/**
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
289
|
+
/**
|
|
290
|
+
* Write a tab's BASE canvas mode. **The only writer, and it cannot find a tab on its own.**
|
|
291
|
+
*
|
|
292
|
+
* It used to open with `const tab = activeTab.value` and every injected `setCanvasMode` was `(mode:
|
|
293
|
+
* string) => void`, which is how the pane context bar's Editor picker — drawn per pane, from
|
|
294
|
+
* `tabOfPane(paneId)` — moved the FOCUSED pane's tab into Code: click "Code" in the side bar and
|
|
295
|
+
* the primary became a Code editor while the side pane went on drawing Design. There is no
|
|
296
|
+
* zero-argument variant anywhere in the graph now; a caller drawn once for the shell passes
|
|
297
|
+
* `activeTab.value` where a reviewer can see it, and a caller drawn for a pane passes that pane's
|
|
298
|
+
* tab.
|
|
299
|
+
*
|
|
300
|
+
* No pane check. It used to refuse a mode the tab's pane could not host, because the side pane was
|
|
301
|
+
* capped to the cheap editor kinds and a cap enforced only at the split is one a context-bar click
|
|
302
|
+
* walks straight back out of. Both panes host every kind, so the only thing that can refuse a mode
|
|
303
|
+
* is the document not declaring it — which `canvas.setMode` checks by name.
|
|
304
|
+
*
|
|
305
|
+
* @param {Tab | null} tab — the tab to move. `null` is a no-op.
|
|
306
|
+
* @param {string} mode
|
|
307
|
+
*/
|
|
308
|
+
function setCanvasMode(tab: Tab | null, mode: string) {
|
|
309
|
+
if (!tab) {
|
|
310
|
+
return;
|
|
198
311
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
312
|
+
/* THIS tab's mode, not the focused pane's. `getCanvasMode()` here meant that leaving git-diff in
|
|
313
|
+
the side pane kept `shell.git.diffState` alive whenever the primary was in some other mode —
|
|
314
|
+
and, worse, that changing the PRIMARY's mode cleared the diff the side pane was still showing. */
|
|
315
|
+
if (canvasModeOfTab(tab) === "git-diff" && mode !== "git-diff") {
|
|
316
|
+
shell.git.diffState = null;
|
|
202
317
|
}
|
|
318
|
+
tab.session.ui.canvasMode = mode;
|
|
203
319
|
}
|
|
204
320
|
|
|
205
|
-
let gitDiffState: GitDiffState | null = null;
|
|
206
|
-
|
|
207
321
|
// ─── Component registry ───────────────────────────────────────────────────────
|
|
208
322
|
|
|
209
|
-
/** @param {string} componentPath */
|
|
210
|
-
async function navigateToComponent(componentPath: string) {
|
|
211
|
-
try {
|
|
212
|
-
const platform = getPlatform();
|
|
213
|
-
const content = await platform.readFile(componentPath);
|
|
214
|
-
if (!content) {
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
const parsed = JSON.parse(content) as JxMutableNode;
|
|
218
|
-
const tab = activeTab.value;
|
|
219
|
-
if (!tab) {
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Push current state onto the document stack
|
|
224
|
-
const frame = {
|
|
225
|
-
dirty: tab.doc.dirty,
|
|
226
|
-
document: tab.doc.document,
|
|
227
|
-
documentPath: tab.documentPath,
|
|
228
|
-
mode: tab.doc.mode,
|
|
229
|
-
selection: tab.session.selection,
|
|
230
|
-
sourceFormat: tab.doc.sourceFormat,
|
|
231
|
-
};
|
|
232
|
-
if (!tab.session.documentStack) {
|
|
233
|
-
tab.session.documentStack = [];
|
|
234
|
-
}
|
|
235
|
-
tab.session.documentStack.push(frame);
|
|
236
|
-
|
|
237
|
-
// Load the component
|
|
238
|
-
tab.doc.document = parsed;
|
|
239
|
-
tab.doc.dirty = false;
|
|
240
|
-
tab.doc.mode = null as unknown as string;
|
|
241
|
-
tab.doc.sourceFormat = null;
|
|
242
|
-
tab.documentPath = componentPath;
|
|
243
|
-
tab.session.selection = null;
|
|
244
|
-
view.leftTab = "layers";
|
|
245
|
-
tab.session.ui.activeMedia = null;
|
|
246
|
-
tab.session.ui.activeSelector = null;
|
|
247
|
-
|
|
248
|
-
render();
|
|
249
|
-
statusMessage(`Editing component: ${parsed.tagName || componentPath}`);
|
|
250
|
-
} catch (error) {
|
|
251
|
-
const err = error as Error;
|
|
252
|
-
statusMessage(`Error: ${err.message}`);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
323
|
/**
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
324
|
+
* Drill into a component (or a layout) — as a REAL TAB, not a document swapped in under the current
|
|
325
|
+
* tab's id, and BESIDE the page rather than over it.
|
|
326
|
+
*
|
|
327
|
+
* The old implementation rewrote `tab.documentPath` and left `tab.id` alone, and everything
|
|
328
|
+
* downstream believed the id: `openFileInTab`'s dedupe stopped matching, so re-opening the page
|
|
329
|
+
* from the tree called `openTab` with an id that was already in the map — overwriting the entry
|
|
330
|
+
* without disposing the old tab's effect scope and pushing a SECOND copy of the id into `tabOrder`,
|
|
331
|
+
* which is a duplicate lit `repeat` key and a lost document stack.
|
|
332
|
+
*
|
|
333
|
+
* What the drill-in keeps is the RELATIONSHIP: the new tab records the document it was opened from
|
|
334
|
+
* and the strip prints it. The parent stays open, right where it was.
|
|
260
335
|
*
|
|
261
|
-
*
|
|
336
|
+
* Four deliberate properties, three of them new and each with a failure behind it:
|
|
337
|
+
*
|
|
338
|
+
* 1. **To the side.** §8.2 has promised this since P3 and it never shipped — the chain ran
|
|
339
|
+
* `openFileInTab` → `openTab` → `activePane()`, so "open the layout that wraps this page" opened
|
|
340
|
+
* it ON TOP of the page it was teaching about.
|
|
341
|
+
* 2. **Focus stays in the page.** An assistant pane that takes the keyboard means the author's next
|
|
342
|
+
* keystroke edits the definition instead of the document they are looking at — and a following
|
|
343
|
+
* pane would immediately have nothing to follow.
|
|
344
|
+
* 3. **A PREVIEW tab**, because drilling in is browsing: the second drill-in takes the same slot
|
|
345
|
+
* instead of littering the side strip, and an edit promotes it (`promoteDirtyPreviewTabs`).
|
|
346
|
+
* 4. **An ordinary tab, not a derivation.** "Edit definition" is a commitment to edit one component; a
|
|
347
|
+
* following pane would yank to a different one on the author's next canvas click. The following
|
|
348
|
+
* form is `pane.derive { preset: "component" }`, opted into by name.
|
|
349
|
+
*
|
|
350
|
+
* `openedFrom` is unchanged — §14.2's relationship, which nothing pops and nothing restores from.
|
|
351
|
+
*
|
|
352
|
+
* @param {string} componentPath
|
|
262
353
|
*/
|
|
263
|
-
async function
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
} catch (error) {
|
|
276
|
-
statusMessage(`Save error: ${(error as Error).message}`);
|
|
277
|
-
return false;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
// "discard": leave without writing — the child's edits are dropped with the popped frame.
|
|
281
|
-
return true;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
async function navigateBack() {
|
|
285
|
-
const tab = activeTab.value;
|
|
286
|
-
if (!tab?.session.documentStack || tab.session.documentStack.length === 0) {
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
if (!(await confirmLeaveDirtyChild(tab))) {
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// Pop the stack
|
|
294
|
-
const frame = tab.session.documentStack.pop() as Record<string, unknown> | undefined;
|
|
295
|
-
if (!frame) {
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
tab.doc.document = frame.document as JxMutableNode;
|
|
299
|
-
tab.doc.dirty = frame.dirty as boolean;
|
|
300
|
-
tab.doc.mode = frame.mode as string;
|
|
301
|
-
tab.doc.sourceFormat = frame.sourceFormat as string | null;
|
|
302
|
-
tab.documentPath = frame.documentPath as string | null;
|
|
303
|
-
tab.session.selection = frame.selection as JxPath | null;
|
|
304
|
-
view.leftTab = "layers";
|
|
305
|
-
|
|
306
|
-
render();
|
|
307
|
-
statusMessage("Returned to parent document");
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/** @param {number} targetIndex */
|
|
311
|
-
async function navigateToLevel(targetIndex: number) {
|
|
312
|
-
const tab = activeTab.value;
|
|
313
|
-
const stack = tab?.session.documentStack;
|
|
314
|
-
if (!stack || targetIndex < 0 || targetIndex >= stack.length) {
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
if (!(await confirmLeaveDirtyChild(tab))) {
|
|
318
|
-
return;
|
|
354
|
+
async function navigateToComponent(componentPath: string) {
|
|
355
|
+
const from = activeTab.value;
|
|
356
|
+
/* {@link receivingPane}, not `sidePane`: the pane beside this one may be a LENS, which owns no
|
|
357
|
+
tab. The open then landed in a `tabOrder` `tabOfPane` hops straight past, so the read below got
|
|
358
|
+
the SOURCE tab back, `opened.documentPath !== componentPath`, and the one relationship this
|
|
359
|
+
function exists to record (§14.2) was skipped without a sound. */
|
|
360
|
+
const target = receivingPane();
|
|
361
|
+
const alreadyOpen = [...workspace.tabs.values()].some((t) => t.documentPath === componentPath);
|
|
362
|
+
await openFileInTab(componentPath, { focus: false, paneId: target.id, preview: true });
|
|
363
|
+
const opened = tabOfPane(target.id);
|
|
364
|
+
if (!alreadyOpen && from && opened && opened.documentPath === componentPath) {
|
|
365
|
+
opened.session.openedFrom = { documentPath: from.documentPath, tabId: from.id };
|
|
319
366
|
}
|
|
320
|
-
|
|
321
|
-
const frame = stack[targetIndex] as DocumentStackEntry;
|
|
322
|
-
tab.session.documentStack = stack.slice(0, targetIndex);
|
|
323
|
-
tab.doc.document = frame.document as JxMutableNode;
|
|
324
|
-
tab.doc.dirty = frame.dirty as boolean;
|
|
325
|
-
tab.doc.mode = frame.mode as string;
|
|
326
|
-
tab.doc.sourceFormat = frame.sourceFormat as string | null;
|
|
327
|
-
tab.documentPath = frame.documentPath as string | null;
|
|
328
|
-
tab.session.selection = frame.selection as JxPath | null;
|
|
329
|
-
view.leftTab = "layers";
|
|
330
|
-
|
|
331
|
-
render();
|
|
332
|
-
statusMessage("Returned to parent document");
|
|
367
|
+
setActivityTab("layers");
|
|
333
368
|
}
|
|
334
369
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if (!editing || !tab) {
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
if (view.functionEditor) {
|
|
344
|
-
const currentCode = view.functionEditor.getValue();
|
|
345
|
-
const minResult = await codeService("minify", { code: currentCode });
|
|
346
|
-
const bodyToStore = minResult?.code ?? currentCode;
|
|
347
|
-
if (editing.type === "def") {
|
|
348
|
-
transactDoc(tab, (t) => mutateUpdateDef(t, editing.defName as string, { body: bodyToStore }));
|
|
349
|
-
} else if (editing.type === "event") {
|
|
350
|
-
const node = getNodeAtPath(tab.doc.document, editing.path as JxPath);
|
|
351
|
-
const current = node?.[editing.eventKey as string] || {};
|
|
352
|
-
transactDoc(tab, (t) =>
|
|
353
|
-
mutateUpdateProperty(t, editing.path as JxPath, editing.eventKey as string, {
|
|
354
|
-
.../** @type {object} */ current,
|
|
355
|
-
$prototype: "Function",
|
|
356
|
-
body: bodyToStore,
|
|
357
|
-
}),
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
view.functionEditor.dispose();
|
|
361
|
-
view.functionEditor = null;
|
|
362
|
-
}
|
|
363
|
-
updateUi("editingFunction", null);
|
|
364
|
-
}
|
|
370
|
+
// There is no `navigateBack` / `navigateToLevel` here any more, and no `confirmLeaveDirtyChild`
|
|
371
|
+
// Beneath them. All three served the sub-document stack, which nothing could push onto — see
|
|
372
|
+
// `tabs/tab.ts`. The dirty prompt they shared was about a POPPED frame discarding a child's edits;
|
|
373
|
+
// A drilled-in component is a real tab now, and `panels/tab-strip.ts` owns the prompt for closing
|
|
374
|
+
// One. That prompt is two-way where this was three-way — see `showSaveDiscardDialog`'s ledger entry.
|
|
365
375
|
|
|
366
376
|
// ─── Webdata: datalists for autocomplete ──────────────────────────────────────
|
|
367
377
|
|
|
@@ -405,28 +415,13 @@ if (!hasPlatform()) {
|
|
|
405
415
|
registerPlatform(resolveDefaultPlatform());
|
|
406
416
|
}
|
|
407
417
|
|
|
408
|
-
// Screenshot/automation runners (scripts/screenshots/) await window.__jxAutomation right after
|
|
409
|
-
// Navigation, so the gated hook must install before the async deep-link project load below.
|
|
410
|
-
installAutomationHook({
|
|
411
|
-
getCanvasMode,
|
|
412
|
-
openBrowseModal,
|
|
413
|
-
openConnectorGrid,
|
|
414
|
-
openNewProjectModal,
|
|
415
|
-
openQuickSearchPalette: openQuickSearch,
|
|
416
|
-
openSettingsModal,
|
|
417
|
-
render,
|
|
418
|
-
renderActivityBar,
|
|
419
|
-
seedAssistantMessages,
|
|
420
|
-
seedPublishConnected,
|
|
421
|
-
setCanvasMode,
|
|
422
|
-
statusMessage,
|
|
423
|
-
});
|
|
424
|
-
|
|
425
418
|
mountResizeEdges();
|
|
426
419
|
|
|
427
420
|
// ─── Render loop ──────────────────────────────────────────────────────────────
|
|
428
421
|
|
|
429
422
|
initShellRefs();
|
|
423
|
+
// One effect projects the dock record onto the shell grid — collapse classes and column widths.
|
|
424
|
+
mountShell();
|
|
430
425
|
|
|
431
426
|
// Mount extracted panel modules
|
|
432
427
|
toolbarPanel.mount(toolbarEl, {
|
|
@@ -443,20 +438,22 @@ toolbarPanel.mount(toolbarEl, {
|
|
|
443
438
|
initLayers();
|
|
444
439
|
initQuickSearch({ openRecentProject: (root: string) => openRecentProject(root) });
|
|
445
440
|
|
|
446
|
-
|
|
441
|
+
/* The pane's four surfaces come from its CELL, not from `document.querySelector`.
|
|
442
|
+
`panels/pane-grid.ts` mounted through `mountShell()` above, so the primary's cell exists by now;
|
|
443
|
+
each of these three modules still holds one host, which is exactly right while the grid draws one
|
|
444
|
+
cell and is what `mountForPane` replaces when it draws two. */
|
|
445
|
+
const primaryCell = cellForPane(PRIMARY_PANE);
|
|
447
446
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
447
|
+
tabStrip.mount(primaryCell?.strip ?? document.createElement("div"));
|
|
448
|
+
|
|
449
|
+
paneContext.mount(primaryCell?.chrome ?? document.createElement("div"), {
|
|
451
450
|
exportFile,
|
|
452
|
-
getCanvasMode
|
|
453
|
-
navigateBack: () => navigateBack(),
|
|
454
|
-
navigateToLevel: (i: number) => navigateToLevel(i),
|
|
451
|
+
// No `getCanvasMode`: the bar is drawn once per pane and asks its own pane. See `PaneContextCtx`.
|
|
455
452
|
parseMediaEntries,
|
|
453
|
+
setCanvasMode,
|
|
456
454
|
});
|
|
457
455
|
|
|
458
456
|
overlaysPanel.mount({
|
|
459
|
-
getCanvasMode,
|
|
460
457
|
isEditing,
|
|
461
458
|
renderBlockActionBar,
|
|
462
459
|
});
|
|
@@ -467,6 +464,10 @@ initBlockActionBar({
|
|
|
467
464
|
});
|
|
468
465
|
// The iframe's re-emitted selection snapshot drives the parent format toolbar refresh (4b-2).
|
|
469
466
|
setToolbarRefresh(renderBlockActionBar);
|
|
467
|
+
// The other half of that seam: a pointerdown inside a canvas frame ends the bar's suppression (the
|
|
468
|
+
// Canvas is in play again). It is the only signal that can, because clicking the SAME
|
|
469
|
+
// Already-selected element changes nothing the render path can compare.
|
|
470
|
+
setCanvasPointerDownHandler(releaseBlockActionBar);
|
|
470
471
|
// The cross-origin insertion "+" click runs the parent-realm slash-menu → mutateInsertNode flow.
|
|
471
472
|
setInsertZoneClickHandler(runInsertZoneAction);
|
|
472
473
|
// Files dragged from the OS onto a canvas: upload, then replace an image's source or insert.
|
|
@@ -475,33 +476,56 @@ setFileDropHandler(applyFileDrop);
|
|
|
475
476
|
// Files afterwards. Injected here because all three modules import from media-upload.
|
|
476
477
|
setMediaChangedHandler(async (dir) => {
|
|
477
478
|
invalidateMediaCache();
|
|
478
|
-
|
|
479
|
+
invalidateLibrary();
|
|
479
480
|
await loadDirectory(dir);
|
|
480
481
|
renderLeftPanel();
|
|
481
482
|
});
|
|
482
483
|
// The in-iframe "/" trigger drives the parent-realm Spectrum slash menu across the bridge.
|
|
483
484
|
setCanvasSlashHandler(canvasSlashHandler);
|
|
484
485
|
// Canvas right-clicks show the parent-realm Jx element context menu across the bridge.
|
|
485
|
-
setCanvasContextMenuHandler(makeCanvasContextMenuHandler(
|
|
486
|
+
setCanvasContextMenuHandler(makeCanvasContextMenuHandler());
|
|
486
487
|
// Stylebook hits decode to a TAG in the host and route here (null = clicked chrome/empty space).
|
|
487
488
|
setStylebookHitHandler((tag, media) => {
|
|
488
489
|
if (tag) {
|
|
489
490
|
selectStylebookTag(tag, media);
|
|
490
491
|
} else {
|
|
491
|
-
|
|
492
|
+
shell.stylebook.selection = null;
|
|
493
|
+
updateSession(activeTab.value, { ui: { activeSelector: null } });
|
|
492
494
|
}
|
|
493
495
|
});
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
496
|
+
/* Two duties, one listener, because both answer the same question: intent has left the canvas.
|
|
497
|
+
Pointerdowns over the canvas land inside the cross-origin iframe and never reach this listener,
|
|
498
|
+
so a hit here IS parent chrome — which is why the listener can be this blunt and why it cannot
|
|
499
|
+
double-fire with the iframe's own click-away commit.
|
|
500
|
+
|
|
501
|
+
1. Commit-on-parent-click: it ends the live inline-edit session, which the iframe cannot observe
|
|
502
|
+
itself (layers panel, tab strip, right panel…).
|
|
503
|
+
2. The block action bar goes with it. The bar is `position: fixed` and clamped into the window,
|
|
504
|
+
so it can sit over the Document Header card, the pane context bar and the docks; an author
|
|
505
|
+
working in the Inspector's Logic tab reported it "blocking a part of the interface that the
|
|
506
|
+
user needs to utilize". It is SUPPRESSED, not dismissed — a plain dismiss flashes back on the
|
|
507
|
+
next snapshot or overlay repaint — and it comes back the moment the canvas takes a pointer or
|
|
508
|
+
the selection moves. Nothing here touches the selection: the Inspector edits the selected
|
|
509
|
+
node, so a click into it must keep the thing it is about to edit.
|
|
510
|
+
|
|
511
|
+
Both spare the edit-session chrome (`isEditChromeTarget`: the bar, its `⋮` menu, the link
|
|
512
|
+
popover, the slash menu) — those surfaces act ON the session and the bar rather than away from
|
|
513
|
+
them. Only the suppression spares the canvas STAGE, whose margins, artboard headers and insertion
|
|
514
|
+
"+" are parent-realm elements around the frame: they are the canvas, not somewhere else in the
|
|
515
|
+
shell, and hiding the bar to pan or to nudge the zoom would be the app losing the author's place
|
|
516
|
+
for them. */
|
|
499
517
|
document.addEventListener(
|
|
500
518
|
"pointerdown",
|
|
501
519
|
(e) => {
|
|
502
|
-
if (
|
|
520
|
+
if (isEditChromeTarget(e.target)) {
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
if (getEditSnapshot().editing) {
|
|
503
524
|
commitActiveEditSession();
|
|
504
525
|
}
|
|
526
|
+
if (!(e.target instanceof Node) || stageContaining(e.target) === null) {
|
|
527
|
+
suppressBlockActionBar();
|
|
528
|
+
}
|
|
505
529
|
},
|
|
506
530
|
true,
|
|
507
531
|
);
|
|
@@ -509,9 +533,19 @@ document.addEventListener(
|
|
|
509
533
|
// Unsaved-changes guard: saving is explicit (no idle autosave), so warn before the window unloads
|
|
510
534
|
// While any open tab has unsaved edits. For collab tabs, `dirty` reflects the room-level unsaved
|
|
511
535
|
// State; closing loses in-memory edits that were never flushed to disk.
|
|
536
|
+
/*
|
|
537
|
+
* `dirty` alone could not see a Monaco buffer, and quitting is the one exit no disposer follows.
|
|
538
|
+
*
|
|
539
|
+
* A keystroke in the source view or the dock's Logic tab reaches the document on a 600ms / 500ms
|
|
540
|
+
* debounce, and nothing marks the tab dirty in the meantime — so typing the last character of a
|
|
541
|
+
* handler and pressing ⌘Q left with no prompt at all. There is no flush to make here: `beforeunload`
|
|
542
|
+
* cannot await, and the source view's commit parses through the format host before it assigns, so
|
|
543
|
+
* the answer would arrive after the window is gone. The buffer is asked directly instead —
|
|
544
|
+
* `tabBufferUnsaved` is the author's own typing that the document has not been given.
|
|
545
|
+
*/
|
|
512
546
|
export function hasUnsavedTabs(): boolean {
|
|
513
547
|
for (const tab of workspace.tabs.values()) {
|
|
514
|
-
if (tab.doc.dirty) {
|
|
548
|
+
if (tab.doc.dirty || tabBufferUnsaved(tab)) {
|
|
515
549
|
return true;
|
|
516
550
|
}
|
|
517
551
|
}
|
|
@@ -519,6 +553,9 @@ export function hasUnsavedTabs(): boolean {
|
|
|
519
553
|
}
|
|
520
554
|
|
|
521
555
|
window.addEventListener("beforeunload", (e: BeforeUnloadEvent) => {
|
|
556
|
+
// The session, so a relaunch reopens what was open (§4.4). Before the prompt, not after: the
|
|
557
|
+
// Author may cancel the close, and the record is the same either way.
|
|
558
|
+
flushSession();
|
|
522
559
|
if (hasUnsavedTabs()) {
|
|
523
560
|
e.preventDefault();
|
|
524
561
|
// Legacy browsers require a truthy returnValue to trigger the native confirm prompt.
|
|
@@ -526,20 +563,22 @@ window.addEventListener("beforeunload", (e: BeforeUnloadEvent) => {
|
|
|
526
563
|
}
|
|
527
564
|
});
|
|
528
565
|
|
|
529
|
-
initCanvasUtils
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
initCanvasLiveRender
|
|
539
|
-
|
|
540
|
-
|
|
566
|
+
/* No `initCanvasUtils` any more, and the three functions it injected are why.
|
|
567
|
+
`getZoom` and `setZoomDirect` were `activeTab.value?.session.ui.zoom` — the FOCUSED pane's tab —
|
|
568
|
+
while every geometry function in `canvas/canvas-utils.ts` already took an explicit
|
|
569
|
+
`CanvasSurface`. So the pan and the wrap were per-stage and the SCALE was not: the unfocused pane
|
|
570
|
+
drew at the focused tab's scale, the side pane's `+` zoomed the primary's document, and the side
|
|
571
|
+
pane entering Design snapped the primary to whatever it had fitted itself to. `getCanvasMode`
|
|
572
|
+
was the same shape one layer down. The module reaches all three through the surface it is given
|
|
573
|
+
(`tabOfPane(surface.paneId)`), so there is nothing left to inject.
|
|
574
|
+
|
|
575
|
+
No `initCanvasLiveRender` either, and it went for the same reason one layer further down again.
|
|
576
|
+
`getCanvasMode` was the ONLY thing that context carried, and `resolveCanvasDocument` used it
|
|
577
|
+
while ALSO reading `activeTab.value` for the document path, the layout toggle and the preview
|
|
578
|
+
params — so the pane the render had already been resolved for was discarded and the focused
|
|
579
|
+
pane's answers substituted for all six. It takes the tab now, and the injection point had
|
|
580
|
+
nothing else in it. */
|
|
541
581
|
initCanvasPatcher({
|
|
542
|
-
getCanvasMode,
|
|
543
582
|
renderOverlays,
|
|
544
583
|
scheduleCanvasRender,
|
|
545
584
|
});
|
|
@@ -548,14 +587,13 @@ setIframePatchEscalation(scheduleCanvasRender);
|
|
|
548
587
|
// One global coordinator monitor drives cross-frame palette→canvas drops (Phase 4c).
|
|
549
588
|
registerCanvasDndBridge();
|
|
550
589
|
initCanvasRender({
|
|
551
|
-
getCanvasMode,
|
|
552
590
|
get gitDiffState() {
|
|
553
|
-
return
|
|
591
|
+
return shell.git.diffState;
|
|
554
592
|
},
|
|
555
593
|
openFileFromTree,
|
|
556
594
|
setCanvasMode,
|
|
557
595
|
setGitDiffState: (state: GitDiffState | null) => {
|
|
558
|
-
|
|
596
|
+
shell.git.diffState = state;
|
|
559
597
|
},
|
|
560
598
|
});
|
|
561
599
|
|
|
@@ -581,59 +619,157 @@ initWelcome({
|
|
|
581
619
|
openRecentProject: (root: string) => openRecentProject(root),
|
|
582
620
|
});
|
|
583
621
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
622
|
+
/* There is no stage-handover effect here any more.
|
|
623
|
+
It existed because the shell had one `#canvas-wrap` and a pane is the unit of render, so "which
|
|
624
|
+
pane owns the stage" had to be re-answered every time focus moved. `panels/pane-grid.ts` builds a
|
|
625
|
+
cell per pane and registers its stage with it; nothing moves, and nothing has to be repainted
|
|
626
|
+
because it changed hands. */
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* One pane's render subscriptions.
|
|
630
|
+
*
|
|
631
|
+
* Effect-driven canvas rendering, split into three triggers so document changes can be
|
|
632
|
+
* distinguished from mode/UI changes:
|
|
633
|
+
*
|
|
634
|
+
* - Doc-effect: tracks only the document root reference. Document mutations that were consumed
|
|
635
|
+
* surgically by the canvas patcher skip the full render here.
|
|
636
|
+
* - Ui-effect: tracks canvas mode and UI flags; always schedules a full render.
|
|
637
|
+
* - The colour-scheme post, which is a document-level attribute flip inside the iframe and
|
|
638
|
+
* deliberately never part of the ui-effect: flipping the scheme must not re-render.
|
|
639
|
+
*
|
|
640
|
+
* Scheduling is deduped inside `scheduleCanvasRender` (double-RAF).
|
|
641
|
+
*
|
|
642
|
+
* **Keyed on the PANE, not on `activeTab`, and this is what makes `⌘\` and Unsplit work at all.**
|
|
643
|
+
* Both effects used to read the focused pane's tab and schedule "the" canvas. A split moves a tab
|
|
644
|
+
* between panes without changing which tab is active, so neither effect re-ran: the pane the tab
|
|
645
|
+
* LEFT went on displaying it, and Unsplit — where the survivor's `activeTabId` changes but
|
|
646
|
+
* `activeTab` does not — left the primary showing document A while the strip, the jump bar and the
|
|
647
|
+
* Inspector all said B. `tabOfPane` tracks the pane's own `activeTabId`, so the pane that changed
|
|
648
|
+
* is the pane that repaints.
|
|
649
|
+
*
|
|
650
|
+
* @param {string} paneId
|
|
651
|
+
*/
|
|
652
|
+
function installPaneRenderEffects(paneId: string): void {
|
|
653
|
+
/* The FOLLOW, in this pane's own scope so it stops when the pane leaves the grid.
|
|
654
|
+
No preset subscribes nothing: `installDerivationEffects` declares this pane's own
|
|
655
|
+
`activeTabId` and `tabOfPane(sourcePaneId)` for EVERY derivation, and `diff` and `breakpoint`
|
|
656
|
+
are the two that add nothing beyond that — a lens follows STRUCTURALLY, through `tabOfPane`'s
|
|
657
|
+
hop, which the three effects below already read. A comment here claimed the opposite of
|
|
658
|
+
`pane-derive.ts`'s own docstring for a release; the inputs are listed once, at the effect that
|
|
659
|
+
declares them, and this line points at it rather than restating it. */
|
|
660
|
+
installDerivationEffects(paneId, derivationDeps);
|
|
661
|
+
effect(() => {
|
|
662
|
+
const tab = tabOfPane(paneId);
|
|
663
|
+
if (tab) {
|
|
664
|
+
const doc = tab.doc.document;
|
|
665
|
+
if (doc && consumePatchedDocument(doc, paneId)) {
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
scheduleCanvasRender(paneId);
|
|
670
|
+
});
|
|
671
|
+
effect(() => {
|
|
672
|
+
const tab = tabOfPane(paneId);
|
|
673
|
+
/* A derived pane's own view axes. A preset change and a breakpoint change are render inputs
|
|
674
|
+
that live on the DERIVATION rather than on the tab, so without these a lens would keep
|
|
675
|
+
drawing whatever it was created with. Accepted cost, stated: the shared `ui.canvasMode` read
|
|
676
|
+
below means a mode flip in the source pane also re-runs the lens's ui-effect — one extra
|
|
677
|
+
full render per human mode flip, which is not a per-keystroke path. */
|
|
678
|
+
const derived = paneById(paneId)?.derived;
|
|
679
|
+
void derived?.status;
|
|
680
|
+
/* …and the SENTENCE, because the stage draws it. `canvas/canvas-render.ts` prints
|
|
681
|
+
`derived.reason` for an unavailable derivation, and `status` alone does not move when the
|
|
682
|
+
reason does: a diff lens that could not read its comparison, whose file the author then
|
|
683
|
+
saves back to HEAD, stays `unavailable` while the sentence becomes "Nothing to compare".
|
|
684
|
+
Untracked, the stage would keep the old sentence until something else repainted it. */
|
|
685
|
+
void derived?.reason;
|
|
686
|
+
if (derived?.kind === "lens") {
|
|
687
|
+
void derived.mode;
|
|
688
|
+
void derived.media;
|
|
689
|
+
}
|
|
690
|
+
if (tab) {
|
|
691
|
+
void tab.doc.mode;
|
|
692
|
+
void tab.session.ui.canvasMode;
|
|
693
|
+
void tab.session.ui.editingFormula;
|
|
694
|
+
void tab.session.ui.editingFunction;
|
|
695
|
+
void tab.session.ui.featureToggles;
|
|
696
|
+
void tab.session.ui.preview;
|
|
697
|
+
void tab.session.ui.previewParams;
|
|
698
|
+
void tab.session.ui.previewProps;
|
|
699
|
+
void tab.session.ui.showLayout;
|
|
700
|
+
}
|
|
701
|
+
// Project-level render inputs: the stylebook catalogue's filters and the settings section are
|
|
702
|
+
// Shell state, so a change repaints the canvas with or without a document focused.
|
|
703
|
+
void shell.settingsTab;
|
|
704
|
+
void shell.stylebook.tab;
|
|
705
|
+
void shell.stylebook.filter;
|
|
706
|
+
void shell.stylebook.customizedOnly;
|
|
707
|
+
scheduleCanvasRender(paneId);
|
|
708
|
+
});
|
|
709
|
+
effect(() => {
|
|
710
|
+
const scheme = tabOfPane(paneId)?.session.ui.previewColorScheme;
|
|
711
|
+
// Scoped to THIS pane's stage: the scheme is a per-tab choice, and an unscoped post flipped
|
|
712
|
+
// Both documents from one pane's control.
|
|
713
|
+
postColorSchemeToLiveHosts(
|
|
714
|
+
scheme === "light" || scheme === "dark" ? scheme : null,
|
|
715
|
+
surfaceForPane(paneId).wrap,
|
|
716
|
+
);
|
|
717
|
+
});
|
|
718
|
+
effect(() => {
|
|
719
|
+
/*
|
|
720
|
+
* The other half of `i18n.switchLocale`. Without this the control moves a chip and the artboard
|
|
721
|
+
* goes on drawing the same document left-to-right, which makes the verb a label rather than a
|
|
722
|
+
* rendering context. Scoped to THIS pane's stage for the reason the scheme effect above gives.
|
|
723
|
+
*/
|
|
724
|
+
const locale = tabOfPane(paneId)?.session.ui.previewLocale ?? null;
|
|
725
|
+
postLocaleToLiveHosts(locale, surfaceForPane(paneId).wrap);
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Subscribe every drawn pane, and unsubscribe one that leaves the grid.
|
|
731
|
+
*
|
|
732
|
+
* A scope per pane rather than one loop over `workspace.panes`, because the distinction the two
|
|
733
|
+
* effects above draw only survives if each pane is tracked separately: an effect that read both
|
|
734
|
+
* panes' documents would re-render BOTH whenever either moved, and `consumePatchedDocument` would
|
|
735
|
+
* skip both whenever one was patched.
|
|
736
|
+
*/
|
|
737
|
+
const _paneRenderScopes = new Map<string, EffectScope>();
|
|
738
|
+
|
|
590
739
|
effect(() => {
|
|
591
|
-
const
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
740
|
+
const wanted = new Set(workspace.panes.map((pane) => pane.id));
|
|
741
|
+
for (const [paneId, scope] of _paneRenderScopes) {
|
|
742
|
+
if (!wanted.has(paneId)) {
|
|
743
|
+
scope.stop();
|
|
744
|
+
_paneRenderScopes.delete(paneId);
|
|
596
745
|
}
|
|
597
746
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
void tab.session.ui.featureToggles;
|
|
608
|
-
void tab.session.ui.preview;
|
|
609
|
-
void tab.session.ui.previewParams;
|
|
610
|
-
void tab.session.ui.previewProps;
|
|
611
|
-
void tab.session.ui.settingsTab;
|
|
612
|
-
void tab.session.ui.showLayout;
|
|
613
|
-
void tab.session.ui.stylebookTab;
|
|
614
|
-
void tab.session.ui.stylebookFilter;
|
|
615
|
-
void tab.session.ui.stylebookCustomizedOnly;
|
|
747
|
+
for (const paneId of wanted) {
|
|
748
|
+
if (_paneRenderScopes.has(paneId)) {
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
const scope = effectScope();
|
|
752
|
+
_paneRenderScopes.set(paneId, scope);
|
|
753
|
+
scope.run(() => {
|
|
754
|
+
installPaneRenderEffects(paneId);
|
|
755
|
+
});
|
|
616
756
|
}
|
|
617
|
-
scheduleCanvasRender();
|
|
618
|
-
});
|
|
619
|
-
// Color-scheme preview is a document-level attribute flip inside the iframe — deliberately its
|
|
620
|
-
// Own effect, never part of the ui-effect above: flipping the scheme must not re-render.
|
|
621
|
-
effect(() => {
|
|
622
|
-
const s = activeTab.value?.session.ui.previewColorScheme;
|
|
623
|
-
postColorSchemeToLiveHosts(s === "light" || s === "dark" ? s : null);
|
|
624
757
|
});
|
|
625
758
|
|
|
626
759
|
rightPanelMod.mount({
|
|
627
760
|
getCanvasMode,
|
|
761
|
+
// The Assistant tab's body is built by the Inspector and handed to the module that owns the
|
|
762
|
+
// Chat. Injected rather than imported so the dependency runs one way: chat-panel.ts calls back
|
|
763
|
+
// Into right-panel.ts to select its own tab, and this is what keeps that from being a cycle.
|
|
764
|
+
mountAssistant: (host) => chatPanelMod.mount(host),
|
|
628
765
|
navigateToComponent,
|
|
629
766
|
renderCanvas: () => renderCanvas(),
|
|
630
767
|
});
|
|
631
768
|
|
|
632
|
-
//
|
|
633
|
-
|
|
769
|
+
// The Document Header card — every document with frontmatter or `$head`. It has no host of its own:
|
|
770
|
+
// The stage hands one over (`canvas-render.ts`), and this only starts the reactive subscription.
|
|
771
|
+
frontmatterPanelMod.mount();
|
|
634
772
|
|
|
635
|
-
// The persistent AI chat sidebar — mounts once, available with or without a project/document.
|
|
636
|
-
chatPanelMod.mount(chatPanelEl);
|
|
637
773
|
// The assistant's create_project tool adopts freshly scaffolded projects through the same
|
|
638
774
|
// Flow as the recent-projects list.
|
|
639
775
|
setProjectAdopter(openRecentProject);
|
|
@@ -654,10 +790,22 @@ leftPanelMod.mount({
|
|
|
654
790
|
// Fetch. Edit/design suppress them (a full render re-resolves every entry, so authoring would
|
|
655
791
|
// Refetch constantly); re-firing them on demand is what that button is for.
|
|
656
792
|
refreshData: () => {
|
|
657
|
-
|
|
658
|
-
|
|
793
|
+
// ONE read of the focus, shared by the arm and the render. The Data panel is a Navigator
|
|
794
|
+
// Surface, so its subject genuinely is the focused pane — but `allowAutoRequestsOnNextRender()`
|
|
795
|
+
// And `renderCanvas()` each used to resolve that for themselves, and two resolutions of the
|
|
796
|
+
// Same fact are two facts: arming a pane and then rendering a different one is a Refresh that
|
|
797
|
+
// Refreshes nothing.
|
|
798
|
+
const paneId = workspace.activePaneId;
|
|
799
|
+
allowAutoRequestsOnNextRender(paneId);
|
|
800
|
+
// Say so BEFORE the render, and let the iframe's `dataScope` reply be what stops saying it.
|
|
801
|
+
// The button used to fire and repaint 200ms later on a timer, which reported "done" over the
|
|
802
|
+
// Old values for anything slower than that — a Refresh that visibly did nothing.
|
|
803
|
+
const tab = activeTab.value;
|
|
804
|
+
if (tab) {
|
|
805
|
+
tab.session.canvas.refreshing = true;
|
|
806
|
+
}
|
|
807
|
+
renderCanvas(paneId);
|
|
659
808
|
},
|
|
660
|
-
renderDataExplorerTemplate,
|
|
661
809
|
renderFilesTemplate,
|
|
662
810
|
renderGitPanel,
|
|
663
811
|
renderHeadTemplate,
|
|
@@ -665,7 +813,7 @@ leftPanelMod.mount({
|
|
|
665
813
|
renderSignalsTemplate,
|
|
666
814
|
setCanvasMode,
|
|
667
815
|
setGitDiffState: (state: GitDiffState | null) => {
|
|
668
|
-
|
|
816
|
+
shell.git.diffState = state;
|
|
669
817
|
},
|
|
670
818
|
setupTreeKeyboard,
|
|
671
819
|
webdata,
|
|
@@ -677,22 +825,21 @@ registerRenderer("leftPanel", () => leftPanelMod.render());
|
|
|
677
825
|
registerRenderer("canvas", () => renderCanvas());
|
|
678
826
|
registerRenderer("rightPanel", () => rightPanelMod.render());
|
|
679
827
|
registerRenderer("frontmatterPanel", () => frontmatterPanelMod.render());
|
|
828
|
+
registerRenderer("seoModal", renderSeoModal);
|
|
680
829
|
registerRenderer("chatPanel", () => chatPanelMod.render());
|
|
681
830
|
registerRenderer("overlays", () => overlaysPanel.render());
|
|
682
|
-
|
|
831
|
+
renderStatusbar();
|
|
683
832
|
mountStatusbar();
|
|
833
|
+
// ⑥ The jump bar, in the pane's own grid cell above the context bar. It renders the whole address
|
|
834
|
+
// — project › file › node › node — and it is the only breadcrumb in the shell: the pane context
|
|
835
|
+
// Bar drew a second one, and it named a sub-document stack nothing could push onto.
|
|
836
|
+
mountJumpBar(primaryCell?.jump ?? document.createElement("div"));
|
|
684
837
|
mountActivityBar();
|
|
685
838
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
}
|
|
691
|
-
if (!activeTab.value?.session.selection) {
|
|
692
|
-
return;
|
|
693
|
-
}
|
|
694
|
-
activeTab.value.session.selection = null;
|
|
695
|
-
});
|
|
839
|
+
/* The background-click deselect moved into `editor/shortcuts.ts`'s `installStageGestures`, beside
|
|
840
|
+
the wheel and the middle-drag, because it is a STAGE gesture: it compared against the app's one
|
|
841
|
+
`#canvas-wrap` and one `view.panzoomWrap` and cleared `activeTab`'s selection, all three of which
|
|
842
|
+
name the focused pane rather than the pane that was clicked. */
|
|
696
843
|
|
|
697
844
|
function safeRenderRightPanel() {
|
|
698
845
|
rightPanelMod.render();
|
|
@@ -712,13 +859,33 @@ configureCollabParser(async (tab, text) => {
|
|
|
712
859
|
}
|
|
713
860
|
return { document: JSON.parse(text) as JxMutableNode };
|
|
714
861
|
});
|
|
715
|
-
|
|
862
|
+
// The source-canonical freeze is a STATE the author is being held in, not an error: a toast that
|
|
863
|
+
// Says so, keyed so a run of freezes is one message rather than a stack of identical ones.
|
|
864
|
+
configureCollabNotifier((message) => {
|
|
865
|
+
notify.warn(message, { key: "collab.freeze", source: "Collaboration" });
|
|
866
|
+
});
|
|
716
867
|
|
|
717
868
|
let fsUnsub: (() => void) | null = null;
|
|
718
869
|
/** (Re)subscribe the sidebar to backend filesystem events for the active project. */
|
|
719
870
|
function ensureFsSync() {
|
|
720
871
|
fsUnsub?.();
|
|
721
|
-
fsUnsub = startFsSync({
|
|
872
|
+
fsUnsub = startFsSync({
|
|
873
|
+
/* The four caches keyed on the project's file listing. Each one is derived — the pages tree
|
|
874
|
+
behind the Link-target picker, the layouts listing plus the effective layout's `$head`, the
|
|
875
|
+
`$paths` value enumerations, and the context bar's own memo of the enumerations it has
|
|
876
|
+
already asked for — so a file appearing or disappearing is the only event that can make any
|
|
877
|
+
of them wrong, and it is the same event for all four. The bar's memo is separate from
|
|
878
|
+
`page-params`' because it is keyed per DOCUMENT rather than globally; leaving it behind kept
|
|
879
|
+
a stale candidate list in the picker after the collection changed. */
|
|
880
|
+
invalidateDerivedCaches: () => {
|
|
881
|
+
invalidatePageRouteCache();
|
|
882
|
+
invalidateLayoutPickerCache();
|
|
883
|
+
invalidateParamValues();
|
|
884
|
+
paneContext.resetParamValues();
|
|
885
|
+
},
|
|
886
|
+
onContentChange: reloadCleanTab,
|
|
887
|
+
renderLeftPanel,
|
|
888
|
+
});
|
|
722
889
|
}
|
|
723
890
|
|
|
724
891
|
const _urlParams = new URLSearchParams(location.search);
|
|
@@ -751,13 +918,19 @@ if (_projectParam) {
|
|
|
751
918
|
_projectParam.startsWith("~") ||
|
|
752
919
|
/^[A-Za-z]:[/\\]/.test(_projectParam);
|
|
753
920
|
if (!isAbsPath) {
|
|
754
|
-
|
|
921
|
+
notify.error(`?project= requires an absolute path (got "${_projectParam}").`, {
|
|
922
|
+
key: "startup.projectParam",
|
|
923
|
+
source: "Startup",
|
|
924
|
+
});
|
|
755
925
|
render();
|
|
756
926
|
} else {
|
|
757
927
|
render();
|
|
758
928
|
const platform = getPlatform();
|
|
759
929
|
// oxlint-disable-next-line unicorn/prefer-top-level-await -- deliberate fire-and-forget: project probing must not block the initial render
|
|
760
930
|
void (async () => {
|
|
931
|
+
// Hoisted out of the try so the failure can NAME what it failed to open. The catch used to
|
|
932
|
+
// Report a bare `Error: <message>` because the only identifying fact was still block-scoped.
|
|
933
|
+
let fileRelPath = _urlParams.get("file") || _projectParam;
|
|
761
934
|
try {
|
|
762
935
|
const siteCtx = platform.resolveSiteContext
|
|
763
936
|
? await platform.resolveSiteContext(_projectParam)
|
|
@@ -817,9 +990,39 @@ if (_projectParam) {
|
|
|
817
990
|
void maybePromptJxsuiteUpdate(siteCtx.sitePath);
|
|
818
991
|
}
|
|
819
992
|
|
|
993
|
+
/*
|
|
994
|
+
* THE SESSION — unless the URL named a document.
|
|
995
|
+
*
|
|
996
|
+
* A named document is an instruction and wins, whether it came from `?file=` or from a
|
|
997
|
+
* `?project=` that pointed INTO the project. A bare `?project=<dir>` is "open this
|
|
998
|
+
* project", and what that means is the documents it was last left with (§4.4) — the
|
|
999
|
+
* `project.json → home page` redirect below is the answer this replaces.
|
|
1000
|
+
*
|
|
1001
|
+
* This branch opens inline and never went near `openHomePage`, so it was the one door of
|
|
1002
|
+
* four the session work would have missed, and it is the door a browser reload comes
|
|
1003
|
+
* through.
|
|
1004
|
+
*
|
|
1005
|
+
* It returns when something LANDED — a restored session, or the home page
|
|
1006
|
+
* `openLastSessionOrHome` falls back to. A project with neither (no session, no
|
|
1007
|
+
* `pages/index.*`) falls through to the inline open below, which is what opens
|
|
1008
|
+
* `project.json` itself for a project that has nothing else to show.
|
|
1009
|
+
*/
|
|
1010
|
+
const named =
|
|
1011
|
+
_urlParams.get("file") ||
|
|
1012
|
+
(siteCtx.fileRelPath && !siteCtx.fileRelPath.endsWith("project.json")
|
|
1013
|
+
? siteCtx.fileRelPath
|
|
1014
|
+
: null);
|
|
1015
|
+
if (!named) {
|
|
1016
|
+
await openLastSessionOrHome();
|
|
1017
|
+
if (activeTab.value) {
|
|
1018
|
+
render();
|
|
1019
|
+
renderLeftPanel();
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
|
|
820
1024
|
// Read and open the file
|
|
821
|
-
|
|
822
|
-
let fileRelPath = _fileParam || siteCtx.fileRelPath || _projectParam;
|
|
1025
|
+
fileRelPath = named ?? siteCtx.fileRelPath ?? _projectParam;
|
|
823
1026
|
|
|
824
1027
|
// When opening project.json, default to home page instead (listing-based, no 404 probes).
|
|
825
1028
|
if (fileRelPath === "project.json" || fileRelPath.endsWith("/project.json")) {
|
|
@@ -828,7 +1031,9 @@ if (_projectParam) {
|
|
|
828
1031
|
|
|
829
1032
|
const content = await platform.readFile(fileRelPath);
|
|
830
1033
|
if (content) {
|
|
831
|
-
let frontmatter
|
|
1034
|
+
let frontmatter;
|
|
1035
|
+
let parsedDoc;
|
|
1036
|
+
let parsedMode;
|
|
832
1037
|
await loadFormats();
|
|
833
1038
|
const fileFormat = formatForPath(fileRelPath);
|
|
834
1039
|
if (fileFormat || !fileRelPath.endsWith(".json")) {
|
|
@@ -859,10 +1064,19 @@ if (_projectParam) {
|
|
|
859
1064
|
}
|
|
860
1065
|
|
|
861
1066
|
render();
|
|
862
|
-
|
|
1067
|
+
// Opening a file is stated by the tab strip and the status bar's DOCUMENT field.
|
|
863
1068
|
}
|
|
1069
|
+
/* This window may now write its session (§4.4). The `?file=` branch above opens inline and
|
|
1070
|
+
never reaches `openLastSessionOrHome`, which is the other place that says this — so
|
|
1071
|
+
without it a window opened at a named file would restore other windows' sessions and
|
|
1072
|
+
never record its own. */
|
|
1073
|
+
markSessionRestored(workspace.projectRoot);
|
|
864
1074
|
} catch (error) {
|
|
865
|
-
|
|
1075
|
+
notify.error(`Could not open ${fileRelPath || "the project"}.`, {
|
|
1076
|
+
detail: errorMessage(error),
|
|
1077
|
+
path: fileRelPath,
|
|
1078
|
+
source: "Open File",
|
|
1079
|
+
});
|
|
866
1080
|
}
|
|
867
1081
|
})();
|
|
868
1082
|
}
|
|
@@ -911,28 +1125,73 @@ function renderLeftPanel() {
|
|
|
911
1125
|
function loadProject() {
|
|
912
1126
|
return _loadProject();
|
|
913
1127
|
}
|
|
914
|
-
|
|
1128
|
+
/**
|
|
1129
|
+
* Open a project, in the window the user asked for, and report what actually happened.
|
|
1130
|
+
*
|
|
1131
|
+
* THE TARGET IS HONOURED HERE OR NOWHERE. `openProjectFlow` asks the question and this is the
|
|
1132
|
+
* bootstrap's side of that contract — which it did not hold up: the hook was wired as `openProject:
|
|
1133
|
+
* () => openProject()` against a function that took no target, so "New Window" was collected,
|
|
1134
|
+
* dropped on the floor, and fell through to the line below that re-roots THIS window. The dialog
|
|
1135
|
+
* worked, the answer was discarded, and picking a project in the file browser replaced the project
|
|
1136
|
+
* the user had just said to keep.
|
|
1137
|
+
*/
|
|
1138
|
+
async function openProject(target: ProjectOpenTarget = "thisWindow"): Promise<ProjectOpenOutcome> {
|
|
915
1139
|
// Repo-list platforms (cloud) pick from the user's writable repositories instead of a
|
|
916
1140
|
// Backend dialog; the choice opens through the same path as a recent project.
|
|
917
1141
|
if (platformUsesRepoPicker()) {
|
|
918
1142
|
const picked = await openProjectPickerModal();
|
|
919
|
-
if (picked) {
|
|
920
|
-
|
|
921
|
-
void hydrateProjectList().then(() => {
|
|
922
|
-
render();
|
|
923
|
-
});
|
|
924
|
-
void openRecentProject(picked.root);
|
|
1143
|
+
if (!picked) {
|
|
1144
|
+
return "cancelled";
|
|
925
1145
|
}
|
|
926
|
-
|
|
1146
|
+
// The catalogue may have gained an entry; refresh it before navigating into the project.
|
|
1147
|
+
void hydrateProjectList().then(() => {
|
|
1148
|
+
render();
|
|
1149
|
+
});
|
|
1150
|
+
void openRecentProject(picked.root);
|
|
1151
|
+
return "opened";
|
|
927
1152
|
}
|
|
928
|
-
const
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
1153
|
+
const platform = getPlatform();
|
|
1154
|
+
// ELSEWHERE, and nothing here closes: no `confirmCloseAll`, no `replaceAllTabs`, no re-rooting.
|
|
1155
|
+
// `pickProject` exists precisely so this branch can ask which project without `openProject()`'s
|
|
1156
|
+
// Side effect of binding the asking window to the answer. The window that opens loads it, adds
|
|
1157
|
+
// Its own recent-projects entry, and this one carries on untouched.
|
|
1158
|
+
if (target === "newWindow" && platform.pickProject && platform.openProjectInNewWindow) {
|
|
1159
|
+
const picked = await platform.pickProject();
|
|
1160
|
+
if (!picked) {
|
|
1161
|
+
return "cancelled";
|
|
1162
|
+
}
|
|
1163
|
+
const { focused } = await platform.openProjectInNewWindow(picked.root);
|
|
1164
|
+
return focused ? "focused" : "newWindow";
|
|
1165
|
+
}
|
|
1166
|
+
// The SECOND destroyer, and it was ungated for as long as the first one was. `openRecentProject`
|
|
1167
|
+
// Below asks `confirmCloseAll` and then calls `closeAllTabs`; this branch reaches
|
|
1168
|
+
// `files.ts`'s `replaceAllTabs`, which throws the same documents away by a different name — so an
|
|
1169
|
+
// Enumeration of "who calls closeAllTabs" reported the matrix complete while ⌘O, the toolbar
|
|
1170
|
+
// Button and the welcome screen all discarded unsaved work in silence. `platformUsesRepoPicker()`
|
|
1171
|
+
// Is true only for the cloud platform, so desktop and browser both arrived here.
|
|
1172
|
+
if (!(await tabStrip.confirmCloseAll("Opening another project"))) {
|
|
1173
|
+
return "cancelled";
|
|
1174
|
+
}
|
|
1175
|
+
const opened = await _openProject({ renderLeftPanel });
|
|
932
1176
|
ensureFsSync();
|
|
933
|
-
return
|
|
1177
|
+
return opened ? "opened" : "cancelled";
|
|
934
1178
|
}
|
|
935
1179
|
async function openRecentProject(root: string) {
|
|
1180
|
+
// One entry for the whole sequence, not three surfaces for its three phases. Opening a project
|
|
1181
|
+
// Used to chain a blocking spinner (dependencies), a transient status line (git sync) and a
|
|
1182
|
+
// Confirm-plus-spinner (@jxsuite update), none of them cancellable and none of them surviving
|
|
1183
|
+
// The frame they were drawn in — so an open that took forty seconds was indistinguishable from
|
|
1184
|
+
// One that had silently failed. Spec studio.md §16.4.
|
|
1185
|
+
const activity = beginActivity({
|
|
1186
|
+
title: `Opening ${root.split("/").pop() || root}`,
|
|
1187
|
+
source: "Open Project",
|
|
1188
|
+
steps: [
|
|
1189
|
+
"Sync with the remote",
|
|
1190
|
+
"Install dependencies",
|
|
1191
|
+
"Read the project",
|
|
1192
|
+
"Open the home page",
|
|
1193
|
+
],
|
|
1194
|
+
});
|
|
936
1195
|
try {
|
|
937
1196
|
const platform = getPlatform();
|
|
938
1197
|
|
|
@@ -940,6 +1199,24 @@ async function openRecentProject(root: string) {
|
|
|
940
1199
|
// Window (focusing an existing window if it's already open) rather than replacing this project.
|
|
941
1200
|
if (projectState && platform.openProjectInNewWindow) {
|
|
942
1201
|
await platform.openProjectInNewWindow(root);
|
|
1202
|
+
activity.done("Opened in another window");
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
/* THE LAST UNGUARDED DESTROYER, and it is the biggest one.
|
|
1207
|
+
Past this point the window replaces its project, and `closeAllTabs()` below disposes every
|
|
1208
|
+
open document — dirty or not, with no prompt anywhere on the path. ⌘W, the tab ×, quitting
|
|
1209
|
+
and the preview slot's replacement each acquired a gate; this one, which throws away the
|
|
1210
|
+
whole workspace at once, never had one.
|
|
1211
|
+
Asked HERE rather than lower down because everything below it is one-way: `setWindowProject`
|
|
1212
|
+
binds this window's backend to the new root, `platform.projectRoot` moves the base every
|
|
1213
|
+
relative path resolves against, and `resetProjectShell` drops the surfaces that describe the
|
|
1214
|
+
project being left. A prompt after any of those can be answered "keep editing" and leave the
|
|
1215
|
+
app pointing at a project it is not showing. The one cost of asking first is the
|
|
1216
|
+
already-open-elsewhere case just below, where the switch turns out to be a window focus and
|
|
1217
|
+
nothing is closed: a redundant question, and never a lost document. */
|
|
1218
|
+
if (!(await tabStrip.confirmCloseAll("Opening another project"))) {
|
|
1219
|
+
activity.done("Kept the current project");
|
|
943
1220
|
return;
|
|
944
1221
|
}
|
|
945
1222
|
|
|
@@ -948,11 +1225,17 @@ async function openRecentProject(root: string) {
|
|
|
948
1225
|
if (platform.setWindowProject) {
|
|
949
1226
|
const res = await platform.setWindowProject(root);
|
|
950
1227
|
if (res.deduped) {
|
|
1228
|
+
activity.done("Already open in another window");
|
|
951
1229
|
return;
|
|
952
1230
|
}
|
|
953
1231
|
}
|
|
954
1232
|
|
|
955
1233
|
platform.projectRoot = root;
|
|
1234
|
+
// Source control, the stylebook selection and the settings tab describe the project being
|
|
1235
|
+
// Left behind; carrying them over showed the previous repository's branch and file count
|
|
1236
|
+
// Under the new project's name. The poll timer is re-armed by the panel's next render.
|
|
1237
|
+
cleanupGitPanel();
|
|
1238
|
+
resetProjectShell();
|
|
956
1239
|
// The format registry is cached per project — the previous root's registry (often empty on a
|
|
957
1240
|
// Fresh desktop launch) must not answer for this project, or non-JSON documents fail with
|
|
958
1241
|
// "No format class imported" until a reload. Mirrors openProject in files.ts.
|
|
@@ -977,8 +1260,11 @@ async function openRecentProject(root: string) {
|
|
|
977
1260
|
});
|
|
978
1261
|
setWorkspaceProject(root, config);
|
|
979
1262
|
|
|
1263
|
+
activity.step("Sync with the remote");
|
|
980
1264
|
await autoSyncProjectOnOpen();
|
|
1265
|
+
activity.step("Install dependencies");
|
|
981
1266
|
await ensureDependenciesInstalled();
|
|
1267
|
+
activity.step("Read the project");
|
|
982
1268
|
await loadDirectory(".");
|
|
983
1269
|
await loadComponentRegistry();
|
|
984
1270
|
|
|
@@ -1000,13 +1286,14 @@ async function openRecentProject(root: string) {
|
|
|
1000
1286
|
}
|
|
1001
1287
|
|
|
1002
1288
|
addRecentProject(requireProjectState().name, root);
|
|
1003
|
-
|
|
1004
|
-
renderActivityBar();
|
|
1289
|
+
setActivityTab("files");
|
|
1005
1290
|
renderLeftPanel();
|
|
1006
|
-
|
|
1291
|
+
// The project's name is now permanent state in the status bar's PROJECT field.
|
|
1007
1292
|
|
|
1008
|
-
|
|
1293
|
+
activity.step("Open the home page");
|
|
1294
|
+
await openLastSessionOrHome();
|
|
1009
1295
|
ensureFsSync();
|
|
1296
|
+
activity.done(`Opened ${requireProjectState().name}`);
|
|
1010
1297
|
void maybePromptJxsuiteUpdate(root);
|
|
1011
1298
|
} catch (error) {
|
|
1012
1299
|
// The project likely moved or was deleted — drop the stale entry so it stops cluttering the
|
|
@@ -1014,7 +1301,10 @@ async function openRecentProject(root: string) {
|
|
|
1014
1301
|
removeRecentProject(root);
|
|
1015
1302
|
toolbarPanel.render();
|
|
1016
1303
|
render();
|
|
1017
|
-
|
|
1304
|
+
// `fail` raises the Problem, so this path does NOT also notify: an operation with an Activity
|
|
1305
|
+
// Entry reports once (`studio-ui-guidelines.md` §13.3 rule 3).
|
|
1306
|
+
activity.log(errorMessage(error));
|
|
1307
|
+
activity.fail(`Could not open the project at ${root}.`, { path: root });
|
|
1018
1308
|
}
|
|
1019
1309
|
}
|
|
1020
1310
|
function renderFilesTemplate() {
|
|
@@ -1028,17 +1318,171 @@ function openFileFromTree(path: string) {
|
|
|
1028
1318
|
return openFileInTab(path);
|
|
1029
1319
|
}
|
|
1030
1320
|
|
|
1031
|
-
// ───
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1321
|
+
// ─── Commands and the keyboard ────────────────────────────────────────────────
|
|
1322
|
+
|
|
1323
|
+
/**
|
|
1324
|
+
* Pointer/pan state for ONE pane's stage, read fresh on every gesture.
|
|
1325
|
+
*
|
|
1326
|
+
* Takes the surface rather than resolving `view.panX` / `getCanvasMode()`, both of which answered
|
|
1327
|
+
* for the focused pane: a wheel over the side pane wrote the primary's pan offsets and asked the
|
|
1328
|
+
* primary's mode whether panning was even allowed.
|
|
1329
|
+
*/
|
|
1330
|
+
const stageContext = (surface: CanvasSurface) => ({
|
|
1331
|
+
applyTransform: () => applyTransform(surface),
|
|
1332
|
+
canvasMode: canvasModeOfPane(surface.paneId),
|
|
1333
|
+
panX: surface.panX,
|
|
1334
|
+
panY: surface.panY,
|
|
1335
|
+
setPan: (x: number, y: number) => {
|
|
1336
|
+
surface.panX = x;
|
|
1337
|
+
surface.panY = y;
|
|
1338
|
+
surface.needsCenter = false;
|
|
1339
|
+
},
|
|
1340
|
+
});
|
|
1341
|
+
|
|
1342
|
+
/**
|
|
1343
|
+
* The registry the keyboard dispatches through.
|
|
1344
|
+
*
|
|
1345
|
+
* Built here, in the bootstrap, because `createCommandRegistry` deliberately has no module-level
|
|
1346
|
+
* singleton: the context it closes over is this window's, and a second window gets its own.
|
|
1347
|
+
*/
|
|
1348
|
+
const commandRegistry = createCommandRegistry({
|
|
1349
|
+
getContext: createLiveContext({
|
|
1350
|
+
aiConfigured: hasAiCredentials,
|
|
1351
|
+
// The probe `live-context.ts` declared optional and nobody ever passed, so `ctx.ai.streaming`
|
|
1352
|
+
// Read false forever. `assistant.stop` is gated on it.
|
|
1353
|
+
aiStreaming: isAssistantStreaming,
|
|
1354
|
+
canvasMode: getCanvasMode,
|
|
1355
|
+
isCaretActive,
|
|
1356
|
+
isModalOpen,
|
|
1357
|
+
platform: () => (hasPlatform() ? getPlatform() : null),
|
|
1358
|
+
}),
|
|
1359
|
+
});
|
|
1360
|
+
|
|
1361
|
+
registerStudioCommands(
|
|
1362
|
+
commandRegistry,
|
|
1363
|
+
{
|
|
1364
|
+
// The toolbar owns this, and the difference matters on the desktop: its `openUrlExternally`
|
|
1365
|
+
// Hands the URL to the launcher's preview-navigate handler — the OS browser — where a bare
|
|
1366
|
+
// `window.open` would open a webview with no address bar. The browser build falls back to a
|
|
1367
|
+
// New tab either way.
|
|
1368
|
+
openInBrowser: () => toolbarPanel.runOpenInBrowser(),
|
|
1369
|
+
openProject,
|
|
1370
|
+
// Wrapped rather than passed by reference: `saveFile` takes an optional tab and reports
|
|
1371
|
+
// Whether the bytes landed, and a hook that neither supplies one nor reads the answer must not
|
|
1372
|
+
// Silently forward its own first argument as the tab to save.
|
|
1373
|
+
saveDocument: async () => {
|
|
1374
|
+
await saveFile();
|
|
1375
|
+
},
|
|
1376
|
+
},
|
|
1377
|
+
stageContext,
|
|
1378
|
+
);
|
|
1379
|
+
|
|
1380
|
+
// Tab navigation (⌃Tab MRU cycling, ⌘⇧T reopen-closed) is defined beside the tab model it drives.
|
|
1381
|
+
registerTabCommands(commandRegistry, { openFile: openFileInTab, openFileInPane });
|
|
1382
|
+
|
|
1383
|
+
/* The derivation's own two verbs (§18.4), beside the `PaneDerivation` they read and write rather
|
|
1384
|
+
than in `paneCommands()`. `tests/app-commands-composition.test.ts` is the guard that this line
|
|
1385
|
+
and `appCommandSet()`'s entry stay in step. */
|
|
1386
|
+
commandRegistry.registerAll(derivationCommands(derivationDeps));
|
|
1387
|
+
|
|
1388
|
+
/*
|
|
1389
|
+
* The rest of the app's contribution points, each defined beside the state it writes.
|
|
1390
|
+
*
|
|
1391
|
+
* This block is the bootstrap's whole share of plan §13's registry work: every record below lives
|
|
1392
|
+
* in the module that implements it, and this is the ONE place that composes them into the registry
|
|
1393
|
+
* `__jxAutomation.run` projects. Nothing here decides what a command is called, when it is
|
|
1394
|
+
* available or what it does — that would be the second definition site the design exists to
|
|
1395
|
+
* prevent (plan §2, principle 1).
|
|
1396
|
+
*/
|
|
1397
|
+
registerShellViewCommands(commandRegistry, {
|
|
1398
|
+
// The registry's own answer, so a gated-off panel cannot be persisted as a showing tab.
|
|
1399
|
+
panelAvailable: (id) => {
|
|
1400
|
+
const panel = getPanel(id);
|
|
1401
|
+
return panel ? (panel.when?.(commandRegistry.context()) ?? true) : true;
|
|
1043
1402
|
},
|
|
1044
|
-
|
|
1403
|
+
inspectorTab: () => rightPanelMod.inspectorTab(),
|
|
1404
|
+
setInspectorTab: (tab) => rightPanelMod.setInspectorTab(tab),
|
|
1405
|
+
});
|
|
1406
|
+
registerCanvasViewCommands(commandRegistry, {
|
|
1407
|
+
getCanvasMode,
|
|
1408
|
+
renderPane: renderCanvas,
|
|
1409
|
+
setCanvasMode,
|
|
1410
|
+
setResolvingOpen: paneContext.setResolvingOpen,
|
|
1411
|
+
});
|
|
1412
|
+
registerSelectionSetCommand(commandRegistry);
|
|
1413
|
+
registerInspectorCommands(commandRegistry);
|
|
1414
|
+
/* Search appearance, behind one record with two buttons: the Document Header card's and the Page
|
|
1415
|
+
panel's. A surface that IS the capability is one the palette cannot reach. */
|
|
1416
|
+
registerSeoCommands(commandRegistry);
|
|
1417
|
+
registerA11yCommands(commandRegistry);
|
|
1418
|
+
/* The element menu's eight verbs, in the APP registry rather than only in the popover's own. They
|
|
1419
|
+
have always declared `menus: ["context/element", "palette"]`; the palette has never listed one,
|
|
1420
|
+
because the only registry holding them was the one `editor/context-menu.ts` builds for itself.
|
|
1421
|
+
Their target falls back to the selection when no menu is open (`commandTarget`), which is what
|
|
1422
|
+
makes "Paste Style" a keyboard-reachable verb rather than a right-click-only one. */
|
|
1423
|
+
commandRegistry.registerAll(liveElementCommands());
|
|
1424
|
+
/* …and the navigation seam for the popover's FALLBACK registry, which it uses only in the window
|
|
1425
|
+
before this line runs (`contextMenuRegistry()` returns the app's registry once one exists). A
|
|
1426
|
+
fallback holding a no-op where navigation belongs is a registry that lies. */
|
|
1427
|
+
setContextMenuNavigate(navigateToComponent);
|
|
1428
|
+
registerDataExplorerCommands(commandRegistry, { renderLeftPanel });
|
|
1429
|
+
registerSignalsCommands(commandRegistry);
|
|
1430
|
+
registerFormulaEditorCommands(commandRegistry);
|
|
1431
|
+
registerGridCommands(commandRegistry);
|
|
1432
|
+
registerSettingsCommands(commandRegistry);
|
|
1433
|
+
registerPreferencesCommands(commandRegistry);
|
|
1434
|
+
registerLibraryCommands(commandRegistry);
|
|
1435
|
+
registerContentCommands(commandRegistry);
|
|
1436
|
+
/* The four translation verbs: open a sibling translation, create the missing one, show the
|
|
1437
|
+
Languages panel, declare a language. `i18n.switchLocale` is deliberately not among them — it sets
|
|
1438
|
+
a rendering context, so it lives with the other axis-3 verbs in `canvasViewCommands`. */
|
|
1439
|
+
registerI18nCommands(commandRegistry);
|
|
1440
|
+
registerNewProjectCommands(commandRegistry);
|
|
1441
|
+
registerStyleCommands(commandRegistry);
|
|
1442
|
+
registerSourceControlCommands(commandRegistry);
|
|
1443
|
+
registerPublishCommands(commandRegistry);
|
|
1444
|
+
registerGridViewCommands(commandRegistry);
|
|
1445
|
+
registerRedirectsCommands(commandRegistry);
|
|
1446
|
+
registerAboutCommands(commandRegistry);
|
|
1447
|
+
registerCollabCommands(commandRegistry);
|
|
1448
|
+
/* The `Assistant:` family (§11.1) — Focus Composer, New Chat, Chat History, Attach Selection, Retry
|
|
1449
|
+
and Stop. Every one existed as a button in the chat view and as nothing else, so the category held
|
|
1450
|
+
zero records and none of them was in the palette, bindable, or reachable by name. The chat header
|
|
1451
|
+
and the error row render these ids through the registry now, which is what makes this line the
|
|
1452
|
+
definition site rather than a second copy. */
|
|
1453
|
+
commandRegistry.registerAll(assistantCommands());
|
|
1454
|
+
/*
|
|
1455
|
+
* The structural selection verbs — Move Up/Down/In/Out, Convert to Component, Edit Component.
|
|
1456
|
+
*
|
|
1457
|
+
* They were already defined in `panels/block-action-bar.ts` beside the mutations they perform, but
|
|
1458
|
+
* only ever registered into that panel's OWN registry, so the palette, the keyboard and
|
|
1459
|
+
* `__jxAutomation` could not see them. `commandTargetPath()` falls back to the current selection
|
|
1460
|
+
* when no menu is open, which is precisely the app-wide meaning of these verbs.
|
|
1461
|
+
*/
|
|
1462
|
+
registerSelectionCommands(commandRegistry, { convertToComponent, navigateToComponent });
|
|
1463
|
+
/* The format family — Bold, Italic, Code, Link and the four with no chord.
|
|
1464
|
+
Same story as the structural verbs above, one wave later: they were the block action bar's own,
|
|
1465
|
+
as a hand-written keydown switch rather than records, and the switch returned early whenever
|
|
1466
|
+
focus was inside the canvas iframe — which is the only place a canvas caret can be. */
|
|
1467
|
+
commandRegistry.registerAll(formatCommands());
|
|
1468
|
+
|
|
1469
|
+
initShortcuts(commandRegistry, stageContext);
|
|
1470
|
+
|
|
1471
|
+
/* Hand the canvas frames the chord table.
|
|
1472
|
+
The iframe cannot see the registry, so it used to answer "does the parent want this keystroke?"
|
|
1473
|
+
from three hand-written lists that disagreed with the registry in both directions — ⌘A forwarded
|
|
1474
|
+
and prevented with nothing to run it, ⌘B withheld for an engine that never handled it. It
|
|
1475
|
+
resolves against this table instead, and `publishKeymap()` reposts it whenever a rebinding lands,
|
|
1476
|
+
which is what makes Preferences › Keyboard reach inside the page. */
|
|
1477
|
+
setKeymapSource(
|
|
1478
|
+
() => ({
|
|
1479
|
+
chords: chordsInScopes(commandRegistry.keymap, FRAME_KEY_SCOPES),
|
|
1480
|
+
mac: commandRegistry.keymap.mac,
|
|
1481
|
+
}),
|
|
1482
|
+
(listener) => commandRegistry.keymap.onChange(listener),
|
|
1483
|
+
);
|
|
1484
|
+
|
|
1485
|
+
// The gated scripting surface (`?automation=1` only) is a PROJECTION of the registry above, so it
|
|
1486
|
+
// Installs after it — still inside this module's synchronous body, which is what the screenshot
|
|
1487
|
+
// Runner's `waitForFunction(() => window.__jxAutomation)` and every deferred project load depend on.
|
|
1488
|
+
installAutomationHook({ registry: commandRegistry, seedAssistantMessages, seedPublishConnected });
|