dsh-calorie 0.3.8 → 0.3.9

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 CHANGED
@@ -54,7 +54,7 @@ window.__ModuleLoader__.load({
54
54
  const SLOT_TITLE = "卡路里";
55
55
  //#endregion
56
56
  //#region src/settings.ts
57
- /** 常用项:用户裁决三点的五项——改了就影响「库与产物落在哪、训记认不认你」,放页面上。 */
57
+ /** 常用项:数据目录/库文件名/产物目录名/照片目录/训记 KEY(可改 3 + 只读 2)。 */
58
58
  const COMMON = [
59
59
  {
60
60
  key: "db.dir",
@@ -69,21 +69,26 @@ window.__ModuleLoader__.load({
69
69
  title: "库文件名",
70
70
  tier: "common",
71
71
  control: "text",
72
- hint: "数据目录下的数据库文件名。改名=换库,旧数据不会被读入。"
72
+ readonly: true,
73
+ resolveFrom: "dbFile",
74
+ hint: "数据目录下的数据库文件。只读,要改请编辑配置文件。"
73
75
  },
74
76
  {
75
77
  key: "html.dir",
76
78
  title: "HTML 产物目录名",
77
79
  tier: "common",
78
80
  control: "text",
79
- hint: "数据目录下存放交付页面的子目录名。改名后新页面落新目录。"
81
+ readonly: true,
82
+ resolveFrom: "htmlDir",
83
+ hint: "数据目录下存放交付页面的子目录。只读,要改请编辑配置文件。"
80
84
  },
81
85
  {
82
86
  key: "photos.dir",
83
87
  title: "照片目录",
84
88
  tier: "common",
85
89
  control: "directory",
86
- hint: "身材照的存放目录(绝对路径)。留空=未配置,生成 GIF 会被拦下。"
90
+ prefillResolved: "photosDir",
91
+ hint: "身材照的存放目录。留空=用默认目录(数据目录下的 photos)。"
87
92
  },
88
93
  {
89
94
  key: "xunji.key",
@@ -93,63 +98,58 @@ window.__ModuleLoader__.load({
93
98
  hint: "训记的凭据。留空=未配置,推送与回写会被拦下。"
94
99
  }
95
100
  ];
96
- /** 高级项:默认收起(13 项减常用 5 项=8 项)。改错了多半只是自己撞上麻烦,不确定就别动。 */
101
+ /** 高级项:默认收起(只读 6 项)。改错了多半只是自己撞上麻烦,不确定就别动。 */
97
102
  const ADVANCED = [
98
103
  {
99
104
  key: "photos.gifs",
100
105
  title: "照片 GIF 子目录",
101
106
  tier: "advanced",
102
107
  control: "text",
103
- hint: "照片目录下存放 GIF 的子目录名。"
108
+ readonly: true,
109
+ resolveFrom: "gifsDir",
110
+ hint: "照片目录下存放 GIF 的子目录。只读,要改请编辑配置文件。"
104
111
  },
105
112
  {
106
113
  key: "xunji.stateDir",
107
114
  title: "训记状态文件目录",
108
115
  tier: "advanced",
109
116
  control: "directory",
110
- hint: "训记的限频与同步游标存放目录。留空=家目录下的 .mavis。"
117
+ readonly: true,
118
+ resolveFrom: "stateDir",
119
+ hint: "训记的限频与同步游标存放目录。只读,要改请编辑配置文件。"
111
120
  },
112
121
  {
113
122
  key: "xunji.catalog",
114
123
  title: "训记动作库路径",
115
124
  tier: "advanced",
116
125
  control: "text",
117
- hint: "动作名判定用的动作库。留空=用包内预置的那份。"
126
+ readonly: true,
127
+ resolveFrom: "catalog",
128
+ hint: "动作名判定用的动作库。只读,要改请编辑配置文件。"
118
129
  },
119
130
  {
120
131
  key: "xunji.backfillDays",
121
132
  title: "回写默认天数",
122
133
  tier: "advanced",
123
134
  control: "number",
124
- hint: "从训记拉实绩时默认回溯的天数(整数)。"
125
- },
126
- {
127
- key: "land.scheduleCli",
128
- title: "跨技能出口 · 作息",
129
- tier: "advanced",
130
- control: "text",
131
- hint: "「落地训练」调作息技能的入口文件。留空=按包布局推断。"
132
- },
133
- {
134
- key: "land.memoCli",
135
- title: "跨技能出口 · 备忘",
136
- tier: "advanced",
137
- control: "text",
138
- hint: "「落地训练」调备忘录技能的入口文件。留空=按包布局推断。"
135
+ readonly: true,
136
+ hint: "从训记拉实绩时默认回溯的天数。只读,要改请编辑配置文件。"
139
137
  },
140
138
  {
141
139
  key: "land.xunjiSeconds",
142
140
  title: "训记调用限时(秒)",
143
141
  tier: "advanced",
144
142
  control: "number",
145
- hint: "调用训记的超时秒数,超过即失败。"
143
+ readonly: true,
144
+ hint: "调用训记的超时秒数。只读,要改请编辑配置文件。"
146
145
  },
147
146
  {
148
147
  key: "land.landSeconds",
149
148
  title: "落地调用限时(秒)",
150
149
  tier: "advanced",
151
150
  control: "number",
152
- hint: "落地训练各步骤的超时秒数。"
151
+ readonly: true,
152
+ hint: "落地训练各步骤的超时秒数。只读,要改请编辑配置文件。"
153
153
  }
154
154
  ];
155
155
  /** 设置页的行表,顺序即页面顺序。 */
@@ -1408,7 +1408,7 @@ window.__ModuleLoader__.load({
1408
1408
  alive = false;
1409
1409
  };
1410
1410
  }, [props.getCall]);
1411
- if (state.kind === "loading") return react.createElement("div", { style: S.card }, react.createElement("div", { style: S.muted }, "卡路里加载中…"));
1411
+ if (state.kind === "loading") return react.createElement("div", { style: S.card }, react.createElement("div", { style: S.muted }, "卡路里加载中"));
1412
1412
  if (state.kind === "data") return react.createElement("div", { style: S.card }, react.createElement("div", { style: S.title }, `卡路里 · ${state.date}`), react.createElement("div", { style: S.total }, `total ${state.total}`), react.createElement(VersionLine, {
1413
1413
  pluginVersion: versions.plugin,
1414
1414
  skillVersion: versions.skill
@@ -1482,27 +1482,50 @@ window.__ModuleLoader__.load({
1482
1482
  function resetConfigSurface(call) {
1483
1483
  return configRpc(call, RPC_ENDPOINT_CONFIG_RESET, {});
1484
1484
  }
1485
- /** 把配置取值铺成「行键 → 输入框文本」(页面表单态;值缺项即空串,不返空留白)。
1486
- *
1487
- * `prefill` 是**落点回执**(配置面那几格解析出来的绝对路径):标了 `prefillFrom` 的行在取值空着时
1488
- * 直接显示那条绝对路径——用户不必自己拼路径(#743),显示的就是技能真会用的那个目录(逐字相同)。 */
1485
+ /** 只读行显示什么:标了 `resolveFrom` ⇒ 回执 `resolved` 组那一格;没标 ⇒ 配置文件里那个值本身
1486
+ * (数字类只读项走这一档:显示的是生效数字,见 #749 补注二的甲档)。 */
1487
+ function readonlyTextOf(item, raw, prefill) {
1488
+ if (item.resolveFrom === void 0) return raw;
1489
+ const shown = prefill.resolved?.[item.resolveFrom];
1490
+ return typeof shown === "string" ? shown : "";
1491
+ }
1489
1492
  function toDraft(values, prefill = {}) {
1490
1493
  const draft = {};
1491
1494
  for (const item of CONFIG_ITEMS) {
1492
1495
  const v = readPath(values, item.key);
1493
1496
  const raw = v === void 0 || v === null ? "" : String(v);
1494
- const fallback = item.prefillFrom === void 0 ? void 0 : prefill[item.prefillFrom];
1495
- draft[item.key] = raw === "" && typeof fallback === "string" && fallback !== "" ? fallback : raw;
1497
+ if (item.readonly === true) {
1498
+ draft[item.key] = readonlyTextOf(item, raw, prefill);
1499
+ continue;
1500
+ }
1501
+ if (raw !== "") {
1502
+ draft[item.key] = raw;
1503
+ continue;
1504
+ }
1505
+ const top = item.prefillFrom === void 0 ? void 0 : prefill[item.prefillFrom];
1506
+ if (typeof top === "string" && top !== "") {
1507
+ draft[item.key] = top;
1508
+ continue;
1509
+ }
1510
+ const resolved = item.prefillResolved === void 0 ? void 0 : prefill.resolved?.[item.prefillResolved];
1511
+ draft[item.key] = typeof resolved === "string" ? resolved : raw;
1496
1512
  }
1497
1513
  return draft;
1498
1514
  }
1499
- /** 表单态 → 配置取值(按控件种类还原类型;空串对文本项照收,语义由「未配」承担)。 */
1515
+ /** 表单态 → 配置取值(按控件种类还原类型;空串对文本项照收,语义由「未配」承担)。
1516
+ *
1517
+ * **只读行不收**(#757):它们显示的是技能算好的绝对路径,写回配置就是把「显示的路径」当成「配置值」——
1518
+ * 保存只提交真能改的那些行(本家=数据目录/照片目录/训记 KEY 三行),其余键由技能侧做组内合并保留现值。 */
1500
1519
  function fromDraft(draft) {
1501
1520
  const values = {};
1502
1521
  for (const item of CONFIG_ITEMS) {
1522
+ if (item.readonly === true) continue;
1503
1523
  const raw = draft[item.key] ?? "";
1504
- const value = item.control === "number" ? Number(raw) : item.control === "switch" ? raw === "true" : raw;
1505
- writePath(values, item.key, item.control === "number" && !Number.isFinite(value) ? 0 : value);
1524
+ if (item.control === "number") {
1525
+ const n = Number(raw);
1526
+ writePath(values, item.key, Number.isFinite(n) ? n : 0);
1527
+ } else if (item.control === "switch") writePath(values, item.key, raw === "true");
1528
+ else writePath(values, item.key, raw);
1506
1529
  }
1507
1530
  return values;
1508
1531
  }
@@ -1558,31 +1581,38 @@ window.__ModuleLoader__.load({
1558
1581
  /** 一行输入(四种控件对齐受限 YAML 子集:文本/数字/布尔/目录)。
1559
1582
  *
1560
1583
  * 目录档=文本框 + 一枚唤起系统文件夹选择器的按钮;`onBrowse` 缺席(命名空间拿不到、
1561
- * 或这条路已被拒)时不画按钮,文本框照旧——那是该缝自己的契约(供不了就收起入口,不是失败)。 */
1584
+ * 或这条路已被拒)时不画按钮,文本框照旧——那是该缝自己的契约(供不了就收起入口,不是失败)。
1585
+ *
1586
+ * **只读行(#757)**:`item.readonly` 为真时控件一律 `disabled`(值只经技能侧解析后显示、不给改),
1587
+ * 目录行的浏览按钮**保留但不可点击**(#748 定稿:入口不作废,只是不能改)。控件文案**不出现省略号**
1588
+ * (#746 处置 9)——按钮就写「选择文件夹」/「浏览」两个字面。 */
1562
1589
  function Row(props) {
1563
1590
  const { item } = props;
1564
- const onChange = (e) => props.onChange(item.key, e.target.value);
1591
+ const readonly = item.readonly === true;
1592
+ const disabled = props.disabled || readonly;
1593
+ const onChange = readonly ? void 0 : (e) => props.onChange(item.key, e.target.value);
1565
1594
  const control = item.control === "switch" ? react.createElement("input", {
1566
1595
  type: "checkbox",
1567
1596
  checked: props.value === "true",
1568
- disabled: props.disabled,
1569
- onChange: (e) => props.onChange(item.key, e.target.checked ? "true" : "false")
1597
+ disabled,
1598
+ onChange: readonly ? void 0 : (e) => props.onChange(item.key, e.target.checked ? "true" : "false")
1570
1599
  }) : react.createElement("input", {
1571
1600
  style: S.input,
1572
1601
  type: item.control === "number" ? "number" : "text",
1573
1602
  value: props.value,
1574
- disabled: props.disabled,
1603
+ disabled,
1575
1604
  spellCheck: false,
1576
1605
  onChange
1577
1606
  });
1578
- /** 三态入口:供不了(`none`/缺席)就不画,不摆一个点了没反应的死按钮。 */
1607
+ /** 三态入口:供不了(`none`/缺席)就不画,不摆一个点了没反应的死按钮。
1608
+ * 只读行照画(按钮保留),但 `disabled` ⇒ 点不动。 */
1579
1609
  const entry = props.browser ?? null;
1580
1610
  const browse = item.control === "directory" && entry !== null && entry.mode !== "none" ? react.createElement("button", {
1581
1611
  style: S.btnPick,
1582
1612
  type: "button",
1583
- disabled: props.disabled,
1613
+ disabled,
1584
1614
  onClick: () => entry.onOpen(item.key)
1585
- }, entry.mode === "native" ? "选择文件夹…" : "浏览…") : null;
1615
+ }, entry.mode === "native" ? "选择文件夹" : "浏览") : null;
1586
1616
  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));
1587
1617
  }
1588
1618
  /** 技能设置页:承载卡路里自己的全部可配置项(只配置,不干活)。
@@ -1708,7 +1738,9 @@ window.__ModuleLoader__.load({
1708
1738
  onOpen: onOpenRow
1709
1739
  };
1710
1740
  const surface = state.kind === "ready" ? state.surface : null;
1711
- const dirty = surface !== null && CONFIG_ITEMS.some((i) => (draft[i.key] ?? "") !== (toDraft(surface.values, surface)[i.key] ?? ""));
1741
+ /** 脏值只看**可改行**(#757):只读行显示的是技能算好的绝对路径,拿它当「改动」会让打开面板就变「未保存」。 */
1742
+ const editableItems = CONFIG_ITEMS.filter((i) => i.readonly !== true);
1743
+ const dirty = surface !== null && editableItems.some((i) => (draft[i.key] ?? "") !== (toDraft(surface.values, surface)[i.key] ?? ""));
1712
1744
  /** 写完(保存/重置)之后重新读一份整面:写回执是 `{path, values}`、重置回执是 `{path, backupPath}`,
1713
1745
  * 都不是整面——拿写回执当整面用,页头那行「数据目录」会显示成 undefined(#743 实测)。 */
1714
1746
  const writeThenReload = react.useCallback(async (done) => {
@@ -1751,8 +1783,11 @@ window.__ModuleLoader__.load({
1751
1783
  if (r.ok) await writeThenReload("已重置为默认(原配置已另存一份 .bak)");
1752
1784
  else setError(r.message);
1753
1785
  }, [props.getCall, writeThenReload]);
1754
- 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);
1755
- if (state.kind === "loading") return react.createElement("div", { style: S.card }, head, react.createElement("div", { style: S.muted }, "配置读取中…"));
1786
+ /** 头部两行:配置文件路径与**生效**数据目录(#757:回执 `resolved.dbDir` 是技能按配置文件算出来的那一个,
1787
+ * 装了旧技能没有那一组时回落到 `dataDir`——两个值在「数据目录留空」时逐字相同)。 */
1788
+ const headDataDir = surface?.resolved?.dbDir ?? surface?.dataDir ?? "";
1789
+ 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 }, `数据目录 ${headDataDir}`), surface.created ? react.createElement("div", { style: S.muted }, "(配置文件刚按默认值生成)") : null) : null);
1790
+ if (state.kind === "loading") return react.createElement("div", { style: S.card }, head, react.createElement("div", { style: S.muted }, "配置读取中"));
1756
1791
  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", {
1757
1792
  style: S.btn,
1758
1793
  type: "button",
@@ -1780,7 +1815,7 @@ window.__ModuleLoader__.load({
1780
1815
  type: "button",
1781
1816
  disabled: busy || !dirty,
1782
1817
  onClick: () => void onSave()
1783
- }, busy ? "处理中…" : "保存"), react.createElement("button", {
1818
+ }, busy ? "处理中" : "保存"), react.createElement("button", {
1784
1819
  style: S.btn,
1785
1820
  type: "button",
1786
1821
  disabled: busy,
@@ -1801,7 +1836,7 @@ window.__ModuleLoader__.load({
1801
1836
  go: "转到",
1802
1837
  showHidden: (n) => "显示隐藏目录(" + n + ")",
1803
1838
  empty: "这个目录里没有子目录。",
1804
- loading: "正在读取…",
1839
+ loading: "正在读取",
1805
1840
  newFolder: "新建文件夹",
1806
1841
  createConfirm: "创建",
1807
1842
  createCancel: "取消",