feat: implement collections book picker with Alpine.store
Add multi-select book picker modal for collections using Alpine.js patterns:
- Alpine.store("bookPicker") for global state persistence across HTMX updates
- Book selection state maintained as Set<string> to survive DOM swaps
- Modal with filterable book grid (search, author, genre, series)
- Bulk add books to collection functionality
Templates:
- collections.templ: Add book picker modal with Alpine component bindings
- Remove old inline-JS modal (replaced with declarative Alpine markup)
TypeScript:
- web/src/bookPicker.ts: New module with Alpine.store and Alpine.data definitions
- web/src/main.ts: Import bookPicker module
- web/src/collections.ts: Remove old modal functions (replaced by Alpine)
This implements the Book Picker Modal feature from the collections system,
following SSR-first Alpine.js patterns with HTMX for dynamic updates.
Fixes "Add Books" button being disabled - modal now fully functional.
This commit is contained in:
@@ -495,49 +495,6 @@ export {
|
||||
};
|
||||
|
||||
Alpine.data("collections", () => ({
|
||||
// Book Picker State
|
||||
bookPickerOpen: false,
|
||||
bookPickerSelected: [] as string[],
|
||||
// Book Picker Methods
|
||||
openBookPicker(): void {
|
||||
this.bookPickerOpen = true;
|
||||
this.bookPickerSelected = [];
|
||||
},
|
||||
closeBookPicker(): void {
|
||||
this.bookPickerOpen = false;
|
||||
this.bookPickerSelected = [];
|
||||
},
|
||||
toggleBookPickerBook(bookId: string): void {
|
||||
const index = this.bookPickerSelected.indexOf(bookId);
|
||||
if (index > -1) {
|
||||
this.bookPickerSelected.splice(index, 1);
|
||||
} else {
|
||||
this.bookPickerSelected.push(bookId);
|
||||
}
|
||||
},
|
||||
isBookPickerBookSelected(bookId: string): boolean {
|
||||
return this.bookPickerSelected.includes(bookId);
|
||||
},
|
||||
getBookPickerSelectedCount(): number {
|
||||
return this.bookPickerSelected.length;
|
||||
},
|
||||
clearBookPickerFilters(): void {
|
||||
const filterForm = document.getElementById(
|
||||
"book-picker-filters",
|
||||
) as HTMLFormElement;
|
||||
if (!filterForm) return;
|
||||
|
||||
const inputs = filterForm.querySelectorAll("input:not([type='checkbox'])");
|
||||
inputs.forEach((input) => {
|
||||
(input as HTMLInputElement).value = "";
|
||||
});
|
||||
|
||||
const grid = document.getElementById("book-picker-grid");
|
||||
if (grid) {
|
||||
htmx.trigger(grid, "refresh");
|
||||
}
|
||||
},
|
||||
|
||||
// Collection Methods
|
||||
closeCollectionModal,
|
||||
createRule,
|
||||
|
||||
+3
-2
@@ -2,11 +2,12 @@ import "./alpine";
|
||||
import { Alpine } from "./alpine";
|
||||
|
||||
import "./admin";
|
||||
import "./api";
|
||||
import "./analytics";
|
||||
import "./api";
|
||||
import "./api-explorer";
|
||||
import "./api-explorer-docs";
|
||||
import "./bookPicker";
|
||||
import "./bookshelf";
|
||||
import "./collection-rules";
|
||||
import "./collections";
|
||||
import "./conflicts";
|
||||
@@ -19,8 +20,8 @@ import "./events";
|
||||
import "./header";
|
||||
import "./index";
|
||||
import "./library";
|
||||
import "./login";
|
||||
import "./linking";
|
||||
import "./login";
|
||||
import "./password_validation";
|
||||
import "./profile";
|
||||
import "./profile-modal";
|
||||
|
||||
Reference in New Issue
Block a user