@fmidev/smartmet-alert-client 4.7.0-alpha.0 → 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 (105) hide show
  1. package/AGENTS.md +26 -0
  2. package/index.html +1 -1
  3. package/package.json +30 -8
  4. package/src/AlertClientVue.vue +126 -136
  5. package/src/App.vue +138 -130
  6. package/src/components/AlertClient.vue +340 -283
  7. package/src/components/CollapsiblePanel.vue +25 -28
  8. package/src/components/DayLarge.vue +153 -122
  9. package/src/components/DaySmall.vue +101 -95
  10. package/src/components/Days.vue +190 -145
  11. package/src/components/DescriptionWarning.vue +70 -52
  12. package/src/components/GrayScaleToggle.vue +56 -57
  13. package/src/components/Legend.vue +102 -113
  14. package/src/components/MapLarge.vue +541 -317
  15. package/src/components/MapSmall.vue +150 -151
  16. package/src/components/PopupRow.vue +27 -18
  17. package/src/components/Region.vue +140 -105
  18. package/src/components/RegionWarning.vue +42 -36
  19. package/src/components/Regions.vue +196 -143
  20. package/src/components/Warning.vue +67 -50
  21. package/src/components/Warnings.vue +146 -97
  22. package/src/composables/useAlertClient.ts +360 -0
  23. package/src/composables/useConfig.ts +573 -0
  24. package/src/composables/useFields.ts +66 -0
  25. package/src/composables/useI18n.ts +62 -0
  26. package/src/composables/useKeyCodes.ts +16 -0
  27. package/src/composables/useMapPaths.ts +477 -0
  28. package/src/composables/useUtils.ts +683 -0
  29. package/src/composables/useWarningsProcessor.ts +1007 -0
  30. package/src/data/geometries.json +993 -0
  31. package/src/mixins/geojsonsvg.d.ts +57 -0
  32. package/src/mixins/geojsonsvg.js +5 -3
  33. package/src/plugins/index.ts +5 -0
  34. package/src/types/index.ts +509 -0
  35. package/src/vite-env.d.ts +23 -0
  36. package/tests/fixtures/{mockWarningData.js → mockWarningData.ts} +29 -12
  37. package/tests/integration/{warning-flow.spec.js → warning-flow.spec.ts} +124 -131
  38. package/tests/{setup.js → setup.ts} +5 -5
  39. package/tests/unit/components/{AlertClient.spec.js → AlertClient.spec.ts} +151 -184
  40. package/tests/unit/components/DayLarge.spec.ts +348 -0
  41. package/tests/unit/components/DaySmall.spec.ts +352 -0
  42. package/tests/unit/components/{Days.spec.js → Days.spec.ts} +88 -105
  43. package/tests/unit/components/DescriptionWarning.spec.ts +385 -0
  44. package/tests/unit/components/{GrayScaleToggle.spec.js → GrayScaleToggle.spec.ts} +64 -57
  45. package/tests/unit/components/Legend.spec.ts +295 -0
  46. package/tests/unit/components/MapLarge.spec.ts +448 -0
  47. package/tests/unit/components/MapSmall.spec.ts +367 -0
  48. package/tests/unit/components/{PopupRow.spec.js → PopupRow.spec.ts} +21 -12
  49. package/tests/unit/components/Region.spec.ts +373 -0
  50. package/tests/unit/components/RegionWarning.snapshot.spec.ts +361 -0
  51. package/tests/unit/components/RegionWarning.spec.ts +381 -0
  52. package/tests/unit/components/Regions.spec.ts +503 -0
  53. package/tests/unit/components/Warning.snapshot.spec.ts +483 -0
  54. package/tests/unit/components/{Warning.spec.js → Warning.spec.ts} +110 -93
  55. package/tests/unit/components/{Warnings.spec.js → Warnings.spec.ts} +77 -63
  56. package/tests/unit/components/__snapshots__/RegionWarning.snapshot.spec.ts.snap +41 -0
  57. package/tests/unit/components/__snapshots__/Warning.snapshot.spec.ts.snap +433 -0
  58. package/tests/unit/{mixins/config.spec.js → composables/useConfig.spec.ts} +69 -59
  59. package/tests/unit/composables/useI18n.spec.ts +116 -0
  60. package/tests/unit/composables/useKeyCodes.spec.ts +27 -0
  61. package/tests/unit/composables/useUtils.spec.ts +213 -0
  62. package/tsconfig.json +43 -0
  63. package/tsconfig.node.json +11 -0
  64. package/vite.config.js +1 -1
  65. package/vitest.config.js +1 -1
  66. package/dist/favicon.ico +0 -0
  67. package/dist/index.dark.html +0 -20
  68. package/dist/index.en.html +0 -15
  69. package/dist/index.fi.html +0 -15
  70. package/dist/index.html +0 -20
  71. package/dist/index.js +0 -251
  72. package/dist/index.mjs +0 -258
  73. package/dist/index.sv.html +0 -15
  74. package/dist/locale-en-DCEKDw5G.js +0 -8
  75. package/dist/locale-fi-DPiOM1rB.js +0 -8
  76. package/dist/locale-sv-B0FlbgEF.js +0 -8
  77. package/dist/vendor-Cfkkvdz7.js +0 -21
  78. package/dist/vue/index.mjs +0 -15245
  79. package/dist/vue/style.css +0 -1
  80. package/dist/xml-parser-BiNO9kc-.js +0 -13
  81. package/src/mixins/alertClientCore.js +0 -210
  82. package/src/mixins/config.js +0 -1384
  83. package/src/mixins/fields.js +0 -26
  84. package/src/mixins/i18n.js +0 -25
  85. package/src/mixins/keycodes.js +0 -10
  86. package/src/mixins/utils.js +0 -914
  87. package/src/plugins/index.js +0 -3
  88. package/tests/unit/components/DayLarge.spec.js +0 -281
  89. package/tests/unit/components/DaySmall.spec.js +0 -278
  90. package/tests/unit/components/DescriptionWarning.spec.js +0 -432
  91. package/tests/unit/components/Legend.spec.js +0 -223
  92. package/tests/unit/components/MapLarge.spec.js +0 -276
  93. package/tests/unit/components/MapSmall.spec.js +0 -226
  94. package/tests/unit/components/Region.spec.js +0 -430
  95. package/tests/unit/components/RegionWarning.snapshot.spec.js +0 -73
  96. package/tests/unit/components/RegionWarning.spec.js +0 -408
  97. package/tests/unit/components/Regions.spec.js +0 -335
  98. package/tests/unit/components/Warning.snapshot.spec.js +0 -107
  99. package/tests/unit/components/__snapshots__/RegionWarning.snapshot.spec.js.snap +0 -21
  100. package/tests/unit/components/__snapshots__/Warning.snapshot.spec.js.snap +0 -199
  101. package/tests/unit/mixins/i18n.spec.js +0 -115
  102. package/tests/unit/mixins/keycodes.spec.js +0 -37
  103. package/tests/unit/mixins/utils.spec.js +0 -624
  104. /package/src/{main.js → main.ts} +0 -0
  105. /package/src/{vue.js → vue.ts} +0 -0
package/AGENTS.md ADDED
@@ -0,0 +1,26 @@
1
+ # Agent Guidelines for SmartMet Alert Client
2
+
3
+ ## Build & Test Commands
4
+
5
+ - `npm run dev` - Start dev server with hot reload
6
+ - `npm run build` - Production build
7
+ - `npm run lint` - Lint .js and .vue files
8
+ - `npm run lint:fix` - Auto-fix linting issues
9
+ - `npm test` - Run tests in watch mode
10
+ - `npm run test:run` - Run all tests once (CI/CD)
11
+ - `vitest path/to/file.spec.js` - Run single test file
12
+
13
+ ## Code Style
14
+
15
+ - **Formatting**: Prettier with single quotes, no semicolons, bracket spacing, arrowParens always
16
+ - **Imports**: Use `@/` alias for src imports (e.g., `import AlertClient from '@/components/AlertClient.vue'`)
17
+ - **Vue**: Vue 3 composition API, relaxed rules (self-closing, multi-word names, v-html allowed)
18
+ - **ESLint**: Standard + Vue3-recommended + SonarJS (cognitive complexity/duplicate strings disabled)
19
+ - **Naming**: camelCase for variables/functions, PascalCase for components
20
+
21
+ ## Architecture
22
+
23
+ - Vue 3 web components with custom elements
24
+ - Mixins for shared logic (config, utils, i18n, geojsonsvg, fields, keycodes)
25
+ - ES2020 modules, SCSS with modern compiler
26
+ - Vitest + @vue/test-utils + jsdom for testing
package/index.html CHANGED
@@ -10,6 +10,6 @@
10
10
 
11
11
  <body>
12
12
  <smartmet-alert-client></smartmet-alert-client>
13
- <script type="module" src="./src/main.js"></script>
13
+ <script type="module" src="./src/main.ts"></script>
14
14
  </body>
15
15
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fmidev/smartmet-alert-client",
3
- "version": "4.7.0-alpha.0",
3
+ "version": "4.7.0-beta.0",
4
4
  "description": "Web application for viewing weather and flood alerts",
5
5
  "author": "Finnish Meteorological Institute",
6
6
  "license": "MIT",
@@ -24,12 +24,13 @@
24
24
  },
25
25
  "scripts": {
26
26
  "dev": "vite",
27
- "build": "vite build && npm run build:vue",
27
+ "build": "vue-tsc --noEmit && vite build && npm run build:vue",
28
28
  "build:ce": "vite build",
29
29
  "build:vue": "BUILD_MODE=vue vite build",
30
30
  "serve": "vite preview",
31
- "lint": "eslint --ext .js,.vue src",
32
- "lint:fix": "eslint --ext .js,.vue --fix src",
31
+ "typecheck": "vue-tsc --noEmit",
32
+ "lint": "eslint --ext .js,.ts,.vue src",
33
+ "lint:fix": "eslint --ext .js,.ts,.vue --fix src",
33
34
  "test": "vitest",
34
35
  "test:ui": "vitest --ui",
35
36
  "test:coverage": "vitest --coverage",
@@ -51,6 +52,10 @@
51
52
  "devDependencies": {
52
53
  "@testing-library/jest-dom": "6.9.1",
53
54
  "@testing-library/vue": "8.1.0",
55
+ "@types/he": "1.2.3",
56
+ "@types/node": "25.0.3",
57
+ "@typescript-eslint/eslint-plugin": "6.21.0",
58
+ "@typescript-eslint/parser": "6.21.0",
54
59
  "@vitejs/plugin-vue": "4.5.2",
55
60
  "@vitest/coverage-v8": "3.2.4",
56
61
  "@vitest/ui": "3.2.4",
@@ -72,27 +77,44 @@
72
77
  "rollup-plugin-visualizer": "5.11.0",
73
78
  "sass": "1.93.2",
74
79
  "svgo": "4.0.0",
80
+ "typescript": "5.9.3",
75
81
  "unplugin-vue-components": "0.26.0",
76
82
  "vite": "5.1.8",
77
83
  "vite-plugin-banner": "0.7.1",
78
84
  "vite-plugin-static-copy": "1.0.6",
79
- "vitest": "3.2.4"
85
+ "vitest": "3.2.4",
86
+ "vue-tsc": "3.2.0"
80
87
  },
81
88
  "eslintConfig": {
82
89
  "root": true,
83
90
  "env": {
84
91
  "node": true
85
92
  },
93
+ "parser": "vue-eslint-parser",
94
+ "parserOptions": {
95
+ "parser": "@typescript-eslint/parser",
96
+ "ecmaVersion": 2020,
97
+ "sourceType": "module"
98
+ },
86
99
  "extends": [
87
100
  "plugin:vue/vue3-recommended",
88
101
  "eslint:recommended",
102
+ "plugin:@typescript-eslint/recommended",
89
103
  "plugin:sonarjs/recommended"
90
104
  ],
91
- "parserOptions": {
92
- "ecmaVersion": 2020
93
- },
105
+ "plugins": [
106
+ "@typescript-eslint"
107
+ ],
94
108
  "rules": {
95
109
  "no-extra-semi": "off",
110
+ "no-unused-vars": "off",
111
+ "@typescript-eslint/no-unused-vars": [
112
+ "error",
113
+ {
114
+ "argsIgnorePattern": "^_"
115
+ }
116
+ ],
117
+ "@typescript-eslint/no-explicit-any": "warn",
96
118
  "vue/html-self-closing": "off",
97
119
  "vue/require-explicit-emits": "off",
98
120
  "vue/singleline-html-element-content-newline": "off",
@@ -11,7 +11,7 @@
11
11
  :warnings-data="warningsData"
12
12
  :daily-warning-types="dailyWarningTypesNormalized"
13
13
  :geometry-id="geometryIdNormalized"
14
- :language="language"
14
+ :language="language as Language"
15
15
  :theme="themeClass"
16
16
  :sleep="sleepNormalized"
17
17
  :loading="loading"
@@ -20,150 +20,140 @@
20
20
  @theme-changed="onThemeChanged"
21
21
  @update-warnings="fetchWarnings" />
22
22
  </template>
23
- <script>
23
+ <script setup lang="ts">
24
24
  /**
25
25
  * Vue wrapper component for AlertClient.
26
26
  * Accepts native JavaScript types (Number, Boolean) unlike the web component
27
27
  * version which only accepts strings.
28
28
  */
29
+ import { computed, toRef, onMounted } from 'vue'
30
+
29
31
  import AlertClient from './components/AlertClient.vue'
30
- import alertClientCore, { toBool, toNum } from './mixins/alertClientCore'
31
- import config from './mixins/config'
32
- import utils from './mixins/utils'
32
+ import { useAlertClient, toBool, toNum } from './composables/useAlertClient'
33
+ import type { Language, WarningsDataResponse } from '@/types'
33
34
 
34
- export default {
35
- name: 'AlertClientVue',
36
- components: {
37
- AlertClient,
38
- },
39
- mixins: [config, utils, alertClientCore],
40
- props: {
41
- currentDate: {
42
- type: [String, Date],
43
- default: null,
44
- },
45
- baseUrl: {
46
- type: String,
47
- default: 'https://www.ilmatieteenlaitos.fi/geoserver/alert/ows',
48
- },
49
- selectedDay: {
50
- type: [String, Number],
51
- default: 0,
52
- },
53
- regionListEnabled: {
54
- type: [String, Boolean],
55
- default: true,
56
- },
57
- spinnerEnabled: {
58
- type: [String, Boolean],
59
- default: true,
60
- },
61
- grayScaleSelector: {
62
- type: [String, Boolean],
63
- default: true,
64
- },
65
- staticDays: {
66
- type: [String, Boolean],
67
- default: true,
68
- },
69
- startFrom: {
70
- type: String,
71
- default: '',
72
- },
73
- weatherUpdated: {
74
- type: String,
75
- default: '',
76
- },
77
- floodUpdated: {
78
- type: String,
79
- default: '',
80
- },
81
- weatherWarnings: {
82
- type: String,
83
- default: '',
84
- },
85
- floodWarnings: {
86
- type: String,
87
- default: '',
88
- },
89
- warnings: {
90
- type: [String, Object],
91
- default: null,
92
- },
93
- dailyWarningTypes: {
94
- type: [String, Array],
95
- default: () => [],
96
- },
97
- refreshInterval: {
98
- type: [String, Number],
99
- default: 900000, // 1000 * 60 * 15
100
- },
101
- geometryId: {
102
- type: [String, Number],
103
- default: () => Number(config.props.defaultGeometryId) || 2021,
104
- },
105
- language: {
106
- type: String,
107
- default: import.meta.env.VITE_LANGUAGE || 'fi',
108
- },
109
- theme: {
110
- type: String,
111
- default: 'light',
112
- },
113
- fontScale: {
114
- type: [String, Number],
115
- default: 1,
116
- },
117
- sleep: {
118
- type: [String, Boolean],
119
- default: true,
120
- },
121
- debugMode: {
122
- type: [String, Boolean],
123
- default: false,
124
- },
35
+ // Default geometry ID from config
36
+ const DEFAULT_GEOMETRY_ID = 2021
37
+
38
+ // Props definition
39
+ const props = withDefaults(
40
+ defineProps<{
41
+ currentDate?: string | Date | null
42
+ baseUrl?: string
43
+ selectedDay?: string | number
44
+ regionListEnabled?: string | boolean
45
+ spinnerEnabled?: string | boolean
46
+ grayScaleSelector?: string | boolean
47
+ staticDays?: string | boolean
48
+ startFrom?: string
49
+ weatherUpdated?: string
50
+ floodUpdated?: string
51
+ weatherWarnings?: string
52
+ floodWarnings?: string
53
+ warnings?: string | WarningsDataResponse | null
54
+ dailyWarningTypes?: string | string[]
55
+ refreshInterval?: string | number
56
+ geometryId?: string | number
57
+ language?: Language | string
58
+ theme?: string
59
+ fontScale?: string | number
60
+ sleep?: string | boolean
61
+ debugMode?: string | boolean
62
+ }>(),
63
+ {
64
+ currentDate: null,
65
+ baseUrl: 'https://www.ilmatieteenlaitos.fi/geoserver/alert/ows',
66
+ selectedDay: 0,
67
+ regionListEnabled: true,
68
+ spinnerEnabled: true,
69
+ grayScaleSelector: true,
70
+ staticDays: true,
71
+ startFrom: '',
72
+ weatherUpdated: '',
73
+ floodUpdated: '',
74
+ weatherWarnings: '',
75
+ floodWarnings: '',
76
+ warnings: null,
77
+ dailyWarningTypes: () => [],
78
+ refreshInterval: 900000, // 1000 * 60 * 15
79
+ geometryId: DEFAULT_GEOMETRY_ID,
80
+ language: (import.meta.env.VITE_LANGUAGE as Language) || 'fi',
81
+ theme: 'light',
82
+ fontScale: 1,
83
+ sleep: true,
84
+ debugMode: false,
85
+ }
86
+ )
87
+
88
+ // Type normalizers for mixed-type props
89
+ const selectedDayNormalized = computed(() => toNum(props.selectedDay, 0))
90
+ const regionListEnabledNormalized = computed(() =>
91
+ toBool(props.regionListEnabled, true)
92
+ )
93
+ const grayScaleSelectorNormalized = computed(() =>
94
+ toBool(props.grayScaleSelector, true)
95
+ )
96
+ const staticDaysNormalized = computed(() => toBool(props.staticDays, true))
97
+ const dailyWarningTypesNormalized = computed<string[]>(() => {
98
+ if (Array.isArray(props.dailyWarningTypes)) return props.dailyWarningTypes
99
+ if (typeof props.dailyWarningTypes === 'string' && props.dailyWarningTypes) {
100
+ return props.dailyWarningTypes.split(',').map((item) => item.trim())
101
+ }
102
+ return []
103
+ })
104
+ const refreshIntervalNormalized = computed(() =>
105
+ toNum(props.refreshInterval, 900000)
106
+ )
107
+ const geometryIdNormalized = computed(() => toNum(props.geometryId, 2021))
108
+ const sleepNormalized = computed(() => toBool(props.sleep, true))
109
+ const spinnerEnabledNormalized = computed(() =>
110
+ toBool(props.spinnerEnabled, true)
111
+ )
112
+ const debugModeNormalized = computed(() => toBool(props.debugMode, false))
113
+
114
+ // Setup composable with refs to props
115
+ const {
116
+ loading,
117
+ themeClass,
118
+ warningsData,
119
+ visible,
120
+ currentTime,
121
+ onLoaded,
122
+ onThemeChanged,
123
+ fetchWarnings,
124
+ initializeWarnings,
125
+ applyFontScale,
126
+ } = useAlertClient({
127
+ baseUrl: toRef(props, 'baseUrl'),
128
+ language: toRef(props, 'language') as ReturnType<typeof toRef<Language>>,
129
+ theme: toRef(props, 'theme'),
130
+ warnings: toRef(props, 'warnings'),
131
+ currentDate: toRef(props, 'currentDate'),
132
+ fontScale: toRef(props, 'fontScale'),
133
+ debugMode: debugModeNormalized,
134
+ weatherUpdated: toRef(props, 'weatherUpdated'),
135
+ floodUpdated: toRef(props, 'floodUpdated'),
136
+ weatherWarnings: toRef(props, 'weatherWarnings'),
137
+ floodWarnings: toRef(props, 'floodWarnings'),
138
+ })
139
+
140
+ // Initialize warnings from props (equivalent to created hook)
141
+ initializeWarnings()
142
+
143
+ // Apply font scale on mount
144
+ onMounted(() => {
145
+ applyFontScale()
146
+ })
147
+
148
+ // Expose methods for parent components
149
+ defineExpose({
150
+ show: () => {
151
+ visible.value = true
125
152
  },
126
- computed: {
127
- // Type normalizers for mixed-type props
128
- selectedDayNormalized() {
129
- return toNum(this.selectedDay, 0)
130
- },
131
- regionListEnabledNormalized() {
132
- return toBool(this.regionListEnabled, true)
133
- },
134
- grayScaleSelectorNormalized() {
135
- return toBool(this.grayScaleSelector, true)
136
- },
137
- staticDaysNormalized() {
138
- return toBool(this.staticDays, true)
139
- },
140
- dailyWarningTypesNormalized() {
141
- if (Array.isArray(this.dailyWarningTypes)) return this.dailyWarningTypes
142
- if (
143
- typeof this.dailyWarningTypes === 'string' &&
144
- this.dailyWarningTypes
145
- ) {
146
- return this.dailyWarningTypes.split(',').map((item) => item.trim())
147
- }
148
- return []
149
- },
150
- refreshIntervalNormalized() {
151
- return toNum(this.refreshInterval, 900000)
152
- },
153
- geometryIdNormalized() {
154
- return toNum(this.geometryId, 2021)
155
- },
156
- sleepNormalized() {
157
- return toBool(this.sleep, true)
158
- },
159
- spinnerEnabledNormalized() {
160
- return toBool(this.spinnerEnabled, true)
161
- },
162
- debugModeNormalized() {
163
- return toBool(this.debugMode, false)
164
- },
153
+ hide: () => {
154
+ visible.value = false
165
155
  },
166
- }
156
+ })
167
157
  </script>
168
158
  <style lang="scss">
169
159
  @import './scss/utilities.scss';