dsh-life-pack 0.2.5 → 0.2.6

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
@@ -132,6 +132,13 @@ window.__ModuleLoader__.load({
132
132
  ];
133
133
  //#endregion
134
134
  //#region src/update-contract.ts
135
+ /** 爱生活页签槽名(总管声明 `children`、各单品技能设置页注册进来;单段名,避开官方 `settings.*` 前缀)。
136
+ *
137
+ * 为什么住这里:这个名字**两侧都要用**——面板侧 `client.ts` 拿它注册/投影槽,
138
+ * 宿主侧(`update-env.ts` 的 `readPanelRegistered`)拿它判「已装产物里到底有没有注册代码」。
139
+ * 一处定义、两边引用(结构纪律「概念唯一」):名字改了只有这一行要动。
140
+ * 宿主判据为什么要读这个字面量、以及它的边界,见 `readPanelRegistered` 的头注。 */
141
+ const CONFIG_TAB_SLOT = "ilife.config-tab";
135
142
  /** 载体:宿主注册 `connection.fetch.register` 的路径 / 面板 `connection.rpc.call` 的两段参数。 */
136
143
  const MANAGER_RPC = {
137
144
  /** 载体基段(`connection.rpc.call` 的**第一段**参数)。传错第一段就是 404:真机上踩过一次。 */
@@ -147,7 +154,7 @@ window.__ModuleLoader__.load({
147
154
  const MANAGER_ACTIONS = {
148
155
  /** 装上缺席的目标包:入参 `{packageName, version}`,回包 `{packageName, version}`。 */
149
156
  install: "ilife-manager.install",
150
- /** 七个更新目标的表:入参 `{}`,回包 `{targets: [{key, title, packageName, phones, runningVersion, installedVersion, skill}]}`。
157
+ /** 七个更新目标的表:入参 `{}`,回包 `{targets: [{key, title, packageName, phones, runningVersion, installedVersion, panelRegistered, skill}]}`。
151
158
  *
152
159
  * 电话名为什么由宿主转交而不是面板侧写死:更新包的三个电话名只有它自己知道
153
160
  * (`update.phoneNames`,`host.ts:335`);面板侧既不能 import 更新包(浏览器产物纯度门)
@@ -308,6 +315,13 @@ window.__ModuleLoader__.load({
308
315
  function isAbsent(target) {
309
316
  return target.runningVersion === null && target.installedVersion === null;
310
317
  }
318
+ /** 一家页签此刻的状态:账本里已注册 → `connected`(面板显示它自己的设置页),否则按宿主装机读数分三态。 */
319
+ function slotStateOf(target, inLedger) {
320
+ if (inLedger) return "connected";
321
+ if (target === null || isAbsent(target)) return "absent";
322
+ if (target.panelRegistered === false) return "unregistered-product";
323
+ return "not-connected";
324
+ }
311
325
  /** 把快照翻成一行结论:还没查 / 已是最新 / 有新版 / 装不了(附人话原因)。 */
312
326
  function verdictOf(target, snapshot) {
313
327
  const absent = isAbsent(target);
@@ -564,20 +578,23 @@ window.__ModuleLoader__.load({
564
578
  const [loadError, setLoadError] = react.useState(null);
565
579
  const [checking, setChecking] = react.useState(false);
566
580
  const [open, setOpen] = react.useState(false);
567
- react.useEffect(() => {
568
- let alive = true;
569
- (async () => {
570
- const loaded = await loadTargets(getCall());
571
- if (!alive) return;
572
- if (loaded.ok) {
573
- setTargets(loaded.value.targets);
574
- setPollMs(loaded.value.pollMs);
575
- } else setLoadError(loaded.message);
576
- })();
577
- return () => {
578
- alive = false;
579
- };
581
+ const alive = react.useRef(true);
582
+ react.useEffect(() => () => {
583
+ alive.current = false;
584
+ }, []);
585
+ /** 重取七个目标的表(装机读数变了就重取:缺席卡的态是拿这张表算的,缓存住它会继续说旧话)。 */
586
+ const reload = react.useCallback(async () => {
587
+ const loaded = await loadTargets(getCall());
588
+ if (!alive.current) return;
589
+ if (loaded.ok) {
590
+ setTargets(loaded.value.targets);
591
+ setPollMs(loaded.value.pollMs);
592
+ setLoadError(null);
593
+ } else setLoadError(loaded.message);
580
594
  }, [getCall]);
595
+ react.useEffect(() => {
596
+ reload();
597
+ }, [reload]);
581
598
  const patch = react.useCallback((key, next) => {
582
599
  setRows((previous) => ({
583
600
  ...previous,
@@ -658,6 +675,7 @@ window.__ModuleLoader__.load({
658
675
  outcome: null,
659
676
  failure: refreshed
660
677
  });
678
+ await reload();
661
679
  return;
662
680
  }
663
681
  patch(target.key, {
@@ -673,6 +691,7 @@ window.__ModuleLoader__.load({
673
691
  getCall,
674
692
  patch,
675
693
  pollMs,
694
+ reload,
676
695
  rows
677
696
  ])
678
697
  };
@@ -771,14 +790,16 @@ window.__ModuleLoader__.load({
771
790
  "aria-label": "关闭"
772
791
  }, "×")), body, react.createElement("div", { style: PANEL_STYLE.dialogNote }, "装与更新都在宿主后台跑:关掉这块不影响正在进行的安装,再点「检查更新」可以看到最新状态。"))) : null);
773
792
  }
774
- /** 缺席卡:**按宿主的装机读数分两态**,不让层与层的结论互相顶替。
793
+ /** 缺席卡:**按两处独立事实分三态**,每态只给能改变状态的动作。
775
794
  *
776
- * - 宿主说磁盘上没有这个包 →「未安装」+「装上」按钮(这是本票原有的能力);
777
- * - 宿主说装着(`installedVersion` 非空)却没进页签槽 → 说实情、**不给「装上」**:
778
- * 重装改不了产物里有没有注册代码这件事(真机实测:作息/居家/大厨/记账 的已发布 0.2.0
779
- * 产物里根本没有页签槽注册,见票 #723)。给出可复制命令仅作参考。
795
+ * - `absent`(磁盘上没有这个包)→「未安装」+「装上」按钮(本票原有的能力);
796
+ * - `unregistered-product`(装着,但产物里没有页签槽注册代码)→ 说实情、**不给按钮**:
797
+ * 重装改不了产物里有没有注册代码(真机实测:作息/居家/大厨/记账 的已发布 0.2.0
798
+ * 产物里根本没有注册代码,见票 #723);给可复制命令只是参考;
799
+ * - `not-connected`(装着、产物也有注册代码,设置页却没送到浏览器)→ 重启宿主;仍不行看日志;
800
+ * - `connected` 不归本卡管:那时面板显示的是那家自己的设置页。
780
801
  *
781
- * 原实现只看页签槽 ledger 就断言「未安装」——那对上面第二种情形是说假话,且点「装上」
802
+ * 原实现只看页签槽 ledger 就断言「未安装」——那对后两种情形是说假话,且点「装上」
782
803
  * 会走进更新包的流程(它已经装着 ⇒ 没有新版 ⇒ 没有凭证)并报出误导的「查新版失败」。
783
804
  */
784
805
  function AbsentCard(props) {
@@ -787,10 +808,20 @@ window.__ModuleLoader__.load({
787
808
  const busy = row?.phase === "installing";
788
809
  const manual = (target ? manualForDisplay(target, row?.failure?.manual ?? row?.outcome?.manual ?? null) : null) ?? props.fallbackCommand;
789
810
  const installed = target ? target.installedVersion ?? target.runningVersion : null;
811
+ const state = slotStateOf(target, props.inLedger);
790
812
  let reason = row?.failure?.message ?? null;
791
813
  if (target && reason === null && row?.outcome && row.outcome.snapshot.blockedReason !== null) reason = verdictOf(target, row.outcome.snapshot).text;
792
- const headline = !target ? "这个页签对应的插件还没装。" : installed === null ? "未安装[" + target.packageName + "],装上后这个页签就能用了。" : "插件已装(" + installed + "),但它这一版没把设置页接上爱生活面板:要等它发新版才对得上(重启与重装都不会变)。";
793
- return react.createElement("div", { style: PANEL_STYLE.reco }, react.createElement("div", null, headline), target && installed === null ? react.createElement("button", {
814
+ let headline;
815
+ switch (state) {
816
+ case "absent":
817
+ headline = target ? "未安装[" + target.packageName + "],装上后这个页签就能用了。" : "这个页签对应的插件还没装。";
818
+ break;
819
+ case "unregistered-product":
820
+ headline = target.packageName + " " + String(installed) + " 装着,但它这一版的产物里没有爱生活页签的注册代码:得等它发一个带注册代码的新版才对得上。重启与重装都不会变(重装只是把同一版再装一次)。";
821
+ break;
822
+ default: headline = target.packageName + " " + String(installed) + " 装着、产物里也有注册代码,但它的设置页没送到浏览器:重启宿主后再看这个页签;仍然这样就去宿主日志里找它的报错。";
823
+ }
824
+ return react.createElement("div", { style: PANEL_STYLE.reco }, react.createElement("div", null, headline), state === "absent" ? react.createElement("button", {
794
825
  type: "button",
795
826
  style: busy ? {
796
827
  ...PANEL_STYLE.btn,
@@ -801,7 +832,9 @@ window.__ModuleLoader__.load({
801
832
  marginTop: 8
802
833
  },
803
834
  disabled: busy,
804
- onClick: () => props.face.act(target)
835
+ onClick: () => {
836
+ if (target) props.face.act(target);
837
+ }
805
838
  }, busy ? "正在装,请稍候…" : "装上") : null, reason ? react.createElement("div", { style: PANEL_STYLE.reason }, reason) : null, react.createElement("code", { style: PANEL_STYLE.cmd }, manual));
806
839
  }
807
840
  //#endregion
@@ -990,8 +1023,10 @@ window.__ModuleLoader__.load({
990
1023
  green: "var(--dsw-alias-state-success-primary, #4ec9a0)"
991
1024
  };
992
1025
  /** 整行底色的浅一档:饱和降一档,免得红黄行「刺眼」压过正文(评审第二轮的扣分点)。 */
1026
+ /** 红黄两档整行上底的底色。红灯要**明显重于**黄灯:复评实测原先两个底色亮度几乎一样
1027
+ * (红 `#FBF3F3` 对黄 `#FCF3E5`,只差蓝通道),最严重那一档反而最淡——红色提到 0.14。 */
993
1028
  const STATUS_TINT = {
994
- red: "rgba(192, 57, 43, 0.06)",
1029
+ red: "rgba(192, 57, 43, 0.14)",
995
1030
  yellow: "rgba(224, 138, 0, 0.10)",
996
1031
  green: "transparent"
997
1032
  };
@@ -1128,11 +1163,6 @@ window.__ModuleLoader__.load({
1128
1163
  }
1129
1164
  return prefixes.sort((a, b) => b.length - a.length);
1130
1165
  }
1131
- /** 那一行「配置文件 … · 数据目录 …」+ 各条报文里的长路径,都缩成「公共前缀 + 短尾巴」。 */
1132
- function shorten(path, prefixes) {
1133
- const hit = prefixes.find((prefix) => path.startsWith(prefix + "/"));
1134
- return hit === void 0 ? path : "…/" + path.slice(hit.length + 1);
1135
- }
1136
1166
  /** 一家里所有会印出来的路径。 */
1137
1167
  function pathsOf(report) {
1138
1168
  const out = [report.configPath, report.dataDir];
@@ -1157,11 +1187,12 @@ window.__ModuleLoader__.load({
1157
1187
  function lightsOf(tabs, reports) {
1158
1188
  return tabs.map((tab) => {
1159
1189
  const report = reports[tab.id];
1190
+ const items = report?.items ?? [];
1160
1191
  return {
1161
1192
  id: tab.id,
1162
1193
  title: tab.title,
1163
- status: report ? worstStatus(report.items) : null,
1164
- counts: countByStatus(report?.items ?? [])
1194
+ status: report ? worstStatus(items) : null,
1195
+ counts: countByStatus(items)
1165
1196
  };
1166
1197
  });
1167
1198
  }
@@ -1173,6 +1204,42 @@ window.__ModuleLoader__.load({
1173
1204
  if (parts.length === 0) return "绿";
1174
1205
  return parts.join(" · ");
1175
1206
  }
1207
+ /** 「红 2 · 黄 3」那一截**按档上色**:每一档用它自己的字色、分隔符走淡色。
1208
+ * 为什么必须拆开:原先整截塞进一个 span、染「最严重那一档」的颜色,六家都红时「黄 3」就跟着印成红字——
1209
+ * 本页自己说「黄=还没配」,文字层却把它涂成红(出图复评五份里三份独立指到,取色同为 `#c03828`)。 */
1210
+ function countSegments(light) {
1211
+ if (light.status === null) return [react.createElement("span", {
1212
+ key: "dash",
1213
+ style: {
1214
+ color: INK_DIM,
1215
+ fontWeight: 700
1216
+ }
1217
+ }, "—")];
1218
+ const parts = [];
1219
+ for (const status of ["red", "yellow"]) {
1220
+ const count = light.counts[status];
1221
+ if (count === 0) continue;
1222
+ if (parts.length > 0) parts.push(react.createElement("span", {
1223
+ key: status + "-sep",
1224
+ style: { color: INK_DIM }
1225
+ }, " · "));
1226
+ parts.push(react.createElement("span", {
1227
+ key: status,
1228
+ style: {
1229
+ color: STATUS_TEXT[status],
1230
+ fontWeight: 700
1231
+ }
1232
+ }, STATUS_LABEL[status] + " " + String(count)));
1233
+ }
1234
+ if (parts.length === 0) return [react.createElement("span", {
1235
+ key: "green",
1236
+ style: {
1237
+ color: INK_DIM,
1238
+ fontWeight: 700
1239
+ }
1240
+ }, "绿")];
1241
+ return parts;
1242
+ }
1176
1243
  /** 顶部那行总览:六家一盏灯 + 一个「体检一次」按钮。 */
1177
1244
  function HealthOverview(props) {
1178
1245
  return react.createElement("div", {
@@ -1217,10 +1284,33 @@ window.__ModuleLoader__.load({
1217
1284
  } }), react.createElement("span", { style: {
1218
1285
  color: INK,
1219
1286
  fontWeight: 650
1220
- } }, light.title), react.createElement("span", { style: {
1221
- color: light.status === null ? INK_DIM : STATUS_TEXT[light.status],
1222
- fontWeight: 700
1223
- } }, light.status === null ? "—" : countsText(light.counts))))), react.createElement("div", { style: HEALTH_STYLE.meta }, props.error !== null ? props.error : "只看不改 · 灯=那一家最严重的一档 · 点一下跳到那家配置页"));
1287
+ } }, light.title), ...countSegments(light)))), react.createElement("div", { style: HEALTH_STYLE.meta }, props.error !== null ? props.error : [
1288
+ react.createElement("span", {
1289
+ key: "r",
1290
+ style: {
1291
+ color: STATUS_TEXT.red,
1292
+ fontWeight: 700
1293
+ }
1294
+ }, "红"),
1295
+ react.createElement("span", { key: "r2" }, "=坏了 · "),
1296
+ react.createElement("span", {
1297
+ key: "y",
1298
+ style: {
1299
+ color: STATUS_TEXT.yellow,
1300
+ fontWeight: 700
1301
+ }
1302
+ }, "黄"),
1303
+ react.createElement("span", { key: "y2" }, "=还没配 · "),
1304
+ react.createElement("span", {
1305
+ key: "g",
1306
+ style: {
1307
+ color: STATUS_TEXT.green,
1308
+ fontWeight: 700
1309
+ }
1310
+ }, "绿"),
1311
+ react.createElement("span", { key: "g2" }, "=正常 | 只看不改:不建目录、不改配置、不自动重置"),
1312
+ react.createElement("span", { key: "tab" }, " · 上面那排圆点是体检灯,点一下跳到那家配置页;下面那排圆点是页签(实心=装上了)")
1313
+ ]));
1224
1314
  }
1225
1315
  /** 正正常常的那些:绿条只留「名字」,一句话与「去哪修」都不印(票面验收第一条:正常的收成一行)。 */
1226
1316
  function isAttentionItem(item) {
@@ -1237,10 +1327,11 @@ window.__ModuleLoader__.load({
1237
1327
  }, react.createElement("div", { style: HEALTH_STYLE.headRow }, react.createElement("div", { style: HEALTH_STYLE.head }, props.title + " · 体检"), report ? react.createElement("div", { style: {
1238
1328
  color: INK_DIM,
1239
1329
  fontSize: 12
1240
- } }, countsText(countByStatus(report.items))) : null), props.phase === "idle" ? react.createElement("div", { style: HEALTH_STYLE.meta }, "还没体检:点上面那个「体检一次」。") : null, props.phase === "running" ? react.createElement("div", { style: HEALTH_STYLE.meta }, "正在体检…") : null, props.error !== null ? react.createElement("div", { style: HEALTH_STYLE.error }, props.error) : null, report ? react.createElement("div", null, react.createElement("div", {
1241
- style: HEALTH_STYLE.meta,
1242
- title: prefixes.join(" ")
1243
- }, "配置文件 " + shorten(report.configPath, prefixes) + " · 数据目录 " + shorten(report.dataDir, prefixes)), report.items.filter(isAttentionItem).map((item) => react.createElement("div", {
1330
+ } }, countsText(countByStatus(report.items))) : null), props.phase === "idle" ? react.createElement("div", { style: HEALTH_STYLE.meta }, "还没体检:点上面那个「体检一次」。") : null, props.phase === "running" ? react.createElement("div", { style: HEALTH_STYLE.meta }, "正在体检…") : null, props.error !== null ? react.createElement("div", { style: HEALTH_STYLE.error }, props.error) : null, report ? react.createElement("div", null, report.dataDir !== "" ? react.createElement("div", { style: {
1331
+ color: INK_DIM,
1332
+ fontSize: 11.5,
1333
+ marginTop: 2
1334
+ } }, "落点根目录:" + report.dataDir + (report.configPath !== "" ? "(配置文件 " + report.configPath + ")" : "")) : null, report.items.filter(isAttentionItem).map((item) => react.createElement("div", {
1244
1335
  key: item.id,
1245
1336
  "data-ilife-health": "item",
1246
1337
  "data-status": item.status,
@@ -1267,18 +1358,12 @@ window.__ModuleLoader__.load({
1267
1358
  },
1268
1359
  "aria-hidden": "true"
1269
1360
  }, STATUS_MARK[item.status]), react.createElement("span", { style: {
1270
- width: 8,
1271
- height: 8,
1272
- borderRadius: "50%",
1273
- display: "inline-block",
1274
- background: STATUS_COLOR[item.status]
1275
- } }), react.createElement("span", { style: {
1276
1361
  fontWeight: 650,
1277
1362
  color: INK
1278
1363
  } }, item.title), react.createElement("span", { style: {
1279
1364
  color: STATUS_TEXT[item.status],
1280
1365
  fontWeight: 700
1281
- } }, STATUS_LABEL[item.status]), item.source ? react.createElement("span", { style: { color: INK_DIM } }, "· 来自" + item.source) : null), react.createElement("div", {
1366
+ } }, STATUS_LABEL[item.status]), item.source && item.source !== "默认值" ? react.createElement("span", { style: { color: INK_DIM } }, "· 来自" + item.source) : null), react.createElement("div", {
1282
1367
  style: { color: INK },
1283
1368
  title: item.message
1284
1369
  }, shortenPathsIn(item.message, prefixes)), item.action.length > 0 ? react.createElement("div", { style: { color: INK_DIM } }, "去哪修:" + actionText(item.action)) : null)), report.items.some((item) => !isAttentionItem(item)) ? react.createElement("div", {
@@ -1289,16 +1374,27 @@ window.__ModuleLoader__.load({
1289
1374
  alignItems: "center",
1290
1375
  gap: "2px 10px",
1291
1376
  marginTop: 8,
1292
- paddingTop: 6,
1293
- borderTop: "1px solid " + BORDER,
1377
+ padding: "6px 8px",
1378
+ borderLeft: "3px solid " + STATUS_COLOR.green,
1379
+ borderRadius: 6,
1294
1380
  fontSize: 12,
1295
1381
  lineHeight: 1.6,
1296
1382
  color: INK_DIM
1297
1383
  }
1298
- }, react.createElement("span", { style: {
1384
+ }, react.createElement("span", {
1385
+ style: {
1386
+ width: 8,
1387
+ height: 8,
1388
+ borderRadius: "50%",
1389
+ display: "inline-block",
1390
+ background: STATUS_COLOR.green,
1391
+ flex: "0 0 auto"
1392
+ },
1393
+ "aria-hidden": "true"
1394
+ }), react.createElement("span", { style: {
1299
1395
  fontWeight: 650,
1300
1396
  color: INK
1301
- } }, "正常 " + String(countByStatus(report.items).green) + " 条"), report.items.filter((item) => !isAttentionItem(item)).map((item) => react.createElement("span", {
1397
+ } }, "正常 " + String(countByStatus(report.items).green) + " 条"), react.createElement("span", { style: { color: INK_DIM } }, "(下列各项都正常工作)"), report.items.filter((item) => !isAttentionItem(item)).map((item) => react.createElement("span", {
1302
1398
  key: item.id,
1303
1399
  "data-ilife-health": "ok-item"
1304
1400
  }, "· " + item.title))) : null) : null);
@@ -1324,8 +1420,6 @@ window.__ModuleLoader__.load({
1324
1420
  * 文案与网址都取自 nav.ts 的两张静态表,本文件只画不算。
1325
1421
  */
1326
1422
  const inject = ["slots", "connection"];
1327
- /** 爱生活页签槽(总管声明的 children,技能设置页注册进来;单段名,避开官方 settings.* 前缀)。 */
1328
- const CONFIG_TAB_SLOT = "ilife.config-tab";
1329
1423
  /** 总管视觉(内联 style;颜色走 DSH 主题别名,深浅主题自适应,写死值只做回退;与技能面板同语言)。 */
1330
1424
  const S = {
1331
1425
  headRow: {
@@ -1559,7 +1653,7 @@ window.__ModuleLoader__.load({
1559
1653
  setActiveId(next.plugin);
1560
1654
  tabRefs.current[nextIndex]?.focus();
1561
1655
  }
1562
- return react.createElement("div", null, react.createElement("div", { style: S.headRow }, react.createElement("div", { style: S.head }, "爱生活"), react.createElement("div", { style: S.headActions }, react.createElement(CheckUpdateButton, { face }), react.createElement(PanelActions, null))), react.createElement("div", { style: S.meta }, react.createElement("div", null, "总开关 · 开关(缺省启用,只读)"), react.createElement("div", null, "总管 dsh-life-pack · 0.2.5")), react.createElement(UpdateResults, { face }), react.createElement(HealthOverview, {
1656
+ return react.createElement("div", null, react.createElement("div", { style: S.headRow }, react.createElement("div", { style: S.head }, "爱生活"), react.createElement("div", { style: S.headActions }, react.createElement(CheckUpdateButton, { face }), react.createElement(PanelActions, null))), react.createElement("div", { style: S.meta }, react.createElement("div", null, "总开关 · 开关(缺省启用,只读)"), react.createElement("div", null, "总管 dsh-life-pack · 0.2.6")), react.createElement(UpdateResults, { face }), react.createElement(HealthOverview, {
1563
1657
  lights: lightsOf(rows.map((row) => ({
1564
1658
  id: row.id,
1565
1659
  title: labelForTab(row.id)
@@ -1612,6 +1706,7 @@ window.__ModuleLoader__.load({
1612
1706
  })) : react.createElement(AbsentCard, {
1613
1707
  target: targetForTab(tab),
1614
1708
  fallbackCommand: recoFor(tab).installCmd,
1709
+ inLedger: present.has(tab.plugin),
1615
1710
  face
1616
1711
  }));
1617
1712
  }), react.createElement(MorePluginsCard, null));