@dative-gpi/foundation-shared-components 1.0.32 → 1.0.34
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/components/FSClickable.vue +9 -0
- package/components/FSDialogFormBody.vue +4 -4
- package/components/FSDialogMultiFormBody.vue +3 -3
- package/components/FSDialogSubmit.vue +3 -3
- package/components/FSFadeOut.vue +10 -3
- package/components/fields/FSAutocompleteField.vue +3 -3
- package/components/fields/FSSelectField.vue +3 -3
- package/components/fields/FSTreeViewField.vue +3 -3
- package/components/fields/periodicField/FSPeriodicDailyField.vue +17 -10
- package/components/fields/periodicField/FSPeriodicMonthlyField.vue +29 -15
- package/components/fields/periodicField/FSPeriodicWeeklyField.vue +13 -7
- package/components/fields/periodicField/FSPeriodicYearlyField.vue +19 -10
- package/components/lists/FSFilterButton.vue +19 -20
- package/components/lists/FSHiddenButton.vue +10 -12
- package/components/map/FSMap.vue +240 -399
- package/components/map/FSMapFeatureGroup.vue +51 -0
- package/components/map/FSMapLayerButton.vue +2 -2
- package/components/map/FSMapMarker.vue +116 -0
- package/components/map/FSMapMarkerClusterGroup.vue +67 -0
- package/components/map/FSMapOverlay.vue +69 -83
- package/components/map/FSMapPolygon.vue +81 -0
- package/components/map/FSMapTileLayer.vue +50 -0
- package/components/map/keys.ts +4 -0
- package/package.json +4 -4
- package/styles/components/fs_card.scss +0 -1
- package/styles/components/fs_clickable.scss +1 -1
- package/styles/components/fs_fade_out.scss +2 -1
- package/styles/components/fs_map.scss +36 -30
- package/styles/components/fs_tabs.scss +4 -0
- package/styles/components/index.scss +0 -1
- package/utils/leafletMarkers.ts +8 -2
- package/components/map/FSMapEditPointAddressOverlay.vue +0 -164
- package/styles/components/fs_map_overlay.scss +0 -38
package/components/map/FSMap.vue
CHANGED
|
@@ -1,162 +1,158 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<FSCard
|
|
3
|
+
class="fs-map"
|
|
3
4
|
:width="$props.width"
|
|
5
|
+
:height="$props.height"
|
|
4
6
|
:style="style"
|
|
5
7
|
v-bind="$attrs"
|
|
6
8
|
>
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
:class="['fs-map', { 'fs-map-fullscreen': fullScreen }]"
|
|
9
|
+
<div
|
|
10
|
+
ref="leafletContainer"
|
|
11
|
+
class="fs-leaflet-container"
|
|
11
12
|
>
|
|
12
|
-
<
|
|
13
|
-
v-if="
|
|
14
|
-
:mode="$props.overlayMode"
|
|
15
|
-
:height="$props.height"
|
|
16
|
-
:mapId="mapId"
|
|
17
|
-
@update:mode="$emit('update:overlayMode', $event)"
|
|
13
|
+
<template
|
|
14
|
+
v-if="map"
|
|
18
15
|
>
|
|
19
|
-
<
|
|
20
|
-
|
|
16
|
+
<FSMapTileLayer
|
|
17
|
+
:layer="actualLayer"
|
|
18
|
+
/>
|
|
19
|
+
<FSMapMarker
|
|
20
|
+
v-if="gpsPosition"
|
|
21
|
+
variant="gps"
|
|
22
|
+
:color="ColorEnum.Primary"
|
|
23
|
+
:latlng="gpsPosition"
|
|
24
|
+
/>
|
|
25
|
+
|
|
26
|
+
<FSMapFeatureGroup
|
|
27
|
+
v-if="$props.areas"
|
|
28
|
+
:expected-layers="$props.areas.length"
|
|
29
|
+
@update:bounds="(bounds) => areaGroupBounds = bounds"
|
|
21
30
|
>
|
|
22
|
-
<
|
|
23
|
-
|
|
31
|
+
<FSMapPolygon
|
|
32
|
+
v-for="area in areas"
|
|
33
|
+
:key="area.id"
|
|
34
|
+
:color="area.color"
|
|
35
|
+
:latlngs="area.coordinates.map((coord) => ({lat: coord.latitude, lng: coord.longitude}))"
|
|
36
|
+
@click="$emit('update:selectedAreaId', area.id)"
|
|
24
37
|
/>
|
|
25
|
-
</
|
|
26
|
-
|
|
27
|
-
|
|
38
|
+
</FSMapFeatureGroup>
|
|
39
|
+
|
|
40
|
+
<FSMapMarkerClusterGroup
|
|
41
|
+
v-if="$props.locations"
|
|
42
|
+
:expected-layers="$props.locations.length"
|
|
43
|
+
@update:bounds="(bounds) => locationGroupBounds = bounds"
|
|
28
44
|
>
|
|
29
|
-
<
|
|
30
|
-
|
|
45
|
+
<FSMapMarker
|
|
46
|
+
v-for="location in $props.locations"
|
|
47
|
+
:selected="location.id === $props.selectedLocationId"
|
|
48
|
+
:key="location.id"
|
|
49
|
+
:color="location.color"
|
|
50
|
+
:icon="location.icon"
|
|
51
|
+
:latlng="{lat: location.address.latitude, lng: location.address.longitude}"
|
|
52
|
+
@click="$emit('update:selectedLocationId', location.id)"
|
|
31
53
|
/>
|
|
32
|
-
</
|
|
33
|
-
</
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
>
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
</FSMapMarkerClusterGroup>
|
|
55
|
+
</template>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<FSMapLayerButton
|
|
59
|
+
v-if="$props.allowedLayers?.length && $props.allowedLayers.length > 1"
|
|
60
|
+
:layers="mapLayers.filter((layer) => $props.allowedLayers?.includes(layer.name) ?? true)"
|
|
61
|
+
:modelValue="$props.currentLayer"
|
|
62
|
+
@update:model-value="$emit('update:currentLayer', $event)"
|
|
63
|
+
/>
|
|
64
|
+
|
|
65
|
+
<FSCol
|
|
66
|
+
v-if="map"
|
|
67
|
+
class="fs-map-control-buttons"
|
|
68
|
+
>
|
|
69
|
+
<FSButton
|
|
70
|
+
v-if="$props.showMyLocation"
|
|
71
|
+
icon="mdi-crosshairs-gps"
|
|
72
|
+
color="primary"
|
|
73
|
+
variant="full"
|
|
74
|
+
:elevation="true"
|
|
75
|
+
@click="() => map!.locate()"
|
|
76
|
+
/>
|
|
77
|
+
<FSCard
|
|
78
|
+
v-if="$props.showZoomButtons"
|
|
79
|
+
:elevation="true"
|
|
53
80
|
>
|
|
54
|
-
<
|
|
55
|
-
|
|
81
|
+
<FSCol
|
|
82
|
+
gap="0"
|
|
56
83
|
>
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
84
|
+
<FSButton
|
|
85
|
+
class="fs-map-zoom-plus-button"
|
|
86
|
+
icon="mdi-plus"
|
|
87
|
+
@click="() => map!.zoomIn()"
|
|
88
|
+
:border="false"
|
|
89
|
+
/>
|
|
90
|
+
<FSButton
|
|
91
|
+
class="fs-map-zoom-minus-button"
|
|
92
|
+
icon="mdi-minus"
|
|
93
|
+
@click="() => map!.zoomOut()"
|
|
94
|
+
:border="false"
|
|
95
|
+
/>
|
|
96
|
+
</FSCol>
|
|
97
|
+
</FSCard>
|
|
98
|
+
</FSCol>
|
|
99
|
+
|
|
100
|
+
<FSMapOverlay
|
|
101
|
+
v-if="$slots['overlay']"
|
|
102
|
+
:mode="$props.overlayMode"
|
|
103
|
+
@update:mode="$emit('update:overlayMode', $event)"
|
|
104
|
+
@update:height="(height) => overlayHeight = height"
|
|
105
|
+
@update:width="(width) => overlayWidth = width"
|
|
106
|
+
>
|
|
107
|
+
<template
|
|
108
|
+
#body
|
|
78
109
|
>
|
|
79
110
|
<slot
|
|
80
|
-
name="
|
|
81
|
-
>
|
|
82
|
-
<FSCol
|
|
83
|
-
class="fs-map-zoom-overlay"
|
|
84
|
-
align="bottom-center"
|
|
85
|
-
width="hug"
|
|
86
|
-
>
|
|
87
|
-
<FSButton
|
|
88
|
-
v-if="$props.showMyLocation"
|
|
89
|
-
prependIcon="mdi-crosshairs-gps"
|
|
90
|
-
padding="0 7px"
|
|
91
|
-
color="primary"
|
|
92
|
-
variant="full"
|
|
93
|
-
:elevation="true"
|
|
94
|
-
@click="locate"
|
|
95
|
-
/>
|
|
96
|
-
<FSCol
|
|
97
|
-
v-if="$props.showZoomButtons"
|
|
98
|
-
gap="0"
|
|
99
|
-
>
|
|
100
|
-
|
|
101
|
-
<FSButton
|
|
102
|
-
prependIcon="mdi-plus"
|
|
103
|
-
padding="0 7px"
|
|
104
|
-
:elevation="true"
|
|
105
|
-
@click="zoomIn"
|
|
106
|
-
/>
|
|
107
|
-
<FSButton
|
|
108
|
-
prependIcon="mdi-minus"
|
|
109
|
-
padding="0 7px"
|
|
110
|
-
:elevation="true"
|
|
111
|
-
@click="zoomOut"
|
|
112
|
-
/>
|
|
113
|
-
</FSCol>
|
|
114
|
-
</FSCol>
|
|
115
|
-
</slot>
|
|
116
|
-
<FSMapEditPointAddressOverlay
|
|
117
|
-
v-if="editingLocation"
|
|
118
|
-
:label="$tr('ui.map.address', 'Address')"
|
|
119
|
-
:modelValue="(innerModelValue.find((loc) => loc.id === $props.selectedLocationId))?.address"
|
|
120
|
-
@update:locationCoordinates="($event: Address) => onNewCoordEntered($event.latitude, $event.longitude)"
|
|
121
|
-
@update:modelValue="($event: Address) => onNewAddressEntered($event)"
|
|
122
|
-
@cancel="onCancel"
|
|
123
|
-
@submit="onSubmit"
|
|
111
|
+
name="overlay"
|
|
124
112
|
/>
|
|
125
|
-
</
|
|
126
|
-
</
|
|
113
|
+
</template>
|
|
114
|
+
</FSMapOverlay>
|
|
127
115
|
</FSCard>
|
|
128
116
|
</template>
|
|
129
117
|
|
|
130
118
|
<script lang="ts">
|
|
131
|
-
import { computed, defineComponent, onMounted,
|
|
132
|
-
|
|
133
|
-
import
|
|
119
|
+
import { computed, defineComponent, onMounted, type Ref, provide, type PropType, ref, type StyleValue, watch } from "vue";
|
|
120
|
+
|
|
121
|
+
import type {} from "leaflet.markercluster";
|
|
122
|
+
import { map as createMap, control, tileLayer, latLngBounds, latLng, type LatLng, LatLngBounds, type FitBoundsOptions } from "leaflet";
|
|
134
123
|
|
|
135
124
|
import { useTranslations as useTranslationsProvider } from "@dative-gpi/bones-ui/composables";
|
|
136
|
-
import { type
|
|
125
|
+
import { type FSArea } from '@dative-gpi/foundation-shared-domain/models';
|
|
137
126
|
|
|
138
|
-
import {
|
|
139
|
-
import { useColors, useAddress, useBreakpoints } from "../../composables";
|
|
127
|
+
import { useBreakpoints, useColors } from "../../composables";
|
|
140
128
|
import { ColorEnum, type FSLocation, type MapLayer } from "../../models";
|
|
141
129
|
|
|
142
|
-
import FSMapEditPointAddressOverlay from "./FSMapEditPointAddressOverlay.vue";
|
|
143
130
|
import FSMapLayerButton from "./FSMapLayerButton.vue";
|
|
144
131
|
import FSMapOverlay from "./FSMapOverlay.vue";
|
|
145
132
|
import FSButton from "../FSButton.vue";
|
|
146
133
|
import FSCard from "../FSCard.vue";
|
|
147
134
|
import FSCol from "../FSCol.vue";
|
|
148
|
-
|
|
135
|
+
|
|
136
|
+
import FSMapMarker from "./FSMapMarker.vue";
|
|
137
|
+
import FSMapTileLayer from "./FSMapTileLayer.vue";
|
|
138
|
+
import FSMapFeatureGroup from "./FSMapFeatureGroup.vue";
|
|
139
|
+
import FSMapMarkerClusterGroup from "./FSMapMarkerClusterGroup.vue";
|
|
140
|
+
import FSMapPolygon from "./FSMapPolygon.vue";
|
|
149
141
|
|
|
150
142
|
export default defineComponent({
|
|
151
143
|
name: "FSMap",
|
|
152
144
|
components: {
|
|
153
|
-
|
|
145
|
+
FSMapMarker,
|
|
146
|
+
FSMapTileLayer,
|
|
147
|
+
FSMapFeatureGroup,
|
|
148
|
+
FSMapMarkerClusterGroup,
|
|
149
|
+
FSMapPolygon,
|
|
150
|
+
|
|
154
151
|
FSMapLayerButton,
|
|
155
152
|
FSMapOverlay,
|
|
156
153
|
FSButton,
|
|
157
154
|
FSCard,
|
|
158
155
|
FSCol,
|
|
159
|
-
FSRow
|
|
160
156
|
},
|
|
161
157
|
props: {
|
|
162
158
|
height: {
|
|
@@ -174,11 +170,6 @@ export default defineComponent({
|
|
|
174
170
|
required: false,
|
|
175
171
|
default: false
|
|
176
172
|
},
|
|
177
|
-
editable: {
|
|
178
|
-
type: Boolean,
|
|
179
|
-
required: false,
|
|
180
|
-
default: false
|
|
181
|
-
},
|
|
182
173
|
overlayMode: {
|
|
183
174
|
type: String as PropType<'collapse' | 'half' | 'expand'>,
|
|
184
175
|
required: false,
|
|
@@ -194,11 +185,6 @@ export default defineComponent({
|
|
|
194
185
|
required: false,
|
|
195
186
|
default: true
|
|
196
187
|
},
|
|
197
|
-
showFullScreen: {
|
|
198
|
-
type: Boolean,
|
|
199
|
-
required: false,
|
|
200
|
-
default: false
|
|
201
|
-
},
|
|
202
188
|
enableScrollWheelZoom: {
|
|
203
189
|
type: Boolean,
|
|
204
190
|
required: false,
|
|
@@ -209,7 +195,7 @@ export default defineComponent({
|
|
|
209
195
|
required: false,
|
|
210
196
|
default: () => [45.71, 5.07]
|
|
211
197
|
},
|
|
212
|
-
|
|
198
|
+
locations: {
|
|
213
199
|
type: Array as PropType<FSLocation[]>,
|
|
214
200
|
required: false,
|
|
215
201
|
default: () => [],
|
|
@@ -219,12 +205,12 @@ export default defineComponent({
|
|
|
219
205
|
required: false,
|
|
220
206
|
default: () => [],
|
|
221
207
|
},
|
|
222
|
-
|
|
208
|
+
currentLayer: {
|
|
223
209
|
type: String as PropType<"map" | "imagery">,
|
|
224
210
|
required: false,
|
|
225
211
|
default: "map"
|
|
226
212
|
},
|
|
227
|
-
|
|
213
|
+
allowedLayers: {
|
|
228
214
|
type: Array as PropType<string[]>,
|
|
229
215
|
required: false,
|
|
230
216
|
default: () => ["map", "imagery"]
|
|
@@ -240,40 +226,30 @@ export default defineComponent({
|
|
|
240
226
|
default: null
|
|
241
227
|
}
|
|
242
228
|
},
|
|
243
|
-
emits: ["update:modelValue", "update:selectedLocationId", "update:selectedAreaId", 'update:overlayMode'],
|
|
229
|
+
emits: ["update:modelValue", "update:selectedLocationId", "update:selectedAreaId", 'update:overlayMode', 'update:currentLayer', "click:latlng"],
|
|
244
230
|
setup(props, { emit }) {
|
|
245
231
|
const { $tr } = useTranslationsProvider();
|
|
246
|
-
const { reverseSearch } = useAddress();
|
|
247
232
|
const { getColors } = useColors();
|
|
248
233
|
const { isExtraSmall } = useBreakpoints();
|
|
249
234
|
|
|
250
|
-
const
|
|
235
|
+
const leafletContainer = ref<HTMLElement>();
|
|
236
|
+
const locationGroupBounds = ref<LatLngBounds>();
|
|
237
|
+
const areaGroupBounds = ref<LatLngBounds>();
|
|
238
|
+
const gpsPosition : Ref<LatLng | null> = ref(null);
|
|
239
|
+
const map: Ref<L.Map | null> = ref(null);
|
|
240
|
+
const overlayHeight = ref<number>();
|
|
241
|
+
const overlayWidth = ref<number>();
|
|
251
242
|
|
|
252
|
-
const innerSelectedLayer = ref(props.selectedLayer);
|
|
253
|
-
const innerModelValue = ref(props.modelValue);
|
|
254
|
-
const editingLocation = ref(false);
|
|
255
|
-
const fullScreen = ref(false);
|
|
256
|
-
const leftOverlayHeight = ref<number>();
|
|
257
|
-
const leftOverlayWidth = ref<number>();
|
|
258
|
-
const resizeObserver = ref<ResizeObserver | null>(null);
|
|
259
|
-
|
|
260
|
-
const mapId = `map-${Math.random().toString(36).substring(7)}`;
|
|
261
243
|
const defaultZoom = 15;
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const areaLayerGroup = new LL.FeatureGroup();
|
|
265
|
-
const baseLayerGroup = new LL.LayerGroup();
|
|
266
|
-
const myLocationLayerGroup = new LL.LayerGroup();
|
|
267
|
-
|
|
268
|
-
let map: L.Map;
|
|
269
|
-
let markerLayerGroup: L.FeatureGroup | any;
|
|
244
|
+
|
|
245
|
+
provide('map', map);
|
|
270
246
|
|
|
271
247
|
const mapLayers: MapLayer[] = [
|
|
272
248
|
{
|
|
273
249
|
name: "map",
|
|
274
250
|
label: $tr("ui.map.layer.map", "Map"),
|
|
275
251
|
image: new URL("../../assets/images/map/map.png", import.meta.url).href,
|
|
276
|
-
layer:
|
|
252
|
+
layer: tileLayer(`http://{s}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}&key=${import.meta.env.VITE_GOOGLE_MAPS_API_KEY ?? ""}`, {
|
|
277
253
|
maxZoom: 22,
|
|
278
254
|
subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
|
|
279
255
|
attribution: '© Google Map Data'
|
|
@@ -283,7 +259,7 @@ export default defineComponent({
|
|
|
283
259
|
name: "imagery",
|
|
284
260
|
label: $tr("ui.map.layer.imagery", "Imagery"),
|
|
285
261
|
image: new URL("../../assets/images/map/imagery.png", import.meta.url).href,
|
|
286
|
-
layer:
|
|
262
|
+
layer: tileLayer(`http://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}&key=${import.meta.env.VITE_GOOGLE_MAPS_API_KEY ?? ""}`, {
|
|
287
263
|
maxZoom: 22,
|
|
288
264
|
subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
|
|
289
265
|
attribution: '© Google Map Data'
|
|
@@ -291,303 +267,168 @@ export default defineComponent({
|
|
|
291
267
|
}
|
|
292
268
|
];
|
|
293
269
|
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
margin += leftOverlayHeight.value;
|
|
270
|
+
const bottomOffset = computed(() => {
|
|
271
|
+
if (props.overlayMode !== 'expand' && overlayHeight.value && isExtraSmall.value) {
|
|
272
|
+
return overlayHeight.value;
|
|
298
273
|
}
|
|
299
|
-
return
|
|
274
|
+
return 0;
|
|
300
275
|
});
|
|
301
276
|
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
margin += leftOverlayWidth.value;
|
|
277
|
+
const leftOffset = computed(() => {
|
|
278
|
+
if (overlayWidth.value && !isExtraSmall.value) {
|
|
279
|
+
return overlayWidth.value;
|
|
306
280
|
}
|
|
307
|
-
return
|
|
281
|
+
return 0;
|
|
308
282
|
});
|
|
309
283
|
|
|
310
284
|
const style = computed((): StyleValue => ({
|
|
311
285
|
"--fs-map-location-pin-color": getColors(ColorEnum.Primary).base,
|
|
312
286
|
"--fs-map-mylocation-pin-color": getColors(ColorEnum.Primary).base,
|
|
313
287
|
"--fs-map-mylocation-pin-color-alpha": getColors(ColorEnum.Primary).base + "50",
|
|
314
|
-
"--fs-map-
|
|
315
|
-
"--fs-map-
|
|
316
|
-
"--fs-map-container-grayscale": props.grayscale ? '0.9' : '0'
|
|
288
|
+
"--fs-map-container-grayscale": props.grayscale ? '0.9' : '0',
|
|
289
|
+
"--fs-map-control-buttons-margin-bottom": `${bottomOffset.value}px`,
|
|
317
290
|
}));
|
|
318
291
|
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
const size = 36;
|
|
323
|
-
const icon = L.divIcon({
|
|
324
|
-
html: locationMarkerHtml(location.icon, getColors(location.color).base),
|
|
325
|
-
iconSize: [size, size],
|
|
326
|
-
className: 'fs-map-location',
|
|
327
|
-
iconAnchor: [size / 2, size / 2],
|
|
328
|
-
});
|
|
329
|
-
const marker = LL.marker([location.address.latitude, location.address.longitude], { icon }).addTo(markerLayerGroup);
|
|
330
|
-
markers[location.id] = marker;
|
|
331
|
-
marker.on('click', () => emit('update:selectedLocationId', location.id));
|
|
332
|
-
});
|
|
333
|
-
};
|
|
292
|
+
const actualLayer = computed(() => {
|
|
293
|
+
return mapLayers.find((layer) => layer.name === props.currentLayer)?.layer ?? mapLayers[0].layer;
|
|
294
|
+
});
|
|
334
295
|
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
296
|
+
const bounds = computed<LatLngBounds | null>(() => {
|
|
297
|
+
if(!locationGroupBounds.value && !areaGroupBounds.value) {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
let bounds = locationGroupBounds.value;
|
|
301
|
+
if(bounds && areaGroupBounds.value) {
|
|
302
|
+
bounds.extend(areaGroupBounds.value);
|
|
303
|
+
} else if(areaGroupBounds.value) {
|
|
304
|
+
bounds = areaGroupBounds.value;
|
|
305
|
+
}
|
|
306
|
+
return bounds as LatLngBounds;
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const calculateTargetPosition = (target: L.LatLng) => {
|
|
310
|
+
if(!map.value) {
|
|
311
|
+
return target;
|
|
312
|
+
}
|
|
313
|
+
const zoom = map.value.getZoom();
|
|
314
|
+
const targetPoint = map.value.project(target, zoom).subtract([leftOffset.value / 2, -bottomOffset.value / 2]);
|
|
315
|
+
return map.value.unproject(targetPoint, zoom);
|
|
316
|
+
}
|
|
349
317
|
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
if (!location) {
|
|
318
|
+
const panTo = (lat: number, lng: number) => {
|
|
319
|
+
if(!map.value) {
|
|
353
320
|
return;
|
|
354
321
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
address: {
|
|
358
|
-
...newAddress
|
|
359
|
-
},
|
|
360
|
-
};
|
|
361
|
-
innerModelValue.value = innerModelValue.value.map((loc) => loc.id === locationId ? newLocation : loc);
|
|
362
|
-
};
|
|
322
|
+
map.value.panTo(calculateTargetPosition(latLng(lat, lng)));
|
|
323
|
+
}
|
|
363
324
|
|
|
364
|
-
const
|
|
365
|
-
if
|
|
366
|
-
|
|
325
|
+
const setView = (lat: number, lng: number, zoom: number) => {
|
|
326
|
+
if(!map.value) {
|
|
327
|
+
return;
|
|
367
328
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
329
|
+
map.value.setView(calculateTargetPosition(latLng(lat, lng)), zoom);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const fitBounds = (bounds: LatLngBounds, options?: FitBoundsOptions) => {
|
|
333
|
+
if(!map.value) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
const calculatedBounds = new LatLngBounds(
|
|
337
|
+
calculateTargetPosition(bounds.getSouthWest()),
|
|
338
|
+
calculateTargetPosition(bounds.getNorthEast())
|
|
339
|
+
);
|
|
340
|
+
map.value.fitBounds(calculatedBounds, options);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
onMounted(() => {
|
|
344
|
+
if(!leafletContainer.value) {
|
|
345
|
+
return;
|
|
383
346
|
}
|
|
347
|
+
|
|
384
348
|
const mapOptions = {
|
|
385
349
|
zoomControl: false,
|
|
386
350
|
scrollWheelZoom: props.enableScrollWheelZoom,
|
|
387
351
|
minZoom: 2,
|
|
388
|
-
|
|
352
|
+
maxZoom: 22,
|
|
353
|
+
maxBounds: latLngBounds(latLng(-90, -180), latLng(90, 180)),
|
|
389
354
|
maxBoundsViscosity: 1.0
|
|
390
355
|
};
|
|
391
|
-
map = LL.map(mapId, mapOptions).setView([props.center[0], props.center[1]], defaultZoom);
|
|
392
|
-
map.attributionControl.remove();
|
|
393
|
-
LL.control.attribution({ position: 'bottomleft' }).addTo(map);
|
|
394
|
-
|
|
395
|
-
baseLayerGroup.addTo(map);
|
|
396
|
-
areaLayerGroup.addTo(map);
|
|
397
|
-
myLocationLayerGroup.addTo(map);
|
|
398
|
-
setMapBaseLayer(innerSelectedLayer.value);
|
|
399
|
-
displayAreas();
|
|
400
|
-
displayLocations();
|
|
401
|
-
markerLayerGroup.addTo(map);
|
|
402
|
-
|
|
403
|
-
if (innerModelValue.value.length > 0) {
|
|
404
|
-
map.fitBounds(markerLayerGroup.getBounds(), { maxZoom: defaultZoom });
|
|
405
|
-
}
|
|
406
356
|
|
|
407
|
-
map.
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
357
|
+
map.value = createMap(leafletContainer.value, mapOptions);
|
|
358
|
+
setView(props.center[0], props.center[1], defaultZoom);
|
|
359
|
+
|
|
360
|
+
map.value.on('click', (e: L.LeafletMouseEvent) => {
|
|
361
|
+
emit('click:latlng', e.latlng);
|
|
411
362
|
});
|
|
412
|
-
};
|
|
413
363
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
layer.layer.addTo(baseLayerGroup);
|
|
418
|
-
};
|
|
364
|
+
map.value.attributionControl.remove();
|
|
365
|
+
// to display google attribution in bottom left corner
|
|
366
|
+
control.attribution({ position: 'bottomleft' }).addTo(map.value);
|
|
419
367
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
modifyLocationAddress(props.selectedLocationId, address);
|
|
425
|
-
map.panTo(calculateTargetPosition(new L.LatLng(address.latitude, address.longitude)));
|
|
426
|
-
};
|
|
368
|
+
map.value.on('locationfound', (e: L.LocationEvent) => {
|
|
369
|
+
if(!e.latlng) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
427
372
|
|
|
428
|
-
|
|
429
|
-
const address = await reverseSearch(lat, lng);
|
|
373
|
+
gpsPosition.value = e.latlng;
|
|
430
374
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
375
|
+
if(!map.value) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
panTo(e.latlng.lat, e.latlng.lng);
|
|
435
380
|
});
|
|
436
|
-
};
|
|
381
|
+
});
|
|
437
382
|
|
|
438
|
-
|
|
439
|
-
if
|
|
383
|
+
watch (() => props.center, (center) => {
|
|
384
|
+
if(!map.value) {
|
|
440
385
|
return;
|
|
441
386
|
}
|
|
442
|
-
|
|
443
|
-
};
|
|
387
|
+
setView(center[0], center[1], defaultZoom);
|
|
388
|
+
});
|
|
444
389
|
|
|
445
|
-
|
|
446
|
-
if
|
|
390
|
+
watch (() => props.selectedLocationId, (selectedLocationId) => {
|
|
391
|
+
if(!map.value) {
|
|
447
392
|
return;
|
|
448
393
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
const locate = () => {
|
|
453
|
-
if (!map) {
|
|
394
|
+
const selectedLocation = props.locations.find((location) => location.id === selectedLocationId);
|
|
395
|
+
if(!selectedLocation) {
|
|
454
396
|
return;
|
|
455
397
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
map.panTo(calculateTargetPosition(e.latlng));
|
|
459
|
-
const size= 16;
|
|
460
|
-
const icon = L.divIcon({
|
|
461
|
-
html: myLocationMarkerHtml(L),
|
|
462
|
-
className: 'fs-map-mylocation',
|
|
463
|
-
iconSize: [size, size],
|
|
464
|
-
iconAnchor: [size / 2, size / 2],
|
|
465
|
-
});
|
|
466
|
-
myLocationLayerGroup.clearLayers();
|
|
467
|
-
LL.marker(e.latlng, { icon }).addTo(myLocationLayerGroup);
|
|
468
|
-
});
|
|
469
|
-
};
|
|
398
|
+
panTo(selectedLocation?.address.latitude, selectedLocation?.address.longitude);
|
|
399
|
+
}, { immediate: true });
|
|
470
400
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
return map.unproject(targetPoint, zoom);
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
const onCancel = () => {
|
|
477
|
-
editingLocation.value = false;
|
|
478
|
-
innerModelValue.value = props.modelValue;
|
|
479
|
-
if (!map) {
|
|
401
|
+
watch(() => props.selectedAreaId, (selectedAreaId) => {
|
|
402
|
+
if(!map.value) {
|
|
480
403
|
return;
|
|
481
404
|
}
|
|
482
|
-
|
|
483
|
-
if
|
|
484
|
-
map.fitBounds(markerLayerGroup.getBounds(), { maxZoom: defaultZoom });
|
|
485
|
-
}
|
|
486
|
-
else {
|
|
487
|
-
map.panTo(calculateTargetPosition(new L.LatLng(props.center[0], props.center[1])), { animate: false });
|
|
488
|
-
}
|
|
489
|
-
if (props.modelValue.length > 1) {
|
|
490
|
-
emit('update:selectedLocationId', null);
|
|
491
|
-
}
|
|
492
|
-
};
|
|
493
|
-
|
|
494
|
-
const onSubmit = () => {
|
|
495
|
-
emit('update:modelValue', innerModelValue.value);
|
|
496
|
-
if (!map) {
|
|
405
|
+
const selectedArea = props.areas.find((area) => area.id === selectedAreaId);
|
|
406
|
+
if(!selectedArea) {
|
|
497
407
|
return;
|
|
498
408
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
}
|
|
503
|
-
else {
|
|
504
|
-
map.panTo(calculateTargetPosition(new L.LatLng(props.center[0], props.center[1])), { animate: false });
|
|
505
|
-
}
|
|
506
|
-
if (props.modelValue.length > 1) {
|
|
507
|
-
emit('update:selectedLocationId', null);
|
|
508
|
-
}
|
|
509
|
-
};
|
|
510
|
-
|
|
511
|
-
onMounted(() => {
|
|
512
|
-
initMap();
|
|
513
|
-
if (props.selectedLocationId && props.modelValue.length === 1) {
|
|
514
|
-
editingLocation.value = true;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
resizeObserver.value = new ResizeObserver(entries => {
|
|
518
|
-
entries.forEach((entry) => {
|
|
519
|
-
if (entry.target.id === `left-overlay-${mapId}`) {
|
|
520
|
-
leftOverlayWidth.value = entry.contentRect.width;
|
|
521
|
-
}
|
|
522
|
-
if (entry.target.id === `left-overlay-mobile-${mapId}`) {
|
|
523
|
-
leftOverlayHeight.value = entry.contentRect.height;
|
|
524
|
-
}
|
|
525
|
-
});
|
|
526
|
-
});
|
|
527
|
-
if (document.querySelector(`#left-overlay-mobile-${mapId}`)) {
|
|
528
|
-
resizeObserver.value.observe(document.querySelector(`#left-overlay-mobile-${mapId}`)!);
|
|
529
|
-
}
|
|
530
|
-
if (document.querySelector(`#left-overlay-${mapId}`)) {
|
|
531
|
-
resizeObserver.value.observe(document.querySelector(`#left-overlay-${mapId}`)!);
|
|
532
|
-
}
|
|
533
|
-
});
|
|
534
|
-
|
|
535
|
-
onUnmounted((): void => {
|
|
536
|
-
if (resizeObserver.value) {
|
|
537
|
-
resizeObserver.value.disconnect();
|
|
538
|
-
}
|
|
539
|
-
});
|
|
540
|
-
|
|
541
|
-
watch(() => innerModelValue.value, () => {
|
|
542
|
-
displayLocations();
|
|
543
|
-
});
|
|
409
|
+
const bounds = latLngBounds(selectedArea.coordinates.map((coord) => latLng(coord.latitude, coord.longitude)));
|
|
410
|
+
fitBounds(bounds);
|
|
411
|
+
}, { immediate: true });
|
|
544
412
|
|
|
545
|
-
watch(() =>
|
|
546
|
-
if
|
|
413
|
+
watch( () => bounds.value, (bounds) => {
|
|
414
|
+
if(!map.value || !bounds) {
|
|
547
415
|
return;
|
|
548
416
|
}
|
|
549
|
-
|
|
550
|
-
Object.values(markers).forEach((marker) => {
|
|
551
|
-
marker.getElement()?.classList.remove('fs-map-location-selected');
|
|
552
|
-
});
|
|
553
|
-
|
|
554
|
-
const marker = markers[props.selectedLocationId];
|
|
555
|
-
map.flyTo(calculateTargetPosition(marker.getLatLng(), 17), 17, { animate: false });
|
|
556
|
-
marker.getElement()?.classList.add('fs-map-location-selected');
|
|
557
|
-
})
|
|
558
|
-
|
|
559
|
-
watch(() => props.selectedAreaId, () => {
|
|
560
|
-
if (!props.selectedAreaId || !map) {
|
|
561
|
-
return;
|
|
562
|
-
}
|
|
563
|
-
const area = areas[props.selectedAreaId];
|
|
564
|
-
if (area) {
|
|
565
|
-
map.fitBounds(area.getBounds(), { maxZoom: 17 });
|
|
566
|
-
}
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
watch(innerSelectedLayer, () => {
|
|
570
|
-
setMapBaseLayer(innerSelectedLayer.value);
|
|
417
|
+
fitBounds(bounds, { maxZoom: defaultZoom });
|
|
571
418
|
});
|
|
572
419
|
|
|
573
420
|
return {
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
421
|
+
ColorEnum,
|
|
422
|
+
leafletContainer,
|
|
423
|
+
locationGroupBounds,
|
|
424
|
+
overlayHeight,
|
|
425
|
+
overlayWidth,
|
|
426
|
+
areaGroupBounds,
|
|
427
|
+
map,
|
|
428
|
+
actualLayer,
|
|
579
429
|
mapLayers,
|
|
580
|
-
|
|
581
|
-
style
|
|
582
|
-
L,
|
|
583
|
-
onNewAddressEntered,
|
|
584
|
-
onNewCoordEntered,
|
|
585
|
-
setMapBaseLayer,
|
|
586
|
-
onCancel,
|
|
587
|
-
onSubmit,
|
|
588
|
-
zoomOut,
|
|
589
|
-
locate,
|
|
590
|
-
zoomIn
|
|
430
|
+
gpsPosition,
|
|
431
|
+
style
|
|
591
432
|
};
|
|
592
433
|
}
|
|
593
434
|
});
|