New Logo
Updated cache system now 15/30mins but scope updates every minute
Font update
This commit is contained in:
fraxle
2026-06-04 14:07:34 +01:00
parent efc9b0eed9
commit ede875f0d0
100 changed files with 231 additions and 106 deletions
+2 -1
View File
@@ -64,7 +64,7 @@ export function useAppState() {
return localStorage.getItem('sunscope_pro') === '1';
});
const { forecast, airQuality, loading, error, now, fetchedAt } = useForecast(location, isPro);
const { forecast, airQuality, loading, error, now, fetchedAt, liveElev } = useForecast(location, isPro);
const [searchQuery, setSearchQuery] = useState('');
const [searchResults, setSearchResults] = useState([]);
@@ -637,6 +637,7 @@ export function useAppState() {
// computation
hourlyRows, days, utcOffsetMs,
visible, tableRows, nowLocalISO, currentRow, currentCat,
liveElev,
// banner + events
activeEvents, lensEvent, selectedDayEvents,
bannerIndex, bannerTransition, bannerPrevIndex,
+30 -17
View File
@@ -32,11 +32,13 @@
// ------------------------------------------------------------------------
import { useState, useEffect, useRef } from '../../vendor/preact-hooks.js';
import { solarElevationDeg } from '../physics.js';
const FIVE_MIN_MS = 5 * 60 * 1000;
const FIFTEEN_MIN_MS = 15 * 60 * 1000;
const THIRTY_MIN_MS = 30 * 60 * 1000;
const SIX_HOURS_MS = 6 * 60 * 60 * 1000;
const TWO_MIN_MS = 2 * 60 * 1000;
const ONE_MIN_MS = 1 * 60 * 1000;
function buildForecastUrl(loc) {
return `https://api.open-meteo.com/v1/forecast` +
@@ -137,7 +139,7 @@ export function useForecast(location, isPro = false) {
// ─── FORECAST LOADER ──────────────────────────────────────────────
//
// Waterfall:
// 1. Cache fresh (< 5 min) → serve immediately, no fetch needed
// 1. Cache fresh (< refresh interval) → serve immediately, no fetch needed
// 2. Cache stale / missing → fetch main auto API
// 3. Main API soil is null → try ICON for soil data
// 4. ICON fails → fall back to stale cache (any age)
@@ -148,21 +150,26 @@ export function useForecast(location, isPro = false) {
const key = forecastCacheKey(loc);
let staleCache = null;
// Step 1 — serve from cache if fresh enough
// Step 1 — stale-while-revalidate: serve any cached data immediately,
// then continue to fetch fresh data silently in the background.
// Only show the loading spinner if there is nothing cached at all.
const cacheMaxAge = isPro ? FIFTEEN_MIN_MS : THIRTY_MIN_MS;
try {
const cached = localStorage.getItem(key);
if (cached) {
const { ts, data } = JSON.parse(cached);
if (Date.now() - ts < FIVE_MIN_MS) {
setForecast(data);
setFetchedAt(new Date(ts));
return;
}
staleCache = { ts, data }; // keep for step 4
staleCache = { ts, data };
// Always show cached data immediately - no spinner
setForecast(data);
setFetchedAt(new Date(ts));
// If fresh enough, no need to re-fetch
if (Date.now() - ts < cacheMaxAge) return;
}
} catch (e) { /* ignore bad cache */ }
setLoading(true); setError(null);
// Only show spinner if nothing could be served from cache
if (!staleCache) { setLoading(true); }
setError(null);
try {
// Step 2 — fetch main forecast
const r = await fetch(buildForecastUrl(loc));
@@ -231,17 +238,23 @@ export function useForecast(location, isPro = false) {
loadAirQuality(location);
}, [location]);
// ─── 2-MINUTE CLOCK TICK (current-row highlight only) ─────────────
// ─── 1-MINUTE CLOCK TICK (current-row highlight + live scope) ─────
useEffect(() => {
const id = setInterval(() => setNow(new Date()), TWO_MIN_MS);
const id = setInterval(() => setNow(new Date()), ONE_MIN_MS);
return () => clearInterval(id);
}, []);
// ─── AUTO REFRESH (Pro: 5 min, free: 15 min) ─────────────────────
// Open-Meteo updates roughly every 15 min; Pro users get a tighter
// poll so the current-conditions row stays fresher.
// ─── LIVE SOLAR ELEVATION (updates every minute with now tick) ─────
const [liveElev, setLiveElev] = useState(0);
useEffect(() => {
const refreshMs = isPro ? FIVE_MIN_MS : FIFTEEN_MIN_MS;
if (location?.lat == null) return;
setLiveElev(solarElevationDeg(location.lat, location.lon, now));
}, [now, location]);
// ─── AUTO REFRESH (Pro: 15 min, free: 30 min) ─────────────────────
// Open-Meteo updates roughly every 15 min; Pro gets tighter polling.
useEffect(() => {
const refreshMs = isPro ? FIFTEEN_MIN_MS : THIRTY_MIN_MS;
const id = setInterval(() => {
async function refresh() {
const key = forecastCacheKey(location);
@@ -263,5 +276,5 @@ export function useForecast(location, isPro = false) {
return () => clearInterval(id);
}, [location, isPro]);
return { forecast, airQuality, loading, error, now, fetchedAt };
return { forecast, airQuality, loading, error, now, fetchedAt, liveElev };
}