diff options
author | Anton Tananaev <anton@traccar.org> | 2023-02-04 15:15:35 -0800 |
---|---|---|
committer | Anton Tananaev <anton@traccar.org> | 2023-02-04 15:15:35 -0800 |
commit | 208251f1c7bef92fb9d52cc40acc3176387a6680 (patch) | |
tree | 6f707d2e59111f6c3545303098e7b9c7d40ad45a /modern/src/map/draw | |
parent | 80815532407d2f92a387ce0c40c87bb81aecad51 (diff) | |
download | trackermap-web-208251f1c7bef92fb9d52cc40acc3176387a6680.tar.gz trackermap-web-208251f1c7bef92fb9d52cc40acc3176387a6680.tar.bz2 trackermap-web-208251f1c7bef92fb9d52cc40acc3176387a6680.zip |
Update web dependencies
Diffstat (limited to 'modern/src/map/draw')
-rw-r--r-- | modern/src/map/draw/MapGeofenceEdit.js | 161 | ||||
-rw-r--r-- | modern/src/map/draw/theme.js | 230 |
2 files changed, 391 insertions, 0 deletions
diff --git a/modern/src/map/draw/MapGeofenceEdit.js b/modern/src/map/draw/MapGeofenceEdit.js new file mode 100644 index 00000000..e547ea05 --- /dev/null +++ b/modern/src/map/draw/MapGeofenceEdit.js @@ -0,0 +1,161 @@ +import 'mapbox-gl/dist/mapbox-gl.css'; +import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css'; +import maplibregl from 'maplibre-gl'; +import MapboxDraw from '@mapbox/mapbox-gl-draw'; +import { useEffect } from 'react'; + +import { useDispatch, useSelector } from 'react-redux'; +import { useNavigate } from 'react-router-dom'; +import { useTheme } from '@mui/styles'; +import { map } from '../core/MapView'; +import { geofenceToFeature, geometryToArea } from '../core/mapUtil'; +import { errorsActions, geofencesActions } from '../../store'; +import { useCatchCallback } from '../../reactHelper'; +import theme from './theme'; + +const draw = new MapboxDraw({ + displayControlsDefault: false, + controls: { + polygon: true, + line_string: true, + trash: true, + }, + userProperties: true, + styles: [...theme, { + id: 'gl-draw-title', + type: 'symbol', + filter: ['all'], + layout: { + 'text-field': '{user_name}', + 'text-font': ['Roboto Regular'], + 'text-size': 12, + }, + paint: { + 'text-halo-color': 'white', + 'text-halo-width': 1, + }, + }], +}); + +const MapGeofenceEdit = ({ selectedGeofenceId }) => { + const theme = useTheme(); + const dispatch = useDispatch(); + const navigate = useNavigate(); + + const geofences = useSelector((state) => state.geofences.items); + + const refreshGeofences = useCatchCallback(async () => { + const response = await fetch('/api/geofences'); + if (response.ok) { + dispatch(geofencesActions.refresh(await response.json())); + } else { + throw Error(await response.text()); + } + }, [dispatch]); + + useEffect(() => { + refreshGeofences(); + + map.addControl(draw, 'top-left'); + return () => map.removeControl(draw); + }, [refreshGeofences]); + + useEffect(() => { + const listener = async (event) => { + const feature = event.features[0]; + const newItem = { name: '', area: geometryToArea(feature.geometry) }; + draw.delete(feature.id); + try { + const response = await fetch('/api/geofences', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(newItem), + }); + if (response.ok) { + const item = await response.json(); + navigate(`/settings/geofence/${item.id}`); + } else { + throw Error(await response.text()); + } + } catch (error) { + dispatch(errorsActions.push(error.message)); + } + }; + + map.on('draw.create', listener); + return () => map.off('draw.create', listener); + }, [dispatch, navigate]); + + useEffect(() => { + const listener = async (event) => { + const feature = event.features[0]; + try { + const response = await fetch(`/api/geofences/${feature.id}`, { method: 'DELETE' }); + if (response.ok) { + refreshGeofences(); + } else { + throw Error(await response.text()); + } + } catch (error) { + dispatch(errorsActions.push(error.message)); + } + }; + + map.on('draw.delete', listener); + return () => map.off('draw.delete', listener); + }, [dispatch, refreshGeofences]); + + useEffect(() => { + const listener = async (event) => { + const feature = event.features[0]; + const item = Object.values(geofences).find((i) => i.id === feature.id); + if (item) { + const updatedItem = { ...item, area: geometryToArea(feature.geometry) }; + try { + const response = await fetch(`/api/geofences/${feature.id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(updatedItem), + }); + if (response.ok) { + refreshGeofences(); + } else { + throw Error(await response.text()); + } + } catch (error) { + dispatch(errorsActions.push(error.message)); + } + } + }; + + map.on('draw.update', listener); + return () => map.off('draw.update', listener); + }, [dispatch, geofences, refreshGeofences]); + + useEffect(() => { + draw.deleteAll(); + Object.values(geofences).forEach((geofence) => { + draw.add(geofenceToFeature(theme, geofence)); + }); + }, [geofences]); + + useEffect(() => { + if (selectedGeofenceId) { + const feature = draw.get(selectedGeofenceId); + let { coordinates } = feature.geometry; + if (Array.isArray(coordinates[0][0])) { + [coordinates] = coordinates; + } + const bounds = coordinates.reduce( + (bounds, coordinate) => bounds.extend(coordinate), + new maplibregl.LngLatBounds(coordinates[0], coordinates[1]), + ); + const canvas = map.getCanvas(); + map.fitBounds(bounds, { padding: Math.min(canvas.width, canvas.height) * 0.1 }); + } + }, [selectedGeofenceId]); + + return null; +}; + +export default MapGeofenceEdit; diff --git a/modern/src/map/draw/theme.js b/modern/src/map/draw/theme.js new file mode 100644 index 00000000..c9864e2f --- /dev/null +++ b/modern/src/map/draw/theme.js @@ -0,0 +1,230 @@ +// Copy of the original theme +// https://github.com/mapbox/mapbox-gl-draw/blob/v1.4.0/src/lib/theme.js + +export default [ + { + 'id': 'gl-draw-polygon-fill-inactive', + 'type': 'fill', + 'filter': ['all', + ['==', 'active', 'false'], + ['==', '$type', 'Polygon'], + ['!=', 'mode', 'static'] + ], + 'paint': { + 'fill-color': '#3bb2d0', + 'fill-outline-color': '#3bb2d0', + 'fill-opacity': 0.1 + } + }, + { + 'id': 'gl-draw-polygon-fill-active', + 'type': 'fill', + 'filter': ['all', ['==', 'active', 'true'], ['==', '$type', 'Polygon']], + 'paint': { + 'fill-color': '#fbb03b', + 'fill-outline-color': '#fbb03b', + 'fill-opacity': 0.1 + } + }, + { + 'id': 'gl-draw-polygon-midpoint', + 'type': 'circle', + 'filter': ['all', + ['==', '$type', 'Point'], + ['==', 'meta', 'midpoint']], + 'paint': { + 'circle-radius': 3, + 'circle-color': '#fbb03b' + } + }, + { + 'id': 'gl-draw-polygon-stroke-inactive', + 'type': 'line', + 'filter': ['all', + ['==', 'active', 'false'], + ['==', '$type', 'Polygon'], + ['!=', 'mode', 'static'] + ], + 'layout': { + 'line-cap': 'round', + 'line-join': 'round' + }, + 'paint': { + 'line-color': '#3bb2d0', + 'line-width': 2 + } + }, + { + 'id': 'gl-draw-polygon-stroke-active', + 'type': 'line', + 'filter': ['all', ['==', 'active', 'true'], ['==', '$type', 'Polygon']], + 'layout': { + 'line-cap': 'round', + 'line-join': 'round' + }, + 'paint': { + 'line-color': '#fbb03b', + 'line-dasharray': [0.2, 2], + 'line-width': 2 + } + }, + { + 'id': 'gl-draw-line-inactive', + 'type': 'line', + 'filter': ['all', + ['==', 'active', 'false'], + ['==', '$type', 'LineString'], + ['!=', 'mode', 'static'] + ], + 'layout': { + 'line-cap': 'round', + 'line-join': 'round' + }, + 'paint': { + 'line-color': '#3bb2d0', + 'line-width': 2 + } + }, + { + 'id': 'gl-draw-line-active', + 'type': 'line', + 'filter': ['all', + ['==', '$type', 'LineString'], + ['==', 'active', 'true'] + ], + 'layout': { + 'line-cap': 'round', + 'line-join': 'round' + }, + 'paint': { + 'line-color': '#fbb03b', + 'line-dasharray': [0.2, 2], + 'line-width': 2 + } + }, + { + 'id': 'gl-draw-polygon-and-line-vertex-stroke-inactive', + 'type': 'circle', + 'filter': ['all', + ['==', 'meta', 'vertex'], + ['==', '$type', 'Point'], + ['!=', 'mode', 'static'] + ], + 'paint': { + 'circle-radius': 5, + 'circle-color': '#fff' + } + }, + { + 'id': 'gl-draw-polygon-and-line-vertex-inactive', + 'type': 'circle', + 'filter': ['all', + ['==', 'meta', 'vertex'], + ['==', '$type', 'Point'], + ['!=', 'mode', 'static'] + ], + 'paint': { + 'circle-radius': 3, + 'circle-color': '#fbb03b' + } + }, + { + 'id': 'gl-draw-point-point-stroke-inactive', + 'type': 'circle', + 'filter': ['all', + ['==', 'active', 'false'], + ['==', '$type', 'Point'], + ['==', 'meta', 'feature'], + ['!=', 'mode', 'static'] + ], + 'paint': { + 'circle-radius': 5, + 'circle-opacity': 1, + 'circle-color': '#fff' + } + }, + { + 'id': 'gl-draw-point-inactive', + 'type': 'circle', + 'filter': ['all', + ['==', 'active', 'false'], + ['==', '$type', 'Point'], + ['==', 'meta', 'feature'], + ['!=', 'mode', 'static'] + ], + 'paint': { + 'circle-radius': 3, + 'circle-color': '#3bb2d0' + } + }, + { + 'id': 'gl-draw-point-stroke-active', + 'type': 'circle', + 'filter': ['all', + ['==', '$type', 'Point'], + ['==', 'active', 'true'], + ['!=', 'meta', 'midpoint'] + ], + 'paint': { + 'circle-radius': 7, + 'circle-color': '#fff' + } + }, + { + 'id': 'gl-draw-point-active', + 'type': 'circle', + 'filter': ['all', + ['==', '$type', 'Point'], + ['!=', 'meta', 'midpoint'], + ['==', 'active', 'true']], + 'paint': { + 'circle-radius': 5, + 'circle-color': '#fbb03b' + } + }, + { + 'id': 'gl-draw-polygon-fill-static', + 'type': 'fill', + 'filter': ['all', ['==', 'mode', 'static'], ['==', '$type', 'Polygon']], + 'paint': { + 'fill-color': '#404040', + 'fill-outline-color': '#404040', + 'fill-opacity': 0.1 + } + }, + { + 'id': 'gl-draw-polygon-stroke-static', + 'type': 'line', + 'filter': ['all', ['==', 'mode', 'static'], ['==', '$type', 'Polygon']], + 'layout': { + 'line-cap': 'round', + 'line-join': 'round' + }, + 'paint': { + 'line-color': '#404040', + 'line-width': 2 + } + }, + { + 'id': 'gl-draw-line-static', + 'type': 'line', + 'filter': ['all', ['==', 'mode', 'static'], ['==', '$type', 'LineString']], + 'layout': { + 'line-cap': 'round', + 'line-join': 'round' + }, + 'paint': { + 'line-color': '#404040', + 'line-width': 2 + } + }, + { + 'id': 'gl-draw-point-static', + 'type': 'circle', + 'filter': ['all', ['==', 'mode', 'static'], ['==', '$type', 'Point']], + 'paint': { + 'circle-radius': 5, + 'circle-color': '#404040' + } + } +]; |