opencs2-dataset-viewer / src /lib /components /match-map.svelte
blanchon's picture
Add eslint with eslint-plugin-better-tailwindcss
8899818
<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}