import type { UploadedFile } from "./upload.ts"; export function formatFileSize(bytes: number, locale = "en"): string { const value = Math.max(0, Number(bytes) || 0); if (value < 1_024) return `${value} B`; const units = ["KB", "MB", "GB", "TB"]; let current = value / 1_024; let unit = units[0]!; for (let index = 1; index < units.length && current >= 1_024; index++) { current /= 1_024; unit = units[index]!; } return `${new Intl.NumberFormat(locale, { maximumFractionDigits: current < 10 ? 1 : 0 }).format(current)} ${unit}`; } export function uploadAccept(value: string | readonly string[]): string { return (typeof value === "string" ? value.split(",") : [...value]) .map((entry) => entry.trim()) .filter(Boolean) .join(","); } export function uploadedFileMap(files: readonly UploadedFile[]): Record { return Object.fromEntries(files.map((file) => [file.key, file])); } export function uploaderAttributes( options: { store?: string; endpoint?: string; accept?: string | readonly string[]; maxBytes?: number; multiple?: boolean; field?: string; label?: string; } = {}, ): Record { return { "data-uploader": options.store ?? "default", "data-endpoint": options.endpoint ?? "/api/upload", ...(options.accept ? { "data-accept": uploadAccept(options.accept) } : {}), ...(options.maxBytes ? { "data-max": String(options.maxBytes) } : {}), ...(options.multiple ? { "data-multiple": true } : {}), ...(options.field ? { "data-field": options.field } : {}), ...(options.label ? { "data-label": options.label } : {}), }; } export function assertUploadedFiles( files: readonly UploadedFile[], options: { min?: number; max?: number } = {}, ): readonly UploadedFile[] { const min = Math.max(0, options.min ?? 0); const max = Math.max(min, options.max ?? Number.POSITIVE_INFINITY); if (files.length < min) throw new Error(`WRN-UPLOAD-MIN-FILES: expected at least ${min}`); if (files.length > max) throw new Error(`WRN-UPLOAD-MAX-FILES: expected at most ${max}`); return files; }