adds upload dialog and duplicates

This commit is contained in:
Christian Beutel
2025-02-01 18:06:27 +01:00
parent ae3ef0e0a1
commit 0cf17cbd86
25 changed files with 499 additions and 147 deletions

View File

@@ -401,23 +401,40 @@ export async function trails_get_bounding_box(f: (url: RequestInfo | URL, config
}
export async function trails_upload(file: File, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch): Promise<TrailFilterValues> {
const fd = new FormData()
export async function trails_upload(file: File, ignoreDuplicates: boolean = false, onProgress?: (progress: number) => void) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const fd = new FormData();
fd.append("name", file.name);
fd.append("file", file);
fd.append("ignoreDuplicates", ignoreDuplicates ? "true" : "false")
fd.append("name", file.name),
fd.append("file", file)
xhr.open("PUT", "/api/v1/trail/upload", true);
const r = await f('/api/v1/trail/upload', {
method: 'PUT',
body: fd
})
if (!r.ok) {
const response = await r.json();
throw new APIError(r.status, response.message, response.detail)
}
xhr.upload.onprogress = function (event) {
if (event.lengthComputable) {
const percentComplete = (event.loaded / event.total) * 100;
onProgress?.(percentComplete)
}
};
return await r.json();
xhr.onload = async () => {
const responseText = xhr.responseText;
const response = responseText ? JSON.parse(responseText) : null;
if (xhr.status >= 200 && xhr.status < 300) {
resolve(response);
} else {
reject(new APIError(xhr.status, response?.message || "Upload failed", response));
}
};
xhr.onerror = () => {
reject(new APIError(xhr.status, xhr.statusText));
};
xhr.send(fd);
});
}
export async function fetchGPX(trail: { gpx?: string } & Record<string, any>, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {

View File

@@ -0,0 +1,57 @@
import { APIError } from "$lib/util/api_util";
export type Upload = {
file: File;
status: "enqueued" | "uploading" | "cancelled" | "success" | "error" | "duplicate";
error?: string;
duplicate?: { id: string, name: string };
progress: number;
function: (f: File, ignoreDuplicates?: boolean, onProgress?: (p: number) => void) => Promise<unknown>
};
class UploadStore {
enqueuedUploads: Upload[] = $state([]);
completedUploads: Upload[] = $state([]);
uploading: boolean = $state(false);
}
export const uploadStore = new UploadStore();
export async function processUploadQueue(batchSize: number = 3, ignoreDuplicates: boolean = false) {
if (uploadStore.uploading) {
return;
}
uploadStore.uploading = true;
while (uploadStore.enqueuedUploads.length > 0) {
const batch = uploadStore.enqueuedUploads.slice(0, batchSize);
const uploadPromises: Promise<unknown>[] = [];
for (const b of batch) {
b.status = "uploading";
uploadPromises.push(
b.function(b.file, ignoreDuplicates, (p: number) => {
b.progress = p
})
);
}
const results = await Promise.all(
uploadPromises.map((p) => p.catch((e) => e)),
);
results.forEach((r, i) => {
const u = batch[i];
if (r instanceof APIError && r.message == "Duplicate trail") {
u.status = "duplicate"
u.duplicate = { id: r.detail.id, name: r.detail.name };
} else if (r instanceof APIError) {
u.status = "error"
u.error = r.message
} else {
u.status = "success"
}
uploadStore.completedUploads.push(u);
});
uploadStore.enqueuedUploads.splice(0, batchSize)
}
uploadStore.uploading = false;
}