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
This commit is contained in:
H0llyW00dzZ 2023-10-03 08:49:03 +07:00
parent 64a17abfe2
commit d2ad01a9ff
No known key found for this signature in database
GPG Key ID: 05C7FFFC0845C930
7 changed files with 106 additions and 22 deletions

View File

@ -433,25 +433,55 @@ export function ImagePreviewer(props: {
const isMobile = useMobileScreen(); const isMobile = useMobileScreen();
const download = () => { const download = async () => {
showToast(Locale.Export.Image.Toast); showToast(Locale.Export.Image.Toast);
const dom = previewRef.current; const dom = previewRef.current;
if (!dom) return; if (!dom) return;
toPng(dom)
.then((blob) => {
if (!blob) return;
if (isMobile || getClientConfig()?.isApp) { const isApp = getClientConfig()?.isApp;
showImageModal(blob);
try {
const blob = await toPng(dom);
if (!blob) return;
if (isMobile || (isApp && window.__TAURI__)) {
if (isApp && window.__TAURI__) {
const result = await window.__TAURI__.dialog.save({
defaultPath: `${props.topic}.png`,
filters: [
{
name: "PNG Files",
extensions: ["png"],
},
{
name: "All Files",
extensions: ["*"],
},
],
});
if (result !== null) {
const response = await fetch(blob);
const buffer = await response.arrayBuffer();
const uint8Array = new Uint8Array(buffer);
await window.__TAURI__.fs.writeBinaryFile(result, uint8Array);
showToast(Locale.Download.Success);
} else {
showToast(Locale.Download.Failed);
}
} else { } else {
const link = document.createElement("a"); showImageModal(blob);
link.download = `${props.topic}.png`;
link.href = blob;
link.click();
refreshPreview();
} }
}) } else {
.catch((e) => console.log("[Export Image] ", e)); const link = document.createElement("a");
link.download = `${props.topic}.png`;
link.href = blob;
link.click();
refreshPreview();
}
} catch (error) {
showToast(Locale.Download.Failed);
}
}; };
const refreshPreview = () => { const refreshPreview = () => {

7
app/global.d.ts vendored
View File

@ -13,6 +13,13 @@ declare module "*.svg";
declare interface Window { declare interface Window {
__TAURI__?: { __TAURI__?: {
writeText(text: string): Promise<void>; writeText(text: string): Promise<void>;
invoke(command: string, payload?: Record<string, unknown>): Promise<any>;
dialog: {
save(options?: Record<string, unknown>): Promise<string | null>;
};
fs: {
writeBinaryFile(path: string, data: Uint8Array): Promise<void>;
};
notification:{ notification:{
requestPermission(): Promise<Permission>; requestPermission(): Promise<Permission>;
isPermissionGranted(): Promise<boolean>; isPermissionGranted(): Promise<boolean>;

View File

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

View File

@ -329,6 +329,10 @@ const en: LocaleType = {
Success: "Copied to clipboard", Success: "Copied to clipboard",
Failed: "Copy failed, please grant permission to access clipboard", Failed: "Copy failed, please grant permission to access clipboard",
}, },
Download: {
Success: "Content downloaded to your directory.",
Failed: "Download failed.",
},
Context: { Context: {
Toast: (x: any) => `With ${x} contextual prompts`, Toast: (x: any) => `With ${x} contextual prompts`,
Edit: "Current Chat Settings", Edit: "Current Chat Settings",

View File

@ -301,6 +301,10 @@ const id: PartialLocaleType = {
Failed: Failed:
"Gagal menyalin, mohon berikan izin untuk mengakses clipboard atau Clipboard API tidak didukung (Tauri)", "Gagal menyalin, mohon berikan izin untuk mengakses clipboard atau Clipboard API tidak didukung (Tauri)",
}, },
Download: {
Success: "Konten berhasil diunduh ke direktori Anda.",
Failed: "Unduhan gagal.",
},
Context: { Context: {
Toast: (x: any) => `Dengan ${x} promp kontekstual`, Toast: (x: any) => `Dengan ${x} promp kontekstual`,
Edit: "Pengaturan Obrolan Saat Ini", Edit: "Pengaturan Obrolan Saat Ini",

View File

@ -1,3 +1,4 @@
import { getClientConfig } from "../config/client";
import { Updater } from "../typing"; import { Updater } from "../typing";
import { ApiPath, STORAGE_KEY, StoreKey } from "../constant"; import { ApiPath, STORAGE_KEY, StoreKey } from "../constant";
import { createPersistStore } from "../utils/store"; import { createPersistStore } from "../utils/store";
@ -20,6 +21,7 @@ export interface WebDavConfig {
password: string; password: string;
} }
const isApp = !!getClientConfig()?.isApp;
export type SyncStore = GetStoreState<typeof useSyncStore>; export type SyncStore = GetStoreState<typeof useSyncStore>;
const DEFAULT_SYNC_STATE = { const DEFAULT_SYNC_STATE = {
@ -57,7 +59,11 @@ export const useSyncStore = createPersistStore(
export() { export() {
const state = getLocalAppState(); const state = getLocalAppState();
const fileName = `Backup-${new Date().toLocaleString()}.json`; const datePart = isApp
? `${new Date().toLocaleDateString().replace(/\//g, '_')} ${new Date().toLocaleTimeString().replace(/:/g, '_')}`
: new Date().toLocaleString();
const fileName = `Backup-${datePart}.json`;
downloadAs(JSON.stringify(state), fileName); downloadAs(JSON.stringify(state), fileName);
}, },

View File

@ -31,12 +31,41 @@ export async function copyToClipboard(text: string) {
} }
} }
export function downloadAs(text: string, filename: string) { export async function downloadAs(text: string, filename: string) {
const element = document.createElement("a"); if (window.__TAURI__) {
element.setAttribute( const result = await window.__TAURI__.dialog.save({
"href", defaultPath: `${filename}`,
"data:text/plain;charset=utf-8," + encodeURIComponent(text), filters: [
); {
name: `${filename.split('.').pop()} files`,
extensions: [`${filename.split('.').pop()}`],
},
{
name: "All Files",
extensions: ["*"],
},
],
});
if (result !== null) {
try {
await window.__TAURI__.fs.writeBinaryFile(
result,
new Uint8Array([...text].map((c) => c.charCodeAt(0)))
);
showToast(Locale.Download.Success);
} catch (error) {
showToast(Locale.Download.Failed);
}
} else {
showToast(Locale.Download.Failed);
}
} else {
const element = document.createElement("a");
element.setAttribute(
"href",
"data:text/plain;charset=utf-8," + encodeURIComponent(text),
);
element.setAttribute("download", filename); element.setAttribute("download", filename);
element.style.display = "none"; element.style.display = "none";
@ -46,7 +75,7 @@ export function downloadAs(text: string, filename: string) {
document.body.removeChild(element); document.body.removeChild(element);
} }
}
export function readFromFile() { export function readFromFile() {
return new Promise<string>((res, rej) => { return new Promise<string>((res, rej) => {
const fileInput = document.createElement("input"); const fileInput = document.createElement("input");