@dative-gpi/foundation-shared-components 1.0.137 → 1.0.138

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.
Binary file
@@ -50,6 +50,7 @@
50
50
  v-if="$props.description"
51
51
  class="fs-checkbox-description"
52
52
  font="text-overline"
53
+ :lineClamp="2"
53
54
  :style="style"
54
55
  >
55
56
  {{ $props.description }}
@@ -0,0 +1,69 @@
1
+ <template>
2
+ <FSWrapGroup
3
+ v-if="$props.variant === 'wrap'"
4
+ v-bind="$attrs"
5
+ >
6
+ <FSChip
7
+ v-for="(label, index) in $props.labels"
8
+ :key="index"
9
+ :variant="$props.chipVariant"
10
+ :color="$props.color"
11
+ :label="label"
12
+ />
13
+ <slot />
14
+ </FSWrapGroup>
15
+ <FSSlideGroup
16
+ v-if="$props.variant === 'slide'"
17
+ v-bind="$attrs"
18
+ >
19
+ <FSChip
20
+ v-for="(label, index) in $props.labels"
21
+ :key="index"
22
+ :variant="$props.chipVariant"
23
+ :color="$props.color"
24
+ :label="label"
25
+ />
26
+ <slot />
27
+ </FSSlideGroup>
28
+ </template>
29
+
30
+ <script lang="ts">
31
+ import { defineComponent, type PropType } from "vue";
32
+
33
+ import { type ColorBase, ColorEnum } from "@dative-gpi/foundation-shared-components/models";
34
+
35
+ import FSSlideGroup from "./FSSlideGroup.vue";
36
+ import FSWrapGroup from "./FSWrapGroup.vue";
37
+ import FSChip from "./FSChip.vue";
38
+
39
+ export default defineComponent({
40
+ name: "FSChipGroup",
41
+ components: {
42
+ FSSlideGroup,
43
+ FSWrapGroup,
44
+ FSChip
45
+ },
46
+ props: {
47
+ labels: {
48
+ type: Array as PropType<string[]>,
49
+ required: false,
50
+ default: () => []
51
+ },
52
+ variant: {
53
+ type: String as PropType<"wrap" | "slide">,
54
+ required: false,
55
+ default: "wrap"
56
+ },
57
+ chipVariant: {
58
+ type: String as PropType<"standard" | "full" | "borderless">,
59
+ required: false,
60
+ default: "full"
61
+ },
62
+ color: {
63
+ type: String as PropType<ColorBase>,
64
+ required: false,
65
+ default: ColorEnum.Light
66
+ }
67
+ }
68
+ });
69
+ </script>
@@ -50,6 +50,7 @@
50
50
  v-if="$props.description"
51
51
  class="fs-radio-description"
52
52
  font="text-overline"
53
+ :lineClamp="2"
53
54
  :style="style"
54
55
  >
55
56
  {{ $props.description }}
@@ -35,7 +35,7 @@
35
35
  :elevation="0"
36
36
  :tickSize="4"
37
37
  :modelValue="$props.modelValue ?? undefined"
38
- @update:modelValue="(value) => $emit('update:modelValue', value)"
38
+ @update:modelValue="$emit('update:modelValue', $event)"
39
39
  v-bind="$attrs"
40
40
  >
41
41
  <template
@@ -55,6 +55,7 @@
55
55
  v-if="$props.description"
56
56
  class="fs-slider-description"
57
57
  font="text-overline"
58
+ :lineClamp="2"
58
59
  :style="style"
59
60
  >
60
61
  {{ $props.description }}
@@ -1,29 +1,25 @@
1
1
  <template>
2
- <FSRow
3
- width="hug"
4
- align="top-left"
5
- gap="16px"
2
+ <FSCol
6
3
  padding="8px 0px"
7
- :wrap="false"
8
4
  >
9
- <v-switch
10
- v-if="variant == 'left'"
11
- class="fs-switch"
12
- hide-details
13
- inset
14
- :validateOn="validateOn"
15
- :rules="$props.rules"
16
- :ripple="false"
17
- :style="style"
18
- :modelValue="$props.modelValue"
19
- @update:modelValue="onToggle"
20
- v-bind="$attrs"
21
- />
22
- <slot>
23
- <FSCol
24
- width="hug"
25
- v-if="$props.label || $props.description || $slots.description"
26
- >
5
+ <FSRow
6
+ gap="16px"
7
+ :wrap="false"
8
+ >
9
+ <v-switch
10
+ v-if="variant == 'left'"
11
+ class="fs-switch"
12
+ hide-details
13
+ inset
14
+ :validateOn="validateOn"
15
+ :rules="$props.rules"
16
+ :ripple="false"
17
+ :style="style"
18
+ :modelValue="$props.modelValue"
19
+ @update:modelValue="onToggle"
20
+ v-bind="$attrs"
21
+ />
22
+ <FSCol>
27
23
  <FSSpan
28
24
  v-if="$props.label"
29
25
  class="fs-switch-label"
@@ -41,33 +37,31 @@
41
37
  class="fs-switch-description"
42
38
  font="text-overline"
43
39
  :style="style"
40
+ :lineClamp="2"
44
41
  >
45
42
  {{ $props.description }}
46
43
  </FSSpan>
47
44
  </slot>
48
- <slot
49
- name="footer"
50
- />
51
45
  </FSCol>
52
- </slot>
53
- <FSRow
54
- v-if="variant == 'right'"
55
- align="center-right"
56
- >
57
- <v-switch
58
- class="fs-switch"
59
- hide-details
60
- inset
61
- :validateOn="validateOn"
62
- :rules="$props.rules"
63
- :ripple="false"
64
- :style="style"
65
- :modelValue="$props.modelValue"
66
- @update:modelValue="onToggle"
67
- v-bind="$attrs"
68
- />
46
+ <FSRow
47
+ v-if="variant == 'right'"
48
+ align="center-right"
49
+ >
50
+ <v-switch
51
+ class="fs-switch"
52
+ hide-details
53
+ inset
54
+ :validateOn="validateOn"
55
+ :rules="$props.rules"
56
+ :ripple="false"
57
+ :style="style"
58
+ :modelValue="$props.modelValue"
59
+ @update:modelValue="onToggle"
60
+ v-bind="$attrs"
61
+ />
62
+ </FSRow>
69
63
  </FSRow>
70
- </FSRow>
64
+ </FSCol>
71
65
  </template>
72
66
 
73
67
  <script lang="ts">
@@ -54,6 +54,7 @@
54
54
  v-if="$props.description"
55
55
  class="fs-base-field-description"
56
56
  font="text-overline"
57
+ :lineClamp="2"
57
58
  >
58
59
  {{ $props.description }}
59
60
  </FSSpan>
@@ -14,18 +14,20 @@
14
14
  :wrap="false"
15
15
  >
16
16
  <FSSelectField
17
+ :editable="$props.editable"
18
+ :items="actualEntityTypes"
17
19
  :hideHeader="true"
20
+ :clearable="false"
18
21
  :modelValue="$props.entityType"
19
22
  @update:modelValue="$emit('update:entityType', $event)"
20
- :items="actualEntityTypes"
21
- :clearable="false"
22
23
  />
23
24
  <template
24
25
  v-if="itemsCount > 0"
25
26
  >
26
27
  <FSButton
27
- :label="$tr('ui.common.edit', 'Edit')"
28
28
  icon="mdi-pencil"
29
+ :label="$tr('ui.common.edit', 'Edit')"
30
+ :editable="$props.editable"
29
31
  @click="$emit('click:select')"
30
32
  />
31
33
  </template>
@@ -33,8 +35,9 @@
33
35
  v-else
34
36
  >
35
37
  <FSButton
36
- :label="$tr('ui.common.select', 'Select')"
37
38
  icon="mdi-plus-circle-multiple-outline"
39
+ :label="$tr('ui.common.select', 'Select')"
40
+ :editable="$props.editable"
38
41
  @click="$emit('click:select')"
39
42
  />
40
43
  </template>
@@ -215,7 +215,8 @@
215
215
  <FSSpan
216
216
  v-if="!readonly && $props.description"
217
217
  class="fs-rich-text-field-description"
218
- font="text-underline"
218
+ font="text-overline"
219
+ :lineClamp="2"
219
220
  :style="style"
220
221
  >
221
222
  {{ $props.description }}
@@ -32,7 +32,9 @@
32
32
  >
33
33
  {{ $tr('translate-rich-text-field.translate-in', 'Translate in {0}', language.label) }}
34
34
  </FSSpan>
35
- <FSIcon>{{ language.icon }}</FSIcon>
35
+ <FSIcon>
36
+ {{ language.icon }}
37
+ </FSIcon>
36
38
  </FSRow>
37
39
  </template>
38
40
  </FSRichTextField>
@@ -14,7 +14,7 @@
14
14
  v-if="map"
15
15
  >
16
16
  <FSMapTileLayer
17
- :layer="actualLayer"
17
+ :layers="actualLayer"
18
18
  />
19
19
  <FSMapMarker
20
20
  v-if="gpsPosition"
@@ -22,38 +22,7 @@
22
22
  :color="ColorEnum.Primary"
23
23
  :latlng="gpsPosition"
24
24
  />
25
-
26
- <FSMapFeatureGroup
27
- v-if="$props.areas"
28
- :expected-layers="$props.areas.length"
29
- @update:bounds="(bounds) => areaGroupBounds = bounds"
30
- >
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)"
37
- />
38
- </FSMapFeatureGroup>
39
-
40
- <FSMapMarkerClusterGroup
41
- v-if="$props.locations"
42
- :expected-layers="$props.locations.length"
43
- :disableClusteringAtZoom="defaultZoom"
44
- @update:bounds="(bounds) => locationGroupBounds = bounds"
45
- >
46
- <FSMapMarker
47
- v-for="location in $props.locations"
48
- :selected="location.id === $props.selectedLocationId"
49
- :key="location.id"
50
- :label="location.label"
51
- :color="location.color ?? ColorEnum.Primary"
52
- :icon="location.icon ?? 'mdi-map-marker'"
53
- :latlng="{lat: location.address.latitude, lng: location.address.longitude}"
54
- @click="$emit('update:selectedLocationId', location.id)"
55
- />
56
- </FSMapMarkerClusterGroup>
25
+ <slot />
57
26
  </template>
58
27
  </div>
59
28
 
@@ -100,6 +69,7 @@
100
69
  </FSCol>
101
70
 
102
71
  <FSMapOverlay
72
+ v-if="overlaySlots && Object.keys(overlaySlots).length"
103
73
  :mode="$props.overlayMode"
104
74
  @update:mode="$emit('update:overlayMode', $event)"
105
75
  @update:height="(height) => overlayHeight = height"
@@ -125,10 +95,9 @@ import type {} from "leaflet.markercluster";
125
95
  import { map as createMap, control, tileLayer, latLngBounds, latLng, type LatLng, type FitBoundsOptions, type ZoomPanOptions, type LatLngBounds } from "leaflet";
126
96
 
127
97
  import { useTranslations as useTranslationsProvider } from "@dative-gpi/bones-ui/composables";
128
- import { type FSArea } from '@dative-gpi/foundation-shared-domain/models';
129
98
 
130
99
  import { useBreakpoints, useColors, useSlots } from "../../composables";
131
- import { ColorEnum, type FSLocation, type MapLayer } from "../../models";
100
+ import { ColorEnum, MapLayers, MapOverlayPositions, type MapLayer } from "../../models";
132
101
 
133
102
  import FSMapLayerButton from "./FSMapLayerButton.vue";
134
103
  import FSMapOverlay from "./FSMapOverlay.vue";
@@ -138,19 +107,12 @@ import FSCol from "../FSCol.vue";
138
107
 
139
108
  import FSMapMarker from "./FSMapMarker.vue";
140
109
  import FSMapTileLayer from "./FSMapTileLayer.vue";
141
- import FSMapFeatureGroup from "./FSMapFeatureGroup.vue";
142
- import FSMapMarkerClusterGroup from "./FSMapMarkerClusterGroup.vue";
143
- import FSMapPolygon from "./FSMapPolygon.vue";
144
110
 
145
111
  export default defineComponent({
146
112
  name: "FSMap",
147
113
  components: {
148
114
  FSMapMarker,
149
115
  FSMapTileLayer,
150
- FSMapFeatureGroup,
151
- FSMapMarkerClusterGroup,
152
- FSMapPolygon,
153
-
154
116
  FSMapLayerButton,
155
117
  FSMapOverlay,
156
118
  FSButton,
@@ -174,9 +136,9 @@ export default defineComponent({
174
136
  default: false
175
137
  },
176
138
  overlayMode: {
177
- type: String as PropType<'collapse' | 'half' | 'expand'>,
139
+ type: String as PropType<MapOverlayPositions>,
178
140
  required: false,
179
- default: 'collapse'
141
+ default: MapOverlayPositions.Collapse
180
142
  },
181
143
  showMyLocation: {
182
144
  type: Boolean,
@@ -194,39 +156,29 @@ export default defineComponent({
194
156
  default: false
195
157
  },
196
158
  center: {
197
- type: Array as PropType<number[]>,
159
+ type: Array as PropType<number[] | null>,
198
160
  required: false,
199
- default: () => [45.71, 5.07]
200
- },
201
- locations: {
202
- type: Array as PropType<FSLocation[]>,
203
- required: false,
204
- default: () => [],
161
+ default: null
205
162
  },
206
- areas: {
207
- type: Array as PropType<FSArea[]>,
163
+ bounds: {
164
+ type: Object as PropType<LatLngBounds | null>,
208
165
  required: false,
209
- default: () => [],
166
+ default: null
210
167
  },
211
168
  currentLayer: {
212
- type: String as PropType<"map" | "imagery">,
169
+ type: String as PropType<MapLayers>,
213
170
  required: false,
214
- default: "map"
171
+ default: MapLayers.Map
215
172
  },
216
173
  allowedLayers: {
217
- type: Array as PropType<string[]>,
174
+ type: Array as PropType<MapLayers[]>,
218
175
  required: false,
219
- default: () => ["map", "imagery"]
176
+ default: () => [MapLayers.Map, MapLayers.Imagery]
220
177
  },
221
- selectedLocationId: {
222
- type: String as PropType<string | null>,
178
+ dirtyZoom: {
179
+ type: Number,
223
180
  required: false,
224
- default: null
225
- },
226
- selectedAreaId: {
227
- type: String as PropType<string | null>,
228
- required: false,
229
- default: null
181
+ default: 16
230
182
  }
231
183
  },
232
184
  emits: ["update:modelValue", "update:selectedLocationId", "update:selectedAreaId", 'update:overlayMode', 'update:currentLayer', "click:latlng"],
@@ -246,7 +198,7 @@ export default defineComponent({
246
198
 
247
199
  provide('map', map);
248
200
 
249
- const defaultZoom = 16;
201
+ const defaultZoom = ref(props.dirtyZoom);
250
202
  const mapResizeObserver = new ResizeObserver(() => {
251
203
  if(!map.value) {
252
204
  return;
@@ -256,29 +208,53 @@ export default defineComponent({
256
208
 
257
209
  const mapLayers: MapLayer[] = [
258
210
  {
259
- name: "map",
211
+ name: MapLayers.Map,
260
212
  label: $tr("ui.map-layer.map", "Map"),
261
213
  image: new URL("../../assets/images/map/map.png", import.meta.url).href,
262
- layer: tileLayer(`https://{s}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}&key=${import.meta.env.VITE_GOOGLE_MAPS_API_KEY ?? ""}`, {
263
- maxZoom: 22,
264
- subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
265
- attribution: '© Google Map Data'
266
- })
214
+ layers: [
215
+ tileLayer(`https://{s}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}&key=${import.meta.env.VITE_GOOGLE_MAPS_API_KEY ?? ""}`, {
216
+ maxZoom: 22,
217
+ subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
218
+ attribution: '© Google Map Data',
219
+ className: 'fs-map-tile-base-layer'
220
+ })
221
+ ]
267
222
  },
268
223
  {
269
- name: "imagery",
224
+ name: MapLayers.Imagery,
270
225
  label: $tr("ui.map-layer.imagery", "Imagery"),
271
226
  image: new URL("../../assets/images/map/imagery.png", import.meta.url).href,
272
- layer: tileLayer(`https://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}&key=${import.meta.env.VITE_GOOGLE_MAPS_API_KEY ?? ""}`, {
273
- maxZoom: 22,
274
- subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
275
- attribution: '© Google Map Data'
276
- })
227
+ layers: [
228
+ tileLayer(`https://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}&key=${import.meta.env.VITE_GOOGLE_MAPS_API_KEY ?? ""}`, {
229
+ maxZoom: 22,
230
+ subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
231
+ attribution: '© Google Map Data',
232
+ className: 'fs-map-tile-base-layer'
233
+ })
234
+ ]
235
+ },
236
+ {
237
+ name: MapLayers.Snow,
238
+ label: $tr("ui.map-layer.snow", "Snow ski map"),
239
+ image: new URL("../../assets/images/map/snow.png", import.meta.url).href,
240
+ layers: [
241
+ tileLayer(`https://{s}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}&key=${import.meta.env.VITE_GOOGLE_MAPS_API_KEY ?? ""}`, {
242
+ maxZoom: 22,
243
+ subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
244
+ attribution: '© Google Map Data',
245
+ className: 'fs-map-tile-base-layer fs-map-tile-grayscale-layer'
246
+ }),
247
+ tileLayer(`https://tiles.opensnowmap.org/pistes/{z}/{x}/{y}.png`, {
248
+ maxZoom: 18,
249
+ attribution: 'Map data: &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors & ODbL, &copy; <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>',
250
+ className: 'fs-map-tile-base-layer'
251
+ })
252
+ ]
277
253
  }
278
254
  ];
279
255
 
280
256
  const bottomOffset = computed(() => {
281
- if (props.overlayMode !== 'expand' && overlayHeight.value && isExtraSmall.value) {
257
+ if (props.overlayMode !== MapOverlayPositions.Expand && overlayHeight.value && isExtraSmall.value) {
282
258
  return overlayHeight.value;
283
259
  }
284
260
  return 0;
@@ -300,20 +276,7 @@ export default defineComponent({
300
276
  }));
301
277
 
302
278
  const actualLayer = computed(() => {
303
- return mapLayers.find((layer) => layer.name === props.currentLayer)?.layer ?? mapLayers[0].layer;
304
- });
305
-
306
- const bounds = computed<LatLngBounds | null>(() => {
307
- if(!locationGroupBounds.value && !areaGroupBounds.value) {
308
- return null;
309
- }
310
- let bounds = locationGroupBounds.value;
311
- if(bounds && areaGroupBounds.value) {
312
- bounds.extend(areaGroupBounds.value);
313
- } else if(areaGroupBounds.value) {
314
- bounds = areaGroupBounds.value;
315
- }
316
- return bounds as LatLngBounds;
279
+ return mapLayers.find((mapLayer) => mapLayer.name === props.currentLayer)?.layers ?? mapLayers[0].layers;
317
280
  });
318
281
 
319
282
  const overlaySlots = computed(() => {
@@ -332,7 +295,7 @@ export default defineComponent({
332
295
  return map.value.unproject(targetPoint, zoom);
333
296
  }
334
297
 
335
- const flyTo = (lat: number, lng: number, zoom: number = defaultZoom, options?: ZoomPanOptions) => {
298
+ const flyTo = (lat: number, lng: number, zoom: number = defaultZoom.value, options?: ZoomPanOptions) => {
336
299
  if(!map.value) {
337
300
  return;
338
301
  }
@@ -360,7 +323,7 @@ export default defineComponent({
360
323
  if(!map.value) {
361
324
  return;
362
325
  }
363
- map.value.setView(calculateTargetPosition(latLng(lat, lng)), zoom);
326
+ map.value.setView(calculateTargetPosition(latLng(lat, lng), zoom), zoom);
364
327
  }
365
328
 
366
329
  const fitBounds = (bounds: LatLngBounds, options?: FitBoundsOptions) => {
@@ -394,11 +357,12 @@ export default defineComponent({
394
357
  minZoom: 2,
395
358
  maxZoom: 22,
396
359
  maxBounds: latLngBounds(latLng(-90, -180), latLng(90, 180)),
397
- maxBoundsViscosity: 1.0
360
+ maxBoundsViscosity: 1.0,
361
+ zoom: defaultZoom.value,
362
+ center: props.center ? latLng(props.center[0], props.center[1]) : latLng(48.85782, 2.29521)
398
363
  };
399
364
 
400
365
  map.value = markRaw(createMap(leafletContainer.value, mapOptions));
401
- setView(props.center[0], props.center[1], defaultZoom);
402
366
 
403
367
  map.value.on('click', (e: L.LeafletMouseEvent) => {
404
368
  emit('click:latlng', e.latlng);
@@ -428,42 +392,26 @@ export default defineComponent({
428
392
  mapResizeObserver.disconnect();
429
393
  });
430
394
 
431
- watch (() => props.center, (center) => {
432
- if(!map.value) {
433
- return;
434
- }
435
- setView(center[0], center[1], defaultZoom);
436
- });
437
-
438
- watch (() => props.selectedLocationId, (selectedLocationId) => {
439
- if(!map.value) {
440
- return;
441
- }
442
- const selectedLocation = props.locations.find((location) => location.id === selectedLocationId);
443
- if(!selectedLocation) {
395
+ watch ([() => props.center, () => map.value], () => {
396
+ if(!map.value || !props.center) {
444
397
  return;
445
398
  }
446
- flyTo(selectedLocation?.address.latitude, selectedLocation?.address.longitude, defaultZoom, { animate: false });
399
+ setView(props.center[0], props.center[1], defaultZoom.value);
447
400
  }, { immediate: true });
448
401
 
449
- watch(() => props.selectedAreaId, (selectedAreaId) => {
450
- if(!map.value) {
451
- return;
452
- }
453
- const selectedArea = props.areas.find((area) => area.id === selectedAreaId);
454
- if(!selectedArea) {
402
+ watch([() => props.bounds, () => map.value], () => {
403
+ if(!map.value || !props.bounds) {
455
404
  return;
456
405
  }
457
- const bounds = latLngBounds(selectedArea.coordinates.map((coord) => latLng(coord.latitude, coord.longitude)));
458
- fitBounds(bounds);
459
- }, { immediate: true });
406
+ fitBounds(props.bounds, { maxZoom: defaultZoom.value });
407
+ });
460
408
 
461
- watch( () => bounds.value, (bounds) => {
462
- if(!map.value || !bounds) {
463
- return;
409
+ watch(() => props.dirtyZoom, (newZoom) => {
410
+ defaultZoom.value = newZoom;
411
+ if(map.value) {
412
+ map.value.setZoom(newZoom);
464
413
  }
465
- fitBounds(bounds, { maxZoom: defaultZoom });
466
- });
414
+ }, { immediate: true });
467
415
 
468
416
  return {
469
417
  ColorEnum,
@@ -82,20 +82,20 @@ export default {
82
82
  iconSize: [size, size],
83
83
  iconAnchor: [size / 2, size / 2],
84
84
  });
85
- } else if(props.variant === 'location' && props.icon) {
85
+ } else if(props.variant === 'location') {
86
86
  const size = 36;
87
87
  icon = divIcon({
88
- html: locationMarkerHtml(props.icon, getColors(props.color).base, props.label),
88
+ html: locationMarkerHtml(props.icon ?? "mdi-map-marker", getColors(props.color).base, props.label),
89
89
  iconSize: [size, size],
90
- className: props.selected ? 'fs-map-location fs-map-location-selected' : 'fs-map-location',
90
+ className: props.selected ? 'fs-map-marker fs-map-location fs-map-location-selected' : 'fs-map-marker fs-map-location',
91
91
  iconAnchor: [size / 2, size / 2],
92
92
  });
93
93
  } else {
94
- const size = 20;
94
+ const size = 16;
95
95
  icon = divIcon({
96
- html: pinMarkerHtml(getColors(props.color).base),
96
+ html: pinMarkerHtml(getColors(props.color).base, props.label),
97
97
  iconSize: [size, size],
98
- className: props.selected ? 'fs-map-location fs-map-location-selected' : 'fs-map-location',
98
+ className: props.selected ? 'fs-map-marker fs-map-pin fs-map-pin-selected' : 'fs-map-marker fs-map-pin',
99
99
  iconAnchor: [size / 2, size / 2],
100
100
  });
101
101
  }
@@ -45,7 +45,7 @@ export default {
45
45
 
46
46
  return divIcon({
47
47
  html: clusterMarkerHtml(cluster.getChildCount()),
48
- className: 'fs-map-location fs-map-location-full',
48
+ className: 'fs-map-marker fs-map-cluster-marker',
49
49
  iconSize: [size, size],
50
50
  iconAnchor: [size / 2, size / 2],
51
51
  });
@@ -1,16 +1,16 @@
1
1
  <template>
2
2
  <v-overlay
3
3
  v-if="isExtraSmall"
4
- :modelValue="$props.mode === 'expand'"
4
+ :modelValue="$props.mode === MapOverlayPositions.Expand"
5
5
  :contained="true"
6
- @click="$emit('update:mode', 'collapse')"
6
+ @click="$emit('update:mode', MapOverlayPositions.Collapse)"
7
7
  zIndex="0"
8
8
  />
9
9
  <div
10
10
  v-show="isExtraSmall"
11
11
  ref="mobileOverlayWrapper"
12
12
  class="fs-map-overlay-mobile"
13
- :style="{ height: $props.mode === 'expand' ? '90%' : ($props.mode === 'half' ? '60%' : 'auto') }"
13
+ :style="{ height: $props.mode === MapOverlayPositions.Expand ? '90%' : ($props.mode === MapOverlayPositions.Half ? '60%' : 'auto') }"
14
14
  >
15
15
  <FSCard
16
16
  width="100%"
@@ -29,15 +29,15 @@
29
29
  @mousedown="onClick"
30
30
  >
31
31
  <FSIcon>
32
- {{ $props.mode === 'expand' ? 'mdi-chevron-down' : 'mdi-chevron-up' }}
32
+ {{ $props.mode === MapOverlayPositions.Expand ? 'mdi-chevron-down' : 'mdi-chevron-up' }}
33
33
  </FSIcon>
34
34
  </FSRow>
35
35
  <slot
36
- v-if="$props.mode === 'collapse'"
36
+ v-if="$props.mode === MapOverlayPositions.Collapse"
37
37
  name="collapsed"
38
38
  />
39
39
  <FSCol
40
- v-if="$props.mode !== 'collapse'"
40
+ v-if="$props.mode !== MapOverlayPositions.Collapse"
41
41
  height="fill"
42
42
  style="min-height: 0;"
43
43
  >
@@ -70,6 +70,8 @@ import { defineComponent, type PropType, onUnmounted, onMounted, ref } from "vue
70
70
 
71
71
  import { useBreakpoints } from "../../composables";
72
72
 
73
+ import { MapOverlayPositions } from '@dative-gpi/foundation-shared-components/models';
74
+
73
75
  import FSCard from "../FSCard.vue";
74
76
  import FSIcon from "../FSIcon.vue";
75
77
  import FSCol from "../FSCol.vue";
@@ -85,9 +87,9 @@ export default defineComponent({
85
87
  },
86
88
  props: {
87
89
  mode: {
88
- type: String as PropType<"collapse" | "half" | "expand">,
90
+ type: String as PropType<MapOverlayPositions>,
89
91
  required: false,
90
- default: "collapse"
92
+ default: MapOverlayPositions.Collapse
91
93
  }
92
94
  },
93
95
  emits: ["update:mode", "update:height", "update:width"],
@@ -101,11 +103,11 @@ export default defineComponent({
101
103
  const desktopResizeObserver = ref<ResizeObserver | null>(null);
102
104
 
103
105
  const onClick = (): void => {
104
- if (props.mode === "expand") {
105
- emit("update:mode", "collapse");
106
+ if (props.mode === MapOverlayPositions.Expand) {
107
+ emit("update:mode", MapOverlayPositions.Collapse);
106
108
  return;
107
109
  }
108
- emit("update:mode", "expand");
110
+ emit("update:mode", MapOverlayPositions.Expand);
109
111
  }
110
112
 
111
113
  onMounted(() => {
@@ -144,6 +146,7 @@ export default defineComponent({
144
146
  return {
145
147
  mobileOverlayWrapper,
146
148
  isTouchScreenEnabled,
149
+ MapOverlayPositions,
147
150
  desktopOverlay,
148
151
  isExtraSmall,
149
152
  onClick
@@ -12,15 +12,15 @@ import { MAP } from './keys';
12
12
  export default {
13
13
  name: 'FSMapTileLayer',
14
14
  props: {
15
- layer: {
16
- type: Object as PropType<Layer>,
15
+ layers: {
16
+ type: Object as PropType<Layer[]>,
17
17
  required: false
18
18
  }
19
19
  },
20
20
  setup(props) {
21
21
  const map = inject<Ref<Map | null>>(MAP);
22
22
 
23
- const lastLayer = props.layer;
23
+ let lastLayers = props.layers;
24
24
 
25
25
  if(!map) {
26
26
  throw new Error('FSMapTileLayer must be used inside a FSMap component');
@@ -31,20 +31,26 @@ export default {
31
31
  }
32
32
 
33
33
  const updateLayer = () => {
34
- if (!props.layer || !map.value) {
34
+ if (!props.layers || !map.value) {
35
35
  return;
36
36
  }
37
37
 
38
- if(lastLayer) {
39
- map.value.removeLayer(lastLayer);
38
+ if(lastLayers) {
39
+ lastLayers.forEach(layer => {
40
+ layer.removeFrom(map.value!);
41
+ });
40
42
  }
41
43
 
42
- props.layer.addTo(map.value);
44
+ lastLayers = [];
45
+
46
+ props.layers.forEach(layer => {
47
+ lastLayers?.push(layer.addTo(map.value!));
48
+ });
43
49
  };
44
50
 
45
51
  onMounted(updateLayer);
46
52
 
47
- watch(() => props.layer, updateLayer);
53
+ watch(() => props.layers, updateLayer);
48
54
  }
49
55
  };
50
56
  </script>
@@ -0,0 +1,68 @@
1
+ <template>
2
+ <FSSelectField
3
+ :items="items"
4
+ :modelValue="$props.modelValue"
5
+ @update:modelValue="$emit('update:modelValue', $event)"
6
+ v-bind="$attrs"
7
+ >
8
+ <template
9
+ #item-prepend="{ item }"
10
+ >
11
+ <FSIcon
12
+ :icon="item.icon"
13
+ />
14
+ </template>
15
+ </FSSelectField>
16
+ </template>
17
+
18
+ <script lang="ts">
19
+ import type { PropType} from "vue";
20
+ import { defineComponent } from "vue";
21
+
22
+ import { useTranslations as useTranslationsProvider } from "@dative-gpi/bones-ui/composables";
23
+
24
+ import { MapLayers } from '@dative-gpi/foundation-shared-components/models';
25
+
26
+ import FSSelectField from "../fields/FSSelectField.vue";
27
+ import FSIcon from '@dative-gpi/foundation-shared-components/components/FSIcon.vue';
28
+
29
+ export default defineComponent({
30
+ name: "FSSelectMapLayer",
31
+ components: {
32
+ FSIcon,
33
+ FSSelectField
34
+ },
35
+ props: {
36
+ modelValue: {
37
+ type: [String, Array] as PropType<MapLayers | MapLayers[]>,
38
+ required: false
39
+ }
40
+ },
41
+ emits: ["update:modelValue"],
42
+ setup() {
43
+ const { $tr } = useTranslationsProvider();
44
+
45
+ const items = [
46
+ {
47
+ id: MapLayers.Map,
48
+ icon: 'mdi-map',
49
+ label: $tr("ui.map-layer.map", "Map")
50
+ },
51
+ {
52
+ id: MapLayers.Imagery,
53
+ icon: 'mdi-satellite',
54
+ label: $tr("ui.map-layer.imagery", "Imagery")
55
+ },
56
+ {
57
+ id: MapLayers.Snow,
58
+ icon: 'mdi-snowflake',
59
+ label: $tr("ui.map-layer.snow", "Snow ski map")
60
+ }
61
+ ]
62
+
63
+ return {
64
+ items
65
+ };
66
+ }
67
+ });
68
+ </script>
@@ -4,48 +4,52 @@
4
4
  :height="$props.height"
5
5
  :width="$props.width"
6
6
  >
7
- <FSClickable
8
- v-if="$props.singleSelect"
9
- padding="12px"
10
- :variant="variant"
11
- :color="color"
12
- :style="style"
13
- width="100%"
14
- height="100%"
15
- @click="() => $emit('update:modelValue', !$props.modelValue)"
16
- v-bind="$attrs"
17
- >
18
- <slot />
19
- </FSClickable>
20
- <FSClickable
21
- v-else-if="$props.href || $props.to || $attrs.onClick"
22
- variant="background"
23
- class="fs-tile"
24
- padding="12px"
25
- :color="ColorEnum.Background"
26
- :href="$props.href"
27
- width="100%"
28
- height="100%"
29
- :to="$props.to"
30
- :style="style"
31
- v-bind="$attrs"
7
+ <template
8
+ v-if="$props.editable"
32
9
  >
33
- <slot />
34
- <FSCard
35
- v-if="$props.editable"
36
- class="fs-tile-checkbox"
10
+ <FSClickable
11
+ v-if="$props.singleSelect"
12
+ padding="12px"
13
+ :variant="variant"
14
+ :color="color"
15
+ :style="style"
16
+ width="100%"
17
+ height="100%"
18
+ @click="() => $emit('update:modelValue', !$props.modelValue)"
19
+ v-bind="$attrs"
20
+ >
21
+ <slot />
22
+ </FSClickable>
23
+ <FSClickable
24
+ v-else-if="$props.href || $props.to || $attrs.onClick"
37
25
  variant="background"
38
- :height="['40px', '32px']"
39
- :width="['40px', '32px']"
40
- :border="false"
26
+ class="fs-tile"
27
+ padding="12px"
28
+ :color="ColorEnum.Background"
29
+ :href="$props.href"
30
+ width="100%"
31
+ height="100%"
32
+ :to="$props.to"
33
+ :style="style"
41
34
  v-bind="$attrs"
42
35
  >
43
- <FSCheckbox
44
- :modelValue="$props.modelValue"
45
- @update:modelValue="() => $emit('update:modelValue', !$props.modelValue)"
46
- />
47
- </FSCard>
48
- </FSClickable>
36
+ <slot />
37
+ <FSCard
38
+ v-if="$props.editable"
39
+ class="fs-tile-checkbox"
40
+ variant="background"
41
+ :height="['40px', '32px']"
42
+ :width="['40px', '32px']"
43
+ :border="false"
44
+ v-bind="$attrs"
45
+ >
46
+ <FSCheckbox
47
+ :modelValue="$props.modelValue"
48
+ @update:modelValue="() => $emit('update:modelValue', !$props.modelValue)"
49
+ />
50
+ </FSCard>
51
+ </FSClickable>
52
+ </template>
49
53
  <FSCard
50
54
  v-else
51
55
  variant="background"
package/models/map.ts CHANGED
@@ -1,18 +1,20 @@
1
1
  import { type Layer } from "leaflet";
2
2
 
3
- import { type Address } from "@dative-gpi/foundation-shared-domain/models";
4
-
5
3
  export interface MapLayer {
6
- name : string;
4
+ name : MapLayers;
7
5
  label: string;
8
6
  image: string;
9
- layer: Layer;
7
+ layers: Layer[];
10
8
  }
11
9
 
12
- export interface FSLocation {
13
- id: string;
14
- label: string;
15
- icon: string | null;
16
- address: Address;
17
- color: string | null;
10
+ export enum MapLayers {
11
+ Map = "map",
12
+ Imagery = "imagery",
13
+ Snow= "snow"
14
+ }
15
+
16
+ export enum MapOverlayPositions {
17
+ Expand = "expand",
18
+ Half = "half",
19
+ Collapse = "collapse",
18
20
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dative-gpi/foundation-shared-components",
3
3
  "sideEffects": false,
4
- "version": "1.0.137",
4
+ "version": "1.0.138",
5
5
  "description": "",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -10,8 +10,8 @@
10
10
  "author": "",
11
11
  "license": "ISC",
12
12
  "dependencies": {
13
- "@dative-gpi/foundation-shared-domain": "1.0.137",
14
- "@dative-gpi/foundation-shared-services": "1.0.137"
13
+ "@dative-gpi/foundation-shared-domain": "1.0.138",
14
+ "@dative-gpi/foundation-shared-services": "1.0.138"
15
15
  },
16
16
  "peerDependencies": {
17
17
  "@dative-gpi/bones-ui": "^1.0.0",
@@ -35,5 +35,5 @@
35
35
  "sass": "1.71.1",
36
36
  "sass-loader": "13.3.2"
37
37
  },
38
- "gitHead": "676162c4362dd556d5ef714f7b17a047ad7f2bad"
38
+ "gitHead": "ec1370eef99afdec2bd4425c7010c1012c23563e"
39
39
  }
@@ -6,7 +6,15 @@
6
6
  .fs-leaflet-container {
7
7
  width: 100%;
8
8
  height: 100%;
9
- filter: grayscale(var(--fs-map-container-grayscale));
9
+ z-index: 0;
10
+
11
+ .fs-map-tile-base-layer {
12
+ filter: grayscale(var(--fs-map-container-grayscale));
13
+ }
14
+
15
+ .fs-map-tile-grayscale-layer {
16
+ filter: grayscale(100%);
17
+ }
10
18
  }
11
19
 
12
20
  .fs-map-overlay-mobile {
@@ -77,17 +85,10 @@
77
85
  }
78
86
  }
79
87
 
80
- .fs-map-point-pin {
81
- background-color: var(--fs-map-point-pin-color);
82
- border-radius: 100%;
83
- }
84
-
85
- .fs-map-location > div {
88
+ .fs-map-marker > div {
86
89
  display: flex;
87
90
  height: 100%;
88
- color: var(--fs-map-location-pin-color);
89
91
  border-radius: 50%;
90
- background-color: white;
91
92
  filter: drop-shadow(0px 2px 4px rgba(0, 0, 0, 0.4));
92
93
  align-items: center;
93
94
  justify-content: center;
@@ -103,7 +104,7 @@
103
104
  }
104
105
  }
105
106
 
106
- .fs-map-location-full > div {
107
+ .fs-map-cluster-marker > div {
107
108
  background-color: var(--fs-map-location-pin-color);
108
109
  color: white;
109
110
  }
@@ -126,6 +127,41 @@
126
127
  }
127
128
  }
128
129
 
130
+ .fs-map-location > div {
131
+ color: var(--fs-map-location-pin-color);
132
+ background-color: white;
133
+ }
134
+
135
+ .fs-map-pin > div {
136
+ background-color: var(--fs-map-point-pin-color);
137
+ position: relative;
138
+
139
+ transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1);
140
+ }
141
+
142
+ .fs-map-pin > div::before {
143
+ content: "";
144
+ position: absolute;
145
+ top: -4px;
146
+ left: -4px;
147
+ width: calc(100% + 8px);
148
+ height: calc(100% + 8px);
149
+ border-radius: 50%;
150
+ border: 2px solid var(--fs-map-point-pin-color);
151
+ opacity: 0.4;
152
+
153
+ @include clickscreen {
154
+ &:hover {
155
+ opacity: 1;
156
+ }
157
+ }
158
+ }
159
+
160
+ .fs-map-pin-selected > div {
161
+ transform: scale(1.35);
162
+ }
163
+
164
+
129
165
  .fs-map-site {
130
166
  opacity: 0.6;
131
167
  transition: opacity 0.28s cubic-bezier(0.4, 0, 0.2, 1);
package/tools/index.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from "./alertsTools";
2
2
  export * from "./chartsTools";
3
+ export * from "./reportsTools";
3
4
  export * from "./timeRangeTools";
@@ -0,0 +1,38 @@
1
+ import { useTranslations as useTranslationsProvider } from "@dative-gpi/bones-ui/composables";
2
+ import { ColorEnum } from "../models";
3
+ import { JobState } from "@dative-gpi/foundation-shared-domain/enums";
4
+
5
+ const { $tr } = useTranslationsProvider();
6
+
7
+ export const getColorByState = (state: number | JobState | undefined) => {
8
+ switch (state) {
9
+ case JobState.Succeeded:
10
+ return ColorEnum.Success;
11
+ case JobState.Failed:
12
+ return ColorEnum.Error;
13
+ default:
14
+ return ColorEnum.Primary;
15
+ }
16
+ };
17
+
18
+ export const getIconByState = (state: number | JobState | undefined) => {
19
+ switch (state) {
20
+ case JobState.Succeeded:
21
+ return 'mdi-check-circle-outline';
22
+ case JobState.Failed:
23
+ return 'mdi-alert-circle-outline';
24
+ default:
25
+ return 'mdi-alert-circle-outline';
26
+ }
27
+ };
28
+
29
+ export const getLabelByState = (state: number | JobState | undefined) => {
30
+ switch (state) {
31
+ case JobState.Succeeded:
32
+ return $tr('ui.common.success', 'Success');
33
+ case JobState.Failed:
34
+ return $tr('ui.common.error', 'Error');
35
+ default:
36
+ return $tr('ui.common.executed', 'Executed');
37
+ }
38
+ };