feat: tik op een module om die bovenaan te zetten met een groter temperatuurgetal
Tik op een van de drie binnenmodules zet die 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 zet 'm weer los. Puur client-side, geen nieuwe API-velden: state.pinnedRoomId + de pure functie pinRoom() (met tests) in format.js. De keuze overleeft de 5-minuten-verversing, reset bij een volledige herlaad. Servicewerker naar v6. 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