diff --git a/src/components/FormInput/FormInput.js b/src/components/FormInput/FormInput.js index 33a50244..6dd1a21b 100644 --- a/src/components/FormInput/FormInput.js +++ b/src/components/FormInput/FormInput.js @@ -124,7 +124,7 @@ const FormInput = (props) => { FormInput.propTypes = { className: PropTypes.string, - label: PropTypes.string, + label: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), type: PropTypes.string, id: PropTypes.string, name: PropTypes.string, diff --git a/src/components/InputButton/InputButton.js b/src/components/InputButton/InputButton.js index c2802671..23db176b 100644 --- a/src/components/InputButton/InputButton.js +++ b/src/components/InputButton/InputButton.js @@ -83,7 +83,7 @@ InputButton.propTypes = { icon: PropTypes.oneOfType([PropTypes.element, PropTypes.array]), forwardedRef: PropTypes.object, id: PropTypes.string, - label: PropTypes.string, + label: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), value: PropTypes.string, name: PropTypes.string, type: PropTypes.string, diff --git a/src/components/MapPreview/MapPreview.js b/src/components/MapPreview/MapPreview.js index eae1b0cf..804ecc50 100644 --- a/src/components/MapPreview/MapPreview.js +++ b/src/components/MapPreview/MapPreview.js @@ -17,7 +17,7 @@ import { import Map from '../Map'; import Marker from '../MapMarker'; -import MapDraw from '../MapDraw'; +import MapPreviewDraw from '../MapPreviewDraw'; const logger = new Logger('FormInput', { isBrowser: true @@ -34,10 +34,18 @@ const MapPreview = ({ projection, fetchLayerData, fitGeoJson = true, - label = "Area of Interest", + label = 'Area of Interest', showGeometryType = true, + shapeOptions, mapRef, - useMapEffect + useMapEffect, + disableDraw = true, + disableEdit = false, + drawControlOptions, + onDrawCreated, + onDrawEdited, + featureRef = useRef(), + emptyMap = false, }) => { const layers = useLayers(availableLayers, fetchLayerData); @@ -137,12 +145,51 @@ const MapPreview = ({ useMapEffect }; + /** + * handleOnDraw + * @description Fires when a draw layer is created. Triggers callback if available. + * Also clears all any previous layers except the newly created one. + */ + function handleOnDraw (drawLayer) { + const drawnItems = featureRef.current?.leafletElement._layers; + if (Object.keys(drawnItems).length > 1) { + Object.keys(drawnItems).forEach((layerid, index) => { + if (index > 0) return; + const layer = drawnItems[layerid]; + featureRef.current.leafletElement.removeLayer(layer); + }); + } + const { leafletElement } = featureRef.current || {}; + if (typeof onDrawCreated === 'function') { + onDrawCreated(drawLayer, leafletElement); + } + } + + /** + * handleOnEditDraw + * @description Fires after editing an existing draw layer. Triggers callback if available + */ + function handleOnEditDraw (drawLayer) { + const { leafletElement } = featureRef.current || {}; + if (typeof onDrawEdited === 'function') { + onDrawEdited(drawLayer, leafletElement); + } + } + return (
- - {features.map((feature) => { + + {!emptyMap && features.map((feature) => { const { geometry, properties } = feature; const { @@ -201,7 +248,7 @@ const MapPreview = ({ return null; })} - +
{label}
@@ -265,7 +312,15 @@ MapPreview.propTypes = { label: PropTypes.string, showGeometryType: PropTypes.bool, mapRef: PropTypes.object, - useMapEffect: PropTypes.func + useMapEffect: PropTypes.func, + shapeOptions: PropTypes.object, + disableDraw: PropTypes.bool, + disableEdit: PropTypes.bool, + drawControlOptions: PropTypes.object, + onDrawCreated: PropTypes.func, + onDrawEdited: PropTypes.func, + featureRef: PropTypes.object, + emptyMap: PropTypes.bool }; export default MapPreview; diff --git a/src/components/MapPreview/stories/MapPreview.EmptyMapDraw.stories.js b/src/components/MapPreview/stories/MapPreview.EmptyMapDraw.stories.js new file mode 100644 index 00000000..067a380d --- /dev/null +++ b/src/components/MapPreview/stories/MapPreview.EmptyMapDraw.stories.js @@ -0,0 +1,51 @@ +import React, { useRef } from 'react'; +import { storiesOf } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; +import Story from '../../../../stories/helpers/Story'; + +import MapPreview from '..'; + +const STORY_COMPONENT = 'Map Preview'; +const STORY_NAME = 'Empty Map & Draw Controls'; + +const stories = storiesOf(`Components/${STORY_COMPONENT}`, module); + +stories.add(STORY_NAME, () => { + const featureRef = useRef(); + + function handleOnDraw (drawLayer, leafletElement) { + action(`${STORY_COMPONENT}::onDraw`)(drawLayer, leafletElement); + } + + function handleOnEdit (drawLayer, leafletElement) { + action(`${STORY_COMPONENT}::onEdit`)(drawLayer, leafletElement); + } + + return ( + + + + ); +}); diff --git a/src/components/MapPreviewDraw/MapPreviewDraw.js b/src/components/MapPreviewDraw/MapPreviewDraw.js new file mode 100644 index 00000000..f5a2218d --- /dev/null +++ b/src/components/MapPreviewDraw/MapPreviewDraw.js @@ -0,0 +1,147 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import 'leaflet-draw/dist/leaflet.draw.css'; +import { Popup } from 'react-leaflet'; +import { EditControl } from 'react-leaflet-draw'; + +import { useMapMarkerIcon } from '../../hooks'; + +import FeatureGroup from '../FeatureGroup'; + +const DEFAULT_CONTROL_OPTIONS = { + circle: false, + circlemarker: false, + marker: true, + polygon: true, + polyline: false, + rectangle: true +}; + +const SHAPE_CONTROLS = ['circle', 'polygon', 'polyline', 'rectangle']; + +const DEFAULT_SHAPE_OPTIONS = { + opacity: 1, + weight: 3 +}; + +const MapPreviewDraw = ({ + children, + forwardedRef, + onCreated, + onEdited, + disableDrawControls = false, + disableEditControls = false, + controlOptions, + shapeOptions, + PopupContent, + featureGroup, + featureRef, + ...rest +}) => { + const { icon } = useMapMarkerIcon(); + + const markerOptions = { + marker: { + icon + } + }; + + const drawOptions = { + ...DEFAULT_CONTROL_OPTIONS, + ...markerOptions, + ...controlOptions + }; + + // Loop through all of our configured options and determine the + // shape configuration for each if a valid shape + + Object.keys(drawOptions).forEach((key) => { + // Check if the option is turned off or if it's a valid shape + + if (!drawOptions[key] || !SHAPE_CONTROLS.includes(key)) return; + + // If it's set to true, we want to initialize the object for the shape + // to set our options on + + if (drawOptions[key] === true) { + drawOptions[key] = {}; + } + + drawOptions[key].shapeOptions = { + ...DEFAULT_SHAPE_OPTIONS, + ...drawOptions[key].shapeOptions, + ...(shapeOptions && shapeOptions.style) + }; + }); + + /** + * handleOnCreated + * @description Fires when a layer is created. Triggers callback if available. + * Clears all other layers except the newly created one. + */ + + function handleOnCreated ({ layer } = {}) { + if (typeof onCreated === 'function') { + onCreated(layer, forwardedRef); + } + } + + /** + * handleOnEdited + * @description Fires when a layer is edited. Triggers callback if available. + */ + + function handleOnEdited ({ target } = {}) { + if (typeof onCreated === 'function') { + onEdited(target, forwardedRef); + } + } + + return ( + + {children} + {!disableDrawControls && ( + <> + + {PopupContent && ( + + + + )} + + )} + + ); +}; + +MapPreviewDraw.propTypes = { + children: PropTypes.node, + forwardedRef: PropTypes.object, + onCreated: PropTypes.func, + onEdited: PropTypes.func, + disableDrawControls: PropTypes.bool, + disableEditControls: PropTypes.bool, + controlOptions: PropTypes.object, + shapeOptions: PropTypes.object, + featureGroup: PropTypes.object, + PopupContent: PropTypes.any, + featureRef: PropTypes.object +}; + +const MapPreviewDrawWithRefs = React.forwardRef(function mapDraw (props, ref) { + return ; +}); + +MapPreviewDrawWithRefs.displayName = 'MapDrawWithRefs'; + +export default MapPreviewDrawWithRefs; diff --git a/src/components/MapPreviewDraw/MapPreviewDraw.test.js b/src/components/MapPreviewDraw/MapPreviewDraw.test.js new file mode 100644 index 00000000..78a97d42 --- /dev/null +++ b/src/components/MapPreviewDraw/MapPreviewDraw.test.js @@ -0,0 +1,51 @@ +import React from 'react'; +import { shallow } from 'enzyme'; + +import MapPreviewDraw from '.'; + +describe('MapDraw', () => { + describe('Render', () => { + const testClass = 'test'; + const testText = 'Hi'; + + const mapdraw = shallow( + +
{testText}
+
+ ); + const mapdrawDive = mapdraw.dive(); + const editcontrol = mapdrawDive.find('ForwardRef(Leaflet(EditControl))'); + + it('should render with the position prop', () => { + expect(editcontrol.prop('position')).toEqual('bottomright'); + }); + + it('should render with the disabled shape features', () => { + expect(editcontrol.prop('draw').circle).toEqual(false); + expect(editcontrol.prop('draw').circlemarker).toEqual(false); + expect(editcontrol.prop('draw').polyline).toEqual(false); + }); + + it('should render children within the component', () => { + expect(mapdraw.find(`.${testClass}`).text()).toEqual(testText); + }); + }); + + describe('Events', () => { + const mapdraw = shallow(); + const mapdrawDive = mapdraw.dive(); + const editcontrol = mapdrawDive.find('ForwardRef(Leaflet(EditControl))'); + + let testCreated = 1; + + function handleOnCreated () { + testCreated++; + } + + editcontrol.prop('onCreated')(); + + it('should fire given onCreated event', () => { + expect(testCreated).toEqual(2); + }); + }); +}); diff --git a/src/components/MapPreviewDraw/index.js b/src/components/MapPreviewDraw/index.js new file mode 100644 index 00000000..e4762261 --- /dev/null +++ b/src/components/MapPreviewDraw/index.js @@ -0,0 +1 @@ +export { default } from './MapPreviewDraw';