- Wrap all Alpine.data() object literals in arrow functions (() => ({}))
- This fixes "n.bind is not a function" errors when Alpine initializes components
- Alpine.data() requires a factory function, not a plain object
- Ensures each component instance gets its own closure and proper this binding
Fixed 24 TypeScript files:
- admin.ts, analytics.ts, api.ts, api-explorer-docs.ts
- bookshelf.ts, collection-rules.ts, collections.ts, conflicts.ts
- device-management.ts, docs.ts, header.ts, index.ts
- library.ts, linking.ts, login.ts, password_validation.ts
- profile-modal.ts, profile.ts, queue.ts, register.ts
- search.ts, theme.ts, toast-error.ts, toast.ts, unlinked_books.ts
Before: Alpine.data("name", { method1, method2 })
After: Alpine.data("name", () => ({ method1, method2 }))
This is a critical fix for Alpine.js v3+ where components must be
registered as factory functions to ensure proper reactivity and
prevent binding errors during initialization.
205 lines
5.7 KiB
TypeScript
205 lines
5.7 KiB
TypeScript
import { Alpine } from "./alpine";
|
|
import { showToast } from "./toast";
|
|
|
|
async function refreshQueue(): Promise<void> {
|
|
const token = localStorage.getItem("token");
|
|
if (!token) return;
|
|
|
|
try {
|
|
const response = await fetch("/api/queue/all", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
renderQueueItems(data.items || []);
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to refresh queue:", error);
|
|
}
|
|
}
|
|
|
|
async function processPendingItems(): Promise<void> {
|
|
const token = localStorage.getItem("token");
|
|
if (!token) return;
|
|
|
|
try {
|
|
const response = await fetch("/api/queue/process", {
|
|
method: "POST",
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
|
|
if (response.ok) {
|
|
showToast("Processing queue items", "success");
|
|
refreshQueue();
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to process queue:", error);
|
|
showToast("Failed to process queue", "error");
|
|
}
|
|
}
|
|
|
|
async function clearFailedItems(): Promise<void> {
|
|
const token = localStorage.getItem("token");
|
|
if (!token) return;
|
|
|
|
if (!confirm("Are you sure you want to clear all failed items?")) return;
|
|
|
|
try {
|
|
const response = await fetch("/api/queue/failed", {
|
|
method: "DELETE",
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
|
|
if (response.ok) {
|
|
showToast("Failed items cleared", "success");
|
|
refreshQueue();
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to clear failed items:", error);
|
|
showToast("Failed to clear items", "error");
|
|
}
|
|
}
|
|
|
|
async function clearAllItems(): Promise<void> {
|
|
const token = localStorage.getItem("token");
|
|
if (!token) return;
|
|
|
|
if (!confirm("Are you sure you want to clear all queue items?")) return;
|
|
|
|
try {
|
|
const response = await fetch("/api/queue/all", {
|
|
method: "DELETE",
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
|
|
if (response.ok) {
|
|
showToast("Queue cleared", "success");
|
|
refreshQueue();
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to clear queue:", error);
|
|
showToast("Failed to clear queue", "error");
|
|
}
|
|
}
|
|
|
|
async function retryQueueItem(itemId: string): Promise<void> {
|
|
const token = localStorage.getItem("token");
|
|
if (!token) return;
|
|
|
|
try {
|
|
const response = await fetch(`/api/queue/items/${itemId}/retry`, {
|
|
method: "POST",
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
|
|
if (response.ok) {
|
|
showToast("Item queued for retry", "success");
|
|
refreshQueue();
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to retry item:", error);
|
|
showToast("Failed to retry item", "error");
|
|
}
|
|
}
|
|
|
|
async function deleteQueueItem(itemId: string): Promise<void> {
|
|
const token = localStorage.getItem("token");
|
|
if (!token) return;
|
|
|
|
try {
|
|
const response = await fetch(`/api/queue/items/${itemId}`, {
|
|
method: "DELETE",
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
|
|
if (response.ok) {
|
|
showToast("Item deleted", "success");
|
|
refreshQueue();
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to delete item:", error);
|
|
showToast("Failed to delete item", "error");
|
|
}
|
|
}
|
|
|
|
function renderQueueItems(items: QueueItemResponse[]): void {
|
|
const container = document.getElementById("queue-items");
|
|
if (!container) return;
|
|
|
|
if (items.length === 0) {
|
|
container.innerHTML =
|
|
'<p class="text-center p-4" style="color: var(--text-secondary)">Queue is empty</p>';
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = items
|
|
.map(
|
|
(item) => `
|
|
<div class="p-3 rounded-lg border mb-2" style="background-color: var(--bg-secondary); border-color: var(--border)">
|
|
<div class="flex justify-between items-center">
|
|
<div>
|
|
<p class="font-medium" style="color: var(--text-primary)">${item.media_title || "Unknown"}</p>
|
|
<p class="text-sm" style="color: var(--text-secondary)">${item.status} - ${item.sync_type}</p>
|
|
<p class="text-xs" style="color: var(--text-secondary)">Attempts: ${item.attempts}/${item.max_attempts}</p>
|
|
</div>
|
|
<div class="flex space-x-2">
|
|
${item.status === "failed" ? `<button onclick="window.retryQueueItem('${item.id}')" class="btn-secondary px-2 py-1 rounded text-sm">Retry</button>` : ""}
|
|
<button onclick="window.deleteQueueItem('${item.id}')" class="btn-secondary px-2 py-1 rounded text-sm">Delete</button>
|
|
</div>
|
|
</div>
|
|
${item.error_message ? `<p class="text-xs mt-2" style="color: var(--accent)">${item.error_message}</p>` : ""}
|
|
</div>
|
|
`,
|
|
)
|
|
.join("");
|
|
}
|
|
|
|
function showQueueItemModal(itemId: string): void {
|
|
const modal = document.getElementById("queue-item-modal");
|
|
const detailsContainer = document.getElementById("queue-item-details");
|
|
|
|
if (modal && detailsContainer) {
|
|
// TODO: Fetch and display item details
|
|
detailsContainer.innerHTML = "<p>Loading item details...</p>";
|
|
modal.classList.remove("hidden");
|
|
}
|
|
}
|
|
|
|
function hideQueueItemModal(): void {
|
|
const modal = document.getElementById("queue-item-modal");
|
|
if (modal) {
|
|
modal.classList.add("hidden");
|
|
}
|
|
}
|
|
|
|
function filterQueue(status: string): void {
|
|
const filterValue = status || "all";
|
|
// TODO: Implement filtering logic
|
|
console.log("Filter queue by:", filterValue);
|
|
}
|
|
|
|
export {
|
|
clearAllItems,
|
|
clearFailedItems,
|
|
deleteQueueItem,
|
|
processPendingItems,
|
|
refreshQueue,
|
|
retryQueueItem,
|
|
showQueueItemModal,
|
|
hideQueueItemModal,
|
|
filterQueue,
|
|
};
|
|
|
|
Alpine.data("queue", () => ({
|
|
clearAllItems,
|
|
clearFailedItems,
|
|
deleteQueueItem,
|
|
processPendingItems,
|
|
refreshQueue,
|
|
retryQueueItem,
|
|
showQueueItemModal,
|
|
hideQueueItemModal,
|
|
filterQueue,
|
|
}));
|