@celestia-island/hikari 0.55.105 → 0.55.106
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/package.json +1 -1
- package/src/components/HkConnectionStatus.tsx +6 -1
- package/src/components/HkStatusBar.test.tsx +141 -0
- package/src/components/HkStatusBar.tsx +49 -2
- package/src/i18n/locales/ar/components.json +1 -0
- package/src/i18n/locales/de/components.json +1 -0
- package/src/i18n/locales/en/components.json +1 -0
- package/src/i18n/locales/es/components.json +1 -0
- package/src/i18n/locales/fr/components.json +1 -0
- package/src/i18n/locales/ja/components.json +1 -0
- package/src/i18n/locales/ko/components.json +1 -0
- package/src/i18n/locales/pt/components.json +1 -0
- package/src/i18n/locales/ru/components.json +1 -0
- package/src/i18n/locales/zh-Hans/components.json +1 -0
- package/src/i18n/locales/zh-Hant/components.json +1 -0
package/package.json
CHANGED
|
@@ -124,7 +124,12 @@ export const HkConnectionStatus = defineComponent({
|
|
|
124
124
|
attemptNumber={probe.value.attemptNumber}
|
|
125
125
|
countdown={probe.value.countdown}
|
|
126
126
|
compact={props.compact}
|
|
127
|
-
|
|
127
|
+
>
|
|
128
|
+
{/* Host-appended popover actions (e.g. a manual page
|
|
129
|
+
refresh) ride the same actions row as the built-in
|
|
130
|
+
retry button. */}
|
|
131
|
+
{{ actions: () => slots.actions?.() }}
|
|
132
|
+
</HkStatusBar>
|
|
128
133
|
{bs && (
|
|
129
134
|
<span class="s-status-bar-tag" style={{ cursor: "default" }}>
|
|
130
135
|
<span
|
|
@@ -322,4 +322,145 @@ describe("HkStatusBar", () => {
|
|
|
322
322
|
vi.useRealTimers();
|
|
323
323
|
}
|
|
324
324
|
});
|
|
325
|
+
|
|
326
|
+
it("shows a working reconnect button in the popover while disconnected (hint text retired)", async () => {
|
|
327
|
+
const onRetry = vi.fn();
|
|
328
|
+
const container = mountBar({
|
|
329
|
+
version: "1.2.3",
|
|
330
|
+
connectionStatus: "disconnected",
|
|
331
|
+
connectionInfo: INFO,
|
|
332
|
+
onRetry,
|
|
333
|
+
});
|
|
334
|
+
await nextTick();
|
|
335
|
+
container
|
|
336
|
+
.querySelector<HTMLElement>(".s-status-bar-tag")!
|
|
337
|
+
.dispatchEvent(new MouseEvent("mouseenter"));
|
|
338
|
+
await nextTick();
|
|
339
|
+
const panel = document.body.querySelector<HTMLElement>(".hk-popover-panel")!;
|
|
340
|
+
// The popover body teleports to <body>: the old italic "click to
|
|
341
|
+
// retry" hint pointed at an element whose clicks never reached the
|
|
342
|
+
// tag handler. The actions row carries a REAL button instead, and
|
|
343
|
+
// the misleading hint is gone whenever a retry is wired.
|
|
344
|
+
const row = panel.querySelector<HTMLElement>("[data-status-actions]")!;
|
|
345
|
+
expect(row, "actions row renders").toBeTruthy();
|
|
346
|
+
const button = row.querySelector<HTMLButtonElement>("button")!;
|
|
347
|
+
expect(button.textContent).toContain("Reconnect now");
|
|
348
|
+
expect(panel.textContent).not.toContain("Click to retry");
|
|
349
|
+
button.click();
|
|
350
|
+
expect(onRetry, "popover button click retries").toHaveBeenCalledTimes(1);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("keeps the fallback hint when no onRetry is wired", async () => {
|
|
354
|
+
const container = mountBar({
|
|
355
|
+
version: "1.2.3",
|
|
356
|
+
connectionStatus: "disconnected",
|
|
357
|
+
connectionInfo: INFO,
|
|
358
|
+
});
|
|
359
|
+
await nextTick();
|
|
360
|
+
container
|
|
361
|
+
.querySelector<HTMLElement>(".s-status-bar-tag")!
|
|
362
|
+
.dispatchEvent(new MouseEvent("mouseenter"));
|
|
363
|
+
await nextTick();
|
|
364
|
+
const panel = document.body.querySelector<HTMLElement>(".hk-popover-panel")!;
|
|
365
|
+
expect(panel.querySelector("[data-status-actions]")).toBeNull();
|
|
366
|
+
expect(panel.textContent).toContain("Click to retry");
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it("renders host actions through the actions slot, connected or not", async () => {
|
|
370
|
+
for (const connectionStatus of ["connected", "disconnected"] as const) {
|
|
371
|
+
document.body.innerHTML = "";
|
|
372
|
+
const onRefresh = vi.fn();
|
|
373
|
+
const container = document.createElement("div");
|
|
374
|
+
document.body.appendChild(container);
|
|
375
|
+
const app = createApp({
|
|
376
|
+
render: () =>
|
|
377
|
+
h(HkStatusBar, {
|
|
378
|
+
version: "1.2.3",
|
|
379
|
+
connectionStatus,
|
|
380
|
+
connectionInfo: INFO,
|
|
381
|
+
onRetry: () => {},
|
|
382
|
+
}, {
|
|
383
|
+
actions: () => h("button", { class: "host-refresh", onClick: onRefresh }, "Refresh page"),
|
|
384
|
+
}),
|
|
385
|
+
});
|
|
386
|
+
app.mount(container);
|
|
387
|
+
mounts.push({ app, container });
|
|
388
|
+
await nextTick();
|
|
389
|
+
container
|
|
390
|
+
.querySelector<HTMLElement>(".s-status-bar-tag")!
|
|
391
|
+
.dispatchEvent(new MouseEvent("mouseenter"));
|
|
392
|
+
await nextTick();
|
|
393
|
+
const panel = document.body.querySelector<HTMLElement>(".hk-popover-panel")!;
|
|
394
|
+
const host = panel.querySelector<HTMLButtonElement>(".host-refresh")!;
|
|
395
|
+
expect(host, `host action renders (${connectionStatus})`).toBeTruthy();
|
|
396
|
+
expect(panel.querySelector("[data-status-actions]")).toBeTruthy();
|
|
397
|
+
host.click();
|
|
398
|
+
expect(onRefresh).toHaveBeenCalledTimes(1);
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it("no actions row when connected without host actions", async () => {
|
|
403
|
+
const container = mountBar({
|
|
404
|
+
version: "1.2.3",
|
|
405
|
+
connectionStatus: "connected",
|
|
406
|
+
connectionInfo: INFO,
|
|
407
|
+
onRetry: () => {},
|
|
408
|
+
});
|
|
409
|
+
await nextTick();
|
|
410
|
+
container
|
|
411
|
+
.querySelector<HTMLElement>(".s-status-bar-tag")!
|
|
412
|
+
.dispatchEvent(new MouseEvent("mouseenter"));
|
|
413
|
+
await nextTick();
|
|
414
|
+
const panel = document.body.querySelector<HTMLElement>(".hk-popover-panel");
|
|
415
|
+
expect(panel!.querySelector("[data-status-actions]")).toBeNull();
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it("keeps the actions row available while connection info is still fetching", async () => {
|
|
419
|
+
const onRetry = vi.fn();
|
|
420
|
+
const container = mountBar({
|
|
421
|
+
version: "1.2.3",
|
|
422
|
+
connectionStatus: "disconnected",
|
|
423
|
+
connectionInfo: null,
|
|
424
|
+
onRetry,
|
|
425
|
+
});
|
|
426
|
+
await nextTick();
|
|
427
|
+
container
|
|
428
|
+
.querySelector<HTMLElement>(".s-status-bar-tag")!
|
|
429
|
+
.dispatchEvent(new MouseEvent("mouseenter"));
|
|
430
|
+
await nextTick();
|
|
431
|
+
const panel = document.body.querySelector<HTMLElement>(".hk-popover-panel")!;
|
|
432
|
+
// The row must live OUTSIDE the info/fetching ternary: an outage that
|
|
433
|
+
// drops connectionInfo to null is exactly when the retry button is
|
|
434
|
+
// most needed.
|
|
435
|
+
const button = panel.querySelector<HTMLButtonElement>("[data-status-actions] button");
|
|
436
|
+
expect(button, "retry button renders without info").toBeTruthy();
|
|
437
|
+
button!.click();
|
|
438
|
+
expect(onRetry).toHaveBeenCalledTimes(1);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it("no actions row when the slot renders nothing (wrapper forwards an empty slot)", async () => {
|
|
442
|
+
const container = document.createElement("div");
|
|
443
|
+
document.body.appendChild(container);
|
|
444
|
+
const app = createApp({
|
|
445
|
+
render: () =>
|
|
446
|
+
h(HkStatusBar, {
|
|
447
|
+
version: "1.2.3",
|
|
448
|
+
connectionStatus: "connected",
|
|
449
|
+
connectionInfo: INFO,
|
|
450
|
+
}, {
|
|
451
|
+
// HkConnectionStatus forwards `actions` unconditionally; an
|
|
452
|
+
// empty render must not paint a hairline-only row.
|
|
453
|
+
actions: () => [],
|
|
454
|
+
}),
|
|
455
|
+
});
|
|
456
|
+
app.mount(container);
|
|
457
|
+
mounts.push({ app, container });
|
|
458
|
+
await nextTick();
|
|
459
|
+
container
|
|
460
|
+
.querySelector<HTMLElement>(".s-status-bar-tag")!
|
|
461
|
+
.dispatchEvent(new MouseEvent("mouseenter"));
|
|
462
|
+
await nextTick();
|
|
463
|
+
const panel = document.body.querySelector<HTMLElement>(".hk-popover-panel");
|
|
464
|
+
expect(panel!.querySelector("[data-status-actions]")).toBeNull();
|
|
465
|
+
});
|
|
325
466
|
});
|
|
@@ -5,6 +5,7 @@ import { useI18n } from "../i18n/context";
|
|
|
5
5
|
import { HIKARI_FONT_MONO } from "../theme/fontContext";
|
|
6
6
|
|
|
7
7
|
import HPopover from "./HkPopover";
|
|
8
|
+
import HkButton from "./HkButton";
|
|
8
9
|
import type { HkConnectionInfo } from "./HkConnectionInfo";
|
|
9
10
|
import { HkCountdownDigit } from "./HkCountdownDigit";
|
|
10
11
|
|
|
@@ -112,7 +113,7 @@ export const HkStatusBar = defineComponent({
|
|
|
112
113
|
default: undefined,
|
|
113
114
|
},
|
|
114
115
|
},
|
|
115
|
-
setup(props) {
|
|
116
|
+
setup(props, { slots }) {
|
|
116
117
|
const popupOpen = ref(false);
|
|
117
118
|
const anchorRef = ref<HTMLElement | null>(null);
|
|
118
119
|
let closeTimer: ReturnType<typeof setTimeout> | null = null;
|
|
@@ -197,6 +198,16 @@ export const HkStatusBar = defineComponent({
|
|
|
197
198
|
}
|
|
198
199
|
}
|
|
199
200
|
|
|
201
|
+
// Popover retry button: the echo guard applies here too — a late
|
|
202
|
+
// synthetic click following the touch activation that opened the
|
|
203
|
+
// popover must not double-fire the retry through the button.
|
|
204
|
+
function onRetryButtonClick() {
|
|
205
|
+
if (Date.now() < touchEchoGuardUntil) return;
|
|
206
|
+
if (props.connectionStatus !== "connected") {
|
|
207
|
+
props.onRetry?.();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
200
211
|
// ── Touch activation ────────────────────────────────────────────
|
|
201
212
|
// Mobile tap-to-retry used to ride the SYNTHESIZED mouse chain
|
|
202
213
|
// (touchend → mouseenter → click → mouseleave), which browsers may
|
|
@@ -268,6 +279,22 @@ export const HkStatusBar = defineComponent({
|
|
|
268
279
|
: t("hikari::statusBar.disconnected", "Disconnected");
|
|
269
280
|
|
|
270
281
|
const connecting = mode === "reconnecting" || mode === "connecting";
|
|
282
|
+
// Actions row: the popover body teleports to <body>, so a click on
|
|
283
|
+
// the "click to reconnect" hint text inside it never reached the
|
|
284
|
+
// tag's own click handler — the popover now carries REAL buttons.
|
|
285
|
+
// The built-in one retriggers the reconnect for any non-green
|
|
286
|
+
// light; hosts append their own (e.g. a full manual refresh)
|
|
287
|
+
// through the `actions` slot.
|
|
288
|
+
const showRetryButton = mode !== "connected" && typeof props.onRetry === "function";
|
|
289
|
+
// Gate the row on RENDERED content, not on the slot function's
|
|
290
|
+
// existence: HkConnectionStatus forwards `actions` unconditionally,
|
|
291
|
+
// so a function-existence gate would paint an empty hairline row in
|
|
292
|
+
// the connected state for every host that passed no #actions.
|
|
293
|
+
const actionVnodes = slots.actions?.();
|
|
294
|
+
const hasHostActions = Array.isArray(actionVnodes)
|
|
295
|
+
? actionVnodes.length > 0
|
|
296
|
+
: Boolean(actionVnodes);
|
|
297
|
+
const hasActionsRow = showRetryButton || hasHostActions;
|
|
271
298
|
|
|
272
299
|
const pv = fmtVer(props.version, props.panelBuildHash);
|
|
273
300
|
const ev = props.engineVersion;
|
|
@@ -370,7 +397,7 @@ export const HkStatusBar = defineComponent({
|
|
|
370
397
|
)}
|
|
371
398
|
</div>
|
|
372
399
|
)}
|
|
373
|
-
{mode === "disconnected" && (
|
|
400
|
+
{mode === "disconnected" && !showRetryButton && (
|
|
374
401
|
<div style={{ fontStyle: "italic", fontSize: "0.6875rem", marginBottom: "4px", opacity: 0.7 }}>
|
|
375
402
|
{t("hikari::statusBar.clickReconnect", "Click to retry")}
|
|
376
403
|
</div>
|
|
@@ -418,6 +445,26 @@ export const HkStatusBar = defineComponent({
|
|
|
418
445
|
) : (
|
|
419
446
|
<div style={{ opacity: 0.5 }}>{t("hikari::statusBar.fetching", "Fetching connection info...")}</div>
|
|
420
447
|
)}
|
|
448
|
+
{hasActionsRow && (
|
|
449
|
+
<div
|
|
450
|
+
data-status-actions
|
|
451
|
+
style={{
|
|
452
|
+
display: "flex",
|
|
453
|
+
alignItems: "center",
|
|
454
|
+
gap: "8px",
|
|
455
|
+
marginTop: "8px",
|
|
456
|
+
paddingTop: "8px",
|
|
457
|
+
borderTop: "1px solid var(--border-faint, rgb(var(--color-border) / 10%))",
|
|
458
|
+
}}
|
|
459
|
+
>
|
|
460
|
+
{showRetryButton && (
|
|
461
|
+
<HkButton size="sm" variant="secondary" onClick={onRetryButtonClick}>
|
|
462
|
+
{t("hikari::statusBar.retryNow", "Reconnect now")}
|
|
463
|
+
</HkButton>
|
|
464
|
+
)}
|
|
465
|
+
{hasHostActions ? actionVnodes : null}
|
|
466
|
+
</div>
|
|
467
|
+
)}
|
|
421
468
|
</div>
|
|
422
469
|
</HPopover>
|
|
423
470
|
</>
|
|
@@ -150,6 +150,7 @@
|
|
|
150
150
|
"hikari::statusBar.backend.unknown": "غير معروف",
|
|
151
151
|
"hikari::statusBar.backend.warn": "متدهور",
|
|
152
152
|
"hikari::statusBar.clickReconnect": "انقر لإعادة المحاولة",
|
|
153
|
+
"hikari::statusBar.retryNow": "أعد الاتصال الآن",
|
|
153
154
|
"hikari::statusBar.connected": "متصل",
|
|
154
155
|
"hikari::statusBar.connecting": "جارٍ الاتصال...",
|
|
155
156
|
"hikari::statusBar.disconnected": "غير متصل",
|
|
@@ -150,6 +150,7 @@
|
|
|
150
150
|
"hikari::statusBar.backend.unknown": "Unbekannt",
|
|
151
151
|
"hikari::statusBar.backend.warn": "Beeinträchtigt",
|
|
152
152
|
"hikari::statusBar.clickReconnect": "Zum Wiederholen klicken",
|
|
153
|
+
"hikari::statusBar.retryNow": "Jetzt neu verbinden",
|
|
153
154
|
"hikari::statusBar.connected": "Verbunden",
|
|
154
155
|
"hikari::statusBar.connecting": "Verbinde...",
|
|
155
156
|
"hikari::statusBar.disconnected": "Getrennt",
|
|
@@ -150,6 +150,7 @@
|
|
|
150
150
|
"hikari::statusBar.backend.unknown": "Unknown",
|
|
151
151
|
"hikari::statusBar.backend.warn": "Degraded",
|
|
152
152
|
"hikari::statusBar.clickReconnect": "Click to retry",
|
|
153
|
+
"hikari::statusBar.retryNow": "Reconnect now",
|
|
153
154
|
"hikari::statusBar.connected": "Connected",
|
|
154
155
|
"hikari::statusBar.connecting": "Connecting...",
|
|
155
156
|
"hikari::statusBar.disconnected": "Disconnected",
|
|
@@ -150,6 +150,7 @@
|
|
|
150
150
|
"hikari::statusBar.backend.unknown": "Desconocido",
|
|
151
151
|
"hikari::statusBar.backend.warn": "Degradado",
|
|
152
152
|
"hikari::statusBar.clickReconnect": "Haga clic para reintentar",
|
|
153
|
+
"hikari::statusBar.retryNow": "Reconectar ahora",
|
|
153
154
|
"hikari::statusBar.connected": "Conectado",
|
|
154
155
|
"hikari::statusBar.connecting": "Conectando...",
|
|
155
156
|
"hikari::statusBar.disconnected": "Desconectado",
|
|
@@ -150,6 +150,7 @@
|
|
|
150
150
|
"hikari::statusBar.backend.unknown": "Inconnu",
|
|
151
151
|
"hikari::statusBar.backend.warn": "Dégradé",
|
|
152
152
|
"hikari::statusBar.clickReconnect": "Cliquez pour réessayer",
|
|
153
|
+
"hikari::statusBar.retryNow": "Reconnecter maintenant",
|
|
153
154
|
"hikari::statusBar.connected": "Connecté",
|
|
154
155
|
"hikari::statusBar.connecting": "Connexion...",
|
|
155
156
|
"hikari::statusBar.disconnected": "Déconnecté",
|
|
@@ -150,6 +150,7 @@
|
|
|
150
150
|
"hikari::statusBar.backend.unknown": "不明",
|
|
151
151
|
"hikari::statusBar.backend.warn": "低下",
|
|
152
152
|
"hikari::statusBar.clickReconnect": "クリックして再接続",
|
|
153
|
+
"hikari::statusBar.retryNow": "今すぐ再接続",
|
|
153
154
|
"hikari::statusBar.connected": "接続済み",
|
|
154
155
|
"hikari::statusBar.connecting": "接続中...",
|
|
155
156
|
"hikari::statusBar.disconnected": "切断",
|
|
@@ -150,6 +150,7 @@
|
|
|
150
150
|
"hikari::statusBar.backend.unknown": "알 수 없음",
|
|
151
151
|
"hikari::statusBar.backend.warn": "저하",
|
|
152
152
|
"hikari::statusBar.clickReconnect": "클릭하여 재연결",
|
|
153
|
+
"hikari::statusBar.retryNow": "지금 재연결",
|
|
153
154
|
"hikari::statusBar.connected": "연결됨",
|
|
154
155
|
"hikari::statusBar.connecting": "연결 중...",
|
|
155
156
|
"hikari::statusBar.disconnected": "연결 끊김",
|
|
@@ -150,6 +150,7 @@
|
|
|
150
150
|
"hikari::statusBar.backend.unknown": "Desconhecido",
|
|
151
151
|
"hikari::statusBar.backend.warn": "Degradado",
|
|
152
152
|
"hikari::statusBar.clickReconnect": "Clique para tentar novamente",
|
|
153
|
+
"hikari::statusBar.retryNow": "Reconectar agora",
|
|
153
154
|
"hikari::statusBar.connected": "Conectado",
|
|
154
155
|
"hikari::statusBar.connecting": "Conectando...",
|
|
155
156
|
"hikari::statusBar.disconnected": "Desconectado",
|
|
@@ -150,6 +150,7 @@
|
|
|
150
150
|
"hikari::statusBar.backend.unknown": "Неизвестно",
|
|
151
151
|
"hikari::statusBar.backend.warn": "Деградация",
|
|
152
152
|
"hikari::statusBar.clickReconnect": "Нажмите, чтобы повторить",
|
|
153
|
+
"hikari::statusBar.retryNow": "Переподключиться сейчас",
|
|
153
154
|
"hikari::statusBar.connected": "Подключено",
|
|
154
155
|
"hikari::statusBar.connecting": "Подключение...",
|
|
155
156
|
"hikari::statusBar.disconnected": "Отключено",
|
|
@@ -150,6 +150,7 @@
|
|
|
150
150
|
"hikari::statusBar.backend.unknown": "未知",
|
|
151
151
|
"hikari::statusBar.backend.warn": "降级",
|
|
152
152
|
"hikari::statusBar.clickReconnect": "点击以尝试重连",
|
|
153
|
+
"hikari::statusBar.retryNow": "立即重连",
|
|
153
154
|
"hikari::statusBar.connected": "已连接",
|
|
154
155
|
"hikari::statusBar.connecting": "连接中...",
|
|
155
156
|
"hikari::statusBar.disconnected": "断开",
|
|
@@ -150,6 +150,7 @@
|
|
|
150
150
|
"hikari::statusBar.backend.unknown": "未知",
|
|
151
151
|
"hikari::statusBar.backend.warn": "降級",
|
|
152
152
|
"hikari::statusBar.clickReconnect": "點擊以嘗試重連",
|
|
153
|
+
"hikari::statusBar.retryNow": "立即重連",
|
|
153
154
|
"hikari::statusBar.connected": "已連線",
|
|
154
155
|
"hikari::statusBar.connecting": "連線中...",
|
|
155
156
|
"hikari::statusBar.disconnected": "斷開",
|