@jxsuite/studio 10.0.0 → 10.2.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/dist/chunks/{2020-j56903f1.js → 2020-bx702wqa.js} +3 -3
- package/dist/chunks/adapter-pp011m5x.js +4 -0
- package/dist/chunks/add-new-flow-ecqmzz76.js +4 -0
- package/dist/chunks/cf-account-picker-c07znkqt.js +4 -0
- package/dist/chunks/cf-settings-0v2bmqy0.js +4 -0
- package/dist/chunks/components-yn0dn2pq.js +4 -0
- package/dist/chunks/content-source-g0j41yy6.js +4 -0
- package/dist/chunks/{convert-to-repeater-fhdwyg14.js → convert-to-repeater-sbjk7tgn.js} +3 -3
- package/dist/chunks/editor-xz02jeh1.js +4 -0
- package/dist/chunks/entry-commands-kmt02crp.js +4 -0
- package/dist/chunks/extension-sections-0rprf12d.js +4 -0
- package/dist/chunks/file-ops-mexj5qed.js +4 -0
- package/dist/chunks/files-b2tcppdd.js +4 -0
- package/dist/chunks/format-host-btsevv0c.js +4 -0
- package/dist/chunks/fs-events-r0apejva.js +4 -0
- package/dist/chunks/grid-open-n2a8xkht.js +4 -0
- package/dist/chunks/{grid-view-ka55z38f.js → grid-view-tghcwrrt.js} +12 -12
- package/dist/chunks/iframe-host-gx0ypyh3.js +4 -0
- package/dist/chunks/index-bs4k9tr6.js +4 -0
- package/dist/chunks/{index-qb98xhse.js → index-vr006kbt.js} +3 -3
- package/dist/chunks/{javascript-ces13wny.js → javascript-z1g3s9fd.js} +2 -2
- package/dist/chunks/{jsonMode-6md16d4v.js → jsonMode-7szwg8c4.js} +5 -5
- package/dist/chunks/jx-validate-ymf10teh.js +4 -0
- package/dist/chunks/layers-p9nfya38.js +4 -0
- package/dist/chunks/layers-panel-tt1hzhhf.js +4 -0
- package/dist/chunks/lit-html-x18n2sfz.js +4 -0
- package/dist/chunks/media-library-0n45gv8r.js +4 -0
- package/dist/chunks/media-picker-s09at95z.js +4 -0
- package/dist/chunks/{monaco-binding-p8j80s4w.js → monaco-binding-v42w9c1v.js} +3 -3
- package/dist/chunks/monaco-buffer-een2ea0v.js +4 -0
- package/dist/chunks/{monaco-setup-wasktzfg.js → monaco-setup-7bffxd6v.js} +3 -3
- package/dist/chunks/notify-qwb8radt.js +4 -0
- package/dist/chunks/parse-v77xtwv7.js +4 -0
- package/dist/chunks/platform-8jnez0wr.js +4 -0
- package/dist/chunks/publish-panel-aysnj611.js +4 -0
- package/dist/chunks/{publish-quick-vr695j26.js → publish-quick-kpbqs9mz.js} +3 -3
- package/dist/chunks/publish-workspace-nd0a8q99.js +4 -0
- package/dist/chunks/quick-search-vg0jkhk2.js +4 -0
- package/dist/chunks/references-a7v6a0e2.js +4 -0
- package/dist/chunks/right-panel-cvk1vxx5.js +4 -0
- package/dist/chunks/source-control-fsqzc6he.js +4 -0
- package/dist/chunks/studio-05jfwbmf.js +5 -0
- package/dist/chunks/{studio-hy4ye0dx.js → studio-0q5jwddh.js} +7 -7
- package/dist/chunks/{studio-jttyqrmh.js → studio-0w2dr0q8.js} +4 -4
- package/dist/chunks/{studio-xkxetm65.js → studio-1tzpds97.js} +5 -5
- package/dist/chunks/{studio-m48v13s3.js → studio-216b27sv.js} +3 -3
- package/dist/chunks/studio-2ejhqns4.js +5 -0
- package/dist/chunks/studio-2r90cts3.js +16 -0
- package/dist/chunks/{studio-h310mbqm.js → studio-399a25za.js} +4 -4
- package/dist/chunks/studio-399xknkb.js +6 -0
- package/dist/chunks/{studio-wxhy1yh7.js → studio-43y04qck.js} +4 -4
- package/dist/chunks/{studio-06j4en5h.js → studio-4g8yb6jn.js} +2 -2
- package/dist/chunks/{studio-jw0q5j6k.js → studio-4jhw38b2.js} +4 -4
- package/dist/chunks/{studio-3v5cagy9.js → studio-4ncmwqw5.js} +4 -4
- package/dist/chunks/studio-5atwjgkj.js +7 -0
- package/dist/chunks/{studio-gr36vnte.js → studio-5frqk7c0.js} +4 -4
- package/dist/chunks/{studio-9dz76yqc.js → studio-646nn2ys.js} +5 -5
- package/dist/chunks/{studio-tj2s0tks.js → studio-6cwj6xrx.js} +4 -4
- package/dist/chunks/studio-6hzdjfv0.js +5 -0
- package/dist/chunks/{studio-hcsba201.js → studio-71m92v9s.js} +7 -7
- package/dist/chunks/{studio-3c8pp7pb.js → studio-75kcdbsk.js} +4 -4
- package/dist/chunks/{studio-gwrhcrss.js → studio-78pywnp2.js} +5 -5
- package/dist/chunks/{studio-9cxmp61c.js → studio-86r8hp7r.js} +5 -5
- package/dist/chunks/studio-886t6ztg.js +5 -0
- package/dist/chunks/studio-8wnvtr4z.js +5 -0
- package/dist/chunks/studio-8z66jk4z.js +5 -0
- package/dist/chunks/{studio-ajw4k8hp.js → studio-9rbe2xpw.js} +3 -3
- package/dist/chunks/{studio-y26hx9re.js → studio-awxwjawq.js} +4 -4
- package/dist/chunks/studio-b2qpt0xy.js +14 -0
- package/dist/chunks/{studio-dx73md4n.js → studio-bvs9kpst.js} +7 -7
- package/dist/chunks/studio-c71806xz.js +7 -0
- package/dist/chunks/{studio-7dzyz9n8.js → studio-cbwks69x.js} +144 -144
- package/dist/chunks/studio-cg1bfyhf.js +9 -0
- package/dist/chunks/studio-cg710sk9.js +5 -0
- package/dist/chunks/studio-cw0wb9qk.js +7 -0
- package/dist/chunks/studio-cyheys7k.js +5 -0
- package/dist/chunks/studio-ea5m9kg5.js +5 -0
- package/dist/chunks/{studio-ry81j4q1.js → studio-ea74s8c1.js} +4 -4
- package/dist/chunks/{studio-xdpz9rpe.js → studio-efdxjwyp.js} +34 -34
- package/dist/chunks/{studio-agyvzfm4.js → studio-egmw2837.js} +4 -4
- package/dist/chunks/{studio-5tzgq6fx.js → studio-er5f383a.js} +4 -4
- package/dist/chunks/studio-f746039f.js +5 -0
- package/dist/chunks/studio-fmdymd9v.js +5 -0
- package/dist/chunks/studio-gcm12z00.js +6 -0
- package/dist/chunks/{studio-vswcpzk7.js → studio-gj98m8gy.js} +5 -5
- package/dist/chunks/{studio-yg3djpnm.js → studio-hasbgjah.js} +4 -4
- package/dist/chunks/studio-hhp4x8xf.js +6 -0
- package/dist/chunks/{studio-3w35qvay.js → studio-hz5n7x27.js} +4 -4
- package/dist/chunks/{studio-7xag4na8.js → studio-j3d2fjqz.js} +4 -4
- package/dist/chunks/studio-jeraxdgv.js +7 -0
- package/dist/chunks/studio-jvgek9xt.js +5 -0
- package/dist/chunks/studio-jws1a87n.js +5 -0
- package/dist/chunks/studio-k2sz13zg.js +5 -0
- package/dist/chunks/studio-k3m4jkdk.js +5 -0
- package/dist/chunks/{studio-r17asgs0.js → studio-kg1abnz0.js} +4 -4
- package/dist/chunks/{studio-s0fg2wav.js → studio-kg5ya5r2.js} +4 -4
- package/dist/chunks/studio-khwbgrtg.js +5 -0
- package/dist/chunks/studio-mx6wzh25.js +5 -0
- package/dist/chunks/studio-mxs3ar9t.js +5 -0
- package/dist/chunks/studio-nm4szey4.js +5 -0
- package/dist/chunks/studio-nqxqf8p2.js +4 -0
- package/dist/chunks/{studio-h5tpz5ke.js → studio-pjtntksy.js} +4 -4
- package/dist/chunks/studio-qh8berdz.js +5 -0
- package/dist/chunks/studio-qn7z4krm.js +5 -0
- package/dist/chunks/studio-qyed0ah6.js +5 -0
- package/dist/chunks/{studio-x97ay0v4.js → studio-r4bcdwhq.js} +4 -4
- package/dist/chunks/{studio-wxdcd42g.js → studio-r7pf0rkv.js} +6 -6
- package/dist/chunks/studio-rtfnhrdg.js +5 -0
- package/dist/chunks/{studio-fy7jzj9j.js → studio-rzqaz21g.js} +4 -4
- package/dist/chunks/studio-s5tzw84t.js +5 -0
- package/dist/chunks/{studio-1e8dw2w2.js → studio-sevbc3xv.js} +4 -4
- package/dist/chunks/{studio-ewarnzm4.js → studio-sxdb9h3q.js} +6 -6
- package/dist/chunks/studio-t51y8v1d.js +5 -0
- package/dist/chunks/{studio-k3bcy02e.js → studio-t98vq4r6.js} +4 -4
- package/dist/chunks/studio-th7z3fg7.js +5 -0
- package/dist/chunks/studio-tsxpqvh0.js +6 -0
- package/dist/chunks/{studio-396srjbk.js → studio-v1yascv2.js} +4 -4
- package/dist/chunks/{studio-ekgtrndg.js → studio-wc6za3zs.js} +4 -4
- package/dist/chunks/{studio-ndskgqef.js → studio-wxq6v5sv.js} +4 -4
- package/dist/chunks/{studio-4kzbrr5t.js → studio-y60sts62.js} +3 -3
- package/dist/chunks/studio-ybnwymxw.js +5 -0
- package/dist/chunks/studio-zndrqawb.js +5 -0
- package/dist/chunks/studio-zwaxn5s2.js +5 -0
- package/dist/chunks/{tsMode-h6tkz0ea.js → tsMode-f54j66ym.js} +5 -5
- package/dist/chunks/workspace-home-x1787wpv.js +4 -0
- package/dist/chunks/workspace-zpe1np43.js +4 -0
- package/dist/chunks/{ws-client-e951my95.js → ws-client-e8kj684q.js} +3 -3
- package/dist/manifest.json +76 -75
- package/dist/studio.js +83 -81
- package/package.json +4 -4
- package/src/browse/library-model.ts +56 -23
- package/src/commands/context.ts +1 -0
- package/src/editor/shortcuts.ts +3 -0
- package/src/panels/data-grid.ts +0 -4
- package/src/panels/settings-menu.ts +3 -3
- package/src/panels/settings-pane.ts +42 -56
- package/src/platform-errors.ts +17 -1
- package/src/platforms/cloud.ts +14 -0
- package/src/publish/publish-changes.ts +136 -4
- package/src/services/ai-import-tools.ts +6 -3
- package/src/services/ai-models.ts +65 -11
- package/src/services/ai-settings.ts +45 -5
- package/src/services/document-assistant.ts +5 -2
- package/src/services/settings/definitions.ts +19 -0
- package/src/settings/contributed-section.ts +16 -7
- package/src/settings/css-vars-editor.ts +4 -4
- package/src/settings/general-settings.ts +1 -4
- package/src/settings/preferences-accounts.ts +2 -2
- package/src/settings/preferences-dialog.ts +4 -2
- package/src/settings/project-sections.ts +1 -1
- package/src/settings/section-registry.ts +5 -4
- package/src/settings/settings-document.ts +33 -24
- package/src/settings/settings-workspace.ts +205 -0
- package/src/shell.ts +31 -6
- package/src/surfaces/ai-credentials-form.json +59 -5
- package/src/surfaces/ai-credentials-form.ts +15 -2
- package/src/surfaces/commandbar.ts +7 -3
- package/src/surfaces/settings-contributed.json +4 -4
- package/src/surfaces/settings-contributed.ts +2 -2
- package/src/surfaces/settings-pane.json +262 -180
- package/src/surfaces/settings-pane.ts +63 -14
- package/src/types.ts +14 -0
- package/src/ui/ai-credentials-form.ts +128 -11
- package/src/ui/regions.ts +5 -5
- package/src/workspace/tab-workspace.ts +3 -2
- package/src/workspace/workspace-home.ts +18 -4
- package/src/workspace/workspace-ids.ts +40 -7
- package/dist/chunks/adapter-wc5zvg9a.js +0 -4
- package/dist/chunks/add-new-flow-snx1s4b0.js +0 -4
- package/dist/chunks/cf-account-picker-xyqw0jex.js +0 -4
- package/dist/chunks/cf-settings-hp1pjm0n.js +0 -4
- package/dist/chunks/components-nkwphrx3.js +0 -4
- package/dist/chunks/content-source-6a253wmk.js +0 -4
- package/dist/chunks/editor-wqrc68ca.js +0 -4
- package/dist/chunks/entry-commands-nef0wrmp.js +0 -4
- package/dist/chunks/extension-sections-8ez2rrk8.js +0 -4
- package/dist/chunks/file-ops-y3w71k1n.js +0 -4
- package/dist/chunks/files-de90apn9.js +0 -4
- package/dist/chunks/format-host-zfjz4rzm.js +0 -4
- package/dist/chunks/fs-events-77c0mr0x.js +0 -4
- package/dist/chunks/grid-open-4n4fjfpf.js +0 -4
- package/dist/chunks/iframe-host-na2mywz4.js +0 -4
- package/dist/chunks/index-ewmgdb7c.js +0 -4
- package/dist/chunks/jx-validate-b309c020.js +0 -4
- package/dist/chunks/layers-8rn8y6qv.js +0 -4
- package/dist/chunks/layers-panel-eyah7h9z.js +0 -4
- package/dist/chunks/lit-html-wyqw04y2.js +0 -4
- package/dist/chunks/media-library-gs3np47y.js +0 -4
- package/dist/chunks/media-picker-tt7dk4tb.js +0 -4
- package/dist/chunks/monaco-buffer-wsf6cmnq.js +0 -4
- package/dist/chunks/notify-qwst108r.js +0 -4
- package/dist/chunks/parse-n8dfxnqf.js +0 -4
- package/dist/chunks/platform-c16mqvmp.js +0 -4
- package/dist/chunks/publish-panel-d34bhvwj.js +0 -4
- package/dist/chunks/publish-workspace-2yr60mt4.js +0 -4
- package/dist/chunks/quick-search-e6xsdv0d.js +0 -4
- package/dist/chunks/references-gsj6trdv.js +0 -4
- package/dist/chunks/right-panel-33jv1knc.js +0 -4
- package/dist/chunks/source-control-bj9bejjv.js +0 -4
- package/dist/chunks/studio-03mqh8ax.js +0 -5
- package/dist/chunks/studio-0ydxv2mr.js +0 -7
- package/dist/chunks/studio-2hsqbq7b.js +0 -7
- package/dist/chunks/studio-2j86y34y.js +0 -6
- package/dist/chunks/studio-2ks6ka4g.js +0 -5
- package/dist/chunks/studio-34jrz12k.js +0 -5
- package/dist/chunks/studio-35vrtpdq.js +0 -5
- package/dist/chunks/studio-38g0jdz7.js +0 -5
- package/dist/chunks/studio-3zge48z4.js +0 -7
- package/dist/chunks/studio-45gyxpcq.js +0 -5
- package/dist/chunks/studio-5wzdy1jc.js +0 -16
- package/dist/chunks/studio-732mw0g8.js +0 -5
- package/dist/chunks/studio-771k2n12.js +0 -5
- package/dist/chunks/studio-88bmt9rh.js +0 -5
- package/dist/chunks/studio-c1pxszat.js +0 -5
- package/dist/chunks/studio-cpkamjdn.js +0 -14
- package/dist/chunks/studio-d038kqbs.js +0 -5
- package/dist/chunks/studio-d83m0wyq.js +0 -5
- package/dist/chunks/studio-dbxv7w4s.js +0 -5
- package/dist/chunks/studio-de1s2x9v.js +0 -5
- package/dist/chunks/studio-epenqmvk.js +0 -7
- package/dist/chunks/studio-et2q4s8n.js +0 -5
- package/dist/chunks/studio-h7weqf2q.js +0 -5
- package/dist/chunks/studio-hwejq03c.js +0 -5
- package/dist/chunks/studio-k3a8rp3d.js +0 -5
- package/dist/chunks/studio-p1n2xjda.js +0 -5
- package/dist/chunks/studio-pgm83a2f.js +0 -6
- package/dist/chunks/studio-pqqq6v85.js +0 -5
- package/dist/chunks/studio-psh8mwnw.js +0 -4
- package/dist/chunks/studio-rzg8hnkf.js +0 -5
- package/dist/chunks/studio-tyw7tk0x.js +0 -5
- package/dist/chunks/studio-v9ywz2se.js +0 -6
- package/dist/chunks/studio-vzgmyt0v.js +0 -5
- package/dist/chunks/studio-x08a3m0a.js +0 -5
- package/dist/chunks/studio-x3mjr5cc.js +0 -5
- package/dist/chunks/studio-xh683qzy.js +0 -5
- package/dist/chunks/studio-yx8hzqja.js +0 -5
- package/dist/chunks/studio-zg16eeq1.js +0 -5
- package/dist/chunks/studio-zgx7svjd.js +0 -9
- package/dist/chunks/studio-zkq3r91p.js +0 -5
- package/dist/chunks/workspace-home-zkkrxq3r.js +0 -4
- package/dist/chunks/workspace-tdqqjqaa.js +0 -4
- package/src/settings/settings-dialog.ts +0 -192
- package/src/surfaces/settings-dialog.json +0 -51
- package/src/surfaces/settings-dialog.ts +0 -169
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
/**
|
|
3
|
-
* The Project Settings
|
|
4
|
-
* else.
|
|
3
|
+
* The Project Settings workspace's home as a mounted document: its heading and Back control over
|
|
4
|
+
* the two columns. The mount seam, and nothing else.
|
|
5
5
|
*
|
|
6
6
|
* `panels/settings-pane.ts` is the flow: which sections are registered, which group lists each,
|
|
7
7
|
* which one is current, what a nav click does, and which renderer draws the body. This is the
|
|
8
|
-
* surface it draws into, and `
|
|
8
|
+
* surface it draws into, and `settings/settings-workspace.ts` mounts it into a pane's stage.
|
|
9
9
|
*
|
|
10
10
|
* **One island, and the sections are why.** A settings section is handed a container and renders
|
|
11
11
|
* into it (some of them still with lit, some by mounting a document of their own) so the body
|
|
@@ -51,8 +51,14 @@ export interface SettingsPaneMovedRow {
|
|
|
51
51
|
linkLabel: string;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
/** What the
|
|
54
|
+
/** What the home says right now. */
|
|
55
55
|
export interface SettingsPaneValues {
|
|
56
|
+
/** `pane.<id>/settings`: the pane this home stands in names it, as Publish's does. */
|
|
57
|
+
region: string;
|
|
58
|
+
/** The heading, which is also the region's name: the workspace's title. */
|
|
59
|
+
heading: string;
|
|
60
|
+
/** The Back control's text, naming where it goes ("Back to Design"). */
|
|
61
|
+
backLabel: string;
|
|
56
62
|
/** The current section's key. */
|
|
57
63
|
active: string;
|
|
58
64
|
/** The current section's row id, so the body is named by the row that chose it. */
|
|
@@ -80,6 +86,8 @@ export interface SettingsPaneActions {
|
|
|
80
86
|
selectSection: (key: string) => void;
|
|
81
87
|
/** A Moved footnote's button was activated, naming the workspace it goes to. */
|
|
82
88
|
openMoved: (workspace: string) => void;
|
|
89
|
+
/** The Back control was activated: leave Settings for the workspace it was opened from. */
|
|
90
|
+
back: () => void;
|
|
83
91
|
}
|
|
84
92
|
|
|
85
93
|
export interface SettingsPaneHandle {
|
|
@@ -95,15 +103,24 @@ export interface SettingsPaneHandle {
|
|
|
95
103
|
body: () => HTMLElement | null;
|
|
96
104
|
/** Settles once the document is mounted, or the surface was disposed first. */
|
|
97
105
|
ready: Promise<void>;
|
|
106
|
+
/**
|
|
107
|
+
* Whether the document still stands in the container it was mounted into: true while the mount is
|
|
108
|
+
* in flight, false once disposed or once something else replaced the container's children.
|
|
109
|
+
*/
|
|
110
|
+
attached: () => boolean;
|
|
98
111
|
/**
|
|
99
112
|
* Put the keyboard on the current section's row.
|
|
100
113
|
*
|
|
101
|
-
*
|
|
102
|
-
* group's tab stop,
|
|
103
|
-
*
|
|
104
|
-
* strip writes
|
|
114
|
+
* Opening Settings on Advanced › Raw JSON has to put the ring on Raw JSON, not on the first
|
|
115
|
+
* group's tab stop, or the selection is three rows down a different list from the keyboard. The
|
|
116
|
+
* row is found by the section key the document was handed (`active`), not by `aria-selected`: the
|
|
117
|
+
* strip writes that a turn after the row is created, and a home opened by a command asks for the
|
|
118
|
+
* keyboard as soon as it has drawn. A row is focusable once its strip has given it a tab stop, so
|
|
119
|
+
* a row whose strip has not synced yet is focused when the strip says it is ready.
|
|
120
|
+
*
|
|
121
|
+
* @returns {Promise<void>} Settles once the keyboard has moved, or at once when there is no row
|
|
105
122
|
*/
|
|
106
|
-
focusActive: () => void
|
|
123
|
+
focusActive: () => Promise<void>;
|
|
107
124
|
/** Take the document down. Idempotent. */
|
|
108
125
|
dispose: () => void;
|
|
109
126
|
}
|
|
@@ -125,12 +142,12 @@ function failureStateOf(failure: string): string {
|
|
|
125
142
|
}
|
|
126
143
|
|
|
127
144
|
/**
|
|
128
|
-
* Mount the
|
|
145
|
+
* Mount the home into `container`, a pane's stage.
|
|
129
146
|
*
|
|
130
147
|
* The container is cleared first, lit's render marker included, so a host handed back after some
|
|
131
148
|
* other renderer used it cannot make the next lit render throw on a part whose nodes have gone.
|
|
132
149
|
*
|
|
133
|
-
* @param {HTMLElement} container The
|
|
150
|
+
* @param {HTMLElement} container The pane's stage
|
|
134
151
|
* @param {SettingsPaneValues} values What to say on the first paint
|
|
135
152
|
* @param {SettingsPaneActions} actions What a row and a Moved button do. Read once.
|
|
136
153
|
* @returns {SettingsPaneHandle}
|
|
@@ -171,6 +188,13 @@ export function mountSettingsPaneSurface(
|
|
|
171
188
|
});
|
|
172
189
|
|
|
173
190
|
return {
|
|
191
|
+
attached: () => {
|
|
192
|
+
if (disposed) {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
const root = mounted?.root;
|
|
196
|
+
return root === undefined ? true : root instanceof HTMLElement && container.contains(root);
|
|
197
|
+
},
|
|
174
198
|
body: () => body,
|
|
175
199
|
dispose() {
|
|
176
200
|
disposed = true;
|
|
@@ -178,10 +202,32 @@ export function mountSettingsPaneSurface(
|
|
|
178
202
|
mounted = null;
|
|
179
203
|
body = null;
|
|
180
204
|
},
|
|
181
|
-
focusActive() {
|
|
205
|
+
async focusActive() {
|
|
182
206
|
const root = mounted?.root;
|
|
183
|
-
|
|
184
|
-
root
|
|
207
|
+
const row =
|
|
208
|
+
root instanceof HTMLElement
|
|
209
|
+
? [...root.querySelectorAll<HTMLElement>('[part="nav-item"]')].find(
|
|
210
|
+
(candidate) => candidate.dataset["section"] === scope.active,
|
|
211
|
+
)
|
|
212
|
+
: undefined;
|
|
213
|
+
if (!row) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const strip = row.closest("jx-tabs");
|
|
217
|
+
// `aria-selected` is what the strip writes when it syncs, and the sync is what gives a tab stop.
|
|
218
|
+
if (strip && row.getAttribute("aria-selected") !== "true") {
|
|
219
|
+
await new Promise<void>((resolve) => {
|
|
220
|
+
strip.addEventListener(
|
|
221
|
+
"jx-ready",
|
|
222
|
+
() => {
|
|
223
|
+
resolve();
|
|
224
|
+
},
|
|
225
|
+
{ once: true },
|
|
226
|
+
);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
if (!disposed) {
|
|
230
|
+
row.focus();
|
|
185
231
|
}
|
|
186
232
|
},
|
|
187
233
|
ready,
|
|
@@ -189,6 +235,9 @@ export function mountSettingsPaneSurface(
|
|
|
189
235
|
/* Field by field, so an unchanged one is inert, and `active` LAST, because it is what each
|
|
190
236
|
strip's selection is compared against: writing it before the rows it names have arrived
|
|
191
237
|
would make `jx-tabs` sync a selection onto a column that does not hold that tab yet. */
|
|
238
|
+
scope.region = next.region;
|
|
239
|
+
scope.heading = next.heading;
|
|
240
|
+
scope.backLabel = next.backLabel;
|
|
192
241
|
scope.groups = next.groups;
|
|
193
242
|
scope.moved = next.moved;
|
|
194
243
|
scope.activeTabId = next.activeTabId;
|
package/src/types.ts
CHANGED
|
@@ -34,6 +34,7 @@ import type {
|
|
|
34
34
|
ExtensionsInfo,
|
|
35
35
|
FsEvent,
|
|
36
36
|
GitBranchesResult,
|
|
37
|
+
GitCommitBranchResult,
|
|
37
38
|
GitLogEntry,
|
|
38
39
|
GitStatusResult,
|
|
39
40
|
ImportProgressEvent,
|
|
@@ -88,6 +89,7 @@ export type {
|
|
|
88
89
|
ExtensionsInfo,
|
|
89
90
|
FsEvent,
|
|
90
91
|
GitBranchesResult,
|
|
92
|
+
GitCommitBranchResult,
|
|
91
93
|
GitFileStatus,
|
|
92
94
|
GitLogEntry,
|
|
93
95
|
GitStatusResult,
|
|
@@ -580,6 +582,18 @@ export interface StudioPlatform {
|
|
|
580
582
|
gitFetch: () => Promise<void>;
|
|
581
583
|
gitCheckout: (branch: string) => Promise<void>;
|
|
582
584
|
gitCreateBranch: (name: string) => Promise<void>;
|
|
585
|
+
/**
|
|
586
|
+
* Commit the change set as a NEW branch, parented on the commit this session's edits were made
|
|
587
|
+
* against, and take the committed files out of this session. Rejects with code `branch_exists`
|
|
588
|
+
* when the name is taken (the ref is created, never moved).
|
|
589
|
+
*
|
|
590
|
+
* The way out on a host where a branch is a session (`gitHosted`): a commit to the session's own
|
|
591
|
+
* branch that the remote conflicts with can still be published, as a branch of its own the pull
|
|
592
|
+
* request then reconciles, and a change set on a branch whose every commit is live can be sent
|
|
593
|
+
* for review without landing there first. An ordinary working tree has no need of it: there a
|
|
594
|
+
* branch is created and checked out with the changes still in it (studio.md §22.8).
|
|
595
|
+
*/
|
|
596
|
+
gitCommitBranch?: (opts: { branch: string; message: string }) => Promise<GitCommitBranchResult>;
|
|
583
597
|
gitDiff: (path?: string) => Promise<string>;
|
|
584
598
|
gitShow: (opts: { path: string; ref?: string }) => Promise<string>;
|
|
585
599
|
gitDiscard: (files: string[]) => Promise<void>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
/**
|
|
3
|
-
* Ai-credentials-form.ts — reusable AI provider credentials form (key / model /
|
|
3
|
+
* Ai-credentials-form.ts — reusable AI provider credentials form (provider / key / model /
|
|
4
|
+
* endpoint).
|
|
4
5
|
*
|
|
5
6
|
* Embedded by every host that needs a provider configured — **Preferences › Assistant** (⌘,) and
|
|
6
7
|
* the New Project modal's Import/Agent gates. All draft/model-list state lives inside the closure,
|
|
@@ -23,9 +24,16 @@
|
|
|
23
24
|
* @docs studio/ai
|
|
24
25
|
*/
|
|
25
26
|
|
|
26
|
-
import {
|
|
27
|
+
import type { AiProviderFamily } from "@jxsuite/ai/streaming-client";
|
|
28
|
+
import { isString } from "@jxsuite/schema/guards";
|
|
29
|
+
import {
|
|
30
|
+
backendProviders,
|
|
31
|
+
fetchAvailableModels,
|
|
32
|
+
proxyModelsErrorMessage,
|
|
33
|
+
} from "../services/ai-models";
|
|
27
34
|
import {
|
|
28
35
|
aiProviderDiffers,
|
|
36
|
+
getAiProvider,
|
|
29
37
|
getBaseUrl,
|
|
30
38
|
getOpenAiKey,
|
|
31
39
|
saveAiProvider,
|
|
@@ -38,10 +46,49 @@ import { createAiCredentialsSurface } from "../surfaces/ai-credentials-form";
|
|
|
38
46
|
import type { AiCredentialsSurface, AiCredentialsView } from "../surfaces/ai-credentials-form";
|
|
39
47
|
import type { SurfaceOption } from "./surface";
|
|
40
48
|
|
|
41
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* The blurb a host that says nothing gets, where the backend reaches the OpenAI-compatible family
|
|
51
|
+
* alone.
|
|
52
|
+
*/
|
|
42
53
|
const DEFAULT_INTRO =
|
|
43
54
|
"Any OpenAI-compatible key works. Stored locally in this browser; sent only to the Studio proxy (never to a third party except your chosen endpoint).";
|
|
44
55
|
|
|
56
|
+
/** The same blurb where the backend also reaches Anthropic, so the picker is drawn. */
|
|
57
|
+
const PROVIDER_INTRO =
|
|
58
|
+
"An Anthropic key for Claude, or any OpenAI-compatible key. Stored locally in this browser; sent only to the Studio proxy (never to a third party except your chosen provider).";
|
|
59
|
+
|
|
60
|
+
/** The families the picker offers, in the order it lists them. */
|
|
61
|
+
const PROVIDER_OPTIONS: SurfaceOption[] = [
|
|
62
|
+
{ label: "OpenAI-compatible", value: "openai-compat" },
|
|
63
|
+
{ label: "Anthropic (Claude)", value: "anthropic" },
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
/** What each field says while it is empty, per family. */
|
|
67
|
+
const PLACEHOLDERS: Readonly<
|
|
68
|
+
Record<AiProviderFamily, { key: string; model: string; endpoint: string }>
|
|
69
|
+
> = {
|
|
70
|
+
anthropic: {
|
|
71
|
+
endpoint: "Endpoint (optional, defaults to https://api.anthropic.com)",
|
|
72
|
+
key: "sk-ant-…",
|
|
73
|
+
model: "Model ID (e.g. claude-opus-5-5)",
|
|
74
|
+
},
|
|
75
|
+
"openai-compat": {
|
|
76
|
+
endpoint: "Endpoint (optional, e.g. http://localhost:11434/v1)",
|
|
77
|
+
key: "sk-… or any compatible key",
|
|
78
|
+
model: "Model ID (e.g. gpt-4o, claude-sonnet-4-20250514, etc.)",
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* A picker value as a family: the one other family, else the default.
|
|
84
|
+
*
|
|
85
|
+
* @param {string} value - What the picker reported
|
|
86
|
+
* @returns {AiProviderFamily}
|
|
87
|
+
*/
|
|
88
|
+
function familyOf(value: string): AiProviderFamily {
|
|
89
|
+
return value === "anthropic" ? "anthropic" : "openai-compat";
|
|
90
|
+
}
|
|
91
|
+
|
|
45
92
|
export interface AiCredentialsFormOptions {
|
|
46
93
|
/** Host re-render scheduler — called whenever the form's internal state changes. */
|
|
47
94
|
requestRender: () => void;
|
|
@@ -56,9 +103,11 @@ export interface AiCredentialsFormOptions {
|
|
|
56
103
|
* Optional context line replacing the default blurb.
|
|
57
104
|
*
|
|
58
105
|
* A string, not a template: the line is drawn by a document, and a document renders text rather
|
|
59
|
-
* than somebody else's markup. Nothing that passed one ever passed more than a sentence.
|
|
106
|
+
* than somebody else's markup. Nothing that passed one ever passed more than a sentence. A
|
|
107
|
+
* function is asked whether the provider picker is drawn, so a host's own sentence can name
|
|
108
|
+
* Claude exactly when Claude is a choice.
|
|
60
109
|
*/
|
|
61
|
-
intro?: string;
|
|
110
|
+
intro?: string | ((offersProviders: boolean) => string);
|
|
62
111
|
}
|
|
63
112
|
|
|
64
113
|
export interface AiCredentialsForm {
|
|
@@ -79,6 +128,7 @@ export interface AiCredentialsForm {
|
|
|
79
128
|
* @returns {AiCredentialsForm}
|
|
80
129
|
*/
|
|
81
130
|
export function createAiCredentialsForm(opts: AiCredentialsFormOptions): AiCredentialsForm {
|
|
131
|
+
let providerDraft: AiProviderFamily = "openai-compat";
|
|
82
132
|
let keyDraft = "";
|
|
83
133
|
let baseUrlDraft = "";
|
|
84
134
|
let modelDraft = "";
|
|
@@ -95,7 +145,12 @@ export function createAiCredentialsForm(opts: AiCredentialsFormOptions): AiCrede
|
|
|
95
145
|
* Cancel replaces it. Starts blank because the drafts do, which is what lets a host that never
|
|
96
146
|
* calls `startEdit` still open on what is stored.
|
|
97
147
|
*/
|
|
98
|
-
let basis: AiProvider = {
|
|
148
|
+
let basis: Required<AiProvider> = {
|
|
149
|
+
apiKey: "",
|
|
150
|
+
baseUrl: "",
|
|
151
|
+
model: "",
|
|
152
|
+
provider: "openai-compat",
|
|
153
|
+
};
|
|
99
154
|
|
|
100
155
|
/** Made on the first render, and kept for the life of the controller. */
|
|
101
156
|
let surface: AiCredentialsSurface | null = null;
|
|
@@ -108,13 +163,19 @@ export function createAiCredentialsForm(opts: AiCredentialsFormOptions): AiCrede
|
|
|
108
163
|
* writes a choice nobody made, and `jx.ai.model: "gpt-4o"` is exactly what a broken install was
|
|
109
164
|
* left holding.
|
|
110
165
|
*/
|
|
111
|
-
function stored(): AiProvider {
|
|
112
|
-
return {
|
|
166
|
+
function stored(): Required<AiProvider> {
|
|
167
|
+
return {
|
|
168
|
+
apiKey: getOpenAiKey(),
|
|
169
|
+
baseUrl: getBaseUrl(),
|
|
170
|
+
model: storedModel(),
|
|
171
|
+
provider: getAiProvider(),
|
|
172
|
+
};
|
|
113
173
|
}
|
|
114
174
|
|
|
115
175
|
/** Load the drafts from what is stored, discarding any edit, and take that as the new basis. */
|
|
116
176
|
function loadDrafts() {
|
|
117
177
|
basis = stored();
|
|
178
|
+
providerDraft = basis.provider;
|
|
118
179
|
keyDraft = basis.apiKey;
|
|
119
180
|
baseUrlDraft = basis.baseUrl;
|
|
120
181
|
modelDraft = basis.model;
|
|
@@ -141,6 +202,7 @@ export function createAiCredentialsForm(opts: AiCredentialsFormOptions): AiCrede
|
|
|
141
202
|
*/
|
|
142
203
|
function followStore() {
|
|
143
204
|
const now = stored();
|
|
205
|
+
providerDraft = providerDraft === basis.provider ? now.provider : providerDraft;
|
|
144
206
|
keyDraft = storedEquals(SETTINGS.aiOpenAiKey, keyDraft, basis.apiKey) ? now.apiKey : keyDraft;
|
|
145
207
|
baseUrlDraft = storedEquals(SETTINGS.aiBaseUrl, baseUrlDraft, basis.baseUrl)
|
|
146
208
|
? now.baseUrl
|
|
@@ -173,7 +235,12 @@ export function createAiCredentialsForm(opts: AiCredentialsFormOptions): AiCrede
|
|
|
173
235
|
* no longer `dirty`: Save and Cancel go away, so a second press has nothing to land on.
|
|
174
236
|
*/
|
|
175
237
|
function save() {
|
|
176
|
-
saveAiProvider({
|
|
238
|
+
saveAiProvider({
|
|
239
|
+
apiKey: keyDraft,
|
|
240
|
+
baseUrl: baseUrlDraft,
|
|
241
|
+
model: modelDraft,
|
|
242
|
+
provider: providerDraft,
|
|
243
|
+
});
|
|
177
244
|
loadDrafts();
|
|
178
245
|
modelsError = "";
|
|
179
246
|
/* The fetched list stays: it was listed under exactly these credentials, and dropping it
|
|
@@ -213,6 +280,7 @@ export function createAiCredentialsForm(opts: AiCredentialsFormOptions): AiCrede
|
|
|
213
280
|
credentials: {
|
|
214
281
|
apiKey: keyDraft || getOpenAiKey(),
|
|
215
282
|
baseUrl: baseUrlDraft || getBaseUrl(),
|
|
283
|
+
provider: providerDraft,
|
|
216
284
|
},
|
|
217
285
|
force: true,
|
|
218
286
|
});
|
|
@@ -243,24 +311,58 @@ export function createAiCredentialsForm(opts: AiCredentialsFormOptions): AiCrede
|
|
|
243
311
|
return availableModels.map((model) => ({ label: model.name, value: model.id }));
|
|
244
312
|
}
|
|
245
313
|
|
|
314
|
+
/**
|
|
315
|
+
* The line under the title: the host's, asked about the picker when it is a function, else the
|
|
316
|
+
* default for whether the picker is drawn.
|
|
317
|
+
*
|
|
318
|
+
* @param {boolean} showProvider - Whether the provider picker is drawn
|
|
319
|
+
* @returns {string}
|
|
320
|
+
*/
|
|
321
|
+
function introFor(showProvider: boolean): string {
|
|
322
|
+
const { intro } = opts;
|
|
323
|
+
if (intro === undefined) {
|
|
324
|
+
return showProvider ? PROVIDER_INTRO : DEFAULT_INTRO;
|
|
325
|
+
}
|
|
326
|
+
return isString(intro) ? intro : intro(showProvider);
|
|
327
|
+
}
|
|
328
|
+
|
|
246
329
|
/** What the form says right now — the whole of what the surface is told. */
|
|
247
330
|
function view(): AiCredentialsView {
|
|
331
|
+
/* Drawn where it is a choice, and never offered to a backend that cannot honour it: one that
|
|
332
|
+
lists no other family forwards every key to an OpenAI-compatible endpoint. A family already
|
|
333
|
+
drafted or stored keeps the picker, so a reader can always switch back. */
|
|
334
|
+
const showProvider =
|
|
335
|
+
backendProviders().includes("anthropic") ||
|
|
336
|
+
providerDraft !== "openai-compat" ||
|
|
337
|
+
basis.provider !== "openai-compat";
|
|
338
|
+
const placeholders = PLACEHOLDERS[providerDraft];
|
|
248
339
|
return {
|
|
249
340
|
baseUrlDraft,
|
|
250
341
|
/* Compared as the store would hold each value, so what Save would erase — whitespace, an
|
|
251
342
|
endpoint's trailing slash — is not an edit. See `aiProviderDiffers`. */
|
|
252
|
-
dirty: aiProviderDiffers({
|
|
343
|
+
dirty: aiProviderDiffers({
|
|
344
|
+
apiKey: keyDraft,
|
|
345
|
+
baseUrl: baseUrlDraft,
|
|
346
|
+
model: modelDraft,
|
|
347
|
+
provider: providerDraft,
|
|
348
|
+
}),
|
|
349
|
+
endpointPlaceholder: placeholders.endpoint,
|
|
253
350
|
fetchLabel: modelsLoading
|
|
254
351
|
? "Fetching…"
|
|
255
352
|
: availableModels.length > 0
|
|
256
353
|
? "Refresh models"
|
|
257
354
|
: "Fetch models",
|
|
258
|
-
intro:
|
|
355
|
+
intro: introFor(showProvider),
|
|
259
356
|
keyDraft,
|
|
357
|
+
keyPlaceholder: placeholders.key,
|
|
260
358
|
modelDraft,
|
|
359
|
+
modelPlaceholder: placeholders.model,
|
|
261
360
|
models: rows(),
|
|
262
361
|
modelsError,
|
|
263
362
|
modelsLoading,
|
|
363
|
+
providerDraft,
|
|
364
|
+
providerOptions: PROVIDER_OPTIONS,
|
|
365
|
+
showProvider,
|
|
264
366
|
};
|
|
265
367
|
}
|
|
266
368
|
|
|
@@ -272,6 +374,20 @@ export function createAiCredentialsForm(opts: AiCredentialsFormOptions): AiCrede
|
|
|
272
374
|
* would leave the field holding something the flow does not have. Nothing rewrites a draft today;
|
|
273
375
|
* the echo is what makes a rewrite possible.
|
|
274
376
|
*/
|
|
377
|
+
/**
|
|
378
|
+
* The family the key belongs to. A change drops the listed catalogue, because it was another
|
|
379
|
+
* family's, and the reason the last listing failed with it: Fetch lists the new family's.
|
|
380
|
+
*/
|
|
381
|
+
function setProvider(value: string) {
|
|
382
|
+
const next = familyOf(value);
|
|
383
|
+
if (next !== providerDraft) {
|
|
384
|
+
providerDraft = next;
|
|
385
|
+
availableModels = [];
|
|
386
|
+
modelsError = "";
|
|
387
|
+
}
|
|
388
|
+
surface?.update(view());
|
|
389
|
+
}
|
|
390
|
+
|
|
275
391
|
function setKey(value: string) {
|
|
276
392
|
keyDraft = value;
|
|
277
393
|
surface?.update(view());
|
|
@@ -305,6 +421,7 @@ export function createAiCredentialsForm(opts: AiCredentialsFormOptions): AiCrede
|
|
|
305
421
|
setBaseUrl,
|
|
306
422
|
setKey,
|
|
307
423
|
setModel,
|
|
424
|
+
setProvider,
|
|
308
425
|
});
|
|
309
426
|
}
|
|
310
427
|
return surface.host;
|
package/src/ui/regions.ts
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* inspector/tab:style the Style tab's body
|
|
21
21
|
* inspector/field:href a field row (the Inspector's documents already emit `data-prop`)
|
|
22
22
|
* pane.primary/tabs the primary pane's own tab strip
|
|
23
|
-
* overlay.dialog:
|
|
23
|
+
* overlay.dialog:about a named overlay slot
|
|
24
24
|
* ```
|
|
25
25
|
*
|
|
26
26
|
* **Ids are DERIVED, not authored**, which is the property that makes them survive a rename. The
|
|
@@ -60,7 +60,7 @@ export type RegionSurface = (typeof REGION_SURFACES)[number];
|
|
|
60
60
|
/** A parsed region id. `instance` and `part` are absent for a bare surface. */
|
|
61
61
|
export interface ParsedRegionId {
|
|
62
62
|
surface: RegionSurface;
|
|
63
|
-
/** `primary` in `pane.primary`, `dialog` in `overlay.dialog:
|
|
63
|
+
/** `primary` in `pane.primary`, `dialog` in `overlay.dialog:about`. */
|
|
64
64
|
instance?: string;
|
|
65
65
|
/** Everything after the first `/` — `publish/commit` in `pane.primary/publish/commit`. */
|
|
66
66
|
part?: string;
|
|
@@ -412,9 +412,9 @@ export const OVERLAY_INSTANCE: Readonly<Record<"popover" | "modal" | "dialog" |
|
|
|
412
412
|
/**
|
|
413
413
|
* Region id for an overlay slot.
|
|
414
414
|
*
|
|
415
|
-
* With an `id` this is `overlay.dialog:
|
|
416
|
-
*
|
|
417
|
-
*
|
|
415
|
+
* With an `id` this is `overlay.dialog:about` — derived from the key `getLayerSlot` already builds,
|
|
416
|
+
* so naming a slot names its region. Without one it is the bare instance, which resolves to the
|
|
417
|
+
* topmost open overlay of that kind.
|
|
418
418
|
*/
|
|
419
419
|
export function overlayRegion(
|
|
420
420
|
layer: "popover" | "modal" | "dialog" | "toast",
|
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
* written in Content even when it is opened from somewhere else.
|
|
12
12
|
* - **Media**: the media home and every media file's viewer.
|
|
13
13
|
* - **Styles**: `project.json`, in any of its three views: Elements (`stylebook`), Variables
|
|
14
|
-
* (`tokens`) and Code (`source`).
|
|
15
|
-
* a tab, so it belongs to none.
|
|
14
|
+
* (`tokens`) and Code (`source`).
|
|
16
15
|
* - **Publish**: the publish home and every comparison (`git-diff`).
|
|
16
|
+
* - **Settings**: its home alone, which hosts the settings editor (§17.1, §22.9). It edits
|
|
17
|
+
* `project.json` through the chokepoint without being a tab over it.
|
|
17
18
|
* - **Design**: everything else: pages, templates, components and every other file.
|
|
18
19
|
*
|
|
19
20
|
* The rules read the tab's CURRENT editor, so switching an entry from its form to the canvas keeps
|
|
@@ -3,16 +3,17 @@
|
|
|
3
3
|
* Workspace homes — the virtual tab a workspace opens on when none of its own tabs is open.
|
|
4
4
|
*
|
|
5
5
|
* A home is a tab like a grid is a tab: it sits in a pane's strip, it can be closed, and it has a
|
|
6
|
-
* canvas mode of its own (`content-home`, `media-home`, `publish-home`) that
|
|
6
|
+
* canvas mode of its own (`content-home`, `media-home`, `publish-home`, `settings-home`) that
|
|
7
7
|
* `canvas/canvas-render.ts` routes here. It has no document — the tab carries a stub, exactly as
|
|
8
8
|
* the grid tabs do — and its id is in the `workspace://` space `workspace/workspace-ids.ts`
|
|
9
9
|
* declares, so it can never collide with a file.
|
|
10
10
|
*
|
|
11
11
|
* What a home DRAWS is its workspace's own surface: the Content home is the collection it shows
|
|
12
12
|
* (`browse/library-pane.ts`, studio.md §22.5), the Media home is the media library
|
|
13
|
-
* (`media/media-library.ts`, §22.6)
|
|
14
|
-
* (`publish/publish-workspace.ts`, §22.8)
|
|
15
|
-
*
|
|
13
|
+
* (`media/media-library.ts`, §22.6), the Publish home is the Publish workspace
|
|
14
|
+
* (`publish/publish-workspace.ts`, §22.8) and the Settings home is the settings editor
|
|
15
|
+
* (`settings/settings-workspace.ts`, §22.9). The mechanism (the tab, the mode, the routing and the
|
|
16
|
+
* per-pane mount) is the same for all four, and this module only routes.
|
|
16
17
|
*
|
|
17
18
|
* @docs studio/interface
|
|
18
19
|
*/
|
|
@@ -30,6 +31,11 @@ import {
|
|
|
30
31
|
publishWorkspaceMounted,
|
|
31
32
|
renderPublishWorkspace,
|
|
32
33
|
} from "../publish/publish-workspace";
|
|
34
|
+
import {
|
|
35
|
+
detachSettingsWorkspace,
|
|
36
|
+
renderSettingsWorkspace,
|
|
37
|
+
settingsWorkspaceMounted,
|
|
38
|
+
} from "../settings/settings-workspace";
|
|
33
39
|
import type { HomeWorkspaceId } from "./workspace-ids";
|
|
34
40
|
import type { Tab } from "../tabs/tab";
|
|
35
41
|
import type { CanvasSurface } from "../canvas/canvas-surface";
|
|
@@ -98,6 +104,9 @@ export function workspaceHomeMounted(paneId: string, tab: Tab): boolean {
|
|
|
98
104
|
if (id === "content") {
|
|
99
105
|
return libraryPaneMounted(paneId, tab);
|
|
100
106
|
}
|
|
107
|
+
if (id === "settings") {
|
|
108
|
+
return settingsWorkspaceMounted(paneId, tab);
|
|
109
|
+
}
|
|
101
110
|
return publishWorkspaceMounted(paneId, tab);
|
|
102
111
|
}
|
|
103
112
|
|
|
@@ -106,6 +115,7 @@ export function detachWorkspaceHome(paneId: string): void {
|
|
|
106
115
|
detachMediaLibrary(paneId);
|
|
107
116
|
detachLibraryPane(paneId);
|
|
108
117
|
detachPublishWorkspace(paneId);
|
|
118
|
+
detachSettingsWorkspace(paneId);
|
|
109
119
|
}
|
|
110
120
|
|
|
111
121
|
/**
|
|
@@ -129,5 +139,9 @@ export function renderWorkspaceHome(surface: CanvasSurface, tab: Tab): void {
|
|
|
129
139
|
renderLibraryMode(surface, tab);
|
|
130
140
|
return;
|
|
131
141
|
}
|
|
142
|
+
if (id === "settings") {
|
|
143
|
+
renderSettingsWorkspace(surface, tab);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
132
146
|
renderPublishWorkspace(surface, tab);
|
|
133
147
|
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
2
|
+
* The six workspaces, declared where a bare Bun process can read them.
|
|
3
3
|
*
|
|
4
4
|
* A workspace is the answer to "what am I doing right now": designing pages, writing content,
|
|
5
|
-
* managing media, editing the project's styles, or
|
|
6
|
-
*
|
|
5
|
+
* managing media, editing the project's styles, publishing, or configuring the project. Studio's
|
|
6
|
+
* top bar switches between the first five, and every open tab belongs to exactly one
|
|
7
|
+
* (`workspace/tab-workspace.ts` derives which). Project Settings is the sixth: a workspace like the
|
|
8
|
+
* others (a frame, a home tab, a place `view.setWorkspace` can name) that the top bar does not draw
|
|
9
|
+
* a tab for, because it is visited for a minute from a "Project Settings" link (the gear menu, ⌘⇧,
|
|
10
|
+
* and every deep link `settings.open` serves) rather than worked in (studio.md §17.1, §22.9).
|
|
7
11
|
*
|
|
8
12
|
* The ids live in a module with no DOM and no state for the reason `NAVIGATOR_PANEL_IDS` does:
|
|
9
13
|
* `view.setWorkspace`'s `args` enum is built from {@link WORKSPACE_IDS}, and
|
|
@@ -19,8 +23,15 @@
|
|
|
19
23
|
* @docs studio/interface
|
|
20
24
|
*/
|
|
21
25
|
|
|
22
|
-
/** The workspaces, in top-bar order. ⌘1 to ⌘5 follow
|
|
23
|
-
export const WORKSPACE_IDS = [
|
|
26
|
+
/** The workspaces, in top-bar order, Settings last. ⌘1 to ⌘5 follow the switcher's five. */
|
|
27
|
+
export const WORKSPACE_IDS = [
|
|
28
|
+
"design",
|
|
29
|
+
"content",
|
|
30
|
+
"media",
|
|
31
|
+
"styles",
|
|
32
|
+
"publish",
|
|
33
|
+
"settings",
|
|
34
|
+
] as const;
|
|
24
35
|
|
|
25
36
|
export type WorkspaceId = (typeof WORKSPACE_IDS)[number];
|
|
26
37
|
|
|
@@ -34,6 +45,11 @@ export interface WorkspaceInfo {
|
|
|
34
45
|
title: string;
|
|
35
46
|
/** A key into the kit's glyph manifest (`@jxsuite/ui/icons`), checked by this module's test. */
|
|
36
47
|
icon: string;
|
|
48
|
+
/**
|
|
49
|
+
* Whether the top bar draws a tab for it, and so whether it has a ⌘-digit chord and a "Go to"
|
|
50
|
+
* palette row. False for Settings alone, which its own links and `settings.open` reach.
|
|
51
|
+
*/
|
|
52
|
+
switcher: boolean;
|
|
37
53
|
}
|
|
38
54
|
|
|
39
55
|
/** Every workspace, in {@link WORKSPACE_IDS} order. */
|
|
@@ -41,30 +57,46 @@ export const WORKSPACES: readonly WorkspaceInfo[] = [
|
|
|
41
57
|
{
|
|
42
58
|
icon: "frame-corners",
|
|
43
59
|
id: "design",
|
|
60
|
+
switcher: true,
|
|
44
61
|
title: "Design",
|
|
45
62
|
},
|
|
46
63
|
{
|
|
47
64
|
icon: "file-text",
|
|
48
65
|
id: "content",
|
|
66
|
+
switcher: true,
|
|
49
67
|
title: "Content",
|
|
50
68
|
},
|
|
51
69
|
{
|
|
52
70
|
icon: "image",
|
|
53
71
|
id: "media",
|
|
72
|
+
switcher: true,
|
|
54
73
|
title: "Media",
|
|
55
74
|
},
|
|
56
75
|
{
|
|
57
76
|
icon: "paint-brush",
|
|
58
77
|
id: "styles",
|
|
78
|
+
switcher: true,
|
|
59
79
|
title: "Styles",
|
|
60
80
|
},
|
|
61
81
|
{
|
|
62
82
|
icon: "paper-plane-tilt",
|
|
63
83
|
id: "publish",
|
|
84
|
+
switcher: true,
|
|
64
85
|
title: "Publish",
|
|
65
86
|
},
|
|
87
|
+
{
|
|
88
|
+
icon: "gear-six",
|
|
89
|
+
id: "settings",
|
|
90
|
+
switcher: false,
|
|
91
|
+
title: "Project settings",
|
|
92
|
+
},
|
|
66
93
|
];
|
|
67
94
|
|
|
95
|
+
/** The workspaces the top bar draws a tab for, in order: every one but Settings. */
|
|
96
|
+
export const SWITCHER_WORKSPACES: readonly WorkspaceInfo[] = WORKSPACES.filter(
|
|
97
|
+
(entry) => entry.switcher,
|
|
98
|
+
);
|
|
99
|
+
|
|
68
100
|
/** Whether a value is a declared workspace id. A stored value is untrusted input. */
|
|
69
101
|
export function isWorkspaceId(value: unknown): value is WorkspaceId {
|
|
70
102
|
return typeof value === "string" && WORKSPACE_IDS.some((id) => id === value);
|
|
@@ -90,14 +122,15 @@ const HOME_SCHEME = "workspace://";
|
|
|
90
122
|
* that tab draws under.
|
|
91
123
|
*
|
|
92
124
|
* Design has no home (an empty stage is its home), and Styles' is `project.json` in its stylebook
|
|
93
|
-
* editor, opened by the module that owns that document. These
|
|
125
|
+
* editor, opened by the module that owns that document. These four are the generic mechanism's:
|
|
94
126
|
* Content's home is the collection it shows (`browse/library-pane.ts`), Media's the media library,
|
|
95
|
-
*
|
|
127
|
+
* Publish's its own surface, and Settings' the settings editor (`settings/settings-workspace.ts`).
|
|
96
128
|
*/
|
|
97
129
|
export const WORKSPACE_HOME_MODES = {
|
|
98
130
|
content: "content-home",
|
|
99
131
|
media: "media-home",
|
|
100
132
|
publish: "publish-home",
|
|
133
|
+
settings: "settings-home",
|
|
101
134
|
} as const satisfies Partial<Record<WorkspaceId, string>>;
|
|
102
135
|
|
|
103
136
|
/** A workspace whose home is a generic home tab. */
|