update frontend
This commit is contained in:
parent
68b859ccf5
commit
78f3ea5d63
|
|
@ -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) {
|
|||
</div>
|
||||
<div class="post-pin-pointer-small"></div>
|
||||
`;
|
||||
// bring back hidden markers
|
||||
clearOcclusion(markersRef);
|
||||
}
|
||||
|
||||
|
|
@ -157,7 +159,7 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) {
|
|||
<div class="post-card-header">
|
||||
<div class="post-card-cat">
|
||||
${escapeHtml(category || "NEWS")}
|
||||
${subCat ? "· " + escapeHtml(subCat) : ""}
|
||||
${subCat ? " · " + escapeHtml(subCat) : ""}
|
||||
</div>
|
||||
<div class="post-card-time">
|
||||
${escapeHtml(created || "")}
|
||||
|
|
@ -166,21 +168,15 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) {
|
|||
|
||||
<div class="post-card-main">
|
||||
<div class="post-card-media">
|
||||
<div class="post-card-media-label">
|
||||
Image / Vidéo
|
||||
</div>
|
||||
<div class="post-card-media-label">Image / Vidéo</div>
|
||||
</div>
|
||||
|
||||
<div class="post-card-info">
|
||||
<div class="post-card-title">
|
||||
${escapeHtml(title)}
|
||||
</div>
|
||||
<div class="post-card-title">${escapeHtml(title)}</div>
|
||||
<div class="post-card-meta">
|
||||
by ${escapeHtml(author)} · lat ${lat.toFixed(3)} · lon ${lon.toFixed(3)}
|
||||
</div>
|
||||
<div class="post-card-body">
|
||||
${escapeHtml(body)}
|
||||
</div>
|
||||
<div class="post-card-body">${escapeHtml(body)}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -191,23 +187,20 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) {
|
|||
<button class="post-card-btn">Fix</button>
|
||||
</div>
|
||||
|
||||
<div class="post-card-comments">
|
||||
Live chat / comments (placeholder)
|
||||
</div>
|
||||
<div class="post-card-comments">Live chat / comments (placeholder)</div>
|
||||
</div>
|
||||
<div class="post-card-pointer"></div>
|
||||
`;
|
||||
|
||||
// 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,7 +234,7 @@ 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, "&")
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,11 +184,14 @@
|
|||
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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue