feat: Share button with options, video pins fixed, polaroid smaller
- Add share menu with 3 options: Copy URL, Copy with coords, Share via - Share URL includes lat/lon/zoom/category/time filters - Restore shared view from URL params on page load - Fix videos ALWAYS using simple markers (blue pin + play button) - Camera pins: blue 64x48 with red LED pulse - Video pins: blue 64x48 with play button overlay (no LED) - Polaroid cards: reduced from 180x230 to 150x190 - Force camera/video to simple markers in fallback scoring Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
0e741329cb
commit
1902515d4a
|
|
@ -282,6 +282,8 @@ export default function MapView({
|
||||||
|
|
||||||
// Live broadcast state
|
// Live broadcast state
|
||||||
const [showLiveBroadcast, setShowLiveBroadcast] = useState(false);
|
const [showLiveBroadcast, setShowLiveBroadcast] = useState(false);
|
||||||
|
const [showShareMenu, setShowShareMenu] = useState(false);
|
||||||
|
const [shareData, setShareData] = useState({ url: '', text: '' });
|
||||||
const [activeLivePost, setActiveLivePost] = useState(null);
|
const [activeLivePost, setActiveLivePost] = useState(null);
|
||||||
const [liveCoords, setLiveCoords] = useState(null);
|
const [liveCoords, setLiveCoords] = useState(null);
|
||||||
// Weather modal state
|
// Weather modal state
|
||||||
|
|
@ -740,6 +742,55 @@ export default function MapView({
|
||||||
});
|
});
|
||||||
}, [onSelectPost, openOverlayWhenReady]);
|
}, [onSelectPost, openOverlayWhenReady]);
|
||||||
|
|
||||||
|
// Restore shared view from URL params (?lat=X&lon=Y&z=Z&cat=CAT&sub=SUB&time=H)
|
||||||
|
const sharedViewRestoredRef = useRef(false);
|
||||||
|
useEffect(() => {
|
||||||
|
if (sharedViewRestoredRef.current) return;
|
||||||
|
const qs = new URLSearchParams(window.location.search || "");
|
||||||
|
const lat = parseFloat(qs.get("lat"));
|
||||||
|
const lon = parseFloat(qs.get("lon"));
|
||||||
|
const z = parseInt(qs.get("z"), 10);
|
||||||
|
const cat = qs.get("cat");
|
||||||
|
const sub = qs.get("sub");
|
||||||
|
const time = parseInt(qs.get("time"), 10);
|
||||||
|
|
||||||
|
// Only restore if lat/lon are present
|
||||||
|
if (!Number.isFinite(lat) || !Number.isFinite(lon)) return;
|
||||||
|
|
||||||
|
sharedViewRestoredRef.current = true;
|
||||||
|
skipAutoZoomRef.current = true;
|
||||||
|
|
||||||
|
// Restore filters
|
||||||
|
if (cat) {
|
||||||
|
const cats = cat.split(",").filter(Boolean);
|
||||||
|
if (cats.length > 0) setMainFilters(cats);
|
||||||
|
}
|
||||||
|
if (sub) {
|
||||||
|
const subs = sub.split(",").filter(Boolean);
|
||||||
|
if (subs.length > 0) setSubFilters(subs);
|
||||||
|
}
|
||||||
|
if (Number.isFinite(time) && time > 0) {
|
||||||
|
setTimeHours(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fly to location when map is ready
|
||||||
|
const flyWhenReady = () => {
|
||||||
|
const map = mapRef.current;
|
||||||
|
if (!map) {
|
||||||
|
setTimeout(flyWhenReady, 200);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const zoom = Number.isFinite(z) ? z : 10;
|
||||||
|
map.flyTo({ center: [lon, lat], zoom, speed: 1.5 });
|
||||||
|
};
|
||||||
|
flyWhenReady();
|
||||||
|
|
||||||
|
// Clean URL
|
||||||
|
try {
|
||||||
|
window.history.replaceState(null, "", "/");
|
||||||
|
} catch {}
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (queryPostRef.current) return;
|
if (queryPostRef.current) return;
|
||||||
if (!CLUSTERS_ENABLED) return;
|
if (!CLUSTERS_ENABLED) return;
|
||||||
|
|
@ -1984,6 +2035,37 @@ export default function MapView({
|
||||||
>
|
>
|
||||||
<i className="fa-solid fa-plus"></i>
|
<i className="fa-solid fa-plus"></i>
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="sw-search__iconBtn"
|
||||||
|
title="Share this view"
|
||||||
|
onClick={() => {
|
||||||
|
const map = mapRef.current;
|
||||||
|
if (!map) return;
|
||||||
|
const center = map.getCenter();
|
||||||
|
const zoom = Math.round(map.getZoom());
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.set('lat', center.lat.toFixed(5));
|
||||||
|
params.set('lon', center.lng.toFixed(5));
|
||||||
|
params.set('z', String(zoom));
|
||||||
|
if (mainFilters.length > 0) {
|
||||||
|
params.set('cat', mainFilters.join(','));
|
||||||
|
}
|
||||||
|
if (subFilters.length > 0) {
|
||||||
|
params.set('sub', subFilters.join(','));
|
||||||
|
}
|
||||||
|
if (timeHours && timeHours !== 336) {
|
||||||
|
params.set('time', String(timeHours));
|
||||||
|
}
|
||||||
|
const shareUrl = `${window.location.origin}/?${params.toString()}`;
|
||||||
|
const filterDesc = mainFilters.length > 0 ? mainFilters.join(', ') : 'All';
|
||||||
|
const shareText = `📍 ${center.lat.toFixed(4)}, ${center.lng.toFixed(4)} | Zoom ${zoom} | ${filterDesc}\n${shareUrl}`;
|
||||||
|
setShareData({ url: shareUrl, text: shareText });
|
||||||
|
setShowShareMenu(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i className="fa-solid fa-share-nodes"></i>
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="sw-search__iconBtn"
|
className="sw-search__iconBtn"
|
||||||
|
|
@ -2320,6 +2402,116 @@ export default function MapView({
|
||||||
onClose={() => setActiveWeatherPost(null)}
|
onClose={() => setActiveWeatherPost(null)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Share Menu */}
|
||||||
|
{showShareMenu && (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className="share-menu-backdrop"
|
||||||
|
onClick={() => setShowShareMenu(false)}
|
||||||
|
style={{
|
||||||
|
position: 'fixed',
|
||||||
|
inset: 0,
|
||||||
|
background: 'rgba(0,0,0,0.5)',
|
||||||
|
zIndex: 9998,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="share-menu"
|
||||||
|
style={{
|
||||||
|
position: 'fixed',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
background: '#1a1f2e',
|
||||||
|
borderRadius: 16,
|
||||||
|
padding: 20,
|
||||||
|
zIndex: 9999,
|
||||||
|
minWidth: 280,
|
||||||
|
boxShadow: '0 8px 32px rgba(0,0,0,0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
||||||
|
<h3 style={{ margin: 0, color: '#fff', fontSize: 16, fontWeight: 700 }}>Share this view</h3>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowShareMenu(false)}
|
||||||
|
style={{ background: 'none', border: 'none', color: '#888', cursor: 'pointer', fontSize: 18 }}
|
||||||
|
>
|
||||||
|
<i className="fa-solid fa-xmark" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
|
||||||
|
{/* Copy URL */}
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(shareData.url).then(() => {
|
||||||
|
setShowShareMenu(false);
|
||||||
|
}).catch(() => {
|
||||||
|
window.prompt('Copy this link:', shareData.url);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
display: 'flex', alignItems: 'center', gap: 12,
|
||||||
|
background: '#2a3142', border: 'none', borderRadius: 10,
|
||||||
|
padding: '12px 16px', cursor: 'pointer', color: '#fff',
|
||||||
|
fontSize: 14, fontWeight: 600, textAlign: 'left',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i className="fa-solid fa-link" style={{ color: '#4ea1ff', width: 20 }} />
|
||||||
|
Copy URL
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Copy as text */}
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(shareData.text).then(() => {
|
||||||
|
setShowShareMenu(false);
|
||||||
|
}).catch(() => {
|
||||||
|
window.prompt('Copy this text:', shareData.text);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
display: 'flex', alignItems: 'center', gap: 12,
|
||||||
|
background: '#2a3142', border: 'none', borderRadius: 10,
|
||||||
|
padding: '12px 16px', cursor: 'pointer', color: '#fff',
|
||||||
|
fontSize: 14, fontWeight: 600, textAlign: 'left',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i className="fa-solid fa-file-lines" style={{ color: '#22c55e', width: 20 }} />
|
||||||
|
Copy with coordinates
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Native share (mobile) */}
|
||||||
|
{typeof navigator !== 'undefined' && navigator.share && (
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
navigator.share({
|
||||||
|
title: 'SocioWire Map View',
|
||||||
|
text: 'Check out this location on SocioWire',
|
||||||
|
url: shareData.url
|
||||||
|
}).then(() => setShowShareMenu(false)).catch(() => {});
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
display: 'flex', alignItems: 'center', gap: 12,
|
||||||
|
background: '#2a3142', border: 'none', borderRadius: 10,
|
||||||
|
padding: '12px 16px', cursor: 'pointer', color: '#fff',
|
||||||
|
fontSize: 14, fontWeight: 600, textAlign: 'left',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i className="fa-solid fa-share-from-square" style={{ color: '#a855f7', width: 20 }} />
|
||||||
|
Share via...
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Preview */}
|
||||||
|
<div style={{ marginTop: 16, padding: 12, background: '#0d1117', borderRadius: 8, fontSize: 11, color: '#888', wordBreak: 'break-all' }}>
|
||||||
|
{shareData.url}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -388,15 +388,13 @@ function mountCard(container, spec, data, theme, post) {
|
||||||
}
|
}
|
||||||
const tokens = cardTokens?.[theme] || cardTokens.blue;
|
const tokens = cardTokens?.[theme] || cardTokens.blue;
|
||||||
const score = truthScore(post || data || {});
|
const score = truthScore(post || data || {});
|
||||||
|
const templateKey = getTemplateKeyForPost(post || {});
|
||||||
|
const isPolaroid = templateKey === 'polaroid';
|
||||||
|
|
||||||
root.render(
|
// Build children array
|
||||||
React.createElement(
|
const children = [
|
||||||
"div",
|
|
||||||
{
|
|
||||||
className: "sw-card-wrap",
|
|
||||||
style: { width: spec?.size?.w, height: spec?.size?.h },
|
|
||||||
},
|
|
||||||
React.createElement(CardRenderer, {
|
React.createElement(CardRenderer, {
|
||||||
|
key: "card",
|
||||||
spec,
|
spec,
|
||||||
data,
|
data,
|
||||||
themeTokens: tokens,
|
themeTokens: tokens,
|
||||||
|
|
@ -407,9 +405,42 @@ function mountCard(container, spec, data, theme, post) {
|
||||||
},
|
},
|
||||||
className: "",
|
className: "",
|
||||||
}),
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
// For polaroid: just a small round indicator at bottom right
|
||||||
|
// For others: full truth bar on the side
|
||||||
|
if (isPolaroid) {
|
||||||
|
children.push(
|
||||||
React.createElement(
|
React.createElement(
|
||||||
"div",
|
"div",
|
||||||
{ className: "sw-truth-mini-bar" },
|
{
|
||||||
|
key: "truth-dot",
|
||||||
|
className: "sw-truth-dot",
|
||||||
|
style: {
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 6,
|
||||||
|
right: 6,
|
||||||
|
width: 18,
|
||||||
|
height: 18,
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: truthColor(score),
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 1px 3px rgba(0,0,0,0.3)',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
`${score}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
children.push(
|
||||||
|
React.createElement(
|
||||||
|
"div",
|
||||||
|
{ key: "truth-bar", className: "sw-truth-mini-bar" },
|
||||||
React.createElement("div", {
|
React.createElement("div", {
|
||||||
className: "sw-truth-mini-marker",
|
className: "sw-truth-mini-marker",
|
||||||
style: { top: `${100 - score}%` },
|
style: { top: `${100 - score}%` },
|
||||||
|
|
@ -417,9 +448,20 @@ function mountCard(container, spec, data, theme, post) {
|
||||||
),
|
),
|
||||||
React.createElement(
|
React.createElement(
|
||||||
"div",
|
"div",
|
||||||
{ className: "sw-truth-mini-score", style: { background: truthColor(score) } },
|
{ key: "truth-score", className: "sw-truth-mini-score", style: { background: truthColor(score) } },
|
||||||
`${score}`
|
`${score}`
|
||||||
),
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
root.render(
|
||||||
|
React.createElement(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
className: "sw-card-wrap",
|
||||||
|
style: { width: spec?.size?.w, height: spec?.size?.h },
|
||||||
|
},
|
||||||
|
...children
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -2434,6 +2476,7 @@ export function createSimpleMarkerForPost(post, mapRef, markersRef, expandedElRe
|
||||||
function renderSimple() {
|
function renderSimple() {
|
||||||
const activePost = root.__post || post;
|
const activePost = root.__post || post;
|
||||||
const isCamera = activePost?.content_type === 'camera';
|
const isCamera = activePost?.content_type === 'camera';
|
||||||
|
const isVideo = activePost?.content_type === 'video';
|
||||||
const isLive = activePost?.content_type === 'live';
|
const isLive = activePost?.content_type === 'live';
|
||||||
const isWeather = activePost?.content_type === 'weather';
|
const isWeather = activePost?.content_type === 'weather';
|
||||||
const liveColor = 'linear-gradient(135deg, rgba(220,38,38,0.95), rgba(239,68,68,0.95))';
|
const liveColor = 'linear-gradient(135deg, rgba(220,38,38,0.95), rgba(239,68,68,0.95))';
|
||||||
|
|
@ -2442,14 +2485,14 @@ export function createSimpleMarkerForPost(post, mapRef, markersRef, expandedElRe
|
||||||
const weatherPin = '#3b82f6';
|
const weatherPin = '#3b82f6';
|
||||||
const color = isLive
|
const color = isLive
|
||||||
? liveColor
|
? liveColor
|
||||||
: isCamera
|
: (isCamera || isVideo)
|
||||||
? 'linear-gradient(135deg, rgba(56, 189, 248, 0.9), rgba(59, 130, 246, 0.9))'
|
? 'linear-gradient(135deg, rgba(56, 189, 248, 0.9), rgba(59, 130, 246, 0.9))'
|
||||||
: isWeather
|
: isWeather
|
||||||
? weatherColor
|
? weatherColor
|
||||||
: getMarkerColorForCategory(activePost?.category);
|
: getMarkerColorForCategory(activePost?.category);
|
||||||
const pinColor = isLive
|
const pinColor = isLive
|
||||||
? livePin
|
? livePin
|
||||||
: isCamera
|
: (isCamera || isVideo)
|
||||||
? 'rgba(56, 189, 248, 0.9)'
|
? 'rgba(56, 189, 248, 0.9)'
|
||||||
: isWeather
|
: isWeather
|
||||||
? weatherPin
|
? weatherPin
|
||||||
|
|
@ -2489,41 +2532,81 @@ export function createSimpleMarkerForPost(post, mapRef, markersRef, expandedElRe
|
||||||
// Weather city name for label
|
// Weather city name for label
|
||||||
const weatherCity = isWeather ? (activePost?.city || (activePost?.title || '').split(':')[0].replace(/^[^\w\s]+/, '').trim()) : '';
|
const weatherCity = isWeather ? (activePost?.city || (activePost?.title || '').split(':')[0].replace(/^[^\w\s]+/, '').trim()) : '';
|
||||||
|
|
||||||
// Camera thumbnail URL
|
// Camera/video thumbnail URL
|
||||||
const cameraThumbnail = isCamera ? (activePost?.thumbnail_url || activePost?.image_small || '') : '';
|
const mediaThumbnail = (isCamera || isVideo) ? (activePost?.thumbnail_url || activePost?.image_small || '') : '';
|
||||||
|
|
||||||
// Camera pin - square thumbnail with red live dot
|
// Camera pin - square thumbnail with red LED that pulses (bigger)
|
||||||
|
// Video pin - square thumbnail with play button overlay (bigger, no red LED)
|
||||||
root.innerHTML = isCamera ? `
|
root.innerHTML = isCamera ? `
|
||||||
<div style="position:relative;">
|
<div style="position:relative;">
|
||||||
<div style="
|
<div style="
|
||||||
width: 44px;
|
width: 64px;
|
||||||
height: 32px;
|
height: 48px;
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
background: #0ea5e9;
|
background: #0ea5e9;
|
||||||
border: 2px solid #0ea5e9;
|
border: 2px solid #0ea5e9;
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.4);
|
box-shadow: 0 3px 10px rgba(0,0,0,0.4);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
">
|
">
|
||||||
${cameraThumbnail ? `<img src="${cameraThumbnail}" style="width:100%;height:100%;object-fit:cover;" onerror="this.style.display='none';" />` : ``}
|
${mediaThumbnail ? `<img src="${mediaThumbnail}" style="width:100%;height:100%;object-fit:cover;" onerror="this.style.display='none';" />` : ``}
|
||||||
</div>
|
</div>
|
||||||
<div style="
|
<div style="
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -4px;
|
top: -6px;
|
||||||
right: -4px;
|
right: -6px;
|
||||||
width: 10px;
|
width: 14px;
|
||||||
height: 10px;
|
height: 14px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: #ef4444;
|
background: #ef4444;
|
||||||
border: 2px solid #fff;
|
border: 2px solid #fff;
|
||||||
box-shadow: 0 0 6px #ef4444;
|
box-shadow: 0 0 10px #ef4444;
|
||||||
animation: pulse 1.5s infinite;
|
animation: pulse 1.5s infinite;
|
||||||
"></div>
|
"></div>
|
||||||
<div style="
|
<div style="
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
border-left: 6px solid transparent;
|
border-left: 8px solid transparent;
|
||||||
border-right: 6px solid transparent;
|
border-right: 8px solid transparent;
|
||||||
border-top: 8px solid #0ea5e9;
|
border-top: 12px solid #0ea5e9;
|
||||||
|
margin: -2px auto 0;
|
||||||
|
"></div>
|
||||||
|
</div>
|
||||||
|
` : isVideo ? `
|
||||||
|
<div style="position:relative;">
|
||||||
|
<div style="
|
||||||
|
width: 64px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #0ea5e9;
|
||||||
|
border: 2px solid #0ea5e9;
|
||||||
|
box-shadow: 0 3px 10px rgba(0,0,0,0.4);
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
">
|
||||||
|
${mediaThumbnail ? `<img src="${mediaThumbnail}" style="width:100%;height:100%;object-fit:cover;" onerror="this.style.display='none';" />` : ``}
|
||||||
|
<div style="
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(14, 165, 233, 0.9);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.35);
|
||||||
|
">
|
||||||
|
<i class="fa-solid fa-play" style="font-size:10px;color:#fff;margin-left:2px;"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 8px solid transparent;
|
||||||
|
border-right: 8px solid transparent;
|
||||||
|
border-top: 12px solid #0ea5e9;
|
||||||
margin: -2px auto 0;
|
margin: -2px auto 0;
|
||||||
"></div>
|
"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -138,8 +138,8 @@ export function prioritizePosts(posts, viewCenter, options = {}) {
|
||||||
console.log('[prioritizePosts] post:', post.id, 'tier:', post.tier, 'content_type:', post.content_type);
|
console.log('[prioritizePosts] post:', post.id, 'tier:', post.tier, 'content_type:', post.content_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Camera posts ALWAYS use simple markers (small blue pin with red dot)
|
// Camera and video posts ALWAYS use simple markers (small blue pin with red dot)
|
||||||
if (contentType === 'camera') {
|
if (contentType === 'camera' || contentType === 'video') {
|
||||||
simpleMarkerPosts.push(post);
|
simpleMarkerPosts.push(post);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -166,18 +166,28 @@ export function prioritizePosts(posts, viewCenter, options = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: old scoring system if no backend tiers
|
// Fallback: old scoring system if no backend tiers
|
||||||
simpleMarkerPosts.length = 0; // Clear for fallback processing
|
// First, separate camera/video posts - they ALWAYS use simple markers
|
||||||
|
const forcedSimple = [];
|
||||||
|
const otherPosts = [];
|
||||||
|
for (const post of posts) {
|
||||||
|
const ct = (post?.content_type || '').toLowerCase();
|
||||||
|
if (ct === 'camera' || ct === 'video') {
|
||||||
|
forcedSimple.push(post);
|
||||||
|
} else {
|
||||||
|
otherPosts.push(post);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const postsWithScores = posts.map(post => ({
|
const postsWithScores = otherPosts.map(post => ({
|
||||||
post,
|
post,
|
||||||
priority: calculatePostPriority(post, viewCenter, posts),
|
priority: calculatePostPriority(post, viewCenter, otherPosts),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
postsWithScores.sort((a, b) => b.priority - a.priority);
|
postsWithScores.sort((a, b) => b.priority - a.priority);
|
||||||
|
|
||||||
const clusters = clusterPosts(posts, clusterRadius);
|
const clusters = clusterPosts(otherPosts, clusterRadius);
|
||||||
const finalFull = [];
|
const finalFull = [];
|
||||||
const finalSimple = [];
|
const finalSimple = [...forcedSimple]; // Start with camera/video posts
|
||||||
const clusterRepresentatives = new Set();
|
const clusterRepresentatives = new Set();
|
||||||
|
|
||||||
for (const [clusterId, clusterPostsList] of clusters.entries()) {
|
for (const [clusterId, clusterPostsList] of clusters.entries()) {
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,13 @@
|
||||||
export const TEMPLATE_SPECS = {
|
export const TEMPLATE_SPECS = {
|
||||||
polaroid: {
|
polaroid: {
|
||||||
mini_spec: {
|
mini_spec: {
|
||||||
size: { w: 180, h: 230 },
|
size: { w: 150, h: 190 },
|
||||||
background: { type: "solid", value: "#ffffff" },
|
background: { type: "solid", value: "#ffffff" },
|
||||||
radius: 8,
|
radius: 6,
|
||||||
layers: [
|
layers: [
|
||||||
{ id: "image", type: "image", x: 10, y: 10, w: 160, h: 160, bind: "data.image", radius: 4 },
|
{ id: "image", type: "image", x: 8, y: 8, w: 134, h: 134, bind: "data.image", radius: 3 },
|
||||||
{ id: "title", type: "text", x: 10, y: 176, w: 160, h: 28, bind: "data.headline", style: "text.polaroidTitle", maxLines: 2 },
|
{ id: "title", type: "text", x: 8, y: 146, w: 134, h: 24, bind: "data.headline", style: "text.polaroidTitle", maxLines: 2 },
|
||||||
{ id: "author", type: "text", x: 10, y: 206, w: 160, h: 18, bind: "data.source", style: "text.polaroidCaption", maxLines: 1 },
|
{ id: "author", type: "text", x: 8, y: 172, w: 134, h: 14, bind: "data.source", style: "text.polaroidCaption", maxLines: 1 },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
full_spec: {
|
full_spec: {
|
||||||
|
|
|
||||||
|
|
@ -717,7 +717,9 @@ export function usePostsEngine({
|
||||||
const have = new Set(next.map((x) => x.id));
|
const have = new Set(next.map((x) => x.id));
|
||||||
for (const [id, entry] of wanted.entries()) {
|
for (const [id, entry] of wanted.entries()) {
|
||||||
if (have.has(id)) continue;
|
if (have.has(id)) continue;
|
||||||
if (entry.type === "simple") {
|
// Camera/video ALWAYS use simple markers regardless of priority
|
||||||
|
const ct = (entry.post?.content_type || '').toLowerCase();
|
||||||
|
if (entry.type === "simple" || ct === 'camera' || ct === 'video') {
|
||||||
createSimpleMarkerForPost(entry.post, mapRef, markersRef, expandedElRef, theme);
|
createSimpleMarkerForPost(entry.post, mapRef, markersRef, expandedElRef, theme);
|
||||||
} else {
|
} else {
|
||||||
// Full cards use createMarkerForPost (template is decided by content_type)
|
// Full cards use createMarkerForPost (template is decided by content_type)
|
||||||
|
|
@ -827,10 +829,12 @@ export function usePostsEngine({
|
||||||
const have = new Set(next.map((x) => x.id));
|
const have = new Set(next.map((x) => x.id));
|
||||||
|
|
||||||
// add missing markers - use appropriate type based on priority
|
// add missing markers - use appropriate type based on priority
|
||||||
|
// Camera/video ALWAYS use simple markers regardless of priority
|
||||||
for (const [id, { post, type }] of wanted.entries()) {
|
for (const [id, { post, type }] of wanted.entries()) {
|
||||||
if (have.has(id)) continue;
|
if (have.has(id)) continue;
|
||||||
|
|
||||||
if (type === 'full') {
|
const ct = (post?.content_type || '').toLowerCase();
|
||||||
|
if (type === 'full' && ct !== 'camera' && ct !== 'video') {
|
||||||
createMarkerForPost(post, mapRef, markersRef, expandedElRef, theme);
|
createMarkerForPost(post, mapRef, markersRef, expandedElRef, theme);
|
||||||
} else {
|
} else {
|
||||||
createSimpleMarkerForPost(post, mapRef, markersRef, expandedElRef, theme);
|
createSimpleMarkerForPost(post, mapRef, markersRef, expandedElRef, theme);
|
||||||
|
|
@ -1416,9 +1420,9 @@ export function usePostsEngine({
|
||||||
const id = getId(normalized);
|
const id = getId(normalized);
|
||||||
const have = new Set((markersRef.current || []).map((x) => x.id));
|
const have = new Set((markersRef.current || []).map((x) => x.id));
|
||||||
if (id != null && !have.has(id)) {
|
if (id != null && !have.has(id)) {
|
||||||
// Camera posts always use simple markers (small blue pin with red dot)
|
// Camera and video posts always use simple markers (small blue pin with red dot)
|
||||||
const ct = (normalized?.content_type || '').toLowerCase();
|
const ct = (normalized?.content_type || '').toLowerCase();
|
||||||
if (ct === 'camera') {
|
if (ct === 'camera' || ct === 'video') {
|
||||||
createSimpleMarkerForPost(normalized, mapRef, markersRef, expandedElRef, theme);
|
createSimpleMarkerForPost(normalized, mapRef, markersRef, expandedElRef, theme);
|
||||||
} else {
|
} else {
|
||||||
createMarkerForPost(normalized, mapRef, markersRef, expandedElRef, theme);
|
createMarkerForPost(normalized, mapRef, markersRef, expandedElRef, theme);
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,9 @@ export const cardTokens = {
|
||||||
h1: { color: "#E9EEF8", fontSize: 22, fontWeight: 900, lineHeight: "26px" },
|
h1: { color: "#E9EEF8", fontSize: 22, fontWeight: 900, lineHeight: "26px" },
|
||||||
body: { color: "#D7DEED", fontSize: 14, fontWeight: 600, lineHeight: "18px" },
|
body: { color: "#D7DEED", fontSize: 14, fontWeight: 600, lineHeight: "18px" },
|
||||||
icon: { color: "rgba(255,255,255,0.3)", fontSize: 48, fontFamily: "Font Awesome 6 Free", display: "flex", alignItems: "center", justifyContent: "center", fontWeight: 900 },
|
icon: { color: "rgba(255,255,255,0.3)", fontSize: 48, fontFamily: "Font Awesome 6 Free", display: "flex", alignItems: "center", justifyContent: "center", fontWeight: 900 },
|
||||||
polaroidTitle: { color: "#1a1a1a", fontSize: 11, fontWeight: 700, lineHeight: "13px" },
|
polaroidTitle: { color: "#1a1a1a", fontSize: 12, fontWeight: 800, lineHeight: "14px", letterSpacing: "-0.2px" },
|
||||||
polaroidCaption: { color: "#666666", fontSize: 10, fontWeight: 500, lineHeight: "12px" },
|
polaroidCaption: { color: "#888888", fontSize: 9, fontWeight: 600, lineHeight: "11px", fontStyle: "italic" },
|
||||||
|
polaroidText: { color: "#333333", fontSize: 10, fontWeight: 500, lineHeight: "12px" },
|
||||||
},
|
},
|
||||||
chip: {
|
chip: {
|
||||||
news: { background: "rgba(78,161,255,0.18)", color: "#CFE6FF", fontWeight: 800, fontSize: 12, borderRadius: 999 },
|
news: { background: "rgba(78,161,255,0.18)", color: "#CFE6FF", fontWeight: 800, fontSize: 12, borderRadius: 999 },
|
||||||
|
|
@ -59,8 +60,9 @@ export const cardTokens = {
|
||||||
h1: { color: "#F0F7FF", fontSize: 22, fontWeight: 900, lineHeight: "26px" },
|
h1: { color: "#F0F7FF", fontSize: 22, fontWeight: 900, lineHeight: "26px" },
|
||||||
body: { color: "#D7E9FF", fontSize: 14, fontWeight: 700, lineHeight: "18px" },
|
body: { color: "#D7E9FF", fontSize: 14, fontWeight: 700, lineHeight: "18px" },
|
||||||
icon: { color: "rgba(255,255,255,0.3)", fontSize: 48, fontFamily: "Font Awesome 6 Free", display: "flex", alignItems: "center", justifyContent: "center", fontWeight: 900 },
|
icon: { color: "rgba(255,255,255,0.3)", fontSize: 48, fontFamily: "Font Awesome 6 Free", display: "flex", alignItems: "center", justifyContent: "center", fontWeight: 900 },
|
||||||
polaroidTitle: { color: "#1a1a1a", fontSize: 11, fontWeight: 700, lineHeight: "13px" },
|
polaroidTitle: { color: "#1a1a1a", fontSize: 12, fontWeight: 800, lineHeight: "14px", letterSpacing: "-0.2px" },
|
||||||
polaroidCaption: { color: "#666666", fontSize: 10, fontWeight: 500, lineHeight: "12px" },
|
polaroidCaption: { color: "#888888", fontSize: 9, fontWeight: 600, lineHeight: "11px", fontStyle: "italic" },
|
||||||
|
polaroidText: { color: "#333333", fontSize: 10, fontWeight: 500, lineHeight: "12px" },
|
||||||
},
|
},
|
||||||
chip: {
|
chip: {
|
||||||
news: { background: "rgba(56,189,248,0.18)", color: "#D7F3FF", fontWeight: 900, fontSize: 12, borderRadius: 999 },
|
news: { background: "rgba(56,189,248,0.18)", color: "#D7F3FF", fontWeight: 900, fontSize: 12, borderRadius: 999 },
|
||||||
|
|
@ -95,8 +97,9 @@ export const cardTokens = {
|
||||||
h1: { color: "#0B1220", fontSize: 22, fontWeight: 900, lineHeight: "26px" },
|
h1: { color: "#0B1220", fontSize: 22, fontWeight: 900, lineHeight: "26px" },
|
||||||
body: { color: "#1A2740", fontSize: 14, fontWeight: 600, lineHeight: "18px" },
|
body: { color: "#1A2740", fontSize: 14, fontWeight: 600, lineHeight: "18px" },
|
||||||
icon: { color: "rgba(0,0,0,0.15)", fontSize: 48, fontFamily: "Font Awesome 6 Free", display: "flex", alignItems: "center", justifyContent: "center", fontWeight: 900 },
|
icon: { color: "rgba(0,0,0,0.15)", fontSize: 48, fontFamily: "Font Awesome 6 Free", display: "flex", alignItems: "center", justifyContent: "center", fontWeight: 900 },
|
||||||
polaroidTitle: { color: "#1a1a1a", fontSize: 11, fontWeight: 700, lineHeight: "13px" },
|
polaroidTitle: { color: "#1a1a1a", fontSize: 12, fontWeight: 800, lineHeight: "14px", letterSpacing: "-0.2px" },
|
||||||
polaroidCaption: { color: "#666666", fontSize: 10, fontWeight: 500, lineHeight: "12px" },
|
polaroidCaption: { color: "#888888", fontSize: 9, fontWeight: 600, lineHeight: "11px", fontStyle: "italic" },
|
||||||
|
polaroidText: { color: "#333333", fontSize: 10, fontWeight: 500, lineHeight: "12px" },
|
||||||
},
|
},
|
||||||
chip: {
|
chip: {
|
||||||
news: { background: "rgba(30,107,255,0.12)", color: "#0B1220", fontWeight: 900, fontSize: 12, borderRadius: 999 },
|
news: { background: "rgba(30,107,255,0.12)", color: "#0B1220", fontWeight: 900, fontSize: 12, borderRadius: 999 },
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue