@defra/interactive-map 0.0.29-alpha → 0.0.31-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.
Files changed (195) hide show
  1. package/dist/css/index.css +1 -1
  2. package/dist/esm/im-core.js +1 -1
  3. package/dist/esm/im-shell.js +1 -1
  4. package/dist/umd/im-core.js +1 -1
  5. package/dist/umd/index.js +1 -1
  6. package/docs/api/context.md +4 -4
  7. package/docs/api.md +111 -37
  8. package/docs/assets/css/docusaurus.scss +20 -4
  9. package/docs/assets/images/basic-map.jpg +0 -0
  10. package/docs/assets/images/button-first.jpg +0 -0
  11. package/docs/assets/images/deuteranopia-ozs-map-thumb.jpg +0 -0
  12. package/docs/assets/images/draw.jpg +0 -0
  13. package/docs/assets/images/marker-panel.jpg +0 -0
  14. package/docs/assets/images/night-ozs-map-thumb.jpg +0 -0
  15. package/docs/assets/images/outdoor-ozs-map-thumb.jpg +0 -0
  16. package/docs/assets/images/search.jpg +0 -0
  17. package/docs/assets/images/select-feature.jpg +0 -0
  18. package/docs/assets/images/style-switcher.jpg +0 -0
  19. package/docs/assets/images/toggle-marker-label.jpg +0 -0
  20. package/docs/assets/images/tritanopia-ozs-map-thumb.jpg +0 -0
  21. package/docs/examples/add-marker-with-panel.mdx +2 -2
  22. package/docs/examples/basic-map.mdx +2 -2
  23. package/docs/examples/draw.mdx +271 -0
  24. package/docs/examples/index.mdx +58 -10
  25. package/docs/examples/search-control.mdx +71 -0
  26. package/docs/examples/select-feature.mdx +146 -0
  27. package/docs/examples/style-switcher.mdx +135 -0
  28. package/docs/examples/toggle-marker-label.mdx +3 -3
  29. package/docs/plugins/datasets.md +15 -28
  30. package/docusaurus.config.cjs +5 -1
  31. package/package.json +1 -1
  32. package/plugins/beta/datasets/dist/css/index.css +4 -0
  33. package/plugins/beta/datasets/dist/esm/im-datasets-ml-adapter.js +1 -0
  34. package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -1
  35. package/plugins/beta/datasets/dist/esm/index.js +1 -1
  36. package/plugins/beta/datasets/dist/umd/im-datasets-ml-adapter.js +2 -0
  37. package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +2 -0
  38. package/plugins/beta/datasets/dist/umd/index.js +2 -0
  39. package/plugins/beta/datasets/src/adapters/loadLayerAdapter.js +33 -0
  40. package/plugins/beta/datasets/src/adapters/maplibre/layerBuilders.js +44 -120
  41. package/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +145 -245
  42. package/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.test.js +516 -0
  43. package/plugins/beta/datasets/src/adapters/maplibre/registry/mapLibreDataset.js +191 -0
  44. package/plugins/beta/datasets/src/adapters/maplibre/registry/mapLibreDataset.test.js +441 -0
  45. package/plugins/beta/datasets/src/api/addDataset.js +2 -5
  46. package/plugins/beta/datasets/src/api/addDataset.test.js +16 -0
  47. package/plugins/beta/datasets/src/api/getOpacity.js +14 -14
  48. package/plugins/beta/datasets/src/api/getOpacity.test.js +72 -0
  49. package/plugins/beta/datasets/src/api/getStyle.js +8 -9
  50. package/plugins/beta/datasets/src/api/getStyle.test.js +42 -0
  51. package/plugins/beta/datasets/src/api/removeDataset.js +7 -7
  52. package/plugins/beta/datasets/src/api/removeDataset.test.js +45 -0
  53. package/plugins/beta/datasets/src/api/setData.js +9 -3
  54. package/plugins/beta/datasets/src/api/setData.test.js +62 -0
  55. package/plugins/beta/datasets/src/api/setDatasetVisibility.js +5 -30
  56. package/plugins/beta/datasets/src/api/setDatasetVisibility.test.js +31 -0
  57. package/plugins/beta/datasets/src/api/setFeatureVisibility.js +3 -21
  58. package/plugins/beta/datasets/src/api/setFeatureVisibility.test.js +32 -0
  59. package/plugins/beta/datasets/src/api/setGlobals.js +20 -0
  60. package/plugins/beta/datasets/src/api/setGlobals.test.js +63 -0
  61. package/plugins/beta/datasets/src/api/setOpacity.js +8 -27
  62. package/plugins/beta/datasets/src/api/setOpacity.test.js +31 -0
  63. package/plugins/beta/datasets/src/api/setStyle.js +5 -21
  64. package/plugins/beta/datasets/src/api/setStyle.test.js +53 -0
  65. package/plugins/beta/datasets/src/components/Key/Key.jsx +51 -0
  66. package/plugins/beta/datasets/src/components/Key/KeyGroupItem.jsx +18 -0
  67. package/plugins/beta/datasets/src/components/Key/KeyItem.jsx +20 -0
  68. package/plugins/beta/datasets/src/components/Key/KeySvg.jsx +22 -0
  69. package/plugins/beta/datasets/src/components/Key/KeySvg.test.jsx +87 -0
  70. package/plugins/beta/datasets/src/components/Key/KeySvgLine.jsx +19 -0
  71. package/plugins/beta/datasets/src/components/Key/KeySvgLine.test.jsx +71 -0
  72. package/plugins/beta/datasets/src/components/{KeySvgPattern.jsx → Key/KeySvgPattern.jsx} +3 -4
  73. package/plugins/beta/datasets/src/components/Key/KeySvgPattern.test.jsx +55 -0
  74. package/plugins/beta/datasets/src/components/Key/KeySvgRect.jsx +22 -0
  75. package/plugins/beta/datasets/src/components/Key/KeySvgRect.test.jsx +85 -0
  76. package/plugins/beta/datasets/src/components/{KeySvgSymbol.jsx → Key/KeySvgSymbol.jsx} +5 -5
  77. package/plugins/beta/datasets/src/components/Key/KeySvgSymbol.test.jsx +80 -0
  78. package/plugins/beta/datasets/src/components/Key/svgProperties.test.js +61 -0
  79. package/plugins/beta/datasets/src/{panels → components/LayersMenu}/Layers.module.scss +4 -0
  80. package/plugins/beta/datasets/src/components/LayersMenu/LayersMenu.jsx +33 -0
  81. package/plugins/beta/datasets/src/components/LayersMenu/LayersMenuCheckbox.jsx +31 -0
  82. package/plugins/beta/datasets/src/components/LayersMenu/LayersMenuGroupWrapper.jsx +19 -0
  83. package/plugins/beta/datasets/src/datasets.scss +2 -2
  84. package/plugins/beta/datasets/src/fetch/createDynamicSource.js +8 -8
  85. package/plugins/beta/datasets/src/{DatasetsInit.jsx → initialise/DatasetsInit.jsx} +19 -21
  86. package/plugins/beta/datasets/src/{defaults.js → initialise/defaults.js} +17 -2
  87. package/plugins/beta/datasets/src/initialise/defaults.test.js +127 -0
  88. package/plugins/beta/datasets/src/{datasets.js → initialise/initialiseDatasets.js} +30 -26
  89. package/plugins/beta/datasets/src/initialise/initialiseDatasets.test.js +273 -0
  90. package/plugins/beta/datasets/src/manifest.js +8 -6
  91. package/plugins/beta/datasets/src/reducers/__data__/demoDatasets.js +253 -0
  92. package/plugins/beta/datasets/src/reducers/datasetsToMenu.js +78 -0
  93. package/plugins/beta/datasets/src/reducers/datasetsToMenu.test.js +35 -0
  94. package/plugins/beta/datasets/src/reducers/mappedDatasetsReducer.js +34 -0
  95. package/plugins/beta/datasets/src/reducers/mappedDatasetsReducer.test.js +117 -0
  96. package/plugins/beta/datasets/src/reducers/pluginState.js +206 -0
  97. package/plugins/beta/datasets/src/reducers/pluginState.test.js +386 -0
  98. package/plugins/beta/datasets/src/registry/__mocks__/datasetRegistry.js +29 -0
  99. package/plugins/beta/datasets/src/registry/createDataset.js +5 -0
  100. package/plugins/beta/datasets/src/registry/dataset.js +172 -0
  101. package/plugins/beta/datasets/src/registry/dataset.test.js +483 -0
  102. package/plugins/beta/datasets/src/registry/datasetDefinitionCache.js +35 -0
  103. package/plugins/beta/datasets/src/registry/datasetRegistry.js +116 -0
  104. package/plugins/beta/datasets/src/registry/datasetRegistry.test.js +335 -0
  105. package/plugins/beta/datasets/src/registry/dynamicGeoJson.js +17 -0
  106. package/plugins/beta/datasets/src/registry/globalDataset.js +36 -0
  107. package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
  108. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
  109. package/plugins/beta/draw-ml/src/events.js +13 -1
  110. package/plugins/beta/draw-ml/src/index.js +1 -1
  111. package/plugins/beta/draw-ml/src/manifest.js +3 -0
  112. package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
  113. package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
  114. package/plugins/interact/src/InteractInit.jsx +2 -2
  115. package/plugins/interact/src/InteractInit.test.js +1 -1
  116. package/plugins/interact/src/events.js +2 -29
  117. package/plugins/interact/src/events.test.js +4 -102
  118. package/plugins/interact/src/hooks/useAttachEvents.js +3 -4
  119. package/plugins/interact/src/hooks/useAttachEvents.test.js +1 -3
  120. package/plugins/interact/src/hooks/useInteractionHandlers.js +10 -1
  121. package/plugins/interact/src/hooks/useInteractionHandlers.test.js +23 -4
  122. package/plugins/interact/src/manifest.js +0 -35
  123. package/plugins/interact/src/manifest.test.js +3 -43
  124. package/providers/beta/esri/dist/esm/im-esri-provider.js +1 -1
  125. package/providers/beta/esri/src/esriProvider.js +4 -0
  126. package/providers/beta/openlayers/dist/esm/im-openlayers-provider.js +1 -1
  127. package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js +1 -1
  128. package/providers/beta/openlayers/src/openlayersProvider.js +4 -0
  129. package/providers/beta/openlayers/src/openlayersProvider.test.js +5 -0
  130. package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
  131. package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
  132. package/providers/maplibre/src/maplibreProvider.js +6 -4
  133. package/providers/maplibre/src/maplibreProvider.test.js +5 -0
  134. package/providers/maplibre/src/utils/highlightFeatures.js +74 -25
  135. package/providers/maplibre/src/utils/highlightFeatures.test.js +132 -0
  136. package/providers/maplibre/src/utils/patternImages.js +2 -4
  137. package/providers/maplibre/src/utils/symbolImages.js +1 -4
  138. package/rollup.esm.mjs +2 -8
  139. package/src/App/components/MapButton/MapButton.jsx +16 -1
  140. package/src/App/components/MapButton/MapButton.module.scss +12 -1
  141. package/src/App/components/MapButton/MapButton.test.jsx +40 -1
  142. package/src/App/components/Tooltip/getTooltipPosition.js +2 -2
  143. package/src/App/hooks/useContinueEnabledEvaluator.js +31 -0
  144. package/src/App/hooks/useContinueEnabledEvaluator.test.js +118 -0
  145. package/src/App/hooks/useEvaluateProp.js +15 -15
  146. package/src/App/hooks/useEvaluateProp.test.js +80 -76
  147. package/src/App/hooks/useLayoutMeasurements.js +1 -3
  148. package/src/App/hooks/useResizeObserver.js +12 -1
  149. package/src/App/hooks/useResizeObserver.test.js +1 -0
  150. package/src/App/renderer/PluginInits.jsx +2 -0
  151. package/src/App/renderer/mapButtons.js +21 -1
  152. package/src/App/renderer/mapButtons.test.js +46 -1
  153. package/src/App/store/appReducer.js +1 -1
  154. package/src/App/store/appReducer.test.js +37 -0
  155. package/src/InteractiveMap/InteractiveMap.js +10 -0
  156. package/src/InteractiveMap/InteractiveMap.test.js +8 -0
  157. package/src/InteractiveMap/polyfills.js +37 -6
  158. package/src/InteractiveMap/polyfills.test.js +132 -101
  159. package/src/config/appConfig.js +25 -0
  160. package/src/config/appConfig.test.js +50 -0
  161. package/src/config/defaults.js +1 -0
  162. package/src/scss/settings/_dimensions.scss +1 -0
  163. package/src/services/patternRegistry.js +8 -4
  164. package/src/types.js +7 -0
  165. package/src/utils/patternUtils.js +1 -2
  166. package/src/utils/patternUtils.test.js +9 -0
  167. package/webpack.dev.mjs +10 -0
  168. package/webpack.umd.mjs +1 -1
  169. package/plugins/beta/datasets/dist/adapters/maplibre/esm/im-datasets-ml-adapter.js +0 -1
  170. package/plugins/beta/datasets/dist/adapters/maplibre/esm/index.js +0 -1
  171. package/plugins/beta/datasets/dist/umd/maplibre/im-datasets-maplibre-adapter.js +0 -2
  172. package/plugins/beta/datasets/dist/umd/maplibre/im-datasets-plugin.js +0 -2
  173. package/plugins/beta/datasets/dist/umd/maplibre/index.js +0 -2
  174. package/plugins/beta/datasets/src/adapters/maplibre/index.js +0 -18
  175. package/plugins/beta/datasets/src/adapters/maplibre/layerIds.js +0 -75
  176. package/plugins/beta/datasets/src/adapters/maplibre/patternImages.js +0 -27
  177. package/plugins/beta/datasets/src/adapters/maplibre/symbolImages.js +0 -31
  178. package/plugins/beta/datasets/src/components/KeySvg.jsx +0 -24
  179. package/plugins/beta/datasets/src/components/KeySvgLine.jsx +0 -19
  180. package/plugins/beta/datasets/src/components/KeySvgRect.jsx +0 -22
  181. package/plugins/beta/datasets/src/index.maplibre.umd.js +0 -23
  182. package/plugins/beta/datasets/src/panels/Key.jsx +0 -62
  183. package/plugins/beta/datasets/src/panels/Layers.jsx +0 -141
  184. package/plugins/beta/datasets/src/reducer.js +0 -219
  185. package/plugins/beta/datasets/src/reducers/keyReducer.js +0 -34
  186. package/plugins/beta/datasets/src/utils/filters.js +0 -34
  187. package/plugins/beta/datasets/src/utils/mergeSublayer.js +0 -86
  188. package/plugins/beta/draw-ml/dist/css/index.css +0 -21
  189. /package/plugins/beta/datasets/dist/umd/{maplibre/im-datasets-maplibre-adapter.js.LICENSE.txt → im-datasets-ml-adapter.js.LICENSE.txt} +0 -0
  190. /package/plugins/beta/datasets/dist/umd/{maplibre/im-datasets-plugin.js.LICENSE.txt → im-datasets-plugin.js.LICENSE.txt} +0 -0
  191. /package/plugins/beta/datasets/dist/umd/{maplibre/index.js.LICENSE.txt → index.js.LICENSE.txt} +0 -0
  192. /package/plugins/beta/datasets/src/components/{EmptyKey.jsx → Key/EmptyKey.jsx} +0 -0
  193. /package/plugins/beta/datasets/src/components/{EmptyKey.test.jsx → Key/EmptyKey.test.jsx} +0 -0
  194. /package/plugins/beta/datasets/src/{panels → components/Key}/Key.module.scss +0 -0
  195. /package/plugins/beta/datasets/src/components/{svgProperties.js → Key/svgProperties.js} +0 -0
@@ -1,17 +1,17 @@
1
- export const getOpacity = ({ pluginState }, options) => {
2
- const { datasetId, sublayerId } = options || {}
1
+ import { logger } from '../../../../../src/services/logger.js'
2
+ import { datasetRegistry } from '../registry/datasetRegistry.js'
3
3
 
4
- if (sublayerId) {
5
- const dataset = pluginState.datasets?.find(d => d.id === datasetId)
6
- const sublayer = dataset?.sublayers?.find(s => s.id === sublayerId)
7
- return sublayer?.style?.opacity ?? null
4
+ export const getOpacity = ({ pluginState: { globals } }, options = {}) => {
5
+ const { datasetId, sublayerId } = options
6
+ const fullId = sublayerId ? `${datasetId}-${sublayerId}` : datasetId
7
+ if (fullId) {
8
+ const registryDataset = datasetRegistry.getDataset(fullId)
9
+ if (!registryDataset) {
10
+ logger.warn(`getOpacity: Dataset with id ${fullId} not found`)
11
+ return null
12
+ }
13
+ return registryDataset.opacity ?? null
8
14
  }
9
-
10
- if (datasetId) {
11
- const dataset = pluginState.datasets?.find(d => d.id === datasetId)
12
- return dataset?.opacity ?? null
13
- }
14
-
15
- // Global — return first dataset's opacity
16
- return pluginState.datasets?.[0]?.opacity ?? null
15
+ // Global
16
+ return globals.opacity ?? null
17
17
  }
@@ -0,0 +1,72 @@
1
+ import { getOpacity } from './getOpacity.js'
2
+ import { datasetRegistry } from '../registry/datasetRegistry.js'
3
+ import { logger } from '../../../../../src/services/logger.js'
4
+
5
+ jest.mock('../registry/datasetRegistry.js', () => ({
6
+ datasetRegistry: { getDataset: jest.fn() }
7
+ }))
8
+
9
+ jest.mock('../../../../../src/services/logger.js', () => ({
10
+ logger: { warn: jest.fn() }
11
+ }))
12
+
13
+ describe('getOpacity', () => {
14
+ beforeEach(() => {
15
+ jest.clearAllMocks()
16
+ })
17
+
18
+ describe('global (no datasetId)', () => {
19
+ it('returns globals.opacity when set', () => {
20
+ const ctx = { pluginState: { globals: { opacity: 0.5 } } }
21
+ expect(getOpacity(ctx)).toBe(0.5)
22
+ })
23
+
24
+ it('returns null when globals.opacity is undefined', () => {
25
+ const ctx = { pluginState: { globals: {} } }
26
+ expect(getOpacity(ctx)).toBeNull()
27
+ })
28
+ })
29
+
30
+ describe('with datasetId', () => {
31
+ it('returns opacity from the registry dataset', () => {
32
+ datasetRegistry.getDataset.mockReturnValue({ opacity: 0.7 })
33
+ const ctx = { pluginState: { globals: {} } }
34
+ expect(getOpacity(ctx, { datasetId: 'my-dataset' })).toBe(0.7)
35
+ })
36
+
37
+ it('returns null when the dataset has no opacity property', () => {
38
+ datasetRegistry.getDataset.mockReturnValue({})
39
+ const ctx = { pluginState: { globals: {} } }
40
+ expect(getOpacity(ctx, { datasetId: 'my-dataset' })).toBeNull()
41
+ })
42
+
43
+ it('returns null and warns when the dataset is not found', () => {
44
+ datasetRegistry.getDataset.mockReturnValue(undefined)
45
+ const ctx = { pluginState: { globals: {} } }
46
+ expect(getOpacity(ctx, { datasetId: 'missing' })).toBeNull()
47
+ expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('missing'))
48
+ })
49
+ })
50
+
51
+ describe('with sublayerId', () => {
52
+ it('combines datasetId and sublayerId for the registry lookup', () => {
53
+ datasetRegistry.getDataset.mockReturnValue({ opacity: 0.3 })
54
+ const ctx = { pluginState: { globals: {} } }
55
+ getOpacity(ctx, { datasetId: 'parent', sublayerId: 'child' })
56
+ expect(datasetRegistry.getDataset).toHaveBeenCalledWith('parent-child')
57
+ })
58
+
59
+ it('returns opacity for the combined id', () => {
60
+ datasetRegistry.getDataset.mockReturnValue({ opacity: 0.3 })
61
+ const ctx = { pluginState: { globals: {} } }
62
+ expect(getOpacity(ctx, { datasetId: 'parent', sublayerId: 'child' })).toBe(0.3)
63
+ })
64
+
65
+ it('returns null and warns when the sublayer dataset is not found', () => {
66
+ datasetRegistry.getDataset.mockReturnValue(undefined)
67
+ const ctx = { pluginState: { globals: {} } }
68
+ expect(getOpacity(ctx, { datasetId: 'parent', sublayerId: 'missing' })).toBeNull()
69
+ expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('parent-missing'))
70
+ })
71
+ })
72
+ })
@@ -1,13 +1,12 @@
1
+ import { logger } from '../../../../../src/services/logger.js'
2
+ import { datasetRegistry } from '../registry/datasetRegistry.js'
3
+
1
4
  export const getStyle = ({ pluginState }, { datasetId, sublayerId } = {}) => {
2
- const dataset = pluginState.datasets?.find(d => d.id === datasetId)
3
- if (!dataset) {
5
+ datasetId = sublayerId ? `${datasetId}-${sublayerId}` : datasetId
6
+ const registryDataset = datasetRegistry.getDataset(datasetId)
7
+ if (!registryDataset) {
8
+ logger.warn(`getStyle: Dataset with id ${datasetId} not found`)
4
9
  return null
5
10
  }
6
-
7
- if (sublayerId) {
8
- const sublayer = dataset.sublayers?.find(s => s.id === sublayerId)
9
- return sublayer?.style ?? null
10
- }
11
-
12
- return dataset.style ?? null
11
+ return registryDataset.style
13
12
  }
@@ -0,0 +1,42 @@
1
+ import { getStyle } from './getStyle.js'
2
+ import { datasetRegistry } from '../registry/datasetRegistry.js'
3
+ import { logger } from '../../../../../src/services/logger.js'
4
+
5
+ jest.mock('../registry/datasetRegistry.js', () => ({
6
+ datasetRegistry: { getDataset: jest.fn() }
7
+ }))
8
+
9
+ jest.mock('../../../../../src/services/logger.js', () => ({
10
+ logger: { warn: jest.fn() }
11
+ }))
12
+
13
+ describe('getStyle', () => {
14
+ beforeEach(() => {
15
+ jest.clearAllMocks()
16
+ })
17
+
18
+ it('returns the style for a known dataset', () => {
19
+ const style = { fill: 'blue', stroke: 'red' }
20
+ datasetRegistry.getDataset.mockReturnValue({ style })
21
+ expect(getStyle({}, { datasetId: 'roads' })).toBe(style)
22
+ })
23
+
24
+ it('returns style for the combined sublayer id', () => {
25
+ const style = { fill: 'green' }
26
+ datasetRegistry.getDataset.mockReturnValue({ style })
27
+ expect(getStyle({}, { datasetId: 'parent', sublayerId: 'child' })).toBe(style)
28
+ expect(datasetRegistry.getDataset).toHaveBeenCalledWith('parent-child')
29
+ })
30
+
31
+ it('returns null and warns when a sublayer dataset is not found', () => {
32
+ datasetRegistry.getDataset.mockReturnValue(undefined)
33
+ expect(getStyle({}, { datasetId: 'parent', sublayerId: 'missing' })).toBeNull()
34
+ expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('parent-missing'))
35
+ })
36
+
37
+ it('uses undefined datasetId when options argument is omitted', () => {
38
+ datasetRegistry.getDataset.mockReturnValue(undefined)
39
+ expect(getStyle({})).toBeNull()
40
+ expect(logger.warn).toHaveBeenCalledWith('getStyle: Dataset with id undefined not found')
41
+ })
42
+ })
@@ -1,9 +1,9 @@
1
- export const removeDataset = ({ pluginState }, datasetId) => {
2
- const dataset = pluginState.datasets?.find(d => d.id === datasetId)
3
- if (!dataset) {
4
- return
5
- }
1
+ import { layerAdapter } from '../adapters/loadLayerAdapter.js'
6
2
 
7
- pluginState.layerAdapter?.removeDataset(dataset, pluginState.datasets)
8
- pluginState.dispatch({ type: 'REMOVE_DATASET', payload: { id: datasetId } })
3
+ export const removeDataset = ({ pluginState: { dispatch } }, datasetId) => {
4
+ // Here we need to remove the dataset from the adapter before removing it from state,
5
+ // otherwise the datasetDefinition will have been removed from the registry by the
6
+ // time the adapter tries to remove the dataset, which causes an error
7
+ layerAdapter.removeDataset(datasetId)
8
+ dispatch({ type: 'REMOVE_DATASET', payload: { id: datasetId } })
9
9
  }
@@ -0,0 +1,45 @@
1
+ import { removeDataset } from './removeDataset.js'
2
+ import { layerAdapter } from '../adapters/loadLayerAdapter.js'
3
+
4
+ jest.mock('../adapters/loadLayerAdapter.js', () => ({
5
+ layerAdapter: {
6
+ removeDataset: jest.fn()
7
+ }
8
+ }))
9
+
10
+ describe('removeDataset', () => {
11
+ const dispatch = jest.fn()
12
+ it('calls layerAdapter.removeDataset with the datasetId', () => {
13
+ removeDataset({ pluginState: { layerAdapter, dispatch } }, 'my-dataset')
14
+
15
+ expect(layerAdapter.removeDataset).toHaveBeenCalledWith('my-dataset')
16
+ })
17
+
18
+ it('dispatches REMOVE_DATASET with the datasetId', () => {
19
+ removeDataset({ pluginState: { layerAdapter, dispatch } }, 'my-dataset')
20
+
21
+ expect(dispatch).toHaveBeenCalledWith({
22
+ type: 'REMOVE_DATASET',
23
+ payload: { id: 'my-dataset' }
24
+ })
25
+ })
26
+
27
+ it('calls layerAdapter.removeDataset before dispatching', () => {
28
+ const callOrder = []
29
+ dispatch.mockImplementationOnce(() => callOrder.push('dispatch'))
30
+ layerAdapter.removeDataset.mockImplementationOnce(() => callOrder.push('adapter'))
31
+
32
+ removeDataset({ pluginState: { layerAdapter, dispatch } }, 'my-dataset')
33
+
34
+ expect(callOrder).toEqual(['adapter', 'dispatch'])
35
+ })
36
+
37
+ it('still dispatches REMOVE_DATASET when layerAdapter is undefined', () => {
38
+ removeDataset({ pluginState: { layerAdapter: undefined, dispatch } }, 'my-dataset')
39
+
40
+ expect(dispatch).toHaveBeenCalledWith({
41
+ type: 'REMOVE_DATASET',
42
+ payload: { id: 'my-dataset' }
43
+ })
44
+ })
45
+ })
@@ -1,10 +1,16 @@
1
1
  import { logger } from '../../../../../src/services/logger.js'
2
+ import { datasetRegistry } from '../registry/datasetRegistry.js'
3
+ import { layerAdapter } from '../adapters/loadLayerAdapter.js'
2
4
 
3
5
  export const setData = ({ pluginState }, geojson, { datasetId }) => {
4
- const dataset = pluginState.datasets?.find(d => d.id === datasetId)
5
- if (dataset?.tiles) {
6
+ const registryDataset = datasetRegistry.getDataset(datasetId)
7
+ if (!registryDataset) {
8
+ logger.warn(`setData: Dataset with id ${datasetId} not found`)
9
+ return
10
+ }
11
+ if (registryDataset?.tiles) {
6
12
  logger.warn(`setData called on vector tile dataset "${datasetId}" — has no effect`)
7
13
  return
8
14
  }
9
- pluginState.layerAdapter?.setData(datasetId, geojson)
15
+ layerAdapter.setData(datasetId, geojson)
10
16
  }
@@ -0,0 +1,62 @@
1
+ import { setData } from './setData.js'
2
+ import { datasetRegistry } from '../registry/datasetRegistry.js'
3
+ import { logger } from '../../../../../src/services/logger.js'
4
+ import { layerAdapter } from '../adapters/loadLayerAdapter.js'
5
+
6
+ jest.mock('../adapters/loadLayerAdapter.js', () => ({
7
+ layerAdapter: {
8
+ setData: jest.fn()
9
+ }
10
+ }))
11
+
12
+ jest.mock('../registry/datasetRegistry.js', () => ({
13
+ datasetRegistry: { getDataset: jest.fn() }
14
+ }))
15
+
16
+ jest.mock('../../../../../src/services/logger.js', () => ({
17
+ logger: { warn: jest.fn() }
18
+ }))
19
+
20
+ describe('setData', () => {
21
+ const geojson = { type: 'FeatureCollection', features: [] }
22
+ beforeEach(() => {
23
+ jest.clearAllMocks()
24
+ })
25
+
26
+ it('calls layerAdapter.setData with datasetId and geojson', () => {
27
+ datasetRegistry.getDataset.mockReturnValue({ id: 'my-dataset' })
28
+
29
+ setData({ pluginState: { layerAdapter } }, geojson, { datasetId: 'my-dataset' })
30
+
31
+ expect(layerAdapter.setData).toHaveBeenCalledWith('my-dataset', geojson)
32
+ })
33
+
34
+ it('does nothing when the dataset is not found in the registry', () => {
35
+ datasetRegistry.getDataset.mockReturnValue(undefined)
36
+
37
+ setData({ pluginState: { layerAdapter } }, geojson, { datasetId: 'missing' })
38
+
39
+ expect(layerAdapter.setData).not.toHaveBeenCalled()
40
+ expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('missing'))
41
+ })
42
+
43
+ it('warns and does nothing when dataset is a vector tile dataset', () => {
44
+ datasetRegistry.getDataset.mockReturnValue({
45
+ id: 'tiles-dataset',
46
+ tiles: ['https://example.com/{z}/{x}/{y}']
47
+ })
48
+
49
+ setData({ pluginState: { layerAdapter } }, geojson, { datasetId: 'tiles-dataset' })
50
+
51
+ expect(layerAdapter.setData).not.toHaveBeenCalled()
52
+ expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('tiles-dataset'))
53
+ })
54
+
55
+ it("doesn't throw when layerAdapter is undefined", () => {
56
+ datasetRegistry.getDataset.mockReturnValue({ id: 'my-dataset' })
57
+ // Should not throw even if layerAdapter is undefined
58
+ expect(() =>
59
+ setData({ pluginState: { layerAdapter: undefined } }, geojson, { datasetId: 'my-dataset' })
60
+ ).not.toThrow()
61
+ })
62
+ })
@@ -1,37 +1,12 @@
1
- export const setDatasetVisibility = ({ pluginState }, visible, options = {}) => {
2
- const { datasetId, sublayerId } = options
3
-
4
- if (sublayerId) {
5
- const visibility = visible ? 'visible' : 'hidden'
6
- pluginState.layerAdapter?.[visible ? 'showSublayer' : 'hideSublayer'](datasetId, sublayerId)
7
- pluginState.dispatch({ type: 'SET_SUBLAYER_VISIBILITY', payload: { datasetId, sublayerId, visibility } })
8
- return
9
- }
1
+ export const setDatasetVisibility = ({ pluginState: { dispatch } }, visible, ids = {}) => {
2
+ const { datasetId: datasetToUpdate, sublayerId } = ids
3
+ const datasetId = sublayerId ? `${datasetToUpdate}-${sublayerId}` : datasetToUpdate
10
4
 
11
5
  if (datasetId) {
12
- pluginState.layerAdapter?.[visible ? 'showDataset' : 'hideDataset'](datasetId)
13
- pluginState.dispatch({ type: 'SET_DATASET_VISIBILITY', payload: { id: datasetId, visibility: visible ? 'visible' : 'hidden' } })
14
- if (visible) {
15
- const dataset = pluginState.datasets?.find(d => d.id === datasetId)
16
- Object.entries(dataset?.sublayerVisibility || {}).forEach(([subId, vis]) => {
17
- if (vis === 'hidden') {
18
- pluginState.layerAdapter?.hideSublayer(datasetId, subId)
19
- }
20
- })
21
- }
6
+ dispatch({ type: 'SET_DATASET_VISIBILITY', payload: { datasetId, visible } })
22
7
  return
23
8
  }
24
9
 
25
10
  // Global
26
- pluginState.dispatch({ type: 'SET_GLOBAL_VISIBILITY', payload: { visibility: visible ? 'visible' : 'hidden' } })
27
- pluginState.datasets?.forEach(dataset => {
28
- pluginState.layerAdapter?.[visible ? 'showDataset' : 'hideDataset'](dataset.id)
29
- if (visible) {
30
- Object.entries(dataset.sublayerVisibility || {}).forEach(([subId, vis]) => {
31
- if (vis === 'hidden') {
32
- pluginState.layerAdapter?.hideSublayer(dataset.id, subId)
33
- }
34
- })
35
- }
36
- })
11
+ dispatch({ type: 'SET_GLOBAL_VISIBILITY', payload: { visible } })
37
12
  }
@@ -0,0 +1,31 @@
1
+ import { setDatasetVisibility } from './setDatasetVisibility.js'
2
+
3
+ describe('setDatasetVisibility', () => {
4
+ const dispatch = jest.fn()
5
+ it('dispatches SET_DATASET_VISIBILITY with visible false', () => {
6
+ setDatasetVisibility({ pluginState: { dispatch } }, false, { datasetId: 'my-dataset' })
7
+
8
+ expect(dispatch).toHaveBeenCalledWith({
9
+ type: 'SET_DATASET_VISIBILITY',
10
+ payload: { datasetId: 'my-dataset', visible: false }
11
+ })
12
+ })
13
+
14
+ it('combines datasetId and sublayerId for the dispatched id', () => {
15
+ setDatasetVisibility({ pluginState: { dispatch } }, true, { datasetId: 'parent', sublayerId: 'child' })
16
+
17
+ expect(dispatch).toHaveBeenCalledWith({
18
+ type: 'SET_DATASET_VISIBILITY',
19
+ payload: { datasetId: 'parent-child', visible: true }
20
+ })
21
+ })
22
+
23
+ it('dispatches SET_GLOBAL_VISIBILITY when ids defaults to empty object', () => {
24
+ setDatasetVisibility({ pluginState: { dispatch } }, false)
25
+
26
+ expect(dispatch).toHaveBeenCalledWith({
27
+ type: 'SET_GLOBAL_VISIBILITY',
28
+ payload: { visible: false }
29
+ })
30
+ })
31
+ })
@@ -1,22 +1,4 @@
1
- export const setFeatureVisibility = ({ pluginState }, visible, featureIds, { datasetId, idProperty = null } = {}) => {
2
- const dataset = pluginState.datasets?.find(d => d.id === datasetId)
3
- if (!dataset) {
4
- return
5
- }
6
-
7
- if (visible) {
8
- const existingHidden = pluginState.hiddenFeatures[datasetId]
9
- if (!existingHidden) {
10
- return
11
- }
12
- const remainingHiddenIds = existingHidden.ids.filter(id => !featureIds.includes(id))
13
- pluginState.dispatch({ type: 'SHOW_FEATURES', payload: { layerId: datasetId, featureIds } })
14
- pluginState.layerAdapter?.showFeatures(dataset, idProperty, remainingHiddenIds)
15
- } else {
16
- const existingHidden = pluginState.hiddenFeatures[datasetId]
17
- const existingIds = existingHidden?.ids || []
18
- const allHiddenIds = [...new Set([...existingIds, ...featureIds])]
19
- pluginState.dispatch({ type: 'HIDE_FEATURES', payload: { layerId: datasetId, idProperty, featureIds } })
20
- pluginState.layerAdapter?.hideFeatures(dataset, idProperty, allHiddenIds)
21
- }
1
+ export const setFeatureVisibility = ({ pluginState: { dispatch } }, visible, featureIds, { datasetId } = {}) => {
2
+ const type = visible ? 'SHOW_FEATURES' : 'HIDE_FEATURES'
3
+ dispatch({ type, payload: { datasetId, featureIds } })
22
4
  }
@@ -0,0 +1,32 @@
1
+ import { setFeatureVisibility } from './setFeatureVisibility.js'
2
+
3
+ describe('setFeatureVisibility', () => {
4
+ const dispatch = jest.fn()
5
+ const featureIds = [1, 2, 3]
6
+ it('dispatches SHOW_FEATURES when visible is true', () => {
7
+ setFeatureVisibility({ pluginState: { dispatch } }, true, featureIds, { datasetId: 'my-dataset' })
8
+
9
+ expect(dispatch).toHaveBeenCalledWith({
10
+ type: 'SHOW_FEATURES',
11
+ payload: { datasetId: 'my-dataset', featureIds }
12
+ })
13
+ })
14
+
15
+ it('dispatches HIDE_FEATURES when visible is false', () => {
16
+ setFeatureVisibility({ pluginState: { dispatch } }, false, featureIds, { datasetId: 'my-dataset' })
17
+
18
+ expect(dispatch).toHaveBeenCalledWith({
19
+ type: 'HIDE_FEATURES',
20
+ payload: { datasetId: 'my-dataset', featureIds }
21
+ })
22
+ })
23
+
24
+ it('uses undefined datasetId when options are not provided', () => {
25
+ setFeatureVisibility({ pluginState: { dispatch } }, true, featureIds)
26
+
27
+ expect(dispatch).toHaveBeenCalledWith({
28
+ type: 'SHOW_FEATURES',
29
+ payload: { datasetId: undefined, featureIds }
30
+ })
31
+ })
32
+ })
@@ -0,0 +1,20 @@
1
+ import { logger } from '../../../../../src/services/logger.js'
2
+ export const setGlobals = ({ pluginState: { dispatch } }, values) => {
3
+ const { opacityMode } = values
4
+ const payload = {}
5
+ let valuesSet = false
6
+ if (opacityMode) {
7
+ if (!['dataset', 'global', 'multiply'].includes(opacityMode)) {
8
+ logger.warn(`Ignoring invalid opacityMode: ${opacityMode}. Must be one of 'dataset', 'global' or 'multiply'.`)
9
+ } else {
10
+ valuesSet = true
11
+ payload.opacityMode = opacityMode
12
+ }
13
+ }
14
+
15
+ if (valuesSet) {
16
+ dispatch({ type: 'SET_GLOBAL_STATE', payload })
17
+ } else {
18
+ logger.warn('setGlobals: requires a valid value for opacityMode to be set')
19
+ }
20
+ }
@@ -0,0 +1,63 @@
1
+ import { setGlobals } from './setGlobals.js'
2
+ import { logger } from '../../../../../src/services/logger.js'
3
+
4
+ jest.mock('../../../../../src/services/logger.js', () => ({
5
+ logger: { warn: jest.fn() }
6
+ }))
7
+
8
+ describe('setGlobals', () => {
9
+ const dispatch = jest.fn()
10
+
11
+ beforeEach(() => {
12
+ jest.clearAllMocks()
13
+ })
14
+
15
+ it('dispatches SET_GLOBAL_STATE with opacityMode when valid value is provided', () => {
16
+ setGlobals({ pluginState: { dispatch } }, { opacityMode: 'dataset' })
17
+
18
+ expect(dispatch).toHaveBeenCalledWith({
19
+ type: 'SET_GLOBAL_STATE',
20
+ payload: { opacityMode: 'dataset' }
21
+ })
22
+ })
23
+
24
+ it.each(['dataset', 'global', 'multiply'])('accepts valid opacityMode: %s', (opacityMode) => {
25
+ setGlobals({ pluginState: { dispatch } }, { opacityMode })
26
+
27
+ expect(dispatch).toHaveBeenCalledWith({
28
+ type: 'SET_GLOBAL_STATE',
29
+ payload: { opacityMode }
30
+ })
31
+ expect(logger.warn).not.toHaveBeenCalled()
32
+ })
33
+
34
+ it('warns and does not dispatch when opacityMode is invalid', () => {
35
+ setGlobals({ pluginState: { dispatch } }, { opacityMode: 'invalid' })
36
+
37
+ expect(logger.warn).toHaveBeenCalledWith(
38
+ "Ignoring invalid opacityMode: invalid. Must be one of 'dataset', 'global' or 'multiply'."
39
+ )
40
+ expect(dispatch).not.toHaveBeenCalled()
41
+ })
42
+
43
+ it('warns and does not dispatch when no valid values are provided', () => {
44
+ setGlobals({ pluginState: { dispatch } }, {})
45
+
46
+ expect(logger.warn).toHaveBeenCalledWith(
47
+ 'setGlobals: requires a valid value for opacityMode to be set'
48
+ )
49
+ expect(dispatch).not.toHaveBeenCalled()
50
+ })
51
+
52
+ it('warns about invalid opacityMode and then warns no valid values set', () => {
53
+ setGlobals({ pluginState: { dispatch } }, { opacityMode: 'bad' })
54
+
55
+ expect(logger.warn).toHaveBeenCalledWith(
56
+ "Ignoring invalid opacityMode: bad. Must be one of 'dataset', 'global' or 'multiply'."
57
+ )
58
+ expect(logger.warn).toHaveBeenCalledWith(
59
+ 'setGlobals: requires a valid value for opacityMode to be set'
60
+ )
61
+ expect(dispatch).not.toHaveBeenCalled()
62
+ })
63
+ })
@@ -1,29 +1,10 @@
1
- export const setOpacity = ({ pluginState }, opacity, options) => {
2
- const { datasetId, sublayerId } = options || {}
3
-
4
- if (sublayerId) {
5
- const dataset = pluginState.datasets?.find(d => d.id === datasetId)
6
- if (!dataset) {
7
- return
8
- }
9
- pluginState.dispatch({ type: 'SET_SUBLAYER_OPACITY', payload: { datasetId, sublayerId, opacity } })
10
- pluginState.layerAdapter?.setSublayerOpacity(datasetId, sublayerId, opacity)
11
- return
1
+ export const setOpacity = ({ pluginState: { dispatch } }, opacity, options = {}) => {
2
+ const { datasetId, sublayerId } = options
3
+ const fullId = sublayerId ? `${datasetId}-${sublayerId}` : datasetId
4
+ if (fullId) {
5
+ dispatch({ type: 'SET_OPACITY', payload: { datasetId: fullId, opacity } })
6
+ } else {
7
+ // Global update
8
+ dispatch({ type: 'SET_GLOBAL_OPACITY', payload: { opacity } })
12
9
  }
13
-
14
- if (datasetId) {
15
- const dataset = pluginState.datasets?.find(d => d.id === datasetId)
16
- if (!dataset) {
17
- return
18
- }
19
- pluginState.dispatch({ type: 'SET_OPACITY', payload: { datasetId, opacity } })
20
- pluginState.layerAdapter?.setOpacity(datasetId, opacity)
21
- return
22
- }
23
-
24
- // Global
25
- pluginState.dispatch({ type: 'SET_GLOBAL_OPACITY', payload: { opacity } })
26
- pluginState.datasets?.forEach(d => {
27
- pluginState.layerAdapter?.setOpacity(d.id, opacity)
28
- })
29
10
  }
@@ -0,0 +1,31 @@
1
+ import { setOpacity } from './setOpacity.js'
2
+
3
+ describe('setOpacity', () => {
4
+ const dispatch = jest.fn()
5
+ it('dispatches SET_OPACITY when datasetId is provided', () => {
6
+ setOpacity({ pluginState: { dispatch } }, 0.5, { datasetId: 'my-dataset' })
7
+
8
+ expect(dispatch).toHaveBeenCalledWith({
9
+ type: 'SET_OPACITY',
10
+ payload: { datasetId: 'my-dataset', opacity: 0.5 }
11
+ })
12
+ })
13
+
14
+ it('combines datasetId and sublayerId for the dispatched datasetId', () => {
15
+ setOpacity({ pluginState: { dispatch } }, 0.4, { datasetId: 'parent', sublayerId: 'child' })
16
+
17
+ expect(dispatch).toHaveBeenCalledWith({
18
+ type: 'SET_OPACITY',
19
+ payload: { datasetId: 'parent-child', opacity: 0.4 }
20
+ })
21
+ })
22
+
23
+ it('dispatches SET_GLOBAL_OPACITY when no datasetId is provided', () => {
24
+ setOpacity({ pluginState: { dispatch } }, 0.8, {})
25
+
26
+ expect(dispatch).toHaveBeenCalledWith({
27
+ type: 'SET_GLOBAL_OPACITY',
28
+ payload: { opacity: 0.8 }
29
+ })
30
+ })
31
+ })
@@ -1,22 +1,6 @@
1
- export const setStyle = ({ pluginState, mapState }, style, { datasetId, sublayerId } = {}) => {
2
- const dataset = pluginState.datasets?.find(d => d.id === datasetId)
3
- if (!dataset) {
4
- return
5
- }
6
-
7
- if (sublayerId) {
8
- pluginState.dispatch({ type: 'SET_SUBLAYER_STYLE', payload: { datasetId, sublayerId, styleChanges: style } })
9
- const updatedSublayerDataset = {
10
- ...dataset,
11
- sublayers: dataset.sublayers?.map(sublayer =>
12
- sublayer.id === sublayerId ? { ...sublayer, style: { ...sublayer.style, ...style } } : sublayer
13
- )
14
- }
15
- pluginState.layerAdapter?.setSublayerStyle(updatedSublayerDataset, sublayerId, mapState.mapStyle)
16
- return
17
- }
18
-
19
- pluginState.dispatch({ type: 'SET_DATASET_STYLE', payload: { datasetId, styleChanges: style } })
20
- const updatedDataset = { ...dataset, ...style }
21
- pluginState.layerAdapter?.setStyle(updatedDataset, mapState.mapStyle)
1
+ export const setStyle = ({ pluginState: { dispatch }, mapState }, styleChanges, { datasetId, sublayerId } = {}) => {
2
+ datasetId = sublayerId ? `${datasetId}-${sublayerId}` : datasetId
3
+ const mapStyle = mapState.mapStyle
4
+ const payload = { datasetId, styleChanges, mapStyle }
5
+ dispatch({ type: 'SET_DATASET_STYLE', payload })
22
6
  }