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 {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 (
<HeroUIProvider className="size-full" navigate={navigate} useHref={useHref}>
+65 -17
View File
@@ -38,11 +38,13 @@ export const {router, routes} = new RouterConfigurationBuilder()
},
{
path: 'search',
element: <SearchView/>
element: <SearchView/>,
handle: {title: 'Search'}
},
{
path: 'recently-added',
element: <RecentlyAddedView/>
element: <RecentlyAddedView/>,
handle: {title: 'Recently Added'}
},
{
path: 'library/:libraryId',
@@ -55,47 +57,93 @@ export const {router, routes} = new RouterConfigurationBuilder()
{
path: 'settings',
element: <ProfileView/>,
handle: {title: 'Profile'},
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',
element: <AdministrationView/>,
handle: {title: 'Administration'},
children: [
{
path: 'libraries',
element: <LibraryManagement/>
element: <LibraryManagement/>,
handle: {title: 'Administration - Libraries'}
},
{
path: 'libraries/library/:libraryId',
element: <LibraryManagementView/>
element: <LibraryManagementView/>,
handle: {title: 'Administration - Library'}
},
{path: 'users', element: <UserManagement/>},
{path: 'sso', element: <SsoManagement/>},
{path: 'messages', element: <MessageManagement/>},
{path: 'plugins', element: <PluginManagement/>},
{path: 'logs', element: <LogManagement/>},
{path: 'system', element: <SystemManagement/>}
{
path: 'users',
element: <UserManagement/>,
handle: {title: 'Administration - Users'}
},
{
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,
* 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;
}
+1
View File
@@ -53,6 +53,7 @@ export default function GameView() {
if (!gameId || !state.state[parseInt(gameId)]) {
navigate("/", {replace: true});
}
document.title = game ? game.title : "Gameyfin";
});
}, [gameId]);
@@ -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]);
@@ -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))