added overlay function for tarot cards
This commit is contained in:
248
app/ui-tarot.js
248
app/ui-tarot.js
@@ -12,6 +12,25 @@
|
||||
filteredCards: [],
|
||||
searchQuery: "",
|
||||
selectedCardId: "",
|
||||
houseFocusMode: false,
|
||||
houseTopCardsVisible: true,
|
||||
houseTopInfoModes: {
|
||||
hebrew: true,
|
||||
planet: true,
|
||||
zodiac: true,
|
||||
trump: true,
|
||||
path: true
|
||||
},
|
||||
houseBottomCardsVisible: true,
|
||||
houseBottomInfoModes: {
|
||||
zodiac: true,
|
||||
decan: true,
|
||||
month: true,
|
||||
ruler: true,
|
||||
date: false
|
||||
},
|
||||
houseExportInProgress: false,
|
||||
houseExportFormat: "png",
|
||||
magickDataset: null,
|
||||
referenceData: null,
|
||||
monthRefsByCardId: new Map(),
|
||||
@@ -248,10 +267,34 @@
|
||||
tarotDetailIChingEl: document.getElementById("tarot-detail-iching"),
|
||||
tarotDetailCalendarEl: document.getElementById("tarot-detail-calendar"),
|
||||
tarotKabPathEl: document.getElementById("tarot-kab-path"),
|
||||
tarotHouseOfCardsEl: document.getElementById("tarot-house-of-cards")
|
||||
tarotHouseOfCardsEl: document.getElementById("tarot-house-of-cards"),
|
||||
tarotBrowseViewEl: document.getElementById("tarot-browse-view"),
|
||||
tarotHouseTopCardsVisibleEl: document.getElementById("tarot-house-top-cards-visible"),
|
||||
tarotHouseTopInfoHebrewEl: document.getElementById("tarot-house-top-info-hebrew"),
|
||||
tarotHouseTopInfoPlanetEl: document.getElementById("tarot-house-top-info-planet"),
|
||||
tarotHouseTopInfoZodiacEl: document.getElementById("tarot-house-top-info-zodiac"),
|
||||
tarotHouseTopInfoTrumpEl: document.getElementById("tarot-house-top-info-trump"),
|
||||
tarotHouseTopInfoPathEl: document.getElementById("tarot-house-top-info-path"),
|
||||
tarotHouseBottomCardsVisibleEl: document.getElementById("tarot-house-bottom-cards-visible"),
|
||||
tarotHouseBottomInfoZodiacEl: document.getElementById("tarot-house-bottom-info-zodiac"),
|
||||
tarotHouseBottomInfoDecanEl: document.getElementById("tarot-house-bottom-info-decan"),
|
||||
tarotHouseBottomInfoMonthEl: document.getElementById("tarot-house-bottom-info-month"),
|
||||
tarotHouseBottomInfoRulerEl: document.getElementById("tarot-house-bottom-info-ruler"),
|
||||
tarotHouseBottomInfoDateEl: document.getElementById("tarot-house-bottom-info-date"),
|
||||
tarotHouseFocusToggleEl: document.getElementById("tarot-house-focus-toggle"),
|
||||
tarotHouseExportEl: document.getElementById("tarot-house-export"),
|
||||
tarotHouseExportWebpEl: document.getElementById("tarot-house-export-webp")
|
||||
};
|
||||
}
|
||||
|
||||
function setHouseBottomInfoCheckboxState(checkbox, enabled) {
|
||||
if (!checkbox) {
|
||||
return;
|
||||
}
|
||||
checkbox.checked = Boolean(enabled);
|
||||
checkbox.disabled = Boolean(state.houseExportInProgress);
|
||||
}
|
||||
|
||||
function normalizeRelationId(value) {
|
||||
return String(value || "")
|
||||
.trim()
|
||||
@@ -485,6 +528,76 @@
|
||||
tarotHouseUi.render?.(elements);
|
||||
}
|
||||
|
||||
function syncHouseControls(elements) {
|
||||
if (elements?.tarotBrowseViewEl) {
|
||||
elements.tarotBrowseViewEl.classList.toggle("is-house-focus", Boolean(state.houseFocusMode));
|
||||
}
|
||||
|
||||
if (elements?.tarotHouseTopCardsVisibleEl) {
|
||||
elements.tarotHouseTopCardsVisibleEl.checked = Boolean(state.houseTopCardsVisible);
|
||||
elements.tarotHouseTopCardsVisibleEl.disabled = Boolean(state.houseExportInProgress);
|
||||
}
|
||||
|
||||
setHouseBottomInfoCheckboxState(elements?.tarotHouseTopInfoHebrewEl, state.houseTopInfoModes.hebrew);
|
||||
setHouseBottomInfoCheckboxState(elements?.tarotHouseTopInfoPlanetEl, state.houseTopInfoModes.planet);
|
||||
setHouseBottomInfoCheckboxState(elements?.tarotHouseTopInfoZodiacEl, state.houseTopInfoModes.zodiac);
|
||||
setHouseBottomInfoCheckboxState(elements?.tarotHouseTopInfoTrumpEl, state.houseTopInfoModes.trump);
|
||||
setHouseBottomInfoCheckboxState(elements?.tarotHouseTopInfoPathEl, state.houseTopInfoModes.path);
|
||||
|
||||
if (elements?.tarotHouseBottomCardsVisibleEl) {
|
||||
elements.tarotHouseBottomCardsVisibleEl.checked = Boolean(state.houseBottomCardsVisible);
|
||||
elements.tarotHouseBottomCardsVisibleEl.disabled = Boolean(state.houseExportInProgress);
|
||||
}
|
||||
|
||||
setHouseBottomInfoCheckboxState(elements?.tarotHouseBottomInfoZodiacEl, state.houseBottomInfoModes.zodiac);
|
||||
setHouseBottomInfoCheckboxState(elements?.tarotHouseBottomInfoDecanEl, state.houseBottomInfoModes.decan);
|
||||
setHouseBottomInfoCheckboxState(elements?.tarotHouseBottomInfoMonthEl, state.houseBottomInfoModes.month);
|
||||
setHouseBottomInfoCheckboxState(elements?.tarotHouseBottomInfoRulerEl, state.houseBottomInfoModes.ruler);
|
||||
setHouseBottomInfoCheckboxState(elements?.tarotHouseBottomInfoDateEl, state.houseBottomInfoModes.date);
|
||||
|
||||
if (elements?.tarotHouseFocusToggleEl) {
|
||||
elements.tarotHouseFocusToggleEl.setAttribute("aria-pressed", state.houseFocusMode ? "true" : "false");
|
||||
elements.tarotHouseFocusToggleEl.textContent = state.houseFocusMode ? "Show Full Tarot" : "Focus House";
|
||||
}
|
||||
|
||||
if (elements?.tarotHouseExportEl) {
|
||||
elements.tarotHouseExportEl.disabled = Boolean(state.houseExportInProgress);
|
||||
elements.tarotHouseExportEl.textContent = state.houseExportInProgress ? "Exporting..." : "Export PNG";
|
||||
}
|
||||
|
||||
if (elements?.tarotHouseExportWebpEl) {
|
||||
const supportsWebp = tarotHouseUi.isExportFormatSupported?.("webp") === true;
|
||||
elements.tarotHouseExportWebpEl.disabled = Boolean(state.houseExportInProgress) || !supportsWebp;
|
||||
elements.tarotHouseExportWebpEl.hidden = !supportsWebp;
|
||||
elements.tarotHouseExportWebpEl.textContent = state.houseExportInProgress && state.houseExportFormat === "webp"
|
||||
? "Exporting..."
|
||||
: "Export WebP";
|
||||
if (supportsWebp) {
|
||||
elements.tarotHouseExportWebpEl.title = "Smaller file size, but not guaranteed lossless like PNG.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function exportHouseOfCards(elements, format = "png") {
|
||||
if (state.houseExportInProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.houseExportInProgress = true;
|
||||
state.houseExportFormat = format;
|
||||
syncHouseControls(elements);
|
||||
|
||||
try {
|
||||
await tarotHouseUi.exportImage?.(format);
|
||||
} catch (error) {
|
||||
window.alert(error instanceof Error ? error.message : "Unable to export the House of Cards image.");
|
||||
} finally {
|
||||
state.houseExportInProgress = false;
|
||||
state.houseExportFormat = "png";
|
||||
syncHouseControls(elements);
|
||||
}
|
||||
}
|
||||
|
||||
function buildTypeLabel(card) {
|
||||
return tarotCardDerivationsUi.buildTypeLabel(card);
|
||||
}
|
||||
@@ -566,6 +679,35 @@
|
||||
renderDetail(card, elements);
|
||||
}
|
||||
|
||||
function scrollCardIntoView(cardIdToReveal, elements) {
|
||||
elements?.tarotCardListEl
|
||||
?.querySelector(`[data-card-id="${cardIdToReveal}"]`)
|
||||
?.scrollIntoView({ block: "nearest" });
|
||||
}
|
||||
|
||||
function buildLightboxCardRequestById(cardIdToResolve) {
|
||||
const card = state.cards.find((entry) => entry.id === cardIdToResolve);
|
||||
if (!card) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const src = typeof resolveTarotCardImage === "function"
|
||||
? resolveTarotCardImage(card.name)
|
||||
: "";
|
||||
if (!src) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const label = getDisplayCardName(card) || card.name || "Tarot card enlarged image";
|
||||
return {
|
||||
src,
|
||||
altText: label,
|
||||
label,
|
||||
cardId: card.id,
|
||||
compareDetails: tarotDetailRenderer.buildCompareDetails?.(card) || []
|
||||
};
|
||||
}
|
||||
|
||||
function renderList(elements) {
|
||||
if (!elements?.tarotCardListEl) {
|
||||
return;
|
||||
@@ -613,8 +755,32 @@
|
||||
clearChildren,
|
||||
normalizeTarotCardLookupName,
|
||||
selectCardById,
|
||||
openCardLightbox: (src, altText, options = {}) => {
|
||||
const cardId = String(options?.cardId || "").trim();
|
||||
const primaryCardRequest = cardId ? buildLightboxCardRequestById(cardId) : null;
|
||||
window.TarotUiLightbox?.open?.({
|
||||
src: primaryCardRequest?.src || src,
|
||||
altText: primaryCardRequest?.altText || altText || "Tarot card enlarged image",
|
||||
label: primaryCardRequest?.label || altText || "Tarot card enlarged image",
|
||||
cardId: primaryCardRequest?.cardId || cardId,
|
||||
compareDetails: primaryCardRequest?.compareDetails || [],
|
||||
allowOverlayCompare: true,
|
||||
sequenceIds: state.cards.map((card) => card.id),
|
||||
resolveCardById: buildLightboxCardRequestById,
|
||||
onSelectCardId: (nextCardId) => {
|
||||
const latestElements = getElements();
|
||||
selectCardById(nextCardId, latestElements);
|
||||
scrollCardIntoView(nextCardId, latestElements);
|
||||
}
|
||||
});
|
||||
},
|
||||
isHouseFocusMode: () => state.houseFocusMode,
|
||||
getCards: () => state.cards,
|
||||
getSelectedCardId: () => state.selectedCardId
|
||||
getSelectedCardId: () => state.selectedCardId,
|
||||
getHouseTopCardsVisible: () => state.houseTopCardsVisible,
|
||||
getHouseTopInfoModes: () => ({ ...state.houseTopInfoModes }),
|
||||
getHouseBottomCardsVisible: () => state.houseBottomCardsVisible,
|
||||
getHouseBottomInfoModes: () => ({ ...state.houseBottomInfoModes })
|
||||
});
|
||||
|
||||
const elements = getElements();
|
||||
@@ -623,6 +789,7 @@
|
||||
state.monthRefsByCardId = buildMonthReferencesByCard(referenceData, state.cards);
|
||||
state.courtCardByDecanId = buildCourtCardByDecanId(state.cards);
|
||||
renderHouseOfCards(elements);
|
||||
syncHouseControls(elements);
|
||||
if (state.selectedCardId) {
|
||||
const selected = state.cards.find((card) => card.id === state.selectedCardId);
|
||||
if (selected) {
|
||||
@@ -652,6 +819,7 @@
|
||||
state.filteredCards = [...cards];
|
||||
renderList(elements);
|
||||
renderHouseOfCards(elements);
|
||||
syncHouseControls(elements);
|
||||
|
||||
if (cards.length > 0) {
|
||||
selectCardById(cards[0].id, elements);
|
||||
@@ -695,6 +863,75 @@
|
||||
});
|
||||
}
|
||||
|
||||
if (elements.tarotHouseFocusToggleEl) {
|
||||
elements.tarotHouseFocusToggleEl.addEventListener("click", () => {
|
||||
state.houseFocusMode = !state.houseFocusMode;
|
||||
syncHouseControls(elements);
|
||||
});
|
||||
}
|
||||
|
||||
if (elements.tarotHouseTopCardsVisibleEl) {
|
||||
elements.tarotHouseTopCardsVisibleEl.addEventListener("change", () => {
|
||||
state.houseTopCardsVisible = Boolean(elements.tarotHouseTopCardsVisibleEl.checked);
|
||||
renderHouseOfCards(elements);
|
||||
syncHouseControls(elements);
|
||||
});
|
||||
}
|
||||
|
||||
[
|
||||
[elements.tarotHouseTopInfoHebrewEl, "hebrew"],
|
||||
[elements.tarotHouseTopInfoPlanetEl, "planet"],
|
||||
[elements.tarotHouseTopInfoZodiacEl, "zodiac"],
|
||||
[elements.tarotHouseTopInfoTrumpEl, "trump"],
|
||||
[elements.tarotHouseTopInfoPathEl, "path"]
|
||||
].forEach(([checkbox, key]) => {
|
||||
if (!checkbox) {
|
||||
return;
|
||||
}
|
||||
checkbox.addEventListener("change", () => {
|
||||
state.houseTopInfoModes[key] = Boolean(checkbox.checked);
|
||||
renderHouseOfCards(elements);
|
||||
syncHouseControls(elements);
|
||||
});
|
||||
});
|
||||
|
||||
if (elements.tarotHouseBottomCardsVisibleEl) {
|
||||
elements.tarotHouseBottomCardsVisibleEl.addEventListener("change", () => {
|
||||
state.houseBottomCardsVisible = Boolean(elements.tarotHouseBottomCardsVisibleEl.checked);
|
||||
renderHouseOfCards(elements);
|
||||
syncHouseControls(elements);
|
||||
});
|
||||
}
|
||||
|
||||
[
|
||||
[elements.tarotHouseBottomInfoZodiacEl, "zodiac"],
|
||||
[elements.tarotHouseBottomInfoDecanEl, "decan"],
|
||||
[elements.tarotHouseBottomInfoMonthEl, "month"],
|
||||
[elements.tarotHouseBottomInfoRulerEl, "ruler"],
|
||||
[elements.tarotHouseBottomInfoDateEl, "date"]
|
||||
].forEach(([checkbox, key]) => {
|
||||
if (!checkbox) {
|
||||
return;
|
||||
}
|
||||
checkbox.addEventListener("change", () => {
|
||||
state.houseBottomInfoModes[key] = Boolean(checkbox.checked);
|
||||
renderHouseOfCards(elements);
|
||||
syncHouseControls(elements);
|
||||
});
|
||||
});
|
||||
|
||||
if (elements.tarotHouseExportEl) {
|
||||
elements.tarotHouseExportEl.addEventListener("click", () => {
|
||||
exportHouseOfCards(elements, "png");
|
||||
});
|
||||
}
|
||||
|
||||
if (elements.tarotHouseExportWebpEl) {
|
||||
elements.tarotHouseExportWebpEl.addEventListener("click", () => {
|
||||
exportHouseOfCards(elements, "webp");
|
||||
});
|
||||
}
|
||||
|
||||
if (elements.tarotDetailImageEl) {
|
||||
elements.tarotDetailImageEl.addEventListener("click", () => {
|
||||
const src = elements.tarotDetailImageEl.getAttribute("src") || "";
|
||||
@@ -714,8 +951,7 @@
|
||||
const card = state.cards.find(c => c.arcana === "Major" && c.number === trumpNumber);
|
||||
if (!card) return;
|
||||
selectCardById(card.id, el);
|
||||
const listItem = el.tarotCardListEl?.querySelector(`[data-card-id="${card.id}"]`);
|
||||
listItem?.scrollIntoView({ block: "nearest" });
|
||||
scrollCardIntoView(card.id, el);
|
||||
}
|
||||
|
||||
function selectCardByName(name) {
|
||||
@@ -725,9 +961,7 @@
|
||||
const card = state.cards.find((entry) => normalizeTarotCardLookupName(entry.name) === needle);
|
||||
if (!card) return;
|
||||
selectCardById(card.id, el);
|
||||
el.tarotCardListEl
|
||||
?.querySelector(`[data-card-id="${card.id}"]`)
|
||||
?.scrollIntoView({ block: "nearest" });
|
||||
scrollCardIntoView(card.id, el);
|
||||
}
|
||||
|
||||
window.TarotSectionUi = {
|
||||
|
||||
Reference in New Issue
Block a user