A complete guide to all configured constellations. This content will be included in the PDF download.
Add, edit, or remove constellations from the database. (Spec #4, #6)
Full Constellation Guide
';
}
};
}
/**
* Renders a single constellation's data into a target element
*/
function displayConstellation(name, targetElement) {
if (!constellationData[name]) return;
const data = constellationData[name];
let stars = [];
let lines = [];
try {
stars = JSON.parse(data.starsJSON);
lines = JSON.parse(data.linesJSON);
} catch (e) {
console.error("Failed to parse constellation JSON:", e);
targetElement.innerHTML = `
Error loading data for ${name}.
`;
return;
}
let svgHTML = '';
targetElement.innerHTML = `
${name}
${svgHTML}
Best Season to View
${data.season || 'N/A'}
Mythology
${data.mythology || 'No mythology data available.'}
`;
}
/**
* Builds the full guide for Tab 2 and the PDF
*/
function buildFullGuide() {
guideAllContainer.innerHTML = "";
pdfGuideAll.innerHTML = "";
for (const name in constellationData) {
// Create div for on-screen guide
const itemDiv = document.createElement("div");
itemDiv.className = "guide-item";
displayConstellation(name, itemDiv);
guideAllContainer.appendChild(itemDiv);
// Create div for PDF buffer
const pdfItemDiv = document.createElement("div");
pdfItemDiv.className = "pdf-guide-item";
// Clone the inner content, but change the SVG class for PDF styling
pdfItemDiv.innerHTML = itemDiv.innerHTML.replace(
'class="constellation-svg-map"',
'class="pdf-constellation-svg-map"'
);
pdfGuideAll.appendChild(pdfItemDiv);
}
}
/**
* Builds the configuration tab inputs from constellationData
*/
function buildConfig() {
configContainer.innerHTML = ""; // Clear existing
for (const name in constellationData) {
configContainer.appendChild(
createConfigItem(name, constellationData[name])
);
}
}
/**
* Helper to create a single config item block
*/
function createConfigItem(name, data) {
const id = `cfg-item-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const itemDiv = document.createElement("div");
itemDiv.className = "config-constellation";
itemDiv.id = id;
itemDiv.innerHTML = `
e.g., [{"x":10, "y":20, "name":"Star1"}, ...]
e.g., [[0,1], [1,2]] (connects star 0 to 1, 1 to 2)
`;
return itemDiv;
}
/**
* Saves the data from the config tab back to constellationData
*/
window.saveConstellationConfig = function () {
const newConstellationData = {};
configContainer
.querySelectorAll(".config-constellation")
.forEach((item) => {
const name = item.querySelector(".config-name").value.trim();
if (name) {
newConstellationData[name] = {
season: item.querySelector(".config-season").value,
mythology: item.querySelector(".config-myth").value,
starsJSON: item.querySelector(".config-stars-json").value,
linesJSON: item.querySelector(".config-lines-json").value,
};
}
});
constellationData = newConstellationData; // Update the global data object
buildDashboard(); // Re-build dashboard (Tab 1)
buildFullGuide(); // Re-build full guide (Tab 2)
if (saveMessage) {
saveMessage.style.display = "inline";
setTimeout(() => {
saveMessage.style.display = "none";
}, 2000);
}
showConstellationTab("constellation-tab-dashboard");
};
/**
* Adds a new empty item to the config tab
*/
window.addConstellationConfigItem = function () {
const defaultData = {
season: "N/A",
mythology: "",
starsJSON: '[{"x":50,"y":50,"name":"New Star"}]',
linesJSON: '[]'
};
configContainer.appendChild(
createConfigItem("New Constellation", defaultData)
);
};
/**
* Removes a DOM element from the config tab
*/
window.removeConstellationElement = function (elementId) {
const element = document.getElementById(elementId);
if (element && element.parentNode) {
element.parentNode.removeChild(element);
}
};
/**
* Switches the active tab
* (Spec II.B.4)
*/
window.showConstellationTab = function (tabId) {
tabContents.forEach((content) => {
content.classList.remove("active");
});
tabButtons.forEach((button) => {
button.classList.remove("active");
});
const contentToShow = document.getElementById(tabId);
const buttonToActivate = document.querySelector(
`[onclick="showConstellationTab('${tabId}')"]`
);
if (contentToShow) contentToShow.classList.add("active");
if (buttonToActivate) buttonToActivate.classList.add("active");
if (tabId === "constellation-tab-config") {
buildConfig();
}
};
/**
* Navigates between tabs using Next/Previous buttons
* (Spec II.B.4.o)
*/
window.navigateConstellationTabs = function (direction) {
let currentIndex = -1;
tabButtons.forEach((button, index) => {
if (button.classList.contains("active")) {
currentIndex = index;
}
});
if (currentIndex === -1) return;
let nextIndex;
if (direction === "next") {
nextIndex = (currentIndex + 1) % tabButtons.length;
} else {
nextIndex =
(currentIndex - 1 + tabButtons.length) % tabButtons.length;
}
const nextButton = tabButtons[nextIndex];
const onclickAttr = nextButton.getAttribute("onclick");
const tabId = onclickAttr.match(/'([^']*)'/)[1];
if (tabId) showConstellationTab(tabId);
};
// Make tab functions global for inline onclick
window.showConstellationTab = showConstellationTab;
window.navigateConstellationTabs = navigateConstellationTabs;
window.addConstellationConfigItem = addConstellationConfigItem;
window.removeConstellationElement = removeConstellationElement;
/**
* Downloads the output as a PDF
* (Spec II.C)
*/
function downloadConstellationPDF() {
if (
typeof jspdf === "undefined" ||
typeof html2canvas === "undefined"
) {
console.error("PDF libraries not loaded.");
return;
}
if (pdfGuideAll.children.length === 0) {
console.error("No guide content to download.");
return;
}
const { jsPDF } = jspdf;
const pdfOutputElement = document.getElementById("pdf-output-content");
pdfOutputElement.style.display = "block";
pdfOutputElement.style.position = "absolute";
pdfOutputElement.style.left = "-9999px";
html2canvas(pdfOutputElement, {
scale: 2,
backgroundColor: "#ffffff",
}).then((canvas) => {
pdfOutputElement.style.display = "none";
pdfOutputElement.style.position = "static";
const imgData = canvas.toDataURL("image/png");
const pdf = new jsPDF({
orientation: "p",
unit: "px",
format: "a4",
});
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = pdf.internal.pageSize.getHeight();
const canvasWidth = canvas.width;
const canvasHeight = canvas.height;
const ratio = canvasWidth / canvasHeight;
const imgWidth = pdfWidth - 40; // 20px margin
const imgHeight = imgWidth / ratio;
const pageMargin = 20;
if (imgHeight <= pdfHeight - 2 * pageMargin) {
pdf.addImage(imgData, "PNG", pageMargin, pageMargin, imgWidth, imgHeight);
} else {
// Handle multiple pages
let heightLeft = canvasHeight;
let canvasPosition = 0;
const pageHeightInPixels = (pdfHeight - 2 * pageMargin) * (canvasWidth / imgWidth);
while (heightLeft > 0.1) {
const pageHeight = Math.min(heightLeft, pageHeightInPixels);
const pageImgHeight = (pageHeight * imgWidth) / canvasWidth;
const tempCanvas = document.createElement("canvas");
tempCanvas.width = canvasWidth;
tempCanvas.height = pageHeight;
const ctx = tempCanvas.getContext("2d");
ctx.drawImage(
canvas,
0, canvasPosition, canvasWidth, pageHeight,
0, 0, canvasWidth, pageHeight
);
const pageImgData = tempCanvas.toDataURL("image/png");
pdf.addImage(
pageImgData, "PNG", pageMargin, pageMargin, imgWidth, pageImgHeight
);
heightLeft -= pageHeight;
canvasPosition += pageHeight;
if (heightLeft > 0.1) pdf.addPage();
}
}
pdf.save("constellation-guide.pdf");
});
}
// --- 5. Event Listeners (Spec IV.C) ---
pdfDownloadBtn.addEventListener("click", downloadConstellationPDF);
addConstellationBtn.addEventListener("click", window.addConstellationConfigItem);
saveConfigBtn.addEventListener("click", window.saveConstellationConfig);
// --- 6. Initialization ---
buildDashboard();
buildFullGuide();
buildConfig();
showConstellationTab("constellation-tab-dashboard");
});