213 lines
5.4 KiB
JavaScript
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, "&")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/"/g, """);
|
|
}
|
|
|
|
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;
|
|
}
|
|
});
|
|
}
|