Files
beaver-gateway/ui/src/lib/format.ts
T

185 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const THOUSAND = 1000;
const MILLION = 1_000_000;
const MINUTE_MS = 60_000;
const HOUR_MS = 3_600_000;
const DAY_MS = 86_400_000;
const KB = 1024;
const WHITESPACE = /\s+/g;
export function fmtTokens(n: number | null | undefined): string {
if (n === null || n === undefined) {
return "";
}
if (n >= MILLION) {
return `${(n / MILLION).toFixed(n >= 10 * MILLION ? 0 : 1)}M`;
}
if (n >= THOUSAND) {
return `${(n / THOUSAND).toFixed(n >= 10 * THOUSAND ? 0 : 1)}k`;
}
return String(n);
}
export function fmtMoney(usd: number | null | undefined): string {
if (usd === null || usd === undefined) {
return "";
}
if (usd === 0) {
return "$0";
}
if (usd < 0.01) {
return `$${usd.toFixed(4)}`;
}
if (usd < 1) {
return `$${usd.toFixed(3)}`;
}
return `$${usd.toFixed(2)}`;
}
export function fmtPct(fraction: number | null | undefined): string {
if (fraction === null || fraction === undefined) {
return "";
}
return `${Math.round(fraction * 100)}%`;
}
export function fmtDuration(ms: number | null | undefined): string {
if (ms === null || ms === undefined) {
return "";
}
if (ms < THOUSAND) {
return `${Math.round(ms)}ms`;
}
if (ms < MINUTE_MS) {
return `${(ms / THOUSAND).toFixed(1)}s`;
}
if (ms < HOUR_MS) {
const minutes = Math.floor(ms / MINUTE_MS);
const seconds = Math.round((ms % MINUTE_MS) / THOUSAND);
return `${minutes}m${String(seconds).padStart(2, "0")}s`;
}
const hours = Math.floor(ms / HOUR_MS);
const minutes = Math.round((ms % HOUR_MS) / MINUTE_MS);
return `${hours}h${String(minutes).padStart(2, "0")}m`;
}
export function fmtSeconds(seconds: number | null | undefined): string {
return seconds === null || seconds === undefined
? ""
: fmtDuration(seconds * THOUSAND);
}
export function fmtBytes(bytes: number | null | undefined): string {
if (bytes === null || bytes === undefined) {
return "";
}
if (bytes >= KB * KB * KB) {
return `${(bytes / (KB * KB * KB)).toFixed(2)} GB`;
}
if (bytes >= KB * KB) {
return `${(bytes / (KB * KB)).toFixed(0)} MB`;
}
if (bytes >= KB) {
return `${(bytes / KB).toFixed(0)} kB`;
}
return `${bytes} B`;
}
export function parseDate(iso: string | null | undefined): Date | null {
if (!iso) {
return null;
}
const date = new Date(iso);
return Number.isNaN(date.getTime()) ? null : date;
}
export function fmtTime(iso: string | null | undefined): string {
const date = parseDate(iso);
return date
? date.toLocaleTimeString(undefined, {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
})
: "";
}
export function fmtDateTime(iso: string | null | undefined): string {
const date = parseDate(iso);
return date
? date.toLocaleString(undefined, {
day: "numeric",
hour: "2-digit",
minute: "2-digit",
month: "short",
})
: "";
}
export function fmtRelative(
iso: string | null | undefined,
now = Date.now()
): string {
const date = parseDate(iso);
if (!date) {
return "";
}
const diff = now - date.getTime();
const abs = Math.abs(diff);
const suffix = diff >= 0 ? "ago" : "from now";
if (abs < MINUTE_MS) {
return diff >= 0 ? "just now" : "in <1m";
}
if (abs < HOUR_MS) {
return `${Math.round(abs / MINUTE_MS)}m ${suffix}`;
}
if (abs < DAY_MS) {
return `${Math.round(abs / HOUR_MS)}h ${suffix}`;
}
return `${Math.round(abs / DAY_MS)}d ${suffix}`;
}
export function fmtCountdown(iso: string | null | undefined, now = Date.now()) {
const date = parseDate(iso);
if (!date) {
return "";
}
const left = date.getTime() - now;
if (left <= 0) {
return "resets now";
}
if (left < HOUR_MS) {
return `resets in ${Math.ceil(left / MINUTE_MS)}m`;
}
if (left < DAY_MS) {
const hours = Math.floor(left / HOUR_MS);
const minutes = Math.round((left % HOUR_MS) / MINUTE_MS);
return `resets in ${hours}h ${String(minutes).padStart(2, "0")}m`;
}
const days = Math.floor(left / DAY_MS);
const hours = Math.round((left % DAY_MS) / HOUR_MS);
return `resets in ${days}d ${hours}h`;
}
export function elapsedMs(from: string, to: string | null, now = Date.now()) {
const a = parseDate(from)?.getTime();
const b = to ? parseDate(to)?.getTime() : now;
if (a === undefined || b === undefined) {
return 0;
}
return Math.max(0, b - a);
}
export function clip(text: string, max: number): string {
const one = text.replace(WHITESPACE, " ").trim();
return one.length > max ? `${one.slice(0, max - 1)}…` : one;
}
export function shortId(id: string | null | undefined): string {
return id ? id.slice(0, 8) : "";
}
export function cacheShare(input: number, cacheRead: number): number | null {
const total = input + cacheRead;
return total > 0 ? cacheRead / total : null;
}