Merge pull request #10845 from tchak/update-maplibre

chore(maplibre): update
This commit is contained in:
Paul Chavard 2024-09-23 12:53:34 +00:00 committed by GitHub
commit 0321226130
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 5936 additions and 5826 deletions

View file

@ -1,6 +1,4 @@
@import "colors";
@import "constants";
.areas {
margin-bottom: 10px;
@ -10,56 +8,49 @@
}
}
.map-style-control {
position: absolute;
bottom: 4px;
left: 10px;
.ds-ctrl button {
color: $dark-grey;
img {
width: 100%;
}
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;
}
}
&.on,
&:hover {
background-color: rgba(0, 0, 0, 0.05);
}
}
.cadastres-selection-control {
z-index: 1;
position: absolute;
top: 135px;
left: 10px;
.react-aria-popover {
&[data-placement='top'] {
--origin: translateY(8px);
}
button {
&.on,
&:hover {
background-color: rgba(0, 0, 0, 0.05);
}
&[data-placement='bottom'] {
--origin: translateY(-8px);
}
&[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;
}
}

View file

@ -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 { CursorClickIcon } from '@heroicons/react/outline';
import { useMapLibre } from '../../shared/maplibre/MapLibre';
import { useMapLibre, ReactControl } from '../../shared/maplibre/MapLibre';
import {
useEvent,
useMapEvent,
@ -18,15 +20,31 @@ export function CadastreLayer({
featureCollection,
createFeatures,
deleteFeatures,
toggle,
enabled
}: {
featureCollection: FeatureCollection;
createFeatures: CreateFeatures;
deleteFeatures: DeleteFeatures;
toggle: () => void;
enabled: boolean;
}) {
const map = useMapLibre();
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(
(cid: string, highlight: boolean) => {
@ -95,7 +113,35 @@ export function CadastreLayer({
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(

View file

@ -1,5 +1,5 @@
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 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.
// 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(
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);
}
}
patchDrawControl();
}
return () => {
@ -228,3 +224,15 @@ const translations = [
['.mapbox-gl-draw_point', 'Ajouter un point'],
['.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);
}
}
});
}

View file

@ -1,6 +1,4 @@
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 type { FeatureCollection } from 'geojson';
@ -51,25 +49,12 @@ export default function MapEditor({
enabled={!cadastreEnabled}
/>
{options.layers.includes('cadastres') ? (
<>
<CadastreLayer
featureCollection={featureCollection}
{...actions}
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>
</>
<CadastreLayer
featureCollection={featureCollection}
{...actions}
toggle={() => setCadastreEnabled((enabled) => !enabled)}
enabled={cadastreEnabled}
/>
) : null}
</MapLibre>
<PointInput featureCollection={featureCollection} />

View file

@ -1,4 +1,3 @@
import 'maplibre-gl/dist/maplibre-gl.css';
import type { FeatureCollection } from 'geojson';
import { MapLibre } from '../shared/maplibre/MapLibre';

View file

@ -8,13 +8,15 @@ import {
createContext,
useCallback
} from 'react';
import maplibre, { Map, NavigationControl } from 'maplibre-gl';
import type { Style } from 'maplibre-gl';
import { createPortal } from 'react-dom';
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 { useStyle, useElementVisible } from './hooks';
import { StyleControl } from './StyleControl';
import { StyleSwitch } from './StyleControl';
const Context = createContext<{ map?: Map | null }>({});
@ -30,16 +32,15 @@ export function useMapLibre() {
}
export function MapLibre({ children, layers }: MapLibreProps) {
const isSupported = useMemo(
() => maplibre.supported({ failIfMajorPerformanceCaveat: true }) && !isIE(),
[]
);
const isSupported = useMemo(() => isWebglSupported(), []);
const containerRef = useRef<HTMLDivElement>(null);
const visible = useElementVisible(containerRef);
const [map, setMap] = useState<Map | null>();
const [styleControlElement, setStyleControlElement] =
useState<HTMLElement | null>(null);
const onStyleChange = useCallback(
(style: Style) => {
(style: StyleSpecification) => {
if (map) {
map.setStyle(style);
}
@ -56,8 +57,11 @@ export function MapLibre({ children, layers }: MapLibreProps) {
style
});
map.addControl(new NavigationControl({}), 'top-right');
const styleControl = new ReactControl();
map.addControl(styleControl, 'bottom-left');
map.on('load', () => {
setMap(map);
setStyleControlElement(styleControl.container);
});
}
}, [map, style, visible, isSupported]);
@ -91,16 +95,54 @@ export function MapLibre({ children, layers }: MapLibreProps) {
return (
<Context.Provider value={{ map }}>
<div ref={containerRef} style={{ height: '500px' }}>
<StyleControl styleId={style.id} {...mapStyleProps} />
{styleControlElement != null
? createPortal(
<StyleSwitch styleId={style.id} {...mapStyleProps} />,
styleControlElement
)
: null}
{map ? children : null}
</div>
</Context.Provider>
);
}
function isIE() {
const ua = window.navigator.userAgent;
const msie = ua.indexOf('MSIE ');
const trident = ua.indexOf('Trident/');
return msie > 0 || trident > 0;
function isWebglSupported() {
if (window.WebGLRenderingContext) {
const canvas = document.createElement('canvas');
try {
// 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;
}
}

View file

@ -1,6 +1,5 @@
import { useState, useId } from 'react';
import { Popover, RadioGroup } from '@headlessui/react';
import { usePopper } from 'react-popper';
import { useId, useRef, useEffect } from 'react';
import { Button, Dialog, DialogTrigger, Popover } from 'react-aria-components';
import { MapIcon } from '@heroicons/react/outline';
import { Slider } from '@reach/slider';
import '@reach/slider/styles.css';
@ -13,7 +12,7 @@ const STYLES = {
ign: 'Carte IGN'
};
export function StyleControl({
export function StyleSwitch({
styleId,
layers,
setStyle,
@ -26,107 +25,97 @@ export function StyleControl({
setLayerEnabled: (layer: string, enabled: boolean) => 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(
([, { configurable }]) => configurable
);
const mapId = useId();
const buttonRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
if (buttonRef.current) {
buttonRef.current.title = 'Sélectionner les couches cartographiques';
}
}, []);
return (
<div
className="form map-style-control mapboxgl-ctrl-group"
style={{ zIndex: 10 }}
>
<Popover>
<Popover.Button
ref={setButtonElement}
className="map-style-button"
title="Sélectionner les couches cartographiques"
>
<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"
<DialogTrigger>
<Button ref={buttonRef}>
<MapIcon className="icon-size" />
</Button>
<Popover className="react-aria-popover">
<Dialog className="fr-modal__body">
<form
className="fr-modal__content flex m-2"
onSubmit={(event) => event.preventDefault()}
>
{Object.entries(STYLES).map(([style, title]) => (
<RadioGroup.Option
key={style}
value={style}
as="li"
className="flex"
>
{({ checked }) => (
<>
<div className="fr-fieldset">
{Object.entries(STYLES).map(([style, title]) => (
<div className="fr-fieldset__element" key={style}>
<div className="fr-radio-group">
<input
id={`${mapId}-${style}`}
value={style}
type="radio"
key={`${style}-${checked}`}
defaultChecked={checked}
name="map-style"
className="m-0 p-0 mr-1"
/>
<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}
defaultValue={style}
checked={styleId == style}
onChange={(event) => {
setLayerEnabled(layer, event.target.checked);
setStyle(event.target.value);
}}
/>
<label className="m-0" htmlFor={`${mapId}-${layer}`}>
{name}
<label htmlFor={`${mapId}-${style}`} className="fr-label">
{title.replace(/\s/g, ' ')}
</label>
</div>
<Slider
min={10}
max={100}
step={5}
value={opacity}
onChange={(value) => {
setLayerOpacity(layer, value);
}}
className="mb-1"
title={`Réglage de lopacité de la couche «${NBS}${name}${NBS}»`}
getAriaLabel={() =>
`Réglage de lopacité de la couche «${NBS}${name}${NBS}»`
}
getAriaValueText={(value) =>
`Lopacité de la couche «${NBS}${name}${NBS}» est à ${value}${NBS}%`
}
/>
</li>
</div>
))}
</ul>
) : null}
</Popover.Panel>
</div>
{configurableLayers.length ? (
<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 lopacité de la couche «${NBS}${name}${NBS}»`}
getAriaLabel={() =>
`Réglage de lopacité de la couche «${NBS}${name}${NBS}»`
}
getAriaValueText={(value) =>
`Lopacité de la couche «${NBS}${name}${NBS}» est à ${value}${NBS}%`
}
/>
</div>
)
)}
</div>
) : null}
</form>
</Dialog>
</Popover>
</div>
</DialogTrigger>
);
}

View file

@ -9,7 +9,7 @@ import type {
LngLatBoundsLike,
LngLat,
MapLayerEventType,
Style,
StyleSpecification,
LngLatLike
} from 'maplibre-gl';
import type { Feature, Geometry } from 'geojson';
@ -104,7 +104,7 @@ function optionalLayersMap(optionalLayers: string[]): LayersMap {
export function useStyle(
optionalLayers: string[],
onStyleChange: (style: Style) => void
onStyleChange: (style: StyleSpecification) => void
) {
const [styleId, setStyle] = useState('ortho');
const [layers, setLayers] = useState(() => optionalLayersMap(optionalLayers));

View file

@ -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 cadastreLayers from './layers/cadastre';
import cadastreLayers from './layers/cadastre.json';
function ignServiceURL(layer: string, style: string, format = 'image/png') {
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 {
type: 'raster',
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 {
id: source,
source,
@ -186,14 +197,14 @@ function rasterLayer(source: string, opacity: number): RasterLayer {
export function buildOptionalLayers(
ids: string[],
opacity: Record<string, number>
): AnyLayer[] {
): LayerSpecification[] {
return OPTIONAL_LAYERS.filter(({ id }) => ids.includes(id))
.flatMap(({ layers, id }) =>
layers.map(([, code]) => [code, opacity[id] / 100] as const)
)
.flatMap(([code, opacity]) =>
code === 'CADASTRE'
? cadastreLayers
? (cadastreLayers as LayerSpecification[])
: [rasterLayer(getLayerCode(code), opacity)]
);
}
@ -210,9 +221,9 @@ function getLayerCode(code: string) {
return code.toLowerCase().replace(/\./g, '-');
}
export default {
export const style: StyleSpecification = {
version: 8,
metadat: {
metadata: {
'mapbox:autocomposite': false,
'mapbox:groups': {
1444849242106.713: { collapsed: false, name: 'Places' },
@ -257,5 +268,6 @@ export default {
...buildSources()
},
sprite: 'https://openmaptiles.github.io/osm-bright-gl-style/sprite',
glyphs: 'https://openmaptiles.geo.data.gouv.fr/fonts/{fontstack}/{range}.pbf'
} as Style;
glyphs: 'https://openmaptiles.geo.data.gouv.fr/fonts/{fontstack}/{range}.pbf',
layers: []
};

View file

@ -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 orthoStyle from './layers/ortho';
import vectorStyle from './layers/vector';
import ignLayers from './layers/ign';
import {
style as baseStyle,
buildOptionalLayers,
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 };
@ -21,20 +26,20 @@ export function getMapStyle(
id: string,
layers: string[],
opacity: Record<string, number>
): Style & { id: string } {
): StyleSpecification & { id: string } {
const style = { ...baseStyle, id };
switch (id) {
case 'ortho':
style.layers = orthoStyle;
style.layers = orthoLayers as LayerSpecification[];
style.name = 'Photographies aériennes';
break;
case 'vector':
style.layers = vectorStyle;
style.layers = vectorLayers as LayerSpecification[];
style.name = 'Carte OSM';
break;
case 'ign':
style.layers = ignLayers;
style.layers = ignLayers as LayerSpecification[];
style.name = 'Carte IGN';
break;
}

View file

@ -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]
}
}
]

View file

@ -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;

View file

@ -0,0 +1,8 @@
[
{
"id": "ign",
"source": "plan-ign",
"type": "raster",
"paint": { "raster-resampling": "linear" }
}
]

View file

@ -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;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -40,3 +40,4 @@
@import '@gouvfr/dsfr/dist/component/accordion/accordion.css';
@import '@gouvfr/dsfr/dist/component/tab/tab.css';
@import '@gouvfr/dsfr/dist/component/tooltip/tooltip.css';
@import '@gouvfr/dsfr/dist/component/range/range.css';

BIN
bun.lockb

Binary file not shown.

View file

@ -10,11 +10,10 @@
"@gouvfr/dsfr": "^1.11.2",
"@graphiql/plugin-explorer": "^3.1.0",
"@graphiql/toolkit": "^0.9.1",
"@headlessui/react": "^1.6.6",
"@heroicons/react": "^1.0.6",
"@hotwired/stimulus": "^3.2.2",
"@hotwired/turbo": "^7.3.0",
"@mapbox/mapbox-gl-draw": "^1.3.0",
"@mapbox/mapbox-gl-draw": "^1.4.3",
"@popperjs/core": "^2.11.8",
"@rails/actiontext": "^7.1.3-4",
"@rails/activestorage": "^7.1.3-4",
@ -52,14 +51,14 @@
"graphql": "^16.9.0",
"highcharts": "^10.3.3",
"lightgallery": "^2.7.2",
"maplibre-gl": "^1.15.2",
"maplibre-gl": "^4.5.0",
"match-sorter": "^6.3.4",
"patch-package": "^8.0.0",
"react": "^18.3.1",
"react-aria-components": "^1.3.1",
"react-coordinate-input": "^1.0.0",
"react-dom": "^18.3.1",
"react-popper": "^2.3.0",
"react-fast-compare": "^3.2.2",
"react-use-event-hook": "^0.9.6",
"spectaql": "^3.0.1",
"stimulus-use": "^0.52.2",
@ -81,7 +80,7 @@
"@types/debounce": "^1.2.4",
"@types/geojson": "^7946.0.14",
"@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__ujs": "^6.0.4",
"@types/react": "^18.3.3",
@ -137,7 +136,10 @@
"process": true,
"gon": true
},
"plugins": ["prettier", "react-hooks"],
"plugins": [
"prettier",
"react-hooks"
],
"extends": [
"eslint:recommended",
"prettier",
@ -156,16 +158,29 @@
"react/no-deprecated": "off"
},
"settings": {
"react": { "version": "detect" }
"react": {
"version": "detect"
}
},
"overrides": [
{
"files": [".eslintrc.js", "vite.config.ts", "postcss.config.js"],
"env": { "node": true }
"files": [
".eslintrc.js",
"vite.config.ts",
"postcss.config.js"
],
"env": {
"node": true
}
},
{
"files": ["**/*.ts", "**/*.tsx"],
"plugins": ["@typescript-eslint"],
"files": [
"**/*.ts",
"**/*.tsx"
],
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",