From 40dabfd788770986a10e844eefcffc76f9efdcf9 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Thu, 6 Aug 2026 11:08:29 -0400 Subject: [PATCH] feat(ui): hide management controls for system collections System collections (Not Started, Continue Reading, etc.) compute their contents dynamically from reading_progress, so manual add/remove has no effect. Hide the search bar, Remove Selected button, Add Books button, per-card checkboxes, and Remove buttons when the collection is system, making the page visually read-only. - Add IsSystem bool to CollectionData, populated from the database is_system_collection flag. - Add data-is-system to #collection-data so the JS renderer can also conditionally omit controls on library switch. - Wrap toolbar controls, book picker modal, card checkboxes, and remove buttons in if !collection.IsSystem in the template. --- internal/router/frontend.go | 1 + templates/collections.templ | 105 +++---- templates/collections_templ.go | 499 ++++++++++++++++++--------------- templates/types.go | 1 + web/src/collections.ts | 12 +- 5 files changed, 343 insertions(+), 275 deletions(-) diff --git a/internal/router/frontend.go b/internal/router/frontend.go index 9a5fa75..91fd900 100644 --- a/internal/router/frontend.go +++ b/internal/router/frontend.go @@ -631,6 +631,7 @@ func registerFrontendRoutes(cfg *Config) { Description: collection.Description.String, Color: collection.Color.String, Icon: collection.Icon.String, + IsSystem: collection.IsSystemCollection.Bool, } var buf bytes.Buffer diff --git a/templates/collections.templ b/templates/collections.templ index 40c75bd..b8480d4 100644 --- a/templates/collections.templ +++ b/templates/collections.templ @@ -139,33 +139,35 @@ templ CollectionDetail(user User, collection CollectionData, books []handlers.Bo

Books in this Collection

-
-
- + if !collection.IsSystem { +
+
+ +
+ +
- - -
+ }
if len(books) == 0 { @@ -174,16 +176,18 @@ templ CollectionDetail(user User, collection CollectionData, books []handlers.Bo for _, book := range books {
-
- -
+ if !collection.IsSystem { +
+ +
+ }
}
-
+ } diff --git a/templates/collections_templ.go b/templates/collections_templ.go index 5b750de..ea1c0d9 100644 --- a/templates/collections_templ.go +++ b/templates/collections_templ.go @@ -311,163 +311,12 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "

Books in this Collection

0\" x-text=\"selectedBooks.length + ' selected'\" class=\"badge\" style=\"display: none; background-color: var(--accent); color: var(--bg-primary);\">

Books in this Collection

0\" x-text=\"selectedBooks.length + ' selected'\" class=\"badge\" style=\"display: none; background-color: var(--accent); color: var(--bg-primary);\">
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = Icon("trash", "h-4 w-4").Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "Remove Selected
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if len(books) == 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "
No books in this collection yet.
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - for _, book := range books { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var18 string - templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(book.Title) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 190, Col: 23} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if book.Author != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "

by ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var19 string - templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(book.Author) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 195, Col: 27} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "Remove Selected
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "

Add Books to Collection

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = Icon("close", "h-5 w-5").Render(ctx, templ_7745c5c3_Buffer) + if len(books) == 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "
No books in this collection yet.
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + for _, book := range books { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !collection.IsSystem { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var18 string + templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(book.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 194, Col: 23} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if book.Author != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "

by ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var19 string + templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(book.Author) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 199, Col: 27} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !collection.IsSystem { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "
books selected
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } } - templ_7745c5c3_Err = Icon("close", "h-4 w-4").Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "Clear
books selected

Remove from Collection

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "\" data-is-system=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var24 string + templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.ResolveAttributeValue(collection.IsSystem) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 385, Col: 39} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var24) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "\" style=\"display: none;\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -554,119 +607,119 @@ func BookPickerGrid(books []handlers.BookInfo) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var24 := templ.GetChildren(ctx) - if templ_7745c5c3_Var24 == nil { - templ_7745c5c3_Var24 = templ.NopComponent + templ_7745c5c3_Var25 := templ.GetChildren(ctx) + if templ_7745c5c3_Var25 == nil { + templ_7745c5c3_Var25 = templ.NopComponent } ctx = templ.ClearChildren(ctx) for _, book := range books { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "\">
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if book.CoverImagePath != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "\"")") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "\"")") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "\" class=\"w-full aspect-[2/3] object-cover\" loading=\"lazy\" onerror=\"this.src='/static/placeholder-book.svg'\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "\"")") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "\" class=\"absolute inset-0 border-4 rounded-lg pointer-events-none\" style=\"border-color: var(--accent); background-color: color-mix(in srgb, var(--accent) 20%, transparent);\">

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "\" class=\"absolute top-1 right-1 w-6 h-6 rounded-full flex items-center justify-center text-sm font-bold pointer-events-none\" style=\"background-color: var(--accent); color: var(--bg-primary);\">✓

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var32 string + templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(book.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 416, Col: 87} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/templates/types.go b/templates/types.go index 9854130..84268d7 100644 --- a/templates/types.go +++ b/templates/types.go @@ -30,6 +30,7 @@ type CollectionData struct { Description string Color string Icon string + IsSystem bool } type LibraryData struct { diff --git a/web/src/collections.ts b/web/src/collections.ts index 3163ecc..d9a7c40 100644 --- a/web/src/collections.ts +++ b/web/src/collections.ts @@ -127,6 +127,9 @@ function renderCollectionBooks(books: BookInfo[]): void { const container = document.getElementById("books-container"); if (!container) return; + const dataEl = document.getElementById("collection-data"); + const isSystem = dataEl?.dataset.isSystem === "true"; + if (books.length === 0) { container.innerHTML = `
No books in this collection yet.
`; return; @@ -137,13 +140,14 @@ function renderCollectionBooks(books: BookInfo[]): void { (book) => `
+ ${isSystem ? "" : `
-
+
`}
+ ${isSystem ? "" : `
-
+ `} `, )