commit
0be8be78b5
60 changed files with 2378 additions and 1605 deletions
32
.eslintrc.js
32
.eslintrc.js
|
@ -6,18 +6,24 @@ module.exports = {
|
||||||
sourceType: 'module'
|
sourceType: 'module'
|
||||||
},
|
},
|
||||||
globals: {
|
globals: {
|
||||||
'process': true,
|
process: true,
|
||||||
'gon': true
|
gon: true
|
||||||
},
|
},
|
||||||
plugins: ['prettier', 'react-hooks'],
|
plugins: ['prettier', 'react-hooks'],
|
||||||
extends: ['eslint:recommended', 'prettier', 'plugin:react/recommended'],
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'prettier',
|
||||||
|
'plugin:react/recommended',
|
||||||
|
'plugin:react-hooks/recommended'
|
||||||
|
],
|
||||||
env: {
|
env: {
|
||||||
es6: true,
|
es6: true,
|
||||||
browser: true
|
browser: true
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
'prettier/prettier': 'error',
|
'prettier/prettier': 'error',
|
||||||
'react-hooks/rules-of-hooks': 'error'
|
'react-hooks/rules-of-hooks': 'error',
|
||||||
|
'react/prop-types': 'off'
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
react: {
|
react: {
|
||||||
|
@ -26,10 +32,26 @@ module.exports = {
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
files: ['config/webpack/**/*.js', 'babel.config.js', 'postcss.config.js'],
|
files: [
|
||||||
|
'.eslintrc.js',
|
||||||
|
'config/webpack/**/*.js',
|
||||||
|
'babel.config.js',
|
||||||
|
'postcss.config.js'
|
||||||
|
],
|
||||||
env: {
|
env: {
|
||||||
node: true
|
node: true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.ts', '**/*.tsx'],
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
plugins: ['@typescript-eslint'],
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:react-hooks/recommended',
|
||||||
|
'prettier'
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-style-panel {
|
.map-style-panel {
|
||||||
z-index: 99;
|
z-index: 1;
|
||||||
padding: $default-spacer;
|
padding: $default-spacer;
|
||||||
margin-bottom: $default-spacer;
|
margin-bottom: $default-spacer;
|
||||||
|
|
||||||
|
@ -60,6 +60,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.cadastres-selection-control {
|
.cadastres-selection-control {
|
||||||
|
z-index: 1;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 135px;
|
top: 135px;
|
||||||
left: 10px;
|
left: 10px;
|
||||||
|
|
|
@ -58,7 +58,7 @@
|
||||||
|
|
||||||
// Labels that we only want for screen readers
|
// Labels that we only want for screen readers
|
||||||
// https://www.coolfields.co.uk/2016/05/text-for-screen-readers-only-updated/
|
// https://www.coolfields.co.uk/2016/05/text-for-screen-readers-only-updated/
|
||||||
.screen-reader-text {
|
.sr-only {
|
||||||
border: none;
|
border: none;
|
||||||
clip: rect(1px, 1px, 1px, 1px);
|
clip: rect(1px, 1px, 1px, 1px);
|
||||||
clip-path: inset(50%);
|
clip-path: inset(50%);
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
import React, { useCallback } from 'react';
|
|
||||||
import { QueryClientProvider } from 'react-query';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import ComboSearch from './ComboSearch';
|
|
||||||
import { queryClient } from './shared/queryClient';
|
|
||||||
|
|
||||||
function ComboAdresseSearch({
|
|
||||||
transformResult = ({ properties: { label } }) => [label, label],
|
|
||||||
allowInputValues = true,
|
|
||||||
...props
|
|
||||||
}) {
|
|
||||||
const transformResults = useCallback((_, { features }) => features);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<QueryClientProvider client={queryClient}>
|
|
||||||
<ComboSearch
|
|
||||||
allowInputValues={allowInputValues}
|
|
||||||
scope="adresse"
|
|
||||||
minimumInputLength={2}
|
|
||||||
transformResult={transformResult}
|
|
||||||
transformResults={transformResults}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</QueryClientProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ComboAdresseSearch.propTypes = {
|
|
||||||
transformResult: PropTypes.func,
|
|
||||||
allowInputValues: PropTypes.bool
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ComboAdresseSearch;
|
|
31
app/javascript/components/ComboAdresseSearch.tsx
Normal file
31
app/javascript/components/ComboAdresseSearch.tsx
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { QueryClientProvider } from 'react-query';
|
||||||
|
import type { FeatureCollection, Geometry } from 'geojson';
|
||||||
|
|
||||||
|
import ComboSearch, { ComboSearchProps } from './ComboSearch';
|
||||||
|
import { queryClient } from './shared/queryClient';
|
||||||
|
|
||||||
|
type RawResult = FeatureCollection<Geometry, { label: string }>;
|
||||||
|
type AdresseResult = RawResult['features'][0];
|
||||||
|
type ComboAdresseSearchProps = Omit<
|
||||||
|
ComboSearchProps<AdresseResult>,
|
||||||
|
'minimumInputLength' | 'transformResult' | 'transformResults' | 'scope'
|
||||||
|
>;
|
||||||
|
|
||||||
|
export default function ComboAdresseSearch({
|
||||||
|
allowInputValues = true,
|
||||||
|
...props
|
||||||
|
}: ComboAdresseSearchProps) {
|
||||||
|
return (
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<ComboSearch<AdresseResult>
|
||||||
|
{...props}
|
||||||
|
allowInputValues={allowInputValues}
|
||||||
|
scope="adresse"
|
||||||
|
minimumInputLength={2}
|
||||||
|
transformResult={({ properties: { label } }) => [label, label, label]}
|
||||||
|
transformResults={(_, result) => (result as RawResult).features}
|
||||||
|
/>
|
||||||
|
</QueryClientProvider>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
import React, { useState, useCallback, useRef } from 'react';
|
import React, { useState, useCallback, useRef } from 'react';
|
||||||
import { useDebounce } from 'use-debounce';
|
import { useDebounce } from 'use-debounce';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import {
|
import {
|
||||||
Combobox,
|
Combobox,
|
||||||
ComboboxInput,
|
ComboboxInput,
|
||||||
|
@ -14,11 +13,33 @@ import invariant from 'tiny-invariant';
|
||||||
|
|
||||||
import { useDeferredSubmit, useHiddenField, groupId } from './shared/hooks';
|
import { useDeferredSubmit, useHiddenField, groupId } from './shared/hooks';
|
||||||
|
|
||||||
function defaultTransformResults(_, results) {
|
type TransformResults<Result> = (term: string, results: unknown) => Result[];
|
||||||
return results;
|
type TransformResult<Result> = (
|
||||||
}
|
result: Result
|
||||||
|
) => [key: string, value: string, label: string];
|
||||||
|
|
||||||
function ComboSearch({
|
export type ComboSearchProps<Result> = {
|
||||||
|
onChange?: (value: string | null, result?: Result) => void;
|
||||||
|
value?: string;
|
||||||
|
scope: string;
|
||||||
|
scopeExtra?: string;
|
||||||
|
minimumInputLength: number;
|
||||||
|
transformResults: TransformResults<Result>;
|
||||||
|
transformResult: TransformResult<Result>;
|
||||||
|
allowInputValues?: boolean;
|
||||||
|
id?: string;
|
||||||
|
describedby?: string;
|
||||||
|
className?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type QueryKey = readonly [
|
||||||
|
scope: string,
|
||||||
|
term: string,
|
||||||
|
extra: string | undefined
|
||||||
|
];
|
||||||
|
|
||||||
|
function ComboSearch<Result>({
|
||||||
onChange,
|
onChange,
|
||||||
value: controlledValue,
|
value: controlledValue,
|
||||||
scope,
|
scope,
|
||||||
|
@ -26,26 +47,28 @@ function ComboSearch({
|
||||||
minimumInputLength,
|
minimumInputLength,
|
||||||
transformResult,
|
transformResult,
|
||||||
allowInputValues = false,
|
allowInputValues = false,
|
||||||
transformResults = defaultTransformResults,
|
transformResults = (_, results) => results as Result[],
|
||||||
id,
|
id,
|
||||||
describedby,
|
describedby,
|
||||||
...props
|
...props
|
||||||
}) {
|
}: ComboSearchProps<Result>) {
|
||||||
invariant(id || onChange, 'ComboSearch: `id` or `onChange` are required');
|
invariant(id || onChange, 'ComboSearch: `id` or `onChange` are required');
|
||||||
|
|
||||||
const group = !onChange ? groupId(id) : null;
|
const group = !onChange && id ? groupId(id) : undefined;
|
||||||
const [externalValue, setExternalValue, hiddenField] = useHiddenField(group);
|
const [externalValue, setExternalValue, hiddenField] = useHiddenField(group);
|
||||||
const [, setExternalId] = useHiddenField(group, 'external_id');
|
const [, setExternalId] = useHiddenField(group, 'external_id');
|
||||||
const initialValue = externalValue ? externalValue : controlledValue;
|
const initialValue = externalValue ? externalValue : controlledValue;
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [debouncedSearchTerm] = useDebounce(searchTerm, 300);
|
const [debouncedSearchTerm] = useDebounce(searchTerm, 300);
|
||||||
const [value, setValue] = useState(initialValue);
|
const [value, setValue] = useState(initialValue);
|
||||||
const resultsMap = useRef({});
|
const resultsMap = useRef<
|
||||||
const getLabel = (result) => {
|
Record<string, { key: string; value: string; result: Result }>
|
||||||
|
>({});
|
||||||
|
const getLabel = (result: Result) => {
|
||||||
const [, value, label] = transformResult(result);
|
const [, value, label] = transformResult(result);
|
||||||
return label ?? value;
|
return label ?? value;
|
||||||
};
|
};
|
||||||
const setExternalValueAndId = useCallback((label) => {
|
const setExternalValueAndId = useCallback((label: string) => {
|
||||||
const { key, value, result } = resultsMap.current[label];
|
const { key, value, result } = resultsMap.current[label];
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
onChange(value, result);
|
onChange(value, result);
|
||||||
|
@ -77,22 +100,22 @@ function ComboSearch({
|
||||||
[minimumInputLength]
|
[minimumInputLength]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleOnSelect = useCallback((value) => {
|
const handleOnSelect = useCallback((value: string) => {
|
||||||
setExternalValueAndId(value);
|
setExternalValueAndId(value);
|
||||||
setValue(value);
|
setValue(value);
|
||||||
setSearchTerm('');
|
setSearchTerm('');
|
||||||
awaitFormSubmit.done();
|
awaitFormSubmit.done();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { isSuccess, data } = useQuery(
|
const { isSuccess, data } = useQuery<void, void, unknown, QueryKey>(
|
||||||
[scope, debouncedSearchTerm, scopeExtra],
|
[scope, debouncedSearchTerm, scopeExtra],
|
||||||
{
|
{
|
||||||
enabled: !!debouncedSearchTerm,
|
enabled: !!debouncedSearchTerm,
|
||||||
notifyOnStatusChange: false,
|
|
||||||
refetchOnMount: false
|
refetchOnMount: false
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const results = isSuccess ? transformResults(debouncedSearchTerm, data) : [];
|
const results =
|
||||||
|
isSuccess && data ? transformResults(debouncedSearchTerm, data) : [];
|
||||||
|
|
||||||
const onBlur = useCallback(() => {
|
const onBlur = useCallback(() => {
|
||||||
if (!allowInputValues && isSuccess && results[0]) {
|
if (!allowInputValues && isSuccess && results[0]) {
|
||||||
|
@ -136,18 +159,4 @@ function ComboSearch({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ComboSearch.propTypes = {
|
|
||||||
value: PropTypes.string,
|
|
||||||
scope: PropTypes.string,
|
|
||||||
minimumInputLength: PropTypes.number,
|
|
||||||
transformResult: PropTypes.func,
|
|
||||||
transformResults: PropTypes.func,
|
|
||||||
allowInputValues: PropTypes.bool,
|
|
||||||
onChange: PropTypes.func,
|
|
||||||
scopeExtra: PropTypes.string,
|
|
||||||
mandatory: PropTypes.bool,
|
|
||||||
id: PropTypes.string,
|
|
||||||
describedby: PropTypes.string
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ComboSearch;
|
export default ComboSearch;
|
|
@ -0,0 +1,27 @@
|
||||||
|
import React from 'react';
|
||||||
|
import type { Point } from 'geojson';
|
||||||
|
|
||||||
|
import ComboAdresseSearch from '../../ComboAdresseSearch';
|
||||||
|
import { useFlyTo } from '../../shared/maplibre/hooks';
|
||||||
|
|
||||||
|
export function AddressInput() {
|
||||||
|
const flyTo = useFlyTo();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginBottom: '10px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ComboAdresseSearch
|
||||||
|
className="no-margin"
|
||||||
|
placeholder="Rechercher une adresse : saisissez au moins 2 caractères"
|
||||||
|
allowInputValues={false}
|
||||||
|
onChange={(_, result) => {
|
||||||
|
const geometry = result?.geometry as Point;
|
||||||
|
flyTo(17, geometry.coordinates as [number, number]);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
164
app/javascript/components/MapEditor/components/CadastreLayer.tsx
Normal file
164
app/javascript/components/MapEditor/components/CadastreLayer.tsx
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
import { useCallback, useRef } from 'react';
|
||||||
|
import type { Feature, FeatureCollection } from 'geojson';
|
||||||
|
|
||||||
|
import { useMapLibre } from '../../shared/maplibre/MapLibre';
|
||||||
|
import {
|
||||||
|
useEvent,
|
||||||
|
useMapEvent,
|
||||||
|
EventHandler
|
||||||
|
} from '../../shared/maplibre/hooks';
|
||||||
|
import {
|
||||||
|
filterFeatureCollection,
|
||||||
|
findFeature
|
||||||
|
} from '../../shared/maplibre/utils';
|
||||||
|
|
||||||
|
import { SOURCE_CADASTRE, CreateFeatures, DeleteFeatures } from '../hooks';
|
||||||
|
|
||||||
|
export function CadastreLayer({
|
||||||
|
featureCollection,
|
||||||
|
createFeatures,
|
||||||
|
deleteFeatures,
|
||||||
|
enabled
|
||||||
|
}: {
|
||||||
|
featureCollection: FeatureCollection;
|
||||||
|
createFeatures: CreateFeatures;
|
||||||
|
deleteFeatures: DeleteFeatures;
|
||||||
|
enabled: boolean;
|
||||||
|
}) {
|
||||||
|
const map = useMapLibre();
|
||||||
|
const selectedCadastresRef = useRef(new Set<string>());
|
||||||
|
|
||||||
|
const highlightFeature = useCallback((cid: string, highlight: boolean) => {
|
||||||
|
if (highlight) {
|
||||||
|
selectedCadastresRef.current.add(cid);
|
||||||
|
} else {
|
||||||
|
selectedCadastresRef.current.delete(cid);
|
||||||
|
}
|
||||||
|
if (selectedCadastresRef.current.size == 0) {
|
||||||
|
map.setFilter('parcelle-highlighted', ['in', 'id', '']);
|
||||||
|
} else {
|
||||||
|
map.setFilter('parcelle-highlighted', [
|
||||||
|
'in',
|
||||||
|
'id',
|
||||||
|
...selectedCadastresRef.current
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const hoverFeature = useCallback((feature: Feature, hover: boolean) => {
|
||||||
|
if (!selectedCadastresRef.current.has(feature.properties?.id)) {
|
||||||
|
map.setFeatureState(
|
||||||
|
{
|
||||||
|
source: 'cadastre',
|
||||||
|
sourceLayer: 'parcelles',
|
||||||
|
id: String(feature.id)
|
||||||
|
},
|
||||||
|
{ hover }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useCadastres(featureCollection, {
|
||||||
|
hoverFeature,
|
||||||
|
createFeatures,
|
||||||
|
deleteFeatures,
|
||||||
|
enabled
|
||||||
|
});
|
||||||
|
|
||||||
|
useMapEvent('styledata', () => {
|
||||||
|
selectedCadastresRef.current = new Set(
|
||||||
|
filterFeatureCollection(featureCollection, SOURCE_CADASTRE).features.map(
|
||||||
|
({ properties }) => properties?.cid
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (selectedCadastresRef.current.size > 0) {
|
||||||
|
map.setFilter('parcelle-highlighted', [
|
||||||
|
'in',
|
||||||
|
'id',
|
||||||
|
...selectedCadastresRef.current
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const onHighlight = useCallback(
|
||||||
|
({ detail }) => {
|
||||||
|
highlightFeature(detail.cid, detail.highlight);
|
||||||
|
},
|
||||||
|
[highlightFeature]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEvent('map:internal:cadastre:highlight', onHighlight);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function useCadastres(
|
||||||
|
featureCollection: FeatureCollection,
|
||||||
|
{
|
||||||
|
enabled,
|
||||||
|
hoverFeature,
|
||||||
|
createFeatures,
|
||||||
|
deleteFeatures
|
||||||
|
}: {
|
||||||
|
enabled: boolean;
|
||||||
|
hoverFeature: (feature: Feature, flag: boolean) => void;
|
||||||
|
createFeatures: CreateFeatures;
|
||||||
|
deleteFeatures: DeleteFeatures;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const hoveredFeature = useRef<Feature | null>();
|
||||||
|
|
||||||
|
const onMouseMove = useCallback<EventHandler>(
|
||||||
|
(event) => {
|
||||||
|
if (enabled && event.features && 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[enabled, hoverFeature]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onMouseLeave = useCallback<EventHandler>(() => {
|
||||||
|
if (hoveredFeature.current) {
|
||||||
|
hoverFeature(hoveredFeature.current, false);
|
||||||
|
}
|
||||||
|
hoveredFeature.current = null;
|
||||||
|
}, [hoverFeature]);
|
||||||
|
|
||||||
|
const onClick = useCallback<EventHandler>(
|
||||||
|
async (event) => {
|
||||||
|
if (enabled && event.features && 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[enabled, featureCollection, createFeatures, deleteFeatures]
|
||||||
|
);
|
||||||
|
|
||||||
|
useMapEvent('click', onClick, 'parcelles-fill');
|
||||||
|
useMapEvent('mousemove', onMouseMove, 'parcelles-fill');
|
||||||
|
useMapEvent('mouseleave', onMouseLeave, 'parcelles-fill');
|
||||||
|
}
|
183
app/javascript/components/MapEditor/components/DrawLayer.tsx
Normal file
183
app/javascript/components/MapEditor/components/DrawLayer.tsx
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
import { useCallback, useRef, useEffect } from 'react';
|
||||||
|
import type { LngLatBoundsLike } from 'maplibre-gl';
|
||||||
|
import DrawControl from '@mapbox/mapbox-gl-draw';
|
||||||
|
import type { FeatureCollection } from 'geojson';
|
||||||
|
|
||||||
|
import { useMapLibre } from '../../shared/maplibre/MapLibre';
|
||||||
|
import {
|
||||||
|
useFitBounds,
|
||||||
|
useEvent,
|
||||||
|
useMapEvent
|
||||||
|
} from '../../shared/maplibre/hooks';
|
||||||
|
import {
|
||||||
|
filterFeatureCollection,
|
||||||
|
findFeature,
|
||||||
|
getBounds
|
||||||
|
} from '../../shared/maplibre/utils';
|
||||||
|
import {
|
||||||
|
SOURCE_SELECTION_UTILISATEUR,
|
||||||
|
CreateFeatures,
|
||||||
|
UpdateFatures,
|
||||||
|
DeleteFeatures
|
||||||
|
} from '../hooks';
|
||||||
|
|
||||||
|
export function DrawLayer({
|
||||||
|
featureCollection,
|
||||||
|
createFeatures,
|
||||||
|
updateFeatures,
|
||||||
|
deleteFeatures,
|
||||||
|
enabled
|
||||||
|
}: {
|
||||||
|
featureCollection: FeatureCollection;
|
||||||
|
createFeatures: CreateFeatures;
|
||||||
|
updateFeatures: UpdateFatures;
|
||||||
|
deleteFeatures: DeleteFeatures;
|
||||||
|
enabled: boolean;
|
||||||
|
}) {
|
||||||
|
const map = useMapLibre();
|
||||||
|
const drawRef = useRef<DrawControl | null>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!drawRef.current && enabled) {
|
||||||
|
const draw = new DrawControl({
|
||||||
|
displayControlsDefault: false,
|
||||||
|
controls: {
|
||||||
|
point: true,
|
||||||
|
line_string: true,
|
||||||
|
polygon: true,
|
||||||
|
trash: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
map.addControl(draw as any, 'top-left');
|
||||||
|
draw.set(
|
||||||
|
filterFeatureCollection(featureCollection, SOURCE_SELECTION_UTILISATEUR)
|
||||||
|
);
|
||||||
|
drawRef.current = draw;
|
||||||
|
|
||||||
|
for (const [selector, translation] of translations) {
|
||||||
|
const element = document.querySelector(selector);
|
||||||
|
if (element) {
|
||||||
|
element.setAttribute('title', translation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (drawRef.current) {
|
||||||
|
map.removeControl(drawRef.current as any);
|
||||||
|
drawRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [enabled]);
|
||||||
|
|
||||||
|
const onSetId = useCallback(({ detail }) => {
|
||||||
|
drawRef.current?.setFeatureProperty(detail.lid, 'id', detail.id);
|
||||||
|
}, []);
|
||||||
|
const onAddFeature = useCallback(({ detail }) => {
|
||||||
|
drawRef.current?.add(detail.feature);
|
||||||
|
}, []);
|
||||||
|
const onDeleteFature = useCallback(({ detail }) => {
|
||||||
|
drawRef.current?.delete(detail.id);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useMapEvent('draw.create', createFeatures);
|
||||||
|
useMapEvent('draw.update', updateFeatures);
|
||||||
|
useMapEvent('draw.delete', deleteFeatures);
|
||||||
|
|
||||||
|
useEvent('map:internal:draw:setId', onSetId);
|
||||||
|
useEvent('map:internal:draw:add', onAddFeature);
|
||||||
|
useEvent('map:internal:draw:delete', onDeleteFature);
|
||||||
|
|
||||||
|
useExternalEvents(featureCollection, {
|
||||||
|
createFeatures,
|
||||||
|
updateFeatures,
|
||||||
|
deleteFeatures
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function useExternalEvents(
|
||||||
|
featureCollection: FeatureCollection,
|
||||||
|
{
|
||||||
|
createFeatures,
|
||||||
|
updateFeatures,
|
||||||
|
deleteFeatures
|
||||||
|
}: {
|
||||||
|
createFeatures: CreateFeatures;
|
||||||
|
updateFeatures: UpdateFatures;
|
||||||
|
deleteFeatures: DeleteFeatures;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const fitBounds = useFitBounds();
|
||||||
|
|
||||||
|
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: [{ type: 'Feature', 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]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fitBounds(featureCollection.bbox as LngLatBoundsLike);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEvent('map:feature:focus', onFeatureFocus);
|
||||||
|
useEvent('map:feature:create', onFeatureCreate);
|
||||||
|
useEvent('map:feature:update', onFeatureUpdate);
|
||||||
|
useEvent('map:feature:delete', onFeatureDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
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']
|
||||||
|
];
|
|
@ -0,0 +1,132 @@
|
||||||
|
import React, { useState, useCallback, MouseEvent, ChangeEvent } from 'react';
|
||||||
|
import type { FeatureCollection } from 'geojson';
|
||||||
|
import invariant from 'tiny-invariant';
|
||||||
|
|
||||||
|
import { readGeoFile } from '../readGeoFile';
|
||||||
|
import { generateId } from '../../shared/maplibre/utils';
|
||||||
|
import { CreateFeatures, DeleteFeatures } from '../hooks';
|
||||||
|
|
||||||
|
export function ImportFileInput({
|
||||||
|
featureCollection,
|
||||||
|
createFeatures,
|
||||||
|
deleteFeatures
|
||||||
|
}: {
|
||||||
|
featureCollection: FeatureCollection;
|
||||||
|
createFeatures: CreateFeatures;
|
||||||
|
deleteFeatures: DeleteFeatures;
|
||||||
|
}) {
|
||||||
|
const { inputs, addInputFile, removeInputFile, onFileChange } =
|
||||||
|
useImportFiles(featureCollection, { createFeatures, deleteFeatures });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="file-import" style={{ marginBottom: '10px' }}>
|
||||||
|
<button className="button send primary" onClick={addInputFile}>
|
||||||
|
Ajouter un fichier GPX ou KML
|
||||||
|
</button>
|
||||||
|
<div>
|
||||||
|
{inputs.map((input) => (
|
||||||
|
<div key={input.id}>
|
||||||
|
<input
|
||||||
|
title="Choisir un fichier gpx ou kml"
|
||||||
|
style={{ marginTop: '15px' }}
|
||||||
|
id={input.id}
|
||||||
|
type="file"
|
||||||
|
accept=".gpx, .kml"
|
||||||
|
disabled={input.disabled}
|
||||||
|
onChange={(e) => onFileChange(e, input.id)}
|
||||||
|
/>
|
||||||
|
{input.hasValue && (
|
||||||
|
<span
|
||||||
|
title="Supprimer le fichier"
|
||||||
|
className="icon refuse"
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer'
|
||||||
|
}}
|
||||||
|
onClick={(e) => removeInputFile(e, input.id)}
|
||||||
|
></span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileInput = {
|
||||||
|
id: string;
|
||||||
|
disabled: boolean;
|
||||||
|
hasValue: boolean;
|
||||||
|
filename: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function useImportFiles(
|
||||||
|
featureCollection: FeatureCollection,
|
||||||
|
{
|
||||||
|
createFeatures,
|
||||||
|
deleteFeatures
|
||||||
|
}: { createFeatures: CreateFeatures; deleteFeatures: DeleteFeatures }
|
||||||
|
) {
|
||||||
|
const [inputs, setInputs] = useState<FileInput[]>([]);
|
||||||
|
const addInput = useCallback(
|
||||||
|
(input: FileInput) => {
|
||||||
|
setInputs((inputs) => [...inputs, input]);
|
||||||
|
},
|
||||||
|
[setInputs]
|
||||||
|
);
|
||||||
|
const removeInput = useCallback(
|
||||||
|
(inputId: string) => {
|
||||||
|
setInputs((inputs) => inputs.filter((input) => input.id !== inputId));
|
||||||
|
},
|
||||||
|
[setInputs]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onFileChange = useCallback(
|
||||||
|
async (event: ChangeEvent<HTMLInputElement>, inputId: string) => {
|
||||||
|
invariant(event.target.files, '');
|
||||||
|
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]
|
||||||
|
);
|
||||||
|
|
||||||
|
const addInputFile = useCallback(
|
||||||
|
(event: MouseEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
addInput({
|
||||||
|
id: generateId(),
|
||||||
|
disabled: false,
|
||||||
|
hasValue: false,
|
||||||
|
filename: ''
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[addInput]
|
||||||
|
);
|
||||||
|
|
||||||
|
const removeInputFile = useCallback(
|
||||||
|
(event: MouseEvent, inputId: string) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const filename = inputs.find((input) => input.id === inputId)?.filename;
|
||||||
|
const features = featureCollection.features.filter(
|
||||||
|
(feature) => feature.properties?.filename == filename
|
||||||
|
);
|
||||||
|
deleteFeatures({ features, external: true });
|
||||||
|
removeInput(inputId);
|
||||||
|
},
|
||||||
|
[inputs, removeInput, deleteFeatures, featureCollection]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
inputs,
|
||||||
|
onFileChange,
|
||||||
|
addInputFile,
|
||||||
|
removeInputFile
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { fire } from '@utils';
|
||||||
|
import type { Feature } from 'geojson';
|
||||||
|
import { PlusIcon, LocationMarkerIcon } from '@heroicons/react/outline';
|
||||||
|
import { useId } from '@reach/auto-id';
|
||||||
|
import CoordinateInput from 'react-coordinate-input';
|
||||||
|
|
||||||
|
import { useFlyTo } from '../../shared/maplibre/hooks';
|
||||||
|
|
||||||
|
export function PointInput() {
|
||||||
|
const flyTo = useFlyTo();
|
||||||
|
|
||||||
|
const inputId = useId();
|
||||||
|
const [value, setValue] = useState('');
|
||||||
|
const [feature, setFeature] = useState<Feature | null>(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 (
|
||||||
|
<>
|
||||||
|
<label
|
||||||
|
className="areas-title mt-1"
|
||||||
|
htmlFor={inputId}
|
||||||
|
style={{ fontSize: '16px' }}
|
||||||
|
>
|
||||||
|
Ajouter un point sur la carte
|
||||||
|
</label>
|
||||||
|
<div className="flex align-center mt-1 mb-2">
|
||||||
|
{navigator.geolocation ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="button mr-1"
|
||||||
|
onClick={getCurrentPosition}
|
||||||
|
title="Localiser votre position"
|
||||||
|
>
|
||||||
|
<span className="sr-only">Localiser votre position</span>
|
||||||
|
<LocationMarkerIcon className="icon-size-big" aria-hidden />
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
<CoordinateInput
|
||||||
|
id={inputId}
|
||||||
|
className="m-0 mr-1"
|
||||||
|
value={value}
|
||||||
|
onChange={(value: string, { dd }: { dd: [number, number] }) => {
|
||||||
|
setValue(value);
|
||||||
|
if (dd.length) {
|
||||||
|
const coordinates: [number, number] = [dd[1], dd[0]];
|
||||||
|
setFeature({
|
||||||
|
type: 'Feature',
|
||||||
|
geometry: {
|
||||||
|
type: 'Point',
|
||||||
|
coordinates
|
||||||
|
},
|
||||||
|
properties: {}
|
||||||
|
});
|
||||||
|
flyTo(17, coordinates);
|
||||||
|
} else {
|
||||||
|
setFeature(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="button"
|
||||||
|
onClick={addPoint}
|
||||||
|
disabled={!feature}
|
||||||
|
title="Ajouter le point avec les coordonnées saisies sur la carte"
|
||||||
|
>
|
||||||
|
<span className="sr-only">
|
||||||
|
Ajouter le point avec les coordonnées saisies sur la carte
|
||||||
|
</span>
|
||||||
|
<PlusIcon className="icon-size-big" aria-hidden />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
208
app/javascript/components/MapEditor/hooks.ts
Normal file
208
app/javascript/components/MapEditor/hooks.ts
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
import { useState, useCallback, useEffect } from 'react';
|
||||||
|
import { getJSON, ajax, fire } from '@utils';
|
||||||
|
import type { Feature, FeatureCollection, Geometry } from 'geojson';
|
||||||
|
|
||||||
|
export const SOURCE_SELECTION_UTILISATEUR = 'selection_utilisateur';
|
||||||
|
export const SOURCE_CADASTRE = 'cadastre';
|
||||||
|
|
||||||
|
export type CreateFeatures = (params: {
|
||||||
|
features: Feature<Geometry>[];
|
||||||
|
source?: string;
|
||||||
|
external?: true;
|
||||||
|
}) => void;
|
||||||
|
export type UpdateFatures = (params: {
|
||||||
|
features: Feature<Geometry>[];
|
||||||
|
source?: string;
|
||||||
|
external?: true;
|
||||||
|
}) => void;
|
||||||
|
export type DeleteFeatures = (params: {
|
||||||
|
features: Feature<Geometry>[];
|
||||||
|
source?: string;
|
||||||
|
external?: true;
|
||||||
|
}) => void;
|
||||||
|
|
||||||
|
export function useFeatureCollection(
|
||||||
|
initialFeatureCollection: FeatureCollection,
|
||||||
|
{ url, enabled = true }: { url: string; enabled: boolean }
|
||||||
|
) {
|
||||||
|
const [error, onError] = useError();
|
||||||
|
const [featureCollection, setFeatureCollection] = useState(
|
||||||
|
initialFeatureCollection
|
||||||
|
);
|
||||||
|
const updateFeatureCollection = useCallback<
|
||||||
|
(callback: (features: Feature[]) => Feature[]) => void
|
||||||
|
>(
|
||||||
|
(callback) => {
|
||||||
|
setFeatureCollection(({ features }) => ({
|
||||||
|
type: 'FeatureCollection',
|
||||||
|
features: callback(features)
|
||||||
|
}));
|
||||||
|
ajax({ url, type: 'GET' })
|
||||||
|
.then(() => fire(document, 'ds:page:update'))
|
||||||
|
.catch(() => null);
|
||||||
|
},
|
||||||
|
[url, setFeatureCollection]
|
||||||
|
);
|
||||||
|
|
||||||
|
const addFeatures = useCallback(
|
||||||
|
(features: (Feature & { lid?: string })[], external: boolean) => {
|
||||||
|
for (const feature of features) {
|
||||||
|
if (feature.lid) {
|
||||||
|
fire(document, 'map:internal:draw:setId', {
|
||||||
|
lid: feature.lid,
|
||||||
|
id: feature.properties?.id
|
||||||
|
});
|
||||||
|
delete feature.lid;
|
||||||
|
}
|
||||||
|
if (external) {
|
||||||
|
if (feature.properties?.source == SOURCE_SELECTION_UTILISATEUR) {
|
||||||
|
fire(document, 'map:internal:draw:add', {
|
||||||
|
feature: {
|
||||||
|
id: feature.properties.id,
|
||||||
|
...feature
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
fire(document, 'map:internal:cadastre:highlight', {
|
||||||
|
cid: feature.properties?.cid,
|
||||||
|
highlight: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const removeFeatures = useCallback(
|
||||||
|
(features: Feature[], external: boolean) => {
|
||||||
|
if (external) {
|
||||||
|
for (const feature of features) {
|
||||||
|
if (feature.properties?.source == SOURCE_SELECTION_UTILISATEUR) {
|
||||||
|
fire(document, 'map:internal:draw:delete', { id: feature.id });
|
||||||
|
} else {
|
||||||
|
fire(document, 'map:internal:cadastre:highlight', {
|
||||||
|
cid: feature.properties?.cid,
|
||||||
|
highlight: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const createFeatures = useCallback<CreateFeatures>(
|
||||||
|
async ({
|
||||||
|
features,
|
||||||
|
source = SOURCE_SELECTION_UTILISATEUR,
|
||||||
|
external = false
|
||||||
|
}) => {
|
||||||
|
if (!enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const newFeatures: Feature[] = [];
|
||||||
|
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]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
onError('Le polygone dessiné n’est pas valide.');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[enabled, url, updateFeatureCollection, addFeatures, onError]
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateFeatures = useCallback<UpdateFatures>(
|
||||||
|
async ({
|
||||||
|
features,
|
||||||
|
source = SOURCE_SELECTION_UTILISATEUR,
|
||||||
|
external = false
|
||||||
|
}) => {
|
||||||
|
if (!enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const newFeatures: Feature[] = [];
|
||||||
|
for (const feature of features) {
|
||||||
|
const id = feature.properties?.id;
|
||||||
|
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, onError]
|
||||||
|
);
|
||||||
|
|
||||||
|
const deleteFeatures = useCallback<DeleteFeatures>(
|
||||||
|
async ({ features, external = false }) => {
|
||||||
|
if (!enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const deletedFeatures = [];
|
||||||
|
for (const feature of features) {
|
||||||
|
const id = feature.properties?.id;
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
onError('Le polygone n’a pas pu être supprimé.');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[enabled, url, updateFeatureCollection, removeFeatures, onError]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
featureCollection,
|
||||||
|
error,
|
||||||
|
createFeatures,
|
||||||
|
updateFeatures,
|
||||||
|
deleteFeatures
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function useError(): [string | undefined, (message: string) => void] {
|
||||||
|
const [error, onError] = useState<string | undefined>();
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => onError(undefined), 5000);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [error]);
|
||||||
|
|
||||||
|
return [error, onError];
|
||||||
|
}
|
|
@ -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 (
|
|
||||||
<p>
|
|
||||||
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
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{error && <FlashMessage message={error} level="alert" fixed={true} />}
|
|
||||||
<div>
|
|
||||||
<p style={{ marginBottom: '20px' }}>
|
|
||||||
Besoin d'aide ?
|
|
||||||
<a
|
|
||||||
href="https://doc.demarches-simplifiees.fr/pour-aller-plus-loin/cartographie"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
consulter les tutoriels video
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="file-import" style={{ marginBottom: '10px' }}>
|
|
||||||
<button className="button send primary" onClick={addInputFile}>
|
|
||||||
Ajouter un fichier GPX ou KML
|
|
||||||
</button>
|
|
||||||
<div>
|
|
||||||
{inputs.map((input) => (
|
|
||||||
<div key={input.id}>
|
|
||||||
<input
|
|
||||||
title="Choisir un fichier gpx ou kml"
|
|
||||||
style={{ marginTop: '15px' }}
|
|
||||||
id={input.id}
|
|
||||||
type="file"
|
|
||||||
accept=".gpx, .kml"
|
|
||||||
disabled={input.disabled}
|
|
||||||
onChange={(e) => onFileChange(e, input.id)}
|
|
||||||
/>
|
|
||||||
{input.hasValue && (
|
|
||||||
<span
|
|
||||||
title="Supprimer le fichier"
|
|
||||||
className="icon refuse"
|
|
||||||
style={{
|
|
||||||
cursor: 'pointer'
|
|
||||||
}}
|
|
||||||
onClick={(e) => removeInputFile(e, input.id)}
|
|
||||||
></span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
marginBottom: '10px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ComboAdresseSearch
|
|
||||||
className="no-margin"
|
|
||||||
placeholder="Rechercher une adresse : saisissez au moins 2 caractères"
|
|
||||||
allowInputValues={false}
|
|
||||||
onChange={(_, { geometry: { coordinates } }) => {
|
|
||||||
setCoords(coordinates);
|
|
||||||
setZoom([17]);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Mapbox
|
|
||||||
onStyleLoad={(map) => onLoad(map)}
|
|
||||||
center={coords}
|
|
||||||
zoom={zoom}
|
|
||||||
style={style}
|
|
||||||
containerStyle={{ height: '500px' }}
|
|
||||||
>
|
|
||||||
{!cadastreEnabled && (
|
|
||||||
<DrawControl
|
|
||||||
ref={drawRef}
|
|
||||||
onDrawCreate={createFeatures}
|
|
||||||
onDrawUpdate={updateFeatures}
|
|
||||||
onDrawDelete={deleteFeatures}
|
|
||||||
displayControlsDefault={false}
|
|
||||||
controls={{
|
|
||||||
point: true,
|
|
||||||
line_string: true,
|
|
||||||
polygon: true,
|
|
||||||
trash: true
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<MapStyleControl
|
|
||||||
style={style.id}
|
|
||||||
layers={layers}
|
|
||||||
setStyle={setStyle}
|
|
||||||
setLayerEnabled={setLayerEnabled}
|
|
||||||
setLayerOpacity={setLayerOpacity}
|
|
||||||
/>
|
|
||||||
<ZoomControl />
|
|
||||||
{options.layers.includes('cadastres') && (
|
|
||||||
<div className="cadastres-selection-control mapboxgl-ctrl-group">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() =>
|
|
||||||
setCadastreEnabled((cadastreEnabled) => !cadastreEnabled)
|
|
||||||
}
|
|
||||||
title="Sélectionner les parcelles cadastrales"
|
|
||||||
className={cadastreEnabled ? 'on' : ''}
|
|
||||||
>
|
|
||||||
<CursorClickIcon className="icon-size" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Mapbox>
|
|
||||||
<PointInput />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<>
|
|
||||||
<label
|
|
||||||
className="areas-title mt-1"
|
|
||||||
htmlFor={inputId}
|
|
||||||
style={{ fontSize: '16px' }}
|
|
||||||
>
|
|
||||||
Ajouter un point sur la carte
|
|
||||||
</label>
|
|
||||||
<div className="flex align-center mt-1 mb-2">
|
|
||||||
{navigator.geolocation ? (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="button mr-1"
|
|
||||||
onClick={getCurrentPosition}
|
|
||||||
title="Localiser votre position"
|
|
||||||
>
|
|
||||||
<VisuallyHidden>Localiser votre position</VisuallyHidden>
|
|
||||||
<LocationMarkerIcon className="icon-size-big" />
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
<CoordinateInput
|
|
||||||
id={inputId}
|
|
||||||
className="m-0 mr-1"
|
|
||||||
value={value}
|
|
||||||
onChange={(value, { dd }) => {
|
|
||||||
setValue(value);
|
|
||||||
setFeature(
|
|
||||||
dd.length
|
|
||||||
? {
|
|
||||||
type: 'Feature',
|
|
||||||
geometry: {
|
|
||||||
type: 'Point',
|
|
||||||
coordinates: dd.reverse()
|
|
||||||
},
|
|
||||||
properties: {}
|
|
||||||
}
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="button"
|
|
||||||
onClick={addPoint}
|
|
||||||
disabled={!feature}
|
|
||||||
title="Ajouter le point avec les coordonnées saisies sur la carte"
|
|
||||||
>
|
|
||||||
<VisuallyHidden>
|
|
||||||
Ajouter le point avec les coordonnées saisies sur la carte
|
|
||||||
</VisuallyHidden>
|
|
||||||
<PlusIcon className="icon-size-big" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
91
app/javascript/components/MapEditor/index.tsx
Normal file
91
app/javascript/components/MapEditor/index.tsx
Normal file
|
@ -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 (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<p style={{ marginBottom: '20px' }}>
|
||||||
|
Besoin d'aide ?
|
||||||
|
<a
|
||||||
|
href="https://doc.demarches-simplifiees.fr/pour-aller-plus-loin/cartographie"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
consulter les tutoriels video
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{error && <FlashMessage message={error} level="alert" fixed={true} />}
|
||||||
|
<MapLibre
|
||||||
|
layers={options.layers}
|
||||||
|
header={
|
||||||
|
<>
|
||||||
|
<ImportFileInput
|
||||||
|
featureCollection={featureCollection}
|
||||||
|
{...actions}
|
||||||
|
/>
|
||||||
|
<AddressInput />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
footer={<PointInput />}
|
||||||
|
>
|
||||||
|
<DrawLayer
|
||||||
|
featureCollection={featureCollection}
|
||||||
|
{...actions}
|
||||||
|
enabled={!preview && !cadastreEnabled}
|
||||||
|
/>
|
||||||
|
{options.layers.includes('cadastres') ? (
|
||||||
|
<>
|
||||||
|
<CadastreLayer
|
||||||
|
featureCollection={featureCollection}
|
||||||
|
{...actions}
|
||||||
|
enabled={!preview && cadastreEnabled}
|
||||||
|
/>
|
||||||
|
<div className="cadastres-selection-control mapboxgl-ctrl-group">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
setCadastreEnabled((cadastreEnabled) => !cadastreEnabled)
|
||||||
|
}
|
||||||
|
title="Sélectionner les parcelles cadastrales"
|
||||||
|
className={cadastreEnabled ? 'on' : ''}
|
||||||
|
>
|
||||||
|
<CursorClickIcon className="icon-size" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</MapLibre>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,29 +1,37 @@
|
||||||
import { gpx, kml } from '@tmcw/togeojson/dist/togeojson.es.js';
|
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 isGpxFile = file.name.includes('.gpx');
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise<ReturnType<typeof normalizeFeatureCollection>>(
|
||||||
reader.onload = (event) => {
|
(resolve) => {
|
||||||
const xml = new DOMParser().parseFromString(
|
reader.onload = (event: FileReaderEventMap['load']) => {
|
||||||
event.target.result,
|
const result = event.target?.result;
|
||||||
'text/xml'
|
const xml = new DOMParser().parseFromString(
|
||||||
);
|
result as string,
|
||||||
const featureCollection = normalizeFeatureCollection(
|
'text/xml'
|
||||||
isGpxFile ? gpx(xml) : kml(xml),
|
);
|
||||||
file.name
|
const featureCollection = normalizeFeatureCollection(
|
||||||
);
|
isGpxFile ? gpx(xml) : kml(xml),
|
||||||
|
file.name
|
||||||
|
);
|
||||||
|
|
||||||
resolve(featureCollection);
|
resolve(featureCollection);
|
||||||
};
|
};
|
||||||
reader.readAsText(file, 'UTF-8');
|
reader.readAsText(file, 'UTF-8');
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeFeatureCollection(featureCollection, filename) {
|
function normalizeFeatureCollection(
|
||||||
const features = [];
|
featureCollection: FeatureCollection,
|
||||||
|
filename: string
|
||||||
|
) {
|
||||||
|
const features: Feature[] = [];
|
||||||
for (const feature of featureCollection.features) {
|
for (const feature of featureCollection.features) {
|
||||||
switch (feature.geometry.type) {
|
switch (feature.geometry.type) {
|
||||||
case 'MultiPoint':
|
case 'MultiPoint':
|
||||||
|
@ -76,13 +84,13 @@ function normalizeFeatureCollection(featureCollection, filename) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
featureCollection.filename = `${generateId()}-${filename}`;
|
const featureCollectionFilename = `${generateId()}-${filename}`;
|
||||||
featureCollection.features = features.map((feature) => ({
|
featureCollection.features = features.map((feature) => ({
|
||||||
...feature,
|
...feature,
|
||||||
properties: {
|
properties: {
|
||||||
...feature.properties,
|
...feature.properties,
|
||||||
filename: featureCollection.filename
|
filename: featureCollectionFilename
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
return featureCollection;
|
return { ...featureCollection, filename: featureCollectionFilename };
|
||||||
}
|
}
|
|
@ -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]);
|
|
||||||
}
|
|
|
@ -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<Set<string>>();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
237
app/javascript/components/MapReader/components/GeoJSONLayer.tsx
Normal file
237
app/javascript/components/MapReader/components/GeoJSONLayer.tsx
Normal file
|
@ -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<EventHandler>(
|
||||||
|
(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) => (
|
||||||
|
<PolygonLayer
|
||||||
|
key={feature.properties?.id}
|
||||||
|
feature={feature}
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{lines.features.map((feature) => (
|
||||||
|
<LineStringLayer
|
||||||
|
key={feature.properties?.id}
|
||||||
|
feature={feature}
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{points.features.map((feature) => (
|
||||||
|
<PointLayer
|
||||||
|
key={feature.properties?.id}
|
||||||
|
feature={feature}
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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'
|
||||||
|
};
|
|
@ -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 (
|
|
||||||
<p>
|
|
||||||
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
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Mapbox
|
|
||||||
onStyleLoad={(map) => onLoad(map)}
|
|
||||||
style={style}
|
|
||||||
containerStyle={{ height: '500px' }}
|
|
||||||
>
|
|
||||||
<SelectionUtilisateurPolygonLayer
|
|
||||||
featureCollection={featureCollection}
|
|
||||||
onMouseEnter={onMouseEnter}
|
|
||||||
onMouseLeave={onMouseLeave}
|
|
||||||
/>
|
|
||||||
<SelectionUtilisateurLineLayer
|
|
||||||
featureCollection={featureCollection}
|
|
||||||
onMouseEnter={onMouseEnter}
|
|
||||||
onMouseLeave={onMouseLeave}
|
|
||||||
/>
|
|
||||||
<SelectionUtilisateurPointLayer
|
|
||||||
featureCollection={featureCollection}
|
|
||||||
onMouseEnter={onMouseEnter}
|
|
||||||
onMouseLeave={onMouseLeave}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MapStyleControl
|
|
||||||
style={style.id}
|
|
||||||
layers={layers}
|
|
||||||
setStyle={setStyle}
|
|
||||||
setLayerEnabled={setLayerEnabled}
|
|
||||||
setLayerOpacity={setLayerOpacity}
|
|
||||||
/>
|
|
||||||
<ZoomControl />
|
|
||||||
</Mapbox>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<GeoJSONLayer
|
|
||||||
data={data}
|
|
||||||
fillPaint={polygonSelectionFill}
|
|
||||||
linePaint={polygonSelectionLine}
|
|
||||||
fillOnMouseEnter={onMouseEnter}
|
|
||||||
fillOnMouseLeave={onMouseLeave}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function SelectionUtilisateurLineLayer({
|
|
||||||
featureCollection,
|
|
||||||
onMouseEnter,
|
|
||||||
onMouseLeave
|
|
||||||
}) {
|
|
||||||
const data = useMemo(
|
|
||||||
() =>
|
|
||||||
filterFeatureCollectionByGeometryType(
|
|
||||||
filterFeatureCollection(featureCollection, 'selection_utilisateur'),
|
|
||||||
'LineString'
|
|
||||||
),
|
|
||||||
[featureCollection]
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<GeoJSONLayer
|
|
||||||
data={data}
|
|
||||||
linePaint={lineStringSelectionLine}
|
|
||||||
lineOnMouseEnter={onMouseEnter}
|
|
||||||
lineOnMouseLeave={onMouseLeave}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function SelectionUtilisateurPointLayer({
|
|
||||||
featureCollection,
|
|
||||||
onMouseEnter,
|
|
||||||
onMouseLeave
|
|
||||||
}) {
|
|
||||||
const data = useMemo(
|
|
||||||
() =>
|
|
||||||
filterFeatureCollectionByGeometryType(
|
|
||||||
filterFeatureCollection(featureCollection, 'selection_utilisateur'),
|
|
||||||
'Point'
|
|
||||||
),
|
|
||||||
[featureCollection]
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<GeoJSONLayer
|
|
||||||
data={data}
|
|
||||||
circlePaint={pointSelectionFill}
|
|
||||||
circleOnMouseEnter={onMouseEnter}
|
|
||||||
circleOnMouseLeave={onMouseLeave}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
24
app/javascript/components/MapReader/index.tsx
Normal file
24
app/javascript/components/MapReader/index.tsx
Normal file
|
@ -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 (
|
||||||
|
<MapLibre layers={options.layers}>
|
||||||
|
<GeoJSONLayer featureCollection={featureCollection} />
|
||||||
|
<CadastreLayer featureCollection={featureCollection} />
|
||||||
|
</MapLibre>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MapReader;
|
|
@ -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]);
|
|
||||||
}
|
|
|
@ -82,7 +82,7 @@ const TypeDeChamp = sortableElement(
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TrashIcon className="icon-size" />
|
<TrashIcon className="icon-size" />
|
||||||
<span className="screen-reader-text">Supprimer</span>
|
<span className="sr-only">Supprimer</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,17 +1,26 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
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(
|
return createPortal(
|
||||||
<div className="flash_message center">
|
<div className="flash_message center">
|
||||||
<div className={flashClassName(level, sticky, fixed)}>{message}</div>
|
<div className={flashClassName(level, sticky, fixed)}>{message}</div>
|
||||||
</div>,
|
</div>,
|
||||||
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 =
|
const className =
|
||||||
level == 'notice' ? ['alert', 'alert-success'] : ['alert', 'alert-danger'];
|
level == 'notice' ? ['alert', 'alert-success'] : ['alert', 'alert-danger'];
|
||||||
|
|
||||||
|
@ -23,10 +32,3 @@ function flashClassName(level, sticky = false, fixed = false) {
|
||||||
}
|
}
|
||||||
return className.join(' ');
|
return className.join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
FlashMessage.propTypes = {
|
|
||||||
message: PropTypes.string,
|
|
||||||
level: PropTypes.string,
|
|
||||||
sticky: PropTypes.bool,
|
|
||||||
fixed: PropTypes.bool
|
|
||||||
};
|
|
|
@ -1,15 +1,18 @@
|
||||||
import { useRef, useCallback, useMemo, useState } from 'react';
|
import { useRef, useCallback, useMemo, useState } from 'react';
|
||||||
import { fire } from '@utils';
|
import { fire } from '@utils';
|
||||||
|
|
||||||
export function useDeferredSubmit(input) {
|
export function useDeferredSubmit(input?: HTMLInputElement): {
|
||||||
|
(callback: () => void): void;
|
||||||
|
done: () => void;
|
||||||
|
} {
|
||||||
const calledRef = useRef(false);
|
const calledRef = useRef(false);
|
||||||
const awaitFormSubmit = useCallback(
|
const awaitFormSubmit = useCallback(
|
||||||
(callback) => {
|
(callback: () => void) => {
|
||||||
const form = input?.form;
|
const form = input?.form;
|
||||||
if (!form) {
|
if (!form) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const interceptFormSubmit = (event) => {
|
const interceptFormSubmit = (event: Event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
runCallback();
|
runCallback();
|
||||||
form.submit();
|
form.submit();
|
||||||
|
@ -27,17 +30,24 @@ export function useDeferredSubmit(input) {
|
||||||
},
|
},
|
||||||
[input]
|
[input]
|
||||||
);
|
);
|
||||||
awaitFormSubmit.done = () => {
|
const done = () => {
|
||||||
calledRef.current = true;
|
calledRef.current = true;
|
||||||
};
|
};
|
||||||
return awaitFormSubmit;
|
return Object.assign(awaitFormSubmit, { done });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function groupId(id) {
|
export function groupId(id: string) {
|
||||||
return `#champ-${id.replace(/-input$/, '')}`;
|
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(
|
const hiddenField = useMemo(
|
||||||
() => selectInputInGroup(group, name),
|
() => selectInputInGroup(group, name),
|
||||||
[group, name]
|
[group, name]
|
||||||
|
@ -53,13 +63,16 @@ export function useHiddenField(group, name = 'value') {
|
||||||
fire(hiddenField, 'autosave:trigger');
|
fire(hiddenField, 'autosave:trigger');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
hiddenField
|
hiddenField ?? undefined
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectInputInGroup(group, name) {
|
function selectInputInGroup(
|
||||||
|
group: string | undefined,
|
||||||
|
name: string
|
||||||
|
): HTMLInputElement | undefined | null {
|
||||||
if (group) {
|
if (group) {
|
||||||
return document.querySelector(
|
return document.querySelector<HTMLInputElement>(
|
||||||
`${group} input[type="hidden"][name$="[${name}]"], ${group} input[type="hidden"][name="${name}"]`
|
`${group} input[type="hidden"][name$="[${name}]"], ${group} input[type="hidden"][name="${name}"]`
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -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;
|
|
||||||
}
|
|
104
app/javascript/components/shared/maplibre/MapLibre.tsx
Normal file
104
app/javascript/components/shared/maplibre/MapLibre.tsx
Normal file
|
@ -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<HTMLDivElement>(null);
|
||||||
|
const [map, setMap] = useState<Map | null>();
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div
|
||||||
|
style={{ marginBottom: '20px' }}
|
||||||
|
className="outdated-browser-banner site-banner"
|
||||||
|
>
|
||||||
|
<div className="container">
|
||||||
|
<div className="site-banner-icon">⚠️</div>
|
||||||
|
<div className="site-banner-text">
|
||||||
|
Nous ne pouvons pas afficher la carte car elle est imcompatible avec
|
||||||
|
votre navigateur. Nous vous conseillons de le mettre à jour ou
|
||||||
|
d’utiliser{' '}
|
||||||
|
<a
|
||||||
|
href="https://browser-update.org/fr/update.html"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
un navigateur plus récent
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Context.Provider value={{ map }}>
|
||||||
|
{map ? header : null}
|
||||||
|
<div ref={containerRef} style={{ height: '500px' }}>
|
||||||
|
<StyleControl styleId={style.id} {...mapStyleProps} />
|
||||||
|
{map ? children : null}
|
||||||
|
</div>
|
||||||
|
{map ? footer : null}
|
||||||
|
</Context.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isIE() {
|
||||||
|
const ua = window.navigator.userAgent;
|
||||||
|
const msie = ua.indexOf('MSIE ');
|
||||||
|
const trident = ua.indexOf('Trident/');
|
||||||
|
return msie > 0 || trident > 0;
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
import React, { useMemo, useState, useEffect } from 'react';
|
import React, { useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { Popover, RadioGroup } from '@headlessui/react';
|
import { Popover, RadioGroup } from '@headlessui/react';
|
||||||
import { usePopper } from 'react-popper';
|
import { usePopper } from 'react-popper';
|
||||||
import { MapIcon } from '@heroicons/react/outline';
|
import { MapIcon } from '@heroicons/react/outline';
|
||||||
|
@ -7,7 +6,7 @@ import { Slider } from '@reach/slider';
|
||||||
import { useId } from '@reach/auto-id';
|
import { useId } from '@reach/auto-id';
|
||||||
import '@reach/slider/styles.css';
|
import '@reach/slider/styles.css';
|
||||||
|
|
||||||
import { getMapStyle, getLayerName, NBS } from './styles';
|
import { LayersMap, NBS } from './styles';
|
||||||
|
|
||||||
const STYLES = {
|
const STYLES = {
|
||||||
ortho: 'Satellite',
|
ortho: 'Satellite',
|
||||||
|
@ -15,68 +14,22 @@ const STYLES = {
|
||||||
ign: 'Carte IGN'
|
ign: 'Carte IGN'
|
||||||
};
|
};
|
||||||
|
|
||||||
function optionalLayersMap(optionalLayers) {
|
export function StyleControl({
|
||||||
return Object.fromEntries(
|
styleId,
|
||||||
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,
|
|
||||||
layers,
|
layers,
|
||||||
setStyle,
|
setStyle,
|
||||||
setLayerEnabled,
|
setLayerEnabled,
|
||||||
setLayerOpacity
|
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 [buttonElement, setButtonElement] =
|
||||||
const [panelElement, setPanelElement] = useState();
|
useState<HTMLButtonElement | null>();
|
||||||
|
const [panelElement, setPanelElement] = useState<HTMLDivElement | null>();
|
||||||
const { styles, attributes } = usePopper(buttonElement, panelElement, {
|
const { styles, attributes } = usePopper(buttonElement, panelElement, {
|
||||||
placement: 'bottom-end'
|
placement: 'bottom-end'
|
||||||
});
|
});
|
||||||
|
@ -86,7 +39,10 @@ function MapStyleControl({
|
||||||
const mapId = useId();
|
const mapId = useId();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="form map-style-control mapboxgl-ctrl-group">
|
<div
|
||||||
|
className="form map-style-control mapboxgl-ctrl-group"
|
||||||
|
style={{ zIndex: 10 }}
|
||||||
|
>
|
||||||
<Popover>
|
<Popover>
|
||||||
<Popover.Button
|
<Popover.Button
|
||||||
ref={setButtonElement}
|
ref={setButtonElement}
|
||||||
|
@ -102,7 +58,7 @@ function MapStyleControl({
|
||||||
{...attributes.popper}
|
{...attributes.popper}
|
||||||
>
|
>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
value={style}
|
value={styleId}
|
||||||
onChange={setStyle}
|
onChange={setStyle}
|
||||||
className="styles-list"
|
className="styles-list"
|
||||||
as="ul"
|
as="ul"
|
||||||
|
@ -175,13 +131,3 @@ function MapStyleControl({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
MapStyleControl.propTypes = {
|
|
||||||
style: PropTypes.string,
|
|
||||||
layers: PropTypes.object,
|
|
||||||
setStyle: PropTypes.func,
|
|
||||||
setLayerEnabled: PropTypes.func,
|
|
||||||
setLayerOpacity: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MapStyleControl;
|
|
110
app/javascript/components/shared/maplibre/hooks.ts
Normal file
110
app/javascript/components/shared/maplibre/hooks.ts
Normal file
|
@ -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<Geometry>[];
|
||||||
|
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 };
|
||||||
|
}
|
|
@ -1,8 +1,11 @@
|
||||||
|
import type { AnyLayer, Style, RasterLayer, RasterSource } from 'maplibre-gl';
|
||||||
|
import invariant from 'tiny-invariant';
|
||||||
|
|
||||||
import cadastreLayers from './layers/cadastre';
|
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 url = `https://wxs.ign.fr/${IGN_TOKEN}/geoportail/wmts`;
|
||||||
const query =
|
const query =
|
||||||
'service=WMTS&request=GetTile&version=1.0.0&tilematrixset=PM&tilematrix={z}&tilecol={x}&tilerow={y}&style=normal';
|
'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}`;
|
return `${url}?${query}&layer=${layer}&format=${format}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OPTIONAL_LAYERS = [
|
const OPTIONAL_LAYERS: { label: string; id: string; layers: string[][] }[] = [
|
||||||
{
|
{
|
||||||
label: 'UNESCO',
|
label: 'UNESCO',
|
||||||
id: 'unesco',
|
id: 'unesco',
|
||||||
|
@ -127,7 +130,7 @@ function buildSources() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function rasterSource(tiles, attribution) {
|
function rasterSource(tiles: string[], attribution: string): RasterSource {
|
||||||
return {
|
return {
|
||||||
type: 'raster',
|
type: 'raster',
|
||||||
tiles,
|
tiles,
|
||||||
|
@ -138,7 +141,7 @@ function rasterSource(tiles, attribution) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function rasterLayer(source, opacity) {
|
function rasterLayer(source: string, opacity: number): RasterLayer {
|
||||||
return {
|
return {
|
||||||
id: source,
|
id: source,
|
||||||
source,
|
source,
|
||||||
|
@ -147,10 +150,13 @@ function rasterLayer(source, opacity) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildOptionalLayers(ids, opacity) {
|
export function buildOptionalLayers(
|
||||||
|
ids: string[],
|
||||||
|
opacity: Record<string, number>
|
||||||
|
): AnyLayer[] {
|
||||||
return OPTIONAL_LAYERS.filter(({ id }) => ids.includes(id))
|
return OPTIONAL_LAYERS.filter(({ id }) => ids.includes(id))
|
||||||
.flatMap(({ layers, id }) =>
|
.flatMap(({ layers, id }) =>
|
||||||
layers.map(([, code]) => [code, opacity[id] / 100])
|
layers.map(([, code]) => [code, opacity[id] / 100] as const)
|
||||||
)
|
)
|
||||||
.flatMap(([code, opacity]) =>
|
.flatMap(([code, opacity]) =>
|
||||||
code === 'CADASTRE'
|
code === 'CADASTRE'
|
||||||
|
@ -159,16 +165,15 @@ export function buildOptionalLayers(ids, opacity) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NBS = ' ';
|
export const NBS = ' ' as const;
|
||||||
|
|
||||||
export function getLayerName(layer) {
|
export function getLayerName(layer: string): string {
|
||||||
return OPTIONAL_LAYERS.find(({ id }) => id == layer).label.replace(
|
const name = OPTIONAL_LAYERS.find(({ id }) => id == layer);
|
||||||
/\s/g,
|
invariant(name, `Layer "${layer}" not found`);
|
||||||
NBS
|
return name.label.replace(/\s/g, NBS);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLayerCode(code) {
|
function getLayerCode(code: string) {
|
||||||
return code.toLowerCase().replace(/\./g, '-');
|
return code.toLowerCase().replace(/\./g, '-');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,4 +225,4 @@ export default {
|
||||||
},
|
},
|
||||||
sprite: 'https://openmaptiles.github.io/osm-bright-gl-style/sprite',
|
sprite: 'https://openmaptiles.github.io/osm-bright-gl-style/sprite',
|
||||||
glyphs: 'https://openmaptiles.geo.data.gouv.fr/fonts/{fontstack}/{range}.pbf'
|
glyphs: 'https://openmaptiles.geo.data.gouv.fr/fonts/{fontstack}/{range}.pbf'
|
||||||
};
|
} as Style;
|
|
@ -1,3 +1,5 @@
|
||||||
|
import type { Style } from 'maplibre-gl';
|
||||||
|
|
||||||
import baseStyle, { buildOptionalLayers, getLayerName, NBS } from './base';
|
import baseStyle, { buildOptionalLayers, getLayerName, NBS } from './base';
|
||||||
import orthoStyle from './layers/ortho';
|
import orthoStyle from './layers/ortho';
|
||||||
import vectorStyle from './layers/vector';
|
import vectorStyle from './layers/vector';
|
||||||
|
@ -5,7 +7,21 @@ import ignLayers from './layers/ign';
|
||||||
|
|
||||||
export { getLayerName, NBS };
|
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<string, number>
|
||||||
|
): Style & { id: string } {
|
||||||
const style = { ...baseStyle, id };
|
const style = { ...baseStyle, id };
|
||||||
|
|
||||||
switch (id) {
|
switch (id) {
|
||||||
|
@ -23,7 +39,7 @@ export function getMapStyle(id, layers, opacity) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
style.layers = style.layers.concat(buildOptionalLayers(layers, opacity));
|
style.layers = style.layers?.concat(buildOptionalLayers(layers, opacity));
|
||||||
|
|
||||||
return style;
|
return style;
|
||||||
}
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
export default [
|
import { AnyLayer } from 'maplibre-gl';
|
||||||
|
|
||||||
|
const layers: AnyLayer[] = [
|
||||||
{
|
{
|
||||||
id: 'batiments-line',
|
id: 'batiments-line',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
|
@ -104,3 +106,5 @@ export default [
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export default layers;
|
|
@ -1,4 +1,6 @@
|
||||||
export default [
|
import type { RasterLayer } from 'maplibre-gl';
|
||||||
|
|
||||||
|
const layers: RasterLayer[] = [
|
||||||
{
|
{
|
||||||
id: 'ign',
|
id: 'ign',
|
||||||
source: 'plan-ign',
|
source: 'plan-ign',
|
||||||
|
@ -6,3 +8,5 @@ export default [
|
||||||
paint: { 'raster-resampling': 'linear' }
|
paint: { 'raster-resampling': 'linear' }
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export default layers;
|
|
@ -1,4 +1,6 @@
|
||||||
export default [
|
import type { AnyLayer } from 'maplibre-gl';
|
||||||
|
|
||||||
|
const layers: AnyLayer[] = [
|
||||||
{
|
{
|
||||||
id: 'photographies-aeriennes',
|
id: 'photographies-aeriennes',
|
||||||
type: 'raster',
|
type: 'raster',
|
||||||
|
@ -2129,7 +2131,7 @@ export default [
|
||||||
[10, 'point'],
|
[10, 'point'],
|
||||||
[11, 'line']
|
[11, 'line']
|
||||||
]
|
]
|
||||||
},
|
} as any,
|
||||||
'symbol-spacing': 200,
|
'symbol-spacing': 200,
|
||||||
'text-field': '{ref}',
|
'text-field': '{ref}',
|
||||||
'text-font': ['Noto Sans Regular'],
|
'text-font': ['Noto Sans Regular'],
|
||||||
|
@ -2160,7 +2162,7 @@ export default [
|
||||||
[10, 'point'],
|
[10, 'point'],
|
||||||
[11, 'line']
|
[11, 'line']
|
||||||
]
|
]
|
||||||
},
|
} as any,
|
||||||
'symbol-spacing': 200,
|
'symbol-spacing': 200,
|
||||||
'text-field': '{ref}',
|
'text-field': '{ref}',
|
||||||
'text-font': ['Noto Sans Regular'],
|
'text-font': ['Noto Sans Regular'],
|
||||||
|
@ -2262,7 +2264,7 @@ export default [
|
||||||
'text-letter-spacing': 0,
|
'text-letter-spacing': 0,
|
||||||
'icon-padding': 2,
|
'icon-padding': 2,
|
||||||
'symbol-placement': 'point',
|
'symbol-placement': 'point',
|
||||||
'symbol-z-order': 'auto',
|
'symbol-z-order': 'auto' as any,
|
||||||
'text-line-height': 1.2,
|
'text-line-height': 1.2,
|
||||||
'text-allow-overlap': false,
|
'text-allow-overlap': false,
|
||||||
'text-ignore-placement': false,
|
'text-ignore-placement': false,
|
||||||
|
@ -2637,3 +2639,5 @@ export default [
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export default layers;
|
|
@ -1,4 +1,6 @@
|
||||||
export default [
|
import type { AnyLayer } from 'maplibre-gl';
|
||||||
|
|
||||||
|
const layers: AnyLayer[] = [
|
||||||
{
|
{
|
||||||
id: 'background',
|
id: 'background',
|
||||||
type: 'background',
|
type: 'background',
|
||||||
|
@ -113,7 +115,7 @@ export default [
|
||||||
[0, false],
|
[0, false],
|
||||||
[9, true]
|
[9, true]
|
||||||
]
|
]
|
||||||
},
|
} as any,
|
||||||
'fill-color': '#6a4',
|
'fill-color': '#6a4',
|
||||||
'fill-opacity': 0.1,
|
'fill-opacity': 0.1,
|
||||||
'fill-outline-color': 'hsla(0, 0%, 0%, 0.03)'
|
'fill-outline-color': 'hsla(0, 0%, 0%, 0.03)'
|
||||||
|
@ -324,7 +326,7 @@ export default [
|
||||||
[6, [2, 0]],
|
[6, [2, 0]],
|
||||||
[8, [0, 0]]
|
[8, [0, 0]]
|
||||||
]
|
]
|
||||||
}
|
} as any
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -427,7 +429,7 @@ export default [
|
||||||
[14, [0, 0]],
|
[14, [0, 0]],
|
||||||
[16, [-2, -2]]
|
[16, [-2, -2]]
|
||||||
]
|
]
|
||||||
}
|
} as any
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -2322,7 +2324,7 @@ export default [
|
||||||
[10, 'point'],
|
[10, 'point'],
|
||||||
[11, 'line']
|
[11, 'line']
|
||||||
]
|
]
|
||||||
},
|
} as any,
|
||||||
'symbol-spacing': 200,
|
'symbol-spacing': 200,
|
||||||
'text-field': '{ref}',
|
'text-field': '{ref}',
|
||||||
'text-font': ['Noto Sans Regular'],
|
'text-font': ['Noto Sans Regular'],
|
||||||
|
@ -2354,7 +2356,7 @@ export default [
|
||||||
[7, 'line'],
|
[7, 'line'],
|
||||||
[8, 'line']
|
[8, 'line']
|
||||||
]
|
]
|
||||||
},
|
} as any,
|
||||||
'symbol-spacing': 200,
|
'symbol-spacing': 200,
|
||||||
'text-field': '{ref}',
|
'text-field': '{ref}',
|
||||||
'text-font': ['Noto Sans Regular'],
|
'text-font': ['Noto Sans Regular'],
|
||||||
|
@ -2385,7 +2387,7 @@ export default [
|
||||||
[10, 'point'],
|
[10, 'point'],
|
||||||
[11, 'line']
|
[11, 'line']
|
||||||
]
|
]
|
||||||
},
|
} as any,
|
||||||
'symbol-spacing': 200,
|
'symbol-spacing': 200,
|
||||||
'text-field': '{ref}',
|
'text-field': '{ref}',
|
||||||
'text-font': ['Noto Sans Regular'],
|
'text-font': ['Noto Sans Regular'],
|
||||||
|
@ -2837,3 +2839,5 @@ export default [
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export default layers;
|
93
app/javascript/components/shared/maplibre/utils.ts
Normal file
93
app/javascript/components/shared/maplibre/utils.ts
Normal file
|
@ -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<G extends Geometry>(
|
||||||
|
featureCollection: FeatureCollection<G>,
|
||||||
|
value: unknown,
|
||||||
|
property = 'id'
|
||||||
|
): Feature<G> | null {
|
||||||
|
return (
|
||||||
|
featureCollection.features.find(
|
||||||
|
(feature) => feature.properties && feature.properties[property] === value
|
||||||
|
) ?? null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function filterFeatureCollection<G extends Geometry>(
|
||||||
|
featureCollection: FeatureCollection<G>,
|
||||||
|
source: string
|
||||||
|
): FeatureCollection<G> {
|
||||||
|
return {
|
||||||
|
type: 'FeatureCollection',
|
||||||
|
features: featureCollection.features.filter(
|
||||||
|
(feature) => feature.properties?.source === source
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function filterFeatureCollectionByGeometryType<G extends Geometry>(
|
||||||
|
featureCollection: FeatureCollection<G>,
|
||||||
|
type: Geometry['type']
|
||||||
|
): FeatureCollection<G> {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { QueryClient } from 'react-query';
|
import { QueryClient, QueryFunction } from 'react-query';
|
||||||
import { getJSON, isNumeric } from '@utils';
|
import { getJSON, isNumeric } from '@utils';
|
||||||
import { matchSorter } from 'match-sorter';
|
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_COMMUNES_QUERY_LIMIT = 60;
|
||||||
|
|
||||||
const { api_geo_url, api_adresse_url, api_education_url } =
|
const { api_geo_url, api_adresse_url, api_education_url } =
|
||||||
gon.autocomplete || {};
|
(window as any).gon.autocomplete || {};
|
||||||
|
|
||||||
export const queryClient = new QueryClient({
|
type QueryKey = readonly [
|
||||||
defaultOptions: {
|
scope: string,
|
||||||
queries: {
|
term: string,
|
||||||
queryFn: defaultQueryFn
|
extra: string | undefined
|
||||||
}
|
];
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function buildURL(scope, term, extra) {
|
function buildURL(scope: string, term: string, extra?: string) {
|
||||||
term = encodeURIComponent(term.replace(/\(|\)/g, ''));
|
term = encodeURIComponent(term.replace(/\(|\)/g, ''));
|
||||||
if (scope === 'adresse') {
|
if (scope === 'adresse') {
|
||||||
return `${api_adresse_url}/search?q=${term}&limit=${API_ADRESSE_QUERY_LIMIT}`;
|
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}`;
|
return `${api_geo_url}/${scope}?nom=${term}&limit=${API_GEO_QUERY_LIMIT}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildOptions() {
|
function buildOptions(): [RequestInit, AbortController | null] {
|
||||||
if (window.AbortController) {
|
if (window.AbortController) {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const signal = controller.signal;
|
const signal = controller.signal;
|
||||||
|
@ -57,7 +55,9 @@ function buildOptions() {
|
||||||
return [{}, null];
|
return [{}, null];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function defaultQueryFn({ queryKey: [scope, term, extra] }) {
|
const defaultQueryFn: QueryFunction<unknown, QueryKey> = async ({
|
||||||
|
queryKey: [scope, term, extra]
|
||||||
|
}) => {
|
||||||
if (scope == 'pays') {
|
if (scope == 'pays') {
|
||||||
return matchSorter(await getPays(), term, { keys: ['label'] });
|
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`);
|
throw new Error(`Error fetching from "${scope}" API`);
|
||||||
});
|
});
|
||||||
promise.cancel = () => controller && controller.abort();
|
(promise as any).cancel = () => controller && controller.abort();
|
||||||
return promise;
|
return promise;
|
||||||
}
|
};
|
||||||
|
|
||||||
let paysCache;
|
let paysCache: { label: string }[];
|
||||||
async function getPays() {
|
async function getPays(): Promise<{ label: string }[]> {
|
||||||
if (!paysCache) {
|
if (!paysCache) {
|
||||||
paysCache = await getJSON('/api/pays', null);
|
paysCache = await getJSON('/api/pays', null);
|
||||||
}
|
}
|
||||||
return paysCache;
|
return paysCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const queryClient = new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
queryFn: defaultQueryFn as any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -5,6 +5,7 @@ import 'core-js/stable';
|
||||||
import 'regenerator-runtime/runtime';
|
import 'regenerator-runtime/runtime';
|
||||||
import 'dom4';
|
import 'dom4';
|
||||||
import 'intersection-observer';
|
import 'intersection-observer';
|
||||||
|
import 'whatwg-fetch';
|
||||||
|
|
||||||
import './polyfills/insertAdjacentElement';
|
import './polyfills/insertAdjacentElement';
|
||||||
import './polyfills/dataset';
|
import './polyfills/dataset';
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { Suspense, lazy, createElement } from 'react';
|
import React, { Suspense, lazy, createElement, ComponentClass } from 'react';
|
||||||
import { render } from 'react-dom';
|
import { render } from 'react-dom';
|
||||||
|
|
||||||
// This attribute holds the name of component which should be mounted
|
// 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
|
// helper method for the mount and unmount methods to find the
|
||||||
// `data-react-class` DOM elements
|
// `data-react-class` DOM elements
|
||||||
function findDOMNodes(searchSelector) {
|
function findDOMNodes(searchSelector?: string): NodeListOf<HTMLDivElement> {
|
||||||
const [selector, parent] = getSelector(searchSelector);
|
const [selector, parent] = getSelector(searchSelector);
|
||||||
return parent.querySelectorAll(selector);
|
return parent.querySelectorAll<HTMLDivElement>(selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSelector(searchSelector) {
|
function getSelector(searchSelector?: string): [string, Document] {
|
||||||
switch (typeof searchSelector) {
|
switch (typeof searchSelector) {
|
||||||
case 'undefined':
|
case 'undefined':
|
||||||
return [CLASS_NAME_SELECTOR, document];
|
return [CLASS_NAME_SELECTOR, document];
|
||||||
|
@ -39,15 +39,15 @@ function getSelector(searchSelector) {
|
||||||
class ReactComponentRegistry {
|
class ReactComponentRegistry {
|
||||||
#components;
|
#components;
|
||||||
|
|
||||||
constructor(components) {
|
constructor(components: Record<string, ComponentClass>) {
|
||||||
this.#components = components;
|
this.#components = components;
|
||||||
}
|
}
|
||||||
|
|
||||||
getConstructor(className) {
|
getConstructor(className: string | null) {
|
||||||
return this.#components[className];
|
return className ? this.#components[className] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
mountComponents(searchSelector) {
|
mountComponents(searchSelector?: string) {
|
||||||
const nodes = findDOMNodes(searchSelector);
|
const nodes = findDOMNodes(searchSelector);
|
||||||
|
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
|
@ -76,10 +76,10 @@ class ReactComponentRegistry {
|
||||||
|
|
||||||
const Loader = () => <div className="spinner left" />;
|
const Loader = () => <div className="spinner left" />;
|
||||||
|
|
||||||
export function Loadable(loader) {
|
export function Loadable(loader: () => Promise<{ default: ComponentClass }>) {
|
||||||
const LazyComponent = lazy(loader);
|
const LazyComponent = lazy(loader);
|
||||||
|
|
||||||
return function PureComponent(props) {
|
return function PureComponent(props: Record<string, unknown>) {
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<Loader />}>
|
<Suspense fallback={<Loader />}>
|
||||||
<LazyComponent {...props} />
|
<LazyComponent {...props} />
|
||||||
|
@ -88,7 +88,9 @@ export function Loadable(loader) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerReactComponents(components) {
|
export function registerReactComponents(
|
||||||
|
components: Record<string, ComponentClass>
|
||||||
|
) {
|
||||||
const registry = new ReactComponentRegistry(components);
|
const registry = new ReactComponentRegistry(components);
|
||||||
|
|
||||||
addEventListener('ds:page:update', () => registry.mountComponents());
|
addEventListener('ds:page:update', () => registry.mountComponents());
|
|
@ -4,17 +4,17 @@ import debounce from 'debounce';
|
||||||
export { debounce };
|
export { debounce };
|
||||||
export const { fire, csrfToken } = Rails;
|
export const { fire, csrfToken } = Rails;
|
||||||
|
|
||||||
export function show(el) {
|
export function show(el: HTMLElement) {
|
||||||
el && el.classList.remove('hidden');
|
el && el.classList.remove('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hide(el) {
|
export function hide(el: HTMLElement) {
|
||||||
el && el.classList.add('hidden');
|
el && el.classList.add('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toggle(el, force) {
|
export function toggle(el: HTMLElement, force?: boolean) {
|
||||||
if (force == undefined) {
|
if (force == undefined) {
|
||||||
el & el.classList.toggle('hidden');
|
el && el.classList.toggle('hidden');
|
||||||
} else if (force) {
|
} else if (force) {
|
||||||
el && el.classList.remove('hidden');
|
el && el.classList.remove('hidden');
|
||||||
} else {
|
} else {
|
||||||
|
@ -22,27 +22,31 @@ export function toggle(el, force) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function enable(el) {
|
export function enable(el: HTMLInputElement) {
|
||||||
el && (el.disabled = false);
|
el && (el.disabled = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function disable(el) {
|
export function disable(el: HTMLInputElement) {
|
||||||
el && (el.disabled = true);
|
el && (el.disabled = true);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasClass(el, cssClass) {
|
export function hasClass(el: HTMLElement, cssClass: string) {
|
||||||
return el && el.classList.contains(cssClass);
|
return el && el.classList.contains(cssClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addClass(el, cssClass) {
|
export function addClass(el: HTMLElement, cssClass: string) {
|
||||||
el && el.classList.add(cssClass);
|
el && el.classList.add(cssClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeClass(el, cssClass) {
|
export function removeClass(el: HTMLElement, cssClass: string) {
|
||||||
el && el.classList.remove(cssClass);
|
el && el.classList.remove(cssClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function delegate(eventNames, selector, callback) {
|
export function delegate(
|
||||||
|
eventNames: string,
|
||||||
|
selector: string,
|
||||||
|
callback: () => void
|
||||||
|
) {
|
||||||
eventNames
|
eventNames
|
||||||
.split(' ')
|
.split(' ')
|
||||||
.forEach((eventName) =>
|
.forEach((eventName) =>
|
||||||
|
@ -57,15 +61,23 @@ export function delegate(eventNames, selector, callback) {
|
||||||
// - rejected with an Error object otherwise.
|
// - rejected with an Error object otherwise.
|
||||||
//
|
//
|
||||||
// See Rails.ajax() code for more details.
|
// See Rails.ajax() code for more details.
|
||||||
export function ajax(options) {
|
export function ajax(options: Rails.AjaxOptions) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
Object.assign(options, {
|
Object.assign(options, {
|
||||||
success: (response, statusText, xhr) => {
|
success: (
|
||||||
|
response: unknown,
|
||||||
|
statusText: string,
|
||||||
|
xhr: { status: number }
|
||||||
|
) => {
|
||||||
resolve({ response, statusText, xhr });
|
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.
|
// NB: on HTTP/2 connections, statusText is always empty.
|
||||||
let error = new Error(
|
const error = new Error(
|
||||||
`Erreur ${xhr.status}` + (statusText ? ` : ${statusText}` : '')
|
`Erreur ${xhr.status}` + (statusText ? ` : ${statusText}` : '')
|
||||||
);
|
);
|
||||||
Object.assign(error, { response, statusText, xhr });
|
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);
|
const { query, ...options } = fetchOptions(data, method);
|
||||||
|
|
||||||
return fetch(`${url}${query}`, options).then((response) => {
|
return fetch(`${url}${query}`, options).then((response) => {
|
||||||
|
@ -86,32 +98,38 @@ export function getJSON(url, data, method = 'GET') {
|
||||||
}
|
}
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
const error = new Error(response.statusText || response.status);
|
const error = new Error(String(response.statusText || response.status));
|
||||||
error.response = response;
|
(error as any).response = response;
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function scrollTo(container, scrollTo) {
|
export function scrollTo(container: HTMLElement, scrollTo: HTMLElement) {
|
||||||
container.scrollTop =
|
container.scrollTop =
|
||||||
offset(scrollTo).top - offset(container).top + container.scrollTop;
|
offset(scrollTo).top - offset(container).top + container.scrollTop;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function scrollToBottom(container) {
|
export function scrollToBottom(container: HTMLElement) {
|
||||||
container.scrollTop = container.scrollHeight;
|
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) =>
|
[...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) {
|
export function isNumeric(n: string) {
|
||||||
return !isNaN(parseFloat(n)) && isFinite(n);
|
return !isNaN(parseFloat(n)) && isFinite(n as any as number);
|
||||||
}
|
}
|
||||||
|
|
||||||
function offset(element) {
|
function offset(element: HTMLElement) {
|
||||||
const rect = element.getBoundingClientRect();
|
const rect = element.getBoundingClientRect();
|
||||||
return {
|
return {
|
||||||
top: rect.top + document.body.scrollTop,
|
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.
|
// Takes a promise, and return a promise that times out after the given delay.
|
||||||
export function timeoutable(promise, timeoutDelay) {
|
export function timeoutable<T>(
|
||||||
let timeoutPromise = new Promise((resolve, reject) => {
|
promise: Promise<T>,
|
||||||
|
timeoutDelay: number
|
||||||
|
): Promise<T> {
|
||||||
|
const timeoutPromise = new Promise<T>((resolve, reject) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
reject(new Error(`Promise timed out after ${timeoutDelay}ms`));
|
reject(new Error(`Promise timed out after ${timeoutDelay}ms`));
|
||||||
}, timeoutDelay);
|
}, timeoutDelay);
|
||||||
|
@ -131,13 +152,16 @@ export function timeoutable(promise, timeoutDelay) {
|
||||||
|
|
||||||
const FETCH_TIMEOUT = 30 * 1000; // 30 sec
|
const FETCH_TIMEOUT = 30 * 1000; // 30 sec
|
||||||
|
|
||||||
function fetchOptions(data, method = 'GET') {
|
function fetchOptions(data: unknown, method = 'GET') {
|
||||||
const options = {
|
const options: RequestInit & {
|
||||||
|
query: string;
|
||||||
|
headers: Record<string, string>;
|
||||||
|
} = {
|
||||||
query: '',
|
query: '',
|
||||||
method: method.toUpperCase(),
|
method: method.toUpperCase(),
|
||||||
headers: {
|
headers: {
|
||||||
accept: 'application/json',
|
accept: 'application/json',
|
||||||
'x-csrf-token': csrfToken(),
|
'x-csrf-token': csrfToken() ?? '',
|
||||||
'x-requested-with': 'XMLHttpRequest'
|
'x-requested-with': 'XMLHttpRequest'
|
||||||
},
|
},
|
||||||
credentials: 'same-origin'
|
credentials: 'same-origin'
|
||||||
|
@ -145,7 +169,7 @@ function fetchOptions(data, method = 'GET') {
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
if (options.method === 'GET') {
|
if (options.method === 'GET') {
|
||||||
options.query = objectToQuerystring(data);
|
options.query = objectToQuerystring(data as Record<string, string>);
|
||||||
} else {
|
} else {
|
||||||
options.headers['content-type'] = 'application/json';
|
options.headers['content-type'] = 'application/json';
|
||||||
options.body = JSON.stringify(data);
|
options.body = JSON.stringify(data);
|
||||||
|
@ -164,7 +188,7 @@ function fetchOptions(data, method = 'GET') {
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
function objectToQuerystring(obj) {
|
function objectToQuerystring(obj: Record<string, string>): string {
|
||||||
return Object.keys(obj).reduce(function (query, key, i) {
|
return Object.keys(obj).reduce(function (query, key, i) {
|
||||||
return [
|
return [
|
||||||
query,
|
query,
|
23
app/javascript/types.d.ts
vendored
Normal file
23
app/javascript/types.d.ts
vendored
Normal file
|
@ -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<TProperties extends GeoJsonProperties>(
|
||||||
|
doc: Document
|
||||||
|
): FeatureCollection<Geometry, TProperties>;
|
||||||
|
|
||||||
|
export function gpx(doc: Document): FeatureCollection;
|
||||||
|
export function gpx<TProperties extends GeoJsonProperties>(
|
||||||
|
doc: Document
|
||||||
|
): FeatureCollection<Geometry, TProperties>;
|
||||||
|
|
||||||
|
export function tcx(doc: Document): FeatureCollection;
|
||||||
|
export function tcx<TProperties extends GeoJsonProperties>(
|
||||||
|
doc: Document
|
||||||
|
): FeatureCollection<Geometry, TProperties>;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'react-coordinate-input';
|
|
@ -17,6 +17,7 @@
|
||||||
# groupe_instructeur_updated_at :datetime
|
# groupe_instructeur_updated_at :datetime
|
||||||
# hidden_at :datetime
|
# hidden_at :datetime
|
||||||
# hidden_by_administration_at :datetime
|
# hidden_by_administration_at :datetime
|
||||||
|
# hidden_by_reason :string
|
||||||
# hidden_by_user_at :datetime
|
# hidden_by_user_at :datetime
|
||||||
# identity_updated_at :datetime
|
# identity_updated_at :datetime
|
||||||
# last_avis_updated_at :datetime
|
# last_avis_updated_at :datetime
|
||||||
|
@ -761,26 +762,26 @@ class Dossier < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def restore_dossier_and_destroy_deleted_dossier(author)
|
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)
|
log_dossier_operation(author, :restaurer, self)
|
||||||
end
|
end
|
||||||
|
|
||||||
def discard_and_keep_track!(author, reason)
|
def discard_and_keep_track!(author, reason)
|
||||||
if termine? && author_is_administration(author)
|
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
|
end
|
||||||
|
|
||||||
if can_be_hidden_by_user? && author_is_user(author)
|
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
|
end
|
||||||
|
|
||||||
deleted_dossier = nil
|
|
||||||
|
|
||||||
transaction do
|
transaction do
|
||||||
if deleted_by_instructeur_and_user? || en_construction? || brouillon?
|
if deleted_by_instructeur_and_user? || en_construction? || brouillon?
|
||||||
if keep_track_on_deletion?
|
if keep_track_on_deletion?
|
||||||
log_dossier_operation(author, :supprimer, self)
|
log_dossier_operation(author, :supprimer, self)
|
||||||
deleted_dossier = DeletedDossier.create_from_dossier(self, reason)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if !(en_construction? && author_is_user(author))
|
if !(en_construction? && author_is_user(author))
|
||||||
|
@ -789,12 +790,11 @@ class Dossier < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if deleted_dossier.present?
|
if en_construction?
|
||||||
if en_construction?
|
update(hidden_by_reason: reason)
|
||||||
administration_emails = followers_instructeurs.present? ? followers_instructeurs.map(&:email) : procedure.administrateurs.map(&:email)
|
administration_emails = followers_instructeurs.present? ? followers_instructeurs.map(&:email) : procedure.administrateurs.map(&:email)
|
||||||
administration_emails.each do |email|
|
administration_emails.each do |email|
|
||||||
DossierMailer.notify_deletion_to_administration(deleted_dossier, email).deliver_later
|
DossierMailer.notify_en_construction_deletion_to_administration(self, email).deliver_later
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -813,6 +813,8 @@ class Dossier < ApplicationRecord
|
||||||
elsif author_is_user(author) && hidden_by_user?
|
elsif author_is_user(author) && hidden_by_user?
|
||||||
transaction do
|
transaction do
|
||||||
update(hidden_by_user_at: nil)
|
update(hidden_by_user_at: nil)
|
||||||
|
!hidden_by_administration? && update(hidden_by_reason: nil)
|
||||||
|
|
||||||
if en_construction?
|
if en_construction?
|
||||||
restore_dossier_and_destroy_deleted_dossier(author)
|
restore_dossier_and_destroy_deleted_dossier(author)
|
||||||
end
|
end
|
||||||
|
@ -820,6 +822,7 @@ class Dossier < ApplicationRecord
|
||||||
elsif author_is_administration(author) && hidden_by_administration?
|
elsif author_is_administration(author) && hidden_by_administration?
|
||||||
transaction do
|
transaction do
|
||||||
update(hidden_by_administration_at: nil)
|
update(hidden_by_administration_at: nil)
|
||||||
|
!hidden_by_user? && update(hidden_by_reason: nil)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1150,20 +1153,21 @@ class Dossier < ApplicationRecord
|
||||||
user&.locale || I18n.default_locale
|
user&.locale || I18n.default_locale
|
||||||
end
|
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
|
def self.purge_discarded
|
||||||
discarded_brouillon_expired.destroy_all
|
discarded_brouillon_expired.find_each(&:purge_discarded)
|
||||||
|
discarded_en_construction_expired.find_each(&:purge_discarded)
|
||||||
transaction do
|
discarded_termine_expired.find_each(&:purge_discarded)
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -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 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 ('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 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 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 archived) AS archives,
|
||||||
COUNT(DISTINCT dossiers.id) FILTER (where
|
COUNT(DISTINCT dossiers.id) FILTER (where
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
module.exports = function(api) {
|
module.exports = function (api) {
|
||||||
var validEnv = ['development', 'test', 'production']
|
var validEnv = ['development', 'test', 'production'];
|
||||||
var currentEnv = api.env()
|
var currentEnv = api.env();
|
||||||
var isDevelopmentEnv = api.env('development')
|
var isDevelopmentEnv = api.env('development');
|
||||||
var isProductionEnv = api.env('production')
|
var isProductionEnv = api.env('production');
|
||||||
var isTestEnv = api.env('test')
|
var isTestEnv = api.env('test');
|
||||||
|
|
||||||
if (!validEnv.includes(currentEnv)) {
|
if (!validEnv.includes(currentEnv)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -12,7 +12,7 @@ module.exports = function(api) {
|
||||||
'"test", and "production". Instead, received: ' +
|
'"test", and "production". Instead, received: ' +
|
||||||
JSON.stringify(currentEnv) +
|
JSON.stringify(currentEnv) +
|
||||||
'.'
|
'.'
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -41,7 +41,8 @@ module.exports = function(api) {
|
||||||
development: isDevelopmentEnv || isTestEnv,
|
development: isDevelopmentEnv || isTestEnv,
|
||||||
useBuiltIns: true
|
useBuiltIns: true
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
['@babel/preset-typescript', { allExtensions: true, isTSX: true }]
|
||||||
].filter(Boolean),
|
].filter(Boolean),
|
||||||
plugins: [
|
plugins: [
|
||||||
'babel-plugin-macros',
|
'babel-plugin-macros',
|
||||||
|
@ -92,5 +93,5 @@ module.exports = function(api) {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
].filter(Boolean)
|
].filter(Boolean)
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
#
|
#
|
||||||
# Examples:
|
# Examples:
|
||||||
# * For local development: localhost:3000
|
# * For local development: localhost:3000
|
||||||
# * For preproduction: staging.ds.organisme.fr
|
# * For preproduction: staging.ds.example.org
|
||||||
# * For production: ds.organisme.fr
|
# * For production: ds.example.org
|
||||||
APP_HOST="localhost:3000"
|
APP_HOST="localhost:3000"
|
||||||
|
|
||||||
# Rails key for signing sensitive data
|
# Rails key for signing sensitive data
|
||||||
|
@ -68,7 +68,7 @@ SENTRY_DSN_JS=""
|
||||||
# External service: Matomo web analytics
|
# External service: Matomo web analytics
|
||||||
MATOMO_ENABLED="disabled"
|
MATOMO_ENABLED="disabled"
|
||||||
MATOMO_ID=""
|
MATOMO_ID=""
|
||||||
MATOMO_HOST="matomo.organisme.fr"
|
MATOMO_HOST="matomo.example.org"
|
||||||
|
|
||||||
# Default SMTP Provider: Mailjet
|
# Default SMTP Provider: Mailjet
|
||||||
MAILJET_API_KEY=""
|
MAILJET_API_KEY=""
|
||||||
|
|
|
@ -88,7 +88,10 @@ DS_ENV="staging"
|
||||||
# API_PARTICULIER_URL="https://particulier.api.gouv.fr/api"
|
# API_PARTICULIER_URL="https://particulier.api.gouv.fr/api"
|
||||||
|
|
||||||
# Admins and instructeurs can freely change their email to these domains
|
# 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
|
# Instance provider
|
||||||
# PROVIDED_BY="la DINUM"
|
# PROVIDED_BY="la DINUM"
|
||||||
|
|
|
@ -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")
|
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")
|
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
|
# rubocop:enable DS/ApplicationName
|
||||||
|
|
|
@ -8,7 +8,7 @@ openstack:
|
||||||
service: OpenStack
|
service: OpenStack
|
||||||
container: "<%= ENV['FOG_ACTIVESTORAGE_DIRECTORY'] %>"
|
container: "<%= ENV['FOG_ACTIVESTORAGE_DIRECTORY'] %>"
|
||||||
credentials:
|
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_api_key: "<%= ENV['FOG_OPENSTACK_API_KEY'] %>"
|
||||||
openstack_username: "<%= ENV['FOG_OPENSTACK_USERNAME'] %>"
|
openstack_username: "<%= ENV['FOG_OPENSTACK_USERNAME'] %>"
|
||||||
openstack_region: "<%= ENV['FOG_OPENSTACK_REGION'] %>"
|
openstack_region: "<%= ENV['FOG_OPENSTACK_REGION'] %>"
|
||||||
|
|
|
@ -24,7 +24,7 @@ if (!Array.isArray(nodeModulesLoader.exclude)) {
|
||||||
nodeModulesLoader.exclude == null ? [] : [nodeModulesLoader.exclude];
|
nodeModulesLoader.exclude == null ? [] : [nodeModulesLoader.exclude];
|
||||||
}
|
}
|
||||||
nodeModulesLoader.exclude.push(
|
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
|
// Uncoment next lines to run webpack-bundle-analyzer
|
||||||
|
|
|
@ -33,6 +33,8 @@ default: &default
|
||||||
- .woff2
|
- .woff2
|
||||||
|
|
||||||
extensions:
|
extensions:
|
||||||
|
- .tsx
|
||||||
|
- .ts
|
||||||
- .mjs
|
- .mjs
|
||||||
- .js
|
- .js
|
||||||
- .jsx
|
- .jsx
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddHiddenByReasonToDossiers < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
add_column :dossiers, :hidden_by_reason, :string
|
||||||
|
end
|
||||||
|
end
|
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# 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
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
@ -324,6 +324,7 @@ ActiveRecord::Schema.define(version: 2022_01_28_135056) do
|
||||||
t.datetime "identity_updated_at"
|
t.datetime "identity_updated_at"
|
||||||
t.datetime "depose_at"
|
t.datetime "depose_at"
|
||||||
t.datetime "hidden_by_user_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 || 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.index "to_tsvector('french'::regconfig, search_terms)", name: "index_dossiers_on_search_terms", using: :gin
|
||||||
t.datetime "hidden_by_administration_at"
|
t.datetime "hidden_by_administration_at"
|
||||||
|
|
|
@ -7,4 +7,5 @@ task :lint do
|
||||||
sh "bundle exec i18n-tasks check-consistent-interpolations"
|
sh "bundle exec i18n-tasks check-consistent-interpolations"
|
||||||
sh "bundle exec brakeman --no-pager"
|
sh "bundle exec brakeman --no-pager"
|
||||||
sh "yarn lint:js"
|
sh "yarn lint:js"
|
||||||
|
sh "yarn lint:types"
|
||||||
end
|
end
|
||||||
|
|
21
package.json
21
package.json
|
@ -1,9 +1,10 @@
|
||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/preset-react": "^7.14.5",
|
"@babel/preset-react": "^7.14.5",
|
||||||
|
"@babel/preset-typescript": "^7.16.7",
|
||||||
"@headlessui/react": "^1.3.0",
|
"@headlessui/react": "^1.3.0",
|
||||||
"@heroicons/react": "^1.0.1",
|
"@heroicons/react": "^1.0.1",
|
||||||
"@mapbox/mapbox-gl-draw": "^1.2.2",
|
"@mapbox/mapbox-gl-draw": "^1.3.0",
|
||||||
"@popperjs/core": "^2.9.2",
|
"@popperjs/core": "^2.9.2",
|
||||||
"@rails/actiontext": "^6.1.4-1",
|
"@rails/actiontext": "^6.1.4-1",
|
||||||
"@rails/activestorage": "^6.1.4-1",
|
"@rails/activestorage": "^6.1.4-1",
|
||||||
|
@ -12,7 +13,6 @@
|
||||||
"@reach/auto-id": "^0.16.0",
|
"@reach/auto-id": "^0.16.0",
|
||||||
"@reach/combobox": "^0.13.0",
|
"@reach/combobox": "^0.13.0",
|
||||||
"@reach/slider": "^0.15.0",
|
"@reach/slider": "^0.15.0",
|
||||||
"@reach/visually-hidden": "^0.15.2",
|
|
||||||
"@sentry/browser": "6.12.0",
|
"@sentry/browser": "6.12.0",
|
||||||
"@tmcw/togeojson": "^4.3.0",
|
"@tmcw/togeojson": "^4.3.0",
|
||||||
"babel-plugin-macros": "^2.8.0",
|
"babel-plugin-macros": "^2.8.0",
|
||||||
|
@ -22,18 +22,17 @@
|
||||||
"debounce": "^1.2.1",
|
"debounce": "^1.2.1",
|
||||||
"dom4": "^2.1.6",
|
"dom4": "^2.1.6",
|
||||||
"email-butler": "^1.0.13",
|
"email-butler": "^1.0.13",
|
||||||
|
"geojson": "^0.5.0",
|
||||||
"highcharts": "^9.0.0",
|
"highcharts": "^9.0.0",
|
||||||
"intersection-observer": "^0.12.0",
|
"intersection-observer": "^0.12.0",
|
||||||
"is-hotkey": "^0.2.0",
|
"is-hotkey": "^0.2.0",
|
||||||
"mapbox-gl": "^1.3.0",
|
"maplibre-gl": "^1.15.2",
|
||||||
"match-sorter": "^6.2.0",
|
"match-sorter": "^6.2.0",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"react": "^17.0.1",
|
"react": "^17.0.1",
|
||||||
"react-coordinate-input": "^1.0.0-rc.2",
|
"react-coordinate-input": "^1.0.0",
|
||||||
"react-dom": "^17.0.1",
|
"react-dom": "^17.0.1",
|
||||||
"react-intersection-observer": "^8.31.0",
|
"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-popper": "^2.2.5",
|
||||||
"react-query": "^3.9.7",
|
"react-query": "^3.9.7",
|
||||||
"react-sortable-hoc": "^1.11.0",
|
"react-sortable-hoc": "^1.11.0",
|
||||||
|
@ -46,6 +45,14 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@2fd/graphdoc": "^2.4.0",
|
"@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",
|
"babel-eslint": "^10.1.0",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^7.32.0",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
|
@ -54,12 +61,14 @@
|
||||||
"eslint-plugin-react-hooks": "^4.2.0",
|
"eslint-plugin-react-hooks": "^4.2.0",
|
||||||
"netlify-cli": "^8.3.0",
|
"netlify-cli": "^8.3.0",
|
||||||
"prettier": "^2.3.2",
|
"prettier": "^2.3.2",
|
||||||
|
"typescript": "^4.5.5",
|
||||||
"webpack-bundle-analyzer": "^3.7.0",
|
"webpack-bundle-analyzer": "^3.7.0",
|
||||||
"webpack-dev-server": "^4.6.0"
|
"webpack-dev-server": "^4.6.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint:js": "eslint --ext .js,.jsx,.ts,.tsx ./app/javascript ./config/webpack",
|
"lint:js": "eslint --ext .js,.jsx,.ts,.tsx ./app/javascript ./config/webpack",
|
||||||
"webpack:build": "NODE_ENV=production bin/webpack",
|
"webpack:build": "NODE_ENV=production bin/webpack",
|
||||||
|
"lint:types": "tsc",
|
||||||
"graphql:docs:build": "graphdoc --force",
|
"graphql:docs:build": "graphdoc --force",
|
||||||
"graphql:docs:deploy": "netlify deploy -d ./docs/graphql --prod",
|
"graphql:docs:deploy": "netlify deploy -d ./docs/graphql --prod",
|
||||||
"graphql:docs:publish": "yarn graphql:docs:build && yarn graphql:docs:deploy"
|
"graphql:docs:publish": "yarn graphql:docs:build && yarn graphql:docs:deploy"
|
||||||
|
|
|
@ -774,11 +774,8 @@ describe Instructeurs::DossiersController, type: :controller do
|
||||||
expect(DossierOperationLog.where(dossier_id: dossier.id).last.operation).to eq('supprimer')
|
expect(DossierOperationLog.where(dossier_id: dossier.id).last.operation).to eq('supprimer')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'add a record into deleted_dossiers table' do
|
it 'does not 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).count).to eq(0)
|
||||||
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)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'discard the dossier' do
|
it 'discard the dossier' do
|
||||||
|
@ -804,6 +801,11 @@ describe Instructeurs::DossiersController, type: :controller do
|
||||||
it 'does not discard the dossier' do
|
it 'does not discard the dossier' do
|
||||||
expect(dossier.reload.hidden_at).to eq(nil)
|
expect(dossier.reload.hidden_at).to eq(nil)
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
context 'when the instructeur want to delete a dossier without a decision' do
|
context 'when the instructeur want to delete a dossier without a decision' do
|
||||||
|
|
|
@ -1007,7 +1007,7 @@ describe Users::DossiersController, type: :controller do
|
||||||
|
|
||||||
shared_examples_for "the dossier can not be deleted" do
|
shared_examples_for "the dossier can not be deleted" do
|
||||||
it "doesn’t notify the deletion" 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
|
subject
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1022,17 +1022,23 @@ describe Users::DossiersController, type: :controller do
|
||||||
let(:dossier) { create(:dossier, :en_construction, user: user, autorisation_donnees: true) }
|
let(:dossier) { create(:dossier, :en_construction, user: user, autorisation_donnees: true) }
|
||||||
|
|
||||||
it "notifies the user and the admin of the deletion" do
|
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
|
subject
|
||||||
end
|
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
|
procedure = dossier.procedure
|
||||||
dossier_id = dossier.id
|
dossier_id = dossier.id
|
||||||
subject
|
subject
|
||||||
expect(Dossier.find_by(id: dossier_id)).to be_present
|
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(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
|
end
|
||||||
|
|
||||||
it { is_expected.to redirect_to(dossiers_path) }
|
it { is_expected.to redirect_to(dossiers_path) }
|
||||||
|
|
|
@ -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, :passer_en_instruction, dossier)
|
||||||
dossier.send(:log_dossier_operation, instructeur, :supprimer, dossier)
|
dossier.send(:log_dossier_operation, instructeur, :supprimer, dossier)
|
||||||
dossier.update_column(:hidden_at, hidden_at)
|
dossier.update_column(:hidden_at, hidden_at)
|
||||||
|
dossier.update_column(:hidden_by_reason, "user_request")
|
||||||
|
|
||||||
Cron::DiscardedDossiersDeletionJob.perform_now
|
Cron::DiscardedDossiersDeletionJob.perform_now
|
||||||
end
|
end
|
||||||
|
@ -42,7 +43,6 @@ RSpec.describe Cron::DiscardedDossiersDeletionJob, type: :job do
|
||||||
|
|
||||||
context 'not hidden' do
|
context 'not hidden' do
|
||||||
let(:hidden_at) { nil }
|
let(:hidden_at) { nil }
|
||||||
|
|
||||||
include_examples "does not delete"
|
include_examples "does not delete"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -60,7 +60,6 @@ RSpec.describe Cron::DiscardedDossiersDeletionJob, type: :job do
|
||||||
|
|
||||||
context 'hidden long ago' do
|
context 'hidden long ago' do
|
||||||
let(:hidden_at) { 1.week.ago - 1.hour }
|
let(:hidden_at) { 1.week.ago - 1.hour }
|
||||||
|
|
||||||
include_examples "does delete"
|
include_examples "does delete"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -794,7 +794,6 @@ describe Dossier do
|
||||||
describe "#discard_and_keep_track!" do
|
describe "#discard_and_keep_track!" do
|
||||||
let(:dossier) { create(:dossier, :en_construction) }
|
let(:dossier) { create(:dossier, :en_construction) }
|
||||||
let(:user) { dossier.user }
|
let(:user) { dossier.user }
|
||||||
let(:deleted_dossier) { DeletedDossier.find_by(dossier_id: dossier.id) }
|
|
||||||
let(:last_operation) { dossier.dossier_operation_logs.last }
|
let(:last_operation) { dossier.dossier_operation_logs.last }
|
||||||
let(:reason) { :user_request }
|
let(:reason) { :user_request }
|
||||||
|
|
||||||
|
@ -811,10 +810,6 @@ describe Dossier do
|
||||||
expect(dossier.discarded?).to be_truthy
|
expect(dossier.discarded?).to be_truthy
|
||||||
end
|
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
|
it 'do not records the operation in the log' do
|
||||||
expect(last_operation).to be_nil
|
expect(last_operation).to be_nil
|
||||||
end
|
end
|
||||||
|
@ -826,14 +821,6 @@ describe Dossier do
|
||||||
expect(dossier.hidden_by_user_at).to be_present
|
expect(dossier.hidden_by_user_at).to be_present
|
||||||
end
|
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
|
it 'records the operation in the log' do
|
||||||
expect(last_operation.operation).to eq("supprimer")
|
expect(last_operation.operation).to eq("supprimer")
|
||||||
expect(last_operation.automatic_operation?).to be_falsey
|
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.groupe_instructeurs << dossier.procedure.defaut_groupe_instructeur
|
||||||
non_following_instructeur
|
non_following_instructeur
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
context 'when dossier is brouillon' do
|
context 'when dossier is brouillon' do
|
||||||
|
@ -894,6 +868,19 @@ describe Dossier do
|
||||||
end
|
end
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
describe 'webhook' do
|
describe 'webhook' do
|
||||||
|
|
|
@ -310,7 +310,7 @@ describe User, type: :model do
|
||||||
it "keep track of dossiers and delete user" do
|
it "keep track of dossiers and delete user" do
|
||||||
user.delete_and_keep_track_dossiers(super_admin)
|
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(DeletedDossier.find_by(dossier_id: dossier_brouillon)).to be_nil
|
||||||
expect(User.find_by(id: user.id)).to be_nil
|
expect(User.find_by(id: user.id)).to be_nil
|
||||||
end
|
end
|
||||||
|
@ -324,7 +324,7 @@ describe User, type: :model do
|
||||||
dossier_to_discard.discard_and_keep_track!(super_admin, :user_request)
|
dossier_to_discard.discard_and_keep_track!(super_admin, :user_request)
|
||||||
user.delete_and_keep_track_dossiers(super_admin)
|
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(DeletedDossier.find_by(dossier_id: dossier_brouillon)).to be_nil
|
||||||
expect(Dossier.find_by(id: dossier_from_another_user.id)).to be_present
|
expect(Dossier.find_by(id: dossier_from_another_user.id)).to be_present
|
||||||
expect(User.find_by(id: user.id)).to be_nil
|
expect(User.find_by(id: user.id)).to be_nil
|
||||||
|
|
27
tsconfig.json
Normal file
27
tsconfig.json
Normal file
|
@ -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
|
||||||
|
}
|
445
yarn.lock
445
yarn.lock
|
@ -38,6 +38,13 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/highlight" "^7.16.0"
|
"@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":
|
"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.16.0", "@babel/compat-data@^7.16.4":
|
||||||
version "7.16.4"
|
version "7.16.4"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.16.4.tgz#081d6bbc336ec5c2435c6346b2ae1fb98b5ac68e"
|
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.16.4.tgz#081d6bbc336ec5c2435c6346b2ae1fb98b5ac68e"
|
||||||
|
@ -73,6 +80,15 @@
|
||||||
jsesc "^2.5.1"
|
jsesc "^2.5.1"
|
||||||
source-map "^0.5.0"
|
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":
|
"@babel/helper-annotate-as-pure@^7.16.0":
|
||||||
version "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"
|
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.0.tgz#9a1f0ebcda53d9a2d00108c4ceace6a5d5f1f08d"
|
||||||
|
@ -80,6 +96,13 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/types" "^7.16.0"
|
"@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":
|
"@babel/helper-builder-binary-assignment-operator-visitor@^7.16.5":
|
||||||
version "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"
|
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-replace-supers" "^7.16.5"
|
||||||
"@babel/helper-split-export-declaration" "^7.16.0"
|
"@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":
|
"@babel/helper-create-regexp-features-plugin@^7.16.0":
|
||||||
version "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"
|
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:
|
dependencies:
|
||||||
"@babel/types" "^7.16.0"
|
"@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":
|
"@babel/helper-explode-assignable-expression@^7.16.0":
|
||||||
version "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"
|
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/template" "^7.16.0"
|
||||||
"@babel/types" "^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":
|
"@babel/helper-get-function-arity@^7.16.0":
|
||||||
version "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"
|
resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz#0088c7486b29a9cb5d948b1a1de46db66e089cfa"
|
||||||
|
@ -163,6 +215,13 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/types" "^7.16.0"
|
"@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":
|
"@babel/helper-hoist-variables@^7.16.0":
|
||||||
version "7.16.0"
|
version "7.16.0"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz#4c9023c2f1def7e28ff46fc1dbcd36a39beaa81a"
|
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz#4c9023c2f1def7e28ff46fc1dbcd36a39beaa81a"
|
||||||
|
@ -170,6 +229,13 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/types" "^7.16.0"
|
"@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":
|
"@babel/helper-member-expression-to-functions@^7.16.5":
|
||||||
version "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"
|
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:
|
dependencies:
|
||||||
"@babel/types" "^7.16.0"
|
"@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":
|
"@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"
|
version "7.16.0"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz#90538e60b672ecf1b448f5f4f5433d37e79a3ec3"
|
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz#90538e60b672ecf1b448f5f4f5433d37e79a3ec3"
|
||||||
|
@ -205,11 +278,23 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/types" "^7.16.0"
|
"@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":
|
"@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"
|
version "7.16.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.5.tgz#afe37a45f39fce44a3d50a7958129ea5b1a5c074"
|
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.5.tgz#afe37a45f39fce44a3d50a7958129ea5b1a5c074"
|
||||||
integrity sha512-59KHWHXxVA9K4HNF4sbHCf+eJeFe0Te/ZFGqBT4OjXhrwvA04sGfaEGsVTdsjoszq0YTP49RC9UKe5g8uN2RwQ==
|
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":
|
"@babel/helper-remap-async-to-generator@^7.16.5":
|
||||||
version "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"
|
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/traverse" "^7.16.5"
|
||||||
"@babel/types" "^7.16.0"
|
"@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":
|
"@babel/helper-simple-access@^7.16.0":
|
||||||
version "7.16.0"
|
version "7.16.0"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz#21d6a27620e383e37534cf6c10bba019a6f90517"
|
resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz#21d6a27620e383e37534cf6c10bba019a6f90517"
|
||||||
|
@ -251,16 +347,33 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/types" "^7.16.0"
|
"@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":
|
"@babel/helper-validator-identifier@^7.15.7":
|
||||||
version "7.15.7"
|
version "7.15.7"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389"
|
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==
|
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":
|
"@babel/helper-validator-option@^7.14.5":
|
||||||
version "7.14.5"
|
version "7.14.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3"
|
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3"
|
||||||
integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==
|
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":
|
"@babel/helper-wrap-function@^7.16.5":
|
||||||
version "7.16.5"
|
version "7.16.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.16.5.tgz#0158fca6f6d0889c3fee8a6ed6e5e07b9b54e41f"
|
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"
|
chalk "^2.0.0"
|
||||||
js-tokens "^4.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":
|
"@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"
|
version "7.16.6"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.6.tgz#8f194828193e8fa79166f34a4b4e52f3e769a314"
|
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.6.tgz#8f194828193e8fa79166f34a4b4e52f3e769a314"
|
||||||
integrity sha512-Gr86ujcNuPDnNOY8mi383Hvi8IYrJVJYuf3XcuBM/Dgd+bINn/7tHqsj+tKkoreMbmGsFLsltI/JJd8fOFWGDQ==
|
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":
|
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.2":
|
||||||
version "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"
|
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:
|
dependencies:
|
||||||
"@babel/helper-plugin-utils" "^7.14.5"
|
"@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":
|
"@babel/plugin-transform-arrow-functions@^7.16.5":
|
||||||
version "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"
|
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.5.tgz#04c18944dd55397b521d9d7511e791acea7acf2d"
|
||||||
|
@ -822,6 +956,15 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/helper-plugin-utils" "^7.16.5"
|
"@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":
|
"@babel/plugin-transform-unicode-escapes@^7.16.5":
|
||||||
version "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"
|
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-jsx-development" "^7.16.5"
|
||||||
"@babel/plugin-transform-react-pure-annotations" "^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":
|
"@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"
|
version "7.16.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.5.tgz#7f3e34bf8bdbbadf03fbb7b1ea0d929569c9487a"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.5.tgz#7f3e34bf8bdbbadf03fbb7b1ea0d929569c9487a"
|
||||||
|
@ -956,6 +1108,15 @@
|
||||||
"@babel/parser" "^7.16.0"
|
"@babel/parser" "^7.16.0"
|
||||||
"@babel/types" "^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":
|
"@babel/traverse@^7.13.0", "@babel/traverse@^7.16.5", "@babel/traverse@^7.7.0":
|
||||||
version "7.16.5"
|
version "7.16.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.16.5.tgz#d7d400a8229c714a59b87624fc67b0f1fbd4b2b3"
|
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.16.5.tgz#d7d400a8229c714a59b87624fc67b0f1fbd4b2b3"
|
||||||
|
@ -972,6 +1133,22 @@
|
||||||
debug "^4.1.0"
|
debug "^4.1.0"
|
||||||
globals "^11.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":
|
"@babel/types@^7.16.0", "@babel/types@^7.4.4", "@babel/types@^7.7.0":
|
||||||
version "7.16.0"
|
version "7.16.0"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.16.0.tgz#db3b313804f96aadd0b776c4823e127ad67289ba"
|
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.16.0.tgz#db3b313804f96aadd0b776c4823e127ad67289ba"
|
||||||
|
@ -980,6 +1157,14 @@
|
||||||
"@babel/helper-validator-identifier" "^7.15.7"
|
"@babel/helper-validator-identifier" "^7.15.7"
|
||||||
to-fast-properties "^2.0.0"
|
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":
|
"@bugsnag/browser@^7.14.1":
|
||||||
version "7.14.1"
|
version "7.14.1"
|
||||||
resolved "https://registry.yarnpkg.com/@bugsnag/browser/-/browser-7.14.1.tgz#ba92aae1fb40aeba0983d2af950f70cc82729882"
|
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"
|
resolved "https://registry.yarnpkg.com/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz#ce56e539f83552b58d10d672ea4d6fc9adc7b234"
|
||||||
integrity sha1-zlblOfg1UrWNENZy6k1vya3HsjQ=
|
integrity sha1-zlblOfg1UrWNENZy6k1vya3HsjQ=
|
||||||
|
|
||||||
"@mapbox/mapbox-gl-draw@^1.2.2":
|
"@mapbox/mapbox-gl-draw@^1.3.0":
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/@mapbox/mapbox-gl-draw/-/mapbox-gl-draw-1.3.0.tgz#7a30fb99488cb47a32c25e99c3c62413b04bbaed"
|
resolved "https://registry.yarnpkg.com/@mapbox/mapbox-gl-draw/-/mapbox-gl-draw-1.3.0.tgz#7a30fb99488cb47a32c25e99c3c62413b04bbaed"
|
||||||
integrity sha512-B+KWK+dAgzLHMNyKVuuMRfjeSlQ77MhNLdfpQQpbp3pkhnrdmydDe3ixto1Ua78hktNut0WTrAaD8gYu4PVcjA==
|
integrity sha512-B+KWK+dAgzLHMNyKVuuMRfjeSlQ77MhNLdfpQQpbp3pkhnrdmydDe3ixto1Ua78hktNut0WTrAaD8gYu4PVcjA==
|
||||||
|
@ -1938,14 +2123,6 @@
|
||||||
tiny-warning "^1.0.3"
|
tiny-warning "^1.0.3"
|
||||||
tslib "^2.3.0"
|
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":
|
"@rollup/plugin-babel@^5.2.0":
|
||||||
version "5.3.0"
|
version "5.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz#9cb1c5146ddd6a4968ad96f209c50c62f92f9879"
|
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"
|
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e"
|
||||||
integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==
|
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":
|
"@types/cacheable-request@^6.0.1":
|
||||||
version "6.0.2"
|
version "6.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.2.tgz#c324da0197de0a98a2312156536ae262429ff6b9"
|
resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.2.tgz#c324da0197de0a98a2312156536ae262429ff6b9"
|
||||||
|
@ -2160,6 +2320,11 @@
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
"@types/responselike" "*"
|
"@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@*":
|
"@types/decompress@*":
|
||||||
version "4.2.4"
|
version "4.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/decompress/-/decompress-4.2.4.tgz#dd2715d3ac1f566d03e6e302d1a26ffab59f8c5c"
|
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"
|
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
|
||||||
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
|
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
|
||||||
|
|
||||||
"@types/geojson@*":
|
"@types/geojson@*", "@types/geojson@^7946.0.8":
|
||||||
version "7946.0.8"
|
version "7946.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.8.tgz#30744afdb385e2945e22f3b033f897f76b1f12ca"
|
resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.8.tgz#30744afdb385e2945e22f3b033f897f76b1f12ca"
|
||||||
integrity sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==
|
integrity sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==
|
||||||
|
@ -2257,6 +2422,21 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@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@*":
|
"@types/minimatch@*":
|
||||||
version "3.0.5"
|
version "3.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40"
|
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"
|
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
|
||||||
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
|
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":
|
"@types/q@^1.5.1":
|
||||||
version "1.5.5"
|
version "1.5.5"
|
||||||
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.5.tgz#75a2a8e7d8ab4b230414505d92335d1dcb53a6df"
|
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.5.tgz#75a2a8e7d8ab4b230414505d92335d1dcb53a6df"
|
||||||
integrity sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==
|
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":
|
"@types/resolve@1.17.1":
|
||||||
version "1.17.1"
|
version "1.17.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
|
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"
|
resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.1.tgz#d8f1c0d0dc23afad6dc16a9e993a0865774b4065"
|
||||||
integrity sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==
|
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":
|
"@types/semver@^7.0.0":
|
||||||
version "7.3.9"
|
version "7.3.9"
|
||||||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.9.tgz#152c6c20a7688c30b967ec1841d31ace569863fc"
|
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.9.tgz#152c6c20a7688c30b967ec1841d31ace569863fc"
|
||||||
integrity sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==
|
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":
|
"@types/warning@^3.0.0":
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.0.tgz#0d2501268ad8f9962b740d387c4654f5f8e23e52"
|
resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.0.tgz#0d2501268ad8f9962b740d387c4654f5f8e23e52"
|
||||||
|
@ -2343,11 +2547,71 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/yargs-parser" "*"
|
"@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":
|
"@typescript-eslint/types@4.33.0":
|
||||||
version "4.33.0"
|
version "4.33.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72"
|
||||||
integrity sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==
|
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":
|
"@typescript-eslint/typescript-estree@^4.8.2":
|
||||||
version "4.33.0"
|
version "4.33.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz#0dfb51c2908f68c5c08d82aefeaf166a17c24609"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz#0dfb51c2908f68c5c08d82aefeaf166a17c24609"
|
||||||
|
@ -2361,6 +2625,18 @@
|
||||||
semver "^7.3.5"
|
semver "^7.3.5"
|
||||||
tsutils "^3.21.0"
|
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":
|
"@typescript-eslint/visitor-keys@4.33.0":
|
||||||
version "4.33.0"
|
version "4.33.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz#2a22f77a41604289b7a186586e9ec48ca92ef1dd"
|
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"
|
"@typescript-eslint/types" "4.33.0"
|
||||||
eslint-visitor-keys "^2.0.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":
|
"@vercel/nft@^0.17.0":
|
||||||
version "0.17.0"
|
version "0.17.0"
|
||||||
resolved "https://registry.yarnpkg.com/@vercel/nft/-/nft-0.17.0.tgz#28851fefe42fae7a116dc5e23a0a9da29929a18b"
|
resolved "https://registry.yarnpkg.com/@vercel/nft/-/nft-0.17.0.tgz#28851fefe42fae7a116dc5e23a0a9da29929a18b"
|
||||||
|
@ -4597,6 +4881,11 @@ csso@^4.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
css-tree "^1.1.2"
|
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:
|
cyclist@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
|
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:
|
dependencies:
|
||||||
ms "2.0.0"
|
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"
|
version "4.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
|
||||||
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
|
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
|
||||||
|
@ -4731,11 +5020,6 @@ decompress@^4.2.1:
|
||||||
pify "^2.3.0"
|
pify "^2.3.0"
|
||||||
strip-dirs "^2.0.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:
|
deep-equal@^1.0.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a"
|
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a"
|
||||||
|
@ -5415,6 +5699,13 @@ eslint-utils@^2.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint-visitor-keys "^1.1.0"
|
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:
|
eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
|
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"
|
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303"
|
||||||
integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==
|
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:
|
eslint@^7.32.0:
|
||||||
version "7.32.0"
|
version "7.32.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d"
|
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"
|
merge2 "^1.3.0"
|
||||||
micromatch "^4.0.4"
|
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:
|
fast-json-stable-stringify@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
|
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"
|
resolved "https://registry.yarnpkg.com/geojson-vt/-/geojson-vt-3.2.1.tgz#f8adb614d2c1d3f6ee7c4265cad4bbf3ad60c8b7"
|
||||||
integrity sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg==
|
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:
|
get-amd-module-type@^3.0.0:
|
||||||
version "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"
|
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"
|
merge2 "^1.3.0"
|
||||||
slash "^3.0.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:
|
globby@^9.2.0:
|
||||||
version "9.2.0"
|
version "9.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/globby/-/globby-9.2.0.tgz#fd029a706c703d29bdd170f4b6db3a3f7a7cb63d"
|
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"
|
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
|
||||||
integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
|
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"
|
version "5.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a"
|
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a"
|
||||||
integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==
|
integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==
|
||||||
|
@ -7337,7 +7661,7 @@ is-glob@^3.0.0, is-glob@^3.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-extglob "^2.1.0"
|
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"
|
version "4.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
|
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
|
||||||
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
|
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
|
||||||
|
@ -8241,10 +8565,10 @@ map-visit@^1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
object-visit "^1.0.0"
|
object-visit "^1.0.0"
|
||||||
|
|
||||||
mapbox-gl@^1.3.0:
|
maplibre-gl@^1.15.2:
|
||||||
version "1.13.2"
|
version "1.15.2"
|
||||||
resolved "https://registry.yarnpkg.com/mapbox-gl/-/mapbox-gl-1.13.2.tgz#76639c44f141f8dff71b7d8f1504f2aed11f7517"
|
resolved "https://registry.yarnpkg.com/maplibre-gl/-/maplibre-gl-1.15.2.tgz#7fb47868b62455af916c090903f2154394450f9c"
|
||||||
integrity sha512-CPjtWygL+f7naL+sGHoC2JQR0DG7u+9ik6WdkjjVmz2uy0kBC2l+aKfdi3ZzUR7VKSQJ6Mc/CeCN+6iVNah+ww==
|
integrity sha512-uPeV530apb4JfX3cRFfE+awFnbcJTOnCv2QvY4mw4huiInbybElWYkNzTs324YLSADq0f4bidRoYcR81ho3aLA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@mapbox/geojson-rewind" "^0.5.0"
|
"@mapbox/geojson-rewind" "^0.5.0"
|
||||||
"@mapbox/geojson-types" "^1.0.2"
|
"@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"
|
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
|
||||||
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
|
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"
|
version "1.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
||||||
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
||||||
|
@ -10821,7 +11145,7 @@ rc@^1.2.7, rc@^1.2.8:
|
||||||
minimist "^1.2.0"
|
minimist "^1.2.0"
|
||||||
strip-json-comments "~2.0.1"
|
strip-json-comments "~2.0.1"
|
||||||
|
|
||||||
react-coordinate-input@^1.0.0-rc.2:
|
react-coordinate-input@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-coordinate-input/-/react-coordinate-input-1.0.0.tgz#884be529fe311820e19651bd74d2a0b1cfc7f823"
|
resolved "https://registry.yarnpkg.com/react-coordinate-input/-/react-coordinate-input-1.0.0.tgz#884be529fe311820e19651bd74d2a0b1cfc7f823"
|
||||||
integrity sha512-9iJti+WU38mk+Pab/5+Rn24IKfgxKEwcq4yJeyttAy50NTg33K5xwc/+NcPQoEB82xG0iTW9lRlx1WZCOPjWoQ==
|
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"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
||||||
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
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:
|
react-popper@^2.2.5:
|
||||||
version "2.2.5"
|
version "2.2.5"
|
||||||
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.2.5.tgz#1214ef3cec86330a171671a4fbcbeeb65ee58e96"
|
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"
|
call-bind "^1.0.2"
|
||||||
define-properties "^1.1.3"
|
define-properties "^1.1.3"
|
||||||
|
|
||||||
regexpp@^3.1.0:
|
regexpp@^3.1.0, regexpp@^3.2.0:
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2"
|
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2"
|
||||||
integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==
|
integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==
|
||||||
|
@ -12164,7 +12472,7 @@ stylehacks@^4.0.0:
|
||||||
postcss "^7.0.0"
|
postcss "^7.0.0"
|
||||||
postcss-selector-parser "^3.0.0"
|
postcss-selector-parser "^3.0.0"
|
||||||
|
|
||||||
supercluster@^7.0.0, supercluster@^7.1.0:
|
supercluster@^7.1.0:
|
||||||
version "7.1.4"
|
version "7.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/supercluster/-/supercluster-7.1.4.tgz#6762aabfd985d3390b49f13b815567d5116a828a"
|
resolved "https://registry.yarnpkg.com/supercluster/-/supercluster-7.1.4.tgz#6762aabfd985d3390b49f13b815567d5116a828a"
|
||||||
integrity sha512-GhKkRM1jMR6WUwGPw05fs66pOFWhf59lXq+Q3J3SxPvhNcmgOtLRV6aVQPMRsmXdpaeFJGivt+t7QXUPL3ff4g==
|
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"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8"
|
||||||
integrity sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==
|
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:
|
uid-safe@2.1.5:
|
||||||
version "2.1.5"
|
version "2.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a"
|
resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a"
|
||||||
|
|
Loading…
Reference in a new issue