Show "Converting..." once an upload's bytes are fully sent
fetch() has no cross-browser upload progress event, so the upload path now uses XMLHttpRequest instead -- its upload.progress firing with loaded === total is the only client-observable signal that the server has moved from receiving bytes to converting them, worth surfacing now that a large POI export's conversion (GpxStore#save) can itself take several seconds on top of the transfer. Multi-file uploads now also show which file (i of N) is in flight, previously just a static "Uploading N files..." for the whole batch. Verified the 401 path still redirects to /login through the new XHR flow the same way fetch/redirectIfUnauthorized did.
This commit is contained in:
1 parent
37bc14f3ba
commit
45567fc520
1 file changed
+34
-6
+34
-6
@@ -720,19 +720,29 @@ export class GpxPanelControl {
|
||||
const files = [...fileList].filter((file) => /\.(gpx|fit|geojson|zip)$/i.test(file.name));
|
||||
if (files.length === 0) return;
|
||||
|
||||
this._setStatus(files.length === 1 ? "Uploading…" : `Uploading ${files.length} files…`);
|
||||
let tree = null;
|
||||
let failed = 0;
|
||||
|
||||
for (const file of files) {
|
||||
for (const [index, file] of files.entries()) {
|
||||
this._setStatus(files.length === 1 ? "Uploading…" : `Uploading ${index + 1} of ${files.length}…`);
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
formData.append("folder", folder);
|
||||
try {
|
||||
const response = await fetch(UPLOAD_URL, { method: "POST", body: formData });
|
||||
if (redirectIfUnauthorized(response)) return;
|
||||
if (!response.ok) throw new Error("upload failed");
|
||||
({ tree } = await response.json());
|
||||
// fetch() has no cross-browser upload progress event, so this one
|
||||
// upload uses XMLHttpRequest instead — its upload.progress firing
|
||||
// with loaded === total is the only client-observable signal that
|
||||
// the server has (or is about to start) converting rather than
|
||||
// still receiving bytes, worth surfacing now that a several-
|
||||
// thousand-waypoint POI export's conversion can itself take
|
||||
// several more seconds on top of the transfer (GpxStore#save).
|
||||
const xhr = await uploadFile(formData, () => this._setStatus("Converting…"));
|
||||
if (xhr.status === 401) {
|
||||
window.location.href = "/login";
|
||||
return;
|
||||
}
|
||||
if (xhr.status < 200 || xhr.status >= 300) throw new Error("upload failed");
|
||||
({ tree } = JSON.parse(xhr.responseText));
|
||||
} catch (err) {
|
||||
failed += 1;
|
||||
}
|
||||
@@ -814,6 +824,24 @@ function redirectIfUnauthorized(response) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// XMLHttpRequest rather than fetch() — only so onUploadComplete (the
|
||||
// upload.progress event) is available at all; every other write action in
|
||||
// this file stays on fetch()/redirectIfUnauthorized. Resolves with the xhr
|
||||
// itself (not just responseText) so the caller can still read .status the
|
||||
// same way a fetch Response would let it.
|
||||
function uploadFile(formData, onUploadComplete) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", UPLOAD_URL);
|
||||
xhr.upload.addEventListener("progress", (event) => {
|
||||
if (event.lengthComputable && event.loaded === event.total) onUploadComplete();
|
||||
});
|
||||
xhr.addEventListener("load", () => resolve(xhr));
|
||||
xhr.addEventListener("error", () => reject(new Error("network error")));
|
||||
xhr.send(formData);
|
||||
});
|
||||
}
|
||||
|
||||
// Rasterizes a hand-authored SVG icon to ImageData for map.addImage(), at a
|
||||
// fixed square size (the source SVGs have no explicit width/height, just a
|
||||
// viewBox, so one is injected — otherwise an <img> falls back to the CSS
|
||||
|
||||
Reference in new issue
Block a user