various ui improvements, including a new sequence nav component and a new kabbalah detail view
This commit is contained in:
+227
-12
@@ -47,6 +47,7 @@
|
||||
courtCardByDecanId: new Map(),
|
||||
loadingPromise: null
|
||||
};
|
||||
let detailNavigator = null;
|
||||
|
||||
const TAROT_TRUMP_NUMBER_BY_NAME = {
|
||||
"the fool": 0,
|
||||
@@ -255,9 +256,14 @@
|
||||
tarotDetailImageEl: document.getElementById("tarot-detail-image"),
|
||||
tarotDetailNameEl: document.getElementById("tarot-detail-name"),
|
||||
tarotDetailTypeEl: document.getElementById("tarot-detail-type"),
|
||||
tarotDetailPrevEl: document.getElementById("tarot-detail-prev"),
|
||||
tarotDetailPositionEl: document.getElementById("tarot-detail-position"),
|
||||
tarotDetailNextEl: document.getElementById("tarot-detail-next"),
|
||||
tarotDetailSummaryEl: document.getElementById("tarot-detail-summary"),
|
||||
tarotDetailUprightEl: document.getElementById("tarot-detail-upright"),
|
||||
tarotDetailReversedEl: document.getElementById("tarot-detail-reversed"),
|
||||
tarotMetaDeckGalleryCardEl: document.getElementById("tarot-meta-deck-gallery-card"),
|
||||
tarotDetailDeckGalleryEl: document.getElementById("tarot-detail-deck-gallery"),
|
||||
tarotMetaMeaningCardEl: document.getElementById("tarot-meta-meaning-card"),
|
||||
tarotDetailMeaningEl: document.getElementById("tarot-detail-meaning"),
|
||||
tarotDetailKeywordsEl: document.getElementById("tarot-detail-keywords"),
|
||||
@@ -355,6 +361,8 @@
|
||||
getMagickDataset: () => state.magickDataset,
|
||||
resolveTarotCardImage,
|
||||
resolveTarotCardThumbnail,
|
||||
getDeckVariantsForCard,
|
||||
openDeckVariantLightbox,
|
||||
getDisplayCardName,
|
||||
buildTypeLabel,
|
||||
clearChildren,
|
||||
@@ -523,11 +531,14 @@
|
||||
if (!state.filteredCards.some((card) => card.id === state.selectedCardId)) {
|
||||
if (state.filteredCards.length > 0) {
|
||||
selectCardById(state.filteredCards[0].id, elements);
|
||||
} else {
|
||||
syncDetailNavigation(elements);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
updateListSelection(elements);
|
||||
syncDetailNavigation(elements);
|
||||
}
|
||||
|
||||
function clearChildren(element) {
|
||||
@@ -723,6 +734,62 @@
|
||||
});
|
||||
}
|
||||
|
||||
function getCardSequenceState() {
|
||||
const total = state.filteredCards.length;
|
||||
const currentIndex = state.filteredCards.findIndex((card) => card.id === state.selectedCardId);
|
||||
|
||||
return {
|
||||
total,
|
||||
currentIndex,
|
||||
previousId: currentIndex > 0 ? state.filteredCards[currentIndex - 1].id : "",
|
||||
nextId: currentIndex >= 0 && currentIndex < total - 1 ? state.filteredCards[currentIndex + 1].id : ""
|
||||
};
|
||||
}
|
||||
|
||||
function getDetailNavigator() {
|
||||
if (detailNavigator || typeof window.TarotSequenceNav?.createSequenceNavigator !== "function") {
|
||||
return detailNavigator;
|
||||
}
|
||||
|
||||
detailNavigator = window.TarotSequenceNav.createSequenceNavigator({
|
||||
getElements,
|
||||
isActive: (elements) => Boolean(elements?.tarotSectionEl && elements.tarotSectionEl.hidden === false),
|
||||
getSequenceState: getCardSequenceState,
|
||||
getPrevButton: (elements) => elements?.tarotDetailPrevEl,
|
||||
getNextButton: (elements) => elements?.tarotDetailNextEl,
|
||||
getPositionEl: (elements) => elements?.tarotDetailPositionEl,
|
||||
formatPositionText: ({ total, currentIndex }) => {
|
||||
if (total > 0 && currentIndex >= 0) {
|
||||
const suffix = state.searchQuery ? " shown" : "";
|
||||
return `${currentIndex + 1} of ${total}${suffix}`;
|
||||
}
|
||||
|
||||
return total > 0 ? `${total} cards` : "No cards";
|
||||
},
|
||||
selectTarget: (targetId, elements) => {
|
||||
selectCardById(targetId, elements);
|
||||
return true;
|
||||
},
|
||||
afterSelect: (targetId, elements) => {
|
||||
scrollCardIntoView(targetId, elements);
|
||||
}
|
||||
});
|
||||
|
||||
return detailNavigator;
|
||||
}
|
||||
|
||||
function syncDetailNavigation(elements) {
|
||||
getDetailNavigator()?.sync(elements);
|
||||
}
|
||||
|
||||
function selectAdjacentCard(offset, elements = getElements()) {
|
||||
return getDetailNavigator()?.step(offset, elements) === true;
|
||||
}
|
||||
|
||||
function bindKeyboardNavigation(elements) {
|
||||
getDetailNavigator()?.bind(elements);
|
||||
}
|
||||
|
||||
function selectCardById(cardIdToSelect, elements) {
|
||||
const card = state.cards.find((entry) => entry.id === cardIdToSelect);
|
||||
if (!card) {
|
||||
@@ -733,6 +800,7 @@
|
||||
updateListSelection(elements);
|
||||
updateHouseSelection(elements);
|
||||
renderDetail(card, elements);
|
||||
syncDetailNavigation(elements);
|
||||
}
|
||||
|
||||
function scrollCardIntoView(cardIdToReveal, elements) {
|
||||
@@ -758,6 +826,47 @@
|
||||
return Array.from(getRegisteredDeckOptionMap().values());
|
||||
}
|
||||
|
||||
function getDeckVariantsForCard(card) {
|
||||
if (!card) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const trumpNumber = Number.isFinite(Number(card?.number)) ? Number(card.number) : undefined;
|
||||
const activeDeckId = String(getActiveDeck?.() || "").trim().toLowerCase();
|
||||
|
||||
return getRegisteredDeckList()
|
||||
.map((deck) => {
|
||||
const deckId = String(deck?.id || "").trim().toLowerCase();
|
||||
if (!deckId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const imageOptions = { deckId, trumpNumber };
|
||||
const src = (typeof resolveTarotCardThumbnail === "function"
|
||||
? resolveTarotCardThumbnail(card.name, imageOptions)
|
||||
: "") || (typeof resolveTarotCardImage === "function"
|
||||
? resolveTarotCardImage(card.name, imageOptions)
|
||||
: "");
|
||||
|
||||
if (!src) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const displayName = (typeof getTarotCardDisplayName === "function"
|
||||
? String(getTarotCardDisplayName(card.name, imageOptions) || "").trim()
|
||||
: "") || getDisplayCardName(card) || card.name;
|
||||
|
||||
return {
|
||||
deckId,
|
||||
label: String(deck?.label || deckId).trim() || deckId,
|
||||
src,
|
||||
displayName,
|
||||
isActive: deckId === activeDeckId
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function buildDeckLightboxCardRequest(cardIdToResolve, deckIdToResolve = "") {
|
||||
const card = state.cards.find((entry) => entry.id === cardIdToResolve);
|
||||
if (!card) {
|
||||
@@ -790,8 +899,8 @@
|
||||
};
|
||||
}
|
||||
|
||||
function buildLightboxCardRequestById(cardIdToResolve) {
|
||||
const request = buildDeckLightboxCardRequest(cardIdToResolve, getActiveDeck?.() || "");
|
||||
function buildLightboxCardRequestById(cardIdToResolve, deckIdToResolve = "") {
|
||||
const request = buildDeckLightboxCardRequest(cardIdToResolve, deckIdToResolve || getActiveDeck?.() || "");
|
||||
if (!request?.src) {
|
||||
return null;
|
||||
}
|
||||
@@ -799,19 +908,67 @@
|
||||
return request;
|
||||
}
|
||||
|
||||
function getDefaultLightboxSequenceIds(cardIdToOpen = "") {
|
||||
const normalizedCardId = String(cardIdToOpen || "").trim();
|
||||
const filteredIds = state.filteredCards
|
||||
.map((card) => String(card?.id || "").trim())
|
||||
.filter(Boolean);
|
||||
|
||||
if (normalizedCardId && filteredIds.includes(normalizedCardId)) {
|
||||
return filteredIds;
|
||||
}
|
||||
|
||||
return state.cards
|
||||
.map((card) => String(card?.id || "").trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function getDeckVariantSequenceEntries(cardIdToResolve) {
|
||||
const card = state.cards.find((entry) => entry.id === cardIdToResolve);
|
||||
if (!card) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return getDeckVariantsForCard(card)
|
||||
.map((variant) => {
|
||||
const deckId = String(variant?.deckId || "").trim().toLowerCase();
|
||||
if (!deckId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
sequenceId: deckId,
|
||||
deckId
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function openCardLightboxById(cardIdToOpen, options = {}) {
|
||||
const normalizedCardId = String(cardIdToOpen || "").trim();
|
||||
if (!normalizedCardId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const primaryCardRequest = buildLightboxCardRequestById(normalizedCardId);
|
||||
const requestedDeckId = String(options?.deckId || getActiveDeck?.() || "").trim();
|
||||
const primaryCardRequest = buildLightboxCardRequestById(normalizedCardId, requestedDeckId);
|
||||
if (!primaryCardRequest?.src) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeDeckId = String(getActiveDeck?.() || primaryCardRequest.deckId || "").trim();
|
||||
const activeDeckId = String(primaryCardRequest.deckId || requestedDeckId || getActiveDeck?.() || "").trim();
|
||||
const availableCompareDecks = getRegisteredDeckList().filter((deck) => deck.id && deck.id !== activeDeckId);
|
||||
const requestedSequenceIds = Array.isArray(options?.sequenceIds)
|
||||
? options.sequenceIds
|
||||
.map((sequenceId) => String(sequenceId || "").trim())
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
const sequenceIds = requestedSequenceIds.length > 0
|
||||
? requestedSequenceIds
|
||||
: getDefaultLightboxSequenceIds(normalizedCardId);
|
||||
const resolveCardById = typeof options?.resolveCardById === "function"
|
||||
? options.resolveCardById
|
||||
: (nextCardId) => buildLightboxCardRequestById(nextCardId, activeDeckId);
|
||||
const onSelectCardId = typeof options?.onSelectCardId === "function"
|
||||
? options.onSelectCardId
|
||||
: (nextCardId) => {
|
||||
@@ -825,6 +982,7 @@
|
||||
altText: primaryCardRequest.altText,
|
||||
label: primaryCardRequest.label,
|
||||
cardId: primaryCardRequest.cardId,
|
||||
sequenceId: String(options?.sequenceId || primaryCardRequest.sequenceId || normalizedCardId).trim(),
|
||||
deckId: primaryCardRequest.deckId || activeDeckId,
|
||||
deckLabel: primaryCardRequest.deckLabel || "",
|
||||
compareDetails: primaryCardRequest.compareDetails || [],
|
||||
@@ -834,13 +992,72 @@
|
||||
activeDeckLabel: primaryCardRequest.deckLabel || "",
|
||||
availableCompareDecks,
|
||||
maxCompareDecks: 2,
|
||||
sequenceIds: state.cards.map((card) => card.id),
|
||||
resolveCardById: buildLightboxCardRequestById,
|
||||
sequenceIds,
|
||||
resolveCardById,
|
||||
resolveDeckCardById: buildDeckLightboxCardRequest,
|
||||
onSelectCardId
|
||||
});
|
||||
}
|
||||
|
||||
function openDeckVariantLightbox(cardIdToOpen, deckIdToOpen = "") {
|
||||
const normalizedCardId = String(cardIdToOpen || "").trim();
|
||||
if (!normalizedCardId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const variantEntries = getDeckVariantSequenceEntries(normalizedCardId);
|
||||
if (variantEntries.length < 1) {
|
||||
openCardLightboxById(normalizedCardId, { deckId: deckIdToOpen });
|
||||
return;
|
||||
}
|
||||
|
||||
const requestedDeckId = String(deckIdToOpen || getActiveDeck?.() || "").trim().toLowerCase();
|
||||
const activeVariant = variantEntries.find((variant) => variant.deckId === requestedDeckId) || variantEntries[0];
|
||||
if (!activeVariant?.deckId) {
|
||||
openCardLightboxById(normalizedCardId);
|
||||
return;
|
||||
}
|
||||
|
||||
const primaryCardRequest = buildLightboxCardRequestById(normalizedCardId, activeVariant.deckId);
|
||||
if (!primaryCardRequest?.src) {
|
||||
return;
|
||||
}
|
||||
|
||||
const availableCompareDecks = getRegisteredDeckList().filter((deck) => deck.id && deck.id !== activeVariant.deckId);
|
||||
|
||||
window.TarotUiLightbox?.open?.({
|
||||
...primaryCardRequest,
|
||||
deckId: activeVariant.deckId,
|
||||
sequenceId: activeVariant.sequenceId,
|
||||
activeDeckId: activeVariant.deckId,
|
||||
activeDeckLabel: primaryCardRequest.deckLabel || "",
|
||||
availableCompareDecks,
|
||||
maxCompareDecks: 2,
|
||||
allowOverlayCompare: true,
|
||||
allowDeckCompare: true,
|
||||
sequenceIds: variantEntries.map((variant) => variant.sequenceId),
|
||||
resolveDeckCardById: buildDeckLightboxCardRequest,
|
||||
resolveCardById: (sequenceId) => {
|
||||
const normalizedSequenceId = String(sequenceId || "").trim().toLowerCase();
|
||||
const matchingVariant = variantEntries.find((variant) => variant.sequenceId === normalizedSequenceId);
|
||||
if (!matchingVariant?.deckId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const request = buildLightboxCardRequestById(normalizedCardId, matchingVariant.deckId);
|
||||
return request
|
||||
? {
|
||||
...request,
|
||||
sequenceId: matchingVariant.sequenceId,
|
||||
deckId: matchingVariant.deckId
|
||||
}
|
||||
: null;
|
||||
},
|
||||
onSelectCardId: () => {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderList(elements) {
|
||||
if (!elements?.tarotCardListEl) {
|
||||
return;
|
||||
@@ -940,6 +1157,7 @@
|
||||
const selected = state.cards.find((card) => card.id === state.selectedCardId);
|
||||
if (selected) {
|
||||
renderDetail(selected, elements);
|
||||
syncDetailNavigation(elements);
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -1007,6 +1225,8 @@
|
||||
});
|
||||
}
|
||||
|
||||
bindKeyboardNavigation(elements);
|
||||
|
||||
if (elements.tarotHouseTopCardsVisibleEl) {
|
||||
elements.tarotHouseTopCardsVisibleEl.addEventListener("change", () => {
|
||||
state.houseTopCardsVisible = Boolean(elements.tarotHouseTopCardsVisibleEl.checked);
|
||||
@@ -1104,12 +1324,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const request = buildLightboxCardRequestById(state.selectedCardId);
|
||||
if (!request?.src) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.TarotUiLightbox?.open?.(request);
|
||||
openCardLightboxById(state.selectedCardId);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user