From 2acbc0d6548abd85a9ae7899abbc279ae1f298d8 Mon Sep 17 00:00:00 2001 From: grimsi <9295182+grimsi@users.noreply.github.com> Date: Sat, 24 May 2025 14:52:22 +0200 Subject: [PATCH] (WIP) SearchView --- .../frontend/components/general/SearchBar.tsx | 21 +--------- gameyfin/src/main/frontend/routes.tsx | 5 +++ gameyfin/src/main/frontend/state/GameState.ts | 28 +++++++++++++ .../src/main/frontend/views/MainLayout.tsx | 17 +++++--- .../src/main/frontend/views/SearchView.tsx | 39 +++++++++++++++++++ 5 files changed, 86 insertions(+), 24 deletions(-) create mode 100644 gameyfin/src/main/frontend/views/SearchView.tsx diff --git a/gameyfin/src/main/frontend/components/general/SearchBar.tsx b/gameyfin/src/main/frontend/components/general/SearchBar.tsx index bc43d05..1755e4c 100644 --- a/gameyfin/src/main/frontend/components/general/SearchBar.tsx +++ b/gameyfin/src/main/frontend/components/general/SearchBar.tsx @@ -4,34 +4,19 @@ import {useSnapshot} from "valtio/react"; import {gameState} from "Frontend/state/GameState"; import GameDto from "Frontend/generated/de/grimsi/gameyfin/games/dto/GameDto"; import {useNavigate} from "react-router"; -import {Key, KeyboardEvent, useState} from "react"; import {GameCover} from "Frontend/components/general/covers/GameCover"; export default function SearchBar() { const navigate = useNavigate(); const state = useSnapshot(gameState); - const games = state.games as GameDto[]; - - const [selectedId, setSelectedId] = useState(); - - function handleKeyDown(event: KeyboardEvent) { - if (event.key === "Enter") { - event.preventDefault(); - navigate("/game/" + selectedId); - } - } - - function updateSelectedId(key: Key | null) { - if (key === null) return; - setSelectedId(key as number); - } + const games = state.sortedByMostRecentlyUpdated as GameDto[]; return } - onSelectionChange={updateSelectedId} - onKeyDown={handleKeyDown} isVirtualized={true} maxListboxHeight={300} itemHeight={91} // 75px (cover) + 16px (margin top/bottom) = 91px diff --git a/gameyfin/src/main/frontend/routes.tsx b/gameyfin/src/main/frontend/routes.tsx index 7001815..d1a1178 100644 --- a/gameyfin/src/main/frontend/routes.tsx +++ b/gameyfin/src/main/frontend/routes.tsx @@ -21,6 +21,7 @@ import PluginManagement from "Frontend/components/administration/PluginManagemen import {SystemManagement} from "Frontend/components/administration/SystemManagement"; import GameView from "Frontend/views/GameView"; import LibraryManagementView from "Frontend/views/LibraryManagementView"; +import SearchView from "Frontend/views/SearchView"; export const routes = protectRoutes([ { @@ -38,6 +39,10 @@ export const routes = protectRoutes([ path: 'game/:gameId', element: }, + { + path: '/search', + element: + }, { path: 'settings', element: , diff --git a/gameyfin/src/main/frontend/state/GameState.ts b/gameyfin/src/main/frontend/state/GameState.ts index 4eade5b..e618ac4 100644 --- a/gameyfin/src/main/frontend/state/GameState.ts +++ b/gameyfin/src/main/frontend/state/GameState.ts @@ -14,6 +14,13 @@ type GameState = { sortedByMostRecentlyAdded: GameDto[]; sortedByMostRecentlyUpdated: GameDto[]; randomlyOrderedGamesByLibraryId: Record; + knownPublishers: Set; + knownDevelopers: Set; + knownGenres: Set; + knownThemes: Set; + knownKeywords: Set; + knownFeatures: Set; + knownPerspectives: Set; }; export const gameState = proxy({ @@ -48,6 +55,27 @@ export const gameState = proxy({ .sort(() => rand.next() - 0.5); } return result; + }, + get knownPublishers() { + return new Set(this.games.flatMap((game: GameDto) => game.publishers ? game.publishers : [])); + }, + get knownDevelopers() { + return new Set(this.games.flatMap((game: GameDto) => game.developers ? game.developers : [])); + }, + get knownGenres() { + return new Set(this.games.flatMap((game: GameDto) => game.genres ? game.genres : [])); + }, + get knownThemes() { + return new Set(this.games.flatMap((game: GameDto) => game.themes ? game.themes : [])); + }, + get knownKeywords() { + return new Set(this.games.flatMap((game: GameDto) => game.keywords ? game.keywords : [])); + }, + get knownFeatures() { + return new Set(this.games.flatMap((game: GameDto) => game.features ? game.features : [])); + }, + get knownPerspectives() { + return new Set(this.games.flatMap((game: GameDto) => game.perspectives ? game.perspectives : [])); } }); diff --git a/gameyfin/src/main/frontend/views/MainLayout.tsx b/gameyfin/src/main/frontend/views/MainLayout.tsx index ed4390f..ae6eb0d 100644 --- a/gameyfin/src/main/frontend/views/MainLayout.tsx +++ b/gameyfin/src/main/frontend/views/MainLayout.tsx @@ -1,12 +1,12 @@ import {useRouteMetadata} from 'Frontend/util/routing.js'; import {useEffect, useState} from 'react'; import ProfileMenu from "Frontend/components/ProfileMenu"; -import {Divider, Link, Navbar, NavbarBrand, NavbarContent, NavbarItem} from "@heroui/react"; +import {Button, Divider, Link, Navbar, NavbarBrand, NavbarContent, NavbarItem, Tooltip} from "@heroui/react"; import GameyfinLogo from "Frontend/components/theming/GameyfinLogo"; import * as PackageJson from "../../../../package.json"; -import {Outlet, useNavigate} from "react-router"; +import {Outlet, useLocation, useNavigate} from "react-router"; import {useAuth} from "Frontend/util/auth"; -import {Heart} from "@phosphor-icons/react"; +import {Heart, ListMagnifyingGlass} from "@phosphor-icons/react"; import Confetti, {ConfettiProps} from "react-confetti-boom"; import {useTheme} from "next-themes"; import {UserPreferenceService} from "Frontend/util/user-preference-service"; @@ -14,9 +14,11 @@ import SearchBar from "Frontend/components/general/SearchBar"; export default function MainLayout() { const navigate = useNavigate(); + const location = useLocation(); const auth = useAuth(); const routeMetadata = useRouteMetadata(); const {setTheme} = useTheme(); + const isSearchPage = location.pathname.startsWith("/search"); const [isExploding, setIsExploding] = useState(false); useEffect(() => { @@ -63,9 +65,14 @@ export default function MainLayout() { - + {!isSearchPage && - + + + + } {auth.state.user?.emailConfirmed === false ? diff --git a/gameyfin/src/main/frontend/views/SearchView.tsx b/gameyfin/src/main/frontend/views/SearchView.tsx new file mode 100644 index 0000000..0ae904e --- /dev/null +++ b/gameyfin/src/main/frontend/views/SearchView.tsx @@ -0,0 +1,39 @@ +import {Input} from "@heroui/react"; +import {MagnifyingGlass} from "@phosphor-icons/react"; +import {useSnapshot} from "valtio/react"; +import {gameState} from "Frontend/state/GameState"; +import {libraryState} from "Frontend/state/LibraryState"; +import {useSearchParams} from "react-router"; +import {ChangeEvent} from "react"; + +export default function SearchView() { + const gamesState = useSnapshot(gameState); + const librariesState = useSnapshot(libraryState); + const [searchParams, setSearchParams] = useSearchParams(); + const term = searchParams.get("term") ?? ""; + + const updateSearchParam = (e: ChangeEvent) => { + const value = e.target.value; + if (value) { + setSearchParams({term: value}); + } else { + setSearchParams({}); + } + }; + + return
+ } + type="search" + value={term} + onChange={updateSearchParam} + /> +
+} \ No newline at end of file