Merge feature/module-pinnen: tik op een module om bovenaan te zetten met groter font
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -252,6 +252,10 @@ De iconen `sun`, `partly`, `rain` en `showers` staan in de mockup. Maak `cloud`,
|
||||
- Daaronder de gradiëntbalk van `--cold` naar `--warm`. De marker staat op `(temp − min) / (max − min)`, begrensd op 0–100%. Bij `max == min` staat hij op 50%.
|
||||
- Onder de balk: "9,2° laagst om 06:10" en "19,8° hoogst om 13:05".
|
||||
- **Binnen (rechts):** 3 rijen met naam, temperatuur (1 decimaal) en "↓ min° ↑ max°". Een onbereikbare module toont "–" in `--muted`.
|
||||
_Toegevoegd 2026-09-13: een module is tikbaar. Een tik zet die module bovenaan de lijst (de overige twee
|
||||
behouden hun onderlinge volgorde) en vergroot alleen het temperatuurgetal van 50 naar 92px. Nogmaals tikken
|
||||
op de bovenste (vastgezette) module maakt hem weer los. De keuze blijft staan bij de 5-minuten-verversing en
|
||||
reset bij een volledige herlaad. Zie `pinRoom()` in `public/format.js`._
|
||||
- **Regen (linksonder):**
|
||||
- "Vandaag" groot, in mm met 1 decimaal.
|
||||
- Daarnaast 4 staafjes: de 3 vorige dagen (labels "Di", "Wo", "Do") en vandaag, uitgelicht. De hoogte is `mm / max(4 waarden) × 70px`, met een minimum van 2px. Zijn alle waarden 0, dan zijn alle staafjes 2px.
|
||||
|
||||
+16
-6
@@ -2,7 +2,7 @@ import {
|
||||
f1, fInt, fmm, markerPos, barHeights, wmoIcon, trendLabel,
|
||||
formatDate, formatTime, dayAbbr, monthName, isOlderThan, msUntil,
|
||||
co2Level, fPct, fHpa, seriesRange, sparklinePath,
|
||||
windArrowDeg, fKmh, formatHour, formatFullDate,
|
||||
windArrowDeg, fKmh, formatHour, formatFullDate, pinRoom,
|
||||
} from './format.js';
|
||||
import { icon, windArrow } from './icons.js';
|
||||
|
||||
@@ -22,10 +22,11 @@ const stage = $('stage');
|
||||
const viewport = $('viewport');
|
||||
|
||||
const state = {
|
||||
data: null, // laatste payload, ook als die uit de SW-cache komt
|
||||
data: null, // laatste payload, ook als die uit de SW-cache komt
|
||||
tz: FALLBACK_TZ,
|
||||
lastOk: null, // Date van de laatste geslaagde live-fetch
|
||||
offlineSince: null // Date vanaf wanneer er geen verbinding is
|
||||
lastOk: null, // Date van de laatste geslaagde live-fetch
|
||||
offlineSince: null, // Date vanaf wanneer er geen verbinding is
|
||||
pinnedRoomId: null, // id van de module die vastgezet is bovenaan; blijft staan tot opnieuw getikt of een volledige herlaad
|
||||
};
|
||||
|
||||
/* ---------- schaal ---------- */
|
||||
@@ -71,11 +72,13 @@ function renderOutdoor(o) {
|
||||
}
|
||||
|
||||
function renderRooms(rooms) {
|
||||
const html = rooms.map((r) => {
|
||||
const ordered = pinRoom(rooms, state.pinnedRoomId);
|
||||
const html = ordered.map((r) => {
|
||||
const off = !r.reachable || r.temp === null;
|
||||
const co2 = co2Level(off ? null : r.co2);
|
||||
const pill = `<span class="pill ${co2.level} num" title="${co2.label}">${co2.level === 'unknown' ? '–' : r.co2 + ' ppm'}</span>`;
|
||||
return `<div class="a-room${off ? ' off' : ''}">
|
||||
const pinned = r.id === state.pinnedRoomId;
|
||||
return `<div class="a-room${off ? ' off' : ''}${pinned ? ' pinned' : ''}" data-id="${esc(r.id)}">
|
||||
<p class="a-rname"><span>${esc(r.name)}</span>${pill}</p>
|
||||
<p class="a-rtemp num">${off ? '–' : f1(r.temp) + '°'}</p>
|
||||
<p class="a-rmm num"><span class="cold">↓ ${off ? '–' : f1(r.min) + '°'}</span><span class="warm">↑ ${off ? '–' : f1(r.max) + '°'}</span><span class="rv">RV ${off ? '–' : fPct(r.humidity)}</span></p>
|
||||
@@ -318,6 +321,13 @@ $('forecast').addEventListener('click', (e) => {
|
||||
});
|
||||
$('hour-overlay').addEventListener('click', closeHourOverlay);
|
||||
|
||||
$('rooms').addEventListener('click', (e) => {
|
||||
const room = e.target.closest('.a-room');
|
||||
if (!room || !state.data) return;
|
||||
state.pinnedRoomId = state.pinnedRoomId === room.dataset.id ? null : room.dataset.id;
|
||||
renderRooms(state.data.indoor);
|
||||
});
|
||||
|
||||
window.addEventListener('resize', fit);
|
||||
window.addEventListener('orientationchange', () => setTimeout(fit, 50));
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
|
||||
@@ -205,3 +205,17 @@ export function formatFullDate(dateStr, tz) {
|
||||
if (Number.isNaN(d.getTime())) return DASH;
|
||||
return formatDate(d, tz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Herschikt kamers zodat de kamer met `pinnedId` vooraan staat; de rest behoudt de onderlinge volgorde.
|
||||
* Zonder pinnedId, of als die niet voorkomt of al vooraan staat, blijft de oorspronkelijke array (referentie) terug.
|
||||
*/
|
||||
export function pinRoom(rooms, pinnedId) {
|
||||
if (!pinnedId) return rooms;
|
||||
const index = rooms.findIndex((r) => r.id === pinnedId);
|
||||
if (index <= 0) return rooms;
|
||||
const copy = rooms.slice();
|
||||
const [pinned] = copy.splice(index, 1);
|
||||
copy.unshift(pinned);
|
||||
return copy;
|
||||
}
|
||||
|
||||
+2
-1
@@ -108,8 +108,9 @@ html, body {
|
||||
.pill.medium { background: var(--warm); }
|
||||
.pill.bad { background: var(--bad); }
|
||||
.pill.unknown { background: var(--line); color: var(--muted); }
|
||||
.a-rtemp { font-size: 50px; font-weight: 300; line-height: 1.05; }
|
||||
.a-rtemp { font-size: 50px; font-weight: 300; line-height: 1.05; transition: font-size .25s ease; }
|
||||
.a-room.off .a-rtemp { color: var(--muted); }
|
||||
.a-room.pinned .a-rtemp { font-size: 92px; }
|
||||
.a-rmm { font-size: 17px; display: flex; gap: 18px; }
|
||||
.a-rmm .rv { color: var(--muted); }
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
/* Service worker: app-shell cache-first, API network-first met timeout en fallback op de laatste response. */
|
||||
|
||||
const VERSION = 'weerscherm-v5';
|
||||
const VERSION = 'weerscherm-v6';
|
||||
const SHELL = [
|
||||
'./',
|
||||
'./index.html',
|
||||
|
||||
+22
-1
@@ -121,7 +121,7 @@ test('msUntil computes the wait until the next local time', () => {
|
||||
|
||||
import {
|
||||
co2Level, fPct, fHpa, seriesRange, sparklinePath, sparklinePoints,
|
||||
windArrowDeg, fKmh, formatHour, formatFullDate,
|
||||
windArrowDeg, fKmh, formatHour, formatFullDate, pinRoom,
|
||||
} from '../../public/format.js';
|
||||
|
||||
test('co2Level uses the Netatmo thresholds', () => {
|
||||
@@ -187,3 +187,24 @@ test('formatFullDate matches formatDate for the same calendar date', () => {
|
||||
assert.equal(formatFullDate('2026-09-14', TZ), 'Maandag 14 september');
|
||||
assert.equal(formatFullDate('kapot', TZ), '–');
|
||||
});
|
||||
|
||||
test('pinRoom moves the matching room to the front, keeping the others in order', () => {
|
||||
const rooms = [{ id: 'a' }, { id: 'b' }, { id: 'c' }];
|
||||
assert.deepEqual(pinRoom(rooms, 'b').map((r) => r.id), ['b', 'a', 'c']);
|
||||
assert.deepEqual(pinRoom(rooms, 'c').map((r) => r.id), ['c', 'a', 'b']);
|
||||
});
|
||||
|
||||
test('pinRoom returns the original array unchanged without a match, id, or when already first', () => {
|
||||
const rooms = [{ id: 'a' }, { id: 'b' }];
|
||||
assert.equal(pinRoom(rooms, null), rooms);
|
||||
assert.equal(pinRoom(rooms, undefined), rooms);
|
||||
assert.equal(pinRoom(rooms, 'x'), rooms, 'onbekend id: geen match, ongewijzigd terug');
|
||||
assert.equal(pinRoom(rooms, 'a'), rooms, 'al vooraan: geen kopie nodig');
|
||||
});
|
||||
|
||||
test('pinRoom does not mutate the input array', () => {
|
||||
const rooms = [{ id: 'a' }, { id: 'b' }, { id: 'c' }];
|
||||
const original = rooms.slice();
|
||||
pinRoom(rooms, 'c');
|
||||
assert.deepEqual(rooms, original);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user