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