import { createEffect, createMemo, createResource, createSignal, For, Show } from 'solid-js'; import { apiHost } from '../../consts'; import { download } from '../../helpers/download'; import { LoadingSpinner } from '../../components/spinner/LoadingSpinner'; import { drawWarningMap, type WeatherWarning, warningSeverity } from './warningDraw'; import { warningTemplates, type WarningOutput } from './warningTemplates'; import './warnings.css'; type WarningResponse = { source: string; fetchedAt: string; warnings: WeatherWarning[] }; const fetchWarnings = async (): Promise => { const response = await fetch(`${apiHost}/api/warnings`); if (!response.ok) throw new Error(`Warnings failed (${response.status})`); return response.json(); }; export function Warnings() { const [output, setOutput] = createSignal('3840x1440'); const [title, setTitle] = createSignal(''); const [selectedDate, setSelectedDate] = createSignal('all'); const [phenomenon, setPhenomenon] = createSignal('all'); const [artwork, setArtwork] = createSignal<{ background: HTMLImageElement; border: HTMLImageElement }>(); const [data, { refetch }] = createResource(fetchWarnings); let canvas!: HTMLCanvasElement; const template = () => warningTemplates[output()]; const loadedWarnings = () => data.error ? [] : data()?.warnings ?? []; const availableDates = createMemo(() => warningDates(loadedWarnings())); const phenomena = createMemo(() => [...new Set(loadedWarnings().map(item => item.phenomenon).filter(Boolean))].sort((a, b) => a.localeCompare(b, 'lv'))); const filteredWarnings = createMemo(() => loadedWarnings().filter(warning => { const dateMatches = selectedDate() === 'all' || activeOnDate(warning, selectedDate()); const phenomenonMatches = phenomenon() === 'all' || warning.phenomenon === phenomenon(); return dateMatches && phenomenonMatches; })); const [selectedId, setSelectedId] = createSignal(); const selectedWarning = createMemo(() => filteredWarnings().find(item => item.id === selectedId())); const loadArtwork = () => { const current = template(); setArtwork(undefined); const background = new Image(); const border = new Image(); let loaded = 0; const finish = () => { loaded += 1; if (loaded === 2 && template() === current) setArtwork({ background, border }); }; background.onload = finish; border.onload = finish; background.src = current.map; border.src = current.border; }; createEffect(() => { output(); loadArtwork(); }); createEffect(() => { const list = filteredWarnings(); if (!list.some(item => item.id === selectedId())) setSelectedId(list[0]?.id); }); createEffect(() => { const warning = selectedWarning(); setTitle(warning ? defaultTitle(warning.phenomenon) : ''); }); createEffect(() => { const images = artwork(); const current = template(); const warning = selectedWarning(); const currentTitle = title(); if (!canvas || !images) return; if (canvas.width !== current.width) canvas.width = current.width; if (canvas.height !== current.height) canvas.height = current.height; void document.fonts.load(`700 ${current.title.fontSize}px Monda`).then(() => drawWarningMap(canvas.getContext('2d')!, images.background, images.border, current, warning, currentTitle), ); }); const downloadPng = () => canvas.toBlob(blob => blob && download(blob, `bridinajumi-${output()}.png`), 'image/png'); return
ĒTERA GRAFIKA

Brīdinājumi

Izvēlies vienu LVĢMC brīdinājumu, kas jārāda kartē; virsraksts aizpildās automātiski.

{filteredWarnings().length} {filteredWarnings().length === 1 ? 'brīdinājums' : 'brīdinājumi'}

1. Izvēlies izmēru

2. Izvēlies brīdinājumu

Brīdinājumu datus neizdevās ielādēt.
Izvēlētajam periodam nav aktīvu brīdinājumu. Karti joprojām var priekšskatīt, bet tajā nebūs iekrāsotu teritoriju.
0}>
{warning => { const active = () => warning.id === selectedId(); return ; }}
3. Kartes priekšskatījums{template().width} × {template().height} px eksports
; } function defaultTitle(phenomenon: string) { return phenomenon ? `Brīdinājums: ${phenomenon}` : ''; } function datePart(value: string) { const match = value.trim().match(/^(\d{4}-\d{2}-\d{2})/); return match?.[1]; } function warningDates(warnings: WeatherWarning[]) { const dates = new Set(); warnings.forEach(warning => { const from = datePart(warning.validFrom); const until = datePart(warning.validUntil); if (!from || !until) return; const cursor = new Date(`${from}T12:00:00Z`); const last = new Date(`${until}T12:00:00Z`); while (cursor.getTime() <= last.getTime()) { dates.add(cursor.toISOString().slice(0, 10)); cursor.setUTCDate(cursor.getUTCDate() + 1); } }); return [...dates].sort(); } function activeOnDate(warning: WeatherWarning, date: string) { const from = datePart(warning.validFrom); const until = datePart(warning.validUntil); if (!from || !until) return false; return from <= date && until >= date; } function formatDate(value: string) { return new Intl.DateTimeFormat('lv-LV', { timeZone: 'Europe/Riga', weekday: 'short', day: '2-digit', month: '2-digit' }).format(new Date(`${value}T12:00:00Z`)); } function formatPeriod(fromValue: string, untilValue: string) { const compact = (value: string) => value.trim().replace(/^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2}).*$/, '$3.$2. $4:$5'); return `${compact(fromValue)}–${compact(untilValue)}`; }