Improuve mapbox utilis and shared components

This commit is contained in:
Paul Chavard 2021-05-06 18:51:19 +02:00
parent 3b85ade440
commit 19440afebf
14 changed files with 168 additions and 136 deletions

View file

@ -1,3 +1,5 @@
@import "colors";
.areas-title { .areas-title {
font-weight: bold; font-weight: bold;
margin-top: 5px; margin-top: 5px;
@ -15,3 +17,38 @@
.form [data-react-class='MapEditor'] [data-reach-combobox-input] { .form [data-react-class='MapEditor'] [data-reach-combobox-input] {
margin-bottom: 0; margin-bottom: 0;
} }
.map-style-control {
position: absolute;
bottom: 4px;
left: 10px;
img {
width: 100%;
}
button {
padding: 0;
border: none;
cursor: pointer;
> div {
position: absolute;
bottom: 5px;
left: 5px;
}
}
}
.cadastres-selection-control {
position: absolute;
top: 135px;
left: 10px;
button {
&.on,
&:hover {
background-color: rgba(0, 0, 0, 0.05);
}
}
}

View file

@ -163,7 +163,7 @@
} }
} }
input[type=text]:not([data-address='true']), input[type=text],
input[type=email], input[type=email],
input[type=password], input[type=password],
input[type=date], input[type=date],
@ -178,6 +178,10 @@
&.small-margin { &.small-margin {
margin-bottom: $default-spacer; margin-bottom: $default-spacer;
} }
&.no-margin {
margin-bottom: 0;
}
} }
.add-row { .add-row {
@ -475,7 +479,7 @@
} }
[data-react-class]:not([data-react-class="ComboMultipleDropdownList"]) { [data-react-class]:not([data-react-class="ComboMultipleDropdownList"]) {
[data-reach-combobox-input] { [data-reach-combobox-input]:not(.no-margin) {
margin-bottom: $default-fields-spacer; margin-bottom: $default-fields-spacer;
} }
} }

View file

@ -11,13 +11,15 @@ function ComboAdresseSearch({
hiddenFieldId, hiddenFieldId,
onChange, onChange,
transformResult = ({ properties: { label } }) => [label, label], transformResult = ({ properties: { label } }) => [label, label],
allowInputValues = true allowInputValues = true,
className
}) { }) {
const transformResults = useCallback((_, { features }) => features); const transformResults = useCallback((_, { features }) => features);
return ( return (
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<ComboSearch <ComboSearch
className={className}
placeholder={placeholder} placeholder={placeholder}
required={mandatory} required={mandatory}
hiddenFieldId={hiddenFieldId} hiddenFieldId={hiddenFieldId}
@ -33,6 +35,7 @@ function ComboAdresseSearch({
} }
ComboAdresseSearch.propTypes = { ComboAdresseSearch.propTypes = {
className: PropTypes.string,
placeholder: PropTypes.string, placeholder: PropTypes.string,
mandatory: PropTypes.bool, mandatory: PropTypes.bool,
hiddenFieldId: PropTypes.string, hiddenFieldId: PropTypes.string,

View file

@ -25,7 +25,8 @@ function ComboSearch({
minimumInputLength, minimumInputLength,
transformResult, transformResult,
allowInputValues = false, allowInputValues = false,
transformResults = defaultTransformResults transformResults = defaultTransformResults,
className
}) { }) {
const label = scope; const label = scope;
const hiddenValueField = useMemo( const hiddenValueField = useMemo(
@ -93,6 +94,7 @@ function ComboSearch({
return ( return (
<Combobox aria-label={label} onSelect={handleOnSelect}> <Combobox aria-label={label} onSelect={handleOnSelect}>
<ComboboxInput <ComboboxInput
className={className}
placeholder={placeholder} placeholder={placeholder}
onChange={handleOnChange} onChange={handleOnChange}
value={value} value={value}
@ -134,7 +136,8 @@ ComboSearch.propTypes = {
transformResult: PropTypes.func, transformResult: PropTypes.func,
transformResults: PropTypes.func, transformResults: PropTypes.func,
allowInputValues: PropTypes.bool, allowInputValues: PropTypes.bool,
onChange: PropTypes.func onChange: PropTypes.func,
className: PropTypes.string
}; };
export default ComboSearch; export default ComboSearch;

View file

@ -0,0 +1,69 @@
import React, { useMemo, useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { getMapStyle } from './styles';
import ortho from './styles/images/preview-ortho.png';
import vector from './styles/images/preview-vector.png';
const STYLES = {
ortho: {
title: 'Satellite',
preview: ortho,
color: '#fff'
},
vector: {
title: 'Vectoriel',
preview: vector,
color: '#000'
},
ign: {
title: 'Carte IGN',
preview: vector,
color: '#000'
}
};
function getNextStyle(style) {
const styleNames = Object.keys(STYLES);
const index = styleNames.indexOf(style) + 1;
if (index === styleNames.length) {
return styleNames[0];
}
return styleNames[index];
}
export function useMapStyle(
optionalLayers,
{ onStyleChange, cadastreEnabled }
) {
const [styleId, setStyle] = useState('ortho');
const style = useMemo(() => getMapStyle(styleId, optionalLayers), [
styleId,
optionalLayers
]);
useEffect(() => onStyleChange(), [styleId, cadastreEnabled]);
return [style, setStyle];
}
function MapStyleControl({ style, setStyle }) {
const nextStyle = getNextStyle(style);
const { title, preview, color } = STYLES[nextStyle];
return (
<div className="map-style-control">
<button type="button" onClick={() => setStyle(nextStyle)}>
<img alt={title} src={preview} />
<div style={{ color }}>{title}</div>
</button>
</div>
);
}
MapStyleControl.propTypes = {
style: PropTypes.string,
setStyle: PropTypes.func
};
export default MapStyleControl;

View file

@ -1,3 +0,0 @@
import ReactMapboxGl from 'react-mapbox-gl';
export default ReactMapboxGl({});

View file

@ -1,81 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import ortho from './styles/images/preview-ortho.png';
import vector from './styles/images/preview-vector.png';
const STYLES = {
ortho: {
title: 'Satellite',
preview: ortho,
color: '#fff'
},
vector: {
title: 'Vectoriel',
preview: vector,
color: '#000'
}
};
const IGN_STYLES = {
...STYLES,
ign: {
title: 'Carte IGN',
preview: vector,
color: '#000'
}
};
function getNextStyle(style, ign) {
const styles = Object.keys(ign ? IGN_STYLES : STYLES);
let index = styles.indexOf(style) + 1;
if (index === styles.length) {
return styles[0];
}
return styles[index];
}
function SwitchMapStyle({ style, setStyle, ign }) {
const nextStyle = getNextStyle(style, ign);
const { title, preview, color } = (ign ? IGN_STYLES : STYLES)[nextStyle];
const imgStyle = {
width: '100%',
height: '100%',
cursor: 'pointer'
};
const textStyle = {
position: 'relative',
bottom: '26px',
left: '4px',
color
};
return (
<div
className="style-switch"
style={{
position: 'absolute',
bottom: 0,
left: 0
}}
onClick={() => setStyle(nextStyle)}
>
<div className="switch-style mapboxgl-ctrl-swith-map-style">
<img alt={title} style={imgStyle} src={preview} />
<div className="text" style={textStyle}>
{title}
</div>
</div>
</div>
);
}
SwitchMapStyle.propTypes = {
style: PropTypes.string,
setStyle: PropTypes.func,
ign: PropTypes.bool
};
export default SwitchMapStyle;

View file

@ -1,4 +1,4 @@
import cadastreLayers from './cadastre-layers'; import cadastreLayers from './layers/cadastre';
const IGN_TOKEN = 'rc1egnbeoss72hxvd143tbyk'; const IGN_TOKEN = 'rc1egnbeoss72hxvd143tbyk';
@ -138,7 +138,16 @@ function rasterSource(tiles, attribution) {
}; };
} }
export function buildLayers(ids) { function rasterLayer(source) {
return {
id: source,
source,
type: 'raster',
paint: { 'raster-resampling': 'linear' }
};
}
export function buildOptionalLayers(ids) {
return OPTIONAL_LAYERS.filter(({ id }) => ids.includes(id)) return OPTIONAL_LAYERS.filter(({ id }) => ids.includes(id))
.flatMap(({ layers }) => layers) .flatMap(({ layers }) => layers)
.flatMap(([, code]) => .flatMap(([, code]) =>
@ -148,15 +157,6 @@ export function buildLayers(ids) {
); );
} }
export function rasterLayer(source) {
return {
id: source,
source,
type: 'raster',
paint: { 'raster-resampling': 'linear' }
};
}
export default { export default {
version: 8, version: 8,
metadat: { metadat: {

View file

@ -1,29 +1,27 @@
import baseStyle, { rasterLayer, buildLayers } from './base'; import baseStyle, { buildOptionalLayers } from './base';
import orthoStyle from './ortho-style'; import orthoStyle from './layers/ortho';
import vectorStyle from './vector-style'; import vectorStyle from './layers/vector';
import ignLayers from './layers/ign';
export function getMapStyle(style, optionalLayers) { export function getMapStyle(id, optionalLayers) {
const mapStyle = { ...baseStyle }; const style = { ...baseStyle, id };
switch (style) { switch (id) {
case 'ortho': case 'ortho':
mapStyle.layers = orthoStyle; style.layers = orthoStyle;
mapStyle.id = 'ortho'; style.name = 'Photographies aériennes';
mapStyle.name = 'Photographies aériennes';
break; break;
case 'vector': case 'vector':
mapStyle.layers = vectorStyle; style.layers = vectorStyle;
mapStyle.id = 'vector'; style.name = 'Carte OSM';
mapStyle.name = 'Carte OSM';
break; break;
case 'ign': case 'ign':
mapStyle.layers = [rasterLayer('plan-ign')]; style.layers = ignLayers;
mapStyle.id = 'ign'; style.name = 'Carte IGN';
mapStyle.name = 'Carte IGN';
break; break;
} }
mapStyle.layers = mapStyle.layers.concat(buildLayers(optionalLayers)); style.layers = style.layers.concat(buildOptionalLayers(optionalLayers));
return mapStyle; return style;
} }

View file

@ -82,7 +82,7 @@ export default [
type: 'fill', type: 'fill',
source: 'cadastre', source: 'cadastre',
'source-layer': 'parcelles', 'source-layer': 'parcelles',
filter: ['==', 'id', ''], filter: ['in', 'id', ''],
paint: { paint: {
'fill-color': 'rgba(1, 129, 0, 1)', 'fill-color': 'rgba(1, 129, 0, 1)',
'fill-opacity': 0.7 'fill-opacity': 0.7

View file

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

View file

@ -1,5 +1,4 @@
import { LngLatBounds } from 'mapbox-gl'; import { LngLatBounds } from 'mapbox-gl';
import { useEffect } from 'react';
export function getBounds(geometry) { export function getBounds(geometry) {
const bbox = new LngLatBounds(); const bbox = new LngLatBounds();
@ -18,15 +17,9 @@ export function getBounds(geometry) {
return bbox; return bbox;
} }
export function fitBounds(map, feature) { export function findFeature(featureCollection, value, property = 'id') {
if (map) {
map.fitBounds(getBounds(feature.geometry), { padding: 100 });
}
}
export function findFeature(featureCollection, id) {
return featureCollection.features.find( return featureCollection.features.find(
(feature) => feature.properties.id === id (feature) => feature.properties[property] === value
); );
} }
@ -48,19 +41,10 @@ export function filterFeatureCollectionByGeometryType(featureCollection, type) {
}; };
} }
export function noop() {}
export function generateId() { export function generateId() {
return Math.random().toString(20).substr(2, 6); return Math.random().toString(20).substr(2, 6);
} }
export function useEvent(eventName, callback) {
return useEffect(() => {
addEventListener(eventName, callback);
return () => removeEventListener(eventName, callback);
}, [eventName, callback]);
}
export function getCenter(geometry, lngLat) { export function getCenter(geometry, lngLat) {
const bbox = new LngLatBounds(); const bbox = new LngLatBounds();
@ -76,3 +60,13 @@ export function getCenter(geometry, lngLat) {
return bbox.getCenter(); 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;
}