();
+ useEffect(() => {
+ const timer = setTimeout(() => onError(undefined), 5000);
+ return () => clearTimeout(timer);
+ }, [error]);
+
+ return [error, onError];
+}
diff --git a/app/javascript/components/MapEditor/index.jsx b/app/javascript/components/MapEditor/index.jsx
deleted file mode 100644
index 20fa26dd4..000000000
--- a/app/javascript/components/MapEditor/index.jsx
+++ /dev/null
@@ -1,263 +0,0 @@
-import React, { useState } from 'react';
-import PropTypes from 'prop-types';
-import ReactMapboxGl, { ZoomControl } from 'react-mapbox-gl';
-import DrawControl from 'react-mapbox-gl-draw';
-import {
- CursorClickIcon,
- PlusIcon,
- LocationMarkerIcon
-} from '@heroicons/react/outline';
-import CoordinateInput from 'react-coordinate-input';
-import { fire } from '@utils';
-import VisuallyHidden from '@reach/visually-hidden';
-import { useId } from '@reach/auto-id';
-import 'mapbox-gl/dist/mapbox-gl.css';
-import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css';
-
-import MapStyleControl, { useMapStyle } from '../shared/mapbox/MapStyleControl';
-import { FlashMessage } from '../shared/FlashMessage';
-
-import ComboAdresseSearch from '../ComboAdresseSearch';
-import { useMapboxEditor } from './useMapboxEditor';
-
-const Mapbox = ReactMapboxGl({});
-
-function MapEditor({ featureCollection, url, options, preview }) {
- const [cadastreEnabled, setCadastreEnabled] = useState(false);
- const [coords, setCoords] = useState([1.7, 46.9]);
- const [zoom, setZoom] = useState([5]);
- const {
- isSupported,
- error,
- inputs,
- onLoad,
- onStyleChange,
- onFileChange,
- drawRef,
- createFeatures,
- updateFeatures,
- deleteFeatures,
- addInputFile,
- removeInputFile
- } = useMapboxEditor(featureCollection, {
- url,
- enabled: !preview,
- cadastreEnabled
- });
- const { style, layers, setStyle, setLayerEnabled, setLayerOpacity } =
- useMapStyle(options.layers, {
- onStyleChange,
- cadastreEnabled
- });
-
- if (!isSupported) {
- return (
-
- Nous ne pouvons pas afficher notre éditeur de carte car il est
- imcompatible avec votre navigateur. Nous vous conseillons de le mettre à
- jour ou utiliser les dernières versions de Chrome, Firefox ou Safari
-
- );
- }
-
- return (
- <>
- {error && }
-
-
-
- Ajouter un fichier GPX ou KML
-
-
- {inputs.map((input) => (
-
- onFileChange(e, input.id)}
- />
- {input.hasValue && (
- removeInputFile(e, input.id)}
- >
- )}
-
- ))}
-
-
-
- {
- setCoords(coordinates);
- setZoom([17]);
- }}
- />
-
- onLoad(map)}
- center={coords}
- zoom={zoom}
- style={style}
- containerStyle={{ height: '500px' }}
- >
- {!cadastreEnabled && (
-
- )}
-
-
- {options.layers.includes('cadastres') && (
-
-
- setCadastreEnabled((cadastreEnabled) => !cadastreEnabled)
- }
- title="Sélectionner les parcelles cadastrales"
- className={cadastreEnabled ? 'on' : ''}
- >
-
-
-
- )}
-
-
- >
- );
-}
-
-function PointInput() {
- const inputId = useId();
- const [value, setValue] = useState('');
- const [feature, setFeature] = useState(null);
- const getCurrentPosition = () => {
- navigator.geolocation &&
- navigator.geolocation.getCurrentPosition(({ coords }) => {
- setValue(
- `${coords.latitude.toPrecision(6)}, ${coords.longitude.toPrecision(
- 6
- )}`
- );
- });
- };
- const addPoint = () => {
- if (feature) {
- fire(document, 'map:feature:create', feature);
- setValue('');
- setFeature(null);
- }
- };
-
- return (
- <>
-
- Ajouter un point sur la carte
-
-
- {navigator.geolocation ? (
-
- Localiser votre position
-
-
- ) : null}
-
{
- setValue(value);
- setFeature(
- dd.length
- ? {
- type: 'Feature',
- geometry: {
- type: 'Point',
- coordinates: dd.reverse()
- },
- properties: {}
- }
- : null
- );
- }}
- />
-
-
- Ajouter le point avec les coordonnées saisies sur la carte
-
-
-
-
- >
- );
-}
-
-MapEditor.propTypes = {
- featureCollection: PropTypes.shape({
- bbox: PropTypes.array,
- features: PropTypes.array
- }),
- url: PropTypes.string,
- preview: PropTypes.bool,
- options: PropTypes.shape({ layers: PropTypes.array })
-};
-
-export default MapEditor;
diff --git a/app/javascript/components/MapEditor/index.tsx b/app/javascript/components/MapEditor/index.tsx
new file mode 100644
index 000000000..a84094835
--- /dev/null
+++ b/app/javascript/components/MapEditor/index.tsx
@@ -0,0 +1,91 @@
+import React, { useState } from 'react';
+import { CursorClickIcon } from '@heroicons/react/outline';
+import 'maplibre-gl/dist/maplibre-gl.css';
+import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css';
+import type { FeatureCollection } from 'geojson';
+
+import { MapLibre } from '../shared/maplibre/MapLibre';
+import { useFeatureCollection } from './hooks';
+import { DrawLayer } from './components/DrawLayer';
+import { CadastreLayer } from './components/CadastreLayer';
+import { AddressInput } from './components/AddressInput';
+import { PointInput } from './components/PointInput';
+import { ImportFileInput } from './components/ImportFileInput';
+import { FlashMessage } from '../shared/FlashMessage';
+
+export default function MapEditor({
+ featureCollection: initialFeatureCollection,
+ url,
+ options,
+ preview
+}: {
+ featureCollection: FeatureCollection;
+ url: string;
+ preview: boolean;
+ options: { layers: string[] };
+}) {
+ const [cadastreEnabled, setCadastreEnabled] = useState(false);
+
+ const { featureCollection, error, ...actions } = useFeatureCollection(
+ initialFeatureCollection,
+ { url, enabled: !preview }
+ );
+
+ return (
+ <>
+
+ {error && }
+
+
+
+ >
+ }
+ footer={ }
+ >
+
+ {options.layers.includes('cadastres') ? (
+ <>
+
+
+
+ setCadastreEnabled((cadastreEnabled) => !cadastreEnabled)
+ }
+ title="Sélectionner les parcelles cadastrales"
+ className={cadastreEnabled ? 'on' : ''}
+ >
+
+
+
+ >
+ ) : null}
+
+ >
+ );
+}
diff --git a/app/javascript/components/MapEditor/readGeoFile.js b/app/javascript/components/MapEditor/readGeoFile.ts
similarity index 62%
rename from app/javascript/components/MapEditor/readGeoFile.js
rename to app/javascript/components/MapEditor/readGeoFile.ts
index 59f972f02..d9f66f10f 100644
--- a/app/javascript/components/MapEditor/readGeoFile.js
+++ b/app/javascript/components/MapEditor/readGeoFile.ts
@@ -1,29 +1,37 @@
import { gpx, kml } from '@tmcw/togeojson/dist/togeojson.es.js';
-import { generateId } from '../shared/mapbox/utils';
+import type { FeatureCollection, Feature } from 'geojson';
-export function readGeoFile(file) {
+import { generateId } from '../shared/maplibre/utils';
+
+export function readGeoFile(file: File) {
const isGpxFile = file.name.includes('.gpx');
const reader = new FileReader();
- return new Promise((resolve) => {
- reader.onload = (event) => {
- const xml = new DOMParser().parseFromString(
- event.target.result,
- 'text/xml'
- );
- const featureCollection = normalizeFeatureCollection(
- isGpxFile ? gpx(xml) : kml(xml),
- file.name
- );
+ return new Promise>(
+ (resolve) => {
+ reader.onload = (event: FileReaderEventMap['load']) => {
+ const result = event.target?.result;
+ const xml = new DOMParser().parseFromString(
+ result as string,
+ 'text/xml'
+ );
+ const featureCollection = normalizeFeatureCollection(
+ isGpxFile ? gpx(xml) : kml(xml),
+ file.name
+ );
- resolve(featureCollection);
- };
- reader.readAsText(file, 'UTF-8');
- });
+ resolve(featureCollection);
+ };
+ reader.readAsText(file, 'UTF-8');
+ }
+ );
}
-function normalizeFeatureCollection(featureCollection, filename) {
- const features = [];
+function normalizeFeatureCollection(
+ featureCollection: FeatureCollection,
+ filename: string
+) {
+ const features: Feature[] = [];
for (const feature of featureCollection.features) {
switch (feature.geometry.type) {
case 'MultiPoint':
@@ -76,13 +84,13 @@ function normalizeFeatureCollection(featureCollection, filename) {
}
}
- featureCollection.filename = `${generateId()}-${filename}`;
+ const featureCollectionFilename = `${generateId()}-${filename}`;
featureCollection.features = features.map((feature) => ({
...feature,
properties: {
...feature.properties,
- filename: featureCollection.filename
+ filename: featureCollectionFilename
}
}));
- return featureCollection;
+ return { ...featureCollection, filename: featureCollectionFilename };
}
diff --git a/app/javascript/components/MapEditor/useMapboxEditor.js b/app/javascript/components/MapEditor/useMapboxEditor.js
deleted file mode 100644
index bc875b344..000000000
--- a/app/javascript/components/MapEditor/useMapboxEditor.js
+++ /dev/null
@@ -1,548 +0,0 @@
-import { useState, useCallback, useRef, useEffect, useMemo } from 'react';
-import mapboxgl from 'mapbox-gl';
-import { getJSON, ajax, fire } from '@utils';
-
-import { readGeoFile } from './readGeoFile';
-import {
- filterFeatureCollection,
- generateId,
- findFeature,
- getBounds,
- defer
-} from '../shared/mapbox/utils';
-
-const SOURCE_SELECTION_UTILISATEUR = 'selection_utilisateur';
-const SOURCE_CADASTRE = 'cadastre';
-
-export function useMapboxEditor(
- featureCollection,
- { url, enabled = true, cadastreEnabled = true }
-) {
- const [isLoaded, setLoaded] = useState(false);
- const mapRef = useRef();
- const drawRef = useRef();
- const loadedRef = useRef(defer());
- const selectedCadastresRef = useRef(() => new Set());
- const isSupported = useMemo(() => mapboxgl.supported());
-
- useEffect(() => {
- const translations = [
- ['.mapbox-gl-draw_line', 'Tracer une ligne'],
- ['.mapbox-gl-draw_polygon', 'Dessiner un polygone'],
- ['.mapbox-gl-draw_point', 'Ajouter un point'],
- ['.mapbox-gl-draw_trash', 'Supprimer']
- ];
- for (const [selector, translation] of translations) {
- const element = document.querySelector(selector);
- if (element) {
- element.setAttribute('title', translation);
- }
- }
- }, [isLoaded]);
-
- const addEventListener = useCallback((eventName, target, callback) => {
- loadedRef.current.promise.then(() => {
- mapRef.current.on(eventName, target, callback);
- });
- return () => {
- if (mapRef.current) {
- mapRef.current.off(eventName, target, callback);
- }
- };
- }, []);
-
- const highlightFeature = useCallback((cid, highlight) => {
- if (highlight) {
- selectedCadastresRef.current.add(cid);
- } else {
- selectedCadastresRef.current.delete(cid);
- }
- if (selectedCadastresRef.current.size == 0) {
- mapRef.current.setFilter('parcelle-highlighted', ['in', 'id', '']);
- } else {
- mapRef.current.setFilter('parcelle-highlighted', [
- 'in',
- 'id',
- ...selectedCadastresRef.current
- ]);
- }
- }, []);
-
- const fitBounds = useCallback((bbox) => {
- mapRef.current.fitBounds(bbox, { padding: 100 });
- }, []);
-
- const hoverFeature = useCallback((feature, hover) => {
- if (!selectedCadastresRef.current.has(feature.properties.id)) {
- mapRef.current.setFeatureState(
- {
- source: 'cadastre',
- sourceLayer: 'parcelles',
- id: feature.id
- },
- { hover }
- );
- }
- }, []);
-
- const addFeatures = useCallback((features, external) => {
- for (const feature of features) {
- if (feature.lid) {
- drawRef.current?.draw.setFeatureProperty(
- feature.lid,
- 'id',
- feature.properties.id
- );
- delete feature.lid;
- }
- if (external) {
- if (feature.properties.source == SOURCE_SELECTION_UTILISATEUR) {
- drawRef.current?.draw.add({ id: feature.properties.id, ...feature });
- } else {
- highlightFeature(feature.properties.cid, true);
- }
- }
- }
- }, []);
-
- const removeFeatures = useCallback((features, external) => {
- if (external) {
- for (const feature of features) {
- if (feature.properties.source == SOURCE_SELECTION_UTILISATEUR) {
- drawRef.current?.draw.delete(feature.id);
- } else {
- highlightFeature(feature.properties.cid, false);
- }
- }
- }
- }, []);
-
- const onLoad = useCallback(
- (map) => {
- if (!mapRef.current) {
- mapRef.current = map;
- mapRef.current.fitBounds(props.featureCollection.bbox, {
- padding: 100
- });
- onStyleChange();
- setLoaded(true);
- loadedRef.current.resolve();
- }
- },
- [featureCollection]
- );
-
- const addEventListeners = useCallback((events) => {
- const unsubscribe = Object.entries(events).map(
- ([eventName, [target, callback]]) =>
- addEventListener(eventName, target, callback)
- );
- return () => unsubscribe.map((unsubscribe) => unsubscribe());
- }, []);
-
- const { createFeatures, updateFeatures, deleteFeatures, ...props } =
- useFeatureCollection(featureCollection, {
- url,
- enabled: isSupported && enabled,
- addFeatures,
- removeFeatures
- });
-
- const onStyleChange = useCallback(() => {
- if (mapRef.current) {
- const featureCollection = props.featureCollection;
- if (!cadastreEnabled) {
- drawRef.current?.draw.set(
- filterFeatureCollection(
- featureCollection,
- SOURCE_SELECTION_UTILISATEUR
- )
- );
- }
- selectedCadastresRef.current = new Set(
- filterFeatureCollection(
- featureCollection,
- SOURCE_CADASTRE
- ).features.map(({ properties }) => properties.cid)
- );
- if (selectedCadastresRef.current.size > 0) {
- mapRef.current.setFilter('parcelle-highlighted', [
- 'in',
- 'id',
- ...selectedCadastresRef.current
- ]);
- }
- }
- }, [props.featureCollection, cadastreEnabled]);
-
- useExternalEvents(props.featureCollection, {
- fitBounds,
- createFeatures,
- updateFeatures,
- deleteFeatures
- });
- useCadastres(props.featureCollection, {
- addEventListeners,
- hoverFeature,
- createFeatures,
- deleteFeatures,
- enabled: cadastreEnabled
- });
-
- return {
- isSupported,
- onLoad,
- onStyleChange,
- drawRef,
- createFeatures,
- updateFeatures,
- deleteFeatures,
- ...props,
- ...useImportFiles(props.featureCollection, {
- createFeatures,
- deleteFeatures
- })
- };
-}
-
-function useFeatureCollection(
- initialFeatureCollection,
- { url, addFeatures, removeFeatures, enabled = true }
-) {
- const [error, onError] = useError();
- const [featureCollection, setFeatureCollection] = useState(
- initialFeatureCollection
- );
- const updateFeatureCollection = useCallback(
- (callback) => {
- setFeatureCollection(({ features }) => ({
- type: 'FeatureCollection',
- features: callback(features)
- }));
- ajax({ url, type: 'GET' })
- .then(() => fire(document, 'ds:page:update'))
- .catch(() => {});
- },
- [setFeatureCollection]
- );
-
- const createFeatures = useCallback(
- async ({ features, source = SOURCE_SELECTION_UTILISATEUR, external }) => {
- if (!enabled) {
- return;
- }
- try {
- const newFeatures = [];
- for (const feature of features) {
- const data = await getJSON(url, { feature, source }, 'post');
- if (data) {
- if (source == SOURCE_SELECTION_UTILISATEUR) {
- data.feature.lid = feature.id;
- }
- newFeatures.push(data.feature);
- }
- }
- addFeatures(newFeatures, external);
- updateFeatureCollection(
- (features) => [...features, ...newFeatures],
- external
- );
- } catch (error) {
- console.error(error);
- onError('Le polygone dessiné n’est pas valide.');
- }
- },
- [enabled, url, updateFeatureCollection, addFeatures]
- );
-
- const updateFeatures = useCallback(
- async ({ features, source = SOURCE_SELECTION_UTILISATEUR, external }) => {
- if (!enabled) {
- return;
- }
- try {
- const newFeatures = [];
- for (const feature of features) {
- const { id } = feature.properties;
- if (id) {
- await getJSON(`${url}/${id}`, { feature }, 'patch');
- } else {
- const data = await getJSON(url, { feature, source }, 'post');
- if (data) {
- if (source == SOURCE_SELECTION_UTILISATEUR) {
- data.feature.lid = feature.id;
- }
- newFeatures.push(data.feature);
- }
- }
- }
- if (newFeatures.length > 0) {
- addFeatures(newFeatures, external);
- updateFeatureCollection((features) => [...features, ...newFeatures]);
- }
- } catch (error) {
- console.error(error);
- onError('Le polygone dessiné n’est pas valide.');
- }
- },
- [enabled, url, updateFeatureCollection, addFeatures]
- );
-
- const deleteFeatures = useCallback(
- async ({ features, external }) => {
- if (!enabled) {
- return;
- }
- try {
- const deletedFeatures = [];
- for (const feature of features) {
- const { id } = feature.properties;
- await getJSON(`${url}/${id}`, null, 'delete');
- deletedFeatures.push(feature);
- }
- removeFeatures(deletedFeatures, external);
- const deletedFeatureIds = deletedFeatures.map(
- ({ properties }) => properties.id
- );
- updateFeatureCollection(
- (features) =>
- features.filter(
- ({ properties }) => !deletedFeatureIds.includes(properties.id)
- ),
- external
- );
- } catch (error) {
- console.error(error);
- onError('Le polygone n’a pas pu être supprimé.');
- }
- },
- [enabled, url, updateFeatureCollection, removeFeatures]
- );
-
- return {
- featureCollection,
- createFeatures,
- updateFeatures,
- deleteFeatures,
- error
- };
-}
-
-function useImportFiles(featureCollection, { createFeatures, deleteFeatures }) {
- const [inputs, setInputs] = useState([]);
- const addInput = useCallback(
- (input) => {
- setInputs((inputs) => [...inputs, input]);
- },
- [setInputs]
- );
- const removeInput = useCallback(
- (inputId) => {
- setInputs((inputs) => inputs.filter((input) => input.id !== inputId));
- },
- [setInputs]
- );
-
- const onFileChange = useCallback(
- async (event, inputId) => {
- const { features, filename } = await readGeoFile(event.target.files[0]);
- createFeatures({ features, external: true });
- setInputs((inputs) => {
- return inputs.map((input) => {
- if (input.id === inputId) {
- return { ...input, disabled: true, hasValue: true, filename };
- }
- return input;
- });
- });
- },
- [setInputs, createFeatures, featureCollection]
- );
-
- const addInputFile = useCallback(
- (event) => {
- event.preventDefault();
- addInput({
- id: generateId(),
- disabled: false,
- hasValue: false,
- filename: ''
- });
- },
- [addInput]
- );
-
- const removeInputFile = useCallback(
- (event, inputId) => {
- event.preventDefault();
- const { filename } = inputs.find((input) => input.id === inputId);
- const features = featureCollection.features.filter(
- (feature) => feature.properties.filename == filename
- );
- deleteFeatures({ features, external: true });
- removeInput(inputId);
- },
- [removeInput, deleteFeatures, featureCollection]
- );
-
- return {
- inputs,
- onFileChange,
- addInputFile,
- removeInputFile
- };
-}
-
-function useExternalEvents(
- featureCollection,
- { fitBounds, createFeatures, updateFeatures, deleteFeatures }
-) {
- const onFeatureFocus = useCallback(
- ({ detail }) => {
- const { id, bbox } = detail;
- if (id) {
- const feature = findFeature(featureCollection, id);
- if (feature) {
- fitBounds(getBounds(feature.geometry));
- }
- } else if (bbox) {
- fitBounds(bbox);
- }
- },
- [featureCollection, fitBounds]
- );
-
- const onFeatureCreate = useCallback(
- ({ detail }) => {
- const { geometry, properties } = detail;
-
- if (geometry) {
- createFeatures({
- features: [{ geometry, properties }],
- external: true
- });
- }
- },
- [createFeatures]
- );
-
- const onFeatureUpdate = useCallback(
- ({ detail }) => {
- const { id, properties } = detail;
- const feature = findFeature(featureCollection, id);
-
- if (feature) {
- feature.properties = { ...feature.properties, ...properties };
- updateFeatures({ features: [feature], external: true });
- }
- },
- [featureCollection, updateFeatures]
- );
-
- const onFeatureDelete = useCallback(
- ({ detail }) => {
- const { id } = detail;
- const feature = findFeature(featureCollection, id);
-
- if (feature) {
- deleteFeatures({ features: [feature], external: true });
- }
- },
- [featureCollection, deleteFeatures]
- );
-
- useEvent('map:feature:focus', onFeatureFocus);
- useEvent('map:feature:create', onFeatureCreate);
- useEvent('map:feature:update', onFeatureUpdate);
- useEvent('map:feature:delete', onFeatureDelete);
-}
-
-function useCadastres(
- featureCollection,
- {
- addEventListeners,
- hoverFeature,
- createFeatures,
- deleteFeatures,
- enabled = true
- }
-) {
- const hoveredFeature = useRef();
-
- const onMouseMove = useCallback(
- (event) => {
- if (event.features.length > 0) {
- const feature = event.features[0];
- if (hoveredFeature.current?.id != feature.id) {
- if (hoveredFeature.current) {
- hoverFeature(hoveredFeature.current, false);
- }
- hoveredFeature.current = feature;
- hoverFeature(feature, true);
- }
- }
- },
- [hoverFeature]
- );
-
- const onMouseLeave = useCallback(() => {
- if (hoveredFeature.current) {
- hoverFeature(hoveredFeature.current, false);
- }
- hoveredFeature.current = null;
- }, [hoverFeature]);
-
- const onClick = useCallback(
- async (event) => {
- if (event.features.length > 0) {
- const currentId = event.features[0].properties.id;
- const feature = findFeature(
- filterFeatureCollection(featureCollection, SOURCE_CADASTRE),
- currentId,
- 'cid'
- );
- if (feature) {
- deleteFeatures({
- features: [feature],
- source: SOURCE_CADASTRE,
- external: true
- });
- } else {
- createFeatures({
- features: event.features,
- source: SOURCE_CADASTRE,
- external: true
- });
- }
- }
- },
- [featureCollection, createFeatures, deleteFeatures]
- );
-
- useEffect(() => {
- if (enabled) {
- return addEventListeners({
- click: ['parcelles-fill', onClick],
- mousemove: ['parcelles-fill', onMouseMove],
- mouseleave: ['parcelles-fill', onMouseLeave]
- });
- }
- }, [onClick, onMouseMove, onMouseLeave, enabled]);
-}
-
-function useError() {
- const [error, onError] = useState();
- useEffect(() => {
- const timer = setTimeout(() => onError(null), 5000);
- return () => clearTimeout(timer);
- }, [error]);
-
- return [error, onError];
-}
-
-export function useEvent(eventName, callback) {
- return useEffect(() => {
- addEventListener(eventName, callback);
- return () => removeEventListener(eventName, callback);
- }, [eventName, callback]);
-}
diff --git a/app/javascript/components/MapReader/components/CadastreLayer.tsx b/app/javascript/components/MapReader/components/CadastreLayer.tsx
new file mode 100644
index 000000000..243610eb1
--- /dev/null
+++ b/app/javascript/components/MapReader/components/CadastreLayer.tsx
@@ -0,0 +1,32 @@
+import { useRef } from 'react';
+import type { FeatureCollection } from 'geojson';
+
+import { useMapLibre } from '../../shared/maplibre/MapLibre';
+import { useMapEvent } from '../../shared/maplibre/hooks';
+import { filterFeatureCollection } from '../../shared/maplibre/utils';
+
+export function CadastreLayer({
+ featureCollection
+}: {
+ featureCollection: FeatureCollection;
+}) {
+ const map = useMapLibre();
+ const selectedCadastresRef = useRef>();
+
+ useMapEvent('styledata', () => {
+ selectedCadastresRef.current = new Set(
+ filterFeatureCollection(featureCollection, 'cadastre').features.map(
+ ({ properties }) => properties?.cid
+ )
+ );
+ if (selectedCadastresRef.current.size > 0) {
+ map.setFilter('parcelle-highlighted', [
+ 'in',
+ 'id',
+ ...selectedCadastresRef.current
+ ]);
+ }
+ });
+
+ return null;
+}
diff --git a/app/javascript/components/MapReader/components/GeoJSONLayer.tsx b/app/javascript/components/MapReader/components/GeoJSONLayer.tsx
new file mode 100644
index 000000000..956a71fc4
--- /dev/null
+++ b/app/javascript/components/MapReader/components/GeoJSONLayer.tsx
@@ -0,0 +1,237 @@
+import React, { useCallback, useEffect, useMemo } from 'react';
+import { Popup, LngLatBoundsLike } from 'maplibre-gl';
+import type { Feature, FeatureCollection } from 'geojson';
+
+import { useMapLibre } from '../../shared/maplibre/MapLibre';
+import {
+ useFitBounds,
+ useEvent,
+ EventHandler,
+ useMapEvent
+} from '../../shared/maplibre/hooks';
+import {
+ filterFeatureCollection,
+ findFeature,
+ getBounds,
+ getCenter,
+ filterFeatureCollectionByGeometryType
+} from '../../shared/maplibre/utils';
+
+export function GeoJSONLayer({
+ featureCollection
+}: {
+ featureCollection: FeatureCollection;
+}) {
+ const map = useMapLibre();
+ const popup = useMemo(
+ () =>
+ new Popup({
+ closeButton: false,
+ closeOnClick: false
+ }),
+ []
+ );
+
+ const onMouseEnter = useCallback(
+ (event) => {
+ const feature = event.features && event.features[0];
+ if (feature?.properties && feature.properties.description) {
+ const coordinates = getCenter(feature.geometry, event.lngLat);
+ const description = feature.properties.description;
+ map.getCanvas().style.cursor = 'pointer';
+ popup.setLngLat(coordinates).setHTML(description).addTo(map);
+ } else {
+ popup.remove();
+ }
+ },
+ [popup]
+ );
+
+ const onMouseLeave = useCallback(() => {
+ map.getCanvas().style.cursor = '';
+ popup.remove();
+ }, [popup]);
+
+ useExternalEvents(featureCollection);
+
+ const polygons = filterFeatureCollectionByGeometryType(
+ filterFeatureCollection(featureCollection, 'selection_utilisateur'),
+ 'Polygon'
+ );
+ const lines = filterFeatureCollectionByGeometryType(
+ filterFeatureCollection(featureCollection, 'selection_utilisateur'),
+ 'LineString'
+ );
+ const points = filterFeatureCollectionByGeometryType(
+ filterFeatureCollection(featureCollection, 'selection_utilisateur'),
+ 'Point'
+ );
+
+ return (
+ <>
+ {polygons.features.map((feature) => (
+
+ ))}
+ {lines.features.map((feature) => (
+
+ ))}
+ {points.features.map((feature) => (
+
+ ))}
+ >
+ );
+}
+
+function useExternalEvents(featureCollection: FeatureCollection) {
+ const fitBounds = useFitBounds();
+ const onFeatureFocus = useCallback(({ detail }) => {
+ const { id } = detail;
+ const feature = findFeature(featureCollection, id);
+ if (feature) {
+ fitBounds(getBounds(feature.geometry));
+ }
+ }, []);
+
+ useEffect(() => {
+ fitBounds(featureCollection.bbox as LngLatBoundsLike);
+ }, []);
+
+ useEvent('map:feature:focus', onFeatureFocus);
+}
+
+function LineStringLayer({
+ feature,
+ onMouseEnter,
+ onMouseLeave
+}: {
+ feature: Feature;
+ onMouseEnter: EventHandler;
+ onMouseLeave: EventHandler;
+}) {
+ const map = useMapLibre();
+ const sourceId = String(feature.properties?.id);
+ const layerId = `${sourceId}-layer`;
+
+ useEffect(() => {
+ map
+ .addSource(sourceId, {
+ type: 'geojson',
+ data: feature
+ })
+ .addLayer({
+ id: layerId,
+ source: sourceId,
+ type: 'line',
+ paint: lineStringSelectionLine
+ });
+ }, []);
+
+ useMapEvent('mouseenter', onMouseEnter, layerId);
+ useMapEvent('mouseleave', onMouseLeave, layerId);
+
+ return null;
+}
+
+function PointLayer({
+ feature,
+ onMouseEnter,
+ onMouseLeave
+}: {
+ feature: Feature;
+ onMouseEnter: EventHandler;
+ onMouseLeave: EventHandler;
+}) {
+ const map = useMapLibre();
+ const sourceId = String(feature.properties?.id);
+ const layerId = `${sourceId}-layer`;
+
+ useEffect(() => {
+ map
+ .addSource(sourceId, {
+ type: 'geojson',
+ data: feature
+ })
+ .addLayer({
+ id: layerId,
+ source: sourceId,
+ type: 'circle',
+ paint: pointSelectionCircle
+ });
+ }, []);
+
+ useMapEvent('mouseenter', onMouseEnter, layerId);
+ useMapEvent('mouseleave', onMouseLeave, layerId);
+
+ return null;
+}
+
+function PolygonLayer({
+ feature,
+ onMouseEnter,
+ onMouseLeave
+}: {
+ feature: Feature;
+ onMouseEnter: EventHandler;
+ onMouseLeave: EventHandler;
+}) {
+ const map = useMapLibre();
+ const sourceId = String(feature.properties?.id);
+ const layerId = `${sourceId}-layer`;
+ const lineLayerId = `${sourceId}-line-layer`;
+
+ useEffect(() => {
+ map
+ .addSource(sourceId, {
+ type: 'geojson',
+ data: feature
+ })
+ .addLayer({
+ id: lineLayerId,
+ source: sourceId,
+ type: 'line',
+ paint: polygonSelectionLine
+ })
+ .addLayer({
+ id: layerId,
+ source: sourceId,
+ type: 'fill',
+ paint: polygonSelectionFill
+ });
+ }, []);
+
+ useMapEvent('mouseenter', onMouseEnter, layerId);
+ useMapEvent('mouseleave', onMouseLeave, layerId);
+
+ return null;
+}
+
+const polygonSelectionFill = {
+ 'fill-color': '#EC3323',
+ 'fill-opacity': 0.5
+};
+const polygonSelectionLine = {
+ 'line-color': 'rgba(255, 0, 0, 1)',
+ 'line-width': 4
+};
+const lineStringSelectionLine = {
+ 'line-color': 'rgba(55, 42, 127, 1.00)',
+ 'line-width': 3
+};
+const pointSelectionCircle = {
+ 'circle-color': '#EC3323'
+};
diff --git a/app/javascript/components/MapReader/index.jsx b/app/javascript/components/MapReader/index.jsx
deleted file mode 100644
index b20dde570..000000000
--- a/app/javascript/components/MapReader/index.jsx
+++ /dev/null
@@ -1,190 +0,0 @@
-import React, { useMemo } from 'react';
-import ReactMapboxGl, { ZoomControl, GeoJSONLayer } from 'react-mapbox-gl';
-import PropTypes from 'prop-types';
-import 'mapbox-gl/dist/mapbox-gl.css';
-
-import MapStyleControl, { useMapStyle } from '../shared/mapbox/MapStyleControl';
-import {
- filterFeatureCollection,
- filterFeatureCollectionByGeometryType
-} from '../shared/mapbox/utils';
-import { useMapbox } from './useMapbox';
-
-const Mapbox = ReactMapboxGl({});
-
-const MapReader = ({ featureCollection, options }) => {
- const { isSupported, onLoad, onStyleChange, onMouseEnter, onMouseLeave } =
- useMapbox(featureCollection);
- const { style, layers, setStyle, setLayerEnabled, setLayerOpacity } =
- useMapStyle(options.layers, { onStyleChange });
-
- if (!isSupported) {
- return (
-
- Nous ne pouvons pas afficher la carte car elle est imcompatible avec
- votre navigateur. Nous vous conseillons de le mettre à jour ou utiliser
- les dernières versions de Chrome, Firefox ou Safari
-
- );
- }
-
- return (
- onLoad(map)}
- style={style}
- containerStyle={{ height: '500px' }}
- >
-
-
-
-
-
-
-
- );
-};
-
-const polygonSelectionFill = {
- 'fill-color': '#EC3323',
- 'fill-opacity': 0.5
-};
-const polygonSelectionLine = {
- 'line-color': 'rgba(255, 0, 0, 1)',
- 'line-width': 4
-};
-const lineStringSelectionLine = {
- 'line-color': 'rgba(55, 42, 127, 1.00)',
- 'line-width': 3
-};
-const pointSelectionFill = {
- 'circle-color': '#EC3323'
-};
-
-function SelectionUtilisateurPolygonLayer({
- featureCollection,
- onMouseEnter,
- onMouseLeave
-}) {
- const data = useMemo(
- () =>
- filterFeatureCollectionByGeometryType(
- filterFeatureCollection(featureCollection, 'selection_utilisateur'),
- 'Polygon'
- ),
- [featureCollection]
- );
-
- return (
-
- );
-}
-
-function SelectionUtilisateurLineLayer({
- featureCollection,
- onMouseEnter,
- onMouseLeave
-}) {
- const data = useMemo(
- () =>
- filterFeatureCollectionByGeometryType(
- filterFeatureCollection(featureCollection, 'selection_utilisateur'),
- 'LineString'
- ),
- [featureCollection]
- );
- return (
-
- );
-}
-
-function SelectionUtilisateurPointLayer({
- featureCollection,
- onMouseEnter,
- onMouseLeave
-}) {
- const data = useMemo(
- () =>
- filterFeatureCollectionByGeometryType(
- filterFeatureCollection(featureCollection, 'selection_utilisateur'),
- 'Point'
- ),
- [featureCollection]
- );
- return (
-
- );
-}
-
-SelectionUtilisateurPolygonLayer.propTypes = {
- featureCollection: PropTypes.shape({
- type: PropTypes.string,
- bbox: PropTypes.array,
- features: PropTypes.array
- }),
- onMouseEnter: PropTypes.func,
- onMouseLeave: PropTypes.func
-};
-
-SelectionUtilisateurLineLayer.propTypes = {
- featureCollection: PropTypes.shape({
- type: PropTypes.string,
- bbox: PropTypes.array,
- features: PropTypes.array
- }),
- onMouseEnter: PropTypes.func,
- onMouseLeave: PropTypes.func
-};
-
-SelectionUtilisateurPointLayer.propTypes = {
- featureCollection: PropTypes.shape({
- type: PropTypes.string,
- bbox: PropTypes.array,
- features: PropTypes.array
- }),
- onMouseEnter: PropTypes.func,
- onMouseLeave: PropTypes.func
-};
-
-MapReader.propTypes = {
- featureCollection: PropTypes.shape({
- bbox: PropTypes.array,
- features: PropTypes.array
- }),
- options: PropTypes.shape({ layers: PropTypes.array })
-};
-
-export default MapReader;
diff --git a/app/javascript/components/MapReader/index.tsx b/app/javascript/components/MapReader/index.tsx
new file mode 100644
index 000000000..1f9fb07d4
--- /dev/null
+++ b/app/javascript/components/MapReader/index.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import 'maplibre-gl/dist/maplibre-gl.css';
+import type { FeatureCollection } from 'geojson';
+
+import { MapLibre } from '../shared/maplibre/MapLibre';
+import { CadastreLayer } from './components/CadastreLayer';
+import { GeoJSONLayer } from './components/GeoJSONLayer';
+
+const MapReader = ({
+ featureCollection,
+ options
+}: {
+ featureCollection: FeatureCollection;
+ options: { layers: string[] };
+}) => {
+ return (
+
+
+
+
+ );
+};
+
+export default MapReader;
diff --git a/app/javascript/components/MapReader/useMapbox.js b/app/javascript/components/MapReader/useMapbox.js
deleted file mode 100644
index d58b4e463..000000000
--- a/app/javascript/components/MapReader/useMapbox.js
+++ /dev/null
@@ -1,104 +0,0 @@
-import { useCallback, useRef, useEffect, useMemo } from 'react';
-import mapboxgl, { Popup } from 'mapbox-gl';
-
-import {
- filterFeatureCollection,
- findFeature,
- getBounds,
- getCenter
-} from '../shared/mapbox/utils';
-
-const SOURCE_CADASTRE = 'cadastre';
-
-export function useMapbox(featureCollection) {
- const mapRef = useRef();
- const selectedCadastresRef = useRef(() => new Set());
- const isSupported = useMemo(() => mapboxgl.supported());
-
- const fitBounds = useCallback((bbox) => {
- mapRef.current.fitBounds(bbox, { padding: 100 });
- }, []);
-
- const onLoad = useCallback(
- (map) => {
- if (!mapRef.current) {
- mapRef.current = map;
- mapRef.current.fitBounds(featureCollection.bbox, { padding: 100 });
- onStyleChange();
- }
- },
- [featureCollection]
- );
-
- const onStyleChange = useCallback(() => {
- if (mapRef.current) {
- selectedCadastresRef.current = new Set(
- filterFeatureCollection(
- featureCollection,
- SOURCE_CADASTRE
- ).features.map(({ properties }) => properties.cid)
- );
- if (selectedCadastresRef.current.size > 0) {
- mapRef.current.setFilter('parcelle-highlighted', [
- 'in',
- 'id',
- ...selectedCadastresRef.current
- ]);
- }
- }
- }, [featureCollection]);
-
- const popup = useMemo(
- () =>
- new Popup({
- closeButton: false,
- closeOnClick: false
- })
- );
-
- const onMouseEnter = useCallback(
- (event) => {
- const feature = event.features[0];
- if (feature.properties && feature.properties.description) {
- const coordinates = getCenter(feature.geometry, event.lngLat);
- const description = feature.properties.description;
- mapRef.current.getCanvas().style.cursor = 'pointer';
- popup.setLngLat(coordinates).setHTML(description).addTo(mapRef.current);
- } else {
- popup.remove();
- }
- },
- [popup]
- );
-
- const onMouseLeave = useCallback(() => {
- mapRef.current.getCanvas().style.cursor = '';
- popup.remove();
- }, [popup]);
-
- useExternalEvents(featureCollection, { fitBounds });
-
- return { isSupported, onLoad, onStyleChange, onMouseEnter, onMouseLeave };
-}
-
-function useExternalEvents(featureCollection, { fitBounds }) {
- const onFeatureFocus = useCallback(
- ({ detail }) => {
- const { id } = detail;
- const feature = findFeature(featureCollection, id);
- if (feature) {
- fitBounds(getBounds(feature.geometry));
- }
- },
- [featureCollection, fitBounds]
- );
-
- useEvent('map:feature:focus', onFeatureFocus);
-}
-
-export function useEvent(eventName, callback) {
- return useEffect(() => {
- addEventListener(eventName, callback);
- return () => removeEventListener(eventName, callback);
- }, [eventName, callback]);
-}
diff --git a/app/javascript/components/TypesDeChampEditor/components/TypeDeChamp.jsx b/app/javascript/components/TypesDeChampEditor/components/TypeDeChamp.jsx
index bce4e7103..68a83fc9c 100644
--- a/app/javascript/components/TypesDeChampEditor/components/TypeDeChamp.jsx
+++ b/app/javascript/components/TypesDeChampEditor/components/TypeDeChamp.jsx
@@ -82,7 +82,7 @@ const TypeDeChamp = sortableElement(
}}
>
- Supprimer
+ Supprimer
diff --git a/app/javascript/components/shared/FlashMessage.jsx b/app/javascript/components/shared/FlashMessage.tsx
similarity index 57%
rename from app/javascript/components/shared/FlashMessage.jsx
rename to app/javascript/components/shared/FlashMessage.tsx
index 027b1c709..6a092ff60 100644
--- a/app/javascript/components/shared/FlashMessage.jsx
+++ b/app/javascript/components/shared/FlashMessage.tsx
@@ -1,17 +1,26 @@
import React from 'react';
import { createPortal } from 'react-dom';
-import PropTypes from 'prop-types';
-export function FlashMessage({ message, level, sticky, fixed }) {
+export function FlashMessage({
+ message,
+ level,
+ sticky,
+ fixed
+}: {
+ message: string;
+ level: string;
+ sticky?: boolean;
+ fixed?: boolean;
+}) {
return createPortal(
,
- document.getElementById('flash_messages')
+ document.getElementById('flash_messages')!
);
}
-function flashClassName(level, sticky = false, fixed = false) {
+function flashClassName(level: string, sticky = false, fixed = false) {
const className =
level == 'notice' ? ['alert', 'alert-success'] : ['alert', 'alert-danger'];
@@ -23,10 +32,3 @@ function flashClassName(level, sticky = false, fixed = false) {
}
return className.join(' ');
}
-
-FlashMessage.propTypes = {
- message: PropTypes.string,
- level: PropTypes.string,
- sticky: PropTypes.bool,
- fixed: PropTypes.bool
-};
diff --git a/app/javascript/components/shared/hooks.js b/app/javascript/components/shared/hooks.ts
similarity index 65%
rename from app/javascript/components/shared/hooks.js
rename to app/javascript/components/shared/hooks.ts
index a07191d75..483d5477f 100644
--- a/app/javascript/components/shared/hooks.js
+++ b/app/javascript/components/shared/hooks.ts
@@ -1,15 +1,18 @@
import { useRef, useCallback, useMemo, useState } from 'react';
import { fire } from '@utils';
-export function useDeferredSubmit(input) {
+export function useDeferredSubmit(input?: HTMLInputElement): {
+ (callback: () => void): void;
+ done: () => void;
+} {
const calledRef = useRef(false);
const awaitFormSubmit = useCallback(
- (callback) => {
+ (callback: () => void) => {
const form = input?.form;
if (!form) {
return;
}
- const interceptFormSubmit = (event) => {
+ const interceptFormSubmit = (event: Event) => {
event.preventDefault();
runCallback();
form.submit();
@@ -27,17 +30,24 @@ export function useDeferredSubmit(input) {
},
[input]
);
- awaitFormSubmit.done = () => {
+ const done = () => {
calledRef.current = true;
};
- return awaitFormSubmit;
+ return Object.assign(awaitFormSubmit, { done });
}
-export function groupId(id) {
+export function groupId(id: string) {
return `#champ-${id.replace(/-input$/, '')}`;
}
-export function useHiddenField(group, name = 'value') {
+export function useHiddenField(
+ group?: string,
+ name = 'value'
+): [
+ value: string | undefined,
+ setValue: (value: string) => void,
+ input: HTMLInputElement | undefined
+] {
const hiddenField = useMemo(
() => selectInputInGroup(group, name),
[group, name]
@@ -53,13 +63,16 @@ export function useHiddenField(group, name = 'value') {
fire(hiddenField, 'autosave:trigger');
}
},
- hiddenField
+ hiddenField ?? undefined
];
}
-function selectInputInGroup(group, name) {
+function selectInputInGroup(
+ group: string | undefined,
+ name: string
+): HTMLInputElement | undefined | null {
if (group) {
- return document.querySelector(
+ return document.querySelector(
`${group} input[type="hidden"][name$="[${name}]"], ${group} input[type="hidden"][name="${name}"]`
);
}
diff --git a/app/javascript/components/shared/mapbox/utils.js b/app/javascript/components/shared/mapbox/utils.js
deleted file mode 100644
index 721a0bfb5..000000000
--- a/app/javascript/components/shared/mapbox/utils.js
+++ /dev/null
@@ -1,72 +0,0 @@
-import { LngLatBounds } from 'mapbox-gl';
-
-export function getBounds(geometry) {
- const bbox = new LngLatBounds();
-
- if (geometry.type === 'Point') {
- return [geometry.coordinates, geometry.coordinates];
- } else if (geometry.type === 'LineString') {
- for (const coordinate of geometry.coordinates) {
- bbox.extend(coordinate);
- }
- } else {
- for (const coordinate of geometry.coordinates[0]) {
- bbox.extend(coordinate);
- }
- }
- return bbox;
-}
-
-export function findFeature(featureCollection, value, property = 'id') {
- return featureCollection.features.find(
- (feature) => feature.properties[property] === value
- );
-}
-
-export function filterFeatureCollection(featureCollection, source) {
- return {
- type: 'FeatureCollection',
- features: featureCollection.features.filter(
- (feature) => feature.properties.source === source
- )
- };
-}
-
-export function filterFeatureCollectionByGeometryType(featureCollection, type) {
- return {
- type: 'FeatureCollection',
- features: featureCollection.features.filter(
- (feature) => feature.geometry.type === type
- )
- };
-}
-
-export function generateId() {
- return Math.random().toString(20).substr(2, 6);
-}
-
-export function getCenter(geometry, lngLat) {
- const bbox = new LngLatBounds();
-
- switch (geometry.type) {
- case 'Point':
- return [...geometry.coordinates];
- case 'LineString':
- return [lngLat.lng, lngLat.lat];
- default:
- for (const coordinate of geometry.coordinates[0]) {
- bbox.extend(coordinate);
- }
- return bbox.getCenter();
- }
-}
-
-export function defer() {
- const deferred = {};
- const promise = new Promise(function (resolve, reject) {
- deferred.resolve = resolve;
- deferred.reject = reject;
- });
- deferred.promise = promise;
- return deferred;
-}
diff --git a/app/javascript/components/shared/maplibre/MapLibre.tsx b/app/javascript/components/shared/maplibre/MapLibre.tsx
new file mode 100644
index 000000000..b123733b2
--- /dev/null
+++ b/app/javascript/components/shared/maplibre/MapLibre.tsx
@@ -0,0 +1,104 @@
+import React, {
+ useState,
+ useContext,
+ useRef,
+ useEffect,
+ useMemo,
+ ReactNode,
+ createContext
+} from 'react';
+import maplibre, { Map, Style, NavigationControl } from 'maplibre-gl';
+
+import invariant from 'tiny-invariant';
+
+import { useStyle } from './hooks';
+import { StyleControl } from './StyleControl';
+
+const Context = createContext<{ map?: Map | null }>({});
+
+type MapLibreProps = {
+ layers: string[];
+ header?: ReactNode;
+ footer?: ReactNode;
+ children: ReactNode;
+};
+
+export function useMapLibre() {
+ const context = useContext(Context);
+ invariant(context.map, 'Maplibre not initialized');
+ return context.map;
+}
+
+export function MapLibre({ children, header, footer, layers }: MapLibreProps) {
+ const isSupported = useMemo(
+ () => maplibre.supported({ failIfMajorPerformanceCaveat: true }) && !isIE(),
+ []
+ );
+ const containerRef = useRef(null);
+ const [map, setMap] = useState();
+
+ const onStyleChange = (style: Style) => {
+ if (map) {
+ map.setStyle(style);
+ }
+ };
+ const { style, ...mapStyleProps } = useStyle(layers, onStyleChange);
+
+ useEffect(() => {
+ if (isSupported && !map) {
+ invariant(containerRef.current, 'Map container not found');
+ const map = new Map({
+ container: containerRef.current,
+ style
+ });
+ map.addControl(new NavigationControl({}), 'top-right');
+ map.on('load', () => {
+ setMap(map);
+ });
+ }
+ }, []);
+
+ if (!isSupported) {
+ return (
+
+
+
⚠️
+
+ Nous ne pouvons pas afficher la carte car elle est imcompatible avec
+ votre navigateur. Nous vous conseillons de le mettre à jour ou
+ d’utiliser{' '}
+
+ un navigateur plus récent
+
+ .
+
+
+
+ );
+ }
+
+ return (
+
+ {map ? header : null}
+
+
+ {map ? children : null}
+
+ {map ? footer : null}
+
+ );
+}
+
+function isIE() {
+ const ua = window.navigator.userAgent;
+ const msie = ua.indexOf('MSIE ');
+ const trident = ua.indexOf('Trident/');
+ return msie > 0 || trident > 0;
+}
diff --git a/app/javascript/components/shared/mapbox/MapStyleControl.jsx b/app/javascript/components/shared/maplibre/StyleControl.tsx
similarity index 64%
rename from app/javascript/components/shared/mapbox/MapStyleControl.jsx
rename to app/javascript/components/shared/maplibre/StyleControl.tsx
index f6bc98a04..d959e41d4 100644
--- a/app/javascript/components/shared/mapbox/MapStyleControl.jsx
+++ b/app/javascript/components/shared/maplibre/StyleControl.tsx
@@ -1,5 +1,4 @@
-import React, { useMemo, useState, useEffect } from 'react';
-import PropTypes from 'prop-types';
+import React, { useState } from 'react';
import { Popover, RadioGroup } from '@headlessui/react';
import { usePopper } from 'react-popper';
import { MapIcon } from '@heroicons/react/outline';
@@ -7,7 +6,7 @@ import { Slider } from '@reach/slider';
import { useId } from '@reach/auto-id';
import '@reach/slider/styles.css';
-import { getMapStyle, getLayerName, NBS } from './styles';
+import { LayersMap, NBS } from './styles';
const STYLES = {
ortho: 'Satellite',
@@ -15,68 +14,22 @@ const STYLES = {
ign: 'Carte IGN'
};
-function optionalLayersMap(optionalLayers) {
- return Object.fromEntries(
- optionalLayers.map((layer) => [
- layer,
- {
- configurable: layer != 'cadastres',
- enabled: true,
- opacity: 70,
- name: getLayerName(layer)
- }
- ])
- );
-}
-
-export function useMapStyle(
- optionalLayers,
- { onStyleChange, cadastreEnabled }
-) {
- const [styleId, setStyle] = useState('ortho');
- const [layers, setLayers] = useState(() => optionalLayersMap(optionalLayers));
- const setLayerEnabled = (layer, enabled) =>
- setLayers((optionalLayers) => {
- optionalLayers[layer].enabled = enabled;
- return { ...optionalLayers };
- });
- const setLayerOpacity = (layer, opacity) =>
- setLayers((optionalLayers) => {
- optionalLayers[layer].opacity = opacity;
- return { ...optionalLayers };
- });
- const enabledLayers = Object.entries(layers).filter(
- ([, { enabled }]) => enabled
- );
- const layerIds = enabledLayers.map(
- ([layer, { opacity }]) => `${layer}-${opacity}`
- );
- const style = useMemo(
- () =>
- getMapStyle(
- styleId,
- enabledLayers.map(([layer]) => layer),
- Object.fromEntries(
- enabledLayers.map(([layer, { opacity }]) => [layer, opacity])
- )
- ),
- [styleId, layerIds]
- );
-
- useEffect(() => onStyleChange(), [styleId, layerIds, cadastreEnabled]);
-
- return { style, layers, setStyle, setLayerEnabled, setLayerOpacity };
-}
-
-function MapStyleControl({
- style,
+export function StyleControl({
+ styleId,
layers,
setStyle,
setLayerEnabled,
setLayerOpacity
+}: {
+ styleId: string;
+ setStyle: (style: string) => void;
+ layers: LayersMap;
+ setLayerEnabled: (layer: string, enabled: boolean) => void;
+ setLayerOpacity: (layer: string, opacity: number) => void;
}) {
- const [buttonElement, setButtonElement] = useState();
- const [panelElement, setPanelElement] = useState();
+ const [buttonElement, setButtonElement] =
+ useState();
+ const [panelElement, setPanelElement] = useState();
const { styles, attributes } = usePopper(buttonElement, panelElement, {
placement: 'bottom-end'
});
@@ -86,7 +39,10 @@ function MapStyleControl({
const mapId = useId();
return (
-
+
);
}
-
-MapStyleControl.propTypes = {
- style: PropTypes.string,
- layers: PropTypes.object,
- setStyle: PropTypes.func,
- setLayerEnabled: PropTypes.func,
- setLayerOpacity: PropTypes.func
-};
-
-export default MapStyleControl;
diff --git a/app/javascript/components/shared/maplibre/hooks.ts b/app/javascript/components/shared/maplibre/hooks.ts
new file mode 100644
index 000000000..dc6aaef97
--- /dev/null
+++ b/app/javascript/components/shared/maplibre/hooks.ts
@@ -0,0 +1,110 @@
+import { useCallback, useEffect, useState, useMemo } from 'react';
+import type {
+ LngLatBoundsLike,
+ LngLat,
+ MapLayerEventType,
+ Style
+} from 'maplibre-gl';
+import type { Feature, Geometry } from 'geojson';
+
+import { getMapStyle, getLayerName, LayersMap } from './styles';
+import { useMapLibre } from './MapLibre';
+
+export function useFitBounds() {
+ const map = useMapLibre();
+ return useCallback((bbox: LngLatBoundsLike) => {
+ map.fitBounds(bbox, { padding: 100 });
+ }, []);
+}
+
+export function useFlyTo() {
+ const map = useMapLibre();
+ return useCallback((zoom: number, center: [number, number]) => {
+ map.flyTo({ zoom, center });
+ }, []);
+}
+
+export function useEvent(eventName: string, callback: EventListener) {
+ return useEffect(() => {
+ addEventListener(eventName, callback);
+ return () => removeEventListener(eventName, callback);
+ }, [eventName, callback]);
+}
+
+export type EventHandler = (event: {
+ features: Feature[];
+ lngLat: LngLat;
+}) => void;
+
+export function useMapEvent(
+ eventName: string,
+ callback: EventHandler,
+ target?: string
+) {
+ const map = useMapLibre();
+ return useEffect(() => {
+ if (target) {
+ map.on(eventName as keyof MapLayerEventType, target, callback as any);
+ } else {
+ map.on(eventName, callback);
+ }
+ return () => {
+ if (target) {
+ map.off(eventName as keyof MapLayerEventType, target, callback as any);
+ } else {
+ map.off(eventName, callback);
+ }
+ };
+ }, [map, eventName, target, callback]);
+}
+
+function optionalLayersMap(optionalLayers: string[]): LayersMap {
+ return Object.fromEntries(
+ optionalLayers.map((layer) => [
+ layer,
+ {
+ configurable: layer != 'cadastres',
+ enabled: true,
+ opacity: 70,
+ name: getLayerName(layer)
+ }
+ ])
+ );
+}
+
+export function useStyle(
+ optionalLayers: string[],
+ onStyleChange: (style: Style) => void
+) {
+ const [styleId, setStyle] = useState('ortho');
+ const [layers, setLayers] = useState(() => optionalLayersMap(optionalLayers));
+ const setLayerEnabled = (layer: string, enabled: boolean) =>
+ setLayers((optionalLayers) => {
+ optionalLayers[layer].enabled = enabled;
+ return { ...optionalLayers };
+ });
+ const setLayerOpacity = (layer: string, opacity: number) =>
+ setLayers((optionalLayers) => {
+ optionalLayers[layer].opacity = opacity;
+ return { ...optionalLayers };
+ });
+ const enabledLayers = useMemo(
+ () => Object.entries(layers).filter(([, { enabled }]) => enabled),
+ [layers]
+ );
+ const style = useMemo(
+ () =>
+ getMapStyle(
+ styleId,
+ enabledLayers.map(([layer]) => layer),
+ Object.fromEntries(
+ enabledLayers.map(([layer, { opacity }]) => [layer, opacity])
+ )
+ ),
+ [styleId, enabledLayers]
+ );
+
+ useEffect(() => onStyleChange(style), [style]);
+
+ return { style, layers, setStyle, setLayerEnabled, setLayerOpacity };
+}
diff --git a/app/javascript/components/shared/mapbox/styles/base.js b/app/javascript/components/shared/maplibre/styles/base.ts
similarity index 85%
rename from app/javascript/components/shared/mapbox/styles/base.js
rename to app/javascript/components/shared/maplibre/styles/base.ts
index 8e7ecd0e1..cc7c880ee 100644
--- a/app/javascript/components/shared/mapbox/styles/base.js
+++ b/app/javascript/components/shared/maplibre/styles/base.ts
@@ -1,8 +1,11 @@
+import type { AnyLayer, Style, RasterLayer, RasterSource } from 'maplibre-gl';
+import invariant from 'tiny-invariant';
+
import cadastreLayers from './layers/cadastre';
-const IGN_TOKEN = 'rc1egnbeoss72hxvd143tbyk';
+const IGN_TOKEN = 'rc1egnbeoss72hxvd143tbyk'; // ggignore
-function ignServiceURL(layer, format = 'image/png') {
+function ignServiceURL(layer: string, format = 'image/png') {
const url = `https://wxs.ign.fr/${IGN_TOKEN}/geoportail/wmts`;
const query =
'service=WMTS&request=GetTile&version=1.0.0&tilematrixset=PM&tilematrix={z}&tilecol={x}&tilerow={y}&style=normal';
@@ -10,7 +13,7 @@ function ignServiceURL(layer, format = 'image/png') {
return `${url}?${query}&layer=${layer}&format=${format}`;
}
-const OPTIONAL_LAYERS = [
+const OPTIONAL_LAYERS: { label: string; id: string; layers: string[][] }[] = [
{
label: 'UNESCO',
id: 'unesco',
@@ -127,7 +130,7 @@ function buildSources() {
);
}
-function rasterSource(tiles, attribution) {
+function rasterSource(tiles: string[], attribution: string): RasterSource {
return {
type: 'raster',
tiles,
@@ -138,7 +141,7 @@ function rasterSource(tiles, attribution) {
};
}
-function rasterLayer(source, opacity) {
+function rasterLayer(source: string, opacity: number): RasterLayer {
return {
id: source,
source,
@@ -147,10 +150,13 @@ function rasterLayer(source, opacity) {
};
}
-export function buildOptionalLayers(ids, opacity) {
+export function buildOptionalLayers(
+ ids: string[],
+ opacity: Record
+): AnyLayer[] {
return OPTIONAL_LAYERS.filter(({ id }) => ids.includes(id))
.flatMap(({ layers, id }) =>
- layers.map(([, code]) => [code, opacity[id] / 100])
+ layers.map(([, code]) => [code, opacity[id] / 100] as const)
)
.flatMap(([code, opacity]) =>
code === 'CADASTRE'
@@ -159,16 +165,15 @@ export function buildOptionalLayers(ids, opacity) {
);
}
-export const NBS = ' ';
+export const NBS = ' ' as const;
-export function getLayerName(layer) {
- return OPTIONAL_LAYERS.find(({ id }) => id == layer).label.replace(
- /\s/g,
- NBS
- );
+export function getLayerName(layer: string): string {
+ const name = OPTIONAL_LAYERS.find(({ id }) => id == layer);
+ invariant(name, `Layer "${layer}" not found`);
+ return name.label.replace(/\s/g, NBS);
}
-function getLayerCode(code) {
+function getLayerCode(code: string) {
return code.toLowerCase().replace(/\./g, '-');
}
@@ -220,4 +225,4 @@ export default {
},
sprite: 'https://openmaptiles.github.io/osm-bright-gl-style/sprite',
glyphs: 'https://openmaptiles.geo.data.gouv.fr/fonts/{fontstack}/{range}.pbf'
-};
+} as Style;
diff --git a/app/javascript/components/shared/mapbox/styles/index.js b/app/javascript/components/shared/maplibre/styles/index.ts
similarity index 61%
rename from app/javascript/components/shared/mapbox/styles/index.js
rename to app/javascript/components/shared/maplibre/styles/index.ts
index ed97b2aee..b7d865e29 100644
--- a/app/javascript/components/shared/mapbox/styles/index.js
+++ b/app/javascript/components/shared/maplibre/styles/index.ts
@@ -1,3 +1,5 @@
+import type { Style } from 'maplibre-gl';
+
import baseStyle, { buildOptionalLayers, getLayerName, NBS } from './base';
import orthoStyle from './layers/ortho';
import vectorStyle from './layers/vector';
@@ -5,7 +7,21 @@ import ignLayers from './layers/ign';
export { getLayerName, NBS };
-export function getMapStyle(id, layers, opacity) {
+export type LayersMap = Record<
+ string,
+ {
+ configurable: boolean;
+ enabled: boolean;
+ opacity: number;
+ name: string;
+ }
+>;
+
+export function getMapStyle(
+ id: string,
+ layers: string[],
+ opacity: Record
+): Style & { id: string } {
const style = { ...baseStyle, id };
switch (id) {
@@ -23,7 +39,7 @@ export function getMapStyle(id, layers, opacity) {
break;
}
- style.layers = style.layers.concat(buildOptionalLayers(layers, opacity));
+ style.layers = style.layers?.concat(buildOptionalLayers(layers, opacity));
return style;
}
diff --git a/app/javascript/components/shared/mapbox/styles/layers/cadastre.js b/app/javascript/components/shared/maplibre/styles/layers/cadastre.ts
similarity index 95%
rename from app/javascript/components/shared/mapbox/styles/layers/cadastre.js
rename to app/javascript/components/shared/maplibre/styles/layers/cadastre.ts
index 0aed8996c..759504f97 100644
--- a/app/javascript/components/shared/mapbox/styles/layers/cadastre.js
+++ b/app/javascript/components/shared/maplibre/styles/layers/cadastre.ts
@@ -1,4 +1,6 @@
-export default [
+import { AnyLayer } from 'maplibre-gl';
+
+const layers: AnyLayer[] = [
{
id: 'batiments-line',
type: 'line',
@@ -104,3 +106,5 @@ export default [
}
}
];
+
+export default layers;
diff --git a/app/javascript/components/shared/mapbox/styles/layers/ign.js b/app/javascript/components/shared/maplibre/styles/layers/ign.ts
similarity index 52%
rename from app/javascript/components/shared/mapbox/styles/layers/ign.js
rename to app/javascript/components/shared/maplibre/styles/layers/ign.ts
index e7e7614a7..545d41911 100644
--- a/app/javascript/components/shared/mapbox/styles/layers/ign.js
+++ b/app/javascript/components/shared/maplibre/styles/layers/ign.ts
@@ -1,4 +1,6 @@
-export default [
+import type { RasterLayer } from 'maplibre-gl';
+
+const layers: RasterLayer[] = [
{
id: 'ign',
source: 'plan-ign',
@@ -6,3 +8,5 @@ export default [
paint: { 'raster-resampling': 'linear' }
}
];
+
+export default layers;
diff --git a/app/javascript/components/shared/mapbox/styles/layers/ortho.js b/app/javascript/components/shared/maplibre/styles/layers/ortho.ts
similarity index 99%
rename from app/javascript/components/shared/mapbox/styles/layers/ortho.js
rename to app/javascript/components/shared/maplibre/styles/layers/ortho.ts
index 0e255a426..5423d46ce 100644
--- a/app/javascript/components/shared/mapbox/styles/layers/ortho.js
+++ b/app/javascript/components/shared/maplibre/styles/layers/ortho.ts
@@ -1,4 +1,6 @@
-export default [
+import type { AnyLayer } from 'maplibre-gl';
+
+const layers: AnyLayer[] = [
{
id: 'photographies-aeriennes',
type: 'raster',
@@ -2129,7 +2131,7 @@ export default [
[10, 'point'],
[11, 'line']
]
- },
+ } as any,
'symbol-spacing': 200,
'text-field': '{ref}',
'text-font': ['Noto Sans Regular'],
@@ -2160,7 +2162,7 @@ export default [
[10, 'point'],
[11, 'line']
]
- },
+ } as any,
'symbol-spacing': 200,
'text-field': '{ref}',
'text-font': ['Noto Sans Regular'],
@@ -2262,7 +2264,7 @@ export default [
'text-letter-spacing': 0,
'icon-padding': 2,
'symbol-placement': 'point',
- 'symbol-z-order': 'auto',
+ 'symbol-z-order': 'auto' as any,
'text-line-height': 1.2,
'text-allow-overlap': false,
'text-ignore-placement': false,
@@ -2637,3 +2639,5 @@ export default [
}
}
];
+
+export default layers;
diff --git a/app/javascript/components/shared/mapbox/styles/layers/vector.js b/app/javascript/components/shared/maplibre/styles/layers/vector.ts
similarity index 99%
rename from app/javascript/components/shared/mapbox/styles/layers/vector.js
rename to app/javascript/components/shared/maplibre/styles/layers/vector.ts
index d69b812a2..b91bf9cb3 100644
--- a/app/javascript/components/shared/mapbox/styles/layers/vector.js
+++ b/app/javascript/components/shared/maplibre/styles/layers/vector.ts
@@ -1,4 +1,6 @@
-export default [
+import type { AnyLayer } from 'maplibre-gl';
+
+const layers: AnyLayer[] = [
{
id: 'background',
type: 'background',
@@ -113,7 +115,7 @@ export default [
[0, false],
[9, true]
]
- },
+ } as any,
'fill-color': '#6a4',
'fill-opacity': 0.1,
'fill-outline-color': 'hsla(0, 0%, 0%, 0.03)'
@@ -324,7 +326,7 @@ export default [
[6, [2, 0]],
[8, [0, 0]]
]
- }
+ } as any
}
},
{
@@ -427,7 +429,7 @@ export default [
[14, [0, 0]],
[16, [-2, -2]]
]
- }
+ } as any
}
},
{
@@ -2322,7 +2324,7 @@ export default [
[10, 'point'],
[11, 'line']
]
- },
+ } as any,
'symbol-spacing': 200,
'text-field': '{ref}',
'text-font': ['Noto Sans Regular'],
@@ -2354,7 +2356,7 @@ export default [
[7, 'line'],
[8, 'line']
]
- },
+ } as any,
'symbol-spacing': 200,
'text-field': '{ref}',
'text-font': ['Noto Sans Regular'],
@@ -2385,7 +2387,7 @@ export default [
[10, 'point'],
[11, 'line']
]
- },
+ } as any,
'symbol-spacing': 200,
'text-field': '{ref}',
'text-font': ['Noto Sans Regular'],
@@ -2837,3 +2839,5 @@ export default [
}
}
];
+
+export default layers;
diff --git a/app/javascript/components/shared/maplibre/utils.ts b/app/javascript/components/shared/maplibre/utils.ts
new file mode 100644
index 000000000..8ab08c181
--- /dev/null
+++ b/app/javascript/components/shared/maplibre/utils.ts
@@ -0,0 +1,93 @@
+import {
+ LngLatBounds,
+ LngLat,
+ LngLatLike,
+ LngLatBoundsLike
+} from 'maplibre-gl';
+import type { Geometry, FeatureCollection, Feature } from 'geojson';
+import invariant from 'tiny-invariant';
+
+export function getBounds(geometry: Geometry): LngLatBoundsLike {
+ const bbox = new LngLatBounds();
+
+ if (geometry.type === 'Point') {
+ return [geometry.coordinates, geometry.coordinates] as [
+ [number, number],
+ [number, number]
+ ];
+ } else if (geometry.type === 'LineString') {
+ for (const coordinate of geometry.coordinates) {
+ bbox.extend(coordinate as [number, number]);
+ }
+ } else {
+ invariant(
+ geometry.type != 'GeometryCollection',
+ 'GeometryCollection not supported'
+ );
+ for (const coordinate of geometry.coordinates[0]) {
+ bbox.extend(coordinate as [number, number]);
+ }
+ }
+ return bbox;
+}
+
+export function findFeature(
+ featureCollection: FeatureCollection,
+ value: unknown,
+ property = 'id'
+): Feature | null {
+ return (
+ featureCollection.features.find(
+ (feature) => feature.properties && feature.properties[property] === value
+ ) ?? null
+ );
+}
+
+export function filterFeatureCollection(
+ featureCollection: FeatureCollection,
+ source: string
+): FeatureCollection {
+ return {
+ type: 'FeatureCollection',
+ features: featureCollection.features.filter(
+ (feature) => feature.properties?.source === source
+ )
+ };
+}
+
+export function filterFeatureCollectionByGeometryType(
+ featureCollection: FeatureCollection,
+ type: Geometry['type']
+): FeatureCollection {
+ return {
+ type: 'FeatureCollection',
+ features: featureCollection.features.filter(
+ (feature) => feature.geometry.type === type
+ )
+ };
+}
+
+export function generateId(): string {
+ return Math.random().toString(20).substring(2, 6);
+}
+
+export function getCenter(geometry: Geometry, lngLat: LngLat): LngLatLike {
+ const bbox = new LngLatBounds();
+
+ invariant(
+ geometry.type != 'GeometryCollection',
+ 'GeometryCollection not supported'
+ );
+
+ switch (geometry.type) {
+ case 'Point':
+ return [...geometry.coordinates] as [number, number];
+ case 'LineString':
+ return [lngLat.lng, lngLat.lat];
+ default:
+ for (const coordinate of geometry.coordinates[0]) {
+ bbox.extend(coordinate as [number, number]);
+ }
+ return bbox.getCenter();
+ }
+}
diff --git a/app/javascript/components/shared/queryClient.js b/app/javascript/components/shared/queryClient.ts
similarity index 78%
rename from app/javascript/components/shared/queryClient.js
rename to app/javascript/components/shared/queryClient.ts
index e37e4fb7f..14164f831 100644
--- a/app/javascript/components/shared/queryClient.js
+++ b/app/javascript/components/shared/queryClient.ts
@@ -1,4 +1,4 @@
-import { QueryClient } from 'react-query';
+import { QueryClient, QueryFunction } from 'react-query';
import { getJSON, isNumeric } from '@utils';
import { matchSorter } from 'match-sorter';
@@ -16,17 +16,15 @@ const API_ADRESSE_QUERY_LIMIT = 5;
const API_GEO_COMMUNES_QUERY_LIMIT = 60;
const { api_geo_url, api_adresse_url, api_education_url } =
- gon.autocomplete || {};
+ (window as any).gon.autocomplete || {};
-export const queryClient = new QueryClient({
- defaultOptions: {
- queries: {
- queryFn: defaultQueryFn
- }
- }
-});
+type QueryKey = readonly [
+ scope: string,
+ term: string,
+ extra: string | undefined
+];
-function buildURL(scope, term, extra) {
+function buildURL(scope: string, term: string, extra?: string) {
term = encodeURIComponent(term.replace(/\(|\)/g, ''));
if (scope === 'adresse') {
return `${api_adresse_url}/search?q=${term}&limit=${API_ADRESSE_QUERY_LIMIT}`;
@@ -48,7 +46,7 @@ function buildURL(scope, term, extra) {
return `${api_geo_url}/${scope}?nom=${term}&limit=${API_GEO_QUERY_LIMIT}`;
}
-function buildOptions() {
+function buildOptions(): [RequestInit, AbortController | null] {
if (window.AbortController) {
const controller = new AbortController();
const signal = controller.signal;
@@ -57,7 +55,9 @@ function buildOptions() {
return [{}, null];
}
-async function defaultQueryFn({ queryKey: [scope, term, extra] }) {
+const defaultQueryFn: QueryFunction = async ({
+ queryKey: [scope, term, extra]
+}) => {
if (scope == 'pays') {
return matchSorter(await getPays(), term, { keys: ['label'] });
}
@@ -70,14 +70,22 @@ async function defaultQueryFn({ queryKey: [scope, term, extra] }) {
}
throw new Error(`Error fetching from "${scope}" API`);
});
- promise.cancel = () => controller && controller.abort();
+ (promise as any).cancel = () => controller && controller.abort();
return promise;
-}
+};
-let paysCache;
-async function getPays() {
+let paysCache: { label: string }[];
+async function getPays(): Promise<{ label: string }[]> {
if (!paysCache) {
paysCache = await getJSON('/api/pays', null);
}
return paysCache;
}
+
+export const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ queryFn: defaultQueryFn as any
+ }
+ }
+});
diff --git a/app/javascript/shared/polyfills.js b/app/javascript/shared/polyfills.js
index d08eb91bf..bcf7ce2b4 100644
--- a/app/javascript/shared/polyfills.js
+++ b/app/javascript/shared/polyfills.js
@@ -5,6 +5,7 @@ import 'core-js/stable';
import 'regenerator-runtime/runtime';
import 'dom4';
import 'intersection-observer';
+import 'whatwg-fetch';
import './polyfills/insertAdjacentElement';
import './polyfills/dataset';
diff --git a/app/javascript/shared/register-react-components.jsx b/app/javascript/shared/register-react-components.tsx
similarity index 75%
rename from app/javascript/shared/register-react-components.jsx
rename to app/javascript/shared/register-react-components.tsx
index 7e628e2d5..48bd43526 100644
--- a/app/javascript/shared/register-react-components.jsx
+++ b/app/javascript/shared/register-react-components.tsx
@@ -1,4 +1,4 @@
-import React, { Suspense, lazy, createElement } from 'react';
+import React, { Suspense, lazy, createElement, ComponentClass } from 'react';
import { render } from 'react-dom';
// This attribute holds the name of component which should be mounted
@@ -13,12 +13,12 @@ const CLASS_NAME_SELECTOR = `[${CLASS_NAME_ATTR}]`;
// helper method for the mount and unmount methods to find the
// `data-react-class` DOM elements
-function findDOMNodes(searchSelector) {
+function findDOMNodes(searchSelector?: string): NodeListOf {
const [selector, parent] = getSelector(searchSelector);
- return parent.querySelectorAll(selector);
+ return parent.querySelectorAll(selector);
}
-function getSelector(searchSelector) {
+function getSelector(searchSelector?: string): [string, Document] {
switch (typeof searchSelector) {
case 'undefined':
return [CLASS_NAME_SELECTOR, document];
@@ -39,15 +39,15 @@ function getSelector(searchSelector) {
class ReactComponentRegistry {
#components;
- constructor(components) {
+ constructor(components: Record) {
this.#components = components;
}
- getConstructor(className) {
- return this.#components[className];
+ getConstructor(className: string | null) {
+ return className ? this.#components[className] : null;
}
- mountComponents(searchSelector) {
+ mountComponents(searchSelector?: string) {
const nodes = findDOMNodes(searchSelector);
for (const node of nodes) {
@@ -76,10 +76,10 @@ class ReactComponentRegistry {
const Loader = () =>
;
-export function Loadable(loader) {
+export function Loadable(loader: () => Promise<{ default: ComponentClass }>) {
const LazyComponent = lazy(loader);
- return function PureComponent(props) {
+ return function PureComponent(props: Record) {
return (
}>
@@ -88,7 +88,9 @@ export function Loadable(loader) {
};
}
-export function registerReactComponents(components) {
+export function registerReactComponents(
+ components: Record
+) {
const registry = new ReactComponentRegistry(components);
addEventListener('ds:page:update', () => registry.mountComponents());
diff --git a/app/javascript/shared/utils.js b/app/javascript/shared/utils.ts
similarity index 60%
rename from app/javascript/shared/utils.js
rename to app/javascript/shared/utils.ts
index 3ac0b7839..4cc346ada 100644
--- a/app/javascript/shared/utils.js
+++ b/app/javascript/shared/utils.ts
@@ -4,17 +4,17 @@ import debounce from 'debounce';
export { debounce };
export const { fire, csrfToken } = Rails;
-export function show(el) {
+export function show(el: HTMLElement) {
el && el.classList.remove('hidden');
}
-export function hide(el) {
+export function hide(el: HTMLElement) {
el && el.classList.add('hidden');
}
-export function toggle(el, force) {
+export function toggle(el: HTMLElement, force?: boolean) {
if (force == undefined) {
- el & el.classList.toggle('hidden');
+ el && el.classList.toggle('hidden');
} else if (force) {
el && el.classList.remove('hidden');
} else {
@@ -22,27 +22,31 @@ export function toggle(el, force) {
}
}
-export function enable(el) {
+export function enable(el: HTMLInputElement) {
el && (el.disabled = false);
}
-export function disable(el) {
+export function disable(el: HTMLInputElement) {
el && (el.disabled = true);
}
-export function hasClass(el, cssClass) {
+export function hasClass(el: HTMLElement, cssClass: string) {
return el && el.classList.contains(cssClass);
}
-export function addClass(el, cssClass) {
+export function addClass(el: HTMLElement, cssClass: string) {
el && el.classList.add(cssClass);
}
-export function removeClass(el, cssClass) {
+export function removeClass(el: HTMLElement, cssClass: string) {
el && el.classList.remove(cssClass);
}
-export function delegate(eventNames, selector, callback) {
+export function delegate(
+ eventNames: string,
+ selector: string,
+ callback: () => void
+) {
eventNames
.split(' ')
.forEach((eventName) =>
@@ -57,15 +61,23 @@ export function delegate(eventNames, selector, callback) {
// - rejected with an Error object otherwise.
//
// See Rails.ajax() code for more details.
-export function ajax(options) {
+export function ajax(options: Rails.AjaxOptions) {
return new Promise((resolve, reject) => {
Object.assign(options, {
- success: (response, statusText, xhr) => {
+ success: (
+ response: unknown,
+ statusText: string,
+ xhr: { status: number }
+ ) => {
resolve({ response, statusText, xhr });
},
- error: (response, statusText, xhr) => {
+ error: (
+ response: unknown,
+ statusText: string,
+ xhr: { status: number }
+ ) => {
// NB: on HTTP/2 connections, statusText is always empty.
- let error = new Error(
+ const error = new Error(
`Erreur ${xhr.status}` + (statusText ? ` : ${statusText}` : '')
);
Object.assign(error, { response, statusText, xhr });
@@ -76,7 +88,7 @@ export function ajax(options) {
});
}
-export function getJSON(url, data, method = 'GET') {
+export function getJSON(url: string, data: unknown, method = 'GET') {
const { query, ...options } = fetchOptions(data, method);
return fetch(`${url}${query}`, options).then((response) => {
@@ -86,32 +98,38 @@ export function getJSON(url, data, method = 'GET') {
}
return response.json();
}
- const error = new Error(response.statusText || response.status);
- error.response = response;
+ const error = new Error(String(response.statusText || response.status));
+ (error as any).response = response;
throw error;
});
}
-export function scrollTo(container, scrollTo) {
+export function scrollTo(container: HTMLElement, scrollTo: HTMLElement) {
container.scrollTop =
offset(scrollTo).top - offset(container).top + container.scrollTop;
}
-export function scrollToBottom(container) {
+export function scrollToBottom(container: HTMLElement) {
container.scrollTop = container.scrollHeight;
}
-export function on(selector, eventName, fn) {
+export function on(
+ selector: string,
+ eventName: string,
+ fn: (event: Event, detail: unknown) => void
+) {
[...document.querySelectorAll(selector)].forEach((element) =>
- element.addEventListener(eventName, (event) => fn(event, event.detail))
+ element.addEventListener(eventName, (event) =>
+ fn(event, (event as CustomEvent).detail)
+ )
);
}
-export function isNumeric(n) {
- return !isNaN(parseFloat(n)) && isFinite(n);
+export function isNumeric(n: string) {
+ return !isNaN(parseFloat(n)) && isFinite(n as any as number);
}
-function offset(element) {
+function offset(element: HTMLElement) {
const rect = element.getBoundingClientRect();
return {
top: rect.top + document.body.scrollTop,
@@ -120,8 +138,11 @@ function offset(element) {
}
// Takes a promise, and return a promise that times out after the given delay.
-export function timeoutable(promise, timeoutDelay) {
- let timeoutPromise = new Promise((resolve, reject) => {
+export function timeoutable(
+ promise: Promise,
+ timeoutDelay: number
+): Promise {
+ const timeoutPromise = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error(`Promise timed out after ${timeoutDelay}ms`));
}, timeoutDelay);
@@ -131,13 +152,16 @@ export function timeoutable(promise, timeoutDelay) {
const FETCH_TIMEOUT = 30 * 1000; // 30 sec
-function fetchOptions(data, method = 'GET') {
- const options = {
+function fetchOptions(data: unknown, method = 'GET') {
+ const options: RequestInit & {
+ query: string;
+ headers: Record;
+ } = {
query: '',
method: method.toUpperCase(),
headers: {
accept: 'application/json',
- 'x-csrf-token': csrfToken(),
+ 'x-csrf-token': csrfToken() ?? '',
'x-requested-with': 'XMLHttpRequest'
},
credentials: 'same-origin'
@@ -145,7 +169,7 @@ function fetchOptions(data, method = 'GET') {
if (data) {
if (options.method === 'GET') {
- options.query = objectToQuerystring(data);
+ options.query = objectToQuerystring(data as Record);
} else {
options.headers['content-type'] = 'application/json';
options.body = JSON.stringify(data);
@@ -164,7 +188,7 @@ function fetchOptions(data, method = 'GET') {
return options;
}
-function objectToQuerystring(obj) {
+function objectToQuerystring(obj: Record): string {
return Object.keys(obj).reduce(function (query, key, i) {
return [
query,
diff --git a/app/javascript/types.d.ts b/app/javascript/types.d.ts
new file mode 100644
index 000000000..200e8886e
--- /dev/null
+++ b/app/javascript/types.d.ts
@@ -0,0 +1,23 @@
+// This file contains type definitions for untyped packages. We are lucky to have only two ;)
+
+declare module '@tmcw/togeojson/dist/togeojson.es.js' {
+ import { FeatureCollection, GeoJsonProperties, Geometry } from 'geojson';
+
+ export function kml(doc: Document): FeatureCollection;
+
+ export function kml(
+ doc: Document
+ ): FeatureCollection;
+
+ export function gpx(doc: Document): FeatureCollection;
+ export function gpx(
+ doc: Document
+ ): FeatureCollection;
+
+ export function tcx(doc: Document): FeatureCollection;
+ export function tcx(
+ doc: Document
+ ): FeatureCollection;
+}
+
+declare module 'react-coordinate-input';
diff --git a/app/models/dossier.rb b/app/models/dossier.rb
index 83a91032a..f80e09aa2 100644
--- a/app/models/dossier.rb
+++ b/app/models/dossier.rb
@@ -17,6 +17,7 @@
# groupe_instructeur_updated_at :datetime
# hidden_at :datetime
# hidden_by_administration_at :datetime
+# hidden_by_reason :string
# hidden_by_user_at :datetime
# identity_updated_at :datetime
# last_avis_updated_at :datetime
@@ -761,26 +762,26 @@ class Dossier < ApplicationRecord
end
def restore_dossier_and_destroy_deleted_dossier(author)
- deleted_dossier&.destroy!
+ if deleted_dossier.present?
+ deleted_dossier&.destroy!
+ end
+
log_dossier_operation(author, :restaurer, self)
end
def discard_and_keep_track!(author, reason)
if termine? && author_is_administration(author)
- update(hidden_by_administration_at: Time.zone.now)
+ update(hidden_by_administration_at: Time.zone.now, hidden_by_reason: reason)
end
if can_be_hidden_by_user? && author_is_user(author)
- update(hidden_by_user_at: Time.zone.now, dossier_transfer_id: nil)
+ update(hidden_by_user_at: Time.zone.now, dossier_transfer_id: nil, hidden_by_reason: reason)
end
- deleted_dossier = nil
-
transaction do
if deleted_by_instructeur_and_user? || en_construction? || brouillon?
if keep_track_on_deletion?
log_dossier_operation(author, :supprimer, self)
- deleted_dossier = DeletedDossier.create_from_dossier(self, reason)
end
if !(en_construction? && author_is_user(author))
@@ -789,12 +790,11 @@ class Dossier < ApplicationRecord
end
end
- if deleted_dossier.present?
- if en_construction?
- administration_emails = followers_instructeurs.present? ? followers_instructeurs.map(&:email) : procedure.administrateurs.map(&:email)
- administration_emails.each do |email|
- DossierMailer.notify_deletion_to_administration(deleted_dossier, email).deliver_later
- end
+ if en_construction?
+ update(hidden_by_reason: reason)
+ administration_emails = followers_instructeurs.present? ? followers_instructeurs.map(&:email) : procedure.administrateurs.map(&:email)
+ administration_emails.each do |email|
+ DossierMailer.notify_en_construction_deletion_to_administration(self, email).deliver_later
end
end
end
@@ -813,6 +813,8 @@ class Dossier < ApplicationRecord
elsif author_is_user(author) && hidden_by_user?
transaction do
update(hidden_by_user_at: nil)
+ !hidden_by_administration? && update(hidden_by_reason: nil)
+
if en_construction?
restore_dossier_and_destroy_deleted_dossier(author)
end
@@ -820,6 +822,7 @@ class Dossier < ApplicationRecord
elsif author_is_administration(author) && hidden_by_administration?
transaction do
update(hidden_by_administration_at: nil)
+ !hidden_by_user? && update(hidden_by_reason: nil)
end
end
end
@@ -1150,20 +1153,21 @@ class Dossier < ApplicationRecord
user&.locale || I18n.default_locale
end
+ def purge_discarded
+ transaction do
+ if keep_track_on_deletion?
+ DeletedDossier.create_from_dossier(self, hidden_by_reason)
+ end
+
+ dossier_operation_logs.not_deletion.destroy_all
+ destroy
+ end
+ end
+
def self.purge_discarded
- discarded_brouillon_expired.destroy_all
-
- transaction do
- DossierOperationLog.discarded_en_construction_expired.destroy_all
- Avis.discarded_en_construction_expired.destroy_all
- discarded_en_construction_expired.destroy_all
- end
-
- transaction do
- DossierOperationLog.discarded_termine_expired.destroy_all
- Avis.discarded_termine_expired.destroy_all
- discarded_termine_expired.destroy_all
- end
+ discarded_brouillon_expired.find_each(&:purge_discarded)
+ discarded_en_construction_expired.find_each(&:purge_discarded)
+ discarded_termine_expired.find_each(&:purge_discarded)
end
private
diff --git a/app/models/instructeur.rb b/app/models/instructeur.rb
index b569ee895..8f72114cd 100644
--- a/app/models/instructeur.rb
+++ b/app/models/instructeur.rb
@@ -235,7 +235,7 @@ class Instructeur < ApplicationRecord
COUNT(DISTINCT dossiers.id) FILTER (where not archived AND NOT (dossiers.hidden_by_user_at IS NOT NULL AND state = 'en_construction') AND dossiers.state in ('en_construction', 'en_instruction') AND follows.id IS NULL) AS a_suivre,
COUNT(DISTINCT dossiers.id) FILTER (where not archived AND dossiers.state in ('en_construction', 'en_instruction') AND follows.instructeur_id = :instructeur_id) AS suivis,
COUNT(DISTINCT dossiers.id) FILTER (where not archived AND dossiers.state in ('accepte', 'refuse', 'sans_suite')) AS traites,
- COUNT(DISTINCT dossiers.id) FILTER (where not archived AND NOT (dossiers.hidden_by_user_at IS NOT NULL AND state = 'en_construction')) AS tous,
+ COUNT(DISTINCT dossiers.id) FILTER (where not archived AND NOT (dossiers.hidden_by_user_at IS NOT NULL AND state = 'en_construction') AND NOT (dossiers.hidden_by_administration_at IS NOT NULL)) AS tous,
COUNT(DISTINCT dossiers.id) FILTER (where not archived AND (dossiers.hidden_by_administration_at IS NOT NULL AND dossiers.state in ('accepte', 'refuse', 'sans_suite') )) AS supprimes_recemment,
COUNT(DISTINCT dossiers.id) FILTER (where archived) AS archives,
COUNT(DISTINCT dossiers.id) FILTER (where
diff --git a/babel.config.js b/babel.config.js
index 36b1d7ede..f236e2bf6 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -1,9 +1,9 @@
-module.exports = function(api) {
- var validEnv = ['development', 'test', 'production']
- var currentEnv = api.env()
- var isDevelopmentEnv = api.env('development')
- var isProductionEnv = api.env('production')
- var isTestEnv = api.env('test')
+module.exports = function (api) {
+ var validEnv = ['development', 'test', 'production'];
+ var currentEnv = api.env();
+ var isDevelopmentEnv = api.env('development');
+ var isProductionEnv = api.env('production');
+ var isTestEnv = api.env('test');
if (!validEnv.includes(currentEnv)) {
throw new Error(
@@ -12,7 +12,7 @@ module.exports = function(api) {
'"test", and "production". Instead, received: ' +
JSON.stringify(currentEnv) +
'.'
- )
+ );
}
return {
@@ -41,7 +41,8 @@ module.exports = function(api) {
development: isDevelopmentEnv || isTestEnv,
useBuiltIns: true
}
- ]
+ ],
+ ['@babel/preset-typescript', { allExtensions: true, isTSX: true }]
].filter(Boolean),
plugins: [
'babel-plugin-macros',
@@ -92,5 +93,5 @@ module.exports = function(api) {
}
]
].filter(Boolean)
- }
-}
+ };
+};
diff --git a/config/env.example b/config/env.example
index 8f3e332f8..f557df768 100644
--- a/config/env.example
+++ b/config/env.example
@@ -2,8 +2,8 @@
#
# Examples:
# * For local development: localhost:3000
-# * For preproduction: staging.ds.organisme.fr
-# * For production: ds.organisme.fr
+# * For preproduction: staging.ds.example.org
+# * For production: ds.example.org
APP_HOST="localhost:3000"
# Rails key for signing sensitive data
@@ -68,7 +68,7 @@ SENTRY_DSN_JS=""
# External service: Matomo web analytics
MATOMO_ENABLED="disabled"
MATOMO_ID=""
-MATOMO_HOST="matomo.organisme.fr"
+MATOMO_HOST="matomo.example.org"
# Default SMTP Provider: Mailjet
MAILJET_API_KEY=""
diff --git a/config/env.example.optional b/config/env.example.optional
index 1c66ecdfd..c91da1971 100644
--- a/config/env.example.optional
+++ b/config/env.example.optional
@@ -88,7 +88,10 @@ DS_ENV="staging"
# API_PARTICULIER_URL="https://particulier.api.gouv.fr/api"
# Admins and instructeurs can freely change their email to these domains
-# LEGIT_ADMIN_DOMAINS = "domaine_1.com;domaine_2.com"
+# LEGIT_ADMIN_DOMAINS = "example.org;example.net"
+
+# External service: Matomo web analytics
+MATOMO_IFRAME_URL="https://matomo.example.org/index.php?module=CoreAdminHome&action=optOut&language=fr&&fontColor=333333&fontSize=16px&fontFamily=Muli"
# Instance provider
# PROVIDED_BY="la DINUM"
diff --git a/config/initializers/urls.rb b/config/initializers/urls.rb
index 732c583f2..7c3fcd282 100644
--- a/config/initializers/urls.rb
+++ b/config/initializers/urls.rb
@@ -43,6 +43,6 @@ FAQ_ERREUR_SIRET_URL = [FAQ_URL, "article", "4-erreur-siret"].join("/")
STATUS_PAGE_URL = ENV.fetch("STATUS_PAGE_URL", "https://status.demarches-simplifiees.fr")
DEMANDE_INSCRIPTION_ADMIN_PAGE_URL = ENV.fetch("DEMANDE_INSCRIPTION_ADMIN_PAGE_URL", "https://www.demarches-simplifiees.fr/commencer/demande-d-inscription-a-demarches-simplifiees")
-MATOMO_IFRAME_URL = "https://stats.data.gouv.fr/index.php?module=CoreAdminHome&action=optOut&language=fr&&fontColor=333333&fontSize=16px&fontFamily=Muli"
+MATOMO_IFRAME_URL = ENV.fetch("MATOMO_IFRAME_URL", "https://#{ENV.fetch('MATOMO_HOST', 'stats.data.gouv.fr')}/index.php?module=CoreAdminHome&action=optOut&language=fr&&fontColor=333333&fontSize=16px&fontFamily=Muli")
# rubocop:enable DS/ApplicationName
diff --git a/config/storage.yml b/config/storage.yml
index 99173ad6f..b24cd1613 100644
--- a/config/storage.yml
+++ b/config/storage.yml
@@ -8,7 +8,7 @@ openstack:
service: OpenStack
container: "<%= ENV['FOG_ACTIVESTORAGE_DIRECTORY'] %>"
credentials:
- openstack_auth_url: "https://auth.cloud.ovh.net"
+ openstack_auth_url: "<%= ENV['FOG_OPENSTACK_URL'] %>"
openstack_api_key: "<%= ENV['FOG_OPENSTACK_API_KEY'] %>"
openstack_username: "<%= ENV['FOG_OPENSTACK_USERNAME'] %>"
openstack_region: "<%= ENV['FOG_OPENSTACK_REGION'] %>"
diff --git a/config/webpack/environment.js b/config/webpack/environment.js
index deb5c4c44..c5e252282 100644
--- a/config/webpack/environment.js
+++ b/config/webpack/environment.js
@@ -24,7 +24,7 @@ if (!Array.isArray(nodeModulesLoader.exclude)) {
nodeModulesLoader.exclude == null ? [] : [nodeModulesLoader.exclude];
}
nodeModulesLoader.exclude.push(
- path.resolve(__dirname, '..', '..', 'node_modules/mapbox-gl')
+ path.resolve(__dirname, '..', '..', 'node_modules/maplibre-gl')
);
// Uncoment next lines to run webpack-bundle-analyzer
diff --git a/config/webpacker.yml b/config/webpacker.yml
index 56183996a..3991058f3 100644
--- a/config/webpacker.yml
+++ b/config/webpacker.yml
@@ -33,6 +33,8 @@ default: &default
- .woff2
extensions:
+ - .tsx
+ - .ts
- .mjs
- .js
- .jsx
diff --git a/db/migrate/20220204093401_add_hidden_by_reason_to_dossiers.rb b/db/migrate/20220204093401_add_hidden_by_reason_to_dossiers.rb
new file mode 100644
index 000000000..0cfd78aa0
--- /dev/null
+++ b/db/migrate/20220204093401_add_hidden_by_reason_to_dossiers.rb
@@ -0,0 +1,5 @@
+class AddHiddenByReasonToDossiers < ActiveRecord::Migration[6.1]
+ def change
+ add_column :dossiers, :hidden_by_reason, :string
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 31cc440d2..d90acf3a6 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2022_01_28_135056) do
+ActiveRecord::Schema.define(version: 2022_02_04_093401) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -324,6 +324,7 @@ ActiveRecord::Schema.define(version: 2022_01_28_135056) do
t.datetime "identity_updated_at"
t.datetime "depose_at"
t.datetime "hidden_by_user_at"
+ t.string "hidden_by_reason"
t.index "to_tsvector('french'::regconfig, (search_terms || private_search_terms))", name: "index_dossiers_on_search_terms_private_search_terms", using: :gin
t.index "to_tsvector('french'::regconfig, search_terms)", name: "index_dossiers_on_search_terms", using: :gin
t.datetime "hidden_by_administration_at"
diff --git a/lib/tasks/lint.rake b/lib/tasks/lint.rake
index 05742b96a..cfdb3f906 100644
--- a/lib/tasks/lint.rake
+++ b/lib/tasks/lint.rake
@@ -7,4 +7,5 @@ task :lint do
sh "bundle exec i18n-tasks check-consistent-interpolations"
sh "bundle exec brakeman --no-pager"
sh "yarn lint:js"
+ sh "yarn lint:types"
end
diff --git a/package.json b/package.json
index 7ff9df2bf..a65a2247b 100644
--- a/package.json
+++ b/package.json
@@ -1,9 +1,10 @@
{
"dependencies": {
"@babel/preset-react": "^7.14.5",
+ "@babel/preset-typescript": "^7.16.7",
"@headlessui/react": "^1.3.0",
"@heroicons/react": "^1.0.1",
- "@mapbox/mapbox-gl-draw": "^1.2.2",
+ "@mapbox/mapbox-gl-draw": "^1.3.0",
"@popperjs/core": "^2.9.2",
"@rails/actiontext": "^6.1.4-1",
"@rails/activestorage": "^6.1.4-1",
@@ -12,7 +13,6 @@
"@reach/auto-id": "^0.16.0",
"@reach/combobox": "^0.13.0",
"@reach/slider": "^0.15.0",
- "@reach/visually-hidden": "^0.15.2",
"@sentry/browser": "6.12.0",
"@tmcw/togeojson": "^4.3.0",
"babel-plugin-macros": "^2.8.0",
@@ -22,18 +22,17 @@
"debounce": "^1.2.1",
"dom4": "^2.1.6",
"email-butler": "^1.0.13",
+ "geojson": "^0.5.0",
"highcharts": "^9.0.0",
"intersection-observer": "^0.12.0",
"is-hotkey": "^0.2.0",
- "mapbox-gl": "^1.3.0",
+ "maplibre-gl": "^1.15.2",
"match-sorter": "^6.2.0",
"prop-types": "^15.7.2",
"react": "^17.0.1",
- "react-coordinate-input": "^1.0.0-rc.2",
+ "react-coordinate-input": "^1.0.0",
"react-dom": "^17.0.1",
"react-intersection-observer": "^8.31.0",
- "react-mapbox-gl": "^5.1.1",
- "react-mapbox-gl-draw": "^2.0.4",
"react-popper": "^2.2.5",
"react-query": "^3.9.7",
"react-sortable-hoc": "^1.11.0",
@@ -46,6 +45,14 @@
},
"devDependencies": {
"@2fd/graphdoc": "^2.4.0",
+ "@types/debounce": "^1.2.1",
+ "@types/geojson": "^7946.0.8",
+ "@types/mapbox__mapbox-gl-draw": "^1.2.3",
+ "@types/rails__ujs": "^6.0.1",
+ "@types/react": "^17.0.38",
+ "@types/react-dom": "^17.0.11",
+ "@typescript-eslint/eslint-plugin": "^5.8.1",
+ "@typescript-eslint/parser": "^5.8.1",
"babel-eslint": "^10.1.0",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
@@ -54,12 +61,14 @@
"eslint-plugin-react-hooks": "^4.2.0",
"netlify-cli": "^8.3.0",
"prettier": "^2.3.2",
+ "typescript": "^4.5.5",
"webpack-bundle-analyzer": "^3.7.0",
"webpack-dev-server": "^4.6.0"
},
"scripts": {
"lint:js": "eslint --ext .js,.jsx,.ts,.tsx ./app/javascript ./config/webpack",
"webpack:build": "NODE_ENV=production bin/webpack",
+ "lint:types": "tsc",
"graphql:docs:build": "graphdoc --force",
"graphql:docs:deploy": "netlify deploy -d ./docs/graphql --prod",
"graphql:docs:publish": "yarn graphql:docs:build && yarn graphql:docs:deploy"
diff --git a/spec/controllers/instructeurs/dossiers_controller_spec.rb b/spec/controllers/instructeurs/dossiers_controller_spec.rb
index 8cde2714c..d5529f59a 100644
--- a/spec/controllers/instructeurs/dossiers_controller_spec.rb
+++ b/spec/controllers/instructeurs/dossiers_controller_spec.rb
@@ -774,11 +774,8 @@ describe Instructeurs::DossiersController, type: :controller do
expect(DossierOperationLog.where(dossier_id: dossier.id).last.operation).to eq('supprimer')
end
- it 'add a record into deleted_dossiers table' do
- expect(DeletedDossier.where(dossier_id: dossier.id).count).to eq(1)
- expect(DeletedDossier.where(dossier_id: dossier.id).first.revision_id).to eq(dossier.revision_id)
- expect(DeletedDossier.where(dossier_id: dossier.id).first.user_id).to eq(dossier.user_id)
- expect(DeletedDossier.where(dossier_id: dossier.id).first.groupe_instructeur_id).to eq(dossier.groupe_instructeur_id)
+ it 'does not add a record into deleted_dossiers table' do
+ expect(DeletedDossier.where(dossier_id: dossier.id).count).to eq(0)
end
it 'discard the dossier' do
@@ -804,6 +801,11 @@ describe Instructeurs::DossiersController, type: :controller do
it 'does not discard the dossier' do
expect(dossier.reload.hidden_at).to eq(nil)
end
+
+ it 'fill hidden by reason' do
+ expect(dossier.reload.hidden_by_reason).not_to eq(nil)
+ expect(dossier.reload.hidden_by_reason).to eq("instructeur_request")
+ end
end
context 'when the instructeur want to delete a dossier without a decision' do
diff --git a/spec/controllers/users/dossiers_controller_spec.rb b/spec/controllers/users/dossiers_controller_spec.rb
index 855e64790..9020996e6 100644
--- a/spec/controllers/users/dossiers_controller_spec.rb
+++ b/spec/controllers/users/dossiers_controller_spec.rb
@@ -1007,7 +1007,7 @@ describe Users::DossiersController, type: :controller do
shared_examples_for "the dossier can not be deleted" do
it "doesn’t notify the deletion" do
- expect(DossierMailer).not_to receive(:notify_deletion_to_administration)
+ expect(DossierMailer).not_to receive(:notify_en_construction_deletion_to_administration)
subject
end
@@ -1022,17 +1022,23 @@ describe Users::DossiersController, type: :controller do
let(:dossier) { create(:dossier, :en_construction, user: user, autorisation_donnees: true) }
it "notifies the user and the admin of the deletion" do
- expect(DossierMailer).to receive(:notify_deletion_to_administration).with(kind_of(DeletedDossier), dossier.procedure.administrateurs.first.email).and_return(double(deliver_later: nil))
+ expect(DossierMailer).to receive(:notify_en_construction_deletion_to_administration).with(kind_of(Dossier), dossier.procedure.administrateurs.first.email).and_return(double(deliver_later: nil))
subject
end
- it "hide the dossier and create a deleted dossier" do
+ it "hide the dossier and does not create a deleted dossier" do
procedure = dossier.procedure
dossier_id = dossier.id
subject
expect(Dossier.find_by(id: dossier_id)).to be_present
expect(Dossier.find_by(id: dossier_id).hidden_by_user_at).to be_present
- expect(procedure.deleted_dossiers.count).to eq(1)
+ expect(procedure.deleted_dossiers.count).to eq(0)
+ end
+
+ it "fill hidden by reason" do
+ subject
+ expect(dossier.reload.hidden_by_reason).not_to eq(nil)
+ expect(dossier.reload.hidden_by_reason).to eq("user_request")
end
it { is_expected.to redirect_to(dossiers_path) }
diff --git a/spec/jobs/cron/discarded_dossiers_deletion_job_spec.rb b/spec/jobs/cron/discarded_dossiers_deletion_job_spec.rb
index c938414ff..603da2fd4 100644
--- a/spec/jobs/cron/discarded_dossiers_deletion_job_spec.rb
+++ b/spec/jobs/cron/discarded_dossiers_deletion_job_spec.rb
@@ -8,6 +8,7 @@ RSpec.describe Cron::DiscardedDossiersDeletionJob, type: :job do
dossier.send(:log_dossier_operation, instructeur, :passer_en_instruction, dossier)
dossier.send(:log_dossier_operation, instructeur, :supprimer, dossier)
dossier.update_column(:hidden_at, hidden_at)
+ dossier.update_column(:hidden_by_reason, "user_request")
Cron::DiscardedDossiersDeletionJob.perform_now
end
@@ -42,7 +43,6 @@ RSpec.describe Cron::DiscardedDossiersDeletionJob, type: :job do
context 'not hidden' do
let(:hidden_at) { nil }
-
include_examples "does not delete"
end
@@ -60,7 +60,6 @@ RSpec.describe Cron::DiscardedDossiersDeletionJob, type: :job do
context 'hidden long ago' do
let(:hidden_at) { 1.week.ago - 1.hour }
-
include_examples "does delete"
end
end
diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb
index 62bee0e85..6026e6b9e 100644
--- a/spec/models/dossier_spec.rb
+++ b/spec/models/dossier_spec.rb
@@ -794,7 +794,6 @@ describe Dossier do
describe "#discard_and_keep_track!" do
let(:dossier) { create(:dossier, :en_construction) }
let(:user) { dossier.user }
- let(:deleted_dossier) { DeletedDossier.find_by(dossier_id: dossier.id) }
let(:last_operation) { dossier.dossier_operation_logs.last }
let(:reason) { :user_request }
@@ -811,10 +810,6 @@ describe Dossier do
expect(dossier.discarded?).to be_truthy
end
- it 'do not creates a DeletedDossier record' do
- expect(deleted_dossier).to be_nil
- end
-
it 'do not records the operation in the log' do
expect(last_operation).to be_nil
end
@@ -826,14 +821,6 @@ describe Dossier do
expect(dossier.hidden_by_user_at).to be_present
end
- it 'creates a DeletedDossier record' do
- expect(deleted_dossier.reason).to eq DeletedDossier.reasons.fetch(reason)
- expect(deleted_dossier.dossier_id).to eq dossier.id
- expect(deleted_dossier.procedure).to eq dossier.procedure
- expect(deleted_dossier.state).to eq dossier.state
- expect(deleted_dossier.deleted_at).to be_present
- end
-
it 'records the operation in the log' do
expect(last_operation.operation).to eq("supprimer")
expect(last_operation.automatic_operation?).to be_falsey
@@ -846,19 +833,6 @@ describe Dossier do
non_following_instructeur.groupe_instructeurs << dossier.procedure.defaut_groupe_instructeur
non_following_instructeur
end
-
- it 'notifies the following instructeurs' do
- expect(DossierMailer).to have_received(:notify_deletion_to_administration).once
- expect(DossierMailer).to have_received(:notify_deletion_to_administration).with(deleted_dossier, dossier.followers_instructeurs.first.email)
- end
- end
-
- context 'when there are no following instructeurs' do
- let(:dossier) { create(:dossier, :en_construction) }
- it 'notifies the procedure administrateur' do
- expect(DossierMailer).to have_received(:notify_deletion_to_administration).once
- expect(DossierMailer).to have_received(:notify_deletion_to_administration).with(deleted_dossier, dossier.procedure.administrateurs.first.email)
- end
end
context 'when dossier is brouillon' do
@@ -894,6 +868,19 @@ describe Dossier do
end
end
end
+
+ context 'termine' do
+ let(:dossier) { create(:dossier, state: "accepte", hidden_by_administration_at: 1.hour.ago) }
+ before { subject }
+
+ it 'affect the right deletion reason to the dossier' do
+ expect(dossier.hidden_by_reason).to eq("user_request")
+ end
+
+ it 'discard the dossier' do
+ expect(dossier.discarded?).to be_truthy
+ end
+ end
end
describe 'webhook' do
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index e7fb8669d..c008fb065 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -310,7 +310,7 @@ describe User, type: :model do
it "keep track of dossiers and delete user" do
user.delete_and_keep_track_dossiers(super_admin)
- expect(DeletedDossier.find_by(dossier_id: dossier_en_construction)).to be_present
+ expect(DeletedDossier.find_by(dossier_id: dossier_en_construction)).to be_nil
expect(DeletedDossier.find_by(dossier_id: dossier_brouillon)).to be_nil
expect(User.find_by(id: user.id)).to be_nil
end
@@ -324,7 +324,7 @@ describe User, type: :model do
dossier_to_discard.discard_and_keep_track!(super_admin, :user_request)
user.delete_and_keep_track_dossiers(super_admin)
- expect(DeletedDossier.find_by(dossier_id: dossier_en_construction)).to be_present
+ expect(DeletedDossier.find_by(dossier_id: dossier_en_construction)).to be_nil
expect(DeletedDossier.find_by(dossier_id: dossier_brouillon)).to be_nil
expect(Dossier.find_by(id: dossier_from_another_user.id)).to be_present
expect(User.find_by(id: user.id)).to be_nil
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 000000000..6995789e9
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,27 @@
+{
+ "compilerOptions": {
+ "declaration": false,
+ "experimentalDecorators": true,
+ "lib": ["DOM", "DOM.Iterable", "ES2019"],
+ "module": "es6",
+ "moduleResolution": "node",
+ "sourceMap": true,
+ "target": "ES2019",
+ "jsx": "react",
+ "noEmit": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "strict": true,
+ "paths": {
+ "~/*": ["./app/javascript/*"],
+ "@utils": ["./app/javascript/shared/utils.ts"]
+ }
+ },
+ "exclude": [
+ "**/*.spec.ts",
+ "node_modules",
+ "vendor",
+ "public"
+ ],
+ "compileOnSave": false
+}
diff --git a/yarn.lock b/yarn.lock
index 610630463..c2b08ad84 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -38,6 +38,13 @@
dependencies:
"@babel/highlight" "^7.16.0"
+"@babel/code-frame@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789"
+ integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==
+ dependencies:
+ "@babel/highlight" "^7.16.7"
+
"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.16.0", "@babel/compat-data@^7.16.4":
version "7.16.4"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.16.4.tgz#081d6bbc336ec5c2435c6346b2ae1fb98b5ac68e"
@@ -73,6 +80,15 @@
jsesc "^2.5.1"
source-map "^0.5.0"
+"@babel/generator@^7.16.8":
+ version "7.16.8"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.16.8.tgz#359d44d966b8cd059d543250ce79596f792f2ebe"
+ integrity sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==
+ dependencies:
+ "@babel/types" "^7.16.8"
+ jsesc "^2.5.1"
+ source-map "^0.5.0"
+
"@babel/helper-annotate-as-pure@^7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.0.tgz#9a1f0ebcda53d9a2d00108c4ceace6a5d5f1f08d"
@@ -80,6 +96,13 @@
dependencies:
"@babel/types" "^7.16.0"
+"@babel/helper-annotate-as-pure@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz#bb2339a7534a9c128e3102024c60760a3a7f3862"
+ integrity sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==
+ dependencies:
+ "@babel/types" "^7.16.7"
+
"@babel/helper-builder-binary-assignment-operator-visitor@^7.16.5":
version "7.16.5"
resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.5.tgz#a8429d064dce8207194b8bf05a70a9ea828746af"
@@ -111,6 +134,19 @@
"@babel/helper-replace-supers" "^7.16.5"
"@babel/helper-split-export-declaration" "^7.16.0"
+"@babel/helper-create-class-features-plugin@^7.16.7":
+ version "7.16.10"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.16.10.tgz#8a6959b9cc818a88815ba3c5474619e9c0f2c21c"
+ integrity sha512-wDeej0pu3WN/ffTxMNCPW5UCiOav8IcLRxSIyp/9+IF2xJUM9h/OYjg0IJLHaL6F8oU8kqMz9nc1vryXhMsgXg==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.16.7"
+ "@babel/helper-environment-visitor" "^7.16.7"
+ "@babel/helper-function-name" "^7.16.7"
+ "@babel/helper-member-expression-to-functions" "^7.16.7"
+ "@babel/helper-optimise-call-expression" "^7.16.7"
+ "@babel/helper-replace-supers" "^7.16.7"
+ "@babel/helper-split-export-declaration" "^7.16.7"
+
"@babel/helper-create-regexp-features-plugin@^7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.16.0.tgz#06b2348ce37fccc4f5e18dcd8d75053f2a7c44ff"
@@ -140,6 +176,13 @@
dependencies:
"@babel/types" "^7.16.0"
+"@babel/helper-environment-visitor@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz#ff484094a839bde9d89cd63cba017d7aae80ecd7"
+ integrity sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==
+ dependencies:
+ "@babel/types" "^7.16.7"
+
"@babel/helper-explode-assignable-expression@^7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.0.tgz#753017337a15f46f9c09f674cff10cee9b9d7778"
@@ -156,6 +199,15 @@
"@babel/template" "^7.16.0"
"@babel/types" "^7.16.0"
+"@babel/helper-function-name@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz#f1ec51551fb1c8956bc8dd95f38523b6cf375f8f"
+ integrity sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==
+ dependencies:
+ "@babel/helper-get-function-arity" "^7.16.7"
+ "@babel/template" "^7.16.7"
+ "@babel/types" "^7.16.7"
+
"@babel/helper-get-function-arity@^7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz#0088c7486b29a9cb5d948b1a1de46db66e089cfa"
@@ -163,6 +215,13 @@
dependencies:
"@babel/types" "^7.16.0"
+"@babel/helper-get-function-arity@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz#ea08ac753117a669f1508ba06ebcc49156387419"
+ integrity sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==
+ dependencies:
+ "@babel/types" "^7.16.7"
+
"@babel/helper-hoist-variables@^7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz#4c9023c2f1def7e28ff46fc1dbcd36a39beaa81a"
@@ -170,6 +229,13 @@
dependencies:
"@babel/types" "^7.16.0"
+"@babel/helper-hoist-variables@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246"
+ integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==
+ dependencies:
+ "@babel/types" "^7.16.7"
+
"@babel/helper-member-expression-to-functions@^7.16.5":
version "7.16.5"
resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.5.tgz#1bc9f7e87354e86f8879c67b316cb03d3dc2caab"
@@ -177,6 +243,13 @@
dependencies:
"@babel/types" "^7.16.0"
+"@babel/helper-member-expression-to-functions@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.7.tgz#42b9ca4b2b200123c3b7e726b0ae5153924905b0"
+ integrity sha512-VtJ/65tYiU/6AbMTDwyoXGPKHgTsfRarivm+YbB5uAzKUyuPjgZSgAFeG87FCigc7KNHu2Pegh1XIT3lXjvz3Q==
+ dependencies:
+ "@babel/types" "^7.16.7"
+
"@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz#90538e60b672ecf1b448f5f4f5433d37e79a3ec3"
@@ -205,11 +278,23 @@
dependencies:
"@babel/types" "^7.16.0"
+"@babel/helper-optimise-call-expression@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz#a34e3560605abbd31a18546bd2aad3e6d9a174f2"
+ integrity sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==
+ dependencies:
+ "@babel/types" "^7.16.7"
+
"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
version "7.16.5"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.5.tgz#afe37a45f39fce44a3d50a7958129ea5b1a5c074"
integrity sha512-59KHWHXxVA9K4HNF4sbHCf+eJeFe0Te/ZFGqBT4OjXhrwvA04sGfaEGsVTdsjoszq0YTP49RC9UKe5g8uN2RwQ==
+"@babel/helper-plugin-utils@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5"
+ integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==
+
"@babel/helper-remap-async-to-generator@^7.16.5":
version "7.16.5"
resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.5.tgz#e706646dc4018942acb4b29f7e185bc246d65ac3"
@@ -230,6 +315,17 @@
"@babel/traverse" "^7.16.5"
"@babel/types" "^7.16.0"
+"@babel/helper-replace-supers@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz#e9f5f5f32ac90429c1a4bdec0f231ef0c2838ab1"
+ integrity sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.16.7"
+ "@babel/helper-member-expression-to-functions" "^7.16.7"
+ "@babel/helper-optimise-call-expression" "^7.16.7"
+ "@babel/traverse" "^7.16.7"
+ "@babel/types" "^7.16.7"
+
"@babel/helper-simple-access@^7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz#21d6a27620e383e37534cf6c10bba019a6f90517"
@@ -251,16 +347,33 @@
dependencies:
"@babel/types" "^7.16.0"
+"@babel/helper-split-export-declaration@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b"
+ integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==
+ dependencies:
+ "@babel/types" "^7.16.7"
+
"@babel/helper-validator-identifier@^7.15.7":
version "7.15.7"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389"
integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==
+"@babel/helper-validator-identifier@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad"
+ integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==
+
"@babel/helper-validator-option@^7.14.5":
version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3"
integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==
+"@babel/helper-validator-option@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23"
+ integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==
+
"@babel/helper-wrap-function@^7.16.5":
version "7.16.5"
resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.16.5.tgz#0158fca6f6d0889c3fee8a6ed6e5e07b9b54e41f"
@@ -289,11 +402,25 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
+"@babel/highlight@^7.16.7":
+ version "7.16.10"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.10.tgz#744f2eb81579d6eea753c227b0f570ad785aba88"
+ integrity sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.16.7"
+ chalk "^2.0.0"
+ js-tokens "^4.0.0"
+
"@babel/parser@^7.0.0", "@babel/parser@^7.15.7", "@babel/parser@^7.16.0", "@babel/parser@^7.16.5", "@babel/parser@^7.7.0":
version "7.16.6"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.6.tgz#8f194828193e8fa79166f34a4b4e52f3e769a314"
integrity sha512-Gr86ujcNuPDnNOY8mi383Hvi8IYrJVJYuf3XcuBM/Dgd+bINn/7tHqsj+tKkoreMbmGsFLsltI/JJd8fOFWGDQ==
+"@babel/parser@^7.16.10", "@babel/parser@^7.16.7":
+ version "7.16.12"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.12.tgz#9474794f9a650cf5e2f892444227f98e28cdf8b6"
+ integrity sha512-VfaV15po8RiZssrkPweyvbGVSe4x2y+aciFCgn0n0/SJMR22cwofRV1mtnJQYcSB1wUTaA/X1LnA3es66MCO5A==
+
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.2":
version "7.16.2"
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.2.tgz#2977fca9b212db153c195674e57cfab807733183"
@@ -543,6 +670,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.14.5"
+"@babel/plugin-syntax-typescript@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz#39c9b55ee153151990fb038651d58d3fd03f98f8"
+ integrity sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.16.7"
+
"@babel/plugin-transform-arrow-functions@^7.16.5":
version "7.16.5"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.5.tgz#04c18944dd55397b521d9d7511e791acea7acf2d"
@@ -822,6 +956,15 @@
dependencies:
"@babel/helper-plugin-utils" "^7.16.5"
+"@babel/plugin-transform-typescript@^7.16.7":
+ version "7.16.8"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.8.tgz#591ce9b6b83504903fa9dd3652c357c2ba7a1ee0"
+ integrity sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.16.7"
+ "@babel/helper-plugin-utils" "^7.16.7"
+ "@babel/plugin-syntax-typescript" "^7.16.7"
+
"@babel/plugin-transform-unicode-escapes@^7.16.5":
version "7.16.5"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.5.tgz#80507c225af49b4f4ee647e2a0ce53d2eeff9e85"
@@ -940,6 +1083,15 @@
"@babel/plugin-transform-react-jsx-development" "^7.16.5"
"@babel/plugin-transform-react-pure-annotations" "^7.16.5"
+"@babel/preset-typescript@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.16.7.tgz#ab114d68bb2020afc069cd51b37ff98a046a70b9"
+ integrity sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.16.7"
+ "@babel/helper-validator-option" "^7.16.7"
+ "@babel/plugin-transform-typescript" "^7.16.7"
+
"@babel/runtime@^7.12.5", "@babel/runtime@^7.15.3", "@babel/runtime@^7.2.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4":
version "7.16.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.5.tgz#7f3e34bf8bdbbadf03fbb7b1ea0d929569c9487a"
@@ -956,6 +1108,15 @@
"@babel/parser" "^7.16.0"
"@babel/types" "^7.16.0"
+"@babel/template@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155"
+ integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==
+ dependencies:
+ "@babel/code-frame" "^7.16.7"
+ "@babel/parser" "^7.16.7"
+ "@babel/types" "^7.16.7"
+
"@babel/traverse@^7.13.0", "@babel/traverse@^7.16.5", "@babel/traverse@^7.7.0":
version "7.16.5"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.16.5.tgz#d7d400a8229c714a59b87624fc67b0f1fbd4b2b3"
@@ -972,6 +1133,22 @@
debug "^4.1.0"
globals "^11.1.0"
+"@babel/traverse@^7.16.7":
+ version "7.16.10"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.16.10.tgz#448f940defbe95b5a8029975b051f75993e8239f"
+ integrity sha512-yzuaYXoRJBGMlBhsMJoUW7G1UmSb/eXr/JHYM/MsOJgavJibLwASijW7oXBdw3NQ6T0bW7Ty5P/VarOs9cHmqw==
+ dependencies:
+ "@babel/code-frame" "^7.16.7"
+ "@babel/generator" "^7.16.8"
+ "@babel/helper-environment-visitor" "^7.16.7"
+ "@babel/helper-function-name" "^7.16.7"
+ "@babel/helper-hoist-variables" "^7.16.7"
+ "@babel/helper-split-export-declaration" "^7.16.7"
+ "@babel/parser" "^7.16.10"
+ "@babel/types" "^7.16.8"
+ debug "^4.1.0"
+ globals "^11.1.0"
+
"@babel/types@^7.16.0", "@babel/types@^7.4.4", "@babel/types@^7.7.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.16.0.tgz#db3b313804f96aadd0b776c4823e127ad67289ba"
@@ -980,6 +1157,14 @@
"@babel/helper-validator-identifier" "^7.15.7"
to-fast-properties "^2.0.0"
+"@babel/types@^7.16.7", "@babel/types@^7.16.8":
+ version "7.16.8"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.16.8.tgz#0ba5da91dd71e0a4e7781a30f22770831062e3c1"
+ integrity sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.16.7"
+ to-fast-properties "^2.0.0"
+
"@bugsnag/browser@^7.14.1":
version "7.14.1"
resolved "https://registry.yarnpkg.com/@bugsnag/browser/-/browser-7.14.1.tgz#ba92aae1fb40aeba0983d2af950f70cc82729882"
@@ -1172,7 +1357,7 @@
resolved "https://registry.yarnpkg.com/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz#ce56e539f83552b58d10d672ea4d6fc9adc7b234"
integrity sha1-zlblOfg1UrWNENZy6k1vya3HsjQ=
-"@mapbox/mapbox-gl-draw@^1.2.2":
+"@mapbox/mapbox-gl-draw@^1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@mapbox/mapbox-gl-draw/-/mapbox-gl-draw-1.3.0.tgz#7a30fb99488cb47a32c25e99c3c62413b04bbaed"
integrity sha512-B+KWK+dAgzLHMNyKVuuMRfjeSlQ77MhNLdfpQQpbp3pkhnrdmydDe3ixto1Ua78hktNut0WTrAaD8gYu4PVcjA==
@@ -1938,14 +2123,6 @@
tiny-warning "^1.0.3"
tslib "^2.3.0"
-"@reach/visually-hidden@^0.15.2":
- version "0.15.2"
- resolved "https://registry.yarnpkg.com/@reach/visually-hidden/-/visually-hidden-0.15.2.tgz#07794cb53f4bd23a9452d53a0ad7778711ee323f"
- integrity sha512-suDSCuKKuqiEB4UDgwWHbrPRxNwrusZ3ImXr85kfsQXGmKptMogaq22xoaHn32NC++lzZXxdWtAJriieszzFXw==
- dependencies:
- prop-types "^15.7.2"
- tslib "^2.3.0"
-
"@rollup/plugin-babel@^5.2.0":
version "5.3.0"
resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz#9cb1c5146ddd6a4968ad96f209c50c62f92f9879"
@@ -2133,23 +2310,6 @@
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e"
integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==
-"@turf/bbox@4.7.3":
- version "4.7.3"
- resolved "https://registry.yarnpkg.com/@turf/bbox/-/bbox-4.7.3.tgz#e3ad4f10a7e9b41b522880d33083198199059067"
- integrity sha1-461PEKfptBtSKIDTMIMZgZkFkGc=
- dependencies:
- "@turf/meta" "^4.7.3"
-
-"@turf/helpers@4.7.3":
- version "4.7.3"
- resolved "https://registry.yarnpkg.com/@turf/helpers/-/helpers-4.7.3.tgz#bc312ac43cab3c532a483151c4c382c5649429e9"
- integrity sha1-vDEqxDyrPFMqSDFRxMOCxWSUKek=
-
-"@turf/meta@^4.7.3":
- version "4.7.4"
- resolved "https://registry.yarnpkg.com/@turf/meta/-/meta-4.7.4.tgz#6de2f1e9890b8f64b669e4b47c09b20893063977"
- integrity sha1-beLx6YkLj2S2aeS0fAmyCJMGOXc=
-
"@types/cacheable-request@^6.0.1":
version "6.0.2"
resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.2.tgz#c324da0197de0a98a2312156536ae262429ff6b9"
@@ -2160,6 +2320,11 @@
"@types/node" "*"
"@types/responselike" "*"
+"@types/debounce@^1.2.1":
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/@types/debounce/-/debounce-1.2.1.tgz#79b65710bc8b6d44094d286aecf38e44f9627852"
+ integrity sha512-epMsEE85fi4lfmJUH/89/iV/LI+F5CvNIvmgs5g5jYFPfhO2S/ae8WSsLOKWdwtoaZw9Q2IhJ4tQ5tFCcS/4HA==
+
"@types/decompress@*":
version "4.2.4"
resolved "https://registry.yarnpkg.com/@types/decompress/-/decompress-4.2.4.tgz#dd2715d3ac1f566d03e6e302d1a26ffab59f8c5c"
@@ -2186,7 +2351,7 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
-"@types/geojson@*":
+"@types/geojson@*", "@types/geojson@^7946.0.8":
version "7946.0.8"
resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.8.tgz#30744afdb385e2945e22f3b033f897f76b1f12ca"
integrity sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==
@@ -2257,6 +2422,21 @@
dependencies:
"@types/node" "*"
+"@types/mapbox-gl@*":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@types/mapbox-gl/-/mapbox-gl-2.6.0.tgz#f9e8e963d5a11eba9d914d95ef8a00d015ca8732"
+ integrity sha512-lHdITzC0IVn9+Pq6WFkkK0N6rUKIqxsdrNeixiQdvROFn2Aeu3TDvhpuag1IdengL5WGGRuEhK6m6HB916ReLw==
+ dependencies:
+ "@types/geojson" "*"
+
+"@types/mapbox__mapbox-gl-draw@^1.2.3":
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/@types/mapbox__mapbox-gl-draw/-/mapbox__mapbox-gl-draw-1.2.3.tgz#1c280afaa813aa0095193db82df117a6c450de0e"
+ integrity sha512-S4Pm3w19S8mduiPgoeSt1UQ4BoqrObJtQRQpkD21hGHb6VsRy3VrD7ZCoC7/r5zwnjsGXhbUqy3lg1mGcI6QzQ==
+ dependencies:
+ "@types/geojson" "*"
+ "@types/mapbox-gl" "*"
+
"@types/minimatch@*":
version "3.0.5"
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40"
@@ -2290,11 +2470,37 @@
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
+"@types/prop-types@*":
+ version "15.7.4"
+ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11"
+ integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==
+
"@types/q@^1.5.1":
version "1.5.5"
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.5.tgz#75a2a8e7d8ab4b230414505d92335d1dcb53a6df"
integrity sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==
+"@types/rails__ujs@^6.0.1":
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/@types/rails__ujs/-/rails__ujs-6.0.1.tgz#83c5aa1dad88ca869de05a9523eff58041ab307a"
+ integrity sha512-CVwNOdzTQ9qn6X6HPwx6ikH1T9ueJTdfjwFlXFhGvzXsQuESUksibfSosgxs1D/Q1kVEpjxeXD2RzqJv0Ma5Gw==
+
+"@types/react-dom@^17.0.11":
+ version "17.0.11"
+ resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.11.tgz#e1eadc3c5e86bdb5f7684e00274ae228e7bcc466"
+ integrity sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==
+ dependencies:
+ "@types/react" "*"
+
+"@types/react@*", "@types/react@^17.0.38":
+ version "17.0.38"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.38.tgz#f24249fefd89357d5fa71f739a686b8d7c7202bd"
+ integrity sha512-SI92X1IA+FMnP3qM5m4QReluXzhcmovhZnLNm3pyeQlooi02qI7sLiepEYqT678uNiyc25XfCqxREFpy3W7YhQ==
+ dependencies:
+ "@types/prop-types" "*"
+ "@types/scheduler" "*"
+ csstype "^3.0.2"
+
"@types/resolve@1.17.1":
version "1.17.1"
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
@@ -2314,18 +2520,16 @@
resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.1.tgz#d8f1c0d0dc23afad6dc16a9e993a0865774b4065"
integrity sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==
+"@types/scheduler@*":
+ version "0.16.2"
+ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"
+ integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==
+
"@types/semver@^7.0.0":
version "7.3.9"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.9.tgz#152c6c20a7688c30b967ec1841d31ace569863fc"
integrity sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==
-"@types/supercluster@^5.0.1":
- version "5.0.3"
- resolved "https://registry.yarnpkg.com/@types/supercluster/-/supercluster-5.0.3.tgz#aa03a77c6545265e63b50fa267ab12afe0c27658"
- integrity sha512-XMSqQEr7YDuNtFwSgaHHOjsbi0ZGL62V9Js4CW45RBuRYlNWSW/KDqN+RFFE7HdHcGhJPtN0klKvw06r9Kg7rg==
- dependencies:
- "@types/geojson" "*"
-
"@types/warning@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.0.tgz#0d2501268ad8f9962b740d387c4654f5f8e23e52"
@@ -2343,11 +2547,71 @@
dependencies:
"@types/yargs-parser" "*"
+"@typescript-eslint/eslint-plugin@^5.8.1":
+ version "5.10.2"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.10.2.tgz#f8c1d59fc37bd6d9d11c97267fdfe722c4777152"
+ integrity sha512-4W/9lLuE+v27O/oe7hXJKjNtBLnZE8tQAFpapdxwSVHqtmIoPB1gph3+ahNwVuNL37BX7YQHyGF9Xv6XCnIX2Q==
+ dependencies:
+ "@typescript-eslint/scope-manager" "5.10.2"
+ "@typescript-eslint/type-utils" "5.10.2"
+ "@typescript-eslint/utils" "5.10.2"
+ debug "^4.3.2"
+ functional-red-black-tree "^1.0.1"
+ ignore "^5.1.8"
+ regexpp "^3.2.0"
+ semver "^7.3.5"
+ tsutils "^3.21.0"
+
+"@typescript-eslint/parser@^5.8.1":
+ version "5.10.2"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.10.2.tgz#b6076d27cc5499ce3f2c625f5ccde946ecb7db9a"
+ integrity sha512-JaNYGkaQVhP6HNF+lkdOr2cAs2wdSZBoalE22uYWq8IEv/OVH0RksSGydk+sW8cLoSeYmC+OHvRyv2i4AQ7Czg==
+ dependencies:
+ "@typescript-eslint/scope-manager" "5.10.2"
+ "@typescript-eslint/types" "5.10.2"
+ "@typescript-eslint/typescript-estree" "5.10.2"
+ debug "^4.3.2"
+
+"@typescript-eslint/scope-manager@5.10.2":
+ version "5.10.2"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.10.2.tgz#92c0bc935ec00f3d8638cdffb3d0e70c9b879639"
+ integrity sha512-39Tm6f4RoZoVUWBYr3ekS75TYgpr5Y+X0xLZxXqcZNDWZdJdYbKd3q2IR4V9y5NxxiPu/jxJ8XP7EgHiEQtFnw==
+ dependencies:
+ "@typescript-eslint/types" "5.10.2"
+ "@typescript-eslint/visitor-keys" "5.10.2"
+
+"@typescript-eslint/type-utils@5.10.2":
+ version "5.10.2"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.10.2.tgz#ad5acdf98a7d2ab030bea81f17da457519101ceb"
+ integrity sha512-uRKSvw/Ccs5FYEoXW04Z5VfzF2iiZcx8Fu7DGIB7RHozuP0VbKNzP1KfZkHBTM75pCpsWxIthEH1B33dmGBKHw==
+ dependencies:
+ "@typescript-eslint/utils" "5.10.2"
+ debug "^4.3.2"
+ tsutils "^3.21.0"
+
"@typescript-eslint/types@4.33.0":
version "4.33.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72"
integrity sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==
+"@typescript-eslint/types@5.10.2":
+ version "5.10.2"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.10.2.tgz#604d15d795c4601fffba6ecb4587ff9fdec68ce8"
+ integrity sha512-Qfp0qk/5j2Rz3p3/WhWgu4S1JtMcPgFLnmAKAW061uXxKSa7VWKZsDXVaMXh2N60CX9h6YLaBoy9PJAfCOjk3w==
+
+"@typescript-eslint/typescript-estree@5.10.2":
+ version "5.10.2"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.10.2.tgz#810906056cd3ddcb35aa333fdbbef3713b0fe4a7"
+ integrity sha512-WHHw6a9vvZls6JkTgGljwCsMkv8wu8XU8WaYKeYhxhWXH/atZeiMW6uDFPLZOvzNOGmuSMvHtZKd6AuC8PrwKQ==
+ dependencies:
+ "@typescript-eslint/types" "5.10.2"
+ "@typescript-eslint/visitor-keys" "5.10.2"
+ debug "^4.3.2"
+ globby "^11.0.4"
+ is-glob "^4.0.3"
+ semver "^7.3.5"
+ tsutils "^3.21.0"
+
"@typescript-eslint/typescript-estree@^4.8.2":
version "4.33.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz#0dfb51c2908f68c5c08d82aefeaf166a17c24609"
@@ -2361,6 +2625,18 @@
semver "^7.3.5"
tsutils "^3.21.0"
+"@typescript-eslint/utils@5.10.2":
+ version "5.10.2"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.10.2.tgz#1fcd37547c32c648ab11aea7173ec30060ee87a8"
+ integrity sha512-vuJaBeig1NnBRkf7q9tgMLREiYD7zsMrsN1DA3wcoMDvr3BTFiIpKjGiYZoKPllfEwN7spUjv7ZqD+JhbVjEPg==
+ dependencies:
+ "@types/json-schema" "^7.0.9"
+ "@typescript-eslint/scope-manager" "5.10.2"
+ "@typescript-eslint/types" "5.10.2"
+ "@typescript-eslint/typescript-estree" "5.10.2"
+ eslint-scope "^5.1.1"
+ eslint-utils "^3.0.0"
+
"@typescript-eslint/visitor-keys@4.33.0":
version "4.33.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz#2a22f77a41604289b7a186586e9ec48ca92ef1dd"
@@ -2369,6 +2645,14 @@
"@typescript-eslint/types" "4.33.0"
eslint-visitor-keys "^2.0.0"
+"@typescript-eslint/visitor-keys@5.10.2":
+ version "5.10.2"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.10.2.tgz#fdbf272d8e61c045d865bd6c8b41bea73d222f3d"
+ integrity sha512-zHIhYGGGrFJvvyfwHk5M08C5B5K4bewkm+rrvNTKk1/S15YHR+SA/QUF8ZWscXSfEaB8Nn2puZj+iHcoxVOD/Q==
+ dependencies:
+ "@typescript-eslint/types" "5.10.2"
+ eslint-visitor-keys "^3.0.0"
+
"@vercel/nft@^0.17.0":
version "0.17.0"
resolved "https://registry.yarnpkg.com/@vercel/nft/-/nft-0.17.0.tgz#28851fefe42fae7a116dc5e23a0a9da29929a18b"
@@ -4597,6 +4881,11 @@ csso@^4.0.2:
dependencies:
css-tree "^1.1.2"
+csstype@^3.0.2:
+ version "3.0.10"
+ resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.10.tgz#2ad3a7bed70f35b965707c092e5f30b327c290e5"
+ integrity sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==
+
cyclist@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
@@ -4633,7 +4922,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
dependencies:
ms "2.0.0"
-debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1:
+debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2:
version "4.3.3"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
@@ -4731,11 +5020,6 @@ decompress@^4.2.1:
pify "^2.3.0"
strip-dirs "^2.0.0"
-deep-equal@1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
- integrity sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=
-
deep-equal@^1.0.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a"
@@ -5415,6 +5699,13 @@ eslint-utils@^2.1.0:
dependencies:
eslint-visitor-keys "^1.1.0"
+eslint-utils@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672"
+ integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==
+ dependencies:
+ eslint-visitor-keys "^2.0.0"
+
eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
@@ -5425,6 +5716,11 @@ eslint-visitor-keys@^2.0.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303"
integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==
+eslint-visitor-keys@^3.0.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz#6fbb166a6798ee5991358bc2daa1ba76cc1254a1"
+ integrity sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ==
+
eslint@^7.32.0:
version "7.32.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d"
@@ -5756,6 +6052,17 @@ fast-glob@^3.0.3, fast-glob@^3.1.1:
merge2 "^1.3.0"
micromatch "^4.0.4"
+fast-glob@^3.2.9:
+ version "3.2.11"
+ resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9"
+ integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==
+ dependencies:
+ "@nodelib/fs.stat" "^2.0.2"
+ "@nodelib/fs.walk" "^1.2.3"
+ glob-parent "^5.1.2"
+ merge2 "^1.3.0"
+ micromatch "^4.0.4"
+
fast-json-stable-stringify@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
@@ -6227,6 +6534,11 @@ geojson-vt@^3.2.1:
resolved "https://registry.yarnpkg.com/geojson-vt/-/geojson-vt-3.2.1.tgz#f8adb614d2c1d3f6ee7c4265cad4bbf3ad60c8b7"
integrity sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg==
+geojson@^0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/geojson/-/geojson-0.5.0.tgz#3cd6c96399be65b56ee55596116fe9191ce701c0"
+ integrity sha1-PNbJY5m+ZbVu5VWWEW/pGRznAcA=
+
get-amd-module-type@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/get-amd-module-type/-/get-amd-module-type-3.0.0.tgz#bb334662fa04427018c937774570de495845c288"
@@ -6467,6 +6779,18 @@ globby@^11.0.0, globby@^11.0.1, globby@^11.0.3:
merge2 "^1.3.0"
slash "^3.0.0"
+globby@^11.0.4:
+ version "11.1.0"
+ resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
+ integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
+ dependencies:
+ array-union "^2.1.0"
+ dir-glob "^3.0.1"
+ fast-glob "^3.2.9"
+ ignore "^5.2.0"
+ merge2 "^1.4.1"
+ slash "^3.0.0"
+
globby@^9.2.0:
version "9.2.0"
resolved "https://registry.yarnpkg.com/globby/-/globby-9.2.0.tgz#fd029a706c703d29bdd170f4b6db3a3f7a7cb63d"
@@ -6943,7 +7267,7 @@ ignore@^4.0.3, ignore@^4.0.6:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
-ignore@^5.1.1, ignore@^5.1.4:
+ignore@^5.1.1, ignore@^5.1.4, ignore@^5.1.8, ignore@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a"
integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==
@@ -7337,7 +7661,7 @@ is-glob@^3.0.0, is-glob@^3.1.0:
dependencies:
is-extglob "^2.1.0"
-is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1:
+is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
version "4.0.3"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
@@ -8241,10 +8565,10 @@ map-visit@^1.0.0:
dependencies:
object-visit "^1.0.0"
-mapbox-gl@^1.3.0:
- version "1.13.2"
- resolved "https://registry.yarnpkg.com/mapbox-gl/-/mapbox-gl-1.13.2.tgz#76639c44f141f8dff71b7d8f1504f2aed11f7517"
- integrity sha512-CPjtWygL+f7naL+sGHoC2JQR0DG7u+9ik6WdkjjVmz2uy0kBC2l+aKfdi3ZzUR7VKSQJ6Mc/CeCN+6iVNah+ww==
+maplibre-gl@^1.15.2:
+ version "1.15.2"
+ resolved "https://registry.yarnpkg.com/maplibre-gl/-/maplibre-gl-1.15.2.tgz#7fb47868b62455af916c090903f2154394450f9c"
+ integrity sha512-uPeV530apb4JfX3cRFfE+awFnbcJTOnCv2QvY4mw4huiInbybElWYkNzTs324YLSADq0f4bidRoYcR81ho3aLA==
dependencies:
"@mapbox/geojson-rewind" "^0.5.0"
"@mapbox/geojson-types" "^1.0.2"
@@ -8374,7 +8698,7 @@ merge-stream@^2.0.0:
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
-merge2@^1.2.3, merge2@^1.3.0:
+merge2@^1.2.3, merge2@^1.3.0, merge2@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
@@ -10821,7 +11145,7 @@ rc@^1.2.7, rc@^1.2.8:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
-react-coordinate-input@^1.0.0-rc.2:
+react-coordinate-input@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/react-coordinate-input/-/react-coordinate-input-1.0.0.tgz#884be529fe311820e19651bd74d2a0b1cfc7f823"
integrity sha512-9iJti+WU38mk+Pab/5+Rn24IKfgxKEwcq4yJeyttAy50NTg33K5xwc/+NcPQoEB82xG0iTW9lRlx1WZCOPjWoQ==
@@ -10859,22 +11183,6 @@ react-is@^17.0.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
-react-mapbox-gl-draw@^2.0.4:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/react-mapbox-gl-draw/-/react-mapbox-gl-draw-2.0.4.tgz#476d70a6efc07c329fa61c11022bcdab60ac4b91"
- integrity sha512-oaBdIlyu+g7PhLUvwnCsl/wvu+5tGB9I3RLjcrYLt6U1sUMzQJqplKtVxXRv9TZqRdNaAU5qNOP+dRs55QKjsA==
-
-react-mapbox-gl@^5.1.1:
- version "5.1.1"
- resolved "https://registry.yarnpkg.com/react-mapbox-gl/-/react-mapbox-gl-5.1.1.tgz#49e1ddf441c3ff9406d10ccd577ac5448d51584c"
- integrity sha512-8vGldFQf7pW8T5ZV2OOhwXoaBvfigB2F7dnhzaZ/bD5/KJzP9zprMbn0xMX95W3eqbKzGGHnwyD5DyTTwR6wGw==
- dependencies:
- "@turf/bbox" "4.7.3"
- "@turf/helpers" "4.7.3"
- "@types/supercluster" "^5.0.1"
- deep-equal "1.0.1"
- supercluster "^7.0.0"
-
react-popper@^2.2.5:
version "2.2.5"
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.2.5.tgz#1214ef3cec86330a171671a4fbcbeeb65ee58e96"
@@ -11028,7 +11336,7 @@ regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.3.1:
call-bind "^1.0.2"
define-properties "^1.1.3"
-regexpp@^3.1.0:
+regexpp@^3.1.0, regexpp@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2"
integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==
@@ -12164,7 +12472,7 @@ stylehacks@^4.0.0:
postcss "^7.0.0"
postcss-selector-parser "^3.0.0"
-supercluster@^7.0.0, supercluster@^7.1.0:
+supercluster@^7.1.0:
version "7.1.4"
resolved "https://registry.yarnpkg.com/supercluster/-/supercluster-7.1.4.tgz#6762aabfd985d3390b49f13b815567d5116a828a"
integrity sha512-GhKkRM1jMR6WUwGPw05fs66pOFWhf59lXq+Q3J3SxPvhNcmgOtLRV6aVQPMRsmXdpaeFJGivt+t7QXUPL3ff4g==
@@ -12740,6 +13048,11 @@ typescript@^4.1.5, typescript@^4.4.3:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8"
integrity sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==
+typescript@^4.5.5:
+ version "4.5.5"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3"
+ integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==
+
uid-safe@2.1.5:
version "2.1.5"
resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a"