Web: Thumbnails cachen (kein Neuladen) + feste Zeilenhoehe
Artikelbilder werden modulweit gecacht (Produkt-ID+Version -> Objekt-URL, auch 'kein Bild'); beim erneuten Oeffnen einer Liste werden sie nicht mehr jedes Mal neu geholt. Der Platzhalter (.thumb-fallback) hat jetzt dieselbe feste Groesse (34x34) wie das Bild, damit die Zeilenhoehe mit/ohne Bild gleich ist und beim Nachladen nicht mehr springt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -12,8 +12,15 @@ import Lightbox from "./Lightbox";
|
||||
* ``onLoaded`` meldet, ob ein Bild vorliegt – so kann die Artikelseite z.B. den
|
||||
* „Entfernen"-Knopf ausblenden, wenn es nichts zu entfernen gibt.
|
||||
*/
|
||||
// Modulweiter Cache: Produkt-ID+Version → Objekt-URL (oder null = „kein Bild").
|
||||
// So werden Thumbnails beim erneuten Öffnen einer Liste nicht jedes Mal neu
|
||||
// geholt. Die Objekt-URLs bleiben absichtlich bestehen (werden nicht widerrufen),
|
||||
// bis eine neue Version dieselbe ID ablöst.
|
||||
const bildCache = new Map();
|
||||
|
||||
function useBildUrl(productId, version = 0, onLoaded) {
|
||||
const [url, setUrl] = useState(null);
|
||||
const key = `${productId}:${version}`;
|
||||
const [url, setUrl] = useState(() => (bildCache.has(key) ? bildCache.get(key) : null));
|
||||
// Über einen Ref, damit ein bei jedem Render neu erzeugtes onLoaded den Effekt
|
||||
// nicht erneut auslöst.
|
||||
const onLoadedRef = useRef(onLoaded);
|
||||
@@ -21,8 +28,13 @@ function useBildUrl(productId, version = 0, onLoaded) {
|
||||
|
||||
useEffect(() => {
|
||||
if (!productId) return undefined;
|
||||
if (bildCache.has(key)) {
|
||||
const cached = bildCache.get(key);
|
||||
onLoadedRef.current?.(Boolean(cached));
|
||||
setUrl(cached);
|
||||
return undefined;
|
||||
}
|
||||
let abgebrochen = false;
|
||||
let erzeugt = null;
|
||||
|
||||
// ``version`` haengt einen Wert an die Adresse, damit der Browser nach dem
|
||||
// Speichern nicht seine zwischengespeicherte Fassung ausliefert.
|
||||
@@ -32,24 +44,26 @@ function useBildUrl(productId, version = 0, onLoaded) {
|
||||
if (neu) URL.revokeObjectURL(neu);
|
||||
return;
|
||||
}
|
||||
onLoadedRef.current?.(Boolean(neu));
|
||||
if (!neu) {
|
||||
setUrl(null);
|
||||
return;
|
||||
// Alte Version derselben ID freigeben, dann neu cachen.
|
||||
const alt = `${productId}:`;
|
||||
for (const k of bildCache.keys()) {
|
||||
if (k !== key && k.startsWith(alt)) {
|
||||
const u = bildCache.get(k);
|
||||
if (u) URL.revokeObjectURL(u);
|
||||
bildCache.delete(k);
|
||||
}
|
||||
}
|
||||
erzeugt = neu;
|
||||
setUrl(neu);
|
||||
bildCache.set(key, neu || null);
|
||||
onLoadedRef.current?.(Boolean(neu));
|
||||
setUrl(neu || null);
|
||||
})
|
||||
.catch(() => {
|
||||
if (!abgebrochen) onLoadedRef.current?.(false);
|
||||
});
|
||||
|
||||
return () => {
|
||||
abgebrochen = true;
|
||||
if (erzeugt) URL.revokeObjectURL(erzeugt);
|
||||
setUrl(null);
|
||||
};
|
||||
}, [productId, version]);
|
||||
// Objekt-URL NICHT widerrufen – sie bleibt im Cache für den nächsten Mount.
|
||||
return () => { abgebrochen = true; };
|
||||
}, [productId, version, key]);
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
@@ -400,7 +400,12 @@ td select { width: auto; min-width: 0; max-width: 100%; }
|
||||
width: 34px; height: 34px; object-fit: cover;
|
||||
border-radius: var(--radius-sm); border: 1px solid var(--border); background: var(--surface-2);
|
||||
}
|
||||
.thumb-fallback { display: grid; place-items: center; color: var(--muted); }
|
||||
/* Gleiche Größe wie das echte Bild, damit die Zeilenhöhe mit und ohne Bild
|
||||
identisch ist und beim Nachladen nicht springt. */
|
||||
.thumb-fallback {
|
||||
width: 34px; height: 34px; display: grid; place-items: center; color: var(--muted);
|
||||
border-radius: var(--radius-sm); border: 1px solid var(--border); background: var(--surface-2);
|
||||
}
|
||||
|
||||
/* ---------- Badges / pills ---------- */
|
||||
.badge {
|
||||
|
||||
Reference in New Issue
Block a user