sw-fe/src/components/Map/markerManager.js

213 lines
5.4 KiB
JavaScript

import maplibregl from "maplibre-gl";
import React from "react";
import { createRoot } from "react-dom/client";
import TemplateMarkerCard from "./TemplateMarkerCard";
import { getDefaultTemplateCached } from "../../api/templates";
export function clearAllMarkers(markersRef, expandedElRef) {
if (markersRef.current) {
for (const m of markersRef.current) {
try {
if (m?.el && m.el.__reactRoot) {
try { m.el.__reactRoot.unmount(); } catch {}
m.el.__reactRoot = null;
}
m.marker.remove();
} catch {}
}
}
markersRef.current = [];
if (expandedElRef) expandedElRef.current = null;
}
function rectsOverlap(a, b) {
return !(a.right < b.left || a.left > b.right || a.bottom < b.top || a.top > b.bottom);
}
export function applyOcclusionForExpanded(markersRef, expandedEl) {
if (!expandedEl) return;
const expandedCard = expandedEl.querySelector(".post-card");
if (!expandedCard) return;
const PAD_X = 26;
const PAD_TOP = 20;
const PAD_BOTTOM = 140;
const raw = expandedCard.getBoundingClientRect();
const expandedRect = {
left: raw.left - PAD_X,
right: raw.right + PAD_X,
top: raw.top - PAD_TOP,
bottom: raw.bottom + PAD_BOTTOM,
};
const list = markersRef.current || [];
for (const item of list) {
const el = item?.el;
if (!el || el === expandedEl) continue;
const r = el.getBoundingClientRect();
const isBelow = r.top >= raw.top;
if (!isBelow) {
el.style.visibility = "visible";
el.style.pointerEvents = "auto";
continue;
}
const hide = rectsOverlap(expandedRect, r);
el.style.visibility = hide ? "hidden" : "visible";
el.style.pointerEvents = hide ? "none" : "auto";
}
}
export function clearOcclusion(markersRef) {
const list = markersRef.current || [];
for (const item of list) {
const el = item?.el;
if (!el) continue;
el.style.visibility = "visible";
el.style.pointerEvents = "auto";
}
}
function escapeHtml(str) {
return String(str)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}
function currentTheme() {
try {
const t = document?.body?.getAttribute("data-theme") || "dark";
if (t === "dark" || t === "blue" || t === "light") return t;
} catch {}
return "dark";
}
function ensureReactRoot(el) {
if (el.__reactRoot) return el.__reactRoot;
el.__reactRoot = createRoot(el);
return el.__reactRoot;
}
export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) {
const map = mapRef.current;
if (!map) return;
const lat =
typeof post.lat === "number"
? post.lat
: typeof post.latitude === "number"
? post.latitude
: null;
const lon =
typeof post.lon === "number"
? post.lon
: typeof post.lng === "number"
? post.lng
: null;
if (lat == null || lon == null) {
console.warn("post sans coordonnée:", post);
return;
}
const title = (post.title || "").trim() || "Untitled";
const root = document.createElement("div");
root.className = "post-pin post-pin--compact";
root.style.zIndex = "1";
function renderCompact() {
root.className = "post-pin post-pin--compact";
root.style.zIndex = "1";
root.innerHTML = `
<div class="post-pin-bubble">
<div class="post-pin-dot"></div>
<div class="post-pin-text">${escapeHtml(title)}</div>
</div>
<div class="post-pin-pointer-small"></div>
`;
if (root.__reactRoot) {
try { root.__reactRoot.unmount(); } catch {}
root.__reactRoot = null;
}
clearOcclusion(markersRef);
}
async function renderExpanded() {
root.className = "post-pin post-pin--expanded";
root.style.zIndex = "999999";
root.innerHTML = `
<div class="post-card">
<div class="post-card-body" style="font-size:11px; opacity:.9;">Loading template…</div>
</div>
<div class="post-card-pointer"></div>
`;
const cardEl = root.querySelector(".post-card");
if (!cardEl) return;
const theme = currentTheme();
try {
const tpl = await getDefaultTemplateCached("news");
const rr = ensureReactRoot(cardEl);
rr.render(
React.createElement(TemplateMarkerCard, {
post,
template: tpl,
theme,
mode: "full",
})
);
} catch (e) {
cardEl.innerHTML = `
<div style="font-weight:800; font-size:13px; margin-bottom:4px;">${escapeHtml(title)}</div>
<div style="font-size:11px; opacity:.9;">(template load failed)</div>
`;
}
requestAnimationFrame(() => {
applyOcclusionForExpanded(markersRef, root);
});
}
renderCompact();
root.__renderCompact = renderCompact;
const marker = new maplibregl.Marker({ element: root, anchor: "bottom" })
.setLngLat([lon, lat])
.addTo(map);
markersRef.current.push({ id: post.id, marker, el: root, post });
root.addEventListener("click", (ev) => {
ev.stopPropagation();
if (expandedElRef.current && expandedElRef.current !== root) {
if (expandedElRef.current.__renderCompact) expandedElRef.current.__renderCompact();
expandedElRef.current = null;
}
const isExpanded = root.classList.contains("post-pin--expanded");
if (isExpanded) {
renderCompact();
expandedElRef.current = null;
} else {
renderExpanded();
expandedElRef.current = root;
}
});
}