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:
parent
64a17abfe2
commit
d2ad01a9ff
|
@ -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 = () => {
|
||||||
|
|
|
@ -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>;
|
||||||
|
|
|
@ -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: "当前对话设置",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
43
app/utils.ts
43
app/utils.ts
|
@ -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");
|
||||||
|
|
Loading…
Reference in New Issue