diff --git a/templates/admin.templ b/templates/admin.templ index 8d5dd2c..bcb31fc 100644 --- a/templates/admin.templ +++ b/templates/admin.templ @@ -9,65 +9,8 @@ templ Admin(user User) { - - + @Header(user, "/admin")
@AdminSidebar(user, "/admin") diff --git a/templates/admin_templ.go b/templates/admin_templ.go index f75f8d5..1782b5c 100644 --- a/templates/admin_templ.go +++ b/templates/admin_templ.go @@ -29,7 +29,7 @@ func Admin(user User) templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "Admin Dashboard - Bookhoard") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "Admin Dashboard - Bookhoard") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/templates/collections.templ b/templates/collections.templ index 503b6d8..8585647 100644 --- a/templates/collections.templ +++ b/templates/collections.templ @@ -12,86 +12,8 @@ templ Collection(user User, collections []CollectionData, errorMessage string) { - - + @Header(user, "/collections") @@ -190,7 +112,7 @@ templ CollectionDetail(user User, collection CollectionData, books []handlers.Bo - + @Header(user, "/collections")
diff --git a/templates/collections_templ.go b/templates/collections_templ.go index 466c96f..c9da4d2 100644 --- a/templates/collections_templ.go +++ b/templates/collections_templ.go @@ -31,7 +31,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "Collections - Bookhoard") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "Collections - Bookhoard") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -57,7 +57,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs("/collections/" + col.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 141, Col: 82} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 63, Col: 82} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -70,7 +70,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(col.Color) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 145, Col: 30} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 67, Col: 30} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -83,7 +83,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(col.Icon) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 148, Col: 41} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 70, Col: 41} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -96,7 +96,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs("/collections/" + col.ID + "/edit-modal") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 151, Col: 60} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 73, Col: 60} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -109,7 +109,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs("/api/collections/" + col.ID + "") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 160, Col: 56} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 82, Col: 56} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -122,7 +122,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(col.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 170, Col: 92} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 92, Col: 92} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -135,7 +135,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(col.Description) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 171, Col: 86} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 93, Col: 86} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -190,13 +190,13 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo var templ_7745c5c3_Var10 string templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 188, Col: 27} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 110, Col: 27} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, " - Bookhoard") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, " - Bookhoard") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -211,7 +211,7 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo var templ_7745c5c3_Var11 string templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Icon) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 201, Col: 81} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 123, Col: 81} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { @@ -224,7 +224,7 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo var templ_7745c5c3_Var12 string templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 203, Col: 90} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 125, Col: 90} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { @@ -237,7 +237,7 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo var templ_7745c5c3_Var13 string templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Description) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 204, Col: 71} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 126, Col: 71} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { @@ -261,7 +261,7 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo var templ_7745c5c3_Var14 string templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(book.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 261, Col: 22} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 183, Col: 22} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -279,7 +279,7 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo var templ_7745c5c3_Var15 string templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(book.Author) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 268, Col: 27} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 190, Col: 27} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { @@ -302,7 +302,7 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo var templ_7745c5c3_Var16 string templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(book.CoverImagePath) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 276, Col: 36} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 198, Col: 36} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) if templ_7745c5c3_Err != nil { diff --git a/web/src/admin.ts b/web/src/admin.ts index e79b2cc..e9cd903 100644 --- a/web/src/admin.ts +++ b/web/src/admin.ts @@ -1,5 +1,46 @@ import { Alpine } from "./alpine"; import { showToast } from "./toast"; +import { createWebSocket } from "./websocket"; + +function initializeScanWebSocket(): void { + createWebSocket({ + onMessage: (message) => { + switch (message.type) { + case "scan_progress": + updateScanProgress(message.data); + break; + case "scan_complete": + showScanComplete(message.data); + break; + case "scan_error": + showScanError(message.data); + break; + } + }, + enableReconnect: true, + reconnectDelay: 5000, + }); +} + +function updateScanProgress(data: { progress: number; files_scanned: number; new_items: number }): void { + const progressBar = document.getElementById("scan-progress-bar"); + if (progressBar) { + progressBar.style.width = (data.progress * 100) + "%"; + } + + const progressText = document.getElementById("scan-progress-text"); + if (progressText) { + progressText.textContent = `${data.files_scanned} files scanned (${data.new_items} new)`; + } +} + +function showScanComplete(_data: unknown): void { + console.log("Scan complete:", _data); +} + +function showScanError(_data: unknown): void { + console.error("Scan error:", _data); +} async function triggerLibraryScan(): Promise { const token = localStorage.getItem("token"); @@ -339,6 +380,7 @@ function stopScanStatusPolling(): void { export { hideScanProgress, + initializeScanWebSocket, loadSystemStats, loadWatchStatus, scanAllLibraries, @@ -349,6 +391,7 @@ export { Alpine.data("admin", () => ({ hideScanProgress, + initializeScanWebSocket, loadSystemStats, loadWatchStatus, scanAllLibraries, diff --git a/web/src/collections.ts b/web/src/collections.ts index 73e8b98..48304ad 100644 --- a/web/src/collections.ts +++ b/web/src/collections.ts @@ -1,5 +1,50 @@ import { Alpine } from "./alpine"; import { showToast } from "./toast"; +import { createWebSocket } from "./websocket"; + +let collectionId: string | null = null; + +function initializeCollectionWebSocket(): void { + const dataEl = document.getElementById("collection-data"); + if (!dataEl) return; + + collectionId = dataEl.dataset.id || null; + + if (!collectionId) return; + + createWebSocket({ + onMessage: (message) => { + if (message.type === "collection_updated" && message.data.collection_id === collectionId) { + const actionText = + message.data.action === "books_added" + ? `Added ${message.data.count || 0} book(s)` + : message.data.action === "book_removed" + ? "Removed a book" + : message.data.action === "books_bulk_removed" + ? `Removed ${message.data.count || 0} book(s)` + : "Collection updated"; + + showToast(actionText, "info"); + + const activeElement = document.activeElement; + const isUserActive = + activeElement && + (activeElement.tagName === "INPUT" || + activeElement.tagName === "TEXTAREA" || + activeElement.tagName === "SELECT" || + activeElement.getAttribute("contenteditable") === "true"); + + if (!isUserActive) { + setTimeout(() => { + location.reload(); + }, 1000); + } + } + }, + enableReconnect: true, + reconnectDelay: 5000, + }); +} async function loadCollections(): Promise { const token = localStorage.getItem("token"); @@ -410,6 +455,7 @@ export { deleteRule, filterIcons, initColorSelection, + initializeCollectionWebSocket, loadCollectionRules, loadCollections, navigateToCollection, @@ -426,6 +472,7 @@ Alpine.data("collections", () => ({ deleteRule, filterIcons, initColorSelection, + initializeCollectionWebSocket, loadCollectionRules, loadCollections, navigateToCollection,