From 05370d236ada0e3395f8b8685139dd64673d4784 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Thu, 30 Jul 2026 11:41:13 -0400 Subject: [PATCH] feat(ui): display media counts in library switcher The UI had no surface showing how many media items have been imported. Surface the total in the library switcher shown on the Dashboard, Series, and Collections pages (via the LibrarySwitcher component) and in the Bookshelf's inline library filter. - Add a MediaCount field to LibraryData and a TotalMediaCount helper to sum counts for the "All Libraries" / "All Books" option. - resolveLibrary() now fetches per-library counts (one query) and maps them onto each LibraryData entry, so the switcher reflects the active scope without changing the component's signature. - Each library option renders "(N)" and the "All" option renders the grand total across the user's visible libraries. The "All" total is the sum of the user's visible libraries, correctly respecting per-user library visibility rather than a raw global count. Regenerated templ files for library_switcher and bookshelf. --- internal/router/helpers.go | 12 ++ templates/bookshelf.templ | 24 ++-- templates/bookshelf_templ.go | 188 ++++++++++++++++++---------- templates/library_switcher.templ | 14 +-- templates/library_switcher_templ.go | 117 +++++++++++------ templates/types.go | 1 + templates/utils.go | 10 ++ 7 files changed, 240 insertions(+), 126 deletions(-) diff --git a/internal/router/helpers.go b/internal/router/helpers.go index 2d3ff3a..88441bf 100644 --- a/internal/router/helpers.go +++ b/internal/router/helpers.go @@ -118,6 +118,17 @@ func resolveLibrary(c *echo.Context, cfg *Config, userUUID string) LibraryResolu libraries = []database.GetUserVisibleLibrariesRow{} } + counts, countErr := cfg.Queries.GetVisibleLibraryMediaCounts(c.Request().Context(), uuidToPGType(userU)) + if countErr != nil { + log.Printf("GetVisibleLibraryMediaCounts failed: %v", countErr) + counts = []database.GetVisibleLibraryMediaCountsRow{} + } + countMap := make(map[string]int64, len(counts)) + for _, mc := range counts { + mcUUID, _ := uuid.FromBytes(mc.ID.Bytes[0:16]) + countMap[mcUUID.String()] = mc.MediaCount + } + res.Libraries = make([]templates.LibraryData, len(libraries)) for i, lib := range libraries { libUUID, _ := uuid.FromBytes(lib.ID.Bytes[0:16]) @@ -126,6 +137,7 @@ func resolveLibrary(c *echo.Context, cfg *Config, userUUID string) LibraryResolu Name: lib.Name, Description: getText(lib.Description), TypeName: lib.TypeName, + MediaCount: countMap[libUUID.String()], } } diff --git a/templates/bookshelf.templ b/templates/bookshelf.templ index b0166b4..3ef859c 100644 --- a/templates/bookshelf.templ +++ b/templates/bookshelf.templ @@ -50,22 +50,22 @@ templ BookShelf( class="w-full px-3 py-2 border rounded-lg" style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);" > - if len(libraries) == 0 { - + if len(libraries) == 0 { + + } else { + if currentLibraryID == "" { + } else { - if currentLibraryID == "" { - + + } + for _, lib := range libraries { + if lib.ID == currentLibraryID { + } else { - - } - for _, lib := range libraries { - if lib.ID == currentLibraryID { - - } else { - - } + } } + } diff --git a/templates/bookshelf_templ.go b/templates/bookshelf_templ.go index 148732b..41af741 100644 --- a/templates/bookshelf_templ.go +++ b/templates/bookshelf_templ.go @@ -63,129 +63,181 @@ func BookShelf( } } else { if currentLibraryID == "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } for _, lib := range libraries { if lib.ID == currentLibraryID { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, " (") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(lib.MediaCount) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bookshelf.templ`, Line: 63, Col: 75} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, ")") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
= 2) fetchAuthorValues($el)\">
= 2) fetchTagValues($el)\" @keydown=\"onTagFilterKeydown($event)\" @blur=\"hideTagDropdown()\">
= 2) fetchSeriesValues($el)\">
= 2) fetchLanguageValues($el)\">

Saved Filters

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
= 2) fetchAuthorValues($el)\">
= 2) fetchTagValues($el)\" @keydown=\"onTagFilterKeydown($event)\" @blur=\"hideTagDropdown()\">
= 2) fetchSeriesValues($el)\">
= 2) fetchLanguageValues($el)\">

Saved Filters

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } for _, filter := range savedFilters { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if len(savedFilters) == 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
No saved filters yet
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "
No saved filters yet
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -193,73 +245,73 @@ func BookShelf( if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if errorMessage != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var8 string - templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(errorMessage) + var templ_7745c5c3_Var12 string + templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(errorMessage) if templ_7745c5c3_Err != nil { return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bookshelf.templ`, Line: 337, Col: 59} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if count > 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, " Page ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, ">← Previous Page ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var9 string - templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(offset/limit + 1) + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(offset/limit + 1) if templ_7745c5c3_Err != nil { return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bookshelf.templ`, Line: 356, Col: 32} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, ">Next →") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "

Save Filter

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "

Save Filter

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/templates/library_switcher.templ b/templates/library_switcher.templ index 224e2bd..6a22181 100644 --- a/templates/library_switcher.templ +++ b/templates/library_switcher.templ @@ -11,14 +11,14 @@ templ LibrarySwitcher(libData []LibraryData, currentLibraryID string, actions .. class="px-4 py-2 rounded-lg border focus:ring-2 focus:ring-blue-500" style="background-color: var(--bg-secondary); color: var(--text-primary);" > - - for _, lib := range libData { - if lib.ID == currentLibraryID { - - } else { - - } + + for _, lib := range libData { + if lib.ID == currentLibraryID { + + } else { + } + } if len(actions) > 0 { diff --git a/templates/library_switcher_templ.go b/templates/library_switcher_templ.go index 74c7220..2c24a48 100644 --- a/templates/library_switcher_templ.go +++ b/templates/library_switcher_templ.go @@ -29,81 +29,120 @@ func LibrarySwitcher(libData []LibraryData, currentLibraryID string, actions ... templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } for _, lib := range libData { if lib.ID == currentLibraryID { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, ")") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if len(actions) > 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -113,12 +152,12 @@ func LibrarySwitcher(libData []LibraryData, currentLibraryID string, actions ... return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -142,12 +181,12 @@ func DashboardActions() templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var6 := templ.GetChildren(ctx) - if templ_7745c5c3_Var6 == nil { - templ_7745c5c3_Var6 = templ.NopComponent + templ_7745c5c3_Var9 := templ.GetChildren(ctx) + if templ_7745c5c3_Var9 == nil { + templ_7745c5c3_Var9 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/templates/types.go b/templates/types.go index 7ace8ba..9854130 100644 --- a/templates/types.go +++ b/templates/types.go @@ -37,6 +37,7 @@ type LibraryData struct { Name string Description string TypeName string + MediaCount int64 } type SeriesCardData struct { diff --git a/templates/utils.go b/templates/utils.go index c0d3ddf..5d3eaaf 100644 --- a/templates/utils.go +++ b/templates/utils.go @@ -29,6 +29,16 @@ func ContainsString(slice []string, item string) bool { return false } +// TotalMediaCount sums the MediaCount across the given libraries, +// used to display the total next to the "All Libraries" option. +func TotalMediaCount(libs []LibraryData) int64 { + var total int64 + for _, l := range libs { + total += l.MediaCount + } + return total +} + func uuidToString(id pgtype.UUID) string { if !id.Valid { return ""