// Download an artifacts/html page as a self-contained zip from the host
// UI (file explorer preview). Mirrors usePdfDownload: POST the workspace
// path to /api/share/pack, receive a binary zip, run the
// blob-to-download dance. Failure sets `packFailed` so the caller can
// render a localized message — the raw server body is never surfaced.
import { ref, type Ref } from "vue";
import { API_ROUTES } from "../config/apiRoutes";
import { apiFetchRaw } from "../utils/api";
import { saveBlob, filenameFromDisposition } from "../utils/blobDownload";
export interface UseSharePackHandle {
packing: Ref;
packFailed: Ref;
downloadZip: (htmlPath: string) => Promise;
/** Clear the failed flag — call on navigation so a stale error banner
* doesn't carry over to the next file. */
reset: () => void;
}
function fallbackName(htmlPath: string): string {
const base =
htmlPath
.split("/")
.pop()
?.replace(/\.html?$/i, "") || "share";
return `${base}.zip`;
}
export function useSharePack(): UseSharePackHandle {
const packing = ref(false);
const packFailed = ref(false);
async function downloadZip(htmlPath: string): Promise {
packFailed.value = false;
packing.value = true;
try {
const response = await apiFetchRaw(API_ROUTES.share.pack, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ path: htmlPath }),
});
if (!response.ok) {
packFailed.value = true;
return;
}
saveBlob(await response.blob(), filenameFromDisposition(response.headers.get("content-disposition"), fallbackName(htmlPath)));
} catch {
packFailed.value = true;
} finally {
packing.value = false;
}
}
function reset(): void {
packFailed.value = false;
}
return { packing, packFailed, downloadZip, reset };
}