Skip to content

Commit 2ecc36f

Browse files
authored
Merge pull request #31 from M3nin0/issue/geometries-mode
form: enabling support for multiple geometry selection mode
2 parents 0d56e49 + dec517a commit 2ecc36f

File tree

10 files changed

+200
-54
lines changed

10 files changed

+200
-54
lines changed

src/lib/components/form/fields/GeometryField/GeometryField.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import { InteractiveMap } from './InteractiveMap';
4848
* @param {Function} onDataLoad Function called when the data is loaded. This function can be used to present
4949
* a message (e.g., toast) to the user.
5050
* @param {Object} interactiveMapConfig Configuration object for the `Interactive Map` component.
51+
* @param {Boolean} uniqueLayer Enable/Disable users to draw multiple geometries in the map.
5152
* @returns {JSX.Element}
5253
*/
5354
export const GeometryField = ({
@@ -60,14 +61,15 @@ export const GeometryField = ({
6061
onDataClean,
6162
onDataLoad,
6263
interactiveMapConfig,
64+
uniqueLayer,
6365
}) => {
6466
// States
6567
const [interactiveMapInitialized, setInteractiveMapInitialized] =
6668
useState(false);
6769
const [activatedBreadcrumb, setActivatedBreadcrumb] = useState('menu');
6870

6971
// Local store
70-
const geometryStore = new GeometryStore();
72+
const geometryStore = new GeometryStore(null, uniqueLayer);
7173

7274
// Handlers
7375
const changeBreadcrumb = (breadcrumbName) =>

src/lib/components/form/fields/GeometryField/GeometryField.stories.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,29 @@ const Template = (args) => (
4949
/**
5050
* Component stories
5151
*/
52-
export const Basic = Template.bind({});
53-
Basic.args = {
52+
export const UniqueLayer = Template.bind({});
53+
UniqueLayer.args = {
54+
uniqueLayer: true,
55+
fieldPath: 'geometry',
56+
label: 'Geometry',
57+
labelIcon: 'location arrow',
58+
onDataClean: () => {
59+
setTimeout((_) => {
60+
toast({
61+
title: 'Data cleaned',
62+
description: 'Data has been cleaned',
63+
type: 'info',
64+
icon: 'info',
65+
time: 5000,
66+
animation: 'fade right',
67+
});
68+
}, 100);
69+
},
70+
};
71+
72+
export const MultipleLayers = Template.bind({});
73+
MultipleLayers.args = {
74+
uniqueLayer: false,
5475
fieldPath: 'geometry',
5576
label: 'Geometry',
5677
labelIcon: 'location arrow',

src/lib/components/form/fields/GeometryField/GeometryStore.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@ export class GeometryStore {
2222
/**
2323
* @constructor
2424
* @param {Object} formikProps Formik Bag object.
25+
* @param {Boolean} uniqueLayer Enable/Disable users to draw multiple geometries in the map.
2526
*/
26-
constructor(formikProps = null) {
27+
constructor(formikProps = null, uniqueLayer = false) {
2728
// definitions
2829
this.formikProps = null;
2930
this.fieldPath = null;
31+
this.uniqueLayer = uniqueLayer;
3032

3133
this.indexKey = 0;
3234
this.geometryIndex = {};
@@ -172,6 +174,10 @@ export class GeometryStore {
172174
addLayer(layer) {
173175
const layerKey = this._generateKey();
174176

177+
if (this.uniqueLayer) {
178+
this.geometryIndex = {};
179+
}
180+
175181
layer._store_identifier = layerKey;
176182
this.geometryIndex[layerKey] = layer;
177183

src/lib/components/form/fields/GeometryField/InteractiveMap/GeometryEditor.js

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ import PropTypes from 'prop-types';
1111

1212
import { MapEventHandler } from './MapEventHandler';
1313

14+
import { LayerLoader } from './LayerLoader';
15+
1416
import { useDrawEvents } from '../../../../../base';
15-
import { GeometryEditorControl, LayerLoader } from '../../../../layers';
17+
import { GeometryEditorControl } from '../../../../layers';
1618

1719
/**
1820
* Geometry editor component.
@@ -25,20 +27,41 @@ import { GeometryEditorControl, LayerLoader } from '../../../../layers';
2527
*/
2628
export const GeometryEditor = ({ geometryStore, geometryEditorConfig }) => {
2729
// States
30+
const [renderFlag, setRenderFlag] = useState(null);
2831
const [controlRendered, setControlRendered] = useState(false);
2932

33+
// Configurations
34+
const uniqueLayer = geometryStore.uniqueLayer;
35+
36+
// Auxiliary functions
37+
const renderFlagGenerator = (id) => {
38+
uniqueLayer ? setRenderFlag(id) : null;
39+
};
40+
3041
// Handlers
31-
const mapEventHandler = new MapEventHandler(geometryStore);
42+
const mapEventHandler = new MapEventHandler(
43+
geometryStore,
44+
renderFlagGenerator
45+
);
3246

3347
// Hooks
3448
useDrawEvents(mapEventHandler, () => setControlRendered(true));
3549

50+
// Checking if the current store is already populated.
51+
if (!geometryStore.isEmpty() && renderFlag === null) {
52+
setRenderFlag(geometryStore.indexKey);
53+
}
54+
3655
return (
3756
<>
3857
{controlRendered ? (
3958
<>
4059
<GeometryEditorControl {...geometryEditorConfig} />
41-
<LayerLoader layers={geometryStore.getLayers()} />
60+
<LayerLoader
61+
renderFlag={renderFlag}
62+
layers={geometryStore.getLayers()}
63+
enableMultipleLayers={!uniqueLayer}
64+
/>
4265
</>
4366
) : null}
4467
</>
@@ -47,7 +70,10 @@ export const GeometryEditor = ({ geometryStore, geometryEditorConfig }) => {
4770

4871
GeometryEditor.propTypes = {
4972
geometryStore: PropTypes.object.isRequired,
50-
geometryEditorConfig: PropTypes.object,
73+
geometryEditorConfig: PropTypes.shape({
74+
toolbarConfig: PropTypes.object.isRequired,
75+
uniqueLayer: PropTypes.bool.isRequired,
76+
}),
5177
};
5278

5379
GeometryEditor.defaultProps = {

src/lib/components/form/fields/GeometryField/InteractiveMap/InteractiveMap.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,16 @@ const InteractiveMapComponent = ({ geometryStore, mapConfig }) => {
4040
* rendering system makes all the content modification.
4141
*
4242
*/
43-
export const InteractiveMap = React.memo(
44-
InteractiveMapComponent,
45-
(props) => true // avoiding re-render
46-
);
43+
export const InteractiveMap = React.memo(InteractiveMapComponent, (props) => {
44+
// We need to define if the map must be rerender (in react) or not.
45+
// In this case, we are defining if rerender is required based on
46+
// the mode selected by the user:
47+
// - uniqueLayer (true): Unique will be presented in the map. In this case,
48+
// we need rerender to sync the map and the formik store;
49+
// - uniqueLayer (false): All drawn layers will be presented, and we do not
50+
// need to rerender.
51+
return !props.geometryStore.uniqueLayer;
52+
});
4753

4854
InteractiveMap.propTypes = {
4955
geometryStore: PropTypes.object.isRequired,
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* This file is part of GEO-Metadata-Previewer.
3+
* Copyright (C) 2022 GEO Secretariat.
4+
*
5+
* GEO-Metadata-Previewer is free software; you can redistribute it and/or modify it
6+
* under the terms of the MIT License; see LICENSE file for more details.
7+
*/
8+
9+
import React, { useEffect, useRef } from 'react';
10+
import PropTypes from 'prop-types';
11+
12+
import _last from 'lodash/last';
13+
14+
import { useLeafletContext } from '@react-leaflet/core';
15+
16+
/**
17+
* Component to load multiple layers on a Leaflet Container (`Layer` or `Map` instance).
18+
* @constructor
19+
*
20+
* @param {Array} layers Array of `Leaflet.Layer` to be added in the `Container`.
21+
* @param {Number} renderFlag Flag to mark the current render step.
22+
* @param {Boolean} enableMultipleLayers Flag to enable/disable the multiple layer usage.
23+
*
24+
* @returns {JSX.Element}
25+
*/
26+
export const LayerLoader = ({ layers, renderFlag, enableMultipleLayers }) => {
27+
const context = useLeafletContext();
28+
const renderFlagRef = useRef(null);
29+
30+
// ToDo: Review if the "multi effect" approach is a good solution.
31+
if (enableMultipleLayers) {
32+
useEffect(() => {
33+
const container = context.map;
34+
35+
layers.forEach((layer) => layer.addTo(container));
36+
37+
return () => {
38+
layers.forEach((layer) => container.removeLayer(layer));
39+
};
40+
}, []);
41+
} else {
42+
useEffect(() => {
43+
const container = context.map;
44+
45+
if (renderFlagRef.current === renderFlag) {
46+
return;
47+
}
48+
49+
// Removing all drawn layers in the map
50+
const drawedLayers = container.pm.getGeomanDrawLayers();
51+
52+
drawedLayers.forEach((layer) => {
53+
container.removeLayer(layer);
54+
});
55+
56+
// Selecting one layer to draw in the map
57+
const layerToDraw = _last(layers);
58+
59+
if (layerToDraw) {
60+
layerToDraw.addTo(container);
61+
62+
renderFlagRef.current = renderFlag;
63+
64+
return () => {
65+
container.removeLayer(layerToDraw);
66+
};
67+
}
68+
}, [renderFlag]);
69+
}
70+
71+
return null;
72+
};
73+
74+
LayerLoader.propTypes = {
75+
layers: PropTypes.array.isRequired,
76+
renderFlag: PropTypes.number,
77+
enableMultipleLayers: PropTypes.bool.isRequired,
78+
};
79+
80+
LayerLoader.defaultProps = {};

src/lib/components/layers/base/LayerLoader.test.js renamed to src/lib/components/form/fields/GeometryField/InteractiveMap/LayerLoader.test.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,21 @@ import { renderWithMapContainer } from '@tests/setup';
1313

1414
describe('LayerLoader tests', () => {
1515
describe('Render tests', () => {
16-
it('should render without crashing', () => {
16+
it('should render without crashing with enableMultipleLayer disabled', () => {
1717
renderWithMapContainer(
1818
<>
19-
<LayerLoader layers={[]} />
19+
<LayerLoader
20+
layers={[]}
21+
renderFlag={1}
22+
enableMultipleLayers={false}
23+
/>
24+
</>
25+
);
26+
});
27+
it('should render without crashing enableMultipleLayer enabled', () => {
28+
renderWithMapContainer(
29+
<>
30+
<LayerLoader layers={[]} renderFlag={1} enableMultipleLayers={true} />
2031
</>
2132
);
2233
});

src/lib/components/form/fields/GeometryField/InteractiveMap/MapEventHandler.js

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,35 @@ export class MapEventHandler {
1313
/**
1414
* @constructor
1515
* @param {Object} geometryStore Geometry Store object.
16+
* @param {Function} renderFlagGenerator Function to generate a flag identifier
17+
* for the current render step.
1618
*/
17-
constructor(geometryStore) {
19+
constructor(geometryStore, renderFlagGenerator) {
1820
this.geometryStore = geometryStore;
21+
this.uniqueLayer = geometryStore.uniqueLayer;
22+
23+
this.renderFlagGenerator = renderFlagGenerator;
24+
}
25+
26+
/**
27+
* Apply the rules to enable the support of unique layer in the map.
28+
* @private
29+
*/
30+
_checkForUniqueLayer() {
31+
if (this.uniqueLayer) {
32+
this.geometryStore.getLayers().forEach((layer) => {
33+
this._removeLayerFromIndex(layer);
34+
});
35+
}
1936
}
2037

38+
/**
39+
* Make a copy of the layer identifier (`_store_identifier`) from the `oldLayer`
40+
* to the `newLayer`.
41+
* @param {Object} oldLayer `Leaflet.Layer` from where the identifier will be copied.
42+
* @param {Object} newLayer `Leaflet.Layer` where the identifier will be set.
43+
* @private
44+
*/
2145
_copyLayerIdentifier(oldLayer, newLayer) {
2246
newLayer._store_identifier = oldLayer._store_identifier;
2347
}
@@ -30,6 +54,8 @@ export class MapEventHandler {
3054
*/
3155
_addLayerToIndex(layer) {
3256
this.geometryStore.addLayer(layer);
57+
58+
this.renderFlagGenerator(this.geometryStore.indexKey);
3359
}
3460

3561
/**
@@ -41,6 +67,8 @@ export class MapEventHandler {
4167
*/
4268
_updateLayerOnIndex(layer) {
4369
this.geometryStore.updateLayer(layer);
70+
71+
this.renderFlagGenerator(this.geometryStore.indexKey);
4472
}
4573

4674
/**
@@ -51,13 +79,16 @@ export class MapEventHandler {
5179
*/
5280
_removeLayerFromIndex(layer) {
5381
this.geometryStore.removeLayer(layer);
82+
83+
this.renderFlagGenerator(this.geometryStore.indexKey);
5484
}
5585

5686
/**
5787
* onCreate event handler.
5888
* @param {Object} e event object;
5989
*/
6090
onCreate(e) {
91+
this._checkForUniqueLayer();
6192
this._addLayerToIndex(e.layer);
6293
}
6394

@@ -72,6 +103,7 @@ export class MapEventHandler {
72103
// updating the index
73104
this._copyLayerIdentifier(oldLayer, newLayer);
74105

106+
this._checkForUniqueLayer();
75107
this._updateLayerOnIndex(newLayer);
76108
}
77109

@@ -88,6 +120,7 @@ export class MapEventHandler {
88120
* @param {Object} e event object;
89121
*/
90122
onCut(e) {
123+
this._checkForUniqueLayer();
91124
this._updateLayerOnIndex(e.layer);
92125
}
93126
}

0 commit comments

Comments
 (0)