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
+369
-53
No files matched your search
@@ -94,6 +94,30 @@
|
|||||||
height: auto;
|
height: auto;
|
||||||
display: block;
|
display: block;
|
||||||
object-fit: none; /* Don't constrain the SVG */
|
object-fit: none; /* Don't constrain the SVG */
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Force SVG elements to show properly with z-index */
|
||||||
|
.svg-container svg * {
|
||||||
|
transform-box: fill-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure the clue points overlay is always on top */
|
||||||
|
.svg-container svg .clue-points-overlay {
|
||||||
|
position: relative !important;
|
||||||
|
z-index: 9999 !important;
|
||||||
|
pointer-events: all !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make clue points very visible with stronger styling */
|
||||||
|
.svg-container .clue-point {
|
||||||
|
fill-opacity: 1 !important;
|
||||||
|
stroke-opacity: 1 !important;
|
||||||
|
pointer-events: all !important;
|
||||||
|
cursor: pointer !important;
|
||||||
|
stroke-width: 3px !important;
|
||||||
|
r: 14px !important;
|
||||||
|
filter: drop-shadow(0px 0px 5px rgba(0, 0, 0, 0.7)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove the default map background when we have the processed SVG */
|
/* Remove the default map background when we have the processed SVG */
|
||||||
@@ -277,6 +301,26 @@
|
|||||||
color: #68543c;
|
color: #68543c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Refresh map button styling */
|
||||||
|
.refresh-map-button {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 80px;
|
||||||
|
right: 20px;
|
||||||
|
background-color: rgba(104, 84, 60, 0.9);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 1000;
|
||||||
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-map-button:hover {
|
||||||
|
background-color: rgba(104, 84, 60, 1);
|
||||||
|
}
|
||||||
|
|
||||||
/* Make sure the map is responsive */
|
/* Make sure the map is responsive */
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
.team-name {
|
.team-name {
|
||||||
|
|||||||
+68
-6
@@ -15,6 +15,7 @@ import {
|
|||||||
updateCluePointStatus,
|
updateCluePointStatus,
|
||||||
attachClueClickHandlers,
|
attachClueClickHandlers,
|
||||||
} from "@/utils/map-populate";
|
} from "@/utils/map-populate";
|
||||||
|
import { addDebugOverlay, fixCluePointsRendering } from "@/utils/map-debug";
|
||||||
|
|
||||||
export default function MapPage() {
|
export default function MapPage() {
|
||||||
const { data: session, status } = useSession();
|
const { data: session, status } = useSession();
|
||||||
@@ -565,28 +566,73 @@ export default function MapPage() {
|
|||||||
}
|
}
|
||||||
}, [status, fetchClues, loadAndEnhanceMap, router]);
|
}, [status, fetchClues, loadAndEnhanceMap, router]);
|
||||||
|
|
||||||
// Modified useEffect to better handle SVG loading and enhancement
|
// Modified useEffect to ensure SVG elements are visible
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Only try to enhance the map if both the SVG and clues are loaded
|
// Only try to enhance the map if both the SVG and clues are loaded
|
||||||
if (!mapSvg || !clues.length) return;
|
if (!mapSvg || !clues.length) return;
|
||||||
|
|
||||||
console.log("Both SVG and clues are loaded, preparing to enhance map");
|
console.log("Both SVG and clues are loaded, preparing to enhance map");
|
||||||
|
|
||||||
// Ensure the SVG is fully loaded in the DOM before enhancing
|
let enhancementAttempts = 0;
|
||||||
requestAnimationFrame(() => {
|
const maxAttempts = 3;
|
||||||
// Longer timeout to ensure the SVG is fully rendered in the DOM
|
|
||||||
setTimeout(() => {
|
function attemptMapEnhancement() {
|
||||||
|
enhancementAttempts++;
|
||||||
|
console.log(
|
||||||
|
`Map enhancement attempt ${enhancementAttempts}/${maxAttempts}`
|
||||||
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
mapContainerRef.current &&
|
mapContainerRef.current &&
|
||||||
mapContainerRef.current.querySelector("svg")
|
mapContainerRef.current.querySelector("svg")
|
||||||
) {
|
) {
|
||||||
|
const svgElement = mapContainerRef.current.querySelector("svg");
|
||||||
console.log("SVG found in DOM, enhancing map now");
|
console.log("SVG found in DOM, enhancing map now");
|
||||||
|
|
||||||
|
// Try to enhance the map
|
||||||
enhanceSvgMap();
|
enhanceSvgMap();
|
||||||
|
|
||||||
|
// Additional check after brief delay to ensure visibility
|
||||||
|
setTimeout(() => {
|
||||||
|
const cluePoints = svgElement.querySelectorAll(".clue-point");
|
||||||
|
console.log(
|
||||||
|
`Found ${cluePoints.length} clue points after enhancement`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (cluePoints.length === 0 && enhancementAttempts < maxAttempts) {
|
||||||
|
console.log(
|
||||||
|
`No clue points found, retrying (attempt ${enhancementAttempts}/${maxAttempts})`
|
||||||
|
);
|
||||||
|
attemptMapEnhancement();
|
||||||
|
} else if (cluePoints.length === 0) {
|
||||||
|
console.error(
|
||||||
|
`Failed to create clue points after ${maxAttempts} attempts`
|
||||||
|
);
|
||||||
|
// Fix rendering issues as a last resort
|
||||||
|
fixCluePointsRendering(svgElement);
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
"Map enhancement successful, fixing any rendering issues"
|
||||||
|
);
|
||||||
|
fixCluePointsRendering(svgElement);
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
} else {
|
} else {
|
||||||
console.error("SVG not found in DOM after rendering timeout");
|
console.error("SVG not found in DOM after rendering timeout");
|
||||||
|
|
||||||
|
if (enhancementAttempts < maxAttempts) {
|
||||||
|
// Try again after a delay
|
||||||
|
setTimeout(attemptMapEnhancement, 500);
|
||||||
|
} else {
|
||||||
setMapError(true);
|
setMapError(true);
|
||||||
}
|
}
|
||||||
}, 500); // Increased timeout for better reliability on slower devices
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the SVG is fully loaded in the DOM before enhancing
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
// Initial delay before first attempt
|
||||||
|
setTimeout(attemptMapEnhancement, 300);
|
||||||
});
|
});
|
||||||
}, [mapSvg, clues, enhanceSvgMap]);
|
}, [mapSvg, clues, enhanceSvgMap]);
|
||||||
|
|
||||||
@@ -669,6 +715,22 @@ export default function MapPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Add a refresh button for users if they have issues with the map */}
|
||||||
|
{mapSvg && (
|
||||||
|
<button
|
||||||
|
className={styles["refresh-map-button"]}
|
||||||
|
onClick={() => {
|
||||||
|
const svgElement = mapContainerRef.current?.querySelector("svg");
|
||||||
|
if (svgElement) {
|
||||||
|
enhanceSvgMap();
|
||||||
|
setTimeout(() => fixCluePointsRendering(svgElement), 300);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Refresh Map
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Navigation bar */}
|
{/* Navigation bar */}
|
||||||
<nav className={styles["nav-bar"]}>
|
<nav className={styles["nav-bar"]}>
|
||||||
<Link
|
<Link
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
+134
-47
@@ -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)); }
|
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 * {
|
svg * {
|
||||||
transform-box: fill-box;
|
pointer-events: none;
|
||||||
stroke-width: 2px;
|
}
|
||||||
|
|
||||||
|
/* Create a stacking context for the overlay */
|
||||||
|
.clue-points-overlay {
|
||||||
pointer-events: all !important;
|
pointer-events: all !important;
|
||||||
transition: fill 0.2s ease, stroke-width 0.2s ease, transform 0.2s ease !important;
|
position: relative !important;
|
||||||
z-index: 100;
|
z-index: 9999 !important;
|
||||||
position: relative;
|
}
|
||||||
|
|
||||||
|
/* 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 {
|
.clue-point:hover {
|
||||||
fill: #ffb74d !important;
|
fill: #ffb74d !important;
|
||||||
stroke: #ff6d00 !important;
|
stroke: #ff6d00 !important;
|
||||||
stroke-width: 4px !important;
|
stroke-width: 5px !important;
|
||||||
transform: scale(1.2) !important;
|
transform: scale(1.3) !important;
|
||||||
z-index: 200 !important;
|
z-index: 10000 !important;
|
||||||
filter: drop-shadow(0px 0px 4px rgba(0, 0, 0, 0.5)) !important;
|
filter: drop-shadow(0px 0px 6px rgba(0, 0, 0, 0.8)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clue-point-found {
|
.clue-point-found {
|
||||||
@@ -61,11 +76,12 @@ function addCustomStyles() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.clue-click-area {
|
.clue-click-area {
|
||||||
opacity: 0;
|
opacity: 0.01;
|
||||||
fill: transparent;
|
fill: transparent !important;
|
||||||
stroke: transparent;
|
stroke: transparent !important;
|
||||||
pointer-events: all !important;
|
pointer-events: all !important;
|
||||||
cursor: pointer !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
|
// Clear any existing clue points and click areas to avoid duplicates
|
||||||
const existingCluePoints = svgElement.querySelectorAll(
|
const existingCluePoints = svgElement.querySelectorAll(
|
||||||
".clue-point, .clue-click-area"
|
".clue-point, .clue-click-area, .clue-points-overlay"
|
||||||
);
|
);
|
||||||
existingCluePoints.forEach((element) => {
|
existingCluePoints.forEach((element) => {
|
||||||
if (element.parentNode) {
|
if (element.parentNode) {
|
||||||
@@ -108,14 +124,65 @@ export async function populateMap(svgElement) {
|
|||||||
console.log(`Found ${allCircles.length} circles in SVG`);
|
console.log(`Found ${allCircles.length} circles in SVG`);
|
||||||
|
|
||||||
if (allCircles.length === 0) {
|
if (allCircles.length === 0) {
|
||||||
console.error("No circles found in SVG");
|
console.error("No circles found in SVG. Creating fallback circles.");
|
||||||
|
|
||||||
|
// 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 },
|
||||||
|
];
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add the defs to the SVG
|
||||||
|
svgElement.appendChild(defs);
|
||||||
|
|
||||||
|
// 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return await processCircles(svgElement, allCircles);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error populating map:", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Hide all existing circles initially
|
/**
|
||||||
allCircles.forEach((circle) => (circle.style.display = "none"));
|
* 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 {
|
try {
|
||||||
|
// Hide original circles
|
||||||
|
circles.forEach((circle) => (circle.style.display = "none"));
|
||||||
|
|
||||||
// Fetch the number of clues from the API
|
// Fetch the number of clues from the API
|
||||||
const response = await fetch("/api/clues/count", {
|
const response = await fetch("/api/clues/count", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -131,16 +198,16 @@ export async function populateMap(svgElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
const clueCount = data.count;
|
const clueCount = parseInt(data.count, 10) || 0;
|
||||||
console.log(`API returned clue count: ${clueCount}`);
|
console.log(`API returned clue count: ${clueCount}`);
|
||||||
|
|
||||||
if (!clueCount || clueCount <= 0) {
|
if (clueCount <= 0) {
|
||||||
console.error("Invalid clue count returned from API");
|
console.error("Invalid clue count returned from API, using default of 5");
|
||||||
return false;
|
clueCount = 5; // Fallback to 5 clues if API returns invalid data
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort circles by vertical position (top to bottom)
|
// Sort circles by vertical position (top to bottom)
|
||||||
const sortedCircles = allCircles.sort((a, b) => {
|
const sortedCircles = circles.sort((a, b) => {
|
||||||
const aY = parseFloat(a.getAttribute("cy") || "0");
|
const aY = parseFloat(a.getAttribute("cy") || "0");
|
||||||
const bY = parseFloat(b.getAttribute("cy") || "0");
|
const bY = parseFloat(b.getAttribute("cy") || "0");
|
||||||
return aY - bY;
|
return aY - bY;
|
||||||
@@ -148,23 +215,30 @@ export async function populateMap(svgElement) {
|
|||||||
|
|
||||||
// Determine how many circles to show
|
// Determine how many circles to show
|
||||||
const circlesToShow = Math.min(sortedCircles.length, clueCount);
|
const circlesToShow = Math.min(sortedCircles.length, clueCount);
|
||||||
console.log(
|
console.log(`Will create ${circlesToShow} clue points from sorted circles`);
|
||||||
`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";
|
const svgNamespace = "http://www.w3.org/2000/svg";
|
||||||
|
|
||||||
// Create a dedicated overlay group for clue points to ensure they're on top
|
// Remove any old overlay group
|
||||||
let overlayGroup = svgElement.querySelector(".clue-points-overlay");
|
const oldOverlay = svgElement.querySelector(".clue-points-overlay");
|
||||||
if (!overlayGroup) {
|
if (oldOverlay && oldOverlay.parentNode) {
|
||||||
overlayGroup = document.createElementNS(svgNamespace, "g");
|
oldOverlay.parentNode.removeChild(oldOverlay);
|
||||||
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
|
// 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++) {
|
for (let i = 0; i < circlesToShow; i++) {
|
||||||
const originalCircle = sortedCircles[i];
|
const originalCircle = sortedCircles[i];
|
||||||
|
|
||||||
@@ -173,7 +247,7 @@ export async function populateMap(svgElement) {
|
|||||||
const cy = originalCircle.getAttribute("cy") || "0";
|
const cy = originalCircle.getAttribute("cy") || "0";
|
||||||
const r = Math.max(
|
const r = Math.max(
|
||||||
parseFloat(originalCircle.getAttribute("r") || "5"),
|
parseFloat(originalCircle.getAttribute("r") || "5"),
|
||||||
12
|
14
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create a click area (larger invisible circle for easier clicking)
|
// Create a click area (larger invisible circle for easier clicking)
|
||||||
@@ -184,10 +258,12 @@ export async function populateMap(svgElement) {
|
|||||||
clickArea.setAttribute("class", "clue-click-area");
|
clickArea.setAttribute("class", "clue-click-area");
|
||||||
clickArea.setAttribute("data-clue-id", (i + 1).toString());
|
clickArea.setAttribute("data-clue-id", (i + 1).toString());
|
||||||
clickArea.setAttribute("data-index", i.toString());
|
clickArea.setAttribute("data-index", i.toString());
|
||||||
// Ensure click area has high z-index
|
clickArea.setAttribute(
|
||||||
clickArea.style.zIndex = "1000";
|
"style",
|
||||||
|
"opacity: 0.01; pointer-events: all !important; cursor: pointer !important; z-index: 9998 !important;"
|
||||||
|
);
|
||||||
|
|
||||||
// Create a new visible circle for the clue point
|
// Create a new visible circle for the clue point with explicit inline styles
|
||||||
const cluePoint = document.createElementNS(svgNamespace, "circle");
|
const cluePoint = document.createElementNS(svgNamespace, "circle");
|
||||||
cluePoint.setAttribute("cx", cx);
|
cluePoint.setAttribute("cx", cx);
|
||||||
cluePoint.setAttribute("cy", cy);
|
cluePoint.setAttribute("cy", cy);
|
||||||
@@ -195,27 +271,38 @@ export async function populateMap(svgElement) {
|
|||||||
cluePoint.setAttribute("class", "clue-point clue-point-locked");
|
cluePoint.setAttribute("class", "clue-point clue-point-locked");
|
||||||
cluePoint.setAttribute("data-clue-id", (i + 1).toString());
|
cluePoint.setAttribute("data-clue-id", (i + 1).toString());
|
||||||
cluePoint.setAttribute("data-index", i.toString());
|
cluePoint.setAttribute("data-index", i.toString());
|
||||||
// Ensure clue point has high z-index
|
cluePoint.setAttribute(
|
||||||
cluePoint.style.zIndex = "1001";
|
"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
|
// Add a tooltip for accessibility
|
||||||
const tooltip = document.createElementNS(svgNamespace, "title");
|
const tooltip = document.createElementNS(svgNamespace, "title");
|
||||||
tooltip.textContent = `Clue #${i + 1}`;
|
tooltip.textContent = `Clue #${i + 1}`;
|
||||||
cluePoint.appendChild(tooltip);
|
cluePoint.appendChild(tooltip);
|
||||||
|
|
||||||
// Add the new elements to the overlay group instead of original parent
|
// Add the new elements to the overlay group
|
||||||
overlayGroup.appendChild(clickArea);
|
overlayGroup.appendChild(clickArea);
|
||||||
overlayGroup.appendChild(cluePoint);
|
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`);
|
console.log(`Successfully created ${circlesToShow} clue points`);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching clue count:", error);
|
console.error("Error processing circles:", error);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error populating map:", error);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user