import { Component, createEffect, createSignal, onMount, Show } from "solid-js"; import { ResultKeyVal } from "../../consts"; import { WindInputs, WindSignals } from "./WindInputs"; import arrowUrl from "../../assets/arrow.webp"; import { cityCoords } from "../cityCoords"; import { MapResolution, resolutionProps, windProps, WindProps } from "./mapConsts"; import { CityData, drawOnMap } from "./canvasDraw"; import { IconInputs } from "../weatherIcons/IconInputs"; import { preloadWeatherIcons } from "../weatherIcons/weatherIconAssets"; import { download } from '../../helpers/download' import { faktiskaTemplates } from "../../pages/map-graphics/faktiskaTemplates"; import type { FaktiskaTemplate } from "../../pages/map-graphics/faktiskaTemplates"; export const MapView: Component<{ type: MapResolution, data: () => ResultKeyVal[], mode?: "temperature" | "faktiska", productionTemplate?: boolean, onEditValue?: (city: string, value: string) => void }> = ({ type, data, mode = "temperature", productionTemplate = false, onEditValue }) => { const [getCanvas, setCanvas] = createSignal(); const [getImg, setImg] = createSignal(new Image()); const [fontReady, setFontReady] = createSignal(false); const [iconsReady, setIconsReady] = createSignal(mode !== "faktiska"); const weatherIconsSignal = createSignal<{[key: string]: string;}>({}); const [getWeatherIcons] = weatherIconsSignal; const [getTitle, setTitle] = createSignal(""); const [getSource, setSource] = createSignal(""); let lastCoords: CityData[] = []; const props = resolutionProps[type]; // Faktiskā only: click a temperature badge on the map to edit it inline, // instead of needing the "Rādīt stacijas" panel for a one-off tweak. // Same canvas-coordinate conversion as Brīdinājumi's drag/resize handles // (getBoundingClientRect scaling) -- preview-only, this input is a plain // DOM element positioned over the canvas and never reaches the export. const clickToEditEnabled = mode === "faktiska" && productionTemplate; const [hoveredCity, setHoveredCity] = createSignal(); const [editingCity, setEditingCity] = createSignal(); const [editValue, setEditValue] = createSignal(""); const canvasPoint = (event: { clientX: number, clientY: number }) => { const canvas = getCanvas()!; const rect = canvas.getBoundingClientRect(); return { x: (event.clientX - rect.left) * (canvas.width / rect.width), y: (event.clientY - rect.top) * (canvas.height / rect.height), }; }; const badgeHitTest = (point: { x: number, y: number }): string | undefined => { if (!clickToEditEnabled) return undefined; const faktiskaTemplate = faktiskaTemplates[type as FaktiskaTemplate]; const half = faktiskaTemplate.badgeSize / 2; for (const [city, marker] of Object.entries(faktiskaTemplate.markers)) { if (Math.abs(point.x - marker.x) <= half && Math.abs(point.y - marker.y) <= half) return city; } return undefined; }; const screenPointFromCanvas = (x: number, y: number) => { const canvas = getCanvas()!; const rect = canvas.getBoundingClientRect(); return { left: rect.left + x * (rect.width / canvas.width), top: rect.top + y * (rect.height / canvas.height), }; }; const onCanvasPointerMove = (event: PointerEvent) => { if (!clickToEditEnabled) return; setHoveredCity(badgeHitTest(canvasPoint(event))); }; const onCanvasClick = (event: MouseEvent) => { if (!clickToEditEnabled) return; const city = badgeHitTest(canvasPoint(event)); if (!city) return; const current = data().find(([c]) => c === city)?.[1]; setEditValue(typeof current === "number" ? current.toString().replace(".", ",") : ""); setEditingCity(city); }; const commitEdit = () => { const city = editingCity(); if (city) onEditValue?.(city, editValue()); setEditingCity(undefined); }; const arrowImg = new Image(); arrowImg.src = arrowUrl; const windSignals: WindSignals = { direction: createSignal(""), speed: createSignal(""), gusts: createSignal(""), roundValues: createSignal(mode === "faktiska"), } function getWindData(): [string, string, string, boolean, WindProps] { return [ windSignals.direction[0](), windSignals.speed[0](), windSignals.gusts[0](), windSignals.roundValues[0](), windProps[type], ]; } onMount(() => { void document.fonts.load(`700 ${props.fontSize}px Monda`).then(() => setFontReady(true)); if (mode === "faktiska") void preloadWeatherIcons().then(() => setIconsReady(true)); const ctx = getCanvas()!.getContext("2d")!; const img = new Image(); img.onload = () => { setImg(img); drawOnMap( ctx, [img, arrowImg], lastCoords, getWindData(), [getTitle(), getSource()], props, productionTemplate, ); }; img.src = props.map; }); createEffect(() => { fontReady(); iconsReady(); const ctx = getCanvas()!.getContext("2d")!; const weatherIcons = getWeatherIcons(); const coordsAndData: CityData[] = data() .map(([city, value]) => [ city, cityCoords[city as keyof typeof cityCoords], typeof value === "number" ? value : -99, weatherIcons[city], ]); lastCoords = coordsAndData; drawOnMap( ctx, [getImg(), arrowImg], coordsAndData, getWindData(), [getTitle(), getSource()], props, productionTemplate, ); }); const getStyleSize = () => { return { "width": "100%", "height": "auto" }; } function downloadPng() { const canvas = getCanvas()!; if (productionTemplate && (canvas.width !== props.width || canvas.height !== props.height)) { console.error(`Faktiskā export blocked: expected ${props.width} × ${props.height}, received ${canvas.width} × ${canvas.height}`); return; } const filename = productionTemplate ? `faktiska-${props.width}x${props.height}.png` : `weather-${type}.png`; canvas.toBlob(blob => blob && download(blob, filename), 'image/png') } return (
{productionTemplate ? "Kartes priekšskatījums" : "Map preview"}{props.width} × {props.height} px {productionTemplate ? "eksports" : "export"}{clickToEditEnabled && Klikšķini uz temperatūras kartē, lai to mainītu}
{productionTemplate ? "Kartes noformējums" : "Broadcast overlay controls"}{!productionTemplate && Optional manual wind and weather-symbol overrides} {!productionTemplate &&

These settings do not change the queried city values. Use them only when preparing a finished broadcast graphic.

} {!productionTemplate &&
}
{productionTemplate ? "Temperatūra un vējš" : "Value and wind settings"}
{ props.showWind && }
{mode === "faktiska" && city)} weatherIconsSignal={weatherIconsSignal} />}
setHoveredCity(undefined)} onClick={onCanvasClick} /> {city => { const marker = () => faktiskaTemplates[type as FaktiskaTemplate].markers[city() as keyof typeof faktiskaTemplates[FaktiskaTemplate]["markers"]]; const pos = () => screenPointFromCanvas(marker().x, marker().y); return { setTimeout(() => { el.focus(); el.select(); }, 0); }} style={{ position: "fixed", left: `${pos().left}px`, top: `${pos().top}px`, transform: "translate(-50%, -50%)" }} type="text" inputmode="decimal" value={editValue()} onInput={e => setEditValue(e.currentTarget.value)} onKeyDown={e => { if (e.key === "Enter") commitEdit(); if (e.key === "Escape") setEditingCity(undefined); }} onBlur={commitEdit} aria-label={`${city()} temperatūra`} />; }}
); }