aboutsummaryrefslogtreecommitdiff
path: root/src/map/draw/MapGeofenceEdit.js
diff options
context:
space:
mode:
authorAnton Tananaev <anton@traccar.org>2024-04-06 09:22:10 -0700
committerAnton Tananaev <anton@traccar.org>2024-04-06 09:22:10 -0700
commitf418231b6b2f5e030a0d2dcc390c314602b1f740 (patch)
tree10326adf3792bc2697e06bb5f2b8ef2a8f7e55fe /src/map/draw/MapGeofenceEdit.js
parentb392a4af78e01c8e0f50aad5468e9583675b24be (diff)
downloadtrackermap-web-f418231b6b2f5e030a0d2dcc390c314602b1f740.tar.gz
trackermap-web-f418231b6b2f5e030a0d2dcc390c314602b1f740.tar.bz2
trackermap-web-f418231b6b2f5e030a0d2dcc390c314602b1f740.zip
Move modern to the top
Diffstat (limited to 'src/map/draw/MapGeofenceEdit.js')
-rw-r--r--src/map/draw/MapGeofenceEdit.js161
1 files changed, 161 insertions, 0 deletions
diff --git a/src/map/draw/MapGeofenceEdit.js b/src/map/draw/MapGeofenceEdit.js
new file mode 100644
index 00000000..e547ea05
--- /dev/null
+++ b/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;