Merge pull request #181 from simoleo89/feat/fortune-wheel-improvements

feat(fortune-wheel): celebration reveal, spin animation & prize editor add/remove
This commit is contained in:
DuckieTM
2026-05-31 15:44:42 +02:00
committed by GitHub
10 changed files with 502 additions and 107 deletions
+28 -2
View File
@@ -1,5 +1,5 @@
import { IWheelAdminPrize, IWheelAdminPrizeEdit, IWheelPrize, IWheelRecentWin, WheelAdminGetPrizesComposer, WheelAdminPrizesEvent, WheelAdminSavePrizesComposer, WheelBuySpinComposer, WheelDataEvent, WheelOpenComposer, WheelRecentWinsEvent, WheelResultEvent, WheelSpinComposer } from '@nitrots/nitro-renderer';
import { useCallback, useState } from 'react';
import { useCallback, useRef, useState } from 'react';
import { useBetween } from 'use-between';
import { SendMessageComposer } from '../../api';
import { useMessageEvent } from '../events';
@@ -18,6 +18,15 @@ const useFortuneWheelState = () =>
const [ isSpinning, setIsSpinning ] = useState(false);
const [ adminPrizes, setAdminPrizes ] = useState<IWheelAdminPrize[]>([]);
// While the wheel is animating we hold back the recent-wins refresh: the
// server pushes the updated list (which already contains the just-won
// prize) the instant it answers the spin, ~5s before the wheel actually
// stops. Showing it immediately would spoil the result in the winners
// panel. We buffer it here and flush it in finishSpin (called when the
// reveal fires).
const spinAnimatingRef = useRef(false);
const bufferedWinsRef = useRef<IWheelRecentWin[] | null>(null);
useMessageEvent<WheelAdminPrizesEvent>(WheelAdminPrizesEvent, event =>
{
setAdminPrizes(event.getParser().prizes);
@@ -35,13 +44,21 @@ const useFortuneWheelState = () =>
useMessageEvent<WheelResultEvent>(WheelResultEvent, event =>
{
// Set synchronously before the recent-wins packet (sent right after by
// the server) is processed, so its handler knows a spin is animating.
spinAnimatingRef.current = true;
setPendingPrizeId(event.getParser().prizeId);
setIsSpinning(true);
});
useMessageEvent<WheelRecentWinsEvent>(WheelRecentWinsEvent, event =>
{
setRecentWins(event.getParser().wins);
const wins = event.getParser().wins;
// Mid-spin: stash the refreshed list and reveal it once the wheel
// stops. Otherwise (initial open, other refreshes) apply immediately.
if(spinAnimatingRef.current) bufferedWinsRef.current = wins;
else setRecentWins(wins);
});
const open = useCallback(() => SendMessageComposer(new WheelOpenComposer()), []);
@@ -56,8 +73,17 @@ const useFortuneWheelState = () =>
const buySpin = useCallback(() => SendMessageComposer(new WheelBuySpinComposer()), []);
const finishSpin = useCallback(() =>
{
spinAnimatingRef.current = false;
setIsSpinning(false);
setPendingPrizeId(-1);
// Flush the winners list that arrived during the spin, now that the
// reveal has happened.
if(bufferedWinsRef.current)
{
setRecentWins(bufferedWinsRef.current);
bufferedWinsRef.current = null;
}
}, []);
const loadAdminPrizes = useCallback(() => SendMessageComposer(new WheelAdminGetPrizesComposer()), []);