@@ -495,6 +538,7 @@ function htmlPage(): string {
processed: 0,
total: 0
};
+ let activeImportController = null;
const map = L.map("map", { zoomControl: true }).setView([52.5208, 13.4095], 13);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
@@ -518,9 +562,11 @@ function htmlPage(): string {
const progressFill = mustGet("progress-fill");
const progressText = mustGet("progress-text");
const progressDetail = mustGet("progress-detail");
+ const activitySpinner = mustGet("activity-spinner");
const shareUrl = mustGet("share-url");
const shareStatus = mustGet("share-status");
const loadShare = mustGet("load-share");
+ const cancelShare = mustGet("cancel-share");
const useDemo = mustGet("use-demo");
function mustGet(id) {
@@ -580,6 +626,13 @@ function htmlPage(): string {
progressDetail.textContent = detail;
}
+ function setImporting(isImporting) {
+ activitySpinner.classList.toggle("active", isImporting);
+ loadShare.disabled = isImporting;
+ useDemo.disabled = isImporting;
+ cancelShare.disabled = !isImporting;
+ }
+
function clearObjectUrls() {
for (const url of state.objectUrls) {
URL.revokeObjectURL(url);
@@ -733,9 +786,12 @@ function htmlPage(): string {
});
}
- async function loadShareListing(davUrl) {
+ async function loadShareListing(davUrl, signal) {
const response = await fetch(
- "/api/nextcloud/list?share=" + encodeURIComponent(shareUrl.value.trim())
+ "/api/nextcloud/list?share=" + encodeURIComponent(shareUrl.value.trim()),
+ {
+ signal
+ }
);
if (!response.ok) {
@@ -745,8 +801,10 @@ function htmlPage(): string {
return parseListing(await response.text(), davUrl);
}
- async function readRemotePhoto(entry) {
- const response = await fetch("/api/nextcloud/blob?url=" + encodeURIComponent(entry.href));
+ async function readRemotePhoto(entry, signal) {
+ const response = await fetch("/api/nextcloud/blob?url=" + encodeURIComponent(entry.href), {
+ signal
+ });
if (!response.ok) {
throw new Error("Bild konnte nicht geladen werden: " + entry.name);
}
@@ -772,12 +830,20 @@ function htmlPage(): string {
}
async function importFromNextcloud() {
+ if (activeImportController) {
+ return;
+ }
+
+ const controller = new AbortController();
+ activeImportController = controller;
+ setImporting(true);
+
try {
clearObjectUrls();
clearGallery();
updateStatus("Share wird geladen...");
const { davUrl } = parseShareInput(shareUrl.value);
- const listing = await loadShareListing(davUrl);
+ const listing = await loadShareListing(davUrl, controller.signal);
if (!listing.length) {
throw new Error("Im Share wurden keine Bilder gefunden.");
@@ -790,8 +856,12 @@ function htmlPage(): string {
let processed = 0;
for (const entry of listing) {
+ if (controller.signal.aborted) {
+ throw new DOMException("Import abgebrochen", "AbortError");
+ }
+
try {
- const photo = await readRemotePhoto(entry);
+ const photo = await readRemotePhoto(entry, controller.signal);
if (photo.latitude !== null && photo.longitude !== null) {
appendPhoto(photo);
loaded += 1;
@@ -831,11 +901,20 @@ function htmlPage(): string {
);
setProgress(listing.length, listing.length, "fertig");
} catch (error) {
+ if (error instanceof DOMException && error.name === "AbortError") {
+ updateStatus("Import abgebrochen.");
+ setProgress(state.processed, state.total, "abgebrochen");
+ return;
+ }
+
console.error(error);
updateStatus(
"Import fehlgeschlagen: " + (error instanceof Error ? error.message : "Unbekannter Fehler"),
"error"
);
+ } finally {
+ activeImportController = null;
+ setImporting(false);
}
}
@@ -859,6 +938,12 @@ function htmlPage(): string {
void importFromNextcloud();
});
+ cancelShare.addEventListener("click", () => {
+ if (activeImportController) {
+ activeImportController.abort();
+ }
+ });
+
useDemo.addEventListener("click", showDemo);
showDemo();