From 9d09efa4e57b6027a9033c2d2f69f616d8de973b Mon Sep 17 00:00:00 2001 From: Ivan Gabriele Date: Thu, 29 Aug 2024 11:48:33 +0200 Subject: [PATCH 01/12] Enable React strict mode --- frontend/src/index.tsx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 5c4d797772..5426cca363 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -1,4 +1,5 @@ import { browserTracingIntegration, init, replayIntegration } from '@sentry/react' +import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import { AuthProvider } from 'react-oidc-context' @@ -33,11 +34,17 @@ const { IS_OIDC_ENABLED, oidcConfig } = getOIDCConfig() if (IS_OIDC_ENABLED) { root.render( - // eslint-disable-next-line react/jsx-props-no-spreading - - - + + {/* eslint-disable-next-line react/jsx-props-no-spreading */} + + + + ) } else { - root.render() + root.render( + + + + ) } From 0c4ad0d3512f36cbf2e6efca7fd540334af10f3a Mon Sep 17 00:00:00 2001 From: Ivan Gabriele Date: Thu, 29 Aug 2024 12:21:46 +0200 Subject: [PATCH 02/12] Auto-lint InterestPointLayer --- .../layers/InterestPointLayer.jsx | 247 +++++++++--------- 1 file changed, 129 insertions(+), 118 deletions(-) diff --git a/frontend/src/features/InterestPoint/layers/InterestPointLayer.jsx b/frontend/src/features/InterestPoint/layers/InterestPointLayer.jsx index b4c1a7ce32..4296f7e5ed 100644 --- a/frontend/src/features/InterestPoint/layers/InterestPointLayer.jsx +++ b/frontend/src/features/InterestPoint/layers/InterestPointLayer.jsx @@ -1,13 +1,21 @@ import { usePrevious } from '@mtes-mct/monitor-ui' -import { useEffect, useRef, useState } from 'react' - -import { useDispatch, useSelector } from 'react-redux' -import VectorSource from 'ol/source/Vector' -import { MapBox, OPENLAYERS_PROJECTION } from '../../../domain/entities/map/constants' +import GeoJSON from 'ol/format/GeoJSON' +import LineString from 'ol/geom/LineString' import Draw from 'ol/interaction/Draw' import VectorLayer from 'ol/layer/Vector' -import { getInterestPointStyle, POIStyle } from './interestPoint.style' +import VectorSource from 'ol/source/Vector' +import { getLength } from 'ol/sphere' +import { useEffect, useRef, useState } from 'react' +import { useDispatch, useSelector } from 'react-redux' import { v4 as uuidv4 } from 'uuid' + +import { getInterestPointStyle, POIStyle } from './interestPoint.style' +import { InterestPointLine } from './interestPointLine' +import { LayerProperties } from '../../../domain/entities/layers/constants' +import { MapBox, OPENLAYERS_PROJECTION } from '../../../domain/entities/map/constants' +import { setRightMapBoxOpened } from '../../../domain/shared_slices/Global' +import saveInterestPointFeature from '../../../domain/use_cases/interestPoint/saveInterestPointFeature' +import { monitorfishMap } from '../../map/monitorfishMap' import { InterestPointOverlay } from '../components/InterestPointOverlay' import { deleteInterestPointBeingDrawed, @@ -18,19 +26,7 @@ import { updateInterestPointBeingDrawed, updateInterestPointKeyBeingDrawed } from '../slice' -import { - coordinatesAreModified, - coordinatesOrTypeAreModified, - InterestPointType -} from '../utils' -import saveInterestPointFeature from '../../../domain/use_cases/interestPoint/saveInterestPointFeature' -import GeoJSON from 'ol/format/GeoJSON' -import LineString from 'ol/geom/LineString' -import { InterestPointLine } from './interestPointLine' -import { getLength } from 'ol/sphere' -import { LayerProperties } from '../../../domain/entities/layers/constants' -import { setRightMapBoxOpened } from '../../../domain/shared_slices/Global' -import { monitorfishMap } from '../../map/monitorfishMap' +import { coordinatesAreModified, coordinatesOrTypeAreModified, InterestPointType } from '../utils' const DRAW_START_EVENT = 'drawstart' const DRAW_ABORT_EVENT = 'drawabort' @@ -38,44 +34,46 @@ const DRAW_END_EVENT = 'drawend' export const MIN_ZOOM = 7 -const InterestPointLayer = ({ mapMovingAndZoomEvent }) => { +function InterestPointLayer({ mapMovingAndZoomEvent }) { const dispatch = useDispatch() const { - isDrawing, - isEditing, - /** @type {InterestPoint | null} interestPointBeingDrawed */ interestPointBeingDrawed, - /** @type {InterestPoint[]} interestPoints */ interestPoints, + /** @type {InterestPoint | null} interestPointBeingDrawed */ + isDrawing, + /** @type {InterestPoint[]} interestPoints */ + isEditing, triggerInterestPointFeatureDeletion } = useSelector(state => state.interestPoint) const [drawObject, setDrawObject] = useState(null) const vectorSourceRef = useRef(null) - function getVectorSource () { + function getVectorSource() { if (vectorSourceRef.current === null) { vectorSourceRef.current = new VectorSource({ - wrapX: false, - projection: OPENLAYERS_PROJECTION + projection: OPENLAYERS_PROJECTION, + wrapX: false }) } + return vectorSourceRef.current } const layerRef = useRef(null) - function getLayer () { + function getLayer() { if (layerRef.current === null) { layerRef.current = new VectorLayer({ - source: getVectorSource(), renderBuffer: 7, + source: getVectorSource(), + style: (feature, resolution) => getInterestPointStyle(feature, resolution), updateWhileAnimating: true, updateWhileInteracting: true, - style: (feature, resolution) => getInterestPointStyle(feature, resolution), zIndex: LayerProperties.INTEREST_POINT.zIndex }) } + return layerRef.current } @@ -84,7 +82,7 @@ const InterestPointLayer = ({ mapMovingAndZoomEvent }) => { const previousInterestPointBeingDrawed = usePrevious(interestPointBeingDrawed) useEffect(() => { - function addLayerToMap () { + function addLayerToMap() { monitorfishMap.getLayers().push(getLayer()) return () => { @@ -96,22 +94,24 @@ const InterestPointLayer = ({ mapMovingAndZoomEvent }) => { }, []) useEffect(() => { - function drawExistingFeaturesOnMap () { + function drawExistingFeaturesOnMap() { if (interestPoints) { - const features = interestPoints.map(interestPoint => { - if (interestPoint.feature) { - const nextFeature = new GeoJSON({ - featureProjection: OPENLAYERS_PROJECTION - }).readFeature(interestPoint.feature) + const features = interestPoints + .map(interestPoint => { + if (interestPoint.feature) { + const nextFeature = new GeoJSON({ + featureProjection: OPENLAYERS_PROJECTION + }).readFeature(interestPoint.feature) - const { feature, ...interestPointWithoutFeature } = interestPoint - nextFeature.setProperties(interestPointWithoutFeature) + const { feature, ...interestPointWithoutFeature } = interestPoint + nextFeature.setProperties(interestPointWithoutFeature) - return nextFeature - } + return nextFeature + } - return null - }).filter(feature => feature) + return null + }) + .filter(feature => feature) getVectorSource().addFeatures(features) } @@ -122,21 +122,23 @@ const InterestPointLayer = ({ mapMovingAndZoomEvent }) => { useEffect(() => { if (isDrawing) { - function addEmptyNextInterestPoint () { - dispatch(updateInterestPointBeingDrawed({ - uuid: uuidv4(), - name: null, - type: InterestPointType.FISHING_VESSEL, - coordinates: null, - observations: null - })) + function addEmptyNextInterestPoint() { + dispatch( + updateInterestPointBeingDrawed({ + coordinates: null, + name: null, + observations: null, + type: InterestPointType.FISHING_VESSEL, + uuid: uuidv4() + }) + ) } - function drawNewFeatureOnMap () { + function drawNewFeatureOnMap() { const draw = new Draw({ source: getVectorSource(), - type: 'Point', - style: POIStyle + style: POIStyle, + type: 'Point' }) monitorfishMap.addInteraction(draw) @@ -149,11 +151,11 @@ const InterestPointLayer = ({ mapMovingAndZoomEvent }) => { }, [isDrawing]) useEffect(() => { - function removeInteraction () { + function removeInteraction() { if (!isDrawing && drawObject) { setDrawObject(null) - function waitForUnwantedZoomAndQuitInteraction () { + function waitForUnwantedZoomAndQuitInteraction() { setTimeout(() => { monitorfishMap.removeInteraction(drawObject) }, 300) @@ -167,17 +169,19 @@ const InterestPointLayer = ({ mapMovingAndZoomEvent }) => { }, [isDrawing]) useEffect(() => { - function handleDrawEvents () { + function handleDrawEvents() { if (drawObject) { drawObject.once(DRAW_START_EVENT, event => { - function startDrawing (event, type) { - dispatch(updateInterestPointBeingDrawed({ - uuid: interestPointBeingDrawed.uuid, - name: null, - type: type, - coordinates: event.feature.getGeometry().getLastCoordinate(), - observations: null - })) + function startDrawing(event, type) { + dispatch( + updateInterestPointBeingDrawed({ + coordinates: event.feature.getGeometry().getLastCoordinate(), + name: null, + observations: null, + type, + uuid: interestPointBeingDrawed.uuid + }) + ) } if (interestPointBeingDrawed) { @@ -202,7 +206,7 @@ const InterestPointLayer = ({ mapMovingAndZoomEvent }) => { }, [drawObject, interestPointBeingDrawed]) useEffect(() => { - function showOrHideInterestPointsOverlays () { + function showOrHideInterestPointsOverlays() { const currentZoom = monitorfishMap.getView().getZoom().toFixed(2) if (currentZoom !== previousMapZoom.current) { previousMapZoom.current = currentZoom @@ -229,7 +233,7 @@ const InterestPointLayer = ({ mapMovingAndZoomEvent }) => { }, [triggerInterestPointFeatureDeletion]) useEffect(() => { - function modifyFeatureWhenCoordinatesOrTypeModified () { + function modifyFeatureWhenCoordinatesOrTypeModified() { if (interestPointBeingDrawed?.coordinates?.length && interestPointBeingDrawed?.uuid) { const drawingFeatureToUpdate = getVectorSource().getFeatureById(interestPointBeingDrawed.uuid) @@ -238,13 +242,16 @@ const InterestPointLayer = ({ mapMovingAndZoomEvent }) => { drawingFeatureToUpdate.getGeometry().setCoordinates(interestPointWithoutFeature.coordinates) drawingFeatureToUpdate.setProperties(interestPointWithoutFeature) - const nextFeature = new GeoJSON() - .writeFeatureObject(drawingFeatureToUpdate, { featureProjection: OPENLAYERS_PROJECTION }) + const nextFeature = new GeoJSON().writeFeatureObject(drawingFeatureToUpdate, { + featureProjection: OPENLAYERS_PROJECTION + }) - dispatch(updateInterestPointKeyBeingDrawed({ - key: 'feature', - value: nextFeature - })) + dispatch( + updateInterestPointKeyBeingDrawed({ + key: 'feature', + value: nextFeature + }) + ) } } } @@ -253,9 +260,16 @@ const InterestPointLayer = ({ mapMovingAndZoomEvent }) => { }, [interestPointBeingDrawed?.coordinates, interestPointBeingDrawed?.type]) useEffect(() => { - function initLineWhenInterestPointCoordinatesModified () { - if (interestPointBeingDrawed && previousInterestPointBeingDrawed && coordinatesAreModified(interestPointBeingDrawed, previousInterestPointBeingDrawed)) { - const line = new LineString([interestPointBeingDrawed.coordinates, previousInterestPointBeingDrawed.coordinates]) + function initLineWhenInterestPointCoordinatesModified() { + if ( + interestPointBeingDrawed && + previousInterestPointBeingDrawed && + coordinatesAreModified(interestPointBeingDrawed, previousInterestPointBeingDrawed) + ) { + const line = new LineString([ + interestPointBeingDrawed.coordinates, + previousInterestPointBeingDrawed.coordinates + ]) const distance = getLength(line, { projection: OPENLAYERS_PROJECTION }) if (distance > 10) { @@ -264,7 +278,9 @@ const InterestPointLayer = ({ mapMovingAndZoomEvent }) => { interestPointToCoordinates.delete(featureId) const feature = getVectorSource().getFeatureById(featureId) if (feature) { - feature.setGeometry(new LineString([interestPointBeingDrawed.coordinates, interestPointBeingDrawed.coordinates])) + feature.setGeometry( + new LineString([interestPointBeingDrawed.coordinates, interestPointBeingDrawed.coordinates]) + ) } } } @@ -274,7 +290,7 @@ const InterestPointLayer = ({ mapMovingAndZoomEvent }) => { initLineWhenInterestPointCoordinatesModified() }, [interestPointBeingDrawed]) - function deleteInterestPoint (uuid) { + function deleteInterestPoint(uuid) { const feature = getVectorSource().getFeatureById(uuid) if (feature) { getVectorSource().removeFeature(feature) @@ -290,7 +306,7 @@ const InterestPointLayer = ({ mapMovingAndZoomEvent }) => { dispatch(removeInterestPoint(uuid)) } - function moveInterestPointLine (uuid, coordinates, nextCoordinates, offset) { + function moveInterestPointLine(uuid, coordinates, nextCoordinates, offset) { const featureId = InterestPointLine.getFeatureId(uuid) if (interestPointToCoordinates.has(featureId)) { @@ -299,14 +315,13 @@ const InterestPointLayer = ({ mapMovingAndZoomEvent }) => { if (existingLabelLineFeature) { if (interestPointFeature) { - existingLabelLineFeature.setGeometry(new LineString([interestPointFeature.getGeometry().getCoordinates(), nextCoordinates])) + existingLabelLineFeature.setGeometry( + new LineString([interestPointFeature.getGeometry().getCoordinates(), nextCoordinates]) + ) } } } else { - const interestPointLineFeature = InterestPointLine.getFeature( - coordinates, - nextCoordinates, - featureId) + const interestPointLineFeature = InterestPointLine.getFeature(coordinates, nextCoordinates, featureId) getVectorSource().addFeature(interestPointLineFeature) } @@ -316,12 +331,12 @@ const InterestPointLayer = ({ mapMovingAndZoomEvent }) => { setInterestPointToCoordinates(nextVesselToCoordinates) } - function modifyInterestPoint (uuid) { + function modifyInterestPoint(uuid) { dispatch(editInterestPoint(uuid)) dispatch(setRightMapBoxOpened(MapBox.INTEREST_POINT)) } - function deleteInterestPointBeingDrawedAndCloseTool () { + function deleteInterestPointBeingDrawedAndCloseTool() { dispatch(endInterestPointDraw()) dispatch(setRightMapBoxOpened(undefined)) dispatch(deleteInterestPointBeingDrawed()) @@ -330,41 +345,37 @@ const InterestPointLayer = ({ mapMovingAndZoomEvent }) => { return ( <>
- { - interestPoints && Array.isArray(interestPoints) - ? interestPoints.map(interestPoint => { - return - }) - : null - } - { - interestPointBeingDrawed && !isEditing - ? ( + {}} - zoomHasChanged={previousMapZoom.current} + modifyInterestPoint={modifyInterestPoint} moveLine={moveInterestPointLine} + name={interestPoint.name} + observations={interestPoint.observations} + uuid={interestPoint.uuid} + zoomHasChanged={previousMapZoom.current} /> - : null - } + ))} + {!!interestPointBeingDrawed && !isEditing && ( + {}} + moveLine={moveInterestPointLine} + name={interestPointBeingDrawed.name} + observations={interestPointBeingDrawed.observations} + uuid={interestPointBeingDrawed.uuid} + zoomHasChanged={previousMapZoom.current} + /> + )}
) From 45a2b6bb303950eb06143111137d672ebc99117f Mon Sep 17 00:00:00 2001 From: Ivan Gabriele Date: Thu, 29 Aug 2024 12:22:26 +0200 Subject: [PATCH 03/12] Migrate InterestPointLayer to TS --- .../layers/{InterestPointLayer.jsx => InterestPointLayer.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename frontend/src/features/InterestPoint/layers/{InterestPointLayer.jsx => InterestPointLayer.tsx} (100%) diff --git a/frontend/src/features/InterestPoint/layers/InterestPointLayer.jsx b/frontend/src/features/InterestPoint/layers/InterestPointLayer.tsx similarity index 100% rename from frontend/src/features/InterestPoint/layers/InterestPointLayer.jsx rename to frontend/src/features/InterestPoint/layers/InterestPointLayer.tsx From c798677e1ac9a2de26e80e5571b97cad596d96bc Mon Sep 17 00:00:00 2001 From: Ivan Gabriele Date: Thu, 29 Aug 2024 13:03:35 +0200 Subject: [PATCH 04/12] Type & lint InterestPointLayer following TS migration --- .../layers/InterestPointLayer.tsx | 447 +++++++++--------- frontend/src/features/map/Map.tsx | 6 +- frontend/src/features/map/types.ts | 5 + 3 files changed, 245 insertions(+), 213 deletions(-) diff --git a/frontend/src/features/InterestPoint/layers/InterestPointLayer.tsx b/frontend/src/features/InterestPoint/layers/InterestPointLayer.tsx index 4296f7e5ed..fbb2b5ff67 100644 --- a/frontend/src/features/InterestPoint/layers/InterestPointLayer.tsx +++ b/frontend/src/features/InterestPoint/layers/InterestPointLayer.tsx @@ -1,12 +1,14 @@ +import { useMainAppDispatch } from '@hooks/useMainAppDispatch' +import { useMainAppSelector } from '@hooks/useMainAppSelector' import { usePrevious } from '@mtes-mct/monitor-ui' +import { omit } from 'lodash' import GeoJSON from 'ol/format/GeoJSON' import LineString from 'ol/geom/LineString' import Draw from 'ol/interaction/Draw' import VectorLayer from 'ol/layer/Vector' import VectorSource from 'ol/source/Vector' import { getLength } from 'ol/sphere' -import { useEffect, useRef, useState } from 'react' -import { useDispatch, useSelector } from 'react-redux' +import { useCallback, useEffect, useRef, useState } from 'react' import { v4 as uuidv4 } from 'uuid' import { getInterestPointStyle, POIStyle } from './interestPoint.style' @@ -28,15 +30,25 @@ import { } from '../slice' import { coordinatesAreModified, coordinatesOrTypeAreModified, InterestPointType } from '../utils' +import type { DummyObjectToForceEffectHookUpdate } from '@features/map/types' +import type { Feature } from 'ol' +import type { Geometry } from 'ol/geom' + const DRAW_START_EVENT = 'drawstart' const DRAW_ABORT_EVENT = 'drawabort' const DRAW_END_EVENT = 'drawend' export const MIN_ZOOM = 7 -function InterestPointLayer({ mapMovingAndZoomEvent }) { - const dispatch = useDispatch() +type InterstPointLayerProps = Readonly<{ + mapMovingAndZoomEvent: DummyObjectToForceEffectHookUpdate +}> +export function InterestPointLayer({ mapMovingAndZoomEvent }: InterstPointLayerProps) { + const layerRef = useRef> | null>(null) + const previousMapZoom = useRef('') + const vectorSourceRef = useRef> | null>(null) + const dispatch = useMainAppDispatch() const { interestPointBeingDrawed, interestPoints, @@ -45,24 +57,26 @@ function InterestPointLayer({ mapMovingAndZoomEvent }) { /** @type {InterestPoint[]} interestPoints */ isEditing, triggerInterestPointFeatureDeletion - } = useSelector(state => state.interestPoint) + } = useMainAppSelector(state => state.interestPoint) - const [drawObject, setDrawObject] = useState(null) + const [drawObject, setDrawObject] = useState(null) + const [interestPointToCoordinates, setInterestPointToCoordinates] = useState(new Map()) + const previousInterestPointBeingDrawed = usePrevious(interestPointBeingDrawed) - const vectorSourceRef = useRef(null) - function getVectorSource() { + const getVectorSource = useCallback(() => { if (vectorSourceRef.current === null) { vectorSourceRef.current = new VectorSource({ + // TODO Fix that: `projection` propr does not exist in type `Options>`. + // @ts-ignore projection: OPENLAYERS_PROJECTION, wrapX: false }) } return vectorSourceRef.current - } + }, []) - const layerRef = useRef(null) - function getLayer() { + const getLayer = useCallback(() => { if (layerRef.current === null) { layerRef.current = new VectorLayer({ renderBuffer: 7, @@ -75,11 +89,38 @@ function InterestPointLayer({ mapMovingAndZoomEvent }) { } return layerRef.current - } - - const previousMapZoom = useRef('') - const [interestPointToCoordinates, setInterestPointToCoordinates] = useState(new Map()) - const previousInterestPointBeingDrawed = usePrevious(interestPointBeingDrawed) + }, [getVectorSource]) + + const addEmptyNextInterestPoint = useCallback(() => { + dispatch( + updateInterestPointBeingDrawed({ + coordinates: null, + name: null, + observations: null, + type: InterestPointType.FISHING_VESSEL, + uuid: uuidv4() + }) + ) + }, [dispatch]) + + const drawNewFeatureOnMap = useCallback(() => { + const draw = new Draw({ + source: getVectorSource(), + style: POIStyle, + type: 'Point' + }) + + monitorfishMap.addInteraction(draw) + setDrawObject(draw) + }, [getVectorSource]) + + const waitForUnwantedZoomAndQuitInteraction = useCallback(() => { + setTimeout(() => { + if (drawObject) { + monitorfishMap.removeInteraction(drawObject) + } + }, 300) + }, [drawObject]) useEffect(() => { function addLayerToMap() { @@ -91,7 +132,175 @@ function InterestPointLayer({ mapMovingAndZoomEvent }) { } addLayerToMap() - }, []) + }, [getLayer]) + + const startDrawing = useCallback( + (event, type) => { + dispatch( + updateInterestPointBeingDrawed({ + coordinates: event.feature.getGeometry().getLastCoordinate(), + name: null, + observations: null, + type, + uuid: interestPointBeingDrawed?.uuid + }) + ) + }, + [dispatch, interestPointBeingDrawed] + ) + + const handleDrawEvents = useCallback(() => { + if (drawObject) { + drawObject.once(DRAW_START_EVENT, event => { + if (interestPointBeingDrawed) { + startDrawing(event, interestPointBeingDrawed.type || InterestPointType.OTHER) + } + }) + + drawObject.once(DRAW_ABORT_EVENT, () => { + dispatch(endInterestPointDraw()) + dispatch(deleteInterestPointBeingDrawed()) + dispatch(setRightMapBoxOpened(undefined)) + }) + + drawObject.once(DRAW_END_EVENT, event => { + dispatch(saveInterestPointFeature(event.feature)) + dispatch(endInterestPointDraw()) + }) + } + }, [dispatch, drawObject, interestPointBeingDrawed, startDrawing]) + + const showOrHideInterestPointsOverlays = useCallback(() => { + const currentZoom = monitorfishMap.getView().getZoom()?.toFixed(2) + if (!!currentZoom && currentZoom !== previousMapZoom.current) { + previousMapZoom.current = currentZoom + if (Number(currentZoom) < MIN_ZOOM) { + getVectorSource().forEachFeature(feature => { + feature.set(InterestPointLine.isHiddenByZoomProperty, true) + }) + } else { + getVectorSource().forEachFeature(feature => { + feature.set(InterestPointLine.isHiddenByZoomProperty, false) + }) + } + } + }, [getVectorSource]) + + const deleteInterestPoint = useCallback( + (uuid: string) => { + const feature = getVectorSource().getFeatureById(uuid) + if (feature) { + getVectorSource().removeFeature(feature) + getVectorSource().changed() + } + + const featureLine = getVectorSource().getFeatureById(InterestPointLine.getFeatureId(uuid)) + if (featureLine) { + getVectorSource().removeFeature(featureLine) + getVectorSource().changed() + } + + dispatch(removeInterestPoint(uuid)) + }, + [dispatch, getVectorSource] + ) + + const moveInterestPointLine = useCallback( + (uuid: string, coordinates: string[], nextCoordinates: string[], offset: number) => { + const featureId = InterestPointLine.getFeatureId(uuid) + + if (interestPointToCoordinates.has(featureId)) { + const existingLabelLineFeature = getVectorSource().getFeatureById(featureId) + const interestPointFeature = getVectorSource().getFeatureById(uuid) + + if (existingLabelLineFeature) { + if (interestPointFeature) { + const geometry = existingLabelLineFeature.getGeometry() + // TODO Fix that. There may be a real bug here: `Property 'getCoordinates' does not exist on type 'Geometry'.`. Legacy code. + // @ts-ignore + const previousCoordinates = geometry?.getCoordinates() + + existingLabelLineFeature.setGeometry(new LineString([previousCoordinates, nextCoordinates])) + } + } + } else { + const interestPointLineFeature = InterestPointLine.getFeature(coordinates, nextCoordinates, featureId) + + getVectorSource().addFeature(interestPointLineFeature) + } + + const nextVesselToCoordinates = interestPointToCoordinates + interestPointToCoordinates.set(featureId, { coordinates: nextCoordinates, offset }) + setInterestPointToCoordinates(nextVesselToCoordinates) + }, + [getVectorSource, interestPointToCoordinates] + ) + + const modifyInterestPoint = useCallback( + (uuid: string) => { + dispatch(editInterestPoint(uuid)) + dispatch(setRightMapBoxOpened(MapBox.INTEREST_POINT)) + }, + [dispatch] + ) + + const deleteInterestPointBeingDrawedAndCloseTool = useCallback(() => { + dispatch(endInterestPointDraw()) + dispatch(setRightMapBoxOpened(undefined)) + dispatch(deleteInterestPointBeingDrawed()) + }, [dispatch]) + + const modifyFeatureWhenCoordinatesOrTypeModified = useCallback(() => { + if (interestPointBeingDrawed?.coordinates?.length && interestPointBeingDrawed?.uuid) { + const drawingFeatureToUpdate = getVectorSource().getFeatureById(interestPointBeingDrawed.uuid) + + if (drawingFeatureToUpdate && coordinatesOrTypeAreModified(drawingFeatureToUpdate, interestPointBeingDrawed)) { + const interestPointWithoutFeature = omit(interestPointBeingDrawed, 'feature') + const drawingFeatureToUpdateGeometry = drawingFeatureToUpdate.getGeometry() + if (drawingFeatureToUpdateGeometry) { + // TODO Fix that. There may be a real bug here: `Property 'setCoordinates' does not exist on type 'Geometry'.`. Legacy code. + // @ts-ignore + drawingFeatureToUpdateGeometry.setCoordinates(interestPointBeingDrawed.coordinates) + } + drawingFeatureToUpdate.setProperties(interestPointWithoutFeature) + + const nextFeature = new GeoJSON().writeFeatureObject(drawingFeatureToUpdate, { + featureProjection: OPENLAYERS_PROJECTION + }) + + dispatch( + updateInterestPointKeyBeingDrawed({ + key: 'feature', + value: nextFeature + }) + ) + } + } + }, [dispatch, getVectorSource, interestPointBeingDrawed]) + + const initLineWhenInterestPointCoordinatesModified = useCallback(() => { + if ( + interestPointBeingDrawed && + previousInterestPointBeingDrawed && + coordinatesAreModified(interestPointBeingDrawed, previousInterestPointBeingDrawed) + ) { + const line = new LineString([interestPointBeingDrawed.coordinates, previousInterestPointBeingDrawed.coordinates]) + const distance = getLength(line, { projection: OPENLAYERS_PROJECTION }) + + if (distance > 10) { + const featureId = InterestPointLine.getFeatureId(interestPointBeingDrawed.uuid) + if (interestPointToCoordinates.has(featureId)) { + interestPointToCoordinates.delete(featureId) + const feature = getVectorSource().getFeatureById(featureId) + if (feature) { + feature.setGeometry( + new LineString([interestPointBeingDrawed.coordinates, interestPointBeingDrawed.coordinates]) + ) + } + } + } + } + }, [getVectorSource, interestPointBeingDrawed, interestPointToCoordinates, previousInterestPointBeingDrawed]) useEffect(() => { function drawExistingFeaturesOnMap() { @@ -103,7 +312,7 @@ function InterestPointLayer({ mapMovingAndZoomEvent }) { featureProjection: OPENLAYERS_PROJECTION }).readFeature(interestPoint.feature) - const { feature, ...interestPointWithoutFeature } = interestPoint + const interestPointWithoutFeature = omit(interestPoint, 'feature') nextFeature.setProperties(interestPointWithoutFeature) return nextFeature @@ -111,236 +320,56 @@ function InterestPointLayer({ mapMovingAndZoomEvent }) { return null }) - .filter(feature => feature) + .filter(feature => !!feature) getVectorSource().addFeatures(features) } } drawExistingFeaturesOnMap() - }, [interestPoints]) + }, [getVectorSource, interestPoints]) useEffect(() => { if (isDrawing) { - function addEmptyNextInterestPoint() { - dispatch( - updateInterestPointBeingDrawed({ - coordinates: null, - name: null, - observations: null, - type: InterestPointType.FISHING_VESSEL, - uuid: uuidv4() - }) - ) - } - - function drawNewFeatureOnMap() { - const draw = new Draw({ - source: getVectorSource(), - style: POIStyle, - type: 'Point' - }) - - monitorfishMap.addInteraction(draw) - setDrawObject(draw) - } - addEmptyNextInterestPoint() drawNewFeatureOnMap() } - }, [isDrawing]) + }, [addEmptyNextInterestPoint, drawNewFeatureOnMap, isDrawing]) useEffect(() => { function removeInteraction() { if (!isDrawing && drawObject) { setDrawObject(null) - function waitForUnwantedZoomAndQuitInteraction() { - setTimeout(() => { - monitorfishMap.removeInteraction(drawObject) - }, 300) - } - waitForUnwantedZoomAndQuitInteraction() } } removeInteraction() - }, [isDrawing]) + }, [drawObject, isDrawing, waitForUnwantedZoomAndQuitInteraction]) useEffect(() => { - function handleDrawEvents() { - if (drawObject) { - drawObject.once(DRAW_START_EVENT, event => { - function startDrawing(event, type) { - dispatch( - updateInterestPointBeingDrawed({ - coordinates: event.feature.getGeometry().getLastCoordinate(), - name: null, - observations: null, - type, - uuid: interestPointBeingDrawed.uuid - }) - ) - } - - if (interestPointBeingDrawed) { - startDrawing(event, interestPointBeingDrawed.type || InterestPointType.OTHER) - } - }) - - drawObject.once(DRAW_ABORT_EVENT, () => { - dispatch(endInterestPointDraw()) - dispatch(deleteInterestPointBeingDrawed()) - dispatch(setRightMapBoxOpened(undefined)) - }) - - drawObject.once(DRAW_END_EVENT, event => { - dispatch(saveInterestPointFeature(event.feature)) - dispatch(endInterestPointDraw()) - }) - } - } - handleDrawEvents() - }, [drawObject, interestPointBeingDrawed]) + }, [handleDrawEvents]) useEffect(() => { - function showOrHideInterestPointsOverlays() { - const currentZoom = monitorfishMap.getView().getZoom().toFixed(2) - if (currentZoom !== previousMapZoom.current) { - previousMapZoom.current = currentZoom - if (currentZoom < MIN_ZOOM) { - getVectorSource().forEachFeature(feature => { - feature.set(InterestPointLine.isHiddenByZoomProperty, true) - }) - } else { - getVectorSource().forEachFeature(feature => { - feature.set(InterestPointLine.isHiddenByZoomProperty, false) - }) - } - } - } - showOrHideInterestPointsOverlays() - }, [mapMovingAndZoomEvent]) + }, [mapMovingAndZoomEvent, showOrHideInterestPointsOverlays]) useEffect(() => { if (triggerInterestPointFeatureDeletion) { deleteInterestPoint(triggerInterestPointFeatureDeletion) resetInterestPointFeatureDeletion() } - }, [triggerInterestPointFeatureDeletion]) + }, [deleteInterestPoint, triggerInterestPointFeatureDeletion]) useEffect(() => { - function modifyFeatureWhenCoordinatesOrTypeModified() { - if (interestPointBeingDrawed?.coordinates?.length && interestPointBeingDrawed?.uuid) { - const drawingFeatureToUpdate = getVectorSource().getFeatureById(interestPointBeingDrawed.uuid) - - if (drawingFeatureToUpdate && coordinatesOrTypeAreModified(drawingFeatureToUpdate, interestPointBeingDrawed)) { - const { feature, ...interestPointWithoutFeature } = interestPointBeingDrawed - drawingFeatureToUpdate.getGeometry().setCoordinates(interestPointWithoutFeature.coordinates) - drawingFeatureToUpdate.setProperties(interestPointWithoutFeature) - - const nextFeature = new GeoJSON().writeFeatureObject(drawingFeatureToUpdate, { - featureProjection: OPENLAYERS_PROJECTION - }) - - dispatch( - updateInterestPointKeyBeingDrawed({ - key: 'feature', - value: nextFeature - }) - ) - } - } - } - modifyFeatureWhenCoordinatesOrTypeModified() - }, [interestPointBeingDrawed?.coordinates, interestPointBeingDrawed?.type]) + }, [modifyFeatureWhenCoordinatesOrTypeModified]) useEffect(() => { - function initLineWhenInterestPointCoordinatesModified() { - if ( - interestPointBeingDrawed && - previousInterestPointBeingDrawed && - coordinatesAreModified(interestPointBeingDrawed, previousInterestPointBeingDrawed) - ) { - const line = new LineString([ - interestPointBeingDrawed.coordinates, - previousInterestPointBeingDrawed.coordinates - ]) - const distance = getLength(line, { projection: OPENLAYERS_PROJECTION }) - - if (distance > 10) { - const featureId = InterestPointLine.getFeatureId(interestPointBeingDrawed.uuid) - if (interestPointToCoordinates.has(featureId)) { - interestPointToCoordinates.delete(featureId) - const feature = getVectorSource().getFeatureById(featureId) - if (feature) { - feature.setGeometry( - new LineString([interestPointBeingDrawed.coordinates, interestPointBeingDrawed.coordinates]) - ) - } - } - } - } - } - initLineWhenInterestPointCoordinatesModified() - }, [interestPointBeingDrawed]) - - function deleteInterestPoint(uuid) { - const feature = getVectorSource().getFeatureById(uuid) - if (feature) { - getVectorSource().removeFeature(feature) - getVectorSource().changed() - } - - const featureLine = getVectorSource().getFeatureById(InterestPointLine.getFeatureId(uuid)) - if (featureLine) { - getVectorSource().removeFeature(featureLine) - getVectorSource().changed() - } - - dispatch(removeInterestPoint(uuid)) - } - - function moveInterestPointLine(uuid, coordinates, nextCoordinates, offset) { - const featureId = InterestPointLine.getFeatureId(uuid) - - if (interestPointToCoordinates.has(featureId)) { - const existingLabelLineFeature = getVectorSource().getFeatureById(featureId) - const interestPointFeature = getVectorSource().getFeatureById(uuid) - - if (existingLabelLineFeature) { - if (interestPointFeature) { - existingLabelLineFeature.setGeometry( - new LineString([interestPointFeature.getGeometry().getCoordinates(), nextCoordinates]) - ) - } - } - } else { - const interestPointLineFeature = InterestPointLine.getFeature(coordinates, nextCoordinates, featureId) - - getVectorSource().addFeature(interestPointLineFeature) - } - - const nextVesselToCoordinates = interestPointToCoordinates - interestPointToCoordinates.set(featureId, { coordinates: nextCoordinates, offset }) - setInterestPointToCoordinates(nextVesselToCoordinates) - } - - function modifyInterestPoint(uuid) { - dispatch(editInterestPoint(uuid)) - dispatch(setRightMapBoxOpened(MapBox.INTEREST_POINT)) - } - - function deleteInterestPointBeingDrawedAndCloseTool() { - dispatch(endInterestPointDraw()) - dispatch(setRightMapBoxOpened(undefined)) - dispatch(deleteInterestPointBeingDrawed()) - } + }, [initLineWhenInterestPointCoordinatesModified]) return ( <> @@ -353,7 +382,6 @@ function InterestPointLayer({ mapMovingAndZoomEvent }) { coordinates={interestPoint.coordinates} deleteInterestPoint={deleteInterestPoint} featureIsShowed - map={monitorfishMap} modifyInterestPoint={modifyInterestPoint} moveLine={moveInterestPointLine} name={interestPoint.name} @@ -367,7 +395,6 @@ function InterestPointLayer({ mapMovingAndZoomEvent }) { coordinates={interestPointBeingDrawed.coordinates} deleteInterestPoint={deleteInterestPointBeingDrawedAndCloseTool} featureIsShowed={drawObject} - map={monitorfishMap} modifyInterestPoint={() => {}} moveLine={moveInterestPointLine} name={interestPointBeingDrawed.name} @@ -380,5 +407,3 @@ function InterestPointLayer({ mapMovingAndZoomEvent }) { ) } - -export default InterestPointLayer diff --git a/frontend/src/features/map/Map.tsx b/frontend/src/features/map/Map.tsx index 29450aeab8..66c75d9cf8 100644 --- a/frontend/src/features/map/Map.tsx +++ b/frontend/src/features/map/Map.tsx @@ -29,7 +29,7 @@ import { FeatureWithCodeAndEntityId } from '../../libs/FeatureWithCodeAndEntityI import { AdministrativeLayers } from '../AdministrativeZone/layers/AdministrativeLayers' import { BaseLayer } from '../BaseMap/layers/BaseLayer' import { DrawLayer } from '../Draw/layer' -import InterestPointLayer from '../InterestPoint/layers/InterestPointLayer' +import { InterestPointLayer } from '../InterestPoint/layers/InterestPointLayer' import MeasurementLayer from '../Measurement/layers/MeasurementLayer' import { MissionOverlay } from '../Mission/components/MissionOverlay' import { SelectedMissionOverlay } from '../Mission/components/SelectedMissionOverlay' @@ -45,6 +45,8 @@ import { HoveredStationOverlay } from '../Station/components/HoveredStationOverl import { SelectedStationOverlay } from '../Station/components/SelectedStationOverlay' import { StationLayer } from '../Station/components/StationLayer' +import type { DummyObjectToForceEffectHookUpdate } from './types' + export function Map() { const isSuperUser = useIsSuperUser() const { areVesselsDisplayed, isMissionsLayerDisplayed, isStationLayerDisplayed } = useMainAppSelector( @@ -53,7 +55,7 @@ export function Map() { const [shouldUpdateView, setShouldUpdateView] = useState(true) const [historyMoveTrigger, setHistoryMoveTrigger] = useState({}) const [hoveredFeature, setHoveredFeature] = useState(undefined) - const [mapMovingAndZoomEvent, setMapMovingAndZoomEvent] = useState({}) + const [mapMovingAndZoomEvent, setMapMovingAndZoomEvent] = useState({}) const [handlePointerMoveEventPixel, setHandlePointerMoveEventPixel] = useState(null) const hoveredFeatureWithCodeAndEntityId = diff --git a/frontend/src/features/map/types.ts b/frontend/src/features/map/types.ts index 0e6a2e25fe..57ad1b03bb 100644 --- a/frontend/src/features/map/types.ts +++ b/frontend/src/features/map/types.ts @@ -9,3 +9,8 @@ export interface VectorSourceForFeatureWithCodeAndEntityId>): void forEachFeature(callback: (feature: FeatureWithCodeAndEntityId) => void): T | undefined } + +// TODO Replace this hack by a cleaner solution. Created for legacy compatibility. +export type DummyObjectToForceEffectHookUpdate = { + dummyUpdate?: true +} From 220c6a8e59d4231277fa9669824abec9fdeb74aa Mon Sep 17 00:00:00 2001 From: Ivan Gabriele Date: Thu, 29 Aug 2024 13:10:24 +0200 Subject: [PATCH 05/12] Migrate InterestPointLine to TS --- .../InterestPoint/layers/InterestPointLayer.tsx | 2 +- .../{interestPointLine.js => InterestPointLine.ts} | 12 ++++++++---- .../InterestPoint/layers/interestPoint.style.tsx | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) rename frontend/src/features/InterestPoint/layers/{interestPointLine.js => InterestPointLine.ts} (55%) diff --git a/frontend/src/features/InterestPoint/layers/InterestPointLayer.tsx b/frontend/src/features/InterestPoint/layers/InterestPointLayer.tsx index fbb2b5ff67..fdb99e052e 100644 --- a/frontend/src/features/InterestPoint/layers/InterestPointLayer.tsx +++ b/frontend/src/features/InterestPoint/layers/InterestPointLayer.tsx @@ -12,7 +12,7 @@ import { useCallback, useEffect, useRef, useState } from 'react' import { v4 as uuidv4 } from 'uuid' import { getInterestPointStyle, POIStyle } from './interestPoint.style' -import { InterestPointLine } from './interestPointLine' +import { InterestPointLine } from './InterestPointLine' import { LayerProperties } from '../../../domain/entities/layers/constants' import { MapBox, OPENLAYERS_PROJECTION } from '../../../domain/entities/map/constants' import { setRightMapBoxOpened } from '../../../domain/shared_slices/Global' diff --git a/frontend/src/features/InterestPoint/layers/interestPointLine.js b/frontend/src/features/InterestPoint/layers/InterestPointLine.ts similarity index 55% rename from frontend/src/features/InterestPoint/layers/interestPointLine.js rename to frontend/src/features/InterestPoint/layers/InterestPointLine.ts index aa6374fa32..bac6ad30ed 100644 --- a/frontend/src/features/InterestPoint/layers/interestPointLine.js +++ b/frontend/src/features/InterestPoint/layers/InterestPointLine.ts @@ -4,14 +4,18 @@ import LineString from 'ol/geom/LineString' export class InterestPointLine { static typeProperty = 'type' static isHiddenByZoomProperty = 'isHiddenByZoom' + /** * InterestPointLine object for building OpenLayers interest point line to draggable overlay - * @param {string[]} fromCoordinates - The [longitude, latitude] of the start of the line (the interest point position) - * @param {string[]} toCoordinates - The [longitude, latitude] of the overlay position - * @param {string} featureId - The feature identifier + * + * @param fromCoordinates - The [longitude, latitude] of the start of the line (the interest point position) + * @param toCoordinates - The [longitude, latitude] of the overlay position + * @param featureId - The feature identifier */ - static getFeature(fromCoordinates, toCoordinates, featureId) { + static getFeature(fromCoordinates: string[], toCoordinates: string[], featureId: string) { const interestPointLineFeature = new Feature({ + // TODO Fix that: `ype 'string[]' is not assignable to type 'number | Coordinate'.`. Legacy code. + // @ts-ignore geometry: new LineString([fromCoordinates, toCoordinates]) }) diff --git a/frontend/src/features/InterestPoint/layers/interestPoint.style.tsx b/frontend/src/features/InterestPoint/layers/interestPoint.style.tsx index f9b452852c..9f3a3ffc29 100644 --- a/frontend/src/features/InterestPoint/layers/interestPoint.style.tsx +++ b/frontend/src/features/InterestPoint/layers/interestPoint.style.tsx @@ -4,7 +4,7 @@ import CircleStyle from 'ol/style/Circle' import Fill from 'ol/style/Fill' import Stroke from 'ol/style/Stroke' -import { InterestPointLine } from './interestPointLine' +import { InterestPointLine } from './InterestPointLine' import { INTEREST_POINT_STYLE, InterestPointType } from '../utils' const interestPointStylesCache = new Map() From 87a5cd50add41ee1ab485daaedd3b613e4a8ea9b Mon Sep 17 00:00:00 2001 From: Ivan Gabriele Date: Wed, 11 Sep 2024 08:05:15 +0200 Subject: [PATCH 06/12] Prefix a few styled components props in side window --- .../PriorNotificationCard/index.tsx | 6 ++-- .../components/ReportingList/index.tsx | 2 +- .../EditReporting.tsx | 31 +++++++++---------- .../Alert/AlertListAndReportingList/index.tsx | 8 ++--- .../src/ui/card-table/FilterTableInput.tsx | 4 +-- 5 files changed, 24 insertions(+), 27 deletions(-) diff --git a/frontend/src/features/PriorNotification/components/PriorNotificationCard/index.tsx b/frontend/src/features/PriorNotification/components/PriorNotificationCard/index.tsx index 31fefa3504..0a6a7755c7 100644 --- a/frontend/src/features/PriorNotification/components/PriorNotificationCard/index.tsx +++ b/frontend/src/features/PriorNotification/components/PriorNotificationCard/index.tsx @@ -101,7 +101,7 @@ export function PriorNotificationCard({ /> {isPendingVerification && Le préavis doit être vérifié par le CNSP avant sa diffusion.} - + Le navire doit respecter un délai d’envoi{hasDesignatedPorts && ' et débarquer dans un port désigné'}. @@ -155,9 +155,9 @@ const Body = styled.div` ` const Intro = styled.p<{ - hasNoTopMargin?: boolean + $hasNoTopMargin?: boolean }>` - ${p => p.hasNoTopMargin && 'margin-top: 2px;'} + ${p => p.$hasNoTopMargin && 'margin-top: 2px;'} color: ${p => p.theme.color.slateGray}; font-style: italic; ` diff --git a/frontend/src/features/Reporting/components/ReportingList/index.tsx b/frontend/src/features/Reporting/components/ReportingList/index.tsx index 6da6123705..e79f4c3bff 100644 --- a/frontend/src/features/Reporting/components/ReportingList/index.tsx +++ b/frontend/src/features/Reporting/components/ReportingList/index.tsx @@ -154,7 +154,7 @@ MMSI: ${reporting.mmsi || ''}` + ) @@ -34,16 +33,16 @@ export function EditReporting() { return (
- + ÉDITER LE SIGNALEMENT dispatch(setEditedReportingInSideWindow())} /> - + {editedReportingInSideWindow && editedReportingInSideWindow.flagState && ( )} @@ -73,11 +72,11 @@ export function EditReporting() { } const EditReportingWrapper = styled.div<{ - isEditedInSideWindow: boolean + $isEditedInSideWindow: boolean }>` - background: ${THEME.color.white}; + background: ${p => p.theme.color.white}; height: 100vh; - margin-right: ${p => (p.isEditedInSideWindow ? 0 : -490)}px; + margin-right: ${p => (p.$isEditedInSideWindow ? 0 : -490)}px; position: fixed; right: 0px; top: 0px; @@ -91,13 +90,11 @@ const StyledReportingForm = styled(ReportingForm)` ` const Line = styled.div` - border-bottom: 1px solid ${THEME.color.lightGray}; + border-bottom: 1px solid ${p => p.theme.color.lightGray}; width: 100%; ` -const Flag = styled.img<{ - rel?: 'preload' -}>` +const Flag = styled.img` cursor: pointer; display: inline-block; height: 14; @@ -106,7 +103,7 @@ const Flag = styled.img<{ ` const VesselName = styled.div` - color: ${THEME.color.gunMetal}; + color: ${p => p.theme.color.gunMetal}; font: normal normal bold 16px/22px Marianne; margin-left: 8px; overflow: hidden; @@ -115,7 +112,7 @@ const VesselName = styled.div` ` const InternalReferenceNumber = styled.div` - color: ${THEME.color.gunMetal}; + color: ${p => p.theme.color.gunMetal}; font: normal normal normal 16px/22px Marianne; margin-left: 5; ` @@ -130,10 +127,10 @@ const CloseIcon = styled(CloseIconSVG)` ` const Row = styled.div<{ - topMargin: number + $topMargin: number }>` display: flex; - margin-top: ${p => p.topMargin}; + margin-top: ${p => p.$topMargin}; ` const Header = styled.div` @@ -141,7 +138,7 @@ const Header = styled.div` ` const Title = styled.span` - color: ${THEME.color.slateGray}; + color: ${p => p.theme.color.slateGray}; font: normal normal bold 16px Marianne; letter-spacing: 0px; margin-left: 10px; diff --git a/frontend/src/features/SideWindow/Alert/AlertListAndReportingList/index.tsx b/frontend/src/features/SideWindow/Alert/AlertListAndReportingList/index.tsx index b9d3f51286..155f1f4d81 100644 --- a/frontend/src/features/SideWindow/Alert/AlertListAndReportingList/index.tsx +++ b/frontend/src/features/SideWindow/Alert/AlertListAndReportingList/index.tsx @@ -39,14 +39,14 @@ export function AlertListAndReportingList({ return ( setSelectedTab(AlertAndReportingTab.ALERT)} > Alertes setSelectedTab(AlertAndReportingTab.REPORTING)} > Signalements @@ -75,9 +75,9 @@ const Wrapper = styled.div` // TODO This should be a `<a />` or a `<button />`. const Title = styled.h2<{ - isSelected: boolean + $isSelected: boolean }>` - border-bottom: 5px solid ${p => (p.isSelected ? p.theme.color.charcoal : p.theme.color.white)}; + border-bottom: 5px solid ${p => (p.$isSelected ? p.theme.color.charcoal : p.theme.color.white)}; color: ${p => p.theme.color.gunMetal}; cursor: pointer; display: inline-block; diff --git a/frontend/src/ui/card-table/FilterTableInput.tsx b/frontend/src/ui/card-table/FilterTableInput.tsx index 808d48881b..876e4bc3ac 100644 --- a/frontend/src/ui/card-table/FilterTableInput.tsx +++ b/frontend/src/ui/card-table/FilterTableInput.tsx @@ -4,7 +4,7 @@ import { COLORS } from '../../constants/constants' import SearchIconSVG from '../../features/icons/Loupe_dark.svg?react' export const FilterTableInput = styled.input<{ - baseUrl: string + $baseUrl: string }>` background-color: white; border: 1px ${COLORS.lightGray} solid; @@ -14,7 +14,7 @@ export const FilterTableInput = styled.input<{ height: 40px; width: 280px; padding: 0 5px 0 10px; - background-image: url(${p => `${p.baseUrl}${SearchIconSVG}`}); + background-image: url(${p => `${p.$baseUrl}${SearchIconSVG}`}); background-size: 25px; background-position: bottom 3px right 5px; background-repeat: no-repeat; From 4e1255df0543ce3c99e2cbf0d5b33ee72e2b13ed Mon Sep 17 00:00:00 2001 From: Ivan Gabriele <ivan.gabriele@protonmail.com> Date: Wed, 11 Sep 2024 08:29:52 +0200 Subject: [PATCH 07/12] Prevent duplicate map layer collection push in InterestPointLayer --- .../layers/InterestPointLayer.tsx | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/frontend/src/features/InterestPoint/layers/InterestPointLayer.tsx b/frontend/src/features/InterestPoint/layers/InterestPointLayer.tsx index fdb99e052e..4e237078ca 100644 --- a/frontend/src/features/InterestPoint/layers/InterestPointLayer.tsx +++ b/frontend/src/features/InterestPoint/layers/InterestPointLayer.tsx @@ -122,18 +122,6 @@ export function InterestPointLayer({ mapMovingAndZoomEvent }: InterstPointLayerP }, 300) }, [drawObject]) - useEffect(() => { - function addLayerToMap() { - monitorfishMap.getLayers().push(getLayer()) - - return () => { - monitorfishMap.removeLayer(getLayer()) - } - } - - addLayerToMap() - }, [getLayer]) - const startDrawing = useCallback( (event, type) => { dispatch( @@ -302,6 +290,16 @@ export function InterestPointLayer({ mapMovingAndZoomEvent }: InterstPointLayerP } }, [getVectorSource, interestPointBeingDrawed, interestPointToCoordinates, previousInterestPointBeingDrawed]) + useEffect(() => { + if (!layerRef.current) { + monitorfishMap.getLayers().push(getLayer()) + } + + return () => { + monitorfishMap.removeLayer(getLayer()) + } + }, [getLayer]) + useEffect(() => { function drawExistingFeaturesOnMap() { if (interestPoints) { From 275382827b780bebd9e2776b3d0bacc49b80ebd2 Mon Sep 17 00:00:00 2001 From: Ivan Gabriele <ivan.gabriele@protonmail.com> Date: Wed, 11 Sep 2024 08:38:29 +0200 Subject: [PATCH 08/12] Simplify vector source & layer init in InterestPointLayer --- .../layers/InterestPointLayer.tsx | 102 ++++++++---------- 1 file changed, 45 insertions(+), 57 deletions(-) diff --git a/frontend/src/features/InterestPoint/layers/InterestPointLayer.tsx b/frontend/src/features/InterestPoint/layers/InterestPointLayer.tsx index 4e237078ca..112b110dd2 100644 --- a/frontend/src/features/InterestPoint/layers/InterestPointLayer.tsx +++ b/frontend/src/features/InterestPoint/layers/InterestPointLayer.tsx @@ -44,9 +44,25 @@ type InterstPointLayerProps = Readonly<{ mapMovingAndZoomEvent: DummyObjectToForceEffectHookUpdate }> export function InterestPointLayer({ mapMovingAndZoomEvent }: InterstPointLayerProps) { - const layerRef = useRef<VectorLayer<Feature<Geometry>> | null>(null) const previousMapZoom = useRef('') - const vectorSourceRef = useRef<VectorSource<Feature<Geometry>> | null>(null) + const vectorSourceRef = useRef<VectorSource<Feature<Geometry>>>( + new VectorSource({ + // TODO Fix that: `projection` propr does not exist in type `Options<Feature<Geometry>>`. + // @ts-ignore + projection: OPENLAYERS_PROJECTION, + wrapX: false + }) + ) + const layerRef = useRef<VectorLayer<Feature<Geometry>>>( + new VectorLayer({ + renderBuffer: 7, + source: vectorSourceRef.current, + style: (feature, resolution) => getInterestPointStyle(feature, resolution), + updateWhileAnimating: true, + updateWhileInteracting: true, + zIndex: LayerProperties.INTEREST_POINT.zIndex + }) + ) const dispatch = useMainAppDispatch() const { @@ -63,34 +79,6 @@ export function InterestPointLayer({ mapMovingAndZoomEvent }: InterstPointLayerP const [interestPointToCoordinates, setInterestPointToCoordinates] = useState(new Map()) const previousInterestPointBeingDrawed = usePrevious(interestPointBeingDrawed) - const getVectorSource = useCallback(() => { - if (vectorSourceRef.current === null) { - vectorSourceRef.current = new VectorSource({ - // TODO Fix that: `projection` propr does not exist in type `Options<Feature<Geometry>>`. - // @ts-ignore - projection: OPENLAYERS_PROJECTION, - wrapX: false - }) - } - - return vectorSourceRef.current - }, []) - - const getLayer = useCallback(() => { - if (layerRef.current === null) { - layerRef.current = new VectorLayer({ - renderBuffer: 7, - source: getVectorSource(), - style: (feature, resolution) => getInterestPointStyle(feature, resolution), - updateWhileAnimating: true, - updateWhileInteracting: true, - zIndex: LayerProperties.INTEREST_POINT.zIndex - }) - } - - return layerRef.current - }, [getVectorSource]) - const addEmptyNextInterestPoint = useCallback(() => { dispatch( updateInterestPointBeingDrawed({ @@ -105,14 +93,14 @@ export function InterestPointLayer({ mapMovingAndZoomEvent }: InterstPointLayerP const drawNewFeatureOnMap = useCallback(() => { const draw = new Draw({ - source: getVectorSource(), + source: vectorSourceRef.current, style: POIStyle, type: 'Point' }) monitorfishMap.addInteraction(draw) setDrawObject(draw) - }, [getVectorSource]) + }, []) const waitForUnwantedZoomAndQuitInteraction = useCallback(() => { setTimeout(() => { @@ -163,34 +151,34 @@ export function InterestPointLayer({ mapMovingAndZoomEvent }: InterstPointLayerP if (!!currentZoom && currentZoom !== previousMapZoom.current) { previousMapZoom.current = currentZoom if (Number(currentZoom) < MIN_ZOOM) { - getVectorSource().forEachFeature(feature => { + vectorSourceRef.current.forEachFeature(feature => { feature.set(InterestPointLine.isHiddenByZoomProperty, true) }) } else { - getVectorSource().forEachFeature(feature => { + vectorSourceRef.current.forEachFeature(feature => { feature.set(InterestPointLine.isHiddenByZoomProperty, false) }) } } - }, [getVectorSource]) + }, []) const deleteInterestPoint = useCallback( (uuid: string) => { - const feature = getVectorSource().getFeatureById(uuid) + const feature = vectorSourceRef.current.getFeatureById(uuid) if (feature) { - getVectorSource().removeFeature(feature) - getVectorSource().changed() + vectorSourceRef.current.removeFeature(feature) + vectorSourceRef.current.changed() } - const featureLine = getVectorSource().getFeatureById(InterestPointLine.getFeatureId(uuid)) + const featureLine = vectorSourceRef.current.getFeatureById(InterestPointLine.getFeatureId(uuid)) if (featureLine) { - getVectorSource().removeFeature(featureLine) - getVectorSource().changed() + vectorSourceRef.current.removeFeature(featureLine) + vectorSourceRef.current.changed() } dispatch(removeInterestPoint(uuid)) }, - [dispatch, getVectorSource] + [dispatch] ) const moveInterestPointLine = useCallback( @@ -198,8 +186,8 @@ export function InterestPointLayer({ mapMovingAndZoomEvent }: InterstPointLayerP const featureId = InterestPointLine.getFeatureId(uuid) if (interestPointToCoordinates.has(featureId)) { - const existingLabelLineFeature = getVectorSource().getFeatureById(featureId) - const interestPointFeature = getVectorSource().getFeatureById(uuid) + const existingLabelLineFeature = vectorSourceRef.current.getFeatureById(featureId) + const interestPointFeature = vectorSourceRef.current.getFeatureById(uuid) if (existingLabelLineFeature) { if (interestPointFeature) { @@ -214,14 +202,14 @@ export function InterestPointLayer({ mapMovingAndZoomEvent }: InterstPointLayerP } else { const interestPointLineFeature = InterestPointLine.getFeature(coordinates, nextCoordinates, featureId) - getVectorSource().addFeature(interestPointLineFeature) + vectorSourceRef.current.addFeature(interestPointLineFeature) } const nextVesselToCoordinates = interestPointToCoordinates interestPointToCoordinates.set(featureId, { coordinates: nextCoordinates, offset }) setInterestPointToCoordinates(nextVesselToCoordinates) }, - [getVectorSource, interestPointToCoordinates] + [interestPointToCoordinates] ) const modifyInterestPoint = useCallback( @@ -240,7 +228,7 @@ export function InterestPointLayer({ mapMovingAndZoomEvent }: InterstPointLayerP const modifyFeatureWhenCoordinatesOrTypeModified = useCallback(() => { if (interestPointBeingDrawed?.coordinates?.length && interestPointBeingDrawed?.uuid) { - const drawingFeatureToUpdate = getVectorSource().getFeatureById(interestPointBeingDrawed.uuid) + const drawingFeatureToUpdate = vectorSourceRef.current.getFeatureById(interestPointBeingDrawed.uuid) if (drawingFeatureToUpdate && coordinatesOrTypeAreModified(drawingFeatureToUpdate, interestPointBeingDrawed)) { const interestPointWithoutFeature = omit(interestPointBeingDrawed, 'feature') @@ -264,7 +252,7 @@ export function InterestPointLayer({ mapMovingAndZoomEvent }: InterstPointLayerP ) } } - }, [dispatch, getVectorSource, interestPointBeingDrawed]) + }, [dispatch, interestPointBeingDrawed]) const initLineWhenInterestPointCoordinatesModified = useCallback(() => { if ( @@ -279,7 +267,7 @@ export function InterestPointLayer({ mapMovingAndZoomEvent }: InterstPointLayerP const featureId = InterestPointLine.getFeatureId(interestPointBeingDrawed.uuid) if (interestPointToCoordinates.has(featureId)) { interestPointToCoordinates.delete(featureId) - const feature = getVectorSource().getFeatureById(featureId) + const feature = vectorSourceRef.current.getFeatureById(featureId) if (feature) { feature.setGeometry( new LineString([interestPointBeingDrawed.coordinates, interestPointBeingDrawed.coordinates]) @@ -288,17 +276,17 @@ export function InterestPointLayer({ mapMovingAndZoomEvent }: InterstPointLayerP } } } - }, [getVectorSource, interestPointBeingDrawed, interestPointToCoordinates, previousInterestPointBeingDrawed]) + }, [interestPointBeingDrawed, interestPointToCoordinates, previousInterestPointBeingDrawed]) useEffect(() => { - if (!layerRef.current) { - monitorfishMap.getLayers().push(getLayer()) - } + const layer = layerRef.current + + monitorfishMap.getLayers().push(layer) return () => { - monitorfishMap.removeLayer(getLayer()) + monitorfishMap.removeLayer(layer) } - }, [getLayer]) + }, []) useEffect(() => { function drawExistingFeaturesOnMap() { @@ -320,12 +308,12 @@ export function InterestPointLayer({ mapMovingAndZoomEvent }: InterstPointLayerP }) .filter(feature => !!feature) - getVectorSource().addFeatures(features) + vectorSourceRef.current.addFeatures(features) } } drawExistingFeaturesOnMap() - }, [getVectorSource, interestPoints]) + }, [interestPoints]) useEffect(() => { if (isDrawing) { From 65750c4417965bb0676a2d1a268d28bf6a0a7a7e Mon Sep 17 00:00:00 2001 From: Ivan Gabriele <ivan.gabriele@protonmail.com> Date: Wed, 11 Sep 2024 08:40:17 +0200 Subject: [PATCH 09/12] Migrate measurement.style.tsx to TSX --- .../layers/{measurement.style.jsx => measurement.style.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename frontend/src/features/Measurement/layers/{measurement.style.jsx => measurement.style.tsx} (100%) diff --git a/frontend/src/features/Measurement/layers/measurement.style.jsx b/frontend/src/features/Measurement/layers/measurement.style.tsx similarity index 100% rename from frontend/src/features/Measurement/layers/measurement.style.jsx rename to frontend/src/features/Measurement/layers/measurement.style.tsx From 66472f329eca3d538ebe76616ca17ccef7912077 Mon Sep 17 00:00:00 2001 From: Ivan Gabriele <ivan.gabriele@protonmail.com> Date: Wed, 11 Sep 2024 08:42:01 +0200 Subject: [PATCH 10/12] Lint measurement.style.tsx following TSX migration --- .../Measurement/layers/measurement.style.tsx | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/frontend/src/features/Measurement/layers/measurement.style.tsx b/frontend/src/features/Measurement/layers/measurement.style.tsx index 127026bb35..5c6642c619 100644 --- a/frontend/src/features/Measurement/layers/measurement.style.tsx +++ b/frontend/src/features/Measurement/layers/measurement.style.tsx @@ -1,32 +1,35 @@ -import Style from 'ol/style/Style' +import { THEME } from '@mtes-mct/monitor-ui' +import { assertNotNullish } from '@utils/assertNotNullish' +import { getCenter } from 'ol/extent' +import Point from 'ol/geom/Point' +import CircleStyle from 'ol/style/Circle' import Fill from 'ol/style/Fill' import Stroke from 'ol/style/Stroke' -import { COLORS } from '@constants/constants' -import CircleStyle from 'ol/style/Circle' -import Point from 'ol/geom/Point' -import { getCenter } from 'ol/extent' +import Style from 'ol/style/Style' export const measurementStyleWithCenter = new Style({ - image: new CircleStyle({ - radius: 2, - fill: new Fill({ - color: COLORS.slateGray - }) - }), geometry: feature => { - if (feature.getGeometry().getType() === 'LineString') { + if (feature.getGeometry()?.getType() === 'LineString') { return undefined } - const extent = feature.getGeometry().getExtent() + const extent = feature.getGeometry()?.getExtent() + assertNotNullish(extent) const center = getCenter(extent) + return new Point(center) - } + }, + image: new CircleStyle({ + fill: new Fill({ + color: THEME.color.slateGray + }), + radius: 2 + }) }) export const measurementStyle = new Style({ stroke: new Stroke({ - color: COLORS.slateGray, + color: THEME.color.slateGray, lineDash: [4, 4], width: 2 }) From e724d68f9e4ccfe82c75cb22cc337ea7736ebfae Mon Sep 17 00:00:00 2001 From: Ivan Gabriele <ivan.gabriele@protonmail.com> Date: Wed, 11 Sep 2024 08:42:41 +0200 Subject: [PATCH 11/12] Migrate MeasurementLayer.tsx to TSX --- .../layers/{MeasurementLayer.jsx => MeasurementLayer.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename frontend/src/features/Measurement/layers/{MeasurementLayer.jsx => MeasurementLayer.tsx} (100%) diff --git a/frontend/src/features/Measurement/layers/MeasurementLayer.jsx b/frontend/src/features/Measurement/layers/MeasurementLayer.tsx similarity index 100% rename from frontend/src/features/Measurement/layers/MeasurementLayer.jsx rename to frontend/src/features/Measurement/layers/MeasurementLayer.tsx From ad692318b3b18b7df7a6aca006507893fdf5ee5b Mon Sep 17 00:00:00 2001 From: Ivan Gabriele <ivan.gabriele@protonmail.com> Date: Wed, 11 Sep 2024 09:31:28 +0200 Subject: [PATCH 12/12] Refactor & prevent duplicate map layer collection push in MeasurementLayer --- .../Measurement/layers/MeasurementLayer.tsx | 463 +++++++++--------- frontend/src/features/Measurement/slice.ts | 7 +- frontend/src/features/map/Map.tsx | 2 +- 3 files changed, 246 insertions(+), 226 deletions(-) diff --git a/frontend/src/features/Measurement/layers/MeasurementLayer.tsx b/frontend/src/features/Measurement/layers/MeasurementLayer.tsx index b7f7aaf79a..c10e4078b8 100644 --- a/frontend/src/features/Measurement/layers/MeasurementLayer.tsx +++ b/frontend/src/features/Measurement/layers/MeasurementLayer.tsx @@ -1,309 +1,326 @@ -import React, { useEffect, useRef, useState } from 'react' - -import { useDispatch, useSelector } from 'react-redux' -import VectorSource from 'ol/source/Vector' -import { MeasurementType, OPENLAYERS_PROJECTION, WSG84_PROJECTION } from '../../../domain/entities/map/constants' -import Draw from 'ol/interaction/Draw' -import { unByKey } from 'ol/Observable' +import { useMainAppDispatch } from '@hooks/useMainAppDispatch' +import { useMainAppSelector } from '@hooks/useMainAppSelector' +import { assertNotNullish } from '@utils/assertNotNullish' +import { noop } from 'lodash' +import { getCenter } from 'ol/extent' +import Feature from 'ol/Feature' +import GeoJSON from 'ol/format/GeoJSON' +import Circle from 'ol/geom/Circle' import LineString from 'ol/geom/LineString' -import { getLength } from 'ol/sphere' -import { - removeMeasurementDrawed, - resetMeasurementTypeToAdd, - setCircleMeasurementInDrawing -} from '../slice' +import Polygon, { circular, fromCircle } from 'ol/geom/Polygon' +import Draw, { DrawEvent } from 'ol/interaction/Draw' import VectorLayer from 'ol/layer/Vector' -import Circle from 'ol/geom/Circle' -import { circular, fromCircle } from 'ol/geom/Polygon' -import Feature from 'ol/Feature' +import { unByKey } from 'ol/Observable' +import { transform } from 'ol/proj' import { METERS_PER_UNIT } from 'ol/proj/Units' -import GeoJSON from 'ol/format/GeoJSON' -import MeasurementOverlay from '../components/MeasurementOverlay' -import { getNauticalMilesFromMeters } from '../../../utils' +import VectorSource from 'ol/source/Vector' +import { getLength } from 'ol/sphere' +import { memo, useCallback, useEffect, useRef, useState } from 'react' + +import { OPENLAYERS_PROJECTION, WSG84_PROJECTION } from '../../../domain/entities/map/constants' import saveMeasurement from '../../../domain/use_cases/measurement/saveMeasurement' +import { getNauticalMilesFromMeters } from '../../../utils' +import MeasurementOverlay from '../components/MeasurementOverlay' +import { removeMeasurementDrawed, resetMeasurementTypeToAdd, setCircleMeasurementInDrawing } from '../slice' import { measurementStyle, measurementStyleWithCenter } from './measurement.style' -import { transform } from 'ol/proj' -import { getCenter } from 'ol/extent' import { LayerProperties } from '../../../domain/entities/layers/constants' import { monitorfishMap } from '../../map/monitorfishMap' +import type { Coordinates } from '@mtes-mct/monitor-ui' +import type { Coordinate } from 'ol/coordinate' + const DRAW_START_EVENT = 'drawstart' const DRAW_ABORT_EVENT = 'drawabort' const DRAW_END_EVENT = 'drawend' -const getNauticalMilesRadiusOfCircle = circle => { +const getNauticalMilesRadiusOfCircle = (circle: Circle): string => { const polygon = fromCircle(circle) return getNauticalMilesRadiusOfCircularPolygon(polygon) } -const getNauticalMilesOfLine = line => { +const getNauticalMilesOfLine = (line: LineString): string => { const length = getLength(line) return `${getNauticalMilesFromMeters(length)} nm` } -function getNauticalMilesRadiusOfCircularPolygon (polygon) { +function getNauticalMilesRadiusOfCircularPolygon(polygon: Polygon): string { const length = getLength(polygon) const radius = length / (2 * Math.PI) return `r = ${getNauticalMilesFromMeters(radius)} nm` } -const MeasurementLayer = () => { - const dispatch = useDispatch() +type MeasurementInProgress = { + center?: Coordinate + coordinates: null + feature?: null + measurement: number | string | null +} - const { - measurementTypeToAdd, - measurementsDrawed, - circleMeasurementToAdd - } = useSelector(state => state.measurement) +function UnmemoizedMeasurementLayer() { + const vectorSourceRef = useRef( + new VectorSource({ + // TODO Fix TS error `'projection' does not exist in type 'Options<Feature<Geometry>>'`. + // @ts-ignore + projection: OPENLAYERS_PROJECTION, + wrapX: false + }) + ) + const vectorLayerRef = useRef( + new VectorLayer({ + className: LayerProperties.MEASUREMENT.code, + renderBuffer: 7, + source: vectorSourceRef.current, + style: [measurementStyle, measurementStyleWithCenter], + updateWhileAnimating: true, + updateWhileInteracting: true, + zIndex: LayerProperties.MEASUREMENT.zIndex + }) + ) + + const dispatch = useMainAppDispatch() + const { circleMeasurementToAdd, measurementsDrawed, measurementTypeToAdd } = useMainAppSelector( + state => state.measurement + ) - const [measurementInProgress, _setMeasurementInProgress] = useState(null) + const [measurementInProgress, setMeasurementInProgress] = useState<MeasurementInProgress | null>(null) const measurementInProgressRef = useRef(measurementInProgress) - const setMeasurementInProgress = value => { - measurementInProgressRef.current = value - _setMeasurementInProgress(value) + const setMeasurementInProgressWithRef = (nextMeasurementInProgress: MeasurementInProgress | null) => { + measurementInProgressRef.current = nextMeasurementInProgress + setMeasurementInProgress(nextMeasurementInProgress) } - const [drawObject, setDrawObject] = useState(null) - const [vectorSource] = useState(new VectorSource({ - wrapX: false, - projection: OPENLAYERS_PROJECTION - })) - const [vectorLayer] = useState(new VectorLayer({ - source: vectorSource, - renderBuffer: 7, - updateWhileAnimating: true, - updateWhileInteracting: true, - style: [measurementStyle, measurementStyleWithCenter], - className: LayerProperties.MEASUREMENT.code, - zIndex: LayerProperties.MEASUREMENT.zIndex - })) + const [drawObject, setDrawObject] = useState<Draw | null>(null) - useEffect(() => { - function addLayerToMap () { - if (vectorLayer) { - monitorfishMap.getLayers().push(vectorLayer) - } + const addCustomCircleMeasurement = useCallback( + (nextCircleMeasurementToAdd: { circleCoordinatesToAdd: Coordinates; circleRadiusToAdd: number }) => { + const metersForOneNauticalMile = 1852 + const longitude = 1 + const latitude = 0 + const numberOfVertices = 64 - return () => { - monitorfishMap.removeLayer(vectorLayer) + if ( + !circleMeasurementHasCoordinatesAndRadiusFromForm() && + !circleMeasurementHasRadiusFromFormAndCoordinatesFromDraw() + ) { + return } - } - addLayerToMap() - }, [vectorLayer]) + function circleMeasurementHasCoordinatesAndRadiusFromForm() { + return ( + nextCircleMeasurementToAdd.circleCoordinatesToAdd?.length === 2 && + nextCircleMeasurementToAdd.circleRadiusToAdd + ) + } - useEffect(() => { - function drawExistingFeaturesOnMap () { - if (measurementsDrawed) { - measurementsDrawed.forEach(measurement => { - const feature = new GeoJSON({ - featureProjection: OPENLAYERS_PROJECTION - }).readFeature(measurement.feature) - - vectorSource.addFeature(feature) - }) + function circleMeasurementHasRadiusFromFormAndCoordinatesFromDraw() { + return nextCircleMeasurementToAdd.circleRadiusToAdd && measurementInProgress?.center?.length === 2 } - } - drawExistingFeaturesOnMap() - }, [measurementsDrawed]) + assertNotNullish(nextCircleMeasurementToAdd.circleRadiusToAdd) - useEffect(() => { - if (measurementTypeToAdd) { - function addEmptyNextMeasurement () { - setMeasurementInProgress({ - feature: null, - measurement: null, - coordinates: null - }) + const radiusInMeters = METERS_PER_UNIT.m * nextCircleMeasurementToAdd.circleRadiusToAdd * metersForOneNauticalMile + let coordinates: Coordinate = [] + if (circleMeasurementHasCoordinatesAndRadiusFromForm()) { + coordinates = [ + nextCircleMeasurementToAdd.circleCoordinatesToAdd[longitude], + nextCircleMeasurementToAdd.circleCoordinatesToAdd[latitude] + ] + } else if (circleMeasurementHasRadiusFromFormAndCoordinatesFromDraw()) { + assertNotNullish(measurementInProgress?.center) + + coordinates = transform(measurementInProgress.center, OPENLAYERS_PROJECTION, WSG84_PROJECTION) } - function drawNewFeatureOnMap () { - const draw = new Draw({ - source: vectorSource, - type: measurementTypeToAdd, - style: [measurementStyle, measurementStyleWithCenter] - }) + const circleFeature = new Feature({ + geometry: circular(coordinates, radiusInMeters, numberOfVertices).transform( + WSG84_PROJECTION, + OPENLAYERS_PROJECTION + ), + style: [measurementStyle, measurementStyleWithCenter] + }) + dispatch(saveMeasurement(circleFeature, `r = ${nextCircleMeasurementToAdd.circleRadiusToAdd} nm`)) + }, + [dispatch, measurementInProgress?.center] + ) - monitorfishMap.addInteraction(draw) - setDrawObject(draw) + const deleteFeature = useCallback( + (featureId: string) => { + const feature = vectorSourceRef.current.getFeatureById(featureId) + if (feature) { + vectorSourceRef.current.removeFeature(feature) + vectorSourceRef.current.changed() } - addEmptyNextMeasurement() - drawNewFeatureOnMap() - } - }, [measurementTypeToAdd]) + dispatch(removeMeasurementDrawed(featureId)) + }, + [dispatch] + ) - useEffect(() => { - function removeInteraction () { - if (!measurementTypeToAdd && drawObject) { - setDrawObject(null) - setMeasurementInProgress(null) + const startDrawing = useCallback((event: DrawEvent) => { + // TODO Fix TS error `Property 'coordinate' does not exist on type 'DrawEvent'`. + // @ts-ignore + let firstTooltipCoordinates = event.coordinate + const geometry = event.feature.getGeometry() + assertNotNullish(geometry) + + setMeasurementInProgressWithRef({ + center: getCenter(geometry.getExtent()), + // TODO Fix TS error `Property 'getLastCoordinate' does not exist on type 'Geometry'`. + // @ts-ignore + coordinates: geometry.getLastCoordinate(), + measurement: 0 + }) - waitForUnwantedZoomAndQuitInteraction() - } - } + return geometry.on('change', changeEvent => { + const geom = changeEvent.target - function waitForUnwantedZoomAndQuitInteraction () { - setTimeout(() => { - monitorfishMap.removeInteraction(drawObject) - }, 300) - } + if (geom instanceof LineString) { + const nextMeasurementOutput = getNauticalMilesOfLine(geom) + firstTooltipCoordinates = geom.getLastCoordinate() - removeInteraction() - }, [measurementTypeToAdd]) + setMeasurementInProgressWithRef({ + coordinates: firstTooltipCoordinates, + measurement: nextMeasurementOutput + }) + } else if (geom instanceof Circle) { + const nextMeasurementOutput = getNauticalMilesRadiusOfCircle(geom) + firstTooltipCoordinates = geom.getLastCoordinate() + + setMeasurementInProgressWithRef({ + center: getCenter(geom.getExtent()), + coordinates: firstTooltipCoordinates, + measurement: nextMeasurementOutput + }) + } + }) + }, []) useEffect(() => { - function addCustomCircleMeasurement () { - const metersForOneNauticalMile = 1852 - const longitude = 1 - const latitude = 0 - const numberOfVertices = 64 - - if (!circleMeasurementHasCoordinatesAndRadiusFromForm() && !circleMeasurementHasRadiusFromFormAndCoordinatesFromDraw()) { - return - } + const vectorLayer = vectorLayerRef.current - function circleMeasurementHasCoordinatesAndRadiusFromForm () { - return circleMeasurementToAdd?.circleCoordinatesToAdd?.length === 2 && circleMeasurementToAdd?.circleRadiusToAdd - } + monitorfishMap.getLayers().push(vectorLayer) - function circleMeasurementHasRadiusFromFormAndCoordinatesFromDraw () { - return circleMeasurementToAdd?.circleRadiusToAdd && measurementInProgress?.center?.length === 2 - } + return () => { + monitorfishMap.removeLayer(vectorLayer) + } + }, []) - const radiusInMeters = METERS_PER_UNIT.m * circleMeasurementToAdd.circleRadiusToAdd * metersForOneNauticalMile - let coordinates = [] - if (circleMeasurementHasCoordinatesAndRadiusFromForm()) { - coordinates = [circleMeasurementToAdd.circleCoordinatesToAdd[longitude], circleMeasurementToAdd.circleCoordinatesToAdd[latitude]] - } else if (circleMeasurementHasRadiusFromFormAndCoordinatesFromDraw()) { - coordinates = transform(measurementInProgress?.center, OPENLAYERS_PROJECTION, WSG84_PROJECTION) - } + useEffect(() => { + if (measurementsDrawed) { + measurementsDrawed.forEach(measurement => { + const feature = new GeoJSON({ + featureProjection: OPENLAYERS_PROJECTION + }).readFeature(measurement.feature) - const circleFeature = new Feature({ - geometry: circular(coordinates, radiusInMeters, numberOfVertices).transform(WSG84_PROJECTION, OPENLAYERS_PROJECTION), - style: [measurementStyle, measurementStyleWithCenter] + vectorSourceRef.current.addFeature(feature) }) - dispatch(saveMeasurement(circleFeature, `r = ${circleMeasurementToAdd.circleRadiusToAdd} nm`)) } - - addCustomCircleMeasurement() - }, [circleMeasurementToAdd]) + }, [measurementsDrawed]) useEffect(() => { - function handleDrawEvents () { - if (drawObject) { - let listener + if (measurementTypeToAdd) { + setMeasurementInProgressWithRef({ + coordinates: null, + feature: null, + measurement: null + }) - drawObject.on(DRAW_START_EVENT, event => { - listener = startDrawing(event) - }) + const draw = new Draw({ + source: vectorSourceRef.current, + style: [measurementStyle, measurementStyleWithCenter], + type: measurementTypeToAdd + }) - drawObject.on(DRAW_ABORT_EVENT, () => { - unByKey(listener) - dispatch(resetMeasurementTypeToAdd()) - setMeasurementInProgress(null) - }) + monitorfishMap.addInteraction(draw) + setDrawObject(draw) + } + }, [measurementTypeToAdd]) - drawObject.on(DRAW_END_EVENT, event => { - dispatch(saveMeasurement(event.feature, measurementInProgressRef.current.measurement)) + useEffect(() => { + if (!measurementTypeToAdd && drawObject) { + setDrawObject(null) + setMeasurementInProgressWithRef(null) - unByKey(listener) - dispatch(resetMeasurementTypeToAdd()) - setMeasurementInProgress(null) - }) - } + setTimeout(() => { + monitorfishMap.removeInteraction(drawObject) + }, 300) } - - handleDrawEvents() - }, [drawObject]) + }, [drawObject, measurementTypeToAdd]) useEffect(() => { - if (measurementInProgress?.center || measurementInProgress?.measurement) { - dispatch(setCircleMeasurementInDrawing({ - measurement: measurementInProgress.measurement, - coordinates: measurementInProgress.center - })) + if (circleMeasurementToAdd) { + addCustomCircleMeasurement(circleMeasurementToAdd) } - }, [measurementInProgress]) + }, [addCustomCircleMeasurement, circleMeasurementToAdd]) - function deleteFeature (featureId) { - const feature = vectorSource.getFeatureById(featureId) - if (feature) { - vectorSource.removeFeature(feature) - vectorSource.changed() + useEffect(() => { + if (!drawObject) { + return } - dispatch(removeMeasurementDrawed(featureId)) - } + let listener - function startDrawing (event) { - const firstTooltipCoordinates = event.coordinate + drawObject.on(DRAW_START_EVENT, event => { + listener = startDrawing(event) + }) - setMeasurementInProgress({ - measurement: 0, - coordinates: event.feature.getGeometry().getLastCoordinate(), - center: getCenter(event.feature.getGeometry().getExtent()) + drawObject.on(DRAW_ABORT_EVENT, () => { + unByKey(listener) + + dispatch(resetMeasurementTypeToAdd()) + + setMeasurementInProgressWithRef(null) }) - return event.feature.getGeometry().on('change', changeEvent => { - function updateMeasurementOnNewPoint (event, tooltipCoordinates) { - const geom = event.target - - if (geom instanceof LineString) { - const nextMeasurementOutput = getNauticalMilesOfLine(geom) - tooltipCoordinates = geom.getLastCoordinate() - - setMeasurementInProgress({ - measurement: nextMeasurementOutput, - coordinates: tooltipCoordinates - }) - } else if (geom instanceof Circle) { - const nextMeasurementOutput = getNauticalMilesRadiusOfCircle(geom) - tooltipCoordinates = geom.getLastCoordinate() - - setMeasurementInProgress({ - measurement: nextMeasurementOutput, - coordinates: tooltipCoordinates, - center: getCenter(geom.getExtent()) - }) - } - } + drawObject.on(DRAW_END_EVENT, event => { + assertNotNullish(measurementInProgressRef.current) + + unByKey(listener) - updateMeasurementOnNewPoint(changeEvent, firstTooltipCoordinates) + dispatch(saveMeasurement(event.feature, measurementInProgressRef.current.measurement)) + dispatch(resetMeasurementTypeToAdd()) + + setMeasurementInProgressWithRef(null) }) - } + }, [dispatch, drawObject, startDrawing]) + + useEffect(() => { + if (!!measurementInProgress?.center || !!measurementInProgress?.measurement) { + dispatch( + setCircleMeasurementInDrawing({ + coordinates: measurementInProgress.center, + measurement: measurementInProgress.measurement + }) + ) + } + }, [dispatch, measurementInProgress]) return ( <> - { - measurementsDrawed.map(measurement => { - return <MeasurementOverlay - id={measurement.feature.id} - key={measurement.feature.id} - map={monitorfishMap} - measurement={measurement.measurement} - coordinates={measurement.coordinates} - deleteFeature={deleteFeature} - /> - }) - } + {measurementsDrawed.map(measurement => ( + <MeasurementOverlay + key={measurement.feature.id} + coordinates={measurement.coordinates} + deleteFeature={deleteFeature} + id={measurement.feature.id} + measurement={measurement.measurement} + /> + ))} <div> - { - measurementInProgress - ? <MeasurementOverlay - map={monitorfishMap} - measurement={measurementInProgress?.measurement} - coordinates={measurementInProgress?.coordinates} - /> - : null - } + {measurementInProgress ? ( + <MeasurementOverlay + coordinates={measurementInProgress?.coordinates} + deleteFeature={noop} + id={undefined} + measurement={measurementInProgress?.measurement} + /> + ) : null} </div> </> ) } -export default React.memo(MeasurementLayer) +export const MeasurementLayer = memo(UnmemoizedMeasurementLayer) diff --git a/frontend/src/features/Measurement/slice.ts b/frontend/src/features/Measurement/slice.ts index d1b8fbcbe6..031194d586 100644 --- a/frontend/src/features/Measurement/slice.ts +++ b/frontend/src/features/Measurement/slice.ts @@ -3,6 +3,7 @@ import { createSlice } from '@reduxjs/toolkit' import { getLocalStorageState } from '../../utils' import type { MeasurementType } from '../../domain/entities/map/constants' +import type { Coordinates } from '@mtes-mct/monitor-ui' const measurementsLocalStorageKey = 'measurements' @@ -12,8 +13,10 @@ export type MeasurementState = { coordinates: number[] measurement: any } | null - // TODO Type this prop. - circleMeasurementToAdd: null + circleMeasurementToAdd: { + circleCoordinatesToAdd: Coordinates + circleRadiusToAdd: number + } | null measurementTypeToAdd: MeasurementType | null // TODO Type this prop. measurementsDrawed: Record<string, any>[] diff --git a/frontend/src/features/map/Map.tsx b/frontend/src/features/map/Map.tsx index 66c75d9cf8..01c21aa089 100644 --- a/frontend/src/features/map/Map.tsx +++ b/frontend/src/features/map/Map.tsx @@ -30,7 +30,7 @@ import { AdministrativeLayers } from '../AdministrativeZone/layers/Administrativ import { BaseLayer } from '../BaseMap/layers/BaseLayer' import { DrawLayer } from '../Draw/layer' import { InterestPointLayer } from '../InterestPoint/layers/InterestPointLayer' -import MeasurementLayer from '../Measurement/layers/MeasurementLayer' +import { MeasurementLayer } from '../Measurement/layers/MeasurementLayer' import { MissionOverlay } from '../Mission/components/MissionOverlay' import { SelectedMissionOverlay } from '../Mission/components/SelectedMissionOverlay' import { MissionHoveredLayer } from '../Mission/layers/HoveredMissionLayer'