From 6f543d3770e311fa4dc827cc6218f29d7d5ac705 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Thu, 11 Mar 2021 12:09:27 +0100 Subject: [PATCH] Handle invalid GeoJSON in the editor --- app/javascript/components/MapEditor/index.jsx | 60 ++++++++++++++----- .../components/shared/FlashMessage.jsx | 24 ++++++++ 2 files changed, 70 insertions(+), 14 deletions(-) create mode 100644 app/javascript/components/shared/FlashMessage.jsx diff --git a/app/javascript/components/MapEditor/index.jsx b/app/javascript/components/MapEditor/index.jsx index 76803b00c..43f18b465 100644 --- a/app/javascript/components/MapEditor/index.jsx +++ b/app/javascript/components/MapEditor/index.jsx @@ -1,4 +1,10 @@ -import React, { useState, useCallback, useRef, useMemo } from 'react'; +import React, { + useState, + useCallback, + useRef, + useMemo, + useEffect +} from 'react'; import PropTypes from 'prop-types'; import mapboxgl from 'mapbox-gl'; import { GeoJSONLayer, ZoomControl } from 'react-mapbox-gl'; @@ -10,6 +16,7 @@ import { getJSON, ajax, fire } from '@utils'; import Mapbox from '../shared/mapbox/Mapbox'; import { getMapStyle } from '../shared/mapbox/styles'; import SwitchMapStyle from '../shared/mapbox/SwitchMapStyle'; +import { FlashMessage } from '../shared/FlashMessage'; import ComboAdresseSearch from '../ComboAdresseSearch'; import { @@ -30,6 +37,7 @@ function MapEditor({ featureCollection, url, preview, options }) { const drawControl = useRef(null); const [currentMap, setCurrentMap] = useState(null); + const [errorMessage, setErrorMessage] = useState(); const [style, setStyle] = useState('ortho'); const [coords, setCoords] = useState([1.7, 46.9]); const [zoom, setZoom] = useState([5]); @@ -44,6 +52,11 @@ function MapEditor({ featureCollection, url, preview, options }) { ]); const hasCadastres = useMemo(() => options.layers.includes('cadastres')); + useEffect(() => { + const timer = setTimeout(() => setErrorMessage(null), 5000); + return () => clearTimeout(timer); + }, [errorMessage]); + const translations = [ ['.mapbox-gl-draw_line', 'Tracer une ligne'], ['.mapbox-gl-draw_polygon', 'Dessiner un polygone'], @@ -118,21 +131,34 @@ function MapEditor({ featureCollection, url, preview, options }) { } async function onDrawCreate({ features }) { - for (const feature of features) { - const data = await getJSON(url, { feature }, 'post'); - setFeatureId(feature.id, data.feature); - } + try { + for (const feature of features) { + const data = await getJSON(url, { feature }, 'post'); + setFeatureId(feature.id, data.feature); + } - updateFeaturesList(features); + updateFeaturesList(features); + } catch { + setErrorMessage('Le polygone dessiné n’est pas valide.'); + } } async function onDrawUpdate({ features }) { - for (const feature of features) { - let { id } = feature.properties; - await getJSON(`${url}/${id}`, { feature }, 'patch'); - } + try { + for (const feature of features) { + const { id } = feature.properties; + if (id) { + await getJSON(`${url}/${id}`, { feature }, 'patch'); + } else { + const data = await getJSON(url, { feature }, 'post'); + setFeatureId(feature.id, data.feature); + } + } - updateFeaturesList(features); + updateFeaturesList(features); + } catch { + setErrorMessage('Le polygone dessiné n’est pas valide.'); + } } async function onDrawDelete({ features }) { @@ -152,8 +178,9 @@ function MapEditor({ featureCollection, url, preview, options }) { ); } - const onFileImport = (e, inputId) => { - readGeoFile(e.target.files[0]).then(async (featureCollection) => { + const onFileImport = async (e, inputId) => { + try { + const featureCollection = await readGeoFile(e.target.files[0]); const resultFeatureCollection = await getJSON( `${url}/import`, featureCollection, @@ -188,7 +215,9 @@ function MapEditor({ featureCollection, url, preview, options }) { updateFeaturesList(resultFeatureCollection.features); setImportInputs(setInputs); setBbox(resultFeatureCollection.bbox); - }); + } catch { + setErrorMessage('Le fichier importé contient des polygones invalides.'); + } }; const addInputFile = (e) => { @@ -233,6 +262,9 @@ function MapEditor({ featureCollection, url, preview, options }) { return ( <> + {errorMessage && ( + + )}

Besoin d'aide ?  diff --git a/app/javascript/components/shared/FlashMessage.jsx b/app/javascript/components/shared/FlashMessage.jsx new file mode 100644 index 000000000..fb12c7ccd --- /dev/null +++ b/app/javascript/components/shared/FlashMessage.jsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { createPortal } from 'react-dom'; + +export function FlashMessage({ message, level, sticky, fixed }) { + return createPortal( +

+
{message}
+
, + document.getElementById('flash_messages') + ); +} + +function flashClassName(level, sticky = false, fixed = false) { + const className = + level == 'notice' ? ['alert', 'alert-success'] : ['alert', 'alert-danger']; + + if (sticky) { + className.push('sticky'); + } + if (fixed) { + className.push('alert-fixed'); + } + return className.join(' '); +}