From 70216080b4c828ec034d01bfd497579c8876c181 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Mon, 14 Sep 2026 18:00:04 +0300 Subject: [PATCH] Show all weapons in multi-weapon VoD when filtering --- .../vods/VodRepository.server.test.ts | 29 +++++++++++++++++++ app/features/vods/VodRepository.server.ts | 15 ++++++++-- .../2026-09-14-vods-weapon-filter-peek.md | 5 ++++ 3 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 changelog/2026-09-14-vods-weapon-filter-peek.md diff --git a/app/features/vods/VodRepository.server.test.ts b/app/features/vods/VodRepository.server.test.ts index ceefd531e..1aad1bd53 100644 --- a/app/features/vods/VodRepository.server.test.ts +++ b/app/features/vods/VodRepository.server.test.ts @@ -127,6 +127,35 @@ describe("findVods", () => { expect(result).toHaveLength(1); }); + test("returns the vod's full weapon list with the filtered weapon first", async () => { + await VodFactory.create({ + submitterUserId: users.id(1), + matches: [ + { mode: "TW", stageId: 1, startsAt: "0:00", weapons: [10, 20, 30] }, + { mode: "SZ", stageId: 2, startsAt: "5:00", weapons: [0] }, + ], + }); + + const [result] = await VodRepository.findVods({ weapon: 0 }); + + expect(result.weapons).toHaveLength(4); + expect(result.weapons[0]).toBe(0); + }); + + test("alt skin of the filtered weapon leads the list", async () => { + await VodFactory.create({ + submitterUserId: users.id(1), + matches: [ + { mode: "TW", stageId: 1, startsAt: "0:00", weapons: [10, 45] }, + ], + }); + + const [result] = await VodRepository.findVods({ weapon: 40 }); + + expect(result.weapons).toHaveLength(2); + expect(result.weapons[0]).toBe(45); + }); + test("filters by type", async () => { for (const type of ["TOURNAMENT", "CAST", "SCRIM"] as const) { await VodFactory.create({ submitterUserId: users.id(1), type }); diff --git a/app/features/vods/VodRepository.server.ts b/app/features/vods/VodRepository.server.ts index 5f565d3ea..cd2557b99 100644 --- a/app/features/vods/VodRepository.server.ts +++ b/app/features/vods/VodRepository.server.ts @@ -72,7 +72,6 @@ export async function findVods({ .whereRef("User.id", "=", "VideoMatchPlayer.playerUserId"), ).as("players"), ]) - .where(vodFilters(filters)) // the page is resolved by id first: with the limit on this read, the aggregates // of every matching vod would be computed before it applies .where( @@ -88,9 +87,10 @@ export async function findVods({ .execute(); const vods = result.map((value) => { - const { playerNames, players, ...vod } = value; + const { playerNames, players, weapons, ...vod } = value; return { ...vod, + weapons: filteredWeaponFirst(weapons, filters.weapon), pov: playerNames[0] ?? players[0], }; }); @@ -318,6 +318,17 @@ type VodsWithMatchesDB = type VodsTables = "Video" | "VideoMatch" | "VideoMatchPlayer"; +/** The filtered weapon and its alt skins lead the list so the listing's peek always shows what was filtered for. */ +function filteredWeaponFirst( + weapons: MainWeaponId[], + weapon: MainWeaponId | undefined, +) { + if (weapon === undefined) return weapons; + + const filtered = new Set(weaponIdToArrayWithAlts(weapon)); + return R.partition(weapons, (id) => filtered.has(id)).flat(); +} + /** Conditions the filters put on the match rows. `userId` makes the vod's own filters moot: it is the user's vods regardless. */ function vodFilters({ weapon, mode, stageId, type, userId }: VodFilters) { return (eb: ExpressionBuilder) => { diff --git a/changelog/2026-09-14-vods-weapon-filter-peek.md b/changelog/2026-09-14-vods-weapon-filter-peek.md new file mode 100644 index 000000000..29d75e9c6 --- /dev/null +++ b/changelog/2026-09-14-vods-weapon-filter-peek.md @@ -0,0 +1,5 @@ +--- +navItem: vods +type: feature +--- +Filtering VODs by a weapon now shows each VOD's full weapon list, with the weapon you filtered by first