Add dedicated Faktiska map production workflow

This commit is contained in:
b0txec
2026-08-18 22:31:56 +03:00
parent 67decb330b
commit ffb89024f1
10 changed files with 89 additions and 72 deletions
+9 -8
View File
@@ -10,11 +10,11 @@ import { CityData, drawOnMap } from "./canvasDraw";
import { IconInputs } from "../weatherIcons/IconInputs";
import { download } from '../../helpers/download'
export const MapView: Component<{ type: MapResolution, data: () => ResultKeyVal[] }> = ({ type, data }) => {
export const MapView: Component<{ type: MapResolution, data: () => ResultKeyVal[], mode?: "temperature" | "faktiska" }> = ({ type, data, mode = "temperature" }) => {
const [getCanvas, setCanvas] = createSignal<HTMLCanvasElement>();
const [getImg, setImg] = createSignal(new Image());
const weatherIconsSingal = createSignal<{[key: string]: string;}>({});
const [getWeatherIcons] = weatherIconsSingal;
const weatherIconsSignal = createSignal<{[key: string]: string;}>({});
const [getWeatherIcons] = weatherIconsSignal;
const [getTitle, setTitle] = createSignal("");
const [getSource, setSource] = createSignal("");
@@ -90,15 +90,16 @@ export const MapView: Component<{ type: MapResolution, data: () => ResultKeyVal[
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">
<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>
<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="grid-1-2">
<div>
<div class="mapControlSections">
<section class="valueControls">
<strong>Value and wind settings</strong>
<div>
<label>
<input
@@ -110,8 +111,8 @@ export const MapView: Component<{ type: MapResolution, data: () => ResultKeyVal[
</label>
</div>
{ props.showWind && <WindInputs signals={windSignals} /> }
</div>
<div><IconInputs weatherIconsSingal={weatherIconsSingal} /></div>
</section>
{mode === "faktiska" && <IconInputs cities={data().map(([city]) => city)} weatherIconsSignal={weatherIconsSignal} />}
</div>
</details>
<div class="canvasFrame">
+14 -45
View File
@@ -1,48 +1,17 @@
import { Component, Signal } from "solid-js";
import { weatherIconCities, weatherIcons } from "./iconConsts";
import { Component, createSignal, For, Show, Signal } from "solid-js";
import { weatherIcons } from "./iconConsts";
import "../../css/weatherIcons.css";
export const IconInputs: Component<{ weatherIconsSingal: Signal<{[key: string]: string;}>; }> = ({
weatherIconsSingal: [getWeatherIcons, setWeatherIcons]
}) => {
export const IconInputs: Component<{cities: string[]; weatherIconsSignal: Signal<Record<string, string>>}> = (props) => {
const [icons, setIcons] = props.weatherIconsSignal;
const [selected, setSelected] = createSignal(weatherIcons[0]);
const assign = (city: string) => setIcons({...icons(), [city]: selected()});
const clear = (city: string) => { const next = {...icons()}; delete next[city]; setIcons(next); };
const applyAll = () => setIcons(Object.fromEntries(props.cities.map(city => [city, selected()])));
function onIconChange(city: string, icon: string) {
const weatherIcons = { ...getWeatherIcons() };
weatherIcons[city] = icon;
if (icon === "") delete weatherIcons[city];
setWeatherIcons(weatherIcons);
}
return (
<>
<div>
<strong>Weather symbols</strong><p class="fieldHint">Click a symbol to copy it, then paste it into a city field below.</p>
{ weatherIcons.map(icon =>
<span class="weatherIconButton" onClick={() => copyToClipboard(icon)}>{ icon }</span>)
}
</div>
<div>
{ weatherIconCities.map(city =>
<div style="float: left;">
{city}:
&nbsp;
<input
type="text"
class="weatherIconInput"
size="1"
maxlength="1"
onInput={e => onIconChange(city, e.target.value)}
/>
&nbsp;
</div>)
}
</div>
</>
)
}
function copyToClipboard(icon: string) {
navigator.clipboard.writeText(icon);
}
return <section class="symbolEditor">
<div class="symbolEditorHeading"><div><strong>Weather symbols</strong><p class="fieldHint">Select a font symbol, apply it to all cities, then change the exceptions.</p></div><button type="button" onClick={applyAll}>Apply selected to all</button></div>
<div class="symbolPalette" role="listbox" aria-label="Weather symbols"><For each={weatherIcons}>{icon => <button type="button" class="weatherIconButton" classList={{selected: selected() === icon}} onClick={() => setSelected(icon)}>{icon}</button>}</For></div>
<div class="citySymbols"><For each={props.cities}>{city => <div class="citySymbolRow"><span>{city}</span><button type="button" class="assignedSymbol" onClick={() => assign(city)} title="Assign selected symbol"><Show when={icons()[city]} fallback="Assign">{icons()[city]}</Show></button><button type="button" class="clearSymbol" disabled={!icons()[city]} onClick={() => clear(city)}>Clear</button></div>}</For></div>
</section>;
};