Skip to content

Commit d2ad01a

Browse files
committed
Client App Fix Issue [Bug] 'export' button does not work #2884
[+] fix(exporter.tsx): add async keyword to download function [+] feat(exporter.tsx): add support for saving image file using window.__TAURI__ API [+] feat(global.d.ts): add types for window.__TAURI__ API methods [+] feat(locales): add translations for download success and failure messages [+] feat(sync.ts): add support for generating backup file name with date and time [+] fix(utils.ts): add async keyword to downloadAs function and add support for saving file using window.__TAURI__ API
1 parent 64a17ab commit d2ad01a

File tree

7 files changed

+106
-22
lines changed

7 files changed

+106
-22
lines changed

app/components/exporter.tsx

+44-14
Original file line numberDiff line numberDiff line change
@@ -433,25 +433,55 @@ export function ImagePreviewer(props: {
433433

434434
const isMobile = useMobileScreen();
435435

436-
const download = () => {
436+
const download = async () => {
437437
showToast(Locale.Export.Image.Toast);
438438
const dom = previewRef.current;
439439
if (!dom) return;
440-
toPng(dom)
441-
.then((blob) => {
442-
if (!blob) return;
443-
444-
if (isMobile || getClientConfig()?.isApp) {
445-
showImageModal(blob);
440+
441+
const isApp = getClientConfig()?.isApp;
442+
443+
try {
444+
const blob = await toPng(dom);
445+
if (!blob) return;
446+
447+
if (isMobile || (isApp && window.__TAURI__)) {
448+
if (isApp && window.__TAURI__) {
449+
const result = await window.__TAURI__.dialog.save({
450+
defaultPath: `${props.topic}.png`,
451+
filters: [
452+
{
453+
name: "PNG Files",
454+
extensions: ["png"],
455+
},
456+
{
457+
name: "All Files",
458+
extensions: ["*"],
459+
},
460+
],
461+
});
462+
463+
if (result !== null) {
464+
const response = await fetch(blob);
465+
const buffer = await response.arrayBuffer();
466+
const uint8Array = new Uint8Array(buffer);
467+
await window.__TAURI__.fs.writeBinaryFile(result, uint8Array);
468+
showToast(Locale.Download.Success);
469+
} else {
470+
showToast(Locale.Download.Failed);
471+
}
446472
} else {
447-
const link = document.createElement("a");
448-
link.download = `${props.topic}.png`;
449-
link.href = blob;
450-
link.click();
451-
refreshPreview();
473+
showImageModal(blob);
452474
}
453-
})
454-
.catch((e) => console.log("[Export Image] ", e));
475+
} else {
476+
const link = document.createElement("a");
477+
link.download = `${props.topic}.png`;
478+
link.href = blob;
479+
link.click();
480+
refreshPreview();
481+
}
482+
} catch (error) {
483+
showToast(Locale.Download.Failed);
484+
}
455485
};
456486

457487
const refreshPreview = () => {

app/global.d.ts

+7
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ declare module "*.svg";
1313
declare interface Window {
1414
__TAURI__?: {
1515
writeText(text: string): Promise<void>;
16+
invoke(command: string, payload?: Record<string, unknown>): Promise<any>;
17+
dialog: {
18+
save(options?: Record<string, unknown>): Promise<string | null>;
19+
};
20+
fs: {
21+
writeBinaryFile(path: string, data: Uint8Array): Promise<void>;
22+
};
1623
notification:{
1724
requestPermission(): Promise<Permission>;
1825
isPermissionGranted(): Promise<boolean>;

app/locales/cn.ts

+4
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,10 @@ const cn = {
323323
Success: "已写入剪切板",
324324
Failed: "复制失败,请赋予剪切板权限",
325325
},
326+
Download: {
327+
Success: "内容已下载到您的目录。",
328+
Failed: "下载失败。",
329+
},
326330
Context: {
327331
Toast: (x: any) => `包含 ${x} 条预设提示词`,
328332
Edit: "当前对话设置",

app/locales/en.ts

+4
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,10 @@ const en: LocaleType = {
329329
Success: "Copied to clipboard",
330330
Failed: "Copy failed, please grant permission to access clipboard",
331331
},
332+
Download: {
333+
Success: "Content downloaded to your directory.",
334+
Failed: "Download failed.",
335+
},
332336
Context: {
333337
Toast: (x: any) => `With ${x} contextual prompts`,
334338
Edit: "Current Chat Settings",

app/locales/id.ts

+4
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,10 @@ const id: PartialLocaleType = {
301301
Failed:
302302
"Gagal menyalin, mohon berikan izin untuk mengakses clipboard atau Clipboard API tidak didukung (Tauri)",
303303
},
304+
Download: {
305+
Success: "Konten berhasil diunduh ke direktori Anda.",
306+
Failed: "Unduhan gagal.",
307+
},
304308
Context: {
305309
Toast: (x: any) => `Dengan ${x} promp kontekstual`,
306310
Edit: "Pengaturan Obrolan Saat Ini",

app/store/sync.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { getClientConfig } from "../config/client";
12
import { Updater } from "../typing";
23
import { ApiPath, STORAGE_KEY, StoreKey } from "../constant";
34
import { createPersistStore } from "../utils/store";
@@ -20,6 +21,7 @@ export interface WebDavConfig {
2021
password: string;
2122
}
2223

24+
const isApp = !!getClientConfig()?.isApp;
2325
export type SyncStore = GetStoreState<typeof useSyncStore>;
2426

2527
const DEFAULT_SYNC_STATE = {
@@ -57,7 +59,11 @@ export const useSyncStore = createPersistStore(
5759

5860
export() {
5961
const state = getLocalAppState();
60-
const fileName = `Backup-${new Date().toLocaleString()}.json`;
62+
const datePart = isApp
63+
? `${new Date().toLocaleDateString().replace(/\//g, '_')} ${new Date().toLocaleTimeString().replace(/:/g, '_')}`
64+
: new Date().toLocaleString();
65+
66+
const fileName = `Backup-${datePart}.json`;
6167
downloadAs(JSON.stringify(state), fileName);
6268
},
6369

app/utils.ts

+36-7
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,41 @@ export async function copyToClipboard(text: string) {
3131
}
3232
}
3333

34-
export function downloadAs(text: string, filename: string) {
35-
const element = document.createElement("a");
36-
element.setAttribute(
37-
"href",
38-
"data:text/plain;charset=utf-8," + encodeURIComponent(text),
39-
);
34+
export async function downloadAs(text: string, filename: string) {
35+
if (window.__TAURI__) {
36+
const result = await window.__TAURI__.dialog.save({
37+
defaultPath: `${filename}`,
38+
filters: [
39+
{
40+
name: `${filename.split('.').pop()} files`,
41+
extensions: [`${filename.split('.').pop()}`],
42+
},
43+
{
44+
name: "All Files",
45+
extensions: ["*"],
46+
},
47+
],
48+
});
49+
50+
if (result !== null) {
51+
try {
52+
await window.__TAURI__.fs.writeBinaryFile(
53+
result,
54+
new Uint8Array([...text].map((c) => c.charCodeAt(0)))
55+
);
56+
showToast(Locale.Download.Success);
57+
} catch (error) {
58+
showToast(Locale.Download.Failed);
59+
}
60+
} else {
61+
showToast(Locale.Download.Failed);
62+
}
63+
} else {
64+
const element = document.createElement("a");
65+
element.setAttribute(
66+
"href",
67+
"data:text/plain;charset=utf-8," + encodeURIComponent(text),
68+
);
4069
element.setAttribute("download", filename);
4170

4271
element.style.display = "none";
@@ -46,7 +75,7 @@ export function downloadAs(text: string, filename: string) {
4675

4776
document.body.removeChild(element);
4877
}
49-
78+
}
5079
export function readFromFile() {
5180
return new Promise<string>((res, rej) => {
5281
const fileInput = document.createElement("input");

0 commit comments

Comments
 (0)