@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,5 +1,3 @@
1
- import 'url-search-params-polyfill'
2
-
3
1
  import { DOMParser } from '@xmldom/xmldom'
4
2
  import he from 'he'
5
3
  import xpath from 'xpath'
@@ -43,7 +41,7 @@ export default {
43
41
  extreme: 4,
44
42
  }),
45
43
  strokeColor() {
46
- return this.colors[this.theme].stroke
44
+ return this.colors?.[this.theme]?.stroke || 'DarkSlateGray'
47
45
  },
48
46
  bluePaths() {
49
47
  return this.paths({
@@ -76,10 +74,10 @@ export default {
76
74
  },
77
75
  overlayPaths() {
78
76
  return this.regionIds.reduce((regions, regionId) => {
77
+ const region = this.geometries?.[this.geometryId]?.[regionId]
79
78
  if (
80
- this.geometries[this.geometryId][regionId].pathLarge &&
81
- (this.geometries[this.geometryId][regionId].type === 'land' ||
82
- this.geometries[this.geometryId][regionId].subType === 'lake')
79
+ region?.pathLarge &&
80
+ (region.type === 'land' || region.subType === 'lake')
83
81
  ) {
84
82
  const visualization = this.regionVisualization(regionId)
85
83
  regions.push({
@@ -140,7 +138,10 @@ export default {
140
138
  return [
141
139
  {
142
140
  key: `border.${area}`,
143
- d: this.geometries[this.geometryId]['borders'][area][`path${this.size}`],
141
+ d:
142
+ this.geometries?.[this.geometryId]?.borders?.[area]?.[
143
+ `path${this.size}`
144
+ ] || '',
144
145
  opacity: '1',
145
146
  strokeWidth: this.strokeWidth,
146
147
  },
@@ -195,7 +196,9 @@ export default {
195
196
  },
196
197
  msSinceStartOfDay(timestamp) {
197
198
  const moment = this.toTimeZone(timestamp)
198
- const ms = ((moment.hour * 60 + moment.minute) * 60 + moment.second) * 1000 + moment.millisecond
199
+ const ms =
200
+ ((moment.hour * 60 + moment.minute) * 60 + moment.second) * 1000 +
201
+ moment.millisecond
199
202
  // Daylight saving time
200
203
  const ref = this.toTimeZone(timestamp - ms)
201
204
  if (ref.day !== moment.day) {
@@ -547,8 +550,10 @@ export default {
547
550
  ;[...this.warnings[key].covRegions.keys()].forEach((covRegion) => {
548
551
  if (
549
552
  (this.coverageRegions[covRegion] == null ||
550
- this.coverageRegions[covRegion] < this.warnings[key].severity) &&
551
- this.warnings[key].covRegions.get(covRegion) >= this.coverageCriterion
553
+ this.coverageRegions[covRegion] <
554
+ this.warnings[key].severity) &&
555
+ this.warnings[key].covRegions.get(covRegion) >=
556
+ this.coverageCriterion
552
557
  ) {
553
558
  this.coverageRegions[covRegion] = this.warnings[key].severity
554
559
  }
@@ -762,8 +767,10 @@ export default {
762
767
  return typeof document !== 'undefined' && document
763
768
  },
764
769
  regionData(regionId) {
765
- const regionType = this.geometries[this.geometryId][regionId].type
766
- return this.input[regionType].find(
770
+ const region = this.geometries?.[this.geometryId]?.[regionId]
771
+ if (!region) return null
772
+ const regionType = region.type
773
+ return this.input?.[regionType]?.find(
767
774
  (regionData) => regionData.key === regionId
768
775
  )
769
776
  },
@@ -796,10 +803,11 @@ export default {
796
803
  const severity = this.regionSeverity(regionId)
797
804
  const isLand =
798
805
  this.geometries[this.geometryId][regionId].type === this.REGION_LAND
806
+ const themeColors = this.colors?.[this.theme]
799
807
  const color =
800
808
  severity || isLand
801
- ? this.colors[this.theme].levels[severity]
802
- : this.colors[this.theme].sea
809
+ ? themeColors?.levels?.[severity] || '#cccccc'
810
+ : themeColors?.sea || '#add8e6'
803
811
  const visible = severity > 0 || geom.subType !== this.REGION_LAKE
804
812
  return {
805
813
  geom,
@@ -810,18 +818,24 @@ export default {
810
818
  },
811
819
  // Include also lakes to prevent overlapping symbols in Saimaa
812
820
  optimizeCovRegions(warnings, regions) {
813
- Object.keys(this.geometries[this.geometryId]).filter((regionId) =>
814
- this.geometries[this.geometryId][regionId]?.type === 'sea' &&
815
- this.geometries[this.geometryId][regionId]?.subType === 'lake'
816
- ).filter((regionId) => regions.some((day) =>
817
- day['sea'].some((region) => region['key'] === regionId
818
- ))).forEach((regionId) =>
819
- Object.keys(warnings).filter((warningKey) =>
820
- warnings[warningKey].covRegions.size > 0
821
- ).forEach((warningKey) => {
822
- warnings[warningKey].covRegions.set(regionId, 0)
823
- }
824
- ))
821
+ Object.keys(this.geometries[this.geometryId])
822
+ .filter(
823
+ (regionId) =>
824
+ this.geometries[this.geometryId][regionId]?.type === 'sea' &&
825
+ this.geometries[this.geometryId][regionId]?.subType === 'lake'
826
+ )
827
+ .filter((regionId) =>
828
+ regions.some((day) =>
829
+ day.sea.some((region) => region.key === regionId)
830
+ )
831
+ )
832
+ .forEach((regionId) =>
833
+ Object.keys(warnings)
834
+ .filter((warningKey) => warnings[warningKey].covRegions.size > 0)
835
+ .forEach((warningKey) => {
836
+ warnings[warningKey].covRegions.set(regionId, 0)
837
+ })
838
+ )
825
839
  },
826
840
  regionsDefault() {
827
841
  return [
@@ -1,3 +1,3 @@
1
1
  export const pluginsWrapper = {
2
- install(GivenVue) {},
2
+ install() {},
3
3
  }
@@ -0,0 +1,193 @@
1
+ // Responsive display utilities
2
+ .mobile-only {
3
+ display: block;
4
+ @media (min-width: 768px) {
5
+ display: none;
6
+ }
7
+ }
8
+
9
+ .desktop-only {
10
+ display: none;
11
+ @media (min-width: 768px) {
12
+ display: block;
13
+ }
14
+ }
15
+
16
+ .d-none {
17
+ display: none !important;
18
+ }
19
+
20
+ .d-block {
21
+ display: block !important;
22
+ }
23
+
24
+ // Medium breakpoint and up (768px+)
25
+ @media (min-width: 768px) {
26
+ .d-md-none {
27
+ display: none !important;
28
+ }
29
+
30
+ .d-md-block {
31
+ display: block !important;
32
+ }
33
+
34
+ .d-md-table-cell {
35
+ display: table-cell !important;
36
+ }
37
+ }
38
+
39
+ // Display inline
40
+ .d-inline {
41
+ display: inline !important;
42
+ }
43
+
44
+ // For screen readers
45
+ .visually-hidden {
46
+ position: absolute;
47
+ width: 1px;
48
+ height: 1px;
49
+ padding: 0;
50
+ margin: -1px;
51
+ overflow: hidden;
52
+ clip: rect(0, 0, 0, 0);
53
+ white-space: nowrap;
54
+ border: 0;
55
+ }
56
+
57
+ // Accessibility utilities
58
+ .visually-hidden-focusable {
59
+ position: absolute !important;
60
+ width: 1px !important;
61
+ height: 1px !important;
62
+ padding: 0 !important;
63
+ margin: -1px !important;
64
+ overflow: hidden !important;
65
+ clip: rect(0, 0, 0, 0) !important;
66
+ white-space: nowrap !important;
67
+ border: 0 !important;
68
+
69
+ &:focus,
70
+ &:focus-within {
71
+ position: static !important;
72
+ width: auto !important;
73
+ height: auto !important;
74
+ overflow: visible !important;
75
+ clip: auto !important;
76
+ white-space: normal !important;
77
+ }
78
+ }
79
+
80
+ .focus-ring {
81
+ outline: none;
82
+
83
+ &:focus {
84
+ outline: 2px solid #0d6efd;
85
+ outline-offset: 2px;
86
+ }
87
+
88
+ &:focus:not(:focus-visible) {
89
+ outline: none;
90
+ }
91
+
92
+ &:focus-visible {
93
+ outline: 2px solid #0d6efd;
94
+ outline-offset: 2px;
95
+ }
96
+ }
97
+
98
+ // Small breakpoint specific (576px+)
99
+ @media (min-width: 576px) {
100
+ .d-sm-block {
101
+ display: block !important;
102
+ }
103
+
104
+ .d-sm-none {
105
+ display: none !important;
106
+ }
107
+ }
108
+
109
+ // Spacing utilities
110
+ .mb-1 {
111
+ margin-bottom: 0.25rem;
112
+ }
113
+
114
+ .p-0 {
115
+ padding: 0;
116
+ }
117
+
118
+ .p-1 {
119
+ padding: 0.25rem;
120
+ }
121
+
122
+ // Position utilities
123
+ .sticky-top {
124
+ position: sticky;
125
+ top: 0;
126
+ z-index: 1020;
127
+ }
128
+
129
+ // Grid/Layout utilities
130
+ .row {
131
+ display: flex;
132
+ flex-wrap: wrap;
133
+ margin-left: 0;
134
+ margin-right: 0;
135
+ }
136
+
137
+ // Tab navigation utilities
138
+ .nav {
139
+ display: flex;
140
+ flex-wrap: wrap;
141
+ padding-left: 0;
142
+ margin-bottom: 0;
143
+ list-style: none;
144
+ }
145
+
146
+ .nav-tabs {
147
+ border-bottom: none;
148
+ }
149
+
150
+ .nav-item {
151
+ margin-bottom: 0;
152
+ }
153
+
154
+ .nav-link {
155
+ display: block;
156
+ padding: 0;
157
+ text-decoration: none;
158
+ background: none;
159
+ border: none;
160
+ cursor: pointer;
161
+ }
162
+
163
+ .tab-content {
164
+ > .tab-pane {
165
+ display: none;
166
+
167
+ &.active,
168
+ &.show {
169
+ display: block;
170
+ }
171
+ }
172
+
173
+ .spinner-border {
174
+ display: inline-block;
175
+ width: 2rem;
176
+ height: 2rem;
177
+ vertical-align: text-bottom;
178
+ border: 0.25em solid currentColor;
179
+ border-right-color: transparent;
180
+ border-radius: 50%;
181
+ animation: spinner-border-animation 0.75s linear infinite;
182
+ }
183
+
184
+ @keyframes spinner-border-animation {
185
+ to {
186
+ transform: rotate(360deg);
187
+ }
188
+ }
189
+
190
+ .text-center {
191
+ text-align: center !important;
192
+ }
193
+ }
@@ -35,7 +35,8 @@ $popup-width: 250px;
35
35
  font-family: 'Noto Sans';
36
36
  font-weight: normal;
37
37
  font-style: normal;
38
- src: url($font-base-path + 'Noto_Sans/Noto-Sans-regular.woff2') format('woff2');
38
+ src: url($font-base-path + 'Noto_Sans/Noto-Sans-regular.woff2')
39
+ format('woff2');
39
40
  font-display: swap;
40
41
  }
41
42
  $font-family: Roboto, Helvetica, Arial, 'Noto Sans', sans-serif;
@@ -1,8 +1,9 @@
1
- @import "./constants.scss";
1
+ @use 'sass:string';
2
+ @import './constants.scss';
2
3
 
3
4
  @for $i from 0 through 72 {
4
5
  $angle: 5 * $i;
5
- $rotationDegree: unquote($angle + 'deg');
6
+ $rotationDegree: string.unquote($angle + 'deg');
6
7
  .transform-rotate-#{$angle} {
7
8
  transform: rotate($rotationDegree);
8
9
  }
@@ -101,7 +102,11 @@ div.symbol-image {
101
102
  }
102
103
 
103
104
  @media (forced-colors: active) {
104
- .level-0, .level-1, .level-2, .level-3, .level-4 {
105
+ .level-0,
106
+ .level-1,
107
+ .level-2,
108
+ .level-3,
109
+ .level-4 {
105
110
  forced-color-adjust: none;
106
111
  }
107
112
  }
package/src/vue.js ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Vue component exports for use in Vue/Nuxt applications.
3
+ *
4
+ * Usage:
5
+ * import { SmartMetAlertClient } from '@fmidev/smartmet-alert-client/vue'
6
+ * import '@fmidev/smartmet-alert-client/vue/style.css'
7
+ *
8
+ * <SmartMetAlertClient
9
+ * :refresh-interval="900000"
10
+ * :selected-day="0"
11
+ * :geometry-id="2021"
12
+ * language="fi"
13
+ * />
14
+ *
15
+ * Props:
16
+ * language String 'fi' Language code: 'fi', 'sv', 'en'
17
+ * theme String 'light' Theme: 'light', 'dark', 'light-gray', 'dark-gray'
18
+ * geometry-id String|Number 2021 Geometry version ID
19
+ * selected-day String|Number 0 Initially selected day (0-4)
20
+ * refresh-interval String|Number 900000 Data refresh interval in ms (0 to disable)
21
+ * region-list-enabled String|Boolean true Show region list below the map
22
+ * static-days String|Boolean true Use static day labels
23
+ * spinner-enabled String|Boolean true Show loading spinner
24
+ * gray-scale-selector String|Boolean true Show grayscale theme toggle
25
+ * sleep String|Boolean true Pause updates when tab is hidden
26
+ * font-scale String|Number 1 Font size multiplier
27
+ * base-url String (FMI GeoServer) Base URL for WFS queries
28
+ * start-from String '' Start date override (ISO format)
29
+ * current-date String|Date null Override current date for testing
30
+ * warnings String|Object null Pre-loaded warnings data (skip fetch)
31
+ * weather-warnings String '' Custom weather warnings query
32
+ * flood-warnings String '' Custom flood warnings query
33
+ * weather-updated String '' Custom weather update time query
34
+ * flood-updated String '' Custom flood update time query
35
+ * daily-warning-types String|Array [] Filter specific warning types
36
+ * debug-mode String|Boolean false Enable debug logging
37
+ */
38
+ import AlertClientVue from './AlertClientVue.vue'
39
+
40
+ export { AlertClientVue as SmartMetAlertClient }
41
+ export default AlertClientVue
package/svgo.config.js ADDED
@@ -0,0 +1,45 @@
1
+ export default {
2
+ multipass: true,
3
+ js2svg: {
4
+ indent: 2,
5
+ pretty: true,
6
+ },
7
+ plugins: [
8
+ {
9
+ name: 'preset-default',
10
+ params: {
11
+ overrides: {
12
+ // Keep useful IDs in case they're referenced
13
+ cleanupIds: {
14
+ minify: false,
15
+ },
16
+ },
17
+ },
18
+ },
19
+ // Keep viewBox as it's needed for responsive scaling
20
+ {
21
+ name: 'removeViewBox',
22
+ active: false,
23
+ },
24
+ // Remove XML declaration (<?xml version="1.0"?>)
25
+ 'removeXMLProcInst',
26
+ // Remove unnecessary xmlns:xlink
27
+ {
28
+ name: 'removeUnusedNS',
29
+ },
30
+ // Clean up numeric values (reduce decimal precision with rounding)
31
+ {
32
+ name: 'cleanupNumericValues',
33
+ params: {
34
+ floatPrecision: 3,
35
+ },
36
+ },
37
+ // Convert style attributes to attributes where possible
38
+ {
39
+ name: 'convertStyleToAttrs',
40
+ params: {
41
+ keepImportant: true,
42
+ },
43
+ },
44
+ ],
45
+ }