feat: add config

main
spacemeowx2 2023-03-06 21:32:00 +08:00 committed by imspace
parent 01c0478a15
commit 830456ea62
8 changed files with 125 additions and 11 deletions

View File

@ -13,7 +13,7 @@ edition = "2021"
tauri-build = { version = "1.2", features = [] } tauri-build = { version = "1.2", features = [] }
[dependencies] [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 = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
tokio = { version = "1.0", features = ["time"] } tokio = { version = "1.0", features = ["time"] }

View File

@ -39,10 +39,14 @@
}, },
"fs": { "fs": {
"scope": [ "scope": [
"$APPCONFIG", "$APPCONFIG/*",
"$APPDATA", "$APPDATA/*",
"$APPCACHE" "$APPCACHE/*"
] ],
"all": true
},
"path": {
"all": true
} }
}, },
"bundle": { "bundle": {

View File

@ -1,3 +1,4 @@
import 'i18n/config';
import { useEffect } from "react"; import { useEffect } from "react";
import { getCurrent } from "@tauri-apps/api/window"; import { getCurrent } from "@tauri-apps/api/window";
import { Routes, Route } from "react-router-dom"; import { Routes, Route } from "react-router-dom";

View File

@ -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 <p>Loading...</p>;
* }
* if (error) {
* return <p>Error: {error.message}</p>;
* }
* return <p>Result: {result}</p>;
*/
export function usePromise<T>(factory: () => Promise<T>) {
const [loading, setLoading] = useState(true);
const [result, setResult] = useState<T | undefined>(undefined);
const [error, setError] = useState<any | undefined>(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 };
}

View File

@ -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]);
};

View File

@ -1,17 +1,24 @@
import { usePromise } from 'hooks/usePromise';
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { AiOutlineLeft } from 'react-icons/ai'; import { AiOutlineLeft } from 'react-icons/ai';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { getConfig } from 'services/config';
export const Settings: React.FC = () => { export const Settings: React.FC = () => {
const { loading, result, error } = usePromise(getConfig);
const navigate = useNavigate(); const navigate = useNavigate();
const { t } = useTranslation(); const { t } = useTranslation();
const onSave = async () => { const onSave = async () => {
} }
if (loading) {
return <div className='card m-2 h-full'>{t('加载中...')}</div>
}
return <> return <>
<div className='card m-2 h-full'> <div className='card m-2 h-full'>
<h2 className="card-title text-center w-full"><button onClick={() => navigate('/')}><AiOutlineLeft /></button>{t('配置')}</h2> <h2 className="card-title" data-tauri-drag-region><button onClick={() => navigate('/')}><AiOutlineLeft /></button>{t('配置')}</h2>
<div className='card'> <div className='card'>
<div className="form-control w-full max-w-xs mb-4"> <div className="form-control w-full max-w-xs mb-4">
<label className="label"> <label className="label">
@ -27,7 +34,7 @@ export const Settings: React.FC = () => {
<input type="text" placeholder={t('长度为43') ?? undefined} className="input input-bordered w-full max-w-xs" /> <input type="text" placeholder={t('长度为43') ?? undefined} className="input input-bordered w-full max-w-xs" />
</div> </div>
</div> </div>
<button className='btn btn-primary' onClick={onSave}>{t('保存')}</button> <button className='btn btn-primary w-20' onClick={onSave}>{t('保存')}</button>
</div> </div>
</> </>
} }

View File

@ -1,8 +1,35 @@
import { fs } from "@tauri-apps/api" import { fs } from "@tauri-apps/api"
import { appConfigDir, join } from '@tauri-apps/api/path' import { appConfigDir, join } from '@tauri-apps/api/path'
import { State } from '../../../src/state';
const configDir = appConfigDir().then(c => join(c, 'config.json')); const configFile = appConfigDir().then(c => join(c, 'config.json'));
const profileDir = appConfigDir().then(c => join(c, 'profile'));
export const useConfig = () => {
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<Config> {
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));
} }

View File

@ -5,7 +5,11 @@ import eslint from 'vite-plugin-eslint';
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ 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` // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
// prevent vite from obscuring rust errors // prevent vite from obscuring rust errors