Only show ungrouped events as fallback

This commit is contained in:
Kalle
2026-08-05 21:50:10 +03:00
parent 6c29a17732
commit 2bbabccaaf
4 changed files with 55 additions and 66 deletions

View File

@@ -1,7 +1,8 @@
/**
* One light line summarizing a scan's raw detections as per-type counts,
* with a toggle for the full event card feed — the matches are the main
* view, the events stay one click away.
* One light line summarizing raw detections as per-type counts, with a
* toggle for their event card feed. Callers pass only events not covered
* by a match card — a fallback for detections not yet grouped, hidden
* entirely once everything lives in a match.
*/
import { DEATH_EVENT_TYPE } from "../core/detectors/death/index";

View File

@@ -207,6 +207,10 @@ export function LivePage({
}
}, [deviceId, refreshFeed, send]);
const builtMatches = buildScannerMatches(feed);
const groupedEvents = new Set(builtMatches.flatMap((b) => b.sources));
const ungroupedFeed = feed.filter((e) => !groupedEvents.has(e));
const stop = useCallback(() => {
stopRef.current?.();
stopRef.current = null;
@@ -314,55 +318,53 @@ export function LivePage({
{feed.length === 0 ? (
<p className="score">No detections yet.</p>
) : null}
{[...buildScannerMatches(feed)]
.reverse()
.map((built, reverseIndex) => {
const id = built.sources[0]!.id!;
const ingestable = isIngestableMatch(built.match);
return (
<MatchCard
key={id}
match={built.match}
live={
running && reverseIndex === 0 && built.match.winner === null
}
inProgress={reverseIndex === 0 && built.match.winner === null}
ingestable={ingestable}
send={aggregateSendStatus(built.sources)}
onSend={
SENDOU_UPLOAD_ENABLED && sendouUser && ingestable
? () => void send(matchContaining(id), { manual: true })
: undefined
}
>
{withoutRepeatEvents(built.sources).map((e) => (
<EventCard
key={e.id}
type={e.type}
t={e.t}
confidence={e.confidence}
data={e.data as FixtureData}
thumbnail={e.thumbnail}
detectedAt={e.detectedAt}
getFrame={
e.hasFrame && e.id !== undefined
? () => loadEventFrame(e.id!)
: undefined
}
/>
))}
</MatchCard>
);
})}
{feed.length > 0 ? (
{[...builtMatches].reverse().map((built, reverseIndex) => {
const id = built.sources[0]!.id!;
const ingestable = isIngestableMatch(built.match);
return (
<MatchCard
key={id}
match={built.match}
live={
running && reverseIndex === 0 && built.match.winner === null
}
inProgress={reverseIndex === 0 && built.match.winner === null}
ingestable={ingestable}
send={aggregateSendStatus(built.sources)}
onSend={
SENDOU_UPLOAD_ENABLED && sendouUser && ingestable
? () => void send(matchContaining(id), { manual: true })
: undefined
}
>
{withoutRepeatEvents(built.sources).map((e) => (
<EventCard
key={e.id}
type={e.type}
t={e.t}
confidence={e.confidence}
data={e.data as FixtureData}
thumbnail={e.thumbnail}
detectedAt={e.detectedAt}
getFrame={
e.hasFrame && e.id !== undefined
? () => loadEventFrame(e.id!)
: undefined
}
/>
))}
</MatchCard>
);
})}
{ungroupedFeed.length > 0 ? (
<EventsSummary
events={feed}
events={ungroupedFeed}
open={eventsOpen}
onToggle={() => setEventsOpen(!eventsOpen)}
/>
) : null}
{eventsOpen
? feed.map((e) => (
? ungroupedFeed.map((e) => (
<EventCard
key={e.id}
type={e.type}

View File

@@ -132,6 +132,8 @@ export function VodPage({
const builtMatches = buildScannerMatches(matches.map((m) => m.event));
const vodMatchByEvent = new Map(matches.map((m) => [m.event, m] as const));
const groupedEvents = new Set(builtMatches.flatMap((b) => b.sources));
const ungroupedMatches = matches.filter((m) => !groupedEvents.has(m.event));
// "Upload as results" sends the whole scan in one go, so its outcome maps
// onto every ingestable card; a partial failure (some chunks sent, some
@@ -607,20 +609,20 @@ export function VodPage({
</MatchCard>
);
})}
{matches.length > 0 ? (
{ungroupedMatches.length > 0 ? (
<EventsSummary
events={matches.map((m) => m.event)}
events={ungroupedMatches.map((m) => m.event)}
open={eventsOpen}
onToggle={() => setEventsOpen(!eventsOpen)}
/>
) : null}
{/* newest detection on top; storage keeps ascending video-time order */}
{eventsOpen
? [...matches]
? [...ungroupedMatches]
.reverse()
.map((m, i) => (
<EventCard
key={matches.length - 1 - i}
key={ungroupedMatches.length - 1 - i}
type={m.event.type}
t={m.event.t}
confidence={m.event.confidence}

View File

@@ -124,22 +124,6 @@ export function updateEventsSend(
);
}
export function deleteEvent(id: number): Promise<void> {
return db().then(
(database) =>
new Promise<void>((resolve, reject) => {
const transaction = database.transaction(
[EVENTS_STORE, FRAMES_STORE],
"readwrite",
);
transaction.objectStore(EVENTS_STORE).delete(id);
transaction.objectStore(FRAMES_STORE).delete(id);
transaction.oncomplete = () => resolve();
transaction.onerror = () => reject(transaction.error);
}),
);
}
export function listEvents(): Promise<StoredEvent[]> {
return tx(
EVENTS_STORE,