Files
peworkshop/utils/map-populate.js
T
2025-03-16 21:11:23 +00:00

178 lines
5.5 KiB
JavaScript

/**
* Utility module for processing and populating the treasure map SVG.
* Handles clue points, their status, and interaction.
*/
/**
* Populates the SVG map with clue points
* @param {SVGElement} svgElement - The SVG DOM element to process
* @returns {Promise<boolean>} - Success flag
*/
export async function populateMap(svgElement) {
try {
if (!svgElement) {
console.error("No SVG element provided");
return false;
}
console.log("Starting map population process");
// First, find all existing circle elements in the SVG
const existingCircles = Array.from(svgElement.querySelectorAll("circle"));
console.log(`Found ${existingCircles.length} existing circles in SVG`);
// Hide all existing circles initially
existingCircles.forEach((circle) => {
circle.style.display = "none";
});
// Query the number of clues via API
const clueCountResponse = await fetch("/api/clues/count", {
credentials: "include",
cache: "no-cache",
});
if (!clueCountResponse.ok) {
throw new Error(
`Failed to fetch clue count: ${clueCountResponse.status}`
);
}
const { count: clueCount } = await clueCountResponse.json();
console.log(`Total clues: ${clueCount}`);
// Determine how many circles to display (min of existing circles and clue count)
const circlesToShow = Math.min(existingCircles.length, clueCount);
console.log(`Will display ${circlesToShow} circles`);
// Sort circles by vertical position (top to bottom)
const sortedCircles = existingCircles.sort((a, b) => {
const aY = parseFloat(a.getAttribute("cy"));
const bY = parseFloat(b.getAttribute("cy"));
return aY - bY;
});
// Display only the needed circles and add clue-point class
for (let i = 0; i < circlesToShow; i++) {
const circle = sortedCircles[i];
circle.style.display = "";
circle.classList.add("clue-point");
circle.setAttribute("data-clue-id", i + 1);
// Set default appearance
circle.style.fill = "#9e9e9e"; // Default grey color
circle.style.stroke = "#757575";
circle.style.strokeWidth = "2";
}
return true;
} catch (error) {
console.error("Error populating map:", error);
return false;
}
}
/**
* Updates the status of each clue point based on clues data
* @param {SVGElement} svgElement - The SVG DOM element
* @param {Array} clues - Array of clue objects with their status
*/
export function updateCluePointStatus(svgElement, clues) {
if (!svgElement || !clues || !clues.length) {
console.error("Missing required parameters for updating clue status");
return;
}
console.log("Updating clue point status");
try {
// Get all clue points
const cluePoints = svgElement.querySelectorAll(".clue-point");
if (!cluePoints.length) {
console.error("No clue points found in SVG");
return;
}
// Find the first unfound clue (the current one)
const currentClueIndex = clues.findIndex((clue) => !clue.found);
const currentClueId =
currentClueIndex !== -1 ? clues[currentClueIndex].id : null;
// Process each clue point
cluePoints.forEach((circle, index) => {
if (index >= clues.length) return;
const clue = clues[index];
const clueId = clue.id;
// Determine status and apply appropriate color
if (clue.found) {
// Found/completed clue - Dark Green
circle.style.fill = "#2e7d32"; // Dark green
} else if (clueId === currentClueId) {
// Current clue - Orange
circle.style.fill = "#ff9800"; // Orange
} else if (clueId < currentClueId) {
// Skipped clue - Blue
circle.style.fill = "#1976d2"; // Blue
} else {
// Not accessible yet - Grey
circle.style.fill = "#9e9e9e"; // Grey
}
// Mark whether this clue point is available
const isAvailable = clue.found || clueId === currentClueId;
circle.setAttribute("data-available", isAvailable.toString());
});
console.log("Clue points updated successfully");
} catch (error) {
console.error("Error updating clue status:", error);
}
}
/**
* Attaches click handlers to clue points
* @param {SVGElement} svgElement - The SVG DOM element
* @param {Array} clues - Array of clue objects with their status
* @param {Function} navigationHandler - Function to handle navigation
*/
export function attachClueClickHandlers(svgElement, clues, navigationHandler) {
if (!svgElement || !clues || !navigationHandler) {
console.error("Missing required parameters for attaching click handlers");
return;
}
try {
const cluePoints = svgElement.querySelectorAll(".clue-point");
cluePoints.forEach((circle, index) => {
if (index >= clues.length) return;
const clue = clues[index];
const isAvailable = circle.getAttribute("data-available") === "true";
// Set cursor style based on availability
circle.style.cursor = isAvailable ? "pointer" : "not-allowed";
// Add click handler
circle.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
if (isAvailable) {
console.log(`Clicked on clue ${clue.id}`);
navigationHandler(clue.id);
} else {
// Alert or other feedback if clue is not accessible
console.log("Clue not available yet");
// Could show a tooltip or message here
}
};
});
} catch (error) {
console.error("Error attaching click handlers:", error);
}
}