This commit is contained in:
Simon
2025-07-21 14:00:20 +02:00
committed by GitHub
parent 3a35adf545
commit 33aeb038bc
6 changed files with 84 additions and 28 deletions
+7
View File
@@ -13,11 +13,18 @@ import {initializeScanState} from "Frontend/state/ScanState";
import {ToastProvider} from "@heroui/toast"; import {ToastProvider} from "@heroui/toast";
import {initializePluginState} from "Frontend/state/PluginState"; import {initializePluginState} from "Frontend/state/PluginState";
import {isAdmin} from "Frontend/util/utils"; import {isAdmin} from "Frontend/util/utils";
import {useRouteMetadata} from "Frontend/util/routing";
import {useEffect} from "react";
export default function App() { export default function App() {
client.middlewares = [ErrorHandlingMiddleware]; client.middlewares = [ErrorHandlingMiddleware];
const navigate = useNavigate(); const navigate = useNavigate();
const routeMetadata = useRouteMetadata();
useEffect(() => {
document.title = routeMetadata?.title ?? "Gameyfin";
}, [routeMetadata, window.location.href]);
return ( return (
<HeroUIProvider className="size-full" navigate={navigate} useHref={useHref}> <HeroUIProvider className="size-full" navigate={navigate} useHref={useHref}>
+65 -17
View File
@@ -38,11 +38,13 @@ export const {router, routes} = new RouterConfigurationBuilder()
}, },
{ {
path: 'search', path: 'search',
element: <SearchView/> element: <SearchView/>,
handle: {title: 'Search'}
}, },
{ {
path: 'recently-added', path: 'recently-added',
element: <RecentlyAddedView/> element: <RecentlyAddedView/>,
handle: {title: 'Recently Added'}
}, },
{ {
path: 'library/:libraryId', path: 'library/:libraryId',
@@ -55,47 +57,93 @@ export const {router, routes} = new RouterConfigurationBuilder()
{ {
path: 'settings', path: 'settings',
element: <ProfileView/>, element: <ProfileView/>,
handle: {title: 'Profile'},
children: [ children: [
{path: 'profile', element: <ProfileManagement/>}, {
{path: 'appearance', element: <ThemeSelector/>} path: 'profile',
element: <ProfileManagement/>,
handle: {title: 'Profile Settings'}
},
{
path: 'appearance',
element: <ThemeSelector/>,
handle: {title: 'Appearance'}
}
] ]
}, },
{ {
path: 'administration', path: 'administration',
element: <AdministrationView/>, element: <AdministrationView/>,
handle: {title: 'Administration'},
children: [ children: [
{ {
path: 'libraries', path: 'libraries',
element: <LibraryManagement/> element: <LibraryManagement/>,
handle: {title: 'Administration - Libraries'}
}, },
{ {
path: 'libraries/library/:libraryId', path: 'libraries/library/:libraryId',
element: <LibraryManagementView/> element: <LibraryManagementView/>,
handle: {title: 'Administration - Library'}
}, },
{path: 'users', element: <UserManagement/>}, {
{path: 'sso', element: <SsoManagement/>}, path: 'users',
{path: 'messages', element: <MessageManagement/>}, element: <UserManagement/>,
{path: 'plugins', element: <PluginManagement/>}, handle: {title: 'Administration - Users'}
{path: 'logs', element: <LogManagement/>}, },
{path: 'system', element: <SystemManagement/>} {
path: 'sso',
element: <SsoManagement/>,
handle: {title: 'Administration - SSO'}
},
{
path: 'messages',
element: <MessageManagement/>,
handle: {title: 'Administration - Messages'}
},
{
path: 'plugins',
element: <PluginManagement/>,
handle: {title: 'Administration - Plugins'}
},
{
path: 'logs',
element: <LogManagement/>,
handle: {title: 'Administration - Logs'}
},
{
path: 'system',
element: <SystemManagement/>,
handle: {title: 'Administration - System'}
}
] ]
} }
] ]
}, },
{ {
path: 'login', element: <LoginView/> path: 'login',
element: <LoginView/>,
handle: {title: 'Login'}
}, },
{ {
path: 'setup', element: <SetupView/> path: 'setup',
element: <SetupView/>,
handle: {title: 'Setup'}
}, },
{ {
path: 'accept-invitation', element: <InvitationRegistrationView/> path: 'accept-invitation',
element: <InvitationRegistrationView/>,
handle: {title: 'You have been invited to Gameyfin!'}
}, },
{ {
path: 'reset-password', element: <PasswordResetView/> path: 'reset-password',
element: <PasswordResetView/>,
handle: {title: 'Reset Password'}
}, },
{ {
path: 'confirm-email', element: <EmailConfirmationView/> path: 'confirm-email',
element: <EmailConfirmationView/>,
handle: {title: 'Confirm Email'}
}, },
] ]
} }
+10 -4
View File
@@ -5,11 +5,17 @@ type RouteMetadata = {
}; };
/** /**
* Returns the `handle` object containing the metadata for the current route, * Returns the closest `handle` object with a `title` property from the current route or its parents.
* or undefined if the route does not have defined a handle.
*/ */
export function useRouteMetadata(): RouteMetadata | undefined { export function useRouteMetadata(): RouteMetadata | undefined {
const matches = useMatches(); const matches = useMatches();
const match = matches[matches.length - 1]; // Walk up from the deepest match to the root
return match?.handle as RouteMetadata | undefined; 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;
} }
+1
View File
@@ -53,6 +53,7 @@ export default function GameView() {
if (!gameId || !state.state[parseInt(gameId)]) { if (!gameId || !state.state[parseInt(gameId)]) {
navigate("/", {replace: true}); navigate("/", {replace: true});
} }
document.title = game ? game.title : "Gameyfin";
}); });
}, [gameId]); }, [gameId]);
@@ -17,6 +17,7 @@ export default function LibraryView() {
if (!libraryId || !state.state[parseInt(libraryId)]) { if (!libraryId || !state.state[parseInt(libraryId)]) {
navigate("/", {replace: true}); navigate("/", {replace: true});
} }
document.title = state.state[parseInt(libraryId!!)]?.name || "Gameyfin";
}); });
}, [libraryId]); }, [libraryId]);
@@ -1,4 +1,3 @@
import {useRouteMetadata} from 'Frontend/util/routing.js';
import {useEffect, useState} from 'react'; import {useEffect, useState} from 'react';
import ProfileMenu from "Frontend/components/ProfileMenu"; import ProfileMenu from "Frontend/components/ProfileMenu";
import {Button, Divider, Link, Navbar, NavbarBrand, NavbarContent, NavbarItem, Tooltip} from "@heroui/react"; import {Button, Divider, Link, Navbar, NavbarBrand, NavbarContent, NavbarItem, Tooltip} from "@heroui/react";
@@ -21,18 +20,12 @@ export default function MainLayout() {
const location = useLocation(); const location = useLocation();
const auth = useAuth(); const auth = useAuth();
const userPreferenceService = useUserPreferenceService(); const userPreferenceService = useUserPreferenceService();
const routeMetadata = useRouteMetadata();
const {setTheme} = useTheme(); const {setTheme} = useTheme();
const isSearchPage = location.pathname.startsWith("/search"); const isSearchPage = location.pathname.startsWith("/search");
const isHomePage = location.pathname === "/"; const isHomePage = location.pathname === "/";
const [isExploding, setIsExploding] = useState(false); const [isExploding, setIsExploding] = useState(false);
const games = useSnapshot(gameState).games; const games = useSnapshot(gameState).games;
useEffect(() => {
let newTitle = `Gameyfin - ${routeMetadata?.title}`;
window.addEventListener('popstate', () => document.title = newTitle);
}, []);
useEffect(() => { useEffect(() => {
userPreferenceService.sync() userPreferenceService.sync()
.then(() => loadUserTheme().catch(console.error)) .then(() => loadUserTheme().catch(console.error))