mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-06-28 14:01:47 +02:00
feat: Add colored markers and type filtering on contact map
- Use CircleMarker with different colors per contact type: - CLI (blue), REP (green), ROOM (purple), SENS (orange) - Add type filter checkboxes in map modal header - Dynamically update markers when filters change - Hide filter panel for single contact view - Reduce message action buttons to 32x32px Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -723,11 +723,11 @@ main {
|
||||
|
||||
/* Icon-only action buttons on message bubbles */
|
||||
.btn-msg-action {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.1rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
+88
-18
@@ -20,6 +20,22 @@ let dmUnreadCounts = {}; // Track unread DM counts per conversation
|
||||
let leafletMap = null;
|
||||
let markersGroup = null;
|
||||
let contactsGeoCache = {}; // { 'contactName': { lat, lon }, ... }
|
||||
let allContactsWithGps = []; // Cached contacts for map filtering
|
||||
|
||||
// Contact type colors for map markers
|
||||
const CONTACT_TYPE_COLORS = {
|
||||
1: '#2196F3', // CLI - blue
|
||||
2: '#4CAF50', // REP - green
|
||||
3: '#9C27B0', // ROOM - purple
|
||||
4: '#FF9800' // SENS - orange
|
||||
};
|
||||
|
||||
const CONTACT_TYPE_NAMES = {
|
||||
1: 'CLI',
|
||||
2: 'REP',
|
||||
3: 'ROOM',
|
||||
4: 'SENS'
|
||||
};
|
||||
|
||||
/**
|
||||
* Global navigation function - closes offcanvas and cleans up before navigation
|
||||
@@ -77,6 +93,10 @@ function showContactOnMap(name, lat, lon) {
|
||||
const modal = new bootstrap.Modal(modalEl);
|
||||
document.getElementById('mapModalTitle').textContent = name;
|
||||
|
||||
// Hide type filter panel for single contact view
|
||||
const filterPanel = document.getElementById('mapTypeFilter');
|
||||
if (filterPanel) filterPanel.classList.add('d-none');
|
||||
|
||||
const onShown = function() {
|
||||
initLeafletMap();
|
||||
markersGroup.clearLayers();
|
||||
@@ -99,6 +119,60 @@ function showContactOnMap(name, lat, lon) {
|
||||
// Make showContactOnMap available globally (for contacts.js)
|
||||
window.showContactOnMap = showContactOnMap;
|
||||
|
||||
/**
|
||||
* Get selected contact types from map filter checkboxes
|
||||
*/
|
||||
function getSelectedMapTypes() {
|
||||
const types = [];
|
||||
if (document.getElementById('mapFilterCLI')?.checked) types.push(1);
|
||||
if (document.getElementById('mapFilterREP')?.checked) types.push(2);
|
||||
if (document.getElementById('mapFilterROOM')?.checked) types.push(3);
|
||||
if (document.getElementById('mapFilterSENS')?.checked) types.push(4);
|
||||
return types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update map markers based on current filter selection
|
||||
*/
|
||||
function updateMapMarkers() {
|
||||
if (!leafletMap || !markersGroup) return;
|
||||
|
||||
markersGroup.clearLayers();
|
||||
const selectedTypes = getSelectedMapTypes();
|
||||
|
||||
const filteredContacts = allContactsWithGps.filter(c => selectedTypes.includes(c.type));
|
||||
|
||||
if (filteredContacts.length === 0) {
|
||||
leafletMap.setView([52.0, 19.0], 6);
|
||||
return;
|
||||
}
|
||||
|
||||
const bounds = [];
|
||||
filteredContacts.forEach(c => {
|
||||
const color = CONTACT_TYPE_COLORS[c.type] || '#2196F3';
|
||||
const typeName = CONTACT_TYPE_NAMES[c.type] || 'Unknown';
|
||||
|
||||
L.circleMarker([c.adv_lat, c.adv_lon], {
|
||||
radius: 10,
|
||||
fillColor: color,
|
||||
color: '#fff',
|
||||
weight: 2,
|
||||
opacity: 1,
|
||||
fillOpacity: 0.8
|
||||
})
|
||||
.addTo(markersGroup)
|
||||
.bindPopup(`<b>${c.name}</b><br><span class="text-muted">${typeName}</span>`);
|
||||
|
||||
bounds.push([c.adv_lat, c.adv_lon]);
|
||||
});
|
||||
|
||||
if (bounds.length === 1) {
|
||||
leafletMap.setView(bounds[0], 13);
|
||||
} else {
|
||||
leafletMap.fitBounds(bounds, { padding: [20, 20] });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show all contacts with GPS on map
|
||||
*/
|
||||
@@ -107,6 +181,10 @@ async function showAllContactsOnMap() {
|
||||
const modal = new bootstrap.Modal(modalEl);
|
||||
document.getElementById('mapModalTitle').textContent = 'All Contacts';
|
||||
|
||||
// Show type filter panel
|
||||
const filterPanel = document.getElementById('mapTypeFilter');
|
||||
if (filterPanel) filterPanel.classList.remove('d-none');
|
||||
|
||||
const onShown = async function() {
|
||||
initLeafletMap();
|
||||
markersGroup.clearLayers();
|
||||
@@ -116,27 +194,11 @@ async function showAllContactsOnMap() {
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success && data.contacts) {
|
||||
const contactsWithGps = data.contacts.filter(c =>
|
||||
allContactsWithGps = data.contacts.filter(c =>
|
||||
c.adv_lat && c.adv_lon && (c.adv_lat !== 0 || c.adv_lon !== 0)
|
||||
);
|
||||
|
||||
if (contactsWithGps.length === 0) {
|
||||
leafletMap.setView([52.0, 19.0], 6);
|
||||
} else {
|
||||
const bounds = [];
|
||||
contactsWithGps.forEach(c => {
|
||||
L.marker([c.adv_lat, c.adv_lon])
|
||||
.addTo(markersGroup)
|
||||
.bindPopup(`<b>${c.name}</b>`);
|
||||
bounds.push([c.adv_lat, c.adv_lon]);
|
||||
});
|
||||
|
||||
if (bounds.length === 1) {
|
||||
leafletMap.setView(bounds[0], 13);
|
||||
} else {
|
||||
leafletMap.fitBounds(bounds, { padding: [20, 20] });
|
||||
}
|
||||
}
|
||||
updateMapMarkers();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error loading contacts for map:', err);
|
||||
@@ -146,6 +208,14 @@ async function showAllContactsOnMap() {
|
||||
modalEl.removeEventListener('shown.bs.modal', onShown);
|
||||
};
|
||||
|
||||
// Setup filter checkbox listeners
|
||||
['mapFilterCLI', 'mapFilterREP', 'mapFilterROOM', 'mapFilterSENS'].forEach(id => {
|
||||
const checkbox = document.getElementById(id);
|
||||
if (checkbox) {
|
||||
checkbox.onchange = updateMapMarkers;
|
||||
}
|
||||
});
|
||||
|
||||
modalEl.addEventListener('shown.bs.modal', onShown);
|
||||
modal.show();
|
||||
}
|
||||
|
||||
@@ -81,6 +81,10 @@ function showContactOnMap(name, lat, lon) {
|
||||
const modal = new bootstrap.Modal(modalEl);
|
||||
document.getElementById('mapModalTitle').textContent = name;
|
||||
|
||||
// Hide type filter panel for single contact view
|
||||
const filterPanel = document.getElementById('mapTypeFilter');
|
||||
if (filterPanel) filterPanel.classList.add('d-none');
|
||||
|
||||
const onShown = function() {
|
||||
initLeafletMap();
|
||||
markersGroup.clearLayers();
|
||||
|
||||
@@ -284,6 +284,36 @@
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body p-0">
|
||||
<!-- Type filter (hidden for single contact view) -->
|
||||
<div id="mapTypeFilter" class="d-none px-3 py-2 border-bottom bg-light">
|
||||
<div class="d-flex flex-wrap gap-3 align-items-center">
|
||||
<span class="small text-muted me-1">Show:</span>
|
||||
<div class="form-check form-check-inline mb-0">
|
||||
<input class="form-check-input" type="checkbox" id="mapFilterCLI" value="1" checked>
|
||||
<label class="form-check-label small" for="mapFilterCLI">
|
||||
<span class="badge" style="background-color: #2196F3;">CLI</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline mb-0">
|
||||
<input class="form-check-input" type="checkbox" id="mapFilterREP" value="2" checked>
|
||||
<label class="form-check-label small" for="mapFilterREP">
|
||||
<span class="badge" style="background-color: #4CAF50;">REP</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline mb-0">
|
||||
<input class="form-check-input" type="checkbox" id="mapFilterROOM" value="3" checked>
|
||||
<label class="form-check-label small" for="mapFilterROOM">
|
||||
<span class="badge" style="background-color: #9C27B0;">ROOM</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline mb-0">
|
||||
<input class="form-check-input" type="checkbox" id="mapFilterSENS" value="4" checked>
|
||||
<label class="form-check-label small" for="mapFilterSENS">
|
||||
<span class="badge" style="background-color: #FF9800;">SENS</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="leafletMap" style="height: 400px; width: 100%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -424,6 +424,36 @@
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body p-0">
|
||||
<!-- Type filter (hidden for single contact view) -->
|
||||
<div id="mapTypeFilter" class="d-none px-3 py-2 border-bottom bg-light">
|
||||
<div class="d-flex flex-wrap gap-3 align-items-center">
|
||||
<span class="small text-muted me-1">Show:</span>
|
||||
<div class="form-check form-check-inline mb-0">
|
||||
<input class="form-check-input" type="checkbox" id="mapFilterCLI" value="1" checked>
|
||||
<label class="form-check-label small" for="mapFilterCLI">
|
||||
<span class="badge" style="background-color: #2196F3;">CLI</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline mb-0">
|
||||
<input class="form-check-input" type="checkbox" id="mapFilterREP" value="2" checked>
|
||||
<label class="form-check-label small" for="mapFilterREP">
|
||||
<span class="badge" style="background-color: #4CAF50;">REP</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline mb-0">
|
||||
<input class="form-check-input" type="checkbox" id="mapFilterROOM" value="3" checked>
|
||||
<label class="form-check-label small" for="mapFilterROOM">
|
||||
<span class="badge" style="background-color: #9C27B0;">ROOM</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline mb-0">
|
||||
<input class="form-check-input" type="checkbox" id="mapFilterSENS" value="4" checked>
|
||||
<label class="form-check-label small" for="mapFilterSENS">
|
||||
<span class="badge" style="background-color: #FF9800;">SENS</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="leafletMap" style="height: 400px; width: 100%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user