diff --git a/app/src/main/frontend/App.tsx b/app/src/main/frontend/App.tsx index 1a7abf7..a620430 100644 --- a/app/src/main/frontend/App.tsx +++ b/app/src/main/frontend/App.tsx @@ -13,11 +13,18 @@ import {initializeScanState} from "Frontend/state/ScanState"; import {ToastProvider} from "@heroui/toast"; import {initializePluginState} from "Frontend/state/PluginState"; import {isAdmin} from "Frontend/util/utils"; +import {useRouteMetadata} from "Frontend/util/routing"; +import {useEffect} from "react"; export default function App() { client.middlewares = [ErrorHandlingMiddleware]; const navigate = useNavigate(); + const routeMetadata = useRouteMetadata(); + + useEffect(() => { + document.title = routeMetadata?.title ?? "Gameyfin"; + }, [routeMetadata, window.location.href]); return ( diff --git a/app/src/main/frontend/routes.tsx b/app/src/main/frontend/routes.tsx index 33ede5c..1508e40 100644 --- a/app/src/main/frontend/routes.tsx +++ b/app/src/main/frontend/routes.tsx @@ -38,11 +38,13 @@ export const {router, routes} = new RouterConfigurationBuilder() }, { path: 'search', - element: + element: , + handle: {title: 'Search'} }, { path: 'recently-added', - element: + element: , + handle: {title: 'Recently Added'} }, { path: 'library/:libraryId', @@ -55,47 +57,93 @@ export const {router, routes} = new RouterConfigurationBuilder() { path: 'settings', element: , + handle: {title: 'Profile'}, children: [ - {path: 'profile', element: }, - {path: 'appearance', element: } + { + path: 'profile', + element: , + handle: {title: 'Profile Settings'} + }, + { + path: 'appearance', + element: , + handle: {title: 'Appearance'} + } ] }, { path: 'administration', element: , + handle: {title: 'Administration'}, children: [ { path: 'libraries', - element: + element: , + handle: {title: 'Administration - Libraries'} }, { path: 'libraries/library/:libraryId', - element: + element: , + handle: {title: 'Administration - Library'} }, - {path: 'users', element: }, - {path: 'sso', element: }, - {path: 'messages', element: }, - {path: 'plugins', element: }, - {path: 'logs', element: }, - {path: 'system', element: } + { + path: 'users', + element: , + handle: {title: 'Administration - Users'} + }, + { + path: 'sso', + element: , + handle: {title: 'Administration - SSO'} + }, + { + path: 'messages', + element: , + handle: {title: 'Administration - Messages'} + }, + { + path: 'plugins', + element: , + handle: {title: 'Administration - Plugins'} + }, + { + path: 'logs', + element: , + handle: {title: 'Administration - Logs'} + }, + { + path: 'system', + element: , + handle: {title: 'Administration - System'} + } ] } ] }, { - path: 'login', element: + path: 'login', + element: , + handle: {title: 'Login'} }, { - path: 'setup', element: + path: 'setup', + element: , + handle: {title: 'Setup'} }, { - path: 'accept-invitation', element: + path: 'accept-invitation', + element: , + handle: {title: 'You have been invited to Gameyfin!'} }, { - path: 'reset-password', element: + path: 'reset-password', + element: , + handle: {title: 'Reset Password'} }, { - path: 'confirm-email', element: + path: 'confirm-email', + element: , + handle: {title: 'Confirm Email'} }, ] } diff --git a/app/src/main/frontend/util/routing.ts b/app/src/main/frontend/util/routing.ts index 90703b6..1d7e423 100644 --- a/app/src/main/frontend/util/routing.ts +++ b/app/src/main/frontend/util/routing.ts @@ -5,11 +5,17 @@ type RouteMetadata = { }; /** - * Returns the `handle` object containing the metadata for the current route, - * or undefined if the route does not have defined a handle. + * Returns the closest `handle` object with a `title` property from the current route or its parents. */ export function useRouteMetadata(): RouteMetadata | undefined { const matches = useMatches(); - const match = matches[matches.length - 1]; - return match?.handle as RouteMetadata | undefined; + // Walk up from the deepest match to the root + for (let i = matches.length - 1; i >= 0; i--) { + const handle = matches[i]?.handle as RouteMetadata | undefined; + if (handle?.title) { + return handle; + } + } + // If no title found, return the deepest match's handle (if any) + return matches[matches.length - 1]?.handle as RouteMetadata | undefined; } diff --git a/app/src/main/frontend/views/GameView.tsx b/app/src/main/frontend/views/GameView.tsx index 0f5c840..31fcba1 100644 --- a/app/src/main/frontend/views/GameView.tsx +++ b/app/src/main/frontend/views/GameView.tsx @@ -53,6 +53,7 @@ export default function GameView() { if (!gameId || !state.state[parseInt(gameId)]) { navigate("/", {replace: true}); } + document.title = game ? game.title : "Gameyfin"; }); }, [gameId]); diff --git a/app/src/main/frontend/views/LibraryView.tsx b/app/src/main/frontend/views/LibraryView.tsx index d52badf..9943fa8 100644 --- a/app/src/main/frontend/views/LibraryView.tsx +++ b/app/src/main/frontend/views/LibraryView.tsx @@ -17,6 +17,7 @@ export default function LibraryView() { if (!libraryId || !state.state[parseInt(libraryId)]) { navigate("/", {replace: true}); } + document.title = state.state[parseInt(libraryId!!)]?.name || "Gameyfin"; }); }, [libraryId]); diff --git a/app/src/main/frontend/views/MainLayout.tsx b/app/src/main/frontend/views/MainLayout.tsx index 9f56f43..a0d632e 100644 --- a/app/src/main/frontend/views/MainLayout.tsx +++ b/app/src/main/frontend/views/MainLayout.tsx @@ -1,4 +1,3 @@ -import {useRouteMetadata} from 'Frontend/util/routing.js'; import {useEffect, useState} from 'react'; import ProfileMenu from "Frontend/components/ProfileMenu"; import {Button, Divider, Link, Navbar, NavbarBrand, NavbarContent, NavbarItem, Tooltip} from "@heroui/react"; @@ -21,18 +20,12 @@ export default function MainLayout() { const location = useLocation(); const auth = useAuth(); const userPreferenceService = useUserPreferenceService(); - const routeMetadata = useRouteMetadata(); const {setTheme} = useTheme(); const isSearchPage = location.pathname.startsWith("/search"); const isHomePage = location.pathname === "/"; const [isExploding, setIsExploding] = useState(false); const games = useSnapshot(gameState).games; - useEffect(() => { - let newTitle = `Gameyfin - ${routeMetadata?.title}`; - window.addEventListener('popstate', () => document.title = newTitle); - }, []); - useEffect(() => { userPreferenceService.sync() .then(() => loadUserTheme().catch(console.error))