Spaces:
Running
Running
| <script lang="ts"> | |
| import MapPreview from '$lib/components/map-preview.svelte'; | |
| import { Skeleton } from '$lib/components/ui/skeleton'; | |
| import { loadRoundWorld, snapshotAt, type RoundWorld } from '$lib/api/world'; | |
| import { playerColor } from '$lib/utils/player-colors'; | |
| import type { PreviewChunk } from '$lib/types'; | |
| type Props = { | |
| matchId: number; | |
| mapName: string; | |
| round: number; | |
| chunks: PreviewChunk[]; | |
| virtualTime: number; | |
| activePlayer?: number | null; | |
| availablePlayers?: Set<number> | null; | |
| onSelect?: (player: number) => void; | |
| }; | |
| let { | |
| matchId, | |
| mapName, | |
| round, | |
| chunks, | |
| virtualTime, | |
| activePlayer = null, | |
| availablePlayers = null, | |
| onSelect | |
| }: Props = $props(); | |
| let world = $state<RoundWorld | null>(null); | |
| let error = $state<string | null>(null); | |
| // On round change, `round` updates a render before `chunks` (the parent's | |
| // `previews` state is replaced after an async fetch). Filter here so we | |
| // only kick off the world load once chunks for the *new* round have | |
| // arrived — otherwise we'd cache an empty result under the new key. | |
| const roundChunks = $derived(chunks.filter((c) => c.round === round)); | |
| $effect(() => { | |
| const _matchId = matchId; | |
| const _mapName = mapName; | |
| const _round = round; | |
| const _chunks = roundChunks; | |
| if (!_chunks.length) { | |
| world = null; | |
| error = null; | |
| return; | |
| } | |
| let cancelled = false; | |
| const ctrl = new AbortController(); | |
| world = null; | |
| error = null; | |
| loadRoundWorld(_matchId, _mapName, _round, _chunks, { signal: ctrl.signal }) | |
| .then((w) => { | |
| if (!cancelled) world = w; | |
| }) | |
| .catch((e) => { | |
| if (cancelled || (e as Error)?.name === 'AbortError') return; | |
| error = (e as Error).message ?? 'Failed to load world data'; | |
| }); | |
| return () => { | |
| cancelled = true; | |
| ctrl.abort(); | |
| }; | |
| }); | |
| const players = $derived( | |
| world | |
| ? snapshotAt(world, virtualTime).map((s) => ({ | |
| ...s, | |
| color: playerColor(s.player), | |
| slot: s.player | |
| })) | |
| : [] | |
| ); | |
| </script> | |
| {#if error} | |
| <div class="rounded-md border bg-muted/30 p-3 text-xs text-muted-foreground"> | |
| Map data unavailable: {error} | |
| </div> | |
| {:else if !world} | |
| <Skeleton class="aspect-square w-full rounded-md" /> | |
| {:else} | |
| <MapPreview {mapName} {players} {activePlayer} {availablePlayers} {onSelect} /> | |
| {/if} | |