updated deck caching to client for faster respones
This commit is contained in:
@@ -106,12 +106,48 @@
|
|||||||
fit: "inside",
|
fit: "inside",
|
||||||
quality: 82
|
quality: 82
|
||||||
};
|
};
|
||||||
|
const standardMajorCardNames = [
|
||||||
|
"Fool",
|
||||||
|
"Magus",
|
||||||
|
"High Priestess",
|
||||||
|
"Empress",
|
||||||
|
"Emperor",
|
||||||
|
"Hierophant",
|
||||||
|
"Lovers",
|
||||||
|
"Chariot",
|
||||||
|
"Lust",
|
||||||
|
"Hermit",
|
||||||
|
"Fortune",
|
||||||
|
"Justice",
|
||||||
|
"Hanged Man",
|
||||||
|
"Death",
|
||||||
|
"Art",
|
||||||
|
"Devil",
|
||||||
|
"Tower",
|
||||||
|
"Star",
|
||||||
|
"Moon",
|
||||||
|
"Sun",
|
||||||
|
"Aeon",
|
||||||
|
"Universe"
|
||||||
|
];
|
||||||
|
const standardMinorRanks = ["Ace", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Knight", "Queen", "Prince", "Princess"];
|
||||||
|
const standardMinorSuits = ["Wands", "Cups", "Swords", "Disks"];
|
||||||
|
const standardDeckCardNames = buildStandardDeckCardNames();
|
||||||
|
|
||||||
let deckManifestSources = buildDeckManifestSources();
|
let deckManifestSources = buildDeckManifestSources();
|
||||||
|
|
||||||
const manifestCache = new Map();
|
const manifestCache = new Map();
|
||||||
const cardBackCache = new Map();
|
const cardBackCache = new Map();
|
||||||
const cardBackThumbnailCache = new Map();
|
const cardBackThumbnailCache = new Map();
|
||||||
|
const imagePreloadCache = new Map();
|
||||||
|
const deckImagePreloadCache = new Map();
|
||||||
|
const deckPreloadStatus = {
|
||||||
|
activeDeckId: DEFAULT_DECK_ID,
|
||||||
|
selectedDeckPhase: "idle",
|
||||||
|
backgroundPhase: "idle",
|
||||||
|
warmedDeckIds: []
|
||||||
|
};
|
||||||
|
let backgroundDeckWarmupPromise = null;
|
||||||
let activeDeckId = DEFAULT_DECK_ID;
|
let activeDeckId = DEFAULT_DECK_ID;
|
||||||
|
|
||||||
function getApiBaseUrl() {
|
function getApiBaseUrl() {
|
||||||
@@ -219,6 +255,82 @@
|
|||||||
return { resolvedDeckId, trumpNumber };
|
return { resolvedDeckId, trumpNumber };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildStandardDeckCardNames() {
|
||||||
|
const cardNames = [...standardMajorCardNames];
|
||||||
|
|
||||||
|
standardMinorSuits.forEach((suit) => {
|
||||||
|
standardMinorRanks.forEach((rank) => {
|
||||||
|
cardNames.push(`${rank} of ${suit}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return cardNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deferPreload(callback) {
|
||||||
|
if (typeof callback !== "function") {
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof window.requestIdleCallback === "function") {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
window.requestIdleCallback(() => {
|
||||||
|
Promise.resolve(callback()).then(resolve).catch(() => resolve([]));
|
||||||
|
}, { timeout: 1200 });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve().then(callback).catch(() => []);
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitDeckPreloadStatus() {
|
||||||
|
const snapshot = {
|
||||||
|
activeDeckId: deckPreloadStatus.activeDeckId,
|
||||||
|
selectedDeckPhase: deckPreloadStatus.selectedDeckPhase,
|
||||||
|
backgroundPhase: deckPreloadStatus.backgroundPhase,
|
||||||
|
warmedDeckIds: [...deckPreloadStatus.warmedDeckIds],
|
||||||
|
warmedDeckCount: deckPreloadStatus.warmedDeckIds.length,
|
||||||
|
totalDeckCount: Object.keys(getDeckManifestSources()).length
|
||||||
|
};
|
||||||
|
|
||||||
|
document.dispatchEvent(new CustomEvent("tarot:deck-cache-status", {
|
||||||
|
detail: snapshot
|
||||||
|
}));
|
||||||
|
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDeckPreloadStatus(partialStatus) {
|
||||||
|
if (!partialStatus || typeof partialStatus !== "object") {
|
||||||
|
return emitDeckPreloadStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(partialStatus, "activeDeckId")) {
|
||||||
|
deckPreloadStatus.activeDeckId = normalizeDeckId(partialStatus.activeDeckId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(partialStatus, "selectedDeckPhase")) {
|
||||||
|
deckPreloadStatus.selectedDeckPhase = String(partialStatus.selectedDeckPhase || "idle");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(partialStatus, "backgroundPhase")) {
|
||||||
|
deckPreloadStatus.backgroundPhase = String(partialStatus.backgroundPhase || "idle");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(partialStatus.warmedDeckIds)) {
|
||||||
|
deckPreloadStatus.warmedDeckIds = Array.from(new Set(partialStatus.warmedDeckIds.map((deckId) => normalizeDeckId(deckId))));
|
||||||
|
}
|
||||||
|
|
||||||
|
return emitDeckPreloadStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function markDeckAsWarmed(deckId) {
|
||||||
|
const normalizedDeckId = normalizeDeckId(deckId);
|
||||||
|
if (!deckPreloadStatus.warmedDeckIds.includes(normalizedDeckId)) {
|
||||||
|
deckPreloadStatus.warmedDeckIds = [...deckPreloadStatus.warmedDeckIds, normalizedDeckId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function parseMinorCard(cardName) {
|
function parseMinorCard(cardName) {
|
||||||
const match = String(cardName || "")
|
const match = String(cardName || "")
|
||||||
.trim()
|
.trim()
|
||||||
@@ -461,6 +573,15 @@
|
|||||||
manifestCache.clear();
|
manifestCache.clear();
|
||||||
cardBackCache.clear();
|
cardBackCache.clear();
|
||||||
cardBackThumbnailCache.clear();
|
cardBackThumbnailCache.clear();
|
||||||
|
imagePreloadCache.clear();
|
||||||
|
deckImagePreloadCache.clear();
|
||||||
|
backgroundDeckWarmupPromise = null;
|
||||||
|
setDeckPreloadStatus({
|
||||||
|
activeDeckId,
|
||||||
|
selectedDeckPhase: "idle",
|
||||||
|
backgroundPhase: "idle",
|
||||||
|
warmedDeckIds: []
|
||||||
|
});
|
||||||
setActiveDeck(activeDeckId);
|
setActiveDeck(activeDeckId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -824,6 +945,212 @@
|
|||||||
return thumbnailPath || null;
|
return thumbnailPath || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function preloadImageUrl(url) {
|
||||||
|
const normalizedUrl = String(url || "").trim();
|
||||||
|
if (!normalizedUrl) {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imagePreloadCache.has(normalizedUrl)) {
|
||||||
|
return imagePreloadCache.get(normalizedUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
const preloadPromise = new Promise((resolve) => {
|
||||||
|
const image = new Image();
|
||||||
|
let settled = false;
|
||||||
|
|
||||||
|
function finalize(result) {
|
||||||
|
if (settled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
settled = true;
|
||||||
|
image.onload = null;
|
||||||
|
image.onerror = null;
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
image.decoding = "async";
|
||||||
|
image.onload = () => finalize(true);
|
||||||
|
image.onerror = () => finalize(false);
|
||||||
|
image.src = normalizedUrl;
|
||||||
|
|
||||||
|
if (image.complete) {
|
||||||
|
finalize(Boolean(image.naturalWidth));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
imagePreloadCache.set(normalizedUrl, preloadPromise);
|
||||||
|
return preloadPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function preloadImageUrls(urls, concurrency = 6) {
|
||||||
|
const queue = Array.from(new Set(Array.isArray(urls) ? urls.map((entry) => String(entry || "").trim()).filter(Boolean) : []));
|
||||||
|
if (queue.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const limit = Math.max(1, Number.isInteger(concurrency) ? concurrency : 6);
|
||||||
|
const results = [];
|
||||||
|
let cursor = 0;
|
||||||
|
|
||||||
|
async function consumeQueue() {
|
||||||
|
while (cursor < queue.length) {
|
||||||
|
const currentIndex = cursor;
|
||||||
|
cursor += 1;
|
||||||
|
results[currentIndex] = await preloadImageUrl(queue[currentIndex]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(Array.from({ length: Math.min(limit, queue.length) }, () => consumeQueue()));
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildDeckImagePreloadUrls(deckId, options = {}) {
|
||||||
|
const normalizedDeckId = normalizeDeckId(deckId);
|
||||||
|
const includeFull = options.includeFull !== false;
|
||||||
|
const includeThumbnails = options.includeThumbnails === true;
|
||||||
|
const includeCardBack = options.includeCardBack !== false;
|
||||||
|
const urls = new Set();
|
||||||
|
|
||||||
|
if (includeFull) {
|
||||||
|
standardDeckCardNames.forEach((cardName) => {
|
||||||
|
const imagePath = resolveWithDeck(normalizedDeckId, cardName, "full");
|
||||||
|
if (imagePath) {
|
||||||
|
urls.add(imagePath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeThumbnails) {
|
||||||
|
standardDeckCardNames.forEach((cardName) => {
|
||||||
|
const thumbnailPath = resolveWithDeck(normalizedDeckId, cardName, "thumbnail");
|
||||||
|
if (thumbnailPath) {
|
||||||
|
urls.add(thumbnailPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeCardBack) {
|
||||||
|
const cardBackImage = resolveTarotCardBackImage(normalizedDeckId);
|
||||||
|
if (cardBackImage) {
|
||||||
|
urls.add(cardBackImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeThumbnails) {
|
||||||
|
const cardBackThumbnail = resolveTarotCardBackThumbnail(normalizedDeckId);
|
||||||
|
if (cardBackThumbnail) {
|
||||||
|
urls.add(cardBackThumbnail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(urls);
|
||||||
|
}
|
||||||
|
|
||||||
|
function preloadDeckImages(deckId, options = {}) {
|
||||||
|
const normalizedDeckId = normalizeDeckId(deckId);
|
||||||
|
const cacheKey = JSON.stringify({
|
||||||
|
deckId: normalizedDeckId,
|
||||||
|
includeFull: options.includeFull !== false,
|
||||||
|
includeThumbnails: options.includeThumbnails === true,
|
||||||
|
includeCardBack: options.includeCardBack !== false
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!options.force && deckImagePreloadCache.has(cacheKey)) {
|
||||||
|
return deckImagePreloadCache.get(cacheKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.background) {
|
||||||
|
setDeckPreloadStatus({
|
||||||
|
activeDeckId: normalizedDeckId,
|
||||||
|
selectedDeckPhase: "loading"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const preloadPromise = preloadImageUrls(buildDeckImagePreloadUrls(normalizedDeckId, options), options.concurrency)
|
||||||
|
.then((result) => {
|
||||||
|
markDeckAsWarmed(normalizedDeckId);
|
||||||
|
if (!options.background) {
|
||||||
|
setDeckPreloadStatus({
|
||||||
|
activeDeckId: normalizedDeckId,
|
||||||
|
selectedDeckPhase: "ready"
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
emitDeckPreloadStatus();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
if (!options.background) {
|
||||||
|
setDeckPreloadStatus({
|
||||||
|
activeDeckId: normalizedDeckId,
|
||||||
|
selectedDeckPhase: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
deckImagePreloadCache.set(cacheKey, preloadPromise);
|
||||||
|
return preloadPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function preloadAllDeckImages(options = {}) {
|
||||||
|
const deckIds = Object.keys(getDeckManifestSources());
|
||||||
|
const startDeckId = options.startDeckId ? normalizeDeckId(options.startDeckId) : "";
|
||||||
|
const orderedDeckIds = startDeckId && deckIds.includes(startDeckId)
|
||||||
|
? [startDeckId, ...deckIds.filter((deckId) => deckId !== startDeckId)]
|
||||||
|
: deckIds;
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
for (const deckId of orderedDeckIds) {
|
||||||
|
results.push(await preloadDeckImages(deckId, options));
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleDeckImagePreload(deckId, options = {}) {
|
||||||
|
return deferPreload(() => preloadDeckImages(deckId, options));
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleBackgroundDeckWarmup(deckId, options = {}) {
|
||||||
|
if (backgroundDeckWarmupPromise) {
|
||||||
|
return backgroundDeckWarmupPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedDeckId = normalizeDeckId(deckId);
|
||||||
|
const warmupOptions = {
|
||||||
|
...options,
|
||||||
|
startDeckId: normalizedDeckId,
|
||||||
|
background: true,
|
||||||
|
concurrency: Number.isInteger(options.concurrency) ? options.concurrency : 2
|
||||||
|
};
|
||||||
|
|
||||||
|
setDeckPreloadStatus({
|
||||||
|
activeDeckId: normalizedDeckId,
|
||||||
|
backgroundPhase: "loading"
|
||||||
|
});
|
||||||
|
|
||||||
|
backgroundDeckWarmupPromise = scheduleDeckImagePreload(normalizedDeckId, warmupOptions)
|
||||||
|
.then(() => deferPreload(() => preloadAllDeckImages(warmupOptions)))
|
||||||
|
.then((result) => {
|
||||||
|
setDeckPreloadStatus({
|
||||||
|
activeDeckId: normalizedDeckId,
|
||||||
|
backgroundPhase: "ready"
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setDeckPreloadStatus({
|
||||||
|
activeDeckId: normalizedDeckId,
|
||||||
|
backgroundPhase: "error"
|
||||||
|
});
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
|
return backgroundDeckWarmupPromise;
|
||||||
|
}
|
||||||
|
|
||||||
function resolveDisplayNameWithDeck(deckId, cardName, trumpNumber) {
|
function resolveDisplayNameWithDeck(deckId, cardName, trumpNumber) {
|
||||||
const manifest = getDeckManifest(deckId);
|
const manifest = getDeckManifest(deckId);
|
||||||
const fallbackName = String(cardName || "").trim();
|
const fallbackName = String(cardName || "").trim();
|
||||||
@@ -908,6 +1235,12 @@
|
|||||||
function setActiveDeck(deckId) {
|
function setActiveDeck(deckId) {
|
||||||
activeDeckId = normalizeDeckId(deckId);
|
activeDeckId = normalizeDeckId(deckId);
|
||||||
getDeckManifest(activeDeckId);
|
getDeckManifest(activeDeckId);
|
||||||
|
setDeckPreloadStatus({
|
||||||
|
activeDeckId,
|
||||||
|
selectedDeckPhase: "idle"
|
||||||
|
});
|
||||||
|
scheduleDeckImagePreload(activeDeckId);
|
||||||
|
scheduleBackgroundDeckWarmup(activeDeckId);
|
||||||
return activeDeckId;
|
return activeDeckId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -933,6 +1266,9 @@
|
|||||||
resolveTarotCardThumbnail,
|
resolveTarotCardThumbnail,
|
||||||
resolveTarotCardBackImage,
|
resolveTarotCardBackImage,
|
||||||
resolveTarotCardBackThumbnail,
|
resolveTarotCardBackThumbnail,
|
||||||
|
preloadDeckImages,
|
||||||
|
preloadAllDeckImages,
|
||||||
|
getDeckPreloadStatus: () => emitDeckPreloadStatus(),
|
||||||
getTarotCardDisplayName,
|
getTarotCardDisplayName,
|
||||||
getTarotCardSearchAliases,
|
getTarotCardSearchAliases,
|
||||||
setActiveDeck,
|
setActiveDeck,
|
||||||
|
|||||||
@@ -7049,6 +7049,9 @@
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
.settings-cache-status {
|
||||||
|
min-height: 1.4em;
|
||||||
|
}
|
||||||
.settings-actions {
|
.settings-actions {
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
timeFormatEl: document.getElementById("time-format"),
|
timeFormatEl: document.getElementById("time-format"),
|
||||||
birthDateEl: document.getElementById("birth-date"),
|
birthDateEl: document.getElementById("birth-date"),
|
||||||
tarotDeckEl: document.getElementById("tarot-deck"),
|
tarotDeckEl: document.getElementById("tarot-deck"),
|
||||||
|
tarotDeckCacheStatusEl: document.getElementById("tarot-deck-cache-status"),
|
||||||
stellariumBackgroundEl: document.getElementById("stellarium-background"),
|
stellariumBackgroundEl: document.getElementById("stellarium-background"),
|
||||||
stellariumBackgroundHintEl: document.getElementById("stellarium-background-hint"),
|
stellariumBackgroundHintEl: document.getElementById("stellarium-background-hint"),
|
||||||
apiBaseUrlEl: document.getElementById("api-base-url"),
|
apiBaseUrlEl: document.getElementById("api-base-url"),
|
||||||
@@ -122,6 +123,50 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatDeckCacheStatus(status) {
|
||||||
|
const activeDeckId = String(status?.activeDeckId || normalizeTarotDeck(getElements().tarotDeckEl?.value)).trim().toLowerCase();
|
||||||
|
const totalDeckCount = Number(status?.totalDeckCount) || 0;
|
||||||
|
const warmedDeckCount = Number(status?.warmedDeckCount) || 0;
|
||||||
|
const warmedAllDecks = totalDeckCount > 0 && warmedDeckCount >= totalDeckCount;
|
||||||
|
|
||||||
|
if (status?.selectedDeckPhase === "loading") {
|
||||||
|
return "Caching selected deck images to this browser...";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status?.selectedDeckPhase === "error") {
|
||||||
|
return "Selected deck cache warmup hit an error. Images will still load on demand.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status?.backgroundPhase === "loading") {
|
||||||
|
if (warmedDeckCount > 0 && totalDeckCount > 0) {
|
||||||
|
return `Selected deck cached. Warming remaining decks in background (${warmedDeckCount}/${totalDeckCount}).`;
|
||||||
|
}
|
||||||
|
return "Selected deck cached. Warming remaining decks in background...";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status?.backgroundPhase === "error") {
|
||||||
|
return "Selected deck cached. Background warmup for other decks stopped early.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status?.selectedDeckPhase === "ready" || warmedAllDecks) {
|
||||||
|
if (warmedAllDecks) {
|
||||||
|
return "Selected deck cached. All available decks are warmed in this browser.";
|
||||||
|
}
|
||||||
|
return `Selected deck cached and ready for fullscreen use (${activeDeckId}).`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Deck cache idle.";
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncDeckCacheStatus(status) {
|
||||||
|
const { tarotDeckCacheStatusEl } = getElements();
|
||||||
|
if (!tarotDeckCacheStatusEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tarotDeckCacheStatusEl.textContent = formatDeckCacheStatus(status);
|
||||||
|
}
|
||||||
|
|
||||||
function syncSky(geo, options) {
|
function syncSky(geo, options) {
|
||||||
if (typeof config.onSyncSkyBackground === "function") {
|
if (typeof config.onSyncSkyBackground === "function") {
|
||||||
config.onSyncSkyBackground(geo, options);
|
config.onSyncSkyBackground(geo, options);
|
||||||
@@ -373,6 +418,7 @@
|
|||||||
if (window.TarotCardImages?.setActiveDeck) {
|
if (window.TarotCardImages?.setActiveDeck) {
|
||||||
window.TarotCardImages.setActiveDeck(normalized.tarotDeck);
|
window.TarotCardImages.setActiveDeck(normalized.tarotDeck);
|
||||||
}
|
}
|
||||||
|
syncDeckCacheStatus(window.TarotCardImages?.getDeckPreloadStatus?.());
|
||||||
applyExternalSettings(normalized);
|
applyExternalSettings(normalized);
|
||||||
return normalized;
|
return normalized;
|
||||||
}
|
}
|
||||||
@@ -577,6 +623,11 @@
|
|||||||
document.addEventListener("connection:updated", () => {
|
document.addEventListener("connection:updated", () => {
|
||||||
syncConnectionInputs();
|
syncConnectionInputs();
|
||||||
syncTarotDeckInputOptions();
|
syncTarotDeckInputOptions();
|
||||||
|
syncDeckCacheStatus(window.TarotCardImages?.getDeckPreloadStatus?.());
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("tarot:deck-cache-status", (event) => {
|
||||||
|
syncDeckCacheStatus(event?.detail);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -125,6 +125,7 @@
|
|||||||
<select id="tarot-deck">
|
<select id="tarot-deck">
|
||||||
<option value="ceremonial-magick" selected>Loading deck manifests...</option>
|
<option value="ceremonial-magick" selected>Loading deck manifests...</option>
|
||||||
</select>
|
</select>
|
||||||
|
<small id="tarot-deck-cache-status" class="settings-field-hint settings-cache-status" aria-live="polite">Deck cache idle.</small>
|
||||||
</label>
|
</label>
|
||||||
<label class="settings-field settings-field-full settings-toggle-field" for="stellarium-background">
|
<label class="settings-field settings-field-full settings-toggle-field" for="stellarium-background">
|
||||||
<span>Stellarium Background</span>
|
<span>Stellarium Background</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user