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) {