From d46f8fe3d6b90c136dc5764363ab74da7d2aea14 Mon Sep 17 00:00:00 2001 From: Simon Seyock Date: Wed, 6 Sep 2023 14:37:25 +0200 Subject: [PATCH 1/5] feat: refactor code into `useDraw` hook --- src/Button/DrawButton/DrawButton.tsx | 195 ++++++--------------------- 1 file changed, 40 insertions(+), 155 deletions(-) diff --git a/src/Button/DrawButton/DrawButton.tsx b/src/Button/DrawButton/DrawButton.tsx index 6009038000..de0d36f794 100644 --- a/src/Button/DrawButton/DrawButton.tsx +++ b/src/Button/DrawButton/DrawButton.tsx @@ -1,45 +1,20 @@ -import useMap from '@terrestris/react-util/dist/Hooks/useMap/useMap'; -import { DigitizeUtil } from '@terrestris/react-util/dist/Util/DigitizeUtil'; -import { EventsKey } from 'ol/events'; -import * as OlEventConditions from 'ol/events/condition'; +import { useDraw, UseDrawProps } from '@terrestris/react-util/dist/Hooks/useDraw/useDraw'; import OlFeature from 'ol/Feature'; import OlGeometry from 'ol/geom/Geometry'; -import OlInteractionDraw, { createBox, DrawEvent as OlDrawEvent, Options as OlDrawOptions } from 'ol/interaction/Draw'; -import OlVectorLayer from 'ol/layer/Vector'; -import { unByKey } from 'ol/Observable'; -import OlVectorSource from 'ol/source/Vector'; -import { StyleLike as OlStyleLike } from 'ol/style/Style'; +import { + DrawEvent +} from 'ol/interaction/Draw'; import * as React from 'react'; -import { ReactNode, useEffect, useState } from 'react'; +import {ReactNode, useCallback, useState} from 'react'; import { CSS_PREFIX } from '../../constants'; import { FeatureLabelModal } from '../../FeatureLabelModal/FeatureLabelModal'; import ToggleButton, { ToggleButtonProps } from '../ToggleButton/ToggleButton'; -type DrawType = 'Point' | 'LineString' | 'Polygon' | 'Circle' | 'Rectangle' | 'Text'; +type ButtonDrawType = 'Point' | 'LineString' | 'Polygon' | 'Circle' | 'Rectangle' | 'Text'; interface OwnProps { - /** - * Whether the line, point, polygon, circle, rectangle or text shape should - * be drawn. - */ - drawType: DrawType; - /** - * Style object / style function for drawn feature. - */ - drawStyle?: OlStyleLike; - /** - * Listener function for the 'drawend' event of an ol.interaction.Draw. - * See https://openlayers.org/en/latest/apidoc/module-ol_interaction_Draw-DrawEvent.html - * for more information. - */ - onDrawEnd?: (event: OlDrawEvent) => void; - /** - * Listener function for the 'drawstart' event of an ol.interaction.Draw. - * See https://openlayers.org/en/latest/apidoc/module-ol_interaction_Draw-DrawEvent.html - * for more information. - */ - onDrawStart?: (event: OlDrawEvent) => void; + drawType: ButtonDrawType; /** * Callback function that will be called when the ok-button of the modal was clicked */ @@ -54,11 +29,6 @@ interface OwnProps { * If exceeded label will be divided into multiple lines. Optional. */ maxLabelLineLength?: number; - /** - * The vector layer which will be used for digitize features. - * The standard digitizeLayer can be retrieved via `DigitizeUtil.getDigitizeLayer(map)`. - */ - digitizeLayer?: OlVectorLayer>; /** * Title for modal used for input of labels for digitize features. */ @@ -71,19 +41,9 @@ interface OwnProps { * Text string for `Cancel` button of the modal. */ modalPromptCancelButtonText?: string; - /** - * Additional configuration object to apply to the ol.interaction.Draw. - * See https://openlayers.org/en/latest/apidoc/module-ol_interaction_Draw-Draw.html - * for more information - * - * Note: The keys source, type, geometryFunction, style and freehandCondition - * are handled internally and shouldn't be overwritten without any - * specific cause. - */ - drawInteractionConfig?: Omit; } -export type DrawButtonProps = OwnProps & Partial; +export type DrawButtonProps = OwnProps & Omit & Partial; /** * The className added to this component. @@ -110,108 +70,28 @@ const DrawButton: React.FC = ({ pressed, ...passThroughProps }) => { - - const [drawInteraction, setDrawInteraction] = useState(); - const [layer, setLayer] = useState> | null>(null); - /** * Currently drawn feature which should be represented as label or post-it. */ const [digitizeTextFeature, setDigitizeTextFeature] = useState | null>(null); - const map = useMap(); - - useEffect(() => { - if (!map) { - return; - } - - if (digitizeLayer) { - setLayer(digitizeLayer); - } else { - setLayer(DigitizeUtil.getDigitizeLayer(map)); - } - }, [map, digitizeLayer]); - - useEffect(() => { - if (!map || !layer) { - return undefined; - } - - let geometryFunction; - let type: 'Point' | 'Circle' | 'LineString' | 'Polygon'; - - if (drawType === 'Rectangle') { - geometryFunction = createBox(); - type = 'Circle'; - } else if (drawType === 'Text') { - type = 'Point'; - } else { - type = drawType; - } - - const newInteraction = new OlInteractionDraw({ - source: layer.getSource() || undefined, - type: type, - geometryFunction: geometryFunction, - style: drawStyle ?? DigitizeUtil.defaultDigitizeStyleFunction, - freehandCondition: OlEventConditions.never, - ...(drawInteractionConfig ?? {}) - }); - - newInteraction.set('name', `react-geo-draw-interaction-${drawType}`); - - newInteraction.setActive(false); - - map.addInteraction(newInteraction); - - setDrawInteraction(newInteraction); - - let key: EventsKey; - + const onDrawEndInternal = useCallback((evt: DrawEvent) => { if (drawType === 'Text') { - key = newInteraction.on('drawend', evt => { - evt.feature.set('isLabel', true); - setDigitizeTextFeature(evt.feature); - }); - } - - return () => { - unByKey(key); - map.removeInteraction(newInteraction); - }; - }, [drawType, layer, drawInteractionConfig, drawStyle, map]); - - useEffect(() => { - if (!drawInteraction) { - return undefined; + evt.feature.set('isLabel', true); + setDigitizeTextFeature(evt.feature); } - - const endKey = drawInteraction.on('drawend', (evt) => { - onDrawEnd?.(evt); - }); - - const startKey = drawInteraction.on('drawstart', (evt) => { - onDrawStart?.(evt); - }); - - return () => { - unByKey(startKey); - unByKey(endKey); - }; - }, [drawInteraction, onDrawStart, onDrawEnd]); - - useEffect(() => { - if (!drawInteraction) { - return; - } - - drawInteraction.setActive(!!pressed); - }, [drawInteraction, pressed]); - - if (!drawInteraction || !layer) { - return null; - } + onDrawEnd?.(evt); + }, [drawType, onDrawEnd]); + + useDraw({ + onDrawEnd: onDrawEndInternal, + digitizeLayer, + drawInteractionConfig, + drawStyle, + drawType: drawType === 'Text' ? 'Point' : drawType, + onDrawStart, + active: !!pressed + }); const finalClassName = className ? `${defaultClassName} ${className}` @@ -228,19 +108,21 @@ const DrawButton: React.FC = ({ const onModalLabelCancelInternal = () => { onModalLabelCancel?.(); - layer.getSource()?.removeFeature(digitizeTextFeature); + digitizeLayer?.getSource()?.removeFeature(digitizeTextFeature); setDigitizeTextFeature(null); }; - modal = ; + modal = ( + + ); } return ( @@ -250,8 +132,11 @@ const DrawButton: React.FC = ({ pressed={pressed} {...passThroughProps} /> - {modal} - ); + { + modal + } + + ); }; export default DrawButton; From a0f50af0b4135805bd4971975952a1bab766dad8 Mon Sep 17 00:00:00 2001 From: Simon Seyock Date: Wed, 6 Sep 2023 16:13:49 +0200 Subject: [PATCH 2/5] refactor: `CopyButton`, `DeleteButton`, `SelectFeaturesButton` use hooks now --- src/Button/CopyButton/CopyButton.tsx | 72 +++++----- src/Button/DeleteButton/DeleteButton.tsx | 71 ++++++---- src/Button/DrawButton/DrawButton.tsx | 2 +- .../SelectFeaturesButton.tsx | 132 ++---------------- 4 files changed, 97 insertions(+), 180 deletions(-) diff --git a/src/Button/CopyButton/CopyButton.tsx b/src/Button/CopyButton/CopyButton.tsx index 40a460b3ee..61503ae6b8 100644 --- a/src/Button/CopyButton/CopyButton.tsx +++ b/src/Button/CopyButton/CopyButton.tsx @@ -1,5 +1,10 @@ import AnimateUtil from '@terrestris/ol-util/dist/AnimateUtil/AnimateUtil'; import useMap from '@terrestris/react-util/dist/Hooks/useMap/useMap'; +import { usePropOrDefault } from '@terrestris/react-util/dist/Hooks/usePropOrDefault/usePropOrDefault'; +import { + useSelectFeatures, + UseSelectFeaturesProps +} from '@terrestris/react-util/dist/Hooks/useSelectFeatures/useSelectFeatures'; import { DigitizeUtil } from '@terrestris/react-util/dist/Util/DigitizeUtil'; import OlFeature from 'ol/Feature'; import { SelectEvent as OlSelectEvent } from 'ol/interaction/Select'; @@ -7,12 +12,12 @@ import OlVectorLayer from 'ol/layer/Vector'; import OlVectorSource from 'ol/source/Vector'; import OlStyle from 'ol/style/Style'; import React, { - useEffect, - useState + useCallback, + useMemo, } from 'react'; import { CSS_PREFIX } from '../../constants'; -import SelectFeaturesButton, { SelectFeaturesButtonProps } from '../SelectFeaturesButton/SelectFeaturesButton'; +import ToggleButton, {ToggleButtonProps} from '../ToggleButton/ToggleButton'; interface OwnProps { /** @@ -29,76 +34,77 @@ interface OwnProps { onFeatureCopy?: (event: OlSelectEvent) => void; } -export type CopyButtonProps = OwnProps & Omit; +export type CopyButtonProps = OwnProps & Omit & Partial; // The class name for the component. const defaultClassName = `${CSS_PREFIX}copybutton`; const CopyButton: React.FC = ({ className, - onFeatureCopy, digitizeLayer, + onFeatureCopy, + selectStyle, + selectInteractionConfig, + hitTolerance, + pressed, ...passThroughProps }) => { - const [layers, setLayers] = useState<[OlVectorLayer>]|null>(null); - const map = useMap(); - useEffect(() => { - if (!map) { - return; - } + const layer = usePropOrDefault( + digitizeLayer, + () => map ? DigitizeUtil.getDigitizeLayer(map) : undefined, + [map] + ); - if (digitizeLayer) { - setLayers([digitizeLayer]); - } else { - setLayers([DigitizeUtil.getDigitizeLayer(map)]); - } - }, [map, digitizeLayer]); + const layers = useMemo(() => layer ? [layer] : [], [layer]); - const onFeatureSelect = (event: OlSelectEvent) => { + const onFeatureSelect = useCallback((event: OlSelectEvent) => { onFeatureCopy?.(event); const feat = event.selected[0]; - if (!feat || !layers || !map) { + if (!feat || !layer || !map) { return; } const copy = feat.clone(); - layers[0].getSource()?.addFeature(copy); + layer.getSource()?.addFeature(copy); AnimateUtil.moveFeature( map, - layers[0], + layer, copy, 500, 50, - layers[0].getStyle() as OlStyle + layer.getStyle() as OlStyle ); - }; + }, [layer, onFeatureCopy, map]); + + useSelectFeatures({ + selectStyle, + selectInteractionConfig, + layers, + active: !!pressed, + hitTolerance, + onFeatureSelect, + clearAfterSelect: true + }); const finalClassName = className ? `${defaultClassName} ${className}` : defaultClassName; - if (!layers) { - return null; - } - return ( - ); - }; export default CopyButton; diff --git a/src/Button/DeleteButton/DeleteButton.tsx b/src/Button/DeleteButton/DeleteButton.tsx index c76e616348..73c1ddb99e 100644 --- a/src/Button/DeleteButton/DeleteButton.tsx +++ b/src/Button/DeleteButton/DeleteButton.tsx @@ -1,14 +1,19 @@ import useMap from '@terrestris/react-util/dist/Hooks/useMap/useMap'; +import { usePropOrDefault } from '@terrestris/react-util/dist/Hooks/usePropOrDefault/usePropOrDefault'; +import { + useSelectFeatures, + UseSelectFeaturesProps +} from '@terrestris/react-util/dist/Hooks/useSelectFeatures/useSelectFeatures'; import { DigitizeUtil } from '@terrestris/react-util/dist/Util/DigitizeUtil'; import OlFeature from 'ol/Feature'; import { SelectEvent as OlSelectEvent } from 'ol/interaction/Select'; import OlVectorLayer from 'ol/layer/Vector'; import OlVectorSource from 'ol/source/Vector'; import * as React from 'react'; -import { useEffect, useState } from 'react'; +import {useCallback, useMemo} from 'react'; import { CSS_PREFIX } from '../../constants'; -import SelectFeaturesButton, { SelectFeaturesButtonProps } from '../SelectFeaturesButton/SelectFeaturesButton'; +import ToggleButton, {ToggleButtonProps} from '../ToggleButton/ToggleButton'; interface OwnProps { /** @@ -25,8 +30,8 @@ interface OwnProps { onFeatureRemove?: (event: OlSelectEvent) => void; } -export type DeleteButtonProps = OwnProps & Omit; +export type DeleteButtonProps = OwnProps & Omit & Partial; /** * The className added to this component. @@ -37,45 +42,53 @@ export const DeleteButton: React.FC = ({ className, digitizeLayer, onFeatureRemove, + selectStyle, + selectInteractionConfig, + hitTolerance, + pressed, ...passThroughProps }) => { - const [layers, setLayers] = useState<[OlVectorLayer>]|null>(null); const map = useMap(); - useEffect(() => { - if (!map) { - return; - } - - if (digitizeLayer) { - setLayers([digitizeLayer]); - } else { - setLayers([DigitizeUtil.getDigitizeLayer(map)]); - } - }, [map, digitizeLayer]); + const layer = usePropOrDefault( + digitizeLayer, + () => map ? DigitizeUtil.getDigitizeLayer(map) : undefined, + [map] + ); - if (!layers) { - return null; - } + const layers = useMemo(() => layer ? [layer] : [], [layer]); - const onFeatureSelect = (event: OlSelectEvent) => { + const onFeatureSelect = useCallback((event: OlSelectEvent) => { + if (!layer) { + return; + } onFeatureRemove?.(event); const feat = event.selected[0]; - layers[0].getSource()?.removeFeature(feat); - }; + layer.getSource()?.removeFeature(feat); + }, [layer, onFeatureRemove]); + + useSelectFeatures({ + selectStyle, + selectInteractionConfig, + layers, + active: !!pressed, + hitTolerance, + onFeatureSelect, + clearAfterSelect: true + }); const finalClassName = className ? `${defaultClassName} ${className}` : defaultClassName; - return ; + return ( + + ); }; export default DeleteButton; diff --git a/src/Button/DrawButton/DrawButton.tsx b/src/Button/DrawButton/DrawButton.tsx index de0d36f794..5f5f3344d9 100644 --- a/src/Button/DrawButton/DrawButton.tsx +++ b/src/Button/DrawButton/DrawButton.tsx @@ -43,7 +43,7 @@ interface OwnProps { modalPromptCancelButtonText?: string; } -export type DrawButtonProps = OwnProps & Omit & Partial; +export type DrawButtonProps = OwnProps & Omit & Partial; /** * The className added to this component. diff --git a/src/Button/SelectFeaturesButton/SelectFeaturesButton.tsx b/src/Button/SelectFeaturesButton/SelectFeaturesButton.tsx index 8e0ec278af..3faea153b6 100644 --- a/src/Button/SelectFeaturesButton/SelectFeaturesButton.tsx +++ b/src/Button/SelectFeaturesButton/SelectFeaturesButton.tsx @@ -1,63 +1,20 @@ -import useMap from '@terrestris/react-util/dist/Hooks/useMap/useMap'; -import { DigitizeUtil } from '@terrestris/react-util/dist/Util/DigitizeUtil'; -import OlCollection from 'ol/Collection'; -import * as OlEventConditions from 'ol/events/condition'; -import OlFeature from 'ol/Feature'; -import OlGeometry from 'ol/geom/Geometry'; -import OlInteractionSelect, { Options as OlSelectOptions, SelectEvent as OlSelectEvent } from 'ol/interaction/Select'; -import OlVectorLayer from 'ol/layer/Vector'; -import { unByKey } from 'ol/Observable'; -import OlVectorSource from 'ol/source/Vector'; -import { StyleLike as OlStyleLike } from 'ol/style/Style'; +import { + useSelectFeatures, + UseSelectFeaturesProps +} from '@terrestris/react-util/dist/Hooks/useSelectFeatures/useSelectFeatures'; import * as React from 'react'; -import { useEffect, useState } from 'react'; import { CSS_PREFIX } from '../../constants'; import ToggleButton, { ToggleButtonProps } from '../ToggleButton/ToggleButton'; interface OwnProps { - /** - * Select style of the selected features. - */ - selectStyle?: OlStyleLike; - /** - * Additional configuration object to apply to the ol.interaction.Select. - * See https://openlayers.org/en/latest/apidoc/module-ol_interaction_Select-Select.html - * for more information - * - * Note: The keys condition, hitTolerance and style are handled internally - * and shouldn't be overwritten without any specific cause. - */ - selectInteractionConfig?: Omit; /** * The className which should be added. */ className?: string; - /** - * Listener function for the 'select' event of the ol.interaction.Select - * See https://openlayers.org/en/latest/apidoc/module-ol_interaction_Select.html - * for more information. - */ - onFeatureSelect?: (event: OlSelectEvent) => void; - /** - * Array of layers the SelectFeaturesButton should operate on. - */ - layers: OlVectorLayer>[]; - /** - * Hit tolerance of the select action. Default: 5 - */ - hitTolerance?: number; - /** - * Clear the feature collection of the interaction after select. Default: false - */ - clearAfterSelect?: boolean; - /** - * A feature collection to use. - */ - featuresCollection?: OlCollection>; } -export type SelectFeaturesButtonProps = OwnProps & Partial; +export type SelectFeaturesButtonProps = OwnProps & Omit & Partial; /** * The className added to this component. @@ -76,76 +33,17 @@ const SelectFeaturesButton: React.FC = ({ pressed, ...passThroughProps }) => { - const [selectInteraction, setSelectInteraction] = useState(); - const [features, setFeatures] = useState>|null>(null); - - const map = useMap(); - - useEffect(() => { - if (featuresCollection) { - setFeatures(featuresCollection); - } else { - setFeatures(new OlCollection()); - } - }, [featuresCollection]); - - useEffect(() => { - if (!map || !features) { - return undefined; - } - - const newInteraction = new OlInteractionSelect({ - condition: OlEventConditions.singleClick, - features, - hitTolerance: hitTolerance, - style: selectStyle ?? DigitizeUtil.DEFAULT_SELECT_STYLE, - layers: layers, - ...(selectInteractionConfig ?? {}) - }); - - newInteraction.set('name', 'react-geo-select-interaction'); - newInteraction.setActive(false); - map.addInteraction(newInteraction); - - setSelectInteraction(newInteraction); - - return () => { - map.removeInteraction(newInteraction); - }; - }, [features, layers, selectStyle, selectInteractionConfig, map, hitTolerance]); - - useEffect(() => { - if (!selectInteraction || !features) { - return undefined; - } - - const key = selectInteraction.on('select', e => { - if (clearAfterSelect) { - features.clear(); - } - onFeatureSelect?.(e); - }); - - return () => { - unByKey(key); - }; - }, [selectInteraction, features, onFeatureSelect, clearAfterSelect]); - - useEffect(() => { - if (!selectInteraction) { - return; - } - - selectInteraction.setActive(!!pressed); - - if (!pressed) { - selectInteraction.getFeatures().clear(); - } - }, [selectInteraction, pressed]); - if (!selectInteraction) { - return null; - } + useSelectFeatures({ + active: !!pressed, + onFeatureSelect, + clearAfterSelect, + featuresCollection, + hitTolerance, + layers, + selectStyle, + selectInteractionConfig + }); const finalClassName = className ? `${defaultClassName} ${className}` From 5664810aa19e6d06c491bcd46fcb9cf2b46679eb Mon Sep 17 00:00:00 2001 From: Simon Seyock Date: Wed, 6 Sep 2023 19:45:06 +0200 Subject: [PATCH 3/5] refactor: modify button uses `useModify` hook --- src/Button/ModifyButton/ModifyButton.tsx | 232 ++++------------------- 1 file changed, 40 insertions(+), 192 deletions(-) diff --git a/src/Button/ModifyButton/ModifyButton.tsx b/src/Button/ModifyButton/ModifyButton.tsx index 86d0b61257..1dabc4833b 100644 --- a/src/Button/ModifyButton/ModifyButton.tsx +++ b/src/Button/ModifyButton/ModifyButton.tsx @@ -1,76 +1,19 @@ -import useMap from '@terrestris/react-util/dist/Hooks/useMap/useMap'; -import { DigitizeUtil } from '@terrestris/react-util/dist/Util/DigitizeUtil'; -import OlCollection from 'ol/Collection'; -import { singleClick } from 'ol/events/condition'; +import { useModify, UseModifyProps } from '@terrestris/react-util/dist/Hooks/useModify/useModify'; import OlFeature from 'ol/Feature'; import OlGeometry from 'ol/geom/Geometry'; -import Modify, { ModifyEvent, Options as ModifyOptions } from 'ol/interaction/Modify'; import { SelectEvent as OlSelectEvent } from 'ol/interaction/Select'; -import Translate, { Options as TranslateOptions, TranslateEvent } from 'ol/interaction/Translate'; -import OlVectorLayer from 'ol/layer/Vector'; -import { unByKey } from 'ol/Observable'; -import OlVectorSource from 'ol/source/Vector'; import * as React from 'react'; -import { ReactNode, useEffect, useState } from 'react'; +import {ReactNode, useCallback, useState} from 'react'; import { CSS_PREFIX } from '../../constants'; import { FeatureLabelModal } from '../../FeatureLabelModal/FeatureLabelModal'; -import SelectFeaturesButton, { SelectFeaturesButtonProps } from '../SelectFeaturesButton/SelectFeaturesButton'; +import ToggleButton, {ToggleButtonProps} from '../ToggleButton/ToggleButton'; interface OwnProps { /** - * Additional configuration object to apply to the ol.interaction.Modify. - * See https://openlayers.org/en/latest/apidoc/module-ol_interaction_Modify-Modify.html - * for more information - * - * Note: The keys features, deleteCondition and style are handled internally - * and shouldn't be overwritten without any specific cause. + * The className which should be added. */ - modifyInteractionConfig?: Omit; - /** - * Additional configuration object to apply to the ol.interaction.Translate. - * See https://openlayers.org/en/latest/apidoc/module-ol_interaction_Translate-Translate.html - * for more information - * - * Note: The key feature is handled internally and shouldn't be overwritten - * without any specific cause. - */ - translateInteractionConfig?: Omit; - /** - * The vector layer which will be used for digitize features. - * The standard digitizeLayer can be retrieved via `DigitizeUtil.getDigitizeLayer(map)`. - */ - digitizeLayer?: OlVectorLayer>; - /** - * Listener function for the 'modifystart' event of an ol.interaction.Modify. - * See https://openlayers.org/en/latest/apidoc/module-ol_interaction_Modify-ModifyEvent.html - * for more information. - */ - onModifyStart?: (event: ModifyEvent) => void; - /** - * Listener function for the 'modifyend' event of an ol.interaction.Modify. - * See https://openlayers.org/en/latest/apidoc/module-ol_interaction_Modify-ModifyEvent.html - * for more information. - */ - onModifyEnd?: (event: ModifyEvent) => void; - /** - * Listener function for the 'translatestart' event of an ol.interaction.Translate. - * See https://openlayers.org/en/latest/apidoc/module-ol_interaction_Translate-TranslateEvent.html - * for more information. - */ - onTranslateStart?: (event: TranslateEvent) => void; - /** - * Listener function for the 'translateend' event of an ol.interaction.Translate. - * See https://openlayers.org/en/latest/apidoc/module-ol_interaction_Translate-TranslateEvent.html - * for more information. - */ - onTranslateEnd?: (event: TranslateEvent) => void; - /** - * Listener function for the 'translating' event of an ol.interaction.Translate. - * See https://openlayers.org/en/latest/apidoc/module-ol_interaction_Translate-TranslateEvent.html - * for more information. - */ - onTranslating?: (event: TranslateEvent) => void; + className?: string; /** * Callback function that will be called when the ok-button of the modal was clicked */ @@ -103,8 +46,7 @@ interface OwnProps { editLabel?: boolean; } -export type ModifyButtonProps = OwnProps & Omit; +export type ModifyButtonProps = OwnProps & Omit & Partial; /** * The className added to this component. @@ -113,6 +55,7 @@ const defaultClassName = `${CSS_PREFIX}modifybutton`; export const ModifyButton: React.FC = ({ className, + hitTolerance = 5, onModifyStart, onModifyEnd, onTranslateStart, @@ -120,6 +63,7 @@ export const ModifyButton: React.FC = ({ onTranslating, digitizeLayer, selectStyle, + selectInteractionConfig, modifyInteractionConfig, translateInteractionConfig, onModalLabelOk, @@ -132,136 +76,36 @@ export const ModifyButton: React.FC = ({ pressed, ...passThroughProps }) => { - const [layers, setLayers] = useState<[OlVectorLayer>]|null>(null); - const [modifyInteraction, setModifyInteraction] = useState(null); - const [translateInteraction, setTranslateInteraction] = useState(null); - const [features, setFeatures] = useState>|null>(null); - - const map = useMap(); - const [editLabelFeature, setEditLabelFeature] = useState|null>(null); - useEffect(() => { - if (!map) { - return; - } - - setLayers([digitizeLayer ?? DigitizeUtil.getDigitizeLayer(map)]); - setFeatures(new OlCollection()); - }, [map, digitizeLayer]); - - useEffect(() => { - if (!map || !features) { - return undefined; - } - - const newTranslateInteraction = new Translate({ - features, - ...translateInteractionConfig - }); - newTranslateInteraction.set('name', 'react-geo-translate-interaction'); - newTranslateInteraction.setActive(false); - - map.addInteraction(newTranslateInteraction); - setTranslateInteraction(newTranslateInteraction); - - const newModifyInteraction = new Modify({ - features, - deleteCondition: singleClick, - style: selectStyle ?? DigitizeUtil.DEFAULT_SELECT_STYLE, - ...modifyInteractionConfig - }); - newModifyInteraction.set('name', 'react-geo-modify-interaction'); - newModifyInteraction.setActive(false); - - map.addInteraction(newModifyInteraction); - setModifyInteraction(newModifyInteraction); - - return () => { - map.removeInteraction(newModifyInteraction); - map.removeInteraction(newTranslateInteraction); - }; - }, [selectStyle, modifyInteractionConfig, translateInteractionConfig, features, map]); - - useEffect(() => { - if (!modifyInteraction) { - return undefined; - } - - const startKey = modifyInteraction.on('modifystart', e => { - onModifyStart?.(e); - }); - - const endKey = modifyInteraction.on('modifyend', e => { - onModifyEnd?.(e); - }); - - return () => { - unByKey(startKey); - unByKey(endKey); - }; - }, [modifyInteraction, onModifyStart, onModifyEnd]); - - useEffect(() => { - if (!translateInteraction) { - return undefined; - } - - const startKey = translateInteraction.on('translatestart', e => { - onTranslateStart?.(e); - }); - - const endKey = translateInteraction.on('translateend', e => { - onTranslateEnd?.(e); - }); - - const translatingKey = translateInteraction.on('translating', e => { - onTranslating?.(e); - }); - - return () => { - unByKey(startKey); - unByKey(endKey); - unByKey(translatingKey); - }; - }, [translateInteraction, onTranslateStart, onTranslateEnd, onTranslating]); - - useEffect(() => { - if (!modifyInteraction || !translateInteraction) { - return; - } - - modifyInteraction.setActive(!!pressed); - translateInteraction.setActive(!!pressed); - }, [modifyInteraction, translateInteraction, pressed]); - - if (!layers || !features || !modifyInteraction || !translateInteraction) { - return null; - } - - const onFeatureSelect = (event: OlSelectEvent) => { + const onFeatureSelect = useCallback((event: OlSelectEvent) => { if (editLabel) { const labeled = event.selected.find(f => f.get('isLabel')); setEditLabelFeature(labeled || null); } - }; + }, [editLabel]); + + useModify({ + selectStyle, + selectInteractionConfig, + digitizeLayer, + onModifyStart, + onModifyEnd, + onTranslateStart, + onTranslateEnd, + onTranslating, + active: !!pressed, + modifyInteractionConfig, + translateInteractionConfig, + onFeatureSelect, + hitTolerance + }); const finalClassName = className ? `${defaultClassName} ${className}` : defaultClassName; - const button = ( - - ); + const btnWrapperClass = `${CSS_PREFIX}digitize-button-wrapper`; let modal: ReactNode = null; if (editLabelFeature) { @@ -277,25 +121,29 @@ export const ModifyButton: React.FC = ({ modal = ( ); } - if (!editLabel) { - return button; - } else { - return <> - {button} - {modal} - ; - } + return ( + + + { + modal + } + + ); }; export default ModifyButton; From f6d998c53d4ce1db2d1387f6095637aeca340050 Mon Sep 17 00:00:00 2001 From: Simon Seyock <8100558+simonseyock@users.noreply.github.com> Date: Tue, 12 Sep 2023 14:04:49 +0200 Subject: [PATCH 4/5] fix: omit correct values from `UseModifyProps` --- src/Button/ModifyButton/ModifyButton.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Button/ModifyButton/ModifyButton.tsx b/src/Button/ModifyButton/ModifyButton.tsx index 1dabc4833b..ff0263ec36 100644 --- a/src/Button/ModifyButton/ModifyButton.tsx +++ b/src/Button/ModifyButton/ModifyButton.tsx @@ -46,7 +46,8 @@ interface OwnProps { editLabel?: boolean; } -export type ModifyButtonProps = OwnProps & Omit & Partial; +export type ModifyButtonProps = OwnProps & Omit & + Partial; /** * The className added to this component. From 421bc13f39a57d6d987cf55102b25fa0b710af76 Mon Sep 17 00:00:00 2001 From: Simon Seyock Date: Tue, 19 Sep 2023 15:52:37 +0200 Subject: [PATCH 5/5] fix(DrawButton): remove text feature from layer on cancel --- src/Button/DrawButton/DrawButton.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Button/DrawButton/DrawButton.tsx b/src/Button/DrawButton/DrawButton.tsx index 5f5f3344d9..e139830493 100644 --- a/src/Button/DrawButton/DrawButton.tsx +++ b/src/Button/DrawButton/DrawButton.tsx @@ -1,4 +1,7 @@ import { useDraw, UseDrawProps } from '@terrestris/react-util/dist/Hooks/useDraw/useDraw'; +import useMap from '@terrestris/react-util/dist/Hooks/useMap/useMap'; +import {usePropOrDefault} from '@terrestris/react-util/dist/Hooks/usePropOrDefault/usePropOrDefault'; +import {DigitizeUtil} from '@terrestris/react-util/dist/Util/DigitizeUtil'; import OlFeature from 'ol/Feature'; import OlGeometry from 'ol/geom/Geometry'; import { @@ -70,6 +73,12 @@ const DrawButton: React.FC = ({ pressed, ...passThroughProps }) => { + const map = useMap(); + const layer = usePropOrDefault( + digitizeLayer, + () => map ? DigitizeUtil.getDigitizeLayer(map) : undefined, + [map] + ); /** * Currently drawn feature which should be represented as label or post-it. */ @@ -85,7 +94,7 @@ const DrawButton: React.FC = ({ useDraw({ onDrawEnd: onDrawEndInternal, - digitizeLayer, + digitizeLayer: layer, drawInteractionConfig, drawStyle, drawType: drawType === 'Text' ? 'Point' : drawType, @@ -107,6 +116,8 @@ const DrawButton: React.FC = ({ }; const onModalLabelCancelInternal = () => { + layer?.getSource()?.removeFeature(digitizeTextFeature); + setDigitizeTextFeature(null); onModalLabelCancel?.(); digitizeLayer?.getSource()?.removeFeature(digitizeTextFeature); setDigitizeTextFeature(null);