@restregular/dsh-powerup 0.2.2 → 0.3.0
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/README.md +38 -13
- package/client.js +688 -140
- package/index.js +161 -14
- package/package.json +2 -2
package/client.js
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
|
|
45
45
|
const NS = 'dsh-powerup';
|
|
46
46
|
const SETTINGS_KEY = 'dsh-powerup:settings:v1';
|
|
47
|
-
const VERSION = '0.
|
|
47
|
+
const VERSION = '0.3.0';
|
|
48
48
|
const MENU_GAP = 8;
|
|
49
49
|
const MENU_WIDTH = 248;
|
|
50
50
|
|
|
@@ -154,6 +154,20 @@
|
|
|
154
154
|
'update.restartUnsupported': '当前运行方式无法自动重启:{reason}',
|
|
155
155
|
'update.restarting': 'DSH 正在重启,窗口即将关闭…',
|
|
156
156
|
'update.restartFailed': '重启请求失败:{message}',
|
|
157
|
+
'update.selfUpdateWarning': '更新 Power-Up 会在后台卸载并安装新版本;本面板会关闭,完成后刷新页面。',
|
|
158
|
+
'update.selfUpdating': 'Power-Up 正在后台更新…',
|
|
159
|
+
'update.selfRefresh': '完成后请刷新页面。',
|
|
160
|
+
'update.setDefault': '设为默认',
|
|
161
|
+
'update.defaultTag': '默认',
|
|
162
|
+
'update.defaultHint': '更新列表默认使用此安装源',
|
|
163
|
+
'restart.tag': '需重启',
|
|
164
|
+
'restart.tagHint': '该插件已更新,重启 DSH 后生效',
|
|
165
|
+
'restart.pending': '{count} 个插件待重启',
|
|
166
|
+
'restart.action': '重启 DSH',
|
|
167
|
+
'restart.title': '重启 DSH?',
|
|
168
|
+
'restart.desc': '重启会关闭并重新打开 DSH,正在运行的任务会中断。',
|
|
169
|
+
'restart.cancel': '取消',
|
|
170
|
+
'restart.confirm': '立即重启',
|
|
157
171
|
'update.doneTitle': '更新完成',
|
|
158
172
|
'update.doneInstalled': '{name} 已更新到 {version}。',
|
|
159
173
|
'update.doneDisabled': '{name} 已安装 {version},尚未启用。',
|
|
@@ -276,6 +290,20 @@
|
|
|
276
290
|
'update.restartUnsupported': 'This runtime cannot restart itself: {reason}',
|
|
277
291
|
'update.restarting': 'DSH is restarting; this window will close…',
|
|
278
292
|
'update.restartFailed': 'Restart request failed: {message}',
|
|
293
|
+
'update.selfUpdateWarning': 'Updating Power-Up removes and reinstalls it in the background; this panel closes, then refresh the page.',
|
|
294
|
+
'update.selfUpdating': 'Power-Up is updating in the background…',
|
|
295
|
+
'update.selfRefresh': 'Refresh the page when it finishes.',
|
|
296
|
+
'update.setDefault': 'Set as default',
|
|
297
|
+
'update.defaultTag': 'Default',
|
|
298
|
+
'update.defaultHint': 'The update list starts from this registry',
|
|
299
|
+
'restart.tag': 'Restart needed',
|
|
300
|
+
'restart.tagHint': 'Updated; restart DSH to apply',
|
|
301
|
+
'restart.pending': '{count} plugins need a restart',
|
|
302
|
+
'restart.action': 'Restart DSH',
|
|
303
|
+
'restart.title': 'Restart DSH?',
|
|
304
|
+
'restart.desc': 'Restarting closes and reopens DSH; running tasks are interrupted.',
|
|
305
|
+
'restart.cancel': 'Cancel',
|
|
306
|
+
'restart.confirm': 'Restart now',
|
|
279
307
|
'update.doneTitle': 'Update complete',
|
|
280
308
|
'update.doneInstalled': '{name} is now on {version}.',
|
|
281
309
|
'update.doneDisabled': '{name} {version} is installed but not enabled.',
|
|
@@ -597,6 +625,15 @@
|
|
|
597
625
|
);
|
|
598
626
|
}
|
|
599
627
|
|
|
628
|
+
function StarIcon({ size = 12 }) {
|
|
629
|
+
return h(Icon, { size },
|
|
630
|
+
h('path', {
|
|
631
|
+
d: 'm8 2.6 1.6 3.3 3.6.5-2.6 2.5.6 3.6L8 10.8l-3.2 1.7.6-3.6L2.8 6.4l3.6-.5L8 2.6Z',
|
|
632
|
+
fill: 'currentColor',
|
|
633
|
+
}),
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
|
|
600
637
|
function WarnIcon({ size = 16 }) {
|
|
601
638
|
return h(Icon, { size },
|
|
602
639
|
h('path', {
|
|
@@ -667,6 +704,15 @@
|
|
|
667
704
|
@keyframes dsh-powerup-toast-in{from{opacity:0;transform:translate(-50%,5px)}to{opacity:1;transform:translate(-50%,0)}}
|
|
668
705
|
.dsh-powerup-update-button{flex:none;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:0;border-radius:var(--dsw-radius-sm,6px);background:transparent;color:var(--dsw-alias-label-caption);cursor:pointer}
|
|
669
706
|
.dsh-powerup-update-button:hover{background:var(--dsw-alias-interactive-bg-hover,var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-label-secondary)}
|
|
707
|
+
.dsh-powerup-restart-tag{flex:none;display:inline-flex;align-items:center;height:18px;padding:0 6px;border:.5px solid color-mix(in srgb,var(--dsw-alias-state-warn-primary) 32%,transparent);border-radius:999px;background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label);font-size:10px;line-height:16px;white-space:nowrap}
|
|
708
|
+
.dsh-powerup-restart-bar{display:flex;flex:none;align-items:center;gap:8px;margin-left:8px}
|
|
709
|
+
.dsh-powerup-restart-count{color:var(--dsw-alias-state-warn-label);font-size:11.5px;line-height:18px;white-space:nowrap}
|
|
710
|
+
.dsh-powerup-restart-action{height:26px;padding:0 10px;border:.5px solid color-mix(in srgb,var(--dsw-alias-state-warn-primary) 36%,transparent);border-radius:var(--dsw-radius-sm,6px);background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label);font:inherit;font-size:11.5px;line-height:18px;cursor:pointer}
|
|
711
|
+
.dsh-powerup-restart-action:hover{background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 14%,transparent)}
|
|
712
|
+
.dsh-powerup-update-default{flex:none;display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:0;border-radius:var(--dsw-radius-sm,6px);background:transparent;color:var(--dsw-alias-label-caption);cursor:pointer}
|
|
713
|
+
.dsh-powerup-update-default:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-state-warn-label)}
|
|
714
|
+
.dsh-powerup-update-default[aria-pressed=true]{color:var(--dsw-alias-state-warn-label)}
|
|
715
|
+
.dsh-powerup-update-default-tag{flex:none;padding:0 6px;border:.5px solid color-mix(in srgb,var(--dsw-alias-state-warn-primary) 32%,transparent);border-radius:999px;color:var(--dsw-alias-state-warn-label);font-size:10px;line-height:16px;white-space:nowrap}
|
|
670
716
|
.dsh-powerup-update-mask{position:fixed;inset:0;z-index:1200;display:flex;align-items:center;justify-content:center;padding:24px;background:var(--dsw-alias-bg-mask-1);backdrop-filter:var(--dsw-mask-blur,blur(3px));pointer-events:auto;animation:dsh-powerup-update-fade .16s ease-out}
|
|
671
717
|
@keyframes dsh-powerup-update-fade{from{opacity:0}to{opacity:1}}
|
|
672
718
|
.dsh-powerup-update-dialog{box-sizing:border-box;position:relative;display:flex;flex-direction:column;width:min(560px,100%);max-height:min(760px,calc(100vh - 48px));max-height:min(760px,calc(100dvh - 48px));border-radius:var(--dsw-radius-panel,14px);background:var(--dsw-alias-bg-layer-2);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px;overflow:hidden;animation:dsh-powerup-update-rise .18s cubic-bezier(.2,.8,.3,1)}
|
|
@@ -698,6 +744,9 @@
|
|
|
698
744
|
.dsh-powerup-update-toggle .chevron{display:inline-flex;color:var(--dsw-alias-label-tertiary);transition:transform .16s ease}
|
|
699
745
|
.dsh-powerup-update-toggle[aria-expanded=true] .chevron{transform:rotate(180deg);color:var(--dsw-alias-label-secondary)}
|
|
700
746
|
.dsh-powerup-update-list{display:flex;flex-direction:column;max-height:240px;overflow-y:auto;border:.5px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-lg,10px);background:var(--dsw-alias-bg-layer-1);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2)}
|
|
747
|
+
.dsh-powerup-update-popover{position:fixed;z-index:1300;box-sizing:border-box;overflow-y:auto;pointer-events:auto;border:.5px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-lg,10px);background:var(--dsw-alias-bg-layer-2);box-shadow:var(--dsw-elevation-prominent);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2)}
|
|
748
|
+
.dsh-powerup-update-popover.dsh-powerup-update-list{border-radius:var(--dsw-radius-lg,10px)}
|
|
749
|
+
.dsh-powerup-update-popover.dsh-powerup-update-select-menu{display:flex;flex-direction:column;padding:4px}
|
|
701
750
|
.dsh-powerup-update-list-head{position:sticky;top:0;z-index:1;display:flex;align-items:center;justify-content:space-between;gap:8px;padding:7px 12px;border-bottom:.5px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}
|
|
702
751
|
.dsh-powerup-update-version{display:flex;align-items:center;gap:8px;width:100%;min-height:34px;padding:0 12px;border:0;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12.5px;line-height:18px;text-align:left;cursor:pointer;font-variant-numeric:tabular-nums}
|
|
703
752
|
.dsh-powerup-update-version>span:first-child{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
@@ -748,7 +797,7 @@
|
|
|
748
797
|
.dsh-powerup-update-ghost{height:36px;padding:0 14px;border:0;border-radius:var(--dsw-radius-md,8px);background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;line-height:22px;cursor:pointer}
|
|
749
798
|
.dsh-powerup-update-ghost:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
750
799
|
.dsh-powerup-update-ghost:active{background:var(--dsw-alias-interactive-bg-active)}
|
|
751
|
-
.dsh-powerup-update-close:focus-visible,.dsh-powerup-update-toggle:focus-visible,.dsh-powerup-update-version:focus-visible,.dsh-powerup-update-select-trigger:focus-visible,.dsh-powerup-update-option:focus-visible,.dsh-powerup-update-primary:focus-visible,.dsh-powerup-update-ghost:focus-visible,.dsh-powerup-update-button:focus-visible{outline:var(--dsw-focus-ring-width,2px) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:1px}
|
|
800
|
+
.dsh-powerup-update-close:focus-visible,.dsh-powerup-update-toggle:focus-visible,.dsh-powerup-update-version:focus-visible,.dsh-powerup-update-select-trigger:focus-visible,.dsh-powerup-update-option:focus-visible,.dsh-powerup-update-primary:focus-visible,.dsh-powerup-update-ghost:focus-visible,.dsh-powerup-update-button:focus-visible,.dsh-powerup-restart-action:focus-visible,.dsh-powerup-update-default:focus-visible{outline:var(--dsw-focus-ring-width,2px) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:1px}
|
|
752
801
|
@media (max-width:680px){.dsh-powerup-page{padding:20px 16px 42px}.dsh-powerup-actions{grid-template-columns:minmax(0,1fr)}.dsh-powerup-hero-art{width:54px;height:54px}.dsh-powerup-hero h1{font-size:19px}.dsh-powerup-card-head{flex-wrap:wrap}.dsh-powerup-switch{margin-left:auto}.dsh-powerup-update-mask{padding:12px}.dsh-powerup-update-dialog{max-height:calc(100dvh - 24px)}.dsh-powerup-update-head{padding:18px 12px 10px 18px}.dsh-powerup-update-body{padding:0 18px 4px}.dsh-powerup-update-foot{padding:14px 18px 18px}}
|
|
753
802
|
`;
|
|
754
803
|
|
|
@@ -1136,7 +1185,10 @@
|
|
|
1136
1185
|
// Plugin update: entry button on the built-in Plugins page + dialog
|
|
1137
1186
|
// ------------------------------------------------------------------
|
|
1138
1187
|
const PLUGIN_REGISTRY_MEMORY_KEY = 'dsh.plugin-manager.install-registry';
|
|
1188
|
+
const POWERUP_REGISTRY_KEY = 'dsh-powerup:update-registry:v1';
|
|
1189
|
+
const PENDING_RESTART_KEY = 'dsh-powerup:pending-restart:v1';
|
|
1139
1190
|
const UPDATE_API = '/dsh-powerup/api/restart';
|
|
1191
|
+
const SELF_UPDATE_API = '/dsh-powerup/api/self-update';
|
|
1140
1192
|
const OFFICIAL_REGISTRY = 'https://registry.npmjs.org/';
|
|
1141
1193
|
const REGISTRY_HOSTS = new Map([
|
|
1142
1194
|
['registry.npmjs.org', 'update.registryDefault'],
|
|
@@ -1185,16 +1237,75 @@
|
|
|
1185
1237
|
}
|
|
1186
1238
|
}
|
|
1187
1239
|
|
|
1240
|
+
/**
|
|
1241
|
+
* Read the registry the user starred as Power-Up's list default.
|
|
1242
|
+
* @returns {{ key?: string, value?: string | null, url?: string, label?: string, hint?: string } | null}
|
|
1243
|
+
*/
|
|
1244
|
+
function readDefaultRegistry() {
|
|
1245
|
+
try {
|
|
1246
|
+
const raw = localStorage.getItem(POWERUP_REGISTRY_KEY);
|
|
1247
|
+
if (typeof raw !== 'string' || raw === '') return null;
|
|
1248
|
+
const parsed = JSON.parse(raw);
|
|
1249
|
+
if (parsed === null || typeof parsed !== 'object') return null;
|
|
1250
|
+
return parsed;
|
|
1251
|
+
} catch {
|
|
1252
|
+
return null;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
/**
|
|
1257
|
+
* Persist the registry the update list should start from.
|
|
1258
|
+
* @param {{ key: string, value: string | null, url: string, label: string, hint: string }} choice
|
|
1259
|
+
*/
|
|
1260
|
+
function writeDefaultRegistry(choice) {
|
|
1261
|
+
try {
|
|
1262
|
+
localStorage.setItem(POWERUP_REGISTRY_KEY, JSON.stringify({
|
|
1263
|
+
key: choice.key,
|
|
1264
|
+
value: choice.value,
|
|
1265
|
+
url: choice.url,
|
|
1266
|
+
label: choice.label,
|
|
1267
|
+
hint: choice.hint,
|
|
1268
|
+
}));
|
|
1269
|
+
} catch {
|
|
1270
|
+
/* A full or private store keeps the in-memory selection. */
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
/** Pending-restart records for this browser session (cleared by a real restart). */
|
|
1275
|
+
function readPendingRestarts() {
|
|
1276
|
+
try {
|
|
1277
|
+
const raw = sessionStorage.getItem(PENDING_RESTART_KEY);
|
|
1278
|
+
if (typeof raw !== 'string' || raw === '') return [];
|
|
1279
|
+
const parsed = JSON.parse(raw);
|
|
1280
|
+
return Array.isArray(parsed) ? parsed.filter((entry) => entry !== null && typeof entry === 'object' && typeof entry.name === 'string') : [];
|
|
1281
|
+
} catch {
|
|
1282
|
+
return [];
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
/**
|
|
1287
|
+
* @param {Array<{ name: string, version?: string, reason?: string, at?: number }>} entries
|
|
1288
|
+
*/
|
|
1289
|
+
function writePendingRestarts(entries) {
|
|
1290
|
+
try {
|
|
1291
|
+
sessionStorage.setItem(PENDING_RESTART_KEY, JSON.stringify(entries));
|
|
1292
|
+
} catch {
|
|
1293
|
+
/* The badge is a hint; failing to persist it must not break updates. */
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1188
1297
|
/**
|
|
1189
1298
|
* The registry choices the update dialog offers: the Host's configured
|
|
1190
1299
|
* default (with the URL pnpm actually names), each configured fallback,
|
|
1191
|
-
*
|
|
1300
|
+
* the built-in Add dialog's remembered custom address, and Power-Up's
|
|
1301
|
+
* own starred default.
|
|
1192
1302
|
* @param {{ registry: string | null, fallbackRegistries: string[], resolved: string | null } | null} registries
|
|
1193
1303
|
* @param {object | null} remembered
|
|
1304
|
+
* @param {object | null} powerupDefault
|
|
1194
1305
|
* @param {(key: string) => string} t
|
|
1195
1306
|
* @returns {Array<{ key: string, label: string, hint: string, value: string | null, url: string }>}
|
|
1196
1307
|
*/
|
|
1197
|
-
function buildRegistryChoices(registries, remembered, t) {
|
|
1308
|
+
function buildRegistryChoices(registries, remembered, powerupDefault, t) {
|
|
1198
1309
|
const choices = [];
|
|
1199
1310
|
const seen = new Set();
|
|
1200
1311
|
const add = (choice) => {
|
|
@@ -1243,6 +1354,18 @@
|
|
|
1243
1354
|
url: normalizeRegistry(rememberedOffered),
|
|
1244
1355
|
});
|
|
1245
1356
|
}
|
|
1357
|
+
if (powerupDefault !== null && typeof powerupDefault === 'object') {
|
|
1358
|
+
const value = powerupDefault.value === undefined ? null : powerupDefault.value;
|
|
1359
|
+
const url = powerupDefault.url ?? value ?? OFFICIAL_REGISTRY;
|
|
1360
|
+
add({
|
|
1361
|
+
label: typeof powerupDefault.label === 'string'
|
|
1362
|
+
? powerupDefault.label
|
|
1363
|
+
: (value === null ? t('update.registryDefault') : t('update.registryCustom')),
|
|
1364
|
+
hint: typeof powerupDefault.hint === 'string' ? powerupDefault.hint : registryHost(url),
|
|
1365
|
+
value,
|
|
1366
|
+
url: normalizeRegistry(url),
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1246
1369
|
if (choices.length === 0) {
|
|
1247
1370
|
add({
|
|
1248
1371
|
label: t('update.registryDefault'),
|
|
@@ -1255,12 +1378,35 @@
|
|
|
1255
1378
|
}
|
|
1256
1379
|
|
|
1257
1380
|
/**
|
|
1258
|
-
*
|
|
1381
|
+
* Find the offered registry matching a persisted Power-Up default.
|
|
1382
|
+
* @param {Array<{ key: string, value: string | null, url: string }>} choices
|
|
1383
|
+
* @param {{ key?: string, value?: string | null, url?: string } | null} stored
|
|
1384
|
+
* @returns {object | undefined}
|
|
1385
|
+
*/
|
|
1386
|
+
function matchStoredChoice(choices, stored) {
|
|
1387
|
+
if (stored === null || typeof stored !== 'object') return undefined;
|
|
1388
|
+
if (typeof stored.key === 'string') {
|
|
1389
|
+
const byKey = choices.find((choice) => choice.key === stored.key);
|
|
1390
|
+
if (byKey !== undefined) return byKey;
|
|
1391
|
+
}
|
|
1392
|
+
const value = stored.value === undefined ? null : stored.value;
|
|
1393
|
+
if (value === null) return choices.find((choice) => choice.value === null);
|
|
1394
|
+
const wanted = normalizeRegistry(value ?? stored.url ?? OFFICIAL_REGISTRY);
|
|
1395
|
+
return choices.find((choice) => choice.value !== null && normalizeRegistry(choice.value) === wanted)
|
|
1396
|
+
?? choices.find((choice) => normalizeRegistry(choice.url) === wanted);
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
/**
|
|
1400
|
+
* Choose the registry the update list starts from: Power-Up's starred
|
|
1401
|
+
* default, then what the Add dialog remembered, then the configured one.
|
|
1259
1402
|
* @param {Array<{ key: string, value: string | null, url: string }>} choices
|
|
1260
1403
|
* @param {object | null} remembered
|
|
1404
|
+
* @param {object | null} powerupDefault
|
|
1261
1405
|
* @returns {object}
|
|
1262
1406
|
*/
|
|
1263
|
-
function pickRegistryChoice(choices, remembered) {
|
|
1407
|
+
function pickRegistryChoice(choices, remembered, powerupDefault) {
|
|
1408
|
+
const starred = matchStoredChoice(choices, powerupDefault);
|
|
1409
|
+
if (starred !== undefined) return starred;
|
|
1264
1410
|
if (remembered !== null && typeof remembered === 'object') {
|
|
1265
1411
|
if (remembered.kind === 'offered') {
|
|
1266
1412
|
const wanted = remembered.registry === null ? null : normalizeRegistry(remembered.registry);
|
|
@@ -1459,20 +1605,23 @@
|
|
|
1459
1605
|
|
|
1460
1606
|
/** Tooltip/accessible label for one injected update button. */
|
|
1461
1607
|
function updateButtonLabel(name, t) {
|
|
1462
|
-
return
|
|
1608
|
+
return `${t('update.button')} · ${name}`;
|
|
1463
1609
|
}
|
|
1464
1610
|
|
|
1465
1611
|
/**
|
|
1466
1612
|
* Ensure every third-party card in the built-in "Installed plugins"
|
|
1467
|
-
* group carries one update button
|
|
1468
|
-
*
|
|
1613
|
+
* group carries one update button and, when its update waits for a
|
|
1614
|
+
* restart, a small restart tag. Idempotent, so React re-renders and the
|
|
1615
|
+
* observer can call it repeatedly.
|
|
1469
1616
|
* @param {Document} doc
|
|
1470
1617
|
* @param {(name: string) => void} open
|
|
1618
|
+
* @param {string[]} pendingNames
|
|
1471
1619
|
* @param {(key: string, params?: object) => string} t
|
|
1472
1620
|
* @param {Set<Element>} buttons
|
|
1473
1621
|
*/
|
|
1474
|
-
function ensureUpdateButtons(doc, open, t, buttons) {
|
|
1622
|
+
function ensureUpdateButtons(doc, open, pendingNames, t, buttons) {
|
|
1475
1623
|
if (doc === null || doc === undefined || typeof doc.querySelectorAll !== 'function') return;
|
|
1624
|
+
const pending = new Set(Array.isArray(pendingNames) ? pendingNames : []);
|
|
1476
1625
|
const cards = doc.querySelectorAll('[data-plugin-group="bundles"] [data-plugin-package]');
|
|
1477
1626
|
for (const card of cards) {
|
|
1478
1627
|
const name = card.getAttribute('data-plugin-package');
|
|
@@ -1498,10 +1647,105 @@
|
|
|
1498
1647
|
const label = updateButtonLabel(name, t);
|
|
1499
1648
|
if (button.title !== label) button.title = label;
|
|
1500
1649
|
if (button.getAttribute('aria-label') !== label) button.setAttribute('aria-label', label);
|
|
1650
|
+
|
|
1651
|
+
let tag = card.querySelector('[data-dsh-powerup-restart-tag]');
|
|
1652
|
+
if (pending.has(name)) {
|
|
1653
|
+
if (tag === null || tag === undefined) {
|
|
1654
|
+
const head = card.firstElementChild;
|
|
1655
|
+
const main = head === null || head === undefined ? null : head.children?.[1];
|
|
1656
|
+
const titleRow = main === null || main === undefined ? null : main.firstElementChild;
|
|
1657
|
+
if (titleRow !== null && titleRow !== undefined) {
|
|
1658
|
+
tag = doc.createElement('span');
|
|
1659
|
+
tag.className = 'dsh-powerup-restart-tag';
|
|
1660
|
+
tag.setAttribute('data-dsh-powerup-restart-tag', '');
|
|
1661
|
+
titleRow.appendChild(tag);
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
if (tag !== null && tag !== undefined) {
|
|
1665
|
+
const text = t('restart.tag');
|
|
1666
|
+
if (tag.textContent !== text) tag.textContent = text;
|
|
1667
|
+
if (tag.title !== t('restart.tagHint')) tag.title = t('restart.tagHint');
|
|
1668
|
+
}
|
|
1669
|
+
} else if (tag !== null && tag !== undefined) {
|
|
1670
|
+
tag.remove();
|
|
1671
|
+
}
|
|
1501
1672
|
}
|
|
1502
1673
|
for (const button of [...buttons]) if (button.isConnected !== true) buttons.delete(button);
|
|
1503
1674
|
}
|
|
1504
1675
|
|
|
1676
|
+
/**
|
|
1677
|
+
* Put "N plugins need a restart" and a restart button beside the
|
|
1678
|
+
* Installed plugins heading. The group head owns the h3; the injected bar
|
|
1679
|
+
* sits at the end of that row and is removed when nothing is pending.
|
|
1680
|
+
* @param {Document} doc
|
|
1681
|
+
* @param {number} count
|
|
1682
|
+
* @param {() => void} onRestart
|
|
1683
|
+
* @param {(key: string, params?: object) => string} t
|
|
1684
|
+
*/
|
|
1685
|
+
function ensureRestartBar(doc, count, onRestart, t) {
|
|
1686
|
+
if (doc === null || doc === undefined || typeof doc.querySelector !== 'function') return;
|
|
1687
|
+
const group = doc.querySelector('[data-plugin-group="bundles"]');
|
|
1688
|
+
if (group === null || group === undefined || count <= 0) {
|
|
1689
|
+
const stale = group?.querySelector?.('[data-dsh-powerup-restart-bar]');
|
|
1690
|
+
stale?.remove?.();
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
1693
|
+
const head = group.firstElementChild;
|
|
1694
|
+
if (head === null || head === undefined) return;
|
|
1695
|
+
let bar = group.querySelector('[data-dsh-powerup-restart-bar]');
|
|
1696
|
+
if (bar === null || bar === undefined) {
|
|
1697
|
+
bar = doc.createElement('div');
|
|
1698
|
+
bar.className = 'dsh-powerup-restart-bar';
|
|
1699
|
+
bar.setAttribute('data-dsh-powerup-restart-bar', '');
|
|
1700
|
+
const label = doc.createElement('span');
|
|
1701
|
+
label.className = 'dsh-powerup-restart-count';
|
|
1702
|
+
const button = doc.createElement('button');
|
|
1703
|
+
button.type = 'button';
|
|
1704
|
+
button.className = 'dsh-powerup-restart-action';
|
|
1705
|
+
button.addEventListener('click', (event) => {
|
|
1706
|
+
event.preventDefault();
|
|
1707
|
+
event.stopPropagation();
|
|
1708
|
+
onRestart();
|
|
1709
|
+
});
|
|
1710
|
+
bar.appendChild(label);
|
|
1711
|
+
bar.appendChild(button);
|
|
1712
|
+
head.appendChild(bar);
|
|
1713
|
+
}
|
|
1714
|
+
const label = bar.firstElementChild;
|
|
1715
|
+
const button = bar.lastElementChild;
|
|
1716
|
+
const text = t('restart.pending', { count });
|
|
1717
|
+
if (label !== null && label !== undefined && label.textContent !== text) label.textContent = text;
|
|
1718
|
+
const action = t('restart.action');
|
|
1719
|
+
if (button !== null && button !== undefined && button.textContent !== action) button.textContent = action;
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
/**
|
|
1723
|
+
* Place a floating panel under an anchor, flipping above when the space
|
|
1724
|
+
* below is short, and clamp it to the viewport.
|
|
1725
|
+
* @param {{ getBoundingClientRect: () => DOMRect }} anchor
|
|
1726
|
+
* @param {number} minWidth
|
|
1727
|
+
* @param {number} preferredHeight
|
|
1728
|
+
* @returns {{ left: number, width: number, top?: number, bottom?: number, maxHeight: number }}
|
|
1729
|
+
*/
|
|
1730
|
+
function popoverPlacement(anchor, minWidth, preferredHeight) {
|
|
1731
|
+
const rect = anchor.getBoundingClientRect();
|
|
1732
|
+
const gap = 6;
|
|
1733
|
+
const margin = 12;
|
|
1734
|
+
const viewportWidth = typeof window === 'undefined' ? 1280 : window.innerWidth;
|
|
1735
|
+
const viewportHeight = typeof window === 'undefined' ? 900 : window.innerHeight;
|
|
1736
|
+
const width = Math.min(Math.max(rect.width, minWidth), Math.max(200, viewportWidth - margin * 2));
|
|
1737
|
+
const left = Math.min(Math.max(margin, rect.left), Math.max(margin, viewportWidth - width - margin));
|
|
1738
|
+
const spaceBelow = viewportHeight - rect.bottom - margin - gap;
|
|
1739
|
+
const spaceAbove = rect.top - margin - gap;
|
|
1740
|
+
const above = spaceBelow < Math.min(preferredHeight, 180) && spaceAbove > spaceBelow;
|
|
1741
|
+
return {
|
|
1742
|
+
left,
|
|
1743
|
+
width,
|
|
1744
|
+
...(above ? { bottom: viewportHeight - rect.top + gap } : { top: rect.bottom + gap }),
|
|
1745
|
+
maxHeight: Math.max(120, Math.min(preferredHeight, above ? spaceAbove : spaceBelow)),
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1505
1749
|
/**
|
|
1506
1750
|
* The Plugins-page update control. It injects one icon button into each
|
|
1507
1751
|
* third-party card of the built-in "Installed plugins" group, then owns
|
|
@@ -1512,12 +1756,21 @@
|
|
|
1512
1756
|
function PluginUpdateOverlay({ manager, remote, ctx }) {
|
|
1513
1757
|
const [view, setView] = useState(null);
|
|
1514
1758
|
const [localeTick, forceLocale] = useState(0);
|
|
1759
|
+
const [pending, setPending] = useState(() => readPendingRestarts());
|
|
1760
|
+
const [restartAsk, setRestartAsk] = useState(false);
|
|
1761
|
+
const [restartCapability, setRestartCapability] = useState(null);
|
|
1762
|
+
const [restartBusy, setRestartBusy] = useState(false);
|
|
1763
|
+
const [restartError, setRestartError] = useState(null);
|
|
1515
1764
|
const tokenRef = useRef(null);
|
|
1516
1765
|
const abortRef = useRef(null);
|
|
1517
1766
|
const buttonsRef = useRef(new Set());
|
|
1518
1767
|
const inputRef = useRef(null);
|
|
1768
|
+
const versionRef = useRef(null);
|
|
1769
|
+
const versionPanelRef = useRef(null);
|
|
1519
1770
|
const registryRef = useRef(null);
|
|
1771
|
+
const registryPanelRef = useRef(null);
|
|
1520
1772
|
const versionSeq = useRef(0);
|
|
1773
|
+
const openRestartRef = useRef(() => {});
|
|
1521
1774
|
const tRef = useRef((key, params) => translateCurrent(key, params));
|
|
1522
1775
|
tRef.current = (key, params) => translateCurrent(key, params);
|
|
1523
1776
|
|
|
@@ -1537,7 +1790,7 @@
|
|
|
1537
1790
|
|
|
1538
1791
|
const close = useCallback(() => {
|
|
1539
1792
|
const current = view;
|
|
1540
|
-
if (current !== null && (current.phase === 'working' || current.phase === 'restarting' || current.enabling === true)) return;
|
|
1793
|
+
if (current !== null && (current.phase === 'working' || current.phase === 'self-updating' || current.phase === 'restarting' || current.enabling === true)) return;
|
|
1541
1794
|
tokenRef.current = null;
|
|
1542
1795
|
try {
|
|
1543
1796
|
abortRef.current?.abort?.();
|
|
@@ -1548,6 +1801,36 @@
|
|
|
1548
1801
|
setView(null);
|
|
1549
1802
|
}, [view]);
|
|
1550
1803
|
|
|
1804
|
+
/** Dismiss both floating panels without touching the dialog. */
|
|
1805
|
+
const closePopovers = useCallback(() => {
|
|
1806
|
+
setView((current) => current === null ? current : {
|
|
1807
|
+
...current,
|
|
1808
|
+
listOpen: false,
|
|
1809
|
+
listPopover: null,
|
|
1810
|
+
registryOpen: false,
|
|
1811
|
+
registryPopover: null,
|
|
1812
|
+
});
|
|
1813
|
+
}, []);
|
|
1814
|
+
|
|
1815
|
+
/** Remember that one updated plugin waits for a whole-app restart. */
|
|
1816
|
+
const recordPending = useCallback((entry) => {
|
|
1817
|
+
setPending((current) => {
|
|
1818
|
+
const next = current.filter((item) => item.name !== entry.name);
|
|
1819
|
+
next.push({ ...entry, at: Date.now() });
|
|
1820
|
+
writePendingRestarts(next);
|
|
1821
|
+
return next;
|
|
1822
|
+
});
|
|
1823
|
+
}, []);
|
|
1824
|
+
|
|
1825
|
+
/** Drop a pending marker when its update never started. */
|
|
1826
|
+
const removePending = useCallback((name) => {
|
|
1827
|
+
setPending((current) => {
|
|
1828
|
+
const next = current.filter((item) => item.name !== name);
|
|
1829
|
+
writePendingRestarts(next);
|
|
1830
|
+
return next;
|
|
1831
|
+
});
|
|
1832
|
+
}, []);
|
|
1833
|
+
|
|
1551
1834
|
const loadVersions = useCallback(async (token, name, choice, choices, resetVersion) => {
|
|
1552
1835
|
const t = tRef.current;
|
|
1553
1836
|
const seq = versionSeq.current + 1;
|
|
@@ -1596,28 +1879,28 @@
|
|
|
1596
1879
|
setView({
|
|
1597
1880
|
name,
|
|
1598
1881
|
title: name,
|
|
1882
|
+
self: name === MODULE_ID,
|
|
1599
1883
|
currentVersion: null,
|
|
1600
1884
|
phase: 'loading',
|
|
1601
1885
|
step: null,
|
|
1602
1886
|
choices: [],
|
|
1603
1887
|
registryKey: '',
|
|
1888
|
+
defaultRegistryKey: null,
|
|
1604
1889
|
versions: [],
|
|
1605
1890
|
versionsState: 'loading',
|
|
1606
1891
|
versionsError: null,
|
|
1607
1892
|
versionsRegistry: null,
|
|
1608
1893
|
version: '',
|
|
1609
|
-
listOpen:
|
|
1894
|
+
listOpen: false,
|
|
1895
|
+
listPopover: null,
|
|
1610
1896
|
registryOpen: false,
|
|
1897
|
+
registryPopover: null,
|
|
1611
1898
|
error: null,
|
|
1612
1899
|
formError: null,
|
|
1613
1900
|
log: '',
|
|
1614
1901
|
enabling: false,
|
|
1615
1902
|
restarting: false,
|
|
1616
1903
|
});
|
|
1617
|
-
if (name === MODULE_ID) {
|
|
1618
|
-
patch(token, { phase: 'self' });
|
|
1619
|
-
return;
|
|
1620
|
-
}
|
|
1621
1904
|
try {
|
|
1622
1905
|
const [bundles, registries] = await Promise.all([manager.listBundles(), manager.registries()]);
|
|
1623
1906
|
if (tokenRef.current !== token) return;
|
|
@@ -1634,8 +1917,10 @@
|
|
|
1634
1917
|
return;
|
|
1635
1918
|
}
|
|
1636
1919
|
const remembered = readRememberedRegistry();
|
|
1637
|
-
const
|
|
1638
|
-
const
|
|
1920
|
+
const powerupDefault = readDefaultRegistry();
|
|
1921
|
+
const choices = buildRegistryChoices(registries.ok ? registries.value : null, remembered, powerupDefault, t);
|
|
1922
|
+
const choice = pickRegistryChoice(choices, remembered, powerupDefault);
|
|
1923
|
+
const starred = matchStoredChoice(choices, powerupDefault);
|
|
1639
1924
|
patch(token, {
|
|
1640
1925
|
title: packageDisplayTitle(pkg),
|
|
1641
1926
|
currentVersion: pkg.version ?? null,
|
|
@@ -1643,6 +1928,7 @@
|
|
|
1643
1928
|
phase: 'choose',
|
|
1644
1929
|
choices,
|
|
1645
1930
|
registryKey: choice.key,
|
|
1931
|
+
defaultRegistryKey: starred === undefined ? null : starred.key,
|
|
1646
1932
|
versionsState: 'loading',
|
|
1647
1933
|
});
|
|
1648
1934
|
await loadVersions(token, name, choice, choices, false);
|
|
@@ -1664,7 +1950,9 @@
|
|
|
1664
1950
|
const ensure = () => {
|
|
1665
1951
|
scheduled = false;
|
|
1666
1952
|
if (stopped) return;
|
|
1667
|
-
|
|
1953
|
+
const names = pending.map((entry) => entry.name);
|
|
1954
|
+
ensureUpdateButtons(document, (name) => openRef.current(name), names, tRef.current, buttons);
|
|
1955
|
+
ensureRestartBar(document, pending.length, () => openRestartRef.current(), tRef.current);
|
|
1668
1956
|
};
|
|
1669
1957
|
const schedule = () => {
|
|
1670
1958
|
if (scheduled || stopped) return;
|
|
@@ -1687,7 +1975,7 @@
|
|
|
1687
1975
|
}
|
|
1688
1976
|
buttons.clear();
|
|
1689
1977
|
};
|
|
1690
|
-
}, [localeTick]);
|
|
1978
|
+
}, [localeTick, pending]);
|
|
1691
1979
|
|
|
1692
1980
|
// Keep the selected version input focused when the chooser opens.
|
|
1693
1981
|
useEffect(() => {
|
|
@@ -1700,19 +1988,18 @@
|
|
|
1700
1988
|
}
|
|
1701
1989
|
}, [view === null ? null : view.phase]);
|
|
1702
1990
|
|
|
1703
|
-
// Escape closes the innermost
|
|
1704
|
-
// version list, then the dialog itself.
|
|
1991
|
+
// Escape closes the innermost floating panel first, then the dialog.
|
|
1705
1992
|
useEffect(() => {
|
|
1706
1993
|
if (view === null) return undefined;
|
|
1707
1994
|
const onKeyDown = (event) => {
|
|
1708
1995
|
if (event.key !== 'Escape') return;
|
|
1709
1996
|
event.stopPropagation();
|
|
1710
1997
|
if (view.registryOpen === true) {
|
|
1711
|
-
setView((current) => current === null ? current : { ...current, registryOpen: false });
|
|
1998
|
+
setView((current) => current === null ? current : { ...current, registryOpen: false, registryPopover: null });
|
|
1712
1999
|
return;
|
|
1713
2000
|
}
|
|
1714
2001
|
if (view.phase === 'choose' && view.listOpen === true) {
|
|
1715
|
-
setView((current) => current === null ? current : { ...current, listOpen: false });
|
|
2002
|
+
setView((current) => current === null ? current : { ...current, listOpen: false, listPopover: null });
|
|
1716
2003
|
return;
|
|
1717
2004
|
}
|
|
1718
2005
|
close();
|
|
@@ -1721,17 +2008,42 @@
|
|
|
1721
2008
|
return () => document.removeEventListener('keydown', onKeyDown, true);
|
|
1722
2009
|
}, [view, close]);
|
|
1723
2010
|
|
|
1724
|
-
//
|
|
2011
|
+
// Clicking outside the open floating panel and its trigger dismisses it.
|
|
1725
2012
|
useEffect(() => {
|
|
1726
|
-
if (view === null || view.registryOpen !== true) return undefined;
|
|
2013
|
+
if (view === null || (view.registryOpen !== true && view.listOpen !== true)) return undefined;
|
|
1727
2014
|
const onPointerDown = (event) => {
|
|
1728
|
-
const
|
|
1729
|
-
if (
|
|
1730
|
-
|
|
2015
|
+
const target = event.target;
|
|
2016
|
+
if (!(target instanceof Node)) return;
|
|
2017
|
+
const inside = [versionRef.current, versionPanelRef.current, registryRef.current, registryPanelRef.current]
|
|
2018
|
+
.some((root) => root !== null && root.contains(target));
|
|
2019
|
+
if (inside) return;
|
|
2020
|
+
closePopovers();
|
|
1731
2021
|
};
|
|
1732
2022
|
document.addEventListener('pointerdown', onPointerDown, true);
|
|
1733
2023
|
return () => document.removeEventListener('pointerdown', onPointerDown, true);
|
|
1734
|
-
}, [view === null ? null : view.registryOpen]);
|
|
2024
|
+
}, [view === null ? null : `${String(view.registryOpen)}:${String(view.listOpen)}`, closePopovers]);
|
|
2025
|
+
|
|
2026
|
+
// Scrolling the dialog or the page would leave a fixed panel behind;
|
|
2027
|
+
// dismiss it instead. Scrolling inside the panel itself is allowed.
|
|
2028
|
+
useEffect(() => {
|
|
2029
|
+
if (view === null || (view.registryOpen !== true && view.listOpen !== true)) return undefined;
|
|
2030
|
+
const onScroll = (event) => {
|
|
2031
|
+
const target = event.target;
|
|
2032
|
+
if (target instanceof Node) {
|
|
2033
|
+
const insidePanel = [versionPanelRef.current, registryPanelRef.current]
|
|
2034
|
+
.some((panel) => panel !== null && panel.contains(target));
|
|
2035
|
+
if (insidePanel) return;
|
|
2036
|
+
}
|
|
2037
|
+
closePopovers();
|
|
2038
|
+
};
|
|
2039
|
+
const onResize = () => closePopovers();
|
|
2040
|
+
document.addEventListener('scroll', onScroll, true);
|
|
2041
|
+
window.addEventListener('resize', onResize);
|
|
2042
|
+
return () => {
|
|
2043
|
+
document.removeEventListener('scroll', onScroll, true);
|
|
2044
|
+
window.removeEventListener('resize', onResize);
|
|
2045
|
+
};
|
|
2046
|
+
}, [view === null ? null : `${String(view.registryOpen)}:${String(view.listOpen)}`, closePopovers]);
|
|
1735
2047
|
|
|
1736
2048
|
// Show pnpm output while an installation runs.
|
|
1737
2049
|
useEffect(() => {
|
|
@@ -1754,9 +2066,22 @@
|
|
|
1754
2066
|
};
|
|
1755
2067
|
}, [remote]);
|
|
1756
2068
|
|
|
2069
|
+
// Escape dismisses the list-header restart confirmation unless a
|
|
2070
|
+
// restart request is already in flight.
|
|
2071
|
+
useEffect(() => {
|
|
2072
|
+
if (!restartAsk) return undefined;
|
|
2073
|
+
const onKeyDown = (event) => {
|
|
2074
|
+
if (event.key !== 'Escape' || restartBusy) return;
|
|
2075
|
+
event.stopPropagation();
|
|
2076
|
+
setRestartAsk(false);
|
|
2077
|
+
};
|
|
2078
|
+
document.addEventListener('keydown', onKeyDown, true);
|
|
2079
|
+
return () => document.removeEventListener('keydown', onKeyDown, true);
|
|
2080
|
+
}, [restartAsk, restartBusy]);
|
|
2081
|
+
|
|
1757
2082
|
const chooseVersion = (version) => {
|
|
1758
2083
|
if (tokenRef.current === null) return;
|
|
1759
|
-
setView((current) => current === null ? current : { ...current, version, listOpen: false, formError: null });
|
|
2084
|
+
setView((current) => current === null ? current : { ...current, version, listOpen: false, listPopover: null, formError: null });
|
|
1760
2085
|
};
|
|
1761
2086
|
|
|
1762
2087
|
const chooseRegistry = (key) => {
|
|
@@ -1766,10 +2091,61 @@
|
|
|
1766
2091
|
if (current === null) return;
|
|
1767
2092
|
const choice = current.choices.find((item) => item.key === key);
|
|
1768
2093
|
if (choice === undefined) return;
|
|
1769
|
-
setView((state) => state === null ? state : { ...state, registryKey: key, registryOpen: false, formError: null });
|
|
2094
|
+
setView((state) => state === null ? state : { ...state, registryKey: key, registryOpen: false, registryPopover: null, formError: null });
|
|
1770
2095
|
loadVersions(token, current.name, choice, current.choices, true);
|
|
1771
2096
|
};
|
|
1772
2097
|
|
|
2098
|
+
/** Star one registry as Power-Up's default and select it for this update. */
|
|
2099
|
+
const setDefaultRegistry = (choice) => {
|
|
2100
|
+
if (choice === null || choice === undefined) return;
|
|
2101
|
+
const current = view;
|
|
2102
|
+
writeDefaultRegistry(choice);
|
|
2103
|
+
setView((state) => state === null ? state : {
|
|
2104
|
+
...state,
|
|
2105
|
+
defaultRegistryKey: choice.key,
|
|
2106
|
+
registryKey: choice.key,
|
|
2107
|
+
});
|
|
2108
|
+
if (current !== null) loadVersions(tokenRef.current, current.name, choice, current.choices, true);
|
|
2109
|
+
};
|
|
2110
|
+
|
|
2111
|
+
/** Open or close the floating version list under the input row. */
|
|
2112
|
+
const toggleVersionList = () => {
|
|
2113
|
+
const current = view;
|
|
2114
|
+
if (current === null || current.phase !== 'choose') return;
|
|
2115
|
+
if (current.listOpen === true) {
|
|
2116
|
+
setView((state) => state === null ? state : { ...state, listOpen: false, listPopover: null });
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
const anchor = versionRef.current;
|
|
2120
|
+
const popover = anchor === null ? null : popoverPlacement(anchor, 280, 260);
|
|
2121
|
+
setView((state) => state === null ? state : {
|
|
2122
|
+
...state,
|
|
2123
|
+
listOpen: true,
|
|
2124
|
+
listPopover: popover,
|
|
2125
|
+
registryOpen: false,
|
|
2126
|
+
registryPopover: null,
|
|
2127
|
+
});
|
|
2128
|
+
};
|
|
2129
|
+
|
|
2130
|
+
/** Open or close the floating registry menu under its trigger. */
|
|
2131
|
+
const toggleRegistry = () => {
|
|
2132
|
+
const current = view;
|
|
2133
|
+
if (current === null || current.phase !== 'choose') return;
|
|
2134
|
+
if (current.registryOpen === true) {
|
|
2135
|
+
setView((state) => state === null ? state : { ...state, registryOpen: false, registryPopover: null });
|
|
2136
|
+
return;
|
|
2137
|
+
}
|
|
2138
|
+
const anchor = registryRef.current;
|
|
2139
|
+
const popover = anchor === null ? null : popoverPlacement(anchor, 280, 220);
|
|
2140
|
+
setView((state) => state === null ? state : {
|
|
2141
|
+
...state,
|
|
2142
|
+
registryOpen: true,
|
|
2143
|
+
registryPopover: popover,
|
|
2144
|
+
listOpen: false,
|
|
2145
|
+
listPopover: null,
|
|
2146
|
+
});
|
|
2147
|
+
};
|
|
2148
|
+
|
|
1773
2149
|
const startUpdate = async () => {
|
|
1774
2150
|
const token = tokenRef.current;
|
|
1775
2151
|
const current = view;
|
|
@@ -1784,7 +2160,29 @@
|
|
|
1784
2160
|
const registry = registryValue(choice);
|
|
1785
2161
|
const spec = `${current.name}@${version}`;
|
|
1786
2162
|
const requestId = newRequestId();
|
|
1787
|
-
patch(token, { phase: 'working', step: 'removing', error: null, formError: null, log: '', logLabel: spec });
|
|
2163
|
+
patch(token, { phase: 'working', step: current.self === true ? 'installing' : 'removing', error: null, formError: null, log: '', logLabel: spec });
|
|
2164
|
+
if (current.self === true) {
|
|
2165
|
+
// Power-Up cannot reinstall itself from its own client: removing
|
|
2166
|
+
// the bundle unloads this dialog. The Host route receives the
|
|
2167
|
+
// request, answers, then hands remove+inspect+install to the
|
|
2168
|
+
// built-in manager service, which keeps running in the Host
|
|
2169
|
+
// process after this plugin unloads.
|
|
2170
|
+
recordPending({ name: MODULE_ID, version, reason: 'self-update' });
|
|
2171
|
+
try {
|
|
2172
|
+
const response = await fetch(SELF_UPDATE_API, {
|
|
2173
|
+
method: 'POST',
|
|
2174
|
+
headers: { 'content-type': 'application/json' },
|
|
2175
|
+
body: JSON.stringify({ version, registry }),
|
|
2176
|
+
});
|
|
2177
|
+
const data = await response.json().catch(() => null);
|
|
2178
|
+
if (!response.ok || data?.ok !== true) throw new Error(data?.error ?? `HTTP ${response.status}`);
|
|
2179
|
+
patch(token, { phase: 'self-updating', step: null });
|
|
2180
|
+
} catch (error) {
|
|
2181
|
+
removePending(MODULE_ID);
|
|
2182
|
+
patch(token, { phase: 'error', step: null, error: errorText(error) || t('update.unknown') });
|
|
2183
|
+
}
|
|
2184
|
+
return;
|
|
2185
|
+
}
|
|
1788
2186
|
try {
|
|
1789
2187
|
// The update flow removes the old version first: the Host's
|
|
1790
2188
|
// inspection refuses a package that is still installed, so the
|
|
@@ -1807,11 +2205,19 @@
|
|
|
1807
2205
|
const value = installed.value;
|
|
1808
2206
|
if (value.error !== undefined || value.application === 'failed') throw new Error(operationReason(value, t));
|
|
1809
2207
|
if (value.application === 'cancelled') throw new Error(t('update.cancelled'));
|
|
2208
|
+
const restartRequired = value.application === 'restart-required';
|
|
2209
|
+
if (restartRequired) {
|
|
2210
|
+
recordPending({
|
|
2211
|
+
name: current.name,
|
|
2212
|
+
version: value.version ?? version,
|
|
2213
|
+
reason: value.restartReason ?? '',
|
|
2214
|
+
});
|
|
2215
|
+
}
|
|
1810
2216
|
patch(token, {
|
|
1811
2217
|
phase: 'enable',
|
|
1812
2218
|
step: null,
|
|
1813
2219
|
installedVersion: value.version ?? version,
|
|
1814
|
-
restartRequired
|
|
2220
|
+
restartRequired,
|
|
1815
2221
|
enabling: false,
|
|
1816
2222
|
});
|
|
1817
2223
|
} catch (error) {
|
|
@@ -1833,6 +2239,35 @@
|
|
|
1833
2239
|
}
|
|
1834
2240
|
};
|
|
1835
2241
|
|
|
2242
|
+
/** Open the list-header restart confirmation and read the capability. */
|
|
2243
|
+
const openRestart = () => {
|
|
2244
|
+
setRestartAsk(true);
|
|
2245
|
+
setRestartCapability(null);
|
|
2246
|
+
setRestartError(null);
|
|
2247
|
+
setRestartBusy(false);
|
|
2248
|
+
void readRestartCapability().then((capability) => setRestartCapability(capability));
|
|
2249
|
+
};
|
|
2250
|
+
openRestartRef.current = openRestart;
|
|
2251
|
+
|
|
2252
|
+
/** Confirm the list-header restart. */
|
|
2253
|
+
const confirmRestart = async () => {
|
|
2254
|
+
if (restartBusy) return;
|
|
2255
|
+
setRestartBusy(true);
|
|
2256
|
+
setRestartError(null);
|
|
2257
|
+
try {
|
|
2258
|
+
const response = await fetch(UPDATE_API, {
|
|
2259
|
+
method: 'POST',
|
|
2260
|
+
headers: { 'content-type': 'application/json' },
|
|
2261
|
+
body: JSON.stringify({ action: 'restart' }),
|
|
2262
|
+
});
|
|
2263
|
+
const data = await response.json().catch(() => null);
|
|
2264
|
+
if (!response.ok || data?.ok !== true) throw new Error(data?.error ?? `HTTP ${response.status}`);
|
|
2265
|
+
} catch (error) {
|
|
2266
|
+
setRestartBusy(false);
|
|
2267
|
+
setRestartError(errorText(error) || tRef.current('update.unknown'));
|
|
2268
|
+
}
|
|
2269
|
+
};
|
|
2270
|
+
|
|
1836
2271
|
const enableNow = async () => {
|
|
1837
2272
|
const token = tokenRef.current;
|
|
1838
2273
|
const current = view;
|
|
@@ -1844,6 +2279,13 @@
|
|
|
1844
2279
|
if (tokenRef.current !== token) return;
|
|
1845
2280
|
if (!result.ok) throw new Error(result.error?.message ?? t('update.unknown'));
|
|
1846
2281
|
if (result.value.error !== undefined) throw new Error(operationReason(result.value, t));
|
|
2282
|
+
if (result.value.application === 'restart-required' || current.restartRequired === true) {
|
|
2283
|
+
recordPending({
|
|
2284
|
+
name: current.name,
|
|
2285
|
+
version: current.installedVersion ?? '',
|
|
2286
|
+
reason: result.value.restartReason ?? '',
|
|
2287
|
+
});
|
|
2288
|
+
}
|
|
1847
2289
|
const restart = await readRestartCapability();
|
|
1848
2290
|
patch(token, { phase: 'restart', enabling: false, restart });
|
|
1849
2291
|
} catch (error) {
|
|
@@ -1882,9 +2324,64 @@
|
|
|
1882
2324
|
}
|
|
1883
2325
|
};
|
|
1884
2326
|
|
|
1885
|
-
if (view === null) return null;
|
|
1886
2327
|
const t = (key, params) => translateCurrent(key, params);
|
|
1887
|
-
|
|
2328
|
+
|
|
2329
|
+
/** One tinted notice: the success/warn/error states share this chrome. */
|
|
2330
|
+
const notice = (tone, icon, title, message) => h('div', { className: `dsh-powerup-update-notice ${tone}` },
|
|
2331
|
+
icon,
|
|
2332
|
+
h('div', { className: 'dsh-powerup-update-notice-copy' },
|
|
2333
|
+
title === null || title === undefined ? null : h('span', { className: 'dsh-powerup-update-notice-title' }, title),
|
|
2334
|
+
message === null || message === undefined ? null : h('span', null, message),
|
|
2335
|
+
),
|
|
2336
|
+
);
|
|
2337
|
+
|
|
2338
|
+
const restartPrompt = restartAsk ? h('div', {
|
|
2339
|
+
className: 'dsh-powerup-update-mask',
|
|
2340
|
+
onMouseDown: (event) => {
|
|
2341
|
+
if (event.target === event.currentTarget && !restartBusy) setRestartAsk(false);
|
|
2342
|
+
},
|
|
2343
|
+
},
|
|
2344
|
+
h('div', {
|
|
2345
|
+
className: 'dsh-powerup-update-dialog',
|
|
2346
|
+
style: { width: 'min(420px,100%)' },
|
|
2347
|
+
role: 'dialog',
|
|
2348
|
+
'aria-modal': 'true',
|
|
2349
|
+
'aria-labelledby': 'dsh-powerup-restart-title',
|
|
2350
|
+
},
|
|
2351
|
+
h('div', { className: 'dsh-powerup-update-head' },
|
|
2352
|
+
h('div', { className: 'dsh-powerup-update-heading' },
|
|
2353
|
+
h('h2', { id: 'dsh-powerup-restart-title' }, t('restart.title')),
|
|
2354
|
+
h('p', null, t('restart.pending', { count: pending.length })),
|
|
2355
|
+
),
|
|
2356
|
+
restartBusy ? null : h('button', { type: 'button', className: 'dsh-powerup-update-close', 'aria-label': t('update.close'), onClick: () => setRestartAsk(false) }, '×'),
|
|
2357
|
+
),
|
|
2358
|
+
h('div', { className: 'dsh-powerup-update-body' },
|
|
2359
|
+
h('p', { className: 'dsh-powerup-update-note' }, t('restart.desc')),
|
|
2360
|
+
restartCapability !== null && restartCapability.supported !== true
|
|
2361
|
+
? notice('warn', h(WarnIcon, { size: 14 }), null, t('update.restartUnsupported', { reason: restartCapability.reason ?? '' }))
|
|
2362
|
+
: null,
|
|
2363
|
+
restartError === null ? null : notice('error', h(WarnIcon, { size: 14 }), null, restartError),
|
|
2364
|
+
restartBusy
|
|
2365
|
+
? h('div', { className: 'dsh-powerup-update-status' },
|
|
2366
|
+
h('span', { className: 'dsh-powerup-update-spinner', 'aria-hidden': 'true' }),
|
|
2367
|
+
t('update.restarting'),
|
|
2368
|
+
)
|
|
2369
|
+
: null,
|
|
2370
|
+
),
|
|
2371
|
+
h('div', { className: 'dsh-powerup-update-foot' },
|
|
2372
|
+
h('button', { type: 'button', className: 'dsh-powerup-update-ghost', disabled: restartBusy, onClick: () => setRestartAsk(false) }, t('restart.cancel')),
|
|
2373
|
+
h('button', {
|
|
2374
|
+
type: 'button',
|
|
2375
|
+
className: 'dsh-powerup-update-primary',
|
|
2376
|
+
disabled: restartBusy || restartCapability?.supported !== true,
|
|
2377
|
+
onClick: confirmRestart,
|
|
2378
|
+
}, restartBusy ? t('update.restarting') : t('restart.confirm')),
|
|
2379
|
+
),
|
|
2380
|
+
),
|
|
2381
|
+
) : null;
|
|
2382
|
+
|
|
2383
|
+
if (view === null) return restartPrompt;
|
|
2384
|
+
const busy = view.phase === 'working' || view.phase === 'self-updating' || view.phase === 'restarting' || view.enabling === true;
|
|
1888
2385
|
const canClose = !busy;
|
|
1889
2386
|
const title = view.title === undefined || view.title === '' ? view.name : view.title;
|
|
1890
2387
|
const showVersions = view.phase === 'choose' && view.listOpen === true;
|
|
@@ -1915,23 +2412,69 @@
|
|
|
1915
2412
|
supported ? h('button', { key: 'now', type: 'button', className: 'dsh-powerup-update-primary', disabled: view.restarting === true, onClick: restartNow }, view.restarting === true ? t('update.restarting') : t('update.restartNow')) : null,
|
|
1916
2413
|
].filter(Boolean);
|
|
1917
2414
|
}
|
|
1918
|
-
if (view.phase === 'restarting' || view.phase === 'working' || view.phase === 'loading') return [];
|
|
2415
|
+
if (view.phase === 'restarting' || view.phase === 'working' || view.phase === 'self-updating' || view.phase === 'loading') return [];
|
|
1919
2416
|
return [
|
|
1920
2417
|
h('button', { key: 'done', type: 'button', className: 'dsh-powerup-update-primary', onClick: close }, view.phase === 'done' ? t('update.doneButton') : t('update.close')),
|
|
1921
2418
|
];
|
|
1922
2419
|
})();
|
|
1923
2420
|
|
|
1924
|
-
/** One tinted notice: the success/warn/error states share this chrome. */
|
|
1925
|
-
const notice = (tone, icon, title, message) => h('div', { className: `dsh-powerup-update-notice ${tone}` },
|
|
1926
|
-
icon,
|
|
1927
|
-
h('div', { className: 'dsh-powerup-update-notice-copy' },
|
|
1928
|
-
title === null || title === undefined ? null : h('span', { className: 'dsh-powerup-update-notice-title' }, title),
|
|
1929
|
-
message === null || message === undefined ? null : h('span', null, message),
|
|
1930
|
-
),
|
|
1931
|
-
);
|
|
1932
|
-
|
|
1933
2421
|
const currentChoice = view.choices.find((item) => item.key === view.registryKey) ?? view.choices[0];
|
|
1934
2422
|
|
|
2423
|
+
/** Rows of the floating version list, including its loading/error states. */
|
|
2424
|
+
const versionRows = (() => {
|
|
2425
|
+
if (view.versionsState === 'loading') {
|
|
2426
|
+
return [h('div', { key: 'state', className: 'dsh-powerup-update-list-state' },
|
|
2427
|
+
h('div', { className: 'dsh-powerup-update-status' },
|
|
2428
|
+
h('span', { className: 'dsh-powerup-update-spinner', 'aria-hidden': 'true' }),
|
|
2429
|
+
t('update.loadingVersions'),
|
|
2430
|
+
),
|
|
2431
|
+
)];
|
|
2432
|
+
}
|
|
2433
|
+
if (view.versionsState === 'error') {
|
|
2434
|
+
return [h('div', { key: 'state', className: 'dsh-powerup-update-list-state' },
|
|
2435
|
+
notice('error', h(WarnIcon, { size: 14 }), null, t('update.versionsFailed', { message: view.versionsError ?? '' })),
|
|
2436
|
+
h('button', {
|
|
2437
|
+
type: 'button',
|
|
2438
|
+
className: 'dsh-powerup-update-ghost',
|
|
2439
|
+
onClick: () => loadVersions(tokenRef.current, view.name, currentChoice, view.choices, false),
|
|
2440
|
+
}, t('update.retry')),
|
|
2441
|
+
)];
|
|
2442
|
+
}
|
|
2443
|
+
if ((view.versions ?? []).length === 0) {
|
|
2444
|
+
return [h('div', { key: 'state', className: 'dsh-powerup-update-list-state' },
|
|
2445
|
+
h('p', { className: 'dsh-powerup-update-note' }, t('update.versionsEmpty')),
|
|
2446
|
+
)];
|
|
2447
|
+
}
|
|
2448
|
+
return [
|
|
2449
|
+
h('div', { key: 'head', className: 'dsh-powerup-update-list-head' },
|
|
2450
|
+
h('span', null, t('update.versionsCount', { count: view.versions.length })),
|
|
2451
|
+
view.versionsRegistry === null || view.versionsRegistry === undefined
|
|
2452
|
+
? null
|
|
2453
|
+
: h('span', { title: view.versionsRegistry.hint }, view.versionsRegistry.hint),
|
|
2454
|
+
),
|
|
2455
|
+
...view.versions.map((version) => {
|
|
2456
|
+
const tagKind = versionTagKind(version, view.currentVersion, view.latest);
|
|
2457
|
+
return h('button', {
|
|
2458
|
+
key: version,
|
|
2459
|
+
type: 'button',
|
|
2460
|
+
role: 'option',
|
|
2461
|
+
className: 'dsh-powerup-update-version',
|
|
2462
|
+
'aria-selected': version === view.version ? 'true' : 'false',
|
|
2463
|
+
'data-current': version === view.currentVersion ? '' : undefined,
|
|
2464
|
+
onClick: () => chooseVersion(version),
|
|
2465
|
+
},
|
|
2466
|
+
h('span', null, version),
|
|
2467
|
+
h('span', { className: 'dsh-powerup-update-trailing' },
|
|
2468
|
+
tagKind === null ? null : h('span', {
|
|
2469
|
+
className: `dsh-powerup-update-tag${tagKind === 'latest' ? '' : ' is-current'}`,
|
|
2470
|
+
}, t(tagKind === 'current-latest' ? 'update.currentAndLatest' : tagKind === 'current' ? 'update.currentTag' : 'update.latest')),
|
|
2471
|
+
version === view.version ? h('span', { className: 'dsh-powerup-update-check' }, h(CheckIcon, { size: 13 })) : null,
|
|
2472
|
+
),
|
|
2473
|
+
);
|
|
2474
|
+
}),
|
|
2475
|
+
];
|
|
2476
|
+
})();
|
|
2477
|
+
|
|
1935
2478
|
const body = (() => {
|
|
1936
2479
|
if (view.phase === 'loading') {
|
|
1937
2480
|
return h('div', { className: 'dsh-powerup-update-status' },
|
|
@@ -1939,66 +2482,20 @@
|
|
|
1939
2482
|
t('update.loading'),
|
|
1940
2483
|
);
|
|
1941
2484
|
}
|
|
1942
|
-
if (view.phase === 'self') {
|
|
1943
|
-
return
|
|
2485
|
+
if (view.phase === 'self-updating') {
|
|
2486
|
+
return [
|
|
2487
|
+
h('div', { key: 'status', className: 'dsh-powerup-update-status' },
|
|
2488
|
+
h('span', { className: 'dsh-powerup-update-spinner', 'aria-hidden': 'true' }),
|
|
2489
|
+
t('update.selfUpdating'),
|
|
2490
|
+
),
|
|
2491
|
+
h('p', { key: 'note', className: 'dsh-powerup-update-note' }, t('update.selfRefresh')),
|
|
2492
|
+
h('span', { key: 'name', className: 'dsh-powerup-update-name' }, view.name),
|
|
2493
|
+
];
|
|
1944
2494
|
}
|
|
1945
2495
|
if (view.phase === 'blocked') {
|
|
1946
2496
|
return notice('error', h(WarnIcon, { size: 15 }), t('update.unsupportedTitle'), view.error ?? t('update.notRemovable'));
|
|
1947
2497
|
}
|
|
1948
2498
|
if (view.phase === 'choose') {
|
|
1949
|
-
const rows = (() => {
|
|
1950
|
-
if (view.versionsState === 'loading') {
|
|
1951
|
-
return [h('div', { key: 'state', className: 'dsh-powerup-update-list-state' },
|
|
1952
|
-
h('div', { className: 'dsh-powerup-update-status' },
|
|
1953
|
-
h('span', { className: 'dsh-powerup-update-spinner', 'aria-hidden': 'true' }),
|
|
1954
|
-
t('update.loadingVersions'),
|
|
1955
|
-
),
|
|
1956
|
-
)];
|
|
1957
|
-
}
|
|
1958
|
-
if (view.versionsState === 'error') {
|
|
1959
|
-
return [h('div', { key: 'state', className: 'dsh-powerup-update-list-state' },
|
|
1960
|
-
notice('error', h(WarnIcon, { size: 14 }), null, t('update.versionsFailed', { message: view.versionsError ?? '' })),
|
|
1961
|
-
h('button', {
|
|
1962
|
-
type: 'button',
|
|
1963
|
-
className: 'dsh-powerup-update-ghost',
|
|
1964
|
-
onClick: () => loadVersions(tokenRef.current, view.name, currentChoice, view.choices, false),
|
|
1965
|
-
}, t('update.retry')),
|
|
1966
|
-
)];
|
|
1967
|
-
}
|
|
1968
|
-
if ((view.versions ?? []).length === 0) {
|
|
1969
|
-
return [h('div', { key: 'state', className: 'dsh-powerup-update-list-state' },
|
|
1970
|
-
h('p', { className: 'dsh-powerup-update-note' }, t('update.versionsEmpty')),
|
|
1971
|
-
)];
|
|
1972
|
-
}
|
|
1973
|
-
return [
|
|
1974
|
-
h('div', { key: 'head', className: 'dsh-powerup-update-list-head' },
|
|
1975
|
-
h('span', null, t('update.versionsCount', { count: view.versions.length })),
|
|
1976
|
-
view.versionsRegistry === null || view.versionsRegistry === undefined
|
|
1977
|
-
? null
|
|
1978
|
-
: h('span', { title: view.versionsRegistry.hint }, view.versionsRegistry.hint),
|
|
1979
|
-
),
|
|
1980
|
-
...view.versions.map((version) => {
|
|
1981
|
-
const tagKind = versionTagKind(version, view.currentVersion, view.latest);
|
|
1982
|
-
return h('button', {
|
|
1983
|
-
key: version,
|
|
1984
|
-
type: 'button',
|
|
1985
|
-
role: 'option',
|
|
1986
|
-
className: 'dsh-powerup-update-version',
|
|
1987
|
-
'aria-selected': version === view.version ? 'true' : 'false',
|
|
1988
|
-
'data-current': version === view.currentVersion ? '' : undefined,
|
|
1989
|
-
onClick: () => chooseVersion(version),
|
|
1990
|
-
},
|
|
1991
|
-
h('span', null, version),
|
|
1992
|
-
h('span', { className: 'dsh-powerup-update-trailing' },
|
|
1993
|
-
tagKind === null ? null : h('span', {
|
|
1994
|
-
className: `dsh-powerup-update-tag${tagKind === 'latest' ? '' : ' is-current'}`,
|
|
1995
|
-
}, t(tagKind === 'current-latest' ? 'update.currentAndLatest' : tagKind === 'current' ? 'update.currentTag' : 'update.latest')),
|
|
1996
|
-
version === view.version ? h('span', { className: 'dsh-powerup-update-check' }, h(CheckIcon, { size: 13 })) : null,
|
|
1997
|
-
),
|
|
1998
|
-
);
|
|
1999
|
-
}),
|
|
2000
|
-
];
|
|
2001
|
-
})();
|
|
2002
2499
|
return [
|
|
2003
2500
|
h('div', { key: 'summary', className: 'dsh-powerup-update-summary' },
|
|
2004
2501
|
h('div', { className: 'dsh-powerup-update-now' },
|
|
@@ -2008,9 +2505,12 @@
|
|
|
2008
2505
|
),
|
|
2009
2506
|
h('span', { className: 'dsh-powerup-update-name' }, view.name),
|
|
2010
2507
|
),
|
|
2508
|
+
view.self === true
|
|
2509
|
+
? notice('warn', h(WarnIcon, { size: 14 }), null, t('update.selfUpdateWarning'))
|
|
2510
|
+
: null,
|
|
2011
2511
|
h('div', { key: 'version', className: 'dsh-powerup-update-field' },
|
|
2012
2512
|
h('label', { className: 'dsh-powerup-update-label', htmlFor: 'dsh-powerup-update-version' }, t('update.versionLabel')),
|
|
2013
|
-
h('div', { className: 'dsh-powerup-update-row' },
|
|
2513
|
+
h('div', { className: 'dsh-powerup-update-row', ref: versionRef },
|
|
2014
2514
|
h('input', {
|
|
2015
2515
|
id: 'dsh-powerup-update-version',
|
|
2016
2516
|
ref: inputRef,
|
|
@@ -2030,7 +2530,7 @@
|
|
|
2030
2530
|
className: 'dsh-powerup-update-toggle',
|
|
2031
2531
|
'aria-expanded': showVersions ? 'true' : 'false',
|
|
2032
2532
|
'aria-controls': 'dsh-powerup-update-versions',
|
|
2033
|
-
onClick:
|
|
2533
|
+
onClick: toggleVersionList,
|
|
2034
2534
|
},
|
|
2035
2535
|
showVersions ? t('update.hideVersions') : t('update.chooseVersion'),
|
|
2036
2536
|
h('span', { className: 'chevron' }, h(ChevronDownIcon, { size: 12 })),
|
|
@@ -2040,7 +2540,6 @@
|
|
|
2040
2540
|
? null
|
|
2041
2541
|
: notice('error', h(WarnIcon, { size: 14 }), null, view.formError),
|
|
2042
2542
|
),
|
|
2043
|
-
showVersions ? h('div', { key: 'versions', id: 'dsh-powerup-update-versions', className: 'dsh-powerup-update-list', role: 'listbox', 'aria-label': t('update.versionLabel') }, rows) : null,
|
|
2044
2543
|
h('div', { key: 'registry', className: 'dsh-powerup-update-field' },
|
|
2045
2544
|
h('span', { className: 'dsh-powerup-update-label' }, t('update.registry')),
|
|
2046
2545
|
h('div', { className: 'dsh-powerup-update-select', ref: registryRef },
|
|
@@ -2049,27 +2548,12 @@
|
|
|
2049
2548
|
className: 'dsh-powerup-update-select-trigger',
|
|
2050
2549
|
'aria-haspopup': 'listbox',
|
|
2051
2550
|
'aria-expanded': view.registryOpen === true ? 'true' : 'false',
|
|
2052
|
-
onClick:
|
|
2551
|
+
onClick: toggleRegistry,
|
|
2053
2552
|
},
|
|
2054
2553
|
h('span', { className: 'dsh-powerup-update-select-value' }, currentChoice?.label ?? ''),
|
|
2055
2554
|
h('span', { className: 'dsh-powerup-update-select-host' }, currentChoice?.hint ?? ''),
|
|
2056
2555
|
h('span', { className: 'chevron' }, h(ChevronDownIcon, { size: 12 })),
|
|
2057
2556
|
),
|
|
2058
|
-
view.registryOpen === true ? h('div', { className: 'dsh-powerup-update-select-menu', role: 'listbox', 'aria-label': t('update.registry') },
|
|
2059
|
-
view.choices.map((choice) => h('button', {
|
|
2060
|
-
key: choice.key,
|
|
2061
|
-
type: 'button',
|
|
2062
|
-
role: 'option',
|
|
2063
|
-
className: 'dsh-powerup-update-option',
|
|
2064
|
-
'aria-selected': choice.key === view.registryKey ? 'true' : 'false',
|
|
2065
|
-
onClick: () => chooseRegistry(choice.key),
|
|
2066
|
-
},
|
|
2067
|
-
h('span', { className: 'dsh-powerup-update-option-copy' },
|
|
2068
|
-
h('span', { className: 'dsh-powerup-update-option-title' }, choice.label),
|
|
2069
|
-
h('span', { className: 'dsh-powerup-update-option-host' }, choice.hint),
|
|
2070
|
-
),
|
|
2071
|
-
choice.key === view.registryKey ? h('span', { className: 'dsh-powerup-update-check' }, h(CheckIcon, { size: 13 })) : null,
|
|
2072
|
-
))) : null,
|
|
2073
2557
|
),
|
|
2074
2558
|
view.versionsRegistry === null || view.versionsRegistry === undefined
|
|
2075
2559
|
? null
|
|
@@ -2152,28 +2636,86 @@
|
|
|
2152
2636
|
return null;
|
|
2153
2637
|
})();
|
|
2154
2638
|
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2639
|
+
const fallbackPopover = { left: 24, top: 120, width: 320, maxHeight: 260 };
|
|
2640
|
+
|
|
2641
|
+
return h(React.Fragment, null,
|
|
2642
|
+
h('div', {
|
|
2643
|
+
className: 'dsh-powerup-update-mask',
|
|
2644
|
+
onMouseDown: (event) => {
|
|
2645
|
+
if (event.target === event.currentTarget) close();
|
|
2646
|
+
},
|
|
2159
2647
|
},
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2648
|
+
h('div', {
|
|
2649
|
+
className: 'dsh-powerup-update-dialog',
|
|
2650
|
+
role: 'dialog',
|
|
2651
|
+
'aria-modal': 'true',
|
|
2652
|
+
'aria-labelledby': 'dsh-powerup-update-title',
|
|
2653
|
+
},
|
|
2654
|
+
h('div', { className: 'dsh-powerup-update-head' },
|
|
2655
|
+
h('div', { className: 'dsh-powerup-update-heading' },
|
|
2656
|
+
h('h2', { id: 'dsh-powerup-update-title' }, t('update.title')),
|
|
2657
|
+
h('p', { title }, title),
|
|
2658
|
+
),
|
|
2659
|
+
canClose ? h('button', { type: 'button', className: 'dsh-powerup-update-close', 'aria-label': t('update.close'), onClick: close }, '×') : null,
|
|
2171
2660
|
),
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2661
|
+
h('div', { className: 'dsh-powerup-update-body' }, body),
|
|
2662
|
+
footer.length === 0 ? null : h('div', { className: 'dsh-powerup-update-foot' }, footer),
|
|
2663
|
+
),
|
|
2664
|
+
),
|
|
2665
|
+
view.listOpen === true
|
|
2666
|
+
? h('div', {
|
|
2667
|
+
id: 'dsh-powerup-update-versions',
|
|
2668
|
+
ref: versionPanelRef,
|
|
2669
|
+
className: 'dsh-powerup-update-popover dsh-powerup-update-list',
|
|
2670
|
+
role: 'listbox',
|
|
2671
|
+
'aria-label': t('update.versionLabel'),
|
|
2672
|
+
style: view.listPopover ?? fallbackPopover,
|
|
2673
|
+
}, versionRows)
|
|
2674
|
+
: null,
|
|
2675
|
+
view.registryOpen === true
|
|
2676
|
+
? h('div', {
|
|
2677
|
+
ref: registryPanelRef,
|
|
2678
|
+
className: 'dsh-powerup-update-popover dsh-powerup-update-select-menu',
|
|
2679
|
+
role: 'listbox',
|
|
2680
|
+
'aria-label': t('update.registry'),
|
|
2681
|
+
style: view.registryPopover ?? fallbackPopover,
|
|
2682
|
+
}, view.choices.map((choice) => {
|
|
2683
|
+
const isDefault = choice.key === view.defaultRegistryKey;
|
|
2684
|
+
const isChosen = choice.key === view.registryKey;
|
|
2685
|
+
return h('div', {
|
|
2686
|
+
key: choice.key,
|
|
2687
|
+
role: 'option',
|
|
2688
|
+
tabIndex: 0,
|
|
2689
|
+
className: 'dsh-powerup-update-option',
|
|
2690
|
+
'aria-selected': isChosen ? 'true' : 'false',
|
|
2691
|
+
onClick: () => chooseRegistry(choice.key),
|
|
2692
|
+
onKeyDown: (event) => {
|
|
2693
|
+
if (event.key !== 'Enter' && event.key !== ' ') return;
|
|
2694
|
+
event.preventDefault();
|
|
2695
|
+
chooseRegistry(choice.key);
|
|
2696
|
+
},
|
|
2697
|
+
},
|
|
2698
|
+
h('span', { className: 'dsh-powerup-update-option-copy' },
|
|
2699
|
+
h('span', { className: 'dsh-powerup-update-option-title' }, choice.label),
|
|
2700
|
+
h('span', { className: 'dsh-powerup-update-option-host' }, choice.hint),
|
|
2701
|
+
),
|
|
2702
|
+
isDefault
|
|
2703
|
+
? h('span', { className: 'dsh-powerup-update-default-tag', title: t('update.defaultHint') }, t('update.defaultTag'))
|
|
2704
|
+
: h('button', {
|
|
2705
|
+
type: 'button',
|
|
2706
|
+
className: 'dsh-powerup-update-default',
|
|
2707
|
+
title: t('update.setDefault'),
|
|
2708
|
+
'aria-label': `${t('update.setDefault')} ${choice.label}`,
|
|
2709
|
+
onClick: (event) => {
|
|
2710
|
+
event.preventDefault();
|
|
2711
|
+
event.stopPropagation();
|
|
2712
|
+
setDefaultRegistry(choice);
|
|
2713
|
+
},
|
|
2714
|
+
}, h(StarIcon, { size: 12 })),
|
|
2715
|
+
isChosen ? h('span', { className: 'dsh-powerup-update-check' }, h(CheckIcon, { size: 13 })) : null,
|
|
2716
|
+
);
|
|
2717
|
+
}))
|
|
2718
|
+
: null,
|
|
2177
2719
|
);
|
|
2178
2720
|
}
|
|
2179
2721
|
|
|
@@ -2547,7 +3089,9 @@
|
|
|
2547
3089
|
PowerUpOverlay,
|
|
2548
3090
|
PluginUpdateOverlay,
|
|
2549
3091
|
ensureUpdateButtons,
|
|
3092
|
+
ensureRestartBar,
|
|
2550
3093
|
updateButtonLabel,
|
|
3094
|
+
popoverPlacement,
|
|
2551
3095
|
rowKeyParts,
|
|
2552
3096
|
resolveForkSeq,
|
|
2553
3097
|
markdownQuote,
|
|
@@ -2559,6 +3103,10 @@
|
|
|
2559
3103
|
compareVersionsDesc,
|
|
2560
3104
|
buildRegistryChoices,
|
|
2561
3105
|
pickRegistryChoice,
|
|
3106
|
+
matchStoredChoice,
|
|
3107
|
+
readDefaultRegistry,
|
|
3108
|
+
writeDefaultRegistry,
|
|
3109
|
+
readPendingRestarts,
|
|
2562
3110
|
lookupRegistries,
|
|
2563
3111
|
fetchRegistryVersions,
|
|
2564
3112
|
registryHost,
|