v2.0.0.beta5 (#626)

* Fix wrong version property used in release.yml

* Implement "Allow access to Gameyfin without login"

* Implement filter by keyword (closes #613)

* Fix bug where secret fields would be displayed as normal text

* Optimize Gradle build performance

* Fix ant path matchers

* Fix NPE in role authority mapper (fixes #614)
This commit is contained in:
Simon
2025-07-16 22:39:09 +02:00
committed by GitHub
parent 49ff9474fb
commit edf7a569df
25 changed files with 168 additions and 66 deletions
@@ -13,7 +13,7 @@ export default function ProfileMenu() {
async function logout() {
if (auth.state.user?.managedBySso) {
window.location.href = (await ConfigEndpoint.getLogoutUrl()) || "/";
window.location.href = (await ConfigEndpoint.getSsoLogoutUrl()) || "/";
} else {
await auth.logout();
}
@@ -38,7 +38,7 @@ function LibraryManagementLayout({getConfig, formik}: any) {
return (
<div className="flex flex-col">
<Section title="Permissions"/>
<ConfigFormField configElement={getConfig("library.allow-public-access")} isDisabled/>
<ConfigFormField configElement={getConfig("library.allow-public-access")}/>
<Section title="Scanning"/>
<div className="flex flex-col gap-4">
@@ -87,6 +87,8 @@ export default function EditGameMetadataModal({game, isOpen, onOpenChange}: Edit
<ArrayInput key="features" name="features" label="Features"/>
<ArrayInput key="perspectives" name="perspectives"
label="Perspectives"/>
<ArrayInput key="keywords" name="keywords"
label="Keywords"/>
</AccordionItem>
</Accordion>
</ModalBody>
+3 -1
View File
@@ -23,6 +23,7 @@ import SearchView from "Frontend/views/SearchView";
import RecentlyAddedView from "Frontend/views/RecentlyAddedView";
import LibraryView from "Frontend/views/LibraryView";
import {RouterConfigurationBuilder} from "@vaadin/hilla-file-router/runtime.js";
import {ConfigEndpoint} from "Frontend/generated/endpoints";
export const {router, routes} = new RouterConfigurationBuilder()
.withReactRoutes([
@@ -32,7 +33,7 @@ export const {router, routes} = new RouterConfigurationBuilder()
children: [
{
element: <MainLayout/>,
handle: {requiresLogin: true},
handle: {requiresLogin: !ConfigEndpoint.isPublicAccessEnabled()},
children: [
{
index: true, element: <HomeView/>
@@ -64,6 +65,7 @@ export const {router, routes} = new RouterConfigurationBuilder()
{
path: 'administration',
element: <AdministrationView/>,
handle: {requiresLogin: true},
children: [
{
path: 'libraries',
+19 -4
View File
@@ -6,7 +6,7 @@ import GameyfinLogo from "Frontend/components/theming/GameyfinLogo";
import * as PackageJson from "../../../../package.json";
import {Outlet, useLocation, useNavigate} from "react-router";
import {useAuth} from "Frontend/util/auth";
import {ArrowLeft, DiceSix, Heart, House, ListMagnifyingGlass} from "@phosphor-icons/react";
import {ArrowLeft, DiceSix, Heart, House, ListMagnifyingGlass, SignIn} from "@phosphor-icons/react";
import Confetti, {ConfettiProps} from "react-confetti-boom";
import {useTheme} from "next-themes";
import {UserPreferenceService} from "Frontend/util/user-preference-service";
@@ -103,9 +103,24 @@ export default function MainLayout() {
<ScanProgressPopover/>
</NavbarItem>
}
<NavbarItem>
<ProfileMenu/>
</NavbarItem>
{auth.state.user &&
<NavbarItem>
<ProfileMenu/>
</NavbarItem>
}
{!auth.state.user &&
<NavbarItem>
<Tooltip content="Sign in to your account" placement="bottom">
<Button color="primary"
radius="full"
isIconOnly
className="gradient-primary"
onPress={() => navigate("/login")}>
<SignIn fill="text-background/80"/>
</Button>
</Tooltip>
</NavbarItem>
}
</NavbarContent>
</Navbar>
+41 -9
View File
@@ -18,6 +18,7 @@ export default function SearchView() {
const knownThemes = useSnapshot(gameState).knownThemes;
const knownFeatures = useSnapshot(gameState).knownFeatures;
const knownPerspectives = useSnapshot(gameState).knownPerspectives;
const knownKeywords = useSnapshot(gameState).knownKeywords;
const libraries = useSnapshot(libraryState).libraries as LibraryDto[];
const [searchParams, setSearchParams] = useSearchParams();
@@ -31,6 +32,7 @@ export default function SearchView() {
const [selectedThemes, setSelectedThemes] = useState<Set<string>>(new Set());
const [selectedFeatures, setSelectedFeatures] = useState<Set<string>>(new Set());
const [selectedPerspectives, setSelectedPerspectives] = useState<Set<string>>(new Set());
const [selectedKeywords, setSelectedKeywords] = useState<Set<string>>(new Set());
// Load initial filter values from URL parameters on component mount
useEffect(() => {
@@ -42,6 +44,7 @@ export default function SearchView() {
const themes = searchParams.getAll("theme");
const features = searchParams.getAll("feature");
const perspectives = searchParams.getAll("perspective");
const keywords = searchParams.getAll("keyword");
setSearchTerm(term);
setSelectedLibraries(new Set(libs));
@@ -50,6 +53,7 @@ export default function SearchView() {
setSelectedThemes(new Set(themes));
setSelectedFeatures(new Set(features));
setSelectedPerspectives(new Set(perspectives));
setSelectedKeywords(new Set(keywords));
setInitialLoadComplete(true);
}, []);
@@ -102,15 +106,21 @@ export default function SearchView() {
});
}
if (selectedKeywords.size > 0) {
selectedKeywords.forEach(keyword => {
newParams.append("keyword", keyword);
});
}
setSearchParams(newParams, {replace: true});
}, [searchTerm, selectedLibraries, selectedDevelopers, selectedGenres,
selectedThemes, selectedFeatures, selectedPerspectives]);
selectedThemes, selectedFeatures, selectedPerspectives, selectedKeywords]);
const filteredGames = useMemo(() => filterGames(), [
games, searchTerm,
selectedLibraries, selectedDevelopers,
selectedGenres, selectedThemes,
selectedFeatures, selectedPerspectives
selectedFeatures, selectedPerspectives, selectedKeywords
]);
function filterGames(): GameDto[] {
@@ -164,6 +174,13 @@ export default function SearchView() {
);
}
// Apply keyword filter
if (selectedKeywords.size > 0) {
filtered = filtered.filter(game =>
game.keywords?.some(keyword => selectedKeywords.has(keyword))
);
}
return filtered;
}
@@ -183,10 +200,17 @@ export default function SearchView() {
onChange={(event) => setSearchTerm(event.target.value)}
onClear={() => setSearchTerm("")}
/>
<div className="flex flex-row flex-wrap gap-2 justify-center">
<div
className="w-full justify-center"
style={{
display: "grid",
gridTemplateColumns: "repeat(auto-fit, minmax(250px, 1fr))",
gap: "0.5rem",
margin: "0 auto"
}}
>
<Select
size="sm"
className="max-w-xs"
selectionMode="multiple"
label="Libraries"
placeholder="Filter by library"
@@ -200,7 +224,6 @@ export default function SearchView() {
</Select>
<Select
size="sm"
className="max-w-xs"
selectionMode="multiple"
label="Developers"
placeholder="Filter by developer"
@@ -214,7 +237,6 @@ export default function SearchView() {
</Select>
<Select
size="sm"
className="max-w-xs"
selectionMode="multiple"
label="Genres"
placeholder="Filter by genre"
@@ -228,7 +250,6 @@ export default function SearchView() {
</Select>
<Select
size="sm"
className="max-w-xs"
selectionMode="multiple"
label="Themes"
placeholder="Filter by theme"
@@ -242,7 +263,6 @@ export default function SearchView() {
</Select>
<Select
size="sm"
className="max-w-xs"
selectionMode="multiple"
label="Features"
placeholder="Filter by feature"
@@ -256,7 +276,6 @@ export default function SearchView() {
</Select>
<Select
size="sm"
className="max-w-xs"
selectionMode="multiple"
label="Perspectives"
placeholder="Filter by perspective"
@@ -268,6 +287,19 @@ export default function SearchView() {
<SelectItem key={perspective}>{toTitleCase(perspective)}</SelectItem>
))}
</Select>
<Select
size="sm"
selectionMode="multiple"
label="Keywords"
placeholder="Filter by keyword"
selectedKeys={selectedKeywords}
//@ts-ignore
onSelectionChange={setSelectedKeywords}
>
{Array.from(knownKeywords).map((keyword) => (
<SelectItem key={keyword}>{keyword}</SelectItem>
))}
</Select>
</div>
<div className="mt-4 w-full px-4 select-none">
<CoverGrid games={filteredGames}/>