@rural-environmental-registry/map_component 1.2.0 → 2.0.0
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/README.md +1 -0
- package/dist/handlers/measureToolsHandler.d.ts +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.mjs +15596 -14769
- package/dist/types/index.d.ts +117 -15
- package/dist/utils/layersConfigNormalizer.d.ts +16 -0
- package/dist/utils/menuHistory.d.ts +3 -3
- package/package.json +1 -1
- package/src/components/MapDPG.vue +90 -2
- package/src/components/fa-icon/FontAwesomeIcon.vue +35 -5
- package/src/components/map/LeafletMap.vue +3 -1
- package/src/components/menu/ChildMenu.vue +307 -27
- package/src/components/menu/LayerMenu.vue +232 -37
- package/src/components/menu/ParentMenu.vue +261 -27
- package/src/components/menu/SectionMenu.vue +512 -0
- package/src/handlers/mapToolsHandler.ts +3 -1
- package/src/handlers/measureToolsHandler.ts +36 -27
- package/src/handlers/toolsConstants.ts +1 -0
- package/src/index.ts +47 -0
- package/src/types/index.ts +148 -14
- package/src/utils/layersConfigNormalizer.ts +273 -0
- package/src/utils/menuHistory.ts +35 -19
|
@@ -8,21 +8,26 @@
|
|
|
8
8
|
<FontAwesomeIcon :iconName="iconButton" />
|
|
9
9
|
</ElButton>
|
|
10
10
|
<ElMenu
|
|
11
|
+
:key="menuOpenedsKey"
|
|
11
12
|
:class="customClasses.customMenu"
|
|
13
|
+
:default-openeds="defaultOpeneds"
|
|
12
14
|
mode="vertical"
|
|
15
|
+
@close="onGroupMenuClose"
|
|
16
|
+
@open="onGroupMenuOpen"
|
|
13
17
|
>
|
|
14
18
|
<div id="external-id-top-menu"></div>
|
|
15
|
-
<
|
|
16
|
-
v-
|
|
17
|
-
:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
<SectionMenu
|
|
20
|
+
v-if="normalizedSections.length"
|
|
21
|
+
:editingLayerKey="activeEditingLayerKey"
|
|
22
|
+
:persist="!!props.options?.persist"
|
|
23
|
+
:sections="normalizedSections"
|
|
24
|
+
:selectedSectionKey="resolvedSelectedSectionKey"
|
|
25
|
+
:visibilityMode="visibilityMode"
|
|
26
|
+
@onChildLayerToggle="onChildLayerChange"
|
|
27
|
+
@onGroupLayerToggle="onGroupLayerToggle"
|
|
28
|
+
@onLayerAction="onLayerAction"
|
|
29
|
+
@onSelectedSectionChange="onSelectedSectionChange"
|
|
30
|
+
/>
|
|
26
31
|
<div id="external-id-bottom-menu"></div>
|
|
27
32
|
</ElMenu>
|
|
28
33
|
</div>
|
|
@@ -33,9 +38,26 @@
|
|
|
33
38
|
import L from 'leaflet'
|
|
34
39
|
import { computed, nextTick, ref, watch } from 'vue'
|
|
35
40
|
import FontAwesomeIcon from '../fa-icon/FontAwesomeIcon.vue'
|
|
36
|
-
import
|
|
37
|
-
import {
|
|
38
|
-
|
|
41
|
+
import SectionMenu from './SectionMenu.vue'
|
|
42
|
+
import {
|
|
43
|
+
GroupLayerData,
|
|
44
|
+
LayerActionPayload,
|
|
45
|
+
LayerData,
|
|
46
|
+
LayersConfig,
|
|
47
|
+
LayersMenuConfig,
|
|
48
|
+
SectionEditStatePayload,
|
|
49
|
+
SectionSelectPayload
|
|
50
|
+
} from '../../types'
|
|
51
|
+
import {
|
|
52
|
+
normalizeLayersConfig,
|
|
53
|
+
shouldApplyMapOverlay
|
|
54
|
+
} from '../../utils/layersConfigNormalizer'
|
|
55
|
+
import {
|
|
56
|
+
resolveGroupCollapsedState,
|
|
57
|
+
resolveLayerActiveState,
|
|
58
|
+
setGroupCollapsedHistory,
|
|
59
|
+
shouldInitLayerOnMap
|
|
60
|
+
} from '../../utils/menuHistory.ts'
|
|
39
61
|
|
|
40
62
|
type MenuProps = {
|
|
41
63
|
layersConfig: LayersConfig
|
|
@@ -57,13 +79,112 @@
|
|
|
57
79
|
(e: 'stopLoading'): void
|
|
58
80
|
(e: 'onChildLayerToggle', data: LayerData): void
|
|
59
81
|
(e: 'onGroupLayerToggle', data: GroupLayerData): void
|
|
82
|
+
(e: 'onLayerAction', payload: LayerActionPayload): void
|
|
83
|
+
(e: 'onSectionEditStateChange', payload: SectionEditStatePayload): void
|
|
84
|
+
(e: 'onSelectedSectionChange', payload: SectionSelectPayload): void
|
|
60
85
|
}>()
|
|
61
86
|
|
|
62
87
|
const props = defineProps<MenuProps>()
|
|
63
88
|
|
|
64
|
-
const isMenuOpen = ref<boolean>(
|
|
89
|
+
const isMenuOpen = ref<boolean>(!!props.options?.defaultOpen)
|
|
65
90
|
const convertedLayers = ref<ConvertedLayers>({})
|
|
66
91
|
const convertedGeoJsonLayers = ref<ConvertedGeoJsonLayers>({})
|
|
92
|
+
const internalEditingLayerKey = ref<string | null>(null)
|
|
93
|
+
const internalSelectedSectionKey = ref<string | null>(null)
|
|
94
|
+
|
|
95
|
+
const normalizedSections = computed(() => normalizeLayersConfig(props.layersConfig))
|
|
96
|
+
|
|
97
|
+
const visibilityMode = computed(() => props.options?.visibilityMode ?? 'switch')
|
|
98
|
+
|
|
99
|
+
const isEditingControlled = computed(() => props.options?.editingLayerKey !== undefined)
|
|
100
|
+
|
|
101
|
+
const activeEditingLayerKey = computed(() => {
|
|
102
|
+
if (isEditingControlled.value) {
|
|
103
|
+
return props.options?.editingLayerKey ?? null
|
|
104
|
+
}
|
|
105
|
+
return internalEditingLayerKey.value
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
const isSectionSelectionControlled = computed(
|
|
109
|
+
() => props.options?.selectedSectionKey !== undefined
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
const activeSelectedSectionKey = computed(() => {
|
|
113
|
+
if (isSectionSelectionControlled.value) {
|
|
114
|
+
return props.options?.selectedSectionKey ?? null
|
|
115
|
+
}
|
|
116
|
+
return internalSelectedSectionKey.value
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
const resolvedSelectedSectionKey = computed((): string | null => {
|
|
120
|
+
const sections = normalizedSections.value
|
|
121
|
+
if (!sections.length) return null
|
|
122
|
+
|
|
123
|
+
const current = activeSelectedSectionKey.value
|
|
124
|
+
if (current && sections.some((section) => section.key === current)) {
|
|
125
|
+
return current
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return sections[0].key
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
watch(
|
|
132
|
+
normalizedSections,
|
|
133
|
+
(sections) => {
|
|
134
|
+
if (isSectionSelectionControlled.value) return
|
|
135
|
+
|
|
136
|
+
const current = internalSelectedSectionKey.value
|
|
137
|
+
if (current && sections.some((section) => section.key === current)) return
|
|
138
|
+
|
|
139
|
+
internalSelectedSectionKey.value = sections[0]?.key ?? null
|
|
140
|
+
},
|
|
141
|
+
{ immediate: true }
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
const defaultOpeneds = computed(() => {
|
|
145
|
+
const persist = !!props.options?.persist
|
|
146
|
+
const openeds: string[] = []
|
|
147
|
+
|
|
148
|
+
normalizedSections.value.forEach((section) => {
|
|
149
|
+
section.groups.forEach((group) => {
|
|
150
|
+
const collapsed =
|
|
151
|
+
group.collapsed !== undefined
|
|
152
|
+
? group.collapsed
|
|
153
|
+
: resolveGroupCollapsedState(group.key, undefined, persist)
|
|
154
|
+
if (!collapsed) openeds.push(group.key)
|
|
155
|
+
})
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
return openeds
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
const menuOpenedsKey = computed(() => {
|
|
162
|
+
const stepOpenKeys: string[] = []
|
|
163
|
+
normalizedSections.value.forEach((section) => {
|
|
164
|
+
section.groups.forEach((group) => {
|
|
165
|
+
if (group.collapsed === false && group.meta?.isCurrentStep) {
|
|
166
|
+
stepOpenKeys.push(group.key)
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
})
|
|
170
|
+
return stepOpenKeys.slice().sort().join('|') || 'none'
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
watch(
|
|
174
|
+
() => menuOpenedsKey.value,
|
|
175
|
+
() => {
|
|
176
|
+
if (!props.options?.persist) return
|
|
177
|
+
|
|
178
|
+
const openSet = new Set(defaultOpeneds.value)
|
|
179
|
+
normalizedSections.value.forEach((section) => {
|
|
180
|
+
section.groups.forEach((group) => {
|
|
181
|
+
if (group.collapsed === undefined) return
|
|
182
|
+
setGroupCollapsedHistory(group.key, !openSet.has(group.key))
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
},
|
|
186
|
+
{ flush: 'post' }
|
|
187
|
+
)
|
|
67
188
|
|
|
68
189
|
type CustomClasses = {
|
|
69
190
|
layerMenu: string
|
|
@@ -86,12 +207,69 @@
|
|
|
86
207
|
return isMenuOpen.value ? 'chevron-left' : 'chevron-right'
|
|
87
208
|
})
|
|
88
209
|
|
|
89
|
-
const
|
|
90
|
-
|
|
210
|
+
const findLayerContext = (
|
|
211
|
+
layerKey: string
|
|
212
|
+
): { sectionKey: string; layer: LayerData } | null => {
|
|
213
|
+
for (const section of normalizedSections.value) {
|
|
214
|
+
for (const group of section.groups) {
|
|
215
|
+
const layer = group.layers?.find((item) => item.key === layerKey)
|
|
216
|
+
if (layer) return { sectionKey: section.key, layer }
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return null
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const setEditingState = (sectionKey: string | null, layerKey: string | null): void => {
|
|
223
|
+
if (!isEditingControlled.value) {
|
|
224
|
+
internalEditingLayerKey.value = layerKey
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const context = layerKey ? findLayerContext(layerKey) : null
|
|
228
|
+
|
|
229
|
+
emit('onSectionEditStateChange', {
|
|
230
|
+
sectionKey,
|
|
231
|
+
layerKey,
|
|
232
|
+
layer: context?.layer ?? null
|
|
233
|
+
})
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const setSelectedSection = (sectionKey: string): void => {
|
|
237
|
+
if (!isSectionSelectionControlled.value) {
|
|
238
|
+
internalSelectedSectionKey.value = sectionKey
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
emit('onSelectedSectionChange', { sectionKey })
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const onSelectedSectionChange = (sectionKey: string): void => {
|
|
245
|
+
setSelectedSection(sectionKey)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const onGroupMenuOpen = (index: string): void => {
|
|
249
|
+
if (!props.options?.persist) return
|
|
250
|
+
setGroupCollapsedHistory(index, false)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const onGroupMenuClose = (index: string): void => {
|
|
254
|
+
if (!props.options?.persist) return
|
|
255
|
+
setGroupCollapsedHistory(index, true)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const applyLayerOverlay = (layer: LayerData): void => {
|
|
259
|
+
if (!shouldApplyMapOverlay(layer)) return
|
|
260
|
+
|
|
261
|
+
if (layer.geojson || layer.mapSource === 'geojson') {
|
|
262
|
+
handleGeoJsonLayer(layer)
|
|
263
|
+
return
|
|
264
|
+
}
|
|
91
265
|
|
|
92
266
|
handleWmsLayer(layer)
|
|
93
267
|
}
|
|
94
268
|
|
|
269
|
+
const onInitDefaultLayer = (layer: LayerData): void => {
|
|
270
|
+
applyLayerOverlay(layer)
|
|
271
|
+
}
|
|
272
|
+
|
|
95
273
|
const initializedLayerKeys = new Set<string>()
|
|
96
274
|
|
|
97
275
|
const initDefaultLayers = (): void => {
|
|
@@ -99,14 +277,17 @@
|
|
|
99
277
|
|
|
100
278
|
const persist = !!props.options?.persist
|
|
101
279
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
280
|
+
normalizedSections.value.forEach((section) => {
|
|
281
|
+
section.groups.forEach((group) => {
|
|
282
|
+
group.layers?.forEach((layer) => {
|
|
283
|
+
if (initializedLayerKeys.has(layer.key)) return
|
|
284
|
+
if (!shouldApplyMapOverlay(layer)) return
|
|
285
|
+
if (!shouldInitLayerOnMap(layer, persist)) return
|
|
286
|
+
|
|
287
|
+
const resolved = resolveLayerActiveState(layer, persist)
|
|
288
|
+
onInitDefaultLayer(resolved)
|
|
289
|
+
initializedLayerKeys.add(layer.key)
|
|
290
|
+
})
|
|
110
291
|
})
|
|
111
292
|
})
|
|
112
293
|
}
|
|
@@ -127,29 +308,43 @@
|
|
|
127
308
|
)
|
|
128
309
|
|
|
129
310
|
const onChildLayerChange = (layer: LayerData): void => {
|
|
130
|
-
|
|
131
|
-
handleGeoJsonLayer(layer)
|
|
132
|
-
} else {
|
|
133
|
-
handleWmsLayer(layer)
|
|
134
|
-
}
|
|
135
|
-
|
|
311
|
+
applyLayerOverlay(layer)
|
|
136
312
|
emit('onChildLayerToggle', layer)
|
|
137
313
|
}
|
|
138
314
|
|
|
139
315
|
const onGroupLayerToggle = (parent: GroupLayerData): void => {
|
|
140
316
|
parent.layers.forEach((childLayer: LayerData) => {
|
|
141
|
-
|
|
142
|
-
handleGeoJsonLayer(childLayer)
|
|
143
|
-
} else {
|
|
144
|
-
handleWmsLayer(childLayer)
|
|
145
|
-
}
|
|
317
|
+
applyLayerOverlay(childLayer)
|
|
146
318
|
})
|
|
147
319
|
|
|
148
320
|
emit('onGroupLayerToggle', parent)
|
|
149
321
|
}
|
|
150
322
|
|
|
323
|
+
const onLayerAction = (payload: LayerActionPayload): void => {
|
|
324
|
+
const isEditAction = payload.actionType === 'edit' || payload.actionKey === 'edit'
|
|
325
|
+
const isEditControlAction =
|
|
326
|
+
payload.source === 'edit-panel' &&
|
|
327
|
+
(payload.actionType === 'cancel' ||
|
|
328
|
+
payload.actionType === 'conclude' ||
|
|
329
|
+
payload.actionKey === 'cancel' ||
|
|
330
|
+
payload.actionKey === 'conclude')
|
|
331
|
+
|
|
332
|
+
if (isEditAction && payload.source === 'child-menu' && payload.layerKey) {
|
|
333
|
+
setEditingState(payload.sectionKey ?? null, payload.layerKey)
|
|
334
|
+
if (payload.sectionKey) {
|
|
335
|
+
setSelectedSection(payload.sectionKey)
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (isEditControlAction) {
|
|
340
|
+
setEditingState(null, null)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
emit('onLayerAction', payload)
|
|
344
|
+
}
|
|
345
|
+
|
|
151
346
|
const convertToWmsLayer = (layer: LayerData): L.TileLayer => {
|
|
152
|
-
const wmsLayer = L.tileLayer.wms(layer.baseUrl, {
|
|
347
|
+
const wmsLayer = L.tileLayer.wms(layer.baseUrl ?? '', {
|
|
153
348
|
layers: layer.layers,
|
|
154
349
|
format: layer.format || 'image/png',
|
|
155
350
|
transparent: layer.transparent
|
|
@@ -7,21 +7,39 @@
|
|
|
7
7
|
<template #title>
|
|
8
8
|
<div class="parent-menu-row">
|
|
9
9
|
<div class="parent-layer-title">{{ groupData.name }}</div>
|
|
10
|
-
<span
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
<span
|
|
11
|
+
v-if="showVisibilityControl"
|
|
12
|
+
class="visibility-control"
|
|
13
|
+
>
|
|
14
|
+
<button
|
|
15
|
+
v-if="visibilityMode === 'eye'"
|
|
16
|
+
type="button"
|
|
17
|
+
class="visibility-eye-button"
|
|
18
|
+
:aria-label="allLayersActive ? hideLabel : showLabel"
|
|
19
|
+
:title="allLayersActive ? hideLabel : showLabel"
|
|
20
|
+
@click.stop="allLayersActive = !allLayersActive"
|
|
14
21
|
>
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
<FontAwesomeIcon :iconName="allLayersActive ? 'eye' : 'eye-slash'" />
|
|
23
|
+
</button>
|
|
24
|
+
<template v-else>
|
|
25
|
+
<ElSwitch
|
|
26
|
+
v-model="allLayersActive"
|
|
27
|
+
@click.stop
|
|
28
|
+
>
|
|
29
|
+
<template #active-action>
|
|
30
|
+
<FontAwesomeIcon iconName="check" />
|
|
31
|
+
</template>
|
|
32
|
+
<template #inactive-action>
|
|
33
|
+
<FontAwesomeIcon iconName="xmark" />
|
|
34
|
+
</template>
|
|
35
|
+
</ElSwitch>
|
|
36
|
+
<span
|
|
37
|
+
v-if="groupData.toggle"
|
|
38
|
+
class="parent-layer-status"
|
|
39
|
+
>
|
|
40
|
+
{{ allLayersActive ? groupData.toggle.active : groupData.toggle.inactive }}
|
|
41
|
+
</span>
|
|
42
|
+
</template>
|
|
25
43
|
<ElDivider
|
|
26
44
|
class="divider-bar"
|
|
27
45
|
direction="vertical"
|
|
@@ -33,43 +51,153 @@
|
|
|
33
51
|
v-for="(child, idx) in childrenLayers"
|
|
34
52
|
:key="child.key"
|
|
35
53
|
>
|
|
54
|
+
<ElMenuItem
|
|
55
|
+
v-if="isMenuSeparator(child)"
|
|
56
|
+
:index="child.key"
|
|
57
|
+
class="parent-menu-subgroup-header"
|
|
58
|
+
disabled
|
|
59
|
+
>
|
|
60
|
+
<div class="parent-menu-subgroup-title">{{ child.name }}</div>
|
|
61
|
+
</ElMenuItem>
|
|
36
62
|
<ChildMenu
|
|
37
|
-
v-if="child.name"
|
|
63
|
+
v-else-if="child.name"
|
|
64
|
+
:actionDefaults="actionDefaults"
|
|
38
65
|
:data="child"
|
|
66
|
+
:groupKey="groupData.key"
|
|
39
67
|
:persist="props.persist"
|
|
68
|
+
:sectionKey="sectionKey"
|
|
69
|
+
:visibilityMode="visibilityMode"
|
|
40
70
|
@onChildLayerToggle="onChildChange($event, idx)"
|
|
71
|
+
@onLayerAction="emit('onLayerAction', $event)"
|
|
41
72
|
/>
|
|
42
73
|
</template>
|
|
74
|
+
|
|
75
|
+
<ElMenuItem
|
|
76
|
+
v-if="visibleGroupActions.length"
|
|
77
|
+
:index="`${groupData.key}__group-actions`"
|
|
78
|
+
class="parent-menu-group-actions-item"
|
|
79
|
+
>
|
|
80
|
+
<div class="parent-menu-group-actions">
|
|
81
|
+
<button
|
|
82
|
+
v-for="action in visibleGroupActions"
|
|
83
|
+
:key="action.key"
|
|
84
|
+
type="button"
|
|
85
|
+
class="parent-group-action-button"
|
|
86
|
+
:style="actionButtonStyle(action)"
|
|
87
|
+
:disabled="action.disabled"
|
|
88
|
+
@click.stop="onGroupActionClick(action)"
|
|
89
|
+
>
|
|
90
|
+
<FontAwesomeIcon
|
|
91
|
+
v-if="action.icon"
|
|
92
|
+
:iconName="action.icon"
|
|
93
|
+
/>
|
|
94
|
+
<span>{{ action.name }}</span>
|
|
95
|
+
</button>
|
|
96
|
+
</div>
|
|
97
|
+
</ElMenuItem>
|
|
43
98
|
</ElSubMenu>
|
|
44
99
|
<ElDivider class="parent-menu-divider-row" />
|
|
45
100
|
</template>
|
|
46
101
|
|
|
47
102
|
<script lang="ts" setup>
|
|
48
|
-
import { ElDivider, ElSubMenu, ElSwitch } from 'element-plus'
|
|
49
|
-
import { computed, ref } from 'vue'
|
|
103
|
+
import { ElDivider, ElMenuItem, ElSubMenu, ElSwitch } from 'element-plus'
|
|
104
|
+
import { computed, ref, watch } from 'vue'
|
|
50
105
|
import FontAwesomeIcon from '../fa-icon/FontAwesomeIcon.vue'
|
|
51
106
|
import ChildMenu from './ChildMenu.vue'
|
|
52
|
-
import {
|
|
107
|
+
import {
|
|
108
|
+
GroupActionConfig,
|
|
109
|
+
GroupLayerData,
|
|
110
|
+
LayerActionDefaults,
|
|
111
|
+
LayerActionPayload,
|
|
112
|
+
LayerActionStyle,
|
|
113
|
+
LayerData
|
|
114
|
+
} from '../../types'
|
|
115
|
+
import {
|
|
116
|
+
isMenuSeparator,
|
|
117
|
+
resolveActiveGroupActions
|
|
118
|
+
} from '../../utils/layersConfigNormalizer'
|
|
53
119
|
import { resolveLayerActiveState, setHistory } from '../../utils/menuHistory.ts'
|
|
54
120
|
|
|
121
|
+
const DEFAULT_ACTION_STYLE: Required<LayerActionStyle> = {
|
|
122
|
+
backgroundColor: 'var(--mapa-base-green)',
|
|
123
|
+
color: 'var(--mapa-base-white)',
|
|
124
|
+
borderColor: 'var(--mapa-base-white)'
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const resolveActionStyle = (style?: LayerActionStyle): Required<LayerActionStyle> => {
|
|
128
|
+
const color = style?.color ?? DEFAULT_ACTION_STYLE.color
|
|
129
|
+
return {
|
|
130
|
+
backgroundColor: style?.backgroundColor ?? DEFAULT_ACTION_STYLE.backgroundColor,
|
|
131
|
+
color,
|
|
132
|
+
borderColor: style?.borderColor ?? color
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const actionButtonStyle = (action: GroupActionConfig): Required<LayerActionStyle> =>
|
|
137
|
+
resolveActionStyle(action.style)
|
|
138
|
+
|
|
55
139
|
type ParentMenuProps = {
|
|
56
140
|
groupData: GroupLayerData
|
|
57
141
|
persist: boolean
|
|
142
|
+
sectionKey?: string
|
|
143
|
+
actionDefaults?: LayerActionDefaults
|
|
144
|
+
visibilityMode?: 'switch' | 'eye'
|
|
58
145
|
}
|
|
59
146
|
|
|
60
|
-
const props = defineProps<ParentMenuProps>()
|
|
147
|
+
const props = withDefaults(defineProps<ParentMenuProps>(), {
|
|
148
|
+
visibilityMode: 'switch'
|
|
149
|
+
})
|
|
61
150
|
|
|
62
151
|
const emit = defineEmits<{
|
|
63
152
|
onChildLayerToggle: [LayerData]
|
|
64
153
|
onGroupLayerToggle: [GroupLayerData]
|
|
154
|
+
onLayerAction: [LayerActionPayload]
|
|
65
155
|
}>()
|
|
66
156
|
|
|
67
157
|
const childrenLayers = ref<LayerData[]>(
|
|
68
158
|
(props.groupData.layers ?? []).map((layer) => resolveLayerActiveState(layer, props.persist))
|
|
69
159
|
)
|
|
70
160
|
|
|
161
|
+
watch(
|
|
162
|
+
() => props.groupData.layers,
|
|
163
|
+
(layers) => {
|
|
164
|
+
childrenLayers.value = (layers ?? []).map((layer) =>
|
|
165
|
+
resolveLayerActiveState(layer, props.persist)
|
|
166
|
+
)
|
|
167
|
+
},
|
|
168
|
+
{ deep: true }
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
const visibleGroupActions = computed(() => resolveActiveGroupActions(props.groupData))
|
|
172
|
+
|
|
173
|
+
const showGroupEye = computed(() => props.groupData.visibility?.show !== false)
|
|
174
|
+
|
|
175
|
+
const showVisibilityControl = computed(() => {
|
|
176
|
+
if (props.visibilityMode === 'eye') return showGroupEye.value
|
|
177
|
+
return true
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
const showLabel = computed(
|
|
181
|
+
() =>
|
|
182
|
+
props.groupData.visibility?.labelShow ??
|
|
183
|
+
props.groupData.toggle?.active ??
|
|
184
|
+
'Exibir'
|
|
185
|
+
)
|
|
186
|
+
const hideLabel = computed(
|
|
187
|
+
() =>
|
|
188
|
+
props.groupData.visibility?.labelHide ??
|
|
189
|
+
props.groupData.toggle?.inactive ??
|
|
190
|
+
'Ocultar'
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
const toggleableLayers = computed(() =>
|
|
194
|
+
childrenLayers.value.filter((layer: LayerData) => !isMenuSeparator(layer))
|
|
195
|
+
)
|
|
196
|
+
|
|
71
197
|
const toggleVisibleAllLayers = (): void => {
|
|
72
|
-
if (props.persist)
|
|
198
|
+
if (props.persist) {
|
|
199
|
+
toggleableLayers.value.forEach((layer: LayerData) => setHistory(layer))
|
|
200
|
+
}
|
|
73
201
|
|
|
74
202
|
emit('onGroupLayerToggle', {
|
|
75
203
|
...props.groupData,
|
|
@@ -78,13 +206,11 @@
|
|
|
78
206
|
}
|
|
79
207
|
|
|
80
208
|
const allLayersActive = computed<boolean>({
|
|
81
|
-
get: () =>
|
|
209
|
+
get: () => toggleableLayers.value.some((layer: LayerData) => layer.active),
|
|
82
210
|
set: (active: boolean) => {
|
|
83
|
-
childrenLayers.value = props.groupData.layers.map((layer: LayerData) => {
|
|
84
|
-
return
|
|
85
|
-
|
|
86
|
-
active
|
|
87
|
-
}
|
|
211
|
+
childrenLayers.value = (props.groupData.layers ?? []).map((layer: LayerData) => {
|
|
212
|
+
if (isMenuSeparator(layer)) return resolveLayerActiveState(layer, props.persist)
|
|
213
|
+
return { ...layer, active }
|
|
88
214
|
})
|
|
89
215
|
|
|
90
216
|
toggleVisibleAllLayers()
|
|
@@ -95,6 +221,18 @@
|
|
|
95
221
|
childrenLayers.value[idx] = layer
|
|
96
222
|
emit('onChildLayerToggle', layer)
|
|
97
223
|
}
|
|
224
|
+
|
|
225
|
+
const onGroupActionClick = (action: GroupActionConfig): void => {
|
|
226
|
+
if (action.disabled) return
|
|
227
|
+
|
|
228
|
+
emit('onLayerAction', {
|
|
229
|
+
actionKey: action.key,
|
|
230
|
+
actionType: action.type,
|
|
231
|
+
sectionKey: props.sectionKey,
|
|
232
|
+
groupKey: props.groupData.key,
|
|
233
|
+
source: 'group-menu'
|
|
234
|
+
})
|
|
235
|
+
}
|
|
98
236
|
</script>
|
|
99
237
|
|
|
100
238
|
<style>
|
|
@@ -106,13 +244,24 @@
|
|
|
106
244
|
font-size: var(--mapa-fs-12);
|
|
107
245
|
}
|
|
108
246
|
|
|
109
|
-
.parent-menu .
|
|
247
|
+
.parent-menu .visibility-control {
|
|
110
248
|
display: flex;
|
|
111
249
|
justify-content: center;
|
|
112
250
|
align-items: center;
|
|
113
251
|
gap: var(--mapa-size-base-5);
|
|
114
252
|
}
|
|
115
253
|
|
|
254
|
+
.parent-menu .visibility-eye-button {
|
|
255
|
+
display: inline-flex;
|
|
256
|
+
align-items: center;
|
|
257
|
+
justify-content: center;
|
|
258
|
+
border: none;
|
|
259
|
+
background: transparent;
|
|
260
|
+
color: var(--mapa-base-green);
|
|
261
|
+
cursor: pointer;
|
|
262
|
+
padding: var(--mapa-size-base-2);
|
|
263
|
+
}
|
|
264
|
+
|
|
116
265
|
.parent-menu .parent-layer-title {
|
|
117
266
|
word-wrap: break-word;
|
|
118
267
|
white-space: normal;
|
|
@@ -126,7 +275,92 @@
|
|
|
126
275
|
border-width: var(--mapa-size-base-2);
|
|
127
276
|
}
|
|
128
277
|
|
|
278
|
+
.parent-menu-group-actions {
|
|
279
|
+
display: flex;
|
|
280
|
+
flex-direction: column;
|
|
281
|
+
align-items: stretch;
|
|
282
|
+
gap: var(--mapa-size-base-5);
|
|
283
|
+
width: 100%;
|
|
284
|
+
box-sizing: border-box;
|
|
285
|
+
padding: var(--mapa-size-base-10) 0;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.parent-menu .parent-menu-group-actions-item {
|
|
289
|
+
height: auto !important;
|
|
290
|
+
line-height: normal !important;
|
|
291
|
+
white-space: normal !important;
|
|
292
|
+
padding-left: var(--mapa-size-base-20) !important;
|
|
293
|
+
padding-right: var(--mapa-size-base-20) !important;
|
|
294
|
+
cursor: default !important;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.parent-menu .parent-menu-group-actions-item:hover {
|
|
298
|
+
background-color: transparent !important;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.parent-group-action-button {
|
|
302
|
+
display: inline-flex;
|
|
303
|
+
flex: 0 0 auto;
|
|
304
|
+
align-items: center;
|
|
305
|
+
justify-content: center;
|
|
306
|
+
gap: var(--mapa-size-base-5);
|
|
307
|
+
width: 100%;
|
|
308
|
+
height: var(--mapa-size-base-40);
|
|
309
|
+
padding: 0 var(--mapa-size-base-10);
|
|
310
|
+
border: 1px solid;
|
|
311
|
+
border-radius: 100em;
|
|
312
|
+
font-size: var(--mapa-fs-15);
|
|
313
|
+
font-weight: 600;
|
|
314
|
+
white-space: nowrap;
|
|
315
|
+
overflow: hidden;
|
|
316
|
+
text-overflow: ellipsis;
|
|
317
|
+
position: relative;
|
|
318
|
+
text-align: center;
|
|
319
|
+
vertical-align: middle;
|
|
320
|
+
cursor: pointer;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.parent-group-action-button:not(:disabled):hover {
|
|
324
|
+
background-image: linear-gradient(rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0.16));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.parent-group-action-button:not(:disabled):active {
|
|
328
|
+
background-image: linear-gradient(rgba(0, 0, 0, 0.32), rgba(0, 0, 0, 0.32));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.parent-group-action-button:disabled {
|
|
332
|
+
opacity: 0.5;
|
|
333
|
+
cursor: not-allowed;
|
|
334
|
+
}
|
|
335
|
+
|
|
129
336
|
.parent-menu-divider-row {
|
|
130
337
|
margin: 0 !important;
|
|
131
338
|
}
|
|
339
|
+
|
|
340
|
+
/* Título de subgrupo: separador visual, sem accordion */
|
|
341
|
+
.parent-menu .parent-menu-subgroup-header {
|
|
342
|
+
height: auto !important;
|
|
343
|
+
line-height: normal !important;
|
|
344
|
+
white-space: normal !important;
|
|
345
|
+
padding: var(--mapa-size-base-10) var(--mapa-size-base-20) var(--mapa-size-base-5) !important;
|
|
346
|
+
cursor: default !important;
|
|
347
|
+
opacity: 1 !important;
|
|
348
|
+
color: inherit !important;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.parent-menu .parent-menu-subgroup-header:hover,
|
|
352
|
+
.parent-menu .parent-menu-subgroup-header.is-disabled {
|
|
353
|
+
background-color: transparent !important;
|
|
354
|
+
cursor: default !important;
|
|
355
|
+
opacity: 1 !important;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.parent-menu .parent-menu-subgroup-title {
|
|
359
|
+
font-size: var(--mapa-fs-12);
|
|
360
|
+
font-weight: 700;
|
|
361
|
+
line-height: 150%;
|
|
362
|
+
color: var(--mapa-base-black, #000000);
|
|
363
|
+
word-wrap: break-word;
|
|
364
|
+
white-space: normal;
|
|
365
|
+
}
|
|
132
366
|
</style>
|