@defra/interactive-map 0.0.40-alpha → 0.0.41-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/css/index.css +1 -1
- package/dist/esm/im-core.js +1 -1
- package/dist/esm/im-shell.js +1 -1
- package/dist/umd/im-core.js +1 -1
- package/dist/umd/index.js +1 -1
- package/docs/api/control-definition.md +9 -1
- package/docs/api/slots.md +26 -5
- package/docs/assets/images/slot-map.svg +61 -262
- package/docs/examples/draw-tools.mdx +3 -3
- package/docs/plugins/draw.md +79 -16
- package/docs/plugins/search.md +1 -1
- package/govuk-prototype-kit.config.json +2 -0
- package/jest.setup.js +5 -0
- package/package.json +9 -1
- package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
- package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ol/dist/esm/im-draw-ol-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/umd/index.js +1 -1
- package/plugins/datasets/dist/css/index.css +1 -1
- package/plugins/datasets/dist/esm/esriLayerAdapter.js +1 -1
- package/plugins/datasets/dist/esm/im-datasets-ml-adapter.js +1 -1
- package/plugins/datasets/dist/esm/im-datasets-plugin.js +1 -1
- package/plugins/datasets/dist/umd/im-datasets-esri-adapter.js +1 -1
- package/plugins/datasets/dist/umd/im-datasets-ml-adapter.js +1 -1
- package/plugins/datasets/dist/umd/im-datasets-plugin.js +1 -1
- package/plugins/datasets/dist/umd/index.js +1 -1
- package/plugins/datasets/src/adapters/esri/esriLayerAdapter.test.js +175 -0
- package/plugins/datasets/src/adapters/esri/registry/esriDataset.test.js +55 -0
- package/plugins/datasets/src/adapters/loadLayerAdapter.test.js +61 -0
- package/plugins/datasets/src/adapters/maplibre/maplibreLayerAdapter.test.js +14 -0
- package/plugins/datasets/src/api/setOpacity.test.js +9 -0
- package/plugins/datasets/src/components/LayersMenu/LayersMenu.jsx +10 -25
- package/plugins/datasets/src/components/LayersMenu/LayersMenu.test.jsx +0 -42
- package/plugins/datasets/src/datasets.scss +0 -1
- package/plugins/datasets/src/fetch/createDynamicSource.js +135 -129
- package/plugins/datasets/src/fetch/createDynamicSource.test.js +318 -0
- package/plugins/datasets/src/fetch/fetchGeoJSON.test.js +83 -0
- package/plugins/datasets/src/index.test.js +36 -0
- package/plugins/datasets/src/initialise/DatasetsInit.jsx +17 -7
- package/plugins/datasets/src/initialise/DatasetsInit.test.jsx +262 -0
- package/plugins/datasets/src/initialise/initialiseDatasets.js +16 -4
- package/plugins/datasets/src/initialise/initialiseDatasets.test.js +42 -0
- package/plugins/datasets/src/manifest.js +0 -38
- package/plugins/datasets/src/manifest.test.js +49 -0
- package/plugins/datasets/src/reducers/datasetsToMenu.test.js +54 -1
- package/plugins/datasets/src/reducers/pluginState.js +3 -16
- package/plugins/datasets/src/reducers/pluginState.test.js +23 -0
- package/plugins/datasets/src/registry/dataset.js +13 -1
- package/plugins/datasets/src/registry/dataset.test.js +61 -0
- package/plugins/datasets/src/registry/datasetRegistry.js +12 -6
- package/plugins/datasets/src/registry/datasetRegistry.test.js +16 -4
- package/plugins/datasets/src/registry/isVisibleWhen.js +6 -3
- package/plugins/datasets/src/registry/isVisibleWhen.test.js +11 -1
- package/plugins/datasets/src/utils/bbox.test.js +125 -0
- package/plugins/draw/dist/esm/im-draw-ml-adapter.js +1 -1
- package/plugins/draw/dist/esm/im-draw-ol-adapter.js +1 -1
- package/plugins/draw/dist/esm/im-draw-plugin.js +1 -1
- package/plugins/draw/dist/esm/index.js +1 -1
- package/plugins/draw/dist/umd/im-draw-ml-adapter.js +2 -1
- package/plugins/draw/dist/umd/im-draw-ml-adapter.js.LICENSE.txt +1 -0
- package/plugins/draw/dist/umd/im-draw-ol-adapter.js +1 -1
- package/plugins/draw/dist/umd/im-draw-plugin.js +1 -1
- package/plugins/draw/dist/umd/index.js +1 -1
- package/plugins/draw/src/DrawInit.jsx +10 -2
- package/plugins/draw/src/DrawInit.test.jsx +51 -10
- package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.js +91 -11
- package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.test.js +246 -4
- package/plugins/draw/src/adapters/maplibre/mapboxDraw.js +109 -14
- package/plugins/draw/src/adapters/maplibre/mapboxDraw.test.js +174 -11
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/clickHandlers.test.js +12 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.js +9 -1
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.test.js +24 -3
- package/plugins/draw/src/adapters/maplibre/modes/drawPointMode.js +194 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawPointMode.test.js +457 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/__helpers__/harness.js +102 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/keyboardHandlers.js +82 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/keyboardHandlers.test.js +83 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointOperations.js +55 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointOperations.test.js +62 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointerHandlers.js +138 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointerHandlers.test.js +197 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/touchHandlers.js +92 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/touchHandlers.test.js +101 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/undoHandlers.js +46 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/undoHandlers.test.js +42 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode.js +151 -0
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode.test.js +156 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.js +4 -27
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.test.js +24 -39
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/pointerHandlers.js +4 -5
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.js +20 -23
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.test.js +25 -5
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.js +6 -36
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.test.js +7 -49
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.js +3 -53
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.test.js +8 -33
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.js +29 -72
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.test.js +10 -3
- package/plugins/draw/src/adapters/maplibre/pointSymbolImages.js +158 -0
- package/plugins/draw/src/adapters/maplibre/pointSymbolImages.test.js +314 -0
- package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.js +6 -1
- package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.test.js +21 -0
- package/plugins/draw/src/adapters/maplibre/snap/snapInstance.js +6 -2
- package/plugins/draw/src/adapters/maplibre/snap/snapInstance.test.js +12 -0
- package/plugins/draw/src/adapters/maplibre/styles.js +38 -11
- package/plugins/draw/src/adapters/maplibre/styles.test.js +39 -0
- package/plugins/draw/src/adapters/maplibre/utils/editModeEvents.js +79 -0
- package/plugins/draw/src/adapters/maplibre/utils/editModeEvents.test.js +135 -0
- package/plugins/draw/src/adapters/maplibre/utils/geometryValidation.js +42 -0
- package/plugins/draw/src/adapters/maplibre/utils/geometryValidation.test.js +85 -0
- package/plugins/draw/src/adapters/maplibre/utils/keyboardShortcuts.js +34 -0
- package/plugins/draw/src/adapters/maplibre/utils/keyboardShortcuts.test.js +78 -0
- package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.js +16 -3
- package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.test.js +21 -3
- package/plugins/draw/src/adapters/maplibre/utils/snapMovement.js +59 -0
- package/plugins/draw/src/adapters/maplibre/utils/snapMovement.test.js +73 -0
- package/plugins/draw/src/adapters/maplibre/utils/touchDragMath.js +33 -0
- package/plugins/draw/src/adapters/maplibre/utils/touchDragMath.test.js +70 -0
- package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.js +27 -2
- package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.test.js +93 -0
- package/plugins/draw/src/adapters/openlayers/__helpers__/harness.js +9 -0
- package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.js +19 -1
- package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.test.js +34 -1
- package/plugins/draw/src/adapters/openlayers/core/styles.js +73 -1
- package/plugins/draw/src/adapters/openlayers/core/styles.test.js +122 -3
- package/plugins/draw/src/adapters/openlayers/draw/DrawMode.test.js +12 -0
- package/plugins/draw/src/adapters/openlayers/draw/drawInput.js +8 -2
- package/plugins/draw/src/adapters/openlayers/edit/EditMode.test.js +31 -0
- package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.js +9 -5
- package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.test.js +33 -0
- package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.js +5 -3
- package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.test.js +11 -0
- package/plugins/draw/src/adapters/openlayers/edit/nudge.js +10 -3
- package/plugins/draw/src/adapters/openlayers/edit/nudge.test.js +24 -1
- package/plugins/draw/src/adapters/openlayers/edit/touchHandler.js +18 -13
- package/plugins/draw/src/adapters/openlayers/edit/touchHandler.test.js +46 -1
- package/plugins/draw/src/adapters/openlayers/olDraw.js +11 -4
- package/plugins/draw/src/adapters/openlayers/olDraw.test.js +48 -6
- package/plugins/draw/src/adapters/openlayers/point/drawPointMode.js +156 -0
- package/plugins/draw/src/adapters/openlayers/point/drawPointMode.test.js +246 -0
- package/plugins/draw/src/adapters/openlayers/point/editPointMode.js +267 -0
- package/plugins/draw/src/adapters/openlayers/point/editPointMode.test.js +327 -0
- package/plugins/draw/src/adapters/openlayers/point/pointDragInteraction.js +71 -0
- package/plugins/draw/src/adapters/openlayers/point/pointOps.js +34 -0
- package/plugins/draw/src/adapters/openlayers/point/pointOps.test.js +32 -0
- package/plugins/draw/src/adapters/openlayers/point/pointSelectionState.js +82 -0
- package/plugins/draw/src/adapters/openlayers/point/pointSelectionState.test.js +107 -0
- package/plugins/draw/src/adapters/openlayers/point/pointSymbolImages.js +83 -0
- package/plugins/draw/src/adapters/openlayers/point/pointSymbolImages.test.js +214 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapIndicator.js +3 -1
- package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.js +6 -9
- package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.test.js +15 -4
- package/plugins/draw/src/adapters/openlayers/utils/olCoords.js +1 -1
- package/plugins/draw/src/api/addFeature.js +16 -1
- package/plugins/draw/src/api/addFeature.test.js +62 -5
- package/plugins/draw/src/api/editFeature.js +7 -4
- package/plugins/draw/src/api/editFeature.test.js +16 -0
- package/plugins/draw/src/api/newPoint.js +42 -0
- package/plugins/draw/src/api/newPoint.test.js +117 -0
- package/plugins/draw/src/api/setStyle.js +43 -0
- package/plugins/draw/src/api/setStyle.test.js +75 -0
- package/plugins/draw/src/events.js +17 -10
- package/plugins/draw/src/events.test.js +14 -0
- package/plugins/draw/src/manifest.js +18 -6
- package/plugins/draw/src/manifest.test.js +32 -1
- package/plugins/draw/src/reducer.test.js +7 -0
- package/plugins/draw/src/utils/stripInternalProperties.js +19 -0
- package/plugins/draw/src/utils/stripInternalProperties.test.js +50 -0
- package/plugins/draw/src/utils/symbolKeys.js +5 -0
- package/plugins/draw/src/validation/liveDrawChecks.test.js +54 -0
- package/plugins/draw/src/validation/liveStroke.test.js +64 -1
- package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
- package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
- package/plugins/interact/dist/umd/index.js +1 -1
- package/plugins/interact/src/hooks/useHighlightSync.js +33 -12
- package/plugins/interact/src/hooks/useHighlightSync.test.js +96 -25
- package/plugins/interact/src/utils/featureQueries.js +18 -0
- package/plugins/interact/src/utils/featureQueries.test.js +15 -0
- package/plugins/interact/src/utils/spatial.js +9 -3
- package/plugins/interact/src/utils/spatial.test.js +12 -1
- package/plugins/map-key/dist/css/index.css +62 -0
- package/plugins/map-key/dist/esm/im-map-key-plugin.js +1 -0
- package/plugins/map-key/dist/esm/index.js +1 -0
- package/plugins/map-key/dist/umd/im-map-key-plugin.js +2 -0
- package/plugins/map-key/dist/umd/im-map-key-plugin.js.LICENSE.txt +1 -0
- package/plugins/map-key/dist/umd/index.js +2 -0
- package/plugins/map-key/dist/umd/index.js.LICENSE.txt +1 -0
- package/plugins/map-key/package.json +28 -0
- package/plugins/map-key/src/components/Key/EmptyKey.jsx +7 -0
- package/plugins/{datasets → map-key}/src/components/Key/EmptyKey.test.jsx +2 -2
- package/plugins/map-key/src/components/Key/Key.jsx +41 -0
- package/plugins/{datasets → map-key}/src/components/Key/Key.module.scss +15 -14
- package/plugins/map-key/src/components/Key/Key.test.jsx +73 -0
- package/plugins/map-key/src/components/Key/KeyGroupItem.jsx +16 -0
- package/plugins/map-key/src/components/Key/KeyGroupItem.test.jsx +53 -0
- package/plugins/map-key/src/components/Key/KeyItem.jsx +29 -0
- package/plugins/map-key/src/components/Key/KeyItem.test.jsx +67 -0
- package/plugins/map-key/src/components/Key/KeySvg.jsx +42 -0
- package/plugins/{datasets → map-key}/src/components/Key/KeySvg.test.jsx +14 -12
- package/plugins/map-key/src/components/Key/KeySvgLine.jsx +28 -0
- package/plugins/{datasets → map-key}/src/components/Key/KeySvgLine.test.jsx +1 -1
- package/plugins/map-key/src/components/Key/KeySvgPattern.jsx +23 -0
- package/plugins/{datasets → map-key}/src/components/Key/KeySvgPattern.test.jsx +2 -2
- package/plugins/map-key/src/components/Key/KeySvgRect.jsx +33 -0
- package/plugins/{datasets → map-key}/src/components/Key/KeySvgRect.test.jsx +1 -1
- package/plugins/map-key/src/components/Key/KeySvgSymbol.jsx +27 -0
- package/plugins/{datasets → map-key}/src/components/Key/KeySvgSymbol.test.jsx +5 -6
- package/plugins/map-key/src/components/Key/MapKey.jsx +50 -0
- package/plugins/map-key/src/components/Key/MapKey.test.jsx +130 -0
- package/plugins/{datasets → map-key}/src/components/Key/svgProperties.js +2 -2
- package/plugins/{datasets → map-key}/src/components/Key/svgProperties.test.js +2 -2
- package/plugins/map-key/src/index.js +13 -0
- package/plugins/map-key/src/index.test.js +31 -0
- package/plugins/map-key/src/initialise/MapKeyInit.jsx +15 -0
- package/plugins/map-key/src/initialise/MapKeyInit.test.jsx +39 -0
- package/plugins/map-key/src/manifest.js +30 -0
- package/plugins/map-key/src/manifest.test.js +63 -0
- package/plugins/map-key/src/mapKey.scss +4 -0
- package/plugins/map-key/src/registry/getDatasetRegistry.js +6 -0
- package/plugins/map-key/src/registry/getDatasetRegistry.test.js +23 -0
- package/plugins/map-key/src/registry/index.js +3 -0
- package/plugins/map-key/src/registry/index.test.js +15 -0
- package/plugins/menu/dist/css/index.css +80 -0
- package/plugins/menu/dist/esm/im-menu-plugin.js +1 -0
- package/plugins/menu/dist/esm/index.js +1 -0
- package/plugins/menu/dist/umd/im-menu-plugin.js +1 -0
- package/plugins/menu/dist/umd/index.js +2 -0
- package/plugins/menu/dist/umd/index.js.LICENSE.txt +1 -0
- package/plugins/menu/package.json +28 -0
- package/plugins/menu/src/components/Menu/CheckboxGroupWrapper.jsx +21 -0
- package/plugins/menu/src/components/Menu/GroupLegend.jsx +17 -0
- package/plugins/menu/src/components/Menu/GroupLegend.test.jsx +68 -0
- package/plugins/menu/src/components/Menu/Menu.jsx +19 -0
- package/plugins/menu/src/components/Menu/Menu.module.scss +90 -0
- package/plugins/menu/src/components/Menu/Menu.test.jsx +161 -0
- package/plugins/menu/src/components/Menu/MenuCheckbox.jsx +35 -0
- package/plugins/menu/src/components/Menu/MenuCheckbox.test.jsx +93 -0
- package/plugins/menu/src/components/Menu/MenuGroup.jsx +11 -0
- package/plugins/{datasets/src/components/LayersMenu/LayersMenuRadio.jsx → menu/src/components/Menu/MenuRadio.jsx} +6 -4
- package/plugins/{datasets/src/components/LayersMenu/LayersMenuRadio.test.jsx → menu/src/components/Menu/MenuRadio.test.jsx} +18 -18
- package/plugins/menu/src/components/Menu/RadioGroupWrapper.jsx +32 -0
- package/plugins/{datasets/src/components/LayersMenu/LayersRadioGroupWrapper.test.jsx → menu/src/components/Menu/RadioGroupWrapper.test.jsx} +21 -21
- package/plugins/menu/src/components/Menu/useIdPrefix.jsx +4 -0
- package/plugins/menu/src/index.js +13 -0
- package/plugins/menu/src/index.test.js +31 -0
- package/plugins/menu/src/initialise/MenuInit.jsx +24 -0
- package/plugins/menu/src/initialise/MenuInit.test.jsx +50 -0
- package/plugins/menu/src/initialise/createMenu.js +10 -0
- package/plugins/menu/src/initialise/createMenu.test.js +37 -0
- package/plugins/menu/src/manifest.js +35 -0
- package/plugins/menu/src/manifest.test.js +63 -0
- package/plugins/menu/src/menu.scss +4 -0
- package/plugins/menu/src/reducers/menuStateReducer.test.js +38 -0
- package/plugins/menu/src/reducers/pluginState.js +35 -0
- package/plugins/menu/src/reducers/pluginState.test.js +57 -0
- package/plugins/menu/src/registry/getDatasetRegistry.js +9 -0
- package/plugins/menu/src/registry/getDatasetRegistry.test.js +23 -0
- package/plugins/menu/src/registry/index.js +3 -0
- package/plugins/menu/src/registry/index.test.js +15 -0
- package/plugins/menu/src/registry/isVisibleWhen.js +43 -0
- package/plugins/menu/src/registry/isVisibleWhen.test.js +48 -0
- package/plugins/search/dist/css/index.css +1 -1
- package/plugins/search/dist/esm/im-search-plugin.js +1 -1
- package/plugins/search/dist/esm/index.js +1 -1
- package/plugins/search/dist/umd/im-search-plugin.js +1 -1
- package/plugins/search/dist/umd/index.js +1 -1
- package/plugins/search/src/Search.jsx +2 -3
- package/plugins/search/src/components/Form/Form.module.scss +9 -20
- package/plugins/search/src/index.js +0 -1
- package/plugins/search/src/index.test.js +2 -4
- package/plugins/search/src/manifest.js +1 -1
- package/plugins/search/src/search.scss +17 -16
- package/providers/beta/openlayers/dist/esm/im-openlayers-provider.js +1 -1
- package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js +1 -1
- package/providers/beta/openlayers/dist/umd/index.js +1 -1
- package/providers/beta/openlayers/src/openlayersProvider.js +4 -1
- package/providers/beta/openlayers/src/openlayersProvider.test.js +18 -0
- package/providers/beta/openlayers/src/utils/highlightFeatures.js +52 -5
- package/providers/beta/openlayers/src/utils/highlightFeatures.test.js +176 -0
- package/providers/beta/openlayers/src/utils/symbolImages.js +40 -0
- package/providers/beta/openlayers/src/utils/symbolImages.test.js +58 -0
- package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/umd/index.js +1 -1
- package/providers/maplibre/src/utils/drawLayerBuckets.js +14 -0
- package/providers/maplibre/src/utils/drawLayerBuckets.test.js +18 -0
- package/providers/maplibre/src/utils/highlightFeatures.js +80 -55
- package/providers/maplibre/src/utils/highlightFeatures.test.js +156 -17
- package/providers/maplibre/src/utils/hoverCursor.js +30 -14
- package/providers/maplibre/src/utils/hoverCursor.test.js +35 -0
- package/providers/maplibre/src/utils/patternImages.js +1 -1
- package/providers/maplibre/src/utils/symbolImages.js +41 -3
- package/providers/maplibre/src/utils/symbolImages.test.js +40 -6
- package/rollup.esm.mjs +11 -1
- package/src/App/components/Actions/Actions.test.jsx +47 -0
- package/src/App/components/Hints/Hints.test.jsx +13 -0
- package/src/App/components/MoveControls/MoveControls.module.scss +41 -10
- package/src/App/components/Panel/Panel.jsx +18 -3
- package/src/App/components/Panel/Panel.module.scss +18 -8
- package/src/App/components/Panel/Panel.test.jsx +34 -0
- package/src/App/components/Tooltip/Tooltip.test.jsx +12 -14
- package/src/App/hooks/useFeatureItems.js +16 -7
- package/src/App/hooks/useFeatureItems.test.js +57 -0
- package/src/App/hooks/useLayoutMeasurements.js +95 -84
- package/src/App/hooks/useLayoutMeasurements.test.js +61 -1
- package/src/App/layout/Layout.jsx +9 -10
- package/src/App/layout/Layout.test.jsx +2 -0
- package/src/App/layout/layout.module.scss +31 -17
- package/src/App/registry/pluginRegistry.js +1 -1
- package/src/App/registry/pluginRegistry.test.js +12 -0
- package/src/App/renderer/HtmlElementHost.jsx +18 -5
- package/src/App/renderer/HtmlElementHost.test.jsx +54 -0
- package/src/App/renderer/mapButtons.js +28 -12
- package/src/App/renderer/mapButtons.test.js +17 -4
- package/src/App/renderer/mapControls.js +3 -1
- package/src/App/renderer/mapControls.test.js +16 -0
- package/src/App/renderer/mapPanels.js +44 -9
- package/src/App/renderer/mapPanels.test.js +51 -2
- package/src/App/renderer/orderItems.js +28 -0
- package/src/App/renderer/orderItems.test.js +50 -0
- package/src/App/renderer/slotAggregator.js +2 -18
- package/src/App/renderer/slotHelpers.js +3 -1
- package/src/App/renderer/slotHelpers.test.js +5 -0
- package/src/App/renderer/slots.js +3 -0
- package/src/App/store/AppProvider.jsx +1 -0
- package/src/App/store/appDispatchMiddleware.js +1 -1
- package/src/App/store/appDispatchMiddleware.test.js +8 -0
- package/src/config/events.js +2 -0
- package/src/scss/settings/_colors.scss +1 -0
- package/src/scss/settings/_dimensions.scss +2 -3
- package/src/scss/settings/_transition.scss +1 -1
- package/src/services/eventBus.js +44 -0
- package/src/services/eventBus.test.js +83 -0
- package/src/services/symbolRegistry.js +1 -1
- package/src/types.js +11 -1
- package/webpack.umd.mjs +2 -0
- package/plugins/datasets/src/components/Key/EmptyKey.jsx +0 -7
- package/plugins/datasets/src/components/Key/Key.jsx +0 -50
- package/plugins/datasets/src/components/Key/KeyGroupItem.jsx +0 -18
- package/plugins/datasets/src/components/Key/KeyItem.jsx +0 -23
- package/plugins/datasets/src/components/Key/KeySvg.jsx +0 -22
- package/plugins/datasets/src/components/Key/KeySvgLine.jsx +0 -19
- package/plugins/datasets/src/components/Key/KeySvgPattern.jsx +0 -13
- package/plugins/datasets/src/components/Key/KeySvgRect.jsx +0 -22
- package/plugins/datasets/src/components/Key/KeySvgSymbol.jsx +0 -16
- package/plugins/datasets/src/components/LayersMenu/LayersRadioGroupWrapper.jsx +0 -41
- /package/plugins/{datasets → menu}/src/reducers/menuStateReducer.js +0 -0
- /package/{providers/maplibre/src → src}/utils/rasteriseToImageData.js +0 -0
- /package/{providers/maplibre/src → src}/utils/rasteriseToImageData.test.js +0 -0
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import { flattenStyleProperties } from '../utils/flattenStyleProperties.js'
|
|
2
|
+
import { stripInternalProperties } from '../utils/stripInternalProperties.js'
|
|
3
|
+
import { logger } from '../../../../src/services/logger.js'
|
|
4
|
+
|
|
5
|
+
const SUPPORTED_GEOMETRY_TYPES = new Set(['Point', 'LineString', 'Polygon'])
|
|
6
|
+
|
|
7
|
+
// type: 'Feature' is defaulted below (both adapters mishandle it missing), but a missing or
|
|
8
|
+
// unsupported geometry is a genuine caller mistake, so that's rejected instead.
|
|
9
|
+
const isValidFeature = (feature) =>
|
|
10
|
+
SUPPORTED_GEOMETRY_TYPES.has(feature.geometry?.type) && feature.geometry?.coordinates !== undefined
|
|
2
11
|
|
|
3
12
|
export const addFeature = ({ mapProvider, services }, feature) => {
|
|
4
13
|
const { draw } = mapProvider
|
|
@@ -10,6 +19,7 @@ export const addFeature = ({ mapProvider, services }, feature) => {
|
|
|
10
19
|
|
|
11
20
|
const { stroke, fill, strokeWidth, properties, ...featureRest } = feature
|
|
12
21
|
const flatFeature = {
|
|
22
|
+
type: 'Feature',
|
|
13
23
|
...featureRest,
|
|
14
24
|
properties: {
|
|
15
25
|
...properties,
|
|
@@ -17,6 +27,11 @@ export const addFeature = ({ mapProvider, services }, feature) => {
|
|
|
17
27
|
}
|
|
18
28
|
}
|
|
19
29
|
|
|
30
|
+
if (!isValidFeature(flatFeature)) {
|
|
31
|
+
logger.warn('addFeature: ignoring invalid GeoJSON feature', flatFeature)
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
20
35
|
draw.add(flatFeature)
|
|
21
|
-
eventBus.emit('draw:add', flatFeature)
|
|
36
|
+
eventBus.emit('draw:add', stripInternalProperties(flatFeature))
|
|
22
37
|
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { addFeature } from './addFeature.js'
|
|
2
2
|
import { flattenStyleProperties } from '../utils/flattenStyleProperties.js'
|
|
3
|
+
import { logger } from '../../../../src/services/logger.js'
|
|
3
4
|
|
|
4
5
|
jest.mock('../utils/flattenStyleProperties.js', () => ({
|
|
5
6
|
flattenStyleProperties: jest.fn(() => ({ _flat: true }))
|
|
6
7
|
}))
|
|
8
|
+
jest.mock('../../../../src/services/logger.js', () => ({
|
|
9
|
+
logger: { warn: jest.fn(), error: jest.fn() }
|
|
10
|
+
}))
|
|
7
11
|
|
|
8
12
|
const setup = (draw) => {
|
|
9
13
|
const eventBus = { emit: jest.fn() }
|
|
@@ -19,15 +23,27 @@ describe('addFeature', () => {
|
|
|
19
23
|
expect(eventBus.emit).not.toHaveBeenCalled()
|
|
20
24
|
})
|
|
21
25
|
|
|
22
|
-
test('flattens style properties and adds the feature', () => {
|
|
26
|
+
test('flattens style properties, defaults type to Feature, and adds the feature', () => {
|
|
23
27
|
const draw = { add: jest.fn() }
|
|
24
28
|
const { context, eventBus } = setup(draw)
|
|
25
|
-
const feature = {
|
|
29
|
+
const feature = {
|
|
30
|
+
id: 'a',
|
|
31
|
+
geometry: { type: 'Point', coordinates: [0, 0] },
|
|
32
|
+
stroke: 'red',
|
|
33
|
+
fill: 'blue',
|
|
34
|
+
strokeWidth: 2,
|
|
35
|
+
properties: { name: 'x' }
|
|
36
|
+
}
|
|
26
37
|
|
|
27
38
|
addFeature(context, feature)
|
|
28
39
|
|
|
29
40
|
expect(flattenStyleProperties).toHaveBeenCalledWith({ stroke: 'red', fill: 'blue', strokeWidth: 2 })
|
|
30
|
-
const expected = {
|
|
41
|
+
const expected = {
|
|
42
|
+
type: 'Feature',
|
|
43
|
+
id: 'a',
|
|
44
|
+
geometry: { type: 'Point', coordinates: [0, 0] },
|
|
45
|
+
properties: { name: 'x', _flat: true }
|
|
46
|
+
}
|
|
31
47
|
expect(draw.add).toHaveBeenCalledWith(expected)
|
|
32
48
|
expect(eventBus.emit).toHaveBeenCalledWith('draw:add', expected)
|
|
33
49
|
})
|
|
@@ -35,7 +51,48 @@ describe('addFeature', () => {
|
|
|
35
51
|
test('handles a feature without existing properties', () => {
|
|
36
52
|
const draw = { add: jest.fn() }
|
|
37
53
|
const { context } = setup(draw)
|
|
38
|
-
addFeature(context, { id: 'b', geometry: {} })
|
|
39
|
-
expect(draw.add).toHaveBeenCalledWith({
|
|
54
|
+
addFeature(context, { id: 'b', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] } })
|
|
55
|
+
expect(draw.add).toHaveBeenCalledWith({
|
|
56
|
+
type: 'Feature',
|
|
57
|
+
id: 'b',
|
|
58
|
+
geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] },
|
|
59
|
+
properties: { _flat: true }
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('does not override an explicitly supplied type', () => {
|
|
64
|
+
const draw = { add: jest.fn() }
|
|
65
|
+
const { context } = setup(draw)
|
|
66
|
+
addFeature(context, { id: 'c', type: 'Feature', geometry: { type: 'Polygon', coordinates: [[]] } })
|
|
67
|
+
expect(draw.add).toHaveBeenCalledWith(expect.objectContaining({ type: 'Feature' }))
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
// A missing/unsupported geometry is a genuine caller mistake — warned and dropped rather
|
|
71
|
+
// than handed to an adapter that would throw or silently misread it.
|
|
72
|
+
describe('invalid geometry', () => {
|
|
73
|
+
test('rejects a feature with no geometry at all', () => {
|
|
74
|
+
const draw = { add: jest.fn() }
|
|
75
|
+
const { context, eventBus } = setup(draw)
|
|
76
|
+
addFeature(context, { id: 'd' })
|
|
77
|
+
expect(draw.add).not.toHaveBeenCalled()
|
|
78
|
+
expect(eventBus.emit).not.toHaveBeenCalled()
|
|
79
|
+
expect(logger.warn).toHaveBeenCalledWith('addFeature: ignoring invalid GeoJSON feature', expect.objectContaining({ id: 'd' }))
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('rejects an unsupported geometry type', () => {
|
|
83
|
+
const draw = { add: jest.fn() }
|
|
84
|
+
const { context } = setup(draw)
|
|
85
|
+
addFeature(context, { id: 'e', geometry: { type: 'MultiPoint', coordinates: [[0, 0]] } })
|
|
86
|
+
expect(draw.add).not.toHaveBeenCalled()
|
|
87
|
+
expect(logger.warn).toHaveBeenCalled()
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test('rejects a geometry with no coordinates', () => {
|
|
91
|
+
const draw = { add: jest.fn() }
|
|
92
|
+
const { context } = setup(draw)
|
|
93
|
+
addFeature(context, { id: 'f', geometry: { type: 'Point' } })
|
|
94
|
+
expect(draw.add).not.toHaveBeenCalled()
|
|
95
|
+
expect(logger.warn).toHaveBeenCalled()
|
|
96
|
+
})
|
|
40
97
|
})
|
|
41
98
|
})
|
|
@@ -18,14 +18,17 @@ export const editFeature = ({ appState, appConfig, mapState, pluginConfig, plugi
|
|
|
18
18
|
// Per-call callback overrides the plugin-level one; events.js reads this on every commit.
|
|
19
19
|
draw._geometryValidator = options.onGeometryChange ?? pluginConfig.onGeometryChange
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
// A Point has no ring of vertices to edit — edit_point relocates the single coordinate
|
|
22
|
+
// instead of edit_vertex's ring-of-vertices machinery.
|
|
23
|
+
const editModeMap = { LineString: 'edit_line', Polygon: 'edit_polygon', Point: 'edit_point' }
|
|
24
|
+
const mode = existingFeature.geometry.type === 'Point' ? 'edit_point' : 'edit_vertex'
|
|
22
25
|
eventBus.emit('draw:editstart', { mode: editModeMap[existingFeature.geometry.type] })
|
|
23
26
|
|
|
24
27
|
const snapLayers = options.snapLayers === undefined ? (pluginConfig.snapLayers ?? null) : options.snapLayers
|
|
25
28
|
draw.setSnapLayers(snapLayers)
|
|
26
29
|
dispatch({ type: 'SET_HAS_SNAP_LAYERS', payload: snapLayers?.length > 0 })
|
|
27
30
|
|
|
28
|
-
draw.changeMode(
|
|
31
|
+
draw.changeMode(mode, {
|
|
29
32
|
container: appState.layoutRefs.viewportRef.current,
|
|
30
33
|
deleteVertexButtonId: `${appConfig.id}-draw-delete-point`,
|
|
31
34
|
undoButtonId: `${appConfig.id}-draw-undo`,
|
|
@@ -42,11 +45,11 @@ export const editFeature = ({ appState, appConfig, mapState, pluginConfig, plugi
|
|
|
42
45
|
payload: { feature, tempFeature: feature }
|
|
43
46
|
})
|
|
44
47
|
|
|
45
|
-
dispatch({ type: 'SET_MODE', payload:
|
|
48
|
+
dispatch({ type: 'SET_MODE', payload: mode })
|
|
46
49
|
|
|
47
50
|
// Seed the Done-button gate and the stroke from the feature's starting validity
|
|
48
51
|
// so an already invalid feature opens dashed and cannot be "finished" until fixed.
|
|
49
|
-
const { valid } = validateGeometry(feature, { phase: 'edit-start', mode
|
|
52
|
+
const { valid } = validateGeometry(feature, { phase: 'edit-start', mode }, { onGeometryChange: draw._geometryValidator })
|
|
50
53
|
dispatch({ type: 'SET_GEOMETRY_VALID', payload: valid })
|
|
51
54
|
draw.setInvalid?.(!valid)
|
|
52
55
|
|
|
@@ -74,6 +74,22 @@ describe('editFeature', () => {
|
|
|
74
74
|
expect(eventBus.emit).toHaveBeenCalledWith('draw:editstart', { mode: 'edit_line' })
|
|
75
75
|
})
|
|
76
76
|
|
|
77
|
+
// A Point has no ring of vertices — editing one goes through edit_point (relocate the
|
|
78
|
+
// single coordinate), not edit_vertex, across every mode-name site this function touches.
|
|
79
|
+
test('uses edit_point mode for a point feature', () => {
|
|
80
|
+
const { context, dispatch, eventBus, draw } = makeContext()
|
|
81
|
+
draw.get.mockReturnValue({ id: 'f1', geometry: { type: 'Point', coordinates: [0, 0] } })
|
|
82
|
+
|
|
83
|
+
editFeature(context, 'f1')
|
|
84
|
+
|
|
85
|
+
expect(eventBus.emit).toHaveBeenCalledWith('draw:editstart', { mode: 'edit_point' })
|
|
86
|
+
expect(draw.changeMode).toHaveBeenCalledWith('edit_point', expect.objectContaining({ featureId: 'f1' }))
|
|
87
|
+
expect(dispatch).toHaveBeenCalledWith({ type: 'SET_MODE', payload: 'edit_point' })
|
|
88
|
+
// Point geometry is always valid (validation/rules.js has no Point branch) — the gate
|
|
89
|
+
// opens immediately, unlike a self-intersecting/zero-area polygon.
|
|
90
|
+
expect(dispatch).toHaveBeenCalledWith({ type: 'SET_GEOMETRY_VALID', payload: true })
|
|
91
|
+
})
|
|
92
|
+
|
|
77
93
|
test('stores a per-call onGeometryChange validator, overriding the plugin-level one', () => {
|
|
78
94
|
const pluginOnGeometryChange = jest.fn()
|
|
79
95
|
const onGeometryChange = jest.fn()
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { SYMBOL_KEYS } from '../utils/symbolKeys.js'
|
|
2
|
+
|
|
3
|
+
export const newPoint = ({ appState, appConfig, pluginConfig, pluginState, mapState, mapProvider, services }, featureId, options = {}) => {
|
|
4
|
+
const { dispatch } = pluginState
|
|
5
|
+
const { draw } = mapProvider
|
|
6
|
+
const { eventBus } = services
|
|
7
|
+
|
|
8
|
+
if (!draw) {
|
|
9
|
+
return
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
eventBus.emit('draw:started', { mode: 'draw_point' })
|
|
13
|
+
|
|
14
|
+
const snapLayers = options.snapLayers === undefined ? (pluginConfig.snapLayers ?? null) : options.snapLayers
|
|
15
|
+
draw.setSnapLayers(snapLayers)
|
|
16
|
+
dispatch({ type: 'SET_HAS_SNAP_LAYERS', payload: snapLayers?.length > 0 })
|
|
17
|
+
|
|
18
|
+
const { properties: customProperties, onGeometryChange, ...modeOptions } = options
|
|
19
|
+
// Per-call callback overrides the plugin-level one; events.js reads this on every commit.
|
|
20
|
+
draw._geometryValidator = onGeometryChange ?? pluginConfig.onGeometryChange
|
|
21
|
+
|
|
22
|
+
const symbolStyle = {}
|
|
23
|
+
SYMBOL_KEYS.forEach((key) => {
|
|
24
|
+
if (modeOptions[key] !== undefined) {
|
|
25
|
+
symbolStyle[key] = modeOptions[key]
|
|
26
|
+
delete modeOptions[key]
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
draw.changeMode('draw_point', {
|
|
31
|
+
container: appState.layoutRefs.viewportRef.current,
|
|
32
|
+
addVertexButtonId: `${appConfig.id}-draw-add-point`,
|
|
33
|
+
interfaceType: appState.interfaceType,
|
|
34
|
+
crossHair: mapState.crossHair,
|
|
35
|
+
getSnapEnabled: () => draw.isSnapEnabled(),
|
|
36
|
+
featureId,
|
|
37
|
+
...modeOptions,
|
|
38
|
+
properties: { ...customProperties, ...symbolStyle }
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
dispatch({ type: 'SET_MODE', payload: 'draw_point' })
|
|
42
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { newPoint } from './newPoint.js'
|
|
2
|
+
|
|
3
|
+
const makeContext = (overrides = {}) => {
|
|
4
|
+
const dispatch = jest.fn()
|
|
5
|
+
const eventBus = { emit: jest.fn() }
|
|
6
|
+
const draw = { setSnapLayers: jest.fn(), changeMode: jest.fn(), isSnapEnabled: jest.fn(() => false) }
|
|
7
|
+
const context = {
|
|
8
|
+
appState: { layoutRefs: { viewportRef: { current: 'viewport' } }, interfaceType: 'mouse' },
|
|
9
|
+
appConfig: { id: 'app' },
|
|
10
|
+
pluginConfig: { snapLayers: ['pc-layer'] },
|
|
11
|
+
pluginState: { dispatch },
|
|
12
|
+
mapState: { crossHair: true },
|
|
13
|
+
mapProvider: { draw },
|
|
14
|
+
services: { eventBus },
|
|
15
|
+
...overrides
|
|
16
|
+
}
|
|
17
|
+
return { context, dispatch, eventBus, draw }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
beforeEach(() => jest.clearAllMocks())
|
|
21
|
+
|
|
22
|
+
describe('newPoint', () => {
|
|
23
|
+
test('does nothing when there is no draw instance', () => {
|
|
24
|
+
const { context, eventBus } = makeContext({ mapProvider: { draw: null } })
|
|
25
|
+
newPoint(context, 'f1')
|
|
26
|
+
expect(eventBus.emit).not.toHaveBeenCalled()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('starts point placement, splitting symbol-config keys out of properties', () => {
|
|
30
|
+
const { context, dispatch, eventBus, draw } = makeContext()
|
|
31
|
+
|
|
32
|
+
newPoint(context, 'f1', {
|
|
33
|
+
symbol: 'pin',
|
|
34
|
+
symbolBackgroundColor: { outdoor: '#1d70b8' },
|
|
35
|
+
properties: { name: 'x' },
|
|
36
|
+
extra: 'opt'
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
expect(eventBus.emit).toHaveBeenCalledWith('draw:started', { mode: 'draw_point' })
|
|
40
|
+
|
|
41
|
+
expect(draw.changeMode).toHaveBeenCalledWith('draw_point', expect.objectContaining({
|
|
42
|
+
container: 'viewport',
|
|
43
|
+
addVertexButtonId: 'app-draw-add-point',
|
|
44
|
+
interfaceType: 'mouse',
|
|
45
|
+
crossHair: true,
|
|
46
|
+
featureId: 'f1',
|
|
47
|
+
extra: 'opt',
|
|
48
|
+
properties: { name: 'x', symbol: 'pin', symbolBackgroundColor: { outdoor: '#1d70b8' } }
|
|
49
|
+
}))
|
|
50
|
+
|
|
51
|
+
const opts = draw.changeMode.mock.calls[0][1]
|
|
52
|
+
expect(opts.getSnapEnabled()).toBe(false)
|
|
53
|
+
expect(draw.isSnapEnabled).toHaveBeenCalled()
|
|
54
|
+
// Symbol keys are pulled off modeOptions entirely, not just copied into properties.
|
|
55
|
+
expect(opts).not.toHaveProperty('symbol')
|
|
56
|
+
expect(opts).not.toHaveProperty('symbolBackgroundColor')
|
|
57
|
+
|
|
58
|
+
expect(dispatch).toHaveBeenCalledWith({ type: 'SET_MODE', payload: 'draw_point' })
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('carries every recognised symbol-config key through to properties', () => {
|
|
62
|
+
const { context, draw } = makeContext()
|
|
63
|
+
const symbolStyle = {
|
|
64
|
+
symbol: 'pin',
|
|
65
|
+
symbolSvgContent: '<path/>',
|
|
66
|
+
symbolBackgroundColor: '#fff',
|
|
67
|
+
symbolForegroundColor: '#000',
|
|
68
|
+
symbolHaloWidth: 2,
|
|
69
|
+
symbolGraphic: 'g',
|
|
70
|
+
symbolViewBox: '0 0 10 10',
|
|
71
|
+
symbolAnchor: [0.5, 0.9]
|
|
72
|
+
}
|
|
73
|
+
newPoint(context, 'f1', symbolStyle)
|
|
74
|
+
expect(draw.changeMode.mock.calls[0][1].properties).toEqual(symbolStyle)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('omits symbol-config keys entirely when none are supplied', () => {
|
|
78
|
+
const { context, draw } = makeContext()
|
|
79
|
+
newPoint(context, 'f1')
|
|
80
|
+
expect(draw.changeMode.mock.calls[0][1].properties).toEqual({})
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('prefers explicit option snapLayers and flags them', () => {
|
|
84
|
+
const { context, draw, dispatch } = makeContext()
|
|
85
|
+
newPoint(context, 'f1', { snapLayers: ['opt'] })
|
|
86
|
+
expect(draw.setSnapLayers).toHaveBeenCalledWith(['opt'])
|
|
87
|
+
expect(dispatch).toHaveBeenCalledWith({ type: 'SET_HAS_SNAP_LAYERS', payload: true })
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test('falls back to the plugin config snapLayers', () => {
|
|
91
|
+
const { context, draw } = makeContext()
|
|
92
|
+
newPoint(context, 'f1')
|
|
93
|
+
expect(draw.setSnapLayers).toHaveBeenCalledWith(['pc-layer'])
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
test('falls back to null when neither option nor config is set', () => {
|
|
97
|
+
const { context, draw, dispatch } = makeContext({ pluginConfig: {} })
|
|
98
|
+
newPoint(context, 'f1')
|
|
99
|
+
expect(draw.setSnapLayers).toHaveBeenCalledWith(null)
|
|
100
|
+
expect(dispatch).toHaveBeenCalledWith({ type: 'SET_HAS_SNAP_LAYERS', payload: false })
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test('stores a per-call onGeometryChange validator without leaking it into mode options', () => {
|
|
104
|
+
const { context, draw } = makeContext()
|
|
105
|
+
const onGeometryChange = jest.fn()
|
|
106
|
+
newPoint(context, 'f1', { onGeometryChange })
|
|
107
|
+
expect(draw._geometryValidator).toBe(onGeometryChange)
|
|
108
|
+
expect(draw.changeMode.mock.calls[0][1]).not.toHaveProperty('onGeometryChange')
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
test('falls back to the plugin-level onGeometryChange validator', () => {
|
|
112
|
+
const pluginOnGeometryChange = jest.fn()
|
|
113
|
+
const { context, draw } = makeContext({ pluginConfig: { onGeometryChange: pluginOnGeometryChange } })
|
|
114
|
+
newPoint(context, 'f1')
|
|
115
|
+
expect(draw._geometryValidator).toBe(pluginOnGeometryChange)
|
|
116
|
+
})
|
|
117
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { flattenStyleProperties } from '../utils/flattenStyleProperties.js'
|
|
2
|
+
import { logger } from '../../../../src/services/logger.js'
|
|
3
|
+
|
|
4
|
+
const STYLE_KEYS = new Set(['stroke', 'fill', 'strokeWidth'])
|
|
5
|
+
|
|
6
|
+
// A partial patch — unlike addFeature.js building a whole new feature from scratch,
|
|
7
|
+
// flattenStyleProperties would otherwise write stroke/fill/strokeWidth as explicit
|
|
8
|
+
// `undefined` for any of the three the caller didn't mention, blanking out whatever that
|
|
9
|
+
// feature already had.
|
|
10
|
+
const definedOnly = (properties) => {
|
|
11
|
+
const result = {}
|
|
12
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
13
|
+
if (value !== undefined) {
|
|
14
|
+
result[key] = value
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return result
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const setStyle = ({ mapProvider, services }, featureId, styleChanges = {}) => {
|
|
21
|
+
const { draw } = mapProvider
|
|
22
|
+
const { eventBus } = services
|
|
23
|
+
|
|
24
|
+
if (!draw) {
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!draw.get(featureId)) {
|
|
29
|
+
logger.warn('setStyle: no feature found for id', featureId)
|
|
30
|
+
return false
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const styleProps = {}
|
|
34
|
+
const symbolProps = {}
|
|
35
|
+
for (const [key, value] of Object.entries(styleChanges)) {
|
|
36
|
+
(STYLE_KEYS.has(key) ? styleProps : symbolProps)[key] = value
|
|
37
|
+
}
|
|
38
|
+
const properties = definedOnly({ ...flattenStyleProperties(styleProps), ...symbolProps })
|
|
39
|
+
|
|
40
|
+
draw.setStyle(featureId, properties)
|
|
41
|
+
eventBus.emit('draw:stylechange', { featureId, properties })
|
|
42
|
+
return true
|
|
43
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { setStyle } from './setStyle.js'
|
|
2
|
+
import { logger } from '../../../../src/services/logger.js'
|
|
3
|
+
|
|
4
|
+
jest.mock('../../../../src/services/logger.js', () => ({
|
|
5
|
+
logger: { warn: jest.fn(), error: jest.fn() }
|
|
6
|
+
}))
|
|
7
|
+
|
|
8
|
+
const setup = (draw) => {
|
|
9
|
+
const eventBus = { emit: jest.fn() }
|
|
10
|
+
return { context: { mapProvider: { draw }, services: { eventBus } }, eventBus }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
beforeEach(() => jest.clearAllMocks())
|
|
14
|
+
|
|
15
|
+
describe('setStyle', () => {
|
|
16
|
+
test('does nothing when there is no draw instance', () => {
|
|
17
|
+
const { context, eventBus } = setup(undefined)
|
|
18
|
+
expect(setStyle(context, 'a', { stroke: 'red' })).toBe(false)
|
|
19
|
+
expect(eventBus.emit).not.toHaveBeenCalled()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('warns and does nothing for an id with no existing feature', () => {
|
|
23
|
+
const draw = { get: jest.fn(() => null), setStyle: jest.fn() }
|
|
24
|
+
const { context, eventBus } = setup(draw)
|
|
25
|
+
expect(setStyle(context, 'missing', { stroke: 'red' })).toBe(false)
|
|
26
|
+
expect(draw.setStyle).not.toHaveBeenCalled()
|
|
27
|
+
expect(eventBus.emit).not.toHaveBeenCalled()
|
|
28
|
+
expect(logger.warn).toHaveBeenCalledWith('setStyle: no feature found for id', 'missing')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test('flattens stroke/fill/strokeWidth and forwards symbol keys as-is', () => {
|
|
32
|
+
const draw = { get: jest.fn(() => ({ id: 'a' })), setStyle: jest.fn() }
|
|
33
|
+
const { context, eventBus } = setup(draw)
|
|
34
|
+
|
|
35
|
+
const result = setStyle(context, 'a', {
|
|
36
|
+
stroke: { outdoor: '#1d70b8', dark: '#4c9ed9' },
|
|
37
|
+
symbol: 'square',
|
|
38
|
+
symbolBackgroundColor: { outdoor: '#ca3535', dark: '#ffffff' }
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const expected = {
|
|
42
|
+
stroke: '#1d70b8',
|
|
43
|
+
strokeOutdoor: '#1d70b8',
|
|
44
|
+
strokeDark: '#4c9ed9',
|
|
45
|
+
symbol: 'square',
|
|
46
|
+
symbolBackgroundColor: { outdoor: '#ca3535', dark: '#ffffff' }
|
|
47
|
+
}
|
|
48
|
+
expect(draw.setStyle).toHaveBeenCalledWith('a', expected)
|
|
49
|
+
expect(eventBus.emit).toHaveBeenCalledWith('draw:stylechange', { featureId: 'a', properties: expected })
|
|
50
|
+
expect(result).toBe(true)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
// A partial patch — omitting fill/strokeWidth must leave whatever the feature already has
|
|
54
|
+
// alone, not blank them out to undefined.
|
|
55
|
+
test('only includes the style keys actually supplied, dropping the unset ones', () => {
|
|
56
|
+
const draw = { get: jest.fn(() => ({ id: 'a' })), setStyle: jest.fn() }
|
|
57
|
+
const { context } = setup(draw)
|
|
58
|
+
setStyle(context, 'a', { stroke: '#e6c700' })
|
|
59
|
+
expect(draw.setStyle).toHaveBeenCalledWith('a', { stroke: '#e6c700' })
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('drops keys explicitly passed as undefined, from both style and symbol props', () => {
|
|
63
|
+
const draw = { get: jest.fn(() => ({ id: 'a' })), setStyle: jest.fn() }
|
|
64
|
+
const { context } = setup(draw)
|
|
65
|
+
setStyle(context, 'a', { stroke: 'red', fill: undefined, symbol: 'pin', symbolBackgroundColor: undefined })
|
|
66
|
+
expect(draw.setStyle).toHaveBeenCalledWith('a', { stroke: 'red', symbol: 'pin' })
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('defaults styleChanges to {} and still resolves cleanly', () => {
|
|
70
|
+
const draw = { get: jest.fn(() => ({ id: 'a' })), setStyle: jest.fn() }
|
|
71
|
+
const { context } = setup(draw)
|
|
72
|
+
setStyle(context, 'a')
|
|
73
|
+
expect(draw.setStyle).toHaveBeenCalledWith('a', {})
|
|
74
|
+
})
|
|
75
|
+
})
|
|
@@ -9,10 +9,17 @@ import { ADAPTER_EVENTS } from './adapterEvents.js'
|
|
|
9
9
|
import { validateGeometry } from './validation/validateGeometry.js'
|
|
10
10
|
import { MIN_VERTICES_REASONS } from './validation/rules.js'
|
|
11
11
|
import { MAP_SIZE_SCALES } from './defaults.js'
|
|
12
|
+
import { stripInternalProperties } from './utils/stripInternalProperties.js'
|
|
12
13
|
|
|
13
14
|
const EDIT_VERTEX_MODE = 'edit_vertex'
|
|
14
15
|
const GEOMETRY_INVALID_EVENT = 'draw:geometryinvalid'
|
|
15
16
|
|
|
17
|
+
// edit_point behaves like edit_vertex for the two checks below (restore-on-cancel, live
|
|
18
|
+
// invalid-stroke gating) — a relocate session, just with one coordinate instead of a ring.
|
|
19
|
+
// enterEditVertexMode (below) stays edit_vertex-only — it's only ever reached for a shape
|
|
20
|
+
// that finished invalid, and Point geometry has no such failure mode (see rules.js).
|
|
21
|
+
const EDIT_MODES = new Set([EDIT_VERTEX_MODE, 'edit_point'])
|
|
22
|
+
|
|
16
23
|
// A shape that simply hasn't reached its minimum vertex count yet isn't a mistake —
|
|
17
24
|
// it's the normal, expected state of "still being drawn" — so it's excluded below
|
|
18
25
|
// even though it's a genuine SOFT_RULES failure like any other.
|
|
@@ -86,10 +93,10 @@ function createHandlers ({ appState, appConfig, mapState, pluginState, mapProvid
|
|
|
86
93
|
handleDone: () => { draw.done() },
|
|
87
94
|
handleCancel: () => {
|
|
88
95
|
const mode = draw.getMode()
|
|
89
|
-
if (mode
|
|
96
|
+
if (EDIT_MODES.has(mode) && tempFeature?.id) { draw.add(feature) }
|
|
90
97
|
pendingCreateId = null
|
|
91
98
|
draw.cancel(); resetState()
|
|
92
|
-
eventBus.emit('draw:cancelled', feature)
|
|
99
|
+
eventBus.emit('draw:cancelled', stripInternalProperties(feature))
|
|
93
100
|
},
|
|
94
101
|
handleUndo: () => draw.undo(),
|
|
95
102
|
handleDeleteVertex: () => draw.deleteVertex(),
|
|
@@ -104,11 +111,11 @@ function createHandlers ({ appState, appConfig, mapState, pluginState, mapProvid
|
|
|
104
111
|
const { valid, reason } = validateGeometry(f, { phase: 'create', mode: draw.getMode() }, { onGeometryChange: draw._geometryValidator })
|
|
105
112
|
if (!valid) {
|
|
106
113
|
pendingCreateId = f.id
|
|
107
|
-
emitGeometryInvalid({ feature: f, reason, phase: 'create', mode: EDIT_VERTEX_MODE })
|
|
114
|
+
emitGeometryInvalid({ feature: stripInternalProperties(f), reason, phase: 'create', mode: EDIT_VERTEX_MODE })
|
|
108
115
|
setTimeout(() => enterEditVertexMode({ draw, appState, appConfig, mapState, dispatch }, f.id), 0)
|
|
109
116
|
return
|
|
110
117
|
}
|
|
111
|
-
resetState(); setTimeout(() => draw.changeMode('disabled'), 0); eventBus.emit('draw:created', f)
|
|
118
|
+
resetState(); setTimeout(() => draw.changeMode('disabled'), 0); eventBus.emit('draw:created', stripInternalProperties(f))
|
|
112
119
|
},
|
|
113
120
|
onEditFinish: (f) => {
|
|
114
121
|
resetState()
|
|
@@ -116,15 +123,15 @@ function createHandlers ({ appState, appConfig, mapState, pluginState, mapProvid
|
|
|
116
123
|
// A shape that was drawn-then-fixed reports as a creation, not an edit.
|
|
117
124
|
if (pendingCreateId && f.id === pendingCreateId) {
|
|
118
125
|
pendingCreateId = null
|
|
119
|
-
eventBus.emit('draw:created', f)
|
|
126
|
+
eventBus.emit('draw:created', stripInternalProperties(f))
|
|
120
127
|
} else {
|
|
121
|
-
eventBus.emit('draw:edited', f)
|
|
128
|
+
eventBus.emit('draw:edited', stripInternalProperties(f))
|
|
122
129
|
}
|
|
123
130
|
},
|
|
124
131
|
onCancel: () => {},
|
|
125
132
|
...createVertexSelectionHandlers({ draw, pluginState, mapProvider, eventBus }),
|
|
126
133
|
onUndoChange: (l) => { pluginState.dispatch({ type: 'SET_UNDO_STACK_LENGTH', payload: l }) },
|
|
127
|
-
onUpdate: (f) => { eventBus.emit('draw:updated', f) },
|
|
134
|
+
onUpdate: (f) => { eventBus.emit('draw:updated', stripInternalProperties(f)) },
|
|
128
135
|
onGeometryChange: (e) => {
|
|
129
136
|
// Only commit-level changes (commit-add/move/insert/delete) carry a `phase`.
|
|
130
137
|
// Preview events (e.g. split's live preview) have none and are ignored.
|
|
@@ -141,17 +148,17 @@ function createHandlers ({ appState, appConfig, mapState, pluginState, mapProvid
|
|
|
141
148
|
// The invalid stroke is committed-validity-driven in edit mode only; in draw
|
|
142
149
|
// mode the adapters drive it live from the displayed geometry (placed
|
|
143
150
|
// vertices + cursor) on every rubber-band move.
|
|
144
|
-
if (mode
|
|
151
|
+
if (EDIT_MODES.has(mode)) {
|
|
145
152
|
draw.setInvalid?.(!valid)
|
|
146
153
|
}
|
|
147
154
|
if (!valid) {
|
|
148
|
-
emitGeometryInvalid({ reason, ...context, feature: e.feature })
|
|
155
|
+
emitGeometryInvalid({ reason, ...context, feature: stripInternalProperties(e.feature) })
|
|
149
156
|
}
|
|
150
157
|
},
|
|
151
158
|
// A vertex placement was rejected (hard rule or user callback veto). Surface it
|
|
152
159
|
// on the public bus with phase 'place', and as a hint toast, so the reason is visible.
|
|
153
160
|
onPlacementBlocked: (e) => {
|
|
154
|
-
emitGeometryInvalid(e)
|
|
161
|
+
emitGeometryInvalid({ ...e, feature: stripInternalProperties(e.feature) })
|
|
155
162
|
},
|
|
156
163
|
// Live (mid-drag) validity flip while editing — the displayed shape is exactly
|
|
157
164
|
// what Done finishes there, so it gates the Done button in real time. Emitted
|
|
@@ -88,6 +88,13 @@ describe('button handlers', () => {
|
|
|
88
88
|
expect(mapProvider.activeMoveTarget).toBeNull()
|
|
89
89
|
})
|
|
90
90
|
|
|
91
|
+
test('cancel re-adds the feature when cancelling a point edit too', () => {
|
|
92
|
+
const { buttonConfig, draw } = setup()
|
|
93
|
+
draw.getMode.mockReturnValue('edit_point')
|
|
94
|
+
buttonConfig.drawCancel.onClick()
|
|
95
|
+
expect(draw.add).toHaveBeenCalledWith({ id: 'F' })
|
|
96
|
+
})
|
|
97
|
+
|
|
91
98
|
test('cancel does not re-add outside a vertex edit', () => {
|
|
92
99
|
const { buttonConfig, draw } = setup()
|
|
93
100
|
draw.getMode.mockReturnValue('draw_polygon')
|
|
@@ -308,6 +315,13 @@ describe('geometrychange validation', () => {
|
|
|
308
315
|
expect(draw.setInvalid).toHaveBeenCalledWith(true)
|
|
309
316
|
})
|
|
310
317
|
|
|
318
|
+
test('drives the invalid stroke from committed validity in a point edit too', () => {
|
|
319
|
+
const { draw } = setup()
|
|
320
|
+
draw.getMode.mockReturnValue('edit_point')
|
|
321
|
+
drawHandler(draw, 'geometrychange')({ feature: squareFeature, phase: 'commit-move', vertexIndex: 0 })
|
|
322
|
+
expect(draw.setInvalid).toHaveBeenCalledWith(false)
|
|
323
|
+
})
|
|
324
|
+
|
|
311
325
|
test('a live validity flip (edit drag) drives the Done gate', () => {
|
|
312
326
|
const { draw, dispatch } = setup()
|
|
313
327
|
drawHandler(draw, 'validitychange')({ valid: false, reason: 'Shape must not intersect itself' })
|
|
@@ -2,8 +2,10 @@ import { initialState, actions } from './reducer.js'
|
|
|
2
2
|
import { DrawInit } from './DrawInit.jsx'
|
|
3
3
|
import { newPolygon } from './api/newPolygon.js'
|
|
4
4
|
import { newLine } from './api/newLine.js'
|
|
5
|
+
import { newPoint } from './api/newPoint.js'
|
|
5
6
|
import { editFeature } from './api/editFeature.js'
|
|
6
7
|
import { addFeature } from './api/addFeature.js'
|
|
8
|
+
import { setStyle } from './api/setStyle.js'
|
|
7
9
|
import { deleteFeature } from './api/deleteFeature.js'
|
|
8
10
|
import { split } from './api/split.js'
|
|
9
11
|
import { merge } from './api/merge.js'
|
|
@@ -11,6 +13,11 @@ import { isMac } from '../../../src/utils/isMac.js'
|
|
|
11
13
|
|
|
12
14
|
const DRAW_ACTIONS_SLOT = 'top-middle'
|
|
13
15
|
|
|
16
|
+
// edit_point behaves like edit_vertex for Done/Menu/Undo, but stays out of drawDeletePoint and mergeShapes (neither applies to a single coordinate).
|
|
17
|
+
const EDIT_MODES = new Set(['edit_vertex', 'edit_point'])
|
|
18
|
+
// .has(), not spread — [...aSet] can silently misbehave under a loose-mode Babel build (see OLDrawManager.js's emit()).
|
|
19
|
+
const isEditMode = (mode) => EDIT_MODES.has(mode)
|
|
20
|
+
|
|
14
21
|
// Show the platform-appropriate undo modifier (⌘ on macOS, Ctrl elsewhere).
|
|
15
22
|
const undoCommand = isMac() ? '<kbd>Command</kbd> + <kbd>Z</kbd>' : '<kbd>Ctrl</kbd> + <kbd>Z</kbd>'
|
|
16
23
|
|
|
@@ -43,7 +50,7 @@ export const manifest = {
|
|
|
43
50
|
variant: 'primary',
|
|
44
51
|
exclusiveSlot: true,
|
|
45
52
|
hiddenWhen: ({ appState, pluginState }) =>
|
|
46
|
-
!['draw_polygon', 'draw_line'].includes(pluginState.mode) || appState.interfaceType !== 'touch',
|
|
53
|
+
!['draw_polygon', 'draw_line', 'draw_point'].includes(pluginState.mode) || appState.interfaceType !== 'touch',
|
|
47
54
|
// Disabled while placing at the crosshair would be vetoed (validatePlacement) —
|
|
48
55
|
// driven live so the button never looks active when a tap would do nothing.
|
|
49
56
|
enableWhen: ({ pluginState }) => pluginState.canAddPoint,
|
|
@@ -54,27 +61,30 @@ export const manifest = {
|
|
|
54
61
|
label: 'Done',
|
|
55
62
|
variant: 'primary',
|
|
56
63
|
exclusiveSlot: true,
|
|
57
|
-
hiddenWhen: ({ pluginState }) => !['draw_polygon', 'draw_line'
|
|
64
|
+
hiddenWhen: ({ pluginState }) => !(['draw_polygon', 'draw_line'].includes(pluginState.mode) || isEditMode(pluginState.mode)),
|
|
58
65
|
enableWhen: ({ pluginState }) => {
|
|
59
66
|
const { mode, geometryValid } = pluginState
|
|
60
67
|
// Min-vertices, area and self-intersection are all enforced by the validation
|
|
61
68
|
// rules via geometryValid, so the gate is simply "is the geometry valid now".
|
|
62
|
-
return ['draw_polygon', 'draw_line'
|
|
69
|
+
return (['draw_polygon', 'draw_line'].includes(mode) || isEditMode(mode)) && geometryValid
|
|
63
70
|
},
|
|
64
71
|
...createButtonSlots(true)
|
|
65
72
|
},
|
|
66
73
|
{
|
|
67
74
|
id: 'drawMenu',
|
|
68
|
-
label: ({ pluginState }) => pluginState.mode
|
|
75
|
+
label: ({ pluginState }) => isEditMode(pluginState.mode) ? 'Edit actions' : 'Draw actions',
|
|
69
76
|
iconId: 'menu',
|
|
70
77
|
exclusiveSlot: true,
|
|
71
|
-
|
|
78
|
+
// draw_point belongs here too — it has no Undo (nothing to undo before a single-click
|
|
79
|
+
// commit, gated separately below) and no delete-vertex, but it DOES support snapping,
|
|
80
|
+
// and the Snap toggle is a menuItem living inside this same button.
|
|
81
|
+
hiddenWhen: ({ pluginState }) => !(['draw_polygon', 'draw_line', 'draw_point'].includes(pluginState.mode) || isEditMode(pluginState.mode)),
|
|
72
82
|
menuItems: [
|
|
73
83
|
{
|
|
74
84
|
id: 'drawUndo',
|
|
75
85
|
label: 'Undo',
|
|
76
86
|
iconId: 'undo',
|
|
77
|
-
hiddenWhen: ({ pluginState }) => !['draw_polygon', 'draw_line'
|
|
87
|
+
hiddenWhen: ({ pluginState }) => !(['draw_polygon', 'draw_line'].includes(pluginState.mode) || isEditMode(pluginState.mode)),
|
|
78
88
|
enableWhen: ({ pluginState }) => {
|
|
79
89
|
if (['draw_polygon', 'draw_line'].includes(pluginState.mode)) {
|
|
80
90
|
return pluginState.numVertices > 0
|
|
@@ -161,8 +171,10 @@ export const manifest = {
|
|
|
161
171
|
api: {
|
|
162
172
|
newPolygon,
|
|
163
173
|
newLine,
|
|
174
|
+
newPoint,
|
|
164
175
|
editFeature,
|
|
165
176
|
addFeature,
|
|
177
|
+
setStyle,
|
|
166
178
|
deleteFeature,
|
|
167
179
|
split,
|
|
168
180
|
merge
|