@huaqiu/dsh-tool-plugin-update 0.6.6 → 0.6.8
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/lib/client.js +1852 -397
- package/lib/client.js.map +1 -1
- package/lib/index.mjs +36 -4
- package/package.json +3 -3
- package/src/client/api.ts +281 -1
- package/src/client/copy.ts +230 -6
- package/src/client/index.ts +881 -12
- package/src/client/update-view.ts +848 -242
- package/src/routes.ts +49 -7
package/src/client/index.ts
CHANGED
|
@@ -21,7 +21,15 @@ import {
|
|
|
21
21
|
observeHostLocale,
|
|
22
22
|
type HostLocale,
|
|
23
23
|
} from '@huaqiu/dsh-sidebar-entry'
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
checkUpdates,
|
|
26
|
+
fetchJobStatus,
|
|
27
|
+
fetchReport,
|
|
28
|
+
TERMINAL_PHASES,
|
|
29
|
+
type PluginUpdateReport,
|
|
30
|
+
type UpdateJob,
|
|
31
|
+
type UpdateJobStatus,
|
|
32
|
+
} from './api.js'
|
|
25
33
|
import { COPY } from './copy.js'
|
|
26
34
|
import { PLUGIN_ICON } from './icons.js'
|
|
27
35
|
import { openUpdateView, type UpdateStoreLike } from './update-view.js'
|
|
@@ -767,6 +775,97 @@ const VIEW_CSS_TEXT = `
|
|
|
767
775
|
padding: 0 24px 20px;
|
|
768
776
|
}
|
|
769
777
|
|
|
778
|
+
/* ── Durable job (active / result) ────────────────────────────────────── */
|
|
779
|
+
.hqpu-job {
|
|
780
|
+
display: flex;
|
|
781
|
+
flex-direction: column;
|
|
782
|
+
gap: 6px;
|
|
783
|
+
padding: 12px;
|
|
784
|
+
border: 0.5px solid var(--dsw-alias-border-l2);
|
|
785
|
+
border-radius: var(--dsw-radius-lg);
|
|
786
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
787
|
+
}
|
|
788
|
+
.hqpu-job-meta {
|
|
789
|
+
font-variant-numeric: tabular-nums;
|
|
790
|
+
}
|
|
791
|
+
.hqpu-progress {
|
|
792
|
+
height: 6px;
|
|
793
|
+
border-radius: 999px;
|
|
794
|
+
background: var(--dsw-alias-bg-module-platform);
|
|
795
|
+
overflow: hidden;
|
|
796
|
+
}
|
|
797
|
+
.hqpu-progress-fill {
|
|
798
|
+
height: 100%;
|
|
799
|
+
border-radius: 999px;
|
|
800
|
+
background: var(--dsw-alias-state-business-primary);
|
|
801
|
+
transition: width var(--ds-transition-duration, 0.2s) ease;
|
|
802
|
+
}
|
|
803
|
+
.hqpu-job-steps {
|
|
804
|
+
display: flex;
|
|
805
|
+
flex-direction: column;
|
|
806
|
+
gap: 4px;
|
|
807
|
+
}
|
|
808
|
+
.hqpu-steps {
|
|
809
|
+
margin: 0;
|
|
810
|
+
padding: 0;
|
|
811
|
+
list-style: none;
|
|
812
|
+
display: flex;
|
|
813
|
+
flex-direction: column;
|
|
814
|
+
gap: 4px;
|
|
815
|
+
}
|
|
816
|
+
.hqpu-step {
|
|
817
|
+
display: flex;
|
|
818
|
+
align-items: center;
|
|
819
|
+
gap: 6px;
|
|
820
|
+
font-size: 12.5px;
|
|
821
|
+
line-height: 18px;
|
|
822
|
+
color: var(--dsw-alias-label-secondary);
|
|
823
|
+
}
|
|
824
|
+
.hqpu-step--skipped,
|
|
825
|
+
.hqpu-step--pending { opacity: 0.6; }
|
|
826
|
+
|
|
827
|
+
/* ── Per-plugin change list + release notes ────────────────────────────── */
|
|
828
|
+
.hqpu-changes {
|
|
829
|
+
padding: 0 8px 8px 70px;
|
|
830
|
+
}
|
|
831
|
+
.hqpu-changes-list,
|
|
832
|
+
.hqpu-release-notes {
|
|
833
|
+
margin: 4px 0 0;
|
|
834
|
+
padding-left: 18px;
|
|
835
|
+
font-size: 12.5px;
|
|
836
|
+
line-height: 19px;
|
|
837
|
+
color: var(--dsw-alias-label-secondary);
|
|
838
|
+
}
|
|
839
|
+
.hqpu-release-link {
|
|
840
|
+
align-self: flex-start;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/* ── Advanced disclosure (source editors) ───────────────────────────────── */
|
|
844
|
+
.hqpu-advanced > summary {
|
|
845
|
+
cursor: pointer;
|
|
846
|
+
font-size: 12.5px;
|
|
847
|
+
line-height: 20px;
|
|
848
|
+
color: var(--dsw-alias-label-tertiary);
|
|
849
|
+
list-style: none;
|
|
850
|
+
padding: 2px 0;
|
|
851
|
+
}
|
|
852
|
+
.hqpu-advanced > summary::-webkit-details-marker { display: none; }
|
|
853
|
+
.hqpu-advanced > summary::before {
|
|
854
|
+
content: '▸ ';
|
|
855
|
+
}
|
|
856
|
+
.hqpu-advanced[open] > summary::before {
|
|
857
|
+
content: '▾ ';
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/* Danger-toned banner (check failure, breaking changes) */
|
|
861
|
+
.hqpu-banner[data-tone='danger'] {
|
|
862
|
+
background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 8%, transparent);
|
|
863
|
+
color: var(--dsw-alias-label-secondary);
|
|
864
|
+
}
|
|
865
|
+
.hqpu-banner[data-tone='danger'] > svg {
|
|
866
|
+
color: var(--dsw-alias-state-error-primary);
|
|
867
|
+
}
|
|
868
|
+
|
|
770
869
|
.hqpu-visually-hidden {
|
|
771
870
|
position: absolute;
|
|
772
871
|
width: 1px;
|
|
@@ -785,10 +884,649 @@ const VIEW_CSS_TEXT = `
|
|
|
785
884
|
.hqpu-panel,
|
|
786
885
|
.hqpu-skeleton-fill,
|
|
787
886
|
.hqpu-spinner,
|
|
788
|
-
.hqpu-toast
|
|
887
|
+
.hqpu-toast,
|
|
888
|
+
.hqpu-pulse-ring,
|
|
889
|
+
.hqpu-job-step--active {
|
|
789
890
|
animation: none;
|
|
790
891
|
}
|
|
791
892
|
}
|
|
893
|
+
|
|
894
|
+
/* ════════════════════════════════════════════════════════════════════════
|
|
895
|
+
* Refactored dock (interactive_refactored_ui_mockup.html) — a narrow dark-zinc
|
|
896
|
+
* update-center column. These rules cascade over the shell-token base: the
|
|
897
|
+
* panel becomes ~340px wide, the zinc palette hard-coded per mockup, with a
|
|
898
|
+
* sticky header / scroll body / sticky footer. No Tailwind, no dependencies.
|
|
899
|
+
* ════════════════════════════════════════════════════════════════════════ */
|
|
900
|
+
.hqpu-panel,
|
|
901
|
+
.hqpu-panel * {
|
|
902
|
+
--hq-bg: #18181b;
|
|
903
|
+
--hq-card: #27272a;
|
|
904
|
+
--hq-card-soft: #1c1c1f;
|
|
905
|
+
--hq-border: #3f3f46;
|
|
906
|
+
--hq-hover: #323236;
|
|
907
|
+
--hq-text: #f4f4f5;
|
|
908
|
+
--hq-muted: #a1a1aa;
|
|
909
|
+
--hq-faint: #71717a;
|
|
910
|
+
--hq-brand: #dc2626;
|
|
911
|
+
--hq-brand-soft: #ef4444;
|
|
912
|
+
--hq-emerald: #34d399;
|
|
913
|
+
--hq-amber: #f59e0b;
|
|
914
|
+
}
|
|
915
|
+
.hqpu-panel {
|
|
916
|
+
width: min(360px, calc(100vw - 32px));
|
|
917
|
+
max-height: min(86vh, 720px);
|
|
918
|
+
background: var(--hq-bg);
|
|
919
|
+
color: var(--hq-text);
|
|
920
|
+
border: 0.5px solid var(--hq-border);
|
|
921
|
+
border-radius: 12px;
|
|
922
|
+
gap: 0;
|
|
923
|
+
overflow: hidden;
|
|
924
|
+
}
|
|
925
|
+
.hqpu-scrim {
|
|
926
|
+
background: rgba(0, 0, 0, 0.55);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/* Sticky header */
|
|
930
|
+
.hqpu-head {
|
|
931
|
+
flex: none;
|
|
932
|
+
display: flex;
|
|
933
|
+
align-items: center;
|
|
934
|
+
justify-content: space-between;
|
|
935
|
+
gap: 8px;
|
|
936
|
+
padding: 10px 12px;
|
|
937
|
+
border-bottom: 0.5px solid var(--hq-border);
|
|
938
|
+
background: color-mix(in srgb, var(--hq-bg) 95%, transparent);
|
|
939
|
+
position: sticky;
|
|
940
|
+
top: 0;
|
|
941
|
+
z-index: 2;
|
|
942
|
+
}
|
|
943
|
+
.hqpu-head-main {
|
|
944
|
+
display: flex;
|
|
945
|
+
align-items: center;
|
|
946
|
+
gap: 8px;
|
|
947
|
+
min-width: 0;
|
|
948
|
+
margin: 0;
|
|
949
|
+
}
|
|
950
|
+
.hqpu-brand-icon {
|
|
951
|
+
display: flex;
|
|
952
|
+
align-items: center;
|
|
953
|
+
justify-content: center;
|
|
954
|
+
flex: none;
|
|
955
|
+
width: 24px;
|
|
956
|
+
height: 24px;
|
|
957
|
+
border-radius: 6px;
|
|
958
|
+
background: color-mix(in srgb, var(--hq-brand) 20%, transparent);
|
|
959
|
+
color: var(--hq-brand-soft);
|
|
960
|
+
}
|
|
961
|
+
.hqpu-brand-icon svg { width: 14px; height: 14px; display: block; }
|
|
962
|
+
.hqpu-title-block { min-width: 0; }
|
|
963
|
+
.hqpu-title {
|
|
964
|
+
margin: 0;
|
|
965
|
+
font-size: 13px;
|
|
966
|
+
font-weight: 600;
|
|
967
|
+
line-height: 16px;
|
|
968
|
+
color: var(--hq-text);
|
|
969
|
+
}
|
|
970
|
+
.hqpu-sub {
|
|
971
|
+
margin: 2px 0 0;
|
|
972
|
+
font-size: 10px;
|
|
973
|
+
line-height: 14px;
|
|
974
|
+
color: var(--hq-muted);
|
|
975
|
+
white-space: nowrap;
|
|
976
|
+
overflow: hidden;
|
|
977
|
+
text-overflow: ellipsis;
|
|
978
|
+
}
|
|
979
|
+
.hqpu-toolbar { gap: 2px; }
|
|
980
|
+
.hqpu-icon-btn {
|
|
981
|
+
width: 26px;
|
|
982
|
+
height: 26px;
|
|
983
|
+
border-radius: 6px;
|
|
984
|
+
color: var(--hq-muted);
|
|
985
|
+
}
|
|
986
|
+
.hqpu-icon-btn:hover:not(:disabled) {
|
|
987
|
+
background: var(--hq-hover);
|
|
988
|
+
color: var(--hq-text);
|
|
989
|
+
}
|
|
990
|
+
.hqpu-icon-btn svg { width: 14px; height: 14px; }
|
|
991
|
+
|
|
992
|
+
/* Collapsible settings panel */
|
|
993
|
+
.hqpu-settings {
|
|
994
|
+
flex: none;
|
|
995
|
+
padding: 10px 12px;
|
|
996
|
+
border-bottom: 0.5px solid var(--hq-border);
|
|
997
|
+
background: #131316;
|
|
998
|
+
}
|
|
999
|
+
.hqpu-settings-row {
|
|
1000
|
+
display: flex;
|
|
1001
|
+
align-items: center;
|
|
1002
|
+
justify-content: space-between;
|
|
1003
|
+
gap: 8px;
|
|
1004
|
+
margin-bottom: 8px;
|
|
1005
|
+
}
|
|
1006
|
+
.hqpu-settings-title {
|
|
1007
|
+
font-size: 11px;
|
|
1008
|
+
font-weight: 500;
|
|
1009
|
+
color: #d4d4d8;
|
|
1010
|
+
}
|
|
1011
|
+
.hqpu-origin-badge {
|
|
1012
|
+
font-size: 9px;
|
|
1013
|
+
padding: 1px 6px;
|
|
1014
|
+
border-radius: 4px;
|
|
1015
|
+
border: 0.5px solid;
|
|
1016
|
+
}
|
|
1017
|
+
.hqpu-origin-badge[data-origin='config'] {
|
|
1018
|
+
background: rgba(6, 78, 59, 0.4);
|
|
1019
|
+
color: var(--hq-emerald);
|
|
1020
|
+
border-color: rgba(6, 95, 70, 0.6);
|
|
1021
|
+
}
|
|
1022
|
+
.hqpu-origin-badge[data-origin='override'] {
|
|
1023
|
+
background: rgba(120, 53, 15, 0.4);
|
|
1024
|
+
color: var(--hq-amber);
|
|
1025
|
+
border-color: rgba(180, 83, 9, 0.5);
|
|
1026
|
+
}
|
|
1027
|
+
.hqpu-settings label {
|
|
1028
|
+
display: block;
|
|
1029
|
+
font-size: 10px;
|
|
1030
|
+
color: var(--hq-muted);
|
|
1031
|
+
margin: 6px 0 3px;
|
|
1032
|
+
}
|
|
1033
|
+
.hqpu-source-input {
|
|
1034
|
+
width: 100%;
|
|
1035
|
+
height: 26px;
|
|
1036
|
+
font-size: 10px;
|
|
1037
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1038
|
+
background: var(--hq-bg);
|
|
1039
|
+
border: 0.5px solid var(--hq-border);
|
|
1040
|
+
border-radius: 5px;
|
|
1041
|
+
color: #d4d4d8;
|
|
1042
|
+
padding: 0 8px;
|
|
1043
|
+
box-sizing: border-box;
|
|
1044
|
+
}
|
|
1045
|
+
.hqpu-source-input:focus-visible {
|
|
1046
|
+
outline: none;
|
|
1047
|
+
border-color: var(--hq-brand-soft);
|
|
1048
|
+
}
|
|
1049
|
+
.hqpu-settings-actions {
|
|
1050
|
+
display: flex;
|
|
1051
|
+
justify-content: flex-end;
|
|
1052
|
+
gap: 6px;
|
|
1053
|
+
margin-top: 8px;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
/* Scroll body */
|
|
1057
|
+
.hqpu-scroll {
|
|
1058
|
+
flex: 1;
|
|
1059
|
+
padding: 12px;
|
|
1060
|
+
background: var(--hq-bg);
|
|
1061
|
+
--dsh-scrollbar-thumb: #3f3f46;
|
|
1062
|
+
--dsh-scrollbar-thumb-hover: #52525b;
|
|
1063
|
+
}
|
|
1064
|
+
.hqpu-column {
|
|
1065
|
+
max-width: none;
|
|
1066
|
+
margin: 0;
|
|
1067
|
+
gap: 10px;
|
|
1068
|
+
}
|
|
1069
|
+
.hqpu-stack { gap: 10px; }
|
|
1070
|
+
|
|
1071
|
+
/* ── Active-job banner ─────────────────────────────────────────────────── */
|
|
1072
|
+
.hqpu-active {
|
|
1073
|
+
position: relative;
|
|
1074
|
+
padding: 12px;
|
|
1075
|
+
border-radius: 8px;
|
|
1076
|
+
border: 0.5px solid color-mix(in srgb, var(--hq-brand-soft) 30%, transparent);
|
|
1077
|
+
background: #18181b;
|
|
1078
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
|
1079
|
+
}
|
|
1080
|
+
.hqpu-active-top {
|
|
1081
|
+
display: flex;
|
|
1082
|
+
align-items: center;
|
|
1083
|
+
justify-content: space-between;
|
|
1084
|
+
gap: 8px;
|
|
1085
|
+
}
|
|
1086
|
+
.hqpu-active-title {
|
|
1087
|
+
display: flex;
|
|
1088
|
+
align-items: center;
|
|
1089
|
+
gap: 6px;
|
|
1090
|
+
min-width: 0;
|
|
1091
|
+
font-size: 11px;
|
|
1092
|
+
font-weight: 600;
|
|
1093
|
+
color: #e4e4e7;
|
|
1094
|
+
}
|
|
1095
|
+
.hqpu-pulse-dot {
|
|
1096
|
+
position: relative;
|
|
1097
|
+
flex: none;
|
|
1098
|
+
width: 8px;
|
|
1099
|
+
height: 8px;
|
|
1100
|
+
}
|
|
1101
|
+
.hqpu-pulse-dot::before {
|
|
1102
|
+
content: '';
|
|
1103
|
+
position: absolute;
|
|
1104
|
+
inset: 0;
|
|
1105
|
+
border-radius: 50%;
|
|
1106
|
+
background: var(--hq-brand-soft);
|
|
1107
|
+
}
|
|
1108
|
+
.hqpu-pulse-ring {
|
|
1109
|
+
position: absolute;
|
|
1110
|
+
inset: 0;
|
|
1111
|
+
border-radius: 50%;
|
|
1112
|
+
background: var(--hq-brand-soft);
|
|
1113
|
+
opacity: 0.6;
|
|
1114
|
+
animation: hqpu-ping 1.4s cubic-bezier(0, 0, 0.2, 1) infinite;
|
|
1115
|
+
}
|
|
1116
|
+
@keyframes hqpu-ping {
|
|
1117
|
+
75%, 100% { transform: scale(2.2); opacity: 0; }
|
|
1118
|
+
}
|
|
1119
|
+
.hqpu-active-percent {
|
|
1120
|
+
flex: none;
|
|
1121
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1122
|
+
font-size: 11px;
|
|
1123
|
+
font-weight: 500;
|
|
1124
|
+
color: var(--hq-brand-soft);
|
|
1125
|
+
}
|
|
1126
|
+
.hqpu-progress {
|
|
1127
|
+
height: 6px;
|
|
1128
|
+
margin-top: 10px;
|
|
1129
|
+
border-radius: 999px;
|
|
1130
|
+
background: #27272a;
|
|
1131
|
+
overflow: hidden;
|
|
1132
|
+
}
|
|
1133
|
+
.hqpu-progress-fill {
|
|
1134
|
+
background: linear-gradient(to right, var(--hq-brand), var(--hq-brand-soft));
|
|
1135
|
+
transition: width 0.3s ease;
|
|
1136
|
+
}
|
|
1137
|
+
.hqpu-active-meta {
|
|
1138
|
+
display: flex;
|
|
1139
|
+
justify-content: space-between;
|
|
1140
|
+
gap: 8px;
|
|
1141
|
+
margin-top: 4px;
|
|
1142
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1143
|
+
font-size: 10px;
|
|
1144
|
+
color: var(--hq-muted);
|
|
1145
|
+
}
|
|
1146
|
+
/* 4-step indicator row */
|
|
1147
|
+
.hqpu-job-steps4 {
|
|
1148
|
+
display: grid;
|
|
1149
|
+
grid-template-columns: repeat(4, 1fr);
|
|
1150
|
+
gap: 4px;
|
|
1151
|
+
margin-top: 10px;
|
|
1152
|
+
padding-top: 8px;
|
|
1153
|
+
border-top: 0.5px solid #27272a;
|
|
1154
|
+
text-align: center;
|
|
1155
|
+
font-size: 9px;
|
|
1156
|
+
font-weight: 500;
|
|
1157
|
+
}
|
|
1158
|
+
.hqpu-job-step--done { color: var(--hq-emerald); }
|
|
1159
|
+
.hqpu-job-step--active {
|
|
1160
|
+
color: var(--hq-brand-soft);
|
|
1161
|
+
animation: hqpu-step-pulse 1.2s ease-in-out infinite;
|
|
1162
|
+
}
|
|
1163
|
+
.hqpu-job-step--todo { color: var(--hq-muted); }
|
|
1164
|
+
@keyframes hqpu-step-pulse {
|
|
1165
|
+
0%, 100% { opacity: 1; }
|
|
1166
|
+
50% { opacity: 0.45; }
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
/* ── Result / rollback alert (amber) vs green success ──────────────────── */
|
|
1170
|
+
.hqpu-alert {
|
|
1171
|
+
display: flex;
|
|
1172
|
+
align-items: flex-start;
|
|
1173
|
+
gap: 8px;
|
|
1174
|
+
padding: 10px;
|
|
1175
|
+
border-radius: 8px;
|
|
1176
|
+
}
|
|
1177
|
+
.hqpu-alert--amber {
|
|
1178
|
+
background: rgba(69, 26, 3, 0.35);
|
|
1179
|
+
border: 0.5px solid rgba(180, 83, 9, 0.4);
|
|
1180
|
+
}
|
|
1181
|
+
.hqpu-alert--amber svg { color: var(--hq-amber); flex: none; margin-top: 1px; }
|
|
1182
|
+
.hqpu-alert-green {
|
|
1183
|
+
background: rgba(6, 78, 59, 0.3);
|
|
1184
|
+
border: 0.5px solid rgba(6, 95, 70, 0.4);
|
|
1185
|
+
color: #a7f3d0;
|
|
1186
|
+
font-size: 11px;
|
|
1187
|
+
}
|
|
1188
|
+
.hqpu-alert-title {
|
|
1189
|
+
font-size: 11px;
|
|
1190
|
+
font-weight: 500;
|
|
1191
|
+
color: #fcd34d;
|
|
1192
|
+
}
|
|
1193
|
+
.hqpu-alert-body {
|
|
1194
|
+
margin: 2px 0 0;
|
|
1195
|
+
font-size: 10px;
|
|
1196
|
+
line-height: 15px;
|
|
1197
|
+
color: rgba(252, 211, 77, 0.8);
|
|
1198
|
+
}
|
|
1199
|
+
.hqpu-alert-body code {
|
|
1200
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1201
|
+
background: rgba(120, 53, 15, 0.5);
|
|
1202
|
+
padding: 1px 4px;
|
|
1203
|
+
border-radius: 3px;
|
|
1204
|
+
}
|
|
1205
|
+
.hqpu-alert-actions {
|
|
1206
|
+
display: flex;
|
|
1207
|
+
gap: 8px;
|
|
1208
|
+
margin-top: 6px;
|
|
1209
|
+
}
|
|
1210
|
+
.hqpu-alert-dismiss {
|
|
1211
|
+
background: none;
|
|
1212
|
+
border: 0;
|
|
1213
|
+
padding: 0;
|
|
1214
|
+
font-size: 10px;
|
|
1215
|
+
color: #fbbf24;
|
|
1216
|
+
cursor: pointer;
|
|
1217
|
+
text-decoration: underline;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
/* ── Runtime engine card ────────────────────────────────────────────────── */
|
|
1221
|
+
.hqpu-engine {
|
|
1222
|
+
padding: 12px;
|
|
1223
|
+
border-radius: 8px;
|
|
1224
|
+
border: 0.5px solid var(--hq-border);
|
|
1225
|
+
background: var(--hq-card);
|
|
1226
|
+
}
|
|
1227
|
+
.hqpu-engine-head {
|
|
1228
|
+
display: flex;
|
|
1229
|
+
align-items: center;
|
|
1230
|
+
justify-content: space-between;
|
|
1231
|
+
gap: 8px;
|
|
1232
|
+
}
|
|
1233
|
+
.hqpu-engine-title {
|
|
1234
|
+
display: flex;
|
|
1235
|
+
align-items: center;
|
|
1236
|
+
gap: 6px;
|
|
1237
|
+
font-size: 12px;
|
|
1238
|
+
font-weight: 600;
|
|
1239
|
+
color: #e4e4e7;
|
|
1240
|
+
}
|
|
1241
|
+
.hqpu-engine-title svg { width: 14px; height: 14px; color: #a1a1aa; }
|
|
1242
|
+
.hqpu-chip {
|
|
1243
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1244
|
+
font-size: 9px;
|
|
1245
|
+
padding: 2px 6px;
|
|
1246
|
+
border-radius: 4px;
|
|
1247
|
+
background: #27272a;
|
|
1248
|
+
border: 0.5px solid #52525b;
|
|
1249
|
+
color: #d4d4d8;
|
|
1250
|
+
}
|
|
1251
|
+
.hqpu-engine-update-row {
|
|
1252
|
+
display: flex;
|
|
1253
|
+
align-items: center;
|
|
1254
|
+
justify-content: space-between;
|
|
1255
|
+
gap: 8px;
|
|
1256
|
+
margin-top: 10px;
|
|
1257
|
+
padding: 8px;
|
|
1258
|
+
border-radius: 6px;
|
|
1259
|
+
border: 0.5px solid rgba(63, 63, 70, 0.7);
|
|
1260
|
+
background: rgba(24, 24, 27, 0.6);
|
|
1261
|
+
}
|
|
1262
|
+
.hqpu-engine-new-line {
|
|
1263
|
+
display: flex;
|
|
1264
|
+
align-items: center;
|
|
1265
|
+
gap: 4px;
|
|
1266
|
+
font-size: 11px;
|
|
1267
|
+
font-weight: 500;
|
|
1268
|
+
color: #d4d4d8;
|
|
1269
|
+
}
|
|
1270
|
+
.hqpu-tag--brand {
|
|
1271
|
+
font-size: 9px;
|
|
1272
|
+
padding: 1px 4px;
|
|
1273
|
+
border-radius: 3px;
|
|
1274
|
+
color: var(--hq-brand-soft);
|
|
1275
|
+
background: rgba(69, 10, 10, 0.6);
|
|
1276
|
+
border: 0.5px solid rgba(153, 27, 27, 0.5);
|
|
1277
|
+
}
|
|
1278
|
+
.hqpu-engine-size {
|
|
1279
|
+
margin: 3px 0 0;
|
|
1280
|
+
font-size: 10px;
|
|
1281
|
+
color: var(--hq-muted);
|
|
1282
|
+
overflow: hidden;
|
|
1283
|
+
text-overflow: ellipsis;
|
|
1284
|
+
white-space: nowrap;
|
|
1285
|
+
}
|
|
1286
|
+
.hqpu-btn--engine {
|
|
1287
|
+
flex: none;
|
|
1288
|
+
height: auto;
|
|
1289
|
+
padding: 6px 10px;
|
|
1290
|
+
font-size: 10px;
|
|
1291
|
+
background: #27272a;
|
|
1292
|
+
border: 0.5px solid #52525b;
|
|
1293
|
+
color: #e4e4e7;
|
|
1294
|
+
border-radius: 5px;
|
|
1295
|
+
}
|
|
1296
|
+
.hqpu-btn--engine:hover:not(:disabled) { background: #3f3f46; }
|
|
1297
|
+
.hqpu-engine-note {
|
|
1298
|
+
display: flex;
|
|
1299
|
+
align-items: center;
|
|
1300
|
+
gap: 4px;
|
|
1301
|
+
margin: 8px 0 0;
|
|
1302
|
+
font-size: 9px;
|
|
1303
|
+
color: var(--hq-muted);
|
|
1304
|
+
}
|
|
1305
|
+
.hqpu-engine-note svg { width: 10px; height: 10px; color: var(--hq-amber); flex: none; }
|
|
1306
|
+
|
|
1307
|
+
/* ── Plugin cards ───────────────────────────────────────────────────────── */
|
|
1308
|
+
.hqpu-section-head {
|
|
1309
|
+
display: flex;
|
|
1310
|
+
align-items: center;
|
|
1311
|
+
justify-content: space-between;
|
|
1312
|
+
gap: 8px;
|
|
1313
|
+
padding: 0 2px;
|
|
1314
|
+
}
|
|
1315
|
+
.hqpu-section-title {
|
|
1316
|
+
display: flex;
|
|
1317
|
+
align-items: center;
|
|
1318
|
+
gap: 6px;
|
|
1319
|
+
font-size: 12px;
|
|
1320
|
+
font-weight: 600;
|
|
1321
|
+
color: #e4e4e7;
|
|
1322
|
+
}
|
|
1323
|
+
.hqpu-count-pill {
|
|
1324
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1325
|
+
font-size: 10px;
|
|
1326
|
+
font-weight: 700;
|
|
1327
|
+
padding: 1px 6px;
|
|
1328
|
+
border-radius: 999px;
|
|
1329
|
+
background: color-mix(in srgb, var(--hq-brand) 20%, transparent);
|
|
1330
|
+
color: var(--hq-brand-soft);
|
|
1331
|
+
}
|
|
1332
|
+
.hqpu-all-changelog {
|
|
1333
|
+
background: none;
|
|
1334
|
+
border: 0;
|
|
1335
|
+
padding: 0;
|
|
1336
|
+
font-size: 10px;
|
|
1337
|
+
color: var(--hq-muted);
|
|
1338
|
+
cursor: pointer;
|
|
1339
|
+
}
|
|
1340
|
+
.hqpu-all-changelog:hover { color: var(--hq-text); }
|
|
1341
|
+
|
|
1342
|
+
.hqpu-cards { gap: 8px; }
|
|
1343
|
+
.hqpu-card {
|
|
1344
|
+
margin: 0;
|
|
1345
|
+
padding: 10px;
|
|
1346
|
+
border-radius: 8px;
|
|
1347
|
+
border: 0.5px solid var(--hq-border);
|
|
1348
|
+
background: var(--hq-card);
|
|
1349
|
+
transition: border-color 0.15s ease;
|
|
1350
|
+
}
|
|
1351
|
+
.hqpu-card:hover { border-color: #52525b; }
|
|
1352
|
+
.hqpu-card-head {
|
|
1353
|
+
display: flex;
|
|
1354
|
+
align-items: flex-start;
|
|
1355
|
+
justify-content: space-between;
|
|
1356
|
+
gap: 8px;
|
|
1357
|
+
padding: 0;
|
|
1358
|
+
}
|
|
1359
|
+
.hqpu-card-head > * { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
1360
|
+
.hqpu-avatar {
|
|
1361
|
+
display: flex;
|
|
1362
|
+
align-items: center;
|
|
1363
|
+
justify-content: center;
|
|
1364
|
+
flex: none;
|
|
1365
|
+
width: 28px;
|
|
1366
|
+
height: 28px;
|
|
1367
|
+
border-radius: 6px;
|
|
1368
|
+
border: 0.5px solid #52525b;
|
|
1369
|
+
background: #27272a;
|
|
1370
|
+
color: #d4d4d8;
|
|
1371
|
+
font-size: 10px;
|
|
1372
|
+
font-weight: 700;
|
|
1373
|
+
}
|
|
1374
|
+
.hqpu-card-title {
|
|
1375
|
+
font-size: 12px;
|
|
1376
|
+
font-weight: 600;
|
|
1377
|
+
color: #e4e4e7;
|
|
1378
|
+
}
|
|
1379
|
+
.hqpu-card-sub {
|
|
1380
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1381
|
+
font-size: 9px;
|
|
1382
|
+
color: var(--hq-faint);
|
|
1383
|
+
}
|
|
1384
|
+
.hqpu-version-row {
|
|
1385
|
+
display: flex;
|
|
1386
|
+
align-items: center;
|
|
1387
|
+
gap: 4px;
|
|
1388
|
+
margin-top: 2px;
|
|
1389
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1390
|
+
font-size: 10px;
|
|
1391
|
+
}
|
|
1392
|
+
.hqpu-ver-old { color: var(--hq-muted); }
|
|
1393
|
+
.hqpu-ver-arrow { color: #52525b; }
|
|
1394
|
+
.hqpu-ver-new { color: var(--hq-emerald); font-weight: 700; }
|
|
1395
|
+
.hqpu-log-toggle {
|
|
1396
|
+
display: inline-flex;
|
|
1397
|
+
align-items: center;
|
|
1398
|
+
gap: 2px;
|
|
1399
|
+
background: none;
|
|
1400
|
+
border: 0;
|
|
1401
|
+
padding: 4px;
|
|
1402
|
+
font-size: 10px;
|
|
1403
|
+
color: var(--hq-muted);
|
|
1404
|
+
cursor: pointer;
|
|
1405
|
+
}
|
|
1406
|
+
.hqpu-log-toggle:hover { color: var(--hq-text); }
|
|
1407
|
+
.hqpu-log-toggle svg { width: 12px; height: 12px; transition: transform 0.15s ease; }
|
|
1408
|
+
.hqpu-log-toggle[data-open='true'] svg { transform: rotate(180deg); }
|
|
1409
|
+
.hqpu-changelog-drawer {
|
|
1410
|
+
margin-top: 8px;
|
|
1411
|
+
padding: 8px;
|
|
1412
|
+
border-radius: 6px;
|
|
1413
|
+
border: 0.5px solid #27272a;
|
|
1414
|
+
background: rgba(24, 24, 27, 0.8);
|
|
1415
|
+
font-size: 10px;
|
|
1416
|
+
}
|
|
1417
|
+
.hqpu-changelog-drawer-title {
|
|
1418
|
+
font-size: 9px;
|
|
1419
|
+
font-weight: 500;
|
|
1420
|
+
text-transform: uppercase;
|
|
1421
|
+
letter-spacing: 0.05em;
|
|
1422
|
+
color: #a1a1aa;
|
|
1423
|
+
}
|
|
1424
|
+
.hqpu-changelog-drawer ul {
|
|
1425
|
+
margin: 4px 0 0;
|
|
1426
|
+
padding-left: 16px;
|
|
1427
|
+
color: #d4d4d8;
|
|
1428
|
+
line-height: 16px;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
/* Blocked / incompatible card: dimmed, rose badge, no install button */
|
|
1432
|
+
.hqpu-card--blocked {
|
|
1433
|
+
opacity: 0.8;
|
|
1434
|
+
background: rgba(39, 39, 42, 0.6);
|
|
1435
|
+
border-color: #27272a;
|
|
1436
|
+
}
|
|
1437
|
+
.hqpu-card--blocked .hqpu-avatar {
|
|
1438
|
+
background: rgba(39, 39, 42, 0.5);
|
|
1439
|
+
border-color: #27272a;
|
|
1440
|
+
color: #71717a;
|
|
1441
|
+
}
|
|
1442
|
+
.hqpu-card--blocked .hqpu-card-title { color: #a1a1aa; }
|
|
1443
|
+
.hqpu-blocked-badge {
|
|
1444
|
+
font-size: 9px;
|
|
1445
|
+
padding: 2px 6px;
|
|
1446
|
+
border-radius: 4px;
|
|
1447
|
+
color: #fb7185;
|
|
1448
|
+
background: rgba(76, 5, 25, 0.5);
|
|
1449
|
+
border: 0.5px solid rgba(136, 19, 55, 0.5);
|
|
1450
|
+
}
|
|
1451
|
+
.hqpu-reason-line {
|
|
1452
|
+
display: flex;
|
|
1453
|
+
align-items: flex-start;
|
|
1454
|
+
gap: 4px;
|
|
1455
|
+
margin: 8px 0 0;
|
|
1456
|
+
padding: 6px;
|
|
1457
|
+
border-radius: 5px;
|
|
1458
|
+
border: 0.5px solid rgba(136, 19, 55, 0.2);
|
|
1459
|
+
background: rgba(76, 5, 25, 0.2);
|
|
1460
|
+
font-size: 10px;
|
|
1461
|
+
color: rgba(251, 113, 133, 0.9);
|
|
1462
|
+
}
|
|
1463
|
+
.hqpu-reason-line svg { width: 12px; height: 12px; flex: none; margin-top: 1px; }
|
|
1464
|
+
|
|
1465
|
+
/* Sticky footer */
|
|
1466
|
+
.hqpu-footer {
|
|
1467
|
+
flex: none;
|
|
1468
|
+
display: flex;
|
|
1469
|
+
flex-direction: column;
|
|
1470
|
+
align-items: stretch;
|
|
1471
|
+
gap: 8px;
|
|
1472
|
+
padding: 10px 12px 12px;
|
|
1473
|
+
border-top: 0.5px solid var(--hq-border);
|
|
1474
|
+
background: color-mix(in srgb, var(--hq-bg) 95%, transparent);
|
|
1475
|
+
}
|
|
1476
|
+
.hqpu-foot-meta {
|
|
1477
|
+
display: flex;
|
|
1478
|
+
align-items: center;
|
|
1479
|
+
justify-content: space-between;
|
|
1480
|
+
gap: 8px;
|
|
1481
|
+
font-size: 10px;
|
|
1482
|
+
color: var(--hq-muted);
|
|
1483
|
+
}
|
|
1484
|
+
.hqpu-foot-set {
|
|
1485
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1486
|
+
}
|
|
1487
|
+
.hqpu-foot-actions {
|
|
1488
|
+
display: flex;
|
|
1489
|
+
gap: 8px;
|
|
1490
|
+
}
|
|
1491
|
+
.hqpu-btn--block {
|
|
1492
|
+
flex: 1;
|
|
1493
|
+
height: 32px;
|
|
1494
|
+
background: var(--hq-brand);
|
|
1495
|
+
color: #fff;
|
|
1496
|
+
font-size: 11px;
|
|
1497
|
+
font-weight: 500;
|
|
1498
|
+
border-radius: 6px;
|
|
1499
|
+
box-shadow: 0 4px 12px rgba(127, 29, 29, 0.4);
|
|
1500
|
+
}
|
|
1501
|
+
.hqpu-btn--block:hover:not(:disabled) { background: var(--hq-brand-soft); }
|
|
1502
|
+
.hqpu-btn--block:disabled { opacity: 0.5; box-shadow: none; }
|
|
1503
|
+
.hqpu-btn--ignore {
|
|
1504
|
+
height: 32px;
|
|
1505
|
+
padding: 0 10px;
|
|
1506
|
+
background: var(--hq-card);
|
|
1507
|
+
border: 0.5px solid var(--hq-border);
|
|
1508
|
+
color: var(--hq-muted);
|
|
1509
|
+
font-size: 10px;
|
|
1510
|
+
border-radius: 6px;
|
|
1511
|
+
}
|
|
1512
|
+
.hqpu-btn--ignore:hover:not(:disabled) { background: var(--hq-hover); color: var(--hq-text); }
|
|
1513
|
+
.hqpu-footnote {
|
|
1514
|
+
margin: 0;
|
|
1515
|
+
font-size: 9px;
|
|
1516
|
+
text-align: center;
|
|
1517
|
+
color: var(--hq-muted);
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
/* Shared bits on the new palette */
|
|
1521
|
+
.hqpu-status { color: var(--hq-muted); font-size: 11px; }
|
|
1522
|
+
.hqpu-empty { color: var(--hq-muted); font-size: 11px; padding: 16px 0; }
|
|
1523
|
+
.hqpu-toast { background: var(--hq-card); border: 0.5px solid var(--hq-border); color: var(--hq-text); font-size: 12px; }
|
|
1524
|
+
.hqpu-skeleton-fill { background: #3f3f46; }
|
|
1525
|
+
.hqpu-job { background: var(--hq-card); border-color: var(--hq-border); }
|
|
1526
|
+
.hqpu-job-meta { color: var(--hq-muted); }
|
|
1527
|
+
.hqpu-release-notes { color: var(--hq-muted); font-size: 11px; }
|
|
1528
|
+
.hqpu-advanced > summary { color: var(--hq-muted); }
|
|
1529
|
+
.hqpu-recent-jobs li { color: var(--hq-muted); font-size: 10px; }
|
|
792
1530
|
`
|
|
793
1531
|
|
|
794
1532
|
const STYLE_TAG_ID = 'data-plugin-css="huaqiu-plugin-update"'
|
|
@@ -929,8 +1667,15 @@ export interface ClientContext {
|
|
|
929
1667
|
class UpdateStore implements UpdateStoreLike {
|
|
930
1668
|
private current: PluginUpdateReport | null = null
|
|
931
1669
|
private installingFlag = false
|
|
1670
|
+
/** The durable job the server currently reports as active (resumed across
|
|
1671
|
+
* panel close and DSH restart); null when idle. */
|
|
1672
|
+
private active: UpdateJob | null = null
|
|
1673
|
+
private recent: UpdateJob[] = []
|
|
932
1674
|
private readonly listeners = new Set<() => void>()
|
|
933
1675
|
private refreshTimer: number | null = null
|
|
1676
|
+
private pollTimer: number | null = null
|
|
1677
|
+
private pollBackoffMs = 1_000
|
|
1678
|
+
private pollInFlight = false
|
|
934
1679
|
|
|
935
1680
|
report(): PluginUpdateReport | null {
|
|
936
1681
|
return this.current
|
|
@@ -940,6 +1685,14 @@ class UpdateStore implements UpdateStoreLike {
|
|
|
940
1685
|
return this.installingFlag
|
|
941
1686
|
}
|
|
942
1687
|
|
|
1688
|
+
activeJob(): UpdateJob | null {
|
|
1689
|
+
return this.active
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
recentJobs(): UpdateJob[] {
|
|
1693
|
+
return [...this.recent]
|
|
1694
|
+
}
|
|
1695
|
+
|
|
943
1696
|
subscribe(listener: () => void): () => void {
|
|
944
1697
|
this.listeners.add(listener)
|
|
945
1698
|
return () => {
|
|
@@ -947,15 +1700,28 @@ class UpdateStore implements UpdateStoreLike {
|
|
|
947
1700
|
}
|
|
948
1701
|
}
|
|
949
1702
|
|
|
950
|
-
/**
|
|
951
|
-
*
|
|
952
|
-
*
|
|
1703
|
+
/**
|
|
1704
|
+
* Badge: counts actionable plugin updates AND an available runtime update AND
|
|
1705
|
+
* (while a job runs) an attention pill. A recent failed/rolled-back job with
|
|
1706
|
+
* nothing actionable left also keeps a dot, so the outcome is never silently
|
|
1707
|
+
* forgotten after the panel closes.
|
|
1708
|
+
*/
|
|
953
1709
|
badgeCount(): number {
|
|
1710
|
+
const job = this.active
|
|
1711
|
+
if (job !== null && !TERMINAL_PHASES.has(job.phase)) return 1
|
|
954
1712
|
const report = this.current
|
|
955
1713
|
if (report === null) return 0
|
|
956
|
-
|
|
1714
|
+
let count = report.items.filter(
|
|
957
1715
|
(item) => item.state === 'update' || item.state === 'install',
|
|
958
1716
|
).length
|
|
1717
|
+
if (report.runtime?.updateAvailable) count += 1
|
|
1718
|
+
if (count === 0) {
|
|
1719
|
+
const latest = this.recent[0]
|
|
1720
|
+
if (latest !== undefined && (latest.phase === 'failed' || latest.phase === 'rolled-back')) {
|
|
1721
|
+
count = 1
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
return count
|
|
959
1725
|
}
|
|
960
1726
|
|
|
961
1727
|
private emit(): void {
|
|
@@ -968,6 +1734,71 @@ class UpdateStore implements UpdateStoreLike {
|
|
|
968
1734
|
}
|
|
969
1735
|
}
|
|
970
1736
|
|
|
1737
|
+
/** Adopt a fresh `/status` snapshot and (re)arm the poll loop. */
|
|
1738
|
+
private applyStatus(status: UpdateJobStatus): void {
|
|
1739
|
+
const wasActive = this.active !== null
|
|
1740
|
+
this.active = status.activeJob
|
|
1741
|
+
this.recent = status.recentJobs
|
|
1742
|
+
if (wasActive && this.active === null) {
|
|
1743
|
+
// The active job settled (moved to recent/terminal) — the installing
|
|
1744
|
+
// footer state must clear on its own, without the panel's closure.
|
|
1745
|
+
this.installingFlag = false
|
|
1746
|
+
}
|
|
1747
|
+
this.emit()
|
|
1748
|
+
this.schedulePoll()
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
/**
|
|
1752
|
+
* Fetch the durable job status. Called on mount, on panel open (BEFORE
|
|
1753
|
+
* discovery) and by the poll loop. A failure (older hq-edge with no
|
|
1754
|
+
* `/status`, or a transient HTTP error) is swallowed: the last known job is
|
|
1755
|
+
* kept and the poller backs off and retries.
|
|
1756
|
+
*/
|
|
1757
|
+
async fetchStatus(): Promise<void> {
|
|
1758
|
+
try {
|
|
1759
|
+
const status = await fetchJobStatus()
|
|
1760
|
+
this.pollBackoffMs = 1_000
|
|
1761
|
+
this.applyStatus(status)
|
|
1762
|
+
} catch {
|
|
1763
|
+
this.schedulePoll()
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
/** ~1 s poll while a job is active; stop on terminal; back off on error
|
|
1768
|
+
* without discarding the last known job; never overlap requests. */
|
|
1769
|
+
private schedulePoll(): void {
|
|
1770
|
+
if (this.pollTimer !== null) {
|
|
1771
|
+
window.clearTimeout(this.pollTimer)
|
|
1772
|
+
this.pollTimer = null
|
|
1773
|
+
}
|
|
1774
|
+
const job = this.active
|
|
1775
|
+
if (job === null || TERMINAL_PHASES.has(job.phase)) {
|
|
1776
|
+
this.pollBackoffMs = 1_000
|
|
1777
|
+
return
|
|
1778
|
+
}
|
|
1779
|
+
this.pollTimer = window.setTimeout(() => {
|
|
1780
|
+
void this.pollTick()
|
|
1781
|
+
}, this.pollBackoffMs)
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
private async pollTick(): Promise<void> {
|
|
1785
|
+
if (this.pollInFlight) {
|
|
1786
|
+
this.schedulePoll()
|
|
1787
|
+
return
|
|
1788
|
+
}
|
|
1789
|
+
this.pollInFlight = true
|
|
1790
|
+
try {
|
|
1791
|
+
const status = await fetchJobStatus()
|
|
1792
|
+
this.pollBackoffMs = 1_000
|
|
1793
|
+
this.applyStatus(status)
|
|
1794
|
+
} catch {
|
|
1795
|
+
this.pollBackoffMs = Math.min(5_000, Math.max(1_000, this.pollBackoffMs * 2))
|
|
1796
|
+
this.schedulePoll()
|
|
1797
|
+
} finally {
|
|
1798
|
+
this.pollInFlight = false
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
|
|
971
1802
|
async refresh(): Promise<PluginUpdateReport | null> {
|
|
972
1803
|
try {
|
|
973
1804
|
const report = await fetchReport()
|
|
@@ -1009,6 +1840,10 @@ class UpdateStore implements UpdateStoreLike {
|
|
|
1009
1840
|
window.clearInterval(this.refreshTimer)
|
|
1010
1841
|
this.refreshTimer = null
|
|
1011
1842
|
}
|
|
1843
|
+
if (this.pollTimer !== null) {
|
|
1844
|
+
window.clearTimeout(this.pollTimer)
|
|
1845
|
+
this.pollTimer = null
|
|
1846
|
+
}
|
|
1012
1847
|
this.listeners.clear()
|
|
1013
1848
|
}
|
|
1014
1849
|
}
|
|
@@ -1060,10 +1895,31 @@ export function apply(ctx: ClientContext): () => void {
|
|
|
1060
1895
|
// publishes them after this plugin mounted must win, and one that never
|
|
1061
1896
|
// does must keep the canonical fallbacks.
|
|
1062
1897
|
ensureDesignTokens()
|
|
1063
|
-
//
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1898
|
+
// Resume-before-discovery: read the durable job status FIRST so an in-flight
|
|
1899
|
+
// install/runtime update is resumed (and its view rendered) even while the
|
|
1900
|
+
// update check round trip is still running. The panel itself opens from
|
|
1901
|
+
// whatever the store already holds; check() refreshes the list behind it.
|
|
1902
|
+
void store.fetchStatus()
|
|
1903
|
+
openUpdateView(store, copyFor())
|
|
1904
|
+
void store.check()
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
// While a job runs the row label shows its progress ("更新中 42%"), and the
|
|
1908
|
+
// tooltip names the phase. Store changes re-apply the label (the badge
|
|
1909
|
+
// subscription already re-renders the count pill).
|
|
1910
|
+
const statusLabel = (): { label: string; tooltip: string } => {
|
|
1911
|
+
const base = entryCopy(locale)
|
|
1912
|
+
const job = store.activeJob()
|
|
1913
|
+
if (job !== null) {
|
|
1914
|
+
const cc = copyFor()
|
|
1915
|
+
const pct = job.progress.percent
|
|
1916
|
+
const phase = cc.job.phase[job.phase] ?? job.phase
|
|
1917
|
+
return {
|
|
1918
|
+
label: pct !== null ? `${base.label} · ${Math.round(pct)}%` : `${base.label} · ${phase}`,
|
|
1919
|
+
tooltip: `${base.tooltip} · ${phase}`,
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
return base
|
|
1067
1923
|
}
|
|
1068
1924
|
|
|
1069
1925
|
const disposeEntry = mountSidebarEntry({
|
|
@@ -1076,8 +1932,8 @@ export function apply(ctx: ClientContext): () => void {
|
|
|
1076
1932
|
// `refresh.subscribe`'s listener, which re-applies these) so the row
|
|
1077
1933
|
// re-localizes when the host UI language changes — matching the sibling
|
|
1078
1934
|
// family rows, which read a live `currentLang` variable the same way.
|
|
1079
|
-
label: () =>
|
|
1080
|
-
tooltip: () =>
|
|
1935
|
+
label: () => statusLabel().label,
|
|
1936
|
+
tooltip: () => statusLabel().tooltip,
|
|
1081
1937
|
refresh: { subscribe: refreshSubscribe },
|
|
1082
1938
|
badge: {
|
|
1083
1939
|
subscribe: (listener) => store.subscribe(listener),
|
|
@@ -1088,8 +1944,21 @@ export function apply(ctx: ClientContext): () => void {
|
|
|
1088
1944
|
familySelectors: HUAQIU_SIDEBAR_ENTRY_FAMILY,
|
|
1089
1945
|
})
|
|
1090
1946
|
|
|
1947
|
+
// Store changes (active-job percentage, result dot) also re-apply the row
|
|
1948
|
+
// label, not just the badge count.
|
|
1949
|
+
store.subscribe(() => {
|
|
1950
|
+
for (const fn of [...localeSubscribers]) {
|
|
1951
|
+
try {
|
|
1952
|
+
fn()
|
|
1953
|
+
} catch {
|
|
1954
|
+
/* one bad subscriber must not strand the rest */
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
})
|
|
1958
|
+
|
|
1091
1959
|
store.startIdleRefresh()
|
|
1092
1960
|
void store.refresh()
|
|
1961
|
+
void store.fetchStatus()
|
|
1093
1962
|
|
|
1094
1963
|
const dispose = (): void => {
|
|
1095
1964
|
try {
|