perf(import): throttle photo rendering

This commit is contained in:
2026-06-07 21:04:34 +02:00
parent d98ec5f23a
commit b30a711f7e

View File

@@ -993,6 +993,11 @@ function htmlPage(): string {
} }
}; };
let activeImportController = null; let activeImportController = null;
const IMPORT_RENDER_INTERVAL_MS = 180;
let scheduledRenderFrame = null;
let scheduledRenderTimer = null;
let scheduledRenderOptions = { fitMap: false };
let lastRenderAt = 0;
const map = L.map("map", { zoomControl: true }).setView([52.5208, 13.4095], 13); const map = L.map("map", { zoomControl: true }).setView([52.5208, 13.4095], 13);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
@@ -1376,6 +1381,7 @@ function htmlPage(): string {
} }
function clearGallery() { function clearGallery() {
cancelScheduledRender();
state.photos = []; state.photos = [];
state.visiblePhotos = []; state.visiblePhotos = [];
state.activePhotoId = null; state.activePhotoId = null;
@@ -1667,6 +1673,60 @@ function htmlPage(): string {
} }
} }
function runScheduledRender() {
scheduledRenderFrame = null;
const options = scheduledRenderOptions;
scheduledRenderOptions = { fitMap: false };
lastRenderAt = performance.now();
renderVisiblePhotos(options);
}
function scheduleVisiblePhotoRender(options = {}) {
scheduledRenderOptions = {
fitMap: Boolean(scheduledRenderOptions.fitMap || options.fitMap)
};
if (scheduledRenderFrame !== null || scheduledRenderTimer !== null) {
return;
}
const elapsed = performance.now() - lastRenderAt;
const delay = Math.max(0, IMPORT_RENDER_INTERVAL_MS - elapsed);
const queueFrame = () => {
scheduledRenderTimer = null;
scheduledRenderFrame = requestAnimationFrame(runScheduledRender);
};
if (delay === 0) {
queueFrame();
} else {
scheduledRenderTimer = setTimeout(queueFrame, delay);
}
}
function cancelScheduledRender() {
if (scheduledRenderFrame !== null) {
cancelAnimationFrame(scheduledRenderFrame);
scheduledRenderFrame = null;
}
if (scheduledRenderTimer !== null) {
clearTimeout(scheduledRenderTimer);
scheduledRenderTimer = null;
}
scheduledRenderOptions = { fitMap: false };
}
function flushVisiblePhotoRender(options = {}) {
const renderOptions = {
fitMap: Boolean(scheduledRenderOptions.fitMap || options.fitMap)
};
cancelScheduledRender();
lastRenderAt = performance.now();
renderVisiblePhotos(renderOptions);
}
let timelinePointerState = null; let timelinePointerState = null;
let timelineClickSuppressed = false; let timelineClickSuppressed = false;
@@ -1792,7 +1852,7 @@ function htmlPage(): string {
function appendPhoto(photo) { function appendPhoto(photo) {
state.photos.push(photo); state.photos.push(photo);
renderVisiblePhotos({ fitMap: state.photos.length === 1 }); scheduleVisiblePhotoRender({ fitMap: state.photos.length === 1 });
} }
function textContent(node, namespace, localName) { function textContent(node, namespace, localName) {
@@ -1938,8 +1998,10 @@ function htmlPage(): string {
"Import complete: " + loaded + " images imported" + (skipped ? ", " + skipped + " skipped" : "") + "." "Import complete: " + loaded + " images imported" + (skipped ? ", " + skipped + " skipped" : "") + "."
); );
setProgress(listing.length, listing.length, "complete"); setProgress(listing.length, listing.length, "complete");
flushVisiblePhotoRender({ fitMap: true });
} catch (error) { } catch (error) {
if (controller.signal.aborted) { if (controller.signal.aborted) {
flushVisiblePhotoRender();
setImportStatus("Import stopped."); setImportStatus("Import stopped.");
setProgress(state.processed, state.total, "canceled"); setProgress(state.processed, state.total, "canceled");
return; return;
@@ -1950,6 +2012,7 @@ function htmlPage(): string {
"Import failed: " + (error instanceof Error ? error.message : "unknown error"), "Import failed: " + (error instanceof Error ? error.message : "unknown error"),
"error" "error"
); );
flushVisiblePhotoRender();
} finally { } finally {
activeImportController = null; activeImportController = null;
setImporting(false); setImporting(false);