fix: update image info panel when navigating between files

Info panel always read files[0] and stored results in a single useState,
so it never changed when switching images. Now tracks selectedIndex,
auto-fetches on navigation after initial "Read Info" click, caches
results per-index, and aborts stale requests on rapid navigation.
This commit is contained in:
SnapOtter
2026-05-12 18:40:33 +08:00
parent 706f78b309
commit d3d50c92be
@@ -1,5 +1,5 @@
import { Loader2 } from "lucide-react"; import { Loader2 } from "lucide-react";
import { useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { formatHeaders } from "@/lib/api"; import { formatHeaders } from "@/lib/api";
import { useFileStore } from "@/stores/file-store"; import { useFileStore } from "@/stores/file-store";
@@ -32,23 +32,39 @@ interface ImageInfoData {
export function InfoSettings() { export function InfoSettings() {
const { files, processing, error, setProcessing, setError } = useFileStore(); const { files, processing, error, setProcessing, setError } = useFileStore();
const selectedIndex = useFileStore((s) => s.selectedIndex);
const [info, setInfo] = useState<ImageInfoData | null>(null); const [info, setInfo] = useState<ImageInfoData | null>(null);
const cacheRef = useRef<Map<number, ImageInfoData>>(new Map());
const autoFetchRef = useRef(false);
const abortRef = useRef<AbortController | null>(null);
const handleProcess = async () => { const fetchInfo = useCallback(
if (files.length === 0) return; async (index: number) => {
const file = useFileStore.getState().entries[index]?.file;
if (!file) return;
const cached = cacheRef.current.get(index);
if (cached) {
setInfo(cached);
return;
}
abortRef.current?.abort();
const controller = new AbortController();
abortRef.current = controller;
setProcessing(true); setProcessing(true);
setError(null); setError(null);
setInfo(null);
try { try {
const formData = new FormData(); const formData = new FormData();
formData.append("file", files[0]); formData.append("file", file);
const res = await fetch("/api/v1/tools/info", { const res = await fetch("/api/v1/tools/info", {
method: "POST", method: "POST",
headers: formatHeaders(), headers: formatHeaders(),
body: formData, body: formData,
signal: controller.signal,
}); });
if (!res.ok) { if (!res.ok) {
@@ -57,12 +73,42 @@ export function InfoSettings() {
} }
const data: ImageInfoData = await res.json(); const data: ImageInfoData = await res.json();
cacheRef.current.set(index, data);
setInfo(data); setInfo(data);
} catch (err) { } catch (err) {
if (err instanceof DOMException && err.name === "AbortError") return;
setError(err instanceof Error ? err.message : "Failed to read info"); setError(err instanceof Error ? err.message : "Failed to read info");
} finally { } finally {
if (!controller.signal.aborted) {
setProcessing(false); setProcessing(false);
} }
}
},
[setProcessing, setError],
);
useEffect(() => {
cacheRef.current.clear();
autoFetchRef.current = false;
setInfo(null);
}, [files.length]);
useEffect(() => {
if (!autoFetchRef.current || files.length === 0) return;
fetchInfo(selectedIndex);
}, [selectedIndex, fetchInfo, files.length]);
useEffect(() => {
return () => {
abortRef.current?.abort();
};
}, []);
const handleProcess = () => {
if (files.length === 0) return;
autoFetchRef.current = true;
setInfo(null);
fetchInfo(selectedIndex);
}; };
const hasFile = files.length > 0; const hasFile = files.length > 0;