update frontend

This commit is contained in:
Your Name 2025-12-13 01:59:12 -05:00
parent d7712324ae
commit bc4cfc9d14
2 changed files with 240 additions and 171 deletions

View File

@ -1,69 +1,100 @@
import maplibregl from "maplibre-gl"; import maplibregl from "maplibre-gl";
/**
* Supprime tous les marqueurs
*/
export function clearAllMarkers(markersRef, expandedElRef) { export function clearAllMarkers(markersRef, expandedElRef) {
if (markersRef.current) { if (markersRef.current) {
for (const m of markersRef.current) { for (const m of markersRef.current) {
try { m.marker.remove(); } catch {} try {
m.marker.remove();
} catch {}
} }
} }
markersRef.current = []; markersRef.current = [];
if (expandedElRef) expandedElRef.current = null; if (expandedElRef) expandedElRef.current = null;
} }
function shortText(s, max = 22) {
const t = String(s || "").trim();
if (!t) return "Post";
return t.length > max ? t.slice(0, max - 3) + "..." : t;
}
/**
* Crée un marqueur pour un post :
* - compact : bulle + pointe
* - expanded : carte + pointe
*/
export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) { export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) {
const map = mapRef.current; const map = mapRef.current;
if (!map) return; if (!map) return;
const lat = const lat =
typeof post.lat === "number" ? post.lat : typeof post.lat === "number"
typeof post.Lat === "number" ? post.Lat : ? post.lat
typeof post.latitude === "number" ? post.latitude : : typeof post.Lat === "number"
null; ? post.Lat
: typeof post.latitude === "number"
? post.latitude
: null;
const lon = const lon =
typeof post.lon === "number" ? post.lon : typeof post.lon === "number"
typeof post.Lon === "number" ? post.Lon : ? post.lon
typeof post.longitude === "number" ? post.longitude : : typeof post.Lon === "number"
null; ? post.Lon
: typeof post.longitude === "number"
? post.longitude
: null;
if (lat == null || lon == null) { if (lat == null || lon == null) return;
console.warn("post sans coordonnée:", post);
return;
}
const title = (post.title || post.Title || "").trim() || "Untitled"; const title = (post.title || post.Title || "").trim() || "Untitled";
const titleShort = shortText(title, 22);
const body = const body =
(post.snippet || post.Snippet || "").trim() || (post.snippet || post.Snippet || "").trim() ||
(post.body || post.Body || "").trim() || (post.body || post.Body || "").trim() ||
title; title;
const author = (post.author || post.Author || "").trim() || "unknown"; const author = (post.author || post.Author || "").trim() || "unknown";
const created = (post.created_at || post.CreatedAt || "").toString().slice(0, 19); const created =
(post.created_at || post.CreatedAt || "").toString().slice(0, 19);
const category = (post.category || post.Category || "").toUpperCase() || ""; const category = (post.category || post.Category || "").toUpperCase() || "";
const subCat = (post.sub_category || post.SubCategory || "").toString(); const subCat = (post.sub_category || post.SubCategory || "").toString();
const root = document.createElement("div"); const root = document.createElement("div");
root.className = "post-pin"; root.className = "post-pin post-pin--compact";
// base z-index (compact)
root.style.zIndex = "1";
const layer = document.createElement("div"); function renderCompact() {
layer.className = "post-pin-layer"; root.className = "post-pin post-pin--compact";
root.style.zIndex = "1";
const compact = document.createElement("div"); root.innerHTML = `
compact.className = "post-pin-compact"; <div class="post-pin-bubble">
compact.innerHTML = `
<div class="post-pin-dot"></div> <div class="post-pin-dot"></div>
<div class="post-pin-text">${escapeHtml(title)}</div> <div class="post-pin-text">${escapeHtml(titleShort)}</div>
</div>
<div class="post-pin-pointer-small"></div>
`; `;
}
const expanded = document.createElement("div"); function renderExpanded() {
expanded.className = "post-pin-expanded post-hidden"; root.className = "post-pin post-pin--expanded";
expanded.innerHTML = ` // ALWAYS on top
root.style.zIndex = "99999";
root.innerHTML = `
<div class="post-card">
<div class="post-card-header"> <div class="post-card-header">
<div class="post-card-cat"> <div class="post-card-cat">
${escapeHtml(category || "NEWS")} ${escapeHtml(category || "NEWS")}
${subCat ? "· " + escapeHtml(subCat) : ""} ${subCat ? "· " + escapeHtml(subCat) : ""}
</div> </div>
<div class="post-card-time">${escapeHtml(created || "")}</div> <div class="post-card-time">
${escapeHtml(created || "")}
</div>
</div> </div>
<div class="post-card-main"> <div class="post-card-main">
@ -81,57 +112,58 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef) {
</div> </div>
<div class="post-card-footer"> <div class="post-card-footer">
<button class="post-card-btn" type="button">Live</button> <button class="post-card-btn">Live</button>
<button class="post-card-btn" type="button">Like</button> <button class="post-card-btn">Like</button>
<button class="post-card-btn" type="button">Share</button> <button class="post-card-btn">Share</button>
<button class="post-card-btn" type="button">Fix</button> <button class="post-card-btn">Fix</button>
</div> </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>
`; `;
}
// IMPORTANT: cliquer sur les boutons ne doit pas re-toggle le marker function setExpanded(next) {
expanded.addEventListener("click", (e) => e.stopPropagation()); if (next) renderExpanded();
else renderCompact();
}
const tip = document.createElement("div"); // expose pour useMapCore (close on map click)
tip.className = "post-pin-tip"; root.__setExpanded = setExpanded;
layer.appendChild(compact); // état initial
layer.appendChild(expanded); renderCompact();
root.appendChild(layer);
root.appendChild(tip);
const marker = new maplibregl.Marker({ element: root, anchor: "bottom" }) const marker = new maplibregl.Marker({
element: root,
anchor: "bottom",
})
.setLngLat([lon, lat]) .setLngLat([lon, lat])
.addTo(map); .addTo(map);
markersRef.current.push({ id: post.id, marker, el: root, post }); markersRef.current.push({ id: post.id, marker, el: root, post });
function setExpanded(isOn) {
if (isOn) {
compact.classList.add("post-hidden");
expanded.classList.remove("post-hidden");
expandedElRef.current = root;
} else {
expanded.classList.add("post-hidden");
compact.classList.remove("post-hidden");
if (expandedElRef.current === root) expandedElRef.current = null;
}
}
// expose pour fermer depuis un autre marker
root.__setExpanded = setExpanded;
root.addEventListener("click", (ev) => { root.addEventListener("click", (ev) => {
ev.stopPropagation(); ev.stopPropagation();
// ferme l'autre expanded
if (expandedElRef.current && expandedElRef.current !== root) { if (expandedElRef.current && expandedElRef.current !== root) {
const prev = expandedElRef.current; expandedElRef.current.__setExpanded &&
if (prev && prev.__setExpanded) prev.__setExpanded(false); expandedElRef.current.__setExpanded(false);
expandedElRef.current = null;
} }
const isExpanded = !expanded.classList.contains("post-hidden"); const isExpanded = root.classList.contains("post-pin--expanded");
setExpanded(!isExpanded); if (isExpanded) {
setExpanded(false);
expandedElRef.current = null;
} else {
setExpanded(true);
expandedElRef.current = root;
}
}); });
} }

View File

@ -1,41 +1,25 @@
/* SocioWire Marker stable (no jump) /* MapLibre gère la position/transform, on ne touche pas */
Root 0x0 anchored, UI absolutely positioned above a fixed tip */
.post-pin { .post-pin {
position: relative;
width: 0;
height: 0;
pointer-events: auto;
transform: translate3d(0,0,0);
}
.post-pin-layer{
position: absolute;
left: 0;
top: 0;
transform: translate(-50%, calc(-100% - 14px)); /* 14px ~ tip height */
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
pointer-events: auto;
/* IMPORTANT: z-index fonctionne seulement si l'élément est "positioned" */
position: relative;
/* par défaut tout est en bas */
z-index: 1;
} }
.post-pin-tip{ /* quand expand => au-dessus de TOUT */
position: absolute; .post-pin--expanded {
left: 0; z-index: 99999;
top: 0;
transform: translate(-50%, -100%);
width: 0;
height: 0;
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));
} }
.post-hidden{ display:none !important; } /* ===== ÉTAT COMPACT : petite bulle + petite pointe ===== */
/* ===== COMPACT ===== */ .post-pin--compact .post-pin-bubble {
.post-pin-compact{
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
padding: 4px 8px; padding: 4px 8px;
@ -43,7 +27,6 @@
background: rgba(6, 40, 80, 0.95); background: rgba(6, 40, 80, 0.95);
border: 1px solid rgba(80, 180, 255, 0.9); border: 1px solid rgba(80, 180, 255, 0.9);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
max-width: 200px;
} }
.post-pin-dot { .post-pin-dot {
@ -52,21 +35,32 @@
border-radius: 999px; border-radius: 999px;
background: #ff8a00; background: #ff8a00;
margin-right: 6px; margin-right: 6px;
flex-shrink: 0;
} }
.post-pin-text { .post-pin-text {
color: #e8f5ff; color: #e8f5ff;
font-size: 11px; font-size: 11px;
font-weight: 500; font-weight: 500;
max-width: 180px; max-width: 120px;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
/* ===== EXPANDED ===== */ /* petite pointe */
.post-pin-expanded{ .post-pin-pointer-small {
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) ===== */
.post-pin--expanded .post-card {
min-width: 260px; min-width: 260px;
max-width: 320px; max-width: 320px;
background: rgba(8, 20, 40, 0.97); background: rgba(8, 20, 40, 0.97);
@ -78,7 +72,7 @@
box-sizing: border-box; box-sizing: border-box;
} }
/* header */ /* header : catégorie + heure/date */
.post-card-header { .post-card-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@ -86,15 +80,25 @@
font-size: 11px; font-size: 11px;
margin-bottom: 6px; margin-bottom: 6px;
} }
.post-card-cat{ font-weight:600; color:#8fc5ff; }
.post-card-time{ color:#9eb7ff; }
/* main */ .post-card-cat {
font-weight: 600;
color: #8fc5ff;
}
.post-card-time {
color: #9eb7ff;
}
/* corps : media + texte */
.post-card-main { .post-card-main {
display: flex; display: flex;
flex-direction: row;
gap: 8px; gap: 8px;
margin-bottom: 6px; margin-bottom: 6px;
} }
/* fake bloc image/vidéo */
.post-card-media { .post-card-media {
width: 72px; width: 72px;
height: 72px; height: 72px;
@ -103,25 +107,45 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
flex-shrink:0;
} }
.post-card-media-label { .post-card-media-label {
font-size: 9px; font-size: 9px;
text-align: center; text-align: center;
color: #e8f5ff; color: #e8f5ff;
} }
.post-card-info{ flex:1; min-width:0; }
.post-card-title{ font-size:13px; font-weight:600; margin-bottom:2px; }
.post-card-meta{ font-size:10px; color:#9eb7ff; margin-bottom:4px; }
.post-card-body{ font-size:11px; color:#c7e3ff; max-height:70px; overflow:hidden; }
/* footer */ /* texte principal */
.post-card-info {
flex: 1;
}
.post-card-title {
font-size: 13px;
font-weight: 600;
margin-bottom: 2px;
}
.post-card-meta {
font-size: 10px;
color: #9eb7ff;
margin-bottom: 4px;
}
.post-card-body {
font-size: 11px;
color: #c7e3ff;
max-height: 70px;
overflow: hidden;
}
/* footer boutons */
.post-card-footer { .post-card-footer {
display: flex; display: flex;
gap: 6px; gap: 6px;
margin-bottom: 6px; margin-bottom: 6px;
flex-wrap:wrap;
} }
.post-card-btn { .post-card-btn {
border: none; border: none;
border-radius: 999px; border-radius: 999px;
@ -131,6 +155,8 @@
color: #e8f5ff; color: #e8f5ff;
cursor: pointer; cursor: pointer;
} }
/* zone commentaires */
.post-card-comments { .post-card-comments {
font-size: 10px; font-size: 10px;
color: #9eb7ff; color: #9eb7ff;
@ -138,3 +164,14 @@
border-radius: 8px; border-radius: 8px;
background: rgba(3, 14, 32, 0.95); background: rgba(3, 14, 32, 0.95);
} }
/* Pointe sous le gros post */
.post-card-pointer {
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));
}