Files
TaroTime/app/ui-calendar-detail-panels.js
2026-03-07 13:38:13 -08:00

457 lines
17 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function () {
"use strict";
function findSignIdByAstrologyName(name, context) {
const { api, getState } = context;
const token = api.normalizeCalendarText(name);
if (!token) {
return null;
}
for (const [signId, sign] of getState().signsById || []) {
const idToken = api.normalizeCalendarText(signId);
const nameToken = api.normalizeCalendarText(sign?.name?.en || sign?.name || "");
if (token === idToken || token === nameToken) {
return signId;
}
}
return null;
}
function buildMajorArcanaRowsForMonth(context) {
const { month, api, getState } = context;
const currentState = getState();
if (currentState.selectedCalendar !== "gregorian") {
return [];
}
const monthOrder = Number(month?.order);
if (!Number.isFinite(monthOrder)) {
return [];
}
const monthStart = new Date(currentState.selectedYear, monthOrder - 1, 1, 12, 0, 0, 0);
const monthEnd = new Date(currentState.selectedYear, monthOrder, 0, 12, 0, 0, 0);
const rows = [];
currentState.hebrewById?.forEach((letter) => {
const astrologyType = api.normalizeCalendarText(letter?.astrology?.type);
if (astrologyType !== "zodiac") {
return;
}
const signId = findSignIdByAstrologyName(letter?.astrology?.name, context);
const sign = signId ? currentState.signsById?.get(signId) : null;
if (!sign) {
return;
}
const startToken = api.parseMonthDayToken(sign?.start);
const endToken = api.parseMonthDayToken(sign?.end);
if (!startToken || !endToken) {
return;
}
const spanStart = new Date(currentState.selectedYear, startToken.month - 1, startToken.day, 12, 0, 0, 0);
const spanEnd = new Date(currentState.selectedYear, endToken.month - 1, endToken.day, 12, 0, 0, 0);
const wraps = spanEnd.getTime() < spanStart.getTime();
const segments = wraps
? [
{
start: spanStart,
end: new Date(currentState.selectedYear, 11, 31, 12, 0, 0, 0)
},
{
start: new Date(currentState.selectedYear, 0, 1, 12, 0, 0, 0),
end: spanEnd
}
]
: [{ start: spanStart, end: spanEnd }];
segments.forEach((segment) => {
const overlap = api.intersectDateRanges(segment.start, segment.end, monthStart, monthEnd);
if (!overlap) {
return;
}
const rangeStartDay = overlap.start.getDate();
const rangeEndDay = overlap.end.getDate();
const cardName = String(letter?.tarot?.card || "").trim();
const trumpNumber = Number(letter?.tarot?.trumpNumber);
if (!cardName) {
return;
}
rows.push({
id: `${signId}-${rangeStartDay}-${rangeEndDay}`,
signId,
signName: sign?.name?.en || sign?.name || signId,
signSymbol: sign?.symbol || "",
cardName,
trumpNumber: Number.isFinite(trumpNumber) ? Math.trunc(trumpNumber) : null,
hebrewLetterId: String(letter?.hebrewLetterId || "").trim(),
hebrewLetterName: String(letter?.name || "").trim(),
hebrewLetterChar: String(letter?.char || "").trim(),
dayStart: rangeStartDay,
dayEnd: rangeEndDay,
rangeLabel: `${month?.name || "Month"} ${rangeStartDay}-${rangeEndDay}`
});
});
});
rows.sort((left, right) => {
if (left.dayStart !== right.dayStart) {
return left.dayStart - right.dayStart;
}
return left.cardName.localeCompare(right.cardName);
});
return rows;
}
function renderMajorArcanaCard(context) {
const { month, api } = context;
const selectedDay = api.getSelectedDayFilterContext(month);
const allRows = buildMajorArcanaRowsForMonth(context);
const rows = selectedDay
? allRows.filter((row) => selectedDay.entries.some((entry) => entry.dayNumber >= row.dayStart && entry.dayNumber <= row.dayEnd))
: allRows;
if (!rows.length) {
return `
<div class="planet-meta-card">
<strong>Major Arcana Windows</strong>
<div class="planet-text">No major arcana windows for this month.</div>
</div>
`;
}
const list = rows.map((row) => {
const label = row.hebrewLetterId
? `${row.hebrewLetterChar ? `${row.hebrewLetterChar} ` : ""}${row.hebrewLetterName || row.hebrewLetterId}`
: "--";
const displayCardName = api.getDisplayTarotName(row.cardName, row.trumpNumber);
return `
<div class="cal-item-row">
<div class="cal-item-head">
<span class="cal-item-name">${displayCardName}${row.trumpNumber != null ? ` · Trump ${row.trumpNumber}` : ""}</span>
<span class="planet-list-meta">${row.rangeLabel}</span>
</div>
<div class="planet-list-meta">${row.signSymbol} ${row.signName} · Hebrew: ${label}</div>
<div class="alpha-nav-btns">
<button class="alpha-nav-btn" data-nav="calendar-day-range" data-range-start="${row.dayStart}" data-range-end="${row.dayEnd}">${row.rangeLabel} ↗</button>
<button class="alpha-nav-btn" data-nav="tarot-card" data-card-name="${row.cardName}" data-trump-number="${row.trumpNumber ?? ""}">${displayCardName} ↗</button>
${row.hebrewLetterId ? `<button class="alpha-nav-btn" data-nav="alphabet" data-alphabet="hebrew" data-hebrew-letter-id="${row.hebrewLetterId}">${label} ↗</button>` : ""}
</div>
</div>
`;
}).join("");
return `
<div class="planet-meta-card">
<strong>Major Arcana Windows</strong>
<div class="cal-item-stack">${list}</div>
</div>
`;
}
function renderDecanTarotCard(context) {
const { month, api } = context;
const selectedDay = api.getSelectedDayFilterContext(month);
const allRows = api.buildDecanTarotRowsForMonth(month);
const rows = selectedDay
? allRows.filter((row) => selectedDay.entries.some((entry) => {
const targetDate = entry.gregorianDate;
if (!(targetDate instanceof Date) || Number.isNaN(targetDate.getTime())) {
return false;
}
const targetMonth = targetDate.getMonth() + 1;
const targetDayNo = targetDate.getDate();
return api.isMonthDayInRange(
targetMonth,
targetDayNo,
row.startMonth,
row.startDay,
row.endMonth,
row.endDay
);
}))
: allRows;
if (!rows.length) {
return `
<div class="planet-meta-card">
<strong>Decan Tarot Windows</strong>
<div class="planet-text">No decan tarot windows for this month.</div>
</div>
`;
}
const list = rows.map((row) => {
const displayCardName = api.getDisplayTarotName(row.cardName);
return `
<div class="cal-item-row">
<div class="cal-item-head">
<span class="cal-item-name">${row.signSymbol} ${row.signName} · Decan ${row.decanIndex}</span>
<span class="planet-list-meta">${row.startDegree}°–${row.endDegree}° · ${row.dateRange}</span>
</div>
<div class="alpha-nav-btns">
<button class="alpha-nav-btn" data-nav="tarot-card" data-card-name="${row.cardName}">${displayCardName} ↗</button>
</div>
</div>
`;
}).join("");
return `
<div class="planet-meta-card">
<strong>Decan Tarot Windows</strong>
<div class="cal-item-stack">${list}</div>
</div>
`;
}
function renderDayLinksCard(context) {
const { month, api } = context;
const rows = api.getMonthDayLinkRows(month);
if (!rows.length) {
return "";
}
const selectedContext = api.getSelectedDayFilterContext(month);
const selectedDaySet = selectedContext?.dayNumbers || new Set();
const selectedDays = selectedContext?.entries?.map((entry) => entry.dayNumber) || [];
const selectedSummary = selectedDays.length ? selectedDays.join(", ") : "";
const links = rows.map((row) => {
if (!row.isResolved) {
return `<span class="planet-list-meta">${row.day}</span>`;
}
const isSelected = selectedDaySet.has(Number(row.day));
return `<button class="alpha-nav-btn${isSelected ? " is-selected" : ""}" data-nav="calendar-day" data-day-number="${row.day}" data-gregorian-date="${row.gregorianDate}" aria-pressed="${isSelected ? "true" : "false"}" title="Filter this month by day ${row.day}">${row.day}</button>`;
}).join("");
const clearButton = selectedContext
? '<button class="alpha-nav-btn" data-nav="calendar-day-clear" type="button">Show All Days</button>'
: "";
const helperText = selectedContext
? `<div class="planet-list-meta">Filtered to days: ${selectedSummary}</div>`
: "";
return `
<div class="planet-meta-card">
<strong>Day Links</strong>
<div class="planet-text">Filter this month to events, holidays, and data connected to a specific day.</div>
${helperText}
<div class="alpha-nav-btns">${links}</div>
${clearButton ? `<div class="alpha-nav-btns">${clearButton}</div>` : ""}
</div>
`;
}
function renderGregorianMonthDetail(context) {
const {
renderFactsCard,
renderAssociationsCard,
renderEventsCard,
renderHolidaysCard,
month
} = context;
return `
<div class="planet-meta-grid">
${renderFactsCard(month)}
${renderDayLinksCard(context)}
${renderAssociationsCard(month)}
${renderMajorArcanaCard(context)}
${renderDecanTarotCard(context)}
${renderEventsCard(month)}
${renderHolidaysCard(month, "Holiday Repository")}
</div>
`;
}
function renderHebrewMonthDetail(context) {
const { month, api, getState, buildAssociationButtons, renderHolidaysCard } = context;
const currentState = getState();
const gregorianStartDate = api.getGregorianReferenceDateForCalendarMonth(month);
const factsRows = [
["Hebrew Name", month.nativeName || "--"],
["Month Order", month.leapYearOnly ? `${month.order} (leap year only)` : String(month.order)],
["Gregorian Reference Year", String(currentState.selectedYear)],
["Month Start (Gregorian)", api.formatGregorianReferenceDate(gregorianStartDate)],
["Days", month.daysVariant ? `${month.days}${month.daysVariant} (varies)` : String(month.days || "--")],
["Season", month.season || "--"],
["Zodiac Sign", api.cap(month.zodiacSign) || "--"],
["Tribe of Israel", month.tribe || "--"],
["Sense", month.sense || "--"],
["Hebrew Letter", month.hebrewLetter || "--"]
].map(([dt, dd]) => `<dt>${dt}</dt><dd>${dd}</dd>`).join("");
const monthOrder = Number(month?.order);
const navButtons = buildAssociationButtons({
...(month?.associations || {}),
...(Number.isFinite(monthOrder) ? { numberValue: Math.trunc(monthOrder) } : {})
});
const connectionsCard = navButtons
? `<div class="planet-meta-card"><strong>Connections</strong>${navButtons}</div>`
: "";
return `
<div class="planet-meta-grid">
<div class="planet-meta-card">
<strong>Month Facts</strong>
<div class="planet-text">
<dl class="alpha-dl">${factsRows}</dl>
</div>
</div>
${connectionsCard}
<div class="planet-meta-card">
<strong>About ${month.name}</strong>
<div class="planet-text">${month.description || "--"}</div>
</div>
${renderDayLinksCard(context)}
${renderHolidaysCard(month, "Holiday Repository")}
</div>
`;
}
function renderIslamicMonthDetail(context) {
const { month, api, getState, buildAssociationButtons, renderHolidaysCard } = context;
const currentState = getState();
const gregorianStartDate = api.getGregorianReferenceDateForCalendarMonth(month);
const factsRows = [
["Arabic Name", month.nativeName || "--"],
["Month Order", String(month.order)],
["Gregorian Reference Year", String(currentState.selectedYear)],
["Month Start (Gregorian)", api.formatGregorianReferenceDate(gregorianStartDate)],
["Meaning", month.meaning || "--"],
["Days", month.daysVariant ? `${month.days}${month.daysVariant} (varies)` : String(month.days || "--")],
["Sacred Month", month.sacred ? "Yes - warfare prohibited" : "No"]
].map(([dt, dd]) => `<dt>${dt}</dt><dd>${dd}</dd>`).join("");
const monthOrder = Number(month?.order);
const hasNumberLink = Number.isFinite(monthOrder) && monthOrder >= 0;
const navButtons = hasNumberLink
? buildAssociationButtons({ numberValue: Math.trunc(monthOrder) })
: "";
const connectionsCard = hasNumberLink
? `<div class="planet-meta-card"><strong>Connections</strong>${navButtons}</div>`
: "";
return `
<div class="planet-meta-grid">
<div class="planet-meta-card">
<strong>Month Facts</strong>
<div class="planet-text">
<dl class="alpha-dl">${factsRows}</dl>
</div>
</div>
${connectionsCard}
<div class="planet-meta-card">
<strong>About ${month.name}</strong>
<div class="planet-text">${month.description || "--"}</div>
</div>
${renderDayLinksCard(context)}
${renderHolidaysCard(month, "Holiday Repository")}
</div>
`;
}
function buildWheelDeityButtons(deities, context) {
const { api, getState } = context;
const buttons = [];
(Array.isArray(deities) ? deities : []).forEach((rawName) => {
const cleanName = String(rawName || "").replace(/\s*\/.*$/, "").replace(/\s*\(.*\)$/, "").trim();
const godId = api.findGodIdByName(cleanName) || api.findGodIdByName(rawName);
if (!godId) {
return;
}
const god = getState().godsById?.get(godId);
const label = god?.name || cleanName;
buttons.push(`<button class="alpha-nav-btn" data-nav="god" data-god-id="${godId}" data-god-name="${label}">${label} ↗</button>`);
});
return buttons;
}
function renderWheelMonthDetail(context) {
const { month, api, getState, buildAssociationButtons, renderHolidaysCard } = context;
const currentState = getState();
const gregorianStartDate = api.getGregorianReferenceDateForCalendarMonth(month);
const assoc = month?.associations;
const themes = Array.isArray(assoc?.themes) ? assoc.themes.join(", ") : "--";
const deities = Array.isArray(assoc?.deities) ? assoc.deities.join(", ") : "--";
const colors = Array.isArray(assoc?.colors) ? assoc.colors.join(", ") : "--";
const herbs = Array.isArray(assoc?.herbs) ? assoc.herbs.join(", ") : "--";
const factsRows = [
["Date", month.date || "--"],
["Type", api.cap(month.type) || "--"],
["Gregorian Reference Year", String(currentState.selectedYear)],
["Start (Gregorian)", api.formatGregorianReferenceDate(gregorianStartDate)],
["Season", month.season || "--"],
["Element", api.cap(month.element) || "--"],
["Direction", assoc?.direction || "--"]
].map(([dt, dd]) => `<dt>${dt}</dt><dd>${dd}</dd>`).join("");
const assocRows = [
["Themes", themes],
["Deities", deities],
["Colors", colors],
["Herbs", herbs]
].map(([dt, dd]) => `<dt>${dt}</dt><dd class="planet-text">${dd}</dd>`).join("");
const deityButtons = buildWheelDeityButtons(assoc?.deities, context);
const deityLinksCard = deityButtons.length
? `<div class="planet-meta-card"><strong>Linked Deities</strong><div class="alpha-nav-btns">${deityButtons.join("")}</div></div>`
: "";
const monthOrder = Number(month?.order);
const hasNumberLink = Number.isFinite(monthOrder) && monthOrder >= 0;
const numberButtons = hasNumberLink
? buildAssociationButtons({ numberValue: Math.trunc(monthOrder) })
: "";
const numberLinksCard = hasNumberLink
? `<div class="planet-meta-card"><strong>Connections</strong>${numberButtons}</div>`
: "";
return `
<div class="planet-meta-grid">
<div class="planet-meta-card">
<strong>Sabbat Facts</strong>
<div class="planet-text">
<dl class="alpha-dl">${factsRows}</dl>
</div>
</div>
<div class="planet-meta-card">
<strong>About ${month.name}</strong>
<div class="planet-text">${month.description || "--"}</div>
</div>
<div class="planet-meta-card">
<strong>Associations</strong>
<div class="planet-text">
<dl class="alpha-dl">${assocRows}</dl>
</div>
</div>
${renderDayLinksCard(context)}
${numberLinksCard}
${deityLinksCard}
${renderHolidaysCard(month, "Holiday Repository")}
</div>
`;
}
window.CalendarDetailPanelsUi = {
renderGregorianMonthDetail,
renderHebrewMonthDetail,
renderIslamicMonthDetail,
renderWheelMonthDetail
};
})();