Selfies
From Treeverse
More actions
Map Tools
👉 Click a map icon to view details.
🖱️ Right click on map icon to delete (if Delete mode is ON).
🖱️ Right click on map icon to delete (if Delete mode is ON).
Saved ✅
${d}
`:''}${img}`;
}
const img = new Image();
img.onload = () => {
const w = img.naturalWidth, h = img.naturalHeight;
const map = L.map('map', { crs:L.CRS.Simple, minZoom:-2, maxZoom:5, zoomControl:true });
const bounds = [[0,0],[h,w]];
L.imageOverlay(MAP_IMAGE_URL, bounds, { crossOrigin:'anonymous' }).addTo(map);
map.fitBounds(bounds);
const selfies = L.layerGroup().addTo(map);
L.control.layers(null, { 'Selfies 📸': selfies }, { collapsed:false, position:'topleft' }).addTo(map);
// Delete mode
let deleteMode=false;
const delBtn=document.getElementById('m-delmode');
delBtn.onclick=()=>{ deleteMode=!deleteMode; delBtn.textContent='Delete mode: '+(deleteMode?'ON':'OFF'); delBtn.classList.toggle('on', deleteMode); };
function addMarker(latlng, title, desc, imgUrl){
const m = L.marker(latlng,{icon:icon(), title:title||'Selfie', desc:desc||'', imgUrl:imgUrl||null}).addTo(selfies);
m.bindPopup(popupHtml(title,desc,imgUrl));
m.on('click', () => m.openPopup());
m.on('contextmenu', () => { if(deleteMode){ selfies.removeLayer(m); saveShared().catch(console.warn); } });
return m;
}
function collectMarkers(){
const all=[]; selfies.eachLayer(l=>{ const ll=l.getLatLng();
all.push({type:'selfie', y:ll.lat, x:ll.lng, title:l.options.title||'Selfie', desc:l.options.desc||'', img:l.options.imgUrl||null});
}); return all;
}
async function saveShared(){
const payload = JSON.stringify(collectMarkers());
const res = await fetch(SAVE_URL,{ method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ data: payload }) });
if(!res.ok) throw new Error(await res.text());
toast('Saved ✅');
}
function applyMarkers(arr){ arr.forEach(m => addMarker([m.y,m.x], m.title||'Selfie', m.desc||'', m.img||null)); }
// UI refs
const tI=document.getElementById('m-title'), dI=document.getElementById('m-desc'), pI=document.getElementById('m-photo'), addBtn=document.getElementById('m-add');
// Client-side file checks (jpg/jpeg/png/webp ≤ 10MB)
function validatePhoto(){
const f = pI.files[0]; if(!f) return true;
const ok = ['image/jpeg','image/png','image/webp'];
if(!ok.includes(f.type)){ alert('Only JPG/JPEG, PNG, or WEBP allowed.'); pI.value=''; return false; }
if(f.size > 10*1024*1024){ alert('Image is over 10MB.'); pI.value=''; return false; }
return true;
}
async function uploadPhotoIfAny(){
const f = pI.files[0]; if(!f) return null;
const fd = new FormData(); fd.append('photo', f);
const res = await fetch(UPLOAD_URL, { method:'POST', body: fd });
if(!res.ok) throw new Error(await res.text());
const j = await res.json(); return j.url;
}
// Add -> click map
let placing=null;
addBtn.onclick = () => {
if(!validatePhoto()) return;
placing = { title: tI.value, desc: dI.value };
addBtn.textContent='Click map…'; addBtn.disabled=true; map.getContainer().style.cursor='crosshair';
function cancel(){ placing=null; addBtn.textContent='Add to Map'; addBtn.disabled=false; map.getContainer().style.cursor=''; document.removeEventListener('keydown',esc); }
function esc(e){ if(e.key==='Escape') cancel(); } document.addEventListener('keydown',esc);
map.once('click', async e => {
try{
const imgUrl = await uploadPhotoIfAny(); // may be null
addMarker([e.latlng.lat,e.latlng.lng], placing.title, placing.desc, imgUrl);
await saveShared();
}catch(err){ alert('Could not add selfie: ' + err.message); }
finally{ cancel(); tI.value=''; dI.value=''; pI.value=''; }
});
};
// Export PNG (hides panel for clean capture)
document.getElementById('m-export').onclick = async () => {
try{
const panel=document.getElementById('tv-panel'); const prev=panel.style.display; panel.style.display='none';
await new Promise(r=>setTimeout(r,50));
const canvas = await html2canvas(document.getElementById('map'), { useCORS:true, backgroundColor:null, scale:2 });
panel.style.display = prev || '';
const a=document.createElement('a'); a.href=canvas.toDataURL('image/png'); a.download='selfies-map.png'; document.body.appendChild(a); a.click(); document.body.removeChild(a);
}catch(e){ alert('Export failed'); console.error(e); }
};
// Initial load
(async function(){
try{
const res = await fetch(DATA_URL,{cache:'no-store'}); if(!res.ok) throw new Error('HTTP '+res.status);
const arr = await res.json(); selfies.clearLayers(); applyMarkers(arr);
}catch(e){ console.warn('Could not load shared data:', e); }
})();
};
img.src = MAP_IMAGE_URL;