freealberta/map/app/public/js/ui-controls.js

740 lines
28 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// UI interaction handlers
import { showStatus, parseGeoLocation } from './utils.js';
import { map, toggleStartLocationVisibility } from './map-manager.js';
import { loadLocations, handleAddLocation, handleEditLocation, handleDeleteLocation, openEditForm, closeEditForm, closeAddModal, openAddModal } from './location-manager.js';
import { toggleEdmontonParcelsLayer } from './external-layers.js';
export let userLocationMarker = null;
export let isAddingLocation = false;
export let isAddressConfirmed = false;
export let isEditAddressConfirmed = false;
// Add this after the existing imports
let isMoveMode = false;
// Export function to get current confirmation states
export function getAddressConfirmationState() {
return {
isAddressConfirmed,
isEditAddressConfirmed
};
}
/**
* Sets the address confirmation state and updates the UI accordingly.
* @param {'add' | 'edit'} mode - The form mode.
* @param {boolean} confirmed - The confirmation state to set.
*/
export function setAddressConfirmed(mode, confirmed) {
const button = mode === 'add' ?
document.getElementById('confirm-address-add-btn') :
document.getElementById('confirm-address-edit-btn');
const saveButton = mode === 'add' ?
document.getElementById('save-location-btn') :
document.getElementById('save-edit-location-btn');
if (mode === 'add') {
isAddressConfirmed = confirmed;
} else {
isEditAddressConfirmed = confirmed;
}
if (confirmed) {
if (button) {
button.textContent = '✅ Address Confirmed';
button.classList.remove('btn-secondary');
button.classList.add('btn-success');
button.disabled = false;
}
} else {
if (button) {
button.textContent = '📍 Confirm Address';
button.classList.remove('btn-success');
button.classList.add('btn-secondary');
button.disabled = false;
}
}
// Always enable save button - confirmation is now optional
if (saveButton) {
saveButton.disabled = false;
}
}
// Add logout function
async function logout() {
try {
const response = await fetch('/api/auth/logout', {
method: 'POST',
credentials: 'include'
});
if (response.ok) {
// Clear any local storage or session storage
localStorage.clear();
sessionStorage.clear();
// Redirect to login page
window.location.href = '/login.html';
} else {
throw new Error('Logout failed');
}
} catch (error) {
console.error('Logout error:', error);
showStatus('Logout failed, redirecting anyway...', 'warning');
// Force redirect even if logout request fails
setTimeout(() => {
window.location.href = '/login.html';
}, 1000);
}
}
// Add logout handler function
function handleLogout(e) {
e.preventDefault();
if (confirm('Are you sure you want to logout?')) {
logout();
}
}
export function getUserLocation() {
if (!navigator.geolocation) {
showStatus('Geolocation is not supported by your browser', 'error');
return;
}
showStatus('Getting your location...', 'info');
navigator.geolocation.getCurrentPosition(
(position) => {
const lat = position.coords.latitude;
const lng = position.coords.longitude;
// Center map on user location
map.setView([lat, lng], 19);
// Add or update user location marker
if (userLocationMarker) {
userLocationMarker.setLatLng([lat, lng]);
} else {
userLocationMarker = L.circleMarker([lat, lng], {
radius: 10,
fillColor: '#2196F3',
color: '#fff',
weight: 3,
opacity: 1,
fillOpacity: 0.8
}).addTo(map);
userLocationMarker.bindPopup('<strong>Your Location</strong>');
}
showStatus('Location found!', 'success');
},
(error) => {
let message = 'Unable to get your location';
switch(error.code) {
case error.PERMISSION_DENIED:
message = 'Location permission denied';
break;
case error.POSITION_UNAVAILABLE:
message = 'Location information unavailable';
break;
case error.TIMEOUT:
message = 'Location request timed out';
break;
}
showStatus(message, 'error');
},
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0
}
);
}
export function toggleAddLocationMode() {
isAddingLocation = !isAddingLocation;
const crosshair = document.getElementById('crosshair');
const addBtn = document.getElementById('add-location-btn');
const mobileAddBtn = document.getElementById('mobile-add-location-btn');
if (isAddingLocation) {
crosshair.classList.remove('hidden');
// Update desktop button
if (addBtn) {
addBtn.classList.add('active');
addBtn.innerHTML = '<span class="btn-icon">✕</span><span class="btn-text">Cancel</span>';
}
// Update mobile button
if (mobileAddBtn) {
mobileAddBtn.classList.add('active');
mobileAddBtn.innerHTML = '✕';
mobileAddBtn.title = 'Cancel';
}
map.on('click', handleMapClick);
} else {
crosshair.classList.add('hidden');
// Update desktop button
if (addBtn) {
addBtn.classList.remove('active');
addBtn.innerHTML = '<span class="btn-icon"></span><span class="btn-text">Add Location Here</span>';
}
// Update mobile button
if (mobileAddBtn) {
mobileAddBtn.classList.remove('active');
mobileAddBtn.innerHTML = '';
mobileAddBtn.title = 'Add Location';
}
map.off('click', handleMapClick);
}
}
function handleMapClick(e) {
if (!isAddingLocation) return;
const { lat, lng } = e.latlng;
openAddModal(lat, lng);
toggleAddLocationMode();
}
export function toggleFullscreen() {
const app = document.getElementById('app');
const btn = document.getElementById('fullscreen-btn');
const mobileBtn = document.getElementById('mobile-fullscreen-btn');
if (!document.fullscreenElement) {
app.requestFullscreen().then(() => {
app.classList.add('fullscreen');
// Update desktop button
if (btn) {
btn.innerHTML = '<span class="btn-icon">◱</span><span class="btn-text">Exit Fullscreen</span>';
}
// Update mobile button
if (mobileBtn) {
mobileBtn.innerHTML = '◱';
mobileBtn.title = 'Exit Fullscreen';
}
}).catch(err => {
console.error('Error entering fullscreen:', err);
showStatus('Unable to enter fullscreen', 'error');
});
} else {
document.exitFullscreen().then(() => {
app.classList.remove('fullscreen');
// Update desktop button
if (btn) {
btn.innerHTML = '<span class="btn-icon">⛶</span><span class="btn-text">Fullscreen</span>';
}
// Update mobile button
if (mobileBtn) {
mobileBtn.innerHTML = '⛶';
mobileBtn.title = 'Fullscreen';
}
});
}
}
export async function confirmAddress(mode) {
let latInput, lngInput, addressInput, saveButton;
if (mode === 'add') {
latInput = document.getElementById('location-lat');
lngInput = document.getElementById('location-lng');
addressInput = document.getElementById('location-address');
saveButton = document.getElementById('save-location-btn');
} else if (mode === 'edit') {
latInput = document.getElementById('edit-location-lat');
lngInput = document.getElementById('edit-location-lng');
addressInput = document.getElementById('edit-location-address');
saveButton = document.getElementById('save-edit-location-btn'); // We'll need to add this ID to edit form
} else {
console.error('Invalid confirm mode:', mode);
return;
}
if (!latInput || !lngInput || !addressInput) {
showStatus('Form elements not found', 'error');
return;
}
const lat = parseFloat(latInput.value);
const lng = parseFloat(lngInput.value);
if (isNaN(lat) || isNaN(lng)) {
showStatus('Please enter valid coordinates first', 'warning');
return;
}
// Get the confirm button
const button = mode === 'add' ?
document.getElementById('confirm-address-add-btn') :
document.getElementById('confirm-address-edit-btn');
const originalText = button ? button.textContent : '';
// Check if already confirmed
const currentlyConfirmed = mode === 'add' ? isAddressConfirmed : isEditAddressConfirmed;
// If already confirmed, toggle back to unconfirmed state
if (currentlyConfirmed) {
if (mode === 'add') {
isAddressConfirmed = false;
} else {
isEditAddressConfirmed = false;
}
// Reset button but keep save enabled
if (button) {
button.textContent = '📍 Confirm Address';
button.classList.remove('btn-success');
button.classList.add('btn-secondary');
}
// Save button remains enabled - confirmation is optional
showStatus('Address confirmation removed - you can still save the location', 'info');
return;
}
if (button) {
button.disabled = true;
button.textContent = 'Looking up...';
}
try {
console.log(`Looking up address for: ${lat}, ${lng}`);
const response = await fetch(`/api/geocode/reverse?lat=${lat}&lng=${lng}`);
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Geocoding failed: ${response.status} ${errorText}`);
}
const data = await response.json();
if (data.success && data.data) {
// Use the formatted address or full address
const address = data.data.formattedAddress || data.data.fullAddress;
if (address) {
addressInput.value = address;
// Mark as confirmed
if (mode === 'add') {
isAddressConfirmed = true;
} else {
isEditAddressConfirmed = true;
}
// Update button to show confirmed state
if (button) {
button.textContent = '✅ Address Confirmed';
button.classList.remove('btn-secondary');
button.classList.add('btn-success');
}
showStatus('Address confirmed and updated!', 'success');
} else {
showStatus('No address found for these coordinates', 'warning');
}
} else {
showStatus('Address lookup failed', 'warning');
}
} catch (error) {
console.error('Address lookup error:', error);
showStatus(`Address lookup failed: ${error.message}`, 'error');
} finally {
// Restore button state if not confirmed
const finallyConfirmed = mode === 'add' ? isAddressConfirmed : isEditAddressConfirmed;
if (button && !finallyConfirmed) {
button.disabled = false;
button.textContent = originalText;
}
}
}
export function resetAddressConfirmation(mode) {
if (mode === 'add') {
isAddressConfirmed = false;
const button = document.getElementById('confirm-address-add-btn');
const saveButton = document.getElementById('save-location-btn');
if (button) {
button.textContent = '📍 Confirm Address';
button.classList.remove('btn-success');
button.classList.add('btn-secondary');
button.disabled = false;
}
// Keep save button enabled - confirmation is now optional
if (saveButton) {
saveButton.disabled = false;
}
} else if (mode === 'edit') {
isEditAddressConfirmed = false;
const button = document.getElementById('confirm-address-edit-btn');
const saveButton = document.getElementById('save-edit-location-btn');
if (button) {
button.textContent = '📍 Confirm Address';
button.classList.remove('btn-success');
button.classList.add('btn-secondary');
button.disabled = false;
}
// Keep save button enabled - confirmation is now optional
if (saveButton) {
saveButton.disabled = false;
}
}
}
export function setupGeoLocationSync() {
// For add form
const addLatInput = document.getElementById('location-lat');
const addLngInput = document.getElementById('location-lng');
const addGeoInput = document.getElementById('geo-location');
if (addLatInput && addLngInput && addGeoInput) {
[addLatInput, addLngInput].forEach(input => {
input.addEventListener('input', () => {
const lat = addLatInput.value;
const lng = addLngInput.value;
if (lat && lng) {
addGeoInput.value = `${lat};${lng}`;
}
// Reset address confirmation when coordinates change
resetAddressConfirmation('add');
});
});
addGeoInput.addEventListener('input', () => {
const coords = parseGeoLocation(addGeoInput.value);
if (coords) {
addLatInput.value = coords.lat;
addLngInput.value = coords.lng;
}
// Reset address confirmation when geo-location changes
resetAddressConfirmation('add');
});
}
// For edit form
const editLatInput = document.getElementById('edit-location-lat');
const editLngInput = document.getElementById('edit-location-lng');
const editGeoInput = document.getElementById('edit-geo-location');
if (editLatInput && editLngInput && editGeoInput) {
[editLatInput, editLngInput].forEach(input => {
input.addEventListener('input', () => {
const lat = editLatInput.value;
const lng = editLngInput.value;
if (lat && lng) {
editGeoInput.value = `${lat};${lng}`;
}
// Reset address confirmation when coordinates change
resetAddressConfirmation('edit');
});
});
editGeoInput.addEventListener('input', () => {
const coords = parseGeoLocation(editGeoInput.value);
if (coords) {
editLatInput.value = coords.lat;
editLngInput.value = coords.lng;
}
// Reset address confirmation when geo-location changes
resetAddressConfirmation('edit');
});
}
}
export function setupEventListeners() {
// Desktop controls
document.getElementById('refresh-btn')?.addEventListener('click', () => {
loadLocations();
showStatus('Locations refreshed', 'success');
});
document.getElementById('geolocate-btn')?.addEventListener('click', getUserLocation);
document.getElementById('toggle-start-location-btn')?.addEventListener('click', toggleStartLocationVisibility);
document.getElementById('add-location-btn')?.addEventListener('click', toggleAddLocationMode);
document.getElementById('toggle-edmonton-layer-btn')?.addEventListener('click', toggleEdmontonParcelsLayer);
document.getElementById('fullscreen-btn')?.addEventListener('click', toggleFullscreen);
// Add logout button event listeners
document.getElementById('logout-btn')?.addEventListener('click', handleLogout);
document.getElementById('mobile-logout-btn')?.addEventListener('click', handleLogout);
// Mobile controls
document.getElementById('mobile-refresh-btn')?.addEventListener('click', () => {
loadLocations();
showStatus('Locations refreshed', 'success');
});
document.getElementById('mobile-geolocate-btn')?.addEventListener('click', getUserLocation);
document.getElementById('mobile-toggle-start-location-btn')?.addEventListener('click', toggleStartLocationVisibility);
document.getElementById('mobile-add-location-btn')?.addEventListener('click', toggleAddLocationMode);
document.getElementById('mobile-overlay-btn')?.addEventListener('click', () => {
console.log('Mobile overlay button clicked!');
// Call the global function to open mobile overlay modal
if (window.openMobileOverlayModal) {
console.log('openMobileOverlayModal function found - calling it');
window.openMobileOverlayModal();
} else {
console.error('openMobileOverlayModal function not available');
console.log('Available window functions:', Object.keys(window).filter(k => k.includes('overlay') || k.includes('Modal')));
}
});
document.getElementById('mobile-toggle-edmonton-layer-btn')?.addEventListener('click', toggleEdmontonParcelsLayer);
document.getElementById('mobile-fullscreen-btn')?.addEventListener('click', toggleFullscreen);
// Mobile dropdown toggle
document.getElementById('mobile-dropdown-toggle')?.addEventListener('click', (e) => {
e.stopPropagation();
const dropdown = document.getElementById('mobile-dropdown');
dropdown.classList.toggle('active');
});
// Close mobile dropdown when clicking outside
document.addEventListener('click', (e) => {
const dropdown = document.getElementById('mobile-dropdown');
if (!dropdown.contains(e.target)) {
dropdown.classList.remove('active');
}
});
// Modal controls
document.getElementById('close-modal-btn')?.addEventListener('click', closeAddModal);
document.getElementById('cancel-modal-btn')?.addEventListener('click', closeAddModal);
// Edit footer controls
document.getElementById('close-edit-footer-btn')?.addEventListener('click', closeEditForm);
// Forms
document.getElementById('location-form')?.addEventListener('submit', handleAddLocation);
document.getElementById('edit-location-form')?.addEventListener('submit', handleEditLocation);
// Delete button
document.getElementById('delete-location-btn')?.addEventListener('click', handleDeleteLocation);
// Address confirm buttons
document.getElementById('confirm-address-add-btn')?.addEventListener('click', () => {
confirmAddress('add');
});
document.getElementById('confirm-address-edit-btn')?.addEventListener('click', () => {
confirmAddress('edit');
});
// Auto address lookup event listener
document.addEventListener('autoAddressLookup', (e) => {
const { mode } = e.detail;
if (mode === 'add') {
// Add a small delay to ensure the form is fully rendered
setTimeout(() => {
confirmAddress('add');
}, 200);
}
});
// Geo-location field sync
setupGeoLocationSync();
// Add event delegation for popup edit buttons
document.addEventListener('click', (e) => {
if (e.target.classList.contains('edit-location-popup-btn')) {
e.preventDefault();
try {
const locationData = JSON.parse(e.target.getAttribute('data-location'));
openEditForm(locationData);
} catch (error) {
console.error('Error parsing location data:', error);
showStatus('Error opening edit form', 'error');
}
}
// Add handler for move button
if (e.target.classList.contains('move-location-popup-btn')) {
e.preventDefault();
try {
const locationData = JSON.parse(e.target.getAttribute('data-location'));
// Find the marker that corresponds to this location
import('./location-manager.js').then(({ markers, startMovingMarker }) => {
const marker = markers.find(m => {
const markerData = m._locationData;
const locationId = locationData.Id || locationData.id || locationData.ID || locationData._id;
const markerId = markerData?.Id || markerData?.id || markerData?.ID || markerData?._id;
return markerId === locationId;
});
if (marker) {
startMovingMarker(locationData, marker);
} else {
console.error('Could not find marker for location:', locationData);
showStatus('Could not find marker for this location', 'error');
}
});
} catch (error) {
console.error('Error starting move:', error);
showStatus('Failed to start move mode', 'error');
}
}
});
// Improve popup button handling for mobile
map.on('popupopen', function(e) {
const popup = e.popup;
const content = popup.getContent();
// Create a temporary container to parse the HTML
const tempDiv = document.createElement('div');
tempDiv.innerHTML = content;
// Find buttons in the popup
const editBtn = tempDiv.querySelector('.edit-location-popup-btn');
const moveBtn = tempDiv.querySelector('.move-location-popup-btn');
if (editBtn || moveBtn) {
// Re-render popup with proper event handlers
setTimeout(() => {
const popupNode = popup._contentNode || popup._container;
if (!popupNode) return;
// Edit button
const editButton = popupNode.querySelector('.edit-location-popup-btn');
if (editButton) {
editButton.addEventListener('click', function(event) {
event.preventDefault();
event.stopPropagation();
try {
const locationData = JSON.parse(this.getAttribute('data-location'));
openEditForm(locationData);
map.closePopup();
} catch (error) {
console.error('Error parsing location data:', error);
showStatus('Error opening edit form', 'error');
}
});
}
// Move button
const moveButton = popupNode.querySelector('.move-location-popup-btn');
if (moveButton) {
moveButton.addEventListener('click', async function(event) {
event.preventDefault();
event.stopPropagation();
try {
const locationData = JSON.parse(this.getAttribute('data-location'));
const { startMovingMarker } = await import('./location-manager.js');
startMovingMarker(locationData, e.popup._source);
} catch (error) {
console.error('Error starting move:', error);
showStatus('Failed to start move mode', 'error');
}
});
}
}, 0);
}
});
// Add this after the existing event listeners
document.addEventListener('updateMoveButtons', (e) => {
isMoveMode = e.detail.isMoving;
if (isMoveMode) {
// Hide add location buttons during move mode
const addBtn = document.getElementById('add-location-btn');
const mobileAddBtn = document.getElementById('mobile-add-location-btn');
if (addBtn) addBtn.style.display = 'none';
if (mobileAddBtn) mobileAddBtn.style.display = 'none';
// Add cancel move button
addCancelMoveButton();
} else {
// Restore add location buttons
const addBtn = document.getElementById('add-location-btn');
const mobileAddBtn = document.getElementById('mobile-add-location-btn');
if (addBtn) addBtn.style.display = '';
if (mobileAddBtn) mobileAddBtn.style.display = '';
// Remove cancel move button
removeCancelMoveButton();
}
});
}
// Add this to the setupMapEventListeners function
export function setupMapEventListeners(mapInstance) {
// ...existing code...
// Handle move location button clicks in popups
mapInstance.on('popupopen', function(e) {
// ...existing code for edit button...
// Handle move button
const moveBtn = e.popup._contentNode?.querySelector('.move-location-popup-btn');
if (moveBtn) {
moveBtn.addEventListener('click', async function() {
try {
const locationData = JSON.parse(this.getAttribute('data-location'));
const marker = e.popup._source;
const { startMovingMarker } = await import('./location-manager.js');
startMovingMarker(locationData, marker);
} catch (error) {
console.error('Error starting move:', error);
showStatus('Failed to start move mode', 'error');
}
});
}
});
}
function addCancelMoveButton() {
// Desktop button
const mapControls = document.querySelector('.map-controls');
if (mapControls && !document.getElementById('cancel-move-btn')) {
const cancelBtn = document.createElement('button');
cancelBtn.id = 'cancel-move-btn';
cancelBtn.className = 'btn btn-danger';
cancelBtn.innerHTML = '<span class="btn-icon">✕</span><span class="btn-text">Cancel Move</span>';
cancelBtn.addEventListener('click', async () => {
const { cancelMove } = await import('./location-manager.js');
cancelMove();
});
mapControls.insertBefore(cancelBtn, mapControls.firstChild);
}
// Mobile button
const mobileSidebar = document.getElementById('mobile-sidebar');
if (mobileSidebar && !document.getElementById('mobile-cancel-move-btn')) {
const mobileCancelBtn = document.createElement('button');
mobileCancelBtn.id = 'mobile-cancel-move-btn';
mobileCancelBtn.className = 'btn btn-danger';
mobileCancelBtn.title = 'Cancel Move';
mobileCancelBtn.innerHTML = '✕';
mobileCancelBtn.addEventListener('click', async () => {
const { cancelMove } = await import('./location-manager.js');
cancelMove();
});
mobileSidebar.insertBefore(mobileCancelBtn, mobileSidebar.firstChild);
}
}
function removeCancelMoveButton() {
document.getElementById('cancel-move-btn')?.remove();
document.getElementById('mobile-cancel-move-btn')?.remove();
}