@fmidev/smartmet-alert-client 4.4.19 → 4.7.0-beta.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 (123) hide show
  1. package/.eslintignore +2 -14
  2. package/.github/workflows/test.yaml +26 -0
  3. package/.nvmrc +1 -0
  4. package/AGENTS.md +26 -0
  5. package/index.html +1 -1
  6. package/package.json +80 -22
  7. package/src/AlertClientVue.vue +160 -0
  8. package/src/App.vue +154 -296
  9. package/src/assets/img/ui/arrow-down.svg +4 -11
  10. package/src/assets/img/ui/arrow-up.svg +4 -11
  11. package/src/assets/img/ui/clear.svg +7 -21
  12. package/src/assets/img/ui/close.svg +4 -15
  13. package/src/assets/img/ui/toggle-selected.svg +5 -6
  14. package/src/assets/img/ui/toggle-unselected.svg +5 -6
  15. package/src/assets/img/warning/cold-weather.svg +3 -6
  16. package/src/assets/img/warning/flood-level-3.svg +4 -7
  17. package/src/assets/img/warning/forest-fire-weather.svg +2 -6
  18. package/src/assets/img/warning/grass-fire-weather.svg +2 -6
  19. package/src/assets/img/warning/hot-weather.svg +3 -6
  20. package/src/assets/img/warning/pedestrian-safety.svg +3 -7
  21. package/src/assets/img/warning/rain.svg +2 -7
  22. package/src/assets/img/warning/sea-icing.svg +2 -6
  23. package/src/assets/img/warning/sea-thunder-storm.svg +2 -5
  24. package/src/assets/img/warning/sea-water-height-high-water.svg +3 -8
  25. package/src/assets/img/warning/sea-water-height-shallow-water.svg +3 -7
  26. package/src/assets/img/warning/sea-wave-height.svg +4 -7
  27. package/src/assets/img/warning/sea-wind-legend.svg +2 -5
  28. package/src/assets/img/warning/sea-wind.svg +2 -5
  29. package/src/assets/img/warning/several.svg +2 -5
  30. package/src/assets/img/warning/thunder-storm.svg +2 -5
  31. package/src/assets/img/warning/traffic-weather.svg +2 -6
  32. package/src/assets/img/warning/uv-note.svg +2 -6
  33. package/src/assets/img/warning/wind.svg +2 -5
  34. package/src/components/AlertClient.vue +330 -251
  35. package/src/components/CollapsiblePanel.vue +281 -0
  36. package/src/components/DayLarge.vue +146 -110
  37. package/src/components/DaySmall.vue +97 -81
  38. package/src/components/Days.vue +229 -159
  39. package/src/components/DescriptionWarning.vue +63 -38
  40. package/src/components/GrayScaleToggle.vue +58 -54
  41. package/src/components/Legend.vue +102 -325
  42. package/src/components/MapLarge.vue +574 -351
  43. package/src/components/MapSmall.vue +137 -122
  44. package/src/components/PopupRow.vue +24 -12
  45. package/src/components/Region.vue +168 -118
  46. package/src/components/RegionWarning.vue +40 -33
  47. package/src/components/Regions.vue +189 -105
  48. package/src/components/Warning.vue +70 -45
  49. package/src/components/Warnings.vue +136 -72
  50. package/src/composables/useAlertClient.ts +360 -0
  51. package/src/composables/useConfig.ts +573 -0
  52. package/src/composables/useFields.ts +66 -0
  53. package/src/composables/useI18n.ts +62 -0
  54. package/src/composables/useKeyCodes.ts +16 -0
  55. package/src/composables/useMapPaths.ts +477 -0
  56. package/src/composables/useUtils.ts +683 -0
  57. package/src/composables/useWarningsProcessor.ts +1007 -0
  58. package/src/data/geometries.json +993 -0
  59. package/src/{main.js → main.ts} +1 -0
  60. package/src/mixins/geojsonsvg.d.ts +57 -0
  61. package/src/mixins/geojsonsvg.js +5 -3
  62. package/src/plugins/index.ts +5 -0
  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/types/index.ts +509 -0
  67. package/src/vite-env.d.ts +23 -0
  68. package/src/vue.ts +41 -0
  69. package/svgo.config.js +45 -0
  70. package/tests/README.md +430 -0
  71. package/tests/fixtures/mockWarningData.ts +152 -0
  72. package/tests/integration/warning-flow.spec.ts +445 -0
  73. package/tests/setup.ts +41 -0
  74. package/tests/unit/components/AlertClient.spec.ts +701 -0
  75. package/tests/unit/components/DayLarge.spec.ts +348 -0
  76. package/tests/unit/components/DaySmall.spec.ts +352 -0
  77. package/tests/unit/components/Days.spec.ts +548 -0
  78. package/tests/unit/components/DescriptionWarning.spec.ts +385 -0
  79. package/tests/unit/components/GrayScaleToggle.spec.ts +318 -0
  80. package/tests/unit/components/Legend.spec.ts +295 -0
  81. package/tests/unit/components/MapLarge.spec.ts +448 -0
  82. package/tests/unit/components/MapSmall.spec.ts +367 -0
  83. package/tests/unit/components/PopupRow.spec.ts +270 -0
  84. package/tests/unit/components/Region.spec.ts +373 -0
  85. package/tests/unit/components/RegionWarning.snapshot.spec.ts +361 -0
  86. package/tests/unit/components/RegionWarning.spec.ts +381 -0
  87. package/tests/unit/components/Regions.spec.ts +503 -0
  88. package/tests/unit/components/Warning.snapshot.spec.ts +483 -0
  89. package/tests/unit/components/Warning.spec.ts +489 -0
  90. package/tests/unit/components/Warnings.spec.ts +343 -0
  91. package/tests/unit/components/__snapshots__/RegionWarning.snapshot.spec.ts.snap +41 -0
  92. package/tests/unit/components/__snapshots__/Warning.snapshot.spec.ts.snap +433 -0
  93. package/tests/unit/composables/useConfig.spec.ts +279 -0
  94. package/tests/unit/composables/useI18n.spec.ts +116 -0
  95. package/tests/unit/composables/useKeyCodes.spec.ts +27 -0
  96. package/tests/unit/composables/useUtils.spec.ts +213 -0
  97. package/tsconfig.json +43 -0
  98. package/tsconfig.node.json +11 -0
  99. package/vite.config.js +96 -26
  100. package/vitest.config.js +40 -0
  101. package/dist/favicon.ico +0 -0
  102. package/dist/index.dark.html +0 -20
  103. package/dist/index.en.html +0 -15
  104. package/dist/index.fi.html +0 -15
  105. package/dist/index.html +0 -15
  106. package/dist/index.js +0 -281
  107. package/dist/index.mjs +0 -281
  108. package/dist/index.mjs.map +0 -1
  109. package/dist/index.relative.html +0 -19
  110. package/dist/index.start.html +0 -20
  111. package/dist/index.sv.html +0 -15
  112. package/playwright.config.ts +0 -18
  113. package/public/index.relative.html +0 -19
  114. package/public/index.start.html +0 -20
  115. package/src/mixins/config.js +0 -1378
  116. package/src/mixins/fields.js +0 -26
  117. package/src/mixins/i18n.js +0 -25
  118. package/src/mixins/keycodes.js +0 -10
  119. package/src/mixins/panzoom.js +0 -900
  120. package/src/mixins/utils.js +0 -900
  121. package/src/plugins/index.js +0 -3
  122. package/test/snapshot.test.ts +0 -126
  123. package/vitest.config.ts +0 -6
@@ -5,378 +5,155 @@
5
5
  :language="language"
6
6
  :gray-scale-selector="grayScaleSelector"
7
7
  :theme="theme"
8
- @themeChanged="onThemeChanged" />
8
+ @theme-changed="onThemeChanged" />
9
9
  <div class="row symbol-list-header-row">
10
10
  <nav class="symbol-list-header bold-text">
11
11
  {{ warningSymbolsText }}
12
12
  <br v-if="input.length > 0" class="symbol-list-header-line-break" />
13
13
  </nav>
14
14
  </div>
15
- <b-card no-body class="mb-1 d-md-none legends-panel">
16
- <b-card-header header-tag="header" class="legends-heading p-1">
17
- <div class="legends-header">
18
- <span class="legends-text">
19
- {{ toggleLegendsText }}
20
- </span>
21
- </div>
22
- <b-button
23
- block
24
- :class="['legends-toggle', visible ? '' : 'collapsed']"
25
- :aria-label="toggleLegendsText"
26
- @click="onLegendToggle" />
27
- </b-card-header>
28
- <b-collapse
29
- id="legends-collapse"
30
- v-model="visible"
31
- class="legends-collapse-item focus-ring"
32
- tabindex="0">
33
- <b-card-body body-class="p-0">
34
- <div class="legends-container">
35
- <Warnings
36
- :input="input"
37
- :visible-warnings="visibleWarnings"
38
- :theme="theme"
39
- :language="language" />
40
- </div>
41
- </b-card-body>
42
- </b-collapse>
43
- </b-card>
44
- <div ref="warningsContainer" class="d-md-block d-none">
15
+ <CollapsiblePanel
16
+ :visible="visible"
17
+ :title="toggleLegendsText"
18
+ :theme="theme"
19
+ @toggle="onLegendToggle">
20
+ <Warnings
21
+ :input="input"
22
+ :visible-warnings="visibleWarnings"
23
+ :theme="theme"
24
+ :language="language" />
25
+ </CollapsiblePanel>
26
+ <div ref="warningsContainer" class="desktop-only">
45
27
  <Warnings
46
28
  :input="input"
47
29
  :visible-warnings="visibleWarnings"
48
30
  :theme="theme"
49
31
  :language="language"
50
- @warningsToggled="onWarningsToggled"
51
- @showAllWarnings="onShowAllWarnings" />
32
+ @warnings-toggled="onWarningsToggled"
33
+ @show-all-warnings="onShowAllWarnings" />
52
34
  </div>
53
35
  <GrayScaleToggle
54
36
  :language="language"
55
37
  :gray-scale-selector="grayScaleSelector"
56
38
  :theme="theme"
57
- @themeChanged="onThemeChanged" />
39
+ @theme-changed="onThemeChanged" />
58
40
  </div>
59
41
  </template>
60
42
 
61
- <script>
62
- import { onMounted, onUnmounted, ref } from 'vue'
43
+ <script setup lang="ts">
44
+ import { ref, computed, watch, toRef, onMounted, onUnmounted } from 'vue'
45
+ import { useI18n } from '@/composables/useI18n'
46
+ import type { LegendItem, Language } from '@/types'
63
47
 
64
- import i18n from '../mixins/i18n'
48
+ import CollapsiblePanel from './CollapsiblePanel.vue'
49
+ import GrayScaleToggle from './GrayScaleToggle.vue'
65
50
  import Warnings from './Warnings.vue'
66
51
 
67
- export default {
68
- name: 'Legend',
69
- components: {
70
- Warnings,
71
- },
72
- mixins: [i18n],
73
- props: {
74
- input: {
75
- type: Array,
76
- default: () => [],
77
- },
78
- language: {
79
- type: String,
80
- default: import.meta.env.VITE_LANGUAGE || 'fi',
81
- },
82
- grayScaleSelector: {
83
- type: Boolean,
84
- default: false,
85
- },
86
- theme: {
87
- type: String,
88
- default: 'light-theme',
89
- },
90
- visibleWarnings: {
91
- type: Array,
92
- default: () => [],
93
- },
94
- },
95
- setup() {
96
- const windowWidth = ref(window.innerWidth)
97
- const updateWidth = () => {
98
- windowWidth.value = window.innerWidth
99
- }
100
- onMounted(() => {
101
- window.addEventListener('resize', updateWidth)
102
- })
103
- onUnmounted(() => {
104
- window.removeEventListener('resize', updateWidth)
105
- })
106
- return { windowWidth }
107
- },
108
- data() {
109
- return {
110
- visible: false,
111
- }
112
- },
113
- computed: {
114
- warnings() {
115
- return this.input
116
- },
117
- warningSymbolsText() {
118
- return this.t('legends')
119
- },
120
- toggleLegendsText() {
121
- return this.visible ? this.t('hideLegends') : this.t('showLegends')
122
- },
123
- },
124
- watch: {
125
- windowWidth() {
126
- if (this.$refs.warningsContainer.clientHeight === 0) {
127
- this.onShowAllWarnings()
128
- }
129
- },
130
- },
131
- methods: {
132
- onLegendToggle() {
133
- this.visible = !this.visible
134
- },
135
- onWarningsToggled(newVisibleWarnings) {
136
- this.$emit('warningsToggled', newVisibleWarnings)
137
- },
138
- onShowAllWarnings() {
139
- this.$emit(
140
- 'warningsToggled',
141
- this.warnings.reduce(
142
- (types, warning) => types.concat([warning.type]),
143
- []
144
- )
145
- )
146
- },
147
- onThemeChanged(newTheme) {
148
- if (this.theme !== newTheme) {
149
- this.$emit('themeChanged', newTheme)
150
- }
151
- },
152
- },
153
- }
154
- </script>
155
-
156
- <style scoped lang="scss">
157
- @import '../scss/constants.scss';
158
-
159
- div.symbol-list-header-row {
160
- padding-left: 0;
161
- padding-right: 0;
162
- padding-bottom: 0;
163
- margin-left: 0;
164
- margin-right: 0;
165
- span {
166
- white-space: nowrap;
52
+ // Props
53
+ const props = withDefaults(
54
+ defineProps<{
55
+ input?: LegendItem[]
56
+ language?: Language
57
+ grayScaleSelector?: boolean
58
+ theme?: string
59
+ visibleWarnings?: string[]
60
+ }>(),
61
+ {
62
+ input: () => [],
63
+ language: (import.meta.env.VITE_LANGUAGE as Language) || 'fi',
64
+ grayScaleSelector: false,
65
+ theme: 'light-theme',
66
+ visibleWarnings: () => [],
167
67
  }
168
- }
169
-
170
- .legends-panel {
171
- margin-left: 0;
172
- margin-right: 0;
173
- border-radius: 0;
174
- }
175
-
176
- .light-theme .legends-panel {
177
- border: 0.5px solid $light-legend-background-color;
178
- }
179
-
180
- .dark-theme .legends-panel {
181
- border: 0.5px solid $dark-legend-background-color;
182
- }
183
-
184
- .light-gray-theme .legends-panel {
185
- border: 0.5px solid $light-gray-legend-background-color;
186
- }
187
-
188
- .dark-gray-theme .legends-panel {
189
- border: 0.5px solid $dark-gray-legend-background-color;
190
- }
191
-
192
- .legends-heading {
193
- height: $current-warning-height;
194
- padding: 0 0 0 15px !important;
195
- line-height: $current-warning-height;
196
- border: none;
197
- border-bottom: 0.5px solid rgba(0, 0, 0, 0.125);
198
- border-radius: 0 !important;
199
- }
200
-
201
- .light-theme .legends-heading {
202
- background-color: $light-legend-heading-background-color;
203
- }
204
-
205
- .dark-theme .legends-heading {
206
- background-color: $dark-legend-heading-background-color;
207
- }
208
-
209
- .light-gray-theme .legends-heading {
210
- background-color: $light-gray-legend-heading-background-color;
211
- }
212
-
213
- .dark-gray-theme .legends-heading {
214
- background-color: $dark-gray-legend-heading-background-color;
215
- }
216
-
217
- .legends-header {
218
- position: absolute;
219
- left: 0;
220
- right: 38px;
221
- border-radius: 0;
222
- display: flex;
223
- align-items: center;
224
- justify-content: start;
225
- height: $current-warning-height;
226
- }
227
-
228
- .light-theme .legends-header {
229
- background: $light-legend-heading-background-color;
230
- }
231
-
232
- .dark-theme .legends-header {
233
- background: $dark-legend-heading-background-color;
234
- }
235
-
236
- .light-gray-theme .legends-header {
237
- background: $light-gray-legend-heading-background-color;
238
- }
68
+ )
239
69
 
240
- .dark-gray-theme .legends-header {
241
- background: $dark-gray-legend-heading-background-color;
242
- }
70
+ // Emits
71
+ const emit = defineEmits<{
72
+ warningsToggled: [warnings: string[]]
73
+ themeChanged: [theme: string]
74
+ }>()
243
75
 
244
- .legends-text {
245
- display: block;
246
- text-align: left;
247
- white-space: nowrap;
248
- overflow: hidden;
249
- text-overflow: ellipsis;
250
- margin-left: 15px;
251
- }
76
+ // Composables
77
+ const { t } = useI18n(toRef(props, 'language'))
252
78
 
253
- .light-theme .legends-text {
254
- background-color: $light-legend-heading-background-color;
255
- }
79
+ // Template refs
80
+ const warningsContainer = ref<HTMLDivElement | null>(null)
256
81
 
257
- .dark-theme .legends-text {
258
- background-color: $dark-legend-heading-background-color;
259
- }
82
+ // Reactive state
83
+ const visible = ref(false)
84
+ const windowWidth = ref(window.innerWidth)
260
85
 
261
- .light-gray-theme .legends-text {
262
- background-color: $light-gray-legend-heading-background-color;
86
+ // Window resize handling
87
+ function updateWidth(): void {
88
+ windowWidth.value = window.innerWidth
263
89
  }
264
90
 
265
- .dark-gray-theme .legends-text {
266
- background-color: $dark-gray-legend-heading-background-color;
267
- }
91
+ onMounted(() => {
92
+ window.addEventListener('resize', updateWidth)
93
+ })
268
94
 
269
- button.legends-toggle {
270
- position: relative;
271
- height: $current-warning-height;
272
- width: $current-warning-height;
273
- min-width: $current-warning-height;
274
- background-image: url($ui-image-path + 'arrow-up.svg');
275
- background-repeat: no-repeat;
276
- background-position: center;
277
- border-radius: 0 !important;
278
- border-style: none;
279
- float: right;
280
- padding: $image-padding;
281
- margin-left: 5px;
95
+ onUnmounted(() => {
96
+ window.removeEventListener('resize', updateWidth)
97
+ })
282
98
 
283
- &.collapsed {
284
- background-image: url($ui-image-path + 'arrow-down.svg');
285
- border-radius: 0 3px 3px 0;
286
- }
287
- }
99
+ // Computed
100
+ const warnings = computed((): LegendItem[] => {
101
+ return props.input
102
+ })
288
103
 
289
- .light-theme .legends-toggle {
290
- background-color: $light-legend-toggle-background-color;
104
+ const warningSymbolsText = computed((): string => {
105
+ return t('legends')
106
+ })
291
107
 
292
- &:hover {
293
- background-color: $light-legend-toggle-background-color;
294
- }
108
+ const toggleLegendsText = computed((): string => {
109
+ return visible.value ? t('hideLegends') : t('showLegends')
110
+ })
295
111
 
296
- &:active {
297
- background-color: $light-legend-toggle-background-color;
112
+ // Watchers
113
+ watch(windowWidth, () => {
114
+ if (warningsContainer.value?.clientHeight === 0) {
115
+ onShowAllWarnings()
298
116
  }
117
+ })
299
118
 
300
- &:not(:disabled):not(.disabled):active {
301
- background-color: $light-current-warning-toggle-active-color;
302
- }
119
+ // Methods
120
+ function onLegendToggle(): void {
121
+ visible.value = !visible.value
303
122
  }
304
123
 
305
- .dark-theme .legends-toggle {
306
- background-color: $dark-legend-toggle-background-color;
307
-
308
- &:hover {
309
- background-color: $dark-legend-toggle-background-color;
310
- }
311
-
312
- &:active {
313
- background-color: $dark-legend-toggle-background-color;
314
- }
315
-
316
- &:not(:disabled):not(.disabled):active {
317
- background-color: $dark-current-warning-toggle-active-color;
318
- }
124
+ function onWarningsToggled(newVisibleWarnings: string[]): void {
125
+ emit('warningsToggled', newVisibleWarnings)
319
126
  }
320
127
 
321
- .light-gray-theme .legends-toggle {
322
- background-color: $light-gray-legend-toggle-background-color;
323
-
324
- &:hover {
325
- background-color: $light-gray-legend-toggle-background-color;
326
- }
327
-
328
- &:active {
329
- background-color: $light-gray-legend-toggle-background-color;
330
- }
331
-
332
- &:not(:disabled):not(.disabled):active {
333
- background-color: $light-gray-current-warning-toggle-active-color;
334
- }
128
+ function onShowAllWarnings(): void {
129
+ emit(
130
+ 'warningsToggled',
131
+ warnings.value.reduce<string[]>(
132
+ (types, warning) => types.concat([warning.type]),
133
+ []
134
+ )
135
+ )
335
136
  }
336
137
 
337
- .dark-gray-theme .legends-toggle {
338
- background-color: $dark-gray-legend-toggle-background-color;
339
-
340
- &:hover {
341
- background-color: $dark-gray-legend-toggle-background-color;
138
+ function onThemeChanged(newTheme: string): void {
139
+ if (props.theme !== newTheme) {
140
+ emit('themeChanged', newTheme)
342
141
  }
343
-
344
- &:active {
345
- background-color: $dark-gray-legend-toggle-background-color;
346
- }
347
-
348
- &:not(:disabled):not(.disabled):active {
349
- background-color: $dark-gray-current-warning-toggle-active-color;
350
- }
351
- }
352
-
353
- .legends-container {
354
- padding: 15px;
355
- border-radius: 0;
356
- }
357
-
358
- .light-theme .legends-container {
359
- background-color: $light-legend-container-background-color;
360
- border-top: 0.5px solid $light-legend-background-color;
361
- }
362
-
363
- .dark-theme .legends-container {
364
- background-color: $dark-legend-container-background-color;
365
- border-top: 0.5px solid $dark-legend-background-color;
366
- }
367
-
368
- .light-gray-theme .legends-container {
369
- background-color: $light-gray-legend-container-background-color;
370
- border-top: 0.5px solid $light-gray-legend-background-color;
371
142
  }
143
+ </script>
372
144
 
373
- .dark-gray-theme .legends-container {
374
- background-color: $dark-gray-legend-container-background-color;
375
- border-top: 0.5px solid $dark-gray-legend-background-color;
376
- }
145
+ <style scoped lang="scss">
146
+ @import '../scss/constants.scss';
377
147
 
378
- div#legends-collapse div.card-body {
379
- padding: 0;
148
+ div.symbol-list-header-row {
149
+ padding-left: 0;
150
+ padding-right: 0;
151
+ padding-bottom: 0;
152
+ margin-left: 0;
153
+ margin-right: 0;
154
+ span {
155
+ white-space: nowrap;
156
+ }
380
157
  }
381
158
 
382
159
  nav.symbol-list-header {