diff --git a/front/src/applications/editor/Map.tsx b/front/src/applications/editor/Map.tsx index f128348c677..1f3d04a056a 100644 --- a/front/src/applications/editor/Map.tsx +++ b/front/src/applications/editor/Map.tsx @@ -4,7 +4,6 @@ import maplibregl from 'maplibre-gl'; import ReactMapGL, { AttributionControl, ScaleControl } from 'react-map-gl'; import { withTranslation } from 'react-i18next'; import { TFunction } from 'i18next'; - import VirtualLayers from 'applications/osrd/views/OSRDSimulation/VirtualLayers'; import colors from 'common/Map/Consts/colors'; import 'common/Map/Map.scss'; @@ -17,6 +16,7 @@ import Platforms from '../../common/Map/Layers/Platforms'; import osmBlankStyle from '../../common/Map/Layers/osmBlankStyle'; import OrthoPhoto from '../../common/Map/Layers/OrthoPhoto'; import { Viewport } from '../../reducers/map'; +import { getMapMouseEventNearestFeature } from '../../utils/mapboxHelper'; import EditorContext from './context'; import { CommonToolState, @@ -99,26 +99,39 @@ const MapUnplugged: FC> = ({ onMove={(e) => setViewport(e.viewState)} onMoveStart={() => setMapState((prev) => ({ ...prev, isDragging: true }))} onMoveEnd={() => setMapState((prev) => ({ ...prev, isDragging: false }))} - onMouseEnter={(e) => { - setMapState((prev) => ({ ...prev, isHovering: true })); - const feature = (e.features || [])[0]; - if (activeTool.onHover) { - activeTool.onHover(e, extendedContext); - } else if (feature && feature.properties) { - const entity = feature?.properties?.id - ? editorState.entitiesIndex[feature.properties.id] - : undefined; - setToolState({ - ...toolState, - hovered: entity || null, - }); + onMouseMove={(e) => { + const nearestResult = getMapMouseEventNearestFeature(e); + const partialToolState: Partial = { + hovered: null, + mousePosition: [e.lngLat.lng, e.lngLat.lat], + }; + const partialMapState: Partial = { isHovering: false }; + + // if we hover something + if (nearestResult) { + const { feature } = nearestResult; + const eventWithFeature = { + ...e, + preventDefault: e.preventDefault, + features: [feature], + }; + partialMapState.isHovering = true; + if (activeTool.onHover) { + activeTool.onHover(eventWithFeature, extendedContext); + } else if (feature && feature.properties) { + const entity = feature?.properties?.id + ? editorState.entitiesIndex[feature.properties.id] + : undefined; + partialToolState.hovered = entity || null; + } } else { - setToolState({ ...toolState, hovered: null }); + if (activeTool.onMove) { + activeTool.onMove(e, extendedContext); + } } - }} - onMouseLeave={() => { - setMapState((prev) => ({ ...prev, isHovering: false })); - setToolState({ ...toolState, mousePosition: null, hovered: null }); + + setToolState({ ...toolState, ...partialToolState }); + setMapState((prev) => ({ ...prev, ...partialMapState })); }} onLoad={(e) => { // need to call resize, otherwise sometime the canvas doesn't take 100% @@ -140,17 +153,19 @@ const MapUnplugged: FC> = ({ } cursor={cursor} onClick={(e) => { + const nearestResult = getMapMouseEventNearestFeature(e); + const eventWithFeature = nearestResult + ? { + ...e, + preventDefault: e.preventDefault, + features: [nearestResult.feature], + } + : e; if (toolState.hovered && activeTool.onClickFeature) { - activeTool.onClickFeature(toolState.hovered, e, extendedContext); + activeTool.onClickFeature(toolState.hovered, eventWithFeature, extendedContext); } if (activeTool.onClickMap) { - activeTool.onClickMap(e, extendedContext); - } - }} - onMouseMove={(e) => { - setToolState({ ...toolState, mousePosition: [e.lngLat.lng, e.lngLat.lat] }); - if (activeTool.onMove) { - activeTool.onMove(e, extendedContext); + activeTool.onClickMap(eventWithFeature, extendedContext); } }} > diff --git a/front/src/applications/editor/components/EditorForm.tsx b/front/src/applications/editor/components/EditorForm.tsx index 9691915cb65..41110cdb716 100644 --- a/front/src/applications/editor/components/EditorForm.tsx +++ b/front/src/applications/editor/components/EditorForm.tsx @@ -3,11 +3,12 @@ import Form, { Field, UiSchema } from '@rjsf/core'; import { useSelector } from 'react-redux'; import { GeoJsonProperties } from 'geojson'; import { JSONSchema7 } from 'json-schema'; +import { isNil, omitBy } from 'lodash'; import './EditorForm.scss'; import { EditorEntity } from '../../../types'; import { FormComponent, FormLineStringLength } from './LinearMetadata'; -import { getJsonSchemaForLayer, getLayerForObjectType } from '../data/utils'; +import { getJsonSchemaForLayer, getLayerForObjectType, NEW_ENTITY_ID } from '../data/utils'; import { EditorState } from '../tools/types'; const fields = { @@ -57,7 +58,7 @@ const EditorForm: React.FC> = ({ * => recompute formData by fixing LM */ useEffect(() => { - setFormData(data.properties); + setFormData(omitBy(data.properties, isNil)); }, [data, schema]); /** @@ -91,13 +92,16 @@ const EditorForm: React.FC> = ({ ...(overrideUiSchema || {}), }} formData={formData} - formContext={{ geometry: data.geometry, length: data.properties?.length }} + formContext={{ + geometry: data.geometry, + length: data.properties?.length, + isCreation: isNil(formData?.id) || formData?.id === NEW_ENTITY_ID, + }} onError={() => setSubmited(true)} - onSubmit={async (event) => { + onSubmit={async () => { try { setError(null); - setFormData(event.formData); - await onSubmit({ ...data, properties: { ...data.properties, ...event.formData } }); + await onSubmit({ ...data, properties: { ...data.properties, ...formData } }); } catch (e) { if (e instanceof Error) setError(e.message); else setError(JSON.stringify(e)); diff --git a/front/src/applications/editor/components/LinearMetadata/FormLineStringLength.tsx b/front/src/applications/editor/components/LinearMetadata/FormLineStringLength.tsx index 1a3bbc7dfcf..f495b559d7f 100644 --- a/front/src/applications/editor/components/LinearMetadata/FormLineStringLength.tsx +++ b/front/src/applications/editor/components/LinearMetadata/FormLineStringLength.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useCallback } from 'react'; import { WidgetProps } from '@rjsf/core'; import { useTranslation } from 'react-i18next'; @@ -8,30 +8,29 @@ export const FormLineStringLength: React.FC = (props) => { const { t } = useTranslation(); const { id, value, required, readonly, onChange, formContext } = props; - const [length, setLength] = useState(value); + const [length, setLength] = useState(value || 0); const [min, setMin] = useState(-Infinity); const [max, setMax] = useState(Infinity); const [geoLength, setGeoLength] = useState(0); + useEffect(() => { + setLength(value || 0); + }, [value]); + /** * When the geometry changes * => recompute min & max plus its length */ useEffect(() => { const distance = getLineStringDistance(formContext.geometry); - setMin(Math.round(distance - distance * DISTANCE_ERROR_RANGE)); - setMax(Math.round(distance + distance * DISTANCE_ERROR_RANGE)); - setGeoLength(distance); - }, [formContext.geometry]); - - /** - * When the input value change - * => if it is valid, we call the onChange - */ - useEffect(() => { - if (value !== undefined) setLength(value); - else setLength(geoLength); - }, [value, geoLength]); + if (formContext.isCreation) { + setTimeout(() => onChange(distance), 0); + } else { + setMin(Math.round(distance - distance * DISTANCE_ERROR_RANGE)); + setMax(Math.round(distance + distance * DISTANCE_ERROR_RANGE)); + setGeoLength(distance); + } + }, [formContext.geometry, formContext.isCreation, onChange]); return (
@@ -43,19 +42,15 @@ export const FormLineStringLength: React.FC = (props) => { id={id} required={required} type="number" - min={min} - max={max} - step="any" value={length} onChange={(e) => { const nValue = parseFloat(e.target.value); - if (nValue >= min && nValue <= max) onChange(nValue); - else setLength(nValue); + onChange(nValue); }} /> )} - {(length === undefined || length < min || length > max) && ( -

+ {geoLength && (length === undefined || length < min || length > max) && ( +

{t('Editor.errors.length-out-of-sync-with-geometry', { min, max })}.{' '}