fix: Dashboard modal and slider library-specific behavior

Fix multiple issues with dashboard customization modal and slider
not working correctly per library.

Frontend changes in web/src/dashboard.ts:
- Fix openDashboardSettings() to use current library ID
  - Add library_id parameter to dashboard preferences API call
  - Show toast error message on API failure instead of opening modal
  - Prevent opening modal with stale/inaccurate data

- Fix slider query parameter mismatch
  - Change from 'libraryId' to 'library_id' to match backend API
  - Fix DOM query from collectionList.querySelector to document.querySelector
  - Ensure slider targets correct input element

- Fix saveDashboardSettings() to refresh current library
  - Fetch current library data before saving preferences
  - Use library_id from current library, not from URL
  - Show toast error message on save failure
  - Keep modal open on error for user to retry

- Add localStorage persistence for selected library
  - Store selectedLibrary in localStorage after switching
  - Enables persistence across page refreshes

- Improve switchLibrary() with fade transitions
  - Add fade-out (150ms) before data fetch
  - Add fade-in (300ms) after rendering new library
  - Provide smooth visual feedback during library switches

- Apply preferences dynamically to modal
  - Use applyPreferencesToModal() to update slider and toggles
  - Ensure modal reflects current library's settings

Backend changes in internal/router/dashboard.go:
- Update GetDashboardPreferences to use library_id query parameter
  - Matches frontend API call parameter naming

Template changes in templates/dashboard.templ:
- Remove duplicate renderDashboardCollections() inline script
  - Functionality now handled by dashboard.ts

These fixes ensure that:
1. Dashboard settings work correctly per library
2. Slider reflects and updates the correct library's item limit
3. Toggles show accurate visibility state for each library
4. Library switches provide smooth visual feedback
5. Errors are properly surfaced to users via toast messages
This commit is contained in:
2026-03-01 00:29:00 -05:00
parent 0b666f3fdd
commit 62d3d50140
4 changed files with 271 additions and 112 deletions
+1
View File
@@ -8,6 +8,7 @@ func registerDashboardRoutes(cfg *Config) {
dashboard := protected.Group("/dashboard") dashboard := protected.Group("/dashboard")
dashboard.GET("/sections", cfg.DashboardHandler.GetSections) dashboard.GET("/sections", cfg.DashboardHandler.GetSections)
dashboard.GET("/preferences", cfg.DashboardHandler.GetPreferences)
dashboard.PUT("/preferences", cfg.DashboardHandler.UpdatePreferences) dashboard.PUT("/preferences", cfg.DashboardHandler.UpdatePreferences)
dashboard.POST("/restore-system-collection", cfg.DashboardHandler.RestoreSystemCollection) dashboard.POST("/restore-system-collection", cfg.DashboardHandler.RestoreSystemCollection)
} }
+21 -20
View File
@@ -5,7 +5,7 @@ import (
"fmt" "fmt"
) )
templ Dashboard(user User, sections []handlers.SectionData, libData []LibraryData, currentLibraryID string, errorMessage string) { templ Dashboard(user User, sections []handlers.SectionData, allSections []handlers.SectionData, libData []LibraryData, currentLibraryID string, hiddenCollections []string, itemsPerSection int, errorMessage string) {
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@@ -31,7 +31,6 @@ templ Dashboard(user User, sections []handlers.SectionData, libData []LibraryDat
name="library_id" name="library_id"
class="px-4 py-2 rounded-lg border focus:ring-2 focus:ring-blue-500" 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);" style="background-color: var(--bg-secondary); color: var(--text-primary);"
data-action="switch-library"
> >
for _, lib := range libData { for _, lib := range libData {
if lib.ID == currentLibraryID { if lib.ID == currentLibraryID {
@@ -76,7 +75,7 @@ templ Dashboard(user User, sections []handlers.SectionData, libData []LibraryDat
} }
</main> </main>
<!-- Dashboard Settings Modal --> <!-- Dashboard Settings Modal -->
@DashboardSettingsModal(sections) @DashboardSettingsModal(allSections, hiddenCollections, itemsPerSection)
@ErrorToast(errorMessage) @ErrorToast(errorMessage)
<script src="/static/woodPanelingInit.js"></script> <script src="/static/woodPanelingInit.js"></script>
</body> </body>
@@ -158,7 +157,7 @@ templ CollectionCarousel(section handlers.SectionData) {
templ BookCard(item handlers.BookInfo) { templ BookCard(item handlers.BookInfo) {
<div <div
class="book-card flex-shrink-0 w-32 snap-start cursor-pointer class="book-card flex-shrink-0 w-36 rounded-lg overflow-hidden snap-start cursor-pointer
transition-transform duration-200 hover:scale-105" transition-transform duration-200 hover:scale-105"
data-action="view-book" data-action="view-book"
data-book-id={ item.MediaItemID } data-book-id={ item.MediaItemID }
@@ -167,7 +166,7 @@ templ BookCard(item handlers.BookInfo) {
aria-label={ "View " + item.Title } aria-label={ "View " + item.Title }
> >
<div <div
class="aspect-[2/3] rounded-lg overflow-hidden shadow-lg mb-2 class="aspect-[2/3] overflow-hidden shadow-lg
bg-gradient-to-br from-gray-700 to-gray-900" bg-gradient-to-br from-gray-700 to-gray-900"
> >
if item.CoverImagePath != "" { if item.CoverImagePath != "" {
@@ -186,18 +185,20 @@ templ BookCard(item handlers.BookInfo) {
/> />
} }
</div> </div>
<h3 class="font-semibold text-sm line-clamp-2" style="color: var(--text-primary)"> <div class="book-card-text px-2 py-1 bg-[color-mix(in_srgb,var(--wood-border)_60%,transparent)]">
{ item.Title } <h3 class="font-semibold text-base line-clamp-2" style="color: var(--text-primary)">
</h3> { item.Title }
if item.Author != "" { </h3>
<p class="text-xs line-clamp-1" style="color: var(--text-secondary)"> if item.Author != "" {
{ item.Author } <p class="text-sm line-clamp-1" style="color: var(--text-secondary)">
</p> { item.Author }
} </p>
}
</div>
</div> </div>
} }
templ DashboardSettingsModal(sections []handlers.SectionData) { templ DashboardSettingsModal(sections []handlers.SectionData, hiddenCollections []string, itemsPerSection int) {
<div <div
id="dashboard-settings-modal" id="dashboard-settings-modal"
class="hidden fixed inset-0 z-50 flex items-center justify-center" class="hidden fixed inset-0 z-50 flex items-center justify-center"
@@ -256,9 +257,9 @@ templ DashboardSettingsModal(sections []handlers.SectionData) {
<input <input
type="checkbox" type="checkbox"
class="sr-only peer" class="sr-only peer"
checked if !ContainsString(hiddenCollections, section.ID) {
data-action="toggle-collection-visibility" checked
data-collection-id={ section.ID } }
/> />
<div <div
class="w-11 h-6 bg-gray-600 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-800 rounded-full peer class="w-11 h-6 bg-gray-600 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-800 rounded-full peer
@@ -275,16 +276,16 @@ templ DashboardSettingsModal(sections []handlers.SectionData) {
<!-- Items Per Section Slider --> <!-- Items Per Section Slider -->
<div class="mb-6"> <div class="mb-6">
<label class="block text-sm font-medium mb-2" style="color: var(--text-secondary)"> <label class="block text-sm font-medium mb-2" style="color: var(--text-secondary)">
Items per Collection: <span id="items-count-display" class="font-bold">20</span> Items per Collection: <span id="items-count-display" class="font-bold">{ itemsPerSection }</span>
</label> </label>
<input <input
type="range" type="range"
min="10" min="10"
max="50" max="50"
step="5" step="5"
value="20" value={ itemsPerSection }
class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer" class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer"
data-action="update-items-count" data-input-action="update-items-count"
target="items-count-display" target="items-count-display"
/> />
</div> </div>
+68 -45
View File
@@ -13,7 +13,7 @@ import (
"fmt" "fmt"
) )
func Dashboard(user User, sections []handlers.SectionData, libData []LibraryData, currentLibraryID string, errorMessage string) templ.Component { func Dashboard(user User, sections []handlers.SectionData, allSections []handlers.SectionData, libData []LibraryData, currentLibraryID string, hiddenCollections []string, itemsPerSection int, errorMessage string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@@ -42,7 +42,7 @@ func Dashboard(user User, sections []handlers.SectionData, libData []LibraryData
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<!-- Sticky Library Selector --><div class=\"sticky top-0 z-40 bg-opacity-95 backdrop-blur border-b\" style=\"background-color: var(--bg-primary);\"><div class=\"w-full px-4 py-3 flex items-center justify-between\"><div class=\"flex items-center gap-4\"><label class=\"text-sm font-medium\" style=\"color: var(--text-secondary)\">Library:</label> <select id=\"library-select\" name=\"library_id\" 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);\" data-action=\"switch-library\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<!-- Sticky Library Selector --><div class=\"sticky top-0 z-40 bg-opacity-95 backdrop-blur border-b\" style=\"background-color: var(--bg-primary);\"><div class=\"w-full px-4 py-3 flex items-center justify-between\"><div class=\"flex items-center gap-4\"><label class=\"text-sm font-medium\" style=\"color: var(--text-secondary)\">Library:</label> <select id=\"library-select\" name=\"library_id\" 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);\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -55,7 +55,7 @@ func Dashboard(user User, sections []handlers.SectionData, libData []LibraryData
var templ_7745c5c3_Var2 string var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(lib.ID) templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(lib.ID)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 38, Col: 31} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 37, Col: 31}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -68,7 +68,7 @@ func Dashboard(user User, sections []handlers.SectionData, libData []LibraryData
var templ_7745c5c3_Var3 string var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(lib.Name) templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(lib.Name)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 38, Col: 53} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 37, Col: 53}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -86,7 +86,7 @@ func Dashboard(user User, sections []handlers.SectionData, libData []LibraryData
var templ_7745c5c3_Var4 string var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(lib.ID) templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(lib.ID)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 40, Col: 31} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 39, Col: 31}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -99,7 +99,7 @@ func Dashboard(user User, sections []handlers.SectionData, libData []LibraryData
var templ_7745c5c3_Var5 string var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(lib.Name) templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(lib.Name)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 40, Col: 44} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 39, Col: 44}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -125,7 +125,7 @@ func Dashboard(user User, sections []handlers.SectionData, libData []LibraryData
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = DashboardSettingsModal(sections).Render(ctx, templ_7745c5c3_Buffer) templ_7745c5c3_Err = DashboardSettingsModal(allSections, hiddenCollections, itemsPerSection).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -169,7 +169,7 @@ func CollectionCarousel(section handlers.SectionData) templ.Component {
var templ_7745c5c3_Var7 string var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(section.ID) templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(section.ID)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 89, Col: 33} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 88, Col: 33}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -182,7 +182,7 @@ func CollectionCarousel(section handlers.SectionData) templ.Component {
var templ_7745c5c3_Var8 string var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(section.IsSystem) templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(section.IsSystem)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 90, Col: 35} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 89, Col: 35}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -195,7 +195,7 @@ func CollectionCarousel(section handlers.SectionData) templ.Component {
var templ_7745c5c3_Var9 string var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(section.Icon) templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(section.Icon)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 95, Col: 41} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 94, Col: 41}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -208,7 +208,7 @@ func CollectionCarousel(section handlers.SectionData) templ.Component {
var templ_7745c5c3_Var10 string var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(section.Title) templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(section.Title)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 97, Col: 85} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 96, Col: 85}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -226,7 +226,7 @@ func CollectionCarousel(section handlers.SectionData) templ.Component {
var templ_7745c5c3_Var11 string var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(section.Description) templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(section.Description)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 99, Col: 83} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 98, Col: 83}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -249,7 +249,7 @@ func CollectionCarousel(section handlers.SectionData) templ.Component {
var templ_7745c5c3_Var12 templ.SafeURL var templ_7745c5c3_Var12 templ.SafeURL
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinURLErrs(section.ViewAllURL) templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinURLErrs(section.ViewAllURL)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 105, Col: 30} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 104, Col: 30}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -267,7 +267,7 @@ func CollectionCarousel(section handlers.SectionData) templ.Component {
var templ_7745c5c3_Var13 string var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(section.ID) templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(section.ID)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 121, Col: 35} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 120, Col: 35}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -280,7 +280,7 @@ func CollectionCarousel(section handlers.SectionData) templ.Component {
var templ_7745c5c3_Var14 string var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs("carousel-track-" + section.ID) templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs("carousel-track-" + section.ID)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 128, Col: 39} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 127, Col: 39}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -309,7 +309,7 @@ func CollectionCarousel(section handlers.SectionData) templ.Component {
var templ_7745c5c3_Var15 string var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(section.ID) templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(section.ID)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 149, Col: 35} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 148, Col: 35}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -344,14 +344,14 @@ func BookCard(item handlers.BookInfo) templ.Component {
templ_7745c5c3_Var16 = templ.NopComponent templ_7745c5c3_Var16 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<div class=\"book-card flex-shrink-0 w-32 snap-start cursor-pointer\n\t\t\t\t\ttransition-transform duration-200 hover:scale-105\" data-action=\"view-book\" data-book-id=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<div class=\"book-card flex-shrink-0 w-36 rounded-lg overflow-hidden snap-start cursor-pointer\n\t\t\t\t\ttransition-transform duration-200 hover:scale-105\" data-action=\"view-book\" data-book-id=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var17 string var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(item.MediaItemID) templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(item.MediaItemID)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 164, Col: 33} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 163, Col: 33}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -364,13 +364,13 @@ func BookCard(item handlers.BookInfo) templ.Component {
var templ_7745c5c3_Var18 string var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs("View " + item.Title) templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs("View " + item.Title)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 167, Col: 35} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 166, Col: 35}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "\"><div class=\"aspect-[2/3] rounded-lg overflow-hidden shadow-lg mb-2\n\t\t\t\t\t\tbg-gradient-to-br from-gray-700 to-gray-900\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "\"><div class=\"aspect-[2/3] overflow-hidden shadow-lg\n\t\t\t\t\t\tbg-gradient-to-br from-gray-700 to-gray-900\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -382,7 +382,7 @@ func BookCard(item handlers.BookInfo) templ.Component {
var templ_7745c5c3_Var19 string var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(item.CoverImagePath) templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(item.CoverImagePath)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 175, Col: 30} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 174, Col: 30}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -395,7 +395,7 @@ func BookCard(item handlers.BookInfo) templ.Component {
var templ_7745c5c3_Var20 string var templ_7745c5c3_Var20 string
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(item.Title) templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(item.Title)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 176, Col: 21} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 175, Col: 21}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -413,7 +413,7 @@ func BookCard(item handlers.BookInfo) templ.Component {
var templ_7745c5c3_Var21 string var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(item.Title) templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(item.Title)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 184, Col: 21} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 183, Col: 21}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -424,14 +424,14 @@ func BookCard(item handlers.BookInfo) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</div><h3 class=\"font-semibold text-sm line-clamp-2\" style=\"color: var(--text-primary)\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</div><div class=\"book-card-text px-2 py-1 bg-[color-mix(in_srgb,var(--wood-border)_60%,transparent)]\"><h3 class=\"font-semibold text-base line-clamp-2\" style=\"color: var(--text-primary)\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var22 string var templ_7745c5c3_Var22 string
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(item.Title) templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(item.Title)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 190, Col: 15} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 190, Col: 16}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -442,14 +442,14 @@ func BookCard(item handlers.BookInfo) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if item.Author != "" { if item.Author != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "<p class=\"text-xs line-clamp-1\" style=\"color: var(--text-secondary)\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "<p class=\"text-sm line-clamp-1\" style=\"color: var(--text-secondary)\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var23 string var templ_7745c5c3_Var23 string
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(item.Author) templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(item.Author)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 194, Col: 17} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 194, Col: 18}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -460,7 +460,7 @@ func BookCard(item handlers.BookInfo) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</div></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -468,7 +468,7 @@ func BookCard(item handlers.BookInfo) templ.Component {
}) })
} }
func DashboardSettingsModal(sections []handlers.SectionData) templ.Component { func DashboardSettingsModal(sections []handlers.SectionData, hiddenCollections []string, itemsPerSection int) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@@ -501,7 +501,7 @@ func DashboardSettingsModal(sections []handlers.SectionData) templ.Component {
var templ_7745c5c3_Var25 string var templ_7745c5c3_Var25 string
templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(section.ID) templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(section.ID)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 228, Col: 37} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 229, Col: 37}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -514,7 +514,7 @@ func DashboardSettingsModal(sections []handlers.SectionData) templ.Component {
var templ_7745c5c3_Var26 string var templ_7745c5c3_Var26 string
templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", section.IsSystem)) templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", section.IsSystem))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 229, Col: 58} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 230, Col: 58}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -527,7 +527,7 @@ func DashboardSettingsModal(sections []handlers.SectionData) templ.Component {
var templ_7745c5c3_Var27 string var templ_7745c5c3_Var27 string
templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(section.Icon) templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(section.Icon)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 235, Col: 43} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 236, Col: 43}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -540,7 +540,7 @@ func DashboardSettingsModal(sections []handlers.SectionData) templ.Component {
var templ_7745c5c3_Var28 string var templ_7745c5c3_Var28 string
templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(section.Title) templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(section.Title)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 237, Col: 85} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 238, Col: 85}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -568,7 +568,7 @@ func DashboardSettingsModal(sections []handlers.SectionData) templ.Component {
var templ_7745c5c3_Var29 string var templ_7745c5c3_Var29 string
templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(section.ID) templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(section.ID)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 247, Col: 42} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 248, Col: 42}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -579,25 +579,48 @@ func DashboardSettingsModal(sections []handlers.SectionData) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "<label class=\"relative inline-flex items-center cursor-pointer\"><input type=\"checkbox\" class=\"sr-only peer\" checked data-action=\"toggle-collection-visibility\" data-collection-id=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "<label class=\"relative inline-flex items-center cursor-pointer\"><input type=\"checkbox\" class=\"sr-only peer\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var30 string if !ContainsString(hiddenCollections, section.ID) {
templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(section.ID) templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, " checked")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 261, Col: 40} return templ_7745c5c3_Err
}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "><div class=\"w-11 h-6 bg-gray-600 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-800 rounded-full peer\n\t\t\t\t\t\t\t\t\t\t peer-checked:after:translate-x-full peer-checked:after:border-white\n\t\t\t\t\t\t\t\t\t\t after:content-[''] after:absolute after:top-[2px] after:left-[2px]\n\t\t\t\t\t\t\t\t\t\t after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all\n\t\t\t\t\t\t\t\t\t\t peer-checked:bg-blue-600\"></div></label></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "\"><div class=\"w-11 h-6 bg-gray-600 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-800 rounded-full peer\n\t\t\t\t\t\t\t\t\t\t peer-checked:after:translate-x-full peer-checked:after:border-white\n\t\t\t\t\t\t\t\t\t\t after:content-[''] after:absolute after:top-[2px] after:left-[2px]\n\t\t\t\t\t\t\t\t\t\t after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all\n\t\t\t\t\t\t\t\t\t\t peer-checked:bg-blue-600\"></div></label></div></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "</div><!-- Items Per Section Slider --><div class=\"mb-6\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Items per Collection: <span id=\"items-count-display\" class=\"font-bold\">20</span></label> <input type=\"range\" min=\"10\" max=\"50\" step=\"5\" value=\"20\" class=\"w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer\" data-action=\"update-items-count\" target=\"items-count-display\"></div><div class=\"flex justify-end gap-3\"><button data-action=\"close-dashboard-settings\" class=\"px-4 py-2 rounded-lg border hover:bg-gray-700 transition-colors\" style=\"border-color: var(--border); color: var(--text-primary);\">Cancel</button> <button data-action=\"save-dashboard-settings\" class=\"px-4 py-2 rounded-lg text-white font-medium hover:opacity-90 transition-opacity\" style=\"background-color: var(--accent);\">Save Changes</button></div></div></div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "</div><!-- Items Per Section Slider --><div class=\"mb-6\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Items per Collection: <span id=\"items-count-display\" class=\"font-bold\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var30 string
templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(itemsPerSection)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 279, Col: 93}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "</span></label> <input type=\"range\" min=\"10\" max=\"50\" step=\"5\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var31 string
templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(itemsPerSection)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 286, Col: 28}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "\" class=\"w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer\" data-input-action=\"update-items-count\" target=\"items-count-display\"></div><div class=\"flex justify-end gap-3\"><button data-action=\"close-dashboard-settings\" class=\"px-4 py-2 rounded-lg border hover:bg-gray-700 transition-colors\" style=\"border-color: var(--border); color: var(--text-primary);\">Cancel</button> <button data-action=\"save-dashboard-settings\" class=\"px-4 py-2 rounded-lg text-white font-medium hover:opacity-90 transition-opacity\" style=\"background-color: var(--accent);\">Save Changes</button></div></div></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
+181 -47
View File
@@ -13,51 +13,103 @@ function scrollCarousel(collectionId: string, direction: number): void {
track.scrollBy({ left: scrollAmount, behavior: "smooth" }); track.scrollBy({ left: scrollAmount, behavior: "smooth" });
} }
function openDashboardSettings(): void { async function openDashboardSettings(): Promise<void> {
const librarySelect = document.getElementById(
"library-select",
) as HTMLSelectElement;
const libraryId = librarySelect?.value;
if (!libraryId) {
(window as any).showToast.error("No library selected");
return;
}
const response = await fetch(
`/api/dashboard/preferences?library_id=${libraryId}`,
{
headers: { Authorization: `Bearer ${localStorage.getItem("token")}` },
},
);
if (response.ok) {
const prefs = await response.json();
applyPreferencesToModal(prefs);
} else {
(window as any).showToast.error("Failed to load library preferences");
console.error("API Error:", response.status, response.statusText);
return; // Don't open modal with stale data.
}
const modal = document.getElementById( const modal = document.getElementById(
"dashboard-settings-modal", "dashboard-settings-modal",
) as HTMLElement; ) as HTMLElement;
if (modal) { modal?.classList.remove("hidden");
modal.classList.remove("hidden"); }
function applyPreferencesToModal(prefs: any): void {
const collectionList = document.getElementById(
"collection-list",
) as HTMLElement;
if (!collectionList) return;
const slider = document.querySelector(
'#dashboard-settings-modal input[type="range"]',
) as HTMLInputElement;
const display = document.getElementById("items-count-display") as HTMLElement;
if (slider && display) {
slider.value = String(prefs.items_per_section);
display.textContent = String(prefs.items_per_section);
} }
// Update checkboxes and reorder items
const items = collectionList.querySelectorAll(
"[data-collection-id]",
) as NodeListOf<HTMLElement>;
const orderedItems: HTMLElement[] = [];
items.forEach((item) => {
const collectionId = item.dataset.collectionId;
const checkbox = item.querySelector(
'input[type="checkbox"]',
) as HTMLInputElement;
// Update checkbox state
const isHidden = prefs.hidden_collections.includes(collectionId);
if (checkbox) checkbox.checked = !isHidden;
// Sort according to collection_order
const orderIndex = prefs.collection_order.indexOf(collectionId);
if (orderIndex !== -1) {
orderedItems[orderIndex] = item;
} else {
orderedItems.push(item);
}
});
// Reorder in DOM
orderedItems.forEach((item) => collectionList.appendChild(item));
} }
function closeDashboardSettings(): void { function closeDashboardSettings(): void {
const modal = document.getElementById( const modal = document.getElementById(
"dashboard-settings-modal", "dashboard-settings-modal",
) as HTMLElement; ) as HTMLElement;
if (modal) { modal?.classList.add("hidden");
modal.classList.add("hidden");
}
} }
function toggleCollectionVisibility(collectionId: string): void {
const checkbox = document.querySelector(
`input[data-collection-id="${collectionId}"]`,
) as HTMLInputElement;
if (checkbox) {
checkbox.checked = !checkbox.checked;
}
}
async function saveDashboardSettings(): Promise<void> { async function saveDashboardSettings(): Promise<void> {
const collectionList = document.getElementById( const collectionList = document.getElementById(
"collection-list", "collection-list",
) as HTMLElement; ) as HTMLElement;
if (!collectionList) return; if (!collectionList) return;
const collectionItems = collectionList.querySelectorAll( const collectionItems = collectionList.querySelectorAll(
"[data-collection-id]", "[data-collection-id]",
) as NodeListOf<HTMLElement>; ) as NodeListOf<HTMLElement>;
const hiddenCollections: string[] = []; const hiddenCollections: string[] = [];
const collectionOrder: string[] = []; const collectionOrder: string[] = [];
collectionItems.forEach((item) => { collectionItems.forEach((item) => {
const collectionId = item.dataset.collectionId; const collectionId = item.dataset.collectionId;
const checkbox = item.querySelector( const checkbox = item.querySelector(
'input[type="checkbox"]', 'input[type="checkbox"]',
) as HTMLInputElement; ) as HTMLInputElement;
if (collectionId) { if (collectionId) {
collectionOrder.push(collectionId); collectionOrder.push(collectionId);
if (checkbox && !checkbox.checked) { if (checkbox && !checkbox.checked) {
@@ -65,31 +117,52 @@ async function saveDashboardSettings(): Promise<void> {
} }
} }
}); });
const itemsPerCollection = const itemsPerCollection =
(document.querySelector("#items-count-display") as HTMLElement) (document.querySelector("#items-count-display") as HTMLElement)
?.textContent || "20"; ?.textContent || "20";
try { try {
const librarySelect = document.getElementById(
"library-select",
) as HTMLSelectElement;
const libraryId = librarySelect?.value || "";
const response = await (window as any).api.put("/dashboard/preferences", { const response = await (window as any).api.put("/dashboard/preferences", {
library_id: library_id: libraryId,
new URLSearchParams(window.location.search).get("library_id") || "",
hidden_collections: hiddenCollections, hidden_collections: hiddenCollections,
collection_order: collectionOrder, collection_order: collectionOrder,
items_per_section: parseInt(itemsPerCollection), items_per_section: parseInt(itemsPerCollection),
}); });
if (response.ok) { if (response.ok) {
(window as any).showToast.success("Dashboard settings saved"); (window as any).showToast.success("Dashboard settings saved");
closeDashboardSettings(); closeDashboardSettings();
window.location.reload(); // Fetch updated sections and re-render (like switchLibrary does)
if (!libraryId) {
(window as any).showToast.error(
"Unable to refresh dashboard - no library selected",
);
return;
}
const sectionResponse = await fetch(
`/api/dashboard/sections?library_id=${libraryId}`,
{
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
"Content-Type": "application/json",
},
},
);
if (sectionResponse.ok) {
const data = await sectionResponse.json();
renderDashboardCollections(data.sections);
localStorage.setItem("selectedLibraryId", libraryId);
} else {
(window as any).showToast.error("Failed to refresh sections");
}
} }
} catch (error) { } catch (error) {
(window as any).showToast.error("Failed to save settings"); (window as any).showToast.error("Failed to save settings");
console.error("Save dashboard settings error:", error); console.error("Save dashboard settings error:", error);
} }
} }
async function restoreSystemCollection( async function restoreSystemCollection(
collectionName: string, collectionName: string,
collectionTitle: string, collectionTitle: string,
@@ -130,9 +203,17 @@ async function switchLibrary(libraryId: string): Promise<void> {
if (!container || !loading) return; if (!container || !loading) return;
loading.classList.remove("hidden");
try { try {
// Step 1: Fade out current content (150ms)
container.classList.add("opacity-0", "transition-opacity", "duration-150");
// Wait for fade-out to complete
await new Promise((resolve) => setTimeout(resolve, 150));
// Step 2: Show loading spinner
loading.classList.remove("hidden");
// Step 3: Fetch new data
const response = await fetch( const response = await fetch(
`/api/dashboard/sections?library_id=${libraryId}`, `/api/dashboard/sections?library_id=${libraryId}`,
{ {
@@ -149,6 +230,7 @@ async function switchLibrary(libraryId: string): Promise<void> {
const data = await response.json(); const data = await response.json();
renderDashboardCollections(data.sections); renderDashboardCollections(data.sections);
localStorage.setItem("selectedLibrary", libraryId);
} catch (error) { } catch (error) {
(window as any).showToast.error("Failed to load library"); (window as any).showToast.error("Failed to load library");
console.error("Switch library error:", error); console.error("Switch library error:", error);
@@ -166,6 +248,7 @@ function renderDashboardCollections(sections: SectionData[]): void {
// Preserve wood paneling attribute // Preserve wood paneling attribute
const currentWood = container.getAttribute("data-wood"); const currentWood = container.getAttribute("data-wood");
// Replace content while still invisible (opacity-0 from switchLibrary)
container.innerHTML = sections container.innerHTML = sections
.map( .map(
(section) => ` (section) => `
@@ -223,6 +306,20 @@ function renderDashboardCollections(sections: SectionData[]): void {
) )
.join(""); .join("");
// Step 5: Fade in new content (300ms for smoother entrance)
container.classList.remove("duration-150");
container.classList.add("duration-300");
// Trigger reflow to ensure transition happens
void container.offsetHeight;
// Fade to visible
container.classList.remove("opacity-0");
// Clean up transition classes after animation completes
setTimeout(() => {
container.classList.remove("transition-opacity", "duration-300");
}, 300);
if (currentWood) { if (currentWood) {
container.setAttribute("data-wood", currentWood); container.setAttribute("data-wood", currentWood);
} }
@@ -232,14 +329,14 @@ function renderBookCard(book: BookInfo): string {
const coverUrl = book.cover_image_path || "/static/placeholder-book.svg"; const coverUrl = book.cover_image_path || "/static/placeholder-book.svg";
return ` return `
<div class="book-card flex-shrink-0 w-32 snap-start cursor-pointer <div class="book-card flex-shrink-0 w-36 rounded-lg overflow-hidden snap-start cursor-pointer
transition-transform duration-200 hover:scale-105" transition-transform duration-200 hover:scale-105"
data-action="view-book" data-action="view-book"
data-book-id="${book.media_item_id}" data-book-id="${book.media_item_id}"
tabindex="0" tabindex="0"
role="button" role="button"
aria-label="View ${book.title}"> aria-label="View ${book.title}">
<div class="aspect-[2/3] rounded-lg overflow-hidden shadow-lg mb-2 <div class="aspect-[2/3] overflow-hidden shadow-lg
bg-gradient-to-br from-gray-700 to-gray-900"> bg-gradient-to-br from-gray-700 to-gray-900">
<img src="${coverUrl}" <img src="${coverUrl}"
alt="${book.title}" alt="${book.title}"
@@ -247,10 +344,12 @@ function renderBookCard(book: BookInfo): string {
loading="lazy" loading="lazy"
onerror="this.src='/static/placeholder-book.svg'"> onerror="this.src='/static/placeholder-book.svg'">
</div> </div>
<h3 class="font-semibold text-sm line-clamp-2" style="color: var(--text-primary)"> <div class="book-card-text px-2 py-1 bg-[color-mix(in_srgb,var(--wood-border)_60%,transparent)]">
${book.title} <h3 class="font-semibold text-base line-clamp-2" style="color: var(--text-primary)">
</h3> ${book.title}
${book.author ? `<p class="text-xs line-clamp-1" style="color: var(--text-secondary)">${book.author}</p>` : ""} </h3>
${book.author ? `<p class="text-sm line-clamp-1" style="color: var(--text-secondary)">${book.author}</p>` : ""}
</div>
</div> </div>
`; `;
} }
@@ -259,14 +358,29 @@ function viewBook(bookId: string): void {
console.log("View book:", bookId); console.log("View book:", bookId);
} }
function reloadPage(): void { async function reloadPage(): Promise<void> {
window.location.reload(); //Get current library from dropdown
const librarySelect = document.getElementById(
"library-select",
) as HTMLSelectElement;
const currentLibraryId = librarySelect?.value;
if (!currentLibraryId) {
(window as any).showToast.error("No library selected");
return;
}
// Reuse switchLibrary logic - it handles the fade transition
await switchLibrary(currentLibraryId);
} }
function updateItemsCount(input: HTMLInputElement, targetId: string): void { function updateItemsCount(
const display = document.getElementById(targetId) as HTMLElement; input: HTMLInputElement,
if (display) { displayTarget: string,
display.textContent = input.value; ): void {
const displayElement = document.getElementById(displayTarget) as HTMLElement;
if (displayElement) {
displayElement.textContent = input.value;
} }
} }
@@ -342,13 +456,6 @@ document.addEventListener("DOMContentLoaded", () => {
closeDashboardSettings(); closeDashboardSettings();
break; break;
case "toggle-collection-visibility": {
const checkbox = target as HTMLInputElement;
const colId = checkbox.dataset.collectionId;
if (colId) toggleCollectionVisibility(colId);
break;
}
case "save-dashboard-settings": case "save-dashboard-settings":
saveDashboardSettings(); saveDashboardSettings();
break; break;
@@ -379,7 +486,15 @@ document.addEventListener("DOMContentLoaded", () => {
if (select.value) switchLibrary(select.value); if (select.value) switchLibrary(select.value);
break; break;
} }
}
});
document.addEventListener("input", (e: Event) => {
const target = e.target as HTMLElement;
const actionElem = target.closest("[data-input-action]") as HTMLElement;
const action = actionElem?.getAttribute("data-input-action");
switch (action) {
case "update-items-count": { case "update-items-count": {
const input = target as HTMLInputElement; const input = target as HTMLInputElement;
const displayTarget = input.getAttribute("target"); const displayTarget = input.getAttribute("target");
@@ -388,4 +503,23 @@ document.addEventListener("DOMContentLoaded", () => {
} }
} }
}); });
// Load saved library on page load
const librarySelect = document.getElementById(
"library-select",
) as HTMLSelectElement;
if (librarySelect) {
librarySelect.addEventListener("change", (e) => {
const target = e.target as HTMLSelectElement;
if (target.value) {
switchLibrary(target.value);
}
});
}
const savedLibrary = localStorage.getItem("selectedLibrary");
const currentLibrary = new URLSearchParams(window.location.search).get(
"library_id",
);
if (savedLibrary && savedLibrary !== currentLibrary) {
window.location.href = `/dashboard?library_id=${savedLibrary}`;
}
}); });