@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
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Internationalization composable
3
+ *
4
+ * Provides translation function for the alert client.
5
+ */
6
+
7
+ import DOMPurify from 'dompurify'
8
+ import { isRef, type MaybeRef } from 'vue'
9
+ import type { Language } from '@/types'
10
+
11
+ import en from '@/locales/en.json'
12
+ import fi from '@/locales/fi.json'
13
+ import sv from '@/locales/sv.json'
14
+
15
+ type TranslationKey = keyof typeof fi | keyof typeof en | keyof typeof sv
16
+
17
+ const translations: Record<Language, Record<string, string>> = {
18
+ en,
19
+ fi,
20
+ sv,
21
+ }
22
+
23
+ export interface UseI18nReturn {
24
+ t: (key: string | null | undefined) => string
25
+ }
26
+
27
+ /**
28
+ * i18n composable for translations
29
+ *
30
+ * @param language - Language code, can be a ref or plain string
31
+ * @returns Object with translation function t()
32
+ */
33
+ export function useI18n(
34
+ language: MaybeRef<Language | string | null | undefined>
35
+ ): UseI18nReturn {
36
+ /**
37
+ * Translate a key to the current language
38
+ * Returns empty string if language or key is not set
39
+ * Sanitizes output with DOMPurify to prevent XSS
40
+ */
41
+ function t(key: string | null | undefined): string {
42
+ const langValue = isRef(language) ? language.value : language
43
+
44
+ if (langValue == null || key == null) {
45
+ return ''
46
+ }
47
+
48
+ const lang = langValue as Language
49
+ const translationMap = translations[lang]
50
+
51
+ if (!translationMap) {
52
+ return ''
53
+ }
54
+
55
+ const translation = translationMap[key as TranslationKey]
56
+ return translation ? DOMPurify.sanitize(translation) : ''
57
+ }
58
+
59
+ return {
60
+ t,
61
+ }
62
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Keyboard navigation key codes composable
3
+ *
4
+ * Provides key code constants for keyboard navigation in the alert client.
5
+ */
6
+
7
+ import { KEY_CODES } from '@/types'
8
+
9
+ export function useKeyCodes() {
10
+ return {
11
+ KEY_CODE_END: KEY_CODES.END,
12
+ KEY_CODE_HOME: KEY_CODES.HOME,
13
+ KEY_CODE_LEFT: KEY_CODES.LEFT,
14
+ KEY_CODE_RIGHT: KEY_CODES.RIGHT,
15
+ }
16
+ }
@@ -0,0 +1,477 @@
1
+ /**
2
+ * Map paths composable
3
+ *
4
+ * Provides computed path data for map visualization components.
5
+ * Extracts path-related logic from utils mixin.
6
+ */
7
+
8
+ import { computed, ref, type Ref, type ComputedRef } from 'vue'
9
+ import type {
10
+ WarningsMap,
11
+ DayRegions,
12
+ Theme,
13
+ Severity,
14
+ GeometryCollection,
15
+ ThemeColorMap,
16
+ RegionGeometry,
17
+ } from '@/types'
18
+ import { isRegionGeometry } from '@/types'
19
+ import { useConfig, REGION_LAND, REGION_SEA, REGION_LAKE } from './useConfig'
20
+
21
+ // ============================================================================
22
+ // Types
23
+ // ============================================================================
24
+
25
+ export interface PathData {
26
+ key: string
27
+ fill: string
28
+ d: string
29
+ opacity: string
30
+ dataRegion?: string
31
+ dataSeverity?: Severity
32
+ strokeWidth: number | string
33
+ }
34
+
35
+ export interface BorderPath {
36
+ key: string
37
+ d: string
38
+ opacity: string
39
+ strokeWidth: number | string
40
+ }
41
+
42
+ export interface CoverageData {
43
+ key: string
44
+ d: string
45
+ fillOpacity: number
46
+ strokeWidth: number | string
47
+ fill: string
48
+ }
49
+
50
+ export interface PathOptions {
51
+ type: string
52
+ severity?: Severity | null
53
+ }
54
+
55
+ export interface RegionVisualization {
56
+ geom: RegionGeometry
57
+ severity: Severity
58
+ color: string
59
+ visible: boolean
60
+ }
61
+
62
+ export interface RegionWarningItem {
63
+ key: string
64
+ warnings: Array<{
65
+ type: string
66
+ coverage: number
67
+ identifiers: string[]
68
+ }>
69
+ }
70
+
71
+ // ============================================================================
72
+ // Composable Options
73
+ // ============================================================================
74
+
75
+ export interface UseMapPathsOptions {
76
+ /** Map size: 'Large' or 'Small' */
77
+ size: Ref<'Large' | 'Small'>
78
+ /** Day index (0-4) */
79
+ index: Ref<number>
80
+ /** Day regions data */
81
+ input: Ref<DayRegions>
82
+ /** All warnings map */
83
+ warnings: Ref<WarningsMap | null>
84
+ /** Currently visible warning types */
85
+ visibleWarnings: Ref<string[]>
86
+ /** Geometry ID */
87
+ geometryId: Ref<number>
88
+ /** Current theme */
89
+ theme: Ref<Theme | string>
90
+ /** Loading state */
91
+ loading: Ref<boolean>
92
+ /** Stroke width for paths */
93
+ strokeWidth: Ref<number | string>
94
+ }
95
+
96
+ export interface UseMapPathsReturn {
97
+ strokeColor: ComputedRef<string>
98
+ bluePaths: ComputedRef<PathData[]>
99
+ greenPaths: ComputedRef<PathData[]>
100
+ yellowPaths: ComputedRef<PathData[]>
101
+ orangePaths: ComputedRef<PathData[]>
102
+ redPaths: ComputedRef<PathData[]>
103
+ overlayPaths: ComputedRef<BorderPath[]>
104
+ landBorders: ComputedRef<BorderPath[]>
105
+ seaBorders: ComputedRef<BorderPath[]>
106
+ yellowCoverages: ComputedRef<CoverageData[]>
107
+ orangeCoverages: ComputedRef<CoverageData[]>
108
+ redCoverages: ComputedRef<CoverageData[]>
109
+ overlayCoverages: ComputedRef<CoverageData[]>
110
+ coverageRegions: Ref<Record<string, number>>
111
+ coverageWarnings: Ref<string[]>
112
+ regionData: (regionId: string) => RegionWarningItem | null
113
+ regionVisualization: (regionId: string) => RegionVisualization
114
+ }
115
+
116
+ // ============================================================================
117
+ // Composable
118
+ // ============================================================================
119
+
120
+ export function useMapPaths(options: UseMapPathsOptions): UseMapPathsReturn {
121
+ const {
122
+ size,
123
+ index,
124
+ input,
125
+ warnings,
126
+ visibleWarnings,
127
+ geometryId,
128
+ theme,
129
+ loading,
130
+ strokeWidth,
131
+ } = options
132
+
133
+ // Get config data
134
+ const config = useConfig()
135
+ const { geometries, colors, regionIds, coverageCriterion } = config
136
+
137
+ // Local state for coverage tracking
138
+ const coverageRegions = ref<Record<string, number>>({})
139
+ const coverageWarnings = ref<string[]>([])
140
+
141
+ // ============================================================================
142
+ // Helper Functions
143
+ // ============================================================================
144
+
145
+ /**
146
+ * Get region warning data for a specific region
147
+ */
148
+ function regionData(regionId: string): RegionWarningItem | null {
149
+ const geomData = geometries as GeometryCollection
150
+ const region = geomData?.[geometryId.value]?.[regionId]
151
+ if (!region || !isRegionGeometry(region)) return null
152
+
153
+ const regionType = region.type as 'land' | 'sea'
154
+ const dayInput = input.value
155
+ if (!dayInput) return null
156
+
157
+ return (
158
+ (dayInput[regionType] as RegionWarningItem[] | undefined)?.find(
159
+ (data: RegionWarningItem) => data.key === regionId
160
+ ) ?? null
161
+ )
162
+ }
163
+
164
+ /**
165
+ * Get severity for a region based on warnings
166
+ */
167
+ function regionSeverity(regionId: string): Severity {
168
+ const geomData = geometries as GeometryCollection
169
+ const region = regionData(regionId)
170
+ let severity: Severity = 0
171
+
172
+ if (region != null && warnings.value) {
173
+ const warningsMap = warnings.value
174
+ region.warnings.find((warning) => {
175
+ if (visibleWarnings.value.includes(warning.type)) {
176
+ const topIdentifier = warning.identifiers.find(
177
+ (id) => warningsMap[id] && warningsMap[id].covRegions.size === 0
178
+ )
179
+ if (topIdentifier != null && warningsMap[topIdentifier]) {
180
+ severity = warningsMap[topIdentifier].severity
181
+ return true
182
+ }
183
+ }
184
+ return false
185
+ })
186
+ }
187
+
188
+ const geomEntry = geomData[geometryId.value]?.[regionId]
189
+ const parentId = isRegionGeometry(geomEntry) ? geomEntry.parent : undefined
190
+ if (parentId) {
191
+ severity = Math.max(severity, regionSeverity(parentId)) as Severity
192
+ }
193
+
194
+ return severity
195
+ }
196
+
197
+ /**
198
+ * Get visualization data for a region
199
+ */
200
+ function regionVisualization(regionId: string): RegionVisualization {
201
+ const geomData = geometries as GeometryCollection
202
+ const geomEntry = geomData[geometryId.value]?.[regionId]
203
+ const severity = regionSeverity(regionId)
204
+
205
+ // Return default visualization if not a valid region geometry
206
+ if (!isRegionGeometry(geomEntry)) {
207
+ const themeColors = colors?.[theme.value as keyof ThemeColorMap]
208
+ return {
209
+ geom: {
210
+ name: regionId,
211
+ type: 'land',
212
+ parent: '',
213
+ children: [],
214
+ neighbours: [],
215
+ weight: 0,
216
+ center: [0, 0],
217
+ },
218
+ severity,
219
+ color: themeColors?.missing || '#cccccc',
220
+ visible: false,
221
+ }
222
+ }
223
+
224
+ const geom = geomEntry
225
+ const isLand = geom.type === REGION_LAND
226
+ const themeColors = colors?.[theme.value as keyof ThemeColorMap]
227
+ const color =
228
+ severity || isLand
229
+ ? themeColors?.levels?.[severity] || '#cccccc'
230
+ : themeColors?.sea || '#add8e6'
231
+ const visible = severity > 0 || geom.subType !== REGION_LAKE
232
+
233
+ return {
234
+ geom,
235
+ severity,
236
+ color,
237
+ visible,
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Get area borders (land or sea)
243
+ */
244
+ function areaBorders(area: 'land' | 'sea'): BorderPath[] {
245
+ const geomData = geometries as GeometryCollection
246
+ const pathKey = `path${size.value}` as 'pathLarge' | 'pathSmall'
247
+
248
+ return [
249
+ {
250
+ key: `border.${area}`,
251
+ d: geomData?.[geometryId.value]?.borders?.[area]?.[pathKey] || '',
252
+ opacity: '1',
253
+ strokeWidth: strokeWidth.value,
254
+ },
255
+ ]
256
+ }
257
+
258
+ /**
259
+ * Generate paths for a given region type and optional severity
260
+ */
261
+ function paths(pathOptions: PathOptions): PathData[] {
262
+ const geomData = geometries as GeometryCollection
263
+ const themeColors = colors?.[theme.value as keyof ThemeColorMap]
264
+ const pathKey = `path${size.value}` as 'pathLarge' | 'pathSmall'
265
+
266
+ return regionIds.reduce((regions: PathData[], regionId: string) => {
267
+ const regionEntry = geomData?.[geometryId.value]?.[regionId]
268
+ if (!isRegionGeometry(regionEntry)) return regions
269
+
270
+ const region = regionEntry
271
+ if (
272
+ region[pathKey] &&
273
+ (region.type === pathOptions.type) === (region.subType == null)
274
+ ) {
275
+ const visualization = regionVisualization(regionId)
276
+ if (
277
+ pathOptions.severity == null ||
278
+ visualization.severity === pathOptions.severity
279
+ ) {
280
+ regions.push({
281
+ key: `${regionId}${size.value}${index.value}Path`,
282
+ fill: loading.value
283
+ ? themeColors?.missing || '#cccccc'
284
+ : visualization.color,
285
+ d: visualization.visible
286
+ ? (visualization.geom[pathKey] as string) || ''
287
+ : '',
288
+ opacity: '1',
289
+ dataRegion: regionId,
290
+ dataSeverity: visualization.severity,
291
+ strokeWidth:
292
+ region.type === 'sea' && region.subType !== 'lake'
293
+ ? strokeWidth.value
294
+ : 0,
295
+ })
296
+ }
297
+ }
298
+ return regions
299
+ }, [])
300
+ }
301
+
302
+ /**
303
+ * Generate coverage geometry data
304
+ */
305
+ function coverageGeom(
306
+ coverageProperty: 'coveragesLarge' | 'coveragesSmall',
307
+ covStrokeWidth: number,
308
+ fillOpacity: number,
309
+ severity?: Severity
310
+ ): CoverageData[] {
311
+ const coverageData: CoverageData[] = []
312
+ const warningsMap = warnings.value
313
+ const themeColors = colors?.[theme.value as keyof ThemeColorMap]
314
+
315
+ if (!warningsMap) return coverageData
316
+
317
+ Object.keys(warningsMap).forEach((key) => {
318
+ const warning = warningsMap[key]
319
+ if (!warning) return
320
+
321
+ if (
322
+ (severity == null || warning.severity === severity) &&
323
+ warning.effectiveDays[index.value] &&
324
+ visibleWarnings.value.includes(warning.type) &&
325
+ warning.coveragesLarge.length > 0
326
+ ) {
327
+ if (!coverageWarnings.value.includes(key)) {
328
+ ;[...warning.covRegions.keys()].forEach((covRegion) => {
329
+ const covValue = warning.covRegions.get(covRegion)
330
+ if (
331
+ (coverageRegions.value[covRegion] == null ||
332
+ coverageRegions.value[covRegion] < warning.severity) &&
333
+ covValue != null &&
334
+ covValue >= coverageCriterion
335
+ ) {
336
+ coverageRegions.value[covRegion] = warning.severity
337
+ }
338
+ })
339
+ coverageWarnings.value.push(key)
340
+ }
341
+ warning[coverageProperty].forEach(
342
+ (coverage: { path: string; reference: number[] }) => {
343
+ coverageData.push({
344
+ key: `${key}${size.value}${index.value}${fillOpacity}Coverage`,
345
+ d: coverage.path,
346
+ fillOpacity,
347
+ strokeWidth: covStrokeWidth,
348
+ fill: themeColors?.levels?.[warning.severity] || '#cccccc',
349
+ })
350
+ }
351
+ )
352
+ }
353
+ })
354
+
355
+ return coverageData
356
+ }
357
+
358
+ // ============================================================================
359
+ // Computed Properties
360
+ // ============================================================================
361
+
362
+ const strokeColor = computed<string>(() => {
363
+ return (
364
+ colors?.[theme.value as keyof ThemeColorMap]?.stroke ?? 'DarkSlateGray'
365
+ )
366
+ })
367
+
368
+ const bluePaths = computed<PathData[]>(() => {
369
+ return paths({ type: REGION_SEA })
370
+ })
371
+
372
+ const greenPaths = computed<PathData[]>(() => {
373
+ return paths({ type: REGION_LAND, severity: 0 })
374
+ })
375
+
376
+ const yellowPaths = computed<PathData[]>(() => {
377
+ return paths({ type: REGION_LAND, severity: 2 })
378
+ })
379
+
380
+ const orangePaths = computed<PathData[]>(() => {
381
+ return paths({ type: REGION_LAND, severity: 3 })
382
+ })
383
+
384
+ const redPaths = computed<PathData[]>(() => {
385
+ return paths({ type: REGION_LAND, severity: 4 })
386
+ })
387
+
388
+ const overlayPaths = computed<BorderPath[]>(() => {
389
+ const geomData = geometries as GeometryCollection
390
+ const pathKey = `path${size.value}` as 'pathLarge' | 'pathSmall'
391
+
392
+ return regionIds.reduce((regions: BorderPath[], regionId: string) => {
393
+ const regionEntry = geomData?.[geometryId.value]?.[regionId]
394
+ if (!isRegionGeometry(regionEntry)) return regions
395
+
396
+ const region = regionEntry
397
+ if (
398
+ region[pathKey] &&
399
+ (region.type === 'land' || region.subType === 'lake')
400
+ ) {
401
+ const visualization = regionVisualization(regionId)
402
+ regions.push({
403
+ key: `${regionId}${size.value}${index.value}Overlay`,
404
+ d: visualization.visible
405
+ ? (visualization.geom[pathKey] as string) || ''
406
+ : '',
407
+ opacity: '1',
408
+ strokeWidth: strokeWidth.value,
409
+ })
410
+ }
411
+ return regions
412
+ }, [])
413
+ })
414
+
415
+ const landBorders = computed<BorderPath[]>(() => {
416
+ return areaBorders('land')
417
+ })
418
+
419
+ const seaBorders = computed<BorderPath[]>(() => {
420
+ return areaBorders('sea')
421
+ })
422
+
423
+ const yellowCoverages = computed<CoverageData[]>(() => {
424
+ return coverageGeom(
425
+ `coverages${size.value}` as 'coveragesLarge' | 'coveragesSmall',
426
+ 0,
427
+ 1,
428
+ 2
429
+ )
430
+ })
431
+
432
+ const orangeCoverages = computed<CoverageData[]>(() => {
433
+ return coverageGeom(
434
+ `coverages${size.value}` as 'coveragesLarge' | 'coveragesSmall',
435
+ 0,
436
+ 1,
437
+ 3
438
+ )
439
+ })
440
+
441
+ const redCoverages = computed<CoverageData[]>(() => {
442
+ return coverageGeom(
443
+ `coverages${size.value}` as 'coveragesLarge' | 'coveragesSmall',
444
+ 0,
445
+ 1,
446
+ 4
447
+ )
448
+ })
449
+
450
+ const overlayCoverages = computed<CoverageData[]>(() => {
451
+ return coverageGeom(
452
+ `coverages${size.value}` as 'coveragesLarge' | 'coveragesSmall',
453
+ 1.1 * Number(strokeWidth.value),
454
+ 0
455
+ )
456
+ })
457
+
458
+ return {
459
+ strokeColor,
460
+ bluePaths,
461
+ greenPaths,
462
+ yellowPaths,
463
+ orangePaths,
464
+ redPaths,
465
+ overlayPaths,
466
+ landBorders,
467
+ seaBorders,
468
+ yellowCoverages,
469
+ orangeCoverages,
470
+ redCoverages,
471
+ overlayCoverages,
472
+ coverageRegions,
473
+ coverageWarnings,
474
+ regionData,
475
+ regionVisualization,
476
+ }
477
+ }