From 725d9e280243faeafed367fe04ef4d61391ae510 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Sun, 19 Jul 2026 17:23:47 +0300 Subject: [PATCH] Fix broken images --- app/features/cv/components/AbilityGrid.tsx | 23 +++++------------ app/features/cv/components/DeathCard.tsx | 15 +++++------ app/features/cv/components/MinimapCard.tsx | 25 ++++++++----------- app/features/cv/components/ScoreboardCard.tsx | 18 ++++++------- .../cv/components/ScoreboardOwnCard.tsx | 14 +++++------ app/features/cv/core/ability-harvest.ts | 3 ++- app/features/cv/cv-types.ts | 2 +- app/features/cv/routes/cv.tsx | 7 ++++++ 8 files changed, 50 insertions(+), 57 deletions(-) diff --git a/app/features/cv/components/AbilityGrid.tsx b/app/features/cv/components/AbilityGrid.tsx index 3f18047e1..b68f8e7b2 100644 --- a/app/features/cv/components/AbilityGrid.tsx +++ b/app/features/cv/components/AbilityGrid.tsx @@ -4,11 +4,12 @@ */ import { useState } from "react"; -import { CV_ASSETS_URL } from "~/utils/urls"; +import { Ability } from "~/components/Ability"; +import type { CvAbility } from "../cv-types"; const ROW_LABELS = ["head", "clothes", "shoes"] as const; -export function AbilityGrid({ abilities }: { abilities: string[][] }) { +export function AbilityGrid({ abilities }: { abilities: CvAbility[][] }) { return ( @@ -17,16 +18,7 @@ export function AbilityGrid({ abilities }: { abilities: string[][] }) { {row.map((id, j) => ( ))} @@ -40,7 +32,7 @@ export function AbilityGrid({ abilities }: { abilities: string[][] }) { * Click-to-toggle popover showing a player's ability grid; the trigger is * the head-main ability icon. Closes when the pointer leaves it. */ -export function AbilityPopover({ abilities }: { abilities: string[][] }) { +export function AbilityPopover({ abilities }: { abilities: CvAbility[][] }) { const [open, setOpen] = useState(false); const trigger = abilities[0]?.[0]; if (!trigger) return null; @@ -53,10 +45,7 @@ export function AbilityPopover({ abilities }: { abilities: string[][] }) { onClick={() => setOpen((o) => !o)} title="Show abilities (from death events)" > - abilities + {open && (
diff --git a/app/features/cv/components/DeathCard.tsx b/app/features/cv/components/DeathCard.tsx index 66fa6fb3f..a0068ba09 100644 --- a/app/features/cv/components/DeathCard.tsx +++ b/app/features/cv/components/DeathCard.tsx @@ -1,4 +1,5 @@ -import { CV_ASSETS_URL } from "~/utils/urls"; +import { WeaponImage } from "~/components/Image"; +import type { MainWeaponId } from "~/modules/in-game-lists/types"; import type { DeathData } from "../core/detectors/death/index"; import { AbilityGrid } from "./AbilityGrid"; import { saveFixtureFromEvent } from "./fixture-export"; @@ -52,14 +53,14 @@ export function DeathCard(props: {
- {data.weaponId !== null && data.weaponType === "MAIN" && ( - {weaponName - )} + ) : null}
diff --git a/app/features/cv/components/MinimapCard.tsx b/app/features/cv/components/MinimapCard.tsx index 0382c7c22..bd0dea154 100644 --- a/app/features/cv/components/MinimapCard.tsx +++ b/app/features/cv/components/MinimapCard.tsx @@ -1,26 +1,21 @@ -import { CV_ASSETS_URL } from "~/utils/urls"; +import { Ability } from "~/components/Ability"; +import { WeaponImage } from "~/components/Image"; import type { MinimapData, MinimapEnemy, MinimapTeammate, } from "../core/detectors/minimap/index"; +import type { CvAbility } from "../cv-types"; import { saveFixtureFromEvent } from "./fixture-export"; import { formatTime } from "./format"; -import { mainWeaponLabel, stageLabel } from "./labels"; +import { stageLabel } from "./labels"; -function AbilityRow({ abilities }: { abilities: (string | null)[] }) { +function AbilityRow({ abilities }: { abilities: (CvAbility | null)[] }) { return ( <> {abilities.map((id, i) => id ? ( - {id} + ) : ( ? @@ -44,11 +39,11 @@ function PlayerRow({
diff --git a/app/features/cv/components/ScoreboardOwnCard.tsx b/app/features/cv/components/ScoreboardOwnCard.tsx index 2eb875422..e380e2866 100644 --- a/app/features/cv/components/ScoreboardOwnCard.tsx +++ b/app/features/cv/components/ScoreboardOwnCard.tsx @@ -1,4 +1,4 @@ -import { CV_ASSETS_URL } from "~/utils/urls"; +import { WeaponImage } from "~/components/Image"; import type { ScoreboardOwnData } from "../core/detectors/scoreboard-own/index"; import { AbilityGrid } from "./AbilityGrid"; import { saveFixtureFromEvent } from "./fixture-export"; @@ -59,14 +59,14 @@ export function ScoreboardOwnCard(props: {
- {data.weaponId !== null && ( - {mainWeaponLabel(data.weaponId) - )} + ) : null}
diff --git a/app/features/cv/core/ability-harvest.ts b/app/features/cv/core/ability-harvest.ts index 4ac30e5f7..75c9b8a5e 100644 --- a/app/features/cv/core/ability-harvest.ts +++ b/app/features/cv/core/ability-harvest.ts @@ -7,6 +7,7 @@ * and weapon id. */ +import type { CvAbility } from "../cv-types"; import { DEATH_EVENT_TYPE, type DeathData } from "./detectors/death/index"; import { SCOREBOARD_EVENT_TYPES } from "./detectors/registry"; import type { @@ -16,7 +17,7 @@ import type { import type { DetectedEvent } from "./detectors/types"; /** player row index (0-7) → [head, clothes, shoes] ability-id rows */ -export type PlayerAbilityMap = Map; +export type PlayerAbilityMap = Map; /** * Match a death's killer to a scoreboard player row. Both signals are OCR diff --git a/app/features/cv/cv-types.ts b/app/features/cv/cv-types.ts index 92ecfec9f..55a4a3861 100644 --- a/app/features/cv/cv-types.ts +++ b/app/features/cv/cv-types.ts @@ -17,7 +17,7 @@ export type CvLobby = (typeof CV_LOBBIES)[number]; * unrecognized marker (the UNKNOWN template in assets/cv/abilities — * distinct from null, which means the badge was covered/absent). */ -export type CvAbility = Ability | "UNKNOWN"; +export type CvAbility = Ability | "UNKNOWN"; // xxx: why not AbilityWithUnknown const MAIN_WEAPON_ID_SET: ReadonlySet = new Set(mainWeaponIds); const ABILITY_SET: ReadonlySet = new Set(abilities.map((a) => a.name)); diff --git a/app/features/cv/routes/cv.tsx b/app/features/cv/routes/cv.tsx index 5b027ec26..19fb7d883 100644 --- a/app/features/cv/routes/cv.tsx +++ b/app/features/cv/routes/cv.tsx @@ -3,6 +3,13 @@ import type { MetaFunction } from "react-router"; import { Placeholder } from "~/components/Placeholder"; import { useHydrated } from "~/hooks/useHydrated"; import { metaTags } from "~/utils/remix"; +import type { SendouRouteHandle } from "~/utils/remix.server"; + +// `builds` powers the empty/UNKNOWN ability label in ; the weapon +// and game-misc namespaces the CV cards rely on are always loaded. +export const handle: SendouRouteHandle = { + i18n: ["builds"], +}; export const meta: MetaFunction = (args) => { return metaTags({
{ROW_LABELS[i]} - {id} +
{player.name ?? ""} {player.weaponId !== null ? ( - {mainWeaponLabel(player.weaponId) ) : ( "?" diff --git a/app/features/cv/components/ScoreboardCard.tsx b/app/features/cv/components/ScoreboardCard.tsx index fe54712e3..da168523d 100644 --- a/app/features/cv/components/ScoreboardCard.tsx +++ b/app/features/cv/components/ScoreboardCard.tsx @@ -1,4 +1,4 @@ -import { CV_ASSETS_URL } from "~/utils/urls"; +import { WeaponImage } from "~/components/Image"; import type { PlayerAbilityMap } from "../core/ability-harvest"; import type { ScoreboardData, @@ -27,17 +27,17 @@ function PlayerRows({
- {p.weaponId !== null && ( - {String(p.weaponId)} - )} - {abilities?.has(offset + i) && ( + ) : null} + {abilities?.has(offset + i) ? ( - )} + ) : null} {p.name || "?"}