dawarich/app/javascript/controllers/area_drawer_controller.js
Evgenii Burmakin 8934c29fce
0.36.2 (#2007)
* fix: move foreman to global gems to fix startup crash (#1971)

* Update exporting code to stream points data to file in batches to red… (#1980)

* Update exporting code to stream points data to file in batches to reduce memory usage

* Update changelog

* Update changelog

* Feature/maplibre frontend (#1953)

* Add a plan to use MapLibre GL JS for the frontend map rendering, replacing Leaflet

* Implement phase 1

* Phases 1-3 + part of 4

* Fix e2e tests

* Phase 6

* Implement fog of war

* Phase 7

* Next step: fix specs, phase 7 done

* Use our own map tiles

* Extract v2 map logic to separate manager classes

* Update settings panel on v2 map

* Update v2 e2e tests structure

* Reimplement location search in maps v2

* Update speed routes

* Implement visits and places creation in v2

* Fix last failing test

* Implement visits merging

* Fix a routes e2e test and simplify the routes layer styling.

* Extract js to modules from maps_v2_controller.js

* Implement area creation

* Fix spec problem

* Fix some e2e tests

* Implement live mode in v2 map

* Update icons and panel

* Extract some styles

* Remove unused file

* Start adding dark theme to popups on MapLibre maps

* Make popups respect dark theme

* Move v2 maps to maplibre namespace

* Update v2 references to maplibre

* Put place, area and visit info into side panel

* Update API to use safe settings config method

* Fix specs

* Fix method name to config in SafeSettings and update usages accordingly

* Add missing public files

* Add handling for real time points

* Fix remembering enabled/disabled layers of the v2 map

* Fix lots of e2e tests

* Add settings to select map version

* Use maps/v2 as main path for MapLibre maps

* Update routing

* Update live mode

* Update maplibre controller

* Update changelog

* Remove some console.log statements

---------

Co-authored-by: Robin Tuszik <mail@robin.gg>
2025-12-06 20:54:49 +01:00

146 lines
3.1 KiB
JavaScript

import { Controller } from '@hotwired/stimulus'
import { createCircle, calculateDistance } from 'maps_maplibre/utils/geometry'
/**
* Area drawer controller
* Draw circular areas on map
*/
export default class extends Controller {
connect() {
this.isDrawing = false
this.center = null
this.radius = 0
this.map = null
// Bind event handlers to maintain context
this.onClick = this.onClick.bind(this)
this.onMouseMove = this.onMouseMove.bind(this)
}
/**
* Start drawing mode
* @param {maplibregl.Map} map - The MapLibre map instance
*/
startDrawing(map) {
if (!map) {
console.error('[Area Drawer] Map instance not provided')
return
}
this.isDrawing = true
this.map = map
map.getCanvas().style.cursor = 'crosshair'
// Add temporary layer
if (!map.getSource('draw-source')) {
map.addSource('draw-source', {
type: 'geojson',
data: { type: 'FeatureCollection', features: [] }
})
map.addLayer({
id: 'draw-fill',
type: 'fill',
source: 'draw-source',
paint: {
'fill-color': '#22c55e',
'fill-opacity': 0.2
}
})
map.addLayer({
id: 'draw-outline',
type: 'line',
source: 'draw-source',
paint: {
'line-color': '#22c55e',
'line-width': 2
}
})
}
// Add event listeners
map.on('click', this.onClick)
map.on('mousemove', this.onMouseMove)
}
/**
* Cancel drawing mode
*/
cancelDrawing() {
if (!this.map) return
this.isDrawing = false
this.center = null
this.radius = 0
this.map.getCanvas().style.cursor = ''
// Clear drawing
const source = this.map.getSource('draw-source')
if (source) {
source.setData({ type: 'FeatureCollection', features: [] })
}
// Remove event listeners
this.map.off('click', this.onClick)
this.map.off('mousemove', this.onMouseMove)
}
/**
* Click handler
*/
onClick(e) {
if (!this.isDrawing || !this.map) return
if (!this.center) {
// First click - set center
this.center = [e.lngLat.lng, e.lngLat.lat]
} else {
// Second click - finish drawing
document.dispatchEvent(new CustomEvent('area:drawn', {
detail: {
center: this.center,
radius: this.radius
}
}))
this.cancelDrawing()
}
}
/**
* Mouse move handler
*/
onMouseMove(e) {
if (!this.isDrawing || !this.center || !this.map) return
const currentPoint = [e.lngLat.lng, e.lngLat.lat]
this.radius = calculateDistance(this.center, currentPoint)
this.updateDrawing()
}
/**
* Update drawing visualization
*/
updateDrawing() {
if (!this.center || this.radius === 0 || !this.map) return
const coordinates = createCircle(this.center, this.radius)
const source = this.map.getSource('draw-source')
if (source) {
source.setData({
type: 'FeatureCollection',
features: [{
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [coordinates]
}
}]
})
}
}
}