fix search/filter bugs in game and TV libraries

- Game series: filter now checks child games for both search and tag matches instead of always passing series through
- TV episodes: tag selector no longer closes after picking a tag
- TV episodes: filter panel now filters episodes within a season view
- TV series list: series now appear when any of their episodes match the active tag filter (via new /api/tv/series-episode-tags endpoint backed by media_items)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Garret Patti
2026-04-06 14:23:34 -04:00
parent 957d884903
commit 5d27ba351b
4 changed files with 92 additions and 8 deletions

View File

@@ -215,6 +215,42 @@ export function getTagAssignmentsForLibrary(libraryId: string): Record<string, s
return result
}
// Returns seriesId -> tagIds[] aggregated from all episodes in each series.
// Requires media_items to be populated by the scanner. Returns {} if not yet scanned.
export function getSeriesEpisodeTagMap(libraryId: string): Record<string, string[]> {
const db = getDb()
const prefix = `${libraryId}:tv_episode:`
const episodes = db
.prepare('SELECT item_key FROM media_items WHERE library_id = ? AND item_type = ?')
.all(libraryId, 'tv_episode') as { item_key: string }[]
if (episodes.length === 0) return {}
const tagRows = db
.prepare('SELECT media_key, tag_id FROM media_tags WHERE media_key LIKE ?')
.all(`${libraryId}:%`) as { media_key: string; tag_id: string }[]
const tagsByMediaKey: Record<string, string[]> = {}
for (const row of tagRows) {
;(tagsByMediaKey[row.media_key] ??= []).push(row.tag_id)
}
const result: Record<string, string[]> = {}
for (const { item_key } of episodes) {
if (!item_key.startsWith(prefix)) continue
// item_key: "libraryId:tv_episode:seriesId:seasonId:episodeId"
const parts = item_key.split(':')
if (parts.length < 5) continue
const seriesId = parts[2]
const episodeId = parts[parts.length - 1]
const episodeTags = tagsByMediaKey[`${libraryId}:${episodeId}`]
if (!episodeTags) continue
const seriesTags = (result[seriesId] ??= [])
for (const tagId of episodeTags) {
if (!seriesTags.includes(tagId)) seriesTags.push(tagId)
}
}
return result
}
export function removeAllAssignmentsForLibrary(libraryId: string): void {
const db = getDb()
db.prepare("DELETE FROM media_tags WHERE media_key LIKE ?").run(`${libraryId}:%`)