Spaces:
Running
Running
| <script lang="ts"> | |
| import { Button } from '$lib/components/ui/button'; | |
| import * as Tooltip from '$lib/components/ui/tooltip'; | |
| import FlagIcon from 'phosphor-svelte/lib/FlagIcon'; | |
| import ArrowRightIcon from 'phosphor-svelte/lib/ArrowRightIcon'; | |
| import ArrowLeftIcon from 'phosphor-svelte/lib/ArrowLeftIcon'; | |
| import XIcon from 'phosphor-svelte/lib/XIcon'; | |
| import { goto } from '$app/navigation'; | |
| import { page } from '$app/state'; | |
| import { | |
| addValidation, | |
| evalUrl, | |
| nextUnreviewed, | |
| reviewedKeySet, | |
| type EvalCandidate | |
| } from '$lib/eval'; | |
| import { toast } from 'svelte-sonner'; | |
| import EvalFlagDialog from './eval-flag-dialog.svelte'; | |
| interface Props { | |
| queue: EvalCandidate[]; | |
| index: number; | |
| matchId: number; | |
| mapName: string; | |
| round: number; | |
| } | |
| let { queue, index, matchId, mapName, round }: Props = $props(); | |
| let flagOpen = $state(false); | |
| // Bumped after each review action so the dialog count and the Next-skip | |
| // logic re-read localStorage without needing a reactive store. | |
| let reviewBump = $state(0); | |
| const reviewedCount = $derived.by(() => { | |
| void reviewBump; | |
| const reviewed = reviewedKeySet(); | |
| let n = 0; | |
| for (const c of queue) if (reviewed.has(`${c.matchId}|${c.mapName}|${c.round}`)) n++; | |
| return n; | |
| }); | |
| function next() { | |
| // Hitting Next without flagging implicitly validates the current candidate. | |
| if (round && index >= 0) { | |
| addValidation({ matchId, mapName, round }); | |
| reviewBump++; | |
| } | |
| const ni = nextUnreviewed(queue, index < 0 ? -1 : index, reviewedKeySet()); | |
| if (ni < 0) { | |
| toast.success('Evaluation complete', { | |
| description: `Reviewed ${reviewedCount + 1} / ${queue.length} candidates.` | |
| }); | |
| return; | |
| } | |
| goto(evalUrl(queue[ni], ni)); | |
| } | |
| function previous() { | |
| if (index <= 0) return; | |
| const pi = index - 1; | |
| goto(evalUrl(queue[pi], pi)); | |
| } | |
| function exitEval() { | |
| const url = new URL(page.url); | |
| url.searchParams.delete('eval'); | |
| url.searchParams.delete('i'); | |
| goto(url.pathname + (url.searchParams.size ? '?' + url.searchParams.toString() : '')); | |
| } | |
| const total = $derived(queue.length); | |
| const positionLabel = $derived(index < 0 ? `— / ${total}` : `${index + 1} / ${total}`); | |
| </script> | |
| <div | |
| class="border-b border-amber-500/30 bg-amber-500/10 dark:border-amber-500/25 dark:bg-amber-500/[0.07]" | |
| > | |
| <div class="mx-auto flex h-10 w-full max-w-[1600px] items-center gap-2 px-4 text-xs"> | |
| <span class="font-mono font-semibold tracking-wide text-amber-700 uppercase dark:text-amber-300" | |
| >Eval</span | |
| > | |
| <span class="text-muted-foreground tabular-nums">{positionLabel}</span> | |
| <span class="text-muted-foreground/60 tabular-nums">·</span> | |
| <span class="text-muted-foreground tabular-nums">{reviewedCount} reviewed</span> | |
| <div class="ml-auto flex items-center gap-1"> | |
| <Tooltip.Root> | |
| <Tooltip.Trigger> | |
| {#snippet child({ props })} | |
| <Button | |
| {...props} | |
| variant="ghost" | |
| size="icon-sm" | |
| onclick={previous} | |
| disabled={index <= 0} | |
| aria-label="Previous candidate" | |
| > | |
| <ArrowLeftIcon size={14} weight="bold" /> | |
| </Button> | |
| {/snippet} | |
| </Tooltip.Trigger> | |
| <Tooltip.Content side="bottom">Previous candidate</Tooltip.Content> | |
| </Tooltip.Root> | |
| <Button variant="outline" size="sm" onclick={() => (flagOpen = true)}> | |
| <FlagIcon size={14} weight="fill" /> Flag | |
| </Button> | |
| <Button onclick={next} size="sm" disabled={total === 0}> | |
| Next <ArrowRightIcon size={14} weight="bold" /> | |
| </Button> | |
| <Tooltip.Root> | |
| <Tooltip.Trigger> | |
| {#snippet child({ props })} | |
| <Button | |
| {...props} | |
| variant="ghost" | |
| size="icon-sm" | |
| onclick={exitEval} | |
| aria-label="Exit evaluation" | |
| > | |
| <XIcon size={14} weight="bold" /> | |
| </Button> | |
| {/snippet} | |
| </Tooltip.Trigger> | |
| <Tooltip.Content side="bottom">Exit evaluation</Tooltip.Content> | |
| </Tooltip.Root> | |
| </div> | |
| </div> | |
| </div> | |
| <EvalFlagDialog | |
| bind:open={flagOpen} | |
| {matchId} | |
| {mapName} | |
| {round} | |
| queueLength={total} | |
| onChange={() => reviewBump++} | |
| /> | |