// components/utilities/files/api.ts export type FsEntry = { name: string; path: string; // absolute or from root, e.g. "/public" or "/public/readme.txt" isDir: boolean; size?: number | null; // bytes modified?: string | null; // ISO date string mime?: string | null; // server-provided mime, optional }; export type ListResponse = { cwd: string; // normalized path we listed entries: FsEntry[]; // unsorted list }; export type SortKey = "name" | "size" | "modified" | "type"; export type SortDir = "asc" | "desc"; export async function list(path: string): Promise { const u = new URL("/api/files/list", location.origin); if (path) u.searchParams.set("path", path); const res = await fetch(u.toString(), { credentials: "include", cache: "no-store" }); if (!res.ok) throw new Error(`list ${path}: ${res.status}`); return res.json(); } export function rawUrl(path: string): string { const u = new URL("/api/files/raw", location.origin); u.searchParams.set("path", path); return u.toString(); } export async function download(path: string): Promise { // try direct browser download via a hidden const u = new URL("/api/files/download", location.origin); u.searchParams.set("path", path); const a = document.createElement("a"); a.href = u.toString(); a.rel = "noopener"; a.download = ""; // hint to save-as document.body.appendChild(a); a.click(); a.remove(); } export function parentDir(p: string): string { if (!p || p === "/") return "/"; const segs = p.replace(/\\/g, "/").split("/").filter(Boolean); segs.pop(); return "/" + segs.join("/"); } export function nicelyFormatBytes(n?: number | null): string { if (!Number.isFinite(n as number) || (n as number) < 0) return "—"; const b = n as number; if (b < 1024) return `${b} B`; const units = ["KB","MB","GB","TB"]; let v = b / 1024, i = 0; while (v >= 1024 && i < units.length - 1) { v /= 1024; i++; } return `${v.toFixed(v >= 100 ? 0 : v >= 10 ? 1 : 2)} ${units[i]}`; } export function extFromName(name: string): string { const m = /\.([^.]+)$/.exec(name || ""); return m ? m[1].toLowerCase() : ""; } export function isPreviewableImage(mime?: string | null, name?: string): boolean { const ext = extFromName(name || ""); return /^image\//.test(mime || "") || ["png","jpg","jpeg","gif","webp","bmp","svg"].includes(ext); } export function isPreviewableText(mime?: string | null, name?: string): boolean { const ext = extFromName(name || ""); return /^text\//.test(mime || "") || ["txt","csv","md","json","log"].includes(ext); } export function isPreviewablePdf(mime?: string | null, name?: string): boolean { const ext = extFromName(name || ""); return (mime || "").includes("pdf") || ext === "pdf"; }