refactoring
This commit is contained in:
734
app/ui-tarot-relations.js
Normal file
734
app/ui-tarot-relations.js
Normal file
@@ -0,0 +1,734 @@
|
||||
/* ui-tarot-relations.js — Tarot relation builders */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
const TAROT_TRUMP_NUMBER_BY_NAME = {
|
||||
"the fool": 0,
|
||||
fool: 0,
|
||||
"the magus": 1,
|
||||
magus: 1,
|
||||
magician: 1,
|
||||
"the high priestess": 2,
|
||||
"high priestess": 2,
|
||||
"the empress": 3,
|
||||
empress: 3,
|
||||
"the emperor": 4,
|
||||
emperor: 4,
|
||||
"the hierophant": 5,
|
||||
hierophant: 5,
|
||||
"the lovers": 6,
|
||||
lovers: 6,
|
||||
"the chariot": 7,
|
||||
chariot: 7,
|
||||
strength: 8,
|
||||
lust: 8,
|
||||
"the hermit": 9,
|
||||
hermit: 9,
|
||||
fortune: 10,
|
||||
"wheel of fortune": 10,
|
||||
justice: 11,
|
||||
"the hanged man": 12,
|
||||
"hanged man": 12,
|
||||
death: 13,
|
||||
temperance: 14,
|
||||
art: 14,
|
||||
"the devil": 15,
|
||||
devil: 15,
|
||||
"the tower": 16,
|
||||
tower: 16,
|
||||
"the star": 17,
|
||||
star: 17,
|
||||
"the moon": 18,
|
||||
moon: 18,
|
||||
"the sun": 19,
|
||||
sun: 19,
|
||||
aeon: 20,
|
||||
judgement: 20,
|
||||
judgment: 20,
|
||||
universe: 21,
|
||||
world: 21,
|
||||
"the world": 21
|
||||
};
|
||||
|
||||
const HEBREW_LETTER_ALIASES = {
|
||||
aleph: "alef",
|
||||
alef: "alef",
|
||||
heh: "he",
|
||||
he: "he",
|
||||
beth: "bet",
|
||||
bet: "bet",
|
||||
cheth: "het",
|
||||
chet: "het",
|
||||
kaph: "kaf",
|
||||
kaf: "kaf",
|
||||
peh: "pe",
|
||||
tzaddi: "tsadi",
|
||||
tzadi: "tsadi",
|
||||
tsadi: "tsadi",
|
||||
qoph: "qof",
|
||||
qof: "qof",
|
||||
taw: "tav",
|
||||
tau: "tav"
|
||||
};
|
||||
|
||||
const CUBE_MOTHER_CONNECTOR_BY_LETTER = {
|
||||
alef: { connectorId: "above-below", connectorName: "Above ↔ Below" },
|
||||
mem: { connectorId: "east-west", connectorName: "East ↔ West" },
|
||||
shin: { connectorId: "south-north", connectorName: "South ↔ North" }
|
||||
};
|
||||
|
||||
const MINOR_RANK_NUMBER_BY_NAME = {
|
||||
ace: 1,
|
||||
two: 2,
|
||||
three: 3,
|
||||
four: 4,
|
||||
five: 5,
|
||||
six: 6,
|
||||
seven: 7,
|
||||
eight: 8,
|
||||
nine: 9,
|
||||
ten: 10
|
||||
};
|
||||
|
||||
function normalizeRelationId(value) {
|
||||
return String(value || "")
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/(^-|-$)/g, "");
|
||||
}
|
||||
|
||||
function normalizeTarotName(value) {
|
||||
return String(value || "")
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, " ");
|
||||
}
|
||||
|
||||
function normalizeHebrewLetterId(value) {
|
||||
const key = String(value || "")
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z]/g, "");
|
||||
return HEBREW_LETTER_ALIASES[key] || key;
|
||||
}
|
||||
|
||||
function resolveTarotTrumpNumber(cardName) {
|
||||
const key = normalizeTarotName(cardName);
|
||||
if (!key) {
|
||||
return null;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(TAROT_TRUMP_NUMBER_BY_NAME, key)) {
|
||||
return TAROT_TRUMP_NUMBER_BY_NAME[key];
|
||||
}
|
||||
const withoutLeadingThe = key.replace(/^the\s+/, "");
|
||||
if (Object.prototype.hasOwnProperty.call(TAROT_TRUMP_NUMBER_BY_NAME, withoutLeadingThe)) {
|
||||
return TAROT_TRUMP_NUMBER_BY_NAME[withoutLeadingThe];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function cardMatchesTarotAssociation(card, tarotCardName) {
|
||||
const associationName = normalizeTarotName(tarotCardName);
|
||||
if (!associationName || !card) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const cardName = normalizeTarotName(card.name);
|
||||
const cardBare = cardName.replace(/^the\s+/, "");
|
||||
const assocBare = associationName.replace(/^the\s+/, "");
|
||||
|
||||
if (
|
||||
associationName === cardName ||
|
||||
associationName === cardBare ||
|
||||
assocBare === cardName ||
|
||||
assocBare === cardBare
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (card.arcana === "Major" && Number.isFinite(Number(card.number))) {
|
||||
const trumpNumber = resolveTarotTrumpNumber(associationName);
|
||||
if (trumpNumber != null) {
|
||||
return trumpNumber === Number(card.number);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function buildCourtCardByDecanId(cards) {
|
||||
const map = new Map();
|
||||
|
||||
(cards || []).forEach((card) => {
|
||||
if (!card || card.arcana !== "Minor") {
|
||||
return;
|
||||
}
|
||||
|
||||
const rankKey = String(card.rank || "").trim().toLowerCase();
|
||||
if (rankKey !== "knight" && rankKey !== "queen" && rankKey !== "prince") {
|
||||
return;
|
||||
}
|
||||
|
||||
const windowRelation = (Array.isArray(card.relations) ? card.relations : [])
|
||||
.find((relation) => relation && typeof relation === "object" && relation.type === "courtDateWindow");
|
||||
|
||||
const decanIds = Array.isArray(windowRelation?.data?.decanIds)
|
||||
? windowRelation.data.decanIds
|
||||
: [];
|
||||
|
||||
decanIds.forEach((decanId) => {
|
||||
const decanKey = normalizeRelationId(decanId);
|
||||
if (!decanKey || map.has(decanKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
map.set(decanKey, {
|
||||
cardName: card.name,
|
||||
rank: card.rank,
|
||||
suit: card.suit,
|
||||
dateRange: String(windowRelation?.data?.dateRange || "").trim()
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
function buildSmallCardCourtLinkRelations(card, relations, courtCardByDecanId) {
|
||||
if (!card || card.arcana !== "Minor") {
|
||||
return [];
|
||||
}
|
||||
|
||||
const rankKey = String(card.rank || "").trim().toLowerCase();
|
||||
const rankNumber = MINOR_RANK_NUMBER_BY_NAME[rankKey];
|
||||
if (!Number.isFinite(rankNumber) || rankNumber < 2 || rankNumber > 10) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const decans = (relations || []).filter((relation) => relation?.type === "decan");
|
||||
if (!decans.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const results = [];
|
||||
const seenCourtCardNames = new Set();
|
||||
|
||||
decans.forEach((decan) => {
|
||||
const signId = String(decan?.data?.signId || "").trim().toLowerCase();
|
||||
const decanIndex = Number(decan?.data?.index);
|
||||
if (!signId || !Number.isFinite(decanIndex)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const decanId = normalizeRelationId(`${signId}-${decanIndex}`);
|
||||
const linkedCourt = courtCardByDecanId?.get(decanId);
|
||||
if (!linkedCourt?.cardName || seenCourtCardNames.has(linkedCourt.cardName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
seenCourtCardNames.add(linkedCourt.cardName);
|
||||
|
||||
results.push({
|
||||
type: "tarotCard",
|
||||
id: `${decanId}-${normalizeRelationId(linkedCourt.cardName)}`,
|
||||
label: `Shared court date window: ${linkedCourt.cardName}${linkedCourt.dateRange ? ` · ${linkedCourt.dateRange}` : ""}`,
|
||||
data: {
|
||||
cardName: linkedCourt.cardName,
|
||||
dateRange: linkedCourt.dateRange || "",
|
||||
decanId
|
||||
},
|
||||
__key: `tarotCard|${decanId}|${normalizeRelationId(linkedCourt.cardName)}`
|
||||
});
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function parseMonthDayToken(value) {
|
||||
const text = String(value || "").trim();
|
||||
const match = text.match(/^(\d{1,2})-(\d{1,2})$/);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const month = Number(match[1]);
|
||||
const day = Number(match[2]);
|
||||
if (!Number.isInteger(month) || !Number.isInteger(day) || month < 1 || month > 12 || day < 1 || day > 31) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { month, day };
|
||||
}
|
||||
|
||||
function toReferenceDate(token, year) {
|
||||
if (!token) {
|
||||
return null;
|
||||
}
|
||||
return new Date(year, token.month - 1, token.day, 12, 0, 0, 0);
|
||||
}
|
||||
|
||||
function splitMonthDayRangeByMonth(startToken, endToken) {
|
||||
const startDate = toReferenceDate(startToken, 2025);
|
||||
const endBase = toReferenceDate(endToken, 2025);
|
||||
if (!startDate || !endBase) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const wrapsYear = endBase.getTime() < startDate.getTime();
|
||||
const endDate = wrapsYear ? toReferenceDate(endToken, 2026) : endBase;
|
||||
if (!endDate) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const segments = [];
|
||||
let cursor = new Date(startDate);
|
||||
|
||||
while (cursor.getTime() <= endDate.getTime()) {
|
||||
const monthEnd = new Date(cursor.getFullYear(), cursor.getMonth() + 1, 0, 12, 0, 0, 0);
|
||||
const segmentEnd = monthEnd.getTime() < endDate.getTime() ? monthEnd : endDate;
|
||||
|
||||
segments.push({
|
||||
monthNo: cursor.getMonth() + 1,
|
||||
startDay: cursor.getDate(),
|
||||
endDay: segmentEnd.getDate()
|
||||
});
|
||||
|
||||
cursor = new Date(segmentEnd.getFullYear(), segmentEnd.getMonth(), segmentEnd.getDate() + 1, 12, 0, 0, 0);
|
||||
}
|
||||
|
||||
return segments;
|
||||
}
|
||||
|
||||
function formatMonthDayRangeLabel(monthName, startDay, endDay) {
|
||||
const start = Number(startDay);
|
||||
const end = Number(endDay);
|
||||
if (!Number.isFinite(start) || !Number.isFinite(end)) {
|
||||
return monthName;
|
||||
}
|
||||
if (start === end) {
|
||||
return `${monthName} ${start}`;
|
||||
}
|
||||
return `${monthName} ${start}-${end}`;
|
||||
}
|
||||
|
||||
function buildMonthReferencesByCard(referenceData, cards) {
|
||||
const map = new Map();
|
||||
const months = Array.isArray(referenceData?.calendarMonths) ? referenceData.calendarMonths : [];
|
||||
const holidays = Array.isArray(referenceData?.celestialHolidays) ? referenceData.celestialHolidays : [];
|
||||
const signs = Array.isArray(referenceData?.signs) ? referenceData.signs : [];
|
||||
const monthById = new Map(months.map((month) => [month.id, month]));
|
||||
|
||||
function parseMonthFromDateToken(value) {
|
||||
const token = parseMonthDayToken(value);
|
||||
return token ? token.month : null;
|
||||
}
|
||||
|
||||
function findMonthByNumber(monthNo) {
|
||||
if (!Number.isInteger(monthNo) || monthNo < 1 || monthNo > 12) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const byOrder = months.find((month) => Number(month?.order) === monthNo);
|
||||
if (byOrder) {
|
||||
return byOrder;
|
||||
}
|
||||
|
||||
return months.find((month) => parseMonthFromDateToken(month?.start) === monthNo) || null;
|
||||
}
|
||||
|
||||
function pushRef(card, month, options = {}) {
|
||||
if (!card?.id || !month?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!map.has(card.id)) {
|
||||
map.set(card.id, []);
|
||||
}
|
||||
|
||||
const rows = map.get(card.id);
|
||||
const monthOrder = Number.isFinite(Number(month.order)) ? Number(month.order) : 999;
|
||||
const startToken = parseMonthDayToken(options.startToken || month.start);
|
||||
const endToken = parseMonthDayToken(options.endToken || month.end);
|
||||
const dateRange = String(options.dateRange || "").trim() || (
|
||||
startToken && endToken
|
||||
? formatMonthDayRangeLabel(month.name || month.id, startToken.day, endToken.day)
|
||||
: ""
|
||||
);
|
||||
|
||||
const uniqueKey = [
|
||||
month.id,
|
||||
dateRange.toLowerCase(),
|
||||
String(options.context || "").trim().toLowerCase(),
|
||||
String(options.source || "").trim().toLowerCase()
|
||||
].join("|");
|
||||
|
||||
if (rows.some((entry) => entry.uniqueKey === uniqueKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
rows.push({
|
||||
id: month.id,
|
||||
name: month.name || month.id,
|
||||
order: monthOrder,
|
||||
startToken: startToken ? `${String(startToken.month).padStart(2, "0")}-${String(startToken.day).padStart(2, "0")}` : null,
|
||||
endToken: endToken ? `${String(endToken.month).padStart(2, "0")}-${String(endToken.day).padStart(2, "0")}` : null,
|
||||
dateRange,
|
||||
context: String(options.context || "").trim(),
|
||||
source: String(options.source || "").trim(),
|
||||
uniqueKey
|
||||
});
|
||||
}
|
||||
|
||||
function captureRefs(associations, month) {
|
||||
const tarotCardName = associations?.tarotCard;
|
||||
if (!tarotCardName) {
|
||||
return;
|
||||
}
|
||||
|
||||
cards.forEach((card) => {
|
||||
if (cardMatchesTarotAssociation(card, tarotCardName)) {
|
||||
pushRef(card, month);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
months.forEach((month) => {
|
||||
captureRefs(month?.associations, month);
|
||||
|
||||
const events = Array.isArray(month?.events) ? month.events : [];
|
||||
events.forEach((event) => {
|
||||
const tarotCardName = event?.associations?.tarotCard;
|
||||
if (!tarotCardName) {
|
||||
return;
|
||||
}
|
||||
cards.forEach((card) => {
|
||||
if (!cardMatchesTarotAssociation(card, tarotCardName)) {
|
||||
return;
|
||||
}
|
||||
pushRef(card, month, {
|
||||
source: "month-event",
|
||||
context: String(event?.name || "").trim()
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
holidays.forEach((holiday) => {
|
||||
const month = monthById.get(holiday?.monthId);
|
||||
if (!month) {
|
||||
return;
|
||||
}
|
||||
const tarotCardName = holiday?.associations?.tarotCard;
|
||||
if (!tarotCardName) {
|
||||
return;
|
||||
}
|
||||
cards.forEach((card) => {
|
||||
if (!cardMatchesTarotAssociation(card, tarotCardName)) {
|
||||
return;
|
||||
}
|
||||
pushRef(card, month, {
|
||||
source: "holiday",
|
||||
context: String(holiday?.name || "").trim()
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
signs.forEach((sign) => {
|
||||
const signTrumpNumber = Number(sign?.tarot?.number);
|
||||
const signTarotName = sign?.tarot?.majorArcana || sign?.tarot?.card || sign?.tarotCard;
|
||||
if (!Number.isFinite(signTrumpNumber) && !signTarotName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const signName = String(sign?.name || sign?.id || "").trim();
|
||||
const startToken = parseMonthDayToken(sign?.start);
|
||||
const endToken = parseMonthDayToken(sign?.end);
|
||||
const monthSegments = splitMonthDayRangeByMonth(startToken, endToken);
|
||||
const fallbackStartMonthNo = parseMonthFromDateToken(sign?.start);
|
||||
const fallbackEndMonthNo = parseMonthFromDateToken(sign?.end);
|
||||
const fallbackStartMonth = findMonthByNumber(fallbackStartMonthNo);
|
||||
const fallbackEndMonth = findMonthByNumber(fallbackEndMonthNo);
|
||||
|
||||
cards.forEach((card) => {
|
||||
const cardTrumpNumber = Number(card?.number);
|
||||
const matchesByTrump = card?.arcana === "Major"
|
||||
&& Number.isFinite(cardTrumpNumber)
|
||||
&& Number.isFinite(signTrumpNumber)
|
||||
&& cardTrumpNumber === signTrumpNumber;
|
||||
const matchesByName = signTarotName ? cardMatchesTarotAssociation(card, signTarotName) : false;
|
||||
|
||||
if (!matchesByTrump && !matchesByName) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (monthSegments.length) {
|
||||
monthSegments.forEach((segment) => {
|
||||
const month = findMonthByNumber(segment.monthNo);
|
||||
if (!month) {
|
||||
return;
|
||||
}
|
||||
|
||||
pushRef(card, month, {
|
||||
source: "zodiac-window",
|
||||
context: signName ? `${signName} window` : "",
|
||||
startToken: `${String(segment.monthNo).padStart(2, "0")}-${String(segment.startDay).padStart(2, "0")}`,
|
||||
endToken: `${String(segment.monthNo).padStart(2, "0")}-${String(segment.endDay).padStart(2, "0")}`,
|
||||
dateRange: formatMonthDayRangeLabel(month.name || month.id, segment.startDay, segment.endDay)
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (fallbackStartMonth) {
|
||||
pushRef(card, fallbackStartMonth, {
|
||||
source: "zodiac-window",
|
||||
context: signName ? `${signName} window` : ""
|
||||
});
|
||||
}
|
||||
if (fallbackEndMonth && (!fallbackStartMonth || fallbackEndMonth.id !== fallbackStartMonth.id)) {
|
||||
pushRef(card, fallbackEndMonth, {
|
||||
source: "zodiac-window",
|
||||
context: signName ? `${signName} window` : ""
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
map.forEach((rows, key) => {
|
||||
const monthIdsWithZodiacWindows = new Set(
|
||||
rows
|
||||
.filter((entry) => entry?.source === "zodiac-window" && entry?.id)
|
||||
.map((entry) => entry.id)
|
||||
);
|
||||
|
||||
const filteredRows = rows.filter((entry) => {
|
||||
if (!entry?.id || entry?.source === "zodiac-window") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!monthIdsWithZodiacWindows.has(entry.id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const month = monthById.get(entry.id);
|
||||
if (!month) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const isFullMonth = String(entry.startToken || "") === String(month.start || "")
|
||||
&& String(entry.endToken || "") === String(month.end || "");
|
||||
|
||||
return !isFullMonth;
|
||||
});
|
||||
|
||||
filteredRows.sort((left, right) => {
|
||||
if (left.order !== right.order) {
|
||||
return left.order - right.order;
|
||||
}
|
||||
|
||||
const startLeft = parseMonthDayToken(left.startToken);
|
||||
const startRight = parseMonthDayToken(right.startToken);
|
||||
const dayLeft = startLeft ? startLeft.day : 999;
|
||||
const dayRight = startRight ? startRight.day : 999;
|
||||
if (dayLeft !== dayRight) {
|
||||
return dayLeft - dayRight;
|
||||
}
|
||||
|
||||
return String(left.dateRange || left.name || "").localeCompare(String(right.dateRange || right.name || ""));
|
||||
});
|
||||
map.set(key, filteredRows);
|
||||
});
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
function buildCubeFaceRelationsForCard(card, magickDataset) {
|
||||
const cube = magickDataset?.grouped?.kabbalah?.cube;
|
||||
const walls = Array.isArray(cube?.walls) ? cube.walls : [];
|
||||
if (!card || !walls.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return walls
|
||||
.map((wall, index) => {
|
||||
const wallTarot = wall?.associations?.tarotCard || wall?.tarotCard;
|
||||
if (!wallTarot || !cardMatchesTarotAssociation(card, wallTarot)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const wallId = String(wall?.id || "").trim();
|
||||
const wallName = String(wall?.name || wallId || "").trim();
|
||||
if (!wallId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
type: "cubeFace",
|
||||
id: wallId,
|
||||
label: `Cube: ${wallName} Wall - Face`,
|
||||
data: {
|
||||
wallId,
|
||||
wallName,
|
||||
edgeId: ""
|
||||
},
|
||||
__key: `cubeFace|${wallId}|${index}`
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function cardMatchesPathTarot(card, path) {
|
||||
if (!card || !path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const trumpNumber = Number(path?.tarot?.trumpNumber);
|
||||
if (card?.arcana === "Major" && Number.isFinite(Number(card?.number)) && Number.isFinite(trumpNumber)) {
|
||||
if (Number(card.number) === trumpNumber) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return cardMatchesTarotAssociation(card, path?.tarot?.card);
|
||||
}
|
||||
|
||||
function buildCubeEdgeRelationsForCard(card, magickDataset) {
|
||||
const cube = magickDataset?.grouped?.kabbalah?.cube;
|
||||
const tree = magickDataset?.grouped?.kabbalah?.["kabbalah-tree"];
|
||||
const edges = Array.isArray(cube?.edges) ? cube.edges : [];
|
||||
const paths = Array.isArray(tree?.paths) ? tree.paths : [];
|
||||
|
||||
if (!card || !edges.length || !paths.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const pathByLetterId = new Map(
|
||||
paths
|
||||
.map((path) => [normalizeHebrewLetterId(path?.hebrewLetter?.transliteration), path])
|
||||
.filter(([letterId]) => Boolean(letterId))
|
||||
);
|
||||
|
||||
return edges
|
||||
.map((edge, index) => {
|
||||
const edgeLetterId = normalizeHebrewLetterId(edge?.hebrewLetterId || edge?.associations?.hebrewLetterId);
|
||||
if (!edgeLetterId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const pathMatch = pathByLetterId.get(edgeLetterId);
|
||||
if (!pathMatch || !cardMatchesPathTarot(card, pathMatch)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const edgeId = String(edge?.id || "").trim();
|
||||
if (!edgeId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const edgeName = String(edge?.name || edgeId).trim();
|
||||
const wallId = String(Array.isArray(edge?.walls) ? (edge.walls[0] || "") : "").trim();
|
||||
|
||||
return {
|
||||
type: "cubeEdge",
|
||||
id: edgeId,
|
||||
label: `Cube: ${edgeName} Edge`,
|
||||
data: {
|
||||
edgeId,
|
||||
edgeName,
|
||||
wallId: wallId || undefined,
|
||||
hebrewLetterId: edgeLetterId
|
||||
},
|
||||
__key: `cubeEdge|${edgeId}|${index}`
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function buildCubeMotherConnectorRelationsForCard(card, magickDataset) {
|
||||
const tree = magickDataset?.grouped?.kabbalah?.["kabbalah-tree"];
|
||||
const paths = Array.isArray(tree?.paths) ? tree.paths : [];
|
||||
const relations = Array.isArray(card?.relations) ? card.relations : [];
|
||||
|
||||
return Object.entries(CUBE_MOTHER_CONNECTOR_BY_LETTER)
|
||||
.map(([letterId, connector]) => {
|
||||
const pathMatch = paths.find((path) => normalizeHebrewLetterId(path?.hebrewLetter?.transliteration) === letterId) || null;
|
||||
|
||||
const matchesByPath = cardMatchesPathTarot(card, pathMatch);
|
||||
|
||||
const matchesByHebrewRelation = relations.some((relation) => {
|
||||
if (relation?.type !== "hebrewLetter") {
|
||||
return false;
|
||||
}
|
||||
const relationLetterId = normalizeHebrewLetterId(
|
||||
relation?.data?.id || relation?.id || relation?.data?.latin || relation?.data?.name
|
||||
);
|
||||
return relationLetterId === letterId;
|
||||
});
|
||||
|
||||
if (!matchesByPath && !matchesByHebrewRelation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
type: "cubeConnector",
|
||||
id: connector.connectorId,
|
||||
label: `Cube: ${connector.connectorName}`,
|
||||
data: {
|
||||
connectorId: connector.connectorId,
|
||||
connectorName: connector.connectorName,
|
||||
hebrewLetterId: letterId
|
||||
},
|
||||
__key: `cubeConnector|${connector.connectorId}|${letterId}`
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function buildCubePrimalPointRelationsForCard(card, magickDataset) {
|
||||
const center = magickDataset?.grouped?.kabbalah?.cube?.center;
|
||||
if (!center || !card) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const centerTarot = center?.associations?.tarotCard || center?.tarotCard;
|
||||
const centerTrump = Number(center?.associations?.tarotTrumpNumber);
|
||||
const matchesByName = cardMatchesTarotAssociation(card, centerTarot);
|
||||
const matchesByTrump = card?.arcana === "Major"
|
||||
&& Number.isFinite(Number(card?.number))
|
||||
&& Number.isFinite(centerTrump)
|
||||
&& Number(card.number) === centerTrump;
|
||||
|
||||
if (!matchesByName && !matchesByTrump) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [{
|
||||
type: "cubeCenter",
|
||||
id: "primal-point",
|
||||
label: "Cube: Primal Point",
|
||||
data: {
|
||||
nodeType: "center",
|
||||
primalPoint: true
|
||||
},
|
||||
__key: "cubeCenter|primal-point"
|
||||
}];
|
||||
}
|
||||
|
||||
function buildCubeRelationsForCard(card, magickDataset) {
|
||||
return [
|
||||
...buildCubeFaceRelationsForCard(card, magickDataset),
|
||||
...buildCubeEdgeRelationsForCard(card, magickDataset),
|
||||
...buildCubePrimalPointRelationsForCard(card, magickDataset),
|
||||
...buildCubeMotherConnectorRelationsForCard(card, magickDataset)
|
||||
];
|
||||
}
|
||||
|
||||
window.TarotRelationsUi = {
|
||||
buildCourtCardByDecanId,
|
||||
buildSmallCardCourtLinkRelations,
|
||||
buildMonthReferencesByCard,
|
||||
buildCubeRelationsForCard,
|
||||
parseMonthDayToken
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user