diff --git a/index.html b/index.html
index b1a01e1..798787b 100644
--- a/index.html
+++ b/index.html
@@ -115,11 +115,144 @@
background-color: transparent;
}
+
+ /* overlay */
+ .overlay {
+ display: none;
+ position: absolute;
+ top: 20px;
+ left: 70px;
+ background: rgba(255, 255, 255, 0.95);
+ border-radius: 10px;
+ padding: 15px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.55);
+ max-width: 300px;
+ max-height: 400px;
+ overflow-y: auto;
+ z-index: 1000;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ }
+
+ .overlay h3 {
+ margin: 0 0 15px 0;
+ font-size: 16px;
+ color: #333;
+ text-align: center;
+ border-bottom: 2px solid #007AFF;
+ padding-bottom: 8px;
+ }
+
+ .contact-item {
+ display: flex;
+ align-items: center;
+ margin-bottom: 5px;
+ padding: 8px;
+ }
+
+ .contact-color {
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ margin-right: 10px;
+ border: 2px solid #fff;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
+ }
+
+ .contact-name {
+ flex: 1;
+ font-size: 14px;
+ color: #333;
+ font-weight: 500;
+ }
+
+ .contact-time {
+ font-size: 12px;
+ color: #666;
+ margin: 0 8px;
+ white-space: nowrap;
+ }
+
+ .item-button {
+ background: #60a1574a;
+ color: white;
+ border: none;
+ border-radius: 50%;
+ font-size: 12px;
+ cursor: pointer;
+ transition: background-color 0.2s;
+ margin-left: 5px;
+ height: 32px;
+ width: 32px;
+ }
+
+ .item-button:hover {
+ background: #60a1578b;
+ }
+
+ .no-items {
+ text-align: center;
+ color: #666;
+ font-style: italic;
+ padding: 20px;
+ }
+
+ .toggle-overlay {
+ position: absolute;
+ top: 20px;
+ left: 20px;
+ background: rgba(255, 255, 255, 0.9);
+ border: none;
+ border-radius: 50%;
+ width: 40px;
+ height: 40px;
+ font-size: 18px;
+ cursor: pointer;
+ z-index: 1001;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.55);
+ transition: all 0.2s;
+ }
+
+ .toggle-overlay:hover {
+ background: rgba(255, 255, 255, 1);
+ transform: scale(1.05);
+ }
+
+ .overlay-hidden .overlay {
+ display: none;
+ }
+
+ /* POI overlay specific styles */
+ .poi-toggle {
+ top: 70px;
+ }
+
+ .poi-overlay {
+ top: 70px;
+ }
+
+
+ 👥
+
+ 📍
+
+
+
+
diff --git a/index.js b/index.js
index 0357499..95dc15a 100644
--- a/index.js
+++ b/index.js
@@ -1,7 +1,7 @@
// set up map
-var map = L.map('map', {
+const map = L.map('map', {
doubleClickZoom: true,
zoomControl: false, // added manually below
tapHold: true
@@ -15,12 +15,164 @@ map.attributionControl.setPrefix('');
L.control.scale({position: 'bottomleft'}).addTo(map);
L.control.zoom({position: 'topright'}).addTo(map);
+// Overlay management
+let contactOverlayVisible = false;
+let poiOverlayVisible = false;
+const contactsData = new Map(); // Store contact data for the overlay
+const poiData = new Map(); // Store POI data for the overlay
+// DOM elements
+const contactOverlay = document.getElementById('contactsOverlay');
+const poiOverlay = document.getElementById('poiOverlay');
+const toggleBtn = document.getElementById('toggleOverlay');
+const poiToggleBtn = document.getElementById('togglePoiOverlay');
+
+function initOverlay() {
+ contactOverlay.style.display = 'none';
+ poiOverlay.style.display = 'none';
+ toggleBtn.textContent ='👤';
+ poiToggleBtn.style.display = 'none'; // Hidden by default
+ console.log(tracks);
+
+ toggleBtn.addEventListener('click', function() {
+ contactOverlayVisible = !contactOverlayVisible;
+ if (contactOverlayVisible) {
+ poiOverlayVisible = false;
+ }
+ showHideOverlays();
+ });
+
+ poiToggleBtn.addEventListener('click', function() {
+ poiOverlayVisible = !poiOverlayVisible;
+ if (poiOverlayVisible) {
+ contactOverlayVisible = false;
+ }
+ showHideOverlays();
+ });
+
+ function showHideOverlays() {
+ contactOverlay.style.display = contactOverlayVisible ? 'block' : 'none';
+ poiOverlay.style.display = poiOverlayVisible ? 'block' : 'none';
+ }
+}
+
+// Update the contacts overlay
+function updateContactsOverlay() {
+ const contactsList = document.getElementById('contactsList');
+
+ if (contactsData.size === 0) {
+ contactsList.innerHTML = 'No contacts shared their location yet
';
+ return;
+ }
+
+ if (contactsData.size > 1) {
+ toggleBtn.textContent = '👥';
+ }
+
+ let html = '';
+ contactsData.forEach((contact, contactId) => {
+ const timeAgo = formatTimeAgo(contact.lastTimestamp);
+ html += `
+
+ `;
+ });
+
+ contactsList.innerHTML = html;
+}
+
+// Update the POI overlay
+function updatePoiOverlay() {
+ const poiList = document.getElementById('poiList');
+
+ if (poiData.size === 0) {
+ poiList.innerHTML = 'No POIs yet
';
+ poiToggleBtn.style.display = 'none';
+ return;
+ }
+
+ // Show POI toggle button if there are POIs
+ poiToggleBtn.style.display = 'block';
+
+ let html = '';
+ poiData.forEach((poi, poiId) => {
+ const timeAgo = formatTimeAgo(poi.timestamp);
+ html += `
+
+ `;
+ });
+
+ 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();
+});
+
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: "© OpenStreetMap"
}).addTo(map);
-var pinIcon = L.icon({
+const pinIcon = L.icon({
iconUrl: 'images/pin-icon.png',
iconRetinaUrl: 'images/pin-icon-2x.png',
iconSize: [12, 29], // size of the icon
@@ -28,18 +180,45 @@ var pinIcon = L.icon({
popupAnchor: [0, -29] // point from which the popup should open relative to the iconAnchor
});
-var tracks = {};
-var initDone = false;
-
-
-
-// set up webxdc
+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) {
- var marker = L.marker([payload.lat, payload.lng], {
+ // 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);
+
+ // Update POI overlay
+ updatePoiOverlay();
+
+ const marker = L.marker([payload.lat, payload.lng], {
icon: pinIcon
}).addTo(map);
if (payload.label) {
@@ -57,6 +236,25 @@ window.webxdc.setUpdateListener((update) => {
}
});
} else {
+ // 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;
+ }
+
+ // Update overlay
+ updateContactsOverlay();
+
if (!tracks[payload.contactId]) {
tracks[payload.contactId] = {
lines: [[]],
@@ -69,7 +267,7 @@ window.webxdc.setUpdateListener((update) => {
tracks[payload.contactId].payload = payload;
}
- var lastLine = tracks[payload.contactId].lines.length - 1;
+ const 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) {
@@ -96,14 +294,14 @@ window.webxdc.setUpdateListener((update) => {
// 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) + ' ';
+ let 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 ';
@@ -117,6 +315,14 @@ function updateTrack(contactId) {
const lastLine = track.lines.length - 1;
const lastLatLng = track.lines[lastLine][ track.lines[lastLine].length-1 ];
+
+ // Update contacts data with latest position
+ if (contactsData.has(contactId)) {
+ const contact = contactsData.get(contactId);
+ contact.lastPosition = lastLatLng;
+ contact.lastTimestamp = track.lastTimestamp;
+ }
+
if (track.marker) {
map.removeLayer(track.marker);
}
@@ -124,7 +330,7 @@ function updateTrack(contactId) {
icon: pinIcon,
opacity: 0
}).addTo(map);
- var tooltip = L.tooltip({
+ const tooltip = L.tooltip({
content: content,
permanent: true,
interactive: true,
@@ -145,6 +351,9 @@ function updateTracks() {
for (contactId in tracks) {
updateTrack(contactId);
}
+ // Update overlays after updating all tracks
+ updateContactsOverlay();
+ updatePoiOverlay();
}
setInterval(() => {
@@ -154,8 +363,8 @@ setInterval(() => {
// share a dedicated location
-var popup;
-var popupLatlng;
+const popup = null;
+const popupLatlng = null;
function onSend() {
const elem = document.getElementById('textToSend');
diff --git a/manifest.toml b/manifest.toml
index 5d74bf3..81a7c4e 100644
--- a/manifest.toml
+++ b/manifest.toml
@@ -1,3 +1,4 @@
name = "Maps"
source_code_url = "https://github.com/deltachat/maps"
request_internet_access = true
+request_integration = 'map'