Improuve mapbox utilis and shared components
This commit is contained in:
parent
3b85ade440
commit
19440afebf
14 changed files with 168 additions and 136 deletions
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
69
app/javascript/components/shared/mapbox/MapStyleControl.jsx
Normal file
69
app/javascript/components/shared/mapbox/MapStyleControl.jsx
Normal 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;
|
|
@ -1,3 +0,0 @@
|
||||||
import ReactMapboxGl from 'react-mapbox-gl';
|
|
||||||
|
|
||||||
export default ReactMapboxGl({});
|
|
|
@ -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;
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
@ -0,0 +1,8 @@
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
id: 'ign',
|
||||||
|
source: 'plan-ign',
|
||||||
|
type: 'raster',
|
||||||
|
paint: { 'raster-resampling': 'linear' }
|
||||||
|
}
|
||||||
|
];
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue