2026-09-09 18:26:24 +08:00
|
|
|
import { lazy, Suspense, useEffect, useRef } from "react";
|
2026-03-31 18:52:37 +08:00
|
|
|
import { Toaster } from "@/components/ui/sonner";
|
|
|
|
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
2026-09-09 18:26:24 +08:00
|
|
|
import { Route, Switch, Router, useLocation } from "wouter";
|
2026-04-30 10:59:50 +08:00
|
|
|
import { useHashLocation } from "wouter/use-hash-location";
|
2026-03-31 18:52:37 +08:00
|
|
|
import ErrorBoundary from "./components/ErrorBoundary";
|
2026-05-26 19:16:42 +08:00
|
|
|
import PageLoader from "./components/PageLoader";
|
2026-03-31 18:52:37 +08:00
|
|
|
import { ThemeProvider } from "./contexts/ThemeContext";
|
|
|
|
|
import { DeviceProvider } from "./contexts/DeviceContext";
|
|
|
|
|
import Home from "./pages/Home";
|
|
|
|
|
import NotFound from "./pages/NotFound";
|
|
|
|
|
import Dashboard from "./pages/Dashboard";
|
|
|
|
|
import AudioPage from "./pages/AudioPage";
|
|
|
|
|
import BluetoothPage from "./pages/BluetoothPage";
|
|
|
|
|
import SystemPage from "./pages/SystemPage";
|
|
|
|
|
import IOPage from "./pages/IOPage";
|
|
|
|
|
import VUPage from "./pages/VUPage";
|
2026-09-09 18:26:24 +08:00
|
|
|
import SelectPage, { consumeSelectReturnScroll } from "./pages/SelectPage";
|
2026-04-29 17:56:03 +08:00
|
|
|
import CommunityPage from "./pages/CommunityPage";
|
2026-08-06 14:39:39 +08:00
|
|
|
import ChangelogPage from "./pages/ChangelogPage";
|
2026-05-08 18:15:52 +08:00
|
|
|
import { AIDrawerProvider } from "./contexts/AIDrawerContext";
|
|
|
|
|
import DesktopAIShell from "./components/DesktopAIShell";
|
2026-06-03 15:42:58 +08:00
|
|
|
import { appShellColumnClass, appShellOuterClass } from "./lib/appShell";
|
2026-05-08 18:15:52 +08:00
|
|
|
|
2026-05-26 19:16:42 +08:00
|
|
|
const EQPage = lazy(() => import("./pages/EQPage"));
|
|
|
|
|
const EffectsPage = lazy(() => import("./pages/EffectsPage"));
|
|
|
|
|
const AIPage = lazy(() => import("./pages/AIPage"));
|
|
|
|
|
|
|
|
|
|
function LazyRoute({ children }: { children: React.ReactNode }) {
|
|
|
|
|
return <Suspense fallback={<PageLoader />}>{children}</Suspense>;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-08 18:15:52 +08:00
|
|
|
function AIPageRoute() {
|
2026-05-26 19:16:42 +08:00
|
|
|
return (
|
|
|
|
|
<LazyRoute>
|
|
|
|
|
<AIPage />
|
|
|
|
|
</LazyRoute>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function EQPageRoute() {
|
|
|
|
|
return (
|
|
|
|
|
<LazyRoute>
|
|
|
|
|
<EQPage />
|
|
|
|
|
</LazyRoute>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function EffectsPageRoute() {
|
|
|
|
|
return (
|
|
|
|
|
<LazyRoute>
|
|
|
|
|
<EffectsPage />
|
|
|
|
|
</LazyRoute>
|
|
|
|
|
);
|
2026-05-08 18:15:52 +08:00
|
|
|
}
|
2026-03-31 18:52:37 +08:00
|
|
|
|
2026-09-09 18:26:24 +08:00
|
|
|
/**
|
|
|
|
|
* Restores the calling page's scroll position when returning from /select.
|
|
|
|
|
* The select page is short, so the browser clamps window scroll to 0 while on
|
|
|
|
|
* it; navigateToSelect() captures the offset before navigating and we re-apply
|
|
|
|
|
* it once the calling page (e.g. Effects) has remounted.
|
|
|
|
|
*/
|
|
|
|
|
function SelectScrollRestore() {
|
|
|
|
|
const [location] = useLocation();
|
|
|
|
|
const prevRef = useRef(location);
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const prev = prevRef.current;
|
|
|
|
|
prevRef.current = location;
|
|
|
|
|
if (prev !== "/select" || location === "/select") return;
|
|
|
|
|
const y = consumeSelectReturnScroll();
|
|
|
|
|
if (y == null || y <= 0) return;
|
|
|
|
|
// Wait a frame so the returning page has rendered and the document has height.
|
|
|
|
|
requestAnimationFrame(() => window.scrollTo(0, y));
|
|
|
|
|
}, [location]);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-09 17:00:44 +08:00
|
|
|
function AppRouter() {
|
2026-03-31 18:52:37 +08:00
|
|
|
return (
|
|
|
|
|
<Switch>
|
|
|
|
|
<Route path="/" component={Home} />
|
|
|
|
|
<Route path="/dashboard" component={Dashboard} />
|
|
|
|
|
<Route path="/audio" component={AudioPage} />
|
2026-05-26 19:16:42 +08:00
|
|
|
<Route path="/eq" component={EQPageRoute} />
|
|
|
|
|
<Route path="/effects" component={EffectsPageRoute} />
|
2026-03-31 18:52:37 +08:00
|
|
|
<Route path="/bluetooth" component={BluetoothPage} />
|
|
|
|
|
<Route path="/system" component={SystemPage} />
|
|
|
|
|
<Route path="/io" component={IOPage} />
|
|
|
|
|
<Route path="/vu" component={VUPage} />
|
|
|
|
|
<Route path="/select" component={SelectPage} />
|
2026-05-08 18:15:52 +08:00
|
|
|
<Route path="/ai" component={AIPageRoute} />
|
2026-04-29 17:56:03 +08:00
|
|
|
<Route path="/community" component={CommunityPage} />
|
2026-08-06 14:39:39 +08:00
|
|
|
<Route path="/changelog" component={ChangelogPage} />
|
2026-03-31 18:52:37 +08:00
|
|
|
<Route path="/404" component={NotFound} />
|
|
|
|
|
<Route component={NotFound} />
|
|
|
|
|
</Switch>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function App() {
|
|
|
|
|
return (
|
|
|
|
|
<ErrorBoundary>
|
|
|
|
|
<ThemeProvider defaultTheme="dark">
|
|
|
|
|
<DeviceProvider>
|
|
|
|
|
<TooltipProvider>
|
|
|
|
|
<Toaster />
|
2026-04-30 10:59:50 +08:00
|
|
|
<Router hook={useHashLocation}>
|
2026-05-08 18:15:52 +08:00
|
|
|
<AIDrawerProvider>
|
2026-09-09 18:26:24 +08:00
|
|
|
<SelectScrollRestore />
|
2026-06-03 15:42:58 +08:00
|
|
|
<div className={appShellOuterClass}>
|
|
|
|
|
<div className={appShellColumnClass}>
|
2026-05-08 18:15:52 +08:00
|
|
|
<DesktopAIShell>
|
|
|
|
|
<AppRouter />
|
|
|
|
|
</DesktopAIShell>
|
|
|
|
|
</div>
|
2026-04-29 10:14:25 +08:00
|
|
|
</div>
|
2026-05-08 18:15:52 +08:00
|
|
|
</AIDrawerProvider>
|
2026-04-09 17:00:44 +08:00
|
|
|
</Router>
|
2026-03-31 18:52:37 +08:00
|
|
|
</TooltipProvider>
|
|
|
|
|
</DeviceProvider>
|
|
|
|
|
</ThemeProvider>
|
|
|
|
|
</ErrorBoundary>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default App;
|