Declutter Faktiskā: collapse stations and map settings by default
Both "Jaunākās temperatūras" and "Kartes noformējums" dumped their
full content on load, forcing a scroll past both just to reach the
map/export on every visit. Both now collapse by default (Faktiskā
only; the older non-production Kartes overlay panel keeps its current
always-open behavior) behind a toggle that still surfaces a manual-
override count so nothing is silently hidden.
Also fixed a real bug found in the process: Faktiskā's resolution
buttons carried both "resolutionChoices" and "faktiskaResolutionChoices"
classes, and the former (defined in waterTemperature.css for a narrow
sidebar layout) was winning the cascade in this wide-content context,
forcing full-width, left-aligned buttons instead of the intended
compact pair.
Restructured "Kartes noformējums" into distinct bordered cards instead
of one continuous flow, translated the still-English "Weather symbols"
/"City assignments" block to Latvian ("Laikapstākļu simboli"/"Mākoņi
pilsētām"), and collapsed the city list to show only active exceptions
by default with a "Rādīt pārējās pilsētas" button to reveal the rest.
This commit is contained in:
@@ -106,7 +106,7 @@ export const MapView: Component<{ type: MapResolution, data: () => ResultKeyVal[
|
||||
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>
|
||||
<details class="broadcastControls" open={!productionTemplate}>
|
||||
<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">
|
||||
|
||||
@@ -6,14 +6,32 @@ import { WeatherIcon } from "./WeatherIcon";
|
||||
export const IconInputs: Component<{cities: string[]; weatherIconsSignal: Signal<Record<string, string>>}> = (props) => {
|
||||
const [icons, setIcons] = props.weatherIconsSignal;
|
||||
const [selected, setSelected] = createSignal(weatherIcons[0]);
|
||||
const [showAllCities, setShowAllCities] = createSignal(false);
|
||||
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()])));
|
||||
|
||||
const exceptedCities = () => props.cities.filter(city => icons()[city]);
|
||||
const remainingCities = () => props.cities.filter(city => !icons()[city]);
|
||||
|
||||
return <section class="symbolEditor">
|
||||
<div class="symbolEditorHeading"><div><strong>Weather symbols</strong><p class="fieldHint">Choose one symbol, apply it broadly, then update the exceptions below.</p></div><div class="bulkSymbol"><span class="selectedPreview"><WeatherIcon code={selected()} size={40} tone="inverse"/></span><button type="button" onClick={applyAll}>Apply to all cities</button></div></div>
|
||||
<div class="symbolPalette" role="radiogroup" aria-label="Choose weather symbol"><For each={weatherIcons}>{icon => <button type="button" role="radio" aria-checked={selected() === icon} aria-label={`Weather symbol ${icon}`} class="weatherIconButton" classList={{selected: selected() === icon}} onClick={() => setSelected(icon)}><WeatherIcon code={icon} size={40} tone={selected() === icon ? "inverse" : "default"}/></button>}</For></div>
|
||||
<div class="assignmentHeading"><strong>City assignments</strong><span>Use the selected symbol for each exception.</span></div>
|
||||
<div class="citySymbols"><For each={props.cities}>{city => <div class="citySymbolRow"><span class="cityName">{city}</span><span class="currentSymbol" classList={{empty: !icons()[city]}}><Show when={icons()[city]} fallback="—">{icon => <WeatherIcon code={icon()} size={40}/>}</Show></span><button type="button" class="assignAction" onClick={() => assign(city)}>Use selected</button><button type="button" class="clearSymbol" disabled={!icons()[city]} onClick={() => clear(city)} aria-label={`Clear ${city} symbol`}>×</button></div>}</For></div>
|
||||
<div class="symbolCard">
|
||||
<div class="symbolEditorHeading"><div><strong>Laikapstākļu simboli</strong><p class="fieldHint">Izvēlies simbolu un pielieto to visām pilsētām, tad pielāgo atsevišķus izņēmumus zemāk.</p></div><div class="bulkSymbol"><span class="selectedPreview"><WeatherIcon code={selected()} size={40} tone="inverse"/></span><button type="button" onClick={applyAll}>Pielietot visām</button></div></div>
|
||||
<div class="symbolPalette" role="radiogroup" aria-label="Izvēlies laikapstākļu simbolu"><For each={weatherIcons}>{icon => <button type="button" role="radio" aria-checked={selected() === icon} aria-label={`Laikapstākļu simbols ${icon}`} class="weatherIconButton" classList={{selected: selected() === icon}} onClick={() => setSelected(icon)}><WeatherIcon code={icon} size={40} tone={selected() === icon ? "inverse" : "default"}/></button>}</For></div>
|
||||
</div>
|
||||
<div class="symbolCard">
|
||||
<div class="assignmentHeading"><strong>Mākoņi pilsētām</strong>{exceptedCities().length > 0 && <span>{exceptedCities().length} no {props.cities.length} pilsētām ar atsevišķu simbolu</span>}</div>
|
||||
<Show when={exceptedCities().length > 0}>
|
||||
<div class="citySymbols"><For each={exceptedCities()}>{city => <div class="citySymbolRow"><span class="cityName">{city}</span><span class="currentSymbol"><WeatherIcon code={icons()[city]} size={40}/></span><button type="button" class="assignAction" onClick={() => assign(city)}>Lietot</button><button type="button" class="clearSymbol" onClick={() => clear(city)} aria-label={`Notīrīt ${city} simbolu`}>×</button></div>}</For></div>
|
||||
</Show>
|
||||
<Show when={remainingCities().length > 0}>
|
||||
<button type="button" class="secondary showAllCitiesToggle" onClick={() => setShowAllCities(v => !v)}>
|
||||
{showAllCities() ? "Slēpt pārējās pilsētas" : `Rādīt pārējās pilsētas (${remainingCities().length})`}
|
||||
</button>
|
||||
<Show when={showAllCities()}>
|
||||
<div class="citySymbols"><For each={remainingCities()}>{city => <div class="citySymbolRow"><span class="cityName">{city}</span><span class="currentSymbol empty">—</span><button type="button" class="assignAction" onClick={() => assign(city)}>Lietot</button><button type="button" class="clearSymbol" disabled aria-label={`Notīrīt ${city} simbolu`}>×</button></div>}</For></div>
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
</section>;
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
font-size: 11px;
|
||||
}
|
||||
.overlayFields{display:grid;grid-template-columns:minmax(260px,2fr) minmax(160px,1fr);gap:12px;padding:16px 16px 0}.overlayFields label,.overlayFields span{display:block}.overlayFields span{margin-bottom:6px;font-size:12px;font-weight:700}.overlayFields input{width:100%}
|
||||
.mapControlSections{display:grid;gap:18px;padding:16px}.valueControls{padding-bottom:16px;border-bottom:1px solid var(--border)}.windControlRow{display:grid;grid-template-columns:minmax(170px,.8fr) repeat(3,minmax(180px,1fr));gap:14px;align-items:end;margin-top:10px}.roundControl{display:flex;align-items:center;gap:8px;min-height:40px}.windField{display:grid;gap:6px}.windField span{font-size:12px;font-weight:700;color:var(--text-muted)}.windField input{width:100%;min-width:0}@media(max-width:900px){.windControlRow{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:560px){.windControlRow{grid-template-columns:1fr}}
|
||||
.mapControlSections{display:grid;gap:16px;padding:16px;background:var(--surface-soft)}.valueControls{padding:16px;border:1px solid var(--border);border-radius:14px;background:var(--surface)}.valueControls>strong{display:block;margin-bottom:2px}.windControlRow{display:grid;grid-template-columns:minmax(170px,.8fr) repeat(3,minmax(180px,1fr));gap:14px;align-items:end;margin-top:10px}.roundControl{display:flex;align-items:center;gap:8px;min-height:40px}.windField{display:grid;gap:6px}.windField span{font-size:12px;font-weight:700;color:var(--text-muted)}.windField input{width:100%;min-width:0}@media(max-width:900px){.windControlRow{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:560px){.windControlRow{grid-template-columns:1fr}}
|
||||
|
||||
.pageWorkspace{max-width:1800px;margin:auto;padding:34px 28px 70px}.pageHeading{display:flex;align-items:end;justify-content:space-between;gap:24px;margin-bottom:24px}.pageHeading h1{margin:5px 0 6px;font-size:34px}.pageHeading p{margin:0;color:var(--text-muted)}.selectionCount{padding:10px 14px;border:1px solid var(--border);border-radius:10px;background:var(--surface);font-weight:700}.aggregator>.cityWorkspace{display:grid;grid-template-columns:240px 300px minmax(0,1fr);gap:18px;align-items:start}.panel{padding:20px;border:1px solid var(--border);border-radius:16px;background:var(--surface);box-shadow:var(--shadow-sm)}.panel h2{margin:0 0 18px;font-size:16px}.cityPanel{max-height:calc(100vh - 160px);overflow:auto}.cityPanel ul{margin:12px 0;padding:0;columns:1}.cityPanel li{padding:3px 0}.queryPanel h4{margin:22px 0 9px}.queryPanel p{margin:10px 0}.resultsPanel{min-width:0}.emptyState{display:flex;min-height:260px;align-items:center;justify-content:center;flex-direction:column;gap:8px;margin-top:18px;padding:30px;border:1px dashed #bac6d4;border-radius:16px;background:rgba(255,255,255,.55);color:var(--text-muted);text-align:center}.emptyState strong{color:var(--text);font-size:18px}.mapWorkspace{margin-top:14px}.mapToolbar{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}.mapToolbar strong,.mapToolbar span{display:block}.mapToolbar span{margin-top:3px;color:var(--text-muted);font-size:12px}.broadcastControls{margin-bottom:14px;border:1px solid var(--border);border-radius:12px;background:var(--surface)}.broadcastControls summary{padding:14px 16px;cursor:pointer}.broadcastControls summary span,.broadcastControls summary strong,.broadcastControls summary small{display:block}.broadcastControls summary small,.controlHelp,.fieldHint{color:var(--text-muted);font-size:12px}.broadcastControls[open] summary{border-bottom:1px solid var(--border)}.broadcastControls .grid-1-2{padding:16px}.controlHelp{margin:16px 16px 0}.canvasFrame{overflow:hidden;border:1px solid var(--border);border-radius:14px;background:#d8dee6;box-shadow:var(--shadow-sm)}.canvasFrame canvas{display:block}.weatherIconButton{border-radius:7px}.fieldHint{margin:4px 0 10px}@media(max-width:1200px){.aggregator>.cityWorkspace{grid-template-columns:220px 1fr}.resultsPanel{grid-column:1/-1}.cityPanel{max-height:none}}@media(max-width:700px){.pageWorkspace{padding:24px 14px}.pageHeading{align-items:start;flex-direction:column}.aggregator>.cityWorkspace{grid-template-columns:1fr}.resultsPanel{grid-column:auto}}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ export function Faktiska() {
|
||||
const [values, setValues] = createSignal(emptyValues());
|
||||
const [overrides, setOverrides] = createSignal<Set<FaktiskaStation>>(new Set());
|
||||
const [template, setTemplate] = createSignal<FaktiskaTemplate>(faktiskaExport.defaultTemplate);
|
||||
const [showStations, setShowStations] = createSignal(false);
|
||||
const productionSize = () => template() === "faktiska_1920x1080"
|
||||
? { width: 1920, height: 1080 }
|
||||
: { width: 3840, height: 1440 };
|
||||
@@ -74,25 +75,33 @@ export function Faktiska() {
|
||||
<section class="faktiskaDataBand">
|
||||
<div class="faktiskaStatus">
|
||||
<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()}>Atjaunot datus</button>
|
||||
<div class="faktiskaStatusActions">
|
||||
<button class="secondary" disabled={latest.loading} onClick={() => refetch()}>Atjaunot datus</button>
|
||||
<button type="button" class="secondary stationsToggle" onClick={() => setShowStations(v => !v)}>
|
||||
{showStations() ? "Slēpt stacijas" : "Rādīt stacijas"}
|
||||
<Show when={overrides().size > 0}><span class="overrideBadge">{overrides().size}</span></Show>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={latest.loading}><LoadingSpinner text="Ielādē jaunākās temperatūras"/></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">
|
||||
<For each={faktiskaStations}>{city => {
|
||||
const observation = () => observationsByCity().get(city);
|
||||
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) ? "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} temperatūra`}/><span>°C</span></span>
|
||||
<button type="button" class="resetTemperature" disabled={!overrides().has(city)} onClick={() => resetValue(city)}>Atiestatīt</button>
|
||||
</label>;
|
||||
}}</For>
|
||||
</div>
|
||||
<Show when={showStations()}>
|
||||
<div class="temperatureEditor">
|
||||
<For each={faktiskaStations}>{city => {
|
||||
const observation = () => observationsByCity().get(city);
|
||||
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) ? "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} temperatūra`}/><span>°C</span></span>
|
||||
<button type="button" class="resetTemperature" disabled={!overrides().has(city)} onClick={() => resetValue(city)}>Atiestatīt</button>
|
||||
</label>;
|
||||
}}</For>
|
||||
</div>
|
||||
</Show>
|
||||
</section>
|
||||
|
||||
<section class="mapProduction faktiskaProduction">
|
||||
<h2>2. Pievieno laikapstākļu simbolus un vēju, tad eksportē</h2>
|
||||
<div class="resolutionChoices faktiskaResolutionChoices" aria-label="Faktiskā eksporta izmērs">
|
||||
<div class="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_3840x1440" }} onClick={() => setTemplate("faktiska_3840x1440")}>3840 × 1440</button>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
.graphicsSetup{display:grid;grid-template-columns:240px minmax(0,1fr);gap:18px;align-items:start}.graphicsMain{display:grid;gap:18px}.productionOptions,.dataOptions{padding:20px}.choiceGroup{display:grid;grid-template-columns:130px 1fr;gap:16px;align-items:start;margin-top:16px}.choiceGroup>span,.dataOptions label>span{display:block;font-size:12px;font-weight:700}.choiceGroup>div{display:flex;flex-wrap:wrap;gap:9px}.choiceGroup button{min-width:150px;text-align:left}.choiceGroup button small{display:block;margin-top:3px;color:var(--text-muted);font-family:Inter,sans-serif}.choiceGroup button.active{border-color:var(--accent);background:var(--accent-pale);color:var(--accent-strong)}.windChoice{display:flex;align-items:center;gap:7px;padding:9px 12px}.dataOptionsGrid{display:grid;grid-template-columns:minmax(250px,1.4fr) 1fr 1fr;gap:18px;align-items:end;margin-bottom:16px}.dataOptions select{width:100%;margin-top:6px}.mapProduction>h2{margin:0 0 10px}.symbolEditor{min-width:0}.symbolEditorHeading{display:flex;align-items:start;justify-content:space-between;gap:12px}.symbolPalette{display:flex;flex-wrap:wrap;gap:5px;margin:10px 0 16px}.citySymbols{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:8px}.citySymbolRow{display:grid;grid-template-columns:1fr 52px 54px;gap:6px;align-items:center;padding:7px;border:1px solid var(--border);border-radius:9px}.assignedSymbol{padding:2px}.clearSymbol{padding:5px;font-size:11px}@media(max-width:900px){.graphicsSetup{grid-template-columns:1fr}.dataOptionsGrid{grid-template-columns:1fr}.choiceGroup{grid-template-columns:1fr}}
|
||||
.symbolEditorHeading{gap:16px}.bulkSymbol{display:flex;align-items:center;gap:8px}.selectedPreview,.currentSymbol{display:flex;align-items:center;justify-content:center}.selectedPreview{width:42px;height:42px;border-radius:9px;background:#17212d;color:#fff}.symbolPalette{display:grid;grid-template-columns:repeat(auto-fill,46px);gap:8px;margin:14px 0 22px;padding:14px;border:1px solid var(--border);border-radius:14px;background:var(--surface-soft)}.assignmentHeading{display:flex;align-items:baseline;gap:10px;margin-bottom:10px}.assignmentHeading span{color:var(--text-muted);font-size:12px}.citySymbols{grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:10px}.citySymbolRow{grid-template-columns:minmax(90px,1fr) 48px 100px 36px;gap:8px;min-height:62px;padding:8px 10px;border-radius:12px;background:#fff}.cityName{overflow:hidden;text-overflow:ellipsis;font-weight:700}.currentSymbol{width:44px;height:44px;border:1px solid #d7dfe8;border-radius:9px;background:#f6f8fa}.currentSymbol.empty{font-family:Inter,sans-serif;font-size:18px;color:#98a2b3}.assignAction{padding:7px 9px;font-size:12px}.clearSymbol{width:36px;padding:0;font-size:20px;color:var(--text-muted)}@media(max-width:900px){.symbolEditorHeading{flex-direction:column}.citySymbols{grid-template-columns:1fr}}
|
||||
.symbolEditorHeading{gap:16px}.bulkSymbol{display:flex;align-items:center;gap:8px}.selectedPreview,.currentSymbol{display:flex;align-items:center;justify-content:center}.selectedPreview{width:42px;height:42px;border-radius:9px;background:#17212d;color:#fff}.symbolPalette{display:grid;grid-template-columns:repeat(auto-fill,46px);gap:8px;margin:14px 0 0;padding:14px;border:1px solid var(--border);border-radius:14px;background:var(--surface-soft)}.assignmentHeading{display:flex;align-items:baseline;gap:10px;margin-bottom:10px}.assignmentHeading span{color:var(--text-muted);font-size:12px}.citySymbols{grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:10px}.citySymbolRow{grid-template-columns:minmax(90px,1fr) 48px 100px 36px;gap:8px;min-height:62px;padding:8px 10px;border-radius:12px;background:#fff}.cityName{overflow:hidden;text-overflow:ellipsis;font-weight:700}.currentSymbol{width:44px;height:44px;border:1px solid #d7dfe8;border-radius:9px;background:#f6f8fa}.currentSymbol.empty{font-family:Inter,sans-serif;font-size:18px;color:#98a2b3}.assignAction{padding:7px 9px;font-size:12px}.clearSymbol{width:36px;padding:0;font-size:20px;color:var(--text-muted)}@media(max-width:900px){.symbolEditorHeading{flex-direction:column}.citySymbols{grid-template-columns:1fr}}
|
||||
.symbolEditor{display:grid;gap:16px}.symbolCard{padding:16px;border:1px solid var(--border);border-radius:14px;background:var(--surface)}.showAllCitiesToggle{margin-top:4px}
|
||||
|
||||
/* Production layout: a compact control band above one uninterrupted workspace. */
|
||||
.graphicsSetup{grid-template-columns:230px minmax(0,1fr);column-gap:28px}
|
||||
@@ -39,6 +40,9 @@
|
||||
.faktiskaDataBand{padding:2px 0 22px;border-bottom:1px solid rgba(90,126,166,.22)}
|
||||
.faktiskaStatus{display:flex;align-items:center;justify-content:space-between;gap:24px;margin-bottom:18px}
|
||||
.faktiskaStatus>div,.faktiskaStatus strong,.faktiskaStatus small{display:block}
|
||||
.faktiskaStatus .faktiskaStatusActions{display:flex;align-items:center;gap:16px}
|
||||
.stationsToggle{display:flex;align-items:center;gap:7px}
|
||||
.overrideBadge{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;border-radius:9px;background:var(--accent);color:#fff;font-size:11px;font-weight:700}
|
||||
.faktiskaStatus strong{margin-top:5px;font-size:18px}
|
||||
.faktiskaStatus small{margin-top:3px;color:var(--text-muted)}
|
||||
.stepLabel{font-family:Monda,sans-serif;font-size:20px;font-weight:700}
|
||||
|
||||
Reference in New Issue
Block a user