Skip to content

Commit 8e49097

Browse files
authored
Release 1.1.0
Release 1.1.0
2 parents 4ebf5d7 + 9a1c5b4 commit 8e49097

9 files changed

Lines changed: 3430 additions & 136 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
# GameVault Frontend Changelog
22

3-
## 16.1.0
4-
3+
## 1.1.0
4+
Recommended Gamevault Server Version: `v16.1.2`
55
### Changes
6+
- Added GameSettings
7+
- Extended library filters
8+
- Bug fix: Youtube player error 153
69

7-
- Added GameView Layout
8-
- Added Support for SSO
9-
- Added Sorting + Filtering
10-
11-
## 16.0.0
10+
## 1.0.0
1211

1312
### Changes
1413

src/components/GameCard.tsx

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import { useAuth } from "@/context/AuthContext";
44
import { useDownloads } from "@/context/DownloadContext";
55
import { getGameCoverMediaId } from "@/hooks/useGames";
66
import { CloudArrowDownIcon } from "@heroicons/react/16/solid";
7-
import { StarIcon as StarSolid } from "@heroicons/react/24/solid";
7+
import { StarIcon as StarSolid, Cog8ToothIcon } from "@heroicons/react/24/solid";
88
import { StarIcon as StarOutline } from "@heroicons/react/24/outline";
99
import { Button } from "@tw/button";
10+
import { GameSettings } from "@/components/admin/GameSettings";
1011
import {
1112
Dropdown,
1213
DropdownButton,
@@ -15,11 +16,10 @@ import {
1516
DropdownMenu,
1617
} from "@tw/dropdown";
1718
import clsx from "clsx";
18-
import { useCallback, useMemo, useState } from "react";
19+
import { useCallback, useMemo, useState, useEffect } from "react";
1920
import { useNavigate } from "react-router";
2021

2122
export function GameCard({ game }: { game: GamevaultGame }) {
22-
const coverId = getGameCoverMediaId(game) as number | string | null;
2323
const { serverUrl, user, authFetch } = useAuth();
2424
// Derive initial bookmarked state from raw API shape (bookmarked_users or bookmarkedUsers)
2525
const currentUserId = (user as any)?.id ?? (user as any)?.ID;
@@ -31,6 +31,14 @@ export function GameCard({ game }: { game: GamevaultGame }) {
3131
}, [game, currentUserId]);
3232
const [bookmarked, setBookmarked] = useState<boolean>(initialBookmarked);
3333
const [bookmarkBusy, setBookmarkBusy] = useState(false);
34+
const [settingsOpen, setSettingsOpen] = useState(false);
35+
const [localGame, setLocalGame] = useState<GamevaultGame>(game);
36+
37+
const coverId = getGameCoverMediaId(localGame) as number | string | null;
38+
39+
useEffect(() => {
40+
setLocalGame(game);
41+
}, [game]);
3442

3543
const toggleBookmark = useCallback(
3644
async (e: React.MouseEvent) => {
@@ -57,10 +65,10 @@ export function GameCard({ game }: { game: GamevaultGame }) {
5765
const { startDownload } = useDownloads() as any;
5866

5967
const filename = (() => {
60-
return `${game.title}.zip`;
68+
return `${localGame.title}.zip`;
6169
})();
6270

63-
const rawSize = game.size;
71+
const rawSize = localGame.size;
6472

6573
const formatBytes = useCallback((bytes?: number) => {
6674
if (bytes === undefined || bytes === null || isNaN(bytes)) return null;
@@ -110,7 +118,17 @@ export function GameCard({ game }: { game: GamevaultGame }) {
110118
[navigate, game.id],
111119
);
112120

121+
const handleOpenSettings = useCallback(
122+
(e: React.MouseEvent) => {
123+
e.preventDefault();
124+
e.stopPropagation();
125+
setSettingsOpen(true);
126+
},
127+
[],
128+
);
129+
113130
return (
131+
<>
114132
<div
115133
className={clsx(
116134
"group flex flex-col rounded-xl bg-zinc-100 dark:bg-zinc-800 shadow-sm ring-1 ring-zinc-950/10 dark:ring-white/5 overflow-hidden focus:outline-none focus:ring-2 focus:ring-indigo-500",
@@ -129,7 +147,7 @@ export function GameCard({ game }: { game: GamevaultGame }) {
129147
size={300}
130148
className="h-full w-full object-contain rounded-none"
131149
square
132-
alt={game.title}
150+
alt={localGame.title}
133151
onClick={handleOpenGameView}
134152
/>
135153
) : (
@@ -158,6 +176,16 @@ export function GameCard({ game }: { game: GamevaultGame }) {
158176
<StarOutline className="h-5 w-5 text-white" />
159177
)}
160178
</button>
179+
{/* Top-left settings button */}
180+
<button
181+
type="button"
182+
onClick={handleOpenSettings}
183+
aria-label="Settings"
184+
className="absolute top-1 left-1 h-8 w-8 flex items-center justify-center rounded-md border shadow-sm backdrop-blur-sm transition-colors bg-zinc-900/40 dark:bg-zinc-700/50 border-white/20 hover:bg-zinc-800/60 dark:hover:bg-zinc-600/60"
185+
title="Settings"
186+
>
187+
<Cog8ToothIcon className="h-5 w-5 text-white" />
188+
</button>
161189
{/* Bottom-right download actions */}
162190
<div className="absolute bottom-0 right-0 p-1 z-10 flex justify-end opacity-85">
163191
<Dropdown>
@@ -181,15 +209,15 @@ export function GameCard({ game }: { game: GamevaultGame }) {
181209
</div>
182210
</div>
183211
<div className="p-2 pt-2">
184-
<h3 className="text-sm font-medium truncate" title={game.title}>
185-
{game.metadata?.title || game.title}
212+
<h3 className="text-sm font-medium truncate" title={localGame.title}>
213+
{localGame.metadata?.title || localGame.title}
186214
</h3>
187-
{(game as any).sort_title && (game as any).sort_title !== game.title && (
215+
{(localGame as any).sort_title && (localGame as any).sort_title !== localGame.title && (
188216
<p
189217
className="mt-0.5 text-xs text-fg-muted truncate"
190-
title={game.title}
218+
title={localGame.title}
191219
>
192-
{game.title}
220+
{localGame.title}
193221
</p>
194222
)}
195223
{formattedSize && (
@@ -199,6 +227,14 @@ export function GameCard({ game }: { game: GamevaultGame }) {
199227
)}
200228
</div>
201229
</div>
230+
{settingsOpen && (
231+
<GameSettings
232+
game={game}
233+
onClose={() => setSettingsOpen(false)}
234+
onGameUpdated={(updatedGame) => setLocalGame(updatedGame)}
235+
/>
236+
)}
237+
</>
202238
);
203239
}
204240

src/components/MediaSlider.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ export const MediaSlider: React.FC<MediaSliderProps> = ({
161161
src={`https://www.youtube.com/embed/${ytId}?rel=0&playsinline=1&mute=1&fs=0&enablejsapi=1${autoPlay ? "&autoplay=1" : ""}`}
162162
title={current.title || "Trailer"}
163163
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
164+
referrerPolicy="strict-origin-when-cross-origin"
164165
// Native fullscreen intentionally disabled to use custom control
165166
ref={(el) => {
166167
if (el && autoPlay) {

0 commit comments

Comments
 (0)