diff --git a/gui/.lintstagedrc.mjs b/gui/.lintstagedrc.mjs index aacd49439..1ecc11d27 100644 --- a/gui/.lintstagedrc.mjs +++ b/gui/.lintstagedrc.mjs @@ -1,5 +1,5 @@ export default { "**/*.{ts,tsx}": () => "tsc -p tsconfig.json --noEmit", - "**/*.{js,jsx,ts,tsx}": "eslint --cache --fix", + "**/*.{js,jsx,ts,tsx}": "eslint --max-warnings=0 --cache --fix", "**/*.{js,jsx,ts,tsx,css,md,json}": "prettier --write" }; diff --git a/gui/src/App.tsx b/gui/src/App.tsx index e282539d2..0ad9d22d0 100644 --- a/gui/src/App.tsx +++ b/gui/src/App.tsx @@ -35,7 +35,6 @@ import { SerialDetectionModal } from './components/SerialDetectionModal'; import { VRCOSCSettings } from './components/settings/pages/VRCOSCSettings'; import { TopBar } from './components/TopBar'; import { TrackerSettingsPage } from './components/tracker/TrackerSettings'; -import { useConfig } from './hooks/config'; import { OSCRouterSettings } from './components/settings/pages/OSCRouterSettings'; import { useLocalization } from '@fluent/react'; import * as os from '@tauri-apps/plugin-os'; @@ -52,6 +51,7 @@ import { useBreakpoint } from './hooks/breakpoint'; import { VRModePage } from './components/vr-mode/VRModePage'; import { InterfaceSettings } from './components/settings/pages/InterfaceSettings'; import { error, log } from './utils/logging'; +import { AppLayout } from './AppLayout'; export const GH_REPO = 'SlimeVR/SlimeVR-Server'; export const VersionContext = createContext(''); @@ -59,92 +59,94 @@ export const DOCS_SITE = 'https://docs.slimevr.dev'; export const SLIMEVR_DISCORD = 'https://discord.gg/slimevr'; function Layout() { - const { loading } = useConfig(); - - if (loading) return <>; - const { isMobile } = useBreakpoint('mobile'); + return ( <> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - > - } /> - } /> - } /> - } /> - } /> - } /> + }> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + > + } /> + } /> + } /> + } /> + } /> + } /> + + + + + } + > + } /> + } /> + } /> + } + /> + } + /> + } /> + } /> + }> + } /> + } /> + } /> + } + /> + } + /> + } + /> + } /> + + }> - - - - } - > - } /> - } /> - } /> - } - /> - } /> - } /> - } /> - }> - } /> - } /> - } /> - } - /> - } - /> - } - /> - } /> - - }> ); diff --git a/gui/src/AppLayout.tsx b/gui/src/AppLayout.tsx new file mode 100644 index 000000000..8b4f8b94c --- /dev/null +++ b/gui/src/AppLayout.tsx @@ -0,0 +1,51 @@ +import { useLayoutEffect } from 'react'; +import { useConfig } from './hooks/config'; +import { Outlet, useNavigate } from 'react-router-dom'; + +export function AppLayout() { + const { loading, config } = useConfig(); + const navigate = useNavigate(); + + useLayoutEffect(() => { + if (loading || !config) return; + if (config.theme !== undefined) { + document.documentElement.dataset.theme = config.theme; + } + + if (config.fonts !== undefined) { + document.documentElement.style.setProperty( + '--font-name', + config.fonts.map((x) => `"${x}"`).join(',') + ); + } + + if (config.textSize !== undefined) { + document.documentElement.style.setProperty( + '--font-size', + `${config.textSize}rem` + ); + } + }, [config, loading]); + + useLayoutEffect(() => { + if (config && !config.doneOnboarding) { + navigate('/onboarding/home'); + } + }, [config?.doneOnboarding]); + + // const location = useLocation(); + // const navigationType = useNavigationType(); + // useEffect(() => { + // if (import.meta.env.PROD) return; + // console.log('The current URL is', { ...location }); + // console.log('The last navigation action was', navigationType); + // }, [location, navigationType]); + + if (loading) return <>; + + return ( + <> + + + ); +} diff --git a/gui/src/hooks/app.ts b/gui/src/hooks/app.ts index 591f628d1..bea4ed160 100644 --- a/gui/src/hooks/app.ts +++ b/gui/src/hooks/app.ts @@ -4,11 +4,9 @@ import { Reducer, useContext, useEffect, - useLayoutEffect, useMemo, useReducer, } from 'react'; -import { useNavigate } from 'react-router-dom'; import { BoneT, DataFeedMessage, @@ -59,7 +57,6 @@ export function useProvideAppContext(): AppContext { useWebsocketAPI(); const { config } = useConfig(); const { dataFeedConfig } = useDataFeedConfig(); - const navigate = useNavigate(); const [state, dispatch] = useReducer>(reducer, { datafeed: new DataFeedUpdateT(), }); @@ -72,12 +69,6 @@ export function useProvideAppContext(): AppContext { } }, [isConnected]); - useLayoutEffect(() => { - if (config && !config.doneOnboarding) { - navigate('/onboarding/home'); - } - }, [config]); - const trackers = useMemo( () => (state.datafeed?.devices || []).reduce( diff --git a/gui/src/hooks/config.ts b/gui/src/hooks/config.ts index d1382389a..47178a2ef 100644 --- a/gui/src/hooks/config.ts +++ b/gui/src/hooks/config.ts @@ -1,8 +1,9 @@ import { BaseDirectory, readTextFile } from '@tauri-apps/plugin-fs'; -import { createContext, useContext, useRef, useState } from 'react'; +import { createContext, useContext, useState } from 'react'; import { DeveloperModeWidgetForm } from '../components/widgets/DeveloperModeWidget'; import { error } from '../utils/logging'; +import { useDebouncedEffect } from './timeout'; export interface WindowConfig { width: number; @@ -50,42 +51,28 @@ function fallbackToDefaults(loadedConfig: any): Config { } export function useConfigProvider(): ConfigContext { - const debounceTimer = useRef(null); const [currConfig, set] = useState(null); const [loading, setLoading] = useState(false); + useDebouncedEffect( + () => { + if (!currConfig) return; + + localStorage.setItem('config.json', JSON.stringify(currConfig)); + }, + [currConfig], + 100 + ); + const setConfig = async (config: Partial) => { - const newConfig = config - ? { - ...currConfig, - ...config, - } - : null; - set(newConfig as Config); - if (config.theme !== undefined) { - document.documentElement.dataset.theme = config.theme; - } - - if (config.fonts !== undefined) { - document.documentElement.style.setProperty( - '--font-name', - config.fonts.map((x) => `"${x}"`).join(',') - ); - } - - if (config.textSize !== undefined) { - document.documentElement.style.setProperty( - '--font-size', - `${config.textSize}rem` - ); - } - - if (!debounceTimer.current) { - debounceTimer.current = setTimeout(async () => { - localStorage.setItem('config.json', JSON.stringify(newConfig)); - debounceTimer.current = null; - }, 10); - } + set((curr) => + config + ? ({ + ...curr, + ...config, + } as Config) + : null + ); }; return { @@ -112,15 +99,7 @@ export function useConfigProvider(): ConfigContext { const loadedConfig = fallbackToDefaults(JSON.parse(json)); set(loadedConfig); - document.documentElement.dataset.theme = loadedConfig.theme; - document.documentElement.style.setProperty( - '--font-size', - `${loadedConfig.textSize}rem` - ); - document.documentElement.style.setProperty( - '--font-name', - loadedConfig.fonts.map((x) => `"${x}"`).join(',') - ); + setLoading(false); return loadedConfig; } catch (e) {