mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-27 13:47:56 -05:00
Detection when debug as separate tab
This commit is contained in:
@@ -315,15 +315,18 @@ button.expand {
|
||||
|
||||
/* the expanded content, indented under its card via a rail */
|
||||
.details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--s-4);
|
||||
margin-inline-start: var(--s-2-5);
|
||||
padding: var(--s-1) 0 var(--s-2) var(--s-3);
|
||||
border-inline-start: var(--border-width) solid var(--color-border);
|
||||
animation: scanner-detail-in 0.25s ease both;
|
||||
}
|
||||
|
||||
.summary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--s-4);
|
||||
}
|
||||
|
||||
@keyframes scanner-detail-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
|
||||
@@ -4,14 +4,21 @@
|
||||
* the game's clips and, once the game is over, its upload state beside the
|
||||
* expand arrow. Expanded it shows the data and
|
||||
* nothing interpreted: the scoreboard, the objective + player-status
|
||||
* timeline and deaths and kills (each with a ▶ when a clip covers it).
|
||||
* timeline and deaths and kills (each with a ▶ when a clip covers it). In
|
||||
* debug mode that summary sits in a tab beside the raw events and frames.
|
||||
*/
|
||||
import clsx from "clsx";
|
||||
import { ChevronDown, Play } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useId, useState } from "react";
|
||||
import { Ability } from "~/components/Ability";
|
||||
import { SendouButton } from "~/components/elements/Button";
|
||||
import { SendouPopover } from "~/components/elements/Popover";
|
||||
import {
|
||||
SendouTab,
|
||||
SendouTabList,
|
||||
SendouTabPanel,
|
||||
SendouTabs,
|
||||
} from "~/components/elements/Tabs";
|
||||
import { GameTimeline } from "~/components/GameTimeline";
|
||||
import { Image, ModeImage, WeaponImage } from "~/components/Image";
|
||||
import { LocaleTime } from "~/components/LocaleTime";
|
||||
@@ -108,6 +115,7 @@ export function MatchCard({
|
||||
}) {
|
||||
const { match } = built;
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const tabIdPrefix = useId();
|
||||
// fixed at mount: re-rendering must not cut the animation short
|
||||
const [enter] = useState(justFormed);
|
||||
const [prevUploadKind, setPrevUploadKind] = useState(upload.kind);
|
||||
@@ -226,6 +234,16 @@ export function MatchCard({
|
||||
</div>
|
||||
);
|
||||
|
||||
const summary = (
|
||||
<div className={styles.summary}>
|
||||
<Scoreboard match={match} result={result} />
|
||||
{match.objective || match.playerStatus ? (
|
||||
<GameTimeline {...gameTimelineProps(match, matchOrigin, TEAM_LABELS)} />
|
||||
) : null}
|
||||
<DeathsAndKills built={built} clips={clips} onPlayClip={onPlayClip} />
|
||||
</div>
|
||||
);
|
||||
|
||||
const card =
|
||||
match.stage !== null ? (
|
||||
<StageBannerBox stageId={match.stage} className={className}>
|
||||
@@ -240,16 +258,22 @@ export function MatchCard({
|
||||
{card}
|
||||
{expanded && expandable ? (
|
||||
<div className={styles.details}>
|
||||
<Scoreboard match={match} result={result} />
|
||||
{match.objective || match.playerStatus ? (
|
||||
<GameTimeline
|
||||
{...gameTimelineProps(match, matchOrigin, TEAM_LABELS)}
|
||||
/>
|
||||
) : null}
|
||||
<DeathsAndKills built={built} clips={clips} onPlayClip={onPlayClip} />
|
||||
{debug ? (
|
||||
<RawDetections sources={built.sources} getFrame={getFrame} />
|
||||
) : null}
|
||||
<SendouTabs>
|
||||
<SendouTabList>
|
||||
<SendouTab id={`${tabIdPrefix}-summary`}>Summary</SendouTab>
|
||||
<SendouTab id={`${tabIdPrefix}-events`}>Events</SendouTab>
|
||||
</SendouTabList>
|
||||
<SendouTabPanel id={`${tabIdPrefix}-summary`}>
|
||||
{summary}
|
||||
</SendouTabPanel>
|
||||
<SendouTabPanel id={`${tabIdPrefix}-events`}>
|
||||
<RawDetections sources={built.sources} getFrame={getFrame} />
|
||||
</SendouTabPanel>
|
||||
</SendouTabs>
|
||||
) : (
|
||||
summary
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -1,23 +1,7 @@
|
||||
.details {
|
||||
font-size: var(--font-2xs);
|
||||
}
|
||||
|
||||
.summary {
|
||||
cursor: pointer;
|
||||
color: var(--color-text-high);
|
||||
font-weight: var(--weight-semi);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
.events {
|
||||
--scanner-card-padding: var(--s-2-5) 14px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--s-2);
|
||||
margin-top: var(--s-2);
|
||||
margin-inline-start: var(--s-2-5);
|
||||
padding-inline-start: var(--s-3);
|
||||
border-inline-start: var(--border-width) solid var(--color-border);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
* their frames (Inspect, Save fixture). Counter/status/strip reads render as
|
||||
* the timeline instead and stay out.
|
||||
*/
|
||||
import { useState } from "react";
|
||||
import { connectAbilities } from "../core/ability-harvest";
|
||||
import { OBJECTIVE_EVENT_TYPE } from "../core/detectors/objective/index";
|
||||
import { PLAYER_STATUS_EVENT_TYPE } from "../core/detectors/objective/player-status";
|
||||
@@ -22,7 +21,6 @@ export function RawDetections({
|
||||
sources: readonly ScanEvent[];
|
||||
getFrame: (event: ScanEvent) => GetFrame | undefined;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const events = withoutRepeatEvents(sources).filter(
|
||||
(e) =>
|
||||
e.type !== OBJECTIVE_EVENT_TYPE &&
|
||||
@@ -31,31 +29,20 @@ export function RawDetections({
|
||||
);
|
||||
const abilityMap = connectAbilities(sources);
|
||||
return (
|
||||
<details
|
||||
className={styles.details}
|
||||
open={open}
|
||||
onToggle={(e) => setOpen(e.currentTarget.open)}
|
||||
>
|
||||
<summary className={styles.summary}>
|
||||
Raw detections · {sources.length}
|
||||
</summary>
|
||||
{open ? (
|
||||
<div className={styles.events}>
|
||||
{events.map((e) => (
|
||||
<EventCard
|
||||
key={e.id ?? `${e.type}-${e.t}`}
|
||||
type={e.type}
|
||||
t={e.t}
|
||||
confidence={e.confidence}
|
||||
data={e.data as FixtureData}
|
||||
abilities={abilityMap.get(e)}
|
||||
thumbnail={e.thumbnail}
|
||||
detectedAt={e.detectedAt}
|
||||
getFrame={getFrame(e)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</details>
|
||||
<div className={styles.events}>
|
||||
{events.map((e) => (
|
||||
<EventCard
|
||||
key={e.id ?? `${e.type}-${e.t}`}
|
||||
type={e.type}
|
||||
t={e.t}
|
||||
confidence={e.confidence}
|
||||
data={e.data as FixtureData}
|
||||
abilities={abilityMap.get(e)}
|
||||
thumbnail={e.thumbnail}
|
||||
detectedAt={e.detectedAt}
|
||||
getFrame={getFrame(e)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user