Public Access
feat(game): play confetti for finished games
This commit is contained in:
1 parent
09e1f767d8
commit
a5458b3f86
9 files changed
+260
-9
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import {useEffect, useMemo, useState} from 'react';
|
||||
import {useEffect, useMemo, useRef, useState} from 'react';
|
||||
import {Link, useNavigate, useParams} from 'react-router-dom';
|
||||
import {useTranslation} from 'react-i18next';
|
||||
import {ArrowLeft, Clock, ExternalLink, Eye, EyeOff, Gamepad2, Image as ImageIcon, ImageOff, Merge, PanelTop, PencilLine, RotateCcw, ShieldOff, Timer, Trophy} from 'lucide-react';
|
||||
@@ -10,6 +10,7 @@ import {Card} from '@/components/ui/card';
|
||||
import {Skeleton} from '@/components/ui/skeleton';
|
||||
import {cn} from '@/lib/utils';
|
||||
import BacklogCard from '../components/BacklogCard.jsx';
|
||||
import Confetti from '../components/Confetti.jsx';
|
||||
import GameCover from '../components/GameCover.jsx';
|
||||
import GameStatusBadge from '../components/GameStatusBadge.jsx';
|
||||
import ManualGameCard from '../components/ManualGameCard.jsx';
|
||||
@@ -28,6 +29,7 @@ import PageContainer from '../components/PageContainer.jsx';
|
||||
import {apiFetch} from '../utils/api.js';
|
||||
import {ACHIEVEMENT_ICONS, reportBrokenImage} from '../utils/brokenImages.js';
|
||||
import {canCorrect} from '../utils/corrections.js';
|
||||
import {celebrationFor, celebrationLevel} from '../utils/confetti.js';
|
||||
import {setGameHidden} from '../utils/hiddenGames.js';
|
||||
import {hasSession} from '../utils/auth.js';
|
||||
import {isSteamKey} from '../utils/manualGames.js';
|
||||
@@ -71,6 +73,16 @@ export default function GameDetail() {
|
||||
const [merging, setMerging] = useState(false);
|
||||
const [reloads, setReloads] = useState(0);
|
||||
const auditor = canCorrect(useCurrentUser(true));
|
||||
const [celebration, setCelebration] = useState(null);
|
||||
const shownLevel = useRef(null);
|
||||
|
||||
// Every status change on this page (backlog card, hints) goes through setData.
|
||||
useEffect(() => {
|
||||
if (!data) return;
|
||||
const size = celebrationFor(shownLevel.current, data);
|
||||
if (size) setCelebration({size, id: Date.now()});
|
||||
shownLevel.current = {appid: data.appid, level: celebrationLevel(data)};
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
@@ -357,6 +369,7 @@ export default function GameDetail() {
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{celebration && <Confetti key={celebration.id} size={celebration.size} onDone={() => setCelebration(null)}/>}
|
||||
{/* Outside the content: the reload after a save must not close it. */}
|
||||
{data && auditor && (
|
||||
<GameCorrectionDialog game={data} open={correcting} onOpenChange={setCorrecting}
|
||||
|
||||
@@ -282,3 +282,35 @@ describe('GameDetail of a game added by hand', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('GameDetail confetti', () => {
|
||||
// jsdom has no canvas drawing.
|
||||
beforeEach(() => {
|
||||
vi.spyOn(HTMLCanvasElement.prototype, 'getContext').mockReturnValue(null);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
const opened = async (entry) => {
|
||||
globalThis.fetch = vi.fn(async () => ({ok: true, status: 200, json: async () => ({...game, ...entry})}));
|
||||
const {container} = renderDetail();
|
||||
await screen.findByText('Half-Life 2');
|
||||
return container.querySelector('canvas[data-size]');
|
||||
};
|
||||
|
||||
it('plays a small one for a finished game', async () => {
|
||||
expect((await opened({status: 'FINISHED'})).dataset.size).toBe('small');
|
||||
});
|
||||
|
||||
it('plays a big one when every achievement is unlocked too', async () => {
|
||||
const canvas = await opened({status: 'COMPLETED', hasAchievements: true, statsVisible: true,
|
||||
achievementsTotal: 3, achievementsUnlocked: 3});
|
||||
expect(canvas.dataset.size).toBe('big');
|
||||
});
|
||||
|
||||
it('plays nothing for a game not finished', async () => {
|
||||
expect(await opened({status: 'PLAYING'})).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user