import { CSSProperties } from 'react'; import { Icon } from '../Icon'; import { avatarColor, formatClock, formatCountdown, formatCountdownShort, formatUntil, isReady, readyCountOf, statusOf, type CallToPlayStatus, } from '../../lib/callToPlay'; import { Game, Nomination } from '../../lib/types'; interface Props { nominations: ReadonlyArray; games: ReadonlyArray; accent: string; onOpen: (callId: string) => void; } const LABEL = { running: 'Running', cancelled: 'Cancelled', scheduled: 'Scheduled', call: 'Call to Play', soon: 'Starting soon', ready: 'Ready', expired: 'Time’s up', } as const; type TickerStatus = CallToPlayStatus; const RANK: Record = { expired: 0, ready: 1, soon: 2, call: 3, scheduled: 3, running: 4, cancelled: 4, }; const tickerStatusOf = (nomination: Nomination, now: number): TickerStatus => statusOf(nomination, now); const MiniBubbles = ({ nomination, now }: { nomination: Nomination; now: number }) => { const entries = Object.entries(nomination.participants); return ( {entries.slice(0, 6).map(([participantId, participant]) => { const name = participant.name; const ready = isReady(participant, now); const state = ready ? 'ready' : participant.status === 'in' ? 'in' : 'pending'; const initials = name.replace(/[^a-z0-9]/gi, '').slice(0, 2).toUpperCase(); const remaining = (participant.readyAt ?? now) - now; return ( {initials} {ready && } {state === 'pending' && ( {formatCountdownShort(remaining)} )} ); })} {entries.length > 6 && +{entries.length - 6}} ); }; export const CallToPlayTicker = ({ nominations, games, accent, onOpen }: Props) => { const now = Date.now(); const gameById = new Map(games.map(game => [game.id, game])); const rows = nominations.map(nomination => ({ nomination, status: tickerStatusOf(nomination, now), })).sort((left, right) => RANK[left.status] - RANK[right.status] || left.nomination.deadline - right.nomination.deadline ); if (rows.length === 0) return null; return (
{rows.map(({ nomination, status }) => { const game = gameById.get(nomination.gameId); const ready = readyCountOf(nomination, now); const total = Object.keys(nomination.participants).length; const remaining = Math.max(0, nomination.deadline - now); const last = nomination.messages[nomination.messages.length - 1]; const count = status === 'running' || status === 'cancelled' ? `${total} players` : status === 'scheduled' ? `${total} in` : `${ready}/${nomination.maxPlayers} ready`; const terminalElapsed = now - (nomination.terminalAt ?? now); const terminalAge = terminalElapsed < 1_000 ? 'just now' : `${formatCountdownShort(terminalElapsed)} ago`; const time = status === 'running' ? `started ${terminalAge}` : status === 'cancelled' ? `cancelled ${terminalAge}` : status === 'ready' ? 'waiting to start' : status === 'expired' ? `${formatCountdownShort(now - nomination.deadline)} ago · waiting for caller` : status === 'scheduled' ? `${formatClock(nomination.scheduledFor!)} · ${formatUntil(nomination.scheduledFor! - now)}` : nomination.scheduledFor !== null ? `starts ${formatClock(nomination.scheduledFor)} · ${formatCountdown(remaining)}` : formatCountdown(remaining); return ( ); })}
); };