feat(templates): add library switcher to collections and dashboard pages
Replace inline library switcher HTML with shared LibrarySwitcher component across all collection pages and the dashboard. templates/collections.templ (Collection): - Update signature to accept libData []LibraryData, currentLibraryID - Add @LibrarySwitcher(libData, currentLibraryID) after header - Change x-init to initCollectionsPage() for unified initialization templates/collections.templ (CollectionDetail): - Update signature to accept libData []LibraryData - Add @LibrarySwitcher(libData, libraryID) after header - Fix broken "Back to Collections" button: replace non-existent backToCollections Alpine method with a plain <a href="/collections"> link - Change x-init to initCollectionsPage() for unified initialization templates/dashboard.templ: - Replace 46-line inline sticky library selector (lines 20-66) with @LibrarySwitcher(libData, currentLibraryID, DashboardActions()) - Dashboard-specific settings and refresh buttons extracted into the DashboardActions sub-component via the variadic actions parameter
This commit is contained in:
@@ -2,7 +2,7 @@ package templates
|
||||
|
||||
import "bookhoard/internal/handlers"
|
||||
|
||||
templ Collection(user User, collections []CollectionData, errorMessage string) {
|
||||
templ Collection(user User, collections []CollectionData, libData []LibraryData, currentLibraryID string, errorMessage string) {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -12,8 +12,9 @@ templ Collection(user User, collections []CollectionData, errorMessage string) {
|
||||
<script src="/static/htmx.min.js"></script>
|
||||
<link href="/static/style.css" rel="stylesheet"/>
|
||||
</head>
|
||||
<body x-data="collections" x-init="initializeCollectionWebSocket(); setupHTMXModalInit()" class="theme-{ user.Theme }">
|
||||
<body x-data="collections" x-init="initCollectionsPage()" class="theme-{ user.Theme }">
|
||||
@Header(user, "/collections")
|
||||
@LibrarySwitcher(libData, currentLibraryID)
|
||||
<!-- Modal Container -->
|
||||
<div id="modal-container"></div>
|
||||
<!-- Actual container page -->
|
||||
@@ -100,7 +101,7 @@ templ Collection(user User, collections []CollectionData, errorMessage string) {
|
||||
</html>
|
||||
}
|
||||
|
||||
templ CollectionDetail(user User, collection CollectionData, books []handlers.BookInfo, libraryID string) {
|
||||
templ CollectionDetail(user User, collection CollectionData, books []handlers.BookInfo, libraryID string, libData []LibraryData) {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -110,13 +111,14 @@ templ CollectionDetail(user User, collection CollectionData, books []handlers.Bo
|
||||
<script src="/static/htmx.min.js"></script>
|
||||
<link href="/static/style.css" rel="stylesheet"/>
|
||||
</head>
|
||||
<body x-data="collections" x-init="initializeCollectionWebSocket()" class="theme-{ user.Theme }">
|
||||
<body x-data="collections" x-init="initCollectionsPage()" class="theme-{ user.Theme }">
|
||||
@Header(user, "/collections")
|
||||
@LibrarySwitcher(libData, libraryID)
|
||||
<div class="w-full px-4 sm:px-6 lg:px-8 py-8">
|
||||
<div class="mb-6">
|
||||
<button @click="backToCollections" class="btn-secondary px-4 py-2 rounded-lg mb-4">
|
||||
<a href="/collections" class="btn-secondary px-4 py-2 rounded-lg mb-4 inline-block">
|
||||
← Back to Collections
|
||||
</button>
|
||||
</a>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="text-4xl" style="color: { collection.Color }">{ collection.Icon }</div>
|
||||
<div>
|
||||
@@ -169,7 +171,6 @@ templ CollectionDetail(user User, collection CollectionData, books []handlers.Bo
|
||||
style="background-color: var(--bg-secondary); border-color: var(--border);"
|
||||
>
|
||||
<div class="flex gap-4">
|
||||
<!-- Checkbox and Book Info -->
|
||||
<div class="flex-shrink-0 pt-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -193,7 +194,6 @@ templ CollectionDetail(user User, collection CollectionData, books []handlers.Bo
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
<!-- Book Cover -->
|
||||
<div class="flex-shrink-0 w-16 sm:w-20">
|
||||
if book.CoverImagePath != "" {
|
||||
<img
|
||||
@@ -211,7 +211,6 @@ templ CollectionDetail(user User, collection CollectionData, books []handlers.Bo
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Remove Button -->
|
||||
<div class="mt-3 pt-3 border-t" style="border-color: var(--border);">
|
||||
<button
|
||||
@click="removeBook('{ book.MediaItemID }')"
|
||||
@@ -227,16 +226,12 @@ templ CollectionDetail(user User, collection CollectionData, books []handlers.Bo
|
||||
</div>
|
||||
</div>
|
||||
<!-- Book Picker Modal -->
|
||||
<!-- CRITICAL: Selection state stored in Alpine.store to persist across HTMX swaps -->
|
||||
<div
|
||||
x-data="bookPicker"
|
||||
@keyup.escape.window="$store.bookPicker.close()"
|
||||
class="fixed inset-0 z-50 flex items-center justify-center"
|
||||
style="display: none;"
|
||||
>
|
||||
<!-- Modal content with same filtering UI as bookshelf + multi-select -->
|
||||
<!-- CRITICAL: x-show manages visibility, style="display: none;" prevents FOUC -->
|
||||
<!-- Following ALPINE_COMPLETION_GUIDE.md principles: Alpine state for UI, not class="hidden" -->
|
||||
<div
|
||||
@click.stop
|
||||
x-show="$store.bookPicker.isOpen"
|
||||
@@ -262,13 +257,11 @@ templ CollectionDetail(user User, collection CollectionData, books []handlers.Bo
|
||||
style="color: var(--text-primary)"
|
||||
>✕</button>
|
||||
</div>
|
||||
<!-- Filter Bar (same as bookshelf) -->
|
||||
<div
|
||||
class="p-4 border-b"
|
||||
style="border-color: var(--border);"
|
||||
>
|
||||
<div class="flex flex-wrap gap-4 items-center">
|
||||
<!-- Search -->
|
||||
<div class="flex-1 min-w-[200px]">
|
||||
<input
|
||||
type="text"
|
||||
@@ -282,7 +275,6 @@ templ CollectionDetail(user User, collection CollectionData, books []handlers.Bo
|
||||
hx-include="#book-picker-filters"
|
||||
/>
|
||||
</div>
|
||||
<!-- Author -->
|
||||
<div class="flex-1 min-w-[150px]">
|
||||
<input
|
||||
type="text"
|
||||
@@ -296,7 +288,6 @@ templ CollectionDetail(user User, collection CollectionData, books []handlers.Bo
|
||||
hx-include="#book-picker-filters"
|
||||
/>
|
||||
</div>
|
||||
<!-- Genre -->
|
||||
<div class="flex-1 min-w-[150px]">
|
||||
<input
|
||||
type="text"
|
||||
@@ -310,7 +301,6 @@ templ CollectionDetail(user User, collection CollectionData, books []handlers.Bo
|
||||
hx-include="#book-picker-filters"
|
||||
/>
|
||||
</div>
|
||||
<!-- Clear -->
|
||||
<div>
|
||||
<button
|
||||
@click="$store.bookPicker.clearFilters()"
|
||||
@@ -321,23 +311,16 @@ templ CollectionDetail(user User, collection CollectionData, books []handlers.Bo
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Hidden form for HTMX -->
|
||||
<!-- NOTE: class="hidden" is acceptable here because form is never shown to user -->
|
||||
<!-- HTMX uses it for parameter inclusion only -->
|
||||
<form id="filter-form" class="hidden">
|
||||
<input type="hidden" name="limit" value="50"/>
|
||||
<input type="hidden" name="offset" value="0"/>
|
||||
</form>
|
||||
</div>
|
||||
<!-- Books Grid with Multi-select -->
|
||||
<!-- CRITICAL: HTMX swaps this div's content, but Alpine.store preserves selection state -->
|
||||
<div
|
||||
id="book-picker-grid"
|
||||
class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-4 p-4 max-h-96 overflow-y-auto"
|
||||
>
|
||||
<!-- Books loaded via HTMX -->
|
||||
</div>
|
||||
<!-- Selected Count & Submit -->
|
||||
<div
|
||||
class="p-4 border-t flex justify-between items-center"
|
||||
style="border-color: var(--border);"
|
||||
|
||||
Reference in New Issue
Block a user