Implement RecentlyAddedView and LibraryView

This commit is contained in:
grimsi
2025-05-24 19:04:05 +02:00
parent 64f81d1329
commit fe561c42b4
8 changed files with 79 additions and 19 deletions
+3 -2
View File
@@ -141,7 +141,8 @@
"react-markdown": "$react-markdown", "react-markdown": "$react-markdown",
"remark-breaks": "$remark-breaks", "remark-breaks": "$remark-breaks",
"valtio": "$valtio", "valtio": "$valtio",
"valtio-reactive": "$valtio-reactive" "valtio-reactive": "$valtio-reactive",
"fzf": "$fzf"
}, },
"vaadin": { "vaadin": {
"dependencies": { "dependencies": {
@@ -202,6 +203,6 @@
"workbox-core": "7.3.0", "workbox-core": "7.3.0",
"workbox-precaching": "7.3.0" "workbox-precaching": "7.3.0"
}, },
"hash": "dc682332ca36d64f455f6e13888e1ffcca97e888cbad8d356973e830f7463a10" "hash": "5946db89f1666178a141f4993199185e21c2d2fba31f8cbe64550829f8450c78"
} }
} }
@@ -10,7 +10,7 @@ export default function SearchBar() {
const navigate = useNavigate(); const navigate = useNavigate();
const state = useSnapshot(gameState); const state = useSnapshot(gameState);
const games = state.sortedByMostRecentlyUpdated as GameDto[]; const games = state.recentlyUpdated as GameDto[];
return <Autocomplete return <Autocomplete
aria-label="Search for games" aria-label="Search for games"
+14 -4
View File
@@ -22,6 +22,8 @@ import {SystemManagement} from "Frontend/components/administration/SystemManagem
import GameView from "Frontend/views/GameView"; import GameView from "Frontend/views/GameView";
import LibraryManagementView from "Frontend/views/LibraryManagementView"; import LibraryManagementView from "Frontend/views/LibraryManagementView";
import SearchView from "Frontend/views/SearchView"; import SearchView from "Frontend/views/SearchView";
import RecentlyAddedView from "Frontend/views/RecentlyAddedView";
import LibraryView from "Frontend/views/LibraryView";
export const routes = protectRoutes([ export const routes = protectRoutes([
{ {
@@ -35,14 +37,22 @@ export const routes = protectRoutes([
{ {
index: true, element: <HomeView/> index: true, element: <HomeView/>
}, },
{
path: 'game/:gameId',
element: <GameView/>
},
{ {
path: '/search', path: '/search',
element: <SearchView/> element: <SearchView/>
}, },
{
path: 'recently-added',
element: <RecentlyAddedView/>
},
{
path: 'library/:libraryId',
element: <LibraryView/>
},
{
path: 'game/:gameId',
element: <GameView/>
},
{ {
path: 'settings', path: 'settings',
element: <ProfileView/>, element: <ProfileView/>,
@@ -12,8 +12,8 @@ type GameState = {
games: GameDto[]; games: GameDto[];
gamesByLibraryId: Record<number, GameDto[]>; gamesByLibraryId: Record<number, GameDto[]>;
sortedAlphabetically: GameDto[]; sortedAlphabetically: GameDto[];
sortedByMostRecentlyAdded: GameDto[]; recentlyAdded: GameDto[];
sortedByMostRecentlyUpdated: GameDto[]; recentlyUpdated: GameDto[];
randomlyOrderedGamesByLibraryId: Record<number, GameDto[]>; randomlyOrderedGamesByLibraryId: Record<number, GameDto[]>;
knownPublishers: Set<string>; knownPublishers: Set<string>;
knownDevelopers: Set<string>; knownDevelopers: Set<string>;
@@ -33,7 +33,7 @@ export const gameState = proxy<GameState>({
return Object.values<GameDto>(this.state); return Object.values<GameDto>(this.state);
}, },
get gamesByLibraryId() { get gamesByLibraryId() {
return this.games.reduce((acc: Record<number, GameDto[]>, game: GameDto) => { return this.sortedAlphabetically.reduce((acc: Record<number, GameDto[]>, game: GameDto) => {
(acc[game.libraryId] ||= []).push(game); (acc[game.libraryId] ||= []).push(game);
return acc; return acc;
}, {}); }, {});
@@ -42,13 +42,15 @@ export const gameState = proxy<GameState>({
return this.games return this.games
.sort((a: GameDto, b: GameDto) => a.title.localeCompare(b.title, undefined, {sensitivity: 'base'})); .sort((a: GameDto, b: GameDto) => a.title.localeCompare(b.title, undefined, {sensitivity: 'base'}));
}, },
get sortedByMostRecentlyAdded() { get recentlyAdded() {
return this.games return this.games
.sort((a: GameDto, b: GameDto) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); .sort((a: GameDto, b: GameDto) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
.slice(0, 25);
}, },
get sortedByMostRecentlyUpdated() { get recentlyUpdated() {
return this.games return this.games
.sort((a: GameDto, b: GameDto) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()); .sort((a: GameDto, b: GameDto) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime())
.slice(0, 25);
}, },
get randomlyOrderedGamesByLibraryId() { get randomlyOrderedGamesByLibraryId() {
const result: Record<number, GameDto[]> = {}; const result: Record<number, GameDto[]> = {};
@@ -38,7 +38,7 @@ export default function GameView() {
useEffect(() => { useEffect(() => {
initializeGameState().then((state) => { initializeGameState().then((state) => {
if (!gameId || !state.state[parseInt(gameId)]) { if (!gameId || !state.state[parseInt(gameId)]) {
navigate("/"); navigate("/", {replace: true});
} }
}); });
}, [gameId]); }, [gameId]);
@@ -3,22 +3,24 @@ import {CoverRow} from "Frontend/components/general/covers/CoverRow";
import {useSnapshot} from "valtio/react"; import {useSnapshot} from "valtio/react";
import {libraryState} from "Frontend/state/LibraryState"; import {libraryState} from "Frontend/state/LibraryState";
import {gameState} from "Frontend/state/GameState"; import {gameState} from "Frontend/state/GameState";
import {useNavigate} from "react-router";
export default function HomeView() { export default function HomeView() {
const navigate = useNavigate();
const librariesState = useSnapshot(libraryState); const librariesState = useSnapshot(libraryState);
const gamesState = useSnapshot(gameState); const gamesState = useSnapshot(gameState);
const recentlyAddedGames = gamesState.sortedByMostRecentlyAdded as GameDto[]; const recentlyAddedGames = gamesState.recentlyAdded as GameDto[];
const gamesByLibrary = gamesState.gamesByLibraryId as Record<number, GameDto[]>; const gamesByLibrary = gamesState.gamesByLibraryId as Record<number, GameDto[]>;
return ( return (
<div className="w-full"> <div className="w-full">
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<CoverRow title="Recently added" games={recentlyAddedGames} <CoverRow title="Recently added" games={recentlyAddedGames}
onPressShowMore={() => alert("show more of 'Recently added'")}/> onPressShowMore={() => navigate("/recently-added")}/>
{librariesState.libraries.map((library) => ( {librariesState.libraries.map((library) => (
<CoverRow key={library.id} title={library.name} <CoverRow key={library.id} title={library.name}
games={gamesByLibrary[library.id] || []} games={gamesByLibrary[library.id] || []}
onPressShowMore={() => alert(`show more of library '${library.name}'`)} onPressShowMore={() => navigate("/library/" + library.id)}
/> />
))} ))}
</div> </div>
@@ -0,0 +1,29 @@
import {useSnapshot} from "valtio/react";
import {initializeLibraryState, libraryState} from "Frontend/state/LibraryState";
import {gameState} from "Frontend/state/GameState";
import React, {useEffect} from "react";
import {useNavigate, useParams} from "react-router";
import CoverGrid from "Frontend/components/general/covers/CoverGrid";
import GameDto from "Frontend/generated/de/grimsi/gameyfin/games/dto/GameDto";
export default function LibraryView() {
const {libraryId} = useParams();
const navigate = useNavigate();
const libraries = useSnapshot(libraryState);
const games = (libraryId ? useSnapshot(gameState).gamesByLibraryId[parseInt(libraryId!!)] || [] : []) as GameDto[];
useEffect(() => {
initializeLibraryState().then((state) => {
if (!libraryId || !state.state[parseInt(libraryId)]) {
navigate("/", {replace: true});
}
});
}, [libraryId]);
return (
<div className="flex flex-col gap-6">
<p className="text-4xl font-bold text-center">{libraries.state[parseInt(libraryId!!)]?.name}</p>
<CoverGrid games={games}/>
</div>
);
}
@@ -0,0 +1,16 @@
import {useSnapshot} from "valtio/react";
import {gameState} from "Frontend/state/GameState";
import GameDto from "Frontend/generated/de/grimsi/gameyfin/games/dto/GameDto";
import React from "react";
import CoverGrid from "Frontend/components/general/covers/CoverGrid";
export default function RecentlyAddedView() {
const games = useSnapshot(gameState).recentlyAdded as GameDto[];
return (
<div className="flex flex-col gap-4">
<p className="text-4xl font-bold text-center">Recently added</p>
<CoverGrid games={games}/>
</div>
);
}