Merge pull request #10845 from tchak/update-maplibre
chore(maplibre): update
This commit is contained in:
commit
0321226130
21 changed files with 5936 additions and 5826 deletions
|
@ -1,6 +1,4 @@
|
||||||
@import "colors";
|
@import "colors";
|
||||||
@import "constants";
|
|
||||||
|
|
||||||
|
|
||||||
.areas {
|
.areas {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
@ -10,56 +8,49 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-style-control {
|
.ds-ctrl button {
|
||||||
position: absolute;
|
color: $dark-grey;
|
||||||
bottom: 4px;
|
|
||||||
left: 10px;
|
|
||||||
|
|
||||||
img {
|
&.on,
|
||||||
width: 100%;
|
&:hover {
|
||||||
}
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
button {
|
|
||||||
padding: 0;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
> div {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 5px;
|
|
||||||
left: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.map-style-panel {
|
|
||||||
z-index: 1;
|
|
||||||
padding: $default-spacer;
|
|
||||||
margin-bottom: $default-spacer;
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style: none;
|
|
||||||
padding: $default-spacer;
|
|
||||||
padding-bottom: 0;
|
|
||||||
margin-bottom: -$default-spacer;
|
|
||||||
|
|
||||||
label {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.cadastres-selection-control {
|
.react-aria-popover {
|
||||||
z-index: 1;
|
&[data-placement='top'] {
|
||||||
position: absolute;
|
--origin: translateY(8px);
|
||||||
top: 135px;
|
}
|
||||||
left: 10px;
|
|
||||||
|
|
||||||
button {
|
&[data-placement='bottom'] {
|
||||||
&.on,
|
--origin: translateY(-8px);
|
||||||
&:hover {
|
}
|
||||||
background-color: rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
&[data-placement='right'] {
|
||||||
|
--origin: translateX(-8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-placement='left'] {
|
||||||
|
--origin: translateX(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-entering] {
|
||||||
|
animation: popover-slide 200ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-exiting] {
|
||||||
|
animation: popover-slide 200ms reverse ease-in;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes popover-slide {
|
||||||
|
from {
|
||||||
|
transform: var(--origin);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { useCallback, useRef } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
import type { Feature, FeatureCollection } from 'geojson';
|
import type { Feature, FeatureCollection } from 'geojson';
|
||||||
|
import { CursorClickIcon } from '@heroicons/react/outline';
|
||||||
|
|
||||||
import { useMapLibre } from '../../shared/maplibre/MapLibre';
|
import { useMapLibre, ReactControl } from '../../shared/maplibre/MapLibre';
|
||||||
import {
|
import {
|
||||||
useEvent,
|
useEvent,
|
||||||
useMapEvent,
|
useMapEvent,
|
||||||
|
@ -18,15 +20,31 @@ export function CadastreLayer({
|
||||||
featureCollection,
|
featureCollection,
|
||||||
createFeatures,
|
createFeatures,
|
||||||
deleteFeatures,
|
deleteFeatures,
|
||||||
|
toggle,
|
||||||
enabled
|
enabled
|
||||||
}: {
|
}: {
|
||||||
featureCollection: FeatureCollection;
|
featureCollection: FeatureCollection;
|
||||||
createFeatures: CreateFeatures;
|
createFeatures: CreateFeatures;
|
||||||
deleteFeatures: DeleteFeatures;
|
deleteFeatures: DeleteFeatures;
|
||||||
|
toggle: () => void;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
}) {
|
}) {
|
||||||
const map = useMapLibre();
|
const map = useMapLibre();
|
||||||
const selectedCadastresRef = useRef(new Set<string>());
|
const selectedCadastresRef = useRef(new Set<string>());
|
||||||
|
const [controlElement, setControlElement] = useState<HTMLElement | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const control = new ReactControl();
|
||||||
|
map.addControl(control, 'top-left');
|
||||||
|
setControlElement(control.container);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
map.removeControl(control);
|
||||||
|
setControlElement(null);
|
||||||
|
};
|
||||||
|
}, [map, enabled]);
|
||||||
|
|
||||||
const highlightFeature = useCallback(
|
const highlightFeature = useCallback(
|
||||||
(cid: string, highlight: boolean) => {
|
(cid: string, highlight: boolean) => {
|
||||||
|
@ -95,7 +113,35 @@ export function CadastreLayer({
|
||||||
|
|
||||||
useEvent('map:internal:cadastre:highlight', onHighlight);
|
useEvent('map:internal:cadastre:highlight', onHighlight);
|
||||||
|
|
||||||
return null;
|
return (
|
||||||
|
<>
|
||||||
|
{controlElement != null
|
||||||
|
? createPortal(
|
||||||
|
<CadastreSwitch enabled={enabled} toggle={toggle} />,
|
||||||
|
controlElement
|
||||||
|
)
|
||||||
|
: null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CadastreSwitch({
|
||||||
|
enabled,
|
||||||
|
toggle
|
||||||
|
}: {
|
||||||
|
enabled: boolean;
|
||||||
|
toggle: () => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={toggle}
|
||||||
|
title="Sélectionner les parcelles cadastrales"
|
||||||
|
className={enabled ? 'on' : 'off'}
|
||||||
|
>
|
||||||
|
<CursorClickIcon className="icon-size" />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function useCadastres(
|
function useCadastres(
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useCallback, useRef, useEffect } from 'react';
|
import { useCallback, useRef, useEffect } from 'react';
|
||||||
import type { LngLatBoundsLike, LngLatLike } from 'maplibre-gl';
|
import type { LngLatBoundsLike, LngLatLike, IControl } from 'maplibre-gl';
|
||||||
import DrawControl from '@mapbox/mapbox-gl-draw';
|
import DrawControl from '@mapbox/mapbox-gl-draw';
|
||||||
import type { FeatureCollection, Feature, Point } from 'geojson';
|
import type { FeatureCollection, Feature, Point } from 'geojson';
|
||||||
|
|
||||||
|
@ -52,18 +52,14 @@ export function DrawLayer({
|
||||||
});
|
});
|
||||||
// We use mapbox-draw plugin with maplibre. They are compatible but types are not.
|
// We use mapbox-draw plugin with maplibre. They are compatible but types are not.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
map.addControl(draw as any, 'top-left');
|
const control = draw as any as IControl;
|
||||||
|
map.addControl(control, 'top-left');
|
||||||
draw.set(
|
draw.set(
|
||||||
filterFeatureCollection(featureCollection, SOURCE_SELECTION_UTILISATEUR)
|
filterFeatureCollection(featureCollection, SOURCE_SELECTION_UTILISATEUR)
|
||||||
);
|
);
|
||||||
drawRef.current = draw;
|
drawRef.current = draw;
|
||||||
|
|
||||||
for (const [selector, translation] of translations) {
|
patchDrawControl();
|
||||||
const element = document.querySelector(selector);
|
|
||||||
if (element) {
|
|
||||||
element.setAttribute('title', translation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -228,3 +224,15 @@ const translations = [
|
||||||
['.mapbox-gl-draw_point', 'Ajouter un point'],
|
['.mapbox-gl-draw_point', 'Ajouter un point'],
|
||||||
['.mapbox-gl-draw_trash', 'Supprimer']
|
['.mapbox-gl-draw_trash', 'Supprimer']
|
||||||
];
|
];
|
||||||
|
|
||||||
|
function patchDrawControl() {
|
||||||
|
document.querySelectorAll('.mapboxgl-ctrl').forEach((control) => {
|
||||||
|
control.classList.add('maplibregl-ctrl', 'maplibregl-ctrl-group');
|
||||||
|
|
||||||
|
for (const [selector, translation] of translations) {
|
||||||
|
for (const button of control.querySelectorAll(selector)) {
|
||||||
|
button.setAttribute('title', translation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import { useState } from 'react';
|
import { 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 '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css';
|
||||||
import type { FeatureCollection } from 'geojson';
|
import type { FeatureCollection } from 'geojson';
|
||||||
|
|
||||||
|
@ -51,25 +49,12 @@ export default function MapEditor({
|
||||||
enabled={!cadastreEnabled}
|
enabled={!cadastreEnabled}
|
||||||
/>
|
/>
|
||||||
{options.layers.includes('cadastres') ? (
|
{options.layers.includes('cadastres') ? (
|
||||||
<>
|
<CadastreLayer
|
||||||
<CadastreLayer
|
featureCollection={featureCollection}
|
||||||
featureCollection={featureCollection}
|
{...actions}
|
||||||
{...actions}
|
toggle={() => setCadastreEnabled((enabled) => !enabled)}
|
||||||
enabled={cadastreEnabled}
|
enabled={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}
|
) : null}
|
||||||
</MapLibre>
|
</MapLibre>
|
||||||
<PointInput featureCollection={featureCollection} />
|
<PointInput featureCollection={featureCollection} />
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import 'maplibre-gl/dist/maplibre-gl.css';
|
|
||||||
import type { FeatureCollection } from 'geojson';
|
import type { FeatureCollection } from 'geojson';
|
||||||
|
|
||||||
import { MapLibre } from '../shared/maplibre/MapLibre';
|
import { MapLibre } from '../shared/maplibre/MapLibre';
|
||||||
|
|
|
@ -8,13 +8,15 @@ import {
|
||||||
createContext,
|
createContext,
|
||||||
useCallback
|
useCallback
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import maplibre, { Map, NavigationControl } from 'maplibre-gl';
|
import { createPortal } from 'react-dom';
|
||||||
import type { Style } from 'maplibre-gl';
|
import { Map, NavigationControl } from 'maplibre-gl';
|
||||||
|
import type { StyleSpecification, IControl } from 'maplibre-gl';
|
||||||
|
import 'maplibre-gl/dist/maplibre-gl.css';
|
||||||
|
|
||||||
import invariant from 'tiny-invariant';
|
import invariant from 'tiny-invariant';
|
||||||
|
|
||||||
import { useStyle, useElementVisible } from './hooks';
|
import { useStyle, useElementVisible } from './hooks';
|
||||||
import { StyleControl } from './StyleControl';
|
import { StyleSwitch } from './StyleControl';
|
||||||
|
|
||||||
const Context = createContext<{ map?: Map | null }>({});
|
const Context = createContext<{ map?: Map | null }>({});
|
||||||
|
|
||||||
|
@ -30,16 +32,15 @@ export function useMapLibre() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MapLibre({ children, layers }: MapLibreProps) {
|
export function MapLibre({ children, layers }: MapLibreProps) {
|
||||||
const isSupported = useMemo(
|
const isSupported = useMemo(() => isWebglSupported(), []);
|
||||||
() => maplibre.supported({ failIfMajorPerformanceCaveat: true }) && !isIE(),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const visible = useElementVisible(containerRef);
|
const visible = useElementVisible(containerRef);
|
||||||
const [map, setMap] = useState<Map | null>();
|
const [map, setMap] = useState<Map | null>();
|
||||||
|
const [styleControlElement, setStyleControlElement] =
|
||||||
|
useState<HTMLElement | null>(null);
|
||||||
|
|
||||||
const onStyleChange = useCallback(
|
const onStyleChange = useCallback(
|
||||||
(style: Style) => {
|
(style: StyleSpecification) => {
|
||||||
if (map) {
|
if (map) {
|
||||||
map.setStyle(style);
|
map.setStyle(style);
|
||||||
}
|
}
|
||||||
|
@ -56,8 +57,11 @@ export function MapLibre({ children, layers }: MapLibreProps) {
|
||||||
style
|
style
|
||||||
});
|
});
|
||||||
map.addControl(new NavigationControl({}), 'top-right');
|
map.addControl(new NavigationControl({}), 'top-right');
|
||||||
|
const styleControl = new ReactControl();
|
||||||
|
map.addControl(styleControl, 'bottom-left');
|
||||||
map.on('load', () => {
|
map.on('load', () => {
|
||||||
setMap(map);
|
setMap(map);
|
||||||
|
setStyleControlElement(styleControl.container);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [map, style, visible, isSupported]);
|
}, [map, style, visible, isSupported]);
|
||||||
|
@ -91,16 +95,54 @@ export function MapLibre({ children, layers }: MapLibreProps) {
|
||||||
return (
|
return (
|
||||||
<Context.Provider value={{ map }}>
|
<Context.Provider value={{ map }}>
|
||||||
<div ref={containerRef} style={{ height: '500px' }}>
|
<div ref={containerRef} style={{ height: '500px' }}>
|
||||||
<StyleControl styleId={style.id} {...mapStyleProps} />
|
{styleControlElement != null
|
||||||
|
? createPortal(
|
||||||
|
<StyleSwitch styleId={style.id} {...mapStyleProps} />,
|
||||||
|
styleControlElement
|
||||||
|
)
|
||||||
|
: null}
|
||||||
{map ? children : null}
|
{map ? children : null}
|
||||||
</div>
|
</div>
|
||||||
</Context.Provider>
|
</Context.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isIE() {
|
function isWebglSupported() {
|
||||||
const ua = window.navigator.userAgent;
|
if (window.WebGLRenderingContext) {
|
||||||
const msie = ua.indexOf('MSIE ');
|
const canvas = document.createElement('canvas');
|
||||||
const trident = ua.indexOf('Trident/');
|
try {
|
||||||
return msie > 0 || trident > 0;
|
// Note that { failIfMajorPerformanceCaveat: true } can be passed as a second argument
|
||||||
|
// to canvas.getContext(), causing the check to fail if hardware rendering is not available. See
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext
|
||||||
|
// for more details.
|
||||||
|
const context = canvas.getContext('webgl2') || canvas.getContext('webgl');
|
||||||
|
if (context && typeof context.getParameter == 'function') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// WebGL is supported, but disabled
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// WebGL not supported
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ReactControl implements IControl {
|
||||||
|
#container: HTMLElement | null = null;
|
||||||
|
|
||||||
|
get container(): HTMLElement | null {
|
||||||
|
return this.#container;
|
||||||
|
}
|
||||||
|
|
||||||
|
onAdd(): HTMLElement {
|
||||||
|
this.#container = document.createElement('div');
|
||||||
|
this.#container.className = 'maplibregl-ctrl maplibregl-ctrl-group ds-ctrl';
|
||||||
|
return this.#container;
|
||||||
|
}
|
||||||
|
|
||||||
|
onRemove(): void {
|
||||||
|
this.#container?.remove();
|
||||||
|
this.#container = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { useState, useId } from 'react';
|
import { useId, useRef, useEffect } from 'react';
|
||||||
import { Popover, RadioGroup } from '@headlessui/react';
|
import { Button, Dialog, DialogTrigger, Popover } from 'react-aria-components';
|
||||||
import { usePopper } from 'react-popper';
|
|
||||||
import { MapIcon } from '@heroicons/react/outline';
|
import { MapIcon } from '@heroicons/react/outline';
|
||||||
import { Slider } from '@reach/slider';
|
import { Slider } from '@reach/slider';
|
||||||
import '@reach/slider/styles.css';
|
import '@reach/slider/styles.css';
|
||||||
|
@ -13,7 +12,7 @@ const STYLES = {
|
||||||
ign: 'Carte IGN'
|
ign: 'Carte IGN'
|
||||||
};
|
};
|
||||||
|
|
||||||
export function StyleControl({
|
export function StyleSwitch({
|
||||||
styleId,
|
styleId,
|
||||||
layers,
|
layers,
|
||||||
setStyle,
|
setStyle,
|
||||||
|
@ -26,107 +25,97 @@ export function StyleControl({
|
||||||
setLayerEnabled: (layer: string, enabled: boolean) => void;
|
setLayerEnabled: (layer: string, enabled: boolean) => void;
|
||||||
setLayerOpacity: (layer: string, opacity: number) => void;
|
setLayerOpacity: (layer: string, opacity: number) => void;
|
||||||
}) {
|
}) {
|
||||||
const [buttonElement, setButtonElement] =
|
|
||||||
useState<HTMLButtonElement | null>();
|
|
||||||
const [panelElement, setPanelElement] = useState<HTMLDivElement | null>();
|
|
||||||
const { styles, attributes } = usePopper(buttonElement, panelElement, {
|
|
||||||
placement: 'bottom-end'
|
|
||||||
});
|
|
||||||
const configurableLayers = Object.entries(layers).filter(
|
const configurableLayers = Object.entries(layers).filter(
|
||||||
([, { configurable }]) => configurable
|
([, { configurable }]) => configurable
|
||||||
);
|
);
|
||||||
const mapId = useId();
|
const mapId = useId();
|
||||||
|
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (buttonRef.current) {
|
||||||
|
buttonRef.current.title = 'Sélectionner les couches cartographiques';
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<DialogTrigger>
|
||||||
className="form map-style-control mapboxgl-ctrl-group"
|
<Button ref={buttonRef}>
|
||||||
style={{ zIndex: 10 }}
|
<MapIcon className="icon-size" />
|
||||||
>
|
</Button>
|
||||||
<Popover>
|
<Popover className="react-aria-popover">
|
||||||
<Popover.Button
|
<Dialog className="fr-modal__body">
|
||||||
ref={setButtonElement}
|
<form
|
||||||
className="map-style-button"
|
className="fr-modal__content flex m-2"
|
||||||
title="Sélectionner les couches cartographiques"
|
onSubmit={(event) => event.preventDefault()}
|
||||||
>
|
|
||||||
<MapIcon className="icon-size" />
|
|
||||||
</Popover.Button>
|
|
||||||
<Popover.Panel
|
|
||||||
className="flex map-style-panel mapboxgl-ctrl-group"
|
|
||||||
ref={setPanelElement}
|
|
||||||
style={styles.popper}
|
|
||||||
{...attributes.popper}
|
|
||||||
>
|
|
||||||
<RadioGroup
|
|
||||||
value={styleId}
|
|
||||||
onChange={setStyle}
|
|
||||||
className="styles-list"
|
|
||||||
as="ul"
|
|
||||||
>
|
>
|
||||||
{Object.entries(STYLES).map(([style, title]) => (
|
<div className="fr-fieldset">
|
||||||
<RadioGroup.Option
|
{Object.entries(STYLES).map(([style, title]) => (
|
||||||
key={style}
|
<div className="fr-fieldset__element" key={style}>
|
||||||
value={style}
|
<div className="fr-radio-group">
|
||||||
as="li"
|
|
||||||
className="flex"
|
|
||||||
>
|
|
||||||
{({ checked }) => (
|
|
||||||
<>
|
|
||||||
<input
|
<input
|
||||||
|
id={`${mapId}-${style}`}
|
||||||
|
value={style}
|
||||||
type="radio"
|
type="radio"
|
||||||
key={`${style}-${checked}`}
|
|
||||||
defaultChecked={checked}
|
|
||||||
name="map-style"
|
name="map-style"
|
||||||
className="m-0 p-0 mr-1"
|
defaultValue={style}
|
||||||
/>
|
checked={styleId == style}
|
||||||
<RadioGroup.Label>
|
|
||||||
{title.replace(/\s/g, ' ')}
|
|
||||||
</RadioGroup.Label>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</RadioGroup.Option>
|
|
||||||
))}
|
|
||||||
</RadioGroup>
|
|
||||||
{configurableLayers.length ? (
|
|
||||||
<ul className="layers-list">
|
|
||||||
{configurableLayers.map(([layer, { enabled, opacity, name }]) => (
|
|
||||||
<li key={layer}>
|
|
||||||
<div className="flex mb-1">
|
|
||||||
<input
|
|
||||||
id={`${mapId}-${layer}`}
|
|
||||||
className="m-0 p-0 mr-1"
|
|
||||||
type="checkbox"
|
|
||||||
checked={enabled}
|
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
setLayerEnabled(layer, event.target.checked);
|
setStyle(event.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<label className="m-0" htmlFor={`${mapId}-${layer}`}>
|
<label htmlFor={`${mapId}-${style}`} className="fr-label">
|
||||||
{name}
|
{title.replace(/\s/g, ' ')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<Slider
|
</div>
|
||||||
min={10}
|
|
||||||
max={100}
|
|
||||||
step={5}
|
|
||||||
value={opacity}
|
|
||||||
onChange={(value) => {
|
|
||||||
setLayerOpacity(layer, value);
|
|
||||||
}}
|
|
||||||
className="mb-1"
|
|
||||||
title={`Réglage de l’opacité de la couche «${NBS}${name}${NBS}»`}
|
|
||||||
getAriaLabel={() =>
|
|
||||||
`Réglage de l’opacité de la couche «${NBS}${name}${NBS}»`
|
|
||||||
}
|
|
||||||
getAriaValueText={(value) =>
|
|
||||||
`L’opacité de la couche «${NBS}${name}${NBS}» est à ${value}${NBS}%`
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
))}
|
))}
|
||||||
</ul>
|
</div>
|
||||||
) : null}
|
{configurableLayers.length ? (
|
||||||
</Popover.Panel>
|
<div className="fr-fieldset__element">
|
||||||
|
{configurableLayers.map(
|
||||||
|
([layer, { enabled, opacity, name }]) => (
|
||||||
|
<div key={layer} className="fr-fieldset__element">
|
||||||
|
<div className="fr-checkbox-group">
|
||||||
|
<input
|
||||||
|
id={`${mapId}-${layer}`}
|
||||||
|
type="checkbox"
|
||||||
|
checked={enabled}
|
||||||
|
onChange={(event) => {
|
||||||
|
setLayerEnabled(layer, event.target.checked);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
className="fr-label"
|
||||||
|
htmlFor={`${mapId}-${layer}`}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<Slider
|
||||||
|
min={10}
|
||||||
|
max={100}
|
||||||
|
step={5}
|
||||||
|
value={opacity}
|
||||||
|
onChange={(value) => {
|
||||||
|
setLayerOpacity(layer, value);
|
||||||
|
}}
|
||||||
|
className="fr-range fr-range--sm mt-1"
|
||||||
|
title={`Réglage de l’opacité de la couche «${NBS}${name}${NBS}»`}
|
||||||
|
getAriaLabel={() =>
|
||||||
|
`Réglage de l’opacité de la couche «${NBS}${name}${NBS}»`
|
||||||
|
}
|
||||||
|
getAriaValueText={(value) =>
|
||||||
|
`L’opacité de la couche «${NBS}${name}${NBS}» est à ${value}${NBS}%`
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</form>
|
||||||
|
</Dialog>
|
||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</DialogTrigger>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import type {
|
||||||
LngLatBoundsLike,
|
LngLatBoundsLike,
|
||||||
LngLat,
|
LngLat,
|
||||||
MapLayerEventType,
|
MapLayerEventType,
|
||||||
Style,
|
StyleSpecification,
|
||||||
LngLatLike
|
LngLatLike
|
||||||
} from 'maplibre-gl';
|
} from 'maplibre-gl';
|
||||||
import type { Feature, Geometry } from 'geojson';
|
import type { Feature, Geometry } from 'geojson';
|
||||||
|
@ -104,7 +104,7 @@ function optionalLayersMap(optionalLayers: string[]): LayersMap {
|
||||||
|
|
||||||
export function useStyle(
|
export function useStyle(
|
||||||
optionalLayers: string[],
|
optionalLayers: string[],
|
||||||
onStyleChange: (style: Style) => void
|
onStyleChange: (style: StyleSpecification) => void
|
||||||
) {
|
) {
|
||||||
const [styleId, setStyle] = useState('ortho');
|
const [styleId, setStyle] = useState('ortho');
|
||||||
const [layers, setLayers] = useState(() => optionalLayersMap(optionalLayers));
|
const [layers, setLayers] = useState(() => optionalLayersMap(optionalLayers));
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
import type { AnyLayer, Style, RasterLayer, RasterSource } from 'maplibre-gl';
|
import type {
|
||||||
|
LayerSpecification,
|
||||||
|
RasterLayerSpecification,
|
||||||
|
RasterSourceSpecification,
|
||||||
|
StyleSpecification
|
||||||
|
} from 'maplibre-gl';
|
||||||
import invariant from 'tiny-invariant';
|
import invariant from 'tiny-invariant';
|
||||||
|
|
||||||
import cadastreLayers from './layers/cadastre';
|
import cadastreLayers from './layers/cadastre.json';
|
||||||
|
|
||||||
function ignServiceURL(layer: string, style: string, format = 'image/png') {
|
function ignServiceURL(layer: string, style: string, format = 'image/png') {
|
||||||
const url = `https://data.geopf.fr/wmts`;
|
const url = `https://data.geopf.fr/wmts`;
|
||||||
|
@ -163,7 +168,10 @@ function buildSources() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function rasterSource(tiles: string[], attribution: string): RasterSource {
|
function rasterSource(
|
||||||
|
tiles: string[],
|
||||||
|
attribution: string
|
||||||
|
): RasterSourceSpecification {
|
||||||
return {
|
return {
|
||||||
type: 'raster',
|
type: 'raster',
|
||||||
tiles,
|
tiles,
|
||||||
|
@ -174,7 +182,10 @@ function rasterSource(tiles: string[], attribution: string): RasterSource {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function rasterLayer(source: string, opacity: number): RasterLayer {
|
function rasterLayer(
|
||||||
|
source: string,
|
||||||
|
opacity: number
|
||||||
|
): RasterLayerSpecification {
|
||||||
return {
|
return {
|
||||||
id: source,
|
id: source,
|
||||||
source,
|
source,
|
||||||
|
@ -186,14 +197,14 @@ function rasterLayer(source: string, opacity: number): RasterLayer {
|
||||||
export function buildOptionalLayers(
|
export function buildOptionalLayers(
|
||||||
ids: string[],
|
ids: string[],
|
||||||
opacity: Record<string, number>
|
opacity: Record<string, number>
|
||||||
): AnyLayer[] {
|
): LayerSpecification[] {
|
||||||
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] as const)
|
layers.map(([, code]) => [code, opacity[id] / 100] as const)
|
||||||
)
|
)
|
||||||
.flatMap(([code, opacity]) =>
|
.flatMap(([code, opacity]) =>
|
||||||
code === 'CADASTRE'
|
code === 'CADASTRE'
|
||||||
? cadastreLayers
|
? (cadastreLayers as LayerSpecification[])
|
||||||
: [rasterLayer(getLayerCode(code), opacity)]
|
: [rasterLayer(getLayerCode(code), opacity)]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -210,9 +221,9 @@ function getLayerCode(code: string) {
|
||||||
return code.toLowerCase().replace(/\./g, '-');
|
return code.toLowerCase().replace(/\./g, '-');
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export const style: StyleSpecification = {
|
||||||
version: 8,
|
version: 8,
|
||||||
metadat: {
|
metadata: {
|
||||||
'mapbox:autocomposite': false,
|
'mapbox:autocomposite': false,
|
||||||
'mapbox:groups': {
|
'mapbox:groups': {
|
||||||
1444849242106.713: { collapsed: false, name: 'Places' },
|
1444849242106.713: { collapsed: false, name: 'Places' },
|
||||||
|
@ -257,5 +268,6 @@ export default {
|
||||||
...buildSources()
|
...buildSources()
|
||||||
},
|
},
|
||||||
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;
|
layers: []
|
||||||
|
};
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
import type { Style } from 'maplibre-gl';
|
import type { LayerSpecification, StyleSpecification } from 'maplibre-gl';
|
||||||
|
|
||||||
import baseStyle, { buildOptionalLayers, getLayerName, NBS } from './base';
|
import {
|
||||||
import orthoStyle from './layers/ortho';
|
style as baseStyle,
|
||||||
import vectorStyle from './layers/vector';
|
buildOptionalLayers,
|
||||||
import ignLayers from './layers/ign';
|
getLayerName,
|
||||||
|
NBS
|
||||||
|
} from './base';
|
||||||
|
import ignLayers from './layers/ign.json';
|
||||||
|
import orthoLayers from './layers/ortho.json';
|
||||||
|
import vectorLayers from './layers/vector.json';
|
||||||
|
|
||||||
export { getLayerName, NBS };
|
export { getLayerName, NBS };
|
||||||
|
|
||||||
|
@ -21,20 +26,20 @@ export function getMapStyle(
|
||||||
id: string,
|
id: string,
|
||||||
layers: string[],
|
layers: string[],
|
||||||
opacity: Record<string, number>
|
opacity: Record<string, number>
|
||||||
): Style & { id: string } {
|
): StyleSpecification & { id: string } {
|
||||||
const style = { ...baseStyle, id };
|
const style = { ...baseStyle, id };
|
||||||
|
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case 'ortho':
|
case 'ortho':
|
||||||
style.layers = orthoStyle;
|
style.layers = orthoLayers as LayerSpecification[];
|
||||||
style.name = 'Photographies aériennes';
|
style.name = 'Photographies aériennes';
|
||||||
break;
|
break;
|
||||||
case 'vector':
|
case 'vector':
|
||||||
style.layers = vectorStyle;
|
style.layers = vectorLayers as LayerSpecification[];
|
||||||
style.name = 'Carte OSM';
|
style.name = 'Carte OSM';
|
||||||
break;
|
break;
|
||||||
case 'ign':
|
case 'ign':
|
||||||
style.layers = ignLayers;
|
style.layers = ignLayers as LayerSpecification[];
|
||||||
style.name = 'Carte IGN';
|
style.name = 'Carte IGN';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "batiments-line",
|
||||||
|
"type": "line",
|
||||||
|
"source": "cadastre",
|
||||||
|
"source-layer": "batiments",
|
||||||
|
"minzoom": 16,
|
||||||
|
"maxzoom": 22,
|
||||||
|
"layout": { "visibility": "visible" },
|
||||||
|
"paint": {
|
||||||
|
"line-opacity": 1,
|
||||||
|
"line-color": "rgba(0, 0, 0, 1)",
|
||||||
|
"line-width": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "batiments-fill",
|
||||||
|
"type": "fill",
|
||||||
|
"source": "cadastre",
|
||||||
|
"source-layer": "batiments",
|
||||||
|
"layout": { "visibility": "visible" },
|
||||||
|
"paint": {
|
||||||
|
"fill-color": "rgba(150, 150, 150, 1)",
|
||||||
|
"fill-opacity": {
|
||||||
|
"stops": [
|
||||||
|
[16, 0],
|
||||||
|
[17, 0.6]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"fill-antialias": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "parcelles",
|
||||||
|
"type": "line",
|
||||||
|
"source": "cadastre",
|
||||||
|
"source-layer": "parcelles",
|
||||||
|
"minzoom": 15.5,
|
||||||
|
"maxzoom": 24,
|
||||||
|
"layout": {
|
||||||
|
"visibility": "visible",
|
||||||
|
"line-cap": "butt",
|
||||||
|
"line-join": "miter",
|
||||||
|
"line-miter-limit": 2
|
||||||
|
},
|
||||||
|
"paint": {
|
||||||
|
"line-color": "rgba(255, 255, 255, 1)",
|
||||||
|
"line-opacity": 0.8,
|
||||||
|
"line-width": {
|
||||||
|
"stops": [
|
||||||
|
[16, 1.5],
|
||||||
|
[17, 2]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"line-offset": 0,
|
||||||
|
"line-blur": 0,
|
||||||
|
"line-translate": [0, 1],
|
||||||
|
"line-dasharray": [1],
|
||||||
|
"line-gap-width": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "parcelles-fill",
|
||||||
|
"type": "fill",
|
||||||
|
"source": "cadastre",
|
||||||
|
"source-layer": "parcelles",
|
||||||
|
"layout": {
|
||||||
|
"visibility": "visible"
|
||||||
|
},
|
||||||
|
"paint": {
|
||||||
|
"fill-color": "rgba(129, 123, 0, 1)",
|
||||||
|
"fill-opacity": [
|
||||||
|
"case",
|
||||||
|
["boolean", ["feature-state", "hover"], false],
|
||||||
|
0.7,
|
||||||
|
0.1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "parcelle-highlighted",
|
||||||
|
"type": "fill",
|
||||||
|
"source": "cadastre",
|
||||||
|
"source-layer": "parcelles",
|
||||||
|
"filter": ["in", "id", ""],
|
||||||
|
"paint": {
|
||||||
|
"fill-color": "rgba(1, 129, 0, 1)",
|
||||||
|
"fill-opacity": 0.7
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sections",
|
||||||
|
"type": "line",
|
||||||
|
"source": "cadastre",
|
||||||
|
"source-layer": "sections",
|
||||||
|
"minzoom": 12,
|
||||||
|
"layout": { "visibility": "visible" },
|
||||||
|
"paint": {
|
||||||
|
"line-color": "rgba(0, 0, 0, 1)",
|
||||||
|
"line-opacity": 0.7,
|
||||||
|
"line-width": 2,
|
||||||
|
"line-dasharray": [3, 3],
|
||||||
|
"line-translate": [0, 0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
|
@ -1,110 +0,0 @@
|
||||||
import type { AnyLayer } from 'maplibre-gl';
|
|
||||||
|
|
||||||
const layers: AnyLayer[] = [
|
|
||||||
{
|
|
||||||
id: 'batiments-line',
|
|
||||||
type: 'line',
|
|
||||||
source: 'cadastre',
|
|
||||||
'source-layer': 'batiments',
|
|
||||||
minzoom: 16,
|
|
||||||
maxzoom: 22,
|
|
||||||
layout: { visibility: 'visible' },
|
|
||||||
paint: {
|
|
||||||
'line-opacity': 1,
|
|
||||||
'line-color': 'rgba(0, 0, 0, 1)',
|
|
||||||
'line-width': 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'batiments-fill',
|
|
||||||
type: 'fill',
|
|
||||||
source: 'cadastre',
|
|
||||||
'source-layer': 'batiments',
|
|
||||||
layout: { visibility: 'visible' },
|
|
||||||
paint: {
|
|
||||||
'fill-color': 'rgba(150, 150, 150, 1)',
|
|
||||||
'fill-opacity': {
|
|
||||||
stops: [
|
|
||||||
[16, 0],
|
|
||||||
[17, 0.6]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
'fill-antialias': true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'parcelles',
|
|
||||||
type: 'line',
|
|
||||||
source: 'cadastre',
|
|
||||||
'source-layer': 'parcelles',
|
|
||||||
minzoom: 15.5,
|
|
||||||
maxzoom: 24,
|
|
||||||
layout: {
|
|
||||||
visibility: 'visible',
|
|
||||||
'line-cap': 'butt',
|
|
||||||
'line-join': 'miter',
|
|
||||||
'line-miter-limit': 2
|
|
||||||
},
|
|
||||||
paint: {
|
|
||||||
'line-color': 'rgba(255, 255, 255, 1)',
|
|
||||||
'line-opacity': 0.8,
|
|
||||||
'line-width': {
|
|
||||||
stops: [
|
|
||||||
[16, 1.5],
|
|
||||||
[17, 2]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
'line-offset': 0,
|
|
||||||
'line-blur': 0,
|
|
||||||
'line-translate': [0, 1],
|
|
||||||
'line-dasharray': [1],
|
|
||||||
'line-gap-width': 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'parcelles-fill',
|
|
||||||
type: 'fill',
|
|
||||||
source: 'cadastre',
|
|
||||||
'source-layer': 'parcelles',
|
|
||||||
layout: {
|
|
||||||
visibility: 'visible'
|
|
||||||
},
|
|
||||||
paint: {
|
|
||||||
'fill-color': 'rgba(129, 123, 0, 1)',
|
|
||||||
'fill-opacity': [
|
|
||||||
'case',
|
|
||||||
['boolean', ['feature-state', 'hover'], false],
|
|
||||||
0.7,
|
|
||||||
0.1
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'parcelle-highlighted',
|
|
||||||
type: 'fill',
|
|
||||||
source: 'cadastre',
|
|
||||||
'source-layer': 'parcelles',
|
|
||||||
filter: ['in', 'id', ''],
|
|
||||||
paint: {
|
|
||||||
'fill-color': 'rgba(1, 129, 0, 1)',
|
|
||||||
'fill-opacity': 0.7
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'sections',
|
|
||||||
type: 'line',
|
|
||||||
source: 'cadastre',
|
|
||||||
'source-layer': 'sections',
|
|
||||||
minzoom: 12,
|
|
||||||
layout: { visibility: 'visible' },
|
|
||||||
paint: {
|
|
||||||
'line-color': 'rgba(0, 0, 0, 1)',
|
|
||||||
'line-opacity': 0.7,
|
|
||||||
'line-width': 2,
|
|
||||||
'line-dasharray': [3, 3],
|
|
||||||
'line-translate': [0, 0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
export default layers;
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "ign",
|
||||||
|
"source": "plan-ign",
|
||||||
|
"type": "raster",
|
||||||
|
"paint": { "raster-resampling": "linear" }
|
||||||
|
}
|
||||||
|
]
|
|
@ -1,12 +0,0 @@
|
||||||
import type { RasterLayer } from 'maplibre-gl';
|
|
||||||
|
|
||||||
const layers: RasterLayer[] = [
|
|
||||||
{
|
|
||||||
id: 'ign',
|
|
||||||
source: 'plan-ign',
|
|
||||||
type: 'raster',
|
|
||||||
paint: { 'raster-resampling': 'linear' }
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
export default layers;
|
|
2647
app/javascript/components/shared/maplibre/styles/layers/ortho.json
Normal file
2647
app/javascript/components/shared/maplibre/styles/layers/ortho.json
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
2866
app/javascript/components/shared/maplibre/styles/layers/vector.json
Normal file
2866
app/javascript/components/shared/maplibre/styles/layers/vector.json
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -40,3 +40,4 @@
|
||||||
@import '@gouvfr/dsfr/dist/component/accordion/accordion.css';
|
@import '@gouvfr/dsfr/dist/component/accordion/accordion.css';
|
||||||
@import '@gouvfr/dsfr/dist/component/tab/tab.css';
|
@import '@gouvfr/dsfr/dist/component/tab/tab.css';
|
||||||
@import '@gouvfr/dsfr/dist/component/tooltip/tooltip.css';
|
@import '@gouvfr/dsfr/dist/component/tooltip/tooltip.css';
|
||||||
|
@import '@gouvfr/dsfr/dist/component/range/range.css';
|
||||||
|
|
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
37
package.json
37
package.json
|
@ -10,11 +10,10 @@
|
||||||
"@gouvfr/dsfr": "^1.11.2",
|
"@gouvfr/dsfr": "^1.11.2",
|
||||||
"@graphiql/plugin-explorer": "^3.1.0",
|
"@graphiql/plugin-explorer": "^3.1.0",
|
||||||
"@graphiql/toolkit": "^0.9.1",
|
"@graphiql/toolkit": "^0.9.1",
|
||||||
"@headlessui/react": "^1.6.6",
|
|
||||||
"@heroicons/react": "^1.0.6",
|
"@heroicons/react": "^1.0.6",
|
||||||
"@hotwired/stimulus": "^3.2.2",
|
"@hotwired/stimulus": "^3.2.2",
|
||||||
"@hotwired/turbo": "^7.3.0",
|
"@hotwired/turbo": "^7.3.0",
|
||||||
"@mapbox/mapbox-gl-draw": "^1.3.0",
|
"@mapbox/mapbox-gl-draw": "^1.4.3",
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"@rails/actiontext": "^7.1.3-4",
|
"@rails/actiontext": "^7.1.3-4",
|
||||||
"@rails/activestorage": "^7.1.3-4",
|
"@rails/activestorage": "^7.1.3-4",
|
||||||
|
@ -52,14 +51,14 @@
|
||||||
"graphql": "^16.9.0",
|
"graphql": "^16.9.0",
|
||||||
"highcharts": "^10.3.3",
|
"highcharts": "^10.3.3",
|
||||||
"lightgallery": "^2.7.2",
|
"lightgallery": "^2.7.2",
|
||||||
"maplibre-gl": "^1.15.2",
|
"maplibre-gl": "^4.5.0",
|
||||||
"match-sorter": "^6.3.4",
|
"match-sorter": "^6.3.4",
|
||||||
"patch-package": "^8.0.0",
|
"patch-package": "^8.0.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-aria-components": "^1.3.1",
|
"react-aria-components": "^1.3.1",
|
||||||
"react-coordinate-input": "^1.0.0",
|
"react-coordinate-input": "^1.0.0",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-popper": "^2.3.0",
|
"react-fast-compare": "^3.2.2",
|
||||||
"react-use-event-hook": "^0.9.6",
|
"react-use-event-hook": "^0.9.6",
|
||||||
"spectaql": "^3.0.1",
|
"spectaql": "^3.0.1",
|
||||||
"stimulus-use": "^0.52.2",
|
"stimulus-use": "^0.52.2",
|
||||||
|
@ -81,7 +80,7 @@
|
||||||
"@types/debounce": "^1.2.4",
|
"@types/debounce": "^1.2.4",
|
||||||
"@types/geojson": "^7946.0.14",
|
"@types/geojson": "^7946.0.14",
|
||||||
"@types/is-hotkey": "^0.1.10",
|
"@types/is-hotkey": "^0.1.10",
|
||||||
"@types/mapbox__mapbox-gl-draw": "^1.2.5",
|
"@types/mapbox__mapbox-gl-draw": "^1.4.6",
|
||||||
"@types/rails__activestorage": "^7.1.1",
|
"@types/rails__activestorage": "^7.1.1",
|
||||||
"@types/rails__ujs": "^6.0.4",
|
"@types/rails__ujs": "^6.0.4",
|
||||||
"@types/react": "^18.3.3",
|
"@types/react": "^18.3.3",
|
||||||
|
@ -137,7 +136,10 @@
|
||||||
"process": true,
|
"process": true,
|
||||||
"gon": true
|
"gon": true
|
||||||
},
|
},
|
||||||
"plugins": ["prettier", "react-hooks"],
|
"plugins": [
|
||||||
|
"prettier",
|
||||||
|
"react-hooks"
|
||||||
|
],
|
||||||
"extends": [
|
"extends": [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"prettier",
|
"prettier",
|
||||||
|
@ -156,16 +158,29 @@
|
||||||
"react/no-deprecated": "off"
|
"react/no-deprecated": "off"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"react": { "version": "detect" }
|
"react": {
|
||||||
|
"version": "detect"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"files": [".eslintrc.js", "vite.config.ts", "postcss.config.js"],
|
"files": [
|
||||||
"env": { "node": true }
|
".eslintrc.js",
|
||||||
|
"vite.config.ts",
|
||||||
|
"postcss.config.js"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"node": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"files": ["**/*.ts", "**/*.tsx"],
|
"files": [
|
||||||
"plugins": ["@typescript-eslint"],
|
"**/*.ts",
|
||||||
|
"**/*.tsx"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"@typescript-eslint"
|
||||||
|
],
|
||||||
"extends": [
|
"extends": [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"plugin:@typescript-eslint/recommended",
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
|
Loading…
Reference in a new issue