Files
WeatherTool/web/src/components/map/MapView.tsx
T

136 lines
5.5 KiB
TypeScript
Raw Normal View History

2023-08-07 23:25:35 +03:00
import { Component, createEffect, createSignal, onMount } from "solid-js";
2023-08-08 17:22:52 +03:00
2023-08-07 23:25:35 +03:00
import { ResultKeyVal } from "../../consts";
2023-08-08 17:22:52 +03:00
import { WindInputs, WindSignals } from "./WindInputs";
2023-08-08 19:00:22 +03:00
import arrowUrl from "../../assets/arrow.webp";
2025-03-14 11:31:47 +02:00
import { cityCoords } from "../cityCoords";
2024-02-11 21:44:29 +02:00
import { MapResolution, resolutionProps, windProps, WindProps } from "./mapConsts";
import { CityData, drawOnMap } from "./canvasDraw";
2025-03-14 11:31:47 +02:00
import { IconInputs } from "../weatherIcons/IconInputs";
import { download } from '../../helpers/download'
2024-01-23 22:58:26 +02:00
2026-08-19 17:11:58 +03:00
export const MapView: Component<{ type: MapResolution, data: () => ResultKeyVal[], mode?: "temperature" | "faktiska", productionTemplate?: boolean }> = ({ type, data, mode = "temperature", productionTemplate = false }) => {
2023-08-07 23:25:35 +03:00
const [getCanvas, setCanvas] = createSignal<HTMLCanvasElement>();
const [getImg, setImg] = createSignal(new Image());
const weatherIconsSignal = createSignal<{[key: string]: string;}>({});
const [getWeatherIcons] = weatherIconsSignal;
2026-08-18 22:14:40 +03:00
const [getTitle, setTitle] = createSignal("");
const [getSource, setSource] = createSignal("");
2024-02-11 21:44:29 +02:00
2025-03-12 13:16:35 +02:00
let lastCoords: CityData[] = [];
2024-01-23 22:58:26 +02:00
const props = resolutionProps[type];
2023-08-07 23:25:35 +03:00
2023-08-08 19:00:22 +03:00
const arrowImg = new Image();
arrowImg.src = arrowUrl;
2023-08-08 17:22:52 +03:00
const windSignals: WindSignals = {
direction: createSignal(""),
speed: createSignal(""),
gusts: createSignal(""),
2026-08-19 17:11:58 +03:00
roundValues: createSignal(mode === "faktiska"),
2023-08-08 17:22:52 +03:00
}
2024-02-08 15:25:51 +02:00
function getWindData(): [string, string, string, boolean, WindProps] {
2023-08-08 17:22:52 +03:00
return [
windSignals.direction[0](),
windSignals.speed[0](),
windSignals.gusts[0](),
2023-08-08 23:05:55 +03:00
windSignals.roundValues[0](),
2024-02-08 15:25:51 +02:00
windProps[type],
2023-08-08 17:22:52 +03:00
];
}
2023-08-07 23:25:35 +03:00
onMount(() => {
const ctx = getCanvas()!.getContext("2d")!;
const img = new Image();
img.onload = () => {
setImg(img);
2023-08-08 23:05:55 +03:00
drawOnMap(
ctx,
[img, arrowImg],
lastCoords,
getWindData(),
2026-08-18 22:14:40 +03:00
[getTitle(), getSource()],
2024-01-23 22:58:26 +02:00
props,
2023-08-08 23:05:55 +03:00
);
2023-08-07 23:25:35 +03:00
};
2024-01-23 22:58:26 +02:00
img.src = props.map;
2023-08-07 23:25:35 +03:00
});
createEffect(() => {
const ctx = getCanvas()!.getContext("2d")!;
2024-02-11 21:44:29 +02:00
const weatherIcons = getWeatherIcons();
2025-03-12 13:16:35 +02:00
const coordsAndData: CityData[] = data()
2023-08-07 23:25:35 +03:00
.map(([city, value]) => [
city,
cityCoords[city as keyof typeof cityCoords],
typeof value === "number" ? value : -99,
2024-02-11 21:44:29 +02:00
weatherIcons[city],
2023-08-07 23:25:35 +03:00
]);
lastCoords = coordsAndData;
2023-08-08 23:05:55 +03:00
drawOnMap(
ctx,
2024-02-11 21:44:29 +02:00
[getImg(), arrowImg],
2023-08-08 23:05:55 +03:00
coordsAndData,
getWindData(),
2026-08-18 22:14:40 +03:00
[getTitle(), getSource()],
2024-01-23 22:58:26 +02:00
props,
2023-08-08 23:05:55 +03:00
);
2023-08-07 23:25:35 +03:00
});
2024-02-08 15:42:36 +02:00
const getStyleSize = () => {
return { "width": "100%", "height": "auto" };
}
function downloadPng() {
2026-08-19 17:23:07 +03:00
const canvas = getCanvas()!;
if (productionTemplate && (canvas.width !== 3840 || canvas.height !== 1440)) {
console.error(`Faktiskā export blocked: expected 3840 × 1440, received ${canvas.width} × ${canvas.height}`);
return;
}
const filename = productionTemplate ? "faktiska-3840x1440.png" : `weather-${type}.png`;
canvas.toBlob(blob => blob && download(blob, filename), 'image/png')
2024-02-08 15:42:36 +02:00
}
2023-08-07 23:25:35 +03:00
return (
<div class="mapWorkspace">
<div class="mapToolbar"><div><strong>Map preview</strong><span>{props.width} × {props.height} px export</span></div><button class="primary" onClick={downloadPng}>Download PNG</button></div>
<details class="broadcastControls" open>
<summary><span><strong>Broadcast overlay controls</strong><small>Optional manual wind and weather-symbol overrides</small></span></summary>
<p class="controlHelp">These settings do not change the queried city values. Use them only when preparing a finished broadcast graphic.</p>
2026-08-19 17:11:58 +03:00
{!productionTemplate && <div class="overlayFields">
2026-08-18 22:14:40 +03:00
<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>
2026-08-19 17:11:58 +03:00
</div>}
<div class="mapControlSections">
<section class="valueControls">
<strong>Value and wind settings</strong>
2026-08-19 17:34:04 +03:00
<div class="windControlRow">
<label class="roundControl">
2025-03-20 22:17:28 +02:00
<input
type="checkbox"
checked={windSignals.roundValues[0]()}
onChange={e => windSignals.roundValues[1](e.target.checked)}
/>
2026-08-19 17:34:04 +03:00
<span>Round temperatures</span>
2025-03-20 22:17:28 +02:00
</label>
2026-08-19 17:34:04 +03:00
{ props.showWind && <WindInputs signals={windSignals} /> }
2025-03-20 22:17:28 +02:00
</div>
</section>
{mode === "faktiska" && <IconInputs cities={data().map(([city]) => city)} weatherIconsSignal={weatherIconsSignal} />}
2024-02-11 21:44:29 +02:00
</div>
</details>
<div class="canvasFrame">
2024-02-06 16:56:31 +02:00
<canvas
ref={setCanvas}
width={props.width}
height={props.height}
2024-02-08 15:42:36 +02:00
style={getStyleSize()}
2024-02-06 16:56:31 +02:00
/>
</div>
</div>
2023-08-07 23:25:35 +03:00
);
}