Files
TaroTime/app/ui-cube-detail.js

538 lines
18 KiB
JavaScript
Raw Normal View History

2026-03-07 05:17:50 -08:00
/* ui-cube-detail.js — Cube detail pane rendering */
(function () {
"use strict";
function toDisplayText(value) {
return String(value ?? "").trim();
}
function escapeHtml(value) {
return String(value)
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/\"/g, "&quot;")
.replace(/'/g, "&#39;");
}
function toDetailValueMarkup(value) {
const text = toDisplayText(value);
return text ? escapeHtml(text) : '<span class="cube-missing-value">!</span>';
}
function createMetaCard(title, bodyContent) {
const card = document.createElement("div");
card.className = "planet-meta-card";
const titleEl = document.createElement("strong");
titleEl.textContent = title;
card.appendChild(titleEl);
if (typeof bodyContent === "string") {
const bodyEl = document.createElement("p");
bodyEl.className = "planet-text";
bodyEl.textContent = bodyContent;
card.appendChild(bodyEl);
} else if (bodyContent instanceof Node) {
card.appendChild(bodyContent);
}
return card;
}
function createNavButton(label, eventName, detail) {
const button = document.createElement("button");
button.type = "button";
button.className = "kab-god-link";
button.textContent = `${label}`;
button.addEventListener("click", () => {
document.dispatchEvent(new CustomEvent(eventName, { detail }));
});
return button;
}
function renderCenterDetail(context) {
const { state, elements, getCubeCenterData, getCenterLetterId, getCenterLetterSymbol, toFiniteNumber } = context;
if (!state.showPrimalPoint) {
return false;
}
const center = getCubeCenterData();
if (!center || !elements?.detailNameEl || !elements?.detailSubEl || !elements?.detailBodyEl) {
return false;
}
const centerLetterId = getCenterLetterId(center);
const centerLetter = getCenterLetterSymbol(center);
const centerLetterText = centerLetterId
? `${centerLetter ? `${centerLetter} ` : ""}${toDisplayText(centerLetterId)}`
: "";
const centerElement = toDisplayText(center?.element);
elements.detailNameEl.textContent = "Primal Point";
elements.detailSubEl.textContent = [centerLetterText, centerElement].filter(Boolean).join(" · ") || "Center of the Cube";
const bodyEl = elements.detailBodyEl;
bodyEl.innerHTML = "";
const summary = document.createElement("div");
summary.className = "planet-text";
summary.innerHTML = `
<dl class="alpha-dl">
<dt>Name</dt><dd>${toDetailValueMarkup(center?.name)}</dd>
<dt>Letter</dt><dd>${toDetailValueMarkup(centerLetterText)}</dd>
<dt>Element</dt><dd>${toDetailValueMarkup(center?.element)}</dd>
</dl>
`;
bodyEl.appendChild(createMetaCard("Center Details", summary));
if (Array.isArray(center?.keywords) && center.keywords.length) {
bodyEl.appendChild(createMetaCard("Keywords", center.keywords.join(", ")));
}
if (center?.description) {
bodyEl.appendChild(createMetaCard("Description", center.description));
}
const associations = center?.associations || {};
const links = document.createElement("div");
links.className = "kab-god-links";
if (centerLetterId) {
links.appendChild(createNavButton(centerLetter || "!", "nav:alphabet", {
alphabet: "hebrew",
hebrewLetterId: centerLetterId
}));
}
const centerTrumpNo = toFiniteNumber(associations?.tarotTrumpNumber);
const centerTarotCard = toDisplayText(associations?.tarotCard);
if (centerTarotCard || centerTrumpNo != null) {
links.appendChild(createNavButton(centerTarotCard || `Trump ${centerTrumpNo}`, "nav:tarot-trump", {
cardName: centerTarotCard,
trumpNumber: centerTrumpNo
}));
}
const centerPathNo = toFiniteNumber(associations?.kabbalahPathNumber);
if (centerPathNo != null) {
links.appendChild(createNavButton(`Path ${centerPathNo}`, "nav:kabbalah-path", {
pathNo: centerPathNo
}));
}
if (links.childElementCount) {
const linksCard = document.createElement("div");
linksCard.className = "planet-meta-card";
linksCard.innerHTML = "<strong>Correspondence Links</strong>";
linksCard.appendChild(links);
bodyEl.appendChild(linksCard);
}
return true;
}
function renderConnectorDetail(context) {
const {
state,
elements,
walls,
normalizeId,
normalizeLetterKey,
formatDirectionName,
getWallById,
getConnectorById,
getConnectorPathEntry,
getHebrewLetterSymbol,
toFiniteNumber
} = context;
const connector = getConnectorById(state.selectedConnectorId);
if (!connector || !elements?.detailNameEl || !elements?.detailSubEl || !elements?.detailBodyEl) {
return false;
}
const fromWallId = normalizeId(connector?.fromWallId);
const toWallId = normalizeId(connector?.toWallId);
const fromWall = getWallById(fromWallId) || walls.find((entry) => normalizeId(entry?.id) === fromWallId) || null;
const toWall = getWallById(toWallId) || walls.find((entry) => normalizeId(entry?.id) === toWallId) || null;
const connectorPath = getConnectorPathEntry(connector);
const letterId = normalizeLetterKey(connector?.hebrewLetterId);
const letterSymbol = getHebrewLetterSymbol(letterId);
const letterText = letterId
? `${letterSymbol ? `${letterSymbol} ` : ""}${toDisplayText(letterId)}`
: "";
const pathNo = toFiniteNumber(connectorPath?.pathNumber);
const tarotCard = toDisplayText(connectorPath?.tarot?.card);
const tarotTrumpNumber = toFiniteNumber(connectorPath?.tarot?.trumpNumber);
const astrologyType = toDisplayText(connectorPath?.astrology?.type);
const astrologyName = toDisplayText(connectorPath?.astrology?.name);
const astrologySummary = [astrologyType, astrologyName].filter(Boolean).join(": ");
elements.detailNameEl.textContent = connector?.name || "Mother Connector";
elements.detailSubEl.textContent = ["Mother Letter", letterText].filter(Boolean).join(" · ") || "Mother Letter";
const bodyEl = elements.detailBodyEl;
bodyEl.innerHTML = "";
const summary = document.createElement("div");
summary.className = "planet-text";
summary.innerHTML = `
<dl class="alpha-dl">
<dt>Letter</dt><dd>${toDetailValueMarkup(letterText)}</dd>
<dt>From</dt><dd>${toDetailValueMarkup(fromWall?.name || formatDirectionName(fromWallId))}</dd>
<dt>To</dt><dd>${toDetailValueMarkup(toWall?.name || formatDirectionName(toWallId))}</dd>
<dt>Tarot</dt><dd>${toDetailValueMarkup(tarotCard || (tarotTrumpNumber != null ? `Trump ${tarotTrumpNumber}` : ""))}</dd>
</dl>
`;
bodyEl.appendChild(createMetaCard("Connector Details", summary));
if (astrologySummary) {
bodyEl.appendChild(createMetaCard("Astrology", astrologySummary));
}
const links = document.createElement("div");
links.className = "kab-god-links";
if (letterId) {
links.appendChild(createNavButton(letterSymbol || "!", "nav:alphabet", {
alphabet: "hebrew",
hebrewLetterId: letterId
}));
}
if (pathNo != null) {
links.appendChild(createNavButton(`Path ${pathNo}`, "nav:kabbalah-path", { pathNo }));
}
if (tarotCard || tarotTrumpNumber != null) {
links.appendChild(createNavButton(tarotCard || `Trump ${tarotTrumpNumber}`, "nav:tarot-trump", {
cardName: tarotCard,
trumpNumber: tarotTrumpNumber
}));
}
if (links.childElementCount) {
const linksCard = document.createElement("div");
linksCard.className = "planet-meta-card";
linksCard.innerHTML = "<strong>Correspondence Links</strong>";
linksCard.appendChild(links);
bodyEl.appendChild(linksCard);
}
return true;
}
function renderEdgeCard(context, wall, detailBodyEl, wallEdgeDirections) {
const {
state,
normalizeId,
normalizeEdgeId,
formatDirectionName,
formatEdgeName,
getEdgeById,
getEdgesForWall,
getEdges,
getEdgeWalls,
getEdgeLetterId,
getEdgeLetter,
getEdgePathEntry,
getEdgeAstrologySymbol,
toFiniteNumber
} = context;
const wallId = normalizeId(wall?.id);
const selectedEdge = getEdgeById(state.selectedEdgeId)
|| getEdgesForWall(wallId)[0]
|| getEdges()[0]
|| null;
if (!selectedEdge) {
return;
}
state.selectedEdgeId = normalizeEdgeId(selectedEdge.id);
const edgeDirection = wallEdgeDirections.get(normalizeEdgeId(selectedEdge.id));
const edgeName = edgeDirection
? formatDirectionName(edgeDirection)
: (toDisplayText(selectedEdge.name) || formatEdgeName(selectedEdge.id));
const edgeWalls = getEdgeWalls(selectedEdge)
.map((entry) => entry.charAt(0).toUpperCase() + entry.slice(1))
.join(" · ");
const edgeLetterId = getEdgeLetterId(selectedEdge);
const edgeLetter = getEdgeLetter(selectedEdge);
const edgePath = getEdgePathEntry(selectedEdge);
const astrologyType = toDisplayText(edgePath?.astrology?.type);
const astrologyName = toDisplayText(edgePath?.astrology?.name);
const astrologySymbol = getEdgeAstrologySymbol(selectedEdge);
const astrologyText = astrologySymbol && astrologyName
? `${astrologySymbol} ${astrologyName}`
: astrologySymbol || astrologyName;
const pathNo = toFiniteNumber(edgePath?.pathNumber);
const tarotCard = toDisplayText(edgePath?.tarot?.card);
const tarotTrumpNumber = toFiniteNumber(edgePath?.tarot?.trumpNumber);
const edgeCard = document.createElement("div");
edgeCard.className = "planet-meta-card";
const title = document.createElement("strong");
title.textContent = `Edge · ${edgeName}`;
edgeCard.appendChild(title);
const dlWrap = document.createElement("div");
dlWrap.className = "planet-text";
dlWrap.innerHTML = `
<dl class="alpha-dl">
<dt>Direction</dt><dd>${toDetailValueMarkup(edgeName)}</dd>
<dt>Edge</dt><dd>${toDetailValueMarkup(edgeWalls)}</dd>
<dt>Letter</dt><dd>${toDetailValueMarkup(edgeLetter)}</dd>
<dt>Astrology</dt><dd>${toDetailValueMarkup(astrologyText)}</dd>
<dt>Tarot</dt><dd>${toDetailValueMarkup(tarotCard)}</dd>
</dl>
`;
edgeCard.appendChild(dlWrap);
if (Array.isArray(selectedEdge.keywords) && selectedEdge.keywords.length) {
const keywords = document.createElement("p");
keywords.className = "planet-text";
keywords.textContent = selectedEdge.keywords.join(", ");
edgeCard.appendChild(keywords);
}
if (selectedEdge.description) {
const description = document.createElement("p");
description.className = "planet-text";
description.textContent = selectedEdge.description;
edgeCard.appendChild(description);
}
const links = document.createElement("div");
links.className = "kab-god-links";
if (edgeLetterId) {
links.appendChild(createNavButton(edgeLetter || "!", "nav:alphabet", {
alphabet: "hebrew",
hebrewLetterId: edgeLetterId
}));
}
if (astrologyType === "zodiac" && astrologyName) {
links.appendChild(createNavButton(astrologyName, "nav:zodiac", {
signId: normalizeId(astrologyName)
}));
}
if (tarotCard) {
links.appendChild(createNavButton(tarotCard, "nav:tarot-trump", {
cardName: tarotCard,
trumpNumber: tarotTrumpNumber
}));
}
if (pathNo != null) {
links.appendChild(createNavButton(`Path ${pathNo}`, "nav:kabbalah-path", { pathNo }));
}
if (links.childElementCount) {
edgeCard.appendChild(links);
}
detailBodyEl.appendChild(edgeCard);
}
function renderWallDetail(context) {
const {
state,
elements,
walls,
normalizeId,
normalizeEdgeId,
formatDirectionName,
formatEdgeName,
getWallById,
getEdgesForWall,
getWallEdgeDirections,
getWallFaceLetterId,
getWallFaceLetter,
getHebrewLetterName,
getEdgeLetter,
localDirectionOrder,
localDirectionRank,
onSelectWall,
onSelectEdge
} = context;
const wall = getWallById(state.selectedWallId) || walls[0] || null;
if (!wall || !elements?.detailNameEl || !elements?.detailSubEl || !elements?.detailBodyEl) {
if (elements?.detailNameEl) {
elements.detailNameEl.textContent = "Cube data unavailable";
}
if (elements?.detailSubEl) {
elements.detailSubEl.textContent = "Could not load cube dataset.";
}
if (elements?.detailBodyEl) {
elements.detailBodyEl.innerHTML = "";
}
return;
}
state.selectedWallId = normalizeId(wall.id);
const wallPlanet = toDisplayText(wall?.planet) || "!";
const wallElement = toDisplayText(wall?.element) || "!";
const wallFaceLetterId = getWallFaceLetterId(wall);
const wallFaceLetter = getWallFaceLetter(wall);
const wallFaceLetterText = wallFaceLetterId
? `${wallFaceLetter ? `${wallFaceLetter} ` : ""}${toDisplayText(wallFaceLetterId)}`
: "";
elements.detailNameEl.textContent = `${wall.name} Wall`;
elements.detailSubEl.textContent = `${wallElement} · ${wallPlanet}`;
const bodyEl = elements.detailBodyEl;
bodyEl.innerHTML = "";
const summary = document.createElement("div");
summary.className = "planet-text";
summary.innerHTML = `
<dl class="alpha-dl">
<dt>Opposite</dt><dd>${toDetailValueMarkup(wall.opposite)}</dd>
<dt>Face Letter</dt><dd>${toDetailValueMarkup(wallFaceLetterText)}</dd>
<dt>Element</dt><dd>${toDetailValueMarkup(wall.element)}</dd>
<dt>Planet</dt><dd>${toDetailValueMarkup(wall.planet)}</dd>
<dt>Archangel</dt><dd>${toDetailValueMarkup(wall.archangel)}</dd>
</dl>
`;
bodyEl.appendChild(createMetaCard("Wall Details", summary));
if (Array.isArray(wall.keywords) && wall.keywords.length) {
bodyEl.appendChild(createMetaCard("Keywords", wall.keywords.join(", ")));
}
if (wall.description) {
bodyEl.appendChild(createMetaCard("Description", wall.description));
}
const wallLinksCard = document.createElement("div");
wallLinksCard.className = "planet-meta-card";
wallLinksCard.innerHTML = "<strong>Correspondence Links</strong>";
const wallLinks = document.createElement("div");
wallLinks.className = "kab-god-links";
if (wallFaceLetterId) {
const wallFaceLetterName = getHebrewLetterName(wallFaceLetterId) || toDisplayText(wallFaceLetterId);
const faceLetterText = [wallFaceLetter, wallFaceLetterName].filter(Boolean).join(" ");
const faceLetterLabel = faceLetterText
? `Face ${faceLetterText}`
: "Face !";
wallLinks.appendChild(createNavButton(faceLetterLabel, "nav:alphabet", {
alphabet: "hebrew",
hebrewLetterId: wallFaceLetterId
}));
}
const wallAssociations = wall.associations || {};
if (wallAssociations.planetId) {
wallLinks.appendChild(createNavButton(toDisplayText(wall.planet) || "!", "nav:planet", {
planetId: wallAssociations.planetId
}));
}
if (wallAssociations.godName) {
wallLinks.appendChild(createNavButton(wallAssociations.godName, "nav:gods", {
godName: wallAssociations.godName
}));
}
if (wall.oppositeWallId) {
const oppositeWall = getWallById(wall.oppositeWallId);
const internal = document.createElement("button");
internal.type = "button";
internal.className = "kab-god-link";
internal.textContent = `Opposite: ${oppositeWall?.name || wall.oppositeWallId}`;
internal.addEventListener("click", () => {
onSelectWall(wall.oppositeWallId);
});
wallLinks.appendChild(internal);
}
if (wallLinks.childElementCount) {
wallLinksCard.appendChild(wallLinks);
bodyEl.appendChild(wallLinksCard);
}
const edgesCard = document.createElement("div");
edgesCard.className = "planet-meta-card";
edgesCard.innerHTML = "<strong>Wall Edges</strong>";
const chips = document.createElement("div");
chips.className = "kab-chips";
const wallEdgeDirections = getWallEdgeDirections(wall);
const wallEdges = getEdgesForWall(wall)
.slice()
.sort((left, right) => {
const leftDirection = wallEdgeDirections.get(normalizeEdgeId(left?.id));
const rightDirection = wallEdgeDirections.get(normalizeEdgeId(right?.id));
const leftRank = localDirectionRank[leftDirection] ?? localDirectionOrder.length;
const rightRank = localDirectionRank[rightDirection] ?? localDirectionOrder.length;
if (leftRank !== rightRank) {
return leftRank - rightRank;
}
return normalizeEdgeId(left?.id).localeCompare(normalizeEdgeId(right?.id));
});
wallEdges.forEach((edge) => {
const id = normalizeEdgeId(edge.id);
const chipLetter = getEdgeLetter(edge);
const chipIsMissing = !chipLetter;
const direction = wallEdgeDirections.get(id);
const directionLabel = direction
? formatDirectionName(direction)
: (toDisplayText(edge.name) || formatEdgeName(edge.id));
const chip = document.createElement("span");
chip.className = `kab-chip${id === normalizeEdgeId(state.selectedEdgeId) ? " is-active" : ""}${chipIsMissing ? " is-missing" : ""}`;
chip.setAttribute("role", "button");
chip.setAttribute("tabindex", "0");
chip.textContent = `${directionLabel} · ${chipLetter || "!"}`;
const selectEdge = () => {
onSelectEdge(id, wall.id);
};
chip.addEventListener("click", selectEdge);
chip.addEventListener("keydown", (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
selectEdge();
}
});
chips.appendChild(chip);
});
edgesCard.appendChild(chips);
bodyEl.appendChild(edgesCard);
renderEdgeCard(context, wall, bodyEl, wallEdgeDirections);
}
function renderDetail(context) {
if (context.state.selectedNodeType === "connector" && renderConnectorDetail(context)) {
return;
}
if (context.state.selectedNodeType === "center" && renderCenterDetail(context)) {
return;
}
renderWallDetail(context);
}
window.CubeDetailUi = {
renderDetail
};
})();