Files
WeatherTool/web/src/components/map/MapView.tsx
T
2026-08-22 14:48:16 +03:00

145 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Component, createEffect, createSignal, onMount } 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'
export const MapView: Component<{ type: MapResolution, data: () => ResultKeyVal[], mode?: "temperature" | "faktiska", productionTemplate?: boolean }> = ({ type, data, mode = "temperature", productionTemplate = false }) => {
const [getCanvas, setCanvas] = createSignal<HTMLCanvasElement>();
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];
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 (
<div class="mapWorkspace">
<div class="mapToolbar"><div><strong>{productionTemplate ? "Kartes priekšskatījums" : "Map preview"}</strong><span>{props.width} × {props.height} px {productionTemplate ? "eksports" : "export"}</span></div><button class="primary" onClick={downloadPng}>{productionTemplate ? "Lejupielādēt PNG" : "Download PNG"}</button></div>
<details class="broadcastControls" open>
<summary><span><strong>{productionTemplate ? "Kartes noformējums" : "Broadcast overlay controls"}</strong>{!productionTemplate && <small>Optional manual wind and weather-symbol overrides</small>}</span></summary>
{!productionTemplate && <p class="controlHelp">These settings do not change the queried city values. Use them only when preparing a finished broadcast graphic.</p>}
{!productionTemplate && <div class="overlayFields">
<label><span>Title</span><input type="text" placeholder="19. APRĪĻA GAISA TEMPERATŪRAS REKORDI" value={getTitle()} onInput={e => setTitle(e.target.value)} /></label>
<label><span>Source</span><input type="text" placeholder="LVĢMC" value={getSource()} onInput={e => setSource(e.target.value)} /></label>
</div>}
<div class="mapControlSections">
<section class="valueControls">
<strong>{productionTemplate ? "Temperatūra un vējš" : "Value and wind settings"}</strong>
<div class="windControlRow">
<label class="roundControl">
<input
type="checkbox"
checked={windSignals.roundValues[0]()}
onChange={e => windSignals.roundValues[1](e.target.checked)}
/>
<span>{productionTemplate ? "Noapaļot temperatūras" : "Round temperatures"}</span>
</label>
{ props.showWind && <WindInputs signals={windSignals} /> }
</div>
</section>
{mode === "faktiska" && <IconInputs cities={data().map(([city]) => city)} weatherIconsSignal={weatherIconsSignal} />}
</div>
</details>
<div class="canvasFrame">
<canvas
ref={setCanvas}
width={props.width}
height={props.height}
style={getStyleSize()}
/>
</div>
</div>
);
}