File size: 2,296 Bytes
31d3580
 
 
 
 
 
 
 
 
 
 
 
 
91677d6
 
 
31d3580
 
91677d6
 
 
 
 
 
 
 
 
 
31d3580
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8899818
31d3580
 
 
 
 
91677d6
31d3580
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
<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}