dsh-calorie 0.2.7 → 0.3.1
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/client.js +176 -55
- package/dist/client.js.map +1 -1
- package/dist/dsh-ctx.d.ts +46 -1
- package/dist/dsh-ctx.d.ts.map +1 -1
- package/dist/dsh-ctx.js +2 -1
- package/dist/dsh-ctx.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -1
- package/dist/index.js.map +1 -1
- package/dist/settings.d.ts +11 -5
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +22 -18
- package/dist/settings.js.map +1 -1
- package/dist/skill-tool.d.ts +21 -0
- package/dist/skill-tool.d.ts.map +1 -0
- package/dist/skill-tool.js +109 -0
- package/dist/skill-tool.js.map +1 -0
- package/package.json +3 -3
package/dist/client.js
CHANGED
|
@@ -62,36 +62,37 @@ window.__ModuleLoader__.load({
|
|
|
62
62
|
key: "db.dir",
|
|
63
63
|
title: "数据目录",
|
|
64
64
|
tier: "common",
|
|
65
|
-
control: "
|
|
66
|
-
|
|
65
|
+
control: "directory",
|
|
66
|
+
prefillFrom: "dataDir",
|
|
67
|
+
hint: "库文件与各类产物的根目录。留空=用默认目录。"
|
|
67
68
|
},
|
|
68
69
|
{
|
|
69
70
|
key: "db.name",
|
|
70
71
|
title: "库文件名",
|
|
71
72
|
tier: "common",
|
|
72
73
|
control: "text",
|
|
73
|
-
hint: "
|
|
74
|
+
hint: "数据目录下的数据库文件名。改名=换库,旧数据不会被读入。"
|
|
74
75
|
},
|
|
75
76
|
{
|
|
76
77
|
key: "html.dir",
|
|
77
78
|
title: "HTML 产物目录名",
|
|
78
79
|
tier: "common",
|
|
79
80
|
control: "text",
|
|
80
|
-
hint: "
|
|
81
|
+
hint: "数据目录下存放交付页面的子目录名。改名后新页面落新目录。"
|
|
81
82
|
},
|
|
82
83
|
{
|
|
83
84
|
key: "photos.dir",
|
|
84
85
|
title: "照片目录",
|
|
85
86
|
tier: "common",
|
|
86
|
-
control: "
|
|
87
|
-
hint: "
|
|
87
|
+
control: "directory",
|
|
88
|
+
hint: "身材照的存放目录(绝对路径)。留空=未配置,生成 GIF 会被拦下。"
|
|
88
89
|
},
|
|
89
90
|
{
|
|
90
91
|
key: "xunji.key",
|
|
91
92
|
title: "训记 KEY",
|
|
92
93
|
tier: "common",
|
|
93
94
|
control: "text",
|
|
94
|
-
hint: "
|
|
95
|
+
hint: "训记的凭据。留空=未配置,推送与回写会被拦下。"
|
|
95
96
|
}
|
|
96
97
|
];
|
|
97
98
|
/** 高级项:默认收起(13 项减常用 5 项=8 项)。改错了多半只是自己撞上麻烦,不确定就别动。 */
|
|
@@ -101,56 +102,56 @@ window.__ModuleLoader__.load({
|
|
|
101
102
|
title: "照片 GIF 子目录",
|
|
102
103
|
tier: "advanced",
|
|
103
104
|
control: "text",
|
|
104
|
-
hint: "
|
|
105
|
+
hint: "照片目录下存放 GIF 的子目录名。"
|
|
105
106
|
},
|
|
106
107
|
{
|
|
107
108
|
key: "xunji.stateDir",
|
|
108
109
|
title: "训记状态文件目录",
|
|
109
110
|
tier: "advanced",
|
|
110
|
-
control: "
|
|
111
|
-
hint: "
|
|
111
|
+
control: "directory",
|
|
112
|
+
hint: "训记的限频与同步游标存放目录。留空=家目录下的 .mavis。"
|
|
112
113
|
},
|
|
113
114
|
{
|
|
114
115
|
key: "xunji.catalog",
|
|
115
116
|
title: "训记动作库路径",
|
|
116
117
|
tier: "advanced",
|
|
117
118
|
control: "text",
|
|
118
|
-
hint: "
|
|
119
|
+
hint: "动作名判定用的动作库。留空=用包内预置的那份。"
|
|
119
120
|
},
|
|
120
121
|
{
|
|
121
122
|
key: "xunji.backfillDays",
|
|
122
123
|
title: "回写默认天数",
|
|
123
124
|
tier: "advanced",
|
|
124
125
|
control: "number",
|
|
125
|
-
hint: "
|
|
126
|
+
hint: "从训记拉实绩时默认回溯的天数(整数)。"
|
|
126
127
|
},
|
|
127
128
|
{
|
|
128
129
|
key: "land.scheduleCli",
|
|
129
130
|
title: "跨技能出口 · 作息",
|
|
130
131
|
tier: "advanced",
|
|
131
132
|
control: "text",
|
|
132
|
-
hint: "
|
|
133
|
+
hint: "「落地训练」调作息技能的入口文件。留空=按包布局推断。"
|
|
133
134
|
},
|
|
134
135
|
{
|
|
135
136
|
key: "land.memoCli",
|
|
136
137
|
title: "跨技能出口 · 备忘",
|
|
137
138
|
tier: "advanced",
|
|
138
139
|
control: "text",
|
|
139
|
-
hint: "
|
|
140
|
+
hint: "「落地训练」调备忘录技能的入口文件。留空=按包布局推断。"
|
|
140
141
|
},
|
|
141
142
|
{
|
|
142
143
|
key: "land.xunjiSeconds",
|
|
143
144
|
title: "训记调用限时(秒)",
|
|
144
145
|
tier: "advanced",
|
|
145
146
|
control: "number",
|
|
146
|
-
hint: "
|
|
147
|
+
hint: "调用训记的超时秒数,超过即失败。"
|
|
147
148
|
},
|
|
148
149
|
{
|
|
149
150
|
key: "land.landSeconds",
|
|
150
151
|
title: "落地调用限时(秒)",
|
|
151
152
|
tier: "advanced",
|
|
152
153
|
control: "number",
|
|
153
|
-
hint: "
|
|
154
|
+
hint: "落地训练各步骤的超时秒数。"
|
|
154
155
|
}
|
|
155
156
|
];
|
|
156
157
|
/** 设置页的行表,顺序即页面顺序。 */
|
|
@@ -159,7 +160,7 @@ window.__ModuleLoader__.load({
|
|
|
159
160
|
const COMMON_ITEM_COUNT = COMMON.length;
|
|
160
161
|
/** 高级组标题与副文案。 */
|
|
161
162
|
const ADVANCED_GROUP_TITLE = "高级";
|
|
162
|
-
const ADVANCED_GROUP_NOTE = "
|
|
163
|
+
const ADVANCED_GROUP_NOTE = "不常改。留空=用默认值。";
|
|
163
164
|
/** 按 `a.b` 路径从配置取值里读(缺层或类型不符一律回 undefined)。 */
|
|
164
165
|
function readPath(values, key) {
|
|
165
166
|
const parts = key.split(".");
|
|
@@ -183,6 +184,10 @@ window.__ModuleLoader__.load({
|
|
|
183
184
|
cur[parts[parts.length - 1]] = value;
|
|
184
185
|
}
|
|
185
186
|
//#endregion
|
|
187
|
+
//#region src/dsh-ctx.ts
|
|
188
|
+
/** 宿主目录选择命名空间的服务名(#736;出处与禁令见 cookbook §13)。 */
|
|
189
|
+
const REMOTE_DIRECTORY_PICKER = "remote.directoryPicker";
|
|
190
|
+
//#endregion
|
|
186
191
|
//#region src/client.ts
|
|
187
192
|
/** dsh-calorie client 适配器(六边形:port=contract+dsh-ctx 镜像,adapter=本文件)。
|
|
188
193
|
*
|
|
@@ -197,7 +202,7 @@ window.__ModuleLoader__.load({
|
|
|
197
202
|
* (betterSidebar 服务可能晚于本模块到达,最多 10 次、间隔 1000ms;卸载清理)。
|
|
198
203
|
* 样式:React 内联 style + DSH 主题别名(var(--dsw-alias-*),带回退),无外部样式表、
|
|
199
204
|
* 无 <style> 注入、无类名冲突;只用 dsh-ctx 镜像内成员(slots.inject/register、
|
|
200
|
-
* connection.rpc.call 经闭包现取、betterSidebar
|
|
205
|
+
* connection.rpc.call 经闭包现取、betterSidebar 与 #736 的目录选择命名空间经 ctx.get 运行时取);
|
|
201
206
|
* 取数只经 connection.rpc.call 进 host 通道;缺席/错误纯条件渲染,不返空冒充。
|
|
202
207
|
* 组件 React.createElement 手写,不引入 JSX。
|
|
203
208
|
*
|
|
@@ -207,6 +212,8 @@ window.__ModuleLoader__.load({
|
|
|
207
212
|
* 照 CONTEXT.md,「技能设置页」只配置、不干活:本页没有任何取数/记一餐入口,
|
|
208
213
|
* 状态读数也已移出(读数属技能功能页)。
|
|
209
214
|
*/
|
|
215
|
+
/** client 短名声明:只有这两个(#736 的目录选择走**可选查找**,不写进来——
|
|
216
|
+
* 写进来=硬依赖,提供方缺席时整包被停靠,设置页会跟着装不上;见 cookbook §13)。 */
|
|
210
217
|
const inject = ["slots", "connection"];
|
|
211
218
|
/** 面板视觉(内联 style;颜色走 DSH 主题别名,深浅主题自适应,写死值只做回退)。 */
|
|
212
219
|
const S = {
|
|
@@ -216,11 +223,10 @@ window.__ModuleLoader__.load({
|
|
|
216
223
|
border: "1px solid var(--dsw-alias-border, rgba(128,128,128,.35))",
|
|
217
224
|
background: "var(--dsw-alias-bg-base, transparent)",
|
|
218
225
|
color: "var(--dsw-alias-label-primary, inherit)",
|
|
219
|
-
fontSize: 13,
|
|
220
226
|
lineHeight: 1.6
|
|
221
227
|
},
|
|
222
228
|
title: {
|
|
223
|
-
fontSize:
|
|
229
|
+
fontSize: "1.08em",
|
|
224
230
|
fontWeight: 700,
|
|
225
231
|
marginBottom: 8
|
|
226
232
|
},
|
|
@@ -231,15 +237,15 @@ window.__ModuleLoader__.load({
|
|
|
231
237
|
},
|
|
232
238
|
muted: {
|
|
233
239
|
color: "var(--dsw-alias-label-secondary, #9a9a9a)",
|
|
234
|
-
fontSize:
|
|
240
|
+
fontSize: "0.92em"
|
|
235
241
|
},
|
|
236
242
|
error: {
|
|
237
243
|
color: "var(--dsw-alias-state-error-primary, #ff6b6b)",
|
|
238
|
-
fontSize:
|
|
244
|
+
fontSize: "1em"
|
|
239
245
|
},
|
|
240
246
|
okText: {
|
|
241
247
|
color: "var(--dsw-alias-state-success-primary, #12805c)",
|
|
242
|
-
fontSize:
|
|
248
|
+
fontSize: "0.96em"
|
|
243
249
|
},
|
|
244
250
|
rows: {
|
|
245
251
|
marginTop: 8,
|
|
@@ -251,22 +257,18 @@ window.__ModuleLoader__.load({
|
|
|
251
257
|
paddingTop: 8,
|
|
252
258
|
borderTop: "1px dashed var(--dsw-alias-border, rgba(128,128,128,.25))",
|
|
253
259
|
color: "var(--dsw-alias-label-tertiary, #8a8a8a)",
|
|
254
|
-
fontSize:
|
|
260
|
+
fontSize: "0.92em"
|
|
255
261
|
},
|
|
256
262
|
info: {
|
|
257
|
-
|
|
258
|
-
font: "12px/1.5 ui-monospace,Consolas,monospace",
|
|
263
|
+
fontSize: "0.92em",
|
|
259
264
|
color: "var(--dsw-alias-label-secondary, #9a9a9a)",
|
|
260
|
-
|
|
265
|
+
overflowWrap: "anywhere"
|
|
261
266
|
},
|
|
262
267
|
row: { marginTop: 10 },
|
|
263
|
-
label: {
|
|
264
|
-
fontSize: 12.5,
|
|
265
|
-
fontWeight: 600
|
|
266
|
-
},
|
|
268
|
+
label: { fontWeight: 600 },
|
|
267
269
|
hint: {
|
|
268
270
|
color: "var(--dsw-alias-label-tertiary, #8a8a8a)",
|
|
269
|
-
fontSize:
|
|
271
|
+
fontSize: "0.92em",
|
|
270
272
|
margin: "1px 0 4px"
|
|
271
273
|
},
|
|
272
274
|
input: {
|
|
@@ -276,8 +278,22 @@ window.__ModuleLoader__.load({
|
|
|
276
278
|
borderRadius: 6,
|
|
277
279
|
border: "1px solid var(--dsw-alias-border, rgba(128,128,128,.45))",
|
|
278
280
|
background: "var(--dsw-alias-bg-base, transparent)",
|
|
281
|
+
color: "var(--dsw-alias-label-primary, inherit)"
|
|
282
|
+
},
|
|
283
|
+
pickRow: {
|
|
284
|
+
display: "flex",
|
|
285
|
+
gap: 6,
|
|
286
|
+
alignItems: "center"
|
|
287
|
+
},
|
|
288
|
+
btnPick: {
|
|
289
|
+
flex: "0 0 auto",
|
|
290
|
+
padding: "4px 10px",
|
|
291
|
+
borderRadius: 6,
|
|
292
|
+
border: "1px solid var(--dsw-alias-border, rgba(128,128,128,.45))",
|
|
293
|
+
background: "var(--dsw-alias-bg-base, transparent)",
|
|
279
294
|
color: "var(--dsw-alias-label-primary, inherit)",
|
|
280
|
-
|
|
295
|
+
whiteSpace: "nowrap",
|
|
296
|
+
cursor: "pointer"
|
|
281
297
|
},
|
|
282
298
|
bar: {
|
|
283
299
|
marginTop: 14,
|
|
@@ -292,7 +308,6 @@ window.__ModuleLoader__.load({
|
|
|
292
308
|
border: "1px solid var(--dsw-alias-border, rgba(128,128,128,.45))",
|
|
293
309
|
background: "var(--dsw-alias-bg-base, transparent)",
|
|
294
310
|
color: "var(--dsw-alias-label-primary, inherit)",
|
|
295
|
-
fontSize: 12.5,
|
|
296
311
|
cursor: "pointer"
|
|
297
312
|
},
|
|
298
313
|
btnPrimary: {
|
|
@@ -301,7 +316,6 @@ window.__ModuleLoader__.load({
|
|
|
301
316
|
border: "1px solid var(--dsw-alias-brand-primary, #2f6fed)",
|
|
302
317
|
background: "var(--dsw-alias-brand-primary, #2f6fed)",
|
|
303
318
|
color: "#fff",
|
|
304
|
-
fontSize: 12.5,
|
|
305
319
|
cursor: "pointer"
|
|
306
320
|
},
|
|
307
321
|
adv: {
|
|
@@ -311,7 +325,6 @@ window.__ModuleLoader__.load({
|
|
|
311
325
|
},
|
|
312
326
|
advSummary: {
|
|
313
327
|
cursor: "pointer",
|
|
314
|
-
fontSize: 12.5,
|
|
315
328
|
fontWeight: 600
|
|
316
329
|
}
|
|
317
330
|
};
|
|
@@ -536,11 +549,11 @@ window.__ModuleLoader__.load({
|
|
|
536
549
|
*/
|
|
537
550
|
function humanizeConfigFailure(code, message) {
|
|
538
551
|
const text = message.trim().length > 0 ? message.trim() : `(${code},宿主未给报文)`;
|
|
539
|
-
if (/解析|YAML|parse/i.test(text)) return `${text}\n改回「键:
|
|
540
|
-
if (/不认识|未知键|UNKNOWN_KEY/i.test(text)) return `${text}\n
|
|
541
|
-
if (/类型|TYPE_MISMATCH/i.test(text)) return `${text}\n
|
|
542
|
-
if (/缺席|missing-cli/.test(text)) return `${text}\n
|
|
543
|
-
return `${text}\n
|
|
552
|
+
if (/解析|YAML|parse/i.test(text)) return `${text}\n改回「键: 值」的写法,或点「重置为默认」。`;
|
|
553
|
+
if (/不认识|未知键|UNKNOWN_KEY/i.test(text)) return `${text}\n删掉页面上没有的行,或点「重置为默认」。`;
|
|
554
|
+
if (/类型|TYPE_MISMATCH/i.test(text)) return `${text}\n按本页的控件形状填,或点「重置为默认」。`;
|
|
555
|
+
if (/缺席|missing-cli/.test(text)) return `${text}\n技能出口没找到:先确认技能包已装好,再点「重新读取」。`;
|
|
556
|
+
return `${text}\n改不动就点「重置为默认」,或照上面那条路径手工改配置文件。`;
|
|
544
557
|
}
|
|
545
558
|
/** 一次配置 RPC(与 fetchRead 同通道、同超时纪律;永不抛,失败落字)。 */
|
|
546
559
|
async function configRpc(call, method, payload) {
|
|
@@ -592,12 +605,17 @@ window.__ModuleLoader__.load({
|
|
|
592
605
|
function resetConfigSurface(call) {
|
|
593
606
|
return configRpc(call, RPC_ENDPOINT_CONFIG_RESET, {});
|
|
594
607
|
}
|
|
595
|
-
/** 把配置取值铺成「行键 → 输入框文本」(页面表单态;值缺项即空串,不返空留白)。
|
|
596
|
-
|
|
608
|
+
/** 把配置取值铺成「行键 → 输入框文本」(页面表单态;值缺项即空串,不返空留白)。
|
|
609
|
+
*
|
|
610
|
+
* `prefill` 是**落点回执**(配置面那几格解析出来的绝对路径):标了 `prefillFrom` 的行在取值空着时
|
|
611
|
+
* 直接显示那条绝对路径——用户不必自己拼路径(#743),显示的就是技能真会用的那个目录(逐字相同)。 */
|
|
612
|
+
function toDraft(values, prefill = {}) {
|
|
597
613
|
const draft = {};
|
|
598
614
|
for (const item of CONFIG_ITEMS) {
|
|
599
615
|
const v = readPath(values, item.key);
|
|
600
|
-
|
|
616
|
+
const raw = v === void 0 || v === null ? "" : String(v);
|
|
617
|
+
const fallback = item.prefillFrom === void 0 ? void 0 : prefill[item.prefillFrom];
|
|
618
|
+
draft[item.key] = raw === "" && typeof fallback === "string" && fallback !== "" ? fallback : raw;
|
|
601
619
|
}
|
|
602
620
|
return draft;
|
|
603
621
|
}
|
|
@@ -611,7 +629,73 @@ window.__ModuleLoader__.load({
|
|
|
611
629
|
}
|
|
612
630
|
return values;
|
|
613
631
|
}
|
|
614
|
-
/**
|
|
632
|
+
/** 形状守卫:拿到的那个东西像不像目录选择命名空间(本包只认 `pick` 是函数)。 */
|
|
633
|
+
function isDirectoryPicker(raw) {
|
|
634
|
+
if (typeof raw !== "object" || raw === null) return false;
|
|
635
|
+
return typeof raw.pick === "function";
|
|
636
|
+
}
|
|
637
|
+
/** 现取目录选择命名空间:**软依赖**,拿不到回 null(页面据此不出入口)。
|
|
638
|
+
*
|
|
639
|
+
* 为什么不用 `inject` 声明:见 cookbook §13——写进声明=硬依赖,提供方缺席时整包被停靠,
|
|
640
|
+
* 设置页会跟着装不上。这里按平台给的「可选查找」办:缺席只是没有入口。
|
|
641
|
+
* 守卫拒绝或提供方中途卸载会抛,一律当「没有」,绝不把设置页带下来。 */
|
|
642
|
+
function resolveDirectoryPicker(getService) {
|
|
643
|
+
if (typeof getService !== "function") return null;
|
|
644
|
+
try {
|
|
645
|
+
const raw = getService(REMOTE_DIRECTORY_PICKER);
|
|
646
|
+
return isDirectoryPicker(raw) ? raw : null;
|
|
647
|
+
} catch {
|
|
648
|
+
return null;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
/** 平台回执 → 三种结果(**永不抛**)。回执是信封 `{ok, value|error}`(见 dsh-ctx.ts 的
|
|
652
|
+
* `DirectoryPickerAnswer`):成功回的是 `value` 不是路径本身,被拒回的是 `ok:false` 不是抛——
|
|
653
|
+
* 照裸值解会把两种情况都误判成「用户取消」(#743 真机现象:点「选择文件夹」什么都没发生)。
|
|
654
|
+
* 裸串照收(老形状兜底),认不出的形状当「供不了」报出来,不当取消吞掉。 */
|
|
655
|
+
function readPickAnswer(raw) {
|
|
656
|
+
if (typeof raw === "string") return raw.trim() === "" ? { kind: "cancelled" } : {
|
|
657
|
+
kind: "picked",
|
|
658
|
+
path: raw
|
|
659
|
+
};
|
|
660
|
+
const answer = typeof raw === "object" && raw !== null ? raw : {};
|
|
661
|
+
if (answer.ok === true) {
|
|
662
|
+
const value = answer.value;
|
|
663
|
+
return typeof value === "string" && value.trim() !== "" ? {
|
|
664
|
+
kind: "picked",
|
|
665
|
+
path: value
|
|
666
|
+
} : { kind: "cancelled" };
|
|
667
|
+
}
|
|
668
|
+
const detail = answer.error?.message?.trim() ?? "";
|
|
669
|
+
return {
|
|
670
|
+
kind: "unavailable",
|
|
671
|
+
message: "打不开系统文件夹对话框" + (detail === "" ? "" : "(" + detail + ")") + ":请直接在框里填绝对路径。"
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
/** 唤起一次系统文件夹选择器并归一结果(**永不抛**)。 */
|
|
675
|
+
async function pickDirectory(picker) {
|
|
676
|
+
try {
|
|
677
|
+
return readPickAnswer(await picker.pick());
|
|
678
|
+
} catch (e) {
|
|
679
|
+
return {
|
|
680
|
+
kind: "unavailable",
|
|
681
|
+
message: "打不开系统文件夹对话框(" + (e instanceof Error ? e.message : String(e)) + "):请直接在框里填绝对路径。"
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
/** 「选择文件夹」按钮的真装配函数(目录行渲染出来的按钮,onClick 就是它)。
|
|
686
|
+
*
|
|
687
|
+
* 回 Promise 是为了本地可判(用例直接 await);接进 React 时调用方 `void` 掉。 */
|
|
688
|
+
function createBrowseHandler(deps) {
|
|
689
|
+
return async (key) => {
|
|
690
|
+
const outcome = await pickDirectory(deps.picker);
|
|
691
|
+
if (outcome.kind === "picked") deps.onChange(key, outcome.path);
|
|
692
|
+
else if (outcome.kind === "unavailable") deps.onUnavailable(outcome.message);
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
/** 一行输入(四种控件对齐受限 YAML 子集:文本/数字/布尔/目录)。
|
|
696
|
+
*
|
|
697
|
+
* 目录档=文本框 + 一枚唤起系统文件夹选择器的按钮;`onBrowse` 缺席(命名空间拿不到、
|
|
698
|
+
* 或这条路已被拒)时不画按钮,文本框照旧——那是该缝自己的契约(供不了就收起入口,不是失败)。 */
|
|
615
699
|
function Row(props) {
|
|
616
700
|
const { item } = props;
|
|
617
701
|
const onChange = (e) => props.onChange(item.key, e.target.value);
|
|
@@ -628,7 +712,13 @@ window.__ModuleLoader__.load({
|
|
|
628
712
|
spellCheck: false,
|
|
629
713
|
onChange
|
|
630
714
|
});
|
|
631
|
-
|
|
715
|
+
const browse = item.control === "directory" && props.onBrowse !== void 0 ? react.createElement("button", {
|
|
716
|
+
style: S.btnPick,
|
|
717
|
+
type: "button",
|
|
718
|
+
disabled: props.disabled,
|
|
719
|
+
onClick: () => props.onBrowse?.(item.key)
|
|
720
|
+
}, "选择文件夹…") : null;
|
|
721
|
+
return react.createElement("div", { style: S.row }, react.createElement("div", { style: S.label }, item.title), react.createElement("div", { style: S.hint }, item.hint), browse === null ? control : react.createElement("div", { style: S.pickRow }, control, browse));
|
|
632
722
|
}
|
|
633
723
|
/** 技能设置页:承载卡路里自己的全部可配置项(只配置,不干活)。
|
|
634
724
|
*
|
|
@@ -636,6 +726,8 @@ window.__ModuleLoader__.load({
|
|
|
636
726
|
* 没有任何干活入口——查数、记一餐在技能功能页(sidebar槽)。 */
|
|
637
727
|
function CalorieConfig(props) {
|
|
638
728
|
const [state, setState] = react.useState({ kind: "loading" });
|
|
729
|
+
const [pickerGone, setPickerGone] = react.useState(false);
|
|
730
|
+
const [picking, setPicking] = react.useState(false);
|
|
639
731
|
const [draft, setDraft] = react.useState({});
|
|
640
732
|
const [busy, setBusy] = react.useState(false);
|
|
641
733
|
const [notice, setNotice] = react.useState(null);
|
|
@@ -653,7 +745,7 @@ window.__ModuleLoader__.load({
|
|
|
653
745
|
kind: "ready",
|
|
654
746
|
surface: r.surface
|
|
655
747
|
});
|
|
656
|
-
setDraft(toDraft(r.surface.values));
|
|
748
|
+
setDraft(toDraft(r.surface.values, r.surface));
|
|
657
749
|
} else setState({
|
|
658
750
|
kind: "failed",
|
|
659
751
|
message: r.message
|
|
@@ -669,7 +761,7 @@ window.__ModuleLoader__.load({
|
|
|
669
761
|
kind: "ready",
|
|
670
762
|
surface: r.surface
|
|
671
763
|
});
|
|
672
|
-
setDraft(toDraft(r.surface.values));
|
|
764
|
+
setDraft(toDraft(r.surface.values, r.surface));
|
|
673
765
|
} else setState({
|
|
674
766
|
kind: "failed",
|
|
675
767
|
message: r.message
|
|
@@ -700,8 +792,26 @@ window.__ModuleLoader__.load({
|
|
|
700
792
|
setNotice(null);
|
|
701
793
|
setError(null);
|
|
702
794
|
}, []);
|
|
795
|
+
/** 目录选择:拿不到命名空间就没有入口;被拒一次即收起入口(不留死按钮)。 */
|
|
796
|
+
const picker = pickerGone ? null : props.getPicker();
|
|
797
|
+
const onBrowse = react.useMemo(() => {
|
|
798
|
+
if (picker === null) return void 0;
|
|
799
|
+
const browse = createBrowseHandler({
|
|
800
|
+
picker,
|
|
801
|
+
onChange,
|
|
802
|
+
onUnavailable: (message) => {
|
|
803
|
+
setError(message);
|
|
804
|
+
setPickerGone(true);
|
|
805
|
+
}
|
|
806
|
+
});
|
|
807
|
+
return (key) => {
|
|
808
|
+
setPicking(true);
|
|
809
|
+
setError(null);
|
|
810
|
+
browse(key).finally(() => setPicking(false));
|
|
811
|
+
};
|
|
812
|
+
}, [picker, onChange]);
|
|
703
813
|
const surface = state.kind === "ready" ? state.surface : null;
|
|
704
|
-
const dirty = surface !== null && CONFIG_ITEMS.some((i) => (draft[i.key] ?? "") !== (toDraft(surface.values)[i.key] ?? ""));
|
|
814
|
+
const dirty = surface !== null && CONFIG_ITEMS.some((i) => (draft[i.key] ?? "") !== (toDraft(surface.values, surface)[i.key] ?? ""));
|
|
705
815
|
const onSave = react.useCallback(async () => {
|
|
706
816
|
setBusy(true);
|
|
707
817
|
setNotice(null);
|
|
@@ -713,7 +823,7 @@ window.__ModuleLoader__.load({
|
|
|
713
823
|
kind: "ready",
|
|
714
824
|
surface: r.surface
|
|
715
825
|
});
|
|
716
|
-
setDraft(toDraft(r.surface.values));
|
|
826
|
+
setDraft(toDraft(r.surface.values, r.surface));
|
|
717
827
|
setNotice("已保存,立即生效(不用重启宿主)");
|
|
718
828
|
} else setError(r.message);
|
|
719
829
|
}, [draft, props.getCall]);
|
|
@@ -728,11 +838,11 @@ window.__ModuleLoader__.load({
|
|
|
728
838
|
kind: "ready",
|
|
729
839
|
surface: r.surface
|
|
730
840
|
});
|
|
731
|
-
setDraft(toDraft(r.surface.values));
|
|
841
|
+
setDraft(toDraft(r.surface.values, r.surface));
|
|
732
842
|
setNotice("已重置为默认(原配置已另存一份 .bak)");
|
|
733
843
|
} else setError(r.message);
|
|
734
844
|
}, [props.getCall]);
|
|
735
|
-
const head = react.createElement("div", null, react.createElement("div", { style: S.title }, `${SLOT_TITLE} · 配置`), surface !== null ? react.createElement("div", null, react.createElement("div", { style: S.info }, `配置文件 ${surface.path}`), react.createElement("div", { style: S.info }, `数据目录 ${surface.dataDir}`), surface.created ? react.createElement("div", { style: S.muted }, "
|
|
845
|
+
const head = react.createElement("div", null, react.createElement("div", { style: S.title }, `${SLOT_TITLE} · 配置`), surface !== null ? react.createElement("div", null, react.createElement("div", { style: S.info }, `配置文件 ${surface.path}`), react.createElement("div", { style: S.info }, `数据目录 ${surface.dataDir}`), surface.created ? react.createElement("div", { style: S.muted }, "(配置文件刚按默认值生成)") : null) : null);
|
|
736
846
|
if (state.kind === "loading") return react.createElement("div", { style: S.card }, head, react.createElement("div", { style: S.muted }, "配置读取中…"));
|
|
737
847
|
if (state.kind === "failed") return react.createElement("div", { style: S.card }, react.createElement("div", { style: S.title }, `${SLOT_TITLE} · 配置`), react.createElement("div", { style: S.error }, state.message), react.createElement("div", { style: S.bar }, react.createElement("button", {
|
|
738
848
|
style: S.btn,
|
|
@@ -753,7 +863,8 @@ window.__ModuleLoader__.load({
|
|
|
753
863
|
item,
|
|
754
864
|
value: draft[item.key] ?? "",
|
|
755
865
|
disabled: busy,
|
|
756
|
-
onChange
|
|
866
|
+
onChange,
|
|
867
|
+
onBrowse
|
|
757
868
|
});
|
|
758
869
|
return react.createElement("div", { style: S.card }, head, react.createElement("div", { style: S.rows }, common.map(renderRow)), react.createElement("details", { style: S.adv }, react.createElement("summary", { style: S.advSummary }, ADVANCED_GROUP_TITLE), react.createElement("div", { style: S.muted }, ADVANCED_GROUP_NOTE), advanced.map(renderRow)), react.createElement("div", { style: S.bar }, react.createElement("button", {
|
|
759
870
|
style: dirty ? S.btnPrimary : S.btn,
|
|
@@ -770,20 +881,24 @@ window.__ModuleLoader__.load({
|
|
|
770
881
|
type: "button",
|
|
771
882
|
disabled: busy,
|
|
772
883
|
onClick: () => void load()
|
|
773
|
-
}, "重新读取")), notice !== null ? react.createElement("div", { style: S.okText }, notice) : null, error !== null ? react.createElement("div", { style: S.error }, error) : null, react.createElement(VersionLine, {
|
|
884
|
+
}, "重新读取")), picking ? react.createElement("div", { style: S.muted }, "已唤起系统文件夹对话框:选中后自动填上,取消则不动。") : null, notice !== null ? react.createElement("div", { style: S.okText }, notice) : null, error !== null ? react.createElement("div", { style: S.error }, error) : null, react.createElement("div", { style: S.muted }, `${PLUGIN} 本页只配置;记一餐、看数据在对话里说。`), react.createElement(VersionLine, {
|
|
774
885
|
pluginVersion: versions.plugin,
|
|
775
886
|
skillVersion: versions.skill
|
|
776
887
|
}));
|
|
777
888
|
}
|
|
778
889
|
function apply(ctx) {
|
|
779
890
|
const getCall = () => ctx.connection?.rpc?.call ?? null;
|
|
891
|
+
const getPicker = () => typeof ctx.get === "function" ? resolveDirectoryPicker((name) => ctx.get?.(name)) : null;
|
|
780
892
|
ctx.slots.inject("ilife.config-tab", () => ctx.slots.register({
|
|
781
893
|
name: "ilife.config-tab",
|
|
782
894
|
id: "dsh-calorie",
|
|
783
895
|
order: 75,
|
|
784
896
|
label: () => SLOT_TITLE,
|
|
785
897
|
channel: RPC_CHANNEL
|
|
786
|
-
}, () => react.createElement(CalorieConfig, {
|
|
898
|
+
}, () => react.createElement(CalorieConfig, {
|
|
899
|
+
getCall,
|
|
900
|
+
getPicker
|
|
901
|
+
})));
|
|
787
902
|
const ensureWorkTab = () => {
|
|
788
903
|
try {
|
|
789
904
|
const get = ctx.get;
|
|
@@ -818,18 +933,24 @@ window.__ModuleLoader__.load({
|
|
|
818
933
|
}
|
|
819
934
|
}
|
|
820
935
|
//#endregion
|
|
936
|
+
exports.Row = Row;
|
|
821
937
|
exports.VERSION_READ_KEY = VERSION_READ_KEY;
|
|
822
938
|
exports.VERSION_UNKNOWN = VERSION_UNKNOWN;
|
|
823
939
|
exports.VersionLine = VersionLine;
|
|
824
940
|
exports.apply = apply;
|
|
941
|
+
exports.createBrowseHandler = createBrowseHandler;
|
|
825
942
|
exports.fetchConfigSurface = fetchConfigSurface;
|
|
826
943
|
exports.fetchVersions = fetchVersions;
|
|
827
944
|
exports.formatVersionLine = formatVersionLine;
|
|
828
945
|
exports.fromDraft = fromDraft;
|
|
829
946
|
exports.humanizeConfigFailure = humanizeConfigFailure;
|
|
830
947
|
exports.inject = inject;
|
|
948
|
+
exports.isDirectoryPicker = isDirectoryPicker;
|
|
831
949
|
exports.normalizeVersion = normalizeVersion;
|
|
950
|
+
exports.pickDirectory = pickDirectory;
|
|
951
|
+
exports.readPickAnswer = readPickAnswer;
|
|
832
952
|
exports.resetConfigSurface = resetConfigSurface;
|
|
953
|
+
exports.resolveDirectoryPicker = resolveDirectoryPicker;
|
|
833
954
|
exports.saveConfigSurface = saveConfigSurface;
|
|
834
955
|
exports.toDraft = toDraft;
|
|
835
956
|
return module.exports;
|