@defra/interactive-map 0.0.39-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/api.md +4 -4
- package/docs/assets/images/slot-map.svg +61 -262
- package/docs/examples/draw-tools.mdx +3 -3
- package/docs/plugins/draw.md +80 -17
- 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/esm/im-map-styles-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.js +2 -3
- package/plugins/datasets/src/adapters/esri/esriLayerAdapter.test.js +175 -0
- package/plugins/datasets/src/adapters/esri/registry/esriDataset.js +1 -1
- package/plugins/datasets/src/adapters/esri/registry/esriDataset.test.js +55 -0
- package/plugins/datasets/src/adapters/loadLayerAdapter.js +1 -1
- package/plugins/datasets/src/adapters/loadLayerAdapter.test.js +61 -0
- package/plugins/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +1 -3
- package/plugins/datasets/src/adapters/maplibre/maplibreLayerAdapter.test.js +17 -3
- 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 -5
- 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 +15 -4
- package/plugins/datasets/src/registry/dataset.test.js +61 -0
- package/plugins/datasets/src/registry/datasetRegistry.js +12 -9
- package/plugins/datasets/src/registry/datasetRegistry.test.js +17 -5
- package/plugins/datasets/src/registry/globalDataset.js +0 -3
- 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 +12 -4
- package/plugins/draw/src/DrawInit.test.jsx +51 -10
- package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.js +92 -12
- 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/drawMode/pointerHandlers.js +1 -1
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/pointerHandlers.test.js +1 -1
- 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 +5 -28
- 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 +4 -54
- 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 +11 -4
- 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.js +1 -0
- package/plugins/draw/src/adapters/openlayers/draw/DrawMode.test.js +27 -1
- package/plugins/draw/src/adapters/openlayers/draw/drawInput.js +24 -4
- package/plugins/draw/src/adapters/openlayers/draw/drawInput.test.js +47 -3
- package/plugins/draw/src/adapters/openlayers/edit/EditMode.js +1 -1
- package/plugins/draw/src/adapters/openlayers/edit/EditMode.test.js +32 -1
- package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.js +9 -5
- package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.test.js +34 -1
- 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 +12 -5
- package/plugins/draw/src/adapters/openlayers/edit/nudge.test.js +26 -3
- 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 +21 -14
- package/plugins/draw/src/events.test.js +15 -1
- 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/useCrossHairVisibility.js +12 -4
- package/plugins/interact/src/hooks/useCrossHairVisibility.test.js +88 -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 +7 -5
- 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/sonar-project.properties +7 -1
- package/src/App/components/Actions/Actions.test.jsx +47 -0
- package/src/App/components/Attributions/Attributions.module.scss +10 -0
- package/src/App/components/Hints/Hints.jsx +7 -2
- package/src/App/components/Hints/Hints.test.jsx +13 -0
- package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +2 -3
- package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +3 -6
- package/src/App/components/{MoveControl/MoveControl.jsx → MoveControls/MoveControls.jsx} +10 -10
- package/src/App/components/{MoveControl/MoveControl.module.scss → MoveControls/MoveControls.module.scss} +60 -29
- package/src/App/components/{MoveControl/MoveControl.test.jsx → MoveControls/MoveControls.test.jsx} +48 -48
- 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/components/Viewport/Viewport.jsx +9 -1
- package/src/App/components/Viewport/Viewport.test.jsx +11 -0
- 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/initialiseApp.js +11 -8
- package/src/App/initialiseApp.test.js +15 -3
- package/src/App/layout/Layout.jsx +12 -11
- package/src/App/layout/Layout.test.jsx +2 -0
- package/src/App/layout/layout.module.scss +31 -17
- package/src/App/registry/keyboardShortcutRegistry.js +25 -13
- package/src/App/registry/keyboardShortcutRegistry.test.js +49 -32
- package/src/App/registry/pluginRegistry.js +2 -3
- package/src/App/registry/pluginRegistry.test.js +16 -3
- 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 +4 -2
- package/src/App/store/appDispatchMiddleware.js +1 -1
- package/src/App/store/appDispatchMiddleware.test.js +8 -0
- package/src/InteractiveMap/InteractiveMap.js +8 -2
- package/src/InteractiveMap/InteractiveMap.test.js +20 -3
- package/src/config/appConfig.js +17 -15
- package/src/config/appConfig.test.js +22 -22
- package/src/config/defaults.js +3 -1
- package/src/config/events.js +2 -0
- package/src/config/mergeConfig.js +7 -0
- package/src/config/mergeConfig.test.js +20 -0
- package/src/scss/main.scss +1 -1
- 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/test-utils.js +5 -0
- package/src/types.js +25 -7
- 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
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { symbolRegistry } from '../../../../../../src/services/symbolRegistry.js'
|
|
2
|
+
import { getOrCreateSymbolImage } from '../../../../../../providers/beta/openlayers/src/utils/symbolImages.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolves a drawn point's symbol-config icon and writes the resolved image id back onto the
|
|
6
|
+
* OL feature, so core/styles.js's createFeatureStyle() can render it via an ol/style/Icon.
|
|
7
|
+
* Also resolves and caches the 'active'/'selected' variants as symbolActiveImageId/
|
|
8
|
+
* symbolSelectedImageId, read directly by highlightFeatures.js's selection overlay.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const VARIANTS = ['normal', 'active', 'selected']
|
|
12
|
+
const PROPERTY_FOR_VARIANT = { normal: 'symbolImageId', active: 'symbolActiveImageId', selected: 'symbolSelectedImageId' }
|
|
13
|
+
|
|
14
|
+
export const hasSymbolStyle = (properties) => !!(properties?.symbol || properties?.symbolSvgContent)
|
|
15
|
+
|
|
16
|
+
// Combines the browser's native devicePixelRatio with this app's own map-size scale factor
|
|
17
|
+
// (mapProvider.drawScale), matching MapController.jsx's construction-time formula.
|
|
18
|
+
export const getPixelRatio = (mapProvider) => (globalThis.devicePixelRatio || 1) * (mapProvider?.drawScale ?? 1)
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Resolve one point feature's symbol image and write it back onto the feature.
|
|
22
|
+
* A no-op for points with no symbol config (nothing to render as an icon).
|
|
23
|
+
*
|
|
24
|
+
* @param {Object} params
|
|
25
|
+
* @param {Object} params.manager - OLDrawManager (needs store.source, mapStyle)
|
|
26
|
+
* @param {Object} params.mapProvider
|
|
27
|
+
* @param {import('ol/Feature.js').default} params.olFeature
|
|
28
|
+
* @returns {Promise<void>}
|
|
29
|
+
*/
|
|
30
|
+
export const resolvePointSymbol = async ({ manager, mapProvider, olFeature }) => {
|
|
31
|
+
const properties = olFeature.getProperties()
|
|
32
|
+
if (!hasSymbolStyle(properties)) {
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const pixelRatio = getPixelRatio(mapProvider)
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const results = await Promise.all(
|
|
40
|
+
VARIANTS.map((variant) => symbolRegistry.rasteriseSymbolImage(properties, manager.mapStyle, variant, pixelRatio))
|
|
41
|
+
)
|
|
42
|
+
// All three variants resolve from the same symbol config, so either all succeed or (an
|
|
43
|
+
// unresolvable symbol id) all fail together — gating on 'normal' alone is enough.
|
|
44
|
+
if (!results[0]) {
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// The feature may have been deleted/cancelled while rasterising was in flight.
|
|
49
|
+
if (!manager.store.source.hasFeature(olFeature)) {
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
VARIANTS.forEach((variant, i) => {
|
|
53
|
+
const result = results[i]
|
|
54
|
+
if (!result) { return }
|
|
55
|
+
getOrCreateSymbolImage(result.imageId, result.imageData)
|
|
56
|
+
olFeature.set(PROPERTY_FOR_VARIANT[variant], result.imageId)
|
|
57
|
+
})
|
|
58
|
+
// ol/style/Icon draws its source canvas at native pixel size — core/styles.js reads this
|
|
59
|
+
// back to apply `scale: 1 / symbolPixelRatio`, correcting for the canvas being rasterised
|
|
60
|
+
// at pixelRatio device pixels for crispness.
|
|
61
|
+
olFeature.set('symbolPixelRatio', pixelRatio)
|
|
62
|
+
} catch (err) {
|
|
63
|
+
// A silent failure here means a point simply never gets/keeps an icon, with nothing in
|
|
64
|
+
// the UI to explain why — surface it instead of letting the rejection vanish.
|
|
65
|
+
console.error('[draw] failed to resolve point symbol', olFeature.getId(), err) // NOSONAR
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Re-resolve every existing drawn point's symbol — called on map style/size change, since
|
|
71
|
+
* the rasterised image (colours, and pixel-ratio-scaled dimensions) depends on both.
|
|
72
|
+
*
|
|
73
|
+
* @param {Object} params
|
|
74
|
+
* @param {Object} params.manager
|
|
75
|
+
* @param {Object} params.mapProvider
|
|
76
|
+
* @returns {Promise<void>}
|
|
77
|
+
*/
|
|
78
|
+
export const refreshAllPointSymbols = ({ manager, mapProvider }) => {
|
|
79
|
+
const points = manager.store.source.getFeatures().filter(
|
|
80
|
+
(f) => f.getGeometry()?.getType() === 'Point' && hasSymbolStyle(f.getProperties())
|
|
81
|
+
)
|
|
82
|
+
return Promise.all(points.map((olFeature) => resolvePointSymbol({ manager, mapProvider, olFeature })))
|
|
83
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { hasSymbolStyle, getPixelRatio, resolvePointSymbol, refreshAllPointSymbols } from './pointSymbolImages.js'
|
|
2
|
+
import { symbolRegistry } from '../../../../../../src/services/symbolRegistry.js'
|
|
3
|
+
import { getCachedSymbolImage, clearSymbolImageCache } from '../../../../../../providers/beta/openlayers/src/utils/symbolImages.js'
|
|
4
|
+
|
|
5
|
+
const mapStyle = { id: 'outdoor', mapColorScheme: 'light' }
|
|
6
|
+
|
|
7
|
+
const createMapProvider = ({ drawScale = 1 } = {}) => ({ drawScale })
|
|
8
|
+
|
|
9
|
+
// A minimal ol.Feature stand-in — resolvePointSymbol only ever calls
|
|
10
|
+
// getProperties()/set() on it, and manager.store.source.hasFeature() to check liveness.
|
|
11
|
+
const createOlFeature = (properties) => {
|
|
12
|
+
const props = { ...properties }
|
|
13
|
+
return {
|
|
14
|
+
getProperties: () => props,
|
|
15
|
+
set: jest.fn((key, value) => { props[key] = value })
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const createManager = ({ features = [] } = {}) => ({
|
|
20
|
+
mapStyle,
|
|
21
|
+
store: {
|
|
22
|
+
source: {
|
|
23
|
+
hasFeature: jest.fn((f) => features.includes(f)),
|
|
24
|
+
getFeatures: jest.fn(() => features)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
// symbolRegistry.rasteriseSymbolImage() draws an SVG through a real `new Image()`/onload
|
|
30
|
+
// round-trip that jsdom never resolves — every other test file touching it (e.g. the
|
|
31
|
+
// MapLibre adapter's pointSymbolImages.test.js) instead lets a mocked provider call
|
|
32
|
+
// (addSymbolsToMap) stand in for it. This adapter calls rasteriseSymbolImage directly (it
|
|
33
|
+
// *is* the last-mile pipeline, with no provider-level call to mock instead), so it's stubbed
|
|
34
|
+
// here with a fake resolved ImageData keyed to the properties/pixelRatio, matching
|
|
35
|
+
// getSymbolImageId's own id shape closely enough for assertions to key off it.
|
|
36
|
+
const stubRasterise = () => jest.spyOn(symbolRegistry, 'rasteriseSymbolImage')
|
|
37
|
+
.mockImplementation(async (style, ms, variant, pixelRatio) => ({
|
|
38
|
+
// variant folded into the id (mirroring the real act-/sel- prefixing) so normal/active/
|
|
39
|
+
// selected resolve to distinct ids, the way real symbol configs do.
|
|
40
|
+
imageId: `${variant}-${symbolRegistry.getSymbolImageId(style, ms, false, pixelRatio)}`,
|
|
41
|
+
imageData: { width: 10, height: 10 }
|
|
42
|
+
}))
|
|
43
|
+
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
clearSymbolImageCache()
|
|
46
|
+
HTMLCanvasElement.prototype.getContext = jest.fn(function () {
|
|
47
|
+
this._ctx ??= { putImageData: jest.fn() }
|
|
48
|
+
return this._ctx
|
|
49
|
+
})
|
|
50
|
+
stubRasterise()
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
afterEach(() => jest.restoreAllMocks())
|
|
54
|
+
|
|
55
|
+
describe('hasSymbolStyle', () => {
|
|
56
|
+
it('is true when symbol is set', () => {
|
|
57
|
+
expect(hasSymbolStyle({ symbol: 'pin' })).toBe(true)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('is true when symbolSvgContent is set', () => {
|
|
61
|
+
expect(hasSymbolStyle({ symbolSvgContent: '<path/>' })).toBe(true)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('is false with neither, or no properties at all', () => {
|
|
65
|
+
expect(hasSymbolStyle({})).toBe(false)
|
|
66
|
+
expect(hasSymbolStyle(undefined)).toBe(false)
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
describe('getPixelRatio', () => {
|
|
71
|
+
it('combines devicePixelRatio and mapProvider.drawScale', () => {
|
|
72
|
+
const restore = globalThis.devicePixelRatio
|
|
73
|
+
globalThis.devicePixelRatio = 2
|
|
74
|
+
expect(getPixelRatio(createMapProvider({ drawScale: 1.5 }))).toBe(3)
|
|
75
|
+
globalThis.devicePixelRatio = restore
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('falls back to 1 for either factor when unset', () => {
|
|
79
|
+
const restore = globalThis.devicePixelRatio
|
|
80
|
+
delete globalThis.devicePixelRatio
|
|
81
|
+
expect(getPixelRatio(createMapProvider({ drawScale: undefined }))).toBe(1)
|
|
82
|
+
expect(getPixelRatio(undefined)).toBe(1)
|
|
83
|
+
globalThis.devicePixelRatio = restore
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
describe('resolvePointSymbol', () => {
|
|
88
|
+
it('does nothing for a feature with no symbol config', async () => {
|
|
89
|
+
const olFeature = createOlFeature({})
|
|
90
|
+
const manager = createManager()
|
|
91
|
+
await resolvePointSymbol({ manager, mapProvider: createMapProvider(), olFeature })
|
|
92
|
+
expect(olFeature.set).not.toHaveBeenCalled()
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('rasterises, caches the canvas and writes symbolImageId/symbolPixelRatio back onto the feature', async () => {
|
|
96
|
+
const properties = { symbol: 'pin' }
|
|
97
|
+
const olFeature = createOlFeature(properties)
|
|
98
|
+
const manager = createManager({ features: [olFeature] })
|
|
99
|
+
const mapProvider = createMapProvider({ drawScale: 2 })
|
|
100
|
+
|
|
101
|
+
await resolvePointSymbol({ manager, mapProvider, olFeature })
|
|
102
|
+
|
|
103
|
+
const pixelRatio = getPixelRatio(mapProvider)
|
|
104
|
+
const baseId = symbolRegistry.getSymbolImageId(properties, mapStyle, false, pixelRatio)
|
|
105
|
+
expect(olFeature.set).toHaveBeenCalledWith('symbolImageId', `normal-${baseId}`)
|
|
106
|
+
// symbolPixelRatio is what core/styles.js later uses to scale the (pixelRatio×-oversized)
|
|
107
|
+
// cached canvas back down to its intended CSS display size.
|
|
108
|
+
expect(olFeature.set).toHaveBeenCalledWith('symbolPixelRatio', pixelRatio)
|
|
109
|
+
expect(getCachedSymbolImage(`normal-${baseId}`)).toBeInstanceOf(HTMLCanvasElement)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('also rasterises, caches and writes the active/selected variants — one image id each, keyed to the same symbol config', async () => {
|
|
113
|
+
const properties = { symbol: 'pin' }
|
|
114
|
+
const olFeature = createOlFeature(properties)
|
|
115
|
+
const manager = createManager({ features: [olFeature] })
|
|
116
|
+
const mapProvider = createMapProvider()
|
|
117
|
+
|
|
118
|
+
await resolvePointSymbol({ manager, mapProvider, olFeature })
|
|
119
|
+
|
|
120
|
+
const pixelRatio = getPixelRatio(mapProvider)
|
|
121
|
+
const baseId = symbolRegistry.getSymbolImageId(properties, mapStyle, false, pixelRatio)
|
|
122
|
+
expect(olFeature.set).toHaveBeenCalledWith('symbolActiveImageId', `active-${baseId}`)
|
|
123
|
+
expect(olFeature.set).toHaveBeenCalledWith('symbolSelectedImageId', `selected-${baseId}`)
|
|
124
|
+
expect(getCachedSymbolImage(`active-${baseId}`)).toBeInstanceOf(HTMLCanvasElement)
|
|
125
|
+
expect(getCachedSymbolImage(`selected-${baseId}`)).toBeInstanceOf(HTMLCanvasElement)
|
|
126
|
+
// objectContaining, not the exact `properties` object — resolvePointSymbol's own
|
|
127
|
+
// property-write calls mutate that same (by-reference) object in place afterwards, and
|
|
128
|
+
// jest's mock.calls records the reference, not a snapshot at call time.
|
|
129
|
+
const symbolMatch = expect.objectContaining({ symbol: 'pin' })
|
|
130
|
+
expect(symbolRegistry.rasteriseSymbolImage).toHaveBeenCalledWith(symbolMatch, mapStyle, 'normal', pixelRatio)
|
|
131
|
+
expect(symbolRegistry.rasteriseSymbolImage).toHaveBeenCalledWith(symbolMatch, mapStyle, 'active', pixelRatio)
|
|
132
|
+
expect(symbolRegistry.rasteriseSymbolImage).toHaveBeenCalledWith(symbolMatch, mapStyle, 'selected', pixelRatio)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('skips just the active/selected properties if only those variants resolve null (normal still succeeds)', async () => {
|
|
136
|
+
const properties = { symbol: 'pin' }
|
|
137
|
+
const olFeature = createOlFeature(properties)
|
|
138
|
+
const manager = createManager({ features: [olFeature] })
|
|
139
|
+
symbolRegistry.rasteriseSymbolImage
|
|
140
|
+
.mockImplementationOnce(async (style, ms, variant, pixelRatio) => // normal — real stub behaviour
|
|
141
|
+
({ imageId: `${variant}-${symbolRegistry.getSymbolImageId(style, ms, false, pixelRatio)}`, imageData: { width: 10, height: 10 } }))
|
|
142
|
+
.mockResolvedValueOnce(null) // active
|
|
143
|
+
.mockResolvedValueOnce(null) // selected
|
|
144
|
+
|
|
145
|
+
await resolvePointSymbol({ manager, mapProvider: createMapProvider(), olFeature })
|
|
146
|
+
|
|
147
|
+
expect(olFeature.set).toHaveBeenCalledWith('symbolImageId', expect.any(String))
|
|
148
|
+
expect(olFeature.set).not.toHaveBeenCalledWith('symbolActiveImageId', expect.anything())
|
|
149
|
+
expect(olFeature.set).not.toHaveBeenCalledWith('symbolSelectedImageId', expect.anything())
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('does nothing when rasterisation resolves null (e.g. an unresolvable symbol id)', async () => {
|
|
153
|
+
const properties = { symbol: 'not-a-real-symbol' }
|
|
154
|
+
const olFeature = createOlFeature(properties)
|
|
155
|
+
const manager = createManager({ features: [olFeature] })
|
|
156
|
+
symbolRegistry.rasteriseSymbolImage.mockResolvedValueOnce(null)
|
|
157
|
+
|
|
158
|
+
await resolvePointSymbol({ manager, mapProvider: createMapProvider(), olFeature })
|
|
159
|
+
|
|
160
|
+
expect(olFeature.set).not.toHaveBeenCalled()
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('does not write back if the feature was removed from the source while rasterising was in flight', async () => {
|
|
164
|
+
const properties = { symbol: 'pin' }
|
|
165
|
+
const olFeature = createOlFeature(properties)
|
|
166
|
+
const manager = createManager({ features: [] }) // not present in the source
|
|
167
|
+
|
|
168
|
+
await resolvePointSymbol({ manager, mapProvider: createMapProvider(), olFeature })
|
|
169
|
+
|
|
170
|
+
expect(olFeature.set).not.toHaveBeenCalled()
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it('surfaces (rather than swallows) a rasterisation failure', async () => {
|
|
174
|
+
const properties = { symbol: 'pin' }
|
|
175
|
+
const olFeature = createOlFeature(properties)
|
|
176
|
+
olFeature.getId = () => 'p1'
|
|
177
|
+
const manager = createManager({ features: [olFeature] })
|
|
178
|
+
const error = new Error('rasterise failed')
|
|
179
|
+
symbolRegistry.rasteriseSymbolImage.mockRejectedValueOnce(error)
|
|
180
|
+
const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {})
|
|
181
|
+
|
|
182
|
+
await resolvePointSymbol({ manager, mapProvider: createMapProvider(), olFeature })
|
|
183
|
+
|
|
184
|
+
expect(olFeature.set).not.toHaveBeenCalled()
|
|
185
|
+
expect(consoleError).toHaveBeenCalledWith('[draw] failed to resolve point symbol', 'p1', error)
|
|
186
|
+
})
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
describe('refreshAllPointSymbols', () => {
|
|
190
|
+
const feature = (getType, properties) => {
|
|
191
|
+
const f = createOlFeature(properties)
|
|
192
|
+
f.getGeometry = () => ({ getType: () => getType })
|
|
193
|
+
return f
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
it('re-resolves every point feature with a symbol config, ignoring non-point and unstyled features', async () => {
|
|
197
|
+
const pointWithSymbol = feature('Point', { symbol: 'pin' })
|
|
198
|
+
const pointWithoutSymbol = feature('Point', {})
|
|
199
|
+
const polygon = feature('Polygon', { symbol: 'pin' })
|
|
200
|
+
const manager = createManager({ features: [pointWithSymbol, pointWithoutSymbol, polygon] })
|
|
201
|
+
manager.store.source.hasFeature = jest.fn(() => true)
|
|
202
|
+
|
|
203
|
+
await refreshAllPointSymbols({ manager, mapProvider: createMapProvider() })
|
|
204
|
+
|
|
205
|
+
expect(pointWithSymbol.set).toHaveBeenCalledWith('symbolImageId', expect.any(String))
|
|
206
|
+
expect(pointWithoutSymbol.set).not.toHaveBeenCalled()
|
|
207
|
+
expect(polygon.set).not.toHaveBeenCalled()
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it('does nothing when there are no drawn points', async () => {
|
|
211
|
+
const manager = createManager({ features: [] })
|
|
212
|
+
await expect(refreshAllPointSymbols({ manager, mapProvider: createMapProvider() })).resolves.toEqual([])
|
|
213
|
+
})
|
|
214
|
+
})
|
|
@@ -37,7 +37,9 @@ export const createSnapIndicator = (map, colors) => {
|
|
|
37
37
|
const layer = new VectorLayer({
|
|
38
38
|
source,
|
|
39
39
|
style: (f) => styles[f.get('snapType')] ?? null,
|
|
40
|
-
zIndex
|
|
40
|
+
// Below the draw layer (zIndex 100) and its vertex/midpoint handles (101-103), matching
|
|
41
|
+
// the ML adapter — the indicator sits behind the symbols/vertices it's snapping to.
|
|
42
|
+
zIndex: 99,
|
|
41
43
|
updateWhileAnimating: true,
|
|
42
44
|
updateWhileInteracting: true
|
|
43
45
|
})
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* mapBrowserEvent.coordinate to the nearest snap candidate before any draw
|
|
4
4
|
* or modify interaction sees it.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* The visual indicator updates on both pointermove (hover preview) and pointerdrag (an active
|
|
7
|
+
* OL Modify drag), staying visible through to release — nothing hides it on release; it only
|
|
8
|
+
* clears on a fresh query, a mode change, or snapping being switched off.
|
|
9
9
|
*
|
|
10
10
|
* Must be added to the map AFTER the Draw/Modify interaction so it is processed
|
|
11
11
|
* first (OL iterates interactions in reverse-add order).
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import Interaction from 'ol/interaction/Interaction.js'
|
|
16
16
|
|
|
17
17
|
const SNAP_EVENTS = new Set(['pointermove', 'pointerdrag', 'pointerdown', 'pointerup', 'singleclick', 'click'])
|
|
18
|
+
const INDICATOR_EVENTS = new Set(['pointermove', 'pointerdrag'])
|
|
18
19
|
|
|
19
20
|
const processSnapEvent = (mapBrowserEvent, engine, indicator, snapRadius, isIndicatorActive) => {
|
|
20
21
|
const { type } = mapBrowserEvent
|
|
@@ -33,13 +34,9 @@ const processSnapEvent = (mapBrowserEvent, engine, indicator, snapRadius, isIndi
|
|
|
33
34
|
mapBrowserEvent.coordinate = result.coord.slice()
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
//
|
|
37
|
-
if (type
|
|
37
|
+
// Hover preview and active drag both keep the indicator live; no update for click/down/up.
|
|
38
|
+
if (INDICATOR_EVENTS.has(type) && isIndicatorActive()) {
|
|
38
39
|
result ? indicator.show(result.coord, result.type) : indicator.hide()
|
|
39
|
-
} else if (type === 'pointerdrag') {
|
|
40
|
-
indicator.hide()
|
|
41
|
-
} else {
|
|
42
|
-
// no indicator update for click/down/up events
|
|
43
40
|
}
|
|
44
41
|
}
|
|
45
42
|
|
|
@@ -35,7 +35,7 @@ test('rewrites the event coordinate to a copy of the snap candidate for all poin
|
|
|
35
35
|
}
|
|
36
36
|
})
|
|
37
37
|
|
|
38
|
-
test('the indicator follows free mouse movement
|
|
38
|
+
test('the indicator follows both free mouse movement and an active drag', () => {
|
|
39
39
|
const { engine, indicator, fire } = setup()
|
|
40
40
|
fire('pointermove')
|
|
41
41
|
expect(indicator.show).toHaveBeenCalledWith([9, 9], 'vertex')
|
|
@@ -44,17 +44,28 @@ test('the indicator follows free mouse movement only', () => {
|
|
|
44
44
|
fire('pointermove')
|
|
45
45
|
expect(indicator.hide).toHaveBeenCalledTimes(1)
|
|
46
46
|
|
|
47
|
+
// A drag (e.g. edit_vertex's mouse-drag path, edit/modifyInteraction.js) keeps it live too —
|
|
48
|
+
// it must stay visible for as long as the dragged vertex sits on a candidate, not just on a
|
|
49
|
+
// free hover. See snapInteraction.js's own comment for why: this used to force-hide it on
|
|
50
|
+
// every pointerdrag, so the indicator could never actually be seen mid-drag.
|
|
47
51
|
engine.query.mockReturnValue({ type: 'edge', coord: [5, 5] })
|
|
48
52
|
fire('pointerdrag')
|
|
49
|
-
expect(indicator.
|
|
53
|
+
expect(indicator.show).toHaveBeenCalledWith([5, 5], 'edge')
|
|
54
|
+
|
|
55
|
+
engine.query.mockReturnValue(null)
|
|
56
|
+
fire('pointerdrag')
|
|
57
|
+
expect(indicator.hide).toHaveBeenCalledTimes(2)
|
|
58
|
+
|
|
50
59
|
fire('singleclick')
|
|
51
|
-
expect(indicator.show).toHaveBeenCalledTimes(
|
|
60
|
+
expect(indicator.show).toHaveBeenCalledTimes(2) // clicks never touch the indicator
|
|
61
|
+
expect(indicator.hide).toHaveBeenCalledTimes(2)
|
|
52
62
|
})
|
|
53
63
|
|
|
54
|
-
test('the indicator stays untouched during pointermove when the indicator is gated off', () => {
|
|
64
|
+
test('the indicator stays untouched during pointermove or a drag when the indicator is gated off', () => {
|
|
55
65
|
const { indicator, fire } = setup({ indicatorActive: false })
|
|
56
66
|
const { event } = fire('pointermove')
|
|
57
67
|
expect(event.coordinate).toEqual([9, 9]) // snapping still applies
|
|
68
|
+
fire('pointerdrag')
|
|
58
69
|
expect(indicator.show).not.toHaveBeenCalled()
|
|
59
70
|
expect(indicator.hide).not.toHaveBeenCalled()
|
|
60
71
|
})
|
|
@@ -16,7 +16,7 @@ export const pixelToCoord = (map, pixel) => {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/** Pixel distance between two { x, y } points */
|
|
19
|
-
export const pixelDist = (a, b) => Math.
|
|
19
|
+
export const pixelDist = (a, b) => Math.hypot(a.x - b.x, a.y - b.y)
|
|
20
20
|
|
|
21
21
|
/** OL pixel array [x, y] → { x, y } */
|
|
22
22
|
export const arrayToPixel = ([x, y]) => ({ x, y })
|
|
@@ -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
|
+
}
|