/** * Utility to dynamically populate the map SVG with clue points * based on the total number of clues in the database */ /** * Fetches the total number of clues from the API * @returns {Promise} The total number of clues */ async function fetchTotalClues() { try { const response = await fetch("/api/clues", { method: "GET", headers: { "Content-Type": "application/json", "Cache-Control": "no-store, no-cache, must-revalidate", }, credentials: "include", }); if (!response.ok) { throw new Error(`Failed to fetch clues: ${response.status}`); } const clues = await response.json(); return clues.length; } catch (error) { console.error("Error fetching total clues:", error); return 0; } } /** * Shows the specified number of clue points on the map * @param {SVGElement} svgElement - The SVG DOM element of the map * @param {number} numClues - Number of clues to display * @returns {boolean} Success status */ function showCluePoints(svgElement, numClues) { if (!svgElement || !numClues) { console.error("Invalid SVG element or number of clues"); return false; } try { // Find all clue points in the SVG - improved selector for better compatibility const cluePoints = Array.from(svgElement.querySelectorAll("circle")).filter( (circle) => circle.classList.contains("clue-point") || circle.getAttribute("class")?.includes("clue-point") ); console.log(`Found ${cluePoints.length} clue points in SVG`); // If no clue points are found, add them programmatically if (cluePoints.length === 0) { console.log("No clue points found, creating them dynamically"); return createCluePoints(svgElement, numClues); } // Hide all clue points first cluePoints.forEach((circle) => { circle.style.display = "none"; }); // Show only the required number of clue points for (let i = 0; i < Math.min(numClues, cluePoints.length); i++) { const circle = cluePoints[i]; circle.style.display = ""; // Ensure the circle has the clue-point class and a data-clue-id circle.classList.add("clue-point"); if (!circle.hasAttribute("data-clue-id")) { circle.setAttribute("data-clue-id", (i + 1).toString()); } } console.log( `Showing ${Math.min(numClues, cluePoints.length)} clue points on map` ); return true; } catch (error) { console.error("Error showing clue points:", error); return false; } } /** * Creates clue points dynamically if they don't exist in the SVG * @param {SVGElement} svgElement - The SVG DOM element of the map * @param {number} numClues - Number of clues to create * @returns {boolean} Success status */ function createCluePoints(svgElement, numClues) { try { const svgWidth = svgElement.viewBox.baseVal.width || 800; const svgHeight = svgElement.viewBox.baseVal.height || 600; // Define positions for clue points - spread across the map const positions = []; // Calculate positions in a grid pattern const cols = Math.ceil(Math.sqrt(numClues)); const rows = Math.ceil(numClues / cols); const marginX = svgWidth * 0.1; const marginY = svgHeight * 0.1; const spacingX = (svgWidth - 2 * marginX) / (cols - 1 || 1); const spacingY = (svgHeight - 2 * marginY) / (rows - 1 || 1); for (let row = 0; row < rows; row++) { for (let col = 0; col < cols; col++) { const index = row * cols + col; if (index < numClues) { positions.push({ x: marginX + col * spacingX, y: marginY + row * spacingY, }); } } } // Create circles for each clue positions.forEach((pos, index) => { const circle = document.createElementNS( "http://www.w3.org/2000/svg", "circle" ); circle.setAttribute("cx", pos.x); circle.setAttribute("cy", pos.y); circle.setAttribute("r", 10); circle.setAttribute("fill", "#9e9e9e"); circle.setAttribute("stroke", "#757575"); circle.setAttribute("stroke-width", "2"); circle.setAttribute("class", "clue-point"); circle.setAttribute("data-clue-id", (index + 1).toString()); svgElement.appendChild(circle); }); console.log(`Created ${numClues} clue points dynamically`); return true; } catch (error) { console.error("Error creating clue points:", error); return false; } } /** * Main function to populate map with the correct number of clue points * @param {SVGElement} svgElement - The SVG DOM element of the map * @returns {Promise} Success status */ export async function populateMap(svgElement) { if (!svgElement) { console.error("No SVG element provided"); return false; } try { console.log("Starting to populate map"); // Fetch total number of clues const totalClues = await fetchTotalClues(); console.log("Total clues fetched:", totalClues); if (totalClues > 0) { // Show the appropriate number of clue points return showCluePoints(svgElement, totalClues); } else { console.warn("No clues found in database"); return false; } } catch (error) { console.error("Error populating map:", error); return false; } } /** * Updates the styling of clue points based on their status * @param {SVGElement} svgElement - The SVG DOM element of the map * @param {Array} clues - Array of clue objects with status information */ export function updateCluePointStatus(svgElement, clues) { if (!svgElement || !clues || !clues.length) { console.error("Invalid SVG element or clues data"); return; } try { clues.forEach((clue, index) => { // Find the corresponding circle in the SVG const circle = svgElement.querySelector( `.clue-point[data-clue-id="${clue.id}"]` ); if (circle) { // Apply appropriate styling based on clue status if (clue.found) { // Found clues are green circle.style.fill = "#4caf50"; circle.style.stroke = "#2e7d32"; } else if (clue.available) { // Available clues are orange circle.style.fill = "#ff9800"; circle.style.stroke = "#d87f00"; } else { // Unavailable clues remain grey circle.style.fill = "#9e9e9e"; circle.style.stroke = "#757575"; } } }); } catch (error) { console.error("Error updating clue point status:", error); } }