@defra/interactive-map 0.0.4-alpha → 0.0.5-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/index.js +1 -1
- package/dist/umd/im-core.js +1 -1
- package/dist/umd/index.js +1 -1
- package/docs/api.md +8 -0
- package/package.json +3 -2
- package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +2 -1
- package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js.LICENSE.txt +1 -0
- package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +2 -1
- package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js.LICENSE.txt +1 -0
- package/plugins/beta/datasets/dist/umd/index.js +1 -1
- package/plugins/beta/datasets/src/datasets.js +55 -2
- package/plugins/beta/datasets/src/fetch/createDynamicSource.js +206 -0
- package/plugins/beta/datasets/src/fetch/fetchGeoJSON.js +38 -0
- package/plugins/beta/datasets/src/handleSetMapStyle.js +8 -1
- package/plugins/beta/datasets/src/mapLayers.js +41 -3
- package/plugins/beta/datasets/src/utils/bbox.js +113 -2
- package/plugins/beta/draw-ml/dist/css/index.css +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-ml/dist/umd/index.js +1 -1
- package/plugins/beta/draw-ml/src/api/addFeature.js +15 -3
- package/plugins/beta/draw-ml/src/api/editFeature.js +16 -5
- package/plugins/beta/draw-ml/src/api/newLine.js +32 -7
- package/plugins/beta/draw-ml/src/api/newPolygon.js +33 -8
- package/plugins/beta/draw-ml/src/defaults.js +1 -0
- package/plugins/beta/draw-ml/src/draw.scss +23 -0
- package/plugins/beta/draw-ml/src/events.js +55 -113
- package/plugins/beta/draw-ml/src/manifest.js +64 -38
- package/plugins/beta/draw-ml/src/mapboxSnap.js +16 -12
- package/plugins/beta/draw-ml/src/modes/createDrawMode.js +4 -3
- package/plugins/beta/draw-ml/src/modes/editVertex/geometryHelpers.js +135 -0
- package/plugins/beta/draw-ml/src/modes/editVertex/helpers.js +2 -0
- package/plugins/beta/draw-ml/src/modes/editVertex/touchHandlers.js +134 -0
- package/plugins/beta/draw-ml/src/modes/editVertex/undoHandlers.js +133 -0
- package/plugins/beta/draw-ml/src/modes/editVertex/vertexOperations.js +141 -0
- package/plugins/beta/draw-ml/src/modes/editVertex/vertexQueries.js +121 -0
- package/plugins/beta/draw-ml/src/modes/editVertexMode.js +51 -406
- package/plugins/beta/draw-ml/src/utils/flattenStyleProperties.js +49 -0
- package/plugins/beta/frame/dist/esm/im-frame-plugin.js +1 -1
- package/plugins/beta/frame/dist/umd/im-frame-plugin.js +1 -1
- package/plugins/beta/frame/src/Frame.jsx +1 -1
- package/plugins/beta/map-styles/dist/css/index.css +1 -1
- package/plugins/beta/map-styles/dist/esm/index.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/beta/map-styles/src/mapStyles.scss +4 -1
- package/plugins/interact/dist/css/index.css +1 -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/InteractInit.jsx +1 -1
- package/plugins/interact/src/defaults.js +1 -0
- package/plugins/interact/src/events.js +37 -52
- package/plugins/interact/src/events.test.js +35 -0
- package/plugins/interact/src/hooks/useHighlightSync.js +1 -1
- package/plugins/interact/src/hooks/useInteractionHandlers.js +80 -48
- package/plugins/interact/src/hooks/useInteractionHandlers.test.js +64 -8
- package/plugins/interact/src/interact.scss +6 -0
- package/plugins/interact/src/reducer.js +25 -27
- package/plugins/interact/src/reducer.test.js +19 -6
- package/plugins/interact/src/utils/featureQueries.js +2 -2
- package/plugins/interact/src/utils/spatial.js +95 -0
- package/plugins/interact/src/utils/spatial.test.js +93 -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/umd/im-search-plugin.js +1 -1
- package/plugins/search/dist/umd/index.js +1 -1
- package/plugins/search/src/Search.jsx +2 -2
- package/plugins/search/src/components/Form/Form.jsx +1 -1
- package/plugins/search/src/components/Suggestions/Suggestions.jsx +5 -5
- package/plugins/search/src/datasets.js +1 -1
- package/plugins/search/src/defaults.js +3 -0
- package/plugins/search/src/events/fetchSuggestions.js +48 -53
- package/plugins/search/src/events/formHandlers.js +3 -2
- package/plugins/search/src/events/index.js +1 -1
- package/plugins/search/src/events/inputHandlers.js +3 -1
- package/plugins/search/src/events/suggestionHandlers.js +12 -3
- package/plugins/search/src/search.scss +4 -1
- package/plugins/search/src/utils/parseOsNamesResults.js +12 -12
- package/providers/beta/esri/src/esriProvider.js +1 -1
- package/providers/beta/esri/src/index.js +1 -1
- package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/esm/index.js +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-framework.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/defaults.js +3 -2
- package/providers/maplibre/src/index.js +22 -19
- package/providers/maplibre/src/maplibreProvider.js +13 -11
- package/providers/maplibre/src/utils/detectWebgl.js +1 -2
- package/providers/maplibre/src/utils/highlightFeatures.js +78 -67
- package/providers/maplibre/src/utils/labels.js +174 -120
- package/providers/maplibre/src/utils/maplibreFixes.js +2 -2
- package/providers/maplibre/src/utils/queryFeatures.js +146 -0
- package/providers/maplibre/src/utils/spatial.js +14 -17
- package/sonar-project.properties +39 -2
- package/src/App/components/Actions/Actions.jsx +2 -2
- package/src/App/components/Actions/Actions.module.scss +2 -2
- package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +3 -1
- package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +9 -0
- package/src/App/components/Logo/Logo.jsx +1 -1
- package/src/App/components/MapButton/MapButton.jsx +217 -41
- package/src/App/components/MapButton/MapButton.module.scss +32 -21
- package/src/App/components/MapButton/MapButton.test.jsx +128 -84
- package/src/App/components/Markers/Markers.jsx +1 -1
- package/src/App/components/Markers/Markers.module.scss +0 -5
- package/src/App/components/Panel/Panel.jsx +75 -45
- package/src/App/components/Panel/Panel.module.scss +18 -24
- package/src/App/components/Panel/Panel.test.jsx +18 -0
- package/src/App/components/PopupMenu/PopupMenu.jsx +246 -0
- package/src/App/components/PopupMenu/PopupMenu.module.scss +70 -0
- package/src/App/components/PopupMenu/PopupMenu.test.jsx +304 -0
- package/src/App/components/Tooltip/Tooltip.jsx +4 -2
- package/src/App/components/Tooltip/getTooltipPosition.js +1 -1
- package/src/App/components/Viewport/MapController.jsx +1 -1
- package/src/App/components/Viewport/MapStatus.jsx +1 -1
- package/src/App/components/Viewport/Viewport.jsx +2 -2
- package/src/App/components/Viewport/Viewport.module.scss +2 -2
- package/src/App/controls/keyboardActions.js +3 -2
- package/src/App/controls/keyboardShortcuts.js +4 -2
- package/src/App/hooks/useButtonStateEvaluator.js +58 -45
- package/src/App/hooks/useButtonStateEvaluator.test.js +36 -3
- package/src/App/hooks/useCrossHairAPI.js +49 -47
- package/src/App/hooks/useFocusVisible.js +2 -2
- package/src/App/hooks/useInterfaceAPI.js +16 -4
- package/src/App/hooks/useKeyboardHint.js +1 -1
- package/src/App/hooks/useKeyboardShortcuts.js +4 -2
- package/src/App/hooks/useMapAnnouncements.js +34 -32
- package/src/App/hooks/useMapEvents.js +12 -1
- package/src/App/hooks/useMapProviderOverrides.js +3 -3
- package/src/App/hooks/useMapStateSync.js +7 -1
- package/src/App/hooks/useMapURLSync.js +6 -1
- package/src/App/hooks/useMarkersAPI.js +88 -50
- package/src/App/hooks/useMediaQueryDispatch.test.js +48 -0
- package/src/App/hooks/useModalPanelBehaviour.js +46 -40
- package/src/App/hooks/useResizeObserver.js +2 -2
- package/src/App/initialiseApp.js +45 -38
- package/src/App/layout/Layout.jsx +6 -4
- package/src/App/layout/layout.module.scss +13 -17
- package/src/App/registry/keyboardShortcutRegistry.js +12 -2
- package/src/App/registry/keyboardShortcutRegistry.test.js +28 -0
- package/src/App/registry/mergeManifests.js +1 -1
- package/src/App/registry/panelRegistry.js +1 -1
- package/src/App/registry/panelRegistry.test.js +30 -1
- package/src/App/registry/pluginRegistry.js +14 -51
- package/src/App/registry/pluginRegistry.test.js +54 -6
- package/src/App/renderer/HtmlElementHost.jsx +186 -0
- package/src/App/renderer/HtmlElementHost.test.jsx +231 -0
- package/src/App/renderer/mapButtons.js +11 -8
- package/src/App/renderer/mapButtons.test.js +6 -4
- package/src/App/renderer/mapControls.js +6 -0
- package/src/App/renderer/mapControls.test.js +10 -2
- package/src/App/renderer/mapPanels.js +39 -26
- package/src/App/renderer/mapPanels.test.js +29 -0
- package/src/App/renderer/pluginWrapper.test.js +28 -0
- package/src/App/renderer/slotHelpers.js +60 -0
- package/src/App/renderer/slotHelpers.test.js +82 -0
- package/src/App/store/AppProvider.jsx +3 -0
- package/src/App/store/AppProvider.test.jsx +40 -0
- package/src/App/store/PluginProvider.jsx +7 -5
- package/src/App/store/appActionsMap.js +25 -1
- package/src/App/store/appActionsMap.test.js +21 -0
- package/src/App/store/appReducer.js +1 -0
- package/src/App/store/mapActionsMap.js +12 -24
- package/src/App/store/mapReducer.js +1 -1
- package/src/InteractiveMap/InteractiveMap.js +79 -6
- package/src/InteractiveMap/InteractiveMap.test.js +164 -2
- package/src/InteractiveMap/behaviourController.js +12 -2
- package/src/InteractiveMap/behaviourController.test.js +82 -1
- package/src/InteractiveMap/deviceChecker.js +1 -1
- package/src/InteractiveMap/deviceChecker.test.js +2 -2
- package/src/InteractiveMap/historyManager.js +41 -5
- package/src/InteractiveMap/historyManager.test.js +37 -6
- package/src/InteractiveMap/polyfills.js +39 -0
- package/src/InteractiveMap/polyfills.test.js +127 -0
- package/src/config/appConfig.js +5 -5
- package/src/config/appConfig.test.js +107 -42
- package/src/config/defaults.js +1 -0
- package/src/index.umd.js +1 -1
- package/src/scss/main.scss +1 -0
- package/src/scss/settings/_dimensions.scss +6 -2
- package/src/services/eventBus.test.js +24 -1
- package/src/test-utils.js +1 -0
- package/src/types.js +5 -0
- package/src/utils/detectBreakpoint.js +78 -77
- package/src/utils/detectBreakpoint.test.js +50 -4
- package/src/utils/detectInterfaceType.js +4 -4
- package/src/utils/getIsFullscreen.js +3 -1
- package/src/utils/getSafeZoneInset.js +23 -7
- package/src/utils/getSafeZoneInset.test.js +18 -0
- package/src/utils/mapStateSync.js +3 -3
- package/src/utils/queryString.js +1 -1
- package/src/utils/stringToKebab.js +1 -1
- package/src/utils/toggleInertElements.js +37 -12
- package/webpack.dev.mjs +3 -4
- package/plugins/interact/src/utils/turfHelpers.js +0 -30
- package/plugins/interact/src/utils/turfHelpers.test.js +0 -30
- package/plugins/search/src/utils/fetchDataset.js +0 -23
- package/providers/maplibre/dist/esm/im-maplibre-legacy-framework.js +0 -1
- package/providers/maplibre/dist/umd/im-maplibre-legacy-framework.js +0 -1
|
@@ -2,8 +2,9 @@ import { spatialNavigate } from './spatial.js'
|
|
|
2
2
|
import { calculateLinearTextSize } from './calculateLinearTextSize.js'
|
|
3
3
|
|
|
4
4
|
const HIGHLIGHT_SCALE_FACTOR = 1.5
|
|
5
|
+
const HIGHLIGHT_LABEL_SOURCE = 'highlighted-label'
|
|
5
6
|
|
|
6
|
-
function getGeometryCenter(geometry) {
|
|
7
|
+
export function getGeometryCenter(geometry) {
|
|
7
8
|
const { type, coordinates } = geometry
|
|
8
9
|
if (type === 'Point') {
|
|
9
10
|
return coordinates
|
|
@@ -23,13 +24,12 @@ function getGeometryCenter(geometry) {
|
|
|
23
24
|
return null
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
function evalInterpolate(expr, zoom) {
|
|
27
|
+
export function evalInterpolate(expr, zoom) {
|
|
27
28
|
if (typeof expr === 'number') {
|
|
28
29
|
return expr
|
|
29
30
|
}
|
|
30
31
|
if (!Array.isArray(expr) || expr[0] !== 'interpolate') {
|
|
31
32
|
return calculateLinearTextSize(expr, zoom)
|
|
32
|
-
// throw new Error('Only interpolate expressions supported')
|
|
33
33
|
}
|
|
34
34
|
const [, , input, ...stops] = expr
|
|
35
35
|
if (input[0] !== 'zoom') {
|
|
@@ -39,7 +39,7 @@ function evalInterpolate(expr, zoom) {
|
|
|
39
39
|
const z0 = stops[i]
|
|
40
40
|
const v0 = stops[i + 1]
|
|
41
41
|
const z1 = stops[i + 2]
|
|
42
|
-
const v1 = stops[i + 3]
|
|
42
|
+
const v1 = stops[i + 3] // NOSONAR: array index offset for interpolation pairs
|
|
43
43
|
if (zoom <= z0) {
|
|
44
44
|
return v0
|
|
45
45
|
}
|
|
@@ -50,166 +50,220 @@ function evalInterpolate(expr, zoom) {
|
|
|
50
50
|
return stops[stops.length - 1]
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
export function
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
let currentPixel = null
|
|
57
|
-
let highlightLayerId = null
|
|
58
|
-
let highlightedExpr = null
|
|
59
|
-
let highlightedFeature = null
|
|
60
|
-
|
|
61
|
-
const colors = {
|
|
62
|
-
get current() {
|
|
63
|
-
if (isDarkStyle) {
|
|
64
|
-
return { text: '#ffffff', halo: '#000000' }
|
|
65
|
-
}
|
|
66
|
-
return { text: '#000000', halo: '#ffffff' }
|
|
67
|
-
}
|
|
53
|
+
export function getHighlightColors(isDarkStyle) {
|
|
54
|
+
if (isDarkStyle) {
|
|
55
|
+
return { text: '#ffffff', halo: '#000000' }
|
|
68
56
|
}
|
|
57
|
+
return { text: '#000000', halo: '#ffffff' }
|
|
58
|
+
}
|
|
69
59
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
60
|
+
export function extractTextPropertyName(textField) {
|
|
61
|
+
if (typeof textField === 'string') {
|
|
62
|
+
return /^{(.+)}$/.exec(textField)?.[1]
|
|
74
63
|
}
|
|
64
|
+
if (Array.isArray(textField)) {
|
|
65
|
+
return textField.find(e => Array.isArray(e) && e[0] === 'get')?.[1]
|
|
66
|
+
}
|
|
67
|
+
return null
|
|
68
|
+
}
|
|
75
69
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
70
|
+
export function buildLabelFromFeature(feature, layer, propName, map) {
|
|
71
|
+
const center = getGeometryCenter(feature.geometry)
|
|
72
|
+
if (!center) {
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
const projected = map.project({ lng: center[0], lat: center[1] })
|
|
76
|
+
return { text: feature.properties[propName], x: projected.x, y: projected.y, feature, layer }
|
|
77
|
+
}
|
|
80
78
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
79
|
+
export function buildLabelsFromLayers(map, symbolLayers, features) {
|
|
80
|
+
return symbolLayers.flatMap(layer => {
|
|
81
|
+
const textField = layer.layout?.['text-field']
|
|
82
|
+
const propName = extractTextPropertyName(textField)
|
|
83
|
+
if (!propName) {
|
|
84
|
+
return []
|
|
85
|
+
}
|
|
86
|
+
return features
|
|
87
|
+
.filter(f => f.layer.id === layer.id && f.properties?.[propName])
|
|
88
|
+
.map(f => buildLabelFromFeature(f, layer, propName, map))
|
|
89
|
+
.filter(Boolean)
|
|
89
90
|
})
|
|
91
|
+
}
|
|
90
92
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
: null
|
|
101
|
-
if (!propName) {
|
|
102
|
-
return []
|
|
103
|
-
}
|
|
104
|
-
return features.filter(f => f.layer.id === layer.id && f.properties?.[propName]).map(f => {
|
|
105
|
-
const center = getGeometryCenter(f.geometry)
|
|
106
|
-
if (!center) {
|
|
107
|
-
return null
|
|
108
|
-
}
|
|
109
|
-
const projected = map.project({ lng: center[0], lat: center[1] })
|
|
110
|
-
return { text: f.properties[propName], x: projected.x, y: projected.y, feature: f, layer }
|
|
111
|
-
}).filter(Boolean)
|
|
112
|
-
})
|
|
113
|
-
}
|
|
93
|
+
export function findClosestLabel(labels, centerPoint) {
|
|
94
|
+
return labels.reduce((best, label) => {
|
|
95
|
+
const dist = (label.x - centerPoint.x) ** 2 + (label.y - centerPoint.y) ** 2
|
|
96
|
+
if (!best || dist < best.dist) {
|
|
97
|
+
return { label, dist }
|
|
98
|
+
}
|
|
99
|
+
return best
|
|
100
|
+
}, null)?.label
|
|
101
|
+
}
|
|
114
102
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
103
|
+
export function createHighlightLayerConfig(sourceLayer, highlightSize, colors) {
|
|
104
|
+
return {
|
|
105
|
+
id: `highlight-${sourceLayer.id}`,
|
|
106
|
+
type: sourceLayer.type,
|
|
107
|
+
source: HIGHLIGHT_LABEL_SOURCE,
|
|
108
|
+
layout: {
|
|
109
|
+
...sourceLayer.layout,
|
|
110
|
+
'text-size': highlightSize,
|
|
111
|
+
'text-allow-overlap': true,
|
|
112
|
+
'text-ignore-placement': true,
|
|
113
|
+
'text-max-angle': 90
|
|
114
|
+
},
|
|
115
|
+
paint: {
|
|
116
|
+
...sourceLayer.paint,
|
|
117
|
+
'text-color': colors.text,
|
|
118
|
+
'text-halo-color': colors.halo,
|
|
119
|
+
'text-halo-width': 3,
|
|
120
|
+
'text-halo-blur': 1,
|
|
121
|
+
'text-opacity': 1
|
|
124
122
|
}
|
|
125
123
|
}
|
|
124
|
+
}
|
|
126
125
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
126
|
+
export function removeHighlightLayer(map, state) {
|
|
127
|
+
if (state.highlightLayerId && map.getLayer(state.highlightLayerId)) {
|
|
128
|
+
try {
|
|
129
|
+
map.removeLayer(state.highlightLayerId)
|
|
130
130
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
131
|
+
catch {}
|
|
132
|
+
state.highlightLayerId = null
|
|
133
|
+
state.highlightedExpr = null
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function applyHighlight(map, labelData, state) {
|
|
138
|
+
if (!labelData?.feature?.layer) {
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
removeHighlightLayer(map, state)
|
|
142
|
+
const { feature, layer } = labelData
|
|
143
|
+
state.highlightLayerId = `highlight-${layer.id}`
|
|
144
|
+
|
|
145
|
+
const { id, type, properties, geometry } = feature
|
|
146
|
+
map.getSource(HIGHLIGHT_LABEL_SOURCE).setData({ id, type, properties, geometry })
|
|
147
|
+
state.highlightedExpr = layer.layout['text-size']
|
|
148
|
+
|
|
149
|
+
const zoom = map.getZoom()
|
|
150
|
+
const baseSize = evalInterpolate(state.highlightedExpr, zoom)
|
|
151
|
+
const highlightSize = baseSize * HIGHLIGHT_SCALE_FACTOR
|
|
152
|
+
const colors = getHighlightColors(state.isDarkStyle)
|
|
153
|
+
const layerConfig = createHighlightLayerConfig(layer, highlightSize, colors)
|
|
154
|
+
|
|
155
|
+
map.addLayer(layerConfig)
|
|
156
|
+
map.moveLayer(state.highlightLayerId)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function navigateToNextLabel(direction, state) {
|
|
160
|
+
if (!state.currentPixel) {
|
|
161
|
+
return null
|
|
162
|
+
}
|
|
163
|
+
const filtered = state.labels
|
|
164
|
+
.map((l, i) => ({ pixel: [l.x, l.y], index: i }))
|
|
165
|
+
.filter(l => l.pixel[0] !== state.currentPixel.x || l.pixel[1] !== state.currentPixel.y)
|
|
166
|
+
if (!filtered.length) {
|
|
167
|
+
return null
|
|
168
|
+
}
|
|
169
|
+
const pixelArray = filtered.map(l => l.pixel)
|
|
170
|
+
let nextFilteredIndex = spatialNavigate(direction, [state.currentPixel.x, state.currentPixel.y], pixelArray)
|
|
171
|
+
if (nextFilteredIndex == null || nextFilteredIndex < 0 || nextFilteredIndex >= filtered.length) {
|
|
172
|
+
nextFilteredIndex = 0
|
|
173
|
+
}
|
|
174
|
+
return state.labels[filtered[nextFilteredIndex].index]
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function initLabelSource(map) {
|
|
178
|
+
if (!map.getSource(HIGHLIGHT_LABEL_SOURCE)) {
|
|
179
|
+
map.addSource(HIGHLIGHT_LABEL_SOURCE, { type: 'geojson', data: { type: 'FeatureCollection', features: [] } })
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function setLineCenterPlacement(map) {
|
|
184
|
+
map.getStyle().layers
|
|
185
|
+
.filter(l => l.layout?.['symbol-placement'] === 'line')
|
|
186
|
+
.forEach(l => map.setLayoutProperty(l.id, 'symbol-placement', 'line-center'))
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function setSymbolTextOpacity(map) {
|
|
190
|
+
map.getStyle().layers
|
|
191
|
+
.filter(l => l.type === 'symbol')
|
|
192
|
+
.forEach(layer => {
|
|
193
|
+
map.setPaintProperty(layer.id, 'text-opacity', ['case', ['boolean', ['feature-state', 'highlighted'], false], 0, 1])
|
|
148
194
|
})
|
|
149
|
-
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function createMapLabelNavigator(map, mapColorScheme, events, eventBus) {
|
|
198
|
+
const state = {
|
|
199
|
+
isDarkStyle: mapColorScheme === 'dark',
|
|
200
|
+
labels: [],
|
|
201
|
+
currentPixel: null,
|
|
202
|
+
highlightLayerId: null,
|
|
203
|
+
highlightedExpr: null
|
|
150
204
|
}
|
|
151
205
|
|
|
206
|
+
setLineCenterPlacement(map)
|
|
207
|
+
initLabelSource(map)
|
|
208
|
+
|
|
209
|
+
eventBus?.on(events.MAP_SET_STYLE, style => {
|
|
210
|
+
map.once('styledata', () => map.once('idle', () => {
|
|
211
|
+
setLineCenterPlacement(map)
|
|
212
|
+
initLabelSource(map)
|
|
213
|
+
state.isDarkStyle = style?.mapColorScheme === 'dark'
|
|
214
|
+
}))
|
|
215
|
+
})
|
|
216
|
+
|
|
152
217
|
map.on('zoom', () => {
|
|
153
|
-
if (highlightLayerId && highlightedExpr) {
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
map.setLayoutProperty(highlightLayerId, 'text-size', baseSize * HIGHLIGHT_SCALE_FACTOR)
|
|
218
|
+
if (state.highlightLayerId && state.highlightedExpr) {
|
|
219
|
+
const baseSize = evalInterpolate(state.highlightedExpr, map.getZoom())
|
|
220
|
+
map.setLayoutProperty(state.highlightLayerId, 'text-size', baseSize * HIGHLIGHT_SCALE_FACTOR)
|
|
157
221
|
}
|
|
158
222
|
})
|
|
159
223
|
|
|
224
|
+
function refreshLabels() {
|
|
225
|
+
const symbolLayers = map.getStyle().layers.filter(l => l.type === 'symbol')
|
|
226
|
+
const features = map.queryRenderedFeatures({ layers: symbolLayers.map(l => l.id) })
|
|
227
|
+
state.labels = buildLabelsFromLayers(map, symbolLayers, features)
|
|
228
|
+
}
|
|
229
|
+
|
|
160
230
|
function highlightCenter() {
|
|
161
231
|
refreshLabels()
|
|
162
|
-
if (!labels.length) {
|
|
232
|
+
if (!state.labels.length) {
|
|
163
233
|
return null
|
|
164
234
|
}
|
|
165
235
|
const centerPoint = map.project(map.getCenter())
|
|
166
|
-
const closest = labels
|
|
167
|
-
const dist = (label.x - centerPoint.x) ** 2 + (label.y - centerPoint.y) ** 2
|
|
168
|
-
if (!best || dist < best.dist) {
|
|
169
|
-
return { label, dist }
|
|
170
|
-
}
|
|
171
|
-
return best
|
|
172
|
-
}, null)?.label
|
|
236
|
+
const closest = findClosestLabel(state.labels, centerPoint)
|
|
173
237
|
if (closest) {
|
|
174
|
-
currentPixel = { x: closest.x, y: closest.y }
|
|
238
|
+
state.currentPixel = { x: closest.x, y: closest.y }
|
|
175
239
|
}
|
|
176
|
-
|
|
240
|
+
applyHighlight(map, closest, state)
|
|
177
241
|
return `${closest.text} (${closest.layer.id})`
|
|
178
242
|
}
|
|
179
243
|
|
|
180
244
|
function highlightNext(direction) {
|
|
181
245
|
refreshLabels()
|
|
182
|
-
if (!labels.length) {
|
|
246
|
+
if (!state.labels.length) {
|
|
183
247
|
return null
|
|
184
248
|
}
|
|
185
|
-
if (!currentPixel) {
|
|
249
|
+
if (!state.currentPixel) {
|
|
186
250
|
return highlightCenter()
|
|
187
251
|
}
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
.filter(l => l.pixel[0] !== currentPixel.x || l.pixel[1] !== currentPixel.y)
|
|
191
|
-
if (!filtered.length) {
|
|
252
|
+
const labelData = navigateToNextLabel(direction, state)
|
|
253
|
+
if (!labelData) {
|
|
192
254
|
return null
|
|
193
255
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if (nextFilteredIndex == null || nextFilteredIndex < 0 || nextFilteredIndex >= filtered.length) {
|
|
197
|
-
nextFilteredIndex = 0
|
|
198
|
-
}
|
|
199
|
-
const labelData = labels[filtered[nextFilteredIndex].index]
|
|
200
|
-
currentPixel = { x: labelData.x, y: labelData.y }
|
|
201
|
-
highlight(labelData)
|
|
256
|
+
state.currentPixel = { x: labelData.x, y: labelData.y }
|
|
257
|
+
applyHighlight(map, labelData, state)
|
|
202
258
|
return `${labelData.text} (${labelData.layer.id})`
|
|
203
259
|
}
|
|
204
260
|
|
|
205
|
-
map
|
|
206
|
-
map.setPaintProperty(layer.id, 'text-opacity', ['case', ['boolean', ['feature-state', 'highlighted'], false], 0, 1])
|
|
207
|
-
})
|
|
261
|
+
setSymbolTextOpacity(map)
|
|
208
262
|
|
|
209
263
|
return {
|
|
210
264
|
refreshLabels,
|
|
211
265
|
highlightNextLabel: highlightNext,
|
|
212
266
|
highlightLabelAtCenter: highlightCenter,
|
|
213
|
-
clearHighlightedLabel:
|
|
267
|
+
clearHighlightedLabel: () => removeHighlightLayer(map, state)
|
|
214
268
|
}
|
|
215
269
|
}
|
|
@@ -13,7 +13,7 @@ export function applyPreventDefaultFix (map) {
|
|
|
13
13
|
const originalPreventDefault = Event.prototype.preventDefault
|
|
14
14
|
|
|
15
15
|
// Override preventDefault only for events targeting our map
|
|
16
|
-
Event.prototype.preventDefault = function () {
|
|
16
|
+
Event.prototype.preventDefault = function () { // NOSONAR: intentional monkey-patch to fix MapLibre touch event bug
|
|
17
17
|
if ((this.type === 'touchmove' || this.type === 'touchstart') && !this.cancelable) {
|
|
18
18
|
// Check if the event target is within our map container
|
|
19
19
|
const canvas = map.getCanvas()
|
|
@@ -21,6 +21,6 @@ export function applyPreventDefaultFix (map) {
|
|
|
21
21
|
return
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
-
|
|
24
|
+
originalPreventDefault.call(this)
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculates the squared distance from a point (p) to a line segment (v to w).
|
|
3
|
+
*/
|
|
4
|
+
const distToSegmentSquared = (p, v, w) => {
|
|
5
|
+
const l2 = (v.x - w.x) ** 2 + (v.y - w.y) ** 2
|
|
6
|
+
if (l2 === 0) {
|
|
7
|
+
return (p.x - v.x) ** 2 + (p.y - v.y) ** 2
|
|
8
|
+
}
|
|
9
|
+
let t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2
|
|
10
|
+
t = Math.max(0, Math.min(1, t))
|
|
11
|
+
return (p.x - (v.x + t * (w.x - v.x))) ** 2 + (p.y - (v.y + t * (w.y - v.y))) ** 2
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Ray-casting algorithm to determine if a point is inside a polygon.
|
|
16
|
+
*/
|
|
17
|
+
const isPointInPolygon = (point, ring) => {
|
|
18
|
+
const [px, py] = point
|
|
19
|
+
let inside = false
|
|
20
|
+
for (let i = 0, j = ring.length - 1; i < ring.length; j = i, i++) {
|
|
21
|
+
const [xi, yi] = ring[i]
|
|
22
|
+
const [xj, yj] = ring[j]
|
|
23
|
+
const isAboveI = yi > py
|
|
24
|
+
const isAboveJ = yj > py
|
|
25
|
+
|
|
26
|
+
if (isAboveI === isAboveJ) {
|
|
27
|
+
continue
|
|
28
|
+
}
|
|
29
|
+
// 2. Calculate horizontal intersection
|
|
30
|
+
const intersectX = ((xj - xi) * (py - yi)) / (yj - yi) + xi
|
|
31
|
+
|
|
32
|
+
if (px < intersectX) {
|
|
33
|
+
inside = !inside;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return inside
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Calculates minimum squared pixel distance to the geometry.
|
|
41
|
+
*/
|
|
42
|
+
const getMinDistToGeometry = (map, point, geometry) => {
|
|
43
|
+
const { coordinates: coords, type } = geometry
|
|
44
|
+
let minSqDist = Infinity
|
|
45
|
+
const getScreenPt = (lngLat) => map.project(lngLat)
|
|
46
|
+
|
|
47
|
+
const processLine = (lineCoords) => {
|
|
48
|
+
for (let i = 0; i < lineCoords.length - 1; i++) {
|
|
49
|
+
const d2 = distToSegmentSquared(point, getScreenPt(lineCoords[i]), getScreenPt(lineCoords[i + 1]))
|
|
50
|
+
if (d2 < minSqDist) {
|
|
51
|
+
minSqDist = d2
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (type === 'Point') {
|
|
57
|
+
const p = getScreenPt(coords)
|
|
58
|
+
minSqDist = (point.x - p.x) ** 2 + (point.y - p.y) ** 2
|
|
59
|
+
} else if (type === 'LineString' || type === 'MultiPoint') {
|
|
60
|
+
if (type === 'LineString') {
|
|
61
|
+
processLine(coords)
|
|
62
|
+
} else {
|
|
63
|
+
coords.forEach((pt) => {
|
|
64
|
+
const p = getScreenPt(pt)
|
|
65
|
+
const d2 = (point.x - p.x) ** 2 + (point.y - p.y) ** 2
|
|
66
|
+
if (d2 < minSqDist) {
|
|
67
|
+
minSqDist = d2
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
} else if (type === 'Polygon' || type === 'MultiLineString') {
|
|
72
|
+
coords.forEach(processLine)
|
|
73
|
+
} else if (type === 'MultiPolygon') {
|
|
74
|
+
coords.forEach((poly) => poly.forEach(processLine))
|
|
75
|
+
} else {
|
|
76
|
+
// No action
|
|
77
|
+
}
|
|
78
|
+
return minSqDist
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Query features prioritizing Layer Order, then Containment for Polygons.
|
|
83
|
+
*/
|
|
84
|
+
export const queryFeatures = (map, point, options = {}) => {
|
|
85
|
+
const { radius = 10 } = options
|
|
86
|
+
const queryArea = [[point.x - radius, point.y - radius], [point.x + radius, point.y + radius]]
|
|
87
|
+
const rawFeatures = map.queryRenderedFeatures(queryArea)
|
|
88
|
+
if (rawFeatures.length === 0) {
|
|
89
|
+
return []
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Identify layer visual hierarchy
|
|
93
|
+
const layerStack = []
|
|
94
|
+
rawFeatures.forEach(f => {
|
|
95
|
+
if (layerStack.includes(f.layer.id) === false) {
|
|
96
|
+
layerStack.push(f.layer.id)
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
// Deduplicate Bottom-Up to favor data layers over highlight layers
|
|
101
|
+
const seenIds = new Set()
|
|
102
|
+
const uniqueFeatures = []
|
|
103
|
+
for (let i = rawFeatures.length - 1; i >= 0; i--) {
|
|
104
|
+
const f = rawFeatures[i]
|
|
105
|
+
const featureId = f.id === undefined ? JSON.stringify(f.properties) : f.id
|
|
106
|
+
if (seenIds.has(featureId) === false) {
|
|
107
|
+
seenIds.add(featureId)
|
|
108
|
+
uniqueFeatures.push(f)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const clickLngLat = map.unproject(point)
|
|
113
|
+
const clickPt = [clickLngLat.lng, clickLngLat.lat]
|
|
114
|
+
|
|
115
|
+
return uniqueFeatures
|
|
116
|
+
.map((f) => {
|
|
117
|
+
let score = 0
|
|
118
|
+
const type = f.geometry.type
|
|
119
|
+
const pixelDistSq = getMinDistToGeometry(map, point, f.geometry)
|
|
120
|
+
|
|
121
|
+
// PRIORITY 1: LAYER ORDER
|
|
122
|
+
const layerRank = layerStack.indexOf(f.layer.id)
|
|
123
|
+
score += (layerRank * 1000000)
|
|
124
|
+
|
|
125
|
+
// PRIORITY 2: CONTAINMENT (Polygon Special Treatment)
|
|
126
|
+
if (type.includes('Polygon')) {
|
|
127
|
+
const polys = type === 'Polygon' ? [f.geometry.coordinates] : f.geometry.coordinates
|
|
128
|
+
const isInside = polys.some((ring) => isPointInPolygon(clickPt, ring[0]))
|
|
129
|
+
|
|
130
|
+
if (isInside === true) {
|
|
131
|
+
// Massive boost for polygons if we are actually inside them
|
|
132
|
+
score -= 500000 // NOSONAR - tolerance used only here
|
|
133
|
+
} else {
|
|
134
|
+
// If we are outside a polygon, it loses significantly to anything we ARE inside
|
|
135
|
+
score += 100000 // NOSONAR - tolerance used only here
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// PRIORITY 3: DISTANCE (Final Tie-breaker)
|
|
140
|
+
score += pixelDistSq
|
|
141
|
+
|
|
142
|
+
return { f, score }
|
|
143
|
+
})
|
|
144
|
+
.sort((a, b) => a.score - b.score)
|
|
145
|
+
.map(({ f }) => f)
|
|
146
|
+
}
|
|
@@ -123,26 +123,23 @@ const getCardinalMove = (from, to) => {
|
|
|
123
123
|
* @param {Array<[number, number]>} pixels - Array of pixel coordinates.
|
|
124
124
|
* @returns {number} Index of the closest pixel in the given direction.
|
|
125
125
|
*/
|
|
126
|
+
const isInDirection = (direction, dx, dy) => {
|
|
127
|
+
switch (direction) {
|
|
128
|
+
case 'ArrowUp': return dy < 0 && Math.abs(dy) >= Math.abs(dx)
|
|
129
|
+
case 'ArrowDown': return dy > 0 && Math.abs(dy) >= Math.abs(dx)
|
|
130
|
+
case 'ArrowLeft': return dx < 0 && Math.abs(dx) > Math.abs(dy)
|
|
131
|
+
case 'ArrowRight': return dx > 0 && Math.abs(dx) > Math.abs(dy)
|
|
132
|
+
default: return false
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
126
136
|
const spatialNavigate = (direction, start, pixels) => {
|
|
127
137
|
const [sx, sy] = start
|
|
128
138
|
|
|
129
139
|
// Direction filters
|
|
130
|
-
const candidates = pixels.filter(([x, y]) =>
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const dx = x - sx
|
|
136
|
-
const dy = y - sy
|
|
137
|
-
|
|
138
|
-
switch (direction) {
|
|
139
|
-
case 'ArrowUp': return dy < 0 && Math.abs(dy) >= Math.abs(dx)
|
|
140
|
-
case 'ArrowDown': return dy > 0 && Math.abs(dy) >= Math.abs(dx)
|
|
141
|
-
case 'ArrowLeft': return dx < 0 && Math.abs(dx) > Math.abs(dy)
|
|
142
|
-
case 'ArrowRight': return dx > 0 && Math.abs(dx) > Math.abs(dy)
|
|
143
|
-
default: return false
|
|
144
|
-
}
|
|
145
|
-
})
|
|
140
|
+
const candidates = pixels.filter(([x, y]) =>
|
|
141
|
+
(x !== sx || y !== sy) && isInDirection(direction, x - sx, y - sy)
|
|
142
|
+
)
|
|
146
143
|
|
|
147
144
|
if (!candidates.length) {
|
|
148
145
|
return pixels.findIndex(p => p[0] === sx && p[1] === sy)
|
|
@@ -169,7 +166,7 @@ const getResolution = (center, zoom) => {
|
|
|
169
166
|
const TILE_SIZE = 512
|
|
170
167
|
const lat = center.lat
|
|
171
168
|
const scale = Math.pow(2, zoom)
|
|
172
|
-
const resolution = (EARTH_CIRCUMFERENCE * Math.cos((lat * Math.PI) / 180)) / (scale * TILE_SIZE)
|
|
169
|
+
const resolution = (EARTH_CIRCUMFERENCE * Math.cos((lat * Math.PI) / 180)) / (scale * TILE_SIZE) // NOSONAR - 180 is degrees-to-radians conversion
|
|
173
170
|
return resolution
|
|
174
171
|
}
|
|
175
172
|
|
package/sonar-project.properties
CHANGED
|
@@ -16,8 +16,45 @@ sonar.tests=src,plugins,providers
|
|
|
16
16
|
sonar.test.inclusions=**/*.test.*,**/__mocks__/**,**/__stubs__/**
|
|
17
17
|
sonar.cpd.exclusions=**/*.test.*,**/__mocks__/**,**/__stubs__/**
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
# Ignored rules
|
|
20
|
+
sonar.issue.ignore.multicriteria=reactPropsJs,reactPropsJsx,preferGlobalThisJs,preferGlobalThisJsx,preferAtJs,preferAtJsx,replaceAllJs,replaceAllJsx,stringReplaceAllJs,stringReplaceAllJsx,pascalCaseFunctionsJs,pascalCaseFunctionsJsx,pascalCaseFunctionsTs,pascalCaseFunctionsTsx
|
|
21
|
+
|
|
22
|
+
# S6774: React props validation - using TypeScript/JSDoc for prop types instead
|
|
20
23
|
sonar.issue.ignore.multicriteria.reactPropsJs.ruleKey=javascript:S6774
|
|
21
24
|
sonar.issue.ignore.multicriteria.reactPropsJs.resourceKey=**/*.js
|
|
22
25
|
sonar.issue.ignore.multicriteria.reactPropsJsx.ruleKey=javascript:S6774
|
|
23
|
-
sonar.issue.ignore.multicriteria.reactPropsJsx.resourceKey=**/*.jsx
|
|
26
|
+
sonar.issue.ignore.multicriteria.reactPropsJsx.resourceKey=**/*.jsx
|
|
27
|
+
|
|
28
|
+
# S7764: Prefer globalThis over window
|
|
29
|
+
sonar.issue.ignore.multicriteria.preferGlobalThisJs.ruleKey=javascript:S7764
|
|
30
|
+
sonar.issue.ignore.multicriteria.preferGlobalThisJs.resourceKey=**/*.js
|
|
31
|
+
sonar.issue.ignore.multicriteria.preferGlobalThisJsx.ruleKey=javascript:S7764
|
|
32
|
+
sonar.issue.ignore.multicriteria.preferGlobalThisJsx.resourceKey=**/*.jsx
|
|
33
|
+
|
|
34
|
+
# S7755: Prefer .at() over [index]
|
|
35
|
+
sonar.issue.ignore.multicriteria.preferAtJs.ruleKey=javascript:S7755
|
|
36
|
+
sonar.issue.ignore.multicriteria.preferAtJs.resourceKey=**/*.js
|
|
37
|
+
sonar.issue.ignore.multicriteria.preferAtJsx.ruleKey=javascript:S7755
|
|
38
|
+
sonar.issue.ignore.multicriteria.preferAtJsx.resourceKey=**/*.jsx
|
|
39
|
+
|
|
40
|
+
# S6316: Array.prototype.replaceAll may not be supported in all browsers
|
|
41
|
+
sonar.issue.ignore.multicriteria.replaceAllJs.ruleKey=javascript:S6316
|
|
42
|
+
sonar.issue.ignore.multicriteria.replaceAllJs.resourceKey=**/*.js
|
|
43
|
+
sonar.issue.ignore.multicriteria.replaceAllJsx.ruleKey=javascript:S6316
|
|
44
|
+
sonar.issue.ignore.multicriteria.replaceAllJsx.resourceKey=**/*.jsx
|
|
45
|
+
|
|
46
|
+
# S7781: Prefer .replaceAll() over .replace() with global regex
|
|
47
|
+
sonar.issue.ignore.multicriteria.stringReplaceAllJs.ruleKey=javascript:S7781
|
|
48
|
+
sonar.issue.ignore.multicriteria.stringReplaceAllJs.resourceKey=**/*.js
|
|
49
|
+
sonar.issue.ignore.multicriteria.stringReplaceAllJsx.ruleKey=javascript:S7781
|
|
50
|
+
sonar.issue.ignore.multicriteria.stringReplaceAllJsx.resourceKey=**/*.jsx
|
|
51
|
+
|
|
52
|
+
# S100: Function names should comply with a naming convention (PascalCase for React/Inits) - mustn't pics up React functions
|
|
53
|
+
sonar.issue.ignore.multicriteria.pascalCaseFunctionsJs.ruleKey=javascript:S100
|
|
54
|
+
sonar.issue.ignore.multicriteria.pascalCaseFunctionsJs.resourceKey=**/*.js
|
|
55
|
+
sonar.issue.ignore.multicriteria.pascalCaseFunctionsJsx.ruleKey=javascript:S100
|
|
56
|
+
sonar.issue.ignore.multicriteria.pascalCaseFunctionsJsx.resourceKey=**/*.jsx
|
|
57
|
+
sonar.issue.ignore.multicriteria.pascalCaseFunctionsTs.ruleKey=javascript:S100
|
|
58
|
+
sonar.issue.ignore.multicriteria.pascalCaseFunctionsTs.resourceKey=**/*.ts
|
|
59
|
+
sonar.issue.ignore.multicriteria.pascalCaseFunctionsTsx.ruleKey=javascript:S100
|
|
60
|
+
sonar.issue.ignore.multicriteria.pascalCaseFunctionsTsx.resourceKey=**/*.tsx
|
|
@@ -3,7 +3,7 @@ import { useApp } from '../../store/appContext'
|
|
|
3
3
|
|
|
4
4
|
// eslint-disable-next-line camelcase, react/jsx-pascal-case
|
|
5
5
|
// sonarjs/disable-next-line function-name
|
|
6
|
-
export const Actions = ({
|
|
6
|
+
export const Actions = ({ children }) => {
|
|
7
7
|
const { openPanels, panelConfig, breakpoint } = useApp()
|
|
8
8
|
|
|
9
9
|
const childArray = React.Children.toArray(children)
|
|
@@ -21,7 +21,7 @@ export const Actions = ({ slot, children }) => {
|
|
|
21
21
|
].filter(Boolean).join(' ')
|
|
22
22
|
|
|
23
23
|
return (
|
|
24
|
-
<div className={className} style={
|
|
24
|
+
<div className={className} style={visibleChild ? undefined : { display: 'none' }}>
|
|
25
25
|
{children}
|
|
26
26
|
</div>
|
|
27
27
|
)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
display: flex;
|
|
3
3
|
flex-direction: row;
|
|
4
4
|
justify-content: center;
|
|
5
|
-
gap: var(--divider-gap);
|
|
5
|
+
gap: calc(var(--divider-gap) / 2);
|
|
6
6
|
padding: var(--panel-margin);
|
|
7
7
|
|
|
8
8
|
.im-c-button-wrapper--wide {
|
|
@@ -22,6 +22,6 @@
|
|
|
22
22
|
|
|
23
23
|
.im-o-app--tablet, .im-o-app--desktop {
|
|
24
24
|
.im-c-actions {
|
|
25
|
-
min-width:
|
|
25
|
+
min-width: 0;
|
|
26
26
|
}
|
|
27
27
|
}
|