added thumbs generation for performation and also added a new deck format for registration
This commit is contained in:
@@ -98,6 +98,14 @@
|
|||||||
swords: ["swords"],
|
swords: ["swords"],
|
||||||
disks: ["disks", "pentacles", "coins"]
|
disks: ["disks", "pentacles", "coins"]
|
||||||
};
|
};
|
||||||
|
const defaultPipRankOrder = ["Ace", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"];
|
||||||
|
const defaultThumbnailConfig = {
|
||||||
|
root: "thumbs",
|
||||||
|
width: 240,
|
||||||
|
height: 360,
|
||||||
|
fit: "inside",
|
||||||
|
quality: 82
|
||||||
|
};
|
||||||
|
|
||||||
const DECK_REGISTRY_PATH = "asset/tarot deck/decks.json";
|
const DECK_REGISTRY_PATH = "asset/tarot deck/decks.json";
|
||||||
|
|
||||||
@@ -105,6 +113,7 @@
|
|||||||
|
|
||||||
const manifestCache = new Map();
|
const manifestCache = new Map();
|
||||||
const cardBackCache = new Map();
|
const cardBackCache = new Map();
|
||||||
|
const cardBackThumbnailCache = new Map();
|
||||||
let activeDeckId = DEFAULT_DECK_ID;
|
let activeDeckId = DEFAULT_DECK_ID;
|
||||||
|
|
||||||
function canonicalMajorName(cardName) {
|
function canonicalMajorName(cardName) {
|
||||||
@@ -278,6 +287,56 @@
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeThumbnailConfig(rawConfig, fallbackRoot = "") {
|
||||||
|
if (rawConfig === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rawConfig || typeof rawConfig !== "object") {
|
||||||
|
const root = String(fallbackRoot || "").trim();
|
||||||
|
if (!root) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...defaultThumbnailConfig,
|
||||||
|
root
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = String(rawConfig.root || fallbackRoot || defaultThumbnailConfig.root).trim();
|
||||||
|
if (!root) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
root,
|
||||||
|
width: Number.isInteger(Number(rawConfig.width)) && Number(rawConfig.width) > 0
|
||||||
|
? Number(rawConfig.width)
|
||||||
|
: defaultThumbnailConfig.width,
|
||||||
|
height: Number.isInteger(Number(rawConfig.height)) && Number(rawConfig.height) > 0
|
||||||
|
? Number(rawConfig.height)
|
||||||
|
: defaultThumbnailConfig.height,
|
||||||
|
fit: String(rawConfig.fit || defaultThumbnailConfig.fit).trim() || defaultThumbnailConfig.fit,
|
||||||
|
quality: Number.isInteger(Number(rawConfig.quality)) && Number(rawConfig.quality) >= 1 && Number(rawConfig.quality) <= 100
|
||||||
|
? Number(rawConfig.quality)
|
||||||
|
: defaultThumbnailConfig.quality
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveDeckThumbnailPath(manifest, relativePath) {
|
||||||
|
if (!manifest || !manifest.thumbnails || manifest.thumbnails === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedPath = String(relativePath || "").trim().replace(/^\.\//, "");
|
||||||
|
if (!normalizedPath || isRemoteAssetPath(normalizedPath) || normalizedPath.startsWith("/")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return toDeckAssetPath(manifest, `${manifest.thumbnails.root}/${normalizedPath}`) || null;
|
||||||
|
}
|
||||||
|
|
||||||
function readManifestJsonSync(path) {
|
function readManifestJsonSync(path) {
|
||||||
try {
|
try {
|
||||||
const request = new XMLHttpRequest();
|
const request = new XMLHttpRequest();
|
||||||
@@ -314,7 +373,8 @@
|
|||||||
label: String(entry?.label || id),
|
label: String(entry?.label || id),
|
||||||
basePath,
|
basePath,
|
||||||
manifestPath,
|
manifestPath,
|
||||||
cardBackPath: String(entry?.cardBackPath || "").trim()
|
cardBackPath: String(entry?.cardBackPath || "").trim(),
|
||||||
|
thumbnailRoot: String(entry?.thumbnailRoot || "").trim()
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -385,6 +445,7 @@
|
|||||||
basePath: String(source.basePath || "").replace(/\/$/, ""),
|
basePath: String(source.basePath || "").replace(/\/$/, ""),
|
||||||
cardBack: String(rawManifest.cardBack || "").trim(),
|
cardBack: String(rawManifest.cardBack || "").trim(),
|
||||||
cardBackPath: String(source.cardBackPath || "").trim(),
|
cardBackPath: String(source.cardBackPath || "").trim(),
|
||||||
|
thumbnails: normalizeThumbnailConfig(rawManifest.thumbnails, source.thumbnailRoot),
|
||||||
majors: rawManifest.majors || {},
|
majors: rawManifest.majors || {},
|
||||||
minors: rawManifest.minors || {},
|
minors: rawManifest.minors || {},
|
||||||
nameOverrides,
|
nameOverrides,
|
||||||
@@ -418,7 +479,13 @@
|
|||||||
return normalizedManifest;
|
return normalizedManifest;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRankIndex(minorRule, parsedMinor) {
|
function getRankOrder(minorRule, fallbackRankOrder = []) {
|
||||||
|
const explicitRankOrder = Array.isArray(minorRule?.rankOrder) ? minorRule.rankOrder : [];
|
||||||
|
const rankOrderSource = explicitRankOrder.length ? explicitRankOrder : fallbackRankOrder;
|
||||||
|
return rankOrderSource.map((entry) => String(entry || "").trim()).filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRankIndex(minorRule, parsedMinor, fallbackRankOrder = []) {
|
||||||
if (!minorRule || !parsedMinor) {
|
if (!minorRule || !parsedMinor) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -434,7 +501,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const rankOrder = Array.isArray(minorRule.rankOrder) ? minorRule.rankOrder : [];
|
const rankOrder = getRankOrder(minorRule, fallbackRankOrder);
|
||||||
for (let i = 0; i < rankOrder.length; i += 1) {
|
for (let i = 0; i < rankOrder.length; i += 1) {
|
||||||
const candidate = String(rankOrder[i] || "").toLowerCase();
|
const candidate = String(rankOrder[i] || "").toLowerCase();
|
||||||
if (candidate && (candidate === lowerRankWord || candidate === lowerRankKey)) {
|
if (candidate && (candidate === lowerRankWord || candidate === lowerRankKey)) {
|
||||||
@@ -445,6 +512,34 @@
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveMinorNumberTemplateGroup(groupRule, parsedMinor, fallbackRankOrder = []) {
|
||||||
|
if (!groupRule || typeof groupRule !== "object") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rankIndex = getRankIndex(groupRule, parsedMinor, fallbackRankOrder);
|
||||||
|
if (!Number.isInteger(rankIndex) || rankIndex < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const suitBaseRaw = Number(groupRule?.suitBase?.[parsedMinor.suitId]);
|
||||||
|
if (!Number.isFinite(suitBaseRaw)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const numberPad = Number.isInteger(groupRule.numberPad) ? groupRule.numberPad : 2;
|
||||||
|
const cardNumber = String(suitBaseRaw + rankIndex).padStart(numberPad, "0");
|
||||||
|
const template = String(groupRule.template || "{number}.png");
|
||||||
|
|
||||||
|
return applyTemplate(template, {
|
||||||
|
number: cardNumber,
|
||||||
|
suitId: parsedMinor.suitId,
|
||||||
|
rank: parsedMinor.rankWord,
|
||||||
|
rankKey: parsedMinor.rankKey,
|
||||||
|
index: rankIndex
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function resolveMajorFile(manifest, canonicalName) {
|
function resolveMajorFile(manifest, canonicalName) {
|
||||||
const majorRule = manifest?.majors;
|
const majorRule = manifest?.majors;
|
||||||
if (!majorRule || typeof majorRule !== "object") {
|
if (!majorRule || typeof majorRule !== "object") {
|
||||||
@@ -487,6 +582,14 @@
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (minorRule.mode === "split-number-template") {
|
||||||
|
if (Number.isFinite(parsedMinor.pipValue)) {
|
||||||
|
return resolveMinorNumberTemplateGroup(minorRule.smalls, parsedMinor, defaultPipRankOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolveMinorNumberTemplateGroup(minorRule.courts, parsedMinor);
|
||||||
|
}
|
||||||
|
|
||||||
const rankIndex = getRankIndex(minorRule, parsedMinor);
|
const rankIndex = getRankIndex(minorRule, parsedMinor);
|
||||||
if (!Number.isInteger(rankIndex) || rankIndex < 0) {
|
if (!Number.isInteger(rankIndex) || rankIndex < 0) {
|
||||||
return null;
|
return null;
|
||||||
@@ -555,8 +658,7 @@
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveWithDeck(deckId, cardName) {
|
function resolveCardRelativePath(manifest, cardName) {
|
||||||
const manifest = getDeckManifest(deckId);
|
|
||||||
if (!manifest) {
|
if (!manifest) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -564,7 +666,7 @@
|
|||||||
const canonical = canonicalMajorName(cardName);
|
const canonical = canonicalMajorName(cardName);
|
||||||
const majorFile = resolveMajorFile(manifest, canonical);
|
const majorFile = resolveMajorFile(manifest, canonical);
|
||||||
if (majorFile) {
|
if (majorFile) {
|
||||||
return `${manifest.basePath}/${majorFile}`;
|
return majorFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsedMinor = parseMinorCard(cardName);
|
const parsedMinor = parseMinorCard(cardName);
|
||||||
@@ -572,12 +674,25 @@
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const minorFile = resolveMinorFile(manifest, parsedMinor);
|
return resolveMinorFile(manifest, parsedMinor);
|
||||||
if (!minorFile) {
|
}
|
||||||
|
|
||||||
|
function resolveWithDeck(deckId, cardName, variant = "full") {
|
||||||
|
const manifest = getDeckManifest(deckId);
|
||||||
|
if (!manifest) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${manifest.basePath}/${minorFile}`;
|
const relativePath = resolveCardRelativePath(manifest, cardName);
|
||||||
|
if (!relativePath) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (variant === "thumbnail") {
|
||||||
|
return resolveDeckThumbnailPath(manifest, relativePath) || `${manifest.basePath}/${relativePath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${manifest.basePath}/${relativePath}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveTarotCardImage(cardName) {
|
function resolveTarotCardImage(cardName) {
|
||||||
@@ -589,6 +704,16 @@
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveTarotCardThumbnail(cardName, optionsOrDeckId) {
|
||||||
|
const { resolvedDeckId } = resolveDeckOptions(optionsOrDeckId);
|
||||||
|
const thumbnailPath = resolveWithDeck(resolvedDeckId, cardName, "thumbnail");
|
||||||
|
if (thumbnailPath) {
|
||||||
|
return encodeURI(thumbnailPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function resolveTarotCardBackImage(optionsOrDeckId) {
|
function resolveTarotCardBackImage(optionsOrDeckId) {
|
||||||
const { resolvedDeckId } = resolveDeckOptions(optionsOrDeckId);
|
const { resolvedDeckId } = resolveDeckOptions(optionsOrDeckId);
|
||||||
|
|
||||||
@@ -608,6 +733,22 @@
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveTarotCardBackThumbnail(optionsOrDeckId) {
|
||||||
|
const { resolvedDeckId } = resolveDeckOptions(optionsOrDeckId);
|
||||||
|
|
||||||
|
if (cardBackThumbnailCache.has(resolvedDeckId)) {
|
||||||
|
const cachedPath = cardBackThumbnailCache.get(resolvedDeckId);
|
||||||
|
return cachedPath ? encodeURI(cachedPath) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const manifest = getDeckManifest(resolvedDeckId);
|
||||||
|
const relativeBackPath = String(manifest?.cardBack || manifest?.cardBackPath || "").trim();
|
||||||
|
const thumbnailPath = resolveDeckThumbnailPath(manifest, relativeBackPath) || resolveDeckCardBackPath(manifest);
|
||||||
|
cardBackThumbnailCache.set(resolvedDeckId, thumbnailPath || null);
|
||||||
|
|
||||||
|
return thumbnailPath ? encodeURI(thumbnailPath) : null;
|
||||||
|
}
|
||||||
|
|
||||||
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();
|
||||||
@@ -712,7 +853,9 @@
|
|||||||
|
|
||||||
window.TarotCardImages = {
|
window.TarotCardImages = {
|
||||||
resolveTarotCardImage,
|
resolveTarotCardImage,
|
||||||
|
resolveTarotCardThumbnail,
|
||||||
resolveTarotCardBackImage,
|
resolveTarotCardBackImage,
|
||||||
|
resolveTarotCardBackThumbnail,
|
||||||
getTarotCardDisplayName,
|
getTarotCardDisplayName,
|
||||||
getTarotCardSearchAliases,
|
getTarotCardSearchAliases,
|
||||||
setActiveDeck,
|
setActiveDeck,
|
||||||
|
|||||||
@@ -767,6 +767,7 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
contain: layout paint;
|
||||||
transform-origin: center;
|
transform-origin: center;
|
||||||
transition: transform 120ms ease, border-color 120ms ease, box-shadow 120ms ease;
|
transition: transform 120ms ease, border-color 120ms ease, box-shadow 120ms ease;
|
||||||
}
|
}
|
||||||
@@ -803,6 +804,12 @@
|
|||||||
height: var(--tarot-house-card-height);
|
height: var(--tarot-house-card-height);
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
background: #09090b;
|
background: #09090b;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 140ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tarot-house-card-image.is-loaded {
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tarot-house-card-text-face {
|
.tarot-house-card-text-face {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const { DAY_IN_MS, getMoonPhaseName, getDecanForDate } = window.TarotCalc || {};
|
const { DAY_IN_MS, getMoonPhaseName, getDecanForDate } = window.TarotCalc || {};
|
||||||
const { resolveTarotCardImage, getTarotCardDisplayName } = window.TarotCardImages || {};
|
const { resolveTarotCardImage, resolveTarotCardThumbnail, getTarotCardDisplayName } = window.TarotCardImages || {};
|
||||||
|
|
||||||
let nowLightboxOverlayEl = null;
|
let nowLightboxOverlayEl = null;
|
||||||
let nowLightboxImageEl = null;
|
let nowLightboxImageEl = null;
|
||||||
@@ -207,7 +207,7 @@
|
|||||||
imageEl.style.cursor = "zoom-in";
|
imageEl.style.cursor = "zoom-in";
|
||||||
imageEl.title = "Click to enlarge";
|
imageEl.title = "Click to enlarge";
|
||||||
imageEl.addEventListener("click", () => {
|
imageEl.addEventListener("click", () => {
|
||||||
const src = imageEl.getAttribute("src");
|
const src = imageEl.dataset.fullSrc || imageEl.getAttribute("src");
|
||||||
if (!src || imageEl.style.display === "none") {
|
if (!src || imageEl.style.display === "none") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -489,23 +489,32 @@
|
|||||||
|
|
||||||
bindNowCardLightbox(imageEl);
|
bindNowCardLightbox(imageEl);
|
||||||
|
|
||||||
if (!cardName || typeof resolveTarotCardImage !== "function") {
|
if (!cardName || (typeof resolveTarotCardThumbnail !== "function" && typeof resolveTarotCardImage !== "function")) {
|
||||||
imageEl.style.display = "none";
|
imageEl.style.display = "none";
|
||||||
imageEl.removeAttribute("src");
|
imageEl.removeAttribute("src");
|
||||||
|
delete imageEl.dataset.fullSrc;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const src = resolveTarotCardImage(cardName);
|
const fullSrc = typeof resolveTarotCardImage === "function"
|
||||||
if (!src) {
|
? resolveTarotCardImage(cardName)
|
||||||
|
: null;
|
||||||
|
const thumbnailSrc = typeof resolveTarotCardThumbnail === "function"
|
||||||
|
? (resolveTarotCardThumbnail(cardName) || fullSrc)
|
||||||
|
: fullSrc;
|
||||||
|
if (!thumbnailSrc) {
|
||||||
imageEl.style.display = "none";
|
imageEl.style.display = "none";
|
||||||
imageEl.removeAttribute("src");
|
imageEl.removeAttribute("src");
|
||||||
|
delete imageEl.dataset.fullSrc;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
imageEl.src = src;
|
imageEl.src = thumbnailSrc;
|
||||||
|
imageEl.dataset.fullSrc = fullSrc || thumbnailSrc;
|
||||||
const displayName = getDisplayTarotName(cardName, trumpNumber);
|
const displayName = getDisplayTarotName(cardName, trumpNumber);
|
||||||
imageEl.alt = `${fallbackLabel}: ${displayName}`;
|
imageEl.alt = `${fallbackLabel}: ${displayName}`;
|
||||||
imageEl.style.display = "block";
|
imageEl.style.display = "block";
|
||||||
|
imageEl.decoding = "async";
|
||||||
}
|
}
|
||||||
|
|
||||||
window.NowUiHelpers = {
|
window.NowUiHelpers = {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
getMonthRefsByCardId,
|
getMonthRefsByCardId,
|
||||||
getMagickDataset,
|
getMagickDataset,
|
||||||
resolveTarotCardImage,
|
resolveTarotCardImage,
|
||||||
|
resolveTarotCardThumbnail,
|
||||||
getDisplayCardName,
|
getDisplayCardName,
|
||||||
buildTypeLabel,
|
buildTypeLabel,
|
||||||
clearChildren,
|
clearChildren,
|
||||||
@@ -203,9 +204,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cardDisplayName = getDisplayCardName(card);
|
const cardDisplayName = getDisplayCardName(card);
|
||||||
const imageUrl = typeof resolveTarotCardImage === "function"
|
const imageUrl = typeof resolveTarotCardThumbnail === "function"
|
||||||
? resolveTarotCardImage(card.name)
|
? resolveTarotCardThumbnail(card.name)
|
||||||
: null;
|
: (typeof resolveTarotCardImage === "function" ? resolveTarotCardImage(card.name) : null);
|
||||||
|
|
||||||
if (elements.tarotDetailImageEl) {
|
if (elements.tarotDetailImageEl) {
|
||||||
if (imageUrl) {
|
if (imageUrl) {
|
||||||
@@ -214,6 +215,7 @@
|
|||||||
elements.tarotDetailImageEl.style.display = "block";
|
elements.tarotDetailImageEl.style.display = "block";
|
||||||
elements.tarotDetailImageEl.style.cursor = "zoom-in";
|
elements.tarotDetailImageEl.style.cursor = "zoom-in";
|
||||||
elements.tarotDetailImageEl.title = "Click to enlarge";
|
elements.tarotDetailImageEl.title = "Click to enlarge";
|
||||||
|
elements.tarotDetailImageEl.decoding = "async";
|
||||||
} else {
|
} else {
|
||||||
elements.tarotDetailImageEl.removeAttribute("src");
|
elements.tarotDetailImageEl.removeAttribute("src");
|
||||||
elements.tarotDetailImageEl.alt = "";
|
elements.tarotDetailImageEl.alt = "";
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
resolveTarotCardImage: null,
|
resolveTarotCardImage: null,
|
||||||
|
resolveTarotCardThumbnail: null,
|
||||||
getDisplayCardName: (card) => card?.name || "",
|
getDisplayCardName: (card) => card?.name || "",
|
||||||
clearChildren: () => {},
|
clearChildren: () => {},
|
||||||
normalizeTarotCardLookupName: (value) => String(value || "").trim().toLowerCase(),
|
normalizeTarotCardLookupName: (value) => String(value || "").trim().toLowerCase(),
|
||||||
@@ -59,6 +60,8 @@
|
|||||||
getHouseBottomInfoModes: () => ({})
|
getHouseBottomInfoModes: () => ({})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let houseImageObserver = null;
|
||||||
|
|
||||||
function init(nextConfig = {}) {
|
function init(nextConfig = {}) {
|
||||||
Object.assign(config, nextConfig || {});
|
Object.assign(config, nextConfig || {});
|
||||||
}
|
}
|
||||||
@@ -597,6 +600,73 @@
|
|||||||
return faceEl;
|
return faceEl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function disconnectHouseImageObserver() {
|
||||||
|
if (!houseImageObserver) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
houseImageObserver.disconnect();
|
||||||
|
houseImageObserver = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hydrateHouseCardImage(image) {
|
||||||
|
if (!(image instanceof HTMLImageElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextSrc = String(image.dataset.src || "").trim();
|
||||||
|
if (!nextSrc || image.dataset.imageHydrated === "true") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
image.dataset.imageHydrated = "true";
|
||||||
|
image.classList.add("is-loading");
|
||||||
|
image.src = nextSrc;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHouseImageObserver(elements) {
|
||||||
|
const root = elements?.tarotHouseOfCardsEl?.closest(".tarot-section-house-top") || null;
|
||||||
|
if (!root || typeof IntersectionObserver !== "function") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (houseImageObserver) {
|
||||||
|
return houseImageObserver;
|
||||||
|
}
|
||||||
|
|
||||||
|
houseImageObserver = new IntersectionObserver((entries, observer) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (!entry.isIntersecting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = entry.target;
|
||||||
|
observer.unobserve(target);
|
||||||
|
hydrateHouseCardImage(target);
|
||||||
|
});
|
||||||
|
}, {
|
||||||
|
root,
|
||||||
|
rootMargin: "160px 0px",
|
||||||
|
threshold: 0.01
|
||||||
|
});
|
||||||
|
|
||||||
|
return houseImageObserver;
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerHouseCardImage(image, elements) {
|
||||||
|
if (!(image instanceof HTMLImageElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const observer = getHouseImageObserver(elements);
|
||||||
|
if (!observer) {
|
||||||
|
hydrateHouseCardImage(image);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
observer.observe(image);
|
||||||
|
}
|
||||||
|
|
||||||
function createHouseCardButton(card, elements) {
|
function createHouseCardButton(card, elements) {
|
||||||
const button = document.createElement("button");
|
const button = document.createElement("button");
|
||||||
button.type = "button";
|
button.type = "button";
|
||||||
@@ -620,16 +690,30 @@
|
|||||||
button.title = labelText ? `${cardDisplayName || card.name} - ${labelText}` : (cardDisplayName || card.name);
|
button.title = labelText ? `${cardDisplayName || card.name} - ${labelText}` : (cardDisplayName || card.name);
|
||||||
button.setAttribute("aria-label", labelText ? `${cardDisplayName || card.name}, ${labelText}` : (cardDisplayName || card.name));
|
button.setAttribute("aria-label", labelText ? `${cardDisplayName || card.name}, ${labelText}` : (cardDisplayName || card.name));
|
||||||
button.dataset.houseCardId = card.id;
|
button.dataset.houseCardId = card.id;
|
||||||
const imageUrl = typeof config.resolveTarotCardImage === "function"
|
const imageUrl = typeof config.resolveTarotCardThumbnail === "function"
|
||||||
? config.resolveTarotCardImage(card.name)
|
? config.resolveTarotCardThumbnail(card.name)
|
||||||
: null;
|
: (typeof config.resolveTarotCardImage === "function" ? config.resolveTarotCardImage(card.name) : null);
|
||||||
|
|
||||||
if (showImage && imageUrl) {
|
if (showImage && imageUrl) {
|
||||||
const image = document.createElement("img");
|
const image = document.createElement("img");
|
||||||
image.className = "tarot-house-card-image";
|
image.className = "tarot-house-card-image";
|
||||||
image.src = imageUrl;
|
image.alt = "";
|
||||||
image.alt = cardDisplayName || card.name;
|
image.setAttribute("aria-hidden", "true");
|
||||||
|
image.loading = "lazy";
|
||||||
|
image.decoding = "async";
|
||||||
|
image.fetchPriority = config.isHouseFocusMode?.() === true ? "auto" : "low";
|
||||||
|
image.dataset.src = imageUrl;
|
||||||
|
image.addEventListener("load", () => {
|
||||||
|
image.classList.remove("is-loading");
|
||||||
|
image.classList.add("is-loaded");
|
||||||
|
}, { once: true });
|
||||||
|
image.addEventListener("error", () => {
|
||||||
|
image.classList.remove("is-loading");
|
||||||
|
image.classList.remove("is-loaded");
|
||||||
|
image.dataset.imageHydrated = "false";
|
||||||
|
});
|
||||||
button.appendChild(image);
|
button.appendChild(image);
|
||||||
|
registerHouseCardImage(image, elements);
|
||||||
} else if (showImage) {
|
} else if (showImage) {
|
||||||
const fallback = document.createElement("span");
|
const fallback = document.createElement("span");
|
||||||
fallback.className = "tarot-house-card-fallback";
|
fallback.className = "tarot-house-card-fallback";
|
||||||
@@ -1095,6 +1179,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cards = config.getCards();
|
const cards = config.getCards();
|
||||||
|
disconnectHouseImageObserver();
|
||||||
config.clearChildren(elements.tarotHouseOfCardsEl);
|
config.clearChildren(elements.tarotHouseOfCardsEl);
|
||||||
const cardLookupMap = getCardLookupMap(cards);
|
const cardLookupMap = getCardLookupMap(cards);
|
||||||
|
|
||||||
|
|||||||
@@ -152,7 +152,11 @@
|
|||||||
|
|
||||||
const normalizedSpread = normalizeTarotSpread(activeTarotSpread);
|
const normalizedSpread = normalizeTarotSpread(activeTarotSpread);
|
||||||
const isCeltic = normalizedSpread === "celtic-cross";
|
const isCeltic = normalizedSpread === "celtic-cross";
|
||||||
const cardBackImageSrc = String(window.TarotCardImages?.resolveTarotCardBackImage?.() || "").trim();
|
const cardBackImageSrc = String(
|
||||||
|
window.TarotCardImages?.resolveTarotCardBackThumbnail?.()
|
||||||
|
|| window.TarotCardImages?.resolveTarotCardBackImage?.()
|
||||||
|
|| ""
|
||||||
|
).trim();
|
||||||
|
|
||||||
if (!activeTarotSpreadDraw.length) {
|
if (!activeTarotSpreadDraw.length) {
|
||||||
regenerateTarotSpreadDraw();
|
regenerateTarotSpreadDraw();
|
||||||
@@ -188,7 +192,8 @@
|
|||||||
tarotSpreadBoardEl.innerHTML = activeTarotSpreadDraw.map((entry, index) => {
|
tarotSpreadBoardEl.innerHTML = activeTarotSpreadDraw.map((entry, index) => {
|
||||||
const position = entry.position;
|
const position = entry.position;
|
||||||
const card = entry.card;
|
const card = entry.card;
|
||||||
const imgSrc = window.TarotCardImages?.resolveTarotCardImage?.(card.name);
|
const imgSrc = window.TarotCardImages?.resolveTarotCardThumbnail?.(card.name)
|
||||||
|
|| window.TarotCardImages?.resolveTarotCardImage?.(card.name);
|
||||||
const isRevealed = Boolean(entry.revealed);
|
const isRevealed = Boolean(entry.revealed);
|
||||||
const cardBackAttr = cardBackImageSrc
|
const cardBackAttr = cardBackImageSrc
|
||||||
? ` data-card-back-src="${escapeHtml(cardBackImageSrc)}"`
|
? ` data-card-back-src="${escapeHtml(cardBackImageSrc)}"`
|
||||||
@@ -203,10 +208,10 @@
|
|||||||
let faceMarkup = "";
|
let faceMarkup = "";
|
||||||
if (isRevealed) {
|
if (isRevealed) {
|
||||||
faceMarkup = imgSrc
|
faceMarkup = imgSrc
|
||||||
? `<img class="spread-card-img" src="${imgSrc}" alt="${escapeHtml(card.name)}" loading="lazy">`
|
? `<img class="spread-card-img" src="${imgSrc}" alt="${escapeHtml(card.name)}" loading="lazy" decoding="async">`
|
||||||
: `<div class="spread-card-placeholder">${escapeHtml(card.name)}</div>`;
|
: `<div class="spread-card-placeholder">${escapeHtml(card.name)}</div>`;
|
||||||
} else if (cardBackImageSrc) {
|
} else if (cardBackImageSrc) {
|
||||||
faceMarkup = '<img class="spread-card-back-img" src="' + cardBackImageSrc + '" alt="Face-down tarot card" loading="lazy">';
|
faceMarkup = '<img class="spread-card-back-img" src="' + cardBackImageSrc + '" alt="Face-down tarot card" loading="lazy" decoding="async">';
|
||||||
} else {
|
} else {
|
||||||
faceMarkup = '<div class="spread-card-back-fallback">CARD BACK</div>';
|
faceMarkup = '<div class="spread-card-back-fallback">CARD BACK</div>';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
(function () {
|
(function () {
|
||||||
const { resolveTarotCardImage, getTarotCardDisplayName, getTarotCardSearchAliases } = window.TarotCardImages || {};
|
const { resolveTarotCardImage, resolveTarotCardThumbnail, getTarotCardDisplayName, getTarotCardSearchAliases } = window.TarotCardImages || {};
|
||||||
const tarotHouseUi = window.TarotHouseUi || {};
|
const tarotHouseUi = window.TarotHouseUi || {};
|
||||||
const tarotRelationsUi = window.TarotRelationsUi || {};
|
const tarotRelationsUi = window.TarotRelationsUi || {};
|
||||||
const tarotCardDerivations = window.TarotCardDerivations || {};
|
const tarotCardDerivations = window.TarotCardDerivations || {};
|
||||||
@@ -339,6 +339,7 @@
|
|||||||
getMonthRefsByCardId: () => state.monthRefsByCardId,
|
getMonthRefsByCardId: () => state.monthRefsByCardId,
|
||||||
getMagickDataset: () => state.magickDataset,
|
getMagickDataset: () => state.magickDataset,
|
||||||
resolveTarotCardImage,
|
resolveTarotCardImage,
|
||||||
|
resolveTarotCardThumbnail,
|
||||||
getDisplayCardName,
|
getDisplayCardName,
|
||||||
buildTypeLabel,
|
buildTypeLabel,
|
||||||
clearChildren,
|
clearChildren,
|
||||||
@@ -751,6 +752,7 @@
|
|||||||
|
|
||||||
tarotHouseUi.init?.({
|
tarotHouseUi.init?.({
|
||||||
resolveTarotCardImage,
|
resolveTarotCardImage,
|
||||||
|
resolveTarotCardThumbnail,
|
||||||
getDisplayCardName,
|
getDisplayCardName,
|
||||||
clearChildren,
|
clearChildren,
|
||||||
normalizeTarotCardLookupName,
|
normalizeTarotCardLookupName,
|
||||||
@@ -934,11 +936,16 @@
|
|||||||
|
|
||||||
if (elements.tarotDetailImageEl) {
|
if (elements.tarotDetailImageEl) {
|
||||||
elements.tarotDetailImageEl.addEventListener("click", () => {
|
elements.tarotDetailImageEl.addEventListener("click", () => {
|
||||||
const src = elements.tarotDetailImageEl.getAttribute("src") || "";
|
if (elements.tarotDetailImageEl.style.display === "none" || !state.selectedCardId) {
|
||||||
if (!src || elements.tarotDetailImageEl.style.display === "none") {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
window.TarotUiLightbox?.open?.(src, elements.tarotDetailImageEl.alt || "Tarot card enlarged image");
|
|
||||||
|
const request = buildLightboxCardRequestById(state.selectedCardId);
|
||||||
|
if (!request?.src) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.TarotUiLightbox?.open?.(request);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
583
package-lock.json
generated
583
package-lock.json
generated
@@ -1,9 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "tarot-clean",
|
"name": "TaroTime",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/amiri": "^5.2.8",
|
"@fontsource/amiri": "^5.2.8",
|
||||||
"@fontsource/noto-naskh-arabic": "^5.2.11",
|
"@fontsource/noto-naskh-arabic": "^5.2.11",
|
||||||
@@ -15,7 +16,19 @@
|
|||||||
"suncalc": "^1.9.0"
|
"suncalc": "^1.9.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"http-server": "^14.1.1"
|
"http-server": "^14.1.1",
|
||||||
|
"sharp": "^0.34.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emnapi/runtime": {
|
||||||
|
"version": "1.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz",
|
||||||
|
"integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@fontsource/amiri": {
|
"node_modules/@fontsource/amiri": {
|
||||||
@@ -63,6 +76,496 @@
|
|||||||
"url": "https://github.com/sponsors/ayuhito"
|
"url": "https://github.com/sponsors/ayuhito"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@img/colour": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-darwin-arm64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-darwin-arm64": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-darwin-x64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-darwin-x64": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-darwin-arm64": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-darwin-x64": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linux-arm": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linux-arm64": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linux-ppc64": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linux-riscv64": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linux-s390x": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linux-x64": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linux-arm": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linux-arm": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linux-arm64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linux-arm64": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linux-ppc64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linux-ppc64": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linux-riscv64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linux-riscv64": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linux-s390x": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linux-s390x": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linux-x64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linux-x64": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linuxmusl-arm64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linuxmusl-x64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linuxmusl-x64": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-wasm32": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
|
||||||
|
"cpu": [
|
||||||
|
"wasm32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@emnapi/runtime": "^1.7.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-win32-arm64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-win32-ia32": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-win32-x64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@toast-ui/calendar": {
|
"node_modules/@toast-ui/calendar": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@toast-ui/calendar/-/calendar-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@toast-ui/calendar/-/calendar-2.1.3.tgz",
|
||||||
@@ -361,6 +864,16 @@
|
|||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/detect-libc": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/domexception": {
|
"node_modules/domexception": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
|
||||||
@@ -1053,6 +1566,64 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/semver": {
|
||||||
|
"version": "7.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||||
|
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sharp": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@img/colour": "^1.0.0",
|
||||||
|
"detect-libc": "^2.1.2",
|
||||||
|
"semver": "^7.7.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-darwin-arm64": "0.34.5",
|
||||||
|
"@img/sharp-darwin-x64": "0.34.5",
|
||||||
|
"@img/sharp-libvips-darwin-arm64": "1.2.4",
|
||||||
|
"@img/sharp-libvips-darwin-x64": "1.2.4",
|
||||||
|
"@img/sharp-libvips-linux-arm": "1.2.4",
|
||||||
|
"@img/sharp-libvips-linux-arm64": "1.2.4",
|
||||||
|
"@img/sharp-libvips-linux-ppc64": "1.2.4",
|
||||||
|
"@img/sharp-libvips-linux-riscv64": "1.2.4",
|
||||||
|
"@img/sharp-libvips-linux-s390x": "1.2.4",
|
||||||
|
"@img/sharp-libvips-linux-x64": "1.2.4",
|
||||||
|
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
|
||||||
|
"@img/sharp-libvips-linuxmusl-x64": "1.2.4",
|
||||||
|
"@img/sharp-linux-arm": "0.34.5",
|
||||||
|
"@img/sharp-linux-arm64": "0.34.5",
|
||||||
|
"@img/sharp-linux-ppc64": "0.34.5",
|
||||||
|
"@img/sharp-linux-riscv64": "0.34.5",
|
||||||
|
"@img/sharp-linux-s390x": "0.34.5",
|
||||||
|
"@img/sharp-linux-x64": "0.34.5",
|
||||||
|
"@img/sharp-linuxmusl-arm64": "0.34.5",
|
||||||
|
"@img/sharp-linuxmusl-x64": "0.34.5",
|
||||||
|
"@img/sharp-wasm32": "0.34.5",
|
||||||
|
"@img/sharp-win32-arm64": "0.34.5",
|
||||||
|
"@img/sharp-win32-ia32": "0.34.5",
|
||||||
|
"@img/sharp-win32-x64": "0.34.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/side-channel": {
|
"node_modules/side-channel": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
||||||
@@ -1190,6 +1761,14 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "0BSD",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/tui-date-picker": {
|
"node_modules/tui-date-picker": {
|
||||||
"version": "4.3.3",
|
"version": "4.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/tui-date-picker/-/tui-date-picker-4.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/tui-date-picker/-/tui-date-picker-4.3.3.tgz",
|
||||||
|
|||||||
@@ -10,13 +10,15 @@
|
|||||||
"suncalc": "^1.9.0"
|
"suncalc": "^1.9.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"http-server": "^14.1.1"
|
"http-server": "^14.1.1",
|
||||||
|
"sharp": "^0.34.5"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"generate:decks": "node scripts/generate-decks-registry.cjs",
|
"generate:decks": "node scripts/generate-decks-registry.cjs",
|
||||||
|
"generate:deck-thumbs": "node scripts/generate-deck-thumbnails.cjs",
|
||||||
"validate:decks": "node scripts/generate-decks-registry.cjs --validate-only --strict",
|
"validate:decks": "node scripts/generate-decks-registry.cjs --validate-only --strict",
|
||||||
"postinstall": "npm run generate:decks",
|
"postinstall": "npm run generate:decks && npm run generate:deck-thumbs",
|
||||||
"prestart": "npm run generate:decks",
|
"prestart": "npm run generate:decks && npm run generate:deck-thumbs",
|
||||||
"start": "npx http-server . -o index.html",
|
"start": "npx http-server . -o index.html",
|
||||||
"dev": "npm run start"
|
"dev": "npm run start"
|
||||||
}
|
}
|
||||||
|
|||||||
206
scripts/generate-deck-thumbnails.cjs
Normal file
206
scripts/generate-deck-thumbnails.cjs
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const sharp = require("sharp");
|
||||||
|
|
||||||
|
const projectRoot = path.resolve(__dirname, "..");
|
||||||
|
const decksRoot = path.join(projectRoot, "asset", "tarot deck");
|
||||||
|
const ignoredFolderNames = new Set(["template", "templates", "example", "examples"]);
|
||||||
|
const supportedImageExtensions = new Set([".png", ".jpg", ".jpeg", ".webp", ".avif", ".gif"]);
|
||||||
|
const defaultThumbnailConfig = {
|
||||||
|
root: "thumbs",
|
||||||
|
width: 240,
|
||||||
|
height: 360,
|
||||||
|
fit: "inside",
|
||||||
|
quality: 82
|
||||||
|
};
|
||||||
|
|
||||||
|
function isPlainObject(value) {
|
||||||
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function asNonEmptyString(value) {
|
||||||
|
const normalized = String(value || "").trim();
|
||||||
|
return normalized || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldIgnoreDeckFolder(folderName) {
|
||||||
|
const normalized = String(folderName || "").trim().toLowerCase();
|
||||||
|
if (!normalized) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized.startsWith("_") || normalized.startsWith(".") || ignoredFolderNames.has(normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readManifestJson(manifestPath) {
|
||||||
|
try {
|
||||||
|
const text = fs.readFileSync(manifestPath, "utf8");
|
||||||
|
const parsed = JSON.parse(text);
|
||||||
|
return parsed && typeof parsed === "object" ? parsed : null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeThumbnailConfig(rawConfig) {
|
||||||
|
if (rawConfig === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isPlainObject(rawConfig)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
root: asNonEmptyString(rawConfig.root) || defaultThumbnailConfig.root,
|
||||||
|
width: Number.isInteger(Number(rawConfig.width)) && Number(rawConfig.width) > 0
|
||||||
|
? Number(rawConfig.width)
|
||||||
|
: defaultThumbnailConfig.width,
|
||||||
|
height: Number.isInteger(Number(rawConfig.height)) && Number(rawConfig.height) > 0
|
||||||
|
? Number(rawConfig.height)
|
||||||
|
: defaultThumbnailConfig.height,
|
||||||
|
fit: asNonEmptyString(rawConfig.fit) || defaultThumbnailConfig.fit,
|
||||||
|
quality: Number.isInteger(Number(rawConfig.quality)) && Number(rawConfig.quality) >= 1 && Number(rawConfig.quality) <= 100
|
||||||
|
? Number(rawConfig.quality)
|
||||||
|
: defaultThumbnailConfig.quality
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureDirectory(dirPath) {
|
||||||
|
fs.mkdirSync(dirPath, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
function listSourceImages(deckFolderPath, thumbnailRoot) {
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
function walk(currentPath) {
|
||||||
|
const entries = fs.readdirSync(currentPath, { withFileTypes: true });
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
const entryPath = path.join(currentPath, entry.name);
|
||||||
|
const relativePath = path.relative(deckFolderPath, entryPath).replace(/\\/g, "/");
|
||||||
|
if (!relativePath) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
if (relativePath === thumbnailRoot || relativePath.startsWith(`${thumbnailRoot}/`)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
walk(entryPath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!entry.isFile()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!supportedImageExtensions.has(path.extname(entry.name).toLowerCase())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
results.push(relativePath);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
walk(deckFolderPath);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldRegenerateThumbnail(sourcePath, outputPath) {
|
||||||
|
if (!fs.existsSync(outputPath)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceStat = fs.statSync(sourcePath);
|
||||||
|
const outputStat = fs.statSync(outputPath);
|
||||||
|
return sourceStat.mtimeMs > outputStat.mtimeMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSharpPipeline(sourcePath, extension, config) {
|
||||||
|
let pipeline = sharp(sourcePath, { animated: extension === ".gif" }).rotate().resize({
|
||||||
|
width: config.width,
|
||||||
|
height: config.height,
|
||||||
|
fit: config.fit,
|
||||||
|
withoutEnlargement: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (extension === ".jpg" || extension === ".jpeg") {
|
||||||
|
pipeline = pipeline.jpeg({ quality: config.quality, mozjpeg: true });
|
||||||
|
} else if (extension === ".png") {
|
||||||
|
pipeline = pipeline.png({ quality: config.quality, compressionLevel: 9 });
|
||||||
|
} else if (extension === ".webp") {
|
||||||
|
pipeline = pipeline.webp({ quality: config.quality });
|
||||||
|
} else if (extension === ".avif") {
|
||||||
|
pipeline = pipeline.avif({ quality: config.quality, effort: 4 });
|
||||||
|
}
|
||||||
|
|
||||||
|
return pipeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateDeckThumbnails() {
|
||||||
|
const entries = fs.readdirSync(decksRoot, { withFileTypes: true });
|
||||||
|
let generatedCount = 0;
|
||||||
|
const warnings = [];
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (!entry.isDirectory() || shouldIgnoreDeckFolder(entry.name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deckFolderPath = path.join(decksRoot, entry.name);
|
||||||
|
const manifestPath = path.join(deckFolderPath, "deck.json");
|
||||||
|
if (!fs.existsSync(manifestPath)) {
|
||||||
|
warnings.push(`Skipped '${entry.name}': missing deck.json`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const manifest = readManifestJson(manifestPath);
|
||||||
|
if (!manifest) {
|
||||||
|
warnings.push(`Skipped '${entry.name}': deck.json is invalid JSON`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const thumbnailConfig = normalizeThumbnailConfig(manifest.thumbnails);
|
||||||
|
if (!thumbnailConfig) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const thumbnailRoot = thumbnailConfig.root.replace(/^\.\//, "").replace(/\/$/, "");
|
||||||
|
ensureDirectory(path.join(deckFolderPath, thumbnailRoot));
|
||||||
|
const sourceImages = listSourceImages(deckFolderPath, thumbnailRoot);
|
||||||
|
|
||||||
|
for (const relativePath of sourceImages) {
|
||||||
|
const sourcePath = path.join(deckFolderPath, relativePath);
|
||||||
|
const outputPath = path.join(deckFolderPath, thumbnailRoot, relativePath);
|
||||||
|
ensureDirectory(path.dirname(outputPath));
|
||||||
|
|
||||||
|
if (!shouldRegenerateThumbnail(sourcePath, outputPath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const extension = path.extname(sourcePath).toLowerCase();
|
||||||
|
try {
|
||||||
|
await buildSharpPipeline(sourcePath, extension, thumbnailConfig).toFile(outputPath);
|
||||||
|
generatedCount += 1;
|
||||||
|
} catch (error) {
|
||||||
|
warnings.push(`Failed '${entry.name}/${relativePath}': ${error instanceof Error ? error.message : "unknown error"}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { generatedCount, warnings };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const { generatedCount, warnings } = await generateDeckThumbnails();
|
||||||
|
console.log(`[deck-thumbs] Generated ${generatedCount} thumbnail${generatedCount === 1 ? "" : "s"}`);
|
||||||
|
warnings.forEach((warning) => {
|
||||||
|
console.warn(`[deck-thumbs] ${warning}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((error) => {
|
||||||
|
console.error(`[deck-thumbs] ${error instanceof Error ? error.message : "Unknown error"}`);
|
||||||
|
process.exitCode = 1;
|
||||||
|
});
|
||||||
@@ -8,6 +8,15 @@ const ignoredFolderNames = new Set(["template", "templates", "example", "example
|
|||||||
const tarotSuits = ["wands", "cups", "swords", "disks"];
|
const tarotSuits = ["wands", "cups", "swords", "disks"];
|
||||||
const majorTrumpNumbers = Array.from({ length: 22 }, (_, index) => index);
|
const majorTrumpNumbers = Array.from({ length: 22 }, (_, index) => index);
|
||||||
const expectedMinorCardCount = 56;
|
const expectedMinorCardCount = 56;
|
||||||
|
const defaultPipRankOrder = ["Ace", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"];
|
||||||
|
const defaultThumbnailConfig = {
|
||||||
|
root: "thumbs",
|
||||||
|
width: 240,
|
||||||
|
height: 360,
|
||||||
|
fit: "inside",
|
||||||
|
quality: 82
|
||||||
|
};
|
||||||
|
const supportedThumbnailFits = new Set(["contain", "cover", "fill", "inside", "outside"]);
|
||||||
const cardBackCandidateExtensions = ["webp", "png", "jpg", "jpeg", "avif", "gif"];
|
const cardBackCandidateExtensions = ["webp", "png", "jpg", "jpeg", "avif", "gif"];
|
||||||
|
|
||||||
function isPlainObject(value) {
|
function isPlainObject(value) {
|
||||||
@@ -61,6 +70,71 @@ function readManifestJson(manifestPath) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeThumbnailConfig(thumbnails) {
|
||||||
|
if (thumbnails == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thumbnails === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isPlainObject(thumbnails)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
root: asNonEmptyString(thumbnails.root) || defaultThumbnailConfig.root,
|
||||||
|
width: Number.isInteger(Number(thumbnails.width)) && Number(thumbnails.width) > 0
|
||||||
|
? Number(thumbnails.width)
|
||||||
|
: defaultThumbnailConfig.width,
|
||||||
|
height: Number.isInteger(Number(thumbnails.height)) && Number(thumbnails.height) > 0
|
||||||
|
? Number(thumbnails.height)
|
||||||
|
: defaultThumbnailConfig.height,
|
||||||
|
fit: asNonEmptyString(thumbnails.fit) || defaultThumbnailConfig.fit,
|
||||||
|
quality: Number.isInteger(Number(thumbnails.quality)) && Number(thumbnails.quality) >= 1 && Number(thumbnails.quality) <= 100
|
||||||
|
? Number(thumbnails.quality)
|
||||||
|
: defaultThumbnailConfig.quality
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateThumbnailConfig(thumbnails) {
|
||||||
|
if (thumbnails == null || thumbnails === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isPlainObject(thumbnails)) {
|
||||||
|
return "thumbnails must be an object or false when provided";
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = asNonEmptyString(thumbnails.root) || defaultThumbnailConfig.root;
|
||||||
|
if (!root || root.startsWith("/") || isRemoteAssetPath(root)) {
|
||||||
|
return "thumbnails.root must be a relative folder name";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thumbnails.width != null && (!Number.isInteger(Number(thumbnails.width)) || Number(thumbnails.width) <= 0)) {
|
||||||
|
return "thumbnails.width must be a positive integer when provided";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thumbnails.height != null && (!Number.isInteger(Number(thumbnails.height)) || Number(thumbnails.height) <= 0)) {
|
||||||
|
return "thumbnails.height must be a positive integer when provided";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thumbnails.quality != null) {
|
||||||
|
const quality = Number(thumbnails.quality);
|
||||||
|
if (!Number.isInteger(quality) || quality < 1 || quality > 100) {
|
||||||
|
return "thumbnails.quality must be an integer between 1 and 100 when provided";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fit = asNonEmptyString(thumbnails.fit) || defaultThumbnailConfig.fit;
|
||||||
|
if (!supportedThumbnailFits.has(fit)) {
|
||||||
|
return `thumbnails.fit must be one of ${Array.from(supportedThumbnailFits).join(", ")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function hasNonEmptyStringMapEntries(value) {
|
function hasNonEmptyStringMapEntries(value) {
|
||||||
if (!isPlainObject(value)) {
|
if (!isPlainObject(value)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -69,12 +143,20 @@ function hasNonEmptyStringMapEntries(value) {
|
|||||||
return Object.values(value).some((entryValue) => String(entryValue || "").trim());
|
return Object.values(value).some((entryValue) => String(entryValue || "").trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasRankResolver(minorRule) {
|
function getNormalizedRankOrder(rankSource, fallbackRankOrder = []) {
|
||||||
|
const explicitRankOrder = Array.isArray(rankSource?.rankOrder) ? rankSource.rankOrder : [];
|
||||||
|
const rankOrderSource = explicitRankOrder.length ? explicitRankOrder : fallbackRankOrder;
|
||||||
|
return rankOrderSource
|
||||||
|
.map((entry) => String(entry || "").trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasRankResolver(minorRule, fallbackRankOrder = []) {
|
||||||
if (!isPlainObject(minorRule)) {
|
if (!isPlainObject(minorRule)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rankOrder = Array.isArray(minorRule.rankOrder) ? minorRule.rankOrder.filter((entry) => String(entry || "").trim()) : [];
|
const rankOrder = getNormalizedRankOrder(minorRule, fallbackRankOrder);
|
||||||
if (rankOrder.length) {
|
if (rankOrder.length) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -166,6 +248,27 @@ function validateMinorsRule(minors) {
|
|||||||
return "minors.mode is required";
|
return "minors.mode is required";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mode === "split-number-template") {
|
||||||
|
const courtsError = validateMinorNumberTemplateGroup(minors.courts, {
|
||||||
|
label: "minors.courts",
|
||||||
|
expectedRankCount: 4
|
||||||
|
});
|
||||||
|
if (courtsError) {
|
||||||
|
return courtsError;
|
||||||
|
}
|
||||||
|
|
||||||
|
const smallsError = validateMinorNumberTemplateGroup(minors.smalls, {
|
||||||
|
label: "minors.smalls",
|
||||||
|
expectedRankCount: defaultPipRankOrder.length,
|
||||||
|
fallbackRankOrder: defaultPipRankOrder
|
||||||
|
});
|
||||||
|
if (smallsError) {
|
||||||
|
return smallsError;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!hasRankResolver(minors)) {
|
if (!hasRankResolver(minors)) {
|
||||||
return "minors must define rankOrder or rankIndexByKey";
|
return "minors must define rankOrder or rankIndexByKey";
|
||||||
}
|
}
|
||||||
@@ -205,6 +308,38 @@ function validateMinorsRule(minors) {
|
|||||||
return `unsupported minors.mode '${mode}'`;
|
return `unsupported minors.mode '${mode}'`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validateMinorNumberTemplateGroup(groupRule, options = {}) {
|
||||||
|
const label = String(options.label || "minor group");
|
||||||
|
const expectedRankCount = Number(options.expectedRankCount);
|
||||||
|
const fallbackRankOrder = Array.isArray(options.fallbackRankOrder) ? options.fallbackRankOrder : [];
|
||||||
|
|
||||||
|
if (!isPlainObject(groupRule)) {
|
||||||
|
return `${label} must be an object`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasRankResolver(groupRule, fallbackRankOrder)) {
|
||||||
|
return `${label} must define rankOrder or rankIndexByKey`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getRankEntries(groupRule, fallbackRankOrder).length !== expectedRankCount) {
|
||||||
|
return `${label} must define exactly ${expectedRankCount} ranks`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasSuitNumberMap(groupRule.suitBase)) {
|
||||||
|
return `${label}.suitBase must define numeric bases for wands, cups, swords, and disks`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!asNonEmptyString(groupRule.template)) {
|
||||||
|
return `${label}.template is required`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupRule.numberPad != null && !Number.isInteger(Number(groupRule.numberPad))) {
|
||||||
|
return `${label}.numberPad must be an integer when provided`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function validateDeckManifest(manifest) {
|
function validateDeckManifest(manifest) {
|
||||||
const errors = [];
|
const errors = [];
|
||||||
|
|
||||||
@@ -250,15 +385,16 @@ function validateDeckManifest(manifest) {
|
|||||||
errors.push("cardBack must be a non-empty string when provided");
|
errors.push("cardBack must be a non-empty string when provided");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const thumbnailsError = validateThumbnailConfig(manifest.thumbnails);
|
||||||
|
if (thumbnailsError) {
|
||||||
|
errors.push(thumbnailsError);
|
||||||
|
}
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRankEntries(minors) {
|
function getRankEntries(minors, fallbackRankOrder = []) {
|
||||||
const rankOrder = Array.isArray(minors?.rankOrder)
|
const rankOrder = getNormalizedRankOrder(minors, fallbackRankOrder);
|
||||||
? minors.rankOrder
|
|
||||||
.map((entry) => String(entry || "").trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
if (rankOrder.length) {
|
if (rankOrder.length) {
|
||||||
return rankOrder.map((rankWord, rankIndex) => ({
|
return rankOrder.map((rankWord, rankIndex) => ({
|
||||||
@@ -311,6 +447,13 @@ function getReferencedMinorFiles(manifest) {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mode === "split-number-template") {
|
||||||
|
return [
|
||||||
|
...getReferencedSplitMinorGroupFiles(minors.courts),
|
||||||
|
...getReferencedSplitMinorGroupFiles(minors.smalls, defaultPipRankOrder)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
const rankEntries = getRankEntries(minors);
|
const rankEntries = getRankEntries(minors);
|
||||||
if (!rankEntries.length) {
|
if (!rankEntries.length) {
|
||||||
return [];
|
return [];
|
||||||
@@ -367,6 +510,27 @@ function getReferencedMinorFiles(manifest) {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getReferencedSplitMinorGroupFiles(groupRule, fallbackRankOrder = []) {
|
||||||
|
const rankEntries = getRankEntries(groupRule, fallbackRankOrder);
|
||||||
|
if (!rankEntries.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const template = String(groupRule?.template || "{number}.png");
|
||||||
|
const numberPad = Number.isInteger(Number(groupRule?.numberPad)) ? Number(groupRule.numberPad) : 2;
|
||||||
|
|
||||||
|
return tarotSuits.flatMap((suitId) => {
|
||||||
|
const suitBase = Number(groupRule?.suitBase?.[suitId]);
|
||||||
|
return rankEntries.map((entry) => applyTemplate(template, {
|
||||||
|
number: String(suitBase + entry.rankIndex).padStart(numberPad, "0"),
|
||||||
|
rank: entry.rankWord,
|
||||||
|
rankKey: entry.rankKey,
|
||||||
|
suitId,
|
||||||
|
index: entry.rankIndex
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function getReferencedCardBackFiles(manifest) {
|
function getReferencedCardBackFiles(manifest) {
|
||||||
const cardBack = asNonEmptyString(manifest?.cardBack);
|
const cardBack = asNonEmptyString(manifest?.cardBack);
|
||||||
if (!cardBack || isRemoteAssetPath(cardBack)) {
|
if (!cardBack || isRemoteAssetPath(cardBack)) {
|
||||||
@@ -491,6 +655,7 @@ function compileDeckRegistry() {
|
|||||||
const label = labelFromManifest || folderName;
|
const label = labelFromManifest || folderName;
|
||||||
const basePath = `asset/tarot deck/${folderName}`;
|
const basePath = `asset/tarot deck/${folderName}`;
|
||||||
const cardBackPath = detectDeckCardBackRelativePath(folderName, manifest);
|
const cardBackPath = detectDeckCardBackRelativePath(folderName, manifest);
|
||||||
|
const thumbnailConfig = normalizeThumbnailConfig(manifest.thumbnails);
|
||||||
|
|
||||||
if (seenIds.has(id)) {
|
if (seenIds.has(id)) {
|
||||||
warnings.push(`Skipped '${folderName}': duplicate deck id '${id}'`);
|
warnings.push(`Skipped '${folderName}': duplicate deck id '${id}'`);
|
||||||
@@ -504,6 +669,7 @@ function compileDeckRegistry() {
|
|||||||
label,
|
label,
|
||||||
basePath,
|
basePath,
|
||||||
manifestPath: `${basePath}/deck.json`,
|
manifestPath: `${basePath}/deck.json`,
|
||||||
|
...(thumbnailConfig && thumbnailConfig !== false ? { thumbnailRoot: thumbnailConfig.root } : {}),
|
||||||
...(cardBackPath ? { cardBackPath } : {})
|
...(cardBackPath ? { cardBackPath } : {})
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user