mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-08-28 06:08:13 -05:00
Fix inspect button
This commit is contained in:
@@ -280,9 +280,16 @@ export function ScreenshotPage() {
|
||||
useEffect(() => {
|
||||
if (!inspectKey) return;
|
||||
setInspectKey(null);
|
||||
void claimInspectFrame(inspectKey).then((frame) => {
|
||||
if (frame) void analyze(frame);
|
||||
});
|
||||
claimInspectFrame(inspectKey).then(
|
||||
(frame) => {
|
||||
if (frame) void analyze(frame);
|
||||
else
|
||||
setError(
|
||||
"Inspected frame did not arrive — go back to the other tab and press Inspect again",
|
||||
);
|
||||
},
|
||||
(e) => setError(String(e)),
|
||||
);
|
||||
}, [inspectKey, setInspectKey, analyze]);
|
||||
|
||||
const event = active?.events[0] as DetectedEvent<CardData> | undefined;
|
||||
@@ -291,6 +298,7 @@ export function ScreenshotPage() {
|
||||
const isDeath = activeDetector === "death";
|
||||
const isMapStart = activeDetector === "map-start";
|
||||
const isOwn = activeDetector === "scoreboard-own";
|
||||
const isMinimap = activeDetector === "minimap";
|
||||
const winnerSide = String(event?.debug?.winnerSide ?? "left");
|
||||
const rowRois = isReplay ? replayRows(winnerSide) : scoreboardRows();
|
||||
|
||||
@@ -535,7 +543,52 @@ export function ScreenshotPage() {
|
||||
</p>
|
||||
)}
|
||||
|
||||
{frame && event && !isDeath && !isMapStart && !isOwn && (
|
||||
{frame && event && isMinimap && (
|
||||
<p>
|
||||
{(() => {
|
||||
const data = event.data as unknown as MinimapData;
|
||||
return (
|
||||
<>
|
||||
stage <b>{stageLabel(data.stage) ?? "?"}</b>
|
||||
{" · "}
|
||||
{data.spectator ? "spectator map" : "POV overlay"}
|
||||
{" · "}team{" "}
|
||||
<b>
|
||||
{data.teammates
|
||||
.map(
|
||||
(p) =>
|
||||
`${p.slot}: ${p.name ?? "?"} (${mainWeaponLabel(p.weaponId) ?? "?"})`,
|
||||
)
|
||||
.join(", ") || "—"}
|
||||
</b>
|
||||
{data.enemies.length > 0 && (
|
||||
<>
|
||||
{" · "}enemies{" "}
|
||||
<b>
|
||||
{data.enemies
|
||||
.map(
|
||||
(p) =>
|
||||
`${p.name ?? "?"} (${mainWeaponLabel(p.weaponId) ?? "?"})`,
|
||||
)
|
||||
.join(", ")}
|
||||
</b>
|
||||
</>
|
||||
)}
|
||||
{!data.spectator && (
|
||||
<>
|
||||
<br />
|
||||
{minimap.CARD_LAYOUTS.map((card) => (
|
||||
<RoiCrop key={card.slot} frame={frame} roi={card.name} />
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{frame && event && !isDeath && !isMapStart && !isOwn && !isMinimap && (
|
||||
<table className="inspector">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
@@ -39,67 +39,118 @@ function extractFrames(source: IDBObjectStore, frames: IDBObjectStore): void {
|
||||
};
|
||||
}
|
||||
|
||||
const ALL_STORES = [
|
||||
EVENTS_STORE,
|
||||
FRAMES_STORE,
|
||||
VODS_STORE,
|
||||
VOD_EVENTS_STORE,
|
||||
VOD_FRAMES_STORE,
|
||||
INSPECT_FRAMES_STORE,
|
||||
];
|
||||
|
||||
// xxx: get rid of migrate before we go live with this
|
||||
/**
|
||||
* Versioned migrations: each `oldVersion < N` block upgrades a database from
|
||||
* below version N and runs exactly once per database. Any schema change —
|
||||
* including one to an EXISTING store (new index, moved field) — must be a new
|
||||
* block plus a DB_VERSION bump, never an edit to an old block: databases that
|
||||
* already ran the old block would silently skip the change otherwise.
|
||||
* Brings any database — fresh, older-versioned or drifted (a dev database
|
||||
* whose version was bumped past DB_VERSION without these stores) — to the
|
||||
* current schema. Store creation is existence-guarded rather than
|
||||
* version-gated so a reopen at version+1 (see openDb) can heal drift; only
|
||||
* data moves stay keyed on the version they shipped in.
|
||||
*/
|
||||
function migrate(
|
||||
database: IDBDatabase,
|
||||
transaction: IDBTransaction,
|
||||
oldVersion: number,
|
||||
): void {
|
||||
if (oldVersion < 2) {
|
||||
// v1/v2 era stores; contains() guards absorb the pre-versioned scheme,
|
||||
// where every creation was unconditionally contains()-gated
|
||||
if (!database.objectStoreNames.contains(EVENTS_STORE)) {
|
||||
const store = database.createObjectStore(EVENTS_STORE, {
|
||||
keyPath: "id",
|
||||
autoIncrement: true,
|
||||
});
|
||||
store.createIndex("t", "t");
|
||||
store.createIndex("detectedAt", "detectedAt");
|
||||
}
|
||||
if (!database.objectStoreNames.contains(VODS_STORE)) {
|
||||
database.createObjectStore(VODS_STORE, { keyPath: "name" });
|
||||
}
|
||||
if (!database.objectStoreNames.contains(VOD_EVENTS_STORE)) {
|
||||
const store = database.createObjectStore(VOD_EVENTS_STORE, {
|
||||
keyPath: "id",
|
||||
autoIncrement: true,
|
||||
});
|
||||
store.createIndex("vod", "vod");
|
||||
}
|
||||
if (!database.objectStoreNames.contains(EVENTS_STORE)) {
|
||||
const store = database.createObjectStore(EVENTS_STORE, {
|
||||
keyPath: "id",
|
||||
autoIncrement: true,
|
||||
});
|
||||
store.createIndex("t", "t");
|
||||
store.createIndex("detectedAt", "detectedAt");
|
||||
}
|
||||
if (!database.objectStoreNames.contains(VODS_STORE)) {
|
||||
database.createObjectStore(VODS_STORE, { keyPath: "name" });
|
||||
}
|
||||
if (!database.objectStoreNames.contains(VOD_EVENTS_STORE)) {
|
||||
const store = database.createObjectStore(VOD_EVENTS_STORE, {
|
||||
keyPath: "id",
|
||||
autoIncrement: true,
|
||||
});
|
||||
store.createIndex("vod", "vod");
|
||||
}
|
||||
if (!database.objectStoreNames.contains(FRAMES_STORE)) {
|
||||
database.createObjectStore(FRAMES_STORE);
|
||||
}
|
||||
if (!database.objectStoreNames.contains(VOD_FRAMES_STORE)) {
|
||||
database.createObjectStore(VOD_FRAMES_STORE);
|
||||
}
|
||||
if (oldVersion < 3) {
|
||||
// frame blobs move out of the event records into keyed frame stores
|
||||
const frames = database.createObjectStore(FRAMES_STORE);
|
||||
const vodFrames = database.createObjectStore(VOD_FRAMES_STORE);
|
||||
extractFrames(transaction.objectStore(EVENTS_STORE), frames);
|
||||
extractFrames(transaction.objectStore(VOD_EVENTS_STORE), vodFrames);
|
||||
// v1/v2 era kept the frame blobs embedded in the event records
|
||||
extractFrames(
|
||||
transaction.objectStore(EVENTS_STORE),
|
||||
transaction.objectStore(FRAMES_STORE),
|
||||
);
|
||||
extractFrames(
|
||||
transaction.objectStore(VOD_EVENTS_STORE),
|
||||
transaction.objectStore(VOD_FRAMES_STORE),
|
||||
);
|
||||
}
|
||||
if (oldVersion < 4) {
|
||||
if (!database.objectStoreNames.contains(INSPECT_FRAMES_STORE)) {
|
||||
database.createObjectStore(INSPECT_FRAMES_STORE);
|
||||
}
|
||||
}
|
||||
|
||||
function openDb(): Promise<IDBDatabase> {
|
||||
function openAt(version?: number): Promise<IDBDatabase> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = indexedDB.open(DB_NAME, DB_VERSION);
|
||||
const req =
|
||||
version === undefined
|
||||
? indexedDB.open(DB_NAME)
|
||||
: indexedDB.open(DB_NAME, version);
|
||||
req.onupgradeneeded = (event) => {
|
||||
migrate(req.result, req.transaction!, event.oldVersion);
|
||||
};
|
||||
req.onblocked = () => {
|
||||
// biome-ignore lint/suspicious/noConsole: the only diagnostic channel for a hang caused by other tabs
|
||||
console.warn(
|
||||
"scanner database upgrade is blocked — close or reload other sendou.ink tabs",
|
||||
);
|
||||
};
|
||||
req.onsuccess = () => resolve(req.result);
|
||||
req.onerror = () => reject(req.error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens at whatever version exists, then upgrades when the schema is behind —
|
||||
* either an honest old version or a drifted database sitting at/above
|
||||
* DB_VERSION without all stores, which a plain open(DB_VERSION) would
|
||||
* silently accept (or reject with VersionError).
|
||||
*/
|
||||
async function openDb(): Promise<IDBDatabase> {
|
||||
let database = await openAt();
|
||||
const missingStore = ALL_STORES.some(
|
||||
(name) => !database.objectStoreNames.contains(name),
|
||||
);
|
||||
if (database.version < DB_VERSION || missingStore) {
|
||||
const nextVersion = Math.max(DB_VERSION, database.version + 1);
|
||||
database.close();
|
||||
database = await openAt(nextVersion);
|
||||
}
|
||||
return database;
|
||||
}
|
||||
|
||||
let dbPromise: Promise<IDBDatabase> | null = null;
|
||||
export function db(): Promise<IDBDatabase> {
|
||||
dbPromise ??= openDb();
|
||||
dbPromise ??= openDb().then((database) => {
|
||||
// when another tab needs to upgrade, release the connection instead of
|
||||
// blocking that tab forever; the next call here reconnects fresh
|
||||
database.onversionchange = () => {
|
||||
database.close();
|
||||
dbPromise = null;
|
||||
};
|
||||
return database;
|
||||
});
|
||||
return dbPromise;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user