feat(web): panneau Fichiers — navigation projet + téléchargements fichier/dossier ZIP — #211

This commit is contained in:
2026-08-15 10:38:53 +02:00
parent bb1fdecf7c
commit 79fc3d184d
22 changed files with 1648 additions and 78 deletions

View File

@ -39,6 +39,7 @@ export type FetchLike = (
/** Parsed JSON body (may reject/return undefined for empty bodies). */
json(): Promise<unknown>;
text(): Promise<string>;
blob?(): Promise<Blob>;
}>;
/**
@ -182,4 +183,51 @@ export class HttpInvoker {
}
return parsed as T;
}
async download(path: string, filename: string, what = `GET ${path}`): Promise<void> {
const headers: Record<string, string> = {};
if (this.token) headers.Authorization = `Bearer ${this.token}`;
let res: Awaited<ReturnType<FetchLike>>;
try {
res = await this.fetchImpl(`${this.baseUrl}${path}`, {
method: "GET",
headers,
credentials: "same-origin",
});
} catch (networkError) {
const err: GatewayError = {
code: "TRANSPORT_ERROR",
message: `HTTP request for ${what} failed: ${String(networkError)}`,
};
throw err;
}
if (!res.ok) {
if (res.status === 401) this.onUnauthorized?.();
let parsed: unknown;
try {
parsed = await res.json();
} catch {
parsed = undefined;
}
throw toGatewayError(parsed, `${what} failed (HTTP ${res.status})`);
}
const blob = res.blob ? await res.blob() : new Blob([await res.text()]);
triggerBrowserDownload(blob, filename);
}
}
function triggerBrowserDownload(blob: Blob, filename: string): void {
if (typeof document === "undefined" || typeof URL === "undefined") return;
const href = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = href;
link.download = filename;
link.rel = "noopener";
document.body.appendChild(link);
link.click();
link.remove();
URL.revokeObjectURL(href);
}