add library filter panel and tag selector enhancements
- Add left sidebar filter panel to MixedView and GamesView with name search and tag toggles; only shows tags/categories used in the current library; AND logic when multiple tags are selected - Add GET /api/tags/library-assignments endpoint returning all tag assignments for a library keyed by mediaKey - Add getTagAssignmentsForLibrary() and getTagsSortedByUsage() to tags lib - Support ?sort=usage on GET /api/tags/items to order by assignment count - Tag selector: per-category search, top-25-by-usage display, inline add tag (auto-assigned to current item) and add category flows - Tag selector: group assigned tags by category into nested pills - Fix nested <button> hydration error in EntryTile (outer element is now a div with role="button") - Keep filter panel assignments in sync when tags are toggled or created Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -130,6 +130,23 @@ export function getTags(categoryId?: string): Tag[] {
|
||||
.all() as Tag[]
|
||||
}
|
||||
|
||||
export function getTagsSortedByUsage(categoryId?: string): Tag[] {
|
||||
const db = getDb()
|
||||
const where = categoryId ? 'WHERE t.category_id = ?' : ''
|
||||
const params = categoryId ? [categoryId] : []
|
||||
return db
|
||||
.prepare(
|
||||
`SELECT t.id, t.name, t.category_id as categoryId,
|
||||
COUNT(mt.tag_id) as use_count
|
||||
FROM tags t
|
||||
LEFT JOIN media_tags mt ON mt.tag_id = t.id
|
||||
${where}
|
||||
GROUP BY t.id
|
||||
ORDER BY use_count DESC, t.name ASC`
|
||||
)
|
||||
.all(...params) as Tag[]
|
||||
}
|
||||
|
||||
export function addTag(name: string, categoryId: string): Tag {
|
||||
const trimmed = name.trim()
|
||||
if (!trimmed) throw new Error('Tag name is required.')
|
||||
@@ -223,6 +240,18 @@ export function getResolvedTagsForItem(mediaKey: string): { tags: Tag[]; categor
|
||||
return { tags, categories }
|
||||
}
|
||||
|
||||
export function getTagAssignmentsForLibrary(libraryId: string): Record<string, string[]> {
|
||||
const db = getDb()
|
||||
const rows = db
|
||||
.prepare('SELECT media_key, tag_id FROM media_tags WHERE media_key LIKE ?')
|
||||
.all(`${libraryId}:%`) as { media_key: string; tag_id: string }[]
|
||||
const result: Record<string, string[]> = {}
|
||||
for (const row of rows) {
|
||||
;(result[row.media_key] ??= []).push(row.tag_id)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export function removeAllAssignmentsForLibrary(libraryId: string): void {
|
||||
const db = getDb()
|
||||
db.prepare("DELETE FROM media_tags WHERE media_key LIKE ?").run(`${libraryId}:%`)
|
||||
|
||||
Reference in New Issue
Block a user