@jxsuite/studio 6.1.2 → 8.0.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/README.md +5 -5
- package/dist/chunks/2020-5wtzce5g.js +4 -0
- package/dist/chunks/adapter-5tphg7xe.js +4 -0
- package/dist/chunks/add-new-flow-psym14sn.js +4 -0
- package/dist/chunks/cf-account-picker-haqazq5r.js +4 -0
- package/dist/chunks/cf-settings-h2wrh6hq.js +4 -0
- package/dist/chunks/{color-area-0fj8a4ga.js → color-area-6rbra5dy.js} +2 -2
- package/dist/chunks/{color-field-xch8kx2g.js → color-field-gy56c94g.js} +3 -3
- package/dist/chunks/color-m7r1vk54.js +4 -0
- package/dist/chunks/{color-slider-jxd8v350.js → color-slider-sqqdyf6c.js} +2 -2
- package/dist/chunks/{combobox-p0efwjke.js → combobox-72h32ejk.js} +3 -3
- package/dist/chunks/components-r22xca09.js +4 -0
- package/dist/chunks/content-source-mmppmcyg.js +4 -0
- package/dist/chunks/convert-to-repeater-he70fc2b.js +4 -0
- package/dist/chunks/editor-ccd9jwsa.js +4 -0
- package/dist/chunks/entry-commands-6qxz9e8p.js +4 -0
- package/dist/chunks/extension-sections-0egb88dm.js +4 -0
- package/dist/chunks/file-ops-qnab36ek.js +4 -0
- package/dist/chunks/files-rcn56qvm.js +4 -0
- package/dist/chunks/format-host-yxz0kjjs.js +4 -0
- package/dist/chunks/fs-events-d1qxq4z4.js +4 -0
- package/dist/chunks/grid-open-4z1g062t.js +4 -0
- package/dist/chunks/{grid-view-d2wq9ezg.js → grid-view-qg1b5eqx.js} +25 -25
- package/dist/chunks/{icons-pnfz478q.js → icons-63bjtbs7.js} +2 -2
- package/dist/chunks/{iframe-entry-jdcktez8.js → iframe-entry-2zggzajv.js} +4 -4
- package/dist/chunks/{iframe-entry-zap785cw.js → iframe-entry-76j8vwwx.js} +4 -4
- package/dist/chunks/iframe-entry-99nr5zfb.js +5 -0
- package/dist/chunks/{iframe-entry-x0ygf2yr.js → iframe-entry-pkgmcgp0.js} +4 -4
- package/dist/chunks/iframe-host-ssv7kyj0.js +4 -0
- package/dist/chunks/{index-zpjmzqfk.js → index-32eqzv1k.js} +3 -3
- package/dist/chunks/index-f0jn7nnp.js +4 -0
- package/dist/chunks/{javascript-m54mnwp6.js → javascript-pjp734q4.js} +3 -3
- package/dist/chunks/jsonMode-njkvsa02.js +13 -0
- package/dist/chunks/jx-validate-ttasrbh7.js +4 -0
- package/dist/chunks/layers-bgyr11tr.js +4 -0
- package/dist/chunks/layers-panel-7ez66zeq.js +4 -0
- package/dist/chunks/{listbox-e74qjyq6.js → listbox-c421cf8c.js} +2 -2
- package/dist/chunks/lit-html-v9gh687x.js +4 -0
- package/dist/chunks/media-library-sq468wts.js +4 -0
- package/dist/chunks/media-picker-shfe59pt.js +4 -0
- package/dist/chunks/{menu-n6yzswmy.js → menu-bdpzbfvs.js} +2 -2
- package/dist/chunks/{monaco-binding-7gx83x3r.js → monaco-binding-vq1hgnn0.js} +3 -3
- package/dist/chunks/monaco-buffer-3afgwvbp.js +4 -0
- package/dist/chunks/monaco-setup-xrd2cbkp.js +4 -0
- package/dist/chunks/notify-kzrjr3fv.js +4 -0
- package/dist/chunks/parse-v77xtwv7.js +4 -0
- package/dist/chunks/platform-7v4rtd7m.js +4 -0
- package/dist/chunks/popover-b89sfej7.js +4 -0
- package/dist/chunks/publish-panel-2z9gw8ee.js +4 -0
- package/dist/chunks/publish-quick-qx2455cj.js +4 -0
- package/dist/chunks/publish-workspace-wrc1dgwx.js +4 -0
- package/dist/chunks/quick-search-n0t0eqxb.js +4 -0
- package/dist/chunks/references-9547rg5s.js +4 -0
- package/dist/chunks/right-panel-wsgnr9pn.js +4 -0
- package/dist/chunks/source-control-kwktr9y6.js +4 -0
- package/dist/chunks/{split-n2gfpm5m.js → split-c9zfdgxf.js} +2 -2
- package/dist/chunks/studio-03ct6yfv.js +9 -0
- package/dist/chunks/studio-07rcfje3.js +5 -0
- package/dist/chunks/studio-3e54qh39.js +7 -0
- package/dist/chunks/studio-3exxhz5g.js +5 -0
- package/dist/chunks/studio-3q40e3rj.js +5 -0
- package/dist/chunks/studio-3qeh9t0h.js +6 -0
- package/dist/chunks/studio-3qqjeep5.js +6 -0
- package/dist/chunks/studio-43sd2mam.js +4 -0
- package/dist/chunks/studio-4hp8pkyt.js +5 -0
- package/dist/chunks/studio-58xg6j2q.js +9 -0
- package/dist/chunks/studio-5g0rprdt.js +7 -0
- package/dist/chunks/{studio-2j1cb95x.js → studio-6m380wp7.js} +4 -4
- package/dist/chunks/studio-80jyh1xh.js +4 -0
- package/dist/chunks/studio-83mk4bgg.js +9 -0
- package/dist/chunks/{studio-v0jzqdjm.js → studio-881z1xrx.js} +4 -4
- package/dist/chunks/studio-8m0y2qe7.js +5 -0
- package/dist/chunks/{studio-d81xfh1g.js → studio-8tr6cfrc.js} +4 -4
- package/dist/chunks/studio-8vcvfs7r.js +6 -0
- package/dist/chunks/studio-9tmebf0s.js +5 -0
- package/dist/chunks/studio-a2bvaptz.js +5 -0
- package/dist/chunks/studio-b7y5mr30.js +5 -0
- package/dist/chunks/studio-bavb31r0.js +5 -0
- package/dist/chunks/{studio-dgz8d0c1.js → studio-bbe4c90a.js} +4 -4
- package/dist/chunks/studio-c05wdnt3.js +5 -0
- package/dist/chunks/studio-ccv2svc6.js +5 -0
- package/dist/chunks/studio-d5grtb9e.js +16 -0
- package/dist/chunks/studio-df0baea9.js +5 -0
- package/dist/chunks/studio-dgk4dqtx.js +7 -0
- package/dist/chunks/studio-e1g64q43.js +5 -0
- package/dist/chunks/studio-ed1hp6zf.js +6 -0
- package/dist/chunks/studio-ed3xx40h.js +7 -0
- package/dist/chunks/studio-f14e1zdj.js +5 -0
- package/dist/chunks/studio-fbhxxc5f.js +5 -0
- package/dist/chunks/studio-fyh0jc96.js +5 -0
- package/dist/chunks/studio-gcxk20n7.js +5 -0
- package/dist/chunks/studio-jaaz8ce9.js +5 -0
- package/dist/chunks/{studio-90pq9km1.js → studio-jcnsdaep.js} +4 -4
- package/dist/chunks/studio-jsgf2q5k.js +7 -0
- package/dist/chunks/{studio-0q988f9w.js → studio-k3xxbagr.js} +3 -3
- package/dist/chunks/{studio-5ve6bbep.js → studio-k7evxqh4.js} +4 -4
- package/dist/chunks/studio-kt3pjkwd.js +5 -0
- package/dist/chunks/studio-m8g16bgz.js +5 -0
- package/dist/chunks/studio-mn0476ag.js +5 -0
- package/dist/chunks/studio-mx1smyq0.js +5 -0
- package/dist/chunks/studio-n0c5h6mf.js +5 -0
- package/dist/chunks/{studio-485046te.js → studio-nh6d342n.js} +7 -7
- package/dist/chunks/studio-nqxqf8p2.js +4 -0
- package/dist/chunks/studio-p4nrkvqr.js +5 -0
- package/dist/chunks/{studio-1fxkaaea.js → studio-pbxa5fpc.js} +4 -4
- package/dist/chunks/studio-pgmvdv7z.js +7 -0
- package/dist/chunks/studio-ph84avkf.js +5 -0
- package/dist/chunks/{studio-tq0rtfwj.js → studio-q0mvbaq6.js} +4 -4
- package/dist/chunks/studio-q0tccd43.js +5 -0
- package/dist/chunks/studio-q7rtx6zq.js +14 -0
- package/dist/chunks/{studio-a3g98wjr.js → studio-qgh13jtz.js} +4 -4
- package/dist/chunks/studio-qmn1xc24.js +5 -0
- package/dist/chunks/studio-qwdqwkj0.js +5 -0
- package/dist/chunks/studio-r5qy30vx.js +5 -0
- package/dist/chunks/studio-rj9eamf8.js +5 -0
- package/dist/chunks/studio-rptgsk96.js +5 -0
- package/dist/chunks/studio-rthyg42z.js +928 -0
- package/dist/chunks/studio-s19tp1xr.js +5 -0
- package/dist/chunks/studio-s32578pn.js +5 -0
- package/dist/chunks/studio-svb7yyve.js +5 -0
- package/dist/chunks/studio-syreb644.js +5 -0
- package/dist/chunks/studio-tf9jryb1.js +10 -0
- package/dist/chunks/studio-tfkznv39.js +5 -0
- package/dist/chunks/studio-tft6r105.js +5 -0
- package/dist/chunks/studio-txf2kysx.js +5 -0
- package/dist/chunks/studio-v8867ddv.js +12 -0
- package/dist/chunks/studio-vxq7fcvv.js +5 -0
- package/dist/chunks/studio-w4rz9hdk.js +5 -0
- package/dist/chunks/studio-wbcjbhy1.js +9 -0
- package/dist/chunks/studio-wc649wss.js +5 -0
- package/dist/chunks/studio-wys23mxz.js +5 -0
- package/dist/chunks/studio-x1x167jh.js +5 -0
- package/dist/chunks/studio-x9hsze61.js +5 -0
- package/dist/chunks/studio-y0n7yxea.js +9 -0
- package/dist/chunks/studio-y3ahhdpx.js +5 -0
- package/dist/chunks/studio-z97c48fk.js +5 -0
- package/dist/chunks/studio-zat62m0c.js +5 -0
- package/dist/chunks/studio-zcd2e7yz.js +5 -0
- package/dist/chunks/studio-zz9qe3rw.js +64 -0
- package/dist/chunks/toast-1gv0h53h.js +4 -0
- package/dist/chunks/{toast-host-ke5z4ng7.js → toast-host-1yg5ar8d.js} +3 -3
- package/dist/chunks/{tooltip-5v8ngwj2.js → tooltip-yypz28gq.js} +3 -3
- package/dist/chunks/{tree-mdq3v5cw.js → tree-9xa4hm7n.js} +2 -2
- package/dist/chunks/tsMode-epyfz1hv.js +14 -0
- package/dist/chunks/workspace-dz7nxfhv.js +4 -0
- package/dist/chunks/workspace-home-xsd7hkjw.js +4 -0
- package/dist/chunks/{ws-client-nze0k6mv.js → ws-client-bv552sfx.js} +3 -3
- package/dist/iframe-entry.js +20 -16
- package/dist/manifest.json +77 -63
- package/dist/studio.js +103 -104
- package/dist/workers/ts.worker.js +3 -3
- package/package.json +12 -11
- package/src/browse/component-examples.ts +272 -0
- package/src/browse/library-commands.ts +64 -104
- package/src/browse/library-layouts.ts +263 -49
- package/src/browse/library-model.ts +181 -133
- package/src/browse/library-pane.ts +738 -446
- package/src/browse/library-preview.ts +149 -19
- package/src/browse/library-source.ts +308 -41
- package/src/browse/preview-root.ts +78 -0
- package/src/browse/preview-style.ts +155 -0
- package/src/canvas/asset-refs.ts +16 -1
- package/src/canvas/canvas-live-render.ts +222 -30
- package/src/canvas/canvas-origin.ts +37 -12
- package/src/canvas/canvas-page-context.ts +112 -0
- package/src/canvas/canvas-render.ts +113 -107
- package/src/canvas/canvas-utils.ts +57 -12
- package/src/canvas/iframe-drop.ts +107 -0
- package/src/canvas/iframe-entry.ts +24 -12
- package/src/canvas/iframe-host.ts +134 -62
- package/src/canvas/iframe-keys.ts +11 -0
- package/src/canvas/iframe-patch.ts +8 -7
- package/src/canvas/iframe-protocol.ts +24 -5
- package/src/canvas/iframe-render.ts +48 -1
- package/src/canvas/iframe-subtree.ts +5 -2
- package/src/canvas/npm-modules.ts +22 -0
- package/src/canvas/preview-navigate.ts +16 -0
- package/src/canvas/repeater-sources.ts +200 -0
- package/src/canvas/stage-scrollbar.ts +5 -1
- package/src/canvas/surface-registry.ts +6 -0
- package/src/commands/app-commands.ts +16 -6
- package/src/commands/budget.ts +10 -8
- package/src/commands/context.ts +35 -10
- package/src/commands/defaults.ts +69 -45
- package/src/commands/keymap.ts +9 -0
- package/src/commands/levels.ts +11 -5
- package/src/commands/live-context.ts +2 -0
- package/src/commands/registry.ts +10 -1
- package/src/content/collection-match.ts +6 -5
- package/src/content/collection-pages.ts +412 -0
- package/src/content/content-collections.ts +270 -0
- package/src/content/entry-commands.ts +51 -60
- package/src/content/entry-editor.ts +111 -18
- package/src/content/entry-fields.ts +13 -1
- package/src/content/entry-inspector.ts +225 -0
- package/src/content/entry-model.ts +17 -3
- package/src/content/entry-open.ts +80 -0
- package/src/content/entry-summaries.ts +440 -0
- package/src/content/file-record-ids.ts +80 -0
- package/src/content/file-records.ts +125 -0
- package/src/content/page-summaries.ts +129 -0
- package/src/content/record-entry.ts +201 -0
- package/src/content/writing-banner.ts +177 -0
- package/src/create/add-new-commands.ts +185 -0
- package/src/create/add-new-flow.ts +1116 -0
- package/src/create/add-new.ts +1014 -0
- package/src/create/element-name.ts +121 -0
- package/src/editor/convert-to-component.ts +67 -43
- package/src/editor/file-drop-action.ts +9 -1
- package/src/editor/inline-edit-apply.ts +44 -0
- package/src/editor/inline-edit.ts +10 -0
- package/src/editor/insert-component.ts +91 -0
- package/src/editor/insert-zone-action.ts +13 -4
- package/src/editor/shortcuts.ts +44 -22
- package/src/editor/slash-menu.ts +51 -8
- package/src/files/components.ts +74 -10
- package/src/files/file-ops.ts +15 -5
- package/src/files/files.ts +164 -150
- package/src/files/fs-events.ts +18 -7
- package/src/files/media-meta.ts +95 -1
- package/src/files/media-types.ts +55 -0
- package/src/files/media-upload.ts +209 -52
- package/src/files/new-file-name.ts +156 -0
- package/src/files/serialize-document.ts +13 -3
- package/src/format/format-host.ts +15 -0
- package/src/github/github-auth.ts +2 -2
- package/src/github/github-publish.ts +4 -3
- package/src/grid/grid-controller.ts +5 -1
- package/src/grid/grid-open.ts +0 -27
- package/src/grid/grid-panel.ts +54 -2
- package/src/grid/grid-reveal.ts +34 -0
- package/src/grid/grid-source.ts +3 -25
- package/src/grid/grid-view.ts +31 -6
- package/src/grid/sources/content-source.ts +75 -7
- package/src/grid/tabulator-tables.d.ts +6 -0
- package/src/hosting/document.ts +2 -2
- package/src/i18n/i18n-commands.ts +2 -1
- package/src/media/media-commands.ts +181 -0
- package/src/media/media-library-model.ts +232 -0
- package/src/media/media-library.ts +1283 -0
- package/src/media/media-pane.ts +47 -19
- package/src/packages/pull-package-sync.ts +1 -1
- package/src/panels/activity-panel.ts +21 -5
- package/src/panels/ai-chat/chat-markdown.ts +8 -8
- package/src/panels/block-action-bar.ts +11 -11
- package/src/panels/canvas-dnd-bridge.ts +5 -2
- package/src/panels/chat-panel.ts +6 -6
- package/src/panels/collections-panel.ts +306 -0
- package/src/panels/component-preview.ts +35 -13
- package/src/panels/data-explorer.ts +1 -1
- package/src/panels/data-grid.ts +4 -0
- package/src/panels/dnd.ts +17 -30
- package/src/panels/elements-panel.ts +9 -2
- package/src/panels/empty-state.ts +2 -2
- package/src/panels/formula-workspace.ts +2 -3
- package/src/panels/frontmatter-fields.ts +9 -9
- package/src/panels/git-diff-open.ts +9 -5
- package/src/panels/head-panel.ts +369 -103
- package/src/panels/i18n-panel.ts +11 -10
- package/src/panels/imports-panel.ts +2 -0
- package/src/panels/jump-bar.ts +27 -2
- package/src/panels/layers-panel.ts +36 -30
- package/src/panels/left-panel.ts +22 -8
- package/src/panels/navigator-panels.ts +23 -11
- package/src/panels/pane-context.ts +111 -29
- package/src/panels/pane-grid.ts +6 -6
- package/src/panels/panel-registry.ts +14 -9
- package/src/panels/properties-panel.ts +7 -3
- package/src/panels/right-panel.ts +199 -43
- package/src/panels/seo-modal.ts +41 -108
- package/src/panels/settings-menu.ts +52 -50
- package/src/panels/settings-pane.ts +268 -162
- package/src/panels/signals-panel.ts +3 -4
- package/src/panels/site-model.ts +367 -0
- package/src/panels/site-panel.ts +638 -0
- package/src/panels/style-panel.ts +37 -3
- package/src/panels/stylebook-doc.ts +142 -10
- package/src/panels/stylebook-layers-panel.ts +39 -80
- package/src/panels/stylebook-panel.ts +47 -10
- package/src/panels/styles-panel.ts +327 -0
- package/src/panels/tab-strip.ts +109 -25
- package/src/platform.ts +1 -1
- package/src/platforms/cloud.ts +28 -1
- package/src/platforms/default-platform.ts +4 -4
- package/src/platforms/devserver.ts +51 -25
- package/src/publish/deploy-checklist.ts +12 -7
- package/src/publish/pages-service.ts +38 -14
- package/src/publish/publish-changes.ts +491 -0
- package/src/publish/publish-commands.ts +83 -18
- package/src/publish/publish-ids.ts +49 -0
- package/src/publish/publish-quick.ts +148 -0
- package/src/publish/publish-workspace.ts +926 -0
- package/src/publish/source-control.ts +907 -0
- package/src/services/automation.ts +16 -20
- package/src/services/cf-settings.ts +4 -4
- package/src/services/connection.ts +2 -2
- package/src/services/directory-picker.ts +2 -2
- package/src/services/references.ts +0 -0
- package/src/services/render-critic.ts +21 -2
- package/src/services/settings/definitions.ts +25 -14
- package/src/settings/contributed-section.ts +49 -10
- package/src/settings/css-vars-editor.ts +93 -27
- package/src/settings/developer-panels.ts +43 -0
- package/src/settings/extension-sections.ts +20 -2
- package/src/settings/general-settings.ts +91 -21
- package/src/settings/preferences-dialog.ts +8 -1
- package/src/settings/project-sections.ts +27 -10
- package/src/settings/schema-field-ui.ts +3 -1
- package/src/settings/section-registry.ts +116 -11
- package/src/settings/settings-dialog.ts +192 -0
- package/src/settings/settings-document.ts +182 -162
- package/src/shell.ts +467 -431
- package/src/site-context.ts +64 -10
- package/src/state.ts +28 -9
- package/src/studio.ts +194 -41
- package/src/style/project-styles.ts +34 -0
- package/src/style/stylebook-specimens.ts +89 -0
- package/src/style/stylebook-target.ts +26 -0
- package/src/style/tag-inheritance.ts +105 -0
- package/src/style/variables-focus.ts +61 -0
- package/src/style/variables-view.ts +235 -0
- package/src/surfaces/add-new.json +1482 -0
- package/src/surfaces/add-new.ts +325 -0
- package/src/surfaces/ai-chat.ts +2 -2
- package/src/surfaces/boot-failure.json +4 -3
- package/src/surfaces/bottom-dock.json +1 -1
- package/src/surfaces/canvas-stage.json +30 -19
- package/src/surfaces/canvas-stage.ts +5 -5
- package/src/surfaces/collections-panel.json +615 -0
- package/src/surfaces/collections-panel.ts +168 -0
- package/src/surfaces/commandbar.json +241 -96
- package/src/surfaces/commandbar.ts +134 -99
- package/src/surfaces/empty-state.json +1 -1
- package/src/surfaces/entry-editor.json +136 -0
- package/src/surfaces/entry-editor.ts +21 -0
- package/src/surfaces/entry-inspector.json +371 -0
- package/src/surfaces/entry-inspector.ts +143 -0
- package/src/surfaces/inspector-dock.json +162 -14
- package/src/surfaces/inspector-dock.ts +46 -19
- package/src/surfaces/library-pane.json +670 -437
- package/src/surfaces/library-pane.ts +94 -59
- package/src/surfaces/media-browser.json +62 -6
- package/src/surfaces/media-browser.ts +15 -1
- package/src/surfaces/media-field.json +1 -1
- package/src/surfaces/media-library.json +1586 -0
- package/src/surfaces/media-library.ts +282 -0
- package/src/surfaces/media-pick-dialog.json +71 -0
- package/src/surfaces/media-pick-dialog.ts +132 -0
- package/src/surfaces/menu.ts +18 -0
- package/src/surfaces/pane-context.json +144 -77
- package/src/surfaces/pane-context.ts +24 -0
- package/src/surfaces/panel-deploy-checklist.json +12 -6
- package/src/surfaces/panel-deploy-checklist.ts +3 -1
- package/src/surfaces/panel-page.json +72 -2
- package/src/surfaces/panel-page.ts +38 -7
- package/src/surfaces/panel-signals.json +1 -1
- package/src/surfaces/panel-signals.ts +5 -5
- package/src/surfaces/panel-stylebook-layers.json +71 -204
- package/src/surfaces/panel-stylebook-layers.ts +0 -21
- package/src/surfaces/preferences.json +120 -48
- package/src/surfaces/preferences.ts +8 -0
- package/src/surfaces/publish-quick.json +428 -0
- package/src/surfaces/publish-quick.ts +165 -0
- package/src/surfaces/publish-workspace.json +2892 -0
- package/src/surfaces/publish-workspace.ts +391 -0
- package/src/surfaces/rail.json +1 -159
- package/src/surfaces/rail.ts +18 -75
- package/src/surfaces/schema-form.json +10 -2
- package/src/surfaces/schema-form.ts +2 -0
- package/src/surfaces/seo.json +1 -1
- package/src/surfaces/settings-contributed.json +5 -5
- package/src/surfaces/settings-contributed.ts +18 -2
- package/src/surfaces/settings-css-vars.json +44 -36
- package/src/surfaces/settings-css-vars.ts +77 -4
- package/src/surfaces/settings-deploy.json +2 -15
- package/src/surfaces/settings-deploy.ts +1 -1
- package/src/surfaces/settings-dialog.json +51 -0
- package/src/surfaces/settings-dialog.ts +169 -0
- package/src/surfaces/settings-overview.json +3 -3
- package/src/surfaces/settings-overview.ts +9 -2
- package/src/surfaces/settings-pane.json +269 -46
- package/src/surfaces/settings-pane.ts +85 -24
- package/src/surfaces/shell.json +2 -1
- package/src/surfaces/site-panel.json +1029 -0
- package/src/surfaces/site-panel.ts +233 -0
- package/src/surfaces/slash-menu.json +63 -21
- package/src/surfaces/slash-menu.ts +23 -0
- package/src/surfaces/statusbar.ts +39 -15
- package/src/surfaces/styles-panel.json +735 -0
- package/src/surfaces/styles-panel.ts +195 -0
- package/src/surfaces/styles-variables.json +149 -0
- package/src/surfaces/styles-variables.ts +145 -0
- package/src/surfaces/tab-strip.json +4 -4
- package/src/surfaces/welcome.ts +1 -1
- package/src/surfaces/writing-banner.json +94 -0
- package/src/surfaces/writing-banner.ts +80 -0
- package/src/tabs/bare-repeaters.ts +101 -0
- package/src/tabs/tab.ts +47 -7
- package/src/types.ts +21 -26
- package/src/ui/form-controls.ts +47 -8
- package/src/ui/layers.ts +13 -0
- package/src/ui/media-picker.ts +284 -40
- package/src/ui/regions.ts +7 -6
- package/src/ui/schema-form.ts +259 -27
- package/src/ui/virtual-window.ts +26 -1
- package/src/utils/base64.ts +3 -3
- package/src/utils/edit-display.ts +322 -67
- package/src/utils/local-day.ts +18 -0
- package/src/workspace/pane-derive.ts +4 -4
- package/src/workspace/session.ts +28 -7
- package/src/workspace/tab-workspace.ts +137 -0
- package/src/workspace/workspace-back.ts +189 -0
- package/src/workspace/workspace-home.ts +133 -0
- package/src/workspace/workspace-ids.ts +129 -0
- package/src/workspace/workspace-switch.ts +372 -0
- package/src/workspace/workspace.ts +125 -11
- package/styles/shell-frame.css +30 -7
- package/styles/shell-frame.json +15 -3
- package/dist/chunks/2020-f2gkpy8d.js +0 -4
- package/dist/chunks/adapter-4912g339.js +0 -4
- package/dist/chunks/cf-account-picker-wccaenh6.js +0 -4
- package/dist/chunks/cf-settings-br6105tr.js +0 -4
- package/dist/chunks/color-28ksk2qm.js +0 -4
- package/dist/chunks/components-db68vsn7.js +0 -4
- package/dist/chunks/content-source-1449qesq.js +0 -4
- package/dist/chunks/convert-to-repeater-5h457egj.js +0 -4
- package/dist/chunks/editor-x9jn86a7.js +0 -4
- package/dist/chunks/entry-commands-may24cpt.js +0 -4
- package/dist/chunks/extension-sections-pvr14vxr.js +0 -4
- package/dist/chunks/file-ops-x9v54pcj.js +0 -4
- package/dist/chunks/files-j3knqf0s.js +0 -4
- package/dist/chunks/format-host-tyre2trb.js +0 -4
- package/dist/chunks/fs-events-64ma9htc.js +0 -4
- package/dist/chunks/git-panel-84ps5dex.js +0 -4
- package/dist/chunks/iframe-entry-fvbk89gm.js +0 -5
- package/dist/chunks/iframe-host-ck6vejt5.js +0 -4
- package/dist/chunks/index-b8xw3vy9.js +0 -4
- package/dist/chunks/jsonMode-w15cmrcm.js +0 -13
- package/dist/chunks/jx-validate-at3zn8wa.js +0 -4
- package/dist/chunks/layers-h4xp1pss.js +0 -4
- package/dist/chunks/layers-panel-m5hw1d7g.js +0 -4
- package/dist/chunks/lit-html-8t8t3dnr.js +0 -4
- package/dist/chunks/media-picker-2cweexc5.js +0 -4
- package/dist/chunks/menu-ynw13y0s.js +0 -4
- package/dist/chunks/monaco-buffer-qzxn567r.js +0 -4
- package/dist/chunks/monaco-setup-gxf6656g.js +0 -4
- package/dist/chunks/notify-cj5zbrs2.js +0 -4
- package/dist/chunks/parse-b7ddhr8m.js +0 -4
- package/dist/chunks/platform-3dfws9js.js +0 -4
- package/dist/chunks/popover-jt2gxmzs.js +0 -4
- package/dist/chunks/publish-panel-1gvxbj21.js +0 -4
- package/dist/chunks/quick-search-0dajrwqg.js +0 -4
- package/dist/chunks/references-49qgt404.js +0 -4
- package/dist/chunks/right-panel-jm1bwe53.js +0 -4
- package/dist/chunks/section-registry-6kwc4fky.js +0 -4
- package/dist/chunks/studio-0rt52vpq.js +0 -5
- package/dist/chunks/studio-18vgbhs5.js +0 -5
- package/dist/chunks/studio-24mccdrj.js +0 -5
- package/dist/chunks/studio-2eh507zk.js +0 -9
- package/dist/chunks/studio-2hcbccfv.js +0 -5
- package/dist/chunks/studio-2jbs3rxv.js +0 -5
- package/dist/chunks/studio-4h8k4dw9.js +0 -5
- package/dist/chunks/studio-5f6n18kx.js +0 -5
- package/dist/chunks/studio-5gthpky6.js +0 -5
- package/dist/chunks/studio-6ep3js6b.js +0 -9
- package/dist/chunks/studio-6wkmf3wz.js +0 -9
- package/dist/chunks/studio-841rf3cg.js +0 -5
- package/dist/chunks/studio-8gmwgheg.js +0 -5
- package/dist/chunks/studio-8qm0ep50.js +0 -5
- package/dist/chunks/studio-924p1tm1.js +0 -6
- package/dist/chunks/studio-a3shpz9y.js +0 -5
- package/dist/chunks/studio-amsbaaar.js +0 -6
- package/dist/chunks/studio-at5spjbc.js +0 -7
- package/dist/chunks/studio-b2xv945e.js +0 -5
- package/dist/chunks/studio-bn5nkafw.js +0 -5
- package/dist/chunks/studio-bw6q5xjf.js +0 -9
- package/dist/chunks/studio-c7q0t9x1.js +0 -5
- package/dist/chunks/studio-camnhc96.js +0 -6
- package/dist/chunks/studio-cffqvnzd.js +0 -5
- package/dist/chunks/studio-efvfrsf8.js +0 -5
- package/dist/chunks/studio-er30bpcx.js +0 -5
- package/dist/chunks/studio-ezscgr64.js +0 -5
- package/dist/chunks/studio-ffrrncvz.js +0 -5
- package/dist/chunks/studio-fpphgn8y.js +0 -10
- package/dist/chunks/studio-j1kvhdjk.js +0 -4
- package/dist/chunks/studio-j430n16g.js +0 -7
- package/dist/chunks/studio-jjkg84nj.js +0 -5
- package/dist/chunks/studio-m0q32ars.js +0 -4
- package/dist/chunks/studio-m6bdst11.js +0 -5
- package/dist/chunks/studio-nv2cptd4.js +0 -64
- package/dist/chunks/studio-nwfrc98n.js +0 -5
- package/dist/chunks/studio-p1wgvp7r.js +0 -5
- package/dist/chunks/studio-pag2wncm.js +0 -5
- package/dist/chunks/studio-qvanvzfd.js +0 -5
- package/dist/chunks/studio-qwa64kt7.js +0 -5
- package/dist/chunks/studio-rk1zc4x3.js +0 -928
- package/dist/chunks/studio-se2r3ptc.js +0 -5
- package/dist/chunks/studio-sg2a0dh0.js +0 -5
- package/dist/chunks/studio-tjzjhe68.js +0 -5
- package/dist/chunks/studio-v2w7q8r1.js +0 -7
- package/dist/chunks/studio-v5vjq1h0.js +0 -4
- package/dist/chunks/studio-v8aga3vp.js +0 -14
- package/dist/chunks/studio-vfcdrfmv.js +0 -5
- package/dist/chunks/studio-vmfkgdjf.js +0 -5
- package/dist/chunks/studio-wt1816jj.js +0 -5
- package/dist/chunks/studio-wz73q5kq.js +0 -4
- package/dist/chunks/studio-y04qtxvp.js +0 -7
- package/dist/chunks/studio-y21g8zzg.js +0 -5
- package/dist/chunks/studio-y64hrt8p.js +0 -12
- package/dist/chunks/studio-ynn90f58.js +0 -16
- package/dist/chunks/studio-yxz682sx.js +0 -5
- package/dist/chunks/studio-zp7snvkq.js +0 -5
- package/dist/chunks/studio-zq4z6tm6.js +0 -5
- package/dist/chunks/toast-c9fn075p.js +0 -4
- package/dist/chunks/tsMode-sf49ee9x.js +0 -14
- package/dist/chunks/workspace-jtfahpee.js +0 -4
- package/src/panels/frontmatter-panel.ts +0 -711
- package/src/panels/git-panel.ts +0 -1108
- package/src/surfaces/doc-header.json +0 -776
- package/src/surfaces/doc-header.ts +0 -222
- package/src/surfaces/git-panel.json +0 -1880
- package/src/surfaces/git-panel.ts +0 -264
|
@@ -0,0 +1,926 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
/**
|
|
3
|
+
* The Publish workspace's home: Review changes, Deploys, Branches and Hosting (studio.md §22.8).
|
|
4
|
+
*
|
|
5
|
+
* This is the flow. It projects four records into the surface (`surfaces/publish-workspace.json`):
|
|
6
|
+
* `shell.git` (what source control said), `shell.publish` (which sub-tab and which destination),
|
|
7
|
+
* the project's `build.deploy` with the deployments this module asks Cloudflare for, and the
|
|
8
|
+
* Activity records a running deploy writes. Every verb a control runs is somebody else's: source
|
|
9
|
+
* control's ({@link import("./source-control")}), publishing's
|
|
10
|
+
* ({@link import("./publish-changes")}), or a command record run by id, so the workspace adds no
|
|
11
|
+
* second list of actions (`studio-ui-guidelines.md` §12.5).
|
|
12
|
+
*
|
|
13
|
+
* **Per pane**, for the reason `media/media-library.ts` gives: two panes can each show the home.
|
|
14
|
+
* Each mount owns an `effect()` that re-projects on every reactive input it read, so a finished
|
|
15
|
+
* fetch, a typed summary, a deploy step and a registry that has just composed all reach the
|
|
16
|
+
* document whether or not anything else on screen moved.
|
|
17
|
+
*
|
|
18
|
+
* @docs studio/publish
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { errorMessage } from "@jxsuite/schema/parse";
|
|
22
|
+
import { activeRegistry } from "../commands/active-registry";
|
|
23
|
+
import { runActiveReported } from "../commands/run-reported";
|
|
24
|
+
import { activities, activityRowView, isFinished } from "../panels/activity-panel";
|
|
25
|
+
import { openUrlExternally } from "../canvas/preview-navigate";
|
|
26
|
+
import { effect, effectScope } from "../reactivity";
|
|
27
|
+
import { disposeDeploySection, renderDeploySection } from "../settings/project-sections";
|
|
28
|
+
import { shell } from "../shell";
|
|
29
|
+
import { projectState } from "../store";
|
|
30
|
+
import { paneRegion } from "../ui/regions";
|
|
31
|
+
import { getPlatform, hasPlatform } from "../platform";
|
|
32
|
+
import { mountPublishWorkspaceSurface } from "../surfaces/publish-workspace";
|
|
33
|
+
import {
|
|
34
|
+
currentDeploy,
|
|
35
|
+
deployChecklistView,
|
|
36
|
+
noteDeployment,
|
|
37
|
+
observedDeployment,
|
|
38
|
+
} from "./deploy-checklist";
|
|
39
|
+
import { listDeployments, platformSupportsPublish } from "./pages-service";
|
|
40
|
+
import {
|
|
41
|
+
canCheckBuild,
|
|
42
|
+
destinationChoices,
|
|
43
|
+
effectiveDestination,
|
|
44
|
+
lastPullRequest,
|
|
45
|
+
publishBlocker,
|
|
46
|
+
publishChanges,
|
|
47
|
+
publishChecks,
|
|
48
|
+
publishLabel,
|
|
49
|
+
reviewRefusal,
|
|
50
|
+
runBuildCheck,
|
|
51
|
+
} from "./publish-changes";
|
|
52
|
+
import {
|
|
53
|
+
PUBLISH_TABS,
|
|
54
|
+
PUBLISH_TAB_TITLES,
|
|
55
|
+
isPublishDestination,
|
|
56
|
+
isPublishTab,
|
|
57
|
+
} from "./publish-ids";
|
|
58
|
+
import {
|
|
59
|
+
armGitPoll,
|
|
60
|
+
checkoutBranch,
|
|
61
|
+
commitRow,
|
|
62
|
+
createBranch,
|
|
63
|
+
discardFile,
|
|
64
|
+
fetchGitLog,
|
|
65
|
+
fetchRemote,
|
|
66
|
+
groupFilesByComponent,
|
|
67
|
+
includeNewChanges,
|
|
68
|
+
lastUpdatedLabel,
|
|
69
|
+
openFileComparison,
|
|
70
|
+
platformHostsGit,
|
|
71
|
+
pullRemote,
|
|
72
|
+
refreshGitStatus,
|
|
73
|
+
relativeDate,
|
|
74
|
+
runCloneRepository,
|
|
75
|
+
setAllIncluded,
|
|
76
|
+
setIncluded,
|
|
77
|
+
platformSupportsClone,
|
|
78
|
+
} from "./source-control";
|
|
79
|
+
import type { EffectScope } from "@vue/reactivity";
|
|
80
|
+
import type { CanvasSurface } from "../canvas/canvas-surface";
|
|
81
|
+
import type { ShellPublish } from "../shell";
|
|
82
|
+
import type { Tab } from "../tabs/tab";
|
|
83
|
+
import type { GitFileStatus } from "../types";
|
|
84
|
+
import type { PagesDeploymentInfo } from "./pages-service";
|
|
85
|
+
import type { PublishCheck } from "./publish-changes";
|
|
86
|
+
import type { PublishTab } from "./publish-ids";
|
|
87
|
+
import type { ChangeGroup } from "./source-control";
|
|
88
|
+
import type {
|
|
89
|
+
BranchRow,
|
|
90
|
+
CommandButtonRow,
|
|
91
|
+
DeploymentRow,
|
|
92
|
+
PublishCheckRow,
|
|
93
|
+
PublishWorkspaceActions,
|
|
94
|
+
PublishWorkspaceSurfaceHandle,
|
|
95
|
+
PublishWorkspaceView,
|
|
96
|
+
ReviewGroupRow,
|
|
97
|
+
} from "../surfaces/publish-workspace";
|
|
98
|
+
|
|
99
|
+
// ─── Deployments ─────────────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Ask the host for every deployment of the connected project.
|
|
103
|
+
*
|
|
104
|
+
* Asked when Deploys is shown and on Refresh, never on a timer: a deployment list is a request to
|
|
105
|
+
* someone else's API. The newest row is also the deploy checklist's observation, so the status bar
|
|
106
|
+
* moves off "not asked" the first time anybody looks.
|
|
107
|
+
*/
|
|
108
|
+
export async function loadDeployments(): Promise<void> {
|
|
109
|
+
const deploy = currentDeploy();
|
|
110
|
+
if (!deploy || !platformSupportsPublish() || shell.publish.deploys.state === "loading") {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
/* The record this read writes into, held across the await. A project switch replaces the record
|
|
114
|
+
(`resetProjectShell`), and a config edit can point `build.deploy` at another Pages project; an
|
|
115
|
+
answer that arrives after either is about something no longer on screen, so it is dropped,
|
|
116
|
+
and in particular never becomes the new project's checklist observation. */
|
|
117
|
+
const record = shell.publish.deploys;
|
|
118
|
+
const project = deploy.projectName;
|
|
119
|
+
const stale = (): boolean =>
|
|
120
|
+
shell.publish.deploys !== record || currentDeploy()?.projectName !== project;
|
|
121
|
+
record.state = "loading";
|
|
122
|
+
record.project = project;
|
|
123
|
+
try {
|
|
124
|
+
const rows = await listDeployments(deploy);
|
|
125
|
+
if (stale()) {
|
|
126
|
+
settleStale(record);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
record.rows = rows;
|
|
130
|
+
record.error = "";
|
|
131
|
+
record.state = "loaded";
|
|
132
|
+
noteDeployment(rows[0] ?? null);
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (stale()) {
|
|
135
|
+
settleStale(record);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
record.rows = [];
|
|
139
|
+
record.error = errorMessage(error);
|
|
140
|
+
record.state = "failed";
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** A dropped answer must not leave a record still on screen marked `loading` for ever. */
|
|
145
|
+
function settleStale(record: ShellPublish["deploys"]): void {
|
|
146
|
+
if (shell.publish.deploys === record) {
|
|
147
|
+
record.state = "idle";
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Whether a deploy has finished since the list was read: the deploy checklist observed a deployment
|
|
153
|
+
* (`runDeploy` records the one it waited for) that the listed rows do not have.
|
|
154
|
+
*/
|
|
155
|
+
function listIsBehind(): boolean {
|
|
156
|
+
const seen = observedDeployment();
|
|
157
|
+
const { deploys } = shell.publish;
|
|
158
|
+
return (
|
|
159
|
+
deploys.state === "loaded" && seen !== null && !deploys.rows.some((row) => row.id === seen.id)
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Whether Deploys, when shown, has to ask again. */
|
|
164
|
+
function deploysNeedReading(): boolean {
|
|
165
|
+
return shell.publish.deploys.state === "idle" || listIsBehind();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ─── Showing a sub-tab ───────────────────────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Show one sub-tab, and start the read it needs: the log for Review changes, the deployment list
|
|
172
|
+
* for Deploys. Called by the strip, by `publish.open { tab }` and by the quick dialog's Review
|
|
173
|
+
* all.
|
|
174
|
+
*/
|
|
175
|
+
export function showPublishTab(tab: PublishTab): void {
|
|
176
|
+
shell.publish.tab = tab;
|
|
177
|
+
if (tab === "review" && shell.git.logEntries === null && shell.git.status?.isRepo) {
|
|
178
|
+
void fetchGitLog();
|
|
179
|
+
}
|
|
180
|
+
if (tab === "deploys" && (shell.publish.deploys.state !== "loaded" || listIsBehind())) {
|
|
181
|
+
void loadDeployments();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ─── The projection ──────────────────────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* The kit icon a check wears, and the words that say its state to a reader who cannot see the
|
|
189
|
+
* icon's shape or colour. Names from the kit's glyph manifest (`@jxsuite/ui/icons`), not text
|
|
190
|
+
* glyphs: a `·` or a `?` beside a sentence read as punctuation, and said nothing to a screen
|
|
191
|
+
* reader.
|
|
192
|
+
*/
|
|
193
|
+
const CHECK_STATE: ReadonlyMap<PublishCheck["state"], { icon: string; label: string }> = new Map([
|
|
194
|
+
["done", { icon: "check", label: "Passed" }],
|
|
195
|
+
["failed", { icon: "x", label: "Failed" }],
|
|
196
|
+
["running", { icon: "spinner", label: "Running" }],
|
|
197
|
+
["todo", { icon: "warning-circle", label: "To do" }],
|
|
198
|
+
["unknown", { icon: "question", label: "Not checked yet" }],
|
|
199
|
+
]);
|
|
200
|
+
|
|
201
|
+
/** The icon and state words for a check's state; an unknown state reads as not checked. */
|
|
202
|
+
export function checkStateMark(state: PublishCheck["state"]): { icon: string; label: string } {
|
|
203
|
+
return CHECK_STATE.get(state) ?? { icon: "question", label: "Not checked yet" };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** "1 change", "3 changes". */
|
|
207
|
+
function changes(count: number): string {
|
|
208
|
+
return `${count} ${count === 1 ? "change" : "changes"}`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** The changed files, as the status last listed them. */
|
|
212
|
+
function changedFiles(): readonly GitFileStatus[] {
|
|
213
|
+
return shell.git.status?.files ?? [];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** The remote the status names first, or `""`. */
|
|
217
|
+
function firstRemote(): string {
|
|
218
|
+
const remotes = shell.git.status?.remotes;
|
|
219
|
+
return Array.isArray(remotes) && remotes.length > 0 ? (remotes[0] ?? "") : "";
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Which of the four moods Review changes is in; the same four the Source Control panel had. */
|
|
223
|
+
function moodOf(): PublishWorkspaceView["mood"] {
|
|
224
|
+
if (!projectState) {
|
|
225
|
+
return "no-project";
|
|
226
|
+
}
|
|
227
|
+
const { error, loading, status } = shell.git;
|
|
228
|
+
if (!status && !loading && !error) {
|
|
229
|
+
return "loading";
|
|
230
|
+
}
|
|
231
|
+
return status && !status.isRepo ? "no-repo" : "repo";
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** The branch a commit lands on now. */
|
|
235
|
+
function branchName(): string {
|
|
236
|
+
return shell.git.branches?.current || shell.git.status?.branch || "";
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** "Up to date", or "2 ahead, 1 behind". */
|
|
240
|
+
function syncLabel(): string {
|
|
241
|
+
const { status } = shell.git;
|
|
242
|
+
const ahead = status?.ahead ? `${status.ahead} ahead` : "";
|
|
243
|
+
const behind = status?.behind ? `${status.behind} behind` : "";
|
|
244
|
+
return ahead || behind ? [ahead, behind].filter(Boolean).join(", ") : "Up to date";
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** The line under the heading: where the author is, and how much there is to publish. */
|
|
248
|
+
function headline(mood: PublishWorkspaceView["mood"]): string {
|
|
249
|
+
if (mood === "no-repo") {
|
|
250
|
+
return "Not tracked by git yet";
|
|
251
|
+
}
|
|
252
|
+
if (mood !== "repo") {
|
|
253
|
+
return "";
|
|
254
|
+
}
|
|
255
|
+
const count = changedFiles().length;
|
|
256
|
+
const where = branchName() ? `On ${branchName()}` : "On a branch with no name yet";
|
|
257
|
+
return `${where} · ${count > 0 ? `${changes(count)} to publish` : "nothing to publish"}`;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** One group, with every control named after the file it acts on. */
|
|
261
|
+
function reviewGroup(group: ChangeGroup): ReviewGroupRow {
|
|
262
|
+
return {
|
|
263
|
+
files: group.files.map((file) => ({
|
|
264
|
+
...file,
|
|
265
|
+
compareLabel: `Before and after: ${file.label}`,
|
|
266
|
+
discardHint: file.cannotDiscard
|
|
267
|
+
? "A new file has no saved version to go back to"
|
|
268
|
+
: "Discard changes",
|
|
269
|
+
discardLabel: `Discard changes to ${file.path}`,
|
|
270
|
+
includeLabel: `Include ${file.path}`,
|
|
271
|
+
})),
|
|
272
|
+
key: group.key,
|
|
273
|
+
name: group.name,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** A command, as a button: drawn only while the registry has it and shows it. */
|
|
278
|
+
function commandRows(ids: readonly string[]): CommandButtonRow[] {
|
|
279
|
+
const registry = activeRegistry();
|
|
280
|
+
if (!registry) {
|
|
281
|
+
return [];
|
|
282
|
+
}
|
|
283
|
+
return ids.flatMap((id) => {
|
|
284
|
+
const command = registry.get(id);
|
|
285
|
+
if (!command || !registry.isVisible(id)) {
|
|
286
|
+
return [];
|
|
287
|
+
}
|
|
288
|
+
const reason = registry.disabledReason(id);
|
|
289
|
+
return [
|
|
290
|
+
{
|
|
291
|
+
disabled: reason !== undefined,
|
|
292
|
+
id,
|
|
293
|
+
label: command.title,
|
|
294
|
+
title: reason ? `${command.title}: requires ${reason}` : command.title,
|
|
295
|
+
},
|
|
296
|
+
];
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** One check, with the one button that resolves it, when there is one. */
|
|
301
|
+
function checkRow(check: PublishCheck): PublishCheckRow {
|
|
302
|
+
const base = {
|
|
303
|
+
actionDisabled: false,
|
|
304
|
+
actionLabel: "",
|
|
305
|
+
actionTitle: "",
|
|
306
|
+
hasAction: false,
|
|
307
|
+
icon: checkStateMark(check.state).icon,
|
|
308
|
+
iconLabel: checkStateMark(check.state).label,
|
|
309
|
+
key: check.key,
|
|
310
|
+
state: check.state,
|
|
311
|
+
text: check.text,
|
|
312
|
+
};
|
|
313
|
+
if (check.key === "build") {
|
|
314
|
+
if (check.state === "running" || !canCheckBuild()) {
|
|
315
|
+
return base;
|
|
316
|
+
}
|
|
317
|
+
const label = check.state === "unknown" ? "Check build" : "Check again";
|
|
318
|
+
return { ...base, actionLabel: label, actionTitle: label, hasAction: true };
|
|
319
|
+
}
|
|
320
|
+
const [row] = check.command ? commandRows([check.command]) : [];
|
|
321
|
+
return row
|
|
322
|
+
? {
|
|
323
|
+
...base,
|
|
324
|
+
actionDisabled: row.disabled,
|
|
325
|
+
actionLabel: row.label,
|
|
326
|
+
actionTitle: row.title,
|
|
327
|
+
hasAction: true,
|
|
328
|
+
}
|
|
329
|
+
: base;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* The production deployment serving the site now: the newest one whose last stage succeeded. The
|
|
334
|
+
* host lists newest first, but the order is not trusted.
|
|
335
|
+
*/
|
|
336
|
+
function liveDeploymentId(rows: readonly PagesDeploymentInfo[]): string {
|
|
337
|
+
const [live] = rows
|
|
338
|
+
.filter((row) => row.environment === "production" && deploymentSucceeded(row))
|
|
339
|
+
.toSorted((a, b) => Date.parse(b.createdOn) - Date.parse(a.createdOn));
|
|
340
|
+
return live?.id ?? "";
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Whether a deployment finished: its last stage, `deploy`, reported success. */
|
|
344
|
+
function deploymentSucceeded(info: PagesDeploymentInfo): boolean {
|
|
345
|
+
return info.status === "success" && info.stage === "deploy";
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* A deployment's state in words, as the board draws it. The stage and status Cloudflare reported
|
|
350
|
+
* (`deploy: success`) stay on the row as its tooltip.
|
|
351
|
+
*/
|
|
352
|
+
function deploymentStatusWord(info: PagesDeploymentInfo, liveId: string): string {
|
|
353
|
+
if (info.status === "failure") {
|
|
354
|
+
return "Failed";
|
|
355
|
+
}
|
|
356
|
+
if (info.status === "canceled") {
|
|
357
|
+
return "Canceled";
|
|
358
|
+
}
|
|
359
|
+
if (!deploymentSucceeded(info)) {
|
|
360
|
+
return "Building…";
|
|
361
|
+
}
|
|
362
|
+
if (info.environment !== "production") {
|
|
363
|
+
return "Ready";
|
|
364
|
+
}
|
|
365
|
+
return info.id === liveId ? "Live" : "Replaced";
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/** One deployment as a row. */
|
|
369
|
+
function deploymentRow(info: PagesDeploymentInfo, liveId: string): DeploymentRow {
|
|
370
|
+
const production = info.environment === "production";
|
|
371
|
+
const label = production
|
|
372
|
+
? `Production${info.branch ? ` · ${info.branch}` : ""}`
|
|
373
|
+
: info.branch || "Preview";
|
|
374
|
+
return {
|
|
375
|
+
environment: info.environment,
|
|
376
|
+
key: info.id,
|
|
377
|
+
label,
|
|
378
|
+
openLabel: `Open ${label}`,
|
|
379
|
+
status: deploymentStatusWord(info, liveId),
|
|
380
|
+
statusDetail: `${info.stage}: ${info.status}`,
|
|
381
|
+
statusKey: info.status,
|
|
382
|
+
url: info.url,
|
|
383
|
+
when: relativeDate(info.createdOn),
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** Deploys' list state: a note when there is nothing to ask, else the listing's state. */
|
|
388
|
+
function deploysState(): Pick<PublishWorkspaceView, "deploysNote" | "deploysState"> {
|
|
389
|
+
if (!platformSupportsPublish()) {
|
|
390
|
+
return {
|
|
391
|
+
deploysNote:
|
|
392
|
+
"This host cannot reach the Cloudflare API, so deployments are not listed here. Your " +
|
|
393
|
+
"host runs bunx jx build and serves dist/.",
|
|
394
|
+
deploysState: "note",
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
if (!currentDeploy()) {
|
|
398
|
+
return {
|
|
399
|
+
deploysNote: "Connect a host under Hosting, and every deployment it makes is listed here.",
|
|
400
|
+
deploysState: "note",
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
if (shell.publish.deploys.state === "failed") {
|
|
404
|
+
return {
|
|
405
|
+
deploysNote: `The host did not answer: ${shell.publish.deploys.error}`,
|
|
406
|
+
deploysState: "note",
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
return {
|
|
410
|
+
deploysNote: "",
|
|
411
|
+
deploysState: shell.publish.deploys.state === "loaded" ? "listed" : "loading",
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/** The rows the host returned, when they are about the project connected now. */
|
|
416
|
+
function deploymentRows(): PagesDeploymentInfo[] {
|
|
417
|
+
return shell.publish.deploys.project === currentDeploy()?.projectName
|
|
418
|
+
? shell.publish.deploys.rows
|
|
419
|
+
: [];
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* The site's production address: what the connected host reports, else the Production URL the
|
|
424
|
+
* project configures (`project.json` `url`, Project Settings › Overview), else nothing. One reader,
|
|
425
|
+
* so Deploys, Hosting and Settings never disagree about where the site lives (studio.md §22.8).
|
|
426
|
+
*/
|
|
427
|
+
function productionAddress(): string {
|
|
428
|
+
return currentDeploy()?.productionUrl || projectState?.projectConfig?.url || "";
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/** The sentence under Your address: where the address came from, or how the site gets one. */
|
|
432
|
+
function domainNote(address: string): string {
|
|
433
|
+
const deploy = currentDeploy();
|
|
434
|
+
if (deploy?.productionUrl) {
|
|
435
|
+
return "Cloudflare Pages serves it. A custom domain is added on the Cloudflare dashboard.";
|
|
436
|
+
}
|
|
437
|
+
if (deploy) {
|
|
438
|
+
return "Cloudflare has not reported an address yet. A custom domain is added on the Cloudflare dashboard.";
|
|
439
|
+
}
|
|
440
|
+
return address
|
|
441
|
+
? "The Production URL in Project Settings › Overview. No host is connected yet; once one is, point this domain at it on the host's dashboard."
|
|
442
|
+
: "Your site gets an address when a host is connected, or set its Production URL in Project Settings › Overview.";
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/** The live site's line: its latest production deployment, said in words. */
|
|
446
|
+
function liveSummary(rows: readonly PagesDeploymentInfo[]): string {
|
|
447
|
+
if (!currentDeploy()) {
|
|
448
|
+
return productionAddress()
|
|
449
|
+
? "No host is connected, so Studio cannot say what is deployed there. Connect one under Hosting."
|
|
450
|
+
: "";
|
|
451
|
+
}
|
|
452
|
+
/* The newer of what the list said and what a deploy since then observed: a Publish live that
|
|
453
|
+
deployed is the live site before the list is read again. */
|
|
454
|
+
const listed = rows.find((row) => row.environment === "production");
|
|
455
|
+
const seen = observedDeployment();
|
|
456
|
+
const observed = seen && (seen.environment === "production" || !listed) ? seen : undefined;
|
|
457
|
+
const [latest] = [listed, observed]
|
|
458
|
+
.filter((row): row is PagesDeploymentInfo => row !== undefined)
|
|
459
|
+
.toSorted((a, b) => Date.parse(b.createdOn) - Date.parse(a.createdOn));
|
|
460
|
+
if (!latest) {
|
|
461
|
+
return "No deployment has been observed yet.";
|
|
462
|
+
}
|
|
463
|
+
const from = latest.branch ? ` from ${latest.branch}` : "";
|
|
464
|
+
return `Last deployed ${relativeDate(latest.createdOn)}${from} · ${latest.stage}: ${latest.status}`;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/** Where the project is saved, in a sentence. */
|
|
468
|
+
function sourceSummary(): string {
|
|
469
|
+
const { status } = shell.git;
|
|
470
|
+
if (!projectState) {
|
|
471
|
+
return "";
|
|
472
|
+
}
|
|
473
|
+
if (!status) {
|
|
474
|
+
return "Source control has not reported on this project yet.";
|
|
475
|
+
}
|
|
476
|
+
if (!status.isRepo) {
|
|
477
|
+
return "Not tracked by git yet. A repository keeps every version you publish.";
|
|
478
|
+
}
|
|
479
|
+
const branch = branchName() || "an unnamed branch";
|
|
480
|
+
const remote = firstRemote();
|
|
481
|
+
if (platformHostsGit()) {
|
|
482
|
+
return `Saved on GitHub${remote ? ` (${remote})` : ""}, on ${branch}. Every commit lands there as it is made.`;
|
|
483
|
+
}
|
|
484
|
+
return remote
|
|
485
|
+
? `Saved in git and pushed to ${remote}, on ${branch}.`
|
|
486
|
+
: `Saved in git on this machine only, on ${branch}. A host builds from a remote, so create the GitHub repository next.`;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/** The source rows' commands, for the state the repository is in. */
|
|
490
|
+
function sourceCommandIds(): string[] {
|
|
491
|
+
const { status } = shell.git;
|
|
492
|
+
if (!status) {
|
|
493
|
+
return ["git.signInToGithub"];
|
|
494
|
+
}
|
|
495
|
+
if (!status.isRepo) {
|
|
496
|
+
return ["git.init", "git.signInToGithub"];
|
|
497
|
+
}
|
|
498
|
+
if (firstRemote() === "") {
|
|
499
|
+
return ["git.createGithubRepository", "git.signInToGithub"];
|
|
500
|
+
}
|
|
501
|
+
return platformHostsGit() ? ["git.signInToGithub"] : ["git.push", "git.signInToGithub"];
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/** The host block: which host, and what connecting it buys. */
|
|
505
|
+
function hostView(): Pick<PublishWorkspaceView, "hostSummary" | "hostTitle"> {
|
|
506
|
+
const deploy = currentDeploy();
|
|
507
|
+
if (deploy) {
|
|
508
|
+
return {
|
|
509
|
+
hostSummary:
|
|
510
|
+
`Every publish is pushed to the remote and built by Cloudflare Pages` +
|
|
511
|
+
`${deploy.productionUrl ? ` at ${deploy.productionUrl}` : ""}.`,
|
|
512
|
+
hostTitle: `Cloudflare Pages: ${deploy.projectName}`,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
if (hasPlatform() && getPlatform().cfConnect) {
|
|
516
|
+
return {
|
|
517
|
+
hostSummary:
|
|
518
|
+
"Runs on Cloudflare with nothing to configure: connect it, and every publish builds and deploys.",
|
|
519
|
+
hostTitle: "Jx Cloud, on Cloudflare",
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
if (platformSupportsPublish()) {
|
|
523
|
+
return {
|
|
524
|
+
hostSummary:
|
|
525
|
+
"Connect a Cloudflare API token, and every publish builds and deploys on your own account.",
|
|
526
|
+
hostTitle: "Your Cloudflare account",
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
return {
|
|
530
|
+
hostSummary:
|
|
531
|
+
"This host cannot reach Cloudflare. Choose a build adapter below; your host runs bunx jx build and serves dist/.",
|
|
532
|
+
hostTitle: "Another host",
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Everything the document draws, as one value, for the pane `region` names.
|
|
538
|
+
*
|
|
539
|
+
* @param region The pane's region (`pane.primary/publish`), which also seeds the document's ids.
|
|
540
|
+
*/
|
|
541
|
+
export function publishWorkspaceView(region: string): PublishWorkspaceView {
|
|
542
|
+
const idBase = region.replaceAll(/[^\w-]/g, "-");
|
|
543
|
+
const mood = moodOf();
|
|
544
|
+
const { git } = shell;
|
|
545
|
+
const files = changedFiles();
|
|
546
|
+
const staged = files.filter((file) => file.staged).length;
|
|
547
|
+
const { destination: chosen, tab } = shell.publish;
|
|
548
|
+
const destination = effectiveDestination(chosen);
|
|
549
|
+
const blocker = mood === "repo" ? publishBlocker(destination) : "";
|
|
550
|
+
const refusal = chosen === "review" ? reviewRefusal() : "";
|
|
551
|
+
const pr = lastPullRequest();
|
|
552
|
+
const commits = git.logEntries ?? [];
|
|
553
|
+
const deploy = currentDeploy();
|
|
554
|
+
const address = productionAddress();
|
|
555
|
+
const rows = deploymentRows();
|
|
556
|
+
const previews = rows.filter((row) => row.environment === "preview");
|
|
557
|
+
const liveId = liveDeploymentId(rows);
|
|
558
|
+
const running = activities.filter((entry) => entry.source === "Publish" && !isFinished(entry));
|
|
559
|
+
const branches = git.branches?.branches ?? [];
|
|
560
|
+
const current = git.branches?.current ?? "";
|
|
561
|
+
const hosted = platformHostsGit();
|
|
562
|
+
const remote = firstRemote();
|
|
563
|
+
const checklist = deployChecklistView();
|
|
564
|
+
const count = files.length;
|
|
565
|
+
return {
|
|
566
|
+
...deploysState(),
|
|
567
|
+
...hostView(),
|
|
568
|
+
allIncluded: count > 0 && staged === count,
|
|
569
|
+
blocker,
|
|
570
|
+
branchNote: hosted
|
|
571
|
+
? "Each branch opens in its own session, so switching reloads Studio on that branch."
|
|
572
|
+
: "",
|
|
573
|
+
branchSummary: current ? `On ${current}` : "",
|
|
574
|
+
branches: branches.map((name): BranchRow => ({
|
|
575
|
+
current: name === current,
|
|
576
|
+
key: name,
|
|
577
|
+
name,
|
|
578
|
+
note: remote ? syncLabel() : "Local only",
|
|
579
|
+
switchLabel: hosted ? "Switch and reload" : "Switch",
|
|
580
|
+
switchName: hosted ? `Switch to ${name} and reload` : `Switch to ${name}`,
|
|
581
|
+
})),
|
|
582
|
+
branchesEmpty:
|
|
583
|
+
mood === "no-repo"
|
|
584
|
+
? "This project is not tracked by git yet."
|
|
585
|
+
: "No branches yet. The first commit makes one.",
|
|
586
|
+
branchesState: branches.length > 0 ? "listed" : "empty",
|
|
587
|
+
busy: git.loading,
|
|
588
|
+
canClone: platformSupportsClone(),
|
|
589
|
+
checklistSteps: checklist.steps,
|
|
590
|
+
checklistSummary: checklist.summary,
|
|
591
|
+
checks: mood === "repo" ? publishChecks().map((check) => checkRow(check)) : [],
|
|
592
|
+
commitRegion: `${region}/commit`,
|
|
593
|
+
commits: commits.map((entry) => commitRow(entry)),
|
|
594
|
+
commitsState: commits.length > 0 ? "listed" : "empty",
|
|
595
|
+
deployCommands: commandRows(
|
|
596
|
+
deploy ? ["publish.deploy", "publish.openDashboard"] : ["publish.setUp"],
|
|
597
|
+
),
|
|
598
|
+
deploysBusy: shell.publish.deploys.state === "loading",
|
|
599
|
+
destination,
|
|
600
|
+
destinationId: `${idBase}-destination`,
|
|
601
|
+
destinations: destinationChoices(chosen),
|
|
602
|
+
domain: address,
|
|
603
|
+
domainNote: domainNote(address),
|
|
604
|
+
domainState: address ? "set" : "none",
|
|
605
|
+
error: git.error ?? "",
|
|
606
|
+
filesState: count > 0 ? "listed" : "empty",
|
|
607
|
+
groups: groupFilesByComponent(files).map((group) => reviewGroup(group)),
|
|
608
|
+
hasBlocker: blocker !== "",
|
|
609
|
+
hasBranchNote: hosted,
|
|
610
|
+
hasError: Boolean(git.error),
|
|
611
|
+
hasLastUpdated: Boolean(git.lastUpdated),
|
|
612
|
+
hasLiveUrl: address !== "",
|
|
613
|
+
hasRefusal: refusal !== "",
|
|
614
|
+
headline: headline(mood),
|
|
615
|
+
history: rows.map((row) => deploymentRow(row, liveId)),
|
|
616
|
+
historyState: rows.length > 0 ? "listed" : "empty",
|
|
617
|
+
hostCommands: commandRows(
|
|
618
|
+
deploy ? ["publish.setUp", "publish.openDashboard"] : ["publish.setUp"],
|
|
619
|
+
),
|
|
620
|
+
includedLabel: count > 0 ? `${staged} of ${count} included` : "",
|
|
621
|
+
lastUpdated: lastUpdatedLabel(git.lastUpdated),
|
|
622
|
+
liveNone: deploy
|
|
623
|
+
? "The host has not reported an address for this site yet."
|
|
624
|
+
: "No host is connected. Connect one under Hosting, and every publish ships to it.",
|
|
625
|
+
liveSummary: liveSummary(rows),
|
|
626
|
+
liveUrl: address,
|
|
627
|
+
mood,
|
|
628
|
+
newBranchDisabled: git.loading || mood !== "repo",
|
|
629
|
+
panelId: `${idBase}-body`,
|
|
630
|
+
previews: previews.map((row) => deploymentRow(row, liveId)),
|
|
631
|
+
previewsState: previews.length > 0 ? "listed" : "empty",
|
|
632
|
+
primaryDisabled: mood !== "repo" || blocker !== "",
|
|
633
|
+
primaryLabel: publishLabel(destination),
|
|
634
|
+
pullLabel: git.status?.behind ? `Pull (${git.status.behind} behind)` : "Pull",
|
|
635
|
+
pushLabel: git.status?.ahead ? `Push (${git.status.ahead} ahead)` : "Push",
|
|
636
|
+
refusal,
|
|
637
|
+
region,
|
|
638
|
+
remoteState: remote ? "remote" : "local",
|
|
639
|
+
reviewLine: pr ? `Pull request #${pr.number} is open for review.` : "",
|
|
640
|
+
reviewState: pr ? "opened" : "none",
|
|
641
|
+
reviewUrl: pr?.url ?? "",
|
|
642
|
+
running: running.map((entry) => activityRowView(entry)),
|
|
643
|
+
runningState: running.length > 0 ? "running" : "none",
|
|
644
|
+
someIncluded: staged > 0 && staged < count,
|
|
645
|
+
sourceCommands: commandRows(sourceCommandIds()),
|
|
646
|
+
sourceSummary: sourceSummary(),
|
|
647
|
+
summary: git.commitMessage,
|
|
648
|
+
syncLabel: remote ? syncLabel() : "Local only (no remote)",
|
|
649
|
+
tab,
|
|
650
|
+
tabTitle: PUBLISH_TAB_TITLES[tab],
|
|
651
|
+
tabs: PUBLISH_TABS.map((value) => ({
|
|
652
|
+
label:
|
|
653
|
+
value === "review" && count > 0
|
|
654
|
+
? `${PUBLISH_TAB_TITLES[value]} (${count})`
|
|
655
|
+
: PUBLISH_TAB_TITLES[value],
|
|
656
|
+
value,
|
|
657
|
+
})),
|
|
658
|
+
titleId: `${idBase}-title`,
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// ─── The actions ─────────────────────────────────────────────────────────────
|
|
663
|
+
|
|
664
|
+
/** Where the build adapter's section is mounted, per pane. */
|
|
665
|
+
const _adapterHosts = new Map<string, HTMLElement>();
|
|
666
|
+
|
|
667
|
+
/** The adapter each mounted section last drew, so a re-projection that changed nothing is free. */
|
|
668
|
+
const _adapterShown = new WeakMap<HTMLElement, string>();
|
|
669
|
+
|
|
670
|
+
/** The adapter `project.json` names now, as the Deploy section reads it. */
|
|
671
|
+
function configuredAdapter(): string {
|
|
672
|
+
return projectState?.projectConfig?.build?.adapter || "static";
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Draw (or redraw) a pane's build-adapter section when the adapter on file is not the one it last
|
|
677
|
+
* drew. The picker is someone else's surface, mounted into a node of this document, so it does not
|
|
678
|
+
* follow this document's state: Raw JSON, the Code view or a collaborator can change the adapter
|
|
679
|
+
* while Hosting is open, and this is what brings the picker along.
|
|
680
|
+
*/
|
|
681
|
+
function syncAdapterSection(paneId: string): void {
|
|
682
|
+
const adapter = configuredAdapter();
|
|
683
|
+
const host = _adapterHosts.get(paneId);
|
|
684
|
+
if (!host?.isConnected || _adapterShown.get(host) === adapter) {
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
_adapterShown.set(host, adapter);
|
|
688
|
+
renderDeploySection(host);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/** Run a check's button: the deploy step's command, or the build. */
|
|
692
|
+
function runCheck(key: string): void {
|
|
693
|
+
if (key === "build") {
|
|
694
|
+
void runBuildCheck();
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
const check = publishChecks().find((entry) => entry.key === key);
|
|
698
|
+
if (check?.command) {
|
|
699
|
+
void runActiveReported(check.command, undefined, "Publish");
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/** Every control's verb, bound to the pane it was drawn in. */
|
|
704
|
+
function actionsFor(paneId: string): PublishWorkspaceActions {
|
|
705
|
+
return {
|
|
706
|
+
adapterHost: (element) => {
|
|
707
|
+
// Hosting was drawn again (the sub-tab came back): the section in the node it left goes.
|
|
708
|
+
const previous = _adapterHosts.get(paneId);
|
|
709
|
+
if (previous && previous !== element) {
|
|
710
|
+
disposeDeploySection(previous);
|
|
711
|
+
}
|
|
712
|
+
_adapterHosts.set(paneId, element);
|
|
713
|
+
_adapterShown.set(element, configuredAdapter());
|
|
714
|
+
renderDeploySection(element);
|
|
715
|
+
},
|
|
716
|
+
checkout: (name) => {
|
|
717
|
+
void checkoutBranch(name);
|
|
718
|
+
},
|
|
719
|
+
clone: () => {
|
|
720
|
+
runCloneRepository();
|
|
721
|
+
},
|
|
722
|
+
compare: (path) => {
|
|
723
|
+
_lastCompared = path;
|
|
724
|
+
void openFileComparison(path);
|
|
725
|
+
},
|
|
726
|
+
compareHost: (element, path) => {
|
|
727
|
+
let buttons = _compareButtons.get(paneId);
|
|
728
|
+
if (!buttons) {
|
|
729
|
+
buttons = new Map();
|
|
730
|
+
_compareButtons.set(paneId, buttons);
|
|
731
|
+
}
|
|
732
|
+
buttons.set(path, element);
|
|
733
|
+
},
|
|
734
|
+
/* The three verbs that are records run AS records, so a refusal (Create GitHub Repository on a
|
|
735
|
+
project git does not track) is the record's, and no button here reaches past it
|
|
736
|
+
(`studio-ui-guidelines.md` §12.5). */
|
|
737
|
+
createRepository: () => {
|
|
738
|
+
void runActiveReported("git.createGithubRepository", undefined, "Publish");
|
|
739
|
+
},
|
|
740
|
+
discard: (path) => {
|
|
741
|
+
void discardFile(path);
|
|
742
|
+
},
|
|
743
|
+
editSummary: (value) => {
|
|
744
|
+
shell.git.commitMessage = value;
|
|
745
|
+
},
|
|
746
|
+
fetch: () => {
|
|
747
|
+
void fetchRemote();
|
|
748
|
+
},
|
|
749
|
+
include: (path, included) => {
|
|
750
|
+
void setIncluded(path, included);
|
|
751
|
+
},
|
|
752
|
+
includeAll: (included) => {
|
|
753
|
+
void setAllIncluded(included);
|
|
754
|
+
},
|
|
755
|
+
initRepository: () => {
|
|
756
|
+
void runActiveReported("git.init", undefined, "Publish");
|
|
757
|
+
},
|
|
758
|
+
newBranch: () => {
|
|
759
|
+
void createBranch();
|
|
760
|
+
},
|
|
761
|
+
openUrl: (url) => {
|
|
762
|
+
if (url) {
|
|
763
|
+
openUrlExternally(url);
|
|
764
|
+
}
|
|
765
|
+
},
|
|
766
|
+
pickDestination: (value) => {
|
|
767
|
+
if (isPublishDestination(value)) {
|
|
768
|
+
shell.publish.destination = value;
|
|
769
|
+
}
|
|
770
|
+
},
|
|
771
|
+
publish: () => {
|
|
772
|
+
void publishChanges(effectiveDestination(shell.publish.destination));
|
|
773
|
+
},
|
|
774
|
+
pull: () => {
|
|
775
|
+
void pullRemote();
|
|
776
|
+
},
|
|
777
|
+
push: () => {
|
|
778
|
+
void runActiveReported("git.push", undefined, "Publish");
|
|
779
|
+
},
|
|
780
|
+
refresh: () => {
|
|
781
|
+
void refreshGitStatus();
|
|
782
|
+
},
|
|
783
|
+
reloadDeployments: () => {
|
|
784
|
+
void loadDeployments();
|
|
785
|
+
},
|
|
786
|
+
run: (commandId) => {
|
|
787
|
+
void runActiveReported(commandId, undefined, "Publish");
|
|
788
|
+
},
|
|
789
|
+
runCheck,
|
|
790
|
+
selectTab: (tab) => {
|
|
791
|
+
if (isPublishTab(tab)) {
|
|
792
|
+
showPublishTab(tab);
|
|
793
|
+
}
|
|
794
|
+
},
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// ─── Mounting ────────────────────────────────────────────────────────────────
|
|
799
|
+
|
|
800
|
+
interface PaneMount {
|
|
801
|
+
tabId: string;
|
|
802
|
+
wrap: HTMLElement;
|
|
803
|
+
handle: PublishWorkspaceSurfaceHandle;
|
|
804
|
+
scope: EffectScope;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/** The workspace mounted in each pane, keyed by pane id. */
|
|
808
|
+
const _panes = new Map<string, PaneMount>();
|
|
809
|
+
|
|
810
|
+
/** Each pane's Compare buttons, by the file each compares, as the surface announced them. */
|
|
811
|
+
const _compareButtons = new Map<string, Map<string, HTMLElement>>();
|
|
812
|
+
|
|
813
|
+
/** The file the last comparison was opened for: where the keyboard goes back to. */
|
|
814
|
+
let _lastCompared: string | null = null;
|
|
815
|
+
|
|
816
|
+
/** Panes whose next mount gives the keyboard to that file's Compare button. */
|
|
817
|
+
const _focusOnMount = new Set<string>();
|
|
818
|
+
|
|
819
|
+
/** Put the keyboard on the Compare button of the last file compared, once the home is drawn. */
|
|
820
|
+
async function focusLastCompared(paneId: string, pane: PaneMount): Promise<void> {
|
|
821
|
+
await pane.handle.ready;
|
|
822
|
+
const button =
|
|
823
|
+
_lastCompared === null ? undefined : _compareButtons.get(paneId)?.get(_lastCompared);
|
|
824
|
+
if (button?.isConnected) {
|
|
825
|
+
// The focusable node of a kit button is the control inside it, never the host (ui.md §5).
|
|
826
|
+
(button.querySelector<HTMLElement>('[part="control"]') ?? button).focus();
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* Give the keyboard back to the Publish home in `paneId`: what leaving a comparison with Back does
|
|
832
|
+
* (`workspace/workspace-back.ts`), so the author lands on the change they compared rather than on
|
|
833
|
+
* the page body. Served now when the home is standing in the pane, or by its next mount there.
|
|
834
|
+
*
|
|
835
|
+
* @param {string} paneId
|
|
836
|
+
*/
|
|
837
|
+
export function focusPublishHome(paneId: string): void {
|
|
838
|
+
const pane = _panes.get(paneId);
|
|
839
|
+
if (pane?.wrap.isConnected && pane.handle.attached()) {
|
|
840
|
+
void focusLastCompared(paneId, pane);
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
_focusOnMount.add(paneId);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/** Whether this tab's workspace is already mounted in this pane and still in the document. */
|
|
847
|
+
export function publishWorkspaceMounted(paneId: string, tab: Tab): boolean {
|
|
848
|
+
const pane = _panes.get(paneId);
|
|
849
|
+
return (
|
|
850
|
+
pane !== undefined && pane.tabId === tab.id && pane.wrap.isConnected && pane.handle.attached()
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
/** Tear one pane's workspace down (mode change, tab switch, project close). Idempotent. */
|
|
855
|
+
export function detachPublishWorkspace(paneId: string): void {
|
|
856
|
+
const pane = _panes.get(paneId);
|
|
857
|
+
if (!pane) {
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
pane.scope.stop();
|
|
861
|
+
const host = _adapterHosts.get(paneId);
|
|
862
|
+
if (host) {
|
|
863
|
+
disposeDeploySection(host);
|
|
864
|
+
}
|
|
865
|
+
pane.handle.dispose();
|
|
866
|
+
_panes.delete(paneId);
|
|
867
|
+
_adapterHosts.delete(paneId);
|
|
868
|
+
_compareButtons.delete(paneId);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Draw the Publish home into a pane's stage.
|
|
873
|
+
*
|
|
874
|
+
* The first mount is what starts the first read, and a read that already FAILED must not re-arm it
|
|
875
|
+
* (Refresh and the poll are the ways back), which is what the `loading` mood means. The background
|
|
876
|
+
* poll is armed once a repository has been seen.
|
|
877
|
+
*
|
|
878
|
+
* @param {CanvasSurface} surface
|
|
879
|
+
* @param {Tab} tab
|
|
880
|
+
*/
|
|
881
|
+
export function renderPublishWorkspace(surface: CanvasSurface, tab: Tab): void {
|
|
882
|
+
const { paneId, wrap } = surface;
|
|
883
|
+
if (publishWorkspaceMounted(paneId, tab)) {
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
detachPublishWorkspace(paneId);
|
|
887
|
+
if (hasPlatform() && moodOf() === "loading") {
|
|
888
|
+
void refreshGitStatus();
|
|
889
|
+
}
|
|
890
|
+
showPublishTab(shell.publish.tab);
|
|
891
|
+
/* The region names the pane; nothing else in the projection is about one. The rows it draws from
|
|
892
|
+
commands are project-level records, so the registry's answer is the same in either pane. */
|
|
893
|
+
const region = paneRegion(paneId, "publish");
|
|
894
|
+
const handle = mountPublishWorkspaceSurface(
|
|
895
|
+
wrap,
|
|
896
|
+
publishWorkspaceView(region),
|
|
897
|
+
actionsFor(paneId),
|
|
898
|
+
);
|
|
899
|
+
const scope = effectScope();
|
|
900
|
+
scope.run(() => {
|
|
901
|
+
effect(() => {
|
|
902
|
+
const view = publishWorkspaceView(region);
|
|
903
|
+
if (view.mood === "repo") {
|
|
904
|
+
armGitPoll();
|
|
905
|
+
}
|
|
906
|
+
handle.update(view);
|
|
907
|
+
syncAdapterSection(paneId);
|
|
908
|
+
// Deploys follows a deploy that finished since it was read, wherever that deploy was run.
|
|
909
|
+
if (view.tab === "deploys" && deploysNeedReading()) {
|
|
910
|
+
void loadDeployments();
|
|
911
|
+
}
|
|
912
|
+
});
|
|
913
|
+
/* Review changes includes what it has not shown before, so a change set opens as the board
|
|
914
|
+
draws it (every change included) and a file the author leaves out stays out. */
|
|
915
|
+
effect(() => {
|
|
916
|
+
if (shell.publish.tab === "review" && moodOf() === "repo") {
|
|
917
|
+
void includeNewChanges();
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
});
|
|
921
|
+
const pane: PaneMount = { handle, scope, tabId: tab.id, wrap };
|
|
922
|
+
_panes.set(paneId, pane);
|
|
923
|
+
if (_focusOnMount.delete(paneId)) {
|
|
924
|
+
void focusLastCompared(paneId, pane);
|
|
925
|
+
}
|
|
926
|
+
}
|