@jxsuite/studio 10.1.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-mkb0qa6p.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-f92ey3kp.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-r22jsqht.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-pxwwf00y.js → index-vr006kbt.js} +3 -3
- package/dist/chunks/{javascript-pzqryby6.js → javascript-z1g3s9fd.js} +2 -2
- package/dist/chunks/{jsonMode-tcnyxyap.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-bqpge64f.js → monaco-binding-v42w9c1v.js} +3 -3
- package/dist/chunks/monaco-buffer-een2ea0v.js +4 -0
- package/dist/chunks/{monaco-setup-1mkab762.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-m9vkhahq.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-qyb5bebs.js → studio-0q5jwddh.js} +7 -7
- package/dist/chunks/{studio-cvz0e00f.js → studio-0w2dr0q8.js} +4 -4
- package/dist/chunks/{studio-eesyjg7h.js → studio-1tzpds97.js} +5 -5
- package/dist/chunks/{studio-a3s6frde.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-dy2xtt7a.js → studio-399a25za.js} +4 -4
- package/dist/chunks/studio-399xknkb.js +6 -0
- package/dist/chunks/{studio-jh6t5qz4.js → studio-43y04qck.js} +4 -4
- package/dist/chunks/{studio-skynbac3.js → studio-4g8yb6jn.js} +2 -2
- package/dist/chunks/{studio-jw0q5j6k.js → studio-4jhw38b2.js} +4 -4
- package/dist/chunks/{studio-4smcnv33.js → studio-4ncmwqw5.js} +4 -4
- package/dist/chunks/studio-5atwjgkj.js +7 -0
- package/dist/chunks/{studio-vcz63n42.js → studio-5frqk7c0.js} +4 -4
- package/dist/chunks/{studio-hsr3k06v.js → studio-646nn2ys.js} +5 -5
- package/dist/chunks/{studio-ch8gt8vt.js → studio-6cwj6xrx.js} +4 -4
- package/dist/chunks/studio-6hzdjfv0.js +5 -0
- package/dist/chunks/{studio-9cp96v90.js → studio-71m92v9s.js} +7 -7
- package/dist/chunks/{studio-brdp85q7.js → studio-75kcdbsk.js} +4 -4
- package/dist/chunks/{studio-6vwvshw8.js → studio-78pywnp2.js} +5 -5
- package/dist/chunks/{studio-ggm7zf70.js → studio-86r8hp7r.js} +5 -5
- package/dist/chunks/studio-886t6ztg.js +5 -0
- package/dist/chunks/{studio-5dmgmyw4.js → studio-8wnvtr4z.js} +4 -4
- package/dist/chunks/studio-8z66jk4z.js +5 -0
- package/dist/chunks/{studio-x2metr13.js → studio-9rbe2xpw.js} +3 -3
- package/dist/chunks/{studio-8qejv8yf.js → studio-awxwjawq.js} +4 -4
- package/dist/chunks/studio-b2qpt0xy.js +14 -0
- package/dist/chunks/{studio-9rcbvksg.js → studio-bvs9kpst.js} +7 -7
- package/dist/chunks/studio-c71806xz.js +7 -0
- package/dist/chunks/{studio-vjhej0w5.js → studio-cbwks69x.js} +140 -140
- 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-4nkm4404.js → studio-ea74s8c1.js} +4 -4
- package/dist/chunks/{studio-37twdaf7.js → studio-efdxjwyp.js} +34 -34
- package/dist/chunks/{studio-pmzg17fj.js → studio-egmw2837.js} +4 -4
- package/dist/chunks/{studio-8jb31dcf.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-t3j8htp7.js → studio-gj98m8gy.js} +5 -5
- package/dist/chunks/{studio-cgb97rwv.js → studio-hasbgjah.js} +4 -4
- package/dist/chunks/studio-hhp4x8xf.js +6 -0
- package/dist/chunks/{studio-848rk4p1.js → studio-hz5n7x27.js} +4 -4
- package/dist/chunks/{studio-0g2sm01j.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-7vb997ff.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-07k0geap.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-5e7p1vgd.js → studio-r4bcdwhq.js} +4 -4
- package/dist/chunks/{studio-mwnmzx7g.js → studio-r7pf0rkv.js} +6 -6
- package/dist/chunks/studio-rtfnhrdg.js +5 -0
- package/dist/chunks/{studio-zacvfm31.js → studio-rzqaz21g.js} +4 -4
- package/dist/chunks/studio-s5tzw84t.js +5 -0
- package/dist/chunks/{studio-nf0pqpbj.js → studio-sevbc3xv.js} +4 -4
- package/dist/chunks/{studio-va0e63sk.js → studio-sxdb9h3q.js} +6 -6
- package/dist/chunks/studio-t51y8v1d.js +5 -0
- package/dist/chunks/{studio-pd9dsqpq.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-7823f0g8.js → studio-v1yascv2.js} +4 -4
- package/dist/chunks/{studio-tpy3rdxn.js → studio-wc6za3zs.js} +4 -4
- package/dist/chunks/{studio-5m0w815d.js → studio-wxq6v5sv.js} +4 -4
- package/dist/chunks/{studio-ahf3cspq.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-6vwhy6ty.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-zcxkfgmf.js → ws-client-e8kj684q.js} +3 -3
- package/dist/manifest.json +76 -75
- package/dist/studio.js +81 -82
- 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/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/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-xcpsmecx.js +0 -4
- package/dist/chunks/add-new-flow-ptmhmzr9.js +0 -4
- package/dist/chunks/cf-account-picker-z1sgwpwd.js +0 -4
- package/dist/chunks/cf-settings-xqvhxxe9.js +0 -4
- package/dist/chunks/components-whsc81a7.js +0 -4
- package/dist/chunks/content-source-jwtxg78w.js +0 -4
- package/dist/chunks/editor-zdqdpq1x.js +0 -4
- package/dist/chunks/entry-commands-w8p2fbpt.js +0 -4
- package/dist/chunks/extension-sections-y2p1te2r.js +0 -4
- package/dist/chunks/file-ops-13ab4swj.js +0 -4
- package/dist/chunks/files-zbc8p6v1.js +0 -4
- package/dist/chunks/format-host-zjsbktbx.js +0 -4
- package/dist/chunks/fs-events-t6x0hch6.js +0 -4
- package/dist/chunks/grid-open-fyzdwpkh.js +0 -4
- package/dist/chunks/iframe-host-kbyqc7pt.js +0 -4
- package/dist/chunks/index-6nvj89vb.js +0 -4
- package/dist/chunks/jx-validate-5sp8a4rm.js +0 -4
- package/dist/chunks/layers-panel-qrxj9vr2.js +0 -4
- package/dist/chunks/layers-tpzayggx.js +0 -4
- package/dist/chunks/lit-html-x6nrxsx3.js +0 -4
- package/dist/chunks/media-library-ywppnjrz.js +0 -4
- package/dist/chunks/media-picker-etxgc8qq.js +0 -4
- package/dist/chunks/monaco-buffer-qrnqhk0b.js +0 -4
- package/dist/chunks/notify-kcv38wwe.js +0 -4
- package/dist/chunks/parse-df2kkmhc.js +0 -4
- package/dist/chunks/platform-dtsysmyd.js +0 -4
- package/dist/chunks/publish-panel-evb1rcxt.js +0 -4
- package/dist/chunks/publish-workspace-ktk9h536.js +0 -4
- package/dist/chunks/quick-search-2f47fxcw.js +0 -4
- package/dist/chunks/references-5w6ajjv7.js +0 -4
- package/dist/chunks/right-panel-kz0a6byf.js +0 -4
- package/dist/chunks/source-control-1phx90zk.js +0 -4
- package/dist/chunks/studio-0hr000ba.js +0 -5
- package/dist/chunks/studio-1wyp7dt5.js +0 -5
- package/dist/chunks/studio-20ap8dds.js +0 -5
- package/dist/chunks/studio-291gqh98.js +0 -5
- package/dist/chunks/studio-4zqfw5ha.js +0 -5
- package/dist/chunks/studio-56egy6vf.js +0 -5
- package/dist/chunks/studio-695k95bw.js +0 -5
- package/dist/chunks/studio-732mw0g8.js +0 -5
- package/dist/chunks/studio-76t560kn.js +0 -6
- package/dist/chunks/studio-8486jn4r.js +0 -5
- package/dist/chunks/studio-8x8616xd.js +0 -5
- package/dist/chunks/studio-bkmv1h3e.js +0 -9
- package/dist/chunks/studio-cqf1k4qk.js +0 -5
- package/dist/chunks/studio-dbgv4fv9.js +0 -16
- package/dist/chunks/studio-et6t8g4m.js +0 -4
- package/dist/chunks/studio-f2j9et3h.js +0 -14
- package/dist/chunks/studio-fep7hr66.js +0 -7
- package/dist/chunks/studio-fyf7j57j.js +0 -5
- package/dist/chunks/studio-g5prntab.js +0 -5
- package/dist/chunks/studio-ga77mf07.js +0 -5
- package/dist/chunks/studio-h7gy12h2.js +0 -5
- package/dist/chunks/studio-j933k8nr.js +0 -5
- package/dist/chunks/studio-jgbvqcgh.js +0 -5
- package/dist/chunks/studio-k4xc5g6h.js +0 -5
- package/dist/chunks/studio-p0nf2e1f.js +0 -6
- package/dist/chunks/studio-pzfa2vmb.js +0 -5
- package/dist/chunks/studio-rf8d6p9f.js +0 -7
- package/dist/chunks/studio-rt498t2v.js +0 -5
- package/dist/chunks/studio-sd07xva5.js +0 -6
- package/dist/chunks/studio-thj81e98.js +0 -7
- package/dist/chunks/studio-tyw7tk0x.js +0 -5
- package/dist/chunks/studio-vffwe9k7.js +0 -5
- package/dist/chunks/studio-vxjap704.js +0 -5
- package/dist/chunks/studio-w67vfgy5.js +0 -5
- package/dist/chunks/studio-xebavr9s.js +0 -5
- package/dist/chunks/studio-yg5n49za.js +0 -5
- package/dist/chunks/studio-yhz5b313.js +0 -5
- package/dist/chunks/studio-zsjpsw08.js +0 -5
- package/dist/chunks/studio-zxm98z2m.js +0 -7
- package/dist/chunks/workspace-d65bz1qk.js +0 -4
- package/dist/chunks/workspace-home-vbg9b3jh.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
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* @license MIT
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
+
import type { AiProviderFamily } from "@jxsuite/ai/streaming-client";
|
|
22
23
|
import { SETTINGS } from "./settings/definitions";
|
|
23
24
|
import type { SettingDefinition } from "./settings/definitions";
|
|
24
25
|
import {
|
|
@@ -58,6 +59,29 @@ export function getBaseUrl() {
|
|
|
58
59
|
return readStoredSetting(SETTINGS.aiBaseUrl);
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
/**
|
|
63
|
+
* The wire the stored key belongs to (specs/ai.md §2.6): `"anthropic"` for a Claude key, and the
|
|
64
|
+
* OpenAI-compatible default for anything else, including nothing stored.
|
|
65
|
+
*
|
|
66
|
+
* @returns {AiProviderFamily}
|
|
67
|
+
*/
|
|
68
|
+
export function getAiProvider(): AiProviderFamily {
|
|
69
|
+
return readStoredSetting(SETTINGS.aiProvider) === "anthropic" ? "anthropic" : "openai-compat";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The stored key when it is an OpenAI-compatible one, and `""` when it belongs to another family.
|
|
74
|
+
*
|
|
75
|
+
* For the paths that only speak the OpenAI-compatible wire, such as the site importer's component
|
|
76
|
+
* naming: handing them a Claude key would send it to an OpenAI endpoint, which is the one thing a
|
|
77
|
+
* key must never do.
|
|
78
|
+
*
|
|
79
|
+
* @returns {string}
|
|
80
|
+
*/
|
|
81
|
+
export function getOpenAiCompatKey(): string {
|
|
82
|
+
return getAiProvider() === "openai-compat" ? getOpenAiKey() : "";
|
|
83
|
+
}
|
|
84
|
+
|
|
61
85
|
// ─── Model selection ────────────────────────────────────────────────────────
|
|
62
86
|
|
|
63
87
|
/**
|
|
@@ -84,11 +108,25 @@ export function setModel(modelId: string) {
|
|
|
84
108
|
setSetting(SETTINGS.aiModel, modelId || "");
|
|
85
109
|
}
|
|
86
110
|
|
|
87
|
-
/**
|
|
111
|
+
/**
|
|
112
|
+
* The provider's values together: what a credentials form drafts and what Save stores. `provider`
|
|
113
|
+
* absent is the OpenAI-compatible default, which is what every caller before it meant.
|
|
114
|
+
*/
|
|
88
115
|
export interface AiProvider {
|
|
89
116
|
apiKey: string;
|
|
90
117
|
baseUrl: string;
|
|
91
118
|
model: string;
|
|
119
|
+
provider?: AiProviderFamily;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* A family as the store holds it: `"anthropic"`, or blank for the default.
|
|
124
|
+
*
|
|
125
|
+
* @param {AiProviderFamily | undefined} family
|
|
126
|
+
* @returns {string}
|
|
127
|
+
*/
|
|
128
|
+
function storedFamily(family: AiProviderFamily | undefined): string {
|
|
129
|
+
return family === "anthropic" ? "anthropic" : "";
|
|
92
130
|
}
|
|
93
131
|
|
|
94
132
|
/**
|
|
@@ -105,6 +143,7 @@ export function saveAiProvider(provider: AiProvider) {
|
|
|
105
143
|
[SETTINGS.aiOpenAiKey, provider.apiKey],
|
|
106
144
|
[SETTINGS.aiBaseUrl, provider.baseUrl],
|
|
107
145
|
[SETTINGS.aiModel, provider.model],
|
|
146
|
+
[SETTINGS.aiProvider, storedFamily(provider.provider)],
|
|
108
147
|
]);
|
|
109
148
|
}
|
|
110
149
|
|
|
@@ -127,13 +166,14 @@ export function saveAiProvider(provider: AiProvider) {
|
|
|
127
166
|
* sentence above always claimed to be making.
|
|
128
167
|
*
|
|
129
168
|
* @param {AiProvider} provider The drafts.
|
|
130
|
-
* @returns {boolean} `true` when at least one of the
|
|
169
|
+
* @returns {boolean} `true` when at least one of the values would move.
|
|
131
170
|
*/
|
|
132
171
|
export function aiProviderDiffers(provider: AiProvider): boolean {
|
|
133
172
|
return (
|
|
134
173
|
!storedEquals(SETTINGS.aiOpenAiKey, provider.apiKey, getOpenAiKey()) ||
|
|
135
174
|
!storedEquals(SETTINGS.aiBaseUrl, provider.baseUrl, getBaseUrl()) ||
|
|
136
|
-
!storedEquals(SETTINGS.aiModel, provider.model, storedModel())
|
|
175
|
+
!storedEquals(SETTINGS.aiModel, provider.model, storedModel()) ||
|
|
176
|
+
storedFamily(provider.provider) !== storedFamily(getAiProvider())
|
|
137
177
|
);
|
|
138
178
|
}
|
|
139
179
|
|
|
@@ -153,11 +193,11 @@ export function storedEquals(
|
|
|
153
193
|
}
|
|
154
194
|
|
|
155
195
|
/**
|
|
156
|
-
* Forget the provider entirely — key, endpoint and
|
|
196
|
+
* Forget the provider entirely — key, endpoint, model and family.
|
|
157
197
|
*
|
|
158
198
|
* The one deletion path, and the one the Accounts list's Disconnect uses. Deliberately all three
|
|
159
199
|
* together: a key with an orphaned endpoint is a state no surface knows how to describe.
|
|
160
200
|
*/
|
|
161
201
|
export function clearAiProvider() {
|
|
162
|
-
clearSettings([SETTINGS.aiOpenAiKey, SETTINGS.aiBaseUrl, SETTINGS.aiModel]);
|
|
202
|
+
clearSettings([SETTINGS.aiOpenAiKey, SETTINGS.aiBaseUrl, SETTINGS.aiModel, SETTINGS.aiProvider]);
|
|
163
203
|
}
|
|
@@ -34,7 +34,7 @@ import { abortImportRun, resetImportRuns } from "./import-run";
|
|
|
34
34
|
import { runAgentLoop } from "./tool-executor";
|
|
35
35
|
import { buildSystemPrompt } from "./ai-system-prompt";
|
|
36
36
|
import type { ExtensionCatalogSummary } from "./ai-system-prompt";
|
|
37
|
-
import { getBaseUrl, getOpenAiKey } from "./ai-settings";
|
|
37
|
+
import { getAiProvider, getBaseUrl, getOpenAiKey } from "./ai-settings";
|
|
38
38
|
import { preferredModel } from "./ai-models";
|
|
39
39
|
import { pruneOrphanToolMessages, trimContext } from "./context-manager";
|
|
40
40
|
import { renderCheck } from "./render-critic";
|
|
@@ -329,8 +329,11 @@ export function createDocumentAssistant() {
|
|
|
329
329
|
model: chatState.model,
|
|
330
330
|
// Sent as X-Api-Key; the proxy falls back to the server's OPENAI_API_KEY when empty.
|
|
331
331
|
apiKey: getOpenAiKey() || undefined,
|
|
332
|
-
// Optional
|
|
332
|
+
// Optional endpoint override; empty uses the proxy default for the family.
|
|
333
333
|
baseUrl: getBaseUrl() || undefined,
|
|
334
|
+
/* The family the key belongs to (specs/ai.md §2.6), named only when it is not the default,
|
|
335
|
+
so an OpenAI-compatible send is the one it always was. */
|
|
336
|
+
provider: getAiProvider() === "anthropic" ? "anthropic" : undefined,
|
|
334
337
|
/* The persisted conversation, so the backend can keep its rounds on one warm prompt cache.
|
|
335
338
|
Sent to the backend only, which hashes it before any provider sees it (specs/ai.md §2.5). */
|
|
336
339
|
sessionId,
|
|
@@ -60,6 +60,15 @@ function normalizeBaseUrl(raw: string): string {
|
|
|
60
60
|
return raw.trim().replace(/\/+$/, "");
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* The provider family, stored only when it is not the default: `"anthropic"`, or `""` for the
|
|
65
|
+
* OpenAI-compatible wire every install had before the setting existed. Anything else reads as the
|
|
66
|
+
* default, so a hand-edited value can never send a key to a family nobody chose.
|
|
67
|
+
*/
|
|
68
|
+
function normalizeProvider(raw: string): string {
|
|
69
|
+
return raw.trim().toLowerCase() === "anthropic" ? "anthropic" : "";
|
|
70
|
+
}
|
|
71
|
+
|
|
63
72
|
/** Trim. Credentials are pasted, and a paste carries whitespace. */
|
|
64
73
|
function trimmed(raw: string): string {
|
|
65
74
|
return raw.trim();
|
|
@@ -91,6 +100,16 @@ export const SETTINGS = {
|
|
|
91
100
|
scope: "user",
|
|
92
101
|
secret: true,
|
|
93
102
|
},
|
|
103
|
+
/**
|
|
104
|
+
* Which wire the key belongs to (specs/ai.md §2.6). Blank is the OpenAI-compatible default, so an
|
|
105
|
+
* install that predates the setting keeps the provider it already had.
|
|
106
|
+
*/
|
|
107
|
+
aiProvider: {
|
|
108
|
+
default: "",
|
|
109
|
+
key: "jx.ai.provider",
|
|
110
|
+
normalize: normalizeProvider,
|
|
111
|
+
scope: "user",
|
|
112
|
+
},
|
|
94
113
|
/**
|
|
95
114
|
* `"gpt-4o"` is what a sender falls back to, NOT a stored choice. A surface that writes back must
|
|
96
115
|
* read through `readStoredSetting`, or it persists a model the user never picked.
|
|
@@ -49,7 +49,8 @@ import { resolveContextPointer } from "../services/context-resolver";
|
|
|
49
49
|
import { deriveSecretEnvName } from "../services/data-service";
|
|
50
50
|
import { validateProjectConfig } from "../services/jx-validate";
|
|
51
51
|
import { notify } from "../services/notify";
|
|
52
|
-
import {
|
|
52
|
+
import { REGION_ATTR } from "../ui/regions";
|
|
53
|
+
import { settingsRegion } from "./settings-workspace";
|
|
53
54
|
import { mountContributedSurface } from "../surfaces/settings-contributed";
|
|
54
55
|
|
|
55
56
|
import type { JsonSchema, SchemaFormContext } from "../ui/schema-form";
|
|
@@ -371,15 +372,23 @@ function sectionValue(key: string): JsonObject | null {
|
|
|
371
372
|
// ─── Per-container mount ──────────────────────────────────────────────────────
|
|
372
373
|
|
|
373
374
|
/**
|
|
374
|
-
* A region under the
|
|
375
|
-
* entry rows and its entry editor are named under `
|
|
375
|
+
* A region under the Settings home, which is where a contributed section is drawn (§17.1): its
|
|
376
|
+
* entry rows and its entry editor are named under `pane.<id>/settings`, so a shot can crop one
|
|
376
377
|
* without a selector.
|
|
377
378
|
*
|
|
379
|
+
* The home is per pane, and a renderer is handed a container and nothing else, so the base is READ
|
|
380
|
+
* off the nearest region around the container, which is the home's own: the id is derived from what
|
|
381
|
+
* is on screen, as every region is (`ui/regions.ts`). A container not yet in a home names the
|
|
382
|
+
* primary pane's.
|
|
383
|
+
*
|
|
384
|
+
* @param {HTMLElement} container The element the section was rendered into
|
|
378
385
|
* @param {string} part
|
|
379
386
|
* @returns {string}
|
|
380
387
|
*/
|
|
381
|
-
function sectionRegion(part: string): string {
|
|
382
|
-
|
|
388
|
+
function sectionRegion(container: HTMLElement, part: string): string {
|
|
389
|
+
const base =
|
|
390
|
+
container.closest(`[${REGION_ATTR}]`)?.getAttribute(REGION_ATTR) ?? settingsRegion("primary");
|
|
391
|
+
return `${base}/${part}`;
|
|
383
392
|
}
|
|
384
393
|
|
|
385
394
|
/** One mounted section: its document, its two island hosts, and what it was last asked to draw. */
|
|
@@ -455,7 +464,7 @@ function project(record: SectionMount): ContributedView {
|
|
|
455
464
|
const sectionKey = contribution.key;
|
|
456
465
|
const title = contribution.title ?? sectionKey;
|
|
457
466
|
const actionsState = opts.actions ? "slot" : "none";
|
|
458
|
-
const editorRegion = sectionRegion("editor");
|
|
467
|
+
const editorRegion = sectionRegion(record.container, "editor");
|
|
459
468
|
const words = entryWords(contribution.settings.entry?.noun);
|
|
460
469
|
|
|
461
470
|
if ((contribution.settings.layout ?? "form") !== "map") {
|
|
@@ -483,7 +492,7 @@ function project(record: SectionMount): ContributedView {
|
|
|
483
492
|
editorState: editing ? "editing" : "empty",
|
|
484
493
|
entries: Object.keys(entries).map((name) => ({
|
|
485
494
|
key: name,
|
|
486
|
-
region: sectionRegion(`entry:${name}`),
|
|
495
|
+
region: sectionRegion(record.container, `entry:${name}`),
|
|
487
496
|
selected: name === selected,
|
|
488
497
|
})),
|
|
489
498
|
entryName: record.echo ?? (editing ? selected : ""),
|
|
@@ -386,9 +386,9 @@ function addOverride(container: HTMLElement, token: string, ctx: string): void {
|
|
|
386
386
|
* per token are defined.
|
|
387
387
|
*
|
|
388
388
|
* Through the command rather than by writing the registry's section: the editor is hosted outside
|
|
389
|
-
* Settings (the Styles workspace's Variables view), so selecting a section of a
|
|
390
|
-
*
|
|
391
|
-
* the editor lives in, as the Variables view's own button files it.
|
|
389
|
+
* Settings (the Styles workspace's Variables view), so selecting a section of a workspace that is
|
|
390
|
+
* not on screen would change nothing the reader can see. A refusal is filed under "Styles", the
|
|
391
|
+
* workspace the editor lives in, as the Variables view's own button files it.
|
|
392
392
|
*/
|
|
393
393
|
function openContextsSection(): void {
|
|
394
394
|
void runActiveReported("settings.open", { section: "contexts" }, "Styles");
|
|
@@ -418,7 +418,7 @@ function actions(container: HTMLElement): CssVarsActions {
|
|
|
418
418
|
* appended `'--dark': '(prefers-color-scheme: dark)'` to `$media` without ever using the word
|
|
419
419
|
* breakpoint, which made this form the fourth and least discoverable definition site for a
|
|
420
420
|
* map whose other three lived in the wizard, Overview and Properties › Media. It goes there
|
|
421
|
-
* through `settings.open`, so
|
|
421
|
+
* through `settings.open`, so Settings opens on Contexts however this editor is hosted.
|
|
422
422
|
*/
|
|
423
423
|
manageContexts: () => {
|
|
424
424
|
openContextsSection();
|
|
@@ -22,7 +22,6 @@ import { projectState } from "../store";
|
|
|
22
22
|
import { updateSiteConfig } from "../site-context";
|
|
23
23
|
import { activeRegistry } from "../commands/active-registry";
|
|
24
24
|
import { runActiveReported } from "../commands/run-reported";
|
|
25
|
-
import { closeSettingsDialog } from "./settings-dialog";
|
|
26
25
|
import { getPlatform } from "../platform";
|
|
27
26
|
import { mountOverviewSurface } from "../surfaces/settings-overview";
|
|
28
27
|
import { previewAssetSrcFor } from "../canvas/asset-refs";
|
|
@@ -265,11 +264,9 @@ function actions(container: HTMLElement): OverviewActions {
|
|
|
265
264
|
* says how to get there. This used to write `session.ui.canvasMode` here directly — a second
|
|
266
265
|
* implementation of a capability, `studio-ui-guidelines.md` §12.5's defect in miniature: the
|
|
267
266
|
* button and the command could disagree about what "open Project Styles" means, and only one of
|
|
268
|
-
* them was reachable by name.
|
|
269
|
-
* first: left up, it would cover the workspace the reader just asked for.
|
|
267
|
+
* them was reachable by name.
|
|
270
268
|
*/
|
|
271
269
|
openStyles: () => {
|
|
272
|
-
closeSettingsDialog();
|
|
273
270
|
void runActiveReported("view.setWorkspace", { workspace: "styles" }, "Settings");
|
|
274
271
|
},
|
|
275
272
|
setDescription: (value: string) => {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
import { clearGithubToken, githubTokenLocation, githubTokenStored } from "../github/github-auth";
|
|
29
29
|
import { clearCfConnection, getCfAccountId, getCfToken } from "../services/cf-settings";
|
|
30
|
-
import { clearAiProvider, getBaseUrl, hasOpenAiKey } from "../services/ai-settings";
|
|
30
|
+
import { clearAiProvider, getAiProvider, getBaseUrl, hasOpenAiKey } from "../services/ai-settings";
|
|
31
31
|
import { preferredModel, resetModelCache } from "../services/ai-models";
|
|
32
32
|
import { getPlatform, hasPlatform } from "../platform";
|
|
33
33
|
import { getAccountEntries } from "../account-status";
|
|
@@ -292,7 +292,7 @@ export function listAccounts(): AccountRecord[] {
|
|
|
292
292
|
id: "ai",
|
|
293
293
|
label: "AI provider",
|
|
294
294
|
detail: hasOpenAiKey()
|
|
295
|
-
?
|
|
295
|
+
? `${getAiProvider() === "anthropic" ? "Anthropic key" : "Key"} stored. Model ${model}${endpoint ? ` via ${endpoint}` : ""}.`
|
|
296
296
|
: "No key stored. The Assistant section is where one is entered.",
|
|
297
297
|
connected: hasOpenAiKey(),
|
|
298
298
|
revoke: clearAiProvider,
|
|
@@ -143,8 +143,10 @@ function sectionShown(): void {
|
|
|
143
143
|
const credsForm = createAiCredentialsForm({
|
|
144
144
|
/* A sentence rather than a template: the form is a document now, and a document renders text
|
|
145
145
|
rather than another module's markup. */
|
|
146
|
-
intro:
|
|
147
|
-
|
|
146
|
+
intro: (offersProviders) =>
|
|
147
|
+
offersProviders
|
|
148
|
+
? "An Anthropic key for Claude, or any OpenAI-compatible key. Stored locally on this machine; sent only to the Studio proxy (never to a third party except your chosen provider)."
|
|
149
|
+
: "Any OpenAI-compatible key works. Stored locally on this machine; sent only to the Studio proxy (never to a third party except your chosen endpoint).",
|
|
148
150
|
// Both buttons keep the sheet open: Preferences is a place, not a wizard step, and the previous
|
|
149
151
|
// Dialog's habit of vanishing on Save is what made "did that take?" unanswerable.
|
|
150
152
|
onCancel: repaint,
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*
|
|
14
14
|
* **Raw JSON does not open a second editor.** It shows what is on disk and hands the author to the
|
|
15
15
|
* Code editor over the `project.json` tab (§17.1): the host says how, through `editAsCode`, because
|
|
16
|
-
* the section sits in the
|
|
16
|
+
* the section sits in the Settings workspace and the tab it opens is the Styles workspace's.
|
|
17
17
|
*
|
|
18
18
|
* **Deploy is no longer a Project Settings section.** Hosting moved to the Publish workspace
|
|
19
19
|
* (§17.1's Moved footnote), which hosts `renderDeploySection`; the renderer is unchanged.
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* `registerSettingsSection` is a **preserved public contract** (specs/extensions.md §9.1): an
|
|
5
5
|
* extension's `project` class declares a `$studio.settings` block and `settings/extension-sections`
|
|
6
|
-
* turns it into one of these records. Project Settings has moved host
|
|
7
|
-
* document in the pane, and now a
|
|
8
|
-
* because a section is a contribution to CONFIGURATION, not
|
|
9
|
-
* is what lets the host be replaced without the contribution
|
|
6
|
+
* turns it into one of these records. Project Settings has moved host three times — a modal, then a
|
|
7
|
+
* document in the pane, then a dialog again, and now a workspace of its own (§17.1, §22.9) — and
|
|
8
|
+
* the registry moved with none of them, because a section is a contribution to CONFIGURATION, not
|
|
9
|
+
* to whatever draws it. Splitting it out is what lets the host be replaced without the contribution
|
|
10
|
+
* point being touched.
|
|
10
11
|
*
|
|
11
12
|
* **This module holds no renderer and imports nothing.** That is load-bearing rather than tidy: the
|
|
12
13
|
* gear menu (`panels/settings-menu.ts`) enumerates these sections from the top bar, so anything
|
|
@@ -3,18 +3,18 @@
|
|
|
3
3
|
* Project Settings: the built-in sections, the `project.json` tab's editors, and the two commands
|
|
4
4
|
* that reach them.
|
|
5
5
|
*
|
|
6
|
-
* **Settings is a
|
|
7
|
-
* under the transaction log with one write chokepoint (`tabs/project-config.ts`, §17.1), and it
|
|
8
|
-
* three editors, the Styles workspace's three views: Elements (`stylebook`), Variables
|
|
9
|
-
* `style/variables-view.ts`) and Code (`source`). Project Settings is neither of them:
|
|
10
|
-
*
|
|
11
|
-
* every workspace, and every section in it writes through the same chokepoint the
|
|
12
|
-
* so the file has one history whichever door changed it.
|
|
6
|
+
* **Settings is a workspace; the configuration DOCUMENT is still a tab.** `project.json` is a real
|
|
7
|
+
* Tab under the transaction log with one write chokepoint (`tabs/project-config.ts`, §17.1), and it
|
|
8
|
+
* has three editors, the Styles workspace's three views: Elements (`stylebook`), Variables
|
|
9
|
+
* (`tokens`, `style/variables-view.ts`) and Code (`source`). Project Settings is neither of them:
|
|
10
|
+
* it is the Settings workspace, whose home hosts the section list (`./settings-workspace.ts`),
|
|
11
|
+
* reached from every workspace, and every section in it writes through the same chokepoint the
|
|
12
|
+
* tab's editors do, so the file has one history whichever door changed it.
|
|
13
13
|
*
|
|
14
14
|
* **`settings.open` did not change what it means.** It still means "reach project settings,
|
|
15
15
|
* optionally at a named section and entry", its args schema still declares `section` and `entry`,
|
|
16
|
-
* and a key nobody registered still fails the call. What changed is the `run`: a
|
|
17
|
-
* a
|
|
16
|
+
* and a key nobody registered still fails the call. What changed is the `run`: a workspace switch
|
|
17
|
+
* instead of a dialog.
|
|
18
18
|
*
|
|
19
19
|
* @docs studio/projects/settings
|
|
20
20
|
*/
|
|
@@ -31,7 +31,8 @@ import { notify } from "../services/notify";
|
|
|
31
31
|
import { projectState } from "../state";
|
|
32
32
|
import { PROJECT_CONFIG_PATH } from "../tabs/tab";
|
|
33
33
|
import { lendProjectConfigLayout } from "../tabs/project-config";
|
|
34
|
-
import { focusPane, openTab, workspace } from "../workspace/workspace";
|
|
34
|
+
import { focusPane, openTab, paneOfTab, workspace } from "../workspace/workspace";
|
|
35
|
+
import { workspaceHomeTabId } from "../workspace/workspace-ids";
|
|
35
36
|
import {
|
|
36
37
|
notifySettingsDocument,
|
|
37
38
|
registerSettingsSection,
|
|
@@ -47,7 +48,7 @@ import { renderGeneralSettings } from "./general-settings";
|
|
|
47
48
|
import { renderHeadEditor } from "./head-editor";
|
|
48
49
|
import { renderLocalesSection } from "./locales-section";
|
|
49
50
|
import { selectContributedEntry } from "./contributed-section";
|
|
50
|
-
import {
|
|
51
|
+
import { focusSettingsHome } from "./settings-workspace";
|
|
51
52
|
// The NAME and the wire value, kept apart by that module on purpose — see its docstring.
|
|
52
53
|
import {
|
|
53
54
|
PROJECT_STYLES_TITLE,
|
|
@@ -79,7 +80,7 @@ const PROJECT_TAB_MODES = STYLES_TAB_MODES;
|
|
|
79
80
|
|
|
80
81
|
Two sections that used to be here are not, and their renderers are not deleted: CSS Variables
|
|
81
82
|
(`./css-vars-editor.ts`) belongs to the Styles workspace, and Deploy (`renderDeploySection`) to
|
|
82
|
-
the Publish workspace.
|
|
83
|
+
the Publish workspace. Settings' Moved footnote says where each went.
|
|
83
84
|
|
|
84
85
|
`icon` is a KEY into the kit's glyph manifest (`@jxsuite/ui/icons`), not a tag: `studio.md`
|
|
85
86
|
§13.5 is about exactly this distinction, and a key with no row draws nothing and says nothing.
|
|
@@ -156,9 +157,9 @@ registerSettingsSection({
|
|
|
156
157
|
render: renderDependenciesEditor,
|
|
157
158
|
});
|
|
158
159
|
/*
|
|
159
|
-
* Raw JSON's "Edit as code" leaves
|
|
160
|
-
* the Styles workspace's, so the
|
|
161
|
-
*
|
|
160
|
+
* Raw JSON's "Edit as code" leaves Settings: the Code editor is the `project.json` tab's, which is
|
|
161
|
+
* the Styles workspace's, so the tab opens in its source editor, and activating it is what switches
|
|
162
|
+
* the workspace (`workspace/tab-workspace.ts`).
|
|
162
163
|
*/
|
|
163
164
|
registerSettingsSection({
|
|
164
165
|
group: "advanced",
|
|
@@ -169,7 +170,6 @@ registerSettingsSection({
|
|
|
169
170
|
render: (container) => {
|
|
170
171
|
renderRawJsonSection(container, {
|
|
171
172
|
editAsCode: () => {
|
|
172
|
-
closeSettingsDialog();
|
|
173
173
|
showSettingsDocument(SOURCE_MODE);
|
|
174
174
|
},
|
|
175
175
|
});
|
|
@@ -271,12 +271,13 @@ function revealTab(tab: Tab): Tab {
|
|
|
271
271
|
/**
|
|
272
272
|
* Reach Project Settings, optionally at a named section.
|
|
273
273
|
*
|
|
274
|
-
*
|
|
275
|
-
* remembers for the session),
|
|
276
|
-
* caller that then asks whether a section exists is
|
|
277
|
-
* COALESCED (`extension-sections.ts`):
|
|
278
|
-
* activation and after every `project.json`
|
|
279
|
-
* take the caller's section down between the
|
|
274
|
+
* Switches to the Settings workspace on the requested section (or on the one it last showed, which
|
|
275
|
+
* the registry remembers for the session), puts the keyboard on that section's row, then awaits the
|
|
276
|
+
* contributed-section sync before returning, so a caller that then asks whether a section exists is
|
|
277
|
+
* asking the complete registry. The sync is COALESCED (`extension-sections.ts`):
|
|
278
|
+
* `refreshExtensionUi` fires the same function on project activation and after every `project.json`
|
|
279
|
+
* write, and a second, interleaved run could otherwise take the caller's section down between the
|
|
280
|
+
* promise resolving and the frame being drawn.
|
|
280
281
|
*
|
|
281
282
|
* @param {string} [section]
|
|
282
283
|
*/
|
|
@@ -284,7 +285,15 @@ export async function openProjectSettings(section?: string): Promise<void> {
|
|
|
284
285
|
if (section !== undefined) {
|
|
285
286
|
setSettingsSection(section);
|
|
286
287
|
}
|
|
287
|
-
|
|
288
|
+
const registry = activeRegistry();
|
|
289
|
+
if (!registry) {
|
|
290
|
+
throw new Error('command "settings.open" needs the window\'s command registry');
|
|
291
|
+
}
|
|
292
|
+
await registry.run("view.setWorkspace", { workspace: "settings" });
|
|
293
|
+
const paneId = paneOfTab(workspaceHomeTabId("settings"))?.id;
|
|
294
|
+
if (paneId !== undefined) {
|
|
295
|
+
void focusSettingsHome(paneId);
|
|
296
|
+
}
|
|
288
297
|
try {
|
|
289
298
|
const { syncExtensionSettingsSections } = await import("./extension-sections");
|
|
290
299
|
await syncExtensionSettingsSections();
|
|
@@ -318,7 +327,7 @@ export async function settingsSectionsReady(): Promise<void> {
|
|
|
318
327
|
* step are the same call.
|
|
319
328
|
*
|
|
320
329
|
* The validation is deliberately asynchronous. Extension-contributed sections (`connections`,
|
|
321
|
-
* `data`, `content`) register a tick after
|
|
330
|
+
* `data`, `content`) register a tick after Settings opens, so refusing synchronously would reject
|
|
322
331
|
* three legitimate section ids; the command opens, awaits the sync, and only then decides whether
|
|
323
332
|
* the caller named something real. A wrong key therefore fails the call instead of rendering an
|
|
324
333
|
* empty body.
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
/**
|
|
3
|
+
* Project Settings as a workspace (§17.1, §22.9): the home that hosts the settings editor, mounted
|
|
4
|
+
* into a pane's stage, and the two ways out of it.
|
|
5
|
+
*
|
|
6
|
+
* Project Settings was a dialog, and before that the `project.json` tab under a `settings` editor
|
|
7
|
+
* mode. The dialog was reachable from every workspace, but a dialog is a box of fixed size over a
|
|
8
|
+
* window that has a whole stage to spare, and the map-layout sections (Content types, Data tables,
|
|
9
|
+
* Connections) are two columns of their own inside the editor's two columns: four columns in 900px.
|
|
10
|
+
* A workspace gives the editor the stage, and keeps what the dialog was for: Settings is reached
|
|
11
|
+
* from every workspace through its links (the gear menu, ⌘⇧, and every `settings.open` deep link),
|
|
12
|
+
* and **Back** returns to the workspace it was opened from, which the top bar cannot say because it
|
|
13
|
+
* draws no tab for Settings.
|
|
14
|
+
*
|
|
15
|
+
* **Per pane**, for the reason `publish/publish-workspace.ts` gives: the home is a tab, so two
|
|
16
|
+
* panes can each show it. Each mount is the editor, built when the home is drawn and taken down
|
|
17
|
+
* when the pane's stage moves on (a switch to another workspace, a project closing). A visit is
|
|
18
|
+
* therefore a fresh editor: a half-typed entry or a failed save's error from the last visit, or
|
|
19
|
+
* from another project's, does not come back. **Which section shows is the registry's**, not this
|
|
20
|
+
* module's (`settings/section-registry.ts`), and the registry outlives every mount, which is what
|
|
21
|
+
* makes Settings reopen where the author left it for the rest of the session.
|
|
22
|
+
*
|
|
23
|
+
* @docs studio/projects/settings
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { activeRegistry } from "../commands/active-registry";
|
|
27
|
+
import { runActiveReported } from "../commands/run-reported";
|
|
28
|
+
import { mountSettingsPane } from "../panels/settings-pane";
|
|
29
|
+
import { previousWorkspace } from "../shell";
|
|
30
|
+
import { paneRegion } from "../ui/regions";
|
|
31
|
+
import { DEFAULT_WORKSPACE, workspaceInfo } from "../workspace/workspace-ids";
|
|
32
|
+
import type { CanvasSurface } from "../canvas/canvas-surface";
|
|
33
|
+
import type { SettingsPaneMount, SettingsPaneOptions } from "../panels/settings-pane";
|
|
34
|
+
import type { PublishTab } from "../publish/publish-ids";
|
|
35
|
+
import type { InspectorTabId } from "../shell";
|
|
36
|
+
import type { StylesView } from "../style/project-styles";
|
|
37
|
+
import type { Tab } from "../tabs/tab";
|
|
38
|
+
import type { WorkspaceId } from "../workspace/workspace-ids";
|
|
39
|
+
|
|
40
|
+
/** The region part the home is stamped with, under its pane: `pane.<id>/settings`. */
|
|
41
|
+
export const SETTINGS_REGION_PART = "settings";
|
|
42
|
+
|
|
43
|
+
/** The region of the Settings home in `paneId`. */
|
|
44
|
+
export function settingsRegion(paneId: string): string {
|
|
45
|
+
return paneRegion(paneId, SETTINGS_REGION_PART);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** One pane's mount: the editor, and which tab and stage it was drawn for. */
|
|
49
|
+
interface PaneMount {
|
|
50
|
+
pane: SettingsPaneMount;
|
|
51
|
+
tabId: string;
|
|
52
|
+
wrap: HTMLElement;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const _panes = new Map<string, PaneMount>();
|
|
56
|
+
|
|
57
|
+
/** Panes whose next mount takes the keyboard: Settings was asked for before its home was drawn. */
|
|
58
|
+
const _focusOnMount = new Set<string>();
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Where Back goes: the workspace entered before Settings, or Design when there was none (a session
|
|
62
|
+
* that was restored into Settings).
|
|
63
|
+
*/
|
|
64
|
+
export function returnWorkspace(): WorkspaceId {
|
|
65
|
+
const previous = previousWorkspace();
|
|
66
|
+
return previous === null || previous === "settings" ? DEFAULT_WORKSPACE : previous;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Leave Settings for the workspace it was opened from.
|
|
71
|
+
*
|
|
72
|
+
* A refusal (the switch is gated on an open project) is filed in Problems under this surface's name
|
|
73
|
+
* instead of being dropped.
|
|
74
|
+
*/
|
|
75
|
+
export async function leaveSettings(): Promise<void> {
|
|
76
|
+
await runActiveReported("view.setWorkspace", { workspace: returnWorkspace() }, "Settings");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Leave Settings for the workspace something moved to: the Moved footnote's buttons.
|
|
81
|
+
*
|
|
82
|
+
* A row that names an Inspector tab ("Head and SEO defaults → Page tab") also shows the Inspector
|
|
83
|
+
* on that tab, AFTER the switch, because entering a workspace applies its frame preset and would
|
|
84
|
+
* otherwise put the dock back as the preset has it. The tab is a document-level view, so with no
|
|
85
|
+
* document open there is no tab to show and asking would only file a refusal; the dock is still
|
|
86
|
+
* opened, which is where the tab appears once one is.
|
|
87
|
+
*
|
|
88
|
+
* A row that names a Styles view ("Colors and fonts → Styles › Variables") runs `styles.open` with
|
|
89
|
+
* that view instead, which is the same workspace switch followed by the view, and a row that names
|
|
90
|
+
* a Publish sub-tab runs `publish.open` on it.
|
|
91
|
+
*
|
|
92
|
+
* @param {WorkspaceId} workspace
|
|
93
|
+
* @param {InspectorTabId} [inspector]
|
|
94
|
+
* @param {StylesView} [view]
|
|
95
|
+
* @param {PublishTab} [publishTab]
|
|
96
|
+
* @returns {Promise<void>} Settles once every command the row runs has settled
|
|
97
|
+
*/
|
|
98
|
+
export async function openMoved(
|
|
99
|
+
workspace: WorkspaceId,
|
|
100
|
+
inspector?: InspectorTabId,
|
|
101
|
+
view?: StylesView,
|
|
102
|
+
publishTab?: PublishTab,
|
|
103
|
+
): Promise<void> {
|
|
104
|
+
if (view !== undefined) {
|
|
105
|
+
await runActiveReported("styles.open", { view }, "Settings");
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Hosting is a Publish sub-tab, not the workspace's first: the button says where it lands.
|
|
109
|
+
if (publishTab !== undefined) {
|
|
110
|
+
await runActiveReported("publish.open", { tab: publishTab }, "Settings");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
await runActiveReported("view.setWorkspace", { workspace }, "Settings");
|
|
114
|
+
if (inspector === undefined) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
await runActiveReported("view.setRightPanel", { open: true }, "Settings");
|
|
118
|
+
if (activeRegistry()?.isEnabled("view.setRightTab") === true) {
|
|
119
|
+
await runActiveReported("view.setRightTab", { tab: inspector }, "Settings");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Whether this tab's home is already mounted in this pane and still in the document. */
|
|
124
|
+
export function settingsWorkspaceMounted(paneId: string, tab: Tab): boolean {
|
|
125
|
+
const mount = _panes.get(paneId);
|
|
126
|
+
return (
|
|
127
|
+
mount !== undefined && mount.tabId === tab.id && mount.wrap.isConnected && mount.pane.attached()
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Tear one pane's home down (mode change, tab switch, project close). Idempotent. */
|
|
132
|
+
export function detachSettingsWorkspace(paneId: string): void {
|
|
133
|
+
const mount = _panes.get(paneId);
|
|
134
|
+
if (!mount) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
_panes.delete(paneId);
|
|
138
|
+
mount.pane.dispose();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Put the keyboard on the current section's row once the editor has drawn it. */
|
|
142
|
+
async function focusWhenReady(paneId: string, mount: PaneMount): Promise<void> {
|
|
143
|
+
await mount.pane.ready;
|
|
144
|
+
if (_panes.get(paneId) === mount) {
|
|
145
|
+
await mount.pane.focusActive();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Give the keyboard to the Settings home in `paneId`, on the current section's row: what opening
|
|
151
|
+
* Settings does, so an author who asked for Advanced › Raw JSON is on Raw JSON rather than on the
|
|
152
|
+
* first group's tab stop. Served now when the home is standing in the pane, or by its next mount
|
|
153
|
+
* there.
|
|
154
|
+
*
|
|
155
|
+
* @param {string} paneId
|
|
156
|
+
* @returns {Promise<void>} Settles once the keyboard has moved, or once the request is queued
|
|
157
|
+
*/
|
|
158
|
+
export async function focusSettingsHome(paneId: string): Promise<void> {
|
|
159
|
+
const mount = _panes.get(paneId);
|
|
160
|
+
if (mount?.wrap.isConnected === true && mount.pane.attached()) {
|
|
161
|
+
await focusWhenReady(paneId, mount);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
_focusOnMount.add(paneId);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* What the home's controls do, the same in every pane: Back and the Moved buttons are workspace
|
|
169
|
+
* switches, window-level commands that act wherever the author is, and no fact about the pane the
|
|
170
|
+
* home is drawn in goes into them. Declared here rather than in {@link renderSettingsWorkspace} so
|
|
171
|
+
* nothing that consults the window's state is reached from a function holding a pane.
|
|
172
|
+
*/
|
|
173
|
+
const HOME_ACTIONS: Omit<SettingsPaneOptions, "region"> = {
|
|
174
|
+
back: () => {
|
|
175
|
+
void leaveSettings();
|
|
176
|
+
},
|
|
177
|
+
backLabel: () => `Back to ${workspaceInfo(returnWorkspace()).title}`,
|
|
178
|
+
openMoved: (workspace, inspector, view, publishTab) => {
|
|
179
|
+
void openMoved(workspace, inspector, view, publishTab);
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Draw the Settings home into a pane's stage. It reads the pane's id and its stage, and nothing
|
|
185
|
+
* else of the canvas surface.
|
|
186
|
+
*
|
|
187
|
+
* @param {Pick<CanvasSurface, "paneId" | "wrap">} surface
|
|
188
|
+
* @param {Tab} tab
|
|
189
|
+
*/
|
|
190
|
+
export function renderSettingsWorkspace(
|
|
191
|
+
surface: Pick<CanvasSurface, "paneId" | "wrap">,
|
|
192
|
+
tab: Tab,
|
|
193
|
+
): void {
|
|
194
|
+
const { paneId, wrap } = surface;
|
|
195
|
+
if (settingsWorkspaceMounted(paneId, tab)) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
detachSettingsWorkspace(paneId);
|
|
199
|
+
const pane = mountSettingsPane(wrap, { ...HOME_ACTIONS, region: settingsRegion(paneId) });
|
|
200
|
+
const mount: PaneMount = { pane, tabId: tab.id, wrap };
|
|
201
|
+
_panes.set(paneId, mount);
|
|
202
|
+
if (_focusOnMount.delete(paneId)) {
|
|
203
|
+
void focusWhenReady(paneId, mount);
|
|
204
|
+
}
|
|
205
|
+
}
|