Support multi-warning Brīdinājumi selection with narrower, solid-color chips
This commit is contained in:
@@ -52,8 +52,13 @@ export function Warnings() {
|
|||||||
const phenomenonMatches = phenomenon() === 'all' || warning.phenomenon === phenomenon();
|
const phenomenonMatches = phenomenon() === 'all' || warning.phenomenon === phenomenon();
|
||||||
return dateMatches && phenomenonMatches;
|
return dateMatches && phenomenonMatches;
|
||||||
}));
|
}));
|
||||||
const [selectedId, setSelectedId] = createSignal<string>();
|
const [selectedIds, setSelectedIds] = createSignal<Set<string>>(new Set());
|
||||||
const selectedWarning = createMemo(() => filteredWarnings().find(item => item.id === selectedId()));
|
const selectedWarnings = createMemo(() => filteredWarnings().filter(item => selectedIds().has(item.id)));
|
||||||
|
const toggleSelected = (id: string) => setSelectedIds(prev => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
if (next.has(id)) next.delete(id); else next.add(id);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
const [detailId, setDetailId] = createSignal<string>();
|
const [detailId, setDetailId] = createSignal<string>();
|
||||||
const detailWarning = createMemo(() => filteredWarnings().find(item => item.id === detailId()));
|
const detailWarning = createMemo(() => filteredWarnings().find(item => item.id === detailId()));
|
||||||
const closeDetail = () => setDetailId(undefined);
|
const closeDetail = () => setDetailId(undefined);
|
||||||
@@ -84,22 +89,30 @@ export function Warnings() {
|
|||||||
createEffect(() => { output(); loadArtwork(); });
|
createEffect(() => { output(); loadArtwork(); });
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const list = filteredWarnings();
|
const list = filteredWarnings();
|
||||||
if (!list.some(item => item.id === selectedId())) setSelectedId(list[0]?.id);
|
const validIds = new Set(list.map(item => item.id));
|
||||||
|
setSelectedIds(prev => {
|
||||||
|
let changed = false;
|
||||||
|
const next = new Set<string>();
|
||||||
|
prev.forEach(id => { if (validIds.has(id)) next.add(id); else changed = true; });
|
||||||
|
if (next.size === 0 && list.length > 0) { next.add(list[0].id); changed = true; }
|
||||||
|
return changed ? next : prev;
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
const primarySelected = createMemo(() => selectedWarnings()[0]);
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const warning = selectedWarning();
|
const warning = primarySelected();
|
||||||
setTitle(warning ? defaultTitle(warning.phenomenon) : '');
|
setTitle(warning ? defaultTitle(warning.phenomenon) : '');
|
||||||
});
|
});
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const images = artwork();
|
const images = artwork();
|
||||||
const current = template();
|
const current = template();
|
||||||
const warning = selectedWarning();
|
const warnings = selectedWarnings();
|
||||||
const currentTitle = title();
|
const currentTitle = title();
|
||||||
if (!canvas || !images) return;
|
if (!canvas || !images) return;
|
||||||
if (canvas.width !== current.width) canvas.width = current.width;
|
if (canvas.width !== current.width) canvas.width = current.width;
|
||||||
if (canvas.height !== current.height) canvas.height = current.height;
|
if (canvas.height !== current.height) canvas.height = current.height;
|
||||||
void document.fonts.load(`700 ${current.title.fontSize}px Monda`).then(() =>
|
void document.fonts.load(`700 ${current.title.fontSize}px Monda`).then(() =>
|
||||||
drawWarningMap(canvas.getContext('2d')!, images.background, images.border, current, warning, currentTitle),
|
drawWarningMap(canvas.getContext('2d')!, images.background, images.border, current, warnings, currentTitle),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -107,7 +120,7 @@ export function Warnings() {
|
|||||||
|
|
||||||
return <section class="pageWorkspace warningsPage">
|
return <section class="pageWorkspace warningsPage">
|
||||||
<div class="pageHeading warningsHeading">
|
<div class="pageHeading warningsHeading">
|
||||||
<div><span class="eyebrow">ĒTERA GRAFIKA</span><h1>Brīdinājumi</h1><p>Izvēlies vienu LVĢMC brīdinājumu, kas jārāda kartē; virsraksts aizpildās automātiski.</p></div>
|
<div><span class="eyebrow">ĒTERA GRAFIKA</span><h1>Brīdinājumi</h1><p>Atzīmē LVĢMC brīdinājumus, kas jārāda kartē; virsraksts aizpildās automātiski.</p></div>
|
||||||
<span class="selectionCount">{filteredWarnings().length} {filteredWarnings().length === 1 ? 'brīdinājums' : 'brīdinājumi'}</span>
|
<span class="selectionCount">{filteredWarnings().length} {filteredWarnings().length === 1 ? 'brīdinājums' : 'brīdinājumi'}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -128,8 +141,11 @@ export function Warnings() {
|
|||||||
<Show when={!data.loading && !data.error && filteredWarnings().length === 0}><div class="inlineNotice">Izvēlētajam periodam nav aktīvu brīdinājumu. Karti joprojām var priekšskatīt, bet tajā nebūs iekrāsotu teritoriju.</div></Show>
|
<Show when={!data.loading && !data.error && filteredWarnings().length === 0}><div class="inlineNotice">Izvēlētajam periodam nav aktīvu brīdinājumu. Karti joprojām var priekšskatīt, bet tajā nebūs iekrāsotu teritoriju.</div></Show>
|
||||||
|
|
||||||
<Show when={filteredWarnings().length > 0}><div class="warningSummary"><For each={filteredWarnings()}>{warning =>
|
<Show when={filteredWarnings().length > 0}><div class="warningSummary"><For each={filteredWarnings()}>{warning =>
|
||||||
<div class={`warningCard ${warningSeverity(warning.intensity)}`} classList={{ active: warning.id === selectedId() }}>
|
<div class={`warningCard ${warningSeverity(warning.intensity)}`} classList={{ active: selectedIds().has(warning.id) }}>
|
||||||
<input type="checkbox" class="warningCardCheck" checked={warning.id === selectedId()} title="Rādīt šo brīdinājumu kartē" aria-label={`Rādīt ${warning.phenomenon || 'šo brīdinājumu'} kartē`} onClick={() => setSelectedId(warning.id)}/>
|
<label class="warningCheckboxLabel" title="Rādīt šo brīdinājumu kartē">
|
||||||
|
<input type="checkbox" class="warningCheckboxInput" checked={selectedIds().has(warning.id)} aria-label={`Rādīt ${warning.phenomenon || 'šo brīdinājumu'} kartē`} onChange={() => toggleSelected(warning.id)}/>
|
||||||
|
<span class="warningCheckmark"/>
|
||||||
|
</label>
|
||||||
<button type="button" class="warningCardOpen" onClick={() => setDetailId(warning.id)}>
|
<button type="button" class="warningCardOpen" onClick={() => setDetailId(warning.id)}>
|
||||||
<span class="warningCardIcon"><PhenomenonIcon phenomenon={warning.phenomenon}/></span>
|
<span class="warningCardIcon"><PhenomenonIcon phenomenon={warning.phenomenon}/></span>
|
||||||
<span class="warningCardText">{warning.phenomenon || 'Brīdinājums'}</span>
|
<span class="warningCardText">{warning.phenomenon || 'Brīdinājums'}</span>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export function drawWarningMap(
|
|||||||
background: HTMLImageElement,
|
background: HTMLImageElement,
|
||||||
border: HTMLImageElement,
|
border: HTMLImageElement,
|
||||||
template: WarningTemplate,
|
template: WarningTemplate,
|
||||||
warning: WeatherWarning | undefined,
|
warnings: WeatherWarning[],
|
||||||
title: string,
|
title: string,
|
||||||
) {
|
) {
|
||||||
ctx.clearRect(0, 0, template.width, template.height);
|
ctx.clearRect(0, 0, template.width, template.height);
|
||||||
@@ -46,7 +46,12 @@ export function drawWarningMap(
|
|||||||
warningLayer.height = template.height;
|
warningLayer.height = template.height;
|
||||||
const warningContext = warningLayer.getContext('2d')!;
|
const warningContext = warningLayer.getContext('2d')!;
|
||||||
|
|
||||||
if (warning) drawWarning(warningContext, template, warning);
|
// Lower severities draw first so a higher one painting the same area on
|
||||||
|
// top reads as the dominant color, matching how newsroom combined maps
|
||||||
|
// (e.g. yellow + orange wind together) are meant to look.
|
||||||
|
[...warnings]
|
||||||
|
.sort((a, b) => severityRank(warningSeverity(a.intensity)) - severityRank(warningSeverity(b.intensity)))
|
||||||
|
.forEach(warning => drawWarning(warningContext, template, warning));
|
||||||
|
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.filter = `blur(${template.feather}px)`;
|
ctx.filter = `blur(${template.feather}px)`;
|
||||||
@@ -60,6 +65,10 @@ export function drawWarningMap(
|
|||||||
drawTitle(ctx, template, title);
|
drawTitle(ctx, template, title);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function severityRank(severity: Severity) {
|
||||||
|
return severity === 'red' ? 3 : severity === 'orange' ? 2 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
function drawWarning(ctx: CanvasRenderingContext2D, template: WarningTemplate, warning: WeatherWarning) {
|
function drawWarning(ctx: CanvasRenderingContext2D, template: WarningTemplate, warning: WeatherWarning) {
|
||||||
const severity = warningSeverity(warning.intensity);
|
const severity = warningSeverity(warning.intensity);
|
||||||
const style = severityStyles[severity];
|
const style = severityStyles[severity];
|
||||||
|
|||||||
@@ -1,21 +1,24 @@
|
|||||||
.warningsPage{max-width:1900px}.warningsHeading{margin-bottom:18px}.warningsSetup{display:grid;grid-template-columns:260px minmax(0,1fr);gap:28px;padding:18px 0 24px;border-top:1px solid var(--border);border-bottom:1px solid var(--border)}.warningsSetup h2{margin:0 0 14px;font-size:20px}.warningFilterGrid{display:grid;grid-template-columns:220px 260px minmax(320px,1fr);gap:12px}.warningFilterGrid label{display:grid;gap:5px}.warningFilterGrid label span{font-size:12px;color:var(--text-muted)}.warningTitle input{width:100%}.warningsPreview{padding-top:24px}.warningsPreview canvas{display:block;width:100%;height:auto}.inlineNotice button{margin-left:14px}
|
.warningsPage{max-width:1900px}.warningsHeading{margin-bottom:18px}.warningsSetup{display:grid;grid-template-columns:260px minmax(0,1fr);gap:28px;padding:18px 0 24px;border-top:1px solid var(--border);border-bottom:1px solid var(--border)}.warningsSetup h2{margin:0 0 14px;font-size:20px}.warningFilterGrid{display:grid;grid-template-columns:220px 260px minmax(320px,1fr);gap:12px}.warningFilterGrid label{display:grid;gap:5px}.warningFilterGrid label span{font-size:12px;color:var(--text-muted)}.warningTitle input{width:100%}.warningsPreview{padding-top:24px}.warningsPreview canvas{display:block;width:100%;height:auto}.inlineNotice button{margin-left:14px}
|
||||||
|
|
||||||
.warningSummary{display:flex;flex-wrap:wrap;gap:10px;margin:18px 0}
|
.warningSummary{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin:18px 0}
|
||||||
.warningCard{position:relative;display:flex;align-items:center;height:44px;flex:0 1 220px;border:1.5px solid var(--border);border-radius:12px;background:rgba(255,255,255,.65);overflow:hidden;transition:border-color 200ms,box-shadow 200ms}
|
.warningCard{position:relative;display:flex;align-items:center;gap:9px;height:44px;flex:0 0 auto;padding:0 12px 0 10px;border:1.5px solid var(--border);border-radius:12px;background:rgba(255,255,255,.65);transition:box-shadow 200ms}
|
||||||
.warningCardCheck{flex:0 0 auto;margin:0 0 0 12px}
|
.warningCardOpen{position:relative;display:flex;align-items:center;justify-content:center;gap:8px;height:32px;padding:0 16px;border:none;border-radius:8px;appearance:none;font:inherit;color:#fff;cursor:pointer;transition:filter 150ms}
|
||||||
.warningCardOpen{position:relative;flex:1 1 auto;align-self:stretch;display:flex;align-items:center;padding:0 14px 0 40px;border:none;background:none;appearance:none;font:inherit;color:var(--text);text-align:left;cursor:pointer}
|
.warningCardOpen:hover{filter:brightness(1.08)}
|
||||||
.warningCardIcon{position:absolute;left:0;top:0;width:36px;height:100%;display:flex;align-items:center;justify-content:center;color:#fff;transition:width 300ms ease}
|
.warningCardOpen:active{filter:brightness(.92)}
|
||||||
.warningCardText{position:relative;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:14px;font-weight:600;transition:color 300ms ease}
|
.warningCardIcon{display:flex;align-items:center}
|
||||||
.warningCard.yellow{border-color:#caa000}
|
.warningCardText{font-size:14px;font-weight:600;white-space:nowrap}
|
||||||
.warningCard.orange{border-color:#c1580f}
|
.warningCard.yellow .warningCardOpen{background:#caa000}
|
||||||
.warningCard.red{border-color:#b91c1c}
|
.warningCard.orange .warningCardOpen{background:#c1580f}
|
||||||
.warningCard.yellow .warningCardIcon{background:#caa000}
|
.warningCard.red .warningCardOpen{background:#b91c1c}
|
||||||
.warningCard.orange .warningCardIcon{background:#c1580f}
|
.warningCard.active{box-shadow:0 0 0 3px var(--accent-pale)}
|
||||||
.warningCard.red .warningCardIcon{background:#b91c1c}
|
|
||||||
.warningCardOpen:hover .warningCardIcon{width:100%}
|
.warningCheckboxLabel{position:relative;display:inline-flex;flex:0 0 auto;width:22px;height:22px;cursor:pointer}
|
||||||
.warningCardOpen:hover .warningCardText{color:transparent}
|
.warningCheckboxLabel .warningCheckboxInput{position:absolute;opacity:0;height:0;width:0;margin:0}
|
||||||
.warningCardOpen:active .warningCardIcon{filter:brightness(.88)}
|
.warningCheckmark{position:absolute;inset:0;background-color:#eee;border-radius:5px;transition:background-color .3s;box-shadow:0 2px 5px rgba(0,0,0,.2)}
|
||||||
.warningCard.active{background:rgba(255,255,255,.95);box-shadow:0 0 0 3px var(--accent-pale)}
|
.warningCheckmark:after{content:"";position:absolute;display:none;left:7px;top:3px;width:5px;height:10px;border:solid #fff;border-width:0 3px 3px 0;transform:rotate(45deg)}
|
||||||
|
.warningCheckboxInput:checked ~ .warningCheckmark{background-color:#2196F3;box-shadow:0 3px 7px rgba(33,150,243,.3)}
|
||||||
|
.warningCheckboxInput:checked ~ .warningCheckmark:after{display:block;animation:warningCheckAnim .2s forwards}
|
||||||
|
@keyframes warningCheckAnim{0%{height:0}100%{height:10px}}
|
||||||
|
|
||||||
.warningBackdrop{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;padding:24px;background:rgba(10,20,35,.55);backdrop-filter:blur(6px);animation:warningFadeIn 180ms ease}
|
.warningBackdrop{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;padding:24px;background:rgba(10,20,35,.55);backdrop-filter:blur(6px);animation:warningFadeIn 180ms ease}
|
||||||
.warningModal{position:relative;width:100%;max-width:900px;max-height:85vh;overflow-y:auto;padding:32px 36px;border-radius:20px;background:var(--surface);box-shadow:var(--shadow-md);animation:warningPopIn 200ms ease}
|
.warningModal{position:relative;width:100%;max-width:900px;max-height:85vh;overflow-y:auto;padding:32px 36px;border-radius:20px;background:var(--surface);box-shadow:var(--shadow-md);animation:warningPopIn 200ms ease}
|
||||||
|
|||||||
Reference in New Issue
Block a user