Public Access
feat(library): add game links, auditor queue, audit log and filter
LinkGameDialog (user request or auditor link), EditionsCard on the details page, LinkQueuePage and AuditLogPage for auditors with paging, hideDuplicates filter and editions badge in the library, audit menu entries, de/en texts.
This commit is contained in:
1 parent
80259cb0e9
commit
f71e8f7aff
21 files changed
+992
-11
No files matched your search
@@ -7,7 +7,7 @@ import useForceSync from '../hooks/useForceSync.js';
|
||||
import {formatPlaytime} from '../utils/games.js';
|
||||
import {libraryQuery, parseStatus} from '../utils/backlog.js';
|
||||
import {GAME_STATUSES, MAX_LIBRARY_SEARCH_LENGTH} from '../utils/limits.js';
|
||||
import {ArrowUpDown, ChevronLeft, ChevronRight, Clock, EyeOff, Gamepad2, Library, Plus, RefreshCw, Search, SearchX, Timer, Trophy, Upload} from 'lucide-react';
|
||||
import {ArrowUpDown, ChevronLeft, ChevronRight, Clock, EyeOff, Gamepad2, Layers, Library, Plus, RefreshCw, Search, SearchX, Timer, Trophy, Upload} from 'lucide-react';
|
||||
|
||||
import {Badge} from '@/components/ui/badge';
|
||||
import {Button} from '@/components/ui/button';
|
||||
@@ -55,10 +55,12 @@ export default function GameLibrary() {
|
||||
const q = searchParams.get('q') ?? '';
|
||||
const shortOnly = searchParams.get('maxMinutes') === String(SHORT_GAME_MINUTES);
|
||||
const hiddenOnly = searchParams.get('hidden') === 'true';
|
||||
const hideDuplicates = searchParams.get('hideDuplicates') === 'true';
|
||||
// The hidden list stands alone, also for a hand-made URL: its status and short filters are ignored.
|
||||
const listStatus = hiddenOnly ? null : status;
|
||||
const listShortOnly = !hiddenOnly && shortOnly;
|
||||
const filtered = Boolean(listStatus || q.trim() || listShortOnly || hiddenOnly);
|
||||
const listHideDuplicates = !hiddenOnly && hideDuplicates;
|
||||
const filtered = Boolean(listStatus || q.trim() || listShortOnly || hiddenOnly || listHideDuplicates);
|
||||
const [search, setSearch] = useState(q);
|
||||
const [adding, setAdding] = useState(false);
|
||||
// Starts true until /games-count answers, so a Steam user does not see the sync controls vanish and come back.
|
||||
@@ -73,7 +75,7 @@ export default function GameLibrary() {
|
||||
const signal = nextSignal();
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
const url = `${import.meta.env.VITE_CORE_API_URL}/api/library/games?${libraryQuery({page, size, sort, status: listStatus, q, maxMinutes: listShortOnly ? SHORT_GAME_MINUTES : null, hidden: hiddenOnly})}`;
|
||||
const url = `${import.meta.env.VITE_CORE_API_URL}/api/library/games?${libraryQuery({page, size, sort, status: listStatus, q, maxMinutes: listShortOnly ? SHORT_GAME_MINUTES : null, hidden: hiddenOnly, hideDuplicates: listHideDuplicates})}`;
|
||||
const {ok, status, data: json} = await request(url, {signal});
|
||||
if (signal.aborted) return;
|
||||
setIsLoading(false);
|
||||
@@ -83,7 +85,7 @@ export default function GameLibrary() {
|
||||
return;
|
||||
}
|
||||
setData(json);
|
||||
}, [nextSignal, page, size, sort, listStatus, q, listShortOnly, hiddenOnly]);
|
||||
}, [nextSignal, page, size, sort, listStatus, q, listShortOnly, hiddenOnly, listHideDuplicates]);
|
||||
|
||||
useEffect(() => {
|
||||
load();
|
||||
@@ -132,7 +134,7 @@ export default function GameLibrary() {
|
||||
};
|
||||
|
||||
const onHiddenChange = () => {
|
||||
updateParams(hiddenOnly ? {hidden: null, page: 0} : {hidden: true, status: null, maxMinutes: null, page: 0});
|
||||
updateParams(hiddenOnly ? {hidden: null, page: 0} : {hidden: true, status: null, maxMinutes: null, hideDuplicates: null, page: 0});
|
||||
};
|
||||
|
||||
const onSizeChange = (value) => {
|
||||
@@ -232,6 +234,15 @@ export default function GameLibrary() {
|
||||
>
|
||||
<Timer className="size-3"/>{t('LIBRARY.FILTER_SHORT')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant={listHideDuplicates ? 'default' : 'outline'}
|
||||
aria-pressed={listHideDuplicates}
|
||||
className="h-7 rounded-full px-3 text-xs"
|
||||
onClick={() => updateParams({hideDuplicates: listHideDuplicates ? null : true, hidden: null, page: 0})}
|
||||
>
|
||||
<Layers className="size-3"/>{t('LIBRARY.FILTER_HIDE_DUPLICATES')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant={hiddenOnly ? 'default' : 'outline'}
|
||||
@@ -299,6 +310,11 @@ export default function GameLibrary() {
|
||||
<Gamepad2 className="size-3"/>{platformLabel(g)}
|
||||
</Badge>
|
||||
)}
|
||||
{g.editions >= 2 && (
|
||||
<Badge variant="outline" className="tabular-nums" title={t('LIBRARY.EDITIONS', {count: g.editions})}>
|
||||
<Layers className="size-3"/>x{g.editions}
|
||||
</Badge>
|
||||
)}
|
||||
<GameStatusBadge status={g.status} compact/>
|
||||
{g.achievementsTotal > 0 && g.achievementsUnlocked != null && (
|
||||
<Badge variant="secondary" className="hidden sm:inline-flex tabular-nums"
|
||||
|
||||
Reference in new issue
Block a user