mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-09 19:41:32 +00:00
## Changes **Batch download** - Add multi-select mode to the create-video results feed (webapp + desktop) with a "Select" toggle in the topbar, checkboxes on completed items, and a floating download bar - Webapp: "Download" bundles selected videos into a single .zip client-side (new jszip dependency, concurrent fetches, progress and partial-failure toasts) - Desktop: "Download" prompts for a folder and saves each selected video as its own file via native Tauri save, with progress toasts - Library: add a Download (zip) button to the existing bulk selection bar - Shared `ui-generation-list` lib gains a selection store, select toggle, action bar, and optional selection props on the grid/list views (existing hosts unaffected) **Credits refresh on failed generations** - Webapp: dispatch a credits refresh when the task queue or generation feed observes a newly failed job (immediately and again after 2.5s so server-side refunds settle) - Desktop: also refresh credits when the 5s task poll discovers a failure that the push event missed, and listen for `credits-change` in the TopBar **UI fixes** - Selection download bar always sits above the promptbox regardless of its size - Mobile Generate/History header no longer overlaps the gallery toggles (flex layout instead of absolute positioning) - Reference deck top-aligns in the expanded prompt box instead of centering - "Play videos with sound" setting now defaults to on
101 lines
2.9 KiB
TypeScript
101 lines
2.9 KiB
TypeScript
|
|
import { download } from "@tauri-apps/plugin-upload";
|
|
import { downloadDir } from "@tauri-apps/api/path";
|
|
import { open, save } from "@tauri-apps/plugin-dialog";
|
|
|
|
const ASK_LOCATION_BEFORE_DOWNLOAD_KEY = "artcraft_ask_location_before_download";
|
|
|
|
export const getAskLocationBeforeDownload = (): boolean => {
|
|
if (typeof window === "undefined") return false;
|
|
try {
|
|
return window.localStorage.getItem(ASK_LOCATION_BEFORE_DOWNLOAD_KEY) === "true";
|
|
} catch {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
export const setAskLocationBeforeDownload = (enabled: boolean): void => {
|
|
if (typeof window === "undefined") return;
|
|
try {
|
|
if (enabled) {
|
|
window.localStorage.setItem(ASK_LOCATION_BEFORE_DOWNLOAD_KEY, "true");
|
|
} else {
|
|
window.localStorage.removeItem(ASK_LOCATION_BEFORE_DOWNLOAD_KEY);
|
|
}
|
|
} catch {
|
|
// ignore storage failures
|
|
}
|
|
};
|
|
|
|
const deriveDownloadFilename = (url: string): string => {
|
|
try {
|
|
const urlObj = new URL(url);
|
|
const last = urlObj.pathname.split("/").pop();
|
|
if (last && last.length > 0) return last;
|
|
} catch {
|
|
// fall through
|
|
}
|
|
return "downloaded_file";
|
|
};
|
|
|
|
/**
|
|
* Prompts the user with a native save dialog if the
|
|
* "Ask location before download" setting is on.
|
|
*
|
|
* Returns:
|
|
* - the chosen absolute path when the user picked one
|
|
* - `null` when the user dismissed the dialog (caller should abort)
|
|
* - `undefined` when the toggle is off (caller should fall back to default)
|
|
*/
|
|
export const promptDownloadLocationIfNeeded = async (
|
|
url: string,
|
|
): Promise<string | null | undefined> => {
|
|
if (!getAskLocationBeforeDownload()) return undefined;
|
|
const filename = deriveDownloadFilename(url);
|
|
const chosen = await save({ defaultPath: filename });
|
|
return chosen ?? null;
|
|
};
|
|
|
|
/**
|
|
* Prompts the user to pick a directory (e.g. for batch downloads).
|
|
* Returns the chosen absolute path, or `null` when dismissed.
|
|
*/
|
|
export const pickDownloadDirectory = async (): Promise<string | null> => {
|
|
const chosen = await open({ directory: true, multiple: false });
|
|
return typeof chosen === "string" ? chosen : null;
|
|
};
|
|
|
|
/** Downloads `url` to an explicit absolute filesystem path. */
|
|
export const downloadUrlToPath = async (url: string, path: string) => {
|
|
await download(url, path);
|
|
};
|
|
|
|
export const downloadFileFromUrl = async (url: string) => {
|
|
console.log("GOT THE URL", url);
|
|
try {
|
|
const filename = deriveDownloadFilename(url);
|
|
|
|
let filePath: string;
|
|
const chosen = await promptDownloadLocationIfNeeded(url);
|
|
if (chosen === null) {
|
|
// User dismissed the picker.
|
|
return;
|
|
}
|
|
if (typeof chosen === "string") {
|
|
filePath = chosen;
|
|
} else {
|
|
const downloadsPath = await downloadDir();
|
|
filePath = `${downloadsPath}/${filename}`;
|
|
}
|
|
|
|
await download(url, filePath);
|
|
|
|
console.log(
|
|
`File downloaded and saved to ${filePath}`,
|
|
);
|
|
} catch (error) {
|
|
console.error("Error downloading file:", error);
|
|
throw error;
|
|
}
|
|
};
|