';
+ // Only hide POI toggle if setting is disabled, otherwise respect the setting
+ if (!showPoiToggle) {
+ poiToggleBtn.style.display = 'none';
+ }
+ return;
+ }
+
+ // Show POI toggle button if there are POIs AND setting is enabled
+ poiToggleBtn.style.display = showPoiToggle ? 'block' : 'none';
+
+ let html = '';
+ poiData.forEach((poi, poiId) => {
+ const timeAgo = formatTimeAgo(poi.timestamp);
+ html += `
+
+
+
${htmlentities(
+ poi.label || poi.name
+ )}
+
${timeAgo}
+
+
+ `;
+ });
+
+ poiList.innerHTML = html;
+}
+
+// Format timestamp to relative time (e.g., "2h ago", "30m ago", "3d ago")
+function formatTimeAgo(timestamp) {
+ if (!timestamp) return '';
+
+ const now = Math.floor(Date.now() / 1000);
+ const diff = now - timestamp;
+
+ if (diff < 60) {
+ return 'now';
+ } else if (diff < 3600) {
+ const minutes = Math.floor(diff / 60);
+ return minutes + 'm ago';
+ } else if (diff < 86400) {
+ const hours = Math.floor(diff / 3600);
+ return hours + 'h ago';
+ } else {
+ const days = Math.floor(diff / 86400);
+ return days + 'd ago';
+ }
+}
+
+// Function to zoom to a specific contact's last position
+function zoomToContact(contactId) {
+ const contact = contactsData.get(contactId);
+ if (contact && contact.lastPosition) {
+ zoomToPosition(contact.lastPosition);
+ } else {
+ console.log('Contact not found or no position');
+ }
+}
+
+// Function to zoom to a specific POI
+function zoomToPoi(poiId) {
+ console.log('poiData contents:', poiData);
+ const poi = poiData.get(poiId);
+ console.log('Found poi:', poi);
+ if (poi && poi.position) {
+ zoomToPosition(poi.position);
+ } else {
+ console.log('POI not found or no position');
+ }
+}
+
+function zoomToPosition(position) {
+ map.setView(position, 15, { animate: true, duration: 1.2 });
+}
+// Initialize overlay when DOM is ready
+document.addEventListener('DOMContentLoaded', function () {
+ initOverlay();
+ updateContactsOverlay();
+ updatePoiOverlay();
+});
+const tracks = {};
+let initDone = false;
+
+/**
+ * @type {Payload}
+ * Example payload:
+ * {
+ * action: "pos",
+ * lat: 47.994828,
+ * lng: 7.849881,
+ * timestamp: 1712928222,
+ * contactId: 123, // can be used as a unique ID to differ tracks etc
+ * name: "Alice",
+ * color: "#ff8080",
+ * independent: false, // false: current or past position of contact, true: a POI
+ * label: "" // used for POI only
+ * }
+ */
+window.webxdc
+ .setUpdateListener((update) => {
+ const payload = update.payload;
+
+ if (payload.action === 'pos') {
+ if (payload.independent) {
+ // Store POI data for overlay
+ const poiId =
+ 'poi_' +
+ Date.now() +
+ '_' +
+ Math.random().toString(36).substr(2, 9);
+ const poiDataObj = {
+ name: payload.name,
+ label: payload.label,
+ color: payload.color,
+ position: [payload.lat, payload.lng],
+ timestamp: payload.timestamp,
+ };
+ console.log('Adding POI with ID:', poiId, 'Data:', poiDataObj);
+ poiData.set(poiId, poiDataObj);
-// set up webxdc
+ // Update POI overlay
+ updatePoiOverlay();
-window.webxdc.setUpdateListener((update) => {
- const payload = update.payload;
- if (payload.action === 'pos') {
- if (payload.independent) {
- var marker = L.marker([payload.lat, payload.lng], {
- icon: pinIcon
+ const marker = L.marker([payload.lat, payload.lng], {
+ icon: pinIcon,
}).addTo(map);
- if (payload.label) {
- marker.bindTooltip(shortLabelHtml(payload.label), {
- permanent: true,
- interactive: true,
- direction: 'bottom',
- offset: [0, -17],
- className: 'poi-tooltip'
- }).openTooltip();
- }
- marker.on('click', function () {
- if (!marker.getPopup()) {
- marker.bindPopup(popupHtml(payload), { closeButton: false }).openPopup();
+ if (payload.label) {
+ marker
+ .bindTooltip(shortLabelHtml(payload.label), {
+ permanent: true,
+ interactive: true,
+ direction: 'bottom',
+ offset: [0, -17],
+ className: 'poi-tooltip',
+ })
+ .openTooltip();
}
- });
- } else {
- if (!tracks[payload.contactId]) {
- tracks[payload.contactId] = {
- lines: [[]],
- payload: payload,
- lastTimestamp: payload.timestamp,
- marker: null,
- polyline: null
- };
+ marker.on('click', function () {
+ if (!marker.getPopup()) {
+ marker
+ .bindPopup(popupHtml(payload), {
+ closeButton: false,
+ })
+ .openPopup();
+ }
+ });
} else {
- tracks[payload.contactId].payload = payload;
- }
+ // Update contacts data for overlay
+ if (!contactsData.has(payload.contactId)) {
+ contactsData.set(payload.contactId, {
+ name: payload.name,
+ color: payload.color,
+ lastPosition: [payload.lat, payload.lng],
+ lastTimestamp: payload.timestamp,
+ });
+ } else {
+ const contact = contactsData.get(payload.contactId);
+ contact.name = payload.name;
+ contact.color = payload.color;
+ contact.lastPosition = [payload.lat, payload.lng];
+ contact.lastTimestamp = payload.timestamp;
+ }
- var lastLine = tracks[payload.contactId].lines.length - 1;
- if ((payload.timestamp - tracks[payload.contactId].lastTimestamp) > 5 * 60) {
- // larger time difference: start new line and connect with previous point on track
- if (tracks[payload.contactId].lines[lastLine].length == 1) {
- tracks[payload.contactId].lines[lastLine].push(tracks[payload.contactId].lines[lastLine][0]);
+ // Update overlay
+ updateContactsOverlay();
+
+ if (!tracks[payload.contactId]) {
+ tracks[payload.contactId] = {
+ lines: [[]],
+ payload: payload,
+ lastTimestamp: payload.timestamp,
+ marker: null,
+ polyline: null,
+ };
+ } else {
+ tracks[payload.contactId].payload = payload;
}
- tracks[payload.contactId].lines.push([]);
- lastLine++;
- }
- tracks[payload.contactId].lines[lastLine].push([payload.lat, payload.lng]);
- tracks[payload.contactId].lastTimestamp = payload.timestamp;
- if (initDone) {
- updateTrack(payload.contactId);
+ let lastLine = tracks[payload.contactId].lines.length - 1;
+ if (
+ payload.timestamp -
+ tracks[payload.contactId].lastTimestamp >
+ 5 * 60
+ ) {
+ // larger time difference: start new line and connect with previous point on track
+ if (tracks[payload.contactId].lines[lastLine].length == 1) {
+ tracks[payload.contactId].lines[lastLine].push(
+ tracks[payload.contactId].lines[lastLine][0]
+ );
+ }
+ tracks[payload.contactId].lines.push([]);
+ lastLine++;
+ }
+
+ tracks[payload.contactId].lines[lastLine].push([
+ payload.lat,
+ payload.lng,
+ ]);
+ tracks[payload.contactId].lastTimestamp = payload.timestamp;
+ if (initDone) {
+ updateTrack(payload.contactId);
+ }
}
}
- }
-}).then(() => {
- updateTracks();
- initDone = true;
-});
-
-
+ })
+ .then(() => {
+ updateTracks();
+ initDone = true;
+ });
// contact's tracks
function updateTrack(contactId) {
- var track = tracks[contactId];
+ const track = tracks[contactId];
if (track.polyline) {
map.removeLayer(track.polyline);
}
- track.polyline = L.polyline(track.lines, {color: track.payload.color, weight: 4}).addTo(map);
-
- var content = '' + shortLabelHtml(track.payload.name) + '';
+ track.polyline = L.polyline(track.lines, {
+ color: track.payload.color,
+ weight: 4,
+ }).addTo(map);
+
+ const content =
+ '' +
+ shortLabelHtml(track.payload.name) +
+ "";
const age = Math.floor(Date.now() / 1000) - track.payload.timestamp;
- if (age > 60*60) {
- content += ' ' + Math.floor(age/60/60) + 'h ago';
- } else if (age > 30*60) {
+ if (age > 60 * 60) {
+ content +=
+ ' ' +
+ Math.floor(age / 60 / 60) +
+ "h ago";
+ } else if (age > 30 * 60) {
content += ' ½h ago';
- } else if (age > 15*60) {
+ } else if (age > 15 * 60) {
content += ' ¼h ago';
} else {
content += ' ';
}
const lastLine = track.lines.length - 1;
- const lastLatLng = track.lines[lastLine][ track.lines[lastLine].length-1 ];
+ const lastLatLng = track.lines[lastLine][track.lines[lastLine].length - 1];
if (track.marker) {
map.removeLayer(track.marker);
}
track.marker = L.marker(lastLatLng, {
- icon: pinIcon,
- opacity: 0
- }).addTo(map);
- var tooltip = L.tooltip({
- content: content,
- permanent: true,
- interactive: true,
- direction: 'bottom',
- offset: [0, -28],
- className: 'ppl-tooltip'
- });
+ icon: pinIcon,
+ opacity: 0,
+ }).addTo(map);
+ const tooltip = L.tooltip({
+ content: content,
+ permanent: true,
+ interactive: true,
+ direction: 'bottom',
+ offset: [0, -28],
+ className: 'ppl-tooltip',
+ });
track.marker.bindTooltip(tooltip).openTooltip();
track.marker.unbindPopup();
track.marker.on('click', function () {
if (!track.marker.getPopup()) {
- track.marker.bindPopup(popupHtml(track.payload), { closeButton: false }).openPopup();
+ track.marker
+ .bindPopup(popupHtml(track.payload), { closeButton: false })
+ .openPopup();
}
});
}
@@ -151,20 +524,20 @@ function updateTracks() {
setInterval(() => {
updateTracks(); // update is needed for the relative time shown
-}, 60*1000);
-
+}, 60 * 1000);
// share a dedicated location
-var popup;
-var popupLatlng;
+let popup;
+let popupLatlng;
function onSend() {
const elem = document.getElementById('textToSend');
- const value = elem.value.trim();
- if (value != "") {
+ const value = elem.value.trim();
+ if (value != '') {
popup.close();
- webxdc.sendUpdate({
+ webxdc.sendUpdate(
+ {
payload: {
action: 'pos',
independent: true,
@@ -173,26 +546,34 @@ function onSend() {
lng: popupLatlng.lng,
label: elem.value,
name: webxdc.selfName,
- color: '#888'
+ color: '#888',
},
- }, 'POI added to map at ' + popupLatlng.lat.toFixed(4) + '/' + popupLatlng.lng.toFixed(4) + ' with text: ' + value);
+ },
+ 'POI added to map at ' +
+ popupLatlng.lat.toFixed(4) +
+ '/' +
+ popupLatlng.lng.toFixed(4) +
+ ' with text: ' +
+ value
+ );
} else {
- elem.placeholder = elem.placeholder == 'Label' ? "Enter label" : "Label"; // just some cheap visual feedback
+ elem.placeholder =
+ elem.placeholder == 'Label' ? 'Enter label' : 'Label'; // just some cheap visual feedback
}
}
function onMapLongClick(e) {
popupLatlng = e.latlng;
- popup = L.popup({closeButton: false, keepInView: true})
+ popup = L.popup({ closeButton: false, keepInView: true })
.setLatLng(popupLatlng)
- .setContent('