added deck progress
This commit is contained in:
+86
-55
@@ -145,10 +145,11 @@
|
|||||||
const deckPreloadStatus = {
|
const deckPreloadStatus = {
|
||||||
activeDeckId: DEFAULT_DECK_ID,
|
activeDeckId: DEFAULT_DECK_ID,
|
||||||
selectedDeckPhase: "idle",
|
selectedDeckPhase: "idle",
|
||||||
backgroundPhase: "idle",
|
selectedDeckLoadedCount: 0,
|
||||||
|
selectedDeckTotalCount: 0,
|
||||||
|
selectedDeckPercent: 0,
|
||||||
warmedDeckIds: []
|
warmedDeckIds: []
|
||||||
};
|
};
|
||||||
let backgroundDeckWarmupPromise = null;
|
|
||||||
let activeDeckId = DEFAULT_DECK_ID;
|
let activeDeckId = DEFAULT_DECK_ID;
|
||||||
|
|
||||||
function getApiBaseUrl() {
|
function getApiBaseUrl() {
|
||||||
@@ -288,7 +289,9 @@
|
|||||||
const snapshot = {
|
const snapshot = {
|
||||||
activeDeckId: deckPreloadStatus.activeDeckId,
|
activeDeckId: deckPreloadStatus.activeDeckId,
|
||||||
selectedDeckPhase: deckPreloadStatus.selectedDeckPhase,
|
selectedDeckPhase: deckPreloadStatus.selectedDeckPhase,
|
||||||
backgroundPhase: deckPreloadStatus.backgroundPhase,
|
selectedDeckLoadedCount: deckPreloadStatus.selectedDeckLoadedCount,
|
||||||
|
selectedDeckTotalCount: deckPreloadStatus.selectedDeckTotalCount,
|
||||||
|
selectedDeckPercent: deckPreloadStatus.selectedDeckPercent,
|
||||||
warmedDeckIds: [...deckPreloadStatus.warmedDeckIds],
|
warmedDeckIds: [...deckPreloadStatus.warmedDeckIds],
|
||||||
warmedDeckCount: deckPreloadStatus.warmedDeckIds.length,
|
warmedDeckCount: deckPreloadStatus.warmedDeckIds.length,
|
||||||
totalDeckCount: Object.keys(getDeckManifestSources()).length
|
totalDeckCount: Object.keys(getDeckManifestSources()).length
|
||||||
@@ -314,8 +317,25 @@
|
|||||||
deckPreloadStatus.selectedDeckPhase = String(partialStatus.selectedDeckPhase || "idle");
|
deckPreloadStatus.selectedDeckPhase = String(partialStatus.selectedDeckPhase || "idle");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.prototype.hasOwnProperty.call(partialStatus, "backgroundPhase")) {
|
if (Object.prototype.hasOwnProperty.call(partialStatus, "selectedDeckLoadedCount")) {
|
||||||
deckPreloadStatus.backgroundPhase = String(partialStatus.backgroundPhase || "idle");
|
const nextLoadedCount = Number(partialStatus.selectedDeckLoadedCount);
|
||||||
|
deckPreloadStatus.selectedDeckLoadedCount = Number.isFinite(nextLoadedCount) && nextLoadedCount >= 0
|
||||||
|
? nextLoadedCount
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(partialStatus, "selectedDeckTotalCount")) {
|
||||||
|
const nextTotalCount = Number(partialStatus.selectedDeckTotalCount);
|
||||||
|
deckPreloadStatus.selectedDeckTotalCount = Number.isFinite(nextTotalCount) && nextTotalCount >= 0
|
||||||
|
? nextTotalCount
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(partialStatus, "selectedDeckPercent")) {
|
||||||
|
const nextPercent = Number(partialStatus.selectedDeckPercent);
|
||||||
|
deckPreloadStatus.selectedDeckPercent = Number.isFinite(nextPercent)
|
||||||
|
? Math.max(0, Math.min(100, nextPercent))
|
||||||
|
: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(partialStatus.warmedDeckIds)) {
|
if (Array.isArray(partialStatus.warmedDeckIds)) {
|
||||||
@@ -577,11 +597,12 @@
|
|||||||
imagePreloadCache.clear();
|
imagePreloadCache.clear();
|
||||||
loadedImageCache.clear();
|
loadedImageCache.clear();
|
||||||
deckImagePreloadCache.clear();
|
deckImagePreloadCache.clear();
|
||||||
backgroundDeckWarmupPromise = null;
|
|
||||||
setDeckPreloadStatus({
|
setDeckPreloadStatus({
|
||||||
activeDeckId,
|
activeDeckId,
|
||||||
selectedDeckPhase: "idle",
|
selectedDeckPhase: "idle",
|
||||||
backgroundPhase: "idle",
|
selectedDeckLoadedCount: 0,
|
||||||
|
selectedDeckTotalCount: 0,
|
||||||
|
selectedDeckPercent: 0,
|
||||||
warmedDeckIds: []
|
warmedDeckIds: []
|
||||||
});
|
});
|
||||||
setActiveDeck(activeDeckId);
|
setActiveDeck(activeDeckId);
|
||||||
@@ -1012,7 +1033,7 @@
|
|||||||
return Boolean(cachedImage?.complete && cachedImage?.naturalWidth);
|
return Boolean(cachedImage?.complete && cachedImage?.naturalWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function preloadImageUrls(urls, concurrency = 6) {
|
async function preloadImageUrls(urls, concurrency = 6, onProgress = null) {
|
||||||
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) {
|
||||||
return [];
|
return [];
|
||||||
@@ -1021,12 +1042,29 @@
|
|||||||
const limit = Math.max(1, Number.isInteger(concurrency) ? concurrency : 6);
|
const limit = Math.max(1, Number.isInteger(concurrency) ? concurrency : 6);
|
||||||
const results = [];
|
const results = [];
|
||||||
let cursor = 0;
|
let cursor = 0;
|
||||||
|
let completedCount = 0;
|
||||||
|
|
||||||
|
function reportProgress(lastUrl) {
|
||||||
|
completedCount += 1;
|
||||||
|
if (typeof onProgress === "function") {
|
||||||
|
onProgress({
|
||||||
|
completedCount,
|
||||||
|
totalCount: queue.length,
|
||||||
|
lastUrl: String(lastUrl || "").trim()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function consumeQueue() {
|
async function consumeQueue() {
|
||||||
while (cursor < queue.length) {
|
while (cursor < queue.length) {
|
||||||
const currentIndex = cursor;
|
const currentIndex = cursor;
|
||||||
cursor += 1;
|
cursor += 1;
|
||||||
results[currentIndex] = await preloadImageUrl(queue[currentIndex]);
|
const currentUrl = queue[currentIndex];
|
||||||
|
try {
|
||||||
|
results[currentIndex] = await preloadImageUrl(currentUrl);
|
||||||
|
} finally {
|
||||||
|
reportProgress(currentUrl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1078,6 +1116,8 @@
|
|||||||
|
|
||||||
function preloadDeckImages(deckId, options = {}) {
|
function preloadDeckImages(deckId, options = {}) {
|
||||||
const normalizedDeckId = normalizeDeckId(deckId);
|
const normalizedDeckId = normalizeDeckId(deckId);
|
||||||
|
const preloadUrls = buildDeckImagePreloadUrls(normalizedDeckId, options);
|
||||||
|
const totalCount = preloadUrls.length;
|
||||||
const cacheKey = JSON.stringify({
|
const cacheKey = JSON.stringify({
|
||||||
deckId: normalizedDeckId,
|
deckId: normalizedDeckId,
|
||||||
includeFull: options.includeFull !== false,
|
includeFull: options.includeFull !== false,
|
||||||
@@ -1086,26 +1126,49 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!options.force && deckImagePreloadCache.has(cacheKey)) {
|
if (!options.force && deckImagePreloadCache.has(cacheKey)) {
|
||||||
|
if (deckPreloadStatus.warmedDeckIds.includes(normalizedDeckId)) {
|
||||||
|
setDeckPreloadStatus({
|
||||||
|
activeDeckId: normalizedDeckId,
|
||||||
|
selectedDeckPhase: "ready",
|
||||||
|
selectedDeckLoadedCount: totalCount,
|
||||||
|
selectedDeckTotalCount: totalCount,
|
||||||
|
selectedDeckPercent: totalCount > 0 ? 100 : 0
|
||||||
|
});
|
||||||
|
}
|
||||||
return deckImagePreloadCache.get(cacheKey);
|
return deckImagePreloadCache.get(cacheKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.background) {
|
if (!options.background) {
|
||||||
setDeckPreloadStatus({
|
setDeckPreloadStatus({
|
||||||
activeDeckId: normalizedDeckId,
|
activeDeckId: normalizedDeckId,
|
||||||
selectedDeckPhase: "loading"
|
selectedDeckPhase: "loading",
|
||||||
|
selectedDeckLoadedCount: 0,
|
||||||
|
selectedDeckTotalCount: totalCount,
|
||||||
|
selectedDeckPercent: 0
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const preloadPromise = preloadImageUrls(buildDeckImagePreloadUrls(normalizedDeckId, options), options.concurrency)
|
const preloadPromise = preloadImageUrls(preloadUrls, options.concurrency, ({ completedCount, totalCount: progressTotalCount }) => {
|
||||||
|
if (!options.background) {
|
||||||
|
setDeckPreloadStatus({
|
||||||
|
activeDeckId: normalizedDeckId,
|
||||||
|
selectedDeckPhase: "loading",
|
||||||
|
selectedDeckLoadedCount: completedCount,
|
||||||
|
selectedDeckTotalCount: progressTotalCount,
|
||||||
|
selectedDeckPercent: progressTotalCount > 0 ? Math.round((completedCount / progressTotalCount) * 100) : 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
markDeckAsWarmed(normalizedDeckId);
|
markDeckAsWarmed(normalizedDeckId);
|
||||||
if (!options.background) {
|
if (!options.background) {
|
||||||
setDeckPreloadStatus({
|
setDeckPreloadStatus({
|
||||||
activeDeckId: normalizedDeckId,
|
activeDeckId: normalizedDeckId,
|
||||||
selectedDeckPhase: "ready"
|
selectedDeckPhase: "ready",
|
||||||
|
selectedDeckLoadedCount: totalCount,
|
||||||
|
selectedDeckTotalCount: totalCount,
|
||||||
|
selectedDeckPercent: totalCount > 0 ? 100 : 0
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
emitDeckPreloadStatus();
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
})
|
})
|
||||||
@@ -1113,7 +1176,8 @@
|
|||||||
if (!options.background) {
|
if (!options.background) {
|
||||||
setDeckPreloadStatus({
|
setDeckPreloadStatus({
|
||||||
activeDeckId: normalizedDeckId,
|
activeDeckId: normalizedDeckId,
|
||||||
selectedDeckPhase: "error"
|
selectedDeckPhase: "error",
|
||||||
|
selectedDeckTotalCount: totalCount
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
@@ -1141,44 +1205,6 @@
|
|||||||
return deferPreload(() => preloadDeckImages(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();
|
||||||
@@ -1263,12 +1289,17 @@
|
|||||||
function setActiveDeck(deckId) {
|
function setActiveDeck(deckId) {
|
||||||
activeDeckId = normalizeDeckId(deckId);
|
activeDeckId = normalizeDeckId(deckId);
|
||||||
getDeckManifest(activeDeckId);
|
getDeckManifest(activeDeckId);
|
||||||
|
const preloadUrls = buildDeckImagePreloadUrls(activeDeckId);
|
||||||
|
const totalCount = preloadUrls.length;
|
||||||
|
const isWarmed = deckPreloadStatus.warmedDeckIds.includes(activeDeckId);
|
||||||
setDeckPreloadStatus({
|
setDeckPreloadStatus({
|
||||||
activeDeckId,
|
activeDeckId,
|
||||||
selectedDeckPhase: "idle"
|
selectedDeckPhase: isWarmed ? "ready" : "idle",
|
||||||
|
selectedDeckLoadedCount: isWarmed ? totalCount : 0,
|
||||||
|
selectedDeckTotalCount: totalCount,
|
||||||
|
selectedDeckPercent: isWarmed && totalCount > 0 ? 100 : 0
|
||||||
});
|
});
|
||||||
scheduleDeckImagePreload(activeDeckId);
|
scheduleDeckImagePreload(activeDeckId);
|
||||||
scheduleBackgroundDeckWarmup(activeDeckId);
|
|
||||||
return activeDeckId;
|
return activeDeckId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -567,6 +567,41 @@
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
.settings-cache-progress-wrap {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
.settings-cache-progress {
|
||||||
|
width: 100%;
|
||||||
|
height: 10px;
|
||||||
|
appearance: none;
|
||||||
|
border: none;
|
||||||
|
border-radius: 999px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: rgba(39, 39, 42, 0.92);
|
||||||
|
}
|
||||||
|
.settings-cache-progress::-webkit-progress-bar {
|
||||||
|
background: rgba(39, 39, 42, 0.92);
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
.settings-cache-progress::-webkit-progress-value {
|
||||||
|
background: linear-gradient(90deg, #f59e0b, #facc15);
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
.settings-cache-progress::-moz-progress-bar {
|
||||||
|
background: linear-gradient(90deg, #f59e0b, #facc15);
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
.settings-cache-progress-label {
|
||||||
|
min-width: 3ch;
|
||||||
|
text-align: right;
|
||||||
|
color: #e2e8f0;
|
||||||
|
font-size: 12px;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
.calendar-year-control {
|
.calendar-year-control {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
+27
-19
@@ -35,6 +35,8 @@
|
|||||||
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"),
|
||||||
|
tarotDeckCacheProgressEl: document.getElementById("tarot-deck-cache-progress"),
|
||||||
|
tarotDeckCacheProgressLabelEl: document.getElementById("tarot-deck-cache-progress-label"),
|
||||||
tarotDeckCacheStatusEl: document.getElementById("tarot-deck-cache-status"),
|
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"),
|
||||||
@@ -205,11 +207,13 @@
|
|||||||
|
|
||||||
function formatDeckCacheStatus(status) {
|
function formatDeckCacheStatus(status) {
|
||||||
const activeDeckId = String(status?.activeDeckId || normalizeTarotDeck(getElements().tarotDeckEl?.value)).trim().toLowerCase();
|
const activeDeckId = String(status?.activeDeckId || normalizeTarotDeck(getElements().tarotDeckEl?.value)).trim().toLowerCase();
|
||||||
const totalDeckCount = Number(status?.totalDeckCount) || 0;
|
const loadedCount = Math.max(0, Number(status?.selectedDeckLoadedCount) || 0);
|
||||||
const warmedDeckCount = Number(status?.warmedDeckCount) || 0;
|
const totalCount = Math.max(0, Number(status?.selectedDeckTotalCount) || 0);
|
||||||
const warmedAllDecks = totalDeckCount > 0 && warmedDeckCount >= totalDeckCount;
|
|
||||||
|
|
||||||
if (status?.selectedDeckPhase === "loading") {
|
if (status?.selectedDeckPhase === "loading") {
|
||||||
|
if (totalCount > 0) {
|
||||||
|
return `Caching selected deck images to this browser... (${loadedCount}/${totalCount})`;
|
||||||
|
}
|
||||||
return "Caching selected deck images to this browser...";
|
return "Caching selected deck images to this browser...";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,21 +221,7 @@
|
|||||||
return "Selected deck cache warmup hit an error. Images will still load on demand.";
|
return "Selected deck cache warmup hit an error. Images will still load on demand.";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status?.backgroundPhase === "loading") {
|
if (status?.selectedDeckPhase === "ready") {
|
||||||
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 `Selected deck cached and ready for fullscreen use (${activeDeckId}).`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,12 +229,30 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function syncDeckCacheStatus(status) {
|
function syncDeckCacheStatus(status) {
|
||||||
const { tarotDeckCacheStatusEl } = getElements();
|
const {
|
||||||
|
tarotDeckCacheStatusEl,
|
||||||
|
tarotDeckCacheProgressEl,
|
||||||
|
tarotDeckCacheProgressLabelEl
|
||||||
|
} = getElements();
|
||||||
if (!tarotDeckCacheStatusEl) {
|
if (!tarotDeckCacheStatusEl) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tarotDeckCacheStatusEl.textContent = formatDeckCacheStatus(status);
|
tarotDeckCacheStatusEl.textContent = formatDeckCacheStatus(status);
|
||||||
|
|
||||||
|
const totalCount = Math.max(0, Number(status?.selectedDeckTotalCount) || 0);
|
||||||
|
const loadedCount = Math.max(0, Number(status?.selectedDeckLoadedCount) || 0);
|
||||||
|
const derivedPercent = totalCount > 0 ? Math.round((loadedCount / totalCount) * 100) : 0;
|
||||||
|
const percent = Math.max(0, Math.min(100, Number(status?.selectedDeckPercent) || derivedPercent));
|
||||||
|
|
||||||
|
if (tarotDeckCacheProgressEl) {
|
||||||
|
tarotDeckCacheProgressEl.max = 100;
|
||||||
|
tarotDeckCacheProgressEl.value = percent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tarotDeckCacheProgressLabelEl) {
|
||||||
|
tarotDeckCacheProgressLabelEl.textContent = `${percent}%`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncSky(geo, options) {
|
function syncSky(geo, options) {
|
||||||
|
|||||||
@@ -146,6 +146,10 @@
|
|||||||
<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>
|
||||||
|
<div class="settings-cache-progress-wrap" aria-hidden="true">
|
||||||
|
<progress id="tarot-deck-cache-progress" class="settings-cache-progress" max="100" value="0"></progress>
|
||||||
|
<span id="tarot-deck-cache-progress-label" class="settings-cache-progress-label">0%</span>
|
||||||
|
</div>
|
||||||
<small id="tarot-deck-cache-status" class="settings-field-hint settings-cache-status" aria-live="polite">Deck cache idle.</small>
|
<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">
|
||||||
|
|||||||
Reference in New Issue
Block a user