updated files with new features and improvements: caching proper

This commit is contained in:
2026-04-22 00:16:20 -07:00
parent b3a11cf735
commit 1b7d752e4e
3 changed files with 106 additions and 14 deletions
+34 -4
View File
@@ -140,6 +140,7 @@
const cardBackCache = new Map(); const cardBackCache = new Map();
const cardBackThumbnailCache = new Map(); const cardBackThumbnailCache = new Map();
const imagePreloadCache = new Map(); const imagePreloadCache = new Map();
const loadedImageCache = new Map();
const deckImagePreloadCache = new Map(); const deckImagePreloadCache = new Map();
const deckPreloadStatus = { const deckPreloadStatus = {
activeDeckId: DEFAULT_DECK_ID, activeDeckId: DEFAULT_DECK_ID,
@@ -574,6 +575,7 @@
cardBackCache.clear(); cardBackCache.clear();
cardBackThumbnailCache.clear(); cardBackThumbnailCache.clear();
imagePreloadCache.clear(); imagePreloadCache.clear();
loadedImageCache.clear();
deckImagePreloadCache.clear(); deckImagePreloadCache.clear();
backgroundDeckWarmupPromise = null; backgroundDeckWarmupPromise = null;
setDeckPreloadStatus({ setDeckPreloadStatus({
@@ -948,7 +950,11 @@
function preloadImageUrl(url) { function preloadImageUrl(url) {
const normalizedUrl = String(url || "").trim(); const normalizedUrl = String(url || "").trim();
if (!normalizedUrl) { if (!normalizedUrl) {
return Promise.resolve(false); return Promise.resolve(null);
}
if (loadedImageCache.has(normalizedUrl)) {
return Promise.resolve(loadedImageCache.get(normalizedUrl));
} }
if (imagePreloadCache.has(normalizedUrl)) { if (imagePreloadCache.has(normalizedUrl)) {
@@ -971,12 +977,20 @@
} }
image.decoding = "async"; image.decoding = "async";
image.onload = () => finalize(true); image.onload = () => {
image.onerror = () => finalize(false); loadedImageCache.set(normalizedUrl, image);
finalize(image);
};
image.onerror = () => finalize(null);
image.src = normalizedUrl; image.src = normalizedUrl;
if (image.complete) { if (image.complete) {
finalize(Boolean(image.naturalWidth)); if (image.naturalWidth) {
loadedImageCache.set(normalizedUrl, image);
finalize(image);
} else {
finalize(null);
}
} }
}); });
@@ -984,6 +998,20 @@
return preloadPromise; return preloadPromise;
} }
function ensureImageLoaded(url) {
return preloadImageUrl(url);
}
function isImageLoaded(url) {
const normalizedUrl = String(url || "").trim();
if (!normalizedUrl) {
return false;
}
const cachedImage = loadedImageCache.get(normalizedUrl);
return Boolean(cachedImage?.complete && cachedImage?.naturalWidth);
}
async function preloadImageUrls(urls, concurrency = 6) { async function preloadImageUrls(urls, concurrency = 6) {
const queue = Array.from(new Set(Array.isArray(urls) ? urls.map((entry) => String(entry || "").trim()).filter(Boolean) : [])); const queue = Array.from(new Set(Array.isArray(urls) ? urls.map((entry) => String(entry || "").trim()).filter(Boolean) : []));
if (queue.length === 0) { if (queue.length === 0) {
@@ -1268,6 +1296,8 @@
resolveTarotCardBackThumbnail, resolveTarotCardBackThumbnail,
preloadDeckImages, preloadDeckImages,
preloadAllDeckImages, preloadAllDeckImages,
ensureImageLoaded,
isImageLoaded,
getDeckPreloadStatus: () => emitDeckPreloadStatus(), getDeckPreloadStatus: () => emitDeckPreloadStatus(),
getTarotCardDisplayName, getTarotCardDisplayName,
getTarotCardSearchAliases, getTarotCardSearchAliases,
+68 -10
View File
@@ -54,6 +54,8 @@
let activePinchGesture = null; let activePinchGesture = null;
let suppressNextCardClick = false; let suppressNextCardClick = false;
let suppressDeckCompareToggleUntil = 0; let suppressDeckCompareToggleUntil = 0;
let primaryImageRequestToken = 0;
let overlayImageRequestToken = 0;
const LIGHTBOX_ZOOM_SCALE = 6.66; const LIGHTBOX_ZOOM_SCALE = 6.66;
const LIGHTBOX_ZOOM_STEP = 0.1; const LIGHTBOX_ZOOM_STEP = 0.1;
@@ -837,6 +839,7 @@
const label = String(normalized.label || normalized.altText || "Tarot card enlarged image").trim() || "Tarot card enlarged image"; const label = String(normalized.label || normalized.altText || "Tarot card enlarged image").trim() || "Tarot card enlarged image";
return { return {
src: String(normalized.src || "").trim(), src: String(normalized.src || "").trim(),
previewSrc: String(normalized.previewSrc || "").trim(),
altText: String(normalized.altText || label).trim() || label, altText: String(normalized.altText || label).trim() || label,
label, label,
cardId: String(normalized.cardId || "").trim(), cardId: String(normalized.cardId || "").trim(),
@@ -847,6 +850,66 @@
}; };
} }
function getImageRequestToken(layer = "primary") {
if (layer === "overlay") {
overlayImageRequestToken += 1;
return overlayImageRequestToken;
}
primaryImageRequestToken += 1;
return primaryImageRequestToken;
}
function getCurrentImageRequestToken(layer = "primary") {
return layer === "overlay" ? overlayImageRequestToken : primaryImageRequestToken;
}
function applyCardImageToElement(targetImageEl, cardRequest, layer = "primary") {
if (!(targetImageEl instanceof HTMLImageElement)) {
return;
}
const normalizedCard = normalizeCardRequest(cardRequest);
const fullSrc = String(normalizedCard.src || "").trim();
const previewSrc = String(normalizedCard.previewSrc || "").trim();
const normalizedPreviewSrc = previewSrc && previewSrc !== fullSrc ? previewSrc : "";
const imageCache = window.TarotCardImages || {};
const fullImageLoaded = typeof imageCache.isImageLoaded === "function"
? imageCache.isImageLoaded(fullSrc)
: false;
const requestToken = getImageRequestToken(layer);
const initialSrc = fullSrc && (!normalizedPreviewSrc || fullImageLoaded)
? fullSrc
: (normalizedPreviewSrc || fullSrc);
if (initialSrc) {
targetImageEl.src = initialSrc;
targetImageEl.alt = normalizedCard.altText;
} else {
targetImageEl.removeAttribute("src");
targetImageEl.alt = normalizedCard.altText;
return;
}
if (!fullSrc || initialSrc === fullSrc || typeof imageCache.ensureImageLoaded !== "function") {
return;
}
imageCache.ensureImageLoaded(fullSrc)
.then((loadedImage) => {
if (!loadedImage || getCurrentImageRequestToken(layer) !== requestToken || !lightboxState.isOpen) {
return;
}
if (targetImageEl.src !== fullSrc) {
targetImageEl.src = fullSrc;
targetImageEl.alt = normalizedCard.altText;
}
})
.catch(() => {
});
}
function normalizeDeckOptions(deckOptions) { function normalizeDeckOptions(deckOptions) {
if (!Array.isArray(deckOptions)) { if (!Array.isArray(deckOptions)) {
return []; return [];
@@ -3198,8 +3261,7 @@
if (isCompactLightboxLayout()) { if (isCompactLightboxLayout()) {
lightboxState.mobileInfoView = "overlay"; lightboxState.mobileInfoView = "overlay";
} }
overlayImageEl.src = normalizedCard.src; applyCardImageToElement(overlayImageEl, normalizedCard, "overlay");
overlayImageEl.alt = normalizedCard.altText;
overlayImageEl.style.display = "block"; overlayImageEl.style.display = "block";
overlayImageEl.style.opacity = String(lightboxState.overlayOpacity); overlayImageEl.style.opacity = String(lightboxState.overlayOpacity);
if (syncSelection && typeof lightboxState.onSelectCardId === "function") { if (syncSelection && typeof lightboxState.onSelectCardId === "function") {
@@ -3254,8 +3316,7 @@
} }
lightboxState.primaryCard = nextCard; lightboxState.primaryCard = nextCard;
imageEl.src = nextCard.src; applyCardImageToElement(imageEl, nextCard, "primary");
imageEl.alt = nextCard.altText;
resetZoom(); resetZoom();
if (lightboxState.deckCompareMode) { if (lightboxState.deckCompareMode) {
syncDeckCompareCards(); syncDeckCompareCards();
@@ -3279,10 +3340,8 @@
lightboxState.primaryCard = nextPrimaryCard; lightboxState.primaryCard = nextPrimaryCard;
lightboxState.secondaryCard = nextSecondaryCard; lightboxState.secondaryCard = nextSecondaryCard;
imageEl.src = nextPrimaryCard.src; applyCardImageToElement(imageEl, nextPrimaryCard, "primary");
imageEl.alt = nextPrimaryCard.altText; applyCardImageToElement(overlayImageEl, nextSecondaryCard, "overlay");
overlayImageEl.src = nextSecondaryCard.src;
overlayImageEl.alt = nextSecondaryCard.altText;
overlayImageEl.style.display = "block"; overlayImageEl.style.display = "block";
overlayImageEl.style.opacity = String(lightboxState.overlayOpacity); overlayImageEl.style.opacity = String(lightboxState.overlayOpacity);
@@ -3723,8 +3782,7 @@
setInfoPanelOpen(getPersistedInfoPanelVisibility(), { persist: false }); setInfoPanelOpen(getPersistedInfoPanelVisibility(), { persist: false });
lightboxState.mobileInfoView = "primary"; lightboxState.mobileInfoView = "primary";
imageEl.src = normalizedPrimary.src; applyCardImageToElement(imageEl, normalizedPrimary, "primary");
imageEl.alt = normalizedPrimary.altText;
clearSecondaryCard(); clearSecondaryCard();
resetZoom(); resetZoom();
previousFocusedEl = document.activeElement instanceof HTMLElement ? document.activeElement : null; previousFocusedEl = document.activeElement instanceof HTMLElement ? document.activeElement : null;
+4
View File
@@ -770,6 +770,9 @@
const src = typeof resolveTarotCardImage === "function" const src = typeof resolveTarotCardImage === "function"
? resolveTarotCardImage(card.name, deckOptions) ? resolveTarotCardImage(card.name, deckOptions)
: ""; : "";
const previewSrc = typeof resolveTarotCardThumbnail === "function"
? (resolveTarotCardThumbnail(card.name, deckOptions) || src)
: src;
const deckMeta = resolvedDeckId ? getRegisteredDeckOptionMap().get(resolvedDeckId) : null; const deckMeta = resolvedDeckId ? getRegisteredDeckOptionMap().get(resolvedDeckId) : null;
const label = (typeof getTarotCardDisplayName === "function" const label = (typeof getTarotCardDisplayName === "function"
? getTarotCardDisplayName(card.name, deckOptions) ? getTarotCardDisplayName(card.name, deckOptions)
@@ -777,6 +780,7 @@
return { return {
src, src,
previewSrc,
altText: label, altText: label,
label, label,
cardId: card.id, cardId: card.id,