From 6418528da9fe2a833ddb79be3ececc90c336d42a Mon Sep 17 00:00:00 2001 From: neoapps-dev Date: Mon, 15 Jun 2026 22:52:53 +0300 Subject: [PATCH] feat(screenshots): queues and caching --- src/components/common/ScreenshotImage.tsx | 92 ++++++++++++++++++++--- 1 file changed, 80 insertions(+), 12 deletions(-) diff --git a/src/components/common/ScreenshotImage.tsx b/src/components/common/ScreenshotImage.tsx index 5034b78..087bcb4 100644 --- a/src/components/common/ScreenshotImage.tsx +++ b/src/components/common/ScreenshotImage.tsx @@ -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(); +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(fallbackSrc); const imgRef = useRef(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); };