@dative-gpi/foundation-shared-components 0.0.213 → 0.0.215
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/images/map/imagery.png +0 -0
- package/assets/images/map/map.png +0 -0
- package/components/fields/FSColorField.vue +3 -3
- package/components/fields/FSGradientField.vue +37 -20
- package/components/map/FSMap.vue +129 -90
- package/components/map/FSMapLayerButton.vue +17 -11
- package/components/map/FSMapOverlay.vue +150 -0
- package/package.json +4 -4
- package/styles/components/fs_map.scss +9 -18
- package/styles/components/fs_map_overlay.scss +38 -0
- package/styles/components/index.scss +1 -0
- package/utils/gradient.ts +1599 -1598
- package/utils/index.ts +1 -0
- package/utils/leafletMarkers.ts +38 -0
- package/assets/images/map/osm.png +0 -0
|
Binary file
|
|
Binary file
|
|
@@ -164,7 +164,7 @@ export default defineComponent({
|
|
|
164
164
|
|
|
165
165
|
const menu = ref(false);
|
|
166
166
|
|
|
167
|
-
const innerColor = ref(props.modelValue
|
|
167
|
+
const innerColor = ref(getColors(props.modelValue).base);
|
|
168
168
|
const innerOpacity = ref(getHexFromPercentage(props.opacityValue));
|
|
169
169
|
const fullColor = ref(innerColor.value + innerOpacity.value);
|
|
170
170
|
|
|
@@ -174,14 +174,14 @@ export default defineComponent({
|
|
|
174
174
|
"--fs-color-field-cursor" : "default",
|
|
175
175
|
"--fs-color-field-border-color" : lights.base,
|
|
176
176
|
"--fs-color-field-color" : lights.dark,
|
|
177
|
-
"--fs-color-field-colorvalue": fullColor.value,
|
|
177
|
+
"--fs-color-field-colorvalue" : fullColor.value,
|
|
178
178
|
};
|
|
179
179
|
}
|
|
180
180
|
return {
|
|
181
181
|
"--fs-color-field-cursor" : "pointer",
|
|
182
182
|
"--fs-color-field-border-color" : lights.dark,
|
|
183
183
|
"--fs-color-field-color" : darks.base,
|
|
184
|
-
"--fs-color-field-colorvalue": fullColor.value,
|
|
184
|
+
"--fs-color-field-colorvalue" : fullColor.value,
|
|
185
185
|
};
|
|
186
186
|
});
|
|
187
187
|
|
|
@@ -12,29 +12,30 @@
|
|
|
12
12
|
<FSRow>
|
|
13
13
|
<FSColorField
|
|
14
14
|
v-for="colorIndex in $props.colorCount"
|
|
15
|
-
:
|
|
16
|
-
:modelValue="$props.modelValue[colorIndex-1]"
|
|
15
|
+
:allowOpacity="$props.allowOpacity"
|
|
16
|
+
:modelValue="$props.modelValue[colorIndex - 1]"
|
|
17
17
|
:required="$props.required"
|
|
18
18
|
:editable="$props.editable"
|
|
19
|
-
|
|
19
|
+
:key="colorIndex"
|
|
20
|
+
@update:modelValue="$emit('update:modelValue', $props.modelValue.map((color, i) => colorIndex === i + 1 ? $event : color))"
|
|
20
21
|
/>
|
|
21
22
|
</FSRow>
|
|
22
23
|
<FSSelectField
|
|
23
24
|
class="fs-gradient-select-field"
|
|
24
|
-
:items="items"
|
|
25
|
-
@update:modelValue="$emit('update:modelValue', JSON.parse($event))"
|
|
26
25
|
:clearable="false"
|
|
27
26
|
:editable="$props.editable"
|
|
27
|
+
:items="items"
|
|
28
28
|
:modelValue="JSON.stringify($props.modelValue)"
|
|
29
|
+
@update:modelValue="$emit('update:modelValue', presetGradients[$event])"
|
|
29
30
|
>
|
|
30
31
|
<template
|
|
31
32
|
v-slot:selection="{ item }"
|
|
32
33
|
>
|
|
33
34
|
<FSRow
|
|
35
|
+
class="fs-gradient-field-preview"
|
|
34
36
|
height="fill"
|
|
35
37
|
width="100%"
|
|
36
|
-
|
|
37
|
-
:style="{ '--fs-gradient-field-background': `linear-gradient(to right, ${JSON.parse(item.value).join(', ')})` }"
|
|
38
|
+
:style="{ '--fs-gradient-field-background': `linear-gradient(to right, ${encodeGradientCssColors(JSON.parse(item.value))})` }"
|
|
38
39
|
>
|
|
39
40
|
<span />
|
|
40
41
|
</FSRow>
|
|
@@ -49,10 +50,10 @@
|
|
|
49
50
|
#title
|
|
50
51
|
>
|
|
51
52
|
<FSRow
|
|
53
|
+
class="fs-gradient-field-preview"
|
|
52
54
|
height="fill"
|
|
53
55
|
width="100%"
|
|
54
|
-
|
|
55
|
-
:style="{ '--fs-gradient-field-background': `linear-gradient(to right, ${JSON.parse(item.value).join(', ')})` }"
|
|
56
|
+
:style="{ '--fs-gradient-field-background': `linear-gradient(to right, ${encodeGradientCssColors(presetGradients[item.value])})` }"
|
|
56
57
|
>
|
|
57
58
|
<span />
|
|
58
59
|
</FSRow>
|
|
@@ -67,21 +68,23 @@
|
|
|
67
68
|
<script lang="ts">
|
|
68
69
|
import { type PropType, defineComponent } from "vue";
|
|
69
70
|
|
|
71
|
+
import { groupedGradients } from "../../utils";
|
|
72
|
+
import { useColors } from "../../composables";
|
|
73
|
+
|
|
74
|
+
import FSSelectField from "./FSSelectField.vue";
|
|
70
75
|
import FSColorField from "./FSColorField.vue";
|
|
76
|
+
import FSBaseField from "./FSBaseField.vue";
|
|
71
77
|
import FSCol from "../FSCol.vue";
|
|
72
78
|
import FSRow from "../FSRow.vue";
|
|
73
|
-
import FSBaseField from "./FSBaseField.vue";
|
|
74
|
-
import FSSelectField from "./FSSelectField.vue";
|
|
75
|
-
import { groupedGradients } from "../../utils";
|
|
76
79
|
|
|
77
80
|
export default defineComponent({
|
|
78
81
|
name: "FSGradientField",
|
|
79
82
|
components: {
|
|
80
|
-
|
|
83
|
+
FSSelectField,
|
|
81
84
|
FSColorField,
|
|
85
|
+
FSBaseField,
|
|
82
86
|
FSCol,
|
|
83
|
-
FSRow
|
|
84
|
-
FSSelectField
|
|
87
|
+
FSRow
|
|
85
88
|
},
|
|
86
89
|
props: {
|
|
87
90
|
label: {
|
|
@@ -94,6 +97,11 @@ export default defineComponent({
|
|
|
94
97
|
required: false,
|
|
95
98
|
default: null
|
|
96
99
|
},
|
|
100
|
+
colorCount: {
|
|
101
|
+
type: Number,
|
|
102
|
+
required: false,
|
|
103
|
+
default: 2
|
|
104
|
+
},
|
|
97
105
|
modelValue: {
|
|
98
106
|
type: Array as PropType<string[]>,
|
|
99
107
|
required: true
|
|
@@ -108,18 +116,27 @@ export default defineComponent({
|
|
|
108
116
|
required: false,
|
|
109
117
|
default: true
|
|
110
118
|
},
|
|
111
|
-
|
|
112
|
-
type:
|
|
119
|
+
allowOpacity: {
|
|
120
|
+
type: Boolean,
|
|
113
121
|
required: false,
|
|
114
|
-
default:
|
|
122
|
+
default: false
|
|
115
123
|
}
|
|
116
124
|
},
|
|
117
125
|
emits: ["update:modelValue"],
|
|
118
126
|
setup(props) {
|
|
119
|
-
const
|
|
127
|
+
const { getColors } = useColors();
|
|
128
|
+
|
|
129
|
+
const presetGradients = groupedGradients[props.colorCount];
|
|
130
|
+
const items = Object.keys(presetGradients)
|
|
131
|
+
|
|
132
|
+
const encodeGradientCssColors = (colors: string[]) => {
|
|
133
|
+
return colors.map((color) => getColors(color).base).join(", ");
|
|
134
|
+
};
|
|
120
135
|
|
|
121
136
|
return {
|
|
122
|
-
|
|
137
|
+
presetGradients,
|
|
138
|
+
items,
|
|
139
|
+
encodeGradientCssColors
|
|
123
140
|
};
|
|
124
141
|
}
|
|
125
142
|
});
|
package/components/map/FSMap.vue
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<FSCard
|
|
3
3
|
:width="$props.width"
|
|
4
|
+
:style="style"
|
|
4
5
|
v-bind="$attrs"
|
|
5
6
|
>
|
|
6
7
|
<FSCol
|
|
@@ -8,27 +9,28 @@
|
|
|
8
9
|
width="fill"
|
|
9
10
|
:class="['fs-map', { 'fs-map-fullscreen': fullScreen }]"
|
|
10
11
|
>
|
|
11
|
-
<
|
|
12
|
-
v-if="$slots
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
<FSMapOverlay
|
|
13
|
+
v-if="$slots['leftoverlay-header'] || $slots['leftoverlay-body']"
|
|
14
|
+
:mode="$props.overlayMode"
|
|
15
|
+
:height="$props.height"
|
|
16
|
+
:mapId="mapId"
|
|
17
|
+
@update:mode="$emit('update:overlayMode', $event)"
|
|
16
18
|
>
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
:elevation="true"
|
|
20
|
-
:border="false"
|
|
19
|
+
<template
|
|
20
|
+
v-slot:leftoverlay-header
|
|
21
21
|
>
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
22
|
+
<slot
|
|
23
|
+
name="leftoverlay-header"
|
|
24
|
+
/>
|
|
25
|
+
</template>
|
|
26
|
+
<template
|
|
27
|
+
v-slot:leftoverlay-body
|
|
28
|
+
>
|
|
29
|
+
<slot
|
|
30
|
+
name="leftoverlay-body"
|
|
31
|
+
/>
|
|
32
|
+
</template>
|
|
33
|
+
</FSMapOverlay>
|
|
32
34
|
<FSRow
|
|
33
35
|
v-if="$props.editable && !editingLocation && $props.selectedLocationId !== null"
|
|
34
36
|
class="fs-map-overlay-edit-button"
|
|
@@ -39,9 +41,7 @@
|
|
|
39
41
|
@click="editingLocation = true"
|
|
40
42
|
/>
|
|
41
43
|
</FSRow>
|
|
42
|
-
<FSCol
|
|
43
|
-
:style="style"
|
|
44
|
-
>
|
|
44
|
+
<FSCol>
|
|
45
45
|
<div
|
|
46
46
|
class="fs-leaflet-container"
|
|
47
47
|
:id="mapId"
|
|
@@ -129,21 +129,21 @@
|
|
|
129
129
|
</template>
|
|
130
130
|
|
|
131
131
|
<script lang="ts">
|
|
132
|
-
import { computed, defineComponent, onMounted, type PropType, ref, watch } from "vue";
|
|
132
|
+
import { computed, defineComponent, onMounted, onUnmounted, type PropType, ref, watch } from "vue";
|
|
133
133
|
|
|
134
134
|
import * as L from "leaflet";
|
|
135
135
|
import "leaflet.markercluster";
|
|
136
136
|
|
|
137
137
|
import { useTranslations as useTranslationsProvider } from "@dative-gpi/bones-ui/composables";
|
|
138
|
-
|
|
139
138
|
import { type Address, type FSArea } from '@dative-gpi/foundation-shared-domain/models';
|
|
140
139
|
|
|
140
|
+
import { clusterMarker, locationMarker, myLocationMarker } from "../../utils";
|
|
141
141
|
import { ColorEnum, type FSLocation, type MapLayer } from "../../models";
|
|
142
|
-
import { useColors, useAddress } from "../../composables";
|
|
142
|
+
import { useColors, useAddress, useBreakpoints } from "../../composables";
|
|
143
143
|
|
|
144
144
|
import FSMapEditPointAddressOverlay from "./FSMapEditPointAddressOverlay.vue";
|
|
145
145
|
import FSMapLayerButton from "./FSMapLayerButton.vue";
|
|
146
|
-
import
|
|
146
|
+
import FSMapOverlay from "./FSMapOverlay.vue";
|
|
147
147
|
import FSButton from "../FSButton.vue";
|
|
148
148
|
import FSCard from "../FSCard.vue";
|
|
149
149
|
import FSCol from "../FSCol.vue";
|
|
@@ -154,7 +154,7 @@ export default defineComponent({
|
|
|
154
154
|
components: {
|
|
155
155
|
FSMapEditPointAddressOverlay,
|
|
156
156
|
FSMapLayerButton,
|
|
157
|
-
|
|
157
|
+
FSMapOverlay,
|
|
158
158
|
FSButton,
|
|
159
159
|
FSCard,
|
|
160
160
|
FSCol,
|
|
@@ -181,6 +181,11 @@ export default defineComponent({
|
|
|
181
181
|
required: false,
|
|
182
182
|
default: false
|
|
183
183
|
},
|
|
184
|
+
overlayMode: {
|
|
185
|
+
type: String as PropType<'collapse' | 'half' | 'expand'>,
|
|
186
|
+
required: false,
|
|
187
|
+
default: 'collapse'
|
|
188
|
+
},
|
|
184
189
|
showMyLocation: {
|
|
185
190
|
type: Boolean,
|
|
186
191
|
required: false,
|
|
@@ -196,6 +201,11 @@ export default defineComponent({
|
|
|
196
201
|
required: false,
|
|
197
202
|
default: false
|
|
198
203
|
},
|
|
204
|
+
enableScrollWheelZoom: {
|
|
205
|
+
type: Boolean,
|
|
206
|
+
required: false,
|
|
207
|
+
default: false
|
|
208
|
+
},
|
|
199
209
|
center: {
|
|
200
210
|
type: Array as PropType<number[]>,
|
|
201
211
|
required: false,
|
|
@@ -212,14 +222,14 @@ export default defineComponent({
|
|
|
212
222
|
default: () => [],
|
|
213
223
|
},
|
|
214
224
|
selectedLayer: {
|
|
215
|
-
type: String as PropType<"
|
|
225
|
+
type: String as PropType<"map" | "imagery">,
|
|
216
226
|
required: false,
|
|
217
|
-
default: "
|
|
227
|
+
default: "map"
|
|
218
228
|
},
|
|
219
229
|
selectableLayers: {
|
|
220
230
|
type: Array as PropType<string[]>,
|
|
221
231
|
required: false,
|
|
222
|
-
default: () => ["
|
|
232
|
+
default: () => ["map", "imagery"]
|
|
223
233
|
},
|
|
224
234
|
selectedLocationId: {
|
|
225
235
|
type: String as PropType<string | null>,
|
|
@@ -232,11 +242,12 @@ export default defineComponent({
|
|
|
232
242
|
default: null
|
|
233
243
|
}
|
|
234
244
|
},
|
|
235
|
-
emits: ["update:modelValue", "update:selectedLocationId", "update:selectedAreaId"],
|
|
245
|
+
emits: ["update:modelValue", "update:selectedLocationId", "update:selectedAreaId", 'update:overlayMode'],
|
|
236
246
|
setup(props, { emit }) {
|
|
247
|
+
const { $tr } = useTranslationsProvider();
|
|
237
248
|
const { reverseSearch } = useAddress();
|
|
238
249
|
const { getColors } = useColors();
|
|
239
|
-
const {
|
|
250
|
+
const { isExtraSmall } = useBreakpoints();
|
|
240
251
|
|
|
241
252
|
const LL = window.L;
|
|
242
253
|
|
|
@@ -244,6 +255,9 @@ export default defineComponent({
|
|
|
244
255
|
const innerModelValue = ref(props.modelValue);
|
|
245
256
|
const editingLocation = ref(false);
|
|
246
257
|
const fullScreen = ref(false);
|
|
258
|
+
const leftOverlayHeight = ref<number>();
|
|
259
|
+
const leftOverlayWidth = ref<number>();
|
|
260
|
+
const resizeObserver = ref<ResizeObserver | null>(null);
|
|
247
261
|
|
|
248
262
|
const mapId = `map-${Math.random().toString(36).substring(7)}`;
|
|
249
263
|
const defaultZoom = 15;
|
|
@@ -256,52 +270,52 @@ export default defineComponent({
|
|
|
256
270
|
let map: L.Map;
|
|
257
271
|
let markerLayerGroup: L.FeatureGroup | any;
|
|
258
272
|
|
|
259
|
-
if (props.editable) {
|
|
260
|
-
markerLayerGroup = new LL.FeatureGroup();
|
|
261
|
-
}
|
|
262
|
-
else {
|
|
263
|
-
markerLayerGroup = new LL.MarkerClusterGroup({
|
|
264
|
-
spiderfyOnMaxZoom: false,
|
|
265
|
-
showCoverageOnHover: false,
|
|
266
|
-
disableClusteringAtZoom: 17,
|
|
267
|
-
iconCreateFunction: function (cluster: any) {
|
|
268
|
-
return LL.divIcon({
|
|
269
|
-
html: `<div>
|
|
270
|
-
<span>${cluster.getChildCount()}</span>
|
|
271
|
-
</div>`,
|
|
272
|
-
className: 'fs-map-location fs-map-location-full',
|
|
273
|
-
iconSize: [36, 36],
|
|
274
|
-
iconAnchor: [18, 18],
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
273
|
const mapLayers: MapLayer[] = [
|
|
280
274
|
{
|
|
281
|
-
name: "
|
|
282
|
-
label: $tr("ui.map.layer.
|
|
283
|
-
image: new URL("../../assets/images/map/
|
|
284
|
-
layer: LL.tileLayer(
|
|
285
|
-
maxZoom:
|
|
286
|
-
|
|
275
|
+
name: "map",
|
|
276
|
+
label: $tr("ui.map.layer.map", "Map"),
|
|
277
|
+
image: new URL("../../assets/images/map/map.png", import.meta.url).href,
|
|
278
|
+
layer: LL.tileLayer(`http://{s}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}&key=${import.meta.env.VITE_GOOGLE_MAPS_API_KEY ?? ""}`, {
|
|
279
|
+
maxZoom: 22,
|
|
280
|
+
subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
|
|
281
|
+
attribution: '© Google Map Data'
|
|
287
282
|
})
|
|
288
283
|
},
|
|
289
284
|
{
|
|
290
285
|
name: "imagery",
|
|
291
286
|
label: $tr("ui.map.layer.imagery", "Imagery"),
|
|
292
287
|
image: new URL("../../assets/images/map/imagery.png", import.meta.url).href,
|
|
293
|
-
layer: LL.tileLayer(
|
|
294
|
-
|
|
295
|
-
|
|
288
|
+
layer: LL.tileLayer(`http://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}&key=${import.meta.env.VITE_GOOGLE_MAPS_API_KEY ?? ""}`, {
|
|
289
|
+
maxZoom: 22,
|
|
290
|
+
subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
|
|
291
|
+
attribution: '© Google Map Data'
|
|
296
292
|
})
|
|
297
293
|
}
|
|
298
294
|
];
|
|
299
295
|
|
|
296
|
+
const bottomMargin = computed(() => {
|
|
297
|
+
let margin = 0;
|
|
298
|
+
if (props.overlayMode !== 'expand' && leftOverlayHeight.value && isExtraSmall.value) {
|
|
299
|
+
margin += leftOverlayHeight.value;
|
|
300
|
+
}
|
|
301
|
+
return margin;
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
const leftMargin = computed(() => {
|
|
305
|
+
let margin = 0;
|
|
306
|
+
if (leftOverlayWidth.value && !isExtraSmall.value) {
|
|
307
|
+
margin += leftOverlayWidth.value;
|
|
308
|
+
}
|
|
309
|
+
return margin;
|
|
310
|
+
});
|
|
311
|
+
|
|
300
312
|
const style = computed((): { [key: string]: string | undefined } => {
|
|
301
313
|
return {
|
|
302
314
|
"--fs-map-location-pin-color": getColors(ColorEnum.Primary).base,
|
|
315
|
+
"--fs-map-mylocation-pin-color": getColors(ColorEnum.Primary).base,
|
|
303
316
|
"--fs-map-mylocation-pin-color-alpha": getColors(ColorEnum.Primary).base + "50",
|
|
304
317
|
"--fs-map-leaflet-container-height": props.height as string,
|
|
318
|
+
"--fs-map-leaflet-bottom-overlay-margin": `${bottomMargin.value}px`,
|
|
305
319
|
"--fs-map-container-grayscale": props.grayscale ? '0.9' : '0'
|
|
306
320
|
};
|
|
307
321
|
});
|
|
@@ -309,18 +323,7 @@ export default defineComponent({
|
|
|
309
323
|
const displayLocations = () => {
|
|
310
324
|
markerLayerGroup.clearLayers();
|
|
311
325
|
innerModelValue.value.forEach((location) => {
|
|
312
|
-
const
|
|
313
|
-
<div style="--fs-map-mylocation-pin-color-alpha:${getColors(location.color).base}50;--fs-map-location-pin-color: ${getColors(location.color).base}">
|
|
314
|
-
<div class="fs-map-location-pin">
|
|
315
|
-
<i class="${location.icon} mdi v-icon notranslate v-theme--DefaultTheme fs-icon" aria-hidden="true" style="--fs-icon-font-size: 22px;" ></i>
|
|
316
|
-
</div>
|
|
317
|
-
</div>`;
|
|
318
|
-
const icon = LL.divIcon({
|
|
319
|
-
html: iconHtml,
|
|
320
|
-
iconSize: [36, 36],
|
|
321
|
-
className: 'fs-map-location',
|
|
322
|
-
iconAnchor: [18, 18],
|
|
323
|
-
});
|
|
326
|
+
const icon = locationMarker(location.icon, getColors(location.color).base, L);
|
|
324
327
|
const marker = LL.marker([location.address.latitude, location.address.longitude], { icon }).addTo(markerLayerGroup);
|
|
325
328
|
markers[location.id] = marker;
|
|
326
329
|
marker.on('click', () => emit('update:selectedLocationId', location.id));
|
|
@@ -357,9 +360,22 @@ export default defineComponent({
|
|
|
357
360
|
};
|
|
358
361
|
|
|
359
362
|
const initMap = () => {
|
|
363
|
+
if (props.editable) {
|
|
364
|
+
markerLayerGroup = new LL.FeatureGroup();
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
markerLayerGroup = new LL.MarkerClusterGroup({
|
|
368
|
+
spiderfyOnMaxZoom: false,
|
|
369
|
+
showCoverageOnHover: false,
|
|
370
|
+
disableClusteringAtZoom: 17,
|
|
371
|
+
iconCreateFunction: function (cluster: any) {
|
|
372
|
+
return clusterMarker(cluster.getChildCount(), L);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
}
|
|
360
376
|
const mapOptions = {
|
|
361
377
|
zoomControl: false,
|
|
362
|
-
scrollWheelZoom:
|
|
378
|
+
scrollWheelZoom: props.enableScrollWheelZoom,
|
|
363
379
|
minZoom: 2,
|
|
364
380
|
maxBounds: LL.latLngBounds(LL.latLng(-90, -180), LL.latLng(90, 180)),
|
|
365
381
|
maxBoundsViscosity: 1.0
|
|
@@ -387,7 +403,7 @@ export default defineComponent({
|
|
|
387
403
|
});
|
|
388
404
|
};
|
|
389
405
|
|
|
390
|
-
const setMapBaseLayer = (layerName: '
|
|
406
|
+
const setMapBaseLayer = (layerName: 'map' | 'imagery') => {
|
|
391
407
|
const layer = mapLayers.find((mapLayer) => mapLayer.name === layerName) ?? mapLayers[0];
|
|
392
408
|
baseLayerGroup.clearLayers();
|
|
393
409
|
layer.layer.addTo(baseLayerGroup);
|
|
@@ -398,7 +414,7 @@ export default defineComponent({
|
|
|
398
414
|
return;
|
|
399
415
|
}
|
|
400
416
|
modifyLocationAddress(props.selectedLocationId, address);
|
|
401
|
-
map.panTo(
|
|
417
|
+
map.panTo(calculateTargetPosition(new L.LatLng(address.latitude, address.longitude)));
|
|
402
418
|
};
|
|
403
419
|
|
|
404
420
|
const onNewCoordEntered = async (lat: number, lng: number) => {
|
|
@@ -431,19 +447,18 @@ export default defineComponent({
|
|
|
431
447
|
}
|
|
432
448
|
map.locate();
|
|
433
449
|
map.on('locationfound', (e: L.LocationEvent) => {
|
|
434
|
-
map.panTo(e.latlng);
|
|
435
|
-
const
|
|
436
|
-
const icon = LL.divIcon({
|
|
437
|
-
html: iconHtml,
|
|
438
|
-
className: 'fs-map-mylocation',
|
|
439
|
-
iconSize: [16, 16],
|
|
440
|
-
iconAnchor: [8, 8],
|
|
441
|
-
});
|
|
450
|
+
map.panTo(calculateTargetPosition(e.latlng));
|
|
451
|
+
const icon = myLocationMarker(L);
|
|
442
452
|
myLocationLayerGroup.clearLayers();
|
|
443
453
|
LL.marker(e.latlng, { icon }).addTo(myLocationLayerGroup);
|
|
444
454
|
});
|
|
445
455
|
};
|
|
446
456
|
|
|
457
|
+
const calculateTargetPosition = (target: L.LatLng, zoom: number = map.getZoom()) => {
|
|
458
|
+
const targetPoint = map.project(target, zoom).subtract([leftMargin.value / 2, -bottomMargin.value / 2]);
|
|
459
|
+
return map.unproject(targetPoint, zoom);
|
|
460
|
+
}
|
|
461
|
+
|
|
447
462
|
const onCancel = () => {
|
|
448
463
|
editingLocation.value = false;
|
|
449
464
|
innerModelValue.value = props.modelValue;
|
|
@@ -455,7 +470,7 @@ export default defineComponent({
|
|
|
455
470
|
map.fitBounds(markerLayerGroup.getBounds(), { maxZoom: defaultZoom });
|
|
456
471
|
}
|
|
457
472
|
else {
|
|
458
|
-
map.panTo(
|
|
473
|
+
map.panTo(calculateTargetPosition(new L.LatLng(props.center[0], props.center[1])), { animate: false });
|
|
459
474
|
}
|
|
460
475
|
if (props.modelValue.length > 1) {
|
|
461
476
|
emit('update:selectedLocationId', null);
|
|
@@ -472,7 +487,7 @@ export default defineComponent({
|
|
|
472
487
|
map.fitBounds(markerLayerGroup.getBounds(), { maxZoom: defaultZoom });
|
|
473
488
|
}
|
|
474
489
|
else {
|
|
475
|
-
map.
|
|
490
|
+
map.panTo(calculateTargetPosition(new L.LatLng(props.center[0], props.center[1])), { animate: false });
|
|
476
491
|
}
|
|
477
492
|
if (props.modelValue.length > 1) {
|
|
478
493
|
emit('update:selectedLocationId', null);
|
|
@@ -484,6 +499,29 @@ export default defineComponent({
|
|
|
484
499
|
if (props.selectedLocationId && props.modelValue.length === 1) {
|
|
485
500
|
editingLocation.value = true;
|
|
486
501
|
}
|
|
502
|
+
|
|
503
|
+
resizeObserver.value = new ResizeObserver(entries => {
|
|
504
|
+
entries.forEach((entry) => {
|
|
505
|
+
if (entry.target.id === `left-overlay-${mapId}`) {
|
|
506
|
+
leftOverlayWidth.value = entry.contentRect.width;
|
|
507
|
+
}
|
|
508
|
+
if(entry.target.id === `left-overlay-mobile-${mapId}`) {
|
|
509
|
+
leftOverlayHeight.value = entry.contentRect.height;
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
if (document.querySelector(`#left-overlay-mobile-${mapId}`)) {
|
|
514
|
+
resizeObserver.value.observe(document.querySelector(`#left-overlay-mobile-${mapId}`)!);
|
|
515
|
+
}
|
|
516
|
+
if (document.querySelector(`#left-overlay-${mapId}`)) {
|
|
517
|
+
resizeObserver.value.observe(document.querySelector(`#left-overlay-${mapId}`)!);
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
onUnmounted((): void => {
|
|
522
|
+
if (resizeObserver.value) {
|
|
523
|
+
resizeObserver.value.disconnect();
|
|
524
|
+
}
|
|
487
525
|
});
|
|
488
526
|
|
|
489
527
|
watch(() => innerModelValue.value, () => {
|
|
@@ -500,7 +538,7 @@ export default defineComponent({
|
|
|
500
538
|
});
|
|
501
539
|
|
|
502
540
|
const marker = markers[props.selectedLocationId];
|
|
503
|
-
map.flyTo(marker.getLatLng(), 17, { animate: false });
|
|
541
|
+
map.flyTo(calculateTargetPosition(marker.getLatLng(), 17), 17, { animate: false });
|
|
504
542
|
marker.getElement()?.classList.add('fs-map-location-selected');
|
|
505
543
|
})
|
|
506
544
|
|
|
@@ -519,10 +557,11 @@ export default defineComponent({
|
|
|
519
557
|
});
|
|
520
558
|
|
|
521
559
|
return {
|
|
522
|
-
|
|
560
|
+
bottomMargin,
|
|
523
561
|
editingLocation,
|
|
524
|
-
innerModelValue,
|
|
525
562
|
fullScreen,
|
|
563
|
+
innerModelValue,
|
|
564
|
+
innerSelectedLayer,
|
|
526
565
|
mapLayers,
|
|
527
566
|
mapId,
|
|
528
567
|
style,
|
|
@@ -536,6 +575,6 @@ export default defineComponent({
|
|
|
536
575
|
locate,
|
|
537
576
|
zoomIn
|
|
538
577
|
};
|
|
539
|
-
}
|
|
578
|
+
}
|
|
540
579
|
});
|
|
541
580
|
</script>
|
|
@@ -7,20 +7,24 @@
|
|
|
7
7
|
<FSDialog
|
|
8
8
|
v-model="dialog"
|
|
9
9
|
title="Select Layers"
|
|
10
|
-
width="
|
|
10
|
+
width="442px"
|
|
11
11
|
>
|
|
12
12
|
<template
|
|
13
13
|
v-slot:body
|
|
14
14
|
>
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
<FSRow
|
|
16
|
+
align="center-center"
|
|
17
|
+
>
|
|
18
|
+
<FSImageCard
|
|
19
|
+
v-for="layer in layers"
|
|
20
|
+
:variant="modelValue === layer.name ? 'full' : 'background'"
|
|
21
|
+
:color="modelValue === layer.name ? 'primary' : 'light'"
|
|
22
|
+
:label="layer.label"
|
|
23
|
+
:src="layer.image"
|
|
24
|
+
:key="layer.name"
|
|
25
|
+
@click="onLayerClick(layer.name)"
|
|
26
|
+
/>
|
|
27
|
+
</FSRow>
|
|
24
28
|
</template>
|
|
25
29
|
</FSDialog>
|
|
26
30
|
</template>
|
|
@@ -33,13 +37,15 @@ import { type MapLayer } from "../../models";
|
|
|
33
37
|
import FSImageCard from "../FSImageCard.vue";
|
|
34
38
|
import FSButton from "../FSButton.vue";
|
|
35
39
|
import FSDialog from "../FSDialog.vue";
|
|
40
|
+
import FSRow from "../FSRow.vue";
|
|
36
41
|
|
|
37
42
|
export default defineComponent({
|
|
38
43
|
name: "FSMapLayerButton",
|
|
39
44
|
components: {
|
|
40
45
|
FSImageCard,
|
|
41
46
|
FSButton,
|
|
42
|
-
FSDialog
|
|
47
|
+
FSDialog,
|
|
48
|
+
FSRow
|
|
43
49
|
},
|
|
44
50
|
props: {
|
|
45
51
|
layers: {
|