dsh-life-pack 0.2.5 → 0.2.7
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 +371 -147
- package/dist/client.js.map +1 -1
- package/dist/health-view.d.ts +46 -10
- package/dist/health-view.d.ts.map +1 -1
- package/dist/health-view.js +152 -68
- package/dist/health-view.js.map +1 -1
- package/dist/nav.d.ts +1 -1
- package/dist/nav.js +1 -1
- package/dist/update-contract.d.ts +8 -1
- package/dist/update-contract.d.ts.map +1 -1
- package/dist/update-contract.js +8 -1
- package/dist/update-contract.js.map +1 -1
- package/dist/update-env.d.ts +16 -0
- package/dist/update-env.d.ts.map +1 -1
- package/dist/update-env.js +70 -0
- package/dist/update-env.js.map +1 -1
- package/dist/update-host.d.ts.map +1 -1
- package/dist/update-host.js +4 -1
- package/dist/update-host.js.map +1 -1
- package/dist/update-view.d.ts +12 -0
- package/dist/update-view.d.ts.map +1 -1
- package/dist/update-view.js +10 -0
- package/dist/update-view.js.map +1 -1
- package/package.json +1 -1
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);
|
|
@@ -480,17 +494,40 @@ window.__ModuleLoader__.load({
|
|
|
480
494
|
marginBottom: 8,
|
|
481
495
|
borderRadius: 10,
|
|
482
496
|
border: "1px solid var(--dsw-alias-border, rgba(128,128,128,.3))",
|
|
497
|
+
borderLeftWidth: 3,
|
|
483
498
|
fontSize: 13,
|
|
484
499
|
lineHeight: 1.7
|
|
485
500
|
},
|
|
486
501
|
rowHead: {
|
|
487
|
-
|
|
502
|
+
display: "flex",
|
|
503
|
+
alignItems: "baseline",
|
|
504
|
+
gap: 6,
|
|
488
505
|
marginBottom: 2
|
|
489
506
|
},
|
|
507
|
+
rowTitle: {
|
|
508
|
+
fontWeight: 700,
|
|
509
|
+
color: "var(--dsw-alias-label-primary, inherit)"
|
|
510
|
+
},
|
|
511
|
+
rowPkg: {
|
|
512
|
+
color: "var(--dsw-alias-label-tertiary, #8a8a8a)",
|
|
513
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
514
|
+
fontSize: 12
|
|
515
|
+
},
|
|
490
516
|
rowMeta: {
|
|
491
517
|
color: "var(--dsw-alias-label-secondary, #9a9a9a)",
|
|
492
518
|
fontSize: 12
|
|
493
519
|
},
|
|
520
|
+
/** 版本号用等宽字:数字要能对齐着读(「当前」「最新」两列一眼比大小)。 */
|
|
521
|
+
ver: {
|
|
522
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
523
|
+
color: "var(--dsw-alias-label-primary, inherit)",
|
|
524
|
+
fontWeight: 600
|
|
525
|
+
},
|
|
526
|
+
/** 结论那一行:**只有要动的那几档上色**(绿/灰不抢注意力,照 health-view 的三档口径)。 */
|
|
527
|
+
verdictLine: {
|
|
528
|
+
fontSize: 12.5,
|
|
529
|
+
marginTop: 2
|
|
530
|
+
},
|
|
494
531
|
reason: {
|
|
495
532
|
color: "var(--dsw-alias-state-error-primary, #ff6b6b)",
|
|
496
533
|
fontSize: 12,
|
|
@@ -499,7 +536,26 @@ window.__ModuleLoader__.load({
|
|
|
499
536
|
skill: {
|
|
500
537
|
color: "var(--dsw-alias-label-tertiary, #8a8a8a)",
|
|
501
538
|
fontSize: 12,
|
|
502
|
-
marginTop: 4
|
|
539
|
+
marginTop: 4,
|
|
540
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace"
|
|
541
|
+
},
|
|
542
|
+
/** 行内主按钮(装上/装上更新):品牌底色 —— 全屏只有「能点的」这一种是这个颜色。 */
|
|
543
|
+
btnPrimary: {
|
|
544
|
+
border: "1px solid transparent",
|
|
545
|
+
background: "var(--dsw-alias-brand-primary, #0a84ff)",
|
|
546
|
+
color: "#fff",
|
|
547
|
+
borderRadius: 8,
|
|
548
|
+
padding: "5px 14px",
|
|
549
|
+
fontSize: 13,
|
|
550
|
+
fontWeight: 700,
|
|
551
|
+
cursor: "pointer",
|
|
552
|
+
marginTop: 6
|
|
553
|
+
},
|
|
554
|
+
/** 分档标记:**形状是第二条读数**(灰度截图、色弱视角下也分得开哪一行要动)。 */
|
|
555
|
+
mark: {
|
|
556
|
+
fontWeight: 700,
|
|
557
|
+
fontSize: 13,
|
|
558
|
+
lineHeight: 1
|
|
503
559
|
},
|
|
504
560
|
overlay: {
|
|
505
561
|
position: "fixed",
|
|
@@ -511,12 +567,14 @@ window.__ModuleLoader__.load({
|
|
|
511
567
|
padding: 16,
|
|
512
568
|
background: "rgba(0,0,0,.45)"
|
|
513
569
|
},
|
|
570
|
+
/** 浮层是**竖排两段**:题头固定、结果列表自己滚(题头跟着滚会把关闭键滚出视野 —— 用户实测反馈)。 */
|
|
514
571
|
dialog: {
|
|
515
572
|
width: "100%",
|
|
516
573
|
maxWidth: 560,
|
|
517
574
|
maxHeight: "80vh",
|
|
518
|
-
|
|
519
|
-
|
|
575
|
+
display: "flex",
|
|
576
|
+
flexDirection: "column",
|
|
577
|
+
overflow: "hidden",
|
|
520
578
|
borderRadius: 12,
|
|
521
579
|
border: "1px solid var(--dsw-alias-border, rgba(128,128,128,.4))",
|
|
522
580
|
background: "var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #222))",
|
|
@@ -528,12 +586,21 @@ window.__ModuleLoader__.load({
|
|
|
528
586
|
alignItems: "center",
|
|
529
587
|
justifyContent: "space-between",
|
|
530
588
|
gap: 8,
|
|
531
|
-
|
|
589
|
+
flex: "0 0 auto",
|
|
590
|
+
padding: "12px 14px",
|
|
591
|
+
borderBottom: "1px solid var(--dsw-alias-border, rgba(128,128,128,.3))"
|
|
532
592
|
},
|
|
533
593
|
dialogTitle: {
|
|
534
594
|
fontSize: 14,
|
|
535
595
|
fontWeight: 700
|
|
536
596
|
},
|
|
597
|
+
/** 唯一会滚的那一段。 */
|
|
598
|
+
dialogBody: {
|
|
599
|
+
flex: "1 1 auto",
|
|
600
|
+
minHeight: 0,
|
|
601
|
+
overflowY: "auto",
|
|
602
|
+
padding: "12px 14px"
|
|
603
|
+
},
|
|
537
604
|
close: {
|
|
538
605
|
border: "1px solid var(--dsw-alias-border, rgba(128,128,128,.35))",
|
|
539
606
|
background: "transparent",
|
|
@@ -551,6 +618,45 @@ window.__ModuleLoader__.load({
|
|
|
551
618
|
marginTop: 4
|
|
552
619
|
}
|
|
553
620
|
};
|
|
621
|
+
const ROW_TONE = {
|
|
622
|
+
update: {
|
|
623
|
+
mark: "↑",
|
|
624
|
+
color: "var(--dsw-alias-brand-primary, #0a84ff)",
|
|
625
|
+
tint: "rgba(10,132,255,.08)"
|
|
626
|
+
},
|
|
627
|
+
blocked: {
|
|
628
|
+
mark: "!",
|
|
629
|
+
color: "var(--dsw-alias-state-warning-primary, #d8a300)",
|
|
630
|
+
tint: "rgba(216,163,0,.10)"
|
|
631
|
+
},
|
|
632
|
+
failed: {
|
|
633
|
+
mark: "✕",
|
|
634
|
+
color: "var(--dsw-alias-state-error-primary, #ff6b6b)",
|
|
635
|
+
tint: "rgba(255,107,107,.10)"
|
|
636
|
+
},
|
|
637
|
+
ok: {
|
|
638
|
+
mark: "✓",
|
|
639
|
+
color: "var(--dsw-alias-state-success-primary, #4ec9a0)",
|
|
640
|
+
tint: "transparent"
|
|
641
|
+
},
|
|
642
|
+
idle: {
|
|
643
|
+
mark: "·",
|
|
644
|
+
color: "var(--dsw-alias-label-tertiary, #8a8a8a)",
|
|
645
|
+
tint: "transparent"
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
/** 这一行算哪一档:取数失败 > 装不上 > 有新版 > 待重启/源码装 > 已是最新 > 未查。 */
|
|
649
|
+
function rowToneOf(row, verdict) {
|
|
650
|
+
if (row.failure !== null || row.phase === "failed") return "failed";
|
|
651
|
+
if (row.phase === "installing") return "update";
|
|
652
|
+
if (verdict === null) return "idle";
|
|
653
|
+
switch (verdict.kind) {
|
|
654
|
+
case "update-available": return "update";
|
|
655
|
+
case "blocked": return "blocked";
|
|
656
|
+
case "up-to-date": return "ok";
|
|
657
|
+
default: return "idle";
|
|
658
|
+
}
|
|
659
|
+
}
|
|
554
660
|
const IDLE$1 = {
|
|
555
661
|
phase: "idle",
|
|
556
662
|
outcome: null,
|
|
@@ -564,20 +670,23 @@ window.__ModuleLoader__.load({
|
|
|
564
670
|
const [loadError, setLoadError] = react.useState(null);
|
|
565
671
|
const [checking, setChecking] = react.useState(false);
|
|
566
672
|
const [open, setOpen] = react.useState(false);
|
|
567
|
-
react.
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
};
|
|
673
|
+
const alive = react.useRef(true);
|
|
674
|
+
react.useEffect(() => () => {
|
|
675
|
+
alive.current = false;
|
|
676
|
+
}, []);
|
|
677
|
+
/** 重取七个目标的表(装机读数变了就重取:缺席卡的态是拿这张表算的,缓存住它会继续说旧话)。 */
|
|
678
|
+
const reload = react.useCallback(async () => {
|
|
679
|
+
const loaded = await loadTargets(getCall());
|
|
680
|
+
if (!alive.current) return;
|
|
681
|
+
if (loaded.ok) {
|
|
682
|
+
setTargets(loaded.value.targets);
|
|
683
|
+
setPollMs(loaded.value.pollMs);
|
|
684
|
+
setLoadError(null);
|
|
685
|
+
} else setLoadError(loaded.message);
|
|
580
686
|
}, [getCall]);
|
|
687
|
+
react.useEffect(() => {
|
|
688
|
+
reload();
|
|
689
|
+
}, [reload]);
|
|
581
690
|
const patch = react.useCallback((key, next) => {
|
|
582
691
|
setRows((previous) => ({
|
|
583
692
|
...previous,
|
|
@@ -658,6 +767,7 @@ window.__ModuleLoader__.load({
|
|
|
658
767
|
outcome: null,
|
|
659
768
|
failure: refreshed
|
|
660
769
|
});
|
|
770
|
+
await reload();
|
|
661
771
|
return;
|
|
662
772
|
}
|
|
663
773
|
patch(target.key, {
|
|
@@ -673,6 +783,7 @@ window.__ModuleLoader__.load({
|
|
|
673
783
|
getCall,
|
|
674
784
|
patch,
|
|
675
785
|
pollMs,
|
|
786
|
+
reload,
|
|
676
787
|
rows
|
|
677
788
|
])
|
|
678
789
|
};
|
|
@@ -730,18 +841,43 @@ window.__ModuleLoader__.load({
|
|
|
730
841
|
const manual = manualForDisplay(target, row.failure?.manual ?? row.outcome?.manual ?? null);
|
|
731
842
|
const busy = row.phase === "installing";
|
|
732
843
|
const showManual = manual !== null && (row.phase === "failed" || verdict?.kind === "blocked");
|
|
844
|
+
const tone = rowToneOf(row, verdict);
|
|
845
|
+
const paint = ROW_TONE[tone];
|
|
846
|
+
/** 这一行「当前/最新」两个版本号:不等才算有新版,把「最新」那格上色(用户扫一眼先看有没有箭头)。 */
|
|
847
|
+
const running = snapshot?.runningVersion ?? target.runningVersion ?? null;
|
|
848
|
+
const latest = snapshot?.latestVersion ?? null;
|
|
849
|
+
const newer = running !== null && latest !== null && latest !== running;
|
|
850
|
+
const lit = tone === "update" || tone === "blocked" || tone === "failed";
|
|
733
851
|
return react.createElement("div", {
|
|
734
852
|
key: target.key,
|
|
735
|
-
style:
|
|
736
|
-
|
|
853
|
+
style: {
|
|
854
|
+
...PANEL_STYLE.row,
|
|
855
|
+
borderLeftColor: paint.color,
|
|
856
|
+
background: paint.tint
|
|
857
|
+
}
|
|
858
|
+
}, react.createElement("div", { style: PANEL_STYLE.rowHead }, react.createElement("span", {
|
|
859
|
+
style: {
|
|
860
|
+
...PANEL_STYLE.mark,
|
|
861
|
+
color: paint.color
|
|
862
|
+
},
|
|
863
|
+
"aria-hidden": "true"
|
|
864
|
+
}, paint.mark), react.createElement("span", { style: PANEL_STYLE.rowTitle }, target.title), react.createElement("span", { style: PANEL_STYLE.rowPkg }, "· " + target.packageName)), react.createElement("div", { style: PANEL_STYLE.rowMeta }, "当前 ", react.createElement("b", { style: PANEL_STYLE.ver }, String(running ?? "未安装")), " · 最新 ", react.createElement("b", { style: newer ? {
|
|
865
|
+
...PANEL_STYLE.ver,
|
|
866
|
+
color: paint.color
|
|
867
|
+
} : PANEL_STYLE.ver }, String(latest ?? "未查"))), react.createElement("div", { style: lit ? {
|
|
868
|
+
...PANEL_STYLE.verdictLine,
|
|
869
|
+
color: paint.color,
|
|
870
|
+
fontWeight: 600
|
|
871
|
+
} : {
|
|
872
|
+
...PANEL_STYLE.verdictLine,
|
|
873
|
+
color: "var(--dsw-alias-label-secondary, #9a9a9a)"
|
|
874
|
+
} }, busy ? "正在装,请稍候…" : verdict?.text ?? (checking ? "检查中…" : "")), verdict?.action ? react.createElement("button", {
|
|
737
875
|
type: "button",
|
|
738
876
|
style: busy ? {
|
|
739
|
-
...PANEL_STYLE.
|
|
740
|
-
opacity: .55
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
marginTop: 6
|
|
744
|
-
},
|
|
877
|
+
...PANEL_STYLE.btnPrimary,
|
|
878
|
+
opacity: .55,
|
|
879
|
+
cursor: "default"
|
|
880
|
+
} : PANEL_STYLE.btnPrimary,
|
|
745
881
|
disabled: busy,
|
|
746
882
|
onClick: () => props.face.act(target)
|
|
747
883
|
}, verdict.action === "install" ? "装上" : "装上更新") : null, row.failure ? react.createElement("div", { style: PANEL_STYLE.reason }, "装不上:" + row.failure.message) : null, versionLines(target, snapshot).map((line, index) => react.createElement("div", {
|
|
@@ -763,22 +899,24 @@ window.__ModuleLoader__.load({
|
|
|
763
899
|
onKeyDown: (event) => {
|
|
764
900
|
if (event.key === "Escape") close();
|
|
765
901
|
}
|
|
766
|
-
}, react.createElement("div", { style: PANEL_STYLE.dialogHead }, react.createElement("div", { style: PANEL_STYLE.dialogTitle }, "
|
|
902
|
+
}, react.createElement("div", { style: PANEL_STYLE.dialogHead }, react.createElement("div", { style: PANEL_STYLE.dialogTitle }, "检查更新"), react.createElement("button", {
|
|
767
903
|
type: "button",
|
|
768
904
|
style: PANEL_STYLE.close,
|
|
769
905
|
onClick: () => close(),
|
|
770
906
|
title: "关闭",
|
|
771
907
|
"aria-label": "关闭"
|
|
772
|
-
}, "×")), body, react.createElement("div", { style: PANEL_STYLE.dialogNote }, "装与更新都在宿主后台跑:关掉这块不影响正在进行的安装,再点「检查更新」可以看到最新状态。"))) : null);
|
|
908
|
+
}, "×")), react.createElement("div", { style: PANEL_STYLE.dialogBody }, body, react.createElement("div", { style: PANEL_STYLE.dialogNote }, "装与更新都在宿主后台跑:关掉这块不影响正在进行的安装,再点「检查更新」可以看到最新状态。")))) : null);
|
|
773
909
|
}
|
|
774
|
-
/**
|
|
910
|
+
/** 缺席卡:**按两处独立事实分三态**,每态只给能改变状态的动作。
|
|
775
911
|
*
|
|
776
|
-
* -
|
|
777
|
-
* -
|
|
778
|
-
*
|
|
779
|
-
*
|
|
912
|
+
* - `absent`(磁盘上没有这个包)→「未安装」+「装上」按钮(本票原有的能力);
|
|
913
|
+
* - `unregistered-product`(装着,但产物里没有页签槽注册代码)→ 说实情、**不给按钮**:
|
|
914
|
+
* 重装改不了产物里有没有注册代码(真机实测:作息/居家/大厨/记账 的已发布 0.2.0
|
|
915
|
+
* 产物里根本没有注册代码,见票 #723);给可复制命令只是参考;
|
|
916
|
+
* - `not-connected`(装着、产物也有注册代码,设置页却没送到浏览器)→ 重启宿主;仍不行看日志;
|
|
917
|
+
* - `connected` 不归本卡管:那时面板显示的是那家自己的设置页。
|
|
780
918
|
*
|
|
781
|
-
* 原实现只看页签槽 ledger
|
|
919
|
+
* 原实现只看页签槽 ledger 就断言「未安装」——那对后两种情形是说假话,且点「装上」
|
|
782
920
|
* 会走进更新包的流程(它已经装着 ⇒ 没有新版 ⇒ 没有凭证)并报出误导的「查新版失败」。
|
|
783
921
|
*/
|
|
784
922
|
function AbsentCard(props) {
|
|
@@ -787,10 +925,20 @@ window.__ModuleLoader__.load({
|
|
|
787
925
|
const busy = row?.phase === "installing";
|
|
788
926
|
const manual = (target ? manualForDisplay(target, row?.failure?.manual ?? row?.outcome?.manual ?? null) : null) ?? props.fallbackCommand;
|
|
789
927
|
const installed = target ? target.installedVersion ?? target.runningVersion : null;
|
|
928
|
+
const state = slotStateOf(target, props.inLedger);
|
|
790
929
|
let reason = row?.failure?.message ?? null;
|
|
791
930
|
if (target && reason === null && row?.outcome && row.outcome.snapshot.blockedReason !== null) reason = verdictOf(target, row.outcome.snapshot).text;
|
|
792
|
-
|
|
793
|
-
|
|
931
|
+
let headline;
|
|
932
|
+
switch (state) {
|
|
933
|
+
case "absent":
|
|
934
|
+
headline = target ? "未安装[" + target.packageName + "],装上后这个页签就能用了。" : "这个页签对应的插件还没装。";
|
|
935
|
+
break;
|
|
936
|
+
case "unregistered-product":
|
|
937
|
+
headline = target.packageName + " " + String(installed) + " 装着,但它这一版的产物里没有爱生活页签的注册代码:得等它发一个带注册代码的新版才对得上。重启与重装都不会变(重装只是把同一版再装一次)。";
|
|
938
|
+
break;
|
|
939
|
+
default: headline = target.packageName + " " + String(installed) + " 装着、产物里也有注册代码,但它的设置页没送到浏览器:重启宿主后再看这个页签;仍然这样就去宿主日志里找它的报错。";
|
|
940
|
+
}
|
|
941
|
+
return react.createElement("div", { style: PANEL_STYLE.reco }, react.createElement("div", null, headline), state === "absent" ? react.createElement("button", {
|
|
794
942
|
type: "button",
|
|
795
943
|
style: busy ? {
|
|
796
944
|
...PANEL_STYLE.btn,
|
|
@@ -801,7 +949,9 @@ window.__ModuleLoader__.load({
|
|
|
801
949
|
marginTop: 8
|
|
802
950
|
},
|
|
803
951
|
disabled: busy,
|
|
804
|
-
onClick: () =>
|
|
952
|
+
onClick: () => {
|
|
953
|
+
if (target) props.face.act(target);
|
|
954
|
+
}
|
|
805
955
|
}, busy ? "正在装,请稍候…" : "装上") : null, reason ? react.createElement("div", { style: PANEL_STYLE.reason }, reason) : null, react.createElement("code", { style: PANEL_STYLE.cmd }, manual));
|
|
806
956
|
}
|
|
807
957
|
//#endregion
|
|
@@ -990,8 +1140,10 @@ window.__ModuleLoader__.load({
|
|
|
990
1140
|
green: "var(--dsw-alias-state-success-primary, #4ec9a0)"
|
|
991
1141
|
};
|
|
992
1142
|
/** 整行底色的浅一档:饱和降一档,免得红黄行「刺眼」压过正文(评审第二轮的扣分点)。 */
|
|
1143
|
+
/** 红黄两档整行上底的底色。红灯要**明显重于**黄灯:复评实测原先两个底色亮度几乎一样
|
|
1144
|
+
* (红 `#FBF3F3` 对黄 `#FCF3E5`,只差蓝通道),最严重那一档反而最淡——红色提到 0.14。 */
|
|
993
1145
|
const STATUS_TINT = {
|
|
994
|
-
red: "rgba(192, 57, 43, 0.
|
|
1146
|
+
red: "rgba(192, 57, 43, 0.14)",
|
|
995
1147
|
yellow: "rgba(224, 138, 0, 0.10)",
|
|
996
1148
|
green: "transparent"
|
|
997
1149
|
};
|
|
@@ -1035,45 +1187,40 @@ window.__ModuleLoader__.load({
|
|
|
1035
1187
|
fontWeight: 700,
|
|
1036
1188
|
color: INK
|
|
1037
1189
|
},
|
|
1038
|
-
/**
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
border: "1px solid " + BORDER,
|
|
1042
|
-
background: "transparent",
|
|
1043
|
-
color: INK,
|
|
1044
|
-
borderRadius: 8,
|
|
1045
|
-
padding: "6px 16px",
|
|
1046
|
-
fontSize: 13,
|
|
1047
|
-
fontWeight: 600,
|
|
1048
|
-
cursor: "pointer"
|
|
1049
|
-
},
|
|
1050
|
-
lightsRow: {
|
|
1190
|
+
/** 顶部那一行汇总(票 #732):一行装下「配置体检 + 一句总账 + 体检按钮」,三样都不独占行。
|
|
1191
|
+
* 它替掉了原先「标题行 + 单独一行的按钮 + 一排灯按钮 + 两行图例」(同屏实测 153px → 一行)。 */
|
|
1192
|
+
summaryRow: {
|
|
1051
1193
|
display: "flex",
|
|
1052
|
-
flexWrap: "wrap",
|
|
1053
|
-
gap: 8
|
|
1054
|
-
},
|
|
1055
|
-
light: {
|
|
1056
|
-
display: "inline-flex",
|
|
1057
1194
|
alignItems: "center",
|
|
1058
|
-
gap:
|
|
1059
|
-
|
|
1060
|
-
border: "1px solid " + BORDER,
|
|
1061
|
-
borderRadius: 999,
|
|
1062
|
-
padding: "3px 10px",
|
|
1063
|
-
fontSize: 12.5,
|
|
1064
|
-
color: INK,
|
|
1065
|
-
cursor: "pointer"
|
|
1195
|
+
gap: 10,
|
|
1196
|
+
marginBottom: 10
|
|
1066
1197
|
},
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1198
|
+
summaryText: {
|
|
1199
|
+
flex: "1 1 auto",
|
|
1200
|
+
minWidth: 0,
|
|
1201
|
+
fontSize: 12.5,
|
|
1202
|
+
whiteSpace: "nowrap",
|
|
1203
|
+
overflow: "hidden",
|
|
1204
|
+
textOverflow: "ellipsis"
|
|
1070
1205
|
},
|
|
1206
|
+
/** 各家那张表里的说明行(「还没体检:点上面那个…」那几句)。 */
|
|
1071
1207
|
meta: {
|
|
1072
1208
|
color: INK_DIM,
|
|
1073
1209
|
fontSize: 12,
|
|
1074
1210
|
lineHeight: 1.7,
|
|
1075
1211
|
marginTop: 12
|
|
1076
1212
|
},
|
|
1213
|
+
btn: {
|
|
1214
|
+
border: "1px solid " + BORDER,
|
|
1215
|
+
background: "transparent",
|
|
1216
|
+
color: INK,
|
|
1217
|
+
borderRadius: 8,
|
|
1218
|
+
flex: "0 0 auto",
|
|
1219
|
+
padding: "3px 10px",
|
|
1220
|
+
fontSize: 12.5,
|
|
1221
|
+
fontWeight: 600,
|
|
1222
|
+
cursor: "pointer"
|
|
1223
|
+
},
|
|
1077
1224
|
error: {
|
|
1078
1225
|
color: STATUS_COLOR.red,
|
|
1079
1226
|
fontSize: 12,
|
|
@@ -1128,11 +1275,6 @@ window.__ModuleLoader__.load({
|
|
|
1128
1275
|
}
|
|
1129
1276
|
return prefixes.sort((a, b) => b.length - a.length);
|
|
1130
1277
|
}
|
|
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
1278
|
/** 一家里所有会印出来的路径。 */
|
|
1137
1279
|
function pathsOf(report) {
|
|
1138
1280
|
const out = [report.configPath, report.dataDir];
|
|
@@ -1157,15 +1299,17 @@ window.__ModuleLoader__.load({
|
|
|
1157
1299
|
function lightsOf(tabs, reports) {
|
|
1158
1300
|
return tabs.map((tab) => {
|
|
1159
1301
|
const report = reports[tab.id];
|
|
1302
|
+
const items = report?.items ?? [];
|
|
1160
1303
|
return {
|
|
1161
1304
|
id: tab.id,
|
|
1162
1305
|
title: tab.title,
|
|
1163
|
-
status: report ? worstStatus(
|
|
1164
|
-
counts: countByStatus(
|
|
1306
|
+
status: report ? worstStatus(items) : null,
|
|
1307
|
+
counts: countByStatus(items),
|
|
1308
|
+
hasChannel: tab.hasChannel ?? true
|
|
1165
1309
|
};
|
|
1166
1310
|
});
|
|
1167
1311
|
}
|
|
1168
|
-
/** 一家的一句话读数:「红 1 黄 2
|
|
1312
|
+
/** 一家的一句话读数:「红 1 黄 2」,全绿就是「绿」。面板侧要**上色**的那几处不用它(见 `countSegments`)。 */
|
|
1169
1313
|
function countsText(counts) {
|
|
1170
1314
|
const parts = [];
|
|
1171
1315
|
if (counts.red > 0) parts.push("红 " + String(counts.red));
|
|
@@ -1173,12 +1317,103 @@ window.__ModuleLoader__.load({
|
|
|
1173
1317
|
if (parts.length === 0) return "绿";
|
|
1174
1318
|
return parts.join(" · ");
|
|
1175
1319
|
}
|
|
1176
|
-
/**
|
|
1177
|
-
|
|
1320
|
+
/** 把一份计数拆成**按档分色**的几截。红黄各自一截、各自上色,互不串色;
|
|
1321
|
+
* 「黄 3」这一截为什么不能被染成红——见 `countSegments` 的注释(出图复评逮到的真缺陷)。 */
|
|
1322
|
+
function countSegsOf(light) {
|
|
1323
|
+
if (light.status === null) return [{
|
|
1324
|
+
status: null,
|
|
1325
|
+
text: "—"
|
|
1326
|
+
}];
|
|
1327
|
+
const segs = [];
|
|
1328
|
+
for (const status of ["red", "yellow"]) {
|
|
1329
|
+
const count = light.counts[status];
|
|
1330
|
+
if (count > 0) segs.push({
|
|
1331
|
+
status,
|
|
1332
|
+
text: STATUS_LABEL[status] + " " + String(count)
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
return segs.length > 0 ? segs : [{
|
|
1336
|
+
status: "green",
|
|
1337
|
+
text: "绿"
|
|
1338
|
+
}];
|
|
1339
|
+
}
|
|
1340
|
+
/** 几截读数之间那个分隔符:不许染档位色(它不属于任何一档)。 */
|
|
1341
|
+
const SEG_SEP = " · ";
|
|
1342
|
+
/** 汇总行那一句:绿要显眼(它是「正常」这件事本身),没通道的那几家不冒充绿。 */
|
|
1343
|
+
function overallText(lights) {
|
|
1344
|
+
const pending = lights.filter((light) => !light.hasChannel).length;
|
|
1345
|
+
const withReport = lights.filter((light) => light.hasChannel);
|
|
1346
|
+
const tail = pending > 0 ? " · " + String(pending) + " 家还没体检" : "";
|
|
1347
|
+
if (withReport.length > 0 && withReport.every((light) => light.status === "green")) return react.createElement("span", { style: {
|
|
1348
|
+
color: STATUS_COLOR.green,
|
|
1349
|
+
fontWeight: 700
|
|
1350
|
+
} }, "六家正常" + tail);
|
|
1351
|
+
const parts = [];
|
|
1352
|
+
for (const status of ["red", "yellow"]) {
|
|
1353
|
+
let count = 0;
|
|
1354
|
+
for (const light of lights) count += light.counts[status];
|
|
1355
|
+
if (count === 0) continue;
|
|
1356
|
+
if (parts.length > 0) parts.push(react.createElement("span", {
|
|
1357
|
+
key: status + "-sep",
|
|
1358
|
+
style: { color: INK_DIM }
|
|
1359
|
+
}, SEG_SEP));
|
|
1360
|
+
parts.push(react.createElement("span", {
|
|
1361
|
+
key: status,
|
|
1362
|
+
style: {
|
|
1363
|
+
color: STATUS_TEXT[status],
|
|
1364
|
+
fontWeight: 700
|
|
1365
|
+
}
|
|
1366
|
+
}, STATUS_LABEL[status] + " " + String(count)));
|
|
1367
|
+
}
|
|
1368
|
+
if (parts.length === 0) return react.createElement("span", { style: { color: INK_DIM } }, "还没体检" + tail);
|
|
1369
|
+
parts.push(react.createElement("span", {
|
|
1370
|
+
key: "todo",
|
|
1371
|
+
style: { color: INK }
|
|
1372
|
+
}, " 要处理" + tail));
|
|
1373
|
+
return react.createElement("span", null, parts);
|
|
1374
|
+
}
|
|
1375
|
+
/** 那枚小数字用的底色(比灯色浅一档,压在页签上不抢眼)。 */
|
|
1376
|
+
const STATUS_DOT = {
|
|
1377
|
+
red: STATUS_COLOR.red,
|
|
1378
|
+
yellow: STATUS_COLOR.yellow,
|
|
1379
|
+
green: STATUS_COLOR.green
|
|
1380
|
+
};
|
|
1381
|
+
/** 页签圆点的颜色与语义(票 #732):从「实心/空心=装没装」改成「颜色=体检档位」。 */
|
|
1382
|
+
const TAB_DOT = {
|
|
1383
|
+
/** 没装(页签槽账本里没有这一家)。 */
|
|
1384
|
+
absent: STATUS_DOT.green,
|
|
1385
|
+
/** 装了、但还没跑过体检。 */
|
|
1386
|
+
unchecked: INK_DIM
|
|
1387
|
+
};
|
|
1388
|
+
/** 页签圆点该用什么色:装了按档位(红黄绿),没装/没跑过走缺席态。 */
|
|
1389
|
+
function tabDotColor(light) {
|
|
1390
|
+
if (!light.hasChannel) return TAB_DOT.absent;
|
|
1391
|
+
return light.status === null ? TAB_DOT.unchecked : STATUS_DOT[light.status];
|
|
1392
|
+
}
|
|
1393
|
+
/** 页签上那枚小数字(票 #732):红黄那几家,家名后带一条计数;全绿与没跑过不带。 */
|
|
1394
|
+
const TAB_NOTE_STYLE = {
|
|
1395
|
+
marginLeft: 5,
|
|
1396
|
+
border: "1px solid " + BORDER,
|
|
1397
|
+
borderRadius: 999,
|
|
1398
|
+
padding: "0 5px",
|
|
1399
|
+
fontSize: 11,
|
|
1400
|
+
fontWeight: 700
|
|
1401
|
+
};
|
|
1402
|
+
/** 那枚小数字写什么:只报「要处理」的那两档;一家里红黄都有时先报红那一档(圆点已经按最严重的档上色了)。 */
|
|
1403
|
+
function tabNote(light) {
|
|
1404
|
+
if (!light.hasChannel || light.status === null || light.status === "green") return null;
|
|
1405
|
+
return countSegsOf(light)[0] ?? null;
|
|
1406
|
+
}
|
|
1407
|
+
/** 顶部那一行汇总(票 #732:不再一家一盏灯按钮,一行总账 + 一个体检按钮)。
|
|
1408
|
+
*
|
|
1409
|
+
* 与页签的分工:这行只答「一共几条要处理」;「哪几家、各几条」由页签的圆点与小数字答;
|
|
1410
|
+
* 「具体哪一条坏了、去哪修」由那家页签里的 `HealthTable` 答。 */
|
|
1411
|
+
function HealthSummaryLine(props) {
|
|
1412
|
+
const hasLights = props.lights.length > 0;
|
|
1178
1413
|
return react.createElement("div", {
|
|
1179
|
-
style: HEALTH_STYLE.
|
|
1180
|
-
"data-ilife-health": "
|
|
1181
|
-
}, react.createElement("div", { style: HEALTH_STYLE.
|
|
1414
|
+
style: HEALTH_STYLE.summaryRow,
|
|
1415
|
+
"data-ilife-health": "summary"
|
|
1416
|
+
}, react.createElement("div", { style: HEALTH_STYLE.head }, "配置体检"), react.createElement("div", { style: HEALTH_STYLE.summaryText }, props.error !== null ? react.createElement("span", { style: { color: STATUS_TEXT.red } }, props.error) : hasLights ? overallText(props.lights) : react.createElement("span", { style: { color: INK_DIM } }, "还没体检")), react.createElement("button", {
|
|
1182
1417
|
type: "button",
|
|
1183
1418
|
style: props.running ? {
|
|
1184
1419
|
...HEALTH_STYLE.btn,
|
|
@@ -1187,40 +1422,8 @@ window.__ModuleLoader__.load({
|
|
|
1187
1422
|
} : HEALTH_STYLE.btn,
|
|
1188
1423
|
disabled: props.running,
|
|
1189
1424
|
onClick: props.onRun,
|
|
1190
|
-
title: "
|
|
1191
|
-
}, props.running ? "体检中…" : "体检一次"))
|
|
1192
|
-
style: HEALTH_STYLE.lightsRow,
|
|
1193
|
-
"data-ilife-health": "lights"
|
|
1194
|
-
}, props.lights.map((light) => react.createElement("button", {
|
|
1195
|
-
key: light.id,
|
|
1196
|
-
type: "button",
|
|
1197
|
-
"data-ilife-health": "light",
|
|
1198
|
-
"data-skill": light.id,
|
|
1199
|
-
style: light.status === null ? {
|
|
1200
|
-
...HEALTH_STYLE.light,
|
|
1201
|
-
...HEALTH_STYLE.lightIdle
|
|
1202
|
-
} : {
|
|
1203
|
-
...HEALTH_STYLE.light,
|
|
1204
|
-
borderColor: STATUS_COLOR[light.status],
|
|
1205
|
-
borderWidth: 1.5,
|
|
1206
|
-
background: STATUS_TINT[light.status]
|
|
1207
|
-
},
|
|
1208
|
-
disabled: light.status === null,
|
|
1209
|
-
onClick: () => props.onJump(light.id),
|
|
1210
|
-
title: light.status === null ? "还没体检(或这一家没装)" : light.title + ":" + countsText(light.counts)
|
|
1211
|
-
}, react.createElement("span", { style: {
|
|
1212
|
-
width: 10,
|
|
1213
|
-
height: 10,
|
|
1214
|
-
borderRadius: "50%",
|
|
1215
|
-
background: light.status === null ? INK_DIM : STATUS_COLOR[light.status],
|
|
1216
|
-
display: "inline-block"
|
|
1217
|
-
} }), react.createElement("span", { style: {
|
|
1218
|
-
color: INK,
|
|
1219
|
-
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 : "只看不改 · 灯=那一家最严重的一档 · 点一下跳到那家配置页"));
|
|
1425
|
+
title: "对六家各跑一次配置体检(只看不改:不建目录、不改配置、不自动重置)"
|
|
1426
|
+
}, props.running ? "体检中…" : "体检一次"));
|
|
1224
1427
|
}
|
|
1225
1428
|
/** 正正常常的那些:绿条只留「名字」,一句话与「去哪修」都不印(票面验收第一条:正常的收成一行)。 */
|
|
1226
1429
|
function isAttentionItem(item) {
|
|
@@ -1237,10 +1440,11 @@ window.__ModuleLoader__.load({
|
|
|
1237
1440
|
}, react.createElement("div", { style: HEALTH_STYLE.headRow }, react.createElement("div", { style: HEALTH_STYLE.head }, props.title + " · 体检"), report ? react.createElement("div", { style: {
|
|
1238
1441
|
color: INK_DIM,
|
|
1239
1442
|
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
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1443
|
+
} }, 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: {
|
|
1444
|
+
color: INK_DIM,
|
|
1445
|
+
fontSize: 11.5,
|
|
1446
|
+
marginTop: 2
|
|
1447
|
+
} }, "落点根目录:" + report.dataDir + (report.configPath !== "" ? "(配置文件 " + report.configPath + ")" : "")) : null, report.items.filter(isAttentionItem).map((item) => react.createElement("div", {
|
|
1244
1448
|
key: item.id,
|
|
1245
1449
|
"data-ilife-health": "item",
|
|
1246
1450
|
"data-status": item.status,
|
|
@@ -1267,18 +1471,12 @@ window.__ModuleLoader__.load({
|
|
|
1267
1471
|
},
|
|
1268
1472
|
"aria-hidden": "true"
|
|
1269
1473
|
}, 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
1474
|
fontWeight: 650,
|
|
1277
1475
|
color: INK
|
|
1278
1476
|
} }, item.title), react.createElement("span", { style: {
|
|
1279
1477
|
color: STATUS_TEXT[item.status],
|
|
1280
1478
|
fontWeight: 700
|
|
1281
|
-
} }, STATUS_LABEL[item.status]), item.source ? react.createElement("span", { style: { color: INK_DIM } }, "· 来自" + item.source) : null), react.createElement("div", {
|
|
1479
|
+
} }, STATUS_LABEL[item.status]), item.source && item.source !== "默认值" ? react.createElement("span", { style: { color: INK_DIM } }, "· 来自" + item.source) : null), react.createElement("div", {
|
|
1282
1480
|
style: { color: INK },
|
|
1283
1481
|
title: item.message
|
|
1284
1482
|
}, 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 +1487,27 @@ window.__ModuleLoader__.load({
|
|
|
1289
1487
|
alignItems: "center",
|
|
1290
1488
|
gap: "2px 10px",
|
|
1291
1489
|
marginTop: 8,
|
|
1292
|
-
|
|
1293
|
-
|
|
1490
|
+
padding: "6px 8px",
|
|
1491
|
+
borderLeft: "3px solid " + STATUS_COLOR.green,
|
|
1492
|
+
borderRadius: 6,
|
|
1294
1493
|
fontSize: 12,
|
|
1295
1494
|
lineHeight: 1.6,
|
|
1296
1495
|
color: INK_DIM
|
|
1297
1496
|
}
|
|
1298
|
-
}, react.createElement("span", {
|
|
1497
|
+
}, react.createElement("span", {
|
|
1498
|
+
style: {
|
|
1499
|
+
width: 8,
|
|
1500
|
+
height: 8,
|
|
1501
|
+
borderRadius: "50%",
|
|
1502
|
+
display: "inline-block",
|
|
1503
|
+
background: STATUS_COLOR.green,
|
|
1504
|
+
flex: "0 0 auto"
|
|
1505
|
+
},
|
|
1506
|
+
"aria-hidden": "true"
|
|
1507
|
+
}), react.createElement("span", { style: {
|
|
1299
1508
|
fontWeight: 650,
|
|
1300
1509
|
color: INK
|
|
1301
|
-
} }, "正常 " + String(countByStatus(report.items).green) + " 条"), report.items.filter((item) => !isAttentionItem(item)).map((item) => react.createElement("span", {
|
|
1510
|
+
} }, "正常 " + String(countByStatus(report.items).green) + " 条"), react.createElement("span", { style: { color: INK_DIM } }, "(下列各项都正常工作)"), report.items.filter((item) => !isAttentionItem(item)).map((item) => react.createElement("span", {
|
|
1302
1511
|
key: item.id,
|
|
1303
1512
|
"data-ilife-health": "ok-item"
|
|
1304
1513
|
}, "· " + item.title))) : null) : null);
|
|
@@ -1324,8 +1533,6 @@ window.__ModuleLoader__.load({
|
|
|
1324
1533
|
* 文案与网址都取自 nav.ts 的两张静态表,本文件只画不算。
|
|
1325
1534
|
*/
|
|
1326
1535
|
const inject = ["slots", "connection"];
|
|
1327
|
-
/** 爱生活页签槽(总管声明的 children,技能设置页注册进来;单段名,避开官方 settings.* 前缀)。 */
|
|
1328
|
-
const CONFIG_TAB_SLOT = "ilife.config-tab";
|
|
1329
1536
|
/** 总管视觉(内联 style;颜色走 DSH 主题别名,深浅主题自适应,写死值只做回退;与技能面板同语言)。 */
|
|
1330
1537
|
const S = {
|
|
1331
1538
|
headRow: {
|
|
@@ -1514,6 +1721,13 @@ window.__ModuleLoader__.load({
|
|
|
1514
1721
|
id: row.id,
|
|
1515
1722
|
channel: row.channel
|
|
1516
1723
|
})), [rows]));
|
|
1724
|
+
/** 六家的灯(票 #732):按**页签槽账本**那一排过(装了但没体检通道的也在内,它显缺席态)。 */
|
|
1725
|
+
const lights = react.useMemo(() => lightsOf(rows.map((row) => ({
|
|
1726
|
+
id: row.id,
|
|
1727
|
+
title: row.label,
|
|
1728
|
+
hasChannel: row.channel.length > 0
|
|
1729
|
+
})), Object.fromEntries(Object.entries(health.rows).map(([id, row]) => [id, row.report ?? void 0]))), [rows, health.rows]);
|
|
1730
|
+
const lightOf = (id) => lights.find((light) => light.id === id);
|
|
1517
1731
|
const [activeId, setActiveId] = react.useState(void 0);
|
|
1518
1732
|
const [visitedIds, setVisitedIds] = react.useState(() => /* @__PURE__ */ new Set());
|
|
1519
1733
|
const active = MANAGER_TABS.some((t) => t.plugin === activeId) ? activeId : MANAGER_TABS[0].plugin;
|
|
@@ -1527,13 +1741,6 @@ window.__ModuleLoader__.load({
|
|
|
1527
1741
|
const row = rows.find((r) => r.id === tab.plugin);
|
|
1528
1742
|
return row && row.label.length > 0 ? row.label : tab.title;
|
|
1529
1743
|
}
|
|
1530
|
-
/** 按包名取页签标题(总览那行按账本行过,账本里没有的退到导航表的标题)。 */
|
|
1531
|
-
function labelForTab(id) {
|
|
1532
|
-
const row = rows.find((r) => r.id === id);
|
|
1533
|
-
if (row && row.label.length > 0) return row.label;
|
|
1534
|
-
const tab = MANAGER_TABS.find((t) => t.plugin === id);
|
|
1535
|
-
return tab ? tab.title : id;
|
|
1536
|
-
}
|
|
1537
1744
|
function targetForTab(tab) {
|
|
1538
1745
|
return face.targets.find((t) => t.packageName === tab.plugin) ?? null;
|
|
1539
1746
|
}
|
|
@@ -1559,21 +1766,20 @@ window.__ModuleLoader__.load({
|
|
|
1559
1766
|
setActiveId(next.plugin);
|
|
1560
1767
|
tabRefs.current[nextIndex]?.focus();
|
|
1561
1768
|
}
|
|
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.
|
|
1563
|
-
lights
|
|
1564
|
-
id: row.id,
|
|
1565
|
-
title: labelForTab(row.id)
|
|
1566
|
-
})), Object.fromEntries(Object.entries(health.rows).map(([id, row]) => [id, row.report ?? void 0]))),
|
|
1769
|
+
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(HealthSummaryLine, {
|
|
1770
|
+
lights,
|
|
1567
1771
|
running: health.running,
|
|
1568
1772
|
error: health.error,
|
|
1569
|
-
onRun: health.run
|
|
1570
|
-
onJump: setActiveId
|
|
1773
|
+
onRun: health.run
|
|
1571
1774
|
}), react.createElement("div", {
|
|
1572
1775
|
role: "tablist",
|
|
1573
1776
|
"aria-label": "爱生活技能页签",
|
|
1574
1777
|
style: S.tablist
|
|
1575
1778
|
}, MANAGER_TABS.map((tab, index) => {
|
|
1576
1779
|
const selected = tab.plugin === active;
|
|
1780
|
+
const light = lightOf(tab.plugin);
|
|
1781
|
+
const dotColor = light === void 0 ? present.has(tab.plugin) ? TAB_DOT.unchecked : TAB_DOT.absent : tabDotColor(light);
|
|
1782
|
+
const note = light === void 0 ? null : tabNote(light);
|
|
1577
1783
|
return react.createElement("button", {
|
|
1578
1784
|
key: tab.plugin,
|
|
1579
1785
|
ref: (element) => {
|
|
@@ -1594,8 +1800,25 @@ window.__ModuleLoader__.load({
|
|
|
1594
1800
|
},
|
|
1595
1801
|
onKeyDown: (event) => {
|
|
1596
1802
|
onTabKeyDown(event, index);
|
|
1803
|
+
},
|
|
1804
|
+
title: light === void 0 ? "这一家没装(或产物没注册页签槽)" : light.title + ":" + (light.hasChannel ? light.status === null ? "还没体检" : "体检读数见本页签下方的表" : "没有配置体检出口")
|
|
1805
|
+
}, react.createElement("span", {
|
|
1806
|
+
"data-ilife-health": "tab-dot",
|
|
1807
|
+
style: {
|
|
1808
|
+
width: 6,
|
|
1809
|
+
height: 6,
|
|
1810
|
+
borderRadius: "50%",
|
|
1811
|
+
background: dotColor,
|
|
1812
|
+
display: "inline-block",
|
|
1813
|
+
flex: "0 0 auto"
|
|
1814
|
+
}
|
|
1815
|
+
}), labelFor(tab), note === null ? null : react.createElement("span", {
|
|
1816
|
+
"data-ilife-health": "tab-note",
|
|
1817
|
+
style: {
|
|
1818
|
+
...TAB_NOTE_STYLE,
|
|
1819
|
+
color: note.status === null ? void 0 : STATUS_TEXT[note.status]
|
|
1597
1820
|
}
|
|
1598
|
-
},
|
|
1821
|
+
}, note.text));
|
|
1599
1822
|
})), MANAGER_TABS.filter((tab) => tab.plugin === active || visitedIds.has(tab.plugin)).map((tab) => {
|
|
1600
1823
|
const selected = tab.plugin === active;
|
|
1601
1824
|
return react.createElement("div", {
|
|
@@ -1612,6 +1835,7 @@ window.__ModuleLoader__.load({
|
|
|
1612
1835
|
})) : react.createElement(AbsentCard, {
|
|
1613
1836
|
target: targetForTab(tab),
|
|
1614
1837
|
fallbackCommand: recoFor(tab).installCmd,
|
|
1838
|
+
inLedger: present.has(tab.plugin),
|
|
1615
1839
|
face
|
|
1616
1840
|
}));
|
|
1617
1841
|
}), react.createElement(MorePluginsCard, null));
|