Merge /conditions into the app as a Station tab
- New Station tab (top tabs, bottom nav, swipe order) carrying the old /conditions look: radar-ring hero, condition pill, wind compass, atmosphere/precip/solar cards, indoor - Scoped sl-* styles and station-live.js; units follow the app's metric/imperial setting - Empty/error states when no station is connected - /conditions now 301-redirects to /?tab=station; removed conditions.html, /api/conditions/public and the old My Station drawer - /api/ambient-config also returns the station name - Dashboard and settings links updated; SW cache v8; manifest shortcut for Station - Drops an indoor-temp block in applyStationOverlay that referenced an out-of-scope helper Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7dea2734ea
commit
1830fb2e2b
11 files changed
+361
-917
No files matched your search
+10
-89
@@ -2658,7 +2658,7 @@ syncOfflineState();
|
||||
|
||||
// ── Swipe gestures for tab switching ──────────────────────────────────────────
|
||||
(function initSwipe() {
|
||||
const TABS = ['conditions', 'unified-map', 'alerts', 'learn', 'settings'];
|
||||
const TABS = ['conditions', 'station', 'unified-map', 'alerts', 'learn', 'settings'];
|
||||
let touchX = 0;
|
||||
let touchY = 0;
|
||||
let blocked = false;
|
||||
@@ -2813,7 +2813,9 @@ if (ambientSaveBtn) {
|
||||
}
|
||||
|
||||
// On load, check if server already has keys configured (other device saved them)
|
||||
fetch('/api/ambient-config').then(r => r.json()).then(({ configured }) => {
|
||||
fetch('/api/ambient-config').then(r => r.json()).then(({ configured, stationName }) => {
|
||||
if (stationName) state.stationName = stationName;
|
||||
if (!configured && !state.ambientApiKey && typeof window.stationTabMessage === 'function') window.stationTabMessage('unconfigured');
|
||||
if (configured && !state.ambientApiKey) {
|
||||
if (ambientStatusEl) { ambientStatusEl.textContent = '✓ Keys configured on server'; show(ambientStatusEl); }
|
||||
fetchAmbientStation();
|
||||
@@ -2968,10 +2970,7 @@ if (pwsSaveBtn) {
|
||||
let stationRefreshTimer = null;
|
||||
|
||||
async function fetchAmbientStation() {
|
||||
const loadingEl = document.getElementById('station-loading');
|
||||
const contentEl = document.getElementById('station-content');
|
||||
const errorEl = document.getElementById('station-error');
|
||||
const peekEl = document.getElementById('station-drawer-peek');
|
||||
const tabMsg = (kind, detail) => { if (typeof window.stationTabMessage === 'function') window.stationTabMessage(kind, detail); };
|
||||
|
||||
const apiKey = state.ambientApiKey;
|
||||
const appKey = state.ambientAppKey;
|
||||
@@ -2984,18 +2983,14 @@ async function fetchAmbientStation() {
|
||||
const r = await fetch(`/api/ambient${qs}`);
|
||||
if (!r.ok) {
|
||||
const { error } = await r.json().catch(() => ({}));
|
||||
if (errorEl) { errorEl.textContent = error || 'Station unavailable'; show(errorEl); }
|
||||
if (loadingEl) hide(loadingEl);
|
||||
tabMsg(r.status === 503 ? 'unconfigured' : 'error', error || 'Station unavailable');
|
||||
return;
|
||||
}
|
||||
const devices = await r.json();
|
||||
if (!Array.isArray(devices) || !devices.length) {
|
||||
if (errorEl) { errorEl.textContent = 'No devices found on this account.'; show(errorEl); }
|
||||
if (loadingEl) hide(loadingEl);
|
||||
tabMsg('error', 'No devices found on this account.');
|
||||
return;
|
||||
}
|
||||
if (loadingEl) hide(loadingEl);
|
||||
if (errorEl) hide(errorEl);
|
||||
renderAmbientStation(devices[0]);
|
||||
|
||||
// Auto-refresh every 60 s
|
||||
@@ -3014,8 +3009,7 @@ async function fetchAmbientStation() {
|
||||
} catch {}
|
||||
}, 60000);
|
||||
} catch (e) {
|
||||
if (errorEl) { errorEl.textContent = 'Could not reach station.'; show(errorEl); }
|
||||
if (loadingEl) hide(loadingEl);
|
||||
tabMsg('error', 'Could not reach your station.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3075,14 +3069,6 @@ function applyStationOverlay(device) {
|
||||
|
||||
if (data.uv != null) setEl('cur-uv', data.uv);
|
||||
|
||||
const indoorCard = document.getElementById('st-indoor-card');
|
||||
if (data.tempinf != null) {
|
||||
setEl('st-tempin', fmtTemp(data.tempinf));
|
||||
setEl('st-tempin-unit', isMetric ? 'C' : 'F');
|
||||
setEl('st-humin', data.humidityin != null ? `${data.humidityin}% RH` : '');
|
||||
if (indoorCard) indoorCard.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// Rain summary in hero sub-line
|
||||
const rainEl = document.getElementById('cur-precip');
|
||||
if (rainEl && data.dailyrainin != null) {
|
||||
@@ -3113,73 +3099,8 @@ function applyStationOverlay(device) {
|
||||
|
||||
function renderAmbientStation(device) {
|
||||
state.ambientDevice = device;
|
||||
const data = device?.lastData || {};
|
||||
const info = device?.info || {};
|
||||
const contentEl = document.getElementById('station-content');
|
||||
const peekEl = document.getElementById('station-drawer-peek');
|
||||
|
||||
const stName = info.name || device?.macAddress || 'My Station';
|
||||
const nameEl = document.getElementById('station-name');
|
||||
if (nameEl) nameEl.textContent = stName;
|
||||
|
||||
const updatedEl = document.getElementById('station-updated');
|
||||
if (updatedEl && data.dateutc) {
|
||||
const ago = Math.round((Date.now() - data.dateutc) / 60000);
|
||||
updatedEl.textContent = ago <= 1 ? 'just now' : `${ago} min ago`;
|
||||
}
|
||||
|
||||
const isMetric = state.units !== 'us';
|
||||
|
||||
function fmtTemp(f) {
|
||||
if (f == null) return '--';
|
||||
return isMetric ? Math.round((f - 32) * 5 / 9) : Math.round(f);
|
||||
}
|
||||
function fmtSpeed(mph) {
|
||||
if (mph == null) return '--';
|
||||
return isMetric ? Math.round(mph * 1.60934) : Math.round(mph);
|
||||
}
|
||||
function fmtRain(inches) {
|
||||
if (inches == null) return '--';
|
||||
return isMetric ? (inches * 25.4).toFixed(1) : inches.toFixed(2);
|
||||
}
|
||||
|
||||
const tempUnit = isMetric ? 'C' : 'F';
|
||||
const speedUnit = isMetric ? 'km/h' : 'mph';
|
||||
const rainUnit = isMetric ? 'mm' : 'in';
|
||||
|
||||
const setEl = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; };
|
||||
|
||||
setEl('st-temp', fmtTemp(data.tempf));
|
||||
setEl('st-temp-unit', tempUnit);
|
||||
setEl('st-feels', data.feelsLike != null ? `Feels ${fmtTemp(data.feelsLike)}°` : '');
|
||||
setEl('st-hum', data.humidity != null ? `${data.humidity}` : '--');
|
||||
|
||||
if (data.tempf != null && data.humidity != null) {
|
||||
const tc = (data.tempf - 32) * 5 / 9;
|
||||
const alpha = Math.log(data.humidity / 100) + (17.62 * tc) / (243.12 + tc);
|
||||
const dewC = (243.12 * alpha) / (17.62 - alpha);
|
||||
const dewDisplay = isMetric ? Math.round(dewC) : Math.round(dewC * 9 / 5 + 32);
|
||||
setEl('st-dew', `Dew ${dewDisplay}°`);
|
||||
}
|
||||
|
||||
setEl('st-wind', fmtSpeed(data.windspeedmph));
|
||||
setEl('st-wind-unit', speedUnit);
|
||||
setEl('st-wind-dir', data.winddir != null ? compassDirFull(data.winddir) : '');
|
||||
setEl('st-gust', fmtSpeed(data.windgustmph));
|
||||
setEl('st-gust-unit', speedUnit);
|
||||
setEl('st-rain-day', fmtRain(data.dailyrainin));
|
||||
setEl('st-rain-unit', rainUnit);
|
||||
const rrDisplay = data.hourlyrainin != null ? `${fmtRain(data.hourlyrainin)} ${rainUnit}/hr` : '';
|
||||
setEl('st-rain-rate', rrDisplay);
|
||||
setEl('st-pressure', data.baromrelin != null ? data.baromrelin.toFixed(2) : '--');
|
||||
setEl('st-uv', data.uv != null ? data.uv : '--');
|
||||
setEl('st-solar', data.solarradiation != null ? Math.round(data.solarradiation) : '--');
|
||||
|
||||
if (peekEl && data.tempf != null) {
|
||||
peekEl.textContent = `· ${fmtTemp(data.tempf)}°${tempUnit} ${data.humidity != null ? data.humidity + '% RH' : ''}`;
|
||||
}
|
||||
|
||||
if (contentEl) show(contentEl);
|
||||
// The Station tab (station-live.js) renders the readings
|
||||
if (typeof window.renderStationTab === 'function') window.renderStationTab(device);
|
||||
|
||||
// Update the main conditions display with real station readings
|
||||
if (state.currentWeather) applyStationOverlay(device);
|
||||
|
||||
Reference in new issue
Block a user