@morlay/dsh-client-ui-primitives 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -266
- package/dist/client.cjs +1 -611
- package/dist/client.d.cts +11 -658
- package/dist/client.d.mts +11 -658
- package/dist/index.mjs +1 -1
- package/dist/{reference-Dxy9uQYz.mjs → reference-Cjcn21uW.mjs} +0 -1
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +2 -2
- package/src/client/index.ts +7 -9
- package/src/client/schema-form/Editor.tsx +25 -38
- package/src/client/schema-form/SchemaForm.tsx +6 -12
- package/src/client/schema-form/controller.ts +65 -123
- package/src/client/schema-form/draft.ts +55 -138
- package/src/client/schema-form/fields.tsx +5 -14
- package/src/client/schema-form/hints.ts +31 -131
- package/src/client/schema-form/index.ts +15 -20
- package/src/client/schema-form/labels.ts +7 -9
- package/src/client/schema-form/lines.ts +36 -60
- package/src/client/schema-form/locales.ts +6 -8
- package/src/client/schema-form/rows.ts +22 -41
- package/src/client/schema-form/schema-node.ts +87 -150
- package/src/client/schema-form/slot-contract.ts +55 -76
- package/src/client/schema-form/styles.ts +39 -51
- package/src/client/schema-form/testing/fake-describe.ts +3 -9
- package/src/client/schema-form/testing/fake-scope.ts +8 -10
- package/src/client/schema-form/value.tsx +14 -26
- package/src/client/settings-form/SettingsForm.tsx +13 -17
- package/src/client/settings-form/fields.tsx +25 -35
- package/src/client/settings-form/form-model.ts +63 -145
- package/src/client/styling/styled.tsx +3 -5
- package/src/client/theme.generated.ts +6 -7
- package/src/reference-markdown.tsx +1 -1
- package/src/reference.ts +1 -1
package/dist/client.cjs
CHANGED
|
@@ -21,7 +21,6 @@ window.__ModuleLoader__.load({
|
|
|
21
21
|
let react = require("react");
|
|
22
22
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
23
23
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
24
|
-
/** 空的元数据:动态子节点(无声明 schema 的 dict 键)与空字段树用它。 */
|
|
25
24
|
function emptyMeta() {
|
|
26
25
|
return {
|
|
27
26
|
description: void 0,
|
|
@@ -42,11 +41,9 @@ window.__ModuleLoader__.load({
|
|
|
42
41
|
extra: void 0
|
|
43
42
|
};
|
|
44
43
|
}
|
|
45
|
-
/** `badges` 只留文本:配色与文案由字典决定。 */
|
|
46
44
|
function badgesOf(meta) {
|
|
47
45
|
return (meta.badges ?? []).map((badge) => badge.text);
|
|
48
46
|
}
|
|
49
|
-
/** 归一化一个节点的 meta。 */
|
|
50
47
|
function metaOf(schema) {
|
|
51
48
|
const meta = schema.meta ?? {};
|
|
52
49
|
return {
|
|
@@ -68,17 +65,14 @@ window.__ModuleLoader__.load({
|
|
|
68
65
|
extra: meta.extra
|
|
69
66
|
};
|
|
70
67
|
}
|
|
71
|
-
/** 外层 meta 与内层 meta 合并:外层的 undefined 不覆盖内层(transform 的 inner 带着自己的角色)。 */
|
|
72
68
|
function mergeMeta(inner, outer) {
|
|
73
69
|
const merged = { ...inner };
|
|
74
70
|
for (const [key, value] of Object.entries(outer)) if (value !== void 0) merged[key] = value;
|
|
75
71
|
return merged;
|
|
76
72
|
}
|
|
77
|
-
/** 节点自己的路径末段:动态段保持占位,根节点为空串。 */
|
|
78
73
|
function keyOf(path) {
|
|
79
74
|
return path[path.length - 1] ?? "";
|
|
80
75
|
}
|
|
81
|
-
/** 一个纯字符串叶子(dict 没有声明键 schema 时用它)。 */
|
|
82
76
|
function stringLeaf(path, meta) {
|
|
83
77
|
return {
|
|
84
78
|
key: keyOf(path),
|
|
@@ -89,7 +83,6 @@ window.__ModuleLoader__.load({
|
|
|
89
83
|
type: "string"
|
|
90
84
|
};
|
|
91
85
|
}
|
|
92
|
-
/** 把 schema 的类型名收口成渲染层认识的那几种。 */
|
|
93
86
|
function leafTypeOf(type) {
|
|
94
87
|
switch (type) {
|
|
95
88
|
case "string": return "string";
|
|
@@ -100,12 +93,6 @@ window.__ModuleLoader__.load({
|
|
|
100
93
|
default: return "any";
|
|
101
94
|
}
|
|
102
95
|
}
|
|
103
|
-
/**
|
|
104
|
-
* 投影一个 schema 节点。
|
|
105
|
-
* @param schema - rehydrate 之后的 volatile 表单 schema(或它的子节点)。
|
|
106
|
-
* @param path - 该节点相对段根的路径;动态子节点由调用方给占位段。
|
|
107
|
-
* @returns 渲染层要的字段树。
|
|
108
|
-
*/
|
|
109
96
|
function projectNode(schema, path = []) {
|
|
110
97
|
return project(schema, path, []);
|
|
111
98
|
}
|
|
@@ -223,13 +210,11 @@ window.__ModuleLoader__.load({
|
|
|
223
210
|
};
|
|
224
211
|
}
|
|
225
212
|
}
|
|
226
|
-
/** 展开 `lazy` 包装:投影里 lazy 只是一层,值仍在同一路径上。 */
|
|
227
213
|
function unwrapLazy(node) {
|
|
228
214
|
let current = node;
|
|
229
215
|
while (current?.type === "lazy") current = current.inner;
|
|
230
216
|
return current;
|
|
231
217
|
}
|
|
232
|
-
/** 某一层可见的字段:object 直接给;union / intersect 把各变体的对象字段并起来。 */
|
|
233
218
|
function fieldsOf(node) {
|
|
234
219
|
const current = unwrapLazy(node);
|
|
235
220
|
switch (current?.type) {
|
|
@@ -239,12 +224,6 @@ window.__ModuleLoader__.load({
|
|
|
239
224
|
default: return [];
|
|
240
225
|
}
|
|
241
226
|
}
|
|
242
|
-
/**
|
|
243
|
-
* 按具体路径找回投影节点(动态段匹配真实键或索引)。
|
|
244
|
-
* @param root - 段根的字段树。
|
|
245
|
-
* @param path - 具体路径。
|
|
246
|
-
* @returns 命中的节点,或 `undefined`。
|
|
247
|
-
*/
|
|
248
227
|
function seekNode(root, path) {
|
|
249
228
|
let current = root;
|
|
250
229
|
for (const segment of path) {
|
|
@@ -270,16 +249,6 @@ window.__ModuleLoader__.load({
|
|
|
270
249
|
}
|
|
271
250
|
return unwrapLazy(current);
|
|
272
251
|
}
|
|
273
|
-
/**
|
|
274
|
-
* 按当前值把字段树展开成渲染顺序的扁平列表(前序):容器在前、子项紧随其后。
|
|
275
|
-
*
|
|
276
|
-
* 出现的**只有值里有的东西,加上必填的**:对象的必填字段始终占行(值是空的也要让用户看见"必须填"),非必填的
|
|
277
|
-
* 按值出现——值里没有就不占行,它在这一层的「添加属性」候选里,选中才加。数组按索引、字典按键展开;字面量
|
|
278
|
-
* union 是叶子控件,对象 union 只展开与当前值匹配的那个变体,intersect 的各成员在同一层展开。
|
|
279
|
-
* @param root - 段根的字段树。
|
|
280
|
-
* @param value - 生效值(或草稿后的值)。
|
|
281
|
-
* @returns 渲染顺序的字段列表。
|
|
282
|
-
*/
|
|
283
252
|
function walkFields(root, value) {
|
|
284
253
|
const out = [];
|
|
285
254
|
visit(root, root.path, value, out, true, void 0, /* @__PURE__ */ new Set(), void 0);
|
|
@@ -345,14 +314,12 @@ window.__ModuleLoader__.load({
|
|
|
345
314
|
default: return;
|
|
346
315
|
}
|
|
347
316
|
}
|
|
348
|
-
/** 数组的项身份:显式声明优先,否则 item 上的 `id` 字段。 */
|
|
349
317
|
function mergeKeyOf(schema, item) {
|
|
350
318
|
const declared = declaredExtra(schema, "mergeKey");
|
|
351
319
|
if (declared !== void 0) return declared;
|
|
352
320
|
const current = unwrapLazy(item);
|
|
353
321
|
return current?.type === "object" && current.fields.some((field) => field.key === "id") ? "id" : void 0;
|
|
354
322
|
}
|
|
355
|
-
/** union 的判别标签:显式声明优先,否则所有对象成员共有的唯一常量字段。 */
|
|
356
323
|
function tagKeyOf(schema, variants) {
|
|
357
324
|
const declared = declaredExtra(schema, "tag");
|
|
358
325
|
if (declared !== void 0) return declared;
|
|
@@ -362,29 +329,18 @@ window.__ModuleLoader__.load({
|
|
|
362
329
|
const candidates = (objects[0]?.fields ?? []).map((field) => unwrapLazy(field)).filter((field) => field?.type === "const").map((field) => field.key).filter((key) => objects.every((object) => unwrapLazy(object.fields.find((field) => field.key === key))?.type === "const"));
|
|
363
330
|
return candidates.length === 1 ? candidates[0] : void 0;
|
|
364
331
|
}
|
|
365
|
-
/** `role(text, { … })` 里声明的字符串约定(项身份 `mergeKey`、判别标签 `tag`)。 */
|
|
366
332
|
function declaredExtra(schema, name) {
|
|
367
333
|
const extra = schema.meta?.extra;
|
|
368
334
|
if (typeof extra !== "object" || extra === null) return void 0;
|
|
369
335
|
const value = Reflect.get(extra, name);
|
|
370
336
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
371
337
|
}
|
|
372
|
-
/** 一项的项身份值(渲染与冲突检查共用的读法)。 */
|
|
373
338
|
function memberLabel(value, mergeKey) {
|
|
374
339
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
375
340
|
const member = Reflect.get(value, mergeKey);
|
|
376
341
|
if (typeof member === "string") return member;
|
|
377
342
|
if (typeof member === "number" && Number.isFinite(member)) return String(member);
|
|
378
343
|
}
|
|
379
|
-
/**
|
|
380
|
-
* 项身份冲突:同一层里两个成员同名,或某个成员没有名字。
|
|
381
|
-
*
|
|
382
|
-
* 名字是成员的「身份」,写盘仍按索引——所以这只是保存前的本地校验:重名的数组在页面上是非法草稿,
|
|
383
|
-
* 而不是悄悄写到 host 上。
|
|
384
|
-
* @param root - 段根的字段树。
|
|
385
|
-
* @param value - 草稿后的段值。
|
|
386
|
-
* @returns 第一处冲突,或 `undefined`。
|
|
387
|
-
*/
|
|
388
344
|
function mergeKeyConflict(root, value) {
|
|
389
345
|
return scanConflict(root, root.path, value);
|
|
390
346
|
}
|
|
@@ -449,34 +405,24 @@ window.__ModuleLoader__.load({
|
|
|
449
405
|
default: return;
|
|
450
406
|
}
|
|
451
407
|
}
|
|
452
|
-
/** 一个容器值里的成员(对象键或数组索引)。 */
|
|
453
408
|
function entriesOf(value) {
|
|
454
409
|
if (Array.isArray(value)) return value.map((item, index) => [String(index), item]);
|
|
455
410
|
if (typeof value === "object" && value !== null) return Object.entries(value);
|
|
456
411
|
return [];
|
|
457
412
|
}
|
|
458
|
-
/** 读一个成员的值。 */
|
|
459
413
|
function memberOf(value, key) {
|
|
460
414
|
if (Array.isArray(value)) return value[Number(key)];
|
|
461
415
|
if (typeof value === "object" && value !== null) return value[String(key)];
|
|
462
416
|
}
|
|
463
|
-
/** 把节点收成 union:渲染层判形状与选支都从这里进。 */
|
|
464
417
|
function unionOf(node) {
|
|
465
418
|
const current = unwrapLazy(node);
|
|
466
419
|
return current?.type === "union" ? current : void 0;
|
|
467
420
|
}
|
|
468
|
-
/**
|
|
469
|
-
* 当前值落在哪一支:与 {@link walkFields} 的展开同一判据(标签值优先,其次值的形状,最后第一支)。
|
|
470
|
-
* @param node - 字段节点(不是 union 就原样返回)。
|
|
471
|
-
* @param value - 这一层的值。
|
|
472
|
-
* @returns 选中变体的节点。
|
|
473
|
-
*/
|
|
474
421
|
function variantOf(node, value) {
|
|
475
422
|
const union = unionOf(node);
|
|
476
423
|
if (union === void 0) return unwrapLazy(node) ?? node;
|
|
477
424
|
return pickVariant(union, value) ?? union;
|
|
478
425
|
}
|
|
479
|
-
/** 变体里那个判别标签字段的常量值(不是 `const` 就没有)。 */
|
|
480
426
|
function tagValueOf(variant, tagKey) {
|
|
481
427
|
const current = unwrapLazy(variant);
|
|
482
428
|
if (current?.type !== "object" && current?.type !== "intersect") return {
|
|
@@ -496,7 +442,6 @@ window.__ModuleLoader__.load({
|
|
|
496
442
|
value: void 0
|
|
497
443
|
};
|
|
498
444
|
}
|
|
499
|
-
/** 一个变体在页面上的名字:能看出"这一支是什么",而不是内部类型名。 */
|
|
500
445
|
function variantLabel(variant, tag) {
|
|
501
446
|
if (tag.found) return JSON.stringify(tag.value);
|
|
502
447
|
switch (unwrapLazy(variant)?.type) {
|
|
@@ -511,14 +456,6 @@ window.__ModuleLoader__.load({
|
|
|
511
456
|
default: return unwrapLazy(variant)?.type ?? "any";
|
|
512
457
|
}
|
|
513
458
|
}
|
|
514
|
-
/**
|
|
515
|
-
* 变体切换的选项。
|
|
516
|
-
*
|
|
517
|
-
* 带判别标签的 union 写**标签值**(`session-rdb` 的 `type: sqlite` ↔ `type: postgres`);没有标签的写目标变体的
|
|
518
|
-
* 空值(`access` 在「一段文本」与「一组文本」之间切)。
|
|
519
|
-
* @param union - 投影出来的 union 节点。
|
|
520
|
-
* @returns 每个变体一项,顺序即声明顺序。
|
|
521
|
-
*/
|
|
522
459
|
function variantChoices(union) {
|
|
523
460
|
return union.variants.map((variant) => {
|
|
524
461
|
const tag = union.tagKey === void 0 ? {
|
|
@@ -531,7 +468,6 @@ window.__ModuleLoader__.load({
|
|
|
531
468
|
};
|
|
532
469
|
});
|
|
533
470
|
}
|
|
534
|
-
/** 按路径读一个值:对象键与数组下标都认(值的读法只有这一处)。 */
|
|
535
471
|
function readPath(root, path) {
|
|
536
472
|
return path.reduce((node, segment) => {
|
|
537
473
|
if (Array.isArray(node)) return node[Number(segment)];
|
|
@@ -539,29 +475,12 @@ window.__ModuleLoader__.load({
|
|
|
539
475
|
return node[segment];
|
|
540
476
|
}, root);
|
|
541
477
|
}
|
|
542
|
-
/**
|
|
543
|
-
* 这个"默认值"其实不是值——它不该用来当新加进来的那一项的初值。
|
|
544
|
-
*
|
|
545
|
-
* 两种:`null`("没有值"的判断位,例如 `defaultModel` 用 `.default(null)` 表达"没配就没有这个键"),以及
|
|
546
|
-
* schemastery 给容器类型自动塞的空壳(`{}`)。两种都该让容器把字段摆出来。
|
|
547
|
-
* @param node - 字段节点。
|
|
548
|
-
* @returns 是不是空壳。
|
|
549
|
-
*/
|
|
550
478
|
function isBlankDefault(node) {
|
|
551
479
|
const value = node.meta.defaultValue;
|
|
552
480
|
if (value === null) return true;
|
|
553
481
|
if (node.type !== "object" && node.type !== "intersect") return false;
|
|
554
482
|
return typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0;
|
|
555
483
|
}
|
|
556
|
-
/**
|
|
557
|
-
* 一个新子节点的初值:声明的默认值优先;对象把字段摆出来(见下);字典/数组给空的容器;其余标量给 **`null`**。
|
|
558
|
-
*
|
|
559
|
-
* `null` 在这里是"还没有值"的判断位(`null` / `undefined` 都算),页面据此把它画成一个等着输入的位子——
|
|
560
|
-
* 而不是 `""`、`0` 这种看着像已经填过的值。host 侧也认这个语义:非必填字段拿到 `null` 回退到默认值,
|
|
561
|
-
* 必填字段拿到 `null` 就是"必须填"。
|
|
562
|
-
* @param node - 子节点的字段树。
|
|
563
|
-
* @returns 初值。
|
|
564
|
-
*/
|
|
565
484
|
function emptyValueOf(node) {
|
|
566
485
|
if (node.meta.hasDefault && !isBlankDefault(node)) return structuredClone(node.meta.defaultValue);
|
|
567
486
|
switch (node.type) {
|
|
@@ -576,7 +495,6 @@ window.__ModuleLoader__.load({
|
|
|
576
495
|
default: return null;
|
|
577
496
|
}
|
|
578
497
|
}
|
|
579
|
-
/** 选中与当前值匹配的 union 变体:对象按判别标签的值判,叶子按值类型判。 */
|
|
580
498
|
function pickVariant(node, value) {
|
|
581
499
|
if (node.tagKey !== void 0 && typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
582
500
|
const tagValue = Reflect.get(value, node.tagKey);
|
|
@@ -598,14 +516,6 @@ window.__ModuleLoader__.load({
|
|
|
598
516
|
}
|
|
599
517
|
//#endregion
|
|
600
518
|
//#region src/client/schema-form/draft.ts
|
|
601
|
-
/**
|
|
602
|
-
* 把校验器抛出的错误收成一条失败。
|
|
603
|
-
*
|
|
604
|
-
* schemastery 的 `ValidationError` 把路径留在 `options.path` 上(消息里的 `$…` 前缀是给命令行看的),所以这里
|
|
605
|
-
* 取路径、去前缀——行内报错据此知道挂在哪一行。
|
|
606
|
-
* @param error - 校验器抛出的错误。
|
|
607
|
-
* @returns 消息与路径。
|
|
608
|
-
*/
|
|
609
519
|
function failureOf(error) {
|
|
610
520
|
const raw = error instanceof Error ? error.message : String(error);
|
|
611
521
|
const path = (error.options?.path ?? []).filter((segment) => typeof segment === "string" || typeof segment === "number").map(String);
|
|
@@ -615,9 +525,6 @@ window.__ModuleLoader__.load({
|
|
|
615
525
|
path
|
|
616
526
|
};
|
|
617
527
|
}
|
|
618
|
-
/**
|
|
619
|
-
* 一页配置的草稿。一个命名空间一个实例:它订阅共享配置表单,投影给渲染层读。
|
|
620
|
-
*/
|
|
621
528
|
var SchemaDraftModel = class {
|
|
622
529
|
#scope;
|
|
623
530
|
#root;
|
|
@@ -629,9 +536,6 @@ window.__ModuleLoader__.load({
|
|
|
629
536
|
#saving = false;
|
|
630
537
|
#failed = false;
|
|
631
538
|
#violation;
|
|
632
|
-
/**
|
|
633
|
-
* @param options - 本页的共享配置表单、字段树与整段校验。
|
|
634
|
-
*/
|
|
635
539
|
constructor(options) {
|
|
636
540
|
this.#scope = options.scope;
|
|
637
541
|
this.#root = options.root;
|
|
@@ -640,32 +544,15 @@ window.__ModuleLoader__.load({
|
|
|
640
544
|
this.#publish();
|
|
641
545
|
});
|
|
642
546
|
}
|
|
643
|
-
/**
|
|
644
|
-
* 换一份字段树。
|
|
645
|
-
*
|
|
646
|
-
* 增删项要按它取节点默认值,而 schema 可能随 describe 变(行的 Config 改了)——所以控制器每次投影都同步一次,
|
|
647
|
-
* 否则加进来的项会取不到默认值。
|
|
648
|
-
* @param root - 段根的字段树。
|
|
649
|
-
*/
|
|
650
547
|
setRoot(root) {
|
|
651
548
|
this.#root = root;
|
|
652
549
|
}
|
|
653
|
-
/**
|
|
654
|
-
* 观察草稿与读数的变化。
|
|
655
|
-
* @param listener - 每次投影都调一次的回调。
|
|
656
|
-
* @returns 取消订阅。
|
|
657
|
-
*/
|
|
658
550
|
subscribe(listener) {
|
|
659
551
|
this.#listeners.add(listener);
|
|
660
552
|
return () => {
|
|
661
553
|
this.#listeners.delete(listener);
|
|
662
554
|
};
|
|
663
555
|
}
|
|
664
|
-
/**
|
|
665
|
-
* 投影一份读数给渲染层。
|
|
666
|
-
* @param project - 用模型的当前读数造渲染要的状态。
|
|
667
|
-
* @returns 渲染层按选择器读的 store。
|
|
668
|
-
*/
|
|
669
556
|
bind(project) {
|
|
670
557
|
const store = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)(project());
|
|
671
558
|
this.subscribe(() => {
|
|
@@ -673,10 +560,6 @@ window.__ModuleLoader__.load({
|
|
|
673
560
|
});
|
|
674
561
|
return store;
|
|
675
562
|
}
|
|
676
|
-
/**
|
|
677
|
-
* 表单级状态(与上游 `SettingsForm` 的壳同形)。
|
|
678
|
-
* @returns 可用性、可写性、脏、非法、保存中、上次保存失败。
|
|
679
|
-
*/
|
|
680
563
|
shell() {
|
|
681
564
|
const { writes, blocked } = this.#plan();
|
|
682
565
|
return {
|
|
@@ -688,22 +571,9 @@ window.__ModuleLoader__.load({
|
|
|
688
571
|
failed: this.#failed
|
|
689
572
|
};
|
|
690
573
|
}
|
|
691
|
-
/**
|
|
692
|
-
* 保存前的整段校验(保存被 schema 挡下时非空)。
|
|
693
|
-
* @returns 失败消息与它的位置,或 `undefined`。
|
|
694
|
-
*/
|
|
695
574
|
violation() {
|
|
696
575
|
return this.#violation;
|
|
697
576
|
}
|
|
698
|
-
/**
|
|
699
|
-
* 读一个字段的草稿状态。
|
|
700
|
-
* @param path - 具体路径(动态段已按真实键或索引替换)。
|
|
701
|
-
* @param preview - 本轮已经算好的读数(`preview()` 的结果)。
|
|
702
|
-
*
|
|
703
|
-
* **容器的值要跟着别处的草稿走**:判别式 union 的选支就是容器的值,若这里读生效层,`type` 的切换控件会显示旧
|
|
704
|
-
* 的那一支(字段树却已经换了)。所以有 `preview` 就用它——控制器一次投影里只算一份。
|
|
705
|
-
* @returns 显示值、文本草稿、非法消息、是否覆盖。
|
|
706
|
-
*/
|
|
707
577
|
field(path, preview) {
|
|
708
578
|
const snapshot = this.#scope.getSnapshot();
|
|
709
579
|
const staged = this.#staged.get(pathKey(path));
|
|
@@ -749,23 +619,12 @@ window.__ModuleLoader__.load({
|
|
|
749
619
|
};
|
|
750
620
|
}
|
|
751
621
|
}
|
|
752
|
-
/**
|
|
753
|
-
* 暂存一个合法值。
|
|
754
|
-
* @param path - 具体路径。
|
|
755
|
-
* @param value - 控件给出的值。
|
|
756
|
-
*/
|
|
757
622
|
set(path, value) {
|
|
758
623
|
this.#stage(path, {
|
|
759
624
|
kind: "value",
|
|
760
625
|
value
|
|
761
626
|
});
|
|
762
627
|
}
|
|
763
|
-
/**
|
|
764
|
-
* 暂存一段文本,保存时才解析。
|
|
765
|
-
* @param path - 具体路径。
|
|
766
|
-
* @param text - 用户输入的原文。
|
|
767
|
-
* @param parse - 解析规则;失败时给出消息并挡住保存。
|
|
768
|
-
*/
|
|
769
628
|
setText(path, text, parse) {
|
|
770
629
|
this.#stage(path, {
|
|
771
630
|
kind: "text",
|
|
@@ -773,17 +632,9 @@ window.__ModuleLoader__.load({
|
|
|
773
632
|
parse
|
|
774
633
|
});
|
|
775
634
|
}
|
|
776
|
-
/**
|
|
777
|
-
* 暂存「恢复默认」。
|
|
778
|
-
* @param path - 具体路径。
|
|
779
|
-
*/
|
|
780
635
|
clear(path) {
|
|
781
636
|
this.#stage(path, { kind: "clear" });
|
|
782
637
|
}
|
|
783
|
-
/**
|
|
784
|
-
* 追加一个数组项,值取该项节点的默认值。
|
|
785
|
-
* @param path - 数组字段的具体路径。
|
|
786
|
-
*/
|
|
787
638
|
appendItem(path) {
|
|
788
639
|
const node = seekNode(this.#root, path);
|
|
789
640
|
if (node?.type !== "array") return;
|
|
@@ -791,21 +642,9 @@ window.__ModuleLoader__.load({
|
|
|
791
642
|
const index = Array.isArray(current) ? current.length : 0;
|
|
792
643
|
this.set([...path, String(index)], emptyValueOf(node.item));
|
|
793
644
|
}
|
|
794
|
-
/**
|
|
795
|
-
* 移除一个数组项。
|
|
796
|
-
* @param path - 数组字段的具体路径。
|
|
797
|
-
* @param index - 项在数组里的位置。
|
|
798
|
-
*/
|
|
799
645
|
removeItem(path, index) {
|
|
800
646
|
this.#stage([...path, String(index)], { kind: "drop" });
|
|
801
647
|
}
|
|
802
|
-
/**
|
|
803
|
-
* 给一层加一项,值取它的默认值。
|
|
804
|
-
*
|
|
805
|
-
* 字典认任意键;对象只认 schema 声明过的字段(写一个没声明的键会被 host 挡下,所以这里直接不写)。
|
|
806
|
-
* @param path - 容器的具体路径。
|
|
807
|
-
* @param key - 要加的键。
|
|
808
|
-
*/
|
|
809
648
|
addKey(path, key) {
|
|
810
649
|
const node = seekNode(this.#root, path);
|
|
811
650
|
if (node === void 0) return;
|
|
@@ -817,25 +656,15 @@ window.__ModuleLoader__.load({
|
|
|
817
656
|
if (field === void 0) return;
|
|
818
657
|
this.set([...path, key], emptyValueOf(field));
|
|
819
658
|
}
|
|
820
|
-
/**
|
|
821
|
-
* 删一个字典键。
|
|
822
|
-
* @param path - 字典字段的具体路径。
|
|
823
|
-
* @param key - 要删的键。
|
|
824
|
-
*/
|
|
825
659
|
removeKey(path, key) {
|
|
826
660
|
this.#stage([...path, key], { kind: "drop" });
|
|
827
661
|
}
|
|
828
|
-
/**
|
|
829
|
-
* 撤销一个字段的草稿(回到它当前的生效值)——行内编辑取消时用。
|
|
830
|
-
* @param path - 具体路径。
|
|
831
|
-
*/
|
|
832
662
|
revert(path) {
|
|
833
663
|
if (!this.#staged.delete(pathKey(path))) return;
|
|
834
664
|
this.#failed = false;
|
|
835
665
|
this.#violation = void 0;
|
|
836
666
|
this.#publish();
|
|
837
667
|
}
|
|
838
|
-
/** 丢弃全部草稿,回到 host 的读数。 */
|
|
839
668
|
discard() {
|
|
840
669
|
if (this.#staged.size === 0 && !this.#failed && this.#violation === void 0) return;
|
|
841
670
|
this.#staged.clear();
|
|
@@ -844,13 +673,6 @@ window.__ModuleLoader__.load({
|
|
|
844
673
|
this.#violation = void 0;
|
|
845
674
|
this.#publish();
|
|
846
675
|
}
|
|
847
|
-
/**
|
|
848
|
-
* 把全部草稿写成一次带 revision 栅栏的提交。
|
|
849
|
-
*
|
|
850
|
-
* host 是「值是否被接受」的唯一权威(有些约束 schema 表达不出来),所以落盘与否读回来决定,而不是在这里
|
|
851
|
-
* 预测;没落盘的保存保留草稿,让用户改而不是重打。
|
|
852
|
-
* @returns 写完与读回之后的收束。
|
|
853
|
-
*/
|
|
854
676
|
async save() {
|
|
855
677
|
const snapshot = this.#scope.getSnapshot();
|
|
856
678
|
if (this.#saving || !snapshot.writable) return;
|
|
@@ -886,12 +708,10 @@ window.__ModuleLoader__.load({
|
|
|
886
708
|
this.#publish();
|
|
887
709
|
}
|
|
888
710
|
}
|
|
889
|
-
/** 释放对共享配置表单的订阅。 */
|
|
890
711
|
dispose() {
|
|
891
712
|
this.#unsubscribe();
|
|
892
713
|
this.#listeners.clear();
|
|
893
714
|
}
|
|
894
|
-
/** 每条草稿这一轮会写什么;被 schema 挡下的草稿没有 op,但仍然是脏的。 */
|
|
895
715
|
#plan() {
|
|
896
716
|
const snapshot = this.#scope.getSnapshot();
|
|
897
717
|
const writes = [];
|
|
@@ -932,7 +752,6 @@ window.__ModuleLoader__.load({
|
|
|
932
752
|
blocked
|
|
933
753
|
};
|
|
934
754
|
}
|
|
935
|
-
/** 生效值套上本轮草稿后的段值:字段树按它展开(追加的数组项立刻出现),整段校验也看它。 */
|
|
936
755
|
preview() {
|
|
937
756
|
const ops = this.#plan().writes.flatMap((item) => item.op === void 0 ? [] : [item.op]);
|
|
938
757
|
return applyOps(this.#scope.getSnapshot().value ?? {}, ops);
|
|
@@ -951,17 +770,14 @@ window.__ModuleLoader__.load({
|
|
|
951
770
|
for (const listener of this.#listeners) listener();
|
|
952
771
|
}
|
|
953
772
|
};
|
|
954
|
-
/** 深比较:值相等就不写(避免把「改回原值」也当成一次写)。 */
|
|
955
773
|
function sameValue$1(left, right) {
|
|
956
774
|
if (Object.is(left, right)) return true;
|
|
957
775
|
if (typeof left !== "object" || typeof right !== "object" || left === null || right === null) return false;
|
|
958
776
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
959
777
|
}
|
|
960
|
-
/** 路径的 map 键。 */
|
|
961
778
|
function pathKey(path) {
|
|
962
779
|
return JSON.stringify(path);
|
|
963
780
|
}
|
|
964
|
-
/** 路径末段是否在用户层里存在(覆盖判据)。 */
|
|
965
781
|
function hasPath(root, path) {
|
|
966
782
|
if (path.length === 0) return root !== void 0;
|
|
967
783
|
const parent = readPath(root, path.slice(0, -1));
|
|
@@ -970,7 +786,6 @@ window.__ModuleLoader__.load({
|
|
|
970
786
|
if (typeof parent !== "object" || parent === null) return false;
|
|
971
787
|
return leaf in parent;
|
|
972
788
|
}
|
|
973
|
-
/** 把一组 op 套到一个段值上(本地读数用,不写 host)。 */
|
|
974
789
|
function applyOps(section, ops) {
|
|
975
790
|
const next = structuredClone(section);
|
|
976
791
|
for (const op of ops) {
|
|
@@ -989,26 +804,15 @@ window.__ModuleLoader__.load({
|
|
|
989
804
|
}
|
|
990
805
|
//#endregion
|
|
991
806
|
//#region src/client/schema-form/controller.ts
|
|
992
|
-
/** `role('select', { source })` 里声明的候选源名字。 */
|
|
993
807
|
function declaredSource(node) {
|
|
994
808
|
const extra = node?.meta.extra;
|
|
995
809
|
if (typeof extra !== "object" || extra === null) return void 0;
|
|
996
810
|
const name = Reflect.get(extra, "source");
|
|
997
811
|
return typeof name === "string" && name.length > 0 ? name : void 0;
|
|
998
812
|
}
|
|
999
|
-
/** 字段在状态表里的键(具体路径的 JSON)。 */
|
|
1000
813
|
function fieldKey(path) {
|
|
1001
814
|
return JSON.stringify(path);
|
|
1002
815
|
}
|
|
1003
|
-
/**
|
|
1004
|
-
* rehydrate 一段配置 schema,并判它能不能当页面的根。
|
|
1005
|
-
*
|
|
1006
|
-
* 能当根的形状有三种:对象段、**相交段**(共享判别字段 + 各分支的 `const` 标记,段值仍是对象)、以及带判别
|
|
1007
|
-
* 标签的 `union`(成员都是对象)。字面量 `union`(整段就是一个标量)不行:那一行的写盘会变成整段替换。
|
|
1008
|
-
* @param serialized - host 发来的 `schema.toJSON()`。
|
|
1009
|
-
* @param rehydrate - host schema 的 rehydrate。
|
|
1010
|
-
* @returns schema 与它的字段树,或 `undefined`(不能当页面根)。
|
|
1011
|
-
*/
|
|
1012
816
|
function projectRoot(serialized, rehydrate) {
|
|
1013
817
|
try {
|
|
1014
818
|
const schema = rehydrate(serialized);
|
|
@@ -1026,16 +830,9 @@ window.__ModuleLoader__.load({
|
|
|
1026
830
|
return;
|
|
1027
831
|
}
|
|
1028
832
|
}
|
|
1029
|
-
/**
|
|
1030
|
-
* 一行能否自动生成配置页:它的 schema 能 rehydrate 且根是 {@link projectRoot} 认的形状。
|
|
1031
|
-
* @param view - host 发来的命名空间视图。
|
|
1032
|
-
* @param rehydrate - host schema 的 rehydrate。
|
|
1033
|
-
* @returns 是否可以渲染。
|
|
1034
|
-
*/
|
|
1035
833
|
function canRender(view, rehydrate) {
|
|
1036
834
|
return view !== void 0 && projectRoot(view.schema, rehydrate) !== void 0;
|
|
1037
835
|
}
|
|
1038
|
-
/** 段根不是可渲染对象时的空字段树。 */
|
|
1039
836
|
const NO_FIELDS = {
|
|
1040
837
|
key: "",
|
|
1041
838
|
path: [],
|
|
@@ -1045,23 +842,11 @@ window.__ModuleLoader__.load({
|
|
|
1045
842
|
type: "object",
|
|
1046
843
|
fields: []
|
|
1047
844
|
};
|
|
1048
|
-
/** 值里已有的键(数组给索引)。 */
|
|
1049
845
|
function keysOf(value) {
|
|
1050
846
|
if (Array.isArray(value)) return value.map((_item, index) => String(index));
|
|
1051
847
|
if (typeof value === "object" && value !== null) return Object.keys(value);
|
|
1052
848
|
return [];
|
|
1053
849
|
}
|
|
1054
|
-
/**
|
|
1055
|
-
* 一个容器当前能加什么。
|
|
1056
|
-
*
|
|
1057
|
-
* 对象层(含相交与 union 的选中变体)是**非必填**、值里又没有的字段——必填的已经占着行了;字典层是业务注册的
|
|
1058
|
-
* 候选键(模式清单这类运行期数据)。数组层不在这里:它的「添加一项」是追加一个空项,没有键名可选。
|
|
1059
|
-
* @param node - 容器节点。
|
|
1060
|
-
* @param path - 该容器的具体路径。
|
|
1061
|
-
* @param value - 该容器自己的值(草稿后的)。
|
|
1062
|
-
* @param hints - 提示面(只用到候选键)。
|
|
1063
|
-
* @returns 可添加项,顺序即 schema / 读数的顺序;列表为空时页面不给输入框。
|
|
1064
|
-
*/
|
|
1065
850
|
function addableAt(node, path, value, hints) {
|
|
1066
851
|
const current = variantOf(node, value);
|
|
1067
852
|
if (current.type === "dict") {
|
|
@@ -1077,17 +862,6 @@ window.__ModuleLoader__.load({
|
|
|
1077
862
|
description: field.meta.description ?? field.meta.comment
|
|
1078
863
|
}] : []);
|
|
1079
864
|
}
|
|
1080
|
-
/**
|
|
1081
|
-
* 一个字段的可选项。
|
|
1082
|
-
*
|
|
1083
|
-
* 字面量集合的 union(`z.union(["wal", "delete"])`)自己就带着候选;其余看提示面注册的读数——读的时候把依赖的
|
|
1084
|
-
* 兄弟字段值喂进去,所以「选完服务商才列它有哪些模型」是投影的一部分,不是额外的一次订阅。
|
|
1085
|
-
* @param node - 字段节点。
|
|
1086
|
-
* @param path - 该字段的具体路径。
|
|
1087
|
-
* @param value - 段值(草稿后的)。
|
|
1088
|
-
* @param hints - 提示面。
|
|
1089
|
-
* @returns 可选项;没有候选就是 `undefined`。
|
|
1090
|
-
*/
|
|
1091
865
|
function optionsFor(node, path, value, hints) {
|
|
1092
866
|
const current = unwrapLazy(node);
|
|
1093
867
|
if (current?.type === "union" && current.choices !== void 0) return current.choices.map((choice) => ({ value: choice }));
|
|
@@ -1098,9 +872,6 @@ window.__ModuleLoader__.load({
|
|
|
1098
872
|
const options = spec.options((relative) => readPath(value, [...parent, ...relative]));
|
|
1099
873
|
return options.length === 0 ? void 0 : options;
|
|
1100
874
|
}
|
|
1101
|
-
/**
|
|
1102
|
-
* 一行配置页的控制器。
|
|
1103
|
-
*/
|
|
1104
875
|
var SchemaFormController = class {
|
|
1105
876
|
#ns;
|
|
1106
877
|
#deps;
|
|
@@ -1110,10 +881,6 @@ window.__ModuleLoader__.load({
|
|
|
1110
881
|
#view;
|
|
1111
882
|
#schema;
|
|
1112
883
|
#root;
|
|
1113
|
-
/**
|
|
1114
|
-
* @param ns - settings 命名空间(行 id)。
|
|
1115
|
-
* @param deps - 该行的共享表单与 schema 面。
|
|
1116
|
-
*/
|
|
1117
884
|
constructor(ns, deps) {
|
|
1118
885
|
this.#ns = ns;
|
|
1119
886
|
this.#deps = deps;
|
|
@@ -1132,7 +899,6 @@ window.__ModuleLoader__.load({
|
|
|
1132
899
|
deps.describe.ensure();
|
|
1133
900
|
this.#sync();
|
|
1134
901
|
}
|
|
1135
|
-
/** 槽位渲染器要的动作与状态。 */
|
|
1136
902
|
face() {
|
|
1137
903
|
return {
|
|
1138
904
|
hooks: { schemaForm: this.#store },
|
|
@@ -1168,20 +934,16 @@ window.__ModuleLoader__.load({
|
|
|
1168
934
|
}
|
|
1169
935
|
};
|
|
1170
936
|
}
|
|
1171
|
-
/** 本行的 settings 命名空间。 */
|
|
1172
937
|
namespace() {
|
|
1173
938
|
return this.#ns;
|
|
1174
939
|
}
|
|
1175
|
-
/** 外部提示(候选键这类)变了:重新投影一次,字段树与草稿都不动。 */
|
|
1176
940
|
refresh() {
|
|
1177
941
|
this.#store.set(this.#project());
|
|
1178
942
|
}
|
|
1179
|
-
/** 释放订阅。 */
|
|
1180
943
|
dispose() {
|
|
1181
944
|
for (const dispose of this.#disposers.splice(0)) dispose();
|
|
1182
945
|
this.#model.dispose();
|
|
1183
946
|
}
|
|
1184
|
-
/** 重新读 schema(describe 变了):只换字段树,草稿与 store 不动。 */
|
|
1185
947
|
#sync() {
|
|
1186
948
|
const view = this.#deps.describe.getSnapshot().view?.namespaces.find((row) => row.ns === this.#ns);
|
|
1187
949
|
this.#view = view;
|
|
@@ -1191,7 +953,6 @@ window.__ModuleLoader__.load({
|
|
|
1191
953
|
this.#model.setRoot(projected?.root ?? NO_FIELDS);
|
|
1192
954
|
this.#store.set(this.#project());
|
|
1193
955
|
}
|
|
1194
|
-
/** 整段校验:schema 先说话,再看数组的项身份有没有冲突。 */
|
|
1195
956
|
#validate(deps, value) {
|
|
1196
957
|
if (this.#schema !== void 0) {
|
|
1197
958
|
const failure = deps.validate(this.#schema, value);
|
|
@@ -1208,7 +969,6 @@ window.__ModuleLoader__.load({
|
|
|
1208
969
|
path: conflict.path
|
|
1209
970
|
};
|
|
1210
971
|
}
|
|
1211
|
-
/** 渲染层读的一份完整投影。 */
|
|
1212
972
|
#project() {
|
|
1213
973
|
const fields = /* @__PURE__ */ new Map();
|
|
1214
974
|
const root = this.#root;
|
|
@@ -1248,41 +1008,21 @@ window.__ModuleLoader__.load({
|
|
|
1248
1008
|
};
|
|
1249
1009
|
//#endregion
|
|
1250
1010
|
//#region src/client/schema-form/hints.ts
|
|
1251
|
-
/**
|
|
1252
|
-
* 通用表单的提示面:业务知道、而 schema 表达不了的结构信息。
|
|
1253
|
-
*
|
|
1254
|
-
* 两件事:**dict 字段的候选键**(例如「会话模式」那一行,键是运行期清单里的模式 id,schema 里写不出来),以及
|
|
1255
|
-
* **字段的候选值**(选服务商、选模型这类——候选来自部署里的另一份配置,还经常互相依赖)。业务在客户端注册一个
|
|
1256
|
-
* 同步读数,表单一侧在投影时用它补出「未配置」行或把字段画成选择器。
|
|
1257
|
-
*
|
|
1258
|
-
* 读数必须是**同步**的:投影发生在渲染帧里。异步来源(HTTP / remote)由业务自己取好再注册;注册本身就当作一次
|
|
1259
|
-
* 变更通知(所以"取到清单后再注册"是推荐用法)。
|
|
1260
|
-
*/
|
|
1261
|
-
/** 提示面的完整服务(业务用注册侧,表单一侧用读侧)。 */
|
|
1262
1011
|
var SchemaFormHints = class extends _deepseek_ai_cordis.Service {
|
|
1263
1012
|
#readers = /* @__PURE__ */ new Map();
|
|
1264
1013
|
#texts = /* @__PURE__ */ new Map();
|
|
1265
1014
|
#selects = /* @__PURE__ */ new Map();
|
|
1266
1015
|
#sources = /* @__PURE__ */ new Map();
|
|
1267
1016
|
#listeners = /* @__PURE__ */ new Set();
|
|
1268
|
-
/** @param ctx - 提供本服务的插件上下文。 */
|
|
1269
1017
|
constructor(ctx) {
|
|
1270
1018
|
super(ctx, "schemaFormHints");
|
|
1271
1019
|
}
|
|
1272
|
-
/** @param ns - settings 命名空间。 @param path - 字段路径。 @returns 该处的键。 */
|
|
1273
1020
|
keysFor(ns, path) {
|
|
1274
1021
|
return lookup(this.#readers, ns, path)?.() ?? [];
|
|
1275
1022
|
}
|
|
1276
|
-
/** @param ns - settings 命名空间。 @param path - 字段路径。 @returns 该处注册的文案。 */
|
|
1277
1023
|
textFor(ns, path) {
|
|
1278
1024
|
return lookup(this.#texts, ns, path)?.() ?? {};
|
|
1279
1025
|
}
|
|
1280
|
-
/**
|
|
1281
|
-
* @param ns - settings 命名空间。
|
|
1282
|
-
* @param path - 字段路径。
|
|
1283
|
-
* @param spec - 候选取法与依赖。
|
|
1284
|
-
* @returns 注销函数。
|
|
1285
|
-
*/
|
|
1286
1026
|
select(ns, path, spec) {
|
|
1287
1027
|
const key = locationKey(ns, path);
|
|
1288
1028
|
this.#selects.set(key, spec);
|
|
@@ -1293,15 +1033,9 @@ window.__ModuleLoader__.load({
|
|
|
1293
1033
|
this.#publish();
|
|
1294
1034
|
};
|
|
1295
1035
|
}
|
|
1296
|
-
/** @param ns - settings 命名空间。 @param path - 字段路径。 @returns 该处注册的候选读数。 */
|
|
1297
1036
|
selectFor(ns, path) {
|
|
1298
1037
|
return lookup(this.#selects, ns, path);
|
|
1299
1038
|
}
|
|
1300
|
-
/**
|
|
1301
|
-
* @param name - 源的名字。
|
|
1302
|
-
* @param spec - 候选取法与依赖。
|
|
1303
|
-
* @returns 注销函数。
|
|
1304
|
-
*/
|
|
1305
1039
|
source(name, spec) {
|
|
1306
1040
|
this.#sources.set(name, spec);
|
|
1307
1041
|
this.#publish();
|
|
@@ -1311,16 +1045,9 @@ window.__ModuleLoader__.load({
|
|
|
1311
1045
|
this.#publish();
|
|
1312
1046
|
};
|
|
1313
1047
|
}
|
|
1314
|
-
/** @param name - 源的名字。 @returns 该源注册的候选读数。 */
|
|
1315
1048
|
sourceFor(name) {
|
|
1316
1049
|
return this.#sources.get(name);
|
|
1317
1050
|
}
|
|
1318
|
-
/**
|
|
1319
|
-
* @param ns - settings 命名空间。
|
|
1320
|
-
* @param path - 字段路径。
|
|
1321
|
-
* @param read - 同步读数。
|
|
1322
|
-
* @returns 注销函数。
|
|
1323
|
-
*/
|
|
1324
1051
|
describe(ns, path, read) {
|
|
1325
1052
|
const key = locationKey(ns, path);
|
|
1326
1053
|
this.#texts.set(key, read);
|
|
@@ -1331,12 +1058,6 @@ window.__ModuleLoader__.load({
|
|
|
1331
1058
|
this.#publish();
|
|
1332
1059
|
};
|
|
1333
1060
|
}
|
|
1334
|
-
/**
|
|
1335
|
-
* @param ns - settings 命名空间。
|
|
1336
|
-
* @param path - 字段路径。
|
|
1337
|
-
* @param read - 同步读数。
|
|
1338
|
-
* @returns 注销函数。
|
|
1339
|
-
*/
|
|
1340
1061
|
suggestKeys(ns, path, read) {
|
|
1341
1062
|
const key = locationKey(ns, path);
|
|
1342
1063
|
this.#readers.set(key, read);
|
|
@@ -1347,20 +1068,12 @@ window.__ModuleLoader__.load({
|
|
|
1347
1068
|
this.#publish();
|
|
1348
1069
|
};
|
|
1349
1070
|
}
|
|
1350
|
-
/**
|
|
1351
|
-
* 观察候选键的变化(注册与注销)。
|
|
1352
|
-
* @param listener - 变更回调。
|
|
1353
|
-
* @returns 取消订阅。
|
|
1354
|
-
*/
|
|
1355
1071
|
subscribe(listener) {
|
|
1356
1072
|
this.#listeners.add(listener);
|
|
1357
1073
|
return () => {
|
|
1358
1074
|
this.#listeners.delete(listener);
|
|
1359
1075
|
};
|
|
1360
1076
|
}
|
|
1361
|
-
/**
|
|
1362
|
-
* 重新通知一次:读数背后的数据变了(业务自己知道),页面据此重算候选行。
|
|
1363
|
-
*/
|
|
1364
1077
|
refresh() {
|
|
1365
1078
|
this.#publish();
|
|
1366
1079
|
}
|
|
@@ -1368,19 +1081,9 @@ window.__ModuleLoader__.load({
|
|
|
1368
1081
|
for (const listener of this.#listeners) listener();
|
|
1369
1082
|
}
|
|
1370
1083
|
};
|
|
1371
|
-
/** 一处字段的定位键(命名空间 + 路径)。 */
|
|
1372
1084
|
function locationKey(ns, path) {
|
|
1373
1085
|
return `${ns}\u0000${path.join("\0")}`;
|
|
1374
1086
|
}
|
|
1375
|
-
/**
|
|
1376
|
-
* 按具体路径找一处注册:先精确匹配,再试**模板变体**(动态键 / 数组下标的段写成 `*`)。
|
|
1377
|
-
*
|
|
1378
|
-
* 业务因此可以注册一次 `models.*.provider` 就覆盖每个模式,不必知道运行期有哪些键。
|
|
1379
|
-
* @param map - 某类注册表。
|
|
1380
|
-
* @param ns - settings 命名空间。
|
|
1381
|
-
* @param path - 具体路径。
|
|
1382
|
-
* @returns 命中的注册,或 `undefined`。
|
|
1383
|
-
*/
|
|
1384
1087
|
function lookup(map, ns, path) {
|
|
1385
1088
|
const exact = map.get(locationKey(ns, path));
|
|
1386
1089
|
if (exact !== void 0) return exact;
|
|
@@ -1389,19 +1092,12 @@ window.__ModuleLoader__.load({
|
|
|
1389
1092
|
if (hit !== void 0) return hit;
|
|
1390
1093
|
}
|
|
1391
1094
|
}
|
|
1392
|
-
/** 具体路径的模板变体(首段是顶层字段名,保持原样):通配得多的先试。 */
|
|
1393
1095
|
function* templates(path) {
|
|
1394
1096
|
const wildcardable = Math.max(0, path.length - 1);
|
|
1395
1097
|
for (let bits = (1 << wildcardable) - 1; bits > 0; bits--) yield path.map((segment, index) => index > 0 && (bits & 1 << index - 1) !== 0 ? "*" : segment);
|
|
1396
1098
|
}
|
|
1397
1099
|
//#endregion
|
|
1398
1100
|
//#region src/client/schema-form/locales.ts
|
|
1399
|
-
/**
|
|
1400
|
-
* 本包字典(命名空间 `settings.schema-form`):表单壳、字段控件与只读呈现的文案。
|
|
1401
|
-
*
|
|
1402
|
-
* 字段自身的人话标签不在这里(那是业务方经字段槽或 schema `description` 给的),这里只放**机制**文案。
|
|
1403
|
-
*/
|
|
1404
|
-
/** 中文文案(部署语言偏好是 zh)。 */
|
|
1405
1101
|
const zh = {
|
|
1406
1102
|
unavailable: "该插件当前未加载,暂时无法配置。",
|
|
1407
1103
|
readOnly: "本部署的设置为只读。",
|
|
@@ -1456,7 +1152,6 @@ window.__ModuleLoader__.load({
|
|
|
1456
1152
|
duplicateItem: "重复的 {key}:{value}",
|
|
1457
1153
|
missingItem: "每一项都需要一个 {key}"
|
|
1458
1154
|
};
|
|
1459
|
-
/** 英文文案。 */
|
|
1460
1155
|
const en = {
|
|
1461
1156
|
unavailable: "This plugin is not loaded, so it cannot be configured right now.",
|
|
1462
1157
|
readOnly: "This deployment stores settings read-only.",
|
|
@@ -1513,12 +1208,6 @@ window.__ModuleLoader__.load({
|
|
|
1513
1208
|
};
|
|
1514
1209
|
//#endregion
|
|
1515
1210
|
//#region src/client/schema-form/rows.ts
|
|
1516
|
-
/**
|
|
1517
|
-
* 配对命名空间与 bundle 行清单。
|
|
1518
|
-
* @param namespaces - host 发来的命名空间视图。
|
|
1519
|
-
* @param bundles - host 的 bundle 清单(含各 bundle 声明的行 id)。
|
|
1520
|
-
* @returns 该自动注册的行,按命名空间视图的顺序。
|
|
1521
|
-
*/
|
|
1522
1211
|
function rowRegistrations(namespaces, bundles) {
|
|
1523
1212
|
const owners = /* @__PURE__ */ new Map();
|
|
1524
1213
|
for (const bundle of bundles) for (const row of bundle.rows) owners.set(row.rowId, bundle.name);
|
|
@@ -1534,28 +1223,15 @@ window.__ModuleLoader__.load({
|
|
|
1534
1223
|
}
|
|
1535
1224
|
return plans;
|
|
1536
1225
|
}
|
|
1537
|
-
/** 自动项的兜底优先级:手写卡片(默认 0)遮住它。 */
|
|
1538
1226
|
const FALLBACK_PRIORITY = 100;
|
|
1539
|
-
/**
|
|
1540
|
-
* 自动注册的差分器:按 describe 与 bundle 清单把「该有的行」与「已在的行」对齐。
|
|
1541
|
-
*/
|
|
1542
1227
|
var RowRegistration = class {
|
|
1543
1228
|
#deps;
|
|
1544
1229
|
#live = /* @__PURE__ */ new Map();
|
|
1545
1230
|
#pending;
|
|
1546
1231
|
#disposed = false;
|
|
1547
|
-
/**
|
|
1548
|
-
* @param deps - 槽位、清单读面与每行的槽项工厂。
|
|
1549
|
-
*/
|
|
1550
1232
|
constructor(deps) {
|
|
1551
1233
|
this.#deps = deps;
|
|
1552
1234
|
}
|
|
1553
|
-
/**
|
|
1554
|
-
* 对齐一次:新增该有的行,注销不该有的行。
|
|
1555
|
-
*
|
|
1556
|
-
* 并发调用折叠成一次(bundle 清单只读一趟),后到的调用等同一趟结束。
|
|
1557
|
-
* @returns 对齐完成。
|
|
1558
|
-
*/
|
|
1559
1235
|
sync() {
|
|
1560
1236
|
if (this.#disposed) return Promise.resolve();
|
|
1561
1237
|
this.#pending ??= this.#run().finally(() => {
|
|
@@ -1563,7 +1239,6 @@ window.__ModuleLoader__.load({
|
|
|
1563
1239
|
});
|
|
1564
1240
|
return this.#pending;
|
|
1565
1241
|
}
|
|
1566
|
-
/** 收掉自己:注销还活着的全部注册并释放它们的控制器。 */
|
|
1567
1242
|
dispose() {
|
|
1568
1243
|
this.#disposed = true;
|
|
1569
1244
|
this.#release();
|
|
@@ -1596,7 +1271,6 @@ window.__ModuleLoader__.load({
|
|
|
1596
1271
|
this.#live.set(ns, dispose);
|
|
1597
1272
|
}
|
|
1598
1273
|
}
|
|
1599
|
-
/** 注销全部并释放控制器。 */
|
|
1600
1274
|
#release() {
|
|
1601
1275
|
for (const [ns, dispose] of this.#live) {
|
|
1602
1276
|
dispose();
|
|
@@ -1607,7 +1281,6 @@ window.__ModuleLoader__.load({
|
|
|
1607
1281
|
};
|
|
1608
1282
|
//#endregion
|
|
1609
1283
|
//#region src/client/schema-form/labels.ts
|
|
1610
|
-
/** 字段的说明(`description` 本地化后 + `comment`),没有就是 undefined。 */
|
|
1611
1284
|
function fieldHint(node, resolveText) {
|
|
1612
1285
|
const parts = [];
|
|
1613
1286
|
if (node.meta.description !== void 0) parts.push(resolveText(node.meta.description));
|
|
@@ -1616,21 +1289,6 @@ window.__ModuleLoader__.load({
|
|
|
1616
1289
|
}
|
|
1617
1290
|
//#endregion
|
|
1618
1291
|
//#region src/client/schema-form/lines.ts
|
|
1619
|
-
/**
|
|
1620
|
-
* 行模型:把字段树(`walkFields` 的结果)排成**编辑器的行**——结构行(`{` / `[` / `}` / `]`)、字段行、注释行、
|
|
1621
|
-
* 以及容器尾部的添加行。
|
|
1622
|
-
*
|
|
1623
|
-
* 渲染层因此只按行画,不必自己递归:行序 = 视觉顺序,行号连续。折叠在这里生效(折叠的容器只留开启行)。
|
|
1624
|
-
*/
|
|
1625
|
-
/**
|
|
1626
|
-
* 容器的形状:对象类用 `{}`,序列类用 `[]`。
|
|
1627
|
-
*
|
|
1628
|
-
* union 没有自己的形状——它跟着**当前选中那一支**走(`access` 是数组就在页面上画 `[`),这也是
|
|
1629
|
-
* `access: string | string[]` 这类字段以前被画成对象容器的原因。
|
|
1630
|
-
* @param node - 字段节点。
|
|
1631
|
-
* @param value - 这一层的值(选支判据)。
|
|
1632
|
-
* @returns 形状;叶子节点是 `undefined`。
|
|
1633
|
-
*/
|
|
1634
1292
|
function containerShape(node, value) {
|
|
1635
1293
|
switch (variantOf(node, value).type) {
|
|
1636
1294
|
case "object":
|
|
@@ -1641,7 +1299,6 @@ window.__ModuleLoader__.load({
|
|
|
1641
1299
|
default: return;
|
|
1642
1300
|
}
|
|
1643
1301
|
}
|
|
1644
|
-
/** 渲染顺序里可见的字段:折叠的容器把整棵子树滤掉,只留它自己的开启行。 */
|
|
1645
1302
|
function visibleFields(state, fold) {
|
|
1646
1303
|
const containers = new Set(state.walked.filter((item) => containerShape(item.node, valueAt(state, item.path)) !== void 0).map((item) => fieldKey(item.path)));
|
|
1647
1304
|
return state.walked.filter((item) => {
|
|
@@ -1652,11 +1309,9 @@ window.__ModuleLoader__.load({
|
|
|
1652
1309
|
return true;
|
|
1653
1310
|
});
|
|
1654
1311
|
}
|
|
1655
|
-
/** 这一层的当前值:与字段行显示的是同一份读数(草稿优先)。 */
|
|
1656
1312
|
function valueAt(state, path) {
|
|
1657
1313
|
return state.fields.get(fieldKey(path))?.value;
|
|
1658
1314
|
}
|
|
1659
|
-
/** 这一层能加什么:数组追加空项,字典敲键名(可来自候选),对象从还没配的声明字段里选。 */
|
|
1660
1315
|
function addOf(node, shape, options) {
|
|
1661
1316
|
if (shape === "array") return { kind: "item" };
|
|
1662
1317
|
if (unwrapLazy(node)?.type === "dict") return {
|
|
@@ -1668,7 +1323,6 @@ window.__ModuleLoader__.load({
|
|
|
1668
1323
|
options
|
|
1669
1324
|
};
|
|
1670
1325
|
}
|
|
1671
|
-
/** 一个节点上的变体切换控件:不是 union、或只有一支、或成员全是常量(字面量集合是选择器)时没有。 */
|
|
1672
1326
|
function variantsOf(node, value) {
|
|
1673
1327
|
const union = unionOf(node);
|
|
1674
1328
|
if (union === void 0 || union.choices !== void 0 || union.variants.length < 2) return void 0;
|
|
@@ -1677,14 +1331,6 @@ window.__ModuleLoader__.load({
|
|
|
1677
1331
|
selected: union.variants.indexOf(variantOf(union, value))
|
|
1678
1332
|
};
|
|
1679
1333
|
}
|
|
1680
|
-
/**
|
|
1681
|
-
* 排成行序列。
|
|
1682
|
-
* @param state - 页面读数。
|
|
1683
|
-
* @param fold - 折叠状态。
|
|
1684
|
-
* @param resolveText - `description` 的本地化。
|
|
1685
|
-
* @param t - 本包字典。
|
|
1686
|
-
* @returns 行序列(行号即下标)。
|
|
1687
|
-
*/
|
|
1688
1334
|
function editorLines(state, fold, resolveText, t) {
|
|
1689
1335
|
const lines = [];
|
|
1690
1336
|
const open = [];
|
|
@@ -1773,10 +1419,6 @@ window.__ModuleLoader__.load({
|
|
|
1773
1419
|
}
|
|
1774
1420
|
return moveVariantControlToTag(lines);
|
|
1775
1421
|
}
|
|
1776
|
-
/**
|
|
1777
|
-
* 判别式 union 的切换控件挪到**标签字段那一行**上:`session-rdb` 的页面上用户改的就是 `type`,把它放在容器
|
|
1778
|
-
* 行上等于让人在两处看同一件事。没有标签字段(`access` 这种按值形状选的)就留在 union 自己那一行。
|
|
1779
|
-
*/
|
|
1780
1422
|
function moveVariantControlToTag(lines) {
|
|
1781
1423
|
for (const line of lines) {
|
|
1782
1424
|
if (line.kind === "comment" || line.kind === "close") continue;
|
|
@@ -1792,12 +1434,6 @@ window.__ModuleLoader__.load({
|
|
|
1792
1434
|
}
|
|
1793
1435
|
return lines;
|
|
1794
1436
|
}
|
|
1795
|
-
/**
|
|
1796
|
-
* 一行的注释:业务给的文案(字段槽的 label/hint)优先,其次 schema 的说明。
|
|
1797
|
-
*
|
|
1798
|
-
* 这一行有问题时**注释位让给那条消息**(参考实现也是把错误画在行尾那一段的位置上)——说明换成"哪里不对",
|
|
1799
|
-
* 错误因此不必挤在值后面。
|
|
1800
|
-
*/
|
|
1801
1437
|
function commentFor(state, item, resolveText, t) {
|
|
1802
1438
|
const key = fieldKey(item.path);
|
|
1803
1439
|
const failure = state.fields.get(key)?.invalid ?? state.invalidAt.get(key);
|
|
@@ -1817,7 +1453,6 @@ window.__ModuleLoader__.load({
|
|
|
1817
1453
|
invalid: false
|
|
1818
1454
|
};
|
|
1819
1455
|
}
|
|
1820
|
-
/** `path` 是不是 `prefix` 的后代(含自身)。 */
|
|
1821
1456
|
function isPrefix(prefix, path) {
|
|
1822
1457
|
if (prefix.length > path.length) return false;
|
|
1823
1458
|
return prefix.every((segment, index) => path[index] === segment);
|
|
@@ -2100,10 +1735,6 @@ window.__ModuleLoader__.load({
|
|
|
2100
1735
|
//#endregion
|
|
2101
1736
|
//#region src/client/styling/styled.tsx
|
|
2102
1737
|
const sxSymbol = Symbol("styled.sx");
|
|
2103
|
-
/**
|
|
2104
|
-
* `styled('div')({ padding: '4px' }, { '&:hover': { … } })` → 带 `data-css-*` 的组件。
|
|
2105
|
-
* 多个样式对象按顺序深合并(后者覆盖前者),便于把变体叠在基样式上。
|
|
2106
|
-
*/
|
|
2107
1738
|
function styled(Component, defaultProps = {}) {
|
|
2108
1739
|
return (...styles) => {
|
|
2109
1740
|
const sx = styles.reduce((merged, style) => toMerged(merged, style), {});
|
|
@@ -2120,19 +1751,11 @@ window.__ModuleLoader__.load({
|
|
|
2120
1751
|
return Object.assign(Styled, { [sxSymbol]: sx });
|
|
2121
1752
|
};
|
|
2122
1753
|
}
|
|
2123
|
-
/** 取回 styled 组件携带的样式对象(需要在其之上继续组合时用)。 */
|
|
2124
1754
|
function styleOf(component) {
|
|
2125
1755
|
return component[sxSymbol];
|
|
2126
1756
|
}
|
|
2127
1757
|
//#endregion
|
|
2128
1758
|
//#region src/client/theme.generated.ts
|
|
2129
|
-
/**
|
|
2130
|
-
* 官方主题的 `--dsw-*` token 全集(400 个),由 packages/client/ui-primitives/scripts/gen-design-tokens.mts 生成,勿手改。
|
|
2131
|
-
*
|
|
2132
|
-
* 叶子是该变量的**默认值**(官方 light 主题里首次出现的定义):既是类型推导的来源,也可作 fallback。
|
|
2133
|
-
* `"$"` 键出现在「自身也是 token 的分支」上(命名体系里有 40 个短名同时是长名前缀的 token)。
|
|
2134
|
-
* 消费时只需树形状——`Token.vars()` 生成 `var(--dsw-…)`,不重新定义变量。
|
|
2135
|
-
* 漂移、往返与默认值完整性由 src/__tests__/design-tokens.spec.ts 守卫。 */
|
|
2136
1759
|
const designTokens = {
|
|
2137
1760
|
alias: {
|
|
2138
1761
|
bg: {
|
|
@@ -2808,36 +2431,18 @@ window.__ModuleLoader__.load({
|
|
|
2808
2431
|
const dsw = Token.vars(designTokens, { prefix: "dsw" });
|
|
2809
2432
|
//#endregion
|
|
2810
2433
|
//#region src/client/schema-form/styles.ts
|
|
2811
|
-
/**
|
|
2812
|
-
* 行式编辑器的样式:等宽、行号、语法色、悬停行为——全部消费官方 `--dsw-*` token。
|
|
2813
|
-
*
|
|
2814
|
-
* 几何只有一套:行高、行内间距、动作按钮尺寸都由下面几个常量给,行与行之间不各自决定。
|
|
2815
|
-
*/
|
|
2816
|
-
/** 等宽字体栈:配置值的观感与代码一致。 */
|
|
2817
2434
|
const MONO = "ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace";
|
|
2818
|
-
/** 行高:行内容、行内输入、折叠箭头、按钮排都在这一档上对齐。 */
|
|
2819
2435
|
const ROW = "22px";
|
|
2820
|
-
/** 行内元素之间的间距。 */
|
|
2821
2436
|
const GAP = "6px";
|
|
2822
|
-
/** 动作按钮:方形图标键,尺寸与行高同一档的紧凑版。 */
|
|
2823
2437
|
const ACTION = "20px";
|
|
2824
|
-
/** 动作按钮之间的间距(比行内间距紧一档)。 */
|
|
2825
2438
|
const ACTION_GAP = "4px";
|
|
2826
|
-
/** 折叠列(chevron 与它的占位)宽度。 */
|
|
2827
2439
|
const FOLD = "14px";
|
|
2828
|
-
/** 行号列宽度。 */
|
|
2829
2440
|
const NUMBER = "32px";
|
|
2830
|
-
/** 行尾留白。 */
|
|
2831
2441
|
const PAD = "8px";
|
|
2832
|
-
/** 细线:theme 给的 0.5px 描边(`0 0 0 0.5px var(--dsw-elevation-stroke-color)`)。 */
|
|
2833
2442
|
const HAIRLINE = dsw.elevation.stroke;
|
|
2834
|
-
/** 描边色:theme 里给描边用的那一档(与官方 `Menu` 面板同源)。 */
|
|
2835
2443
|
const LINE = dsw.elevation.stroke.color;
|
|
2836
|
-
/** 行左侧那道「改过」的标记宽度。 */
|
|
2837
2444
|
const EDGE = "2px";
|
|
2838
|
-
/** 出错那一行的底色:theme 的红掺进透明里(半透明红),比整行实红轻、又比只有一道条明显。 */
|
|
2839
2445
|
const ERROR_WASH = `color-mix(in srgb, ${String(dsw.alias.state.error.secondary)} 12%, transparent)`;
|
|
2840
|
-
/** 编辑器的根:纵向排列的行。 */
|
|
2841
2446
|
const EditorRoot = styled("div")({
|
|
2842
2447
|
display: "flex",
|
|
2843
2448
|
flexDirection: "column",
|
|
@@ -2847,7 +2452,6 @@ window.__ModuleLoader__.load({
|
|
|
2847
2452
|
lineHeight: ROW,
|
|
2848
2453
|
color: dsw.alias.label.primary
|
|
2849
2454
|
});
|
|
2850
|
-
/** 一行:行号 + 折进 + 行内容。悬停给一层浅底。 */
|
|
2851
2455
|
const LineRow = styled("div")({
|
|
2852
2456
|
display: "flex",
|
|
2853
2457
|
alignItems: "flex-start",
|
|
@@ -2868,7 +2472,6 @@ window.__ModuleLoader__.load({
|
|
|
2868
2472
|
"&[data-invalid='true'] [data-role='key']": { color: dsw.alias.state.error.primary },
|
|
2869
2473
|
"&:hover [data-role='actions']": { opacity: 1 }
|
|
2870
2474
|
});
|
|
2871
|
-
/** 一行的内容区:**缩进只作用在这里**——行号列固定在左边,缩进不推它。 */
|
|
2872
2475
|
const LineBody = styled("div")({
|
|
2873
2476
|
display: "flex",
|
|
2874
2477
|
alignItems: "center",
|
|
@@ -2877,7 +2480,6 @@ window.__ModuleLoader__.load({
|
|
|
2877
2480
|
minWidth: 0,
|
|
2878
2481
|
minHeight: ROW
|
|
2879
2482
|
});
|
|
2880
|
-
/** 行号列:右对齐、不可选、淡色。 */
|
|
2881
2483
|
const LineNumber = styled("span")({
|
|
2882
2484
|
flex: "none",
|
|
2883
2485
|
width: NUMBER,
|
|
@@ -2887,7 +2489,6 @@ window.__ModuleLoader__.load({
|
|
|
2887
2489
|
userSelect: "none",
|
|
2888
2490
|
cursor: "pointer"
|
|
2889
2491
|
});
|
|
2890
|
-
/** 折叠 chevron(官方箭头图标,尺寸与别的行内图标同源)。 */
|
|
2891
2492
|
const LineFold = styled("button")({
|
|
2892
2493
|
display: "inline-flex",
|
|
2893
2494
|
alignItems: "center",
|
|
@@ -2902,23 +2503,19 @@ window.__ModuleLoader__.load({
|
|
|
2902
2503
|
cursor: "pointer",
|
|
2903
2504
|
"&:hover": { color: dsw.alias.label.secondary }
|
|
2904
2505
|
});
|
|
2905
|
-
/** 叶子行的折叠占位(保持列对齐)。 */
|
|
2906
2506
|
const LineFoldSpacer = styled("span")({
|
|
2907
2507
|
flex: "none",
|
|
2908
2508
|
width: FOLD
|
|
2909
2509
|
});
|
|
2910
|
-
/** 键名(配置键原样;数组下标淡一些)。 */
|
|
2911
2510
|
const LineKey = styled("span")({
|
|
2912
2511
|
flex: "none",
|
|
2913
2512
|
color: dsw.alias.label.primary,
|
|
2914
2513
|
"&[data-index='true']": { color: dsw.alias.label.caption }
|
|
2915
2514
|
});
|
|
2916
|
-
/** 结构符与标点(`{` `}` `[` `]` `:`)。 */
|
|
2917
2515
|
const LineToken = styled("span")({
|
|
2918
2516
|
flex: "none",
|
|
2919
2517
|
color: dsw.alias.label.caption
|
|
2920
2518
|
});
|
|
2921
|
-
/** 值(token):字符串 / 数字 / 布尔各有语法色。 */
|
|
2922
2519
|
const LineValue = styled("span")({
|
|
2923
2520
|
minWidth: 0,
|
|
2924
2521
|
overflow: "hidden",
|
|
@@ -2929,9 +2526,6 @@ window.__ModuleLoader__.load({
|
|
|
2929
2526
|
"&[data-tone='boolean']": { color: dsw.alias.state.success.primary },
|
|
2930
2527
|
"&[data-tone='empty']": { color: dsw.alias.label.caption }
|
|
2931
2528
|
});
|
|
2932
|
-
/**
|
|
2933
|
-
* 值上的下拉触发:**它自己就是值的呈现**(不再是「值 + 一个下拉」两遍),点开才出菜单。
|
|
2934
|
-
*/
|
|
2935
2529
|
const ValueTrigger = styled("button")({
|
|
2936
2530
|
display: "inline-flex",
|
|
2937
2531
|
alignItems: "center",
|
|
@@ -2947,11 +2541,6 @@ window.__ModuleLoader__.load({
|
|
|
2947
2541
|
"&:disabled": { cursor: "default" },
|
|
2948
2542
|
"&:focus-visible": { outline: "none" }
|
|
2949
2543
|
});
|
|
2950
|
-
/**
|
|
2951
|
-
* 行内输入的外壳:官方 `Input` 压到与值同一行高,并**吃掉剩下的宽度**。
|
|
2952
|
-
*
|
|
2953
|
-
* 行内编辑与容器闭合行的添加输入共用它——两处的输入框因此长得一模一样。
|
|
2954
|
-
*/
|
|
2955
2544
|
const CompactInput = styled("span")({
|
|
2956
2545
|
display: "inline-flex",
|
|
2957
2546
|
flex: "1 1 auto",
|
|
@@ -2967,10 +2556,6 @@ window.__ModuleLoader__.load({
|
|
|
2967
2556
|
lineHeight: ROW
|
|
2968
2557
|
}
|
|
2969
2558
|
});
|
|
2970
|
-
/**
|
|
2971
|
-
* 多行值的行内输入:官方没有多行原子,所以这里用同一套观感的字段壳(同边框、同圆角、同焦点色),
|
|
2972
|
-
* 高度跟着内容长、宽度同样撑满剩下的位置。
|
|
2973
|
-
*/
|
|
2974
2559
|
const CompactTextField = styled("span")({
|
|
2975
2560
|
display: "inline-flex",
|
|
2976
2561
|
flex: "1 1 auto",
|
|
@@ -2998,7 +2583,6 @@ window.__ModuleLoader__.load({
|
|
|
2998
2583
|
color: dsw.alias.label.primary
|
|
2999
2584
|
}
|
|
3000
2585
|
});
|
|
3001
|
-
/** 行内注释(schema 说明与业务文案):比配置值小一档,占满这一行剩下的宽度,长了就截断(hover 看全文)。 */
|
|
3002
2586
|
const LineComment = styled("span")({
|
|
3003
2587
|
flex: "1 1 auto",
|
|
3004
2588
|
minWidth: 0,
|
|
@@ -3008,16 +2592,12 @@ window.__ModuleLoader__.load({
|
|
|
3008
2592
|
fontFamily: "inherit",
|
|
3009
2593
|
fontSize: "12px"
|
|
3010
2594
|
});
|
|
3011
|
-
/** 行内的校验消息(与注释放同一档字号)。 */
|
|
3012
2595
|
const LineInvalid = styled("span")({
|
|
3013
2596
|
flex: "none",
|
|
3014
2597
|
color: dsw.alias.state.error.primary,
|
|
3015
2598
|
fontFamily: "inherit",
|
|
3016
2599
|
fontSize: "12px"
|
|
3017
2600
|
});
|
|
3018
|
-
/**
|
|
3019
|
-
* 悬停行时才显示这一行的动作按钮:显形规则写在行上(`[data-role='actions']`),组件只需要带这个属性。
|
|
3020
|
-
*/
|
|
3021
2601
|
const HoverActions = styled(styled("span")({
|
|
3022
2602
|
display: "inline-flex",
|
|
3023
2603
|
alignItems: "center",
|
|
@@ -3045,12 +2625,7 @@ window.__ModuleLoader__.load({
|
|
|
3045
2625
|
color: dsw.alias.label.dimmed
|
|
3046
2626
|
}
|
|
3047
2627
|
}
|
|
3048
|
-
}))({
|
|
3049
|
-
/**
|
|
3050
|
-
* 编辑态的两个按钮常显,而且**要有底色与描边**:行底色上只有淡色图标是看不见的,
|
|
3051
|
-
* 所以这里给实体按钮(theme 的描边 + 一层底),确认用成功档、取消用中性档。
|
|
3052
|
-
*/
|
|
3053
|
-
"&[data-editing='true']": {
|
|
2628
|
+
}))({ "&[data-editing='true']": {
|
|
3054
2629
|
opacity: 1,
|
|
3055
2630
|
gap: GAP,
|
|
3056
2631
|
"& > button": {
|
|
@@ -3060,7 +2635,6 @@ window.__ModuleLoader__.load({
|
|
|
3060
2635
|
"&:first-of-type": { color: dsw.alias.state.success.primary }
|
|
3061
2636
|
}
|
|
3062
2637
|
} });
|
|
3063
|
-
/** 添加输入:**跟在容器的闭合括号同一行**(菜单锚在它上面),宽度吃掉剩下的位置。 */
|
|
3064
2638
|
const AddWrap = styled("span")({
|
|
3065
2639
|
position: "relative",
|
|
3066
2640
|
display: "inline-flex",
|
|
@@ -3071,7 +2645,6 @@ window.__ModuleLoader__.load({
|
|
|
3071
2645
|
marginLeft: GAP,
|
|
3072
2646
|
"&[data-invalid='true'] input": { borderColor: dsw.alias.state.error.primary }
|
|
3073
2647
|
});
|
|
3074
|
-
/** 一行说明(不可用 / 没有可生成项时用)。 */
|
|
3075
2648
|
const Hint$1 = styled("p")({
|
|
3076
2649
|
margin: 0,
|
|
3077
2650
|
fontSize: "12px",
|
|
@@ -3080,42 +2653,27 @@ window.__ModuleLoader__.load({
|
|
|
3080
2653
|
});
|
|
3081
2654
|
//#endregion
|
|
3082
2655
|
//#region src/client/schema-form/value.tsx
|
|
3083
|
-
/**
|
|
3084
|
-
* 行内值的呈现与编辑文本规则,以及字段槽注册方复用的默认值组件。
|
|
3085
|
-
*
|
|
3086
|
-
* 值本身仍走草稿:这个文件只决定"怎么显示"(语法色 token、下拉触发的呈现)与"怎么编辑"(一行输入框里的文本
|
|
3087
|
-
* 形状——字符串把换行写成 `\n`,与一行的输入框相配)。
|
|
3088
|
-
*/
|
|
3089
|
-
/** 编辑态文本:字符串就是原文(换行照旧),其余按 JSON 写。 */
|
|
3090
2656
|
function valueText(value) {
|
|
3091
2657
|
if (value === void 0 || value === null) return "";
|
|
3092
2658
|
if (typeof value === "string") return value;
|
|
3093
2659
|
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
3094
2660
|
return JSON.stringify(value);
|
|
3095
2661
|
}
|
|
3096
|
-
/** 这个值要在多行输入里编辑吗(字符串带换行)。 */
|
|
3097
2662
|
function isMultiline(value) {
|
|
3098
2663
|
return typeof value === "string" && /[\r\n]/.test(value);
|
|
3099
2664
|
}
|
|
3100
|
-
/** 显示态文本:字符串带引号;多行与特殊字符都按 JSON 的写法收在一行里。 */
|
|
3101
2665
|
function tokenText(value) {
|
|
3102
2666
|
if (value === void 0 || value === null) return "null";
|
|
3103
2667
|
if (typeof value === "string") return JSON.stringify(value);
|
|
3104
2668
|
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
3105
2669
|
return JSON.stringify(value);
|
|
3106
2670
|
}
|
|
3107
|
-
/** 值的语法色分组。 */
|
|
3108
2671
|
function tokenTone(value) {
|
|
3109
2672
|
if (value === void 0 || value === null) return "empty";
|
|
3110
2673
|
if (typeof value === "number") return "number";
|
|
3111
2674
|
if (typeof value === "boolean") return "boolean";
|
|
3112
2675
|
return "string";
|
|
3113
2676
|
}
|
|
3114
|
-
/**
|
|
3115
|
-
* 行内值的默认呈现(显示态 token)。
|
|
3116
|
-
* @param props.owner - 字段上下文(值、是否 secret、只读原因)。
|
|
3117
|
-
* @returns 一行里的值节点。
|
|
3118
|
-
*/
|
|
3119
2677
|
function InlineValue({ owner }) {
|
|
3120
2678
|
if (owner.options !== void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OptionSelect, {
|
|
3121
2679
|
owner,
|
|
@@ -3132,11 +2690,6 @@ window.__ModuleLoader__.load({
|
|
|
3132
2690
|
children: tokenText(owner.value)
|
|
3133
2691
|
});
|
|
3134
2692
|
}
|
|
3135
|
-
/**
|
|
3136
|
-
* 选择器:**值本身**就是下拉的触发(不再另画一个 `select` 与它并列),点开是官方菜单。
|
|
3137
|
-
*
|
|
3138
|
-
* 当前值不在候选里时(例如目录里已经没有那个服务商)照原样显示,选中才换成新值。
|
|
3139
|
-
*/
|
|
3140
2693
|
function OptionSelect({ owner, options }) {
|
|
3141
2694
|
const [open, setOpen] = (0, react.useState)(false);
|
|
3142
2695
|
const selected = options.findIndex((option) => sameValue(option.value, owner.value));
|
|
@@ -3175,31 +2728,16 @@ window.__ModuleLoader__.load({
|
|
|
3175
2728
|
})
|
|
3176
2729
|
});
|
|
3177
2730
|
}
|
|
3178
|
-
/** 值是否就是这一项(菜单的高亮判据)。 */
|
|
3179
2731
|
function sameValue(left, right) {
|
|
3180
2732
|
if (Object.is(left, right)) return true;
|
|
3181
2733
|
if (typeof left !== "object" || typeof right !== "object" || left === null || right === null) return false;
|
|
3182
2734
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
3183
2735
|
}
|
|
3184
|
-
/**
|
|
3185
|
-
* 字段槽注册方复用的默认值:只改文案时拿它包一层即可(`owner` 上覆盖 `label`/`hint`)。
|
|
3186
|
-
*
|
|
3187
|
-
* 注意行式视图里键名来自配置键本身、注释来自 `hint`,所以 `label` 只在业务自写控件时才需要。
|
|
3188
|
-
*/
|
|
3189
2736
|
function SchemaFieldDefault({ owner }) {
|
|
3190
2737
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InlineValue, { owner });
|
|
3191
2738
|
}
|
|
3192
2739
|
//#endregion
|
|
3193
2740
|
//#region src/client/schema-form/fields.tsx
|
|
3194
|
-
/**
|
|
3195
|
-
* 各类型的文本解析规则(消息来自本包字典)。
|
|
3196
|
-
*
|
|
3197
|
-
* 解析不只看类型:schema 上声明的界限(`pattern` / `min` / `max` / `step`)与字面量集合都在这里说话——
|
|
3198
|
-
* 用户敲完当场就知道对不对,而不是等保存时被整段校验退回。
|
|
3199
|
-
* @param node - 字段节点(`meta` 是约束的来源)。
|
|
3200
|
-
* @param t - 本包字典。
|
|
3201
|
-
* @returns 这段文本的解析规则。
|
|
3202
|
-
*/
|
|
3203
2741
|
function parseFor(node, t) {
|
|
3204
2742
|
switch (node.type) {
|
|
3205
2743
|
case "number": return (text) => {
|
|
@@ -3313,20 +2851,6 @@ window.__ModuleLoader__.load({
|
|
|
3313
2851
|
}
|
|
3314
2852
|
//#endregion
|
|
3315
2853
|
//#region src/client/schema-form/Editor.tsx
|
|
3316
|
-
/**
|
|
3317
|
-
* 行式编辑器:按 `editorLines` 排出的行画 JSON 结构视图——行号、折进、`key: value`、结构行、注释行、容器闭合行
|
|
3318
|
-
* 后面的添加入口;值在行内点开编辑(Enter 提交 / Esc 撤销)。
|
|
3319
|
-
*
|
|
3320
|
-
* 缩进只作用在**行内容**上(行号列固定),添加输入与闭合括号同一行;行内控件用官方的 `Input` / `Menu` / `Button`,
|
|
3321
|
-
* 与设置页其余部分同一套视觉与键盘行为。
|
|
3322
|
-
*
|
|
3323
|
-
* 字段槽仍然生效:命中时该字段**值**的位置换成注册方的组件,键名、注释、行号与行为按钮仍由这里画。
|
|
3324
|
-
*/
|
|
3325
|
-
/**
|
|
3326
|
-
* 画一行的配置。
|
|
3327
|
-
* @param props - 读数、动作、字典与字段槽入口。
|
|
3328
|
-
* @returns 行式编辑器。
|
|
3329
|
-
*/
|
|
3330
2854
|
function Editor(props) {
|
|
3331
2855
|
const { ns, state, face, t, resolveText, renderField } = props;
|
|
3332
2856
|
const [toggled, setToggled] = (0, react.useState)(() => /* @__PURE__ */ new Set());
|
|
@@ -3377,7 +2901,6 @@ window.__ModuleLoader__.load({
|
|
|
3377
2901
|
}, `${line.kind}:${fieldKey(line.path)}:${String(index)}`))
|
|
3378
2902
|
});
|
|
3379
2903
|
}
|
|
3380
|
-
/** 折叠状态:记「用户切换过的路径」,开合 = 切换过就反转 schema 的默认值。 */
|
|
3381
2904
|
function useFold(state, toggled, setToggled) {
|
|
3382
2905
|
const defaults = (0, react.useMemo)(() => {
|
|
3383
2906
|
const collapsed = /* @__PURE__ */ new Set();
|
|
@@ -3405,7 +2928,6 @@ window.__ModuleLoader__.load({
|
|
|
3405
2928
|
toggled
|
|
3406
2929
|
]);
|
|
3407
2930
|
}
|
|
3408
|
-
/** 一行的渲染分派。 */
|
|
3409
2931
|
function Row({ line, n, ns, state, face, fold, t, resolveText, disabled, editing, selected, dismissed, onEdit, onAdded, onDismiss, onSelect, renderField }) {
|
|
3410
2932
|
const key = fieldKey(line.path);
|
|
3411
2933
|
const indent = { paddingLeft: `${String(line.depth * 14)}px` };
|
|
@@ -3485,7 +3007,6 @@ window.__ModuleLoader__.load({
|
|
|
3485
3007
|
})]
|
|
3486
3008
|
});
|
|
3487
3009
|
}
|
|
3488
|
-
/** 字段行:`key: value`(数组成员的键名是下标,淡一些)。 */
|
|
3489
3010
|
function FieldLine({ line, ns, state, face, t, disabled, editing, dismissed, onEdit, onDismiss, renderField }) {
|
|
3490
3011
|
const key = fieldKey(line.path);
|
|
3491
3012
|
const member = line.member;
|
|
@@ -3642,11 +3163,9 @@ window.__ModuleLoader__.load({
|
|
|
3642
3163
|
})
|
|
3643
3164
|
] });
|
|
3644
3165
|
}
|
|
3645
|
-
/** 复制用的文本(字符串带引号,粘到哪里都是合法片段)。 */
|
|
3646
3166
|
function tokenFor(value) {
|
|
3647
3167
|
return typeof value === "string" ? JSON.stringify(value) : JSON.stringify(value ?? null);
|
|
3648
3168
|
}
|
|
3649
|
-
/** 变体切换:判别式 union 挂在标签行上,按形状选的挂在字段行上。触发元素就是当前那一支。 */
|
|
3650
3169
|
function VariantSelect({ control, path, face, disabled, t }) {
|
|
3651
3170
|
const [open, setOpen] = (0, react.useState)(false);
|
|
3652
3171
|
if (control === void 0) return null;
|
|
@@ -3685,7 +3204,6 @@ window.__ModuleLoader__.load({
|
|
|
3685
3204
|
})
|
|
3686
3205
|
});
|
|
3687
3206
|
}
|
|
3688
|
-
/** 开启行的前缀:字段键名(对象里的一层)或数组成员的下标;成员行另外带移除。 */
|
|
3689
3207
|
function LinePrefix({ line, face, t, disabled }) {
|
|
3690
3208
|
const member = line.member;
|
|
3691
3209
|
const key = member?.key ?? line.node.key;
|
|
@@ -3710,7 +3228,6 @@ window.__ModuleLoader__.load({
|
|
|
3710
3228
|
})
|
|
3711
3229
|
] });
|
|
3712
3230
|
}
|
|
3713
|
-
/** 成员行的移除(悬停时出现):数组项按索引、字典键按键名。 */
|
|
3714
3231
|
function RemoveLineButton({ member, face, t, disabled }) {
|
|
3715
3232
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3716
3233
|
type: "button",
|
|
@@ -3724,12 +3241,6 @@ window.__ModuleLoader__.load({
|
|
|
3724
3241
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutlineRegular, { size: 14 })
|
|
3725
3242
|
});
|
|
3726
3243
|
}
|
|
3727
|
-
/**
|
|
3728
|
-
* 容器闭合行后面的添加入口。
|
|
3729
|
-
*
|
|
3730
|
-
* 数组是「追加一个空项」;对象/字典给出**候选**——对象列的是 schema 声明了、值里还没有的字段(带说明),
|
|
3731
|
-
* 字典列的是业务注册的候选键(还能自己敲键名)。粘一整段 `{…}` / `[…]` 则整层覆盖。
|
|
3732
|
-
*/
|
|
3733
3244
|
function AddLine({ line, state, face, t, resolveText, disabled, onAdded }) {
|
|
3734
3245
|
const spec = line.add;
|
|
3735
3246
|
const [text, setText] = (0, react.useState)("");
|
|
@@ -3824,17 +3335,6 @@ window.__ModuleLoader__.load({
|
|
|
3824
3335
|
}
|
|
3825
3336
|
//#endregion
|
|
3826
3337
|
//#region src/client/schema-form/SchemaForm.tsx
|
|
3827
|
-
/**
|
|
3828
|
-
* 一行的配置页:官方表单壳(保存 / 丢弃 / 只读 / 不可用)+ 行式 JSON 结构编辑器 + 字段槽。
|
|
3829
|
-
*
|
|
3830
|
-
* 每个可渲染的行注册项经 `renderFactorySlot('settings.schema-form.form', …)` 渲染这个 Factory,字段槽因此只需在
|
|
3831
|
-
* Factory 的 `children` 里声明一次(slots 的 children 声明 per key 唯一,而本包给每个行都注册了一项)。
|
|
3832
|
-
*/
|
|
3833
|
-
/**
|
|
3834
|
-
* 渲染一行的配置页。
|
|
3835
|
-
* @param props - 行 id、控制器面、本地化解析与框架注入的槽位渲染座位。
|
|
3836
|
-
* @returns 表单壳里的行式编辑器,或「没有可自动生成的配置项」一行。
|
|
3837
|
-
*/
|
|
3838
3338
|
function SchemaForm(props) {
|
|
3839
3339
|
const { ns, face, t, resolveText, renderSlotChain } = props;
|
|
3840
3340
|
const store = face.hooks.schemaForm;
|
|
@@ -3876,7 +3376,6 @@ window.__ModuleLoader__.load({
|
|
|
3876
3376
|
})]
|
|
3877
3377
|
});
|
|
3878
3378
|
}
|
|
3879
|
-
/** 文本草稿的解析规则按字段类型给:行内编辑只上报原文,值在保存那一刻成形。 */
|
|
3880
3379
|
function withParse(owner, t) {
|
|
3881
3380
|
return (text, parse) => {
|
|
3882
3381
|
owner.onEditText(text, parse ?? parseFor(owner.node, t));
|
|
@@ -3907,9 +3406,7 @@ window.__ModuleLoader__.load({
|
|
|
3907
3406
|
visibleFields: () => visibleFields,
|
|
3908
3407
|
walkFields: () => walkFields
|
|
3909
3408
|
});
|
|
3910
|
-
/** 本包字典与槽位文案的命名空间。 */
|
|
3911
3409
|
const NS = "settings.schema-form";
|
|
3912
|
-
/** 需要的服务:槽位、字典、共享配置表单、schema 服务与插件管理(bundle 行清单)。 */
|
|
3913
3410
|
const inject$1 = [
|
|
3914
3411
|
"slots",
|
|
3915
3412
|
"locale",
|
|
@@ -3918,10 +3415,6 @@ window.__ModuleLoader__.load({
|
|
|
3918
3415
|
"remote",
|
|
3919
3416
|
"remote.pluginManager"
|
|
3920
3417
|
];
|
|
3921
|
-
/**
|
|
3922
|
-
* 装上本包:字典、Factory(字段槽的声明者)与按行自动注册。
|
|
3923
|
-
* @param ctx - 浏览器插件上下文。
|
|
3924
|
-
*/
|
|
3925
3418
|
function apply$1(ctx) {
|
|
3926
3419
|
ctx.effect(() => ctx.locale.register(NS, {
|
|
3927
3420
|
zh,
|
|
@@ -4030,26 +3523,6 @@ window.__ModuleLoader__.load({
|
|
|
4030
3523
|
}
|
|
4031
3524
|
//#endregion
|
|
4032
3525
|
//#region src/client/settings-form/form-model.ts
|
|
4033
|
-
/**
|
|
4034
|
-
* The staged form model behind a plugin's settings page.
|
|
4035
|
-
*
|
|
4036
|
-
* A card stages what the user types and writes it only when they save. Each
|
|
4037
|
-
* settings write is a durable, revision-fenced document mutation, so a control
|
|
4038
|
-
* that committed as it settled turned one edit into a write the user never
|
|
4039
|
-
* asked for and could not preview; staged text makes what is on screen exactly
|
|
4040
|
-
* what a save would store.
|
|
4041
|
-
*
|
|
4042
|
-
* A field shows its effective value — the user layer over the composition
|
|
4043
|
-
* layer over the schema default — and whether the user layer carries it. That
|
|
4044
|
-
* presence, not a value comparison, is what marks a field overridden: an
|
|
4045
|
-
* override equal to the composition default is still an override.
|
|
4046
|
-
*/
|
|
4047
|
-
/**
|
|
4048
|
-
* A whole-number field. An empty draft clears the field; any other draft that
|
|
4049
|
-
* is not a finite number blocks the save.
|
|
4050
|
-
* @param field - field name inside the namespace section.
|
|
4051
|
-
* @returns the field's conversion spec.
|
|
4052
|
-
*/
|
|
4053
3526
|
function settingsNumberField(field) {
|
|
4054
3527
|
return {
|
|
4055
3528
|
field,
|
|
@@ -4065,12 +3538,6 @@ window.__ModuleLoader__.load({
|
|
|
4065
3538
|
}
|
|
4066
3539
|
};
|
|
4067
3540
|
}
|
|
4068
|
-
/**
|
|
4069
|
-
* A free-text field. An empty draft clears the field, so emptying the control
|
|
4070
|
-
* and saving is the same gesture as resetting it.
|
|
4071
|
-
* @param field - field name inside the namespace section.
|
|
4072
|
-
* @returns the field's conversion spec.
|
|
4073
|
-
*/
|
|
4074
3541
|
function settingsTextField(field) {
|
|
4075
3542
|
return {
|
|
4076
3543
|
field,
|
|
@@ -4084,13 +3551,6 @@ window.__ModuleLoader__.load({
|
|
|
4084
3551
|
}
|
|
4085
3552
|
};
|
|
4086
3553
|
}
|
|
4087
|
-
/**
|
|
4088
|
-
* Stages one card's edits over one settings namespace and writes them on save.
|
|
4089
|
-
*
|
|
4090
|
-
* The form publishes through a snapshot store because slot components read
|
|
4091
|
-
* through a snapshot selector, while both the scope and the local drafts
|
|
4092
|
-
* change underneath; every projection is rebuilt from the two together.
|
|
4093
|
-
*/
|
|
4094
3554
|
var SettingsFormModel = class {
|
|
4095
3555
|
scope;
|
|
4096
3556
|
specs;
|
|
@@ -4101,11 +3561,6 @@ window.__ModuleLoader__.load({
|
|
|
4101
3561
|
unsubscribe;
|
|
4102
3562
|
saving = false;
|
|
4103
3563
|
failed = false;
|
|
4104
|
-
/**
|
|
4105
|
-
* @param scope - the shared configuration form for this card's namespace.
|
|
4106
|
-
* @param specs - the section fields this card edits.
|
|
4107
|
-
* @param secrets - the card's write-only controls, written outside the section.
|
|
4108
|
-
*/
|
|
4109
3564
|
constructor(scope, specs, secrets = []) {
|
|
4110
3565
|
this.scope = scope;
|
|
4111
3566
|
this.specs = new Map(specs.map((spec) => [spec.field, spec]));
|
|
@@ -4114,11 +3569,6 @@ window.__ModuleLoader__.load({
|
|
|
4114
3569
|
this.publish();
|
|
4115
3570
|
});
|
|
4116
3571
|
}
|
|
4117
|
-
/**
|
|
4118
|
-
* Publish a projection of this form, rebuilt whenever the scope or a draft changes.
|
|
4119
|
-
* @param project - build the card's state from the form's current reads.
|
|
4120
|
-
* @returns the store the card's component reads through its bound selector.
|
|
4121
|
-
*/
|
|
4122
3572
|
bind(project) {
|
|
4123
3573
|
const store = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)(project());
|
|
4124
3574
|
this.listeners.add(() => {
|
|
@@ -4126,10 +3576,6 @@ window.__ModuleLoader__.load({
|
|
|
4126
3576
|
});
|
|
4127
3577
|
return store;
|
|
4128
3578
|
}
|
|
4129
|
-
/**
|
|
4130
|
-
* Read the card-level state: what the Host serves, and what a save would do.
|
|
4131
|
-
* @returns the form state every card shares.
|
|
4132
|
-
*/
|
|
4133
3579
|
shell() {
|
|
4134
3580
|
const snapshot = this.scope.getSnapshot();
|
|
4135
3581
|
const plan = this.plan();
|
|
@@ -4142,11 +3588,6 @@ window.__ModuleLoader__.load({
|
|
|
4142
3588
|
failed: this.failed
|
|
4143
3589
|
};
|
|
4144
3590
|
}
|
|
4145
|
-
/**
|
|
4146
|
-
* Read one control's state.
|
|
4147
|
-
* @param field - field name of a section field or of a write-only control.
|
|
4148
|
-
* @returns the draft text, whether a save would leave an override, and whether it is invalid.
|
|
4149
|
-
*/
|
|
4150
3591
|
field(field) {
|
|
4151
3592
|
const staged = this.staged.get(field);
|
|
4152
3593
|
if (this.secretSpecs.has(field)) return {
|
|
@@ -4167,10 +3608,6 @@ window.__ModuleLoader__.load({
|
|
|
4167
3608
|
invalid: write === void 0
|
|
4168
3609
|
};
|
|
4169
3610
|
}
|
|
4170
|
-
/**
|
|
4171
|
-
* Build the edit, reset, save, and discard actions bound to this form.
|
|
4172
|
-
* @returns the actions a card's slot entry injects.
|
|
4173
|
-
*/
|
|
4174
3611
|
actions() {
|
|
4175
3612
|
return {
|
|
4176
3613
|
edit: (field, text) => {
|
|
@@ -4197,15 +3634,6 @@ window.__ModuleLoader__.load({
|
|
|
4197
3634
|
}
|
|
4198
3635
|
};
|
|
4199
3636
|
}
|
|
4200
|
-
/**
|
|
4201
|
-
* Write every staged edit, then re-seed from what the Host accepted.
|
|
4202
|
-
*
|
|
4203
|
-
* The Host is the only authority on whether a value was accepted — its
|
|
4204
|
-
* validators own the constraints no schema can express — so the outcome is
|
|
4205
|
-
* read back from the section rather than predicted here. A save that did not
|
|
4206
|
-
* land keeps its drafts, so the user can correct them instead of retyping.
|
|
4207
|
-
* @returns settlement after every write and the read-back.
|
|
4208
|
-
*/
|
|
4209
3637
|
async save() {
|
|
4210
3638
|
const plan = this.plan();
|
|
4211
3639
|
if (!plan.length || this.saving || !this.scope.getSnapshot().writable || plan.some((item) => item.run === void 0 && item.op === void 0)) return;
|
|
@@ -4232,17 +3660,10 @@ window.__ModuleLoader__.load({
|
|
|
4232
3660
|
this.publish();
|
|
4233
3661
|
}
|
|
4234
3662
|
}
|
|
4235
|
-
/** Release the form's accepted-value subscription. */
|
|
4236
3663
|
dispose() {
|
|
4237
3664
|
this.unsubscribe();
|
|
4238
3665
|
this.listeners.clear();
|
|
4239
3666
|
}
|
|
4240
|
-
/**
|
|
4241
|
-
* Every staged edit a save would write. An entry whose draft is not a value
|
|
4242
|
-
* its field accepts carries no write: the form is still dirty, and the save
|
|
4243
|
-
* refuses rather than dropping the edit.
|
|
4244
|
-
* @returns the planned writes, in the order the fields were staged.
|
|
4245
|
-
*/
|
|
4246
3667
|
plan() {
|
|
4247
3668
|
const plan = [];
|
|
4248
3669
|
for (const [field, staged] of this.staged) {
|
|
@@ -4469,7 +3890,6 @@ window.__ModuleLoader__.load({
|
|
|
4469
3890
|
const Input = styled("input")(styles$1.input);
|
|
4470
3891
|
const Invalid = styled("p")(styles$1.invalid);
|
|
4471
3892
|
const Hint = styled("p")(styles$1.hint);
|
|
4472
|
-
/** 上游 icons 里的 IconInfoOutlineRegular(fill-only 几何,整份图标表不搬)。 */
|
|
4473
3893
|
function InfoGlyph({ size }) {
|
|
4474
3894
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
4475
3895
|
width: size,
|
|
@@ -4494,13 +3914,6 @@ window.__ModuleLoader__.load({
|
|
|
4494
3914
|
]
|
|
4495
3915
|
});
|
|
4496
3916
|
}
|
|
4497
|
-
/**
|
|
4498
|
-
* A staged value field. `numeric` only hints the keypad: which drafts a field
|
|
4499
|
-
* accepts is decided by its spec, so the control never silently rewrites what
|
|
4500
|
-
* the user typed.
|
|
4501
|
-
* @param props - the field's copy, its staged text, and the edit actions.
|
|
4502
|
-
* @returns the labelled control.
|
|
4503
|
-
*/
|
|
4504
3917
|
function SettingsValueField(props) {
|
|
4505
3918
|
const [helpOpen, setHelpOpen] = (0, react.useState)(false);
|
|
4506
3919
|
const helpId = `${props.id}-help`;
|
|
@@ -4554,14 +3967,6 @@ window.__ModuleLoader__.load({
|
|
|
4554
3967
|
}) : null
|
|
4555
3968
|
] });
|
|
4556
3969
|
}
|
|
4557
|
-
/**
|
|
4558
|
-
* A write-only credential control. The value never rides a response, so the
|
|
4559
|
-
* control reports only whether one is configured and starts blank; a blank
|
|
4560
|
-
* draft writes nothing, which keeps the stored key rather than clearing it.
|
|
4561
|
-
* 控件要求浏览器不要把已存的登录密码自动填进来(上游 11c0511271)。
|
|
4562
|
-
* @param props - the field's copy, its staged text, and the configured state.
|
|
4563
|
-
* @returns the labelled control.
|
|
4564
|
-
*/
|
|
4565
3970
|
function SettingsSecretField(props) {
|
|
4566
3971
|
const Badge = props.configured ? NeutralBadge : QuietBadge;
|
|
4567
3972
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Field, { children: [
|
|
@@ -4635,11 +4040,6 @@ window.__ModuleLoader__.load({
|
|
|
4635
4040
|
const Footer = styled("div")(styles.footer);
|
|
4636
4041
|
const Failed = styled("p")(styles.failed);
|
|
4637
4042
|
const Save = styled("button")(styles.save);
|
|
4638
|
-
/**
|
|
4639
|
-
* Render one plugin's settings form.
|
|
4640
|
-
* @param props - the form's copy and state, its controls, and the save and discard actions.
|
|
4641
|
-
* @returns the form, or the unavailable line while the namespace is not served.
|
|
4642
|
-
*/
|
|
4643
4043
|
function SettingsForm(props) {
|
|
4644
4044
|
const { state, labels } = props;
|
|
4645
4045
|
const discard = (0, react.useRef)(props.onDiscard);
|
|
@@ -17492,7 +16892,6 @@ window.__ModuleLoader__.load({
|
|
|
17492
16892
|
...column === void 0 ? {} : { column: Number(column) }
|
|
17493
16893
|
};
|
|
17494
16894
|
}
|
|
17495
|
-
/** 引号 mention 的起手字符:`@"path with spaces"`。 */
|
|
17496
16895
|
const QUOTED_PATH = "\"";
|
|
17497
16896
|
function parseQuotedReference(body) {
|
|
17498
16897
|
const end = body.indexOf(QUOTED_PATH, 1);
|
|
@@ -17629,15 +17028,6 @@ window.__ModuleLoader__.load({
|
|
|
17629
17028
|
}
|
|
17630
17029
|
//#endregion
|
|
17631
17030
|
//#region src/client/index.ts
|
|
17632
|
-
/**
|
|
17633
|
-
* 需要的**服务名**:样式与引用那半不要服务,schema 表单那半要槽位、字典与配置表单。
|
|
17634
|
-
*
|
|
17635
|
-
* 与包清单里的 `dsh.client.inject` 不是一回事:那份是**装配行 id**(谁先到),交给模块系统排 arrival 顺序;
|
|
17636
|
-
* 这里读的是 cordis 服务名(谁已 provide),Loader 拿它决定这一行何时才算 active。写错成包名,这一行就永远
|
|
17637
|
-
* pending(`web boot: ... waiting for services`)。
|
|
17638
|
-
*
|
|
17639
|
-
* 值直接取自 schema 表单那一面,不另抄一份清单。
|
|
17640
|
-
*/
|
|
17641
17031
|
const inject = [...inject$1];
|
|
17642
17032
|
function apply(ctx) {
|
|
17643
17033
|
apply$1(ctx);
|