diff --git a/src/workspaces/officerdev/src/apps/FileViewer/renderers/ImageRenderer.tsx b/src/workspaces/officerdev/src/apps/FileViewer/renderers/ImageRenderer.tsx index 88724e8e..f1dda488 100644 --- a/src/workspaces/officerdev/src/apps/FileViewer/renderers/ImageRenderer.tsx +++ b/src/workspaces/officerdev/src/apps/FileViewer/renderers/ImageRenderer.tsx @@ -16,8 +16,22 @@ export const ImageRenderer = ({ src, fileName }: ImageRendererProps) => { const containerRef = useRef(null); const [zoom, setZoom] = useState(1); const [rotation, setRotation] = useState(0); - const [loaded, setLoaded] = useState(false); - const [error, setError] = useState(false); + /* + WHICH src has loaded, not whether one has. + + The first fix reset a boolean in an effect on `src`, and that has a race a cached image wins every + time: the effect runs AFTER render, so the browser fires `onLoad` for an image already in cache + before the effect gets to clear the flag — and then the effect clears it, and `onLoad` never comes + again. The spinner stayed forever on exactly the files that were fastest. + + Comparing the loaded src to the current one has no ordering at all. It is false the instant `src` + changes, true the moment that src's own load fires, and there is no window in which the two can be + written in the wrong order. + */ + const [loadedSrc, setLoadedSrc] = useState(null); + const [errorSrc, setErrorSrc] = useState(null); + const loaded = loadedSrc === src; + const error = errorSrc === src; const [dragging, setDragging] = useState(false); const [offset, setOffset] = useState({ x: 0, y: 0 }); const dragStart = useRef({ x: 0, y: 0, ox: 0, oy: 0 }); @@ -90,9 +104,12 @@ export const ImageRenderer = ({ src, fileName }: ImageRendererProps) => { onto the next photograph means arriving somewhere nobody asked to be, in the corner of a picture they have not seen yet. */ + /* + Zoom, rotation and pan are a view of ONE image, so they reset when the image changes. These stay in + an effect because they have no race to lose: nothing else writes them during a load, and being one + render late is invisible. + */ useEffect(() => { - setLoaded(false); - setError(false); setZoom(1); setRotation(0); setOffset({ x: 0, y: 0 }); @@ -125,8 +142,8 @@ export const ImageRenderer = ({ src, fileName }: ImageRendererProps) => { {fileName} setLoaded(true)} - onError={() => setError(true)} + onLoad={() => setLoadedSrc(src)} + onError={() => setErrorSrc(src)} className="transition-transform duration-150 select-none" draggable={false} style={{