mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 01:01:39 -05:00
Extract map controls to a separate file
This commit is contained in:
parent
36289d2469
commit
44cbfff8ff
6 changed files with 369 additions and 258 deletions
File diff suppressed because one or more lines are too long
|
|
@ -1,7 +1,10 @@
|
|||
import { Controller } from "@hotwired/stimulus";
|
||||
import L from "leaflet";
|
||||
import { showFlashMessage } from "../maps/helpers";
|
||||
import { applyThemeToButton } from "../maps/theme_utils";
|
||||
import {
|
||||
setAddVisitButtonActive,
|
||||
setAddVisitButtonInactive
|
||||
} from "../maps/map_controls";
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = [""];
|
||||
|
|
@ -71,40 +74,26 @@ export default class extends Controller {
|
|||
setupAddVisitButton() {
|
||||
if (!this.map || this.addVisitButton) return;
|
||||
|
||||
// Create the Add Visit control
|
||||
const AddVisitControl = L.Control.extend({
|
||||
onAdd: (map) => {
|
||||
const button = L.DomUtil.create('button', 'leaflet-control-button add-visit-button');
|
||||
button.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-map-pin-check-icon lucide-map-pin-check"><path d="M19.43 12.935c.357-.967.57-1.955.57-2.935a8 8 0 0 0-16 0c0 4.993 5.539 10.193 7.399 11.799a1 1 0 0 0 1.202 0 32.197 32.197 0 0 0 .813-.728"/><circle cx="12" cy="10" r="3"/><path d="m16 18 2 2 4-4"/></svg>';
|
||||
button.title = 'Add a visit';
|
||||
// The Add Visit button is now created centrally by maps_controller.js
|
||||
// via addTopRightButtons(). We just need to find it and attach our handler.
|
||||
setTimeout(() => {
|
||||
this.addVisitButton = document.querySelector('.add-visit-button');
|
||||
|
||||
// Style the button with theme-aware styling
|
||||
applyThemeToButton(button, this.userThemeValue || 'dark');
|
||||
button.style.width = '48px';
|
||||
button.style.height = '48px';
|
||||
button.style.borderRadius = '4px';
|
||||
button.style.padding = '0';
|
||||
button.style.display = 'flex';
|
||||
button.style.alignItems = 'center';
|
||||
button.style.justifyContent = 'center';
|
||||
button.style.fontSize = '18px';
|
||||
button.style.transition = 'all 0.2s ease';
|
||||
|
||||
// Disable map interactions when clicking the button
|
||||
L.DomEvent.disableClickPropagation(button);
|
||||
|
||||
// Toggle add visit mode on button click
|
||||
L.DomEvent.on(button, 'click', () => {
|
||||
this.toggleAddVisitMode(button);
|
||||
});
|
||||
|
||||
this.addVisitButton = button;
|
||||
return button;
|
||||
if (this.addVisitButton) {
|
||||
// Attach our click handler to the existing button
|
||||
// Use event capturing and stopPropagation to prevent map click
|
||||
this.addVisitButton.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.toggleAddVisitMode(this.addVisitButton);
|
||||
}, true); // Use capture phase
|
||||
} else {
|
||||
console.warn('Add visit button not found, retrying...');
|
||||
// Retry if button hasn't been created yet
|
||||
this.addVisitButton = null;
|
||||
setTimeout(() => this.setupAddVisitButton(), 200);
|
||||
}
|
||||
});
|
||||
|
||||
// Add the control to the map (top right, below existing buttons)
|
||||
this.map.addControl(new AddVisitControl({ position: 'topright' }));
|
||||
}, 100);
|
||||
}
|
||||
|
||||
toggleAddVisitMode(button) {
|
||||
|
|
@ -121,15 +110,18 @@ export default class extends Controller {
|
|||
this.isAddingVisit = true;
|
||||
|
||||
// Update button style to show active state
|
||||
button.style.backgroundColor = '#dc3545';
|
||||
button.style.color = 'white';
|
||||
button.innerHTML = '✕';
|
||||
setAddVisitButtonActive(button);
|
||||
|
||||
// Change cursor to crosshair
|
||||
this.map.getContainer().style.cursor = 'crosshair';
|
||||
|
||||
// Add map click listener
|
||||
this.map.on('click', this.onMapClick, this);
|
||||
// Add map click listener with a small delay to prevent immediate trigger
|
||||
// This ensures the button click doesn't propagate to the map
|
||||
setTimeout(() => {
|
||||
if (this.isAddingVisit) {
|
||||
this.map.on('click', this.onMapClick, this);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
showFlashMessage('notice', 'Click on the map to place a visit');
|
||||
}
|
||||
|
|
@ -137,9 +129,8 @@ export default class extends Controller {
|
|||
exitAddVisitMode(button) {
|
||||
this.isAddingVisit = false;
|
||||
|
||||
// Reset button style with theme-aware styling
|
||||
applyThemeToButton(button, this.userThemeValue || 'dark');
|
||||
button.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-map-pin-check-icon lucide-map-pin-check"><path d="M19.43 12.935c.357-.967.57-1.955.57-2.935a8 8 0 0 0-16 0c0 4.993 5.539 10.193 7.399 11.799a1 1 0 0 0 1.202 0 32.197 32.197 0 0 0 .813-.728"/><circle cx="12" cy="10" r="3"/><path d="m16 18 2 2 4-4"/></svg>';
|
||||
// Reset button style to inactive state
|
||||
setAddVisitButtonInactive(button, this.userThemeValue || 'dark');
|
||||
|
||||
// Reset cursor
|
||||
this.map.getContainer().style.cursor = '';
|
||||
|
|
@ -186,6 +177,12 @@ export default class extends Controller {
|
|||
}
|
||||
|
||||
showVisitForm(lat, lng) {
|
||||
// Close any existing popup first to ensure only one popup is open
|
||||
if (this.currentPopup) {
|
||||
this.map.closePopup(this.currentPopup);
|
||||
this.currentPopup = null;
|
||||
}
|
||||
|
||||
// Get current date/time for default values
|
||||
const now = new Date();
|
||||
const oneHourLater = new Date(now.getTime() + (60 * 60 * 1000));
|
||||
|
|
@ -291,7 +288,8 @@ export default class extends Controller {
|
|||
started_at: formData.get('started_at'),
|
||||
ended_at: formData.get('ended_at'),
|
||||
latitude: formData.get('latitude'),
|
||||
longitude: formData.get('longitude')
|
||||
longitude: formData.get('longitude'),
|
||||
status: 'confirmed' // Manually created visits should be confirmed
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -325,15 +323,14 @@ export default class extends Controller {
|
|||
|
||||
if (response.ok) {
|
||||
showFlashMessage('notice', `Visit "${visitData.visit.name}" created successfully!`);
|
||||
|
||||
// Store the created visit data
|
||||
const createdVisit = data;
|
||||
|
||||
this.exitAddVisitMode(this.addVisitButton);
|
||||
|
||||
// Refresh visits layer - this will clear and refetch data
|
||||
this.refreshVisitsLayer();
|
||||
|
||||
// Ensure confirmed visits layer is enabled (with a small delay for the API call to complete)
|
||||
setTimeout(() => {
|
||||
this.ensureVisitsLayersEnabled();
|
||||
}, 300);
|
||||
// Add the newly created visit marker immediately to the map
|
||||
this.addCreatedVisitToMap(createdVisit, visitData.visit.latitude, visitData.visit.longitude);
|
||||
} else {
|
||||
const errorMessage = data.error || data.message || 'Failed to create visit';
|
||||
showFlashMessage('error', errorMessage);
|
||||
|
|
@ -348,96 +345,92 @@ export default class extends Controller {
|
|||
}
|
||||
}
|
||||
|
||||
refreshVisitsLayer() {
|
||||
console.log('Attempting to refresh visits layer...');
|
||||
addCreatedVisitToMap(visitData, latitude, longitude) {
|
||||
console.log('Adding newly created visit to map immediately', { latitude, longitude, visitData });
|
||||
|
||||
// Try multiple approaches to refresh the visits layer
|
||||
const mapsController = document.querySelector('[data-controller*="maps"]');
|
||||
if (mapsController) {
|
||||
// Try to get the Stimulus controller instance
|
||||
const stimulusController = this.application.getControllerForElementAndIdentifier(mapsController, 'maps');
|
||||
|
||||
if (stimulusController && stimulusController.visitsManager) {
|
||||
console.log('Found maps controller with visits manager');
|
||||
|
||||
// Clear existing visits and fetch fresh data
|
||||
if (stimulusController.visitsManager.visitCircles) {
|
||||
stimulusController.visitsManager.visitCircles.clearLayers();
|
||||
}
|
||||
if (stimulusController.visitsManager.confirmedVisitCircles) {
|
||||
stimulusController.visitsManager.confirmedVisitCircles.clearLayers();
|
||||
}
|
||||
|
||||
// Refresh the visits data
|
||||
if (typeof stimulusController.visitsManager.fetchAndDisplayVisits === 'function') {
|
||||
console.log('Refreshing visits data...');
|
||||
stimulusController.visitsManager.fetchAndDisplayVisits();
|
||||
}
|
||||
} else {
|
||||
console.log('Could not find maps controller or visits manager');
|
||||
|
||||
// Fallback: Try to dispatch a custom event
|
||||
const refreshEvent = new CustomEvent('visits:refresh', { bubbles: true });
|
||||
mapsController.dispatchEvent(refreshEvent);
|
||||
}
|
||||
} else {
|
||||
if (!mapsController) {
|
||||
console.log('Could not find maps controller element');
|
||||
return;
|
||||
}
|
||||
|
||||
const stimulusController = this.application.getControllerForElementAndIdentifier(mapsController, 'maps');
|
||||
if (!stimulusController || !stimulusController.visitsManager) {
|
||||
console.log('Could not find maps controller or visits manager');
|
||||
return;
|
||||
}
|
||||
|
||||
const visitsManager = stimulusController.visitsManager;
|
||||
|
||||
// Create a circle for the newly created visit (always confirmed)
|
||||
const circle = L.circle([latitude, longitude], {
|
||||
color: '#4A90E2', // Border color for confirmed visits
|
||||
fillColor: '#4A90E2', // Fill color for confirmed visits
|
||||
fillOpacity: 0.5,
|
||||
radius: 110, // Confirmed visit size
|
||||
weight: 2,
|
||||
interactive: true,
|
||||
bubblingMouseEvents: false,
|
||||
pane: 'confirmedVisitsPane'
|
||||
});
|
||||
|
||||
// Add the circle to the confirmed visits layer
|
||||
visitsManager.confirmedVisitCircles.addLayer(circle);
|
||||
console.log('✅ Added newly created confirmed visit circle to layer');
|
||||
console.log('Confirmed visits layer info:', {
|
||||
layerCount: visitsManager.confirmedVisitCircles.getLayers().length,
|
||||
isOnMap: this.map.hasLayer(visitsManager.confirmedVisitCircles)
|
||||
});
|
||||
|
||||
// Make sure the layer is visible on the map
|
||||
if (!this.map.hasLayer(visitsManager.confirmedVisitCircles)) {
|
||||
this.map.addLayer(visitsManager.confirmedVisitCircles);
|
||||
console.log('✅ Added confirmed visits layer to map');
|
||||
}
|
||||
|
||||
// Check if the layer control has the confirmed visits layer enabled
|
||||
this.ensureConfirmedVisitsLayerEnabled();
|
||||
}
|
||||
|
||||
ensureVisitsLayersEnabled() {
|
||||
console.log('Ensuring visits layers are enabled...');
|
||||
|
||||
const mapsController = document.querySelector('[data-controller*="maps"]');
|
||||
if (mapsController) {
|
||||
const stimulusController = this.application.getControllerForElementAndIdentifier(mapsController, 'maps');
|
||||
|
||||
if (stimulusController && stimulusController.map && stimulusController.visitsManager) {
|
||||
const map = stimulusController.map;
|
||||
const visitsManager = stimulusController.visitsManager;
|
||||
|
||||
// Get the confirmed visits layer (newly created visits are always confirmed)
|
||||
const confirmedVisitsLayer = visitsManager.getConfirmedVisitCirclesLayer();
|
||||
|
||||
// Ensure confirmed visits layer is added to map since we create confirmed visits
|
||||
if (confirmedVisitsLayer && !map.hasLayer(confirmedVisitsLayer)) {
|
||||
console.log('Adding confirmed visits layer to map');
|
||||
map.addLayer(confirmedVisitsLayer);
|
||||
|
||||
// Update the layer control checkbox to reflect the layer is now active
|
||||
this.updateLayerControlCheckbox('Confirmed Visits', true);
|
||||
}
|
||||
|
||||
// Refresh visits data to include the new visit
|
||||
if (typeof visitsManager.fetchAndDisplayVisits === 'function') {
|
||||
console.log('Final refresh of visits to show new visit...');
|
||||
visitsManager.fetchAndDisplayVisits();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateLayerControlCheckbox(layerName, isEnabled) {
|
||||
// Find the layer control input for the specified layer
|
||||
ensureConfirmedVisitsLayerEnabled() {
|
||||
// Find the layer control and check/enable the "Confirmed Visits" checkbox
|
||||
const layerControlContainer = document.querySelector('.leaflet-control-layers');
|
||||
if (!layerControlContainer) {
|
||||
console.log('Layer control container not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const inputs = layerControlContainer.querySelectorAll('input[type="checkbox"]');
|
||||
inputs.forEach(input => {
|
||||
const label = input.nextElementSibling;
|
||||
if (label && label.textContent.trim() === layerName) {
|
||||
console.log(`Updating ${layerName} checkbox to ${isEnabled}`);
|
||||
input.checked = isEnabled;
|
||||
// Expand the layer control if it's collapsed
|
||||
const layerControlExpand = layerControlContainer.querySelector('.leaflet-control-layers-toggle');
|
||||
if (layerControlExpand) {
|
||||
layerControlExpand.click();
|
||||
}
|
||||
|
||||
// Trigger change event to ensure proper state management
|
||||
input.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}
|
||||
});
|
||||
setTimeout(() => {
|
||||
const inputs = layerControlContainer.querySelectorAll('input[type="checkbox"]');
|
||||
inputs.forEach(input => {
|
||||
const label = input.nextElementSibling;
|
||||
if (label && label.textContent.trim().includes('Confirmed Visits')) {
|
||||
console.log('Found Confirmed Visits checkbox, current state:', input.checked);
|
||||
if (!input.checked) {
|
||||
console.log('Enabling Confirmed Visits layer via checkbox');
|
||||
input.checked = true;
|
||||
input.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
|
||||
refreshVisitsLayer() {
|
||||
// Don't auto-refresh after creating a visit
|
||||
// The visit is already visible on the map from addCreatedVisitToMap()
|
||||
// Auto-refresh would clear it because fetchAndDisplayVisits uses URL date params
|
||||
// which might not include the newly created visit
|
||||
console.log('Skipping auto-refresh - visit already added to map');
|
||||
}
|
||||
|
||||
|
||||
cleanup() {
|
||||
if (this.map) {
|
||||
this.map.off('click', this.onMapClick, this);
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ import { TileMonitor } from "../maps/tile_monitor";
|
|||
import BaseController from "./base_controller";
|
||||
import { createAllMapLayers } from "../maps/layers";
|
||||
import { applyThemeToControl, applyThemeToButton, applyThemeToPanel } from "../maps/theme_utils";
|
||||
import { addTopRightButtons } from "../maps/map_controls";
|
||||
|
||||
export default class extends BaseController {
|
||||
static targets = ["container"];
|
||||
|
|
@ -212,22 +213,6 @@ export default class extends BaseController {
|
|||
// Expose maps controller globally for family integration
|
||||
window.mapsController = this;
|
||||
|
||||
// Initialize layers for the layer control
|
||||
const controlsLayer = {
|
||||
Points: this.markersLayer,
|
||||
Routes: this.polylinesLayer,
|
||||
Tracks: this.tracksLayer,
|
||||
Heatmap: this.heatmapLayer,
|
||||
"Fog of War": this.fogOverlay,
|
||||
"Scratch map": this.scratchLayerManager?.getLayer() || L.layerGroup(),
|
||||
Areas: this.areasLayer,
|
||||
Photos: this.photoMarkers,
|
||||
"Suggested Visits": this.visitsManager.getVisitCirclesLayer(),
|
||||
"Confirmed Visits": this.visitsManager.getConfirmedVisitCirclesLayer()
|
||||
};
|
||||
|
||||
this.layerControl = L.control.layers(this.baseMaps(), controlsLayer).addTo(this.map);
|
||||
|
||||
// Initialize tile monitor
|
||||
this.tileMonitor = new TileMonitor(this.map, this.apiKey);
|
||||
|
||||
|
|
@ -253,11 +238,25 @@ export default class extends BaseController {
|
|||
// Preload areas
|
||||
fetchAndDrawAreas(this.areasLayer, this.apiKey);
|
||||
|
||||
// Add right panel toggle
|
||||
this.addTogglePanelButton();
|
||||
// Add all top-right buttons in the correct order
|
||||
this.initializeTopRightButtons();
|
||||
|
||||
// Initialize layers for the layer control
|
||||
const controlsLayer = {
|
||||
Points: this.markersLayer,
|
||||
Routes: this.polylinesLayer,
|
||||
Tracks: this.tracksLayer,
|
||||
Heatmap: this.heatmapLayer,
|
||||
"Fog of War": this.fogOverlay,
|
||||
"Scratch map": this.scratchLayerManager?.getLayer() || L.layerGroup(),
|
||||
Areas: this.areasLayer,
|
||||
Photos: this.photoMarkers,
|
||||
"Suggested Visits": this.visitsManager.getVisitCirclesLayer(),
|
||||
"Confirmed Visits": this.visitsManager.getConfirmedVisitCirclesLayer()
|
||||
};
|
||||
|
||||
this.layerControl = L.control.layers(this.baseMaps(), controlsLayer).addTo(this.map);
|
||||
|
||||
// Add visits buttons after calendar button to position them below
|
||||
this.visitsManager.addDrawerButton();
|
||||
|
||||
// Initialize Live Map Handler
|
||||
this.initializeLiveMapHandler();
|
||||
|
|
@ -1184,48 +1183,35 @@ export default class extends BaseController {
|
|||
}
|
||||
}
|
||||
|
||||
initializeTopRightButtons() {
|
||||
// Add all top-right buttons in the correct order:
|
||||
// 1. Select Area, 2. Add Visit, 3. Open Calendar, 4. Open Drawer
|
||||
// Note: Layer control is added separately and appears at the top
|
||||
|
||||
addTogglePanelButton() {
|
||||
// Store reference to the controller instance for use in the control
|
||||
const controller = this;
|
||||
this.topRightControls = addTopRightButtons(
|
||||
this.map,
|
||||
{
|
||||
onSelectArea: () => this.visitsManager.toggleSelectionMode(),
|
||||
// onAddVisit is intentionally null - the add_visit_controller will attach its handler
|
||||
onAddVisit: null,
|
||||
onToggleCalendar: () => this.toggleRightPanel(),
|
||||
onToggleDrawer: () => this.visitsManager.toggleDrawer()
|
||||
},
|
||||
this.userTheme
|
||||
);
|
||||
|
||||
const TogglePanelControl = L.Control.extend({
|
||||
onAdd: function(map) {
|
||||
const button = L.DomUtil.create('button', 'toggle-panel-button');
|
||||
button.innerHTML = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M8 2v4" />
|
||||
<path d="M16 2v4" />
|
||||
<path d="M21 14V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h8" />
|
||||
<path d="M3 10h18" />
|
||||
<path d="m16 20 2 2 4-4" />
|
||||
</svg>
|
||||
`;
|
||||
|
||||
// Style the button with theme-aware styling
|
||||
applyThemeToButton(button, controller.userTheme);
|
||||
button.style.width = '48px';
|
||||
button.style.height = '48px';
|
||||
button.style.borderRadius = '4px';
|
||||
button.style.padding = '0';
|
||||
button.style.display = 'flex';
|
||||
button.style.alignItems = 'center';
|
||||
button.style.justifyContent = 'center';
|
||||
|
||||
// Disable map interactions when clicking the button
|
||||
L.DomEvent.disableClickPropagation(button);
|
||||
|
||||
// Toggle panel on button click
|
||||
L.DomEvent.on(button, 'click', () => {
|
||||
controller.toggleRightPanel();
|
||||
});
|
||||
|
||||
return button;
|
||||
}
|
||||
});
|
||||
|
||||
// Add the control to the map
|
||||
this.map.addControl(new TogglePanelControl({ position: 'topright' }));
|
||||
// Add CSS for selection button active state (needed by visits manager)
|
||||
if (!document.getElementById('selection-tool-active-style')) {
|
||||
const style = document.createElement('style');
|
||||
style.id = 'selection-tool-active-style';
|
||||
style.textContent = `
|
||||
#selection-tool-button.active {
|
||||
border: 2px dashed #3388ff !important;
|
||||
box-shadow: 0 0 8px rgba(51, 136, 255, 0.5) !important;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
}
|
||||
|
||||
shouldShowTracksSelector() {
|
||||
|
|
|
|||
|
|
@ -125,14 +125,16 @@ export function showFlashMessage(type, message) {
|
|||
if (!flashContainer) {
|
||||
flashContainer = document.createElement('div');
|
||||
flashContainer.id = 'flash-messages';
|
||||
flashContainer.className = 'fixed top-5 right-5 flex flex-col-reverse gap-2 z-50';
|
||||
// Use z-[9999] to ensure flash messages appear above navbar (z-50)
|
||||
flashContainer.className = 'fixed top-20 right-5 flex flex-col-reverse gap-2';
|
||||
flashContainer.style.zIndex = '9999';
|
||||
document.body.appendChild(flashContainer);
|
||||
}
|
||||
|
||||
// Create the flash message div
|
||||
const flashDiv = document.createElement('div');
|
||||
flashDiv.setAttribute('data-controller', 'removals');
|
||||
flashDiv.className = `flex items-center justify-between ${classesForFlash(type)} py-3 px-5 rounded-lg z-50`;
|
||||
flashDiv.className = `flex items-center justify-between ${classesForFlash(type)} py-3 px-5 rounded-lg shadow-lg`;
|
||||
|
||||
// Create the message div
|
||||
const messageDiv = document.createElement('div');
|
||||
|
|
|
|||
193
app/javascript/maps/map_controls.js
Normal file
193
app/javascript/maps/map_controls.js
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
// Map control buttons and utilities
|
||||
// This file contains all button controls that are positioned on the top-right corner of the map
|
||||
import L from "leaflet";
|
||||
import { applyThemeToButton } from "./theme_utils";
|
||||
|
||||
/**
|
||||
* Creates a standardized button element for map controls
|
||||
* @param {String} className - CSS class name for the button
|
||||
* @param {String} svgIcon - SVG icon HTML
|
||||
* @param {String} title - Button title/tooltip
|
||||
* @param {String} userTheme - User's theme preference ('dark' or 'light')
|
||||
* @param {Function} onClickCallback - Callback function to execute when button is clicked
|
||||
* @returns {HTMLElement} Button element
|
||||
*/
|
||||
function createStandardButton(className, svgIcon, title, userTheme, onClickCallback) {
|
||||
const button = L.DomUtil.create('button', className);
|
||||
button.innerHTML = svgIcon;
|
||||
button.title = title;
|
||||
|
||||
// Apply standard button styling
|
||||
applyThemeToButton(button, userTheme);
|
||||
button.style.width = '48px';
|
||||
button.style.height = '48px';
|
||||
button.style.borderRadius = '4px';
|
||||
button.style.padding = '0';
|
||||
button.style.display = 'flex';
|
||||
button.style.alignItems = 'center';
|
||||
button.style.justifyContent = 'center';
|
||||
button.style.fontSize = '18px';
|
||||
button.style.transition = 'all 0.2s ease';
|
||||
|
||||
// Disable map interactions when clicking the button
|
||||
L.DomEvent.disableClickPropagation(button);
|
||||
|
||||
// Attach click handler if provided
|
||||
// Note: Some buttons (like Add Visit) have their handlers attached separately
|
||||
if (onClickCallback && typeof onClickCallback === 'function') {
|
||||
L.DomEvent.on(button, 'click', () => {
|
||||
onClickCallback(button);
|
||||
});
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a "Toggle Panel" button control for the map
|
||||
* @param {Function} onClickCallback - Callback function to execute when button is clicked
|
||||
* @param {String} userTheme - User's theme preference ('dark' or 'light')
|
||||
* @returns {L.Control} Leaflet control instance
|
||||
*/
|
||||
export function createTogglePanelControl(onClickCallback, userTheme = 'dark') {
|
||||
const TogglePanelControl = L.Control.extend({
|
||||
onAdd: function(map) {
|
||||
const svgIcon = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M8 2v4" />
|
||||
<path d="M16 2v4" />
|
||||
<path d="M21 14V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h8" />
|
||||
<path d="M3 10h18" />
|
||||
<path d="m16 20 2 2 4-4" />
|
||||
</svg>
|
||||
`;
|
||||
return createStandardButton('toggle-panel-button', svgIcon, 'Toggle Panel', userTheme, onClickCallback);
|
||||
}
|
||||
});
|
||||
|
||||
return TogglePanelControl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a "Visits Drawer" button control for the map
|
||||
* @param {Function} onClickCallback - Callback function to execute when button is clicked
|
||||
* @param {String} userTheme - User's theme preference ('dark' or 'light')
|
||||
* @returns {L.Control} Leaflet control instance
|
||||
*/
|
||||
export function createVisitsDrawerControl(onClickCallback, userTheme = 'dark') {
|
||||
const DrawerControl = L.Control.extend({
|
||||
onAdd: function(map) {
|
||||
const svgIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-panel-right-open-icon lucide-panel-right-open"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M15 3v18"/><path d="m10 15-3-3 3-3"/></svg>';
|
||||
return createStandardButton('leaflet-control-button drawer-button', svgIcon, 'Toggle Visits Drawer', userTheme, onClickCallback);
|
||||
}
|
||||
});
|
||||
|
||||
return DrawerControl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an "Area Selection" button control for the map
|
||||
* @param {Function} onClickCallback - Callback function to execute when button is clicked
|
||||
* @param {String} userTheme - User's theme preference ('dark' or 'light')
|
||||
* @returns {L.Control} Leaflet control instance
|
||||
*/
|
||||
export function createAreaSelectionControl(onClickCallback, userTheme = 'dark') {
|
||||
const SelectionControl = L.Control.extend({
|
||||
onAdd: function(map) {
|
||||
const svgIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-dashed-mouse-pointer-icon lucide-square-dashed-mouse-pointer"><path d="M12.034 12.681a.498.498 0 0 1 .647-.647l9 3.5a.5.5 0 0 1-.033.943l-3.444 1.068a1 1 0 0 0-.66.66l-1.067 3.443a.5.5 0 0 1-.943.033z"/><path d="M5 3a2 2 0 0 0-2 2"/><path d="M19 3a2 2 0 0 1 2 2"/><path d="M5 21a2 2 0 0 1-2-2"/><path d="M9 3h1"/><path d="M9 21h2"/><path d="M14 3h1"/><path d="M3 9v1"/><path d="M21 9v2"/><path d="M3 14v1"/></svg>';
|
||||
const button = createStandardButton('leaflet-bar leaflet-control leaflet-control-custom', svgIcon, 'Select Area', userTheme, onClickCallback);
|
||||
button.id = 'selection-tool-button';
|
||||
return button;
|
||||
}
|
||||
});
|
||||
|
||||
return SelectionControl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an "Add Visit" button control for the map
|
||||
* @param {Function} onClickCallback - Callback function to execute when button is clicked
|
||||
* @param {String} userTheme - User's theme preference ('dark' or 'light')
|
||||
* @returns {L.Control} Leaflet control instance
|
||||
*/
|
||||
export function createAddVisitControl(onClickCallback, userTheme = 'dark') {
|
||||
const AddVisitControl = L.Control.extend({
|
||||
onAdd: function(map) {
|
||||
const svgIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-map-pin-check-icon lucide-map-pin-check"><path d="M19.43 12.935c.357-.967.57-1.955.57-2.935a8 8 0 0 0-16 0c0 4.993 5.539 10.193 7.399 11.799a1 1 0 0 0 1.202 0 32.197 32.197 0 0 0 .813-.728"/><circle cx="12" cy="10" r="3"/><path d="m16 18 2 2 4-4"/></svg>';
|
||||
return createStandardButton('leaflet-control-button add-visit-button', svgIcon, 'Add a visit', userTheme, onClickCallback);
|
||||
}
|
||||
});
|
||||
|
||||
return AddVisitControl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all top-right corner buttons to the map in the correct order
|
||||
* Order: 1. Select Area, 2. Add Visit, 3. Open Calendar, 4. Open Drawer
|
||||
* Note: Layer control is added separately by Leaflet and appears at the top
|
||||
*
|
||||
* @param {Object} map - Leaflet map instance
|
||||
* @param {Object} callbacks - Object containing callback functions for each button
|
||||
* @param {Function} callbacks.onSelectArea - Callback for select area button
|
||||
* @param {Function} callbacks.onAddVisit - Callback for add visit button
|
||||
* @param {Function} callbacks.onToggleCalendar - Callback for toggle calendar/panel button
|
||||
* @param {Function} callbacks.onToggleDrawer - Callback for toggle drawer button
|
||||
* @param {String} userTheme - User's theme preference ('dark' or 'light')
|
||||
* @returns {Object} Object containing references to all created controls
|
||||
*/
|
||||
export function addTopRightButtons(map, callbacks, userTheme = 'dark') {
|
||||
const controls = {};
|
||||
|
||||
// 1. Select Area button
|
||||
if (callbacks.onSelectArea) {
|
||||
const SelectionControl = createAreaSelectionControl(callbacks.onSelectArea, userTheme);
|
||||
controls.selectionControl = new SelectionControl({ position: 'topright' });
|
||||
map.addControl(controls.selectionControl);
|
||||
}
|
||||
|
||||
// 2. Add Visit button
|
||||
// Note: Button is always created, callback is optional (add_visit_controller attaches its own handler)
|
||||
const AddVisitControl = createAddVisitControl(callbacks.onAddVisit, userTheme);
|
||||
controls.addVisitControl = new AddVisitControl({ position: 'topright' });
|
||||
map.addControl(controls.addVisitControl);
|
||||
|
||||
// 3. Open Calendar (Toggle Panel) button
|
||||
if (callbacks.onToggleCalendar) {
|
||||
const TogglePanelControl = createTogglePanelControl(callbacks.onToggleCalendar, userTheme);
|
||||
controls.togglePanelControl = new TogglePanelControl({ position: 'topright' });
|
||||
map.addControl(controls.togglePanelControl);
|
||||
}
|
||||
|
||||
// 4. Open Drawer button
|
||||
if (callbacks.onToggleDrawer) {
|
||||
const DrawerControl = createVisitsDrawerControl(callbacks.onToggleDrawer, userTheme);
|
||||
controls.drawerControl = new DrawerControl({ position: 'topright' });
|
||||
map.addControl(controls.drawerControl);
|
||||
}
|
||||
|
||||
return controls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the Add Visit button to show active state
|
||||
* @param {HTMLElement} button - The button element to update
|
||||
*/
|
||||
export function setAddVisitButtonActive(button) {
|
||||
if (!button) return;
|
||||
|
||||
button.style.backgroundColor = '#dc3545';
|
||||
button.style.color = 'white';
|
||||
button.innerHTML = '✕';
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the Add Visit button to show inactive/default state
|
||||
* @param {HTMLElement} button - The button element to update
|
||||
* @param {String} userTheme - User's theme preference ('dark' or 'light')
|
||||
*/
|
||||
export function setAddVisitButtonInactive(button, userTheme = 'dark') {
|
||||
if (!button) return;
|
||||
|
||||
applyThemeToButton(button, userTheme);
|
||||
button.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-map-pin-check-icon lucide-map-pin-check"><path d="M19.43 12.935c.357-.967.57-1.955.57-2.935a8 8 0 0 0-16 0c0 4.993 5.539 10.193 7.399 11.799a1 1 0 0 0 1.202 0 32.197 32.197 0 0 0 .813-.728"/><circle cx="12" cy="10" r="3"/><path d="m16 18 2 2 4-4"/></svg>';
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import L from "leaflet";
|
||||
import { showFlashMessage } from "./helpers";
|
||||
import { applyThemeToButton } from "./theme_utils";
|
||||
|
||||
/**
|
||||
* Manages visits functionality including displaying, fetching, and interacting with visits
|
||||
|
|
@ -65,76 +64,14 @@ export class VisitsManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Adds a button to toggle the visits drawer
|
||||
* Note: Drawer and selection buttons are now added centrally via addTopRightButtons()
|
||||
* in maps_controller.js to ensure correct button ordering.
|
||||
*
|
||||
* The methods below are kept for backwards compatibility but are no longer called
|
||||
* during initialization. Button callbacks are wired directly in maps_controller.js:
|
||||
* - onSelectArea -> this.toggleSelectionMode()
|
||||
* - onToggleDrawer -> this.toggleDrawer()
|
||||
*/
|
||||
addDrawerButton() {
|
||||
const DrawerControl = L.Control.extend({
|
||||
onAdd: (map) => {
|
||||
const button = L.DomUtil.create('button', 'leaflet-control-button drawer-button');
|
||||
button.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-panel-right-open-icon lucide-panel-right-open"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M15 3v18"/><path d="m10 15-3-3 3-3"/></svg>'; // Left arrow icon
|
||||
// Style the button with theme-aware styling
|
||||
applyThemeToButton(button, this.userTheme);
|
||||
button.style.width = '48px';
|
||||
button.style.height = '48px';
|
||||
button.style.borderRadius = '4px';
|
||||
button.style.padding = '0';
|
||||
button.style.display = 'flex';
|
||||
button.style.alignItems = 'center';
|
||||
button.style.justifyContent = 'center';
|
||||
button.style.fontSize = '18px';
|
||||
|
||||
L.DomEvent.disableClickPropagation(button);
|
||||
L.DomEvent.on(button, 'click', () => {
|
||||
this.toggleDrawer();
|
||||
});
|
||||
|
||||
return button;
|
||||
}
|
||||
});
|
||||
|
||||
this.map.addControl(new DrawerControl({ position: 'topright' }));
|
||||
|
||||
// Add the selection tool button
|
||||
this.addSelectionButton();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a button to enable/disable the area selection tool
|
||||
*/
|
||||
addSelectionButton() {
|
||||
const SelectionControl = L.Control.extend({
|
||||
onAdd: (map) => {
|
||||
const button = L.DomUtil.create('button', 'leaflet-bar leaflet-control leaflet-control-custom');
|
||||
button.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-dashed-mouse-pointer-icon lucide-square-dashed-mouse-pointer"><path d="M12.034 12.681a.498.498 0 0 1 .647-.647l9 3.5a.5.5 0 0 1-.033.943l-3.444 1.068a1 1 0 0 0-.66.66l-1.067 3.443a.5.5 0 0 1-.943.033z"/><path d="M5 3a2 2 0 0 0-2 2"/><path d="M19 3a2 2 0 0 1 2 2"/><path d="M5 21a2 2 0 0 1-2-2"/><path d="M9 3h1"/><path d="M9 21h2"/><path d="M14 3h1"/><path d="M3 9v1"/><path d="M21 9v2"/><path d="M3 14v1"/></svg>';
|
||||
button.title = 'Select Area';
|
||||
button.id = 'selection-tool-button';
|
||||
// Style the button with theme-aware styling
|
||||
applyThemeToButton(button, this.userTheme);
|
||||
button.style.width = '48px';
|
||||
button.style.height = '48px';
|
||||
button.style.borderRadius = '4px';
|
||||
button.style.padding = '0';
|
||||
button.style.display = 'flex';
|
||||
button.style.alignItems = 'center';
|
||||
button.style.justifyContent = 'center';
|
||||
button.style.fontSize = '18px';
|
||||
button.onclick = () => this.toggleSelectionMode();
|
||||
return button;
|
||||
}
|
||||
});
|
||||
|
||||
new SelectionControl({ position: 'topright' }).addTo(this.map);
|
||||
|
||||
// Add CSS for selection button active state
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
#selection-tool-button.active {
|
||||
border: 2px dashed #3388ff !important;
|
||||
box-shadow: 0 0 8px rgba(51, 136, 255, 0.5) !important;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the area selection mode
|
||||
|
|
|
|||
Loading…
Reference in a new issue