dsh-life-pack 0.3.12 → 0.3.13
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 +217 -26
- package/dist/client.js.map +1 -1
- package/dist/config-panel-contract.d.ts +8 -0
- package/dist/config-panel-contract.d.ts.map +1 -1
- package/dist/config-panel-contract.js.map +1 -1
- package/dist/config-panel-value.d.ts +0 -12
- package/dist/config-panel-value.d.ts.map +1 -1
- package/dist/config-panel-value.js +6 -16
- package/dist/config-panel-value.js.map +1 -1
- package/dist/config-panel-view.d.ts +175 -12
- package/dist/config-panel-view.d.ts.map +1 -1
- package/dist/config-panel-view.js +484 -95
- package/dist/config-panel-view.js.map +1 -1
- package/dist/config-panel.d.ts +7 -0
- package/dist/config-panel.d.ts.map +1 -1
- package/dist/config-panel.js +21 -0
- package/dist/config-panel.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/manager-version.d.ts +11 -0
- package/dist/manager-version.d.ts.map +1 -1
- package/dist/manager-version.js +39 -6
- package/dist/manager-version.js.map +1 -1
- package/dist/update-queue.d.ts +42 -0
- package/dist/update-queue.d.ts.map +1 -0
- package/dist/update-queue.js +91 -0
- package/dist/update-queue.js.map +1 -0
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -518,6 +518,112 @@ window.__ModuleLoader__.load({
|
|
|
518
518
|
return lines;
|
|
519
519
|
}
|
|
520
520
|
//#endregion
|
|
521
|
+
//#region src/update-queue.ts
|
|
522
|
+
/** 任一行 installing 即忙:题头检查更新禁用、行按钮全局互斥、checkAll/act 入口直接返回的同一判据。 */
|
|
523
|
+
function hasInstallingRow(rows) {
|
|
524
|
+
for (const key of Object.keys(rows)) if (rows[key]?.phase === "installing") return true;
|
|
525
|
+
return false;
|
|
526
|
+
}
|
|
527
|
+
/** 按目标顺序逐家查→装→收尾:一家收尾才起下一家;失败记 failed 继续;待重启跳过安装。
|
|
528
|
+
*
|
|
529
|
+
* 跳过安装只有一种:快照事实已是待重启(`restartPendingOf`)——由横幅点名,不在这里装第二遍。
|
|
530
|
+
* 不可装(已是最新/拦下且无安装动作)记 ready 收尾;缺席且无最新版本号记 ready 收尾(等下一次检查)。
|
|
531
|
+
* 本函数不汇总排队数、不碰他行(`patch` 只收本家 key)。
|
|
532
|
+
*/
|
|
533
|
+
async function runSerialUpdateAll(targets, deps) {
|
|
534
|
+
for (const target of targets) {
|
|
535
|
+
const current = deps.getRow(target.key);
|
|
536
|
+
deps.patch(target.key, {
|
|
537
|
+
phase: "checking",
|
|
538
|
+
outcome: current?.outcome ?? null,
|
|
539
|
+
failure: null
|
|
540
|
+
});
|
|
541
|
+
const checked = await deps.check(target);
|
|
542
|
+
if (!checked.ok) {
|
|
543
|
+
deps.patch(target.key, {
|
|
544
|
+
phase: "failed",
|
|
545
|
+
outcome: null,
|
|
546
|
+
failure: checked
|
|
547
|
+
});
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
if (restartPendingOf(checked.value.snapshot)) {
|
|
551
|
+
deps.patch(target.key, {
|
|
552
|
+
phase: "ready",
|
|
553
|
+
outcome: checked.value,
|
|
554
|
+
failure: null
|
|
555
|
+
});
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
const verdict = verdictOf(target, checked.value.snapshot);
|
|
559
|
+
if (!(verdict.action === "update" || verdict.action === "install" || verdict.action === "retry")) {
|
|
560
|
+
deps.patch(target.key, {
|
|
561
|
+
phase: "ready",
|
|
562
|
+
outcome: checked.value,
|
|
563
|
+
failure: null
|
|
564
|
+
});
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
deps.patch(target.key, {
|
|
568
|
+
phase: "installing",
|
|
569
|
+
outcome: checked.value,
|
|
570
|
+
failure: null
|
|
571
|
+
});
|
|
572
|
+
if (isAbsent(target)) {
|
|
573
|
+
const version = checked.value.snapshot.latestVersion;
|
|
574
|
+
if (!version) {
|
|
575
|
+
deps.patch(target.key, {
|
|
576
|
+
phase: "ready",
|
|
577
|
+
outcome: checked.value,
|
|
578
|
+
failure: null
|
|
579
|
+
});
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
const done = await deps.installAbsent(target, version);
|
|
583
|
+
if (done.ok) {
|
|
584
|
+
const refreshed = await deps.checkStatus(target);
|
|
585
|
+
deps.patch(target.key, refreshed.ok ? {
|
|
586
|
+
phase: "ready",
|
|
587
|
+
outcome: refreshed.value,
|
|
588
|
+
failure: null
|
|
589
|
+
} : {
|
|
590
|
+
phase: "failed",
|
|
591
|
+
outcome: null,
|
|
592
|
+
failure: refreshed
|
|
593
|
+
});
|
|
594
|
+
} else deps.patch(target.key, {
|
|
595
|
+
phase: "failed",
|
|
596
|
+
outcome: checked.value,
|
|
597
|
+
failure: {
|
|
598
|
+
...done,
|
|
599
|
+
manual: done.manual ?? checked.value.manual ?? null
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
const done = await deps.installPresent(target);
|
|
605
|
+
if (done.ok) {
|
|
606
|
+
const refreshed = await deps.checkStatus(target);
|
|
607
|
+
deps.patch(target.key, refreshed.ok ? {
|
|
608
|
+
phase: "ready",
|
|
609
|
+
outcome: refreshed.value,
|
|
610
|
+
failure: null
|
|
611
|
+
} : {
|
|
612
|
+
phase: "failed",
|
|
613
|
+
outcome: null,
|
|
614
|
+
failure: refreshed
|
|
615
|
+
});
|
|
616
|
+
} else deps.patch(target.key, {
|
|
617
|
+
phase: "failed",
|
|
618
|
+
outcome: checked.value,
|
|
619
|
+
failure: {
|
|
620
|
+
...done,
|
|
621
|
+
manual: done.manual ?? checked.value.manual ?? null
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
//#endregion
|
|
521
627
|
//#region src/update-panel.ts
|
|
522
628
|
/** 爱生活面板的更新界面:检查更新按钮、七家结果行、待重启横幅、缺席卡的「装上」。
|
|
523
629
|
*
|
|
@@ -794,13 +900,21 @@ window.__ModuleLoader__.load({
|
|
|
794
900
|
outcome: null,
|
|
795
901
|
failure: null
|
|
796
902
|
};
|
|
797
|
-
/** 面板的更新状态源:挂载时取七个目标的表,之后按需查/装(组件与按钮共用一个 face)。
|
|
903
|
+
/** 面板的更新状态源:挂载时取七个目标的表,之后按需查/装(组件与按钮共用一个 face)。
|
|
904
|
+
*
|
|
905
|
+
* 并发形状(#925 拍板四条,用户已确认):
|
|
906
|
+
* - 一键全部更新按目标顺序串行(查→装→轮询到收尾,一家收尾才起下一家;失败记 failed 继续);
|
|
907
|
+
* - 任一行 installing 时 checkAll 入口直接返回、题头禁用;
|
|
908
|
+
* - act 入口有忙直接返回(有忙拒 update-busy 且不改 rows,含同家第二点);
|
|
909
|
+
* - 每行各自显示(patch 只写本家 key),题头不汇总排队数。
|
|
910
|
+
*/
|
|
798
911
|
function useUpdateRows(getCall) {
|
|
799
912
|
const [targets, setTargets] = react.useState([]);
|
|
800
913
|
const [rows, setRows] = react.useState({});
|
|
801
914
|
const [pollMs, setPollMs] = react.useState(1e3);
|
|
802
915
|
const [loadError, setLoadError] = react.useState(null);
|
|
803
916
|
const [checking, setChecking] = react.useState(false);
|
|
917
|
+
const [updatingAll, setUpdatingAll] = react.useState(false);
|
|
804
918
|
const [open, setOpen] = react.useState(false);
|
|
805
919
|
const alive = react.useRef(true);
|
|
806
920
|
react.useEffect(() => () => {
|
|
@@ -825,21 +939,31 @@ window.__ModuleLoader__.load({
|
|
|
825
939
|
[key]: next
|
|
826
940
|
}));
|
|
827
941
|
}, []);
|
|
942
|
+
const rowsRef = react.useRef(rows);
|
|
943
|
+
rowsRef.current = rows;
|
|
944
|
+
const targetsRef = react.useRef(targets);
|
|
945
|
+
targetsRef.current = targets;
|
|
946
|
+
const pollMsRef = react.useRef(pollMs);
|
|
947
|
+
pollMsRef.current = pollMs;
|
|
948
|
+
const updatingAllRef = react.useRef(false);
|
|
828
949
|
return {
|
|
829
950
|
targets,
|
|
830
951
|
rows,
|
|
831
952
|
pollMs,
|
|
832
953
|
loadError,
|
|
833
954
|
checking,
|
|
955
|
+
updatingAll,
|
|
834
956
|
open,
|
|
835
957
|
close: () => setOpen(false),
|
|
836
958
|
checkAll: react.useCallback(() => {
|
|
959
|
+
if (hasInstallingRow(rowsRef.current) || updatingAllRef.current) return;
|
|
837
960
|
setChecking(true);
|
|
838
961
|
setOpen(true);
|
|
839
|
-
const list =
|
|
962
|
+
const list = targetsRef.current;
|
|
963
|
+
const snapshot = rowsRef.current;
|
|
840
964
|
for (const target of list) patch(target.key, {
|
|
841
965
|
phase: "checking",
|
|
842
|
-
outcome:
|
|
966
|
+
outcome: snapshot[target.key]?.outcome ?? null,
|
|
843
967
|
failure: null
|
|
844
968
|
});
|
|
845
969
|
Promise.all(list.map(async (target) => {
|
|
@@ -853,15 +977,41 @@ window.__ModuleLoader__.load({
|
|
|
853
977
|
outcome: null,
|
|
854
978
|
failure: result
|
|
855
979
|
});
|
|
856
|
-
})).finally(() =>
|
|
980
|
+
})).finally(() => {
|
|
981
|
+
if (alive.current) setChecking(false);
|
|
982
|
+
});
|
|
983
|
+
}, [getCall, patch]),
|
|
984
|
+
updateAll: react.useCallback(() => {
|
|
985
|
+
if (hasInstallingRow(rowsRef.current) || updatingAllRef.current) return;
|
|
986
|
+
const list = targetsRef.current;
|
|
987
|
+
if (list.length === 0) return;
|
|
988
|
+
updatingAllRef.current = true;
|
|
989
|
+
setUpdatingAll(true);
|
|
990
|
+
setOpen(true);
|
|
991
|
+
(async () => {
|
|
992
|
+
try {
|
|
993
|
+
await runSerialUpdateAll(list, {
|
|
994
|
+
check: (target) => checkTarget(getCall(), target),
|
|
995
|
+
checkStatus: (target) => checkTarget(getCall(), target, target.phones?.status),
|
|
996
|
+
installAbsent: (target, version) => installAbsent(getCall(), target, version),
|
|
997
|
+
installPresent: (target) => updateInstalled(getCall(), target, pollMsRef.current),
|
|
998
|
+
patch,
|
|
999
|
+
getRow: (key) => rowsRef.current[key]
|
|
1000
|
+
});
|
|
1001
|
+
await reload();
|
|
1002
|
+
} finally {
|
|
1003
|
+
updatingAllRef.current = false;
|
|
1004
|
+
if (alive.current) setUpdatingAll(false);
|
|
1005
|
+
}
|
|
1006
|
+
})();
|
|
857
1007
|
}, [
|
|
858
1008
|
getCall,
|
|
859
1009
|
patch,
|
|
860
|
-
|
|
861
|
-
targets
|
|
1010
|
+
reload
|
|
862
1011
|
]),
|
|
863
1012
|
act: react.useCallback((target, action) => {
|
|
864
|
-
|
|
1013
|
+
if (hasInstallingRow(rowsRef.current) || updatingAllRef.current) return;
|
|
1014
|
+
const current = rowsRef.current[target.key] ?? IDLE$1;
|
|
865
1015
|
if (action === "recheck") {
|
|
866
1016
|
patch(target.key, {
|
|
867
1017
|
phase: "checking",
|
|
@@ -907,7 +1057,7 @@ window.__ModuleLoader__.load({
|
|
|
907
1057
|
});
|
|
908
1058
|
version = checked.value.snapshot.latestVersion;
|
|
909
1059
|
}
|
|
910
|
-
const result = absent ? await installAbsent(getCall(), target, version ?? "") : await updateInstalled(getCall(), target,
|
|
1060
|
+
const result = absent ? await installAbsent(getCall(), target, version ?? "") : await updateInstalled(getCall(), target, pollMsRef.current);
|
|
911
1061
|
if (result.ok) {
|
|
912
1062
|
const refreshed = await checkTarget(getCall(), target, target.phones?.status);
|
|
913
1063
|
patch(target.key, refreshed.ok ? {
|
|
@@ -934,27 +1084,42 @@ window.__ModuleLoader__.load({
|
|
|
934
1084
|
}, [
|
|
935
1085
|
getCall,
|
|
936
1086
|
patch,
|
|
937
|
-
|
|
938
|
-
reload,
|
|
939
|
-
rows
|
|
1087
|
+
reload
|
|
940
1088
|
])
|
|
941
1089
|
};
|
|
942
1090
|
}
|
|
943
|
-
/**
|
|
1091
|
+
/** 标题右侧两件:「检查更新」一次查七家 + 「全部更新」按顺序串行装(吃 `useUpdateRows` 的表)。
|
|
1092
|
+
*
|
|
1093
|
+
* 禁用形状(#925 第 2 条):任一行 installing 时两件都点不得;人话沿用 `update-busy` 原句
|
|
1094
|
+
* (挂在 title 上,按钮文本仍是动作名)。题头不汇总排队数:串行进度只在各行自己显示。
|
|
1095
|
+
*/
|
|
944
1096
|
function CheckUpdateButton(props) {
|
|
945
|
-
const { checking, checkAll, targets } = props.face;
|
|
946
|
-
const
|
|
947
|
-
|
|
1097
|
+
const { checking, updatingAll, checkAll, updateAll, targets, rows } = props.face;
|
|
1098
|
+
const busy = hasInstallingRow(rows) || updatingAll;
|
|
1099
|
+
const checkDisabled = checking || updatingAll || busy || targets.length === 0;
|
|
1100
|
+
const allDisabled = checking || updatingAll || busy || targets.length === 0;
|
|
1101
|
+
const busyText = reasonText("update-busy");
|
|
1102
|
+
return react.createElement("span", { style: PANEL_STYLE.actions }, react.createElement("button", {
|
|
948
1103
|
type: "button",
|
|
949
|
-
style:
|
|
1104
|
+
style: checkDisabled ? {
|
|
950
1105
|
...PANEL_STYLE.btn,
|
|
951
1106
|
opacity: .55,
|
|
952
1107
|
cursor: "default"
|
|
953
1108
|
} : PANEL_STYLE.btn,
|
|
954
|
-
disabled,
|
|
1109
|
+
disabled: checkDisabled,
|
|
955
1110
|
onClick: checkAll,
|
|
956
|
-
title: "检查总管与六家插件的版本"
|
|
957
|
-
}, checking ? "检查中…" : "检查更新")
|
|
1111
|
+
title: busy ? busyText : "检查总管与六家插件的版本"
|
|
1112
|
+
}, checking ? "检查中…" : "检查更新"), react.createElement("button", {
|
|
1113
|
+
type: "button",
|
|
1114
|
+
style: allDisabled ? {
|
|
1115
|
+
...PANEL_STYLE.btn,
|
|
1116
|
+
opacity: .55,
|
|
1117
|
+
cursor: "default"
|
|
1118
|
+
} : PANEL_STYLE.btn,
|
|
1119
|
+
disabled: allDisabled,
|
|
1120
|
+
onClick: updateAll,
|
|
1121
|
+
title: busy ? busyText : "按顺序逐家装上更新(一家收尾才起下一家)"
|
|
1122
|
+
}, updatingAll ? "更新中…" : "全部更新"));
|
|
958
1123
|
}
|
|
959
1124
|
/** 待重启横幅:一行总账,只点名哪几家要重启(理由与做法写在各家卡片上,见 `restartBannerText`)。 */
|
|
960
1125
|
function RestartBanners(props) {
|
|
@@ -977,7 +1142,7 @@ window.__ModuleLoader__.load({
|
|
|
977
1142
|
* 手工命令只在「装不了」或「装失败」的行显示:正常用户用不到,摊在每行上既吵又误导。
|
|
978
1143
|
*/
|
|
979
1144
|
function UpdateResults(props) {
|
|
980
|
-
const { targets, rows, loadError, checking, open, close } = props.face;
|
|
1145
|
+
const { targets, rows, loadError, checking, updatingAll, open, close } = props.face;
|
|
981
1146
|
const dialogRef = react.useRef(null);
|
|
982
1147
|
react.useEffect(() => {
|
|
983
1148
|
if (open) dialogRef.current?.focus();
|
|
@@ -986,6 +1151,7 @@ window.__ModuleLoader__.load({
|
|
|
986
1151
|
if (loadError) return react.createElement("div", { style: PANEL_STYLE.reason }, "更新能力没接上:" + loadError);
|
|
987
1152
|
if (targets.length === 0) return null;
|
|
988
1153
|
if (shown.length === 0 && !open) return null;
|
|
1154
|
+
const locked = hasInstallingRow(rows) || updatingAll;
|
|
989
1155
|
const body = shown.map((target) => {
|
|
990
1156
|
const row = rows[target.key];
|
|
991
1157
|
const snapshot = row.outcome?.snapshot ?? null;
|
|
@@ -1026,12 +1192,13 @@ window.__ModuleLoader__.load({
|
|
|
1026
1192
|
color: "var(--dsw-alias-label-secondary, #9a9a9a)"
|
|
1027
1193
|
} }, busy ? "正在装,请稍候…" : verdict?.text ?? (checking ? "检查中…" : "")), buttonAction ? react.createElement("button", {
|
|
1028
1194
|
type: "button",
|
|
1029
|
-
style:
|
|
1195
|
+
style: locked ? {
|
|
1030
1196
|
...PANEL_STYLE.btnPrimary,
|
|
1031
1197
|
opacity: .55,
|
|
1032
1198
|
cursor: "default"
|
|
1033
1199
|
} : PANEL_STYLE.btnPrimary,
|
|
1034
|
-
disabled:
|
|
1200
|
+
disabled: locked,
|
|
1201
|
+
title: locked ? reasonText("update-busy") : void 0,
|
|
1035
1202
|
onClick: () => {
|
|
1036
1203
|
props.face.act(target, buttonAction);
|
|
1037
1204
|
}
|
|
@@ -1078,6 +1245,7 @@ window.__ModuleLoader__.load({
|
|
|
1078
1245
|
const target = props.target;
|
|
1079
1246
|
const row = target ? props.face.rows[target.key] : null;
|
|
1080
1247
|
const busy = row?.phase === "installing";
|
|
1248
|
+
const locked = busy || hasInstallingRow(props.face.rows) || props.face.updatingAll;
|
|
1081
1249
|
const manual = (target ? manualForDisplay(target, row?.failure?.manual ?? row?.outcome?.manual ?? null) : null) ?? props.fallbackCommand;
|
|
1082
1250
|
const installed = target ? target.installedVersion ?? target.runningVersion : null;
|
|
1083
1251
|
const state = slotStateOf(target, props.inLedger);
|
|
@@ -1097,7 +1265,7 @@ window.__ModuleLoader__.load({
|
|
|
1097
1265
|
}
|
|
1098
1266
|
return react.createElement("div", { style: PANEL_STYLE.reco }, react.createElement("div", null, headline), state === "absent" ? react.createElement("button", {
|
|
1099
1267
|
type: "button",
|
|
1100
|
-
style:
|
|
1268
|
+
style: locked ? {
|
|
1101
1269
|
...PANEL_STYLE.btn,
|
|
1102
1270
|
marginTop: 8,
|
|
1103
1271
|
opacity: .55
|
|
@@ -1105,13 +1273,14 @@ window.__ModuleLoader__.load({
|
|
|
1105
1273
|
...PANEL_STYLE.btn,
|
|
1106
1274
|
marginTop: 8
|
|
1107
1275
|
},
|
|
1108
|
-
disabled:
|
|
1276
|
+
disabled: locked,
|
|
1277
|
+
title: locked ? reasonText("update-busy") : void 0,
|
|
1109
1278
|
onClick: () => {
|
|
1110
1279
|
if (target) props.face.act(target, "install");
|
|
1111
1280
|
}
|
|
1112
1281
|
}, busy ? "正在装,请稍候…" : "装上") : null, retryAction === "recheck" && target ? react.createElement("button", {
|
|
1113
1282
|
type: "button",
|
|
1114
|
-
style:
|
|
1283
|
+
style: locked ? {
|
|
1115
1284
|
...PANEL_STYLE.btn,
|
|
1116
1285
|
marginTop: 8,
|
|
1117
1286
|
marginLeft: 8,
|
|
@@ -1121,7 +1290,8 @@ window.__ModuleLoader__.load({
|
|
|
1121
1290
|
marginTop: 8,
|
|
1122
1291
|
marginLeft: 8
|
|
1123
1292
|
},
|
|
1124
|
-
disabled:
|
|
1293
|
+
disabled: locked,
|
|
1294
|
+
title: locked ? reasonText("update-busy") : void 0,
|
|
1125
1295
|
onClick: () => props.face.act(target, "recheck")
|
|
1126
1296
|
}, ACTION_LABEL.recheck) : null, reason ? react.createElement("div", { style: PANEL_STYLE.reason }, reason) : null, react.createElement("code", { style: PANEL_STYLE.cmd }, manual));
|
|
1127
1297
|
}
|
|
@@ -2759,6 +2929,27 @@ window.__ModuleLoader__.load({
|
|
|
2759
2929
|
return /* @__PURE__ */ new Set([...previous, active]);
|
|
2760
2930
|
});
|
|
2761
2931
|
}, [active]);
|
|
2932
|
+
/** 预热:section 一挂载,就把其余几家**错峰**挂上(隐藏面板照常挂载、只切 `hidden`),
|
|
2933
|
+
* 让各家那一次配置读提前跑掉——用户点哪个页签,多半值已经在了。
|
|
2934
|
+
* 错峰(每 150ms 一家)是为了不让六家子进程同时抢开工:首帧那一口气优先留给框架。
|
|
2935
|
+
* 只在页签账本非空时起一次(`rows` 要等各家 slot 注入后才齐)。
|
|
2936
|
+
*
|
|
2937
|
+
* **一次性**(p10 定时器门的例外依据):每个页签只挂一次(`warmed` 闸门 + 一批固定数量的定时器),
|
|
2938
|
+
* 不是轮询、也不是重试——它没有「次数」可言;卸载即 `clearTimeout` 清干净。
|
|
2939
|
+
* 不做轮询是本意:这里只补一次提前量,取数与刷新仍旧由各家用例自己的通道走。 */
|
|
2940
|
+
const warmed = react.useRef(false);
|
|
2941
|
+
const tabCount = rows.length;
|
|
2942
|
+
react.useEffect(() => {
|
|
2943
|
+
if (warmed.current || tabCount === 0) return;
|
|
2944
|
+
warmed.current = true;
|
|
2945
|
+
const STAGGER_MS = 150;
|
|
2946
|
+
const timers = MANAGER_TABS.filter((tab) => rows.some((row) => row.id === tab.plugin)).map((tab, index) => setTimeout(() => {
|
|
2947
|
+
setVisitedIds((previous) => previous.has(tab.plugin) ? previous : /* @__PURE__ */ new Set([...previous, tab.plugin]));
|
|
2948
|
+
}, STAGGER_MS * (index + 1)));
|
|
2949
|
+
return () => {
|
|
2950
|
+
for (const timer of timers) clearTimeout(timer);
|
|
2951
|
+
};
|
|
2952
|
+
}, [tabCount]);
|
|
2762
2953
|
function labelFor(tab) {
|
|
2763
2954
|
const row = rows.find((r) => r.id === tab.plugin);
|
|
2764
2955
|
return row && row.label.length > 0 ? row.label : tab.title;
|