diff --git a/gui/src-tauri/Cargo.toml b/gui/src-tauri/Cargo.toml
index 5c46677..2941aa6 100644
--- a/gui/src-tauri/Cargo.toml
+++ b/gui/src-tauri/Cargo.toml
@@ -13,7 +13,7 @@ edition = "2021"
tauri-build = { version = "1.2", features = [] }
[dependencies]
-tauri = { version = "1.2", features = ["shell-execute", "shell-open", "shell-sidecar", "window-all"] }
+tauri = { version = "1.2", features = ["fs-all", "path-all", "shell-execute", "shell-open", "shell-sidecar", "window-all"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1.0", features = ["time"] }
diff --git a/gui/src-tauri/tauri.conf.json b/gui/src-tauri/tauri.conf.json
index bac3573..99a0ebd 100644
--- a/gui/src-tauri/tauri.conf.json
+++ b/gui/src-tauri/tauri.conf.json
@@ -39,10 +39,14 @@
},
"fs": {
"scope": [
- "$APPCONFIG",
- "$APPDATA",
- "$APPCACHE"
- ]
+ "$APPCONFIG/*",
+ "$APPDATA/*",
+ "$APPCACHE/*"
+ ],
+ "all": true
+ },
+ "path": {
+ "all": true
}
},
"bundle": {
diff --git a/gui/src/App.tsx b/gui/src/App.tsx
index bb15a5d..eadea29 100644
--- a/gui/src/App.tsx
+++ b/gui/src/App.tsx
@@ -1,3 +1,4 @@
+import 'i18n/config';
import { useEffect } from "react";
import { getCurrent } from "@tauri-apps/api/window";
import { Routes, Route } from "react-router-dom";
diff --git a/gui/src/hooks/usePromise.ts b/gui/src/hooks/usePromise.ts
new file mode 100644
index 0000000..a89ce41
--- /dev/null
+++ b/gui/src/hooks/usePromise.ts
@@ -0,0 +1,40 @@
+import { useState } from "react";
+
+/**
+ * A hook that returns a promise and its state.
+ *
+ * The promise is only created once, and the state is updated when the promise resolves or rejects.
+ *
+ * @param factory A function that returns a promise.
+ * @returns An object containing the promise's state and result.
+ * @example
+ * const { loading, result, error } = usePromise(() => fetch('https://example.com')
+ * .then(response => response.text())
+ * );
+ * if (loading) {
+ * return
Loading...
;
+ * }
+ * if (error) {
+ * return Error: {error.message}
;
+ * }
+ * return Result: {result}
;
+ */
+export function usePromise(factory: () => Promise) {
+ const [loading, setLoading] = useState(true);
+ const [result, setResult] = useState(undefined);
+ const [error, setError] = useState(undefined);
+ const [promise] = useState(() => {
+ const promise = factory();
+ if (!promise || typeof promise.then !== "function") {
+ throw new Error("The factory function must return a promise.");
+ }
+ return promise
+ .then(setResult)
+ .catch(setError)
+ .finally(() => {
+ setLoading(false);
+ });
+ });
+
+ return { loading, result, error, promise };
+}
diff --git a/gui/src/hooks/useWindowSize.ts b/gui/src/hooks/useWindowSize.ts
new file mode 100644
index 0000000..461cdaa
--- /dev/null
+++ b/gui/src/hooks/useWindowSize.ts
@@ -0,0 +1,31 @@
+import { getCurrent, LogicalSize, appWindow } from '@tauri-apps/api/window'
+import { useEffect, useRef } from 'react';
+
+/**
+ * Sets the window size, and disable resizable, and restores it on unmount.
+ */
+export const useWindowSize = ({ w, h }: { w: number, h: number }) => {
+ const oldSize = useRef<{ w: number, h: number }>();
+
+ useEffect(() => {
+ const run = async () => {
+ const factor = await appWindow.scaleFactor();
+ const outerSize = (await getCurrent().outerSize()).toLogical(factor);
+ oldSize.current = {
+ w: outerSize.width,
+ h: outerSize.height
+ };
+
+ await getCurrent().setResizable(false);
+ await getCurrent().setSize(new LogicalSize(w, h));
+ }
+ run();
+ return () => {
+ const size = oldSize.current;
+ if (size) {
+ getCurrent().setSize(new LogicalSize(size.w, size.h));
+ getCurrent().setResizable(true);
+ }
+ }
+ }, [oldSize, w, h]);
+};
diff --git a/gui/src/pages/Settings.tsx b/gui/src/pages/Settings.tsx
index f823c62..83ff952 100644
--- a/gui/src/pages/Settings.tsx
+++ b/gui/src/pages/Settings.tsx
@@ -1,17 +1,24 @@
+import { usePromise } from 'hooks/usePromise';
import React from 'react'
import { useTranslation } from 'react-i18next';
import { AiOutlineLeft } from 'react-icons/ai';
import { useNavigate } from 'react-router-dom';
+import { getConfig } from 'services/config';
export const Settings: React.FC = () => {
+ const { loading, result, error } = usePromise(getConfig);
const navigate = useNavigate();
const { t } = useTranslation();
const onSave = async () => {
}
+ if (loading) {
+ return {t('加载中...')}
+ }
+
return <>
-
{t('配置')}
+
{t('配置')}
-
+
>
}
\ No newline at end of file
diff --git a/gui/src/services/config.ts b/gui/src/services/config.ts
index 2f06aa4..4e9966b 100644
--- a/gui/src/services/config.ts
+++ b/gui/src/services/config.ts
@@ -1,8 +1,35 @@
import { fs } from "@tauri-apps/api"
import { appConfigDir, join } from '@tauri-apps/api/path'
+import { State } from '../../../src/state';
-const configDir = appConfigDir().then(c => join(c, 'config.json'));
-
-export const useConfig = () => {
+const configFile = appConfigDir().then(c => join(c, 'config.json'));
+const profileDir = appConfigDir().then(c => join(c, 'profile'));
+export type Profile = {
+ state: State,
+}
+
+export type Config = {
+}
+
+const defaultConfig: Config = {
+}
+
+export async function initFiles() {
+ await fs.createDir(await profileDir, { recursive: true });
+ await configFile;
+}
+initFiles().catch(console.error);
+
+export async function getConfig(): Promise {
+ const config = await fs.readTextFile(await configFile);
+ try {
+ return JSON.parse(config);
+ } catch (e) {
+ return defaultConfig;
+ }
+}
+
+export async function setConfig(config: Config) {
+ await fs.writeTextFile(await configFile, JSON.stringify(config));
}
diff --git a/gui/vite.config.ts b/gui/vite.config.ts
index 8493565..cb16e10 100644
--- a/gui/vite.config.ts
+++ b/gui/vite.config.ts
@@ -5,7 +5,11 @@ import eslint from 'vite-plugin-eslint';
// https://vitejs.dev/config/
export default defineConfig({
- plugins: [react(), tsconfigPaths(), eslint()],
+ plugins: [
+ react(),
+ tsconfigPaths(),
+ eslint(),
+ ],
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
// prevent vite from obscuring rust errors