@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
package/.eslintignore CHANGED
@@ -1,17 +1,5 @@
1
- # package directories
2
1
  node_modules
3
- jspm_packages
4
2
  dist
3
+ src/assets
4
+ src/mixins/geojsonsvg.js
5
5
 
6
- # Serverless directories
7
- .serverless
8
-
9
- #nuxt compile dir
10
- .nuxt
11
-
12
- # env file
13
- .env
14
-
15
- static
16
- staticS3
17
- assets
@@ -0,0 +1,26 @@
1
+ name: Test
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ test:
9
+ name: Test
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - name: Code checkout
14
+ uses: actions/checkout@v5
15
+ with:
16
+ persist-credentials: false
17
+
18
+ - name: Setup Node.js
19
+ uses: actions/setup-node@v6
20
+ with:
21
+ node-version-file: '.nvmrc'
22
+
23
+ - name: Lint
24
+ run: |
25
+ npm install
26
+ npm run lint
package/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ 24
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,40 +1,66 @@
1
1
  {
2
2
  "name": "@fmidev/smartmet-alert-client",
3
- "version": "4.4.19",
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",
7
7
  "main": "dist/index.mjs",
8
+ "module": "dist/index.mjs",
8
9
  "type": "module",
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.js"
14
+ },
15
+ "./vue": {
16
+ "import": "./dist/vue/index.mjs",
17
+ "types": "./dist/vue/index.d.ts"
18
+ },
19
+ "./vue/style.css": "./dist/vue/style.css"
20
+ },
9
21
  "repository": {
10
22
  "type": "git",
11
23
  "url": "git+https://github.com/fmidev/smartmet-alert-client.git"
12
24
  },
13
25
  "scripts": {
14
26
  "dev": "vite",
15
- "build": "vite build",
27
+ "build": "vue-tsc --noEmit && vite build && npm run build:vue",
28
+ "build:ce": "vite build",
29
+ "build:vue": "BUILD_MODE=vue vite build",
16
30
  "serve": "vite preview",
17
- "lint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src",
18
- "test": "vitest --reporter=verbose --disable-console-intercept"
31
+ "typecheck": "vue-tsc --noEmit",
32
+ "lint": "eslint --ext .js,.ts,.vue src",
33
+ "lint:fix": "eslint --ext .js,.ts,.vue --fix src",
34
+ "test": "vitest",
35
+ "test:ui": "vitest --ui",
36
+ "test:coverage": "vitest --coverage",
37
+ "test:run": "vitest run",
38
+ "optimize:svg": "svgo -f src/assets/img --recursive"
19
39
  },
20
40
  "dependencies": {
41
+ "@panzoom/panzoom": "4.6.0",
21
42
  "@xmldom/xmldom": "0.8.10",
22
- "bootstrap": "5.3.3",
23
- "bootstrap-vue-next": "0.14.10",
24
43
  "cross-fetch": "3.1.8",
25
44
  "dompurify": "3.2.7",
26
45
  "flatbush": "3.2.1",
27
- "focus-visible": "5.2.1",
28
46
  "he": "1.2.0",
29
47
  "svgpath": "2.6.0",
30
- "url-search-params-polyfill": "8.2.5",
31
48
  "vue": "3.4.38",
32
49
  "vue-web-component-wrapper": "1.7.5",
33
50
  "xpath": "0.0.34"
34
51
  },
35
52
  "devDependencies": {
53
+ "@testing-library/jest-dom": "6.9.1",
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",
36
59
  "@vitejs/plugin-vue": "4.5.2",
60
+ "@vitest/coverage-v8": "3.2.4",
61
+ "@vitest/ui": "3.2.4",
37
62
  "@vue/eslint-config-standard": "8.0.1",
63
+ "@vue/test-utils": "2.4.6",
38
64
  "eslint": "8.55.0",
39
65
  "eslint-config-prettier": "9.1.0",
40
66
  "eslint-plugin-node": "11.1.0",
@@ -43,34 +69,69 @@
43
69
  "eslint-plugin-sonarjs": "0.23.0",
44
70
  "eslint-plugin-vue": "9.19.2",
45
71
  "fs-extra": "11.2.0",
46
- "playwright": "1.41.2",
72
+ "happy-dom": "20.0.10",
73
+ "jsdom": "27.0.1",
47
74
  "postcss-prefixwrap": "1.53.0",
48
75
  "postcss-url": "10.1.3",
49
76
  "prettier": "3.1.1",
50
77
  "rollup-plugin-visualizer": "5.11.0",
78
+ "sass": "1.93.2",
79
+ "svgo": "4.0.0",
80
+ "typescript": "5.9.3",
51
81
  "unplugin-vue-components": "0.26.0",
52
82
  "vite": "5.1.8",
53
83
  "vite-plugin-banner": "0.7.1",
54
84
  "vite-plugin-static-copy": "1.0.6",
55
- "vitest": "1.6.0"
85
+ "vitest": "3.2.4",
86
+ "vue-tsc": "3.2.0"
56
87
  },
57
88
  "eslintConfig": {
89
+ "root": true,
58
90
  "env": {
59
- "node": true,
60
- "es2022": true
91
+ "node": true
92
+ },
93
+ "parser": "vue-eslint-parser",
94
+ "parserOptions": {
95
+ "parser": "@typescript-eslint/parser",
96
+ "ecmaVersion": 2020,
97
+ "sourceType": "module"
61
98
  },
62
99
  "extends": [
63
- "@vue/standard",
64
- "plugin:vue/recommended",
65
- "plugin:sonarjs/recommended",
66
- "plugin:prettier/recommended"
100
+ "plugin:vue/vue3-recommended",
101
+ "eslint:recommended",
102
+ "plugin:@typescript-eslint/recommended",
103
+ "plugin:sonarjs/recommended"
67
104
  ],
68
105
  "plugins": [
69
- "simple-import-sort"
106
+ "@typescript-eslint"
70
107
  ],
71
108
  "rules": {
72
- "simple-import-sort/imports": "error",
73
- "simple-import-sort/exports": "error"
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",
118
+ "vue/html-self-closing": "off",
119
+ "vue/require-explicit-emits": "off",
120
+ "vue/singleline-html-element-content-newline": "off",
121
+ "vue/no-v-for-template-key": "off",
122
+ "vue/multi-word-component-names": "off",
123
+ "vue/no-reserved-component-names": "off",
124
+ "vue/require-default-prop": "off",
125
+ "vue/require-prop-types": "off",
126
+ "vue/no-v-html": "off",
127
+ "vue/v-on-event-hyphenation": "off",
128
+ "vue/html-closing-bracket-newline": "off",
129
+ "vue/max-attributes-per-line": "off",
130
+ "vue/html-indent": "off",
131
+ "vue/order-in-components": "off",
132
+ "sonarjs/cognitive-complexity": "off",
133
+ "sonarjs/no-duplicate-string": "off",
134
+ "sonarjs/no-nested-switch": "off"
74
135
  }
75
136
  },
76
137
  "prettier": {
@@ -85,9 +146,6 @@
85
146
  "url": "https://github.com/fmidev/smartmet-alert-client/issues"
86
147
  },
87
148
  "homepage": "https://github.com/fmidev/smartmet-alert-client#readme",
88
- "directories": {
89
- "test": "test"
90
- },
91
149
  "engines": {
92
150
  "npm": ">=8.0.0",
93
151
  "node": ">=14.0.0"
@@ -0,0 +1,160 @@
1
+ <template>
2
+ <AlertClient
3
+ v-if="visible"
4
+ :refresh-interval="refreshIntervalNormalized"
5
+ :default-day="selectedDayNormalized"
6
+ :static-days="staticDaysNormalized"
7
+ :start-from="startFrom"
8
+ :region-list-enabled="regionListEnabledNormalized"
9
+ :gray-scale-selector="grayScaleSelectorNormalized"
10
+ :current-time="currentTime"
11
+ :warnings-data="warningsData"
12
+ :daily-warning-types="dailyWarningTypesNormalized"
13
+ :geometry-id="geometryIdNormalized"
14
+ :language="language as Language"
15
+ :theme="themeClass"
16
+ :sleep="sleepNormalized"
17
+ :loading="loading"
18
+ :spinner-enabled="spinnerEnabledNormalized"
19
+ @loaded="onLoaded"
20
+ @theme-changed="onThemeChanged"
21
+ @update-warnings="fetchWarnings" />
22
+ </template>
23
+ <script setup lang="ts">
24
+ /**
25
+ * Vue wrapper component for AlertClient.
26
+ * Accepts native JavaScript types (Number, Boolean) unlike the web component
27
+ * version which only accepts strings.
28
+ */
29
+ import { computed, toRef, onMounted } from 'vue'
30
+
31
+ import AlertClient from './components/AlertClient.vue'
32
+ import { useAlertClient, toBool, toNum } from './composables/useAlertClient'
33
+ import type { Language, WarningsDataResponse } from '@/types'
34
+
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
152
+ },
153
+ hide: () => {
154
+ visible.value = false
155
+ },
156
+ })
157
+ </script>
158
+ <style lang="scss">
159
+ @import './scss/utilities.scss';
160
+ </style>