app: ask location before download (#1483)

- toggle in settings to enable disable ask location on download button
pressed
This commit is contained in:
BFlat authored and GitHub committed 2026-05-08 16:25:27 +07:00
1 parent 5a7c253189
commit f666e68dcd
8 files changed
+133 -26

No files matched your search

@@ -17,7 +17,11 @@ import {
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { signal } from "@preact/signals-react";
import { useSignals } from "@preact/signals-react/runtime";
import { FilterMediaClasses } from "@storyteller/api";
import {
FilterMediaClasses,
downloadUrlToPath,
promptDownloadLocationIfNeeded,
} from "@storyteller/api";
import {
getCreatorIcon,
ModelCreator,
@@ -288,8 +292,16 @@ export const TopBar = ({ pageName }: Props) => {
const downloadFile = async (url: string, mediaClass?: string) => {
try {
//await downloadFileFromUrl(url);
await DownloadUrl(url);
const chosenPath = await promptDownloadLocationIfNeeded(url);
if (chosenPath === null) {
// User dismissed the picker.
return;
}
if (typeof chosenPath === "string") {
await downloadUrlToPath(url, chosenPath);
} else {
await DownloadUrl(url);
}
if (mediaClass === FilterMediaClasses.DIMENSIONAL) {
toast.success(`Downloaded 3D model`);
} else {
+1 -1
View File
@@ -1 +1 @@
{"fileNames":["../../node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/typescript/lib/lib.es2021.d.ts","../../node_modules/typescript/lib/lib.es2022.d.ts","../../node_modules/typescript/lib/lib.es2023.d.ts","../../node_modules/typescript/lib/lib.es2024.d.ts","../../node_modules/typescript/lib/lib.esnext.d.ts","../../node_modules/typescript/lib/lib.dom.d.ts","../../node_modules/typescript/lib/lib.dom.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/typescript/lib/lib.es2016.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/typescript/lib/lib.es2021.promise.d.ts","../../node_modules/typescript/lib/lib.es2021.string.d.ts","../../node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../node_modules/typescript/lib/lib.es2021.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.array.d.ts","../../node_modules/typescript/lib/lib.es2022.error.d.ts","../../node_modules/typescript/lib/lib.es2022.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.object.d.ts","../../node_modules/typescript/lib/lib.es2022.string.d.ts","../../node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../node_modules/typescript/lib/lib.es2023.array.d.ts","../../node_modules/typescript/lib/lib.es2023.collection.d.ts","../../node_modules/typescript/lib/lib.es2023.intl.d.ts","../../node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2024.collection.d.ts","../../node_modules/typescript/lib/lib.es2024.object.d.ts","../../node_modules/typescript/lib/lib.es2024.promise.d.ts","../../node_modules/typescript/lib/lib.es2024.regexp.d.ts","../../node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2024.string.d.ts","../../node_modules/typescript/lib/lib.esnext.array.d.ts","../../node_modules/typescript/lib/lib.esnext.collection.d.ts","../../node_modules/typescript/lib/lib.esnext.intl.d.ts","../../node_modules/typescript/lib/lib.esnext.disposable.d.ts","../../node_modules/typescript/lib/lib.esnext.promise.d.ts","../../node_modules/typescript/lib/lib.esnext.decorators.d.ts","../../node_modules/typescript/lib/lib.esnext.iterator.d.ts","../../node_modules/typescript/lib/lib.esnext.float16.d.ts","../../node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../node_modules/@remix-run/node/dist/globals.d.ts","../../node_modules/@types/cookie/index.d.ts","../../node_modules/@remix-run/server-runtime/dist/crypto.d.ts","../../node_modules/@remix-run/server-runtime/dist/cookies.d.ts","../../node_modules/@remix-run/server-runtime/dLine truncated
{"fileNames":["../../node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/typescript/lib/lib.es2021.d.ts","../../node_modules/typescript/lib/lib.es2022.d.ts","../../node_modules/typescript/lib/lib.es2023.d.ts","../../node_modules/typescript/lib/lib.es2024.d.ts","../../node_modules/typescript/lib/lib.esnext.d.ts","../../node_modules/typescript/lib/lib.dom.d.ts","../../node_modules/typescript/lib/lib.dom.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/typescript/lib/lib.es2016.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/typescript/lib/lib.es2021.promise.d.ts","../../node_modules/typescript/lib/lib.es2021.string.d.ts","../../node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../node_modules/typescript/lib/lib.es2021.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.array.d.ts","../../node_modules/typescript/lib/lib.es2022.error.d.ts","../../node_modules/typescript/lib/lib.es2022.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.object.d.ts","../../node_modules/typescript/lib/lib.es2022.string.d.ts","../../node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../node_modules/typescript/lib/lib.es2023.array.d.ts","../../node_modules/typescript/lib/lib.es2023.collection.d.ts","../../node_modules/typescript/lib/lib.es2023.intl.d.ts","../../node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2024.collection.d.ts","../../node_modules/typescript/lib/lib.es2024.object.d.ts","../../node_modules/typescript/lib/lib.es2024.promise.d.ts","../../node_modules/typescript/lib/lib.es2024.regexp.d.ts","../../node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2024.string.d.ts","../../node_modules/typescript/lib/lib.esnext.array.d.ts","../../node_modules/typescript/lib/lib.esnext.collection.d.ts","../../node_modules/typescript/lib/lib.esnext.intl.d.ts","../../node_modules/typescript/lib/lib.esnext.disposable.d.ts","../../node_modules/typescript/lib/lib.esnext.promise.d.ts","../../node_modules/typescript/lib/lib.esnext.decorators.d.ts","../../node_modules/typescript/lib/lib.esnext.iterator.d.ts","../../node_modules/typescript/lib/lib.esnext.float16.d.ts","../../node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../node_modules/@remix-run/node/dist/globals.d.ts","../../node_modules/@types/cookie/index.d.ts","../../node_modules/@remix-run/server-runtime/dist/crypto.d.ts","../../node_modules/@remix-run/server-runtime/dist/cookies.d.ts","../../node_modules/@remix-run/server-runtime/dLine truncated
+1
View File
@@ -16,6 +16,7 @@
}
},
"dependencies": {
"@tauri-apps/plugin-dialog": "^2.2.1",
"@tauri-apps/plugin-upload": "2.2.1"
}
}
+84 -22
View File
@@ -1,29 +1,91 @@
import { download } from "@tauri-apps/plugin-upload";
import { downloadDir } from "@tauri-apps/api/path";
import { 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;
};
/** 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 {
// Extract filename from URL
const urlObj = new URL(url);
let filename = urlObj.pathname.split("/").pop();
if (!filename || filename === "") {
filename = "downloaded_file";
}
console.log("GOT THE URL", url);
try {
const filename = deriveDownloadFilename(url);
// Get downloads directory path
const downloadsPath = await downloadDir();
const filePath = `${downloadsPath}/${filename}`;
// Download and save the file
await download(url, filePath);
console.log(
`File downloaded and saved as ${filename} in downloads folder`,
);
} catch (error) {
console.error("Error downloading file:", error);
throw error;
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;
}
};
+1
View File
@@ -0,0 +1 @@
{"fileNames":[],"fileInfos":[],"root":[],"options":{"composite":true,"declarationMap":true,"emitDeclarationOnly":true,"importHelpers":true,"module":99,"noEmitOnError":true,"noFallthroughCasesInSwitch":true,"noImplicitOverride":true,"noImplicitReturns":true,"noUnusedLocals":true,"skipLibCheck":true,"strict":true,"target":9},"version":"5.8.3"}
@@ -18,6 +18,10 @@ import {
faRotateLeft,
} from "@fortawesome/pro-solid-svg-icons";
import { useEnterToGenerateStore } from "@storyteller/ui-promptbox";
import {
getAskLocationBeforeDownload,
setAskLocationBeforeDownload,
} from "@storyteller/api";
interface MiscSettingsPaneProps {}
@@ -29,6 +33,14 @@ export const MiscSettingsPane = (args: MiscSettingsPaneProps) => {
const enterToGenerate = useEnterToGenerateStore((s) => s.enabled);
const setEnterToGenerate = useEnterToGenerateStore((s) => s.setEnabled);
const [askLocationBeforeDownload, setAskLocationBeforeDownloadState] =
useState<boolean>(() => getAskLocationBeforeDownload());
const toggleAskLocationBeforeDownload = (enabled: boolean) => {
setAskLocationBeforeDownload(enabled);
setAskLocationBeforeDownloadState(enabled);
};
useEffect(() => {
const fetchData = async () => {
const prefs = await GetAppPreferences();
@@ -113,6 +125,23 @@ export const MiscSettingsPane = (args: MiscSettingsPaneProps) => {
Show Directory
</Button>
</div>
<div className="flex flex-col gap-2 pt-3">
<div className="flex flex-col gap-0.5">
<Label htmlFor="ask-location-before-download">
Ask location before download
</Label>
<p className="text-xs opacity-70">
When on, a system file picker appears every time you download from
the lightbox or anywhere in the app, letting you choose the save
location for that file. When off, downloads go straight to the
default download directory above.
</p>
</div>
<Switch
enabled={askLocationBeforeDownload}
setEnabled={toggleAskLocationBeforeDownload}
/>
</div>
<div className="flex flex-col gap-2 pt-3">
<div className="flex flex-col gap-0.5">
<Label htmlFor="enter-to-generate">Enter to generate</Label>
@@ -0,0 +1 @@
{"fileNames":[],"fileInfos":[],"root":[],"options":{"composite":true,"declarationMap":true,"emitDeclarationOnly":true,"importHelpers":true,"module":99,"noEmitOnError":true,"noFallthroughCasesInSwitch":true,"noImplicitOverride":true,"noImplicitReturns":true,"noUnusedLocals":true,"skipLibCheck":true,"strict":true,"target":9},"version":"5.8.3"}
+1
View File
@@ -476,6 +476,7 @@
"name": "@storyteller/api",
"version": "0.0.1",
"dependencies": {
"@tauri-apps/plugin-dialog": "^2.2.1",
"@tauri-apps/plugin-upload": "2.2.1"
}
},