From 78f3ea5d63242682d7ff5e163b2cbddff75fc3fa Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 13 Dec 2025 02:42:20 -0500 Subject: [PATCH] update frontend --- = | 0 b.bottom | 0 b.right | 0 src/components/Map/markerManager.js | 64 ++++++++++------------- src/components/Map/useMapCore.js | 45 ++++------------ src/styles/mapMarkers.css | 80 +++++++++++++++++++---------- 6 files changed, 91 insertions(+), 98 deletions(-) create mode 100644 = create mode 100644 b.bottom create mode 100644 b.right diff --git a/= b/= new file mode 100644 index 0000000..e69de29 diff --git a/b.bottom b/b.bottom new file mode 100644 index 0000000..e69de29 diff --git a/b.right b/b.right new file mode 100644 index 0000000..e69de29 diff --git a/src/components/Map/markerManager.js b/src/components/Map/markerManager.js index 026d484..e8c8506 100644 --- a/src/components/Map/markerManager.js +++ b/src/components/Map/markerManager.js @@ -33,7 +33,8 @@ function rectsOverlap(a, b) { /** * Hide only markers that overlap the expanded card. - * - expandedEl: the marker root that is expanded + * IMPORTANT: we hide ONLY markers that are BELOW the expanded card + * (so markers above stay visible). */ export function applyOcclusionForExpanded(markersRef, expandedEl) { if (!expandedEl) return; @@ -41,9 +42,9 @@ export function applyOcclusionForExpanded(markersRef, expandedEl) { const expandedCard = expandedEl.querySelector(".post-card"); if (!expandedCard) return; - const PAD_X = 26; // left/right - const PAD_TOP = 20; // top - const PAD_BOTTOM = 95; // more bottom padding (image + actions + comments) + const PAD_X = 26; // left/right padding + const PAD_TOP = 20; // top padding + const PAD_BOTTOM = 140; // EXTRA bottom padding (image + buttons + comments) const raw = expandedCard.getBoundingClientRect(); const expandedRect = { @@ -52,17 +53,24 @@ export function applyOcclusionForExpanded(markersRef, expandedEl) { 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; - // Compute marker rect (bubble + pointer) const r = el.getBoundingClientRect(); - const hide = rectsOverlap(expandedRect, r); + // ✅ only hide stuff that is BELOW the expanded card zone + 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"; } @@ -82,20 +90,15 @@ export function clearOcclusion(markersRef) { } /** - * Crée un marqueur pour un post : - * - état compact : petite bulle + pointe - * - état étendu : gros post (carte) - * - * Règles: - * - 1 seul post ouvert à la fois - * - clic sur la map => ferme (useMapCore) - * - quand un post est ouvert => on cache SEULEMENT les markers qui le recouvrent + * Crée un marker stable: + * - root 0x0 anchored by MapLibre (anchor bottom) + * - UI is ABSOLUTE above the anchor, so expanded doesn't "shift" other markers */ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) { const map = mapRef.current; if (!map) return; - // --- coordonnées --- + // --- coords --- const lat = typeof post.lat === "number" ? post.lat @@ -145,7 +148,6 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) {
`; - // bring back hidden markers clearOcclusion(markersRef); } @@ -157,7 +159,7 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) {
${escapeHtml(category || "NEWS")} - ${subCat ? "· " + escapeHtml(subCat) : ""} + ${subCat ? " · " + escapeHtml(subCat) : ""}
${escapeHtml(created || "")} @@ -166,21 +168,15 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) {
-
- Image / Vidéo -
+
Image / Vidéo
-
- ${escapeHtml(title)} -
+
${escapeHtml(title)}
by ${escapeHtml(author)} · lat ${lat.toFixed(3)} · lon ${lon.toFixed(3)}
-
- ${escapeHtml(body)} -
+
${escapeHtml(body)}
@@ -191,23 +187,20 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) {
-
- Live chat / comments (placeholder) -
+
Live chat / comments (placeholder)
`; - // Wait a frame so DOM has final size, then hide only overlapping markers requestAnimationFrame(() => { applyOcclusionForExpanded(markersRef, root); }); } - // état initial : compact + // initial compact renderCompact(); - // utilisé par useMapCore pour fermer via clic map + // used by map click to close root.__renderCompact = renderCompact; const marker = new maplibregl.Marker({ @@ -222,7 +215,7 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) { root.addEventListener("click", (ev) => { ev.stopPropagation(); - // Fermer l'ancien gros post + // close previous expanded if (expandedElRef.current && expandedElRef.current !== root) { if (expandedElRef.current.__renderCompact) { expandedElRef.current.__renderCompact(); @@ -230,7 +223,6 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) { expandedElRef.current = null; } - // Toggle const isExpanded = root.classList.contains("post-pin--expanded"); if (isExpanded) { renderCompact(); @@ -242,11 +234,11 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) { }); } -/* petite fonction pour éviter d’injecter du HTML sale */ +/* escape html */ function escapeHtml(str) { return String(str) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); -} +} \ No newline at end of file diff --git a/src/components/Map/useMapCore.js b/src/components/Map/useMapCore.js index f41cc28..166546c 100644 --- a/src/components/Map/useMapCore.js +++ b/src/components/Map/useMapCore.js @@ -4,7 +4,6 @@ import { LAST_VIEW_KEY } from "./mapConfig"; import { getViewFromMap } from "./mapGeo"; import { clearAllMarkers, applyOcclusionForExpanded } from "./markerManager"; -// renvoie le style MapLibre en fonction du thème function getBaseStyle(theme) { const t = theme || "dark"; let tiles; @@ -32,13 +31,7 @@ function getBaseStyle(theme) { attribution: "© OpenStreetMap contributors © CARTO", }, }, - layers: [ - { - id: "carto-base-layer", - type: "raster", - source: "carto-base", - }, - ], + layers: [{ id: "carto-base-layer", type: "raster", source: "carto-base" }], }; } @@ -49,16 +42,13 @@ export function useMapCore(theme) { const markersRef = useRef([]); const expandedElRef = useRef(null); - const [viewParams, setViewParams] = useState({ - center: null, - radiusKm: 250, - }); + const [viewParams, setViewParams] = useState({ center: null, radiusKm: 250 }); const [hasLastView, setHasLastView] = useState(false); function closeExpandedIfAny() { const el = expandedElRef.current; - if (el && el.__setExpanded) { - el.__setExpanded(false); + if (el && el.__renderCompact) { + el.__renderCompact(); } expandedElRef.current = null; } @@ -76,7 +66,7 @@ export function useMapCore(theme) { antialias: true, }); - // Reprise dernière vue + // restore last view let hadLastView = false; try { const raw = localStorage.getItem(LAST_VIEW_KEY); @@ -98,7 +88,7 @@ export function useMapCore(theme) { map.addControl(new maplibregl.NavigationControl(), "top-right"); mapRef.current = map; - // If a post is expanded, re-apply occlusion on zoom/move (only hides overlapping ones) + // re-apply occlusion during move/zoom (only hides below overlapping ones) const reOcclude = () => { const el = expandedElRef.current; if (el) applyOcclusionForExpanded(markersRef, el); @@ -106,18 +96,8 @@ export function useMapCore(theme) { map.on("move", reOcclude); map.on("zoom", reOcclude); - // Close expanded post when clicking on the map (outside markers) - map.on("click", () => { - const el = expandedElRef.current; - if (el && el.__renderCompact) { - el.__renderCompact(); - expandedElRef.current = null; - } - }); - map.on("load", () => { - const vp = getViewFromMap(map); - setViewParams(vp); + setViewParams(getViewFromMap(map)); }); map.on("moveend", () => { @@ -127,16 +107,12 @@ export function useMapCore(theme) { const center = vp.center; localStorage.setItem( LAST_VIEW_KEY, - JSON.stringify({ - lat: center[1], - lon: center[0], - zoom: map.getZoom(), - }) + JSON.stringify({ lat: center[1], lon: center[0], zoom: map.getZoom() }) ); } catch {} }); - // IMPORTANT: click/drag sur la map => fermer le gros post + // ✅ close expanded when clicking/dragging map map.on("click", () => closeExpandedIfAny()); map.on("dragstart", () => closeExpandedIfAny()); @@ -148,6 +124,7 @@ export function useMapCore(theme) { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + // theme switch useEffect(() => { const map = mapRef.current; if (!map) return; @@ -162,4 +139,4 @@ export function useMapCore(theme) { viewParams, hasLastView, }; -} +} \ No newline at end of file diff --git a/src/styles/mapMarkers.css b/src/styles/mapMarkers.css index b5edd9d..8fc15c4 100644 --- a/src/styles/mapMarkers.css +++ b/src/styles/mapMarkers.css @@ -1,23 +1,31 @@ -/* MapLibre gère la position/transform via .maplibregl-marker, on ne touche PAS */ +/* ========================================================== + SOCIOWIRE MARKERS — ANCHOR STABLE (NO SHIFT) + Root = 0x0 on the exact geo point. + Bubble/Card are ABSOLUTE above the anchor. + ========================================================== */ + +/* Root anchored by MapLibre at the geo point */ .post-pin { - display: flex; - flex-direction: column; - align-items: center; + position: relative; + width: 0; + height: 0; + pointer-events: none; /* children clickable */ + transform: translate3d(0, 0, 0); +} + +.post-pin * { pointer-events: auto; - - /* IMPORTANT: NE PAS mettre position ici, sinon on casse l'ancrage MapLibre */ - /* MapLibre applique position:absolute via .maplibregl-marker */ - z-index: 1; + box-sizing: border-box; } -/* quand expand => au-dessus de TOUT */ -.post-pin--expanded { - z-index: 99999; -} - -/* ===== ÉTAT COMPACT : petite bulle + petite pointe ===== */ - +/* ========================= + COMPACT + ========================= */ .post-pin--compact .post-pin-bubble { + position: absolute; + left: 0; + top: 0; + transform: translate(-50%, calc(-100% - 12px)); display: inline-flex; align-items: center; padding: 4px 8px; @@ -25,6 +33,7 @@ background: rgba(6, 40, 80, 0.95); border: 1px solid rgba(80, 180, 255, 0.9); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5); + max-width: 180px; } .post-pin-dot { @@ -33,32 +42,45 @@ border-radius: 999px; background: #ff8a00; margin-right: 6px; + flex-shrink: 0; } .post-pin-text { color: #e8f5ff; font-size: 11px; font-weight: 500; - max-width: 120px; + max-width: 160px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -/* petite pointe */ -.post-pin-pointer-small { +/* little pointer tip */ +.post-pin--compact .post-pin-pointer-small { + position: absolute; + left: 0; + top: 0; + transform: translate(-50%, -100%); width: 0; height: 0; - margin-top: 2px; border-left: 7px solid transparent; border-right: 7px solid transparent; border-top: 9px solid rgba(6, 40, 80, 0.95); filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.6)); } -/* ===== ÉTAT ÉTENDU : gros post (carte) ===== */ +/* ========================= + EXPANDED + ========================= */ +.post-pin--expanded { + z-index: 999999; +} .post-pin--expanded .post-card { + position: absolute; + left: 0; + top: 0; + transform: translate(-50%, calc(-100% - 14px)); min-width: 260px; max-width: 320px; background: rgba(8, 20, 40, 0.97); @@ -67,7 +89,6 @@ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.8); border: 1px solid rgba(90, 190, 255, 0.9); color: #e8f5ff; - box-sizing: border-box; } /* header : catégorie + heure/date */ @@ -96,7 +117,6 @@ margin-bottom: 6px; } -/* fake bloc image/vidéo */ .post-card-media { width: 72px; height: 72px; @@ -105,6 +125,7 @@ display: flex; align-items: center; justify-content: center; + flex-shrink: 0; } .post-card-media-label { @@ -113,9 +134,9 @@ color: #e8f5ff; } -/* texte principal */ .post-card-info { flex: 1; + min-width: 0; } .post-card-title { @@ -142,6 +163,7 @@ display: flex; gap: 6px; margin-bottom: 6px; + flex-wrap: wrap; } .post-card-btn { @@ -154,7 +176,6 @@ cursor: pointer; } -/* zone commentaires */ .post-card-comments { font-size: 10px; color: #9eb7ff; @@ -163,13 +184,16 @@ background: rgba(3, 14, 32, 0.95); } -/* Pointe sous le gros post */ -.post-card-pointer { +/* pointer under expanded card */ +.post-pin--expanded .post-card-pointer { + position: absolute; + left: 0; + top: 0; + transform: translate(-50%, -100%); width: 0; height: 0; - margin-top: 3px; border-left: 10px solid transparent; border-right: 10px solid transparent; border-top: 11px solid rgba(8, 20, 40, 0.97); filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.8)); -} +} \ No newline at end of file