Localize navigation and production UI

This commit is contained in:
b0txec
2026-08-22 14:48:16 +03:00
parent f3197bf340
commit 06088653b1
6 changed files with 41 additions and 38 deletions
+12 -9
View File
@@ -1,6 +1,6 @@
import { Component, createSignal, onCleanup, onMount, ParentComponent } from 'solid-js' import { Component, createSignal, onCleanup, onMount, ParentComponent } from 'solid-js'
import { A, Route, Router, useLocation } from '@solidjs/router' import { A, Route, Router, useLocation } from '@solidjs/router'
import { Menu, X } from 'lucide-solid' import { Archive, Database as DatabaseIcon, Map, Menu, Wind, X } from 'lucide-solid'
import styles from './css/menu.module.css'; import styles from './css/menu.module.css';
import { Country } from './pages/country/Country'; import { Country } from './pages/country/Country';
@@ -39,12 +39,15 @@ const AppLayout: ParentComponent = (props) => {
const location = useLocation() const location = useLocation()
let moreMenu: HTMLDivElement | undefined let moreMenu: HTMLDivElement | undefined
const primaryPages = [ const primaryPages = [
['/station', 'Stations'], ['/cities', 'City analysis'], ['/faktiska', 'Faktiskā'], ['/udens-temperatura', 'Ūdens temperatūra'], ['/station', 'Stacijas'], ['/cities', 'Kartes'], ['/faktiska', 'Faktiskā'], ['/udens-temperatura', 'Ūdens'],
] ]
const secondaryPages = [ const secondaryPages = [
['/latvia', 'Latvia overview'], ['/database', 'Data archive'], ['/harmonie', 'HARMONIE'], ['/lvgmc-forecast', 'LVĢMC forecast'], {path: '/latvia', label: 'Apskats', icon: Map},
{path: '/database', label: 'Arhīvs', icon: Archive},
{path: '/harmonie', label: 'Harmonie', icon: Wind},
{path: '/lvgmc-forecast', label: 'LVĢMC', icon: DatabaseIcon},
] ]
const isSecondaryPage = () => secondaryPages.some(([path]) => location.pathname === path) const isSecondaryPage = () => secondaryPages.some(page => location.pathname === page.path)
onMount(() => { onMount(() => {
const updateHeader = () => setIsScrolled(window.scrollY > 24) const updateHeader = () => setIsScrolled(window.scrollY > 24)
const closeMoreMenu = (event: PointerEvent) => { const closeMoreMenu = (event: PointerEvent) => {
@@ -66,15 +69,15 @@ const AppLayout: ParentComponent = (props) => {
return ( return (
<div class="appShell"> <div class="appShell">
<header class={styles.header} classList={{[styles.scrolled]: isScrolled()}}> <header class={styles.header} classList={{[styles.scrolled]: isScrolled()}}>
<A class={styles.brand} href="/" aria-label="WeatherTool main menu" onClick={() => setIsMoreOpen(false)}><span class={styles.brandMark}>W</span><span class={styles.brandCopy}><strong>WeatherTool</strong><small>Weather data workspace</small></span></A> <A class={styles.brand} href="/" aria-label="WeatherTool galvenā izvēlne" onClick={() => setIsMoreOpen(false)}><span class={styles.brandMark}>W</span><span class={styles.brandCopy}><strong>WeatherTool</strong><small>Laikapstākļu datu vide</small></span></A>
<nav class={styles.navigation} aria-label="Main navigation"> <nav class={styles.navigation} aria-label="Galvenā navigācija">
<ul class={styles.menu}>{primaryPages.map(([path, label]) => <li><A href={path} activeClass={styles.active} onClick={() => setIsMoreOpen(false)}>{label}</A></li>)}</ul> <ul class={styles.menu}>{primaryPages.map(([path, label]) => <li><A href={path} activeClass={styles.active} onClick={() => setIsMoreOpen(false)}>{label}</A></li>)}</ul>
<div class={styles.moreMenu} ref={moreMenu}> <div class={styles.moreMenu} ref={moreMenu}>
<button class={styles.moreButton} classList={{[styles.active]: isSecondaryPage()}} type="button" aria-label={isMoreOpen() ? 'Close additional navigation' : 'Open additional navigation'} aria-haspopup="menu" aria-expanded={isMoreOpen()} onClick={() => setIsMoreOpen(value => !value)}> <button class={styles.moreButton} classList={{[styles.active]: isSecondaryPage()}} type="button" aria-label={isMoreOpen() ? 'Aizvērt papildu izvēlni' : 'Atvērt papildu izvēlni'} aria-haspopup="menu" aria-expanded={isMoreOpen()} onClick={() => setIsMoreOpen(value => !value)}>
{isMoreOpen() ? <X size={23} strokeWidth={2.2}/> : <Menu size={23} strokeWidth={2.2}/>}<span class={styles.moreLabel}>More</span> {isMoreOpen() ? <X size={23} strokeWidth={2.2}/> : <Menu size={23} strokeWidth={2.2}/>}<span class={styles.moreLabel}>Vairāk</span>
</button> </button>
<div class={styles.morePanel} classList={{[styles.open]: isMoreOpen()}} role="menu"> <div class={styles.morePanel} classList={{[styles.open]: isMoreOpen()}} role="menu">
{secondaryPages.map(([path, label]) => <A href={path} activeClass={styles.active} role="menuitem" onClick={() => setIsMoreOpen(false)}>{label}</A>)} {secondaryPages.map(page => <A href={page.path} activeClass={styles.active} role="menuitem" onClick={() => setIsMoreOpen(false)}><page.icon size={22} strokeWidth={1.8}/><span>{page.label}</span></A>)}
</div> </div>
</div> </div>
</nav> </nav>
+5 -5
View File
@@ -105,17 +105,17 @@ export const MapView: Component<{ type: MapResolution, data: () => ResultKeyVal[
return ( return (
<div class="mapWorkspace"> <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> <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> <details class="broadcastControls" open>
<summary><span><strong>Broadcast overlay controls</strong><small>Optional manual wind and weather-symbol overrides</small></span></summary> <summary><span><strong>{productionTemplate ? "Kartes noformējums" : "Broadcast overlay controls"}</strong>{!productionTemplate && <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> {!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"> {!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>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> <label><span>Source</span><input type="text" placeholder="LVĢMC" value={getSource()} onInput={e => setSource(e.target.value)} /></label>
</div>} </div>}
<div class="mapControlSections"> <div class="mapControlSections">
<section class="valueControls"> <section class="valueControls">
<strong>Value and wind settings</strong> <strong>{productionTemplate ? "Temperatūra un vējš" : "Value and wind settings"}</strong>
<div class="windControlRow"> <div class="windControlRow">
<label class="roundControl"> <label class="roundControl">
<input <input
@@ -123,7 +123,7 @@ export const MapView: Component<{ type: MapResolution, data: () => ResultKeyVal[
checked={windSignals.roundValues[0]()} checked={windSignals.roundValues[0]()}
onChange={e => windSignals.roundValues[1](e.target.checked)} onChange={e => windSignals.roundValues[1](e.target.checked)}
/> />
<span>Round temperatures</span> <span>{productionTemplate ? "Noapaļot temperatūras" : "Round temperatures"}</span>
</label> </label>
{ props.showWind && <WindInputs signals={windSignals} /> } { props.showWind && <WindInputs signals={windSignals} /> }
</div> </div>
+1 -1
View File
@@ -2,6 +2,6 @@
.header.scrolled{border-color:rgba(79,127,185,.3);background:linear-gradient(110deg,rgba(246,251,255,.82),rgba(218,234,253,.78),rgba(235,246,255,.84));box-shadow:0 8px 26px rgba(31,77,132,.12);backdrop-filter:blur(24px) saturate(140%)} .header.scrolled{border-color:rgba(79,127,185,.3);background:linear-gradient(110deg,rgba(246,251,255,.82),rgba(218,234,253,.78),rgba(235,246,255,.84));box-shadow:0 8px 26px rgba(31,77,132,.12);backdrop-filter:blur(24px) saturate(140%)}
.brand{display:flex;align-items:center;gap:14px;color:var(--text);text-decoration:none;transition:opacity 180ms}.brand:hover{opacity:.88}.brandMark{display:grid;place-items:center;width:48px;height:48px;border-radius:14px;background:linear-gradient(145deg,#4499e6,#15579b);color:#fff;font-family:Monda,sans-serif;font-size:23px;box-shadow:0 9px 22px rgba(26,91,157,.28),0 0 0 5px rgba(52,137,216,.08);transform-origin:left center;transition:transform 280ms cubic-bezier(.23,1,.32,1),box-shadow 280ms}.brandCopy{width:190px;transform-origin:left center;transition:transform 280ms cubic-bezier(.23,1,.32,1),opacity 220ms}.brand strong,.brand small{display:block;white-space:nowrap}.brand strong{font-size:18px;letter-spacing:-.01em}.brand small{margin-top:3px;color:var(--text-muted);font-size:12px}.scrolled .brandMark{transform:scale(.8);box-shadow:0 5px 13px rgba(26,91,157,.22)}.scrolled .brandCopy{transform:translateX(-7px) scale(.88);opacity:.82} .brand{display:flex;align-items:center;gap:14px;color:var(--text);text-decoration:none;transition:opacity 180ms}.brand:hover{opacity:.88}.brandMark{display:grid;place-items:center;width:48px;height:48px;border-radius:14px;background:linear-gradient(145deg,#4499e6,#15579b);color:#fff;font-family:Monda,sans-serif;font-size:23px;box-shadow:0 9px 22px rgba(26,91,157,.28),0 0 0 5px rgba(52,137,216,.08);transform-origin:left center;transition:transform 280ms cubic-bezier(.23,1,.32,1),box-shadow 280ms}.brandCopy{width:190px;transform-origin:left center;transition:transform 280ms cubic-bezier(.23,1,.32,1),opacity 220ms}.brand strong,.brand small{display:block;white-space:nowrap}.brand strong{font-size:18px;letter-spacing:-.01em}.brand small{margin-top:3px;color:var(--text-muted);font-size:12px}.scrolled .brandMark{transform:scale(.8);box-shadow:0 5px 13px rgba(26,91,157,.22)}.scrolled .brandCopy{transform:translateX(-7px) scale(.88);opacity:.82}
.navigation,.menu{display:flex;align-items:center}.navigation{gap:8px}.menu{gap:5px;margin:0;padding:0;list-style:none}.menu li{margin:0;padding:0}.menu a,.moreButton,.morePanel a{font-size:14px;font-weight:700}.menu a{position:relative;display:block;padding:10px 13px;border-radius:10px;color:var(--text-muted);text-decoration:none;transition:color 180ms,background 180ms,transform 180ms}.menu a:after{content:"";position:absolute;right:13px;bottom:4px;left:13px;height:2px;border-radius:2px;background:var(--accent);opacity:0;transform:scaleX(.35);transition:opacity 180ms,transform 220ms}.menu a:hover{background:rgba(255,255,255,.5);color:var(--text);transform:translateY(-1px)}.menu a.active{background:rgba(224,239,255,.78);color:var(--accent-strong)}.menu a.active:after{opacity:1;transform:scaleX(1)} .navigation,.menu{display:flex;align-items:center}.navigation{gap:8px}.menu{gap:5px;margin:0;padding:0;list-style:none}.menu li{margin:0;padding:0}.menu a,.moreButton,.morePanel a{font-size:14px;font-weight:700}.menu a{position:relative;display:block;padding:10px 13px;border-radius:10px;color:var(--text-muted);text-decoration:none;transition:color 180ms,background 180ms,transform 180ms}.menu a:after{content:"";position:absolute;right:13px;bottom:4px;left:13px;height:2px;border-radius:2px;background:var(--accent);opacity:0;transform:scaleX(.35);transition:opacity 180ms,transform 220ms}.menu a:hover{background:rgba(255,255,255,.5);color:var(--text);transform:translateY(-1px)}.menu a.active{background:rgba(224,239,255,.78);color:var(--accent-strong)}.menu a.active:after{opacity:1;transform:scaleX(1)}
.moreMenu{position:relative}.moreButton{display:flex;align-items:center;justify-content:center;gap:8px;min-width:48px;height:44px;padding:0 12px;border:1px solid rgba(88,134,187,.24);border-radius:12px;background:rgba(255,255,255,.56);color:var(--text-muted);cursor:pointer;transition:color 180ms,background 180ms,border-color 180ms,transform 180ms}.moreButton:hover,.moreButton.active{border-color:rgba(42,119,199,.36);background:rgba(224,239,255,.88);color:var(--accent-strong)}.moreButton:hover{transform:translateY(-1px)}.moreLabel{font-size:13px}.morePanel{position:absolute;top:calc(100% + 10px);right:0;display:grid;min-width:230px;padding:8px;border:1px solid rgba(87,133,185,.28);border-radius:15px;background:rgba(247,251,255,.97);box-shadow:0 18px 50px rgba(25,65,110,.2);opacity:0;visibility:hidden;transform:translateY(-7px) scale(.98);transform-origin:top right;transition:opacity 160ms,transform 180ms,visibility 160ms;backdrop-filter:blur(22px)}.morePanel.open{opacity:1;visibility:visible;transform:none}.morePanel a{padding:12px 14px;border-radius:10px;color:var(--text-muted);text-decoration:none;transition:color 160ms,background 160ms}.morePanel a:hover,.morePanel a.active{background:rgba(222,238,255,.88);color:var(--accent-strong)} .moreMenu{position:relative}.moreButton{display:flex;align-items:center;justify-content:center;gap:8px;min-width:48px;height:44px;padding:0 12px;border:1px solid rgba(88,134,187,.24);border-radius:12px;background:rgba(255,255,255,.56);color:var(--text-muted);cursor:pointer;transition:color 180ms,background 180ms,border-color 180ms,transform 180ms}.moreButton:hover,.moreButton.active{border-color:rgba(42,119,199,.36);background:rgba(224,239,255,.88);color:var(--accent-strong)}.moreButton:hover{transform:translateY(-1px)}.moreLabel{font-size:13px}.morePanel{position:absolute;top:calc(100% + 10px);right:0;display:grid;gap:3px;min-width:230px;padding:8px;border:1px solid rgba(87,133,185,.28);border-radius:15px;background:rgba(247,251,255,.97);box-shadow:0 18px 50px rgba(25,65,110,.2);opacity:0;visibility:hidden;transform:translateY(-7px) scale(.98);transform-origin:top right;transition:opacity 160ms,transform 180ms,visibility 160ms;backdrop-filter:blur(22px)}.morePanel.open{opacity:1;visibility:visible;transform:none}.morePanel a{display:flex;align-items:center;gap:12px;padding:12px 14px;border-radius:10px;color:var(--text-muted);text-decoration:none;transition:color 160ms,background 160ms,transform 160ms}.morePanel a svg{flex:0 0 auto;color:var(--accent)}.morePanel a:hover,.morePanel a.active{background:rgba(222,238,255,.88);color:var(--accent-strong);transform:translateX(-2px)}
@media(max-width:980px){.header{gap:16px;padding-inline:20px}.brandCopy{width:auto}.brand small{display:none}.moreLabel{display:none}.menu a{padding-inline:9px;font-size:13px}} @media(max-width:980px){.header{gap:16px;padding-inline:20px}.brandCopy{width:auto}.brand small{display:none}.moreLabel{display:none}.menu a{padding-inline:9px;font-size:13px}}
@media(max-width:760px){.header{height:auto;min-height:74px;align-items:flex-start;flex-direction:column;padding-block:12px}.navigation{width:100%}.menu{max-width:calc(100% - 58px);overflow-x:auto}.menu a{white-space:nowrap}.moreMenu{margin-left:auto}.morePanel{position:fixed;top:132px;right:16px;left:16px}} @media(max-width:760px){.header{height:auto;min-height:74px;align-items:flex-start;flex-direction:column;padding-block:12px}.navigation{width:100%}.menu{max-width:calc(100% - 58px);overflow-x:auto}.menu a{white-space:nowrap}.moreMenu{margin-left:auto}.morePanel{position:fixed;top:132px;right:16px;left:16px}}
+4 -4
View File
@@ -22,15 +22,15 @@ export function Cities() {
return ( return (
<div class="aggregator pageWorkspace"> <div class="aggregator pageWorkspace">
<div class="pageHeading"><div><span class="eyebrow">Historical observations</span><h1>City analysis</h1><p>Choose stations and a calculation, then compare the resulting values.</p></div><div class="selectionCount">{getCities().size} cities selected</div></div> <div class="pageHeading"><div><span class="eyebrow">VĒSTURISKIE NOVĒROJUMI</span><h1>Kartes</h1><p>Izvēlies stacijas un aprēķinu, pēc tam salīdzini rezultātus vai sagatavo karti.</p></div><div class="selectionCount">{getCities().size === 1 ? "1 pilsēta izvēlēta" : `${getCities().size} pilsētas izvēlētas`}</div></div>
<div class="cityWorkspace"> <div class="cityWorkspace">
<aside class="cityPanel workspaceRail"> <aside class="cityPanel workspaceRail">
<h2>1. Choose cities</h2> <h2>1. Izvēlies pilsētas</h2>
<SelectCity getCities={getCities} setCities={setCities} /> <SelectCity getCities={getCities} setCities={setCities} />
</aside> </aside>
<section class="analysisMain"> <section class="analysisMain">
<aside class="queryPanel workspaceToolbar"> <aside class="queryPanel workspaceToolbar">
<h2>2. Configure query</h2> <h2>2. Iestati atlasi</h2>
<div class="queryTimeRange"> <div class="queryTimeRange">
<SelectTimeRange <SelectTimeRange
getStart={getStart} getStart={getStart}
@@ -41,7 +41,7 @@ export function Cities() {
</div> </div>
<div class="queryOptions"> <div class="queryOptions">
<div class="queryField"> <div class="queryField">
<h4>Select field</h4> <h4>Datu lauks</h4>
<SelectField getField={getField} setField={setField} /> <SelectField getField={getField} setField={setField} />
</div> </div>
<SelectKey getKey={getKey} setKey={setKey} /> <SelectKey getKey={getKey} setKey={setKey} />
+9 -9
View File
@@ -3,13 +3,13 @@ import { Archive, BarChart3, CloudSun, Database, Map, RadioTower, Waves, Wind }
import './home.css' import './home.css'
const tools = [ const tools = [
{path:'/station', title:'Stations', description:'Recent measurements and station charts.', icon:RadioTower}, {path:'/station', title:'Stacijas', description:'Jaunākie novērojumi un staciju grafiki.', icon:RadioTower},
{path:'/cities', title:'City analysis', description:'Compare values and prepare temperature maps.', icon:BarChart3}, {path:'/cities', title:'Kartes', description:'Vērtību salīdzināšana un temperatūras kartes.', icon:BarChart3},
{path:'/faktiska', title:'Faktiskā', description:'Temperatures and weather symbols for daily production.', icon:CloudSun}, {path:'/faktiska', title:'Faktiskā', description:'Temperatūras, laikapstākļu simboli un vējš.', icon:CloudSun},
{path:'/udens-temperatura', title:'Ūdens temperatūra', description:'Manuāli ūdens temperatūras diapazoni ētera kartei.', icon:Waves}, {path:'/udens-temperatura', title:'Ūdens', description:'Ūdens temperatūru diapazoni ētera kartei.', icon:Waves},
{path:'/latvia', title:'Latvia overview', description:'Country-wide weather summaries.', icon:Map}, {path:'/latvia', title:'Apskats', description:'Latvijas laikapstākļu kopsavilkums.', icon:Map},
{path:'/database', title:'Data archive', description:'Browse dates and stored observations.', icon:Archive}, {path:'/database', title:'Arhīvs', description:'Datumi un saglabātie novērojumi.', icon:Archive},
{path:'/harmonie', title:'HARMONIE', description:'Render locally stored forecast fields.', icon:Wind}, {path:'/harmonie', title:'Harmonie', description:'Lokāli saglabāto prognožu lauku kartes.', icon:Wind},
{path:'/lvgmc-forecast', title:'LVĢMC forecast', description:'Prepared city forecasts and broadcast tables.', icon:Database}, {path:'/lvgmc-forecast', title:'LVĢMC', description:'Pilsētu prognozes un ētera tabulas.', icon:Database},
] ]
export function Home() { return <section class="homePage"><div class="homeHero"><div><p class="eyebrow">Weather data and production tools</p><h1>Choose a workspace</h1><p class="heroCopy">Analysis, forecasts and newsroom graphics.</p></div><div class="environmentBadge"><span class="statusDot"/>Development dataset<small>Synthetic observations</small></div></div><div class="toolGrid">{tools.map(tool => <A class="toolCard" href={tool.path}><span class="toolIcon"><tool.icon size={64} strokeWidth={1.65}/></span><div class="toolCardCopy"><h2>{tool.title}</h2><p>{tool.description}</p></div><span class="cardArrow"></span></A>)}</div></section> } export function Home() { return <section class="homePage"><div class="homeHero"><div><h1>Izvēlies sadaļu</h1><p class="heroCopy">Dati, prognozes un ētera grafikas.</p></div><div class="environmentBadge"><span class="statusDot"/>Testa dati<small>Sintētiskie novērojumi</small></div></div><div class="toolGrid">{tools.map(tool => <A class="toolCard" href={tool.path}><span class="toolIcon"><tool.icon size={64} strokeWidth={1.65}/></span><div class="toolCardCopy"><h2>{tool.title}</h2><p>{tool.description}</p></div><span class="cardArrow"></span></A>)}</div></section> }
+10 -10
View File
@@ -67,32 +67,32 @@ export function Faktiska() {
return <div class="mapGraphicsPage pageWorkspace faktiskaWorkspace"> return <div class="mapGraphicsPage pageWorkspace faktiskaWorkspace">
<div class="pageHeading"> <div class="pageHeading">
<div><span class="eyebrow">Daily newsroom production</span><h1>Faktiskā</h1><p>Current temperatures with manually prepared weather symbols and wind.</p></div> <div><span class="eyebrow">ĒTERA GRAFIKA</span><h1>Faktiskā</h1><p>Faktiskā temperatūra, laikapstākļu simboli un vējš.</p></div>
<div class="productionSize">{productionSize().width} × {productionSize().height} PNG</div> <div class="productionSize">{productionSize().width} × {productionSize().height} PNG</div>
</div> </div>
<section class="faktiskaDataBand"> <section class="faktiskaDataBand">
<div class="faktiskaStatus"> <div class="faktiskaStatus">
<div><span class="stepLabel">1. Latest temperatures</span><strong>{newestTimestamp() ? newestTimestamp()!.toLocaleString("lv-LV") : "Waiting for observations"}</strong><small>Newest available reading in the configured station set</small></div> <div><span class="stepLabel">1. Jaunākās temperatūras</span><strong>{newestTimestamp() ? newestTimestamp()!.toLocaleString("lv-LV") : "Gaida novērojumus"}</strong><small>Jaunākie pieejamie staciju novērojumi</small></div>
<button class="secondary" disabled={latest.loading} onClick={() => refetch()}>Refresh data</button> <button class="secondary" disabled={latest.loading} onClick={() => refetch()}>Atjaunot datus</button>
</div> </div>
<Show when={latest.loading}><LoadingSpinner text="Loading latest temperatures"/></Show> <Show when={latest.loading}><LoadingSpinner text="Ielādē jaunākās temperatūras"/></Show>
<Show when={latest.error}><div class="inlineNotice error"><strong>Could not load observations.</strong> Existing manual fields remain available.</div></Show> <Show when={latest.error}><div class="inlineNotice error"><strong>Novērojumus neizdevās ielādēt.</strong> Manuālie lauki joprojām ir pieejami.</div></Show>
<div class="temperatureEditor"> <div class="temperatureEditor">
<For each={faktiskaStations}>{city => { <For each={faktiskaStations}>{city => {
const observation = () => observationsByCity().get(city); const observation = () => observationsByCity().get(city);
return <label class="temperatureField" classList={{manual: overrides().has(city), stale: isStale(city), missing: observation()?.value == null}}> return <label class="temperatureField" classList={{manual: overrides().has(city), stale: isStale(city), missing: observation()?.value == null}}>
<span class="temperatureFieldHeading"><strong>{city}</strong><small>{overrides().has(city) ? "Manual" : isStale(city) ? "Older reading" : observation()?.observedAt ? new Date(observation()!.observedAt).toLocaleTimeString("lv-LV", {hour:"2-digit", minute:"2-digit"}) : "No data"}</small></span> <span class="temperatureFieldHeading"><strong>{city}</strong><small>{overrides().has(city) ? "Manuāli" : isStale(city) ? "Vecāks novērojums" : observation()?.observedAt ? new Date(observation()!.observedAt).toLocaleTimeString("lv-LV", {hour:"2-digit", minute:"2-digit"}) : "Nav datu"}</small></span>
<span class="temperatureInput"><input type="text" inputmode="decimal" value={values()[city]} onInput={event => updateValue(city, event.currentTarget.value)} aria-label={`${city} temperature`}/><span>°C</span></span> <span class="temperatureInput"><input type="text" inputmode="decimal" value={values()[city]} onInput={event => updateValue(city, event.currentTarget.value)} aria-label={`${city} temperatūra`}/><span>°C</span></span>
<button type="button" class="resetTemperature" disabled={!overrides().has(city)} onClick={() => resetValue(city)}>Reset</button> <button type="button" class="resetTemperature" disabled={!overrides().has(city)} onClick={() => resetValue(city)}>Atiestatīt</button>
</label>; </label>;
}}</For> }}</For>
</div> </div>
</section> </section>
<section class="mapProduction faktiskaProduction"> <section class="mapProduction faktiskaProduction">
<h2>2. Add weather symbols and wind, then export</h2> <h2>2. Pievieno laikapstākļu simbolus un vēju, tad eksportē</h2>
<div class="resolutionChoices faktiskaResolutionChoices" aria-label="Faktiskā export size"> <div class="resolutionChoices faktiskaResolutionChoices" aria-label="Faktiskā eksporta izmērs">
<button classList={{ active: template() === "faktiska_1920x1080" }} onClick={() => setTemplate("faktiska_1920x1080")}>1920 × 1080</button> <button classList={{ active: template() === "faktiska_1920x1080" }} onClick={() => setTemplate("faktiska_1920x1080")}>1920 × 1080</button>
<button classList={{ active: template() === "faktiska_3840x1440" }} onClick={() => setTemplate("faktiska_3840x1440")}>3840 × 1440</button> <button classList={{ active: template() === "faktiska_3840x1440" }} onClick={() => setTemplate("faktiska_3840x1440")}>3840 × 1440</button>
</div> </div>