feat(screenshots): queues and caching

This commit is contained in:
neoapps-dev
2026-06-15 22:52:53 +03:00
parent 6f64b43358
commit 6418528da9

View File

@@ -1,6 +1,5 @@
import { useState, useEffect, useRef } from "react";
import { TauriService } from "../../services/TauriService";
interface ScreenshotImageProps {
path: string;
className?: string;
@@ -10,6 +9,50 @@ interface ScreenshotImageProps {
fallbackSrc?: string;
}
const imgCache = new Map<string, string>();
let activeLoads = 0;
const MAX_CONCURRENT = 4;
const loadQueue: Array<() => void> = [];
function dequeue() {
while (activeLoads < MAX_CONCURRENT && loadQueue.length > 0) {
const next = loadQueue.shift()!;
activeLoads++;
next();
}
}
function enqueueLoad(
path: string,
onLoad: (url: string) => void,
onError: () => void,
) {
const cached = imgCache.get(path);
if (cached) {
onLoad(cached);
return;
}
const run = () => {
TauriService.readScreenshotAsDataUrl(path)
.then((url) => {
imgCache.set(path, url);
onLoad(url);
})
.catch(() => onError())
.finally(() => {
activeLoads--;
dequeue();
});
};
if (activeLoads < MAX_CONCURRENT) {
activeLoads++;
run();
} else {
loadQueue.push(run);
}
}
export function ScreenshotImage({
path,
className,
@@ -20,22 +63,47 @@ export function ScreenshotImage({
}: ScreenshotImageProps) {
const [src, setSrc] = useState<string | undefined>(fallbackSrc);
const imgRef = useRef<HTMLImageElement>(null);
const loadedRef = useRef(false);
useEffect(() => {
const el = imgRef.current?.parentElement || imgRef.current;
if (!el) return;
let cancelled = false;
setSrc(fallbackSrc);
TauriService.readScreenshotAsDataUrl(path)
.then((url) => {
if (!cancelled) setSrc(url);
})
.catch(() => {
if (!cancelled && fallbackSrc) setSrc(fallbackSrc);
});
const doLoad = () => {
if (loadedRef.current) return;
loadedRef.current = true;
enqueueLoad(
path,
(url) => {
if (!cancelled) setSrc(url);
},
() => {
if (!cancelled && fallbackSrc) setSrc(fallbackSrc);
},
);
};
if (loading === "eager") {
doLoad();
return () => {
cancelled = true;
};
}
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
observer.disconnect();
doLoad();
}
},
{ rootMargin: "800px" },
);
observer.observe(el);
return () => {
cancelled = true;
observer.disconnect();
};
}, [path, fallbackSrc]);
}, [path, fallbackSrc, loading]);
const handleError = () => {
if (fallbackSrc) setSrc(fallbackSrc);
};