mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-30 23:27:55 -05:00
Fixtures/screenshots tweaks
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import clsx from "clsx";
|
||||
import { ExternalLink } from "lucide-react";
|
||||
import { type ReactNode, useEffect, useState } from "react";
|
||||
import { SendouButton } from "~/components/elements/Button";
|
||||
import { useSearchParam } from "~/modules/search-params/hooks";
|
||||
import { mainWeaponImageUrl, SCANNER_PAGE } from "~/utils/urls";
|
||||
import type { Roi } from "../core/canonical";
|
||||
@@ -12,6 +14,7 @@ import { newInspectKey, putInspectFrame } from "../store/inspect";
|
||||
import styles from "./FixturesPage.module.css";
|
||||
import { drawNormalizedCanvas } from "./normalized-canvas";
|
||||
import { formatTimer, RoiCrop } from "./ScreenshotPage";
|
||||
import { SessionHeader } from "./SessionHeader";
|
||||
|
||||
const FIXTURES_ENDPOINT = "/scanner/fixtures";
|
||||
/** Filtering down to this many cases opens every card, for one-glance review */
|
||||
@@ -76,25 +79,27 @@ export function FixturesPage() {
|
||||
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<div className={styles.controls}>
|
||||
<input
|
||||
type="search"
|
||||
className={styles.filter}
|
||||
value={q}
|
||||
onChange={(e) => setQ(e.target.value)}
|
||||
placeholder="Narrow by name substring — comma separates alternatives"
|
||||
/>
|
||||
<span className={styles.count}>
|
||||
{fixtures
|
||||
? `${filtered.length} / ${fixtures.length} fixtures`
|
||||
: "loading…"}
|
||||
</span>
|
||||
</div>
|
||||
<p className={styles.hint}>
|
||||
Ground-truth review: check each expected label against its frame. The
|
||||
filter lives in the URL, so a narrowed selection can be shared as a
|
||||
link.
|
||||
</p>
|
||||
<SessionHeader>
|
||||
<div className={styles.controls}>
|
||||
<input
|
||||
type="search"
|
||||
className={styles.filter}
|
||||
value={q}
|
||||
onChange={(e) => setQ(e.target.value)}
|
||||
placeholder="Narrow by name substring — comma separates alternatives"
|
||||
/>
|
||||
<span className={styles.count}>
|
||||
{fixtures
|
||||
? `${filtered.length} / ${fixtures.length} fixtures`
|
||||
: "loading…"}
|
||||
</span>
|
||||
</div>
|
||||
<p className={styles.hint}>
|
||||
Ground-truth review: check each expected label against its frame. The
|
||||
filter lives in the URL, so a narrowed selection can be shared as a
|
||||
link.
|
||||
</p>
|
||||
</SessionHeader>
|
||||
{error ? <p className="text-error">{error}</p> : null}
|
||||
{[...groups.entries()].map(([detector, group]) => (
|
||||
<section key={detector} className={styles.group}>
|
||||
@@ -150,9 +155,14 @@ function FixtureCard(props: { fixture: FixtureListItem; autoExpand: boolean }) {
|
||||
{fixture.name}
|
||||
</button>
|
||||
<span className={styles.cardEvent}>{fixture.expected.event}</span>
|
||||
<button type="button" onClick={() => inspectFixture(url)}>
|
||||
<SendouButton
|
||||
size="small"
|
||||
variant="outlined"
|
||||
icon={<ExternalLink />}
|
||||
onClick={() => inspectFixture(url)}
|
||||
>
|
||||
Inspect
|
||||
</button>
|
||||
</SendouButton>
|
||||
</header>
|
||||
{open ? (
|
||||
<div className={styles.cardBody}>
|
||||
|
||||
@@ -50,6 +50,7 @@ import { downloadCsv } from "./download";
|
||||
import { type CardData, downloadExpectedJson } from "./fixture-export";
|
||||
import { drawNormalizedCanvas } from "./normalized-canvas";
|
||||
import styles from "./ScreenshotPage.module.css";
|
||||
import { SessionHeader } from "./SessionHeader";
|
||||
|
||||
type Result = Extract<WorkerResponse, { kind: "result" }>;
|
||||
|
||||
@@ -528,23 +529,25 @@ export function ScreenshotPage() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Dropzone onFile={(file: File) => void analyze(file)}>
|
||||
Drop a frame (PNG/JPEG) here, or{" "}
|
||||
<label>
|
||||
pick a file
|
||||
<input
|
||||
type="file"
|
||||
accept="image/png,image/jpeg"
|
||||
style={{ display: "none" }}
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
e.target.value = ""; // allow re-picking the same file
|
||||
if (file) void analyze(file);
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
{busy ? " — analyzing…" : null}
|
||||
</Dropzone>
|
||||
<SessionHeader>
|
||||
<Dropzone onFile={(file: File) => void analyze(file)}>
|
||||
Drop a frame (PNG/JPEG) here, or{" "}
|
||||
<label>
|
||||
pick a file
|
||||
<input
|
||||
type="file"
|
||||
accept="image/png,image/jpeg"
|
||||
style={{ display: "none" }}
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
e.target.value = ""; // allow re-picking the same file
|
||||
if (file) void analyze(file);
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
{busy ? " — analyzing…" : null}
|
||||
</Dropzone>
|
||||
</SessionHeader>
|
||||
{error ? <p className="text-error">{error}</p> : null}
|
||||
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user