2.6
New Logo Updated cache system now 15/30mins but scope updates every minute Font update
This commit is contained in:
@@ -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 };
|
||||
}
|
||||
Reference in New Issue
Block a user