mirror of
https://github.com/mudabbir-ahmad/peworkshop.git
synced 2026-10-07 19:50:20 +00:00
map WIP
This commit is contained in:
1 parent
25e877cc3a
commit
2236dceac9
4 files changed
+441
-125
No files matched your search
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Utility for debugging SVG rendering issues
|
||||
*/
|
||||
|
||||
/**
|
||||
* Adds a visible debug overlay to the SVG to test z-index rendering
|
||||
* @param {SVGElement} svgElement - The SVG DOM element
|
||||
*/
|
||||
export function addDebugOverlay(svgElement) {
|
||||
if (!svgElement) return;
|
||||
|
||||
const svgNS = "http://www.w3.org/2000/svg";
|
||||
|
||||
// Create a debug group at the highest level
|
||||
const debugGroup = document.createElementNS(svgNS, "g");
|
||||
debugGroup.setAttribute("id", "debug-overlay");
|
||||
debugGroup.setAttribute(
|
||||
"style",
|
||||
"pointer-events: none; z-index: 10000; position: relative;"
|
||||
);
|
||||
|
||||
// Add a semi-transparent rectangle across the entire SVG
|
||||
const svgWidth = svgElement.viewBox?.baseVal?.width || 1000;
|
||||
const svgHeight = svgElement.viewBox?.baseVal?.height || 1000;
|
||||
|
||||
const rect = document.createElementNS(svgNS, "rect");
|
||||
rect.setAttribute("x", "0");
|
||||
rect.setAttribute("y", "0");
|
||||
rect.setAttribute("width", svgWidth.toString());
|
||||
rect.setAttribute("height", svgHeight.toString());
|
||||
rect.setAttribute("fill", "rgba(255, 255, 0, 0.1)");
|
||||
rect.setAttribute("stroke", "none");
|
||||
|
||||
// Add some test circles at fixed positions
|
||||
const testPositions = [
|
||||
{ x: 50, y: 50, color: "red" },
|
||||
{ x: 150, y: 150, color: "blue" },
|
||||
{ x: 250, y: 250, color: "green" },
|
||||
{ x: 350, y: 350, color: "purple" },
|
||||
];
|
||||
|
||||
testPositions.forEach((pos, i) => {
|
||||
const testCircle = document.createElementNS(svgNS, "circle");
|
||||
testCircle.setAttribute("cx", pos.x.toString());
|
||||
testCircle.setAttribute("cy", pos.y.toString());
|
||||
testCircle.setAttribute("r", "20");
|
||||
testCircle.setAttribute("fill", pos.color);
|
||||
testCircle.setAttribute("fill-opacity", "0.5");
|
||||
testCircle.setAttribute("stroke", "black");
|
||||
testCircle.setAttribute("stroke-width", "2");
|
||||
testCircle.setAttribute("class", "debug-circle");
|
||||
|
||||
debugGroup.appendChild(testCircle);
|
||||
});
|
||||
|
||||
// Add the debug group to the SVG
|
||||
svgElement.appendChild(debugGroup);
|
||||
|
||||
console.log("Debug overlay added to SVG");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if clue points are visible and fixes rendering issues
|
||||
* @param {SVGElement} svgElement - The SVG DOM element
|
||||
*/
|
||||
export function fixCluePointsRendering(svgElement) {
|
||||
if (!svgElement) return;
|
||||
|
||||
// Find the clue points overlay group
|
||||
let overlayGroup = svgElement.querySelector(".clue-points-overlay");
|
||||
|
||||
// If no overlay group exists, create one
|
||||
if (!overlayGroup) {
|
||||
console.log("No clue points overlay found, creating one");
|
||||
overlayGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
||||
overlayGroup.setAttribute("class", "clue-points-overlay");
|
||||
overlayGroup.setAttribute(
|
||||
"style",
|
||||
"pointer-events: all !important; z-index: 9999 !important; position: relative !important;"
|
||||
);
|
||||
svgElement.appendChild(overlayGroup);
|
||||
}
|
||||
|
||||
// Find all clue points
|
||||
const cluePoints = svgElement.querySelectorAll(".clue-point");
|
||||
const clickAreas = svgElement.querySelectorAll(".clue-click-area");
|
||||
|
||||
console.log(
|
||||
`Found ${cluePoints.length} clue points and ${clickAreas.length} click areas`
|
||||
);
|
||||
|
||||
// Ensure all clue points are in the overlay group and have proper styles
|
||||
cluePoints.forEach((point, i) => {
|
||||
// Clone the point to remove any existing event handlers and styling issues
|
||||
const newPoint = point.cloneNode(true);
|
||||
|
||||
// Apply explicit styles to ensure visibility
|
||||
newPoint.setAttribute(
|
||||
"style",
|
||||
"fill: #ff9800; stroke: #e65100; stroke-width: 3px; " +
|
||||
"pointer-events: all !important; cursor: pointer !important; " +
|
||||
"z-index: 9999 !important; position: relative !important; " +
|
||||
"r: 14px; fill-opacity: 1; stroke-opacity: 1; " +
|
||||
"filter: drop-shadow(0px 0px 5px rgba(0, 0, 0, 0.7));"
|
||||
);
|
||||
|
||||
// Move to overlay group
|
||||
if (point.parentNode !== overlayGroup) {
|
||||
// Remove from current location
|
||||
if (point.parentNode) {
|
||||
point.parentNode.removeChild(point);
|
||||
}
|
||||
|
||||
// Add to overlay group
|
||||
overlayGroup.appendChild(newPoint);
|
||||
}
|
||||
});
|
||||
|
||||
// Move the overlay group to be the last child of the SVG
|
||||
svgElement.appendChild(overlayGroup);
|
||||
|
||||
console.log("Clue points rendering fixed");
|
||||
}
|
||||
+199
-112
@@ -20,23 +20,38 @@ function addCustomStyles() {
|
||||
100% { stroke-width: 3px; transform: scale(1); filter: drop-shadow(0px 0px 3px rgba(255, 152, 0, 0.9)); }
|
||||
}
|
||||
|
||||
/* Ensure SVG elements use CSS z-index correctly */
|
||||
/* Force all SVG elements to be transparent to mouse events by default */
|
||||
svg * {
|
||||
transform-box: fill-box;
|
||||
stroke-width: 2px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Create a stacking context for the overlay */
|
||||
.clue-points-overlay {
|
||||
pointer-events: all !important;
|
||||
transition: fill 0.2s ease, stroke-width 0.2s ease, transform 0.2s ease !important;
|
||||
z-index: 100;
|
||||
position: relative;
|
||||
position: relative !important;
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
|
||||
/* Make clue points highly visible with strong styling */
|
||||
.clue-point {
|
||||
r: 14 !important;
|
||||
cursor: pointer !important;
|
||||
stroke-width: 3px !important;
|
||||
pointer-events: all !important;
|
||||
z-index: 9999 !important;
|
||||
position: relative !important;
|
||||
fill-opacity: 1 !important;
|
||||
stroke-opacity: 1 !important;
|
||||
filter: drop-shadow(0px 0px 3px rgba(0, 0, 0, 0.7)) !important;
|
||||
}
|
||||
|
||||
.clue-point:hover {
|
||||
fill: #ffb74d !important;
|
||||
stroke: #ff6d00 !important;
|
||||
stroke-width: 4px !important;
|
||||
transform: scale(1.2) !important;
|
||||
z-index: 200 !important;
|
||||
filter: drop-shadow(0px 0px 4px rgba(0, 0, 0, 0.5)) !important;
|
||||
stroke-width: 5px !important;
|
||||
transform: scale(1.3) !important;
|
||||
z-index: 10000 !important;
|
||||
filter: drop-shadow(0px 0px 6px rgba(0, 0, 0, 0.8)) !important;
|
||||
}
|
||||
|
||||
.clue-point-found {
|
||||
@@ -61,11 +76,12 @@ function addCustomStyles() {
|
||||
}
|
||||
|
||||
.clue-click-area {
|
||||
opacity: 0;
|
||||
fill: transparent;
|
||||
stroke: transparent;
|
||||
opacity: 0.01;
|
||||
fill: transparent !important;
|
||||
stroke: transparent !important;
|
||||
pointer-events: all !important;
|
||||
cursor: pointer !important;
|
||||
z-index: 9998 !important;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -93,7 +109,7 @@ export async function populateMap(svgElement) {
|
||||
|
||||
// Clear any existing clue points and click areas to avoid duplicates
|
||||
const existingCluePoints = svgElement.querySelectorAll(
|
||||
".clue-point, .clue-click-area"
|
||||
".clue-point, .clue-click-area, .clue-points-overlay"
|
||||
);
|
||||
existingCluePoints.forEach((element) => {
|
||||
if (element.parentNode) {
|
||||
@@ -108,111 +124,47 @@ export async function populateMap(svgElement) {
|
||||
console.log(`Found ${allCircles.length} circles in SVG`);
|
||||
|
||||
if (allCircles.length === 0) {
|
||||
console.error("No circles found in SVG");
|
||||
return false;
|
||||
}
|
||||
console.error("No circles found in SVG. Creating fallback circles.");
|
||||
|
||||
// Hide all existing circles initially
|
||||
allCircles.forEach((circle) => (circle.style.display = "none"));
|
||||
// Create some fallback circles at predefined positions
|
||||
const defs = document.createElementNS(
|
||||
"http://www.w3.org/2000/svg",
|
||||
"defs"
|
||||
);
|
||||
const positions = [
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 200, y: 150 },
|
||||
{ x: 300, y: 200 },
|
||||
{ x: 400, y: 300 },
|
||||
{ x: 500, y: 400 },
|
||||
{ x: 600, y: 500 },
|
||||
];
|
||||
|
||||
try {
|
||||
// Fetch the number of clues from the API
|
||||
const response = await fetch("/api/clues/count", {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Cache-Control": "no-cache, no-store, must-revalidate",
|
||||
Pragma: "no-cache",
|
||||
},
|
||||
positions.forEach((pos, i) => {
|
||||
const circle = document.createElementNS(
|
||||
"http://www.w3.org/2000/svg",
|
||||
"circle"
|
||||
);
|
||||
circle.setAttribute("cx", pos.x.toString());
|
||||
circle.setAttribute("cy", pos.y.toString());
|
||||
circle.setAttribute("r", "5");
|
||||
circle.setAttribute("fill", "transparent");
|
||||
defs.appendChild(circle);
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch clue count: ${response.status}`);
|
||||
}
|
||||
// Add the defs to the SVG
|
||||
svgElement.appendChild(defs);
|
||||
|
||||
const data = await response.json();
|
||||
const clueCount = data.count;
|
||||
console.log(`API returned clue count: ${clueCount}`);
|
||||
|
||||
if (!clueCount || clueCount <= 0) {
|
||||
console.error("Invalid clue count returned from API");
|
||||
// Now find all circles again
|
||||
const fallbackCircles = Array.from(defs.querySelectorAll("circle"));
|
||||
if (fallbackCircles.length > 0) {
|
||||
console.log(`Created ${fallbackCircles.length} fallback circles`);
|
||||
return await processCircles(svgElement, fallbackCircles);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sort circles by vertical position (top to bottom)
|
||||
const sortedCircles = allCircles.sort((a, b) => {
|
||||
const aY = parseFloat(a.getAttribute("cy") || "0");
|
||||
const bY = parseFloat(b.getAttribute("cy") || "0");
|
||||
return aY - bY;
|
||||
});
|
||||
|
||||
// Determine how many circles to show
|
||||
const circlesToShow = Math.min(sortedCircles.length, clueCount);
|
||||
console.log(
|
||||
`Will create ${circlesToShow} clue points from sorted circles`
|
||||
);
|
||||
|
||||
const svgNamespace = "http://www.w3.org/2000/svg";
|
||||
|
||||
// Create a dedicated overlay group for clue points to ensure they're on top
|
||||
let overlayGroup = svgElement.querySelector(".clue-points-overlay");
|
||||
if (!overlayGroup) {
|
||||
overlayGroup = document.createElementNS(svgNamespace, "g");
|
||||
overlayGroup.setAttribute("class", "clue-points-overlay");
|
||||
overlayGroup.style.pointerEvents = "all";
|
||||
// Append to the end of SVG to ensure it's rendered last (on top)
|
||||
svgElement.appendChild(overlayGroup);
|
||||
}
|
||||
|
||||
// Create interactive clue points by cloning and enhancing each original circle
|
||||
for (let i = 0; i < circlesToShow; i++) {
|
||||
const originalCircle = sortedCircles[i];
|
||||
|
||||
// Get original circle attributes
|
||||
const cx = originalCircle.getAttribute("cx") || "0";
|
||||
const cy = originalCircle.getAttribute("cy") || "0";
|
||||
const r = Math.max(
|
||||
parseFloat(originalCircle.getAttribute("r") || "5"),
|
||||
12
|
||||
);
|
||||
|
||||
// Create a click area (larger invisible circle for easier clicking)
|
||||
const clickArea = document.createElementNS(svgNamespace, "circle");
|
||||
clickArea.setAttribute("cx", cx);
|
||||
clickArea.setAttribute("cy", cy);
|
||||
clickArea.setAttribute("r", (r * 2).toString());
|
||||
clickArea.setAttribute("class", "clue-click-area");
|
||||
clickArea.setAttribute("data-clue-id", (i + 1).toString());
|
||||
clickArea.setAttribute("data-index", i.toString());
|
||||
// Ensure click area has high z-index
|
||||
clickArea.style.zIndex = "1000";
|
||||
|
||||
// Create a new visible circle for the clue point
|
||||
const cluePoint = document.createElementNS(svgNamespace, "circle");
|
||||
cluePoint.setAttribute("cx", cx);
|
||||
cluePoint.setAttribute("cy", cy);
|
||||
cluePoint.setAttribute("r", r.toString());
|
||||
cluePoint.setAttribute("class", "clue-point clue-point-locked");
|
||||
cluePoint.setAttribute("data-clue-id", (i + 1).toString());
|
||||
cluePoint.setAttribute("data-index", i.toString());
|
||||
// Ensure clue point has high z-index
|
||||
cluePoint.style.zIndex = "1001";
|
||||
|
||||
// Add a tooltip for accessibility
|
||||
const tooltip = document.createElementNS(svgNamespace, "title");
|
||||
tooltip.textContent = `Clue #${i + 1}`;
|
||||
cluePoint.appendChild(tooltip);
|
||||
|
||||
// Add the new elements to the overlay group instead of original parent
|
||||
overlayGroup.appendChild(clickArea);
|
||||
overlayGroup.appendChild(cluePoint);
|
||||
}
|
||||
|
||||
console.log(`Successfully created ${circlesToShow} clue points`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error fetching clue count:", error);
|
||||
return false;
|
||||
} else {
|
||||
return await processCircles(svgElement, allCircles);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error populating map:", error);
|
||||
@@ -220,6 +172,141 @@ export async function populateMap(svgElement) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process circles to create clue points
|
||||
* @param {SVGElement} svgElement - The SVG DOM element
|
||||
* @param {Array} circles - Array of circle elements
|
||||
* @returns {Promise<boolean>} - Success flag
|
||||
*/
|
||||
async function processCircles(svgElement, circles) {
|
||||
try {
|
||||
// Hide original circles
|
||||
circles.forEach((circle) => (circle.style.display = "none"));
|
||||
|
||||
// Fetch the number of clues from the API
|
||||
const response = await fetch("/api/clues/count", {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Cache-Control": "no-cache, no-store, must-revalidate",
|
||||
Pragma: "no-cache",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch clue count: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const clueCount = parseInt(data.count, 10) || 0;
|
||||
console.log(`API returned clue count: ${clueCount}`);
|
||||
|
||||
if (clueCount <= 0) {
|
||||
console.error("Invalid clue count returned from API, using default of 5");
|
||||
clueCount = 5; // Fallback to 5 clues if API returns invalid data
|
||||
}
|
||||
|
||||
// Sort circles by vertical position (top to bottom)
|
||||
const sortedCircles = circles.sort((a, b) => {
|
||||
const aY = parseFloat(a.getAttribute("cy") || "0");
|
||||
const bY = parseFloat(b.getAttribute("cy") || "0");
|
||||
return aY - bY;
|
||||
});
|
||||
|
||||
// Determine how many circles to show
|
||||
const circlesToShow = Math.min(sortedCircles.length, clueCount);
|
||||
console.log(`Will create ${circlesToShow} clue points from sorted circles`);
|
||||
|
||||
// Create the overlay element that will go on top of everything
|
||||
const svgNamespace = "http://www.w3.org/2000/svg";
|
||||
|
||||
// Remove any old overlay group
|
||||
const oldOverlay = svgElement.querySelector(".clue-points-overlay");
|
||||
if (oldOverlay && oldOverlay.parentNode) {
|
||||
oldOverlay.parentNode.removeChild(oldOverlay);
|
||||
}
|
||||
|
||||
// Create a new overlay group that will be the last child of the SVG
|
||||
// so it renders on top of everything else
|
||||
const overlayGroup = document.createElementNS(svgNamespace, "g");
|
||||
overlayGroup.setAttribute("class", "clue-points-overlay");
|
||||
overlayGroup.setAttribute(
|
||||
"style",
|
||||
"pointer-events: all !important; z-index: 9999 !important;"
|
||||
);
|
||||
|
||||
// Add the overlay group as the LAST element in the SVG to ensure proper rendering order
|
||||
svgElement.appendChild(overlayGroup);
|
||||
|
||||
// Create interactive clue points
|
||||
for (let i = 0; i < circlesToShow; i++) {
|
||||
const originalCircle = sortedCircles[i];
|
||||
|
||||
// Get original circle attributes
|
||||
const cx = originalCircle.getAttribute("cx") || "0";
|
||||
const cy = originalCircle.getAttribute("cy") || "0";
|
||||
const r = Math.max(
|
||||
parseFloat(originalCircle.getAttribute("r") || "5"),
|
||||
14
|
||||
);
|
||||
|
||||
// Create a click area (larger invisible circle for easier clicking)
|
||||
const clickArea = document.createElementNS(svgNamespace, "circle");
|
||||
clickArea.setAttribute("cx", cx);
|
||||
clickArea.setAttribute("cy", cy);
|
||||
clickArea.setAttribute("r", (r * 2).toString());
|
||||
clickArea.setAttribute("class", "clue-click-area");
|
||||
clickArea.setAttribute("data-clue-id", (i + 1).toString());
|
||||
clickArea.setAttribute("data-index", i.toString());
|
||||
clickArea.setAttribute(
|
||||
"style",
|
||||
"opacity: 0.01; pointer-events: all !important; cursor: pointer !important; z-index: 9998 !important;"
|
||||
);
|
||||
|
||||
// Create a new visible circle for the clue point with explicit inline styles
|
||||
const cluePoint = document.createElementNS(svgNamespace, "circle");
|
||||
cluePoint.setAttribute("cx", cx);
|
||||
cluePoint.setAttribute("cy", cy);
|
||||
cluePoint.setAttribute("r", r.toString());
|
||||
cluePoint.setAttribute("class", "clue-point clue-point-locked");
|
||||
cluePoint.setAttribute("data-clue-id", (i + 1).toString());
|
||||
cluePoint.setAttribute("data-index", i.toString());
|
||||
cluePoint.setAttribute(
|
||||
"style",
|
||||
"fill: #9e9e9e; stroke: #757575; stroke-width: 3px; cursor: pointer; " +
|
||||
"pointer-events: all !important; z-index: 9999 !important; position: relative !important; " +
|
||||
"fill-opacity: 1 !important; stroke-opacity: 1 !important; " +
|
||||
"filter: drop-shadow(0px 0px 3px rgba(0, 0, 0, 0.7));"
|
||||
);
|
||||
|
||||
// Add a tooltip for accessibility
|
||||
const tooltip = document.createElementNS(svgNamespace, "title");
|
||||
tooltip.textContent = `Clue #${i + 1}`;
|
||||
cluePoint.appendChild(tooltip);
|
||||
|
||||
// Add the new elements to the overlay group
|
||||
overlayGroup.appendChild(clickArea);
|
||||
overlayGroup.appendChild(cluePoint);
|
||||
}
|
||||
|
||||
// Set explicit width and height on the overlay group to ensure it covers the whole SVG
|
||||
const svgWidth = svgElement.getAttribute("width") || "100%";
|
||||
const svgHeight = svgElement.getAttribute("height") || "100%";
|
||||
overlayGroup.setAttribute("width", svgWidth);
|
||||
overlayGroup.setAttribute("height", svgHeight);
|
||||
|
||||
// Bring the overlay forward by appending it again
|
||||
// This ensures it's the last child of the SVG
|
||||
svgElement.appendChild(overlayGroup);
|
||||
|
||||
console.log(`Successfully created ${circlesToShow} clue points`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error processing circles:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the clue points based on the latest clue data
|
||||
* @param {SVGElement} svgElement - The SVG DOM element
|
||||
|
||||
Reference in new issue
Block a user