diff --git a/src-tauri/src/core/cfgmanager/mod.rs b/src-tauri/src/core/cfgmanager/mod.rs index 899f67bc..8fa3ae5e 100644 --- a/src-tauri/src/core/cfgmanager/mod.rs +++ b/src-tauri/src/core/cfgmanager/mod.rs @@ -63,8 +63,6 @@ impl MakuConfigManager { let data = serde_json::to_string(&self.config) .unwrap(); - dbg!(&data); - let mut writer = File::create(&self.path) .expect("cannot create the file writer"); diff --git a/src/App.tsx b/src/App.tsx index b488a0c5..d20f276e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,6 +3,7 @@ import { useRoutes } from 'react-router-dom'; import { Notifications } from '@mantine/notifications'; import { Box, MantineProvider, AppShell } from '@mantine/core'; import { ContextMenuProvider } from 'mantine-contextmenu'; +import { useViewportSize } from '@mantine/hooks'; import { invoke } from '@tauri-apps/api/tauri'; @@ -12,7 +13,7 @@ import { CreateTagModal } from '@modals/tag'; import { CreateResourceModal } from '@modals/resource'; import { ImportCategoryModal, CreateCategoryModal } from '@modals/category'; import { MainHeader } from '@components/header'; -import { useViewportSize } from '@mantine/hooks'; +import { useConfigRedux } from '@store/global'; import { ROUTE_OBJECTS } from './router/RoutingTable'; @@ -29,10 +30,25 @@ import classes from './App.module.scss'; function App() { const routes = useRoutes(ROUTE_OBJECTS); + const { configChanged, reloadConfig } = useConfigRedux(); const [theme, setTheme] = useState(false); const [isConnected, setIsConnected] = useState(false); const { height, width } = useViewportSize(); + let isInitial = true; + + useEffect(() => { + if (isInitial) { + reloadConfig(); + isInitial = false; + return; + } + + if (configChanged) { + reloadConfig(); + } + }, [configChanged, reloadConfig]); + useEffect(() => { if (isConnected === false) { invoke('connect_db') diff --git a/src/api/config/Dto.ts b/src/api/config/Dto.ts new file mode 100644 index 00000000..27fc202d --- /dev/null +++ b/src/api/config/Dto.ts @@ -0,0 +1,9 @@ +import { SupportLangsType } from '@modules/i18next'; + +export interface UpdateConfigDto { + lang?: SupportLangsType, +} + +export interface ConfigResDto { + lang: SupportLangsType, +} diff --git a/src/api/config/configAPI.ts b/src/api/config/configAPI.ts new file mode 100644 index 00000000..42b98c6b --- /dev/null +++ b/src/api/config/configAPI.ts @@ -0,0 +1,13 @@ +import { invoke } from '@tauri-apps/api/tauri'; +import { ConfigResDto, UpdateConfigDto } from './Dto'; + +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace ConfigAPI { + export function get() { + return invoke('get_config'); + } + + export function update(data: UpdateConfigDto) { + return invoke('update_config', { data }); + } +} diff --git a/src/api/config/index.ts b/src/api/config/index.ts new file mode 100644 index 00000000..d63b8ad3 --- /dev/null +++ b/src/api/config/index.ts @@ -0,0 +1,4 @@ +export * from './configAPI'; +export * from './Dto'; +export * from './configMutaion'; +export * from './configQuery'; diff --git a/src/components/header/MainHeader.tsx b/src/components/header/MainHeader.tsx index 0b654008..1dae29f7 100644 --- a/src/components/header/MainHeader.tsx +++ b/src/components/header/MainHeader.tsx @@ -1,4 +1,4 @@ -import { PropsWithChildren } from 'react'; +import { PropsWithChildren, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { Button, Divider, Group, Image, Menu, Select, Tooltip, UnstyledButton, @@ -7,7 +7,8 @@ import { LuImport } from 'react-icons/lu'; import { BsGear } from 'react-icons/bs'; import { AiOutlineThunderbolt } from 'react-icons/ai'; import { MdLanguage } from 'react-icons/md'; -import { SupportLangs, defaultLang } from '@modules/i18next'; +import { useConfigRedux } from '@store/global'; +import { SupportLangs, defaultLang, SupportLangsType } from '@modules/i18next'; import { useHomeNavigate } from '@router/navigateHook'; import { useImportCategoryModal } from '@store/modal'; @@ -55,9 +56,16 @@ function HeaderMenuItem(props: PropsWithChildren) { export function MainHeader() { const { t, i18n } = useTranslation('common', { keyPrefix: 'Header.MainHeader' }); + const { config, updateConfig } = useConfigRedux(); const navigateToHome = useHomeNavigate(); const [_, { open }] = useImportCategoryModal(); + useEffect(() => { + if (config) { + i18n.changeLanguage(config.lang); + } + }, [i18n, config]); + return ( @@ -101,13 +109,16 @@ export function MainHeader() { rightSectionPointerEvents="none" rightSectionWidth={0} styles={{ dropdown: { maxHeight: 200, overflowY: 'auto' } }} - defaultValue={defaultLang.key} + defaultValue={config?.lang ?? defaultLang.key} + value={config?.lang} data={Object.values(SupportLangs).map((val) => ({ value: val.key, label: val.displayName, disabled: i18n.language === val.key, }))} - onChange={(val) => i18n.changeLanguage(val!)} + onChange={(val) => { + updateConfig({ lang: val as SupportLangsType }); + }} /> ); diff --git a/src/store/global/global.hook.ts b/src/store/global/global.hook.ts index 47212bc8..906f5457 100644 --- a/src/store/global/global.hook.ts +++ b/src/store/global/global.hook.ts @@ -1,7 +1,8 @@ -import { useMemo } from 'react'; +import { useCallback, useMemo } from 'react'; +import { UpdateConfigDto } from '@api/config'; import { useGlobalDispatch, useGlobalSelector } from '../hook'; import GlobalLocalStorage from './global.local'; -import { ActiveCategory, setActiveCategory as setActiveCategoryRedux } from './global.slice'; +import { ActiveCategory, fetchConfigThunk, setActiveCategory as setActiveCategoryRedux, updateConfigThunk } from './global.slice'; export function useActiveCategoryRedux() { const { activeCategory: categoryRedux } = useGlobalSelector(); @@ -16,3 +17,20 @@ export function useActiveCategoryRedux() { return { activeCategory, setActiveCategory }; } + +export function useConfigRedux() { + const { config, configChanged } = useGlobalSelector(); + const dispatch = useGlobalDispatch(); + + const reloadConfig = useCallback(() => { + dispatch(fetchConfigThunk()); + }, [dispatch]); + + const updateConfig = useCallback((data: UpdateConfigDto) => { + dispatch(updateConfigThunk(data)); + }, [dispatch]); + + return { + config, reloadConfig, updateConfig, configChanged, + }; +} diff --git a/src/store/global/global.slice.ts b/src/store/global/global.slice.ts index 938bc7c9..ece3cb0f 100644 --- a/src/store/global/global.slice.ts +++ b/src/store/global/global.slice.ts @@ -1,4 +1,15 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { ConfigAPI, ConfigResDto, UpdateConfigDto } from '@api/config'; +import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; + +export const fetchConfigThunk = createAsyncThunk('global/config/fetch', async (_none) => { + const config = await ConfigAPI.get(); + return config; +}); + +export const updateConfigThunk = createAsyncThunk('global/config/update', async (data: UpdateConfigDto) => { + const response = await ConfigAPI.update(data); + return response; +}); export interface ActiveCategory { id: string, @@ -7,9 +18,15 @@ export interface ActiveCategory { export interface GlobalState { activeCategory: ActiveCategory | null; + config: ConfigResDto | null; + configChanged: boolean; } -const initialState: GlobalState = { activeCategory: null }; +const initialState: GlobalState = { + activeCategory: null, + config: null, + configChanged: false, +}; const globalSlice = createSlice({ name: 'global', @@ -19,6 +36,15 @@ const globalSlice = createSlice({ state.activeCategory = action.payload; }, }, + extraReducers(builder) { + builder.addCase(fetchConfigThunk.fulfilled, (state, action) => { + state.config = action.payload; + state.configChanged = false; + }); + builder.addCase(updateConfigThunk.fulfilled, (state, _action) => { + state.configChanged = true; + }); + }, }); export const { setActiveCategory } = globalSlice.actions;