import { type ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } export const b2s = (t: number) => { let e = (Math.log2(t) / 10) | 0; return `${(t / 1024 ** (e = e <= 0 ? 0 : e)).toFixed(1)}${" KMGP"[e]}B`; }; type Node = { count: number; next: Map }; function node(): Node { return { count: 0, next: new Map() }; } // find unique shortest code for each string export function shortestPrefixes(values: readonly string[]) { const uniq = [...new Set(values)]; const root = node(); for (const v of uniq) { let cur = root; for (const ch of v) { let n = cur.next.get(ch); if (!n) cur.next.set(ch, (n = node())); n.count++; cur = n; } } const res = new Map(); for (const v of uniq) { let cur = root; let prefix = ""; for (const ch of v) { const n = cur.next.get(ch)!; prefix += ch; if (n.count === 1) break; cur = n; } res.set(v, prefix); } return res; } export async function fileToDataUrl(file: File) { const buffer = Buffer.from(await file.arrayBuffer()); return `data:${file.type};base64,${buffer.toString("base64")}`; } export function parseSearchNumber( param: string | string[] | undefined, def: number = 0, ) { if (Array.isArray(param)) return parseSearchNumber(param[0], def); const parsed = parseFloat(param ?? ""); return Number.isNaN(parsed) ? def : parsed; } export function parseSearchString( param: string | string[] | undefined, def: string = "", ) { if (Array.isArray(param)) return parseSearchString(param[0], def); return param ?? def; } export function pausePass(time: number): (res: R) => Promise { return (res) => new Promise((r) => setTimeout(() => r(res), time)); } // taken from astral/api export class ResponseNotOkError extends Error { constructor( readonly status: number, readonly response: Response, ) { super(`RESPONSE_NOT_OK: Got status ${status}`); } } export function throwNotOk(res: Response) { if (!res.ok) throw new ResponseNotOkError(res.status, res); return res; }