Fix TypeScript issues in device-management.ts and unlinked_books.ts: 1. device-management.ts: - Move 'deviceType' variable declaration to function scope in showDeviceSettings() - Previously declared inside a Promise chain, creating potential scope issues - Now properly declared at function level before async operations 2. unlinked_books.ts: - Remove unused 'result' parameter from .then() handlers - Fixes autoLinkBook() and confirmManualLink() functions - Handlers don't use the API response result, only need success/failure These changes improve code clarity and resolve potential runtime issues with variable accessibility in async callback chains. Technical details: - deviceType: moved from Promise .then() block to function scope - Unused parameters: removed to prevent linting warnings and improve clarity
725 lines
22 KiB
TypeScript
725 lines
22 KiB
TypeScript
import { Alpine } from "./alpine";
|
|
import { showToast } from "./toast";
|
|
import { getToken } from "./storage";
|
|
|
|
function getDeviceIcon(typeName: string): string {
|
|
const deviceIcons: Record<string, string> = {
|
|
koreader: "📖",
|
|
kobo: "📚",
|
|
web: "🌐",
|
|
mobile: "📱",
|
|
};
|
|
return deviceIcons[typeName] || "📱";
|
|
}
|
|
|
|
function showAddDeviceModal(): void {
|
|
const modal = document.getElementById("add-device-modal");
|
|
if (modal) modal.classList.remove("hidden");
|
|
}
|
|
|
|
function hideAddDeviceModal(): void {
|
|
const modal = document.getElementById("add-device-modal");
|
|
const form = document.getElementById("add-device-form");
|
|
if (modal) modal.classList.add("hidden");
|
|
if (form && form instanceof HTMLFormElement) form.reset();
|
|
}
|
|
|
|
async function handleAddDevice(event: Event): Promise<void> {
|
|
event.preventDefault();
|
|
|
|
const token = getToken();
|
|
if (!token) return;
|
|
|
|
const deviceNameInput = document.getElementById(
|
|
"device-name",
|
|
) as HTMLInputElement;
|
|
const deviceTypeInput = document.getElementById(
|
|
"device-type",
|
|
) as HTMLSelectElement;
|
|
const deviceIdentifierInput = document.getElementById(
|
|
"device-identifier",
|
|
) as HTMLInputElement;
|
|
|
|
if (!deviceNameInput || !deviceTypeInput || !deviceIdentifierInput) return;
|
|
|
|
const data = {
|
|
device_name: deviceNameInput.value,
|
|
device_type: deviceTypeInput.value,
|
|
device_identifier: deviceIdentifierInput.value,
|
|
};
|
|
|
|
try {
|
|
const response = await fetch("/api/devices/register", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify(data),
|
|
});
|
|
|
|
if (response.ok) {
|
|
showToast(
|
|
"Device registered! Check your device for sync instructions.",
|
|
"success",
|
|
);
|
|
hideAddDeviceModal();
|
|
window.location.reload();
|
|
} else {
|
|
showToast("Failed to register device", "error");
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to register device", error);
|
|
showToast("Failed to register device", "error");
|
|
}
|
|
}
|
|
|
|
function showDeviceSettings(deviceId: string): void {
|
|
const modal = document.getElementById("device-settings-modal");
|
|
const deviceIdInput = document.getElementById(
|
|
"settings-device-id",
|
|
) as HTMLInputElement;
|
|
const deviceTypeInput = document.getElementById(
|
|
"settings-device-type",
|
|
) as HTMLInputElement;
|
|
const deviceNameInput = document.getElementById(
|
|
"settings-device-name",
|
|
) as HTMLInputElement;
|
|
const syncEnabledInput = document.getElementById(
|
|
"settings-sync-enabled",
|
|
) as HTMLInputElement;
|
|
const autoSyncInput = document.getElementById(
|
|
"settings-auto-sync",
|
|
) as HTMLInputElement;
|
|
const syncFrequencyInput = document.getElementById(
|
|
"settings-sync-frequency",
|
|
) as HTMLInputElement;
|
|
|
|
if (!modal || !deviceIdInput) return;
|
|
|
|
deviceIdInput.value = deviceId;
|
|
|
|
const token = getToken();
|
|
if (!token) return;
|
|
|
|
let deviceType: any;
|
|
|
|
fetch(`/api/devices/${deviceId}`, {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
})
|
|
.then((res) => res.json())
|
|
.then((device) => {
|
|
if (deviceTypeInput) deviceTypeInput.value = device.device_type || "";
|
|
if (deviceNameInput) deviceNameInput.value = device.device_name || "";
|
|
if (syncEnabledInput)
|
|
syncEnabledInput.checked = device.sync_enabled || false;
|
|
if (autoSyncInput) autoSyncInput.checked = device.auto_sync || false;
|
|
if (syncFrequencyInput)
|
|
syncFrequencyInput.value = String(device.sync_frequency_minutes || 60);
|
|
|
|
deviceType = device.device_type;
|
|
return fetch("/api/collections", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
})
|
|
.then((res) => res.json())
|
|
.then((result) => {
|
|
if (result.collections && result.collections.length > 0) {
|
|
const firstCollection = result.collections[0];
|
|
const viewSettings = firstCollection.view_settings || {};
|
|
const deviceSettings = viewSettings[deviceType] || {};
|
|
|
|
const viewModeInput = document.getElementById(
|
|
"settings-view-mode",
|
|
) as HTMLInputElement;
|
|
const sortOrderInput = document.getElementById(
|
|
"settings-sort-order",
|
|
) as HTMLInputElement;
|
|
const itemsPerPageInput = document.getElementById(
|
|
"settings-items-per-page",
|
|
) as HTMLInputElement;
|
|
const showCoversInput = document.getElementById(
|
|
"settings-show-covers",
|
|
) as HTMLInputElement;
|
|
const showProgressInput = document.getElementById(
|
|
"settings-show-progress",
|
|
) as HTMLInputElement;
|
|
|
|
if (viewModeInput)
|
|
viewModeInput.value = deviceSettings.view_mode || "grid";
|
|
if (sortOrderInput)
|
|
sortOrderInput.value = deviceSettings.sort_order || "name";
|
|
if (itemsPerPageInput)
|
|
itemsPerPageInput.value = String(deviceSettings.items_per_page || 24);
|
|
if (showCoversInput)
|
|
showCoversInput.checked = deviceSettings.show_covers !== false;
|
|
if (showProgressInput)
|
|
showProgressInput.checked = deviceSettings.show_progress || false;
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.error("Failed to load view settings:", error);
|
|
});
|
|
|
|
if (modal) modal.classList.remove("hidden");
|
|
}
|
|
|
|
function showShelfMappings(deviceId: string): void {
|
|
const mappingsDeviceIdInput = document.getElementById(
|
|
"mappings-device-id",
|
|
) as HTMLInputElement;
|
|
const modal = document.getElementById("shelf-mappings-modal");
|
|
|
|
if (mappingsDeviceIdInput) mappingsDeviceIdInput.value = deviceId;
|
|
loadShelfMappings(deviceId);
|
|
if (modal) modal.classList.remove("hidden");
|
|
}
|
|
|
|
function hideShelfMappingsModal(): void {
|
|
const modal = document.getElementById("shelf-mappings-modal");
|
|
if (modal) modal.classList.add("hidden");
|
|
}
|
|
|
|
async function loadShelfMappings(deviceId: string): Promise<void> {
|
|
const container = document.getElementById("shelf-mappings-container");
|
|
const token = getToken();
|
|
if (!container || !token) return;
|
|
|
|
try {
|
|
const response = await fetch(`/api/devices/${deviceId}/collections`, {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
const result = await response.json();
|
|
|
|
if (result.mappings && result.mappings.length > 0) {
|
|
container.innerHTML = result.mappings
|
|
.map(
|
|
(mapping: any) => `
|
|
<div class="card p-4 rounded-lg border" style="background-color: var(--bg-primary); border-color: var(--border);">
|
|
<div class="flex justify-between items-center">
|
|
<div>
|
|
<h4 class="font-semibold" style="color: var(--text-primary)">${mapping.collection_name}</h4>
|
|
<p class="text-sm" style="color: var(--text-secondary)">
|
|
→ Device Shelf: <strong>${mapping.device_shelf_name}</strong>
|
|
</p>
|
|
<p class="text-xs" style="color: var(--text-secondary)">
|
|
Sync: ${mapping.sync_direction}
|
|
</p>
|
|
</div>
|
|
<div class="flex space-x-2">
|
|
<button data-action="edit-mapping" data-mapping-id="${mapping.id}" data-collection-id="${mapping.collection_id}" data-shelf-name="${mapping.device_shelf_name}" data-sync-direction="${mapping.sync_direction}"
|
|
class="p-2 hover:opacity-80 rounded" style="color: var(--text-secondary); background-color: var(--bg-secondary);">
|
|
✏️
|
|
</button>
|
|
<button data-action="delete-mapping" data-mapping-id="${mapping.id}"
|
|
class="p-2 hover:opacity-80 rounded" style="color: var(--text-secondary); background-color: var(--bg-secondary);">
|
|
🗑️
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`,
|
|
)
|
|
.join("");
|
|
} else {
|
|
container.innerHTML =
|
|
'<p style="color: var(--text-secondary)">No shelf mappings configured. Click "Add Mapping" to create one.</p>';
|
|
}
|
|
} catch (error) {
|
|
container.innerHTML =
|
|
'<p style="color: var(--error)">Failed to load mappings</p>';
|
|
}
|
|
}
|
|
|
|
function showAddMappingModal(): void {
|
|
const mappingsDeviceIdInput = document.getElementById(
|
|
"mappings-device-id",
|
|
) as HTMLInputElement;
|
|
const mappingIdInput = document.getElementById(
|
|
"mapping-id",
|
|
) as HTMLInputElement;
|
|
const mappingCollectionInput = document.getElementById(
|
|
"mapping-collection",
|
|
) as HTMLSelectElement;
|
|
const mappingShelfNameInput = document.getElementById(
|
|
"mapping-shelf-name",
|
|
) as HTMLInputElement;
|
|
const mappingSyncDirectionInput = document.getElementById(
|
|
"mapping-sync-direction",
|
|
) as HTMLSelectElement;
|
|
const modal = document.getElementById("add-mapping-modal");
|
|
|
|
if (mappingsDeviceIdInput) {
|
|
const mappingDeviceIdInput = document.getElementById(
|
|
"mapping-device-id",
|
|
) as HTMLInputElement;
|
|
if (mappingDeviceIdInput)
|
|
mappingDeviceIdInput.value = mappingsDeviceIdInput.value;
|
|
}
|
|
if (mappingIdInput) mappingIdInput.value = "";
|
|
if (mappingCollectionInput) mappingCollectionInput.value = "";
|
|
if (mappingShelfNameInput) mappingShelfNameInput.value = "";
|
|
if (mappingSyncDirectionInput)
|
|
mappingSyncDirectionInput.value = "bidirectional";
|
|
|
|
loadCollections();
|
|
if (modal) modal.classList.remove("hidden");
|
|
}
|
|
|
|
function hideAddMappingModal(): void {
|
|
const modal = document.getElementById("add-mapping-modal");
|
|
const form = document.getElementById("mapping-form");
|
|
if (modal) modal.classList.add("hidden");
|
|
if (form && form instanceof HTMLFormElement) form.reset();
|
|
}
|
|
|
|
async function loadCollections(): Promise<void> {
|
|
const select = document.getElementById(
|
|
"mapping-collection",
|
|
) as HTMLSelectElement;
|
|
const token = getToken();
|
|
if (!select || !token) return;
|
|
|
|
try {
|
|
const response = await fetch("/api/collections", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
const result = await response.json();
|
|
|
|
if (result.collections) {
|
|
select.innerHTML =
|
|
'<option value="">Select collection...</option>' +
|
|
result.collections
|
|
.map((col: any) => `<option value="${col.id}">${col.name}</option>`)
|
|
.join("");
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to load collections", error);
|
|
}
|
|
}
|
|
|
|
function editMapping(
|
|
mappingId: string,
|
|
collectionId: string,
|
|
shelfName: string,
|
|
syncDirection: string,
|
|
): void {
|
|
const mappingIdInput = document.getElementById(
|
|
"mapping-id",
|
|
) as HTMLInputElement;
|
|
const mappingCollectionInput = document.getElementById(
|
|
"mapping-collection",
|
|
) as HTMLSelectElement;
|
|
const mappingShelfNameInput = document.getElementById(
|
|
"mapping-shelf-name",
|
|
) as HTMLInputElement;
|
|
const mappingSyncDirectionInput = document.getElementById(
|
|
"mapping-sync-direction",
|
|
) as HTMLSelectElement;
|
|
const modal = document.getElementById("add-mapping-modal");
|
|
|
|
if (mappingIdInput) mappingIdInput.value = mappingId;
|
|
if (mappingCollectionInput) mappingCollectionInput.value = collectionId;
|
|
if (mappingShelfNameInput) mappingShelfNameInput.value = shelfName;
|
|
if (mappingSyncDirectionInput)
|
|
mappingSyncDirectionInput.value = syncDirection;
|
|
|
|
if (modal) modal.classList.remove("hidden");
|
|
}
|
|
|
|
async function handleSaveMapping(event: Event): Promise<void> {
|
|
event.preventDefault();
|
|
|
|
const token = getToken();
|
|
const mappingsDeviceIdInput = document.getElementById(
|
|
"mapping-device-id",
|
|
) as HTMLInputElement;
|
|
const mappingIdInput = document.getElementById(
|
|
"mapping-id",
|
|
) as HTMLInputElement;
|
|
const mappingCollectionInput = document.getElementById(
|
|
"mapping-collection",
|
|
) as HTMLSelectElement;
|
|
const mappingShelfNameInput = document.getElementById(
|
|
"mapping-shelf-name",
|
|
) as HTMLInputElement;
|
|
const mappingSyncDirectionInput = document.getElementById(
|
|
"mapping-sync-direction",
|
|
) as HTMLSelectElement;
|
|
|
|
if (
|
|
!token ||
|
|
!mappingsDeviceIdInput ||
|
|
!mappingCollectionInput ||
|
|
!mappingShelfNameInput ||
|
|
!mappingSyncDirectionInput
|
|
)
|
|
return;
|
|
|
|
const deviceId = mappingsDeviceIdInput.value;
|
|
const mappingId = mappingIdInput.value;
|
|
const isUpdate = mappingId !== "";
|
|
|
|
const data = {
|
|
collection_id: mappingCollectionInput.value,
|
|
device_shelf_name: mappingShelfNameInput.value,
|
|
sync_direction: mappingSyncDirectionInput.value,
|
|
};
|
|
|
|
const url = isUpdate
|
|
? `/api/devices/${deviceId}/collections/${mappingId}`
|
|
: `/api/devices/${deviceId}/collections`;
|
|
const method = isUpdate ? "PUT" : "POST";
|
|
|
|
try {
|
|
const response = await fetch(url, {
|
|
method: method,
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify(data),
|
|
});
|
|
|
|
if (response.ok) {
|
|
showToast(isUpdate ? "Mapping updated" : "Mapping created", "success");
|
|
hideAddMappingModal();
|
|
loadShelfMappings(deviceId);
|
|
} else {
|
|
showToast("Failed to save mapping", "error");
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to save mapping", error);
|
|
showToast("Failed to save mapping", "error");
|
|
}
|
|
}
|
|
|
|
async function deleteMapping(mappingId: string): Promise<void> {
|
|
if (!confirm("Are you sure you want to delete this mapping?")) return;
|
|
|
|
const token = getToken();
|
|
const mappingsDeviceIdInput = document.getElementById(
|
|
"mappings-device-id",
|
|
) as HTMLInputElement;
|
|
if (!token || !mappingsDeviceIdInput) return;
|
|
|
|
const deviceId = mappingsDeviceIdInput.value;
|
|
|
|
try {
|
|
const response = await fetch(
|
|
`/api/devices/${deviceId}/collections/${mappingId}`,
|
|
{
|
|
method: "DELETE",
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
},
|
|
);
|
|
|
|
if (response.ok) {
|
|
showToast("Mapping deleted", "success");
|
|
loadShelfMappings(deviceId);
|
|
} else {
|
|
showToast("Failed to delete mapping", "error");
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to delete mapping", error);
|
|
showToast("Failed to delete mapping", "error");
|
|
}
|
|
}
|
|
|
|
function hideDeviceSettingsModal(): void {
|
|
const modal = document.getElementById("device-settings-modal");
|
|
if (modal) modal.classList.add("hidden");
|
|
}
|
|
|
|
async function handleSaveDeviceSettings(event: Event): Promise<void> {
|
|
event.preventDefault();
|
|
|
|
const token = getToken();
|
|
const settingsDeviceIdInput = document.getElementById(
|
|
"settings-device-id",
|
|
) as HTMLInputElement;
|
|
const settingsDeviceTypeInput = document.getElementById(
|
|
"settings-device-type",
|
|
) as HTMLInputElement;
|
|
const settingsDeviceNameInput = document.getElementById(
|
|
"settings-device-name",
|
|
) as HTMLInputElement;
|
|
const settingsSyncEnabledInput = document.getElementById(
|
|
"settings-sync-enabled",
|
|
) as HTMLInputElement;
|
|
const settingsAutoSyncInput = document.getElementById(
|
|
"settings-auto-sync",
|
|
) as HTMLInputElement;
|
|
const settingsSyncFrequencyInput = document.getElementById(
|
|
"settings-sync-frequency",
|
|
) as HTMLInputElement;
|
|
|
|
if (!token || !settingsDeviceIdInput || !settingsDeviceNameInput) return;
|
|
|
|
const deviceId = settingsDeviceIdInput.value;
|
|
const deviceType = settingsDeviceTypeInput?.value || "";
|
|
|
|
const deviceData = {
|
|
device_name: settingsDeviceNameInput.value,
|
|
sync_enabled: settingsSyncEnabledInput?.checked || false,
|
|
auto_sync: settingsAutoSyncInput?.checked || false,
|
|
sync_frequency_minutes: parseInt(
|
|
settingsSyncFrequencyInput?.value || "60",
|
|
10,
|
|
),
|
|
};
|
|
|
|
const viewModeInput = document.getElementById(
|
|
"settings-view-mode",
|
|
) as HTMLSelectElement;
|
|
const sortOrderInput = document.getElementById(
|
|
"settings-sort-order",
|
|
) as HTMLSelectElement;
|
|
const itemsPerPageInput = document.getElementById(
|
|
"settings-items-per-page",
|
|
) as HTMLInputElement;
|
|
const showCoversInput = document.getElementById(
|
|
"settings-show-covers",
|
|
) as HTMLInputElement;
|
|
const showProgressInput = document.getElementById(
|
|
"settings-show-progress",
|
|
) as HTMLInputElement;
|
|
|
|
const viewSettings = {
|
|
view_mode: viewModeInput?.value || "grid",
|
|
sort_order: sortOrderInput?.value || "name",
|
|
items_per_page: parseInt(itemsPerPageInput?.value || "24", 10),
|
|
show_covers: showCoversInput?.checked !== false,
|
|
show_progress: showProgressInput?.checked || false,
|
|
};
|
|
|
|
try {
|
|
await fetch(`/api/devices/${deviceId}`, {
|
|
method: "PUT",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify(deviceData),
|
|
});
|
|
|
|
const collectionsRes = await fetch("/api/collections", {
|
|
method: "GET",
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
const collections = await collectionsRes.json();
|
|
|
|
const updatePromises = collections.collections.map((collection: any) => {
|
|
const currentSettings = collection.view_settings || {};
|
|
currentSettings[deviceType] = viewSettings;
|
|
|
|
return fetch(`/api/collections/${collection.id}`, {
|
|
method: "PUT",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify({
|
|
...collection,
|
|
view_settings: currentSettings,
|
|
}),
|
|
});
|
|
});
|
|
|
|
await Promise.all(updatePromises);
|
|
showToast("Device settings saved", "success");
|
|
hideDeviceSettingsModal();
|
|
window.location.reload();
|
|
} catch (error) {
|
|
console.error("Failed to save settings", error);
|
|
showToast("Failed to save settings", "error");
|
|
}
|
|
}
|
|
|
|
async function copyToClipboard(
|
|
text: string,
|
|
description: string,
|
|
): Promise<void> {
|
|
try {
|
|
await navigator.clipboard.writeText(text);
|
|
showToast(`${description} copied!`, "success");
|
|
} catch (error) {
|
|
console.error("Failed to copy to clipboard", error);
|
|
showToast("Failed to copy to clipboard", "error");
|
|
}
|
|
}
|
|
|
|
async function handleRevokeDevice(): Promise<void> {
|
|
const token = getToken();
|
|
const settingsDeviceIdInput = document.getElementById(
|
|
"settings-device-id",
|
|
) as HTMLInputElement;
|
|
if (!token || !settingsDeviceIdInput) return;
|
|
|
|
if (
|
|
!confirm(
|
|
"Are you sure you want to revoke this device? It will no longer be able to sync.",
|
|
)
|
|
)
|
|
return;
|
|
|
|
const deviceId = settingsDeviceIdInput.value;
|
|
|
|
try {
|
|
const response = await fetch(`/api/devices/${deviceId}`, {
|
|
method: "DELETE",
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
|
|
if (response.ok) {
|
|
showToast("Device revoked successfully", "success");
|
|
hideDeviceSettingsModal();
|
|
window.location.reload();
|
|
} else {
|
|
showToast("Failed to revoke device", "error");
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to revoke device", error);
|
|
showToast("Failed to revoke device", "error");
|
|
}
|
|
}
|
|
|
|
async function approveDevice(registrationId: string): Promise<void> {
|
|
const token = getToken();
|
|
if (!token) return;
|
|
|
|
try {
|
|
const response = await fetch(`/api/devices/approve/${registrationId}`, {
|
|
method: "GET",
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
|
|
if (response.ok) {
|
|
showToast("Device approved successfully", "success");
|
|
window.location.reload();
|
|
} else {
|
|
showToast("Failed to approve device", "error");
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to approve device", error);
|
|
showToast("Failed to approve device", "error");
|
|
}
|
|
}
|
|
|
|
async function rejectDevice(registrationId: string): Promise<void> {
|
|
const token = getToken();
|
|
if (!token) return;
|
|
|
|
try {
|
|
const response = await fetch(`/api/devices/reject/${registrationId}`, {
|
|
method: "POST",
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
|
|
if (response.ok) {
|
|
showToast("Device registration rejected", "info");
|
|
window.location.reload();
|
|
} else {
|
|
showToast("Failed to reject device", "error");
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to reject device", error);
|
|
showToast("Failed to reject device", "error");
|
|
}
|
|
}
|
|
|
|
async function clearSyncQueue(): Promise<void> {
|
|
if (!confirm("Are you sure you want to clear all sync queue items?")) return;
|
|
|
|
const token = getToken();
|
|
if (!token) return;
|
|
|
|
try {
|
|
const response = await fetch("/api/queue/clear", {
|
|
method: "DELETE",
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
|
|
if (response.ok) {
|
|
showToast("Sync queue cleared", "success");
|
|
} else {
|
|
showToast("Failed to clear queue", "error");
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to clear queue", error);
|
|
showToast("Failed to clear queue", "error");
|
|
}
|
|
}
|
|
|
|
function setupEventDelegation(): void {
|
|
document.addEventListener("click", (e) => {
|
|
const target = e.target as HTMLElement;
|
|
const button = target.closest("button") as HTMLButtonElement;
|
|
|
|
if (!button) return;
|
|
|
|
const action = button.dataset.action;
|
|
|
|
if (action === "edit-mapping") {
|
|
editMapping(
|
|
button.dataset.mappingId || "",
|
|
button.dataset.collectionId || "",
|
|
button.dataset.shelfName || "",
|
|
button.dataset.syncDirection || "",
|
|
);
|
|
} else if (action === "delete-mapping") {
|
|
deleteMapping(button.dataset.mappingId || "");
|
|
}
|
|
});
|
|
}
|
|
|
|
export {
|
|
approveDevice,
|
|
clearSyncQueue,
|
|
copyToClipboard,
|
|
deleteMapping,
|
|
editMapping,
|
|
getDeviceIcon,
|
|
handleAddDevice,
|
|
handleRevokeDevice,
|
|
handleSaveDeviceSettings,
|
|
handleSaveMapping,
|
|
hideAddDeviceModal,
|
|
hideAddMappingModal,
|
|
hideDeviceSettingsModal,
|
|
hideShelfMappingsModal,
|
|
loadCollections,
|
|
loadShelfMappings,
|
|
rejectDevice,
|
|
setupEventDelegation,
|
|
showAddDeviceModal,
|
|
showAddMappingModal,
|
|
showDeviceSettings,
|
|
showShelfMappings,
|
|
};
|
|
|
|
Alpine.data("devices", () => ({
|
|
approveDevice,
|
|
clearSyncQueue,
|
|
copyToClipboard,
|
|
deleteMapping,
|
|
editMapping,
|
|
getDeviceIcon,
|
|
handleAddDevice,
|
|
handleRevokeDevice,
|
|
handleSaveDeviceSettings,
|
|
handleSaveMapping,
|
|
hideAddDeviceModal,
|
|
hideAddMappingModal,
|
|
hideDeviceSettingsModal,
|
|
hideShelfMappingsModal,
|
|
loadCollections,
|
|
loadShelfMappings,
|
|
rejectDevice,
|
|
setupEventDelegation,
|
|
showAddDeviceModal,
|
|
showAddMappingModal,
|
|
showDeviceSettings,
|
|
showShelfMappings,
|
|
}));
|