import { AddLinkEventTracker, GetRoomEngine, ILinkEventTracker, IWheelPrize, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; import { FC, useEffect, useMemo, useRef, useState } from 'react'; import { LocalizeText } from '../../api'; import { Column, Flex, LayoutAvatarImageView, LayoutBadgeImageView, LayoutCurrencyIcon, LayoutImage, Text } from '../../common'; import { useFortuneWheel, useHasPermission } from '../../hooks'; import { NitroCard } from '../../layout'; import { FortuneWheelSettingsView } from './FortuneWheelSettingsView'; // Stock UI palette (white / light-blue / grey / black). const SLICE_COLORS = [ '#eef2f5', '#c3dcec' ]; const RIM = '#4c606c'; const WHEEL_SIZE = 420; const ICON_RADIUS = 150; const FULL_TURNS = 5; const renderPrizeIcon = (prize: IWheelPrize) => { switch(prize.type) { case 'item': return ; case 'badge': return ; case 'credits': return ( { prize.amount } ); case 'points': return ( { prize.amount } ); case 'spin': return +{ prize.amount }; default: return —; } }; export const FortuneWheelView: FC<{}> = () => { const [ isVisible, setIsVisible ] = useState(false); const [ isSettingsOpen, setIsSettingsOpen ] = useState(false); const { freeSpins, extraSpins, spinCost, spinCostType, prizes, recentWins, pendingPrizeId, isSpinning, open, spin, buySpin, finishSpin } = useFortuneWheel(); const canManage = useHasPermission('acc_wheeladmin'); const [ rotation, setRotation ] = useState(0); const rotationRef = useRef(0); const prizesRef = useRef([]); prizesRef.current = prizes; useEffect(() => { const linkTracker: ILinkEventTracker = { linkReceived: (url: string) => { const parts = url.split('/'); if(parts.length < 2) return; switch(parts[1]) { case 'show': setIsVisible(true); return; case 'hide': setIsVisible(false); return; case 'toggle': setIsVisible(prev => !prev); return; } }, eventUrlPrefix: 'fortune-wheel/', }; AddLinkEventTracker(linkTracker); return () => RemoveLinkEventTracker(linkTracker); }, []); useEffect(() => { if(isVisible) open(); }, [ isVisible, open ]); // Drive the spin animation when the server reports the winning slice. useEffect(() => { if(pendingPrizeId < 0) return; const list = prizesRef.current; const idx = list.findIndex(prize => prize.id === pendingPrizeId); if(!list.length || (idx < 0)) { finishSpin(); return; } const sliceAngle = 360 / list.length; const centerAngle = ((idx + 0.5) * sliceAngle); const current = rotationRef.current; const target = (current - (current % 360)) + (FULL_TURNS * 360) + (360 - centerAngle); rotationRef.current = target; setRotation(target); }, [ pendingPrizeId, finishSpin ]); const sliceAngle = prizes.length ? (360 / prizes.length) : 0; const background = useMemo(() => { if(!prizes.length) return SLICE_COLORS[0]; const stops = prizes.map((_, i) => `${ SLICE_COLORS[i % 2] } ${ i * sliceAngle }deg ${ (i + 1) * sliceAngle }deg`).join(', '); return `conic-gradient(${ stops })`; }, [ prizes, sliceAngle ]); if(!isVisible) return null; const canSpin = ((freeSpins + extraSpins) > 0) && !isSpinning && (prizes.length > 0); return ( setIsVisible(false) } /> { if(isSpinning) finishSpin(); } }> { prizes.map((_, i) => ( )) } { prizes.map((prize, i) => { const centerAngle = ((i + 0.5) * sliceAngle); return ( { renderPrizeIcon(prize) } ); }) } { LocalizeText('wheel.free.today', [ 'count' ], [ freeSpins.toString() ]) } { LocalizeText('wheel.extra', [ 'count' ], [ extraSpins.toString() ]) } spin() } className="cursor-pointer rounded bg-[#3a7bb5] px-4 py-2 font-bold text-white hover:bg-[#336ea3] disabled:cursor-default disabled:opacity-40"> { LocalizeText('wheel.spin') } buySpin() } className="flex cursor-pointer items-center gap-1 rounded bg-[#6b7884] px-3 py-2 text-white hover:bg-[#5e6a75]"> { LocalizeText('wheel.buy') } { spinCost } { canManage && setIsSettingsOpen(true) } className="cursor-pointer rounded bg-[#8a6b3a] px-3 py-2 font-bold text-white hover:bg-[#735730]"> { LocalizeText('wheel.settings') } } { LocalizeText('wheel.winners') } { recentWins.map((win, i) => ( { win.username } { win.prizeLabel } )) } { !recentWins.length && { LocalizeText('wheel.winners.empty') } } { canManage && isSettingsOpen && setIsSettingsOpen(false) } /> } ); };