mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-13 16:40:01 +00:00
@@ -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}>
|
||||
|
||||
@@ -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'}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user