@defra/interactive-map 0.0.36-alpha → 0.0.38-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/assets/templates/draw-tools.njk +4 -3
- 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.md +32 -0
- package/docs/examples/draw-tools.mdx +0 -4
- package/docs/plugins/draw.md +482 -0
- package/docs/plugins.md +2 -6
- package/govuk-prototype-kit.config.json +3 -2
- package/package.json +2 -4
- package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
- package/plugins/beta/draw-es/src/events.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/css/index.css +1 -97
- package/plugins/datasets/dist/esm/esriLayerAdapter.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-plugin.js +1 -1
- package/plugins/datasets/src/adapters/esri/esriLayerAdapter.js +13 -0
- package/plugins/draw/dist/esm/im-draw-ml-adapter.js +1 -0
- package/plugins/draw/dist/esm/im-draw-ol-adapter.js +1 -0
- package/plugins/draw/dist/esm/im-draw-plugin.js +1 -0
- package/plugins/draw/dist/esm/index.js +1 -0
- package/plugins/draw/dist/umd/im-draw-ml-adapter.js +1 -0
- package/plugins/draw/dist/umd/im-draw-ol-adapter.js +2 -0
- package/plugins/draw/dist/umd/im-draw-ol-adapter.js.LICENSE.txt +1 -0
- package/plugins/draw/dist/umd/im-draw-plugin.js +2 -0
- package/plugins/draw/dist/umd/im-draw-plugin.js.LICENSE.txt +1 -0
- package/plugins/draw/dist/umd/index.js +2 -0
- package/plugins/draw/dist/umd/index.js.LICENSE.txt +1 -0
- package/plugins/draw/src/DrawInit.jsx +89 -0
- package/plugins/draw/src/DrawInit.test.jsx +184 -0
- package/plugins/draw/src/adapterEvents.js +127 -0
- package/plugins/draw/src/adapterEvents.test.js +20 -0
- package/plugins/draw/src/adapters/adapterContract.test.js +48 -0
- package/plugins/draw/src/adapters/loadDrawAdapter.js +14 -0
- package/plugins/draw/src/adapters/loadDrawAdapter.test.js +53 -0
- package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.js +367 -0
- package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.test.js +799 -0
- package/plugins/draw/src/adapters/maplibre/defaults.js +1 -0
- package/plugins/draw/src/adapters/maplibre/drawEvents.js +35 -0
- package/plugins/draw/src/adapters/maplibre/mapboxDraw.js +126 -0
- package/plugins/draw/src/adapters/maplibre/mapboxDraw.test.js +268 -0
- package/plugins/draw/src/adapters/maplibre/mapboxSnap.js +39 -0
- package/plugins/draw/src/adapters/maplibre/mapboxSnap.test.js +91 -0
- package/plugins/draw/src/adapters/maplibre/modes/createDrawMode.js +58 -0
- package/plugins/draw/src/adapters/maplibre/modes/createDrawMode.test.js +53 -0
- package/plugins/draw/src/adapters/maplibre/modes/disabledMode.js +23 -0
- package/plugins/draw/src/adapters/maplibre/modes/disabledMode.test.js +21 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawLineMode.js +15 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawLineMode.test.js +37 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/__helpers__/harness.js +115 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/clickHandlers.js +193 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/clickHandlers.test.js +241 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/keyboardHandlers.js +78 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/keyboardHandlers.test.js +75 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.js +78 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.test.js +48 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/pointerHandlers.js +103 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/pointerHandlers.test.js +127 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/renderHelpers.js +59 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/renderHelpers.test.js +34 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/undoHandlers.js +193 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/undoHandlers.test.js +195 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawPolygonMode.js +16 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawPolygonMode.test.js +27 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/__helpers__/harness.js +107 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/geometryHelpers.js +139 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/geometryHelpers.test.js +46 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/helpers.js +2 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/helpers.test.js +17 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.js +151 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.test.js +123 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/pointerHandlers.js +161 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/pointerHandlers.test.js +98 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.js +133 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.test.js +105 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.js +166 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.test.js +133 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.js +210 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.test.js +167 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexQueries.js +121 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexQueries.test.js +73 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.js +249 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.test.js +153 -0
- package/plugins/draw/src/adapters/maplibre/snap/constants.js +2 -0
- package/plugins/draw/src/adapters/maplibre/snap/mapHandlers.js +39 -0
- package/plugins/draw/src/adapters/maplibre/snap/mapHandlers.test.js +106 -0
- package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.js +148 -0
- package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.test.js +238 -0
- package/plugins/draw/src/adapters/maplibre/snap/snapInstance.js +115 -0
- package/plugins/draw/src/adapters/maplibre/snap/snapInstance.test.js +151 -0
- package/plugins/draw/src/adapters/maplibre/snap/sourceData.js +34 -0
- package/plugins/draw/src/adapters/maplibre/snap/sourceData.test.js +62 -0
- package/plugins/draw/src/adapters/maplibre/styles.js +207 -0
- package/plugins/draw/src/adapters/maplibre/styles.test.js +155 -0
- package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.js +202 -0
- package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.test.js +253 -0
- package/plugins/draw/src/adapters/maplibre/utils/touchClickWorkaround.js +57 -0
- package/plugins/draw/src/adapters/maplibre/utils/touchClickWorkaround.test.js +106 -0
- package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.js +117 -0
- package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.test.js +159 -0
- package/plugins/draw/src/adapters/openlayers/__helpers__/harness.js +90 -0
- package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.js +178 -0
- package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.test.js +153 -0
- package/plugins/draw/src/adapters/openlayers/core/featureStore.js +69 -0
- package/plugins/draw/src/adapters/openlayers/core/featureStore.test.js +58 -0
- package/plugins/draw/src/adapters/openlayers/core/internalEvents.js +8 -0
- package/plugins/draw/src/adapters/openlayers/core/styles.js +155 -0
- package/plugins/draw/src/adapters/openlayers/core/styles.test.js +143 -0
- package/plugins/draw/src/adapters/openlayers/defaults.js +1 -0
- package/plugins/draw/src/adapters/openlayers/draw/DrawMode.js +272 -0
- package/plugins/draw/src/adapters/openlayers/draw/DrawMode.test.js +431 -0
- package/plugins/draw/src/adapters/openlayers/draw/drawInput.js +139 -0
- package/plugins/draw/src/adapters/openlayers/draw/drawInput.test.js +140 -0
- package/plugins/draw/src/adapters/openlayers/draw/vertexPlacement.js +144 -0
- package/plugins/draw/src/adapters/openlayers/draw/vertexPlacement.test.js +196 -0
- package/plugins/draw/src/adapters/openlayers/edit/EditMode.js +385 -0
- package/plugins/draw/src/adapters/openlayers/edit/EditMode.test.js +303 -0
- package/plugins/draw/src/adapters/openlayers/edit/activeVertexLayer.js +55 -0
- package/plugins/draw/src/adapters/openlayers/edit/activeVertexLayer.test.js +53 -0
- package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.js +157 -0
- package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.test.js +195 -0
- package/plugins/draw/src/adapters/openlayers/edit/midpointLayer.js +57 -0
- package/plugins/draw/src/adapters/openlayers/edit/midpointLayer.test.js +42 -0
- package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.js +83 -0
- package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.test.js +80 -0
- package/plugins/draw/src/adapters/openlayers/edit/nudge.js +118 -0
- package/plugins/draw/src/adapters/openlayers/edit/nudge.test.js +165 -0
- package/plugins/draw/src/adapters/openlayers/edit/pointerHandlers.js +79 -0
- package/plugins/draw/src/adapters/openlayers/edit/pointerHandlers.test.js +78 -0
- package/plugins/draw/src/adapters/openlayers/edit/selectionState.js +116 -0
- package/plugins/draw/src/adapters/openlayers/edit/selectionState.test.js +109 -0
- package/plugins/draw/src/adapters/openlayers/edit/touchHandler.js +184 -0
- package/plugins/draw/src/adapters/openlayers/edit/touchHandler.test.js +133 -0
- package/plugins/draw/src/adapters/openlayers/edit/undoOps.js +94 -0
- package/plugins/draw/src/adapters/openlayers/edit/undoOps.test.js +64 -0
- package/plugins/draw/src/adapters/openlayers/edit/vertexHitTest.js +74 -0
- package/plugins/draw/src/adapters/openlayers/edit/vertexHitTest.test.js +28 -0
- package/plugins/draw/src/adapters/openlayers/edit/vertexLayer.js +49 -0
- package/plugins/draw/src/adapters/openlayers/edit/vertexLayer.test.js +42 -0
- package/plugins/draw/src/adapters/openlayers/edit/vertexOps.js +100 -0
- package/plugins/draw/src/adapters/openlayers/edit/vertexOps.test.js +80 -0
- package/plugins/draw/src/adapters/openlayers/olDraw.js +40 -0
- package/plugins/draw/src/adapters/openlayers/olDraw.test.js +70 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapEngine.js +220 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapEngine.test.js +232 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapGeometry.js +198 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapGeometry.test.js +136 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapIndicator.js +81 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapIndicator.test.js +72 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.js +57 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.test.js +68 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapManager.js +99 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapManager.test.js +101 -0
- package/plugins/draw/src/adapters/openlayers/utils/geometryHelpers.js +103 -0
- package/plugins/draw/src/adapters/openlayers/utils/geometryHelpers.test.js +76 -0
- package/plugins/draw/src/adapters/openlayers/utils/olCoords.js +35 -0
- package/plugins/draw/src/adapters/openlayers/utils/olCoords.test.js +22 -0
- package/plugins/draw/src/adapters/openlayers/utils/sketchHelpers.js +22 -0
- package/plugins/draw/src/adapters/openlayers/utils/sketchHelpers.test.js +18 -0
- package/plugins/draw/src/adapters/openlayers/utils/touchTarget.js +8 -0
- package/plugins/draw/src/api/addFeature.js +22 -0
- package/plugins/draw/src/api/addFeature.test.js +41 -0
- package/plugins/draw/src/api/deleteFeature.js +11 -0
- package/plugins/draw/src/api/deleteFeature.test.js +24 -0
- package/plugins/draw/src/api/editFeature.js +54 -0
- package/plugins/draw/src/api/editFeature.test.js +134 -0
- package/plugins/draw/src/api/merge.js +30 -0
- package/plugins/draw/src/api/merge.test.js +57 -0
- package/plugins/draw/src/api/newLine.js +41 -0
- package/plugins/draw/src/api/newLine.test.js +93 -0
- package/plugins/draw/src/api/newPolygon.js +41 -0
- package/plugins/draw/src/api/newPolygon.test.js +93 -0
- package/plugins/draw/src/api/split.js +117 -0
- package/plugins/draw/src/api/split.test.js +204 -0
- package/plugins/draw/src/defaults.js +52 -0
- package/plugins/draw/src/defaults.test.js +18 -0
- package/plugins/draw/src/draw.scss +43 -0
- package/plugins/draw/src/events.js +237 -0
- package/plugins/draw/src/events.test.js +369 -0
- package/plugins/draw/src/index.js +10 -0
- package/plugins/draw/src/index.test.js +24 -0
- package/plugins/draw/src/manifest.js +170 -0
- package/plugins/draw/src/manifest.test.js +147 -0
- package/plugins/draw/src/reducer.js +69 -0
- package/plugins/draw/src/reducer.test.js +85 -0
- package/plugins/draw/src/utils/debounce.js +16 -0
- package/plugins/draw/src/utils/debounce.test.js +40 -0
- package/plugins/draw/src/utils/eventBus.js +36 -0
- package/plugins/draw/src/utils/eventBus.test.js +47 -0
- package/plugins/draw/src/utils/flattenStyleProperties.js +25 -0
- package/plugins/draw/src/utils/flattenStyleProperties.test.js +33 -0
- package/plugins/draw/src/utils/getValueForStyle.js +33 -0
- package/plugins/draw/src/utils/getValueForStyle.test.js +32 -0
- package/plugins/draw/src/utils/resolveColors.js +39 -0
- package/plugins/draw/src/utils/resolveColors.test.js +34 -0
- package/plugins/draw/src/utils/spatial.js +243 -0
- package/plugins/draw/src/utils/spatial.test.js +243 -0
- package/plugins/draw/src/utils/touchTarget.js +95 -0
- package/plugins/draw/src/utils/touchTarget.test.js +107 -0
- package/plugins/draw/src/utils/undoStack.js +31 -0
- package/plugins/draw/src/utils/undoStack.test.js +49 -0
- package/plugins/draw/src/validation/liveDrawChecks.js +120 -0
- package/plugins/draw/src/validation/liveDrawChecks.test.js +159 -0
- package/plugins/draw/src/validation/liveStroke.js +85 -0
- package/plugins/draw/src/validation/liveStroke.test.js +150 -0
- package/plugins/draw/src/validation/rules.areaError.test.js +12 -0
- package/plugins/draw/src/validation/rules.js +186 -0
- package/plugins/draw/src/validation/rules.test.js +120 -0
- package/plugins/draw/src/validation/validateGeometry.js +119 -0
- package/plugins/draw/src/validation/validateGeometry.test.js +200 -0
- 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/src/hooks/useInteractionHandlers.js +1 -1
- package/plugins/interact/src/hooks/useInteractionHandlers.test.js +1 -1
- package/plugins/interact/src/utils/buildStylesMap.js +1 -0
- package/plugins/search/dist/css/index.css +1 -317
- package/rollup.esm.mjs +35 -28
- package/sonar-project.properties +2 -2
- package/src/App/components/Attributions/Attributions.module.scss +1 -0
- package/src/App/components/Hints/Hints.module.scss +5 -4
- package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +1 -1
- package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +11 -0
- package/src/App/components/MoveControl/MoveControl.jsx +73 -9
- package/src/App/components/MoveControl/MoveControl.module.scss +12 -2
- package/src/App/components/MoveControl/MoveControl.test.jsx +136 -0
- package/src/App/hooks/useHintsAPI.js +67 -0
- package/src/App/hooks/useHintsAPI.test.js +145 -0
- package/src/App/renderer/PluginInits.jsx +4 -0
- package/src/App/renderer/PluginInits.test.jsx +7 -1
- package/src/InteractiveMap/InteractiveMap.js +20 -0
- package/src/InteractiveMap/InteractiveMap.test.js +7 -0
- package/src/config/events.js +4 -0
- package/src/scss/settings/_dimensions.scss +3 -0
- package/src/services/announcer.js +38 -3
- package/src/services/announcer.test.js +133 -1
- package/src/services/hints.js +3 -0
- package/src/services/hints.test.js +19 -0
- package/src/types.js +12 -0
- package/src/utils/detectInterfaceType.js +5 -1
- package/src/utils/isMac.js +15 -0
- package/src/utils/isMac.test.js +37 -0
- package/webpack.umd.mjs +1 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { flattenStyleProperties } from '../utils/flattenStyleProperties.js'
|
|
2
|
+
|
|
3
|
+
export const addFeature = ({ mapProvider, services }, feature) => {
|
|
4
|
+
const { draw } = mapProvider
|
|
5
|
+
const { eventBus } = services
|
|
6
|
+
|
|
7
|
+
if (!draw) {
|
|
8
|
+
return
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { stroke, fill, strokeWidth, properties, ...featureRest } = feature
|
|
12
|
+
const flatFeature = {
|
|
13
|
+
...featureRest,
|
|
14
|
+
properties: {
|
|
15
|
+
...properties,
|
|
16
|
+
...flattenStyleProperties({ stroke, fill, strokeWidth })
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
draw.add(flatFeature)
|
|
21
|
+
eventBus.emit('draw:add', flatFeature)
|
|
22
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { addFeature } from './addFeature.js'
|
|
2
|
+
import { flattenStyleProperties } from '../utils/flattenStyleProperties.js'
|
|
3
|
+
|
|
4
|
+
jest.mock('../utils/flattenStyleProperties.js', () => ({
|
|
5
|
+
flattenStyleProperties: jest.fn(() => ({ _flat: true }))
|
|
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('addFeature', () => {
|
|
16
|
+
test('does nothing when there is no draw instance', () => {
|
|
17
|
+
const { context, eventBus } = setup(undefined)
|
|
18
|
+
addFeature(context, { id: 'a' })
|
|
19
|
+
expect(eventBus.emit).not.toHaveBeenCalled()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('flattens style properties and adds the feature', () => {
|
|
23
|
+
const draw = { add: jest.fn() }
|
|
24
|
+
const { context, eventBus } = setup(draw)
|
|
25
|
+
const feature = { id: 'a', geometry: {}, stroke: 'red', fill: 'blue', strokeWidth: 2, properties: { name: 'x' } }
|
|
26
|
+
|
|
27
|
+
addFeature(context, feature)
|
|
28
|
+
|
|
29
|
+
expect(flattenStyleProperties).toHaveBeenCalledWith({ stroke: 'red', fill: 'blue', strokeWidth: 2 })
|
|
30
|
+
const expected = { id: 'a', geometry: {}, properties: { name: 'x', _flat: true } }
|
|
31
|
+
expect(draw.add).toHaveBeenCalledWith(expected)
|
|
32
|
+
expect(eventBus.emit).toHaveBeenCalledWith('draw:add', expected)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('handles a feature without existing properties', () => {
|
|
36
|
+
const draw = { add: jest.fn() }
|
|
37
|
+
const { context } = setup(draw)
|
|
38
|
+
addFeature(context, { id: 'b', geometry: {} })
|
|
39
|
+
expect(draw.add).toHaveBeenCalledWith({ id: 'b', geometry: {}, properties: { _flat: true } })
|
|
40
|
+
})
|
|
41
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { deleteFeature } from './deleteFeature.js'
|
|
2
|
+
|
|
3
|
+
const setup = (draw) => {
|
|
4
|
+
const eventBus = { emit: jest.fn() }
|
|
5
|
+
return { context: { mapProvider: { draw }, services: { eventBus } }, eventBus }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
describe('deleteFeature', () => {
|
|
9
|
+
test('does nothing when there is no draw instance', () => {
|
|
10
|
+
const { context, eventBus } = setup(undefined)
|
|
11
|
+
deleteFeature(context, 'id1')
|
|
12
|
+
expect(eventBus.emit).not.toHaveBeenCalled()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test('deletes the feature and emits the delete event', () => {
|
|
16
|
+
const draw = { delete: jest.fn() }
|
|
17
|
+
const { context, eventBus } = setup(draw)
|
|
18
|
+
|
|
19
|
+
deleteFeature(context, 'id1')
|
|
20
|
+
|
|
21
|
+
expect(draw.delete).toHaveBeenCalledWith('id1')
|
|
22
|
+
expect(eventBus.emit).toHaveBeenCalledWith('draw:delete', { featureId: 'id1' })
|
|
23
|
+
})
|
|
24
|
+
})
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { MAP_SIZE_SCALES } from '../defaults.js'
|
|
2
|
+
import { validateGeometry } from '../validation/validateGeometry.js'
|
|
3
|
+
|
|
4
|
+
export const editFeature = ({ appState, appConfig, mapState, pluginConfig, pluginState, mapProvider, services }, featureId, options = {}) => {
|
|
5
|
+
const { dispatch } = pluginState
|
|
6
|
+
const { draw } = mapProvider
|
|
7
|
+
const { eventBus } = services
|
|
8
|
+
|
|
9
|
+
if (!draw) {
|
|
10
|
+
return false
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const existingFeature = draw.get(featureId)
|
|
14
|
+
if (!existingFeature) {
|
|
15
|
+
return false
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Per-call callback overrides the plugin-level one; events.js reads this on every commit.
|
|
19
|
+
draw._geometryValidator = options.onGeometryChange ?? pluginConfig.onGeometryChange
|
|
20
|
+
|
|
21
|
+
const editModeMap = { LineString: 'edit_line', Polygon: 'edit_polygon' }
|
|
22
|
+
eventBus.emit('draw:editstart', { mode: editModeMap[existingFeature.geometry.type] })
|
|
23
|
+
|
|
24
|
+
const snapLayers = options.snapLayers === undefined ? (pluginConfig.snapLayers ?? null) : options.snapLayers
|
|
25
|
+
draw.setSnapLayers(snapLayers)
|
|
26
|
+
dispatch({ type: 'SET_HAS_SNAP_LAYERS', payload: snapLayers?.length > 0 })
|
|
27
|
+
|
|
28
|
+
draw.changeMode('edit_vertex', {
|
|
29
|
+
container: appState.layoutRefs.viewportRef.current,
|
|
30
|
+
deleteVertexButtonId: `${appConfig.id}-draw-delete-point`,
|
|
31
|
+
undoButtonId: `${appConfig.id}-draw-undo`,
|
|
32
|
+
isPanEnabled: appState.interfaceType !== 'keyboard',
|
|
33
|
+
interfaceType: appState.interfaceType,
|
|
34
|
+
scale: MAP_SIZE_SCALES[mapState.mapSize],
|
|
35
|
+
featureId,
|
|
36
|
+
getSnapEnabled: () => draw.isSnapEnabled()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const feature = draw.get(featureId)
|
|
40
|
+
dispatch({
|
|
41
|
+
type: 'SET_FEATURE',
|
|
42
|
+
payload: { feature, tempFeature: feature }
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
dispatch({ type: 'SET_MODE', payload: 'edit_vertex' })
|
|
46
|
+
|
|
47
|
+
// Seed the Done-button gate and the stroke from the feature's starting validity
|
|
48
|
+
// so an already invalid feature opens dashed and cannot be "finished" until fixed.
|
|
49
|
+
const { valid } = validateGeometry(feature, { phase: 'edit-start', mode: 'edit_vertex' }, { onGeometryChange: draw._geometryValidator })
|
|
50
|
+
dispatch({ type: 'SET_GEOMETRY_VALID', payload: valid })
|
|
51
|
+
draw.setInvalid?.(!valid)
|
|
52
|
+
|
|
53
|
+
return true
|
|
54
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { editFeature } from './editFeature.js'
|
|
2
|
+
|
|
3
|
+
jest.mock('../defaults.js', () => ({ MAP_SIZE_SCALES: { medium: 1.5, large: 2 } }))
|
|
4
|
+
|
|
5
|
+
const makeContext = (overrides = {}) => {
|
|
6
|
+
const dispatch = jest.fn()
|
|
7
|
+
const eventBus = { emit: jest.fn() }
|
|
8
|
+
const draw = {
|
|
9
|
+
get: jest.fn(() => ({ id: 'f1', geometry: { type: 'Polygon' } })),
|
|
10
|
+
setSnapLayers: jest.fn(),
|
|
11
|
+
changeMode: jest.fn(),
|
|
12
|
+
isSnapEnabled: jest.fn(() => true)
|
|
13
|
+
}
|
|
14
|
+
const context = {
|
|
15
|
+
appState: { layoutRefs: { viewportRef: { current: 'viewport' } }, interfaceType: 'mouse' },
|
|
16
|
+
appConfig: { id: 'app' },
|
|
17
|
+
mapState: { mapSize: 'medium' },
|
|
18
|
+
pluginConfig: { snapLayers: ['pc-layer'] },
|
|
19
|
+
pluginState: { dispatch },
|
|
20
|
+
mapProvider: { draw },
|
|
21
|
+
services: { eventBus },
|
|
22
|
+
...overrides
|
|
23
|
+
}
|
|
24
|
+
return { context, dispatch, eventBus, draw }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
beforeEach(() => jest.clearAllMocks())
|
|
28
|
+
|
|
29
|
+
describe('editFeature', () => {
|
|
30
|
+
test('returns false when there is no draw instance', () => {
|
|
31
|
+
const { context } = makeContext({ mapProvider: { draw: null } })
|
|
32
|
+
expect(editFeature(context, 'f1')).toBe(false)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('returns false when the feature does not exist', () => {
|
|
36
|
+
const { context, draw, eventBus } = makeContext()
|
|
37
|
+
draw.get.mockReturnValue(undefined)
|
|
38
|
+
expect(editFeature(context, 'missing')).toBe(false)
|
|
39
|
+
expect(eventBus.emit).not.toHaveBeenCalled()
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('enters edit mode for a polygon and wires all options', () => {
|
|
43
|
+
const { context, dispatch, eventBus, draw } = makeContext()
|
|
44
|
+
|
|
45
|
+
const result = editFeature(context, 'f1')
|
|
46
|
+
|
|
47
|
+
expect(result).toBe(true)
|
|
48
|
+
expect(eventBus.emit).toHaveBeenCalledWith('draw:editstart', { mode: 'edit_polygon' })
|
|
49
|
+
expect(draw.setSnapLayers).toHaveBeenCalledWith(['pc-layer'])
|
|
50
|
+
expect(dispatch).toHaveBeenCalledWith({ type: 'SET_HAS_SNAP_LAYERS', payload: true })
|
|
51
|
+
|
|
52
|
+
expect(draw.changeMode).toHaveBeenCalledWith('edit_vertex', expect.objectContaining({
|
|
53
|
+
container: 'viewport',
|
|
54
|
+
deleteVertexButtonId: 'app-draw-delete-point',
|
|
55
|
+
undoButtonId: 'app-draw-undo',
|
|
56
|
+
isPanEnabled: true,
|
|
57
|
+
interfaceType: 'mouse',
|
|
58
|
+
scale: 1.5,
|
|
59
|
+
featureId: 'f1'
|
|
60
|
+
}))
|
|
61
|
+
|
|
62
|
+
const opts = draw.changeMode.mock.calls[0][1]
|
|
63
|
+
expect(opts.getSnapEnabled()).toBe(true)
|
|
64
|
+
expect(draw.isSnapEnabled).toHaveBeenCalled()
|
|
65
|
+
|
|
66
|
+
expect(dispatch).toHaveBeenCalledWith({ type: 'SET_FEATURE', payload: { feature: { id: 'f1', geometry: { type: 'Polygon' } }, tempFeature: { id: 'f1', geometry: { type: 'Polygon' } } } })
|
|
67
|
+
expect(dispatch).toHaveBeenCalledWith({ type: 'SET_MODE', payload: 'edit_vertex' })
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('uses edit_line mode for a line string', () => {
|
|
71
|
+
const { context, eventBus, draw } = makeContext()
|
|
72
|
+
draw.get.mockReturnValue({ id: 'f1', geometry: { type: 'LineString' } })
|
|
73
|
+
editFeature(context, 'f1')
|
|
74
|
+
expect(eventBus.emit).toHaveBeenCalledWith('draw:editstart', { mode: 'edit_line' })
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('stores a per-call onGeometryChange validator, overriding the plugin-level one', () => {
|
|
78
|
+
const pluginOnGeometryChange = jest.fn()
|
|
79
|
+
const onGeometryChange = jest.fn()
|
|
80
|
+
const { context, draw } = makeContext({ pluginConfig: { snapLayers: ['pc-layer'], onGeometryChange: pluginOnGeometryChange } })
|
|
81
|
+
editFeature(context, 'f1', { onGeometryChange })
|
|
82
|
+
expect(draw._geometryValidator).toBe(onGeometryChange)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test('seeds the geometry-valid gate from the feature starting validity', () => {
|
|
86
|
+
const { context, dispatch, draw } = makeContext()
|
|
87
|
+
// A valid square → gate opens.
|
|
88
|
+
draw.get.mockReturnValue({ id: 'f1', geometry: { type: 'Polygon', coordinates: [[[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]]] } })
|
|
89
|
+
editFeature(context, 'f1')
|
|
90
|
+
expect(dispatch).toHaveBeenCalledWith({ type: 'SET_GEOMETRY_VALID', payload: true })
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test('seeds the gate closed for an already self-intersecting feature', () => {
|
|
94
|
+
const { context, dispatch, draw } = makeContext()
|
|
95
|
+
draw.get.mockReturnValue({ id: 'f1', geometry: { type: 'Polygon', coordinates: [[[0, 0], [1, 1], [1, 0], [0, 1], [0, 0]]] } })
|
|
96
|
+
editFeature(context, 'f1')
|
|
97
|
+
expect(dispatch).toHaveBeenCalledWith({ type: 'SET_GEOMETRY_VALID', payload: false })
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('seeds the stroke alongside the gate — an invalid feature opens edit mode dashed', () => {
|
|
101
|
+
const { context, draw } = makeContext()
|
|
102
|
+
draw.setInvalid = jest.fn()
|
|
103
|
+
draw.get.mockReturnValue({ id: 'f1', geometry: { type: 'Polygon', coordinates: [[[0, 0], [1, 1], [1, 0], [0, 1], [0, 0]]] } })
|
|
104
|
+
editFeature(context, 'f1')
|
|
105
|
+
expect(draw.setInvalid).toHaveBeenCalledWith(true)
|
|
106
|
+
|
|
107
|
+
draw.setInvalid.mockClear()
|
|
108
|
+
draw.get.mockReturnValue({ id: 'f1', geometry: { type: 'Polygon', coordinates: [[[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]]] } })
|
|
109
|
+
editFeature(context, 'f1')
|
|
110
|
+
expect(draw.setInvalid).toHaveBeenCalledWith(false)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test('disables panning for the keyboard interface', () => {
|
|
114
|
+
const { context, draw } = makeContext({
|
|
115
|
+
appState: { layoutRefs: { viewportRef: { current: 'viewport' } }, interfaceType: 'keyboard' }
|
|
116
|
+
})
|
|
117
|
+
editFeature(context, 'f1')
|
|
118
|
+
expect(draw.changeMode.mock.calls[0][1].isPanEnabled).toBe(false)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test('prefers explicit option snapLayers over the plugin config', () => {
|
|
122
|
+
const { context, draw, dispatch } = makeContext()
|
|
123
|
+
editFeature(context, 'f1', { snapLayers: ['opt-layer'] })
|
|
124
|
+
expect(draw.setSnapLayers).toHaveBeenCalledWith(['opt-layer'])
|
|
125
|
+
expect(dispatch).toHaveBeenCalledWith({ type: 'SET_HAS_SNAP_LAYERS', payload: true })
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
test('falls back to null snapLayers when neither option nor config is set', () => {
|
|
129
|
+
const { context, draw, dispatch } = makeContext({ pluginConfig: {} })
|
|
130
|
+
editFeature(context, 'f1')
|
|
131
|
+
expect(draw.setSnapLayers).toHaveBeenCalledWith(null)
|
|
132
|
+
expect(dispatch).toHaveBeenCalledWith({ type: 'SET_HAS_SNAP_LAYERS', payload: false })
|
|
133
|
+
})
|
|
134
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { mergePolygons } from '../utils/spatial.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Merge multiple contiguous polygons into a single polygon.
|
|
5
|
+
*
|
|
6
|
+
* Pure computation, like split — this does not touch the feature store. The
|
|
7
|
+
* caller is responsible for deleting the original features and adding the
|
|
8
|
+
* merged result (see the draw:merge event).
|
|
9
|
+
*
|
|
10
|
+
* @param {object} context - plugin context
|
|
11
|
+
* @param {string[]} featureIds - IDs of the polygons to merge
|
|
12
|
+
* @returns {object|null} the merged GeoJSON feature, or null if the merge failed
|
|
13
|
+
*/
|
|
14
|
+
export const merge = ({ mapProvider, services }, featureIds) => {
|
|
15
|
+
const { draw } = mapProvider
|
|
16
|
+
const { eventBus } = services
|
|
17
|
+
|
|
18
|
+
if (!draw) {
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const polygons = featureIds.map((id) => draw.get(id))
|
|
23
|
+
const feature = mergePolygons(polygons)
|
|
24
|
+
|
|
25
|
+
if (feature) {
|
|
26
|
+
eventBus.emit('draw:merge', { originalFeatureIds: featureIds, feature })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return feature
|
|
30
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { merge } from './merge.js'
|
|
2
|
+
import { mergePolygons } from '../utils/spatial.js'
|
|
3
|
+
|
|
4
|
+
jest.mock('../utils/spatial.js', () => ({ mergePolygons: jest.fn() }))
|
|
5
|
+
|
|
6
|
+
const makeContext = (overrides = {}) => {
|
|
7
|
+
const eventBus = { emit: jest.fn() }
|
|
8
|
+
const draw = { get: jest.fn((id) => ({ id })) }
|
|
9
|
+
const context = {
|
|
10
|
+
mapProvider: { draw },
|
|
11
|
+
services: { eventBus },
|
|
12
|
+
...overrides
|
|
13
|
+
}
|
|
14
|
+
return { context, draw, eventBus }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
beforeEach(() => jest.clearAllMocks())
|
|
18
|
+
|
|
19
|
+
describe('merge', () => {
|
|
20
|
+
test('does nothing when there is no draw instance', () => {
|
|
21
|
+
const { context, eventBus } = makeContext({ mapProvider: { draw: null } })
|
|
22
|
+
expect(merge(context, ['a', 'b'])).toBeNull()
|
|
23
|
+
expect(eventBus.emit).not.toHaveBeenCalled()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('resolves each feature id via the adapter and merges them', () => {
|
|
27
|
+
const { context, draw } = makeContext()
|
|
28
|
+
mergePolygons.mockReturnValue({ id: 'a', geometry: { type: 'Polygon', coordinates: [] } })
|
|
29
|
+
|
|
30
|
+
merge(context, ['a', 'b'])
|
|
31
|
+
|
|
32
|
+
expect(draw.get).toHaveBeenCalledWith('a')
|
|
33
|
+
expect(draw.get).toHaveBeenCalledWith('b')
|
|
34
|
+
expect(mergePolygons).toHaveBeenCalledWith([{ id: 'a' }, { id: 'b' }])
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('returns the merged feature and emits draw:merge on success', () => {
|
|
38
|
+
const { context, eventBus } = makeContext()
|
|
39
|
+
const feature = { id: 'a', geometry: { type: 'Polygon', coordinates: [] } }
|
|
40
|
+
mergePolygons.mockReturnValue(feature)
|
|
41
|
+
|
|
42
|
+
const result = merge(context, ['a', 'b'])
|
|
43
|
+
|
|
44
|
+
expect(result).toBe(feature)
|
|
45
|
+
expect(eventBus.emit).toHaveBeenCalledWith('draw:merge', { originalFeatureIds: ['a', 'b'], feature })
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('returns null and does not emit when the merge fails', () => {
|
|
49
|
+
const { context, eventBus } = makeContext()
|
|
50
|
+
mergePolygons.mockReturnValue(null)
|
|
51
|
+
|
|
52
|
+
const result = merge(context, ['a', 'b'])
|
|
53
|
+
|
|
54
|
+
expect(result).toBeNull()
|
|
55
|
+
expect(eventBus.emit).not.toHaveBeenCalled()
|
|
56
|
+
})
|
|
57
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { flattenStyleProperties } from '../utils/flattenStyleProperties.js'
|
|
2
|
+
|
|
3
|
+
// Near-identical to newPolygon.js, differing only in the 'draw_line'/'draw_polygon'
|
|
4
|
+
// mode string — kept as two small, explicit entry points rather than one
|
|
5
|
+
// mode-parameterised factory. Revisit if a third geometry type is ever added.
|
|
6
|
+
export const newLine = ({ appState, appConfig, pluginConfig, pluginState, mapState, mapProvider, services }, featureId, options = {}) => {
|
|
7
|
+
const { dispatch } = pluginState
|
|
8
|
+
const { draw } = mapProvider
|
|
9
|
+
const { eventBus } = services
|
|
10
|
+
|
|
11
|
+
if (!draw) {
|
|
12
|
+
return
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
eventBus.emit('draw:started', { mode: 'draw_line' })
|
|
16
|
+
|
|
17
|
+
const snapLayers = options.snapLayers === undefined ? (pluginConfig.snapLayers ?? null) : options.snapLayers
|
|
18
|
+
draw.setSnapLayers(snapLayers)
|
|
19
|
+
dispatch({ type: 'SET_HAS_SNAP_LAYERS', payload: snapLayers?.length > 0 })
|
|
20
|
+
|
|
21
|
+
const { stroke, fill, strokeWidth, properties: customProperties, onGeometryChange, ...modeOptions } = options
|
|
22
|
+
// Per-call callback overrides the plugin-level one; events.js reads this on every commit.
|
|
23
|
+
draw._geometryValidator = onGeometryChange ?? pluginConfig.onGeometryChange
|
|
24
|
+
const properties = {
|
|
25
|
+
...customProperties,
|
|
26
|
+
...flattenStyleProperties({ stroke, fill, strokeWidth })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
draw.changeMode('draw_line', {
|
|
30
|
+
container: appState.layoutRefs.viewportRef.current,
|
|
31
|
+
addVertexButtonId: `${appConfig.id}-draw-add-point`,
|
|
32
|
+
interfaceType: appState.interfaceType,
|
|
33
|
+
crossHair: mapState.crossHair,
|
|
34
|
+
getSnapEnabled: () => draw.isSnapEnabled(),
|
|
35
|
+
featureId,
|
|
36
|
+
...modeOptions,
|
|
37
|
+
properties
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
dispatch({ type: 'SET_MODE', payload: 'draw_line' })
|
|
41
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { newLine } from './newLine.js'
|
|
2
|
+
import { flattenStyleProperties } from '../utils/flattenStyleProperties.js'
|
|
3
|
+
|
|
4
|
+
jest.mock('../utils/flattenStyleProperties.js', () => ({
|
|
5
|
+
flattenStyleProperties: jest.fn(() => ({ _flat: true }))
|
|
6
|
+
}))
|
|
7
|
+
|
|
8
|
+
const makeContext = (overrides = {}) => {
|
|
9
|
+
const dispatch = jest.fn()
|
|
10
|
+
const eventBus = { emit: jest.fn() }
|
|
11
|
+
const draw = { setSnapLayers: jest.fn(), changeMode: jest.fn(), isSnapEnabled: jest.fn(() => false) }
|
|
12
|
+
const context = {
|
|
13
|
+
appState: { layoutRefs: { viewportRef: { current: 'viewport' } }, interfaceType: 'mouse' },
|
|
14
|
+
appConfig: { id: 'app' },
|
|
15
|
+
pluginConfig: { snapLayers: ['pc-layer'] },
|
|
16
|
+
pluginState: { dispatch },
|
|
17
|
+
mapState: { crossHair: true },
|
|
18
|
+
mapProvider: { draw },
|
|
19
|
+
services: { eventBus },
|
|
20
|
+
...overrides
|
|
21
|
+
}
|
|
22
|
+
return { context, dispatch, eventBus, draw }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
beforeEach(() => jest.clearAllMocks())
|
|
26
|
+
|
|
27
|
+
describe('newLine', () => {
|
|
28
|
+
test('does nothing when there is no draw instance', () => {
|
|
29
|
+
const { context, eventBus } = makeContext({ mapProvider: { draw: null } })
|
|
30
|
+
newLine(context, 'f1')
|
|
31
|
+
expect(eventBus.emit).not.toHaveBeenCalled()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('starts line drawing with flattened style properties', () => {
|
|
35
|
+
const { context, dispatch, eventBus, draw } = makeContext()
|
|
36
|
+
|
|
37
|
+
newLine(context, 'f1', { stroke: 'red', fill: 'blue', strokeWidth: 3, properties: { name: 'x' }, extra: 'opt' })
|
|
38
|
+
|
|
39
|
+
expect(eventBus.emit).toHaveBeenCalledWith('draw:started', { mode: 'draw_line' })
|
|
40
|
+
expect(flattenStyleProperties).toHaveBeenCalledWith({ stroke: 'red', fill: 'blue', strokeWidth: 3 })
|
|
41
|
+
|
|
42
|
+
expect(draw.changeMode).toHaveBeenCalledWith('draw_line', expect.objectContaining({
|
|
43
|
+
container: 'viewport',
|
|
44
|
+
addVertexButtonId: 'app-draw-add-point',
|
|
45
|
+
interfaceType: 'mouse',
|
|
46
|
+
crossHair: true,
|
|
47
|
+
featureId: 'f1',
|
|
48
|
+
extra: 'opt',
|
|
49
|
+
properties: { name: 'x', _flat: true }
|
|
50
|
+
}))
|
|
51
|
+
|
|
52
|
+
const opts = draw.changeMode.mock.calls[0][1]
|
|
53
|
+
expect(opts.getSnapEnabled()).toBe(false)
|
|
54
|
+
expect(draw.isSnapEnabled).toHaveBeenCalled()
|
|
55
|
+
|
|
56
|
+
expect(dispatch).toHaveBeenCalledWith({ type: 'SET_MODE', payload: 'draw_line' })
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test('prefers explicit option snapLayers and flags them', () => {
|
|
60
|
+
const { context, draw, dispatch } = makeContext()
|
|
61
|
+
newLine(context, 'f1', { snapLayers: ['opt'] })
|
|
62
|
+
expect(draw.setSnapLayers).toHaveBeenCalledWith(['opt'])
|
|
63
|
+
expect(dispatch).toHaveBeenCalledWith({ type: 'SET_HAS_SNAP_LAYERS', payload: true })
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('falls back to the plugin config snapLayers', () => {
|
|
67
|
+
const { context, draw } = makeContext()
|
|
68
|
+
newLine(context, 'f1')
|
|
69
|
+
expect(draw.setSnapLayers).toHaveBeenCalledWith(['pc-layer'])
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('falls back to null when neither option nor config is set', () => {
|
|
73
|
+
const { context, draw, dispatch } = makeContext({ pluginConfig: {} })
|
|
74
|
+
newLine(context, 'f1')
|
|
75
|
+
expect(draw.setSnapLayers).toHaveBeenCalledWith(null)
|
|
76
|
+
expect(dispatch).toHaveBeenCalledWith({ type: 'SET_HAS_SNAP_LAYERS', payload: false })
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('stores a per-call onGeometryChange validator without leaking it into mode options', () => {
|
|
80
|
+
const { context, draw } = makeContext()
|
|
81
|
+
const onGeometryChange = jest.fn()
|
|
82
|
+
newLine(context, 'f1', { onGeometryChange })
|
|
83
|
+
expect(draw._geometryValidator).toBe(onGeometryChange)
|
|
84
|
+
expect(draw.changeMode.mock.calls[0][1]).not.toHaveProperty('onGeometryChange')
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('falls back to the plugin-level onGeometryChange validator', () => {
|
|
88
|
+
const pluginOnGeometryChange = jest.fn()
|
|
89
|
+
const { context, draw } = makeContext({ pluginConfig: { onGeometryChange: pluginOnGeometryChange } })
|
|
90
|
+
newLine(context, 'f1')
|
|
91
|
+
expect(draw._geometryValidator).toBe(pluginOnGeometryChange)
|
|
92
|
+
})
|
|
93
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { flattenStyleProperties } from '../utils/flattenStyleProperties.js'
|
|
2
|
+
|
|
3
|
+
// Near-identical to newLine.js, differing only in the 'draw_polygon'/'draw_line'
|
|
4
|
+
// mode string — kept as two small, explicit entry points rather than one
|
|
5
|
+
// mode-parameterised factory. Revisit if a third geometry type is ever added.
|
|
6
|
+
export const newPolygon = ({ appState, appConfig, pluginConfig, pluginState, mapState, mapProvider, services }, featureId, options = {}) => {
|
|
7
|
+
const { dispatch } = pluginState
|
|
8
|
+
const { draw } = mapProvider
|
|
9
|
+
const { eventBus } = services
|
|
10
|
+
|
|
11
|
+
if (!draw) {
|
|
12
|
+
return
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
eventBus.emit('draw:started', { mode: 'draw_polygon' })
|
|
16
|
+
|
|
17
|
+
const snapLayers = options.snapLayers === undefined ? (pluginConfig.snapLayers ?? null) : options.snapLayers
|
|
18
|
+
draw.setSnapLayers(snapLayers)
|
|
19
|
+
dispatch({ type: 'SET_HAS_SNAP_LAYERS', payload: snapLayers?.length > 0 })
|
|
20
|
+
|
|
21
|
+
const { stroke, fill, strokeWidth, properties: customProperties, onGeometryChange, ...modeOptions } = options
|
|
22
|
+
// Per-call callback overrides the plugin-level one; events.js reads this on every commit.
|
|
23
|
+
draw._geometryValidator = onGeometryChange ?? pluginConfig.onGeometryChange
|
|
24
|
+
const properties = {
|
|
25
|
+
...customProperties,
|
|
26
|
+
...flattenStyleProperties({ stroke, fill, strokeWidth })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
draw.changeMode('draw_polygon', {
|
|
30
|
+
container: appState.layoutRefs.viewportRef.current,
|
|
31
|
+
addVertexButtonId: `${appConfig.id}-draw-add-point`,
|
|
32
|
+
interfaceType: appState.interfaceType,
|
|
33
|
+
crossHair: mapState.crossHair,
|
|
34
|
+
getSnapEnabled: () => draw.isSnapEnabled(),
|
|
35
|
+
featureId,
|
|
36
|
+
...modeOptions,
|
|
37
|
+
properties
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
dispatch({ type: 'SET_MODE', payload: 'draw_polygon' })
|
|
41
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { newPolygon } from './newPolygon.js'
|
|
2
|
+
import { flattenStyleProperties } from '../utils/flattenStyleProperties.js'
|
|
3
|
+
|
|
4
|
+
jest.mock('../utils/flattenStyleProperties.js', () => ({
|
|
5
|
+
flattenStyleProperties: jest.fn(() => ({ _flat: true }))
|
|
6
|
+
}))
|
|
7
|
+
|
|
8
|
+
const makeContext = (overrides = {}) => {
|
|
9
|
+
const dispatch = jest.fn()
|
|
10
|
+
const eventBus = { emit: jest.fn() }
|
|
11
|
+
const draw = { setSnapLayers: jest.fn(), changeMode: jest.fn(), isSnapEnabled: jest.fn(() => true) }
|
|
12
|
+
const context = {
|
|
13
|
+
appState: { layoutRefs: { viewportRef: { current: 'viewport' } }, interfaceType: 'mouse' },
|
|
14
|
+
appConfig: { id: 'app' },
|
|
15
|
+
pluginConfig: { snapLayers: ['pc-layer'] },
|
|
16
|
+
pluginState: { dispatch },
|
|
17
|
+
mapState: { crossHair: false },
|
|
18
|
+
mapProvider: { draw },
|
|
19
|
+
services: { eventBus },
|
|
20
|
+
...overrides
|
|
21
|
+
}
|
|
22
|
+
return { context, dispatch, eventBus, draw }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
beforeEach(() => jest.clearAllMocks())
|
|
26
|
+
|
|
27
|
+
describe('newPolygon', () => {
|
|
28
|
+
test('does nothing when there is no draw instance', () => {
|
|
29
|
+
const { context, eventBus } = makeContext({ mapProvider: { draw: null } })
|
|
30
|
+
newPolygon(context, 'f1')
|
|
31
|
+
expect(eventBus.emit).not.toHaveBeenCalled()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('starts polygon drawing with flattened style properties', () => {
|
|
35
|
+
const { context, dispatch, eventBus, draw } = makeContext()
|
|
36
|
+
|
|
37
|
+
newPolygon(context, 'f1', { stroke: 'red', fill: 'blue', strokeWidth: 3, properties: { name: 'x' }, extra: 'opt' })
|
|
38
|
+
|
|
39
|
+
expect(eventBus.emit).toHaveBeenCalledWith('draw:started', { mode: 'draw_polygon' })
|
|
40
|
+
expect(flattenStyleProperties).toHaveBeenCalledWith({ stroke: 'red', fill: 'blue', strokeWidth: 3 })
|
|
41
|
+
|
|
42
|
+
expect(draw.changeMode).toHaveBeenCalledWith('draw_polygon', expect.objectContaining({
|
|
43
|
+
container: 'viewport',
|
|
44
|
+
addVertexButtonId: 'app-draw-add-point',
|
|
45
|
+
interfaceType: 'mouse',
|
|
46
|
+
crossHair: false,
|
|
47
|
+
featureId: 'f1',
|
|
48
|
+
extra: 'opt',
|
|
49
|
+
properties: { name: 'x', _flat: true }
|
|
50
|
+
}))
|
|
51
|
+
|
|
52
|
+
const opts = draw.changeMode.mock.calls[0][1]
|
|
53
|
+
expect(opts.getSnapEnabled()).toBe(true)
|
|
54
|
+
expect(draw.isSnapEnabled).toHaveBeenCalled()
|
|
55
|
+
|
|
56
|
+
expect(dispatch).toHaveBeenCalledWith({ type: 'SET_MODE', payload: 'draw_polygon' })
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test('prefers explicit option snapLayers and flags them', () => {
|
|
60
|
+
const { context, draw, dispatch } = makeContext()
|
|
61
|
+
newPolygon(context, 'f1', { snapLayers: ['opt'] })
|
|
62
|
+
expect(draw.setSnapLayers).toHaveBeenCalledWith(['opt'])
|
|
63
|
+
expect(dispatch).toHaveBeenCalledWith({ type: 'SET_HAS_SNAP_LAYERS', payload: true })
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('falls back to the plugin config snapLayers', () => {
|
|
67
|
+
const { context, draw } = makeContext()
|
|
68
|
+
newPolygon(context, 'f1')
|
|
69
|
+
expect(draw.setSnapLayers).toHaveBeenCalledWith(['pc-layer'])
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('falls back to null when neither option nor config is set', () => {
|
|
73
|
+
const { context, draw, dispatch } = makeContext({ pluginConfig: {} })
|
|
74
|
+
newPolygon(context, 'f1')
|
|
75
|
+
expect(draw.setSnapLayers).toHaveBeenCalledWith(null)
|
|
76
|
+
expect(dispatch).toHaveBeenCalledWith({ type: 'SET_HAS_SNAP_LAYERS', payload: false })
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('stores a per-call onGeometryChange validator without leaking it into mode options', () => {
|
|
80
|
+
const { context, draw } = makeContext()
|
|
81
|
+
const onGeometryChange = jest.fn()
|
|
82
|
+
newPolygon(context, 'f1', { onGeometryChange })
|
|
83
|
+
expect(draw._geometryValidator).toBe(onGeometryChange)
|
|
84
|
+
expect(draw.changeMode.mock.calls[0][1]).not.toHaveProperty('onGeometryChange')
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('falls back to the plugin-level onGeometryChange validator', () => {
|
|
88
|
+
const pluginOnGeometryChange = jest.fn()
|
|
89
|
+
const { context, draw } = makeContext({ pluginConfig: { onGeometryChange: pluginOnGeometryChange } })
|
|
90
|
+
newPolygon(context, 'f1')
|
|
91
|
+
expect(draw._geometryValidator).toBe(pluginOnGeometryChange)
|
|
92
|
+
})
|
|
93
|
+
})
|