@fmidev/smartmet-alert-client 4.4.19 → 4.7.0-alpha.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.
Files changed (105) hide show
  1. package/.eslintignore +2 -14
  2. package/.github/workflows/test.yaml +26 -0
  3. package/.nvmrc +1 -0
  4. package/dist/index.html +5 -0
  5. package/dist/index.js +105 -135
  6. package/dist/index.mjs +112 -135
  7. package/dist/locale-en-DCEKDw5G.js +8 -0
  8. package/dist/locale-fi-DPiOM1rB.js +8 -0
  9. package/dist/locale-sv-B0FlbgEF.js +8 -0
  10. package/dist/vendor-Cfkkvdz7.js +21 -0
  11. package/dist/vue/index.mjs +15245 -0
  12. package/dist/vue/style.css +1 -0
  13. package/dist/xml-parser-BiNO9kc-.js +13 -0
  14. package/package.json +60 -24
  15. package/src/AlertClientVue.vue +170 -0
  16. package/src/App.vue +55 -205
  17. package/src/assets/img/ui/arrow-down.svg +4 -11
  18. package/src/assets/img/ui/arrow-up.svg +4 -11
  19. package/src/assets/img/ui/clear.svg +7 -21
  20. package/src/assets/img/ui/close.svg +4 -15
  21. package/src/assets/img/ui/toggle-selected.svg +5 -6
  22. package/src/assets/img/ui/toggle-unselected.svg +5 -6
  23. package/src/assets/img/warning/cold-weather.svg +3 -6
  24. package/src/assets/img/warning/flood-level-3.svg +4 -7
  25. package/src/assets/img/warning/forest-fire-weather.svg +2 -6
  26. package/src/assets/img/warning/grass-fire-weather.svg +2 -6
  27. package/src/assets/img/warning/hot-weather.svg +3 -6
  28. package/src/assets/img/warning/pedestrian-safety.svg +3 -7
  29. package/src/assets/img/warning/rain.svg +2 -7
  30. package/src/assets/img/warning/sea-icing.svg +2 -6
  31. package/src/assets/img/warning/sea-thunder-storm.svg +2 -5
  32. package/src/assets/img/warning/sea-water-height-high-water.svg +3 -8
  33. package/src/assets/img/warning/sea-water-height-shallow-water.svg +3 -7
  34. package/src/assets/img/warning/sea-wave-height.svg +4 -7
  35. package/src/assets/img/warning/sea-wind-legend.svg +2 -5
  36. package/src/assets/img/warning/sea-wind.svg +2 -5
  37. package/src/assets/img/warning/several.svg +2 -5
  38. package/src/assets/img/warning/thunder-storm.svg +2 -5
  39. package/src/assets/img/warning/traffic-weather.svg +2 -6
  40. package/src/assets/img/warning/uv-note.svg +2 -6
  41. package/src/assets/img/warning/wind.svg +2 -5
  42. package/src/components/AlertClient.vue +41 -19
  43. package/src/components/CollapsiblePanel.vue +284 -0
  44. package/src/components/DayLarge.vue +12 -7
  45. package/src/components/DaySmall.vue +16 -6
  46. package/src/components/Days.vue +76 -51
  47. package/src/components/DescriptionWarning.vue +15 -8
  48. package/src/components/GrayScaleToggle.vue +11 -6
  49. package/src/components/Legend.vue +36 -248
  50. package/src/components/MapLarge.vue +41 -42
  51. package/src/components/MapSmall.vue +44 -28
  52. package/src/components/PopupRow.vue +6 -3
  53. package/src/components/Region.vue +30 -15
  54. package/src/components/RegionWarning.vue +6 -5
  55. package/src/components/Regions.vue +50 -19
  56. package/src/components/Warning.vue +18 -10
  57. package/src/components/Warnings.vue +36 -21
  58. package/src/main.js +1 -0
  59. package/src/mixins/alertClientCore.js +210 -0
  60. package/src/mixins/config.js +262 -256
  61. package/src/mixins/utils.js +40 -26
  62. package/src/plugins/index.js +1 -1
  63. package/src/scss/_utilities.scss +193 -0
  64. package/src/scss/constants.scss +2 -1
  65. package/src/scss/warningImages.scss +8 -3
  66. package/src/vue.js +41 -0
  67. package/svgo.config.js +45 -0
  68. package/tests/README.md +430 -0
  69. package/tests/fixtures/mockWarningData.js +135 -0
  70. package/tests/integration/warning-flow.spec.js +452 -0
  71. package/tests/setup.js +41 -0
  72. package/tests/unit/components/AlertClient.spec.js +734 -0
  73. package/tests/unit/components/DayLarge.spec.js +281 -0
  74. package/tests/unit/components/DaySmall.spec.js +278 -0
  75. package/tests/unit/components/Days.spec.js +565 -0
  76. package/tests/unit/components/DescriptionWarning.spec.js +432 -0
  77. package/tests/unit/components/GrayScaleToggle.spec.js +311 -0
  78. package/tests/unit/components/Legend.spec.js +223 -0
  79. package/tests/unit/components/MapLarge.spec.js +276 -0
  80. package/tests/unit/components/MapSmall.spec.js +226 -0
  81. package/tests/unit/components/PopupRow.spec.js +261 -0
  82. package/tests/unit/components/Region.spec.js +430 -0
  83. package/tests/unit/components/RegionWarning.snapshot.spec.js +73 -0
  84. package/tests/unit/components/RegionWarning.spec.js +408 -0
  85. package/tests/unit/components/Regions.spec.js +335 -0
  86. package/tests/unit/components/Warning.snapshot.spec.js +107 -0
  87. package/tests/unit/components/Warning.spec.js +472 -0
  88. package/tests/unit/components/Warnings.spec.js +329 -0
  89. package/tests/unit/components/__snapshots__/RegionWarning.snapshot.spec.js.snap +21 -0
  90. package/tests/unit/components/__snapshots__/Warning.snapshot.spec.js.snap +199 -0
  91. package/tests/unit/mixins/config.spec.js +269 -0
  92. package/tests/unit/mixins/i18n.spec.js +115 -0
  93. package/tests/unit/mixins/keycodes.spec.js +37 -0
  94. package/tests/unit/mixins/utils.spec.js +624 -0
  95. package/vite.config.js +96 -26
  96. package/vitest.config.js +40 -0
  97. package/dist/index.mjs.map +0 -1
  98. package/dist/index.relative.html +0 -19
  99. package/dist/index.start.html +0 -20
  100. package/playwright.config.ts +0 -18
  101. package/public/index.relative.html +0 -19
  102. package/public/index.start.html +0 -20
  103. package/src/mixins/panzoom.js +0 -900
  104. package/test/snapshot.test.ts +0 -126
  105. package/vitest.config.ts +0 -6
@@ -1,11 +1,11 @@
1
1
  <template>
2
2
  <h3>
3
3
  <button
4
+ :id="`accordion-${code}`"
4
5
  type="button"
5
6
  :aria-expanded="open"
6
7
  :class="['accordion-trigger', 'focus-ring', open ? '' : 'collapsed']"
7
8
  :aria-controls="`accordion-section-${code}`"
8
- :id="`accordion-${code}`"
9
9
  :aria-label="ariaButton"
10
10
  @click="onRegionToggle">
11
11
  <div class="region-header">
@@ -21,9 +21,7 @@
21
21
  </RegionWarning>
22
22
  </div>
23
23
  </div>
24
- <div
25
- block
26
- :class="['current-warning-toggle', open ? '' : 'collapsed']" />
24
+ <div block :class="['current-warning-toggle', open ? '' : 'collapsed']" />
27
25
  </button>
28
26
  </h3>
29
27
  <div
@@ -32,17 +30,15 @@
32
30
  :aria-labelledby="`accordion-${code}`"
33
31
  :aria-expanded="open"
34
32
  class="accordion-panel"
35
- :hidden="open ? null : ''"
36
- >
33
+ :hidden="open ? null : ''">
37
34
  <div class="current-description">
38
35
  <div class="current-description-table">
39
36
  <DescriptionWarning
40
37
  v-for="warning in reducedWarnings"
41
- :key="warning.identification"
42
- :input="warning"
43
- :theme="theme"
44
- :language="language"
45
- />
38
+ :key="warning.identification"
39
+ :input="warning"
40
+ :theme="theme"
41
+ :language="language" />
46
42
  </div>
47
43
  </div>
48
44
  </div>
@@ -192,6 +188,7 @@ export default {
192
188
 
193
189
  button {
194
190
  border: none;
191
+ cursor: pointer;
195
192
  &:focus:not(:focus-visible) {
196
193
  box-shadow: none;
197
194
  }
@@ -366,7 +363,10 @@ h3 {
366
363
  border-radius: 0;
367
364
  }
368
365
 
369
- .accordion > div:first-child:last-child .accordion-trigger.collapsed > .region-header {
366
+ .accordion
367
+ > div:first-child:last-child
368
+ .accordion-trigger.collapsed
369
+ > .region-header {
370
370
  border-radius: 0;
371
371
  }
372
372
 
@@ -398,15 +398,30 @@ h3 {
398
398
  border-radius: 0;
399
399
  }
400
400
 
401
- .accordion > div:last-child > div > h3 > button > div.current-warning-toggle.collapsed {
401
+ .accordion
402
+ > div:last-child
403
+ > div
404
+ > h3
405
+ > button
406
+ > div.current-warning-toggle.collapsed {
402
407
  border-radius: 0;
403
408
  }
404
409
 
405
- .accordion > div:first-child:last-child > div > h3 > button > div.current-warning-toggle {
410
+ .accordion
411
+ > div:first-child:last-child
412
+ > div
413
+ > h3
414
+ > button
415
+ > div.current-warning-toggle {
406
416
  border-radius: 0;
407
417
  }
408
418
 
409
- .accordion > div:first-child:last-child > div > h3 > button > div.current-warning-toggle.collapsed {
419
+ .accordion
420
+ > div:first-child:last-child
421
+ > div
422
+ > h3
423
+ > button
424
+ > div.current-warning-toggle.collapsed {
410
425
  border-radius: 0;
411
426
  }
412
427
 
@@ -18,8 +18,7 @@
18
18
  'symbol-text',
19
19
  `transform-rotate-${invertedRotation}`,
20
20
  ]"
21
- >{{ input.text }}</span
22
- >
21
+ >{{ input.text }}</span>
23
22
  </div>
24
23
  </template>
25
24
 
@@ -45,14 +44,16 @@ export default {
45
44
  return this.t(`warningLevel${this.input.severity}`)
46
45
  },
47
46
  warningTypeText() {
48
- return this.t(this.input.type).toLowerCase();
47
+ return this.t(this.input.type).toLowerCase()
49
48
  },
50
49
  warningDetails() {
51
50
  if (this.input.text == null || this.input.direction == null) {
52
51
  return ''
53
52
  }
54
- return ` (${this.input.text} m/s ${this.t("fromDirection")} ${this.input.direction + 180}°)`
55
- }
53
+ return ` (${this.input.text} m/s ${this.t('fromDirection')} ${
54
+ this.input.direction + 180
55
+ }°)`
56
+ },
56
57
  },
57
58
  }
58
59
  </script>
@@ -1,17 +1,33 @@
1
1
  <template>
2
- <div id="region-warnings" class="row">
3
- <div v-if="anyLandWarnings" class="region-type-container">
4
- <h3 id="header-land" class="header-region">{{ landText }}</h3>
2
+ <div
3
+ id="region-warnings"
4
+ class="row"
5
+ >
6
+ <div
7
+ v-if="anyLandWarnings"
8
+ class="region-type-container"
9
+ >
10
+ <h3
11
+ id="header-land"
12
+ class="header-region"
13
+ >
14
+ {{ landText }}
15
+ </h3>
5
16
  <a
6
17
  id="fmi-warnings-region-content"
7
18
  :href="fromLandToNextContentHref"
8
19
  tabindex="0"
9
20
  class="fmi-warnings-to-next-content visually-hidden-focusable focus-ring"
10
21
  @click="fromLandToNextContentClicked"
11
- >{{ fromLandToNextContentText }}</a
22
+ >{{ fromLandToNextContentText }}</a>
23
+ <div
24
+ id="accordion-group-land"
25
+ class="accordion"
12
26
  >
13
- <div id="accordion-group-land" class="accordion">
14
- <div v-for="region in regions.land" :key="region.key">
27
+ <div
28
+ v-for="region in regions.land"
29
+ :key="region.key"
30
+ >
15
31
  <Region
16
32
  v-if="region.warnings.length"
17
33
  type="land"
@@ -20,23 +36,37 @@
20
36
  :input="region.warnings"
21
37
  :warnings="warnings"
22
38
  :theme="theme"
23
- :language="language" />
39
+ :language="language"
40
+ />
24
41
  </div>
25
42
  </div>
26
43
  </div>
27
44
 
28
- <div v-if="anySeaWarnings" class="region-type-container">
29
- <h3 id="header-sea" class="header-region">{{ seaText }}</h3>
45
+ <div
46
+ v-if="anySeaWarnings"
47
+ class="region-type-container"
48
+ >
49
+ <h3
50
+ id="header-sea"
51
+ class="header-region"
52
+ >
53
+ {{ seaText }}
54
+ </h3>
30
55
  <a
31
56
  :id="fromSeaToNextContentId"
32
57
  href="#fmi-warnings-end-of-regions"
33
58
  tabindex="0"
34
59
  class="fmi-warnings-to-next-content visually-hidden-focusable focus-ring"
35
60
  @click="fromSeaToNextContentClicked"
36
- >{{ fromSeaToNextContentText }}</a
61
+ >{{ fromSeaToNextContentText }}</a>
62
+ <div
63
+ id="accordion-group-sea"
64
+ class="accordion"
37
65
  >
38
- <div id="accordion-group-sea" class="accordion">
39
- <div v-for="region in regions.sea" :key="region.key">
66
+ <div
67
+ v-for="region in regions.sea"
68
+ :key="region.key"
69
+ >
40
70
  <Region
41
71
  v-if="region.warnings.length"
42
72
  type="sea"
@@ -45,7 +75,8 @@
45
75
  :input="region.warnings"
46
76
  :warnings="warnings"
47
77
  :theme="theme"
48
- :language="language" />
78
+ :language="language"
79
+ />
49
80
  </div>
50
81
  </div>
51
82
  </div>
@@ -83,14 +114,14 @@ export default {
83
114
  return this.t('regionSea')
84
115
  },
85
116
  fromLandToNextContentText() {
86
- return `${this.t('warningsInAreasStart')} ${
87
- this.t(`in${this.regions.land.length}Areas`)
88
- }. ${this.t('toNextContent')}`
117
+ return `${this.t('warningsInAreasStart')} ${this.t(
118
+ `in${this.regions.land.length}Areas`
119
+ )}. ${this.t('toNextContent')}`
89
120
  },
90
121
  fromSeaToNextContentText() {
91
- return `${this.t('warningsInAreasStart')} ${
92
- this.t(`in${this.regions.sea.length}Areas`)
93
- }. ${this.t('toNextContent')}`
122
+ return `${this.t('warningsInAreasStart')} ${this.t(
123
+ `in${this.regions.sea.length}Areas`
124
+ )}. ${this.t('toNextContent')}`
94
125
  },
95
126
  fromLandToNextContentHref() {
96
127
  return this.anySeaWarnings
@@ -1,15 +1,21 @@
1
1
  <template>
2
- <div class="symbol-list-table" :class="theme">
2
+ <div
3
+ class="symbol-list-table"
4
+ :class="theme"
5
+ >
3
6
  <div class="symbol-list-cell symbol-list-cell-image">
4
7
  <div
5
8
  :class="`level-${severity} ${typeClass} symbol-list-image-column symbol-list-image warning-image`"
6
- :aria-label="`${warningLevelText} ${title.toLowerCase()}`">
7
- </div>
9
+ :aria-label="`${warningLevelText} ${title.toLowerCase()}`"
10
+ ></div>
8
11
  </div>
9
12
  <div class="symbol-list-cell symbol-list-cell-text">
10
13
  <div class="symbol-list-text-select">
11
14
  <!-- eslint-disable-next-line vue/no-v-html -->
12
- <div class="item-text symbol-list-text" v-html="title"></div>
15
+ <div
16
+ class="item-text symbol-list-text"
17
+ v-html="title"
18
+ ></div>
13
19
  <div class="symbol-list-select-container d-none d-md-table-cell">
14
20
  <div
15
21
  :id="id"
@@ -27,11 +33,12 @@
27
33
  @mousedown="preventEvents"
28
34
  @click="toggle"
29
35
  @keydown.enter="toggle"
30
- @keydown.space="toggle">
31
- <span>
32
- {{ toggleText }}
33
- </span>
34
- </div>
36
+ @keydown.space="toggle"
37
+ >
38
+ <span>
39
+ {{ toggleText }}
40
+ </span>
41
+ </div>
35
42
  </div>
36
43
  </div>
37
44
  <hr />
@@ -204,6 +211,7 @@ div.symbol-list-text {
204
211
  justify-content: center;
205
212
  width: 100%;
206
213
  height: $symbol-list-select-height;
214
+ cursor: pointer;
207
215
  margin: 0;
208
216
  background-repeat: no-repeat;
209
217
  background-position: center;
@@ -222,7 +230,7 @@ div.symbol-list-text {
222
230
  }
223
231
  }
224
232
  span {
225
- font-family: "Noto Sans", sans-serif;
233
+ font-family: 'Noto Sans', sans-serif;
226
234
  font-size: $font-size;
227
235
  forced-color-adjust: none;
228
236
  }
@@ -1,18 +1,26 @@
1
1
  <template>
2
- <div id="fmi-warnings-view" :class="theme">
2
+ <div
3
+ id="fmi-warnings-view"
4
+ :class="theme"
5
+ >
3
6
  <div
4
7
  v-if="input.length > 0"
5
- :class="['row', 'symbol-list-main-row', 'show-text-row']">
8
+ :class="['row', 'symbol-list-main-row', 'show-text-row']"
9
+ >
6
10
  <button
7
11
  tabindex="0"
8
12
  type="button"
9
13
  class="bold-text show-text d-none focus-ring"
10
14
  :class="{ 'd-sm-block': hiddenWarnings }"
11
- @click="showAll">
15
+ @click="showAll"
16
+ >
12
17
  {{ showWarningsText }}
13
18
  </button>
14
19
  </div>
15
- <div v-if="input.length > 0" class="row symbol-list-main-row">
20
+ <div
21
+ v-if="input.length > 0"
22
+ class="row symbol-list-main-row"
23
+ >
16
24
  <hr class="symbol-block-separator" />
17
25
  </div>
18
26
  <div id="fmi-warnings-list">
@@ -23,25 +31,28 @@
23
31
  :hideable="warnings.length > 1"
24
32
  :theme="theme"
25
33
  :language="language"
26
- @warningToggled="onWarningToggled" />
34
+ @warning-toggled="onWarningToggled"
35
+ />
27
36
  </div>
28
37
  <div class="row symbol-list-main-row">
29
38
  <hr
30
39
  class="symbol-block-separator legend-separator"
31
- :class="noWarnings ? 'no-warnings' : ''" />
40
+ :class="noWarnings ? 'no-warnings' : ''"
41
+ />
32
42
  </div>
33
43
  <div class="row symbol-list-main-row">
34
44
  <div class="symbol-list-table">
35
45
  <div class="symbol-list-cell symbol-list-cell-image">
36
46
  <div
37
47
  class="gray several symbol-list-image-column symbol-list-image warning-image"
38
- aria-labelledby="symbol-list-several-warnings-text">
39
- </div>
48
+ aria-labelledby="symbol-list-several-warnings-text"
49
+ ></div>
40
50
  </div>
41
51
  <div class="symbol-list-cell symbol-list-cell-text">
42
52
  <div
43
53
  id="symbol-list-several-warnings-text"
44
- class="item-text symbol-list-text">
54
+ class="item-text symbol-list-text"
55
+ >
45
56
  {{ severalWarningsText }}
46
57
  </div>
47
58
  </div>
@@ -52,13 +63,14 @@
52
63
  <div class="symbol-list-cell symbol-list-cell-image">
53
64
  <div
54
65
  class="level-1 symbol-list-image-column symbol-list-image warning-image"
55
- aria-labelledby="symbol-list-warning-level-1-text">
56
- </div>
66
+ aria-labelledby="symbol-list-warning-level-1-text"
67
+ ></div>
57
68
  </div>
58
69
  <div class="symbol-list-cell symbol-list-cell-text">
59
70
  <div
60
71
  id="symbol-list-warning-level-1-text"
61
- class="item-text symbol-list-text">
72
+ class="item-text symbol-list-text"
73
+ >
62
74
  {{ warningLevel1Text }}
63
75
  </div>
64
76
  </div>
@@ -69,13 +81,14 @@
69
81
  <div class="symbol-list-cell symbol-list-cell-image">
70
82
  <div
71
83
  class="level-2 symbol-list-image-column symbol-list-image warning-image"
72
- aria-labelledby="symbol-list-warning-level-2-text">
73
- </div>
84
+ aria-labelledby="symbol-list-warning-level-2-text"
85
+ ></div>
74
86
  </div>
75
87
  <div class="symbol-list-cell symbol-list-cell-text">
76
88
  <div
77
89
  id="symbol-list-warning-level-2-text"
78
- class="item-text symbol-list-text">
90
+ class="item-text symbol-list-text"
91
+ >
79
92
  {{ warningLevel2Text }}
80
93
  </div>
81
94
  </div>
@@ -86,13 +99,14 @@
86
99
  <div class="symbol-list-cell symbol-list-cell-image">
87
100
  <div
88
101
  class="level-3 symbol-list-image-column symbol-list-image warning-image"
89
- aria-labelledby="symbol-list-warning-level-3-text">
90
- </div>
102
+ aria-labelledby="symbol-list-warning-level-3-text"
103
+ ></div>
91
104
  </div>
92
105
  <div class="symbol-list-cell symbol-list-cell-text">
93
106
  <div
94
107
  id="symbol-list-warning-level-3-text"
95
- class="item-text symbol-list-text">
108
+ class="item-text symbol-list-text"
109
+ >
96
110
  {{ warningLevel3Text }}
97
111
  </div>
98
112
  </div>
@@ -103,13 +117,14 @@
103
117
  <div class="symbol-list-cell symbol-list-cell-image">
104
118
  <div
105
119
  class="level-4 symbol-list-image-column symbol-list-image warning-image"
106
- aria-labelledby="symbol-list-warning-level-4-text">
107
- </div>
120
+ aria-labelledby="symbol-list-warning-level-4-text"
121
+ ></div>
108
122
  </div>
109
123
  <div class="symbol-list-cell symbol-list-cell-text">
110
124
  <div
111
125
  id="symbol-list-warning-level-4-text"
112
- class="item-text symbol-list-text">
126
+ class="item-text symbol-list-text"
127
+ >
113
128
  {{ warningLevel4Text }}
114
129
  </div>
115
130
  <hr class="bottom-separator" />
package/src/main.js CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  h,
6
6
  } from 'vue'
7
7
  import { createWebComponent } from 'vue-web-component-wrapper'
8
+
8
9
  import app from './App.vue'
9
10
 
10
11
  createWebComponent({
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Core mixin for AlertClient wrapper components.
3
+ * Contains shared logic for both web component (App.vue) and Vue component (AlertClientVue.vue).
4
+ *
5
+ * This mixin provides:
6
+ * - Data state management (loading, warningsData, themeClass, etc.)
7
+ * - Computed properties for API queries
8
+ * - Lifecycle hooks (created, mounted, serverPrefetch)
9
+ * - Methods for fetching warnings and handling events
10
+ *
11
+ * Components using this mixin must provide:
12
+ * - Props with appropriate types (string-only for web components, mixed for Vue)
13
+ * - Normalized computed properties that convert props to correct types
14
+ */
15
+ import fetch from 'cross-fetch'
16
+
17
+ // Helper to normalize string|boolean to boolean
18
+ export const toBool = (val, defaultVal = true) => {
19
+ if (typeof val === 'boolean') return val
20
+ if (typeof val === 'string') return val.toLowerCase() !== 'false'
21
+ return defaultVal
22
+ }
23
+
24
+ // Helper to normalize string|number to number
25
+ export const toNum = (val, defaultVal = 0) => {
26
+ if (typeof val === 'number') return val
27
+ if (typeof val === 'string') return Number(val)
28
+ return defaultVal
29
+ }
30
+
31
+ export default {
32
+ data() {
33
+ return {
34
+ loading: 1,
35
+ updatedAt: null,
36
+ refreshedAt: null,
37
+ themeClass: `${this.theme}-theme`,
38
+ warningsData: null,
39
+ visible: true,
40
+ }
41
+ },
42
+ computed: {
43
+ // API query type names
44
+ weatherUpdatedType() {
45
+ return 'weather_update_time'
46
+ },
47
+ floodUpdatedType() {
48
+ return 'flood_update_time'
49
+ },
50
+ weatherWarningsType() {
51
+ return 'weather_finland_active_all'
52
+ },
53
+ floodWarningsType() {
54
+ return 'flood_finland_active_all'
55
+ },
56
+
57
+ // Query builders
58
+ weatherUpdatedQuery() {
59
+ return this.weatherUpdated || `${this.query}${this.weatherUpdatedType}`
60
+ },
61
+ floodUpdatedQuery() {
62
+ return this.floodUpdated || `${this.query}${this.floodUpdatedType}`
63
+ },
64
+ weatherWarningsQuery() {
65
+ return this.weatherWarnings || `${this.query}${this.weatherWarningsType}`
66
+ },
67
+ floodWarningsQuery() {
68
+ return (
69
+ this.floodWarnings ||
70
+ `${this.query}${this.floodWarningsType}${this.floodFilter}`
71
+ )
72
+ },
73
+ query() {
74
+ return '?service=WFS&version=1.0.0&request=GetFeature&maxFeatures=1000&outputFormat=application%2Fjson&typeName='
75
+ },
76
+ floodSupportedSeverities() {
77
+ return ['moderate', 'severe', 'extreme']
78
+ },
79
+ floodFilter() {
80
+ return `${this.floodSupportedSeverities.reduce(
81
+ (filter, severity, index) =>
82
+ `${filter + (index === 0 ? '' : ',')}%27${severity.toUpperCase()}%27`,
83
+ '&cql_filter=severity%20IN%20('
84
+ )})%20AND%20language=%27${this.capLanguage()}%27`
85
+ },
86
+ capLanguage() {
87
+ return () =>
88
+ ({
89
+ fi: 'fi-FI',
90
+ sv: 'sv-SV',
91
+ en: 'en-US',
92
+ })[this.language]
93
+ },
94
+
95
+ // Current time calculation
96
+ currentTime() {
97
+ if (this.refreshedAt) {
98
+ return this.refreshedAt
99
+ }
100
+ if (this.currentDate) {
101
+ const date =
102
+ this.currentDate instanceof Date
103
+ ? this.currentDate
104
+ : new Date(this.currentDate)
105
+ return date.getTime()
106
+ }
107
+ return Date.now()
108
+ },
109
+ },
110
+ created() {
111
+ if (this.warnings) {
112
+ this.warningsData =
113
+ typeof this.warnings === 'string'
114
+ ? JSON.parse(this.warnings)
115
+ : this.warnings
116
+ }
117
+ },
118
+ mounted() {
119
+ const fontScaleNum = toNum(this.fontScale, 1)
120
+ if (fontScaleNum !== 1) {
121
+ let originalFontSize
122
+ if (
123
+ typeof window !== 'undefined' &&
124
+ typeof document !== 'undefined' &&
125
+ document.documentElement &&
126
+ window.getComputedStyle
127
+ ) {
128
+ const htmlElement = document.documentElement
129
+ const computedStyle = window.getComputedStyle(htmlElement)
130
+ originalFontSize = parseFloat(computedStyle.fontSize)
131
+ }
132
+ if (originalFontSize == null || Number.isNaN(originalFontSize)) {
133
+ originalFontSize = 16 // Fallback
134
+ }
135
+ const scaledFontSize = fontScaleNum * originalFontSize
136
+ const newFontSize = Math.round(scaledFontSize * 100) / 100
137
+ document.documentElement.style.fontSize = `${newFontSize}px`
138
+ }
139
+ },
140
+ serverPrefetch() {
141
+ if (!this.warnings) {
142
+ return this.fetchWarnings()
143
+ }
144
+ },
145
+ methods: {
146
+ onLoaded(loaded) {
147
+ if (loaded !== 0) {
148
+ this.loading = loaded === -1 ? -1 : 0
149
+ }
150
+ },
151
+ onThemeChanged(newTheme) {
152
+ this.themeClass = `${
153
+ newTheme != null && newTheme.length > 0 ? newTheme : this.theme
154
+ }-theme`
155
+ },
156
+ fetchWarnings() {
157
+ if (this.warnings) {
158
+ return
159
+ }
160
+ this.loading = 1
161
+ // debugModeNormalized is provided by the component using this mixin
162
+ if (this.debugModeNormalized) {
163
+ console.log(`Updating warnings at ${new Date()}`)
164
+ }
165
+ const queries = new Map()
166
+ .set(
167
+ `${this.baseUrl}${this.weatherUpdatedQuery}`,
168
+ this.weatherUpdatedType
169
+ )
170
+ .set(`${this.baseUrl}${this.floodUpdatedQuery}`, this.floodUpdatedType)
171
+ .set(
172
+ `${this.baseUrl}${this.weatherWarningsQuery}`,
173
+ this.weatherWarningsType
174
+ )
175
+ .set(
176
+ `${this.baseUrl}${this.floodWarningsQuery}`,
177
+ this.floodWarningsType
178
+ )
179
+ const responseData = {}
180
+ return Promise.allSettled(
181
+ [...queries.keys()].map(async (query) =>
182
+ fetch(query).then((response) =>
183
+ response
184
+ .json()
185
+ .then((json) => {
186
+ const currentTime = Date.now()
187
+ if (this.updatedAt != null) {
188
+ this.refreshedAt = currentTime
189
+ }
190
+ this.updatedAt = currentTime
191
+ responseData[queries.get(query)] = json
192
+ })
193
+ .catch((error) => {
194
+ this.loading = -1
195
+ console.log(error)
196
+ })
197
+ )
198
+ )
199
+ ).then(() => {
200
+ this.warningsData = responseData
201
+ })
202
+ },
203
+ show() {
204
+ this.visible = true
205
+ },
206
+ hide() {
207
+ this.visible = false
208
+ },
209
+ },
210
+ }