|
| 1 | +export type OutcomeKind = "idle" | "success" | "error" | "warning" | "pending"; |
| 2 | + |
| 3 | +export interface OutcomeState { |
| 4 | + kind: OutcomeKind; |
| 5 | + message: string; |
| 6 | + at?: string; |
| 7 | +} |
| 8 | + |
| 9 | +export function renderJson(target: HTMLElement, value: unknown): void { |
| 10 | + target.textContent = JSON.stringify(value, null, 2); |
| 11 | +} |
| 12 | + |
| 13 | +export function formatTimestamp(timestamp: string | undefined): string { |
| 14 | + if (!timestamp) { |
| 15 | + return "Waiting for activity"; |
| 16 | + } |
| 17 | + |
| 18 | + const date = new Date(timestamp); |
| 19 | + |
| 20 | + if (Number.isNaN(date.getTime())) { |
| 21 | + return "Waiting for activity"; |
| 22 | + } |
| 23 | + |
| 24 | + const deltaMs = Date.now() - date.getTime(); |
| 25 | + |
| 26 | + if (Math.abs(deltaMs) < 10_000) { |
| 27 | + return "Updated just now"; |
| 28 | + } |
| 29 | + |
| 30 | + return `Updated ${date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" })}`; |
| 31 | +} |
| 32 | + |
| 33 | +export function extractErrorMessage(body: unknown, fallback: string): string { |
| 34 | + if (typeof body === "string" && body.trim()) { |
| 35 | + return body; |
| 36 | + } |
| 37 | + |
| 38 | + if (typeof body !== "object" || body === null) { |
| 39 | + return fallback; |
| 40 | + } |
| 41 | + |
| 42 | + if ("error" in body) { |
| 43 | + const error = body.error; |
| 44 | + |
| 45 | + if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") { |
| 46 | + return error.message; |
| 47 | + } |
| 48 | + } |
| 49 | + |
| 50 | + if ("detail" in body && typeof body.detail === "string") { |
| 51 | + return body.detail; |
| 52 | + } |
| 53 | + |
| 54 | + return fallback; |
| 55 | +} |
| 56 | + |
| 57 | +export function setButtonBusy( |
| 58 | + button: HTMLButtonElement, |
| 59 | + isBusy: boolean, |
| 60 | + busyLabel: string, |
| 61 | +): void { |
| 62 | + if (!button.dataset.defaultLabel) { |
| 63 | + button.dataset.defaultLabel = button.textContent?.trim() ?? ""; |
| 64 | + } |
| 65 | + |
| 66 | + button.dataset.busy = String(isBusy); |
| 67 | + button.disabled = isBusy; |
| 68 | + button.textContent = isBusy ? busyLabel : button.dataset.defaultLabel; |
| 69 | +} |
0 commit comments