diff --git a/frontend.tgz b/frontend.tgz new file mode 100644 index 0000000..e032bd2 Binary files /dev/null and b/frontend.tgz differ diff --git a/frontend/static/app.js b/frontend/static/app.js index 28978de..a9dee35 100644 --- a/frontend/static/app.js +++ b/frontend/static/app.js @@ -11,6 +11,7 @@ let activeSession = null; let latestReading = null; let setpoints = []; let chartInstance = null; +let cardsInitialized = false; async function api(path, options = {}) { const resp = await fetch(path, options); @@ -28,46 +29,33 @@ function statusClass(connected) { return connected ? 'ok' : 'danger'; } -function renderProbeCards(reading, setpoints) { +function createProbeCards() { const container = document.getElementById('probe-cards'); container.innerHTML = ''; - const probes = reading?.probes || []; - const unit = reading?.unit || 'F'; - for (let i = 0; i < 4; i++) { - const probe = probes[i] || { id: i + 1, name: DEFAULT_PROBE_NAMES[i], temperature: null, connected: false }; - const setpoint = setpoints.find(s => s.probe_number === (i + 1)); - const card = document.createElement('div'); - card.className = `probe-card ${probe.connected ? 'connected' : ''}`; - - const tempDisplay = probe.connected - ? `
${probe.temperature.toFixed(1)}°${unit}
` - : `
---
`; - - const targetDisplay = setpoint - ? `
Target: ${setpoint.label || ''} ${setpoint.target_temp.toFixed(0)}°${unit}
` - : `
No target set
`; + card.className = 'probe-card'; + card.dataset.probe = i + 1; card.innerHTML = `
- ${probe.name || DEFAULT_PROBE_NAMES[i]} - #${i + 1} + ${DEFAULT_PROBE_NAMES[i]} + #${i + 1}
- ${targetDisplay} - ${tempDisplay} +
No target set
+
---
- - + +
`; @@ -75,7 +63,7 @@ function renderProbeCards(reading, setpoints) { container.appendChild(card); } - // Wire up preset selectors and save buttons. + // Wire up preset selectors and save buttons once. document.querySelectorAll('.preset-select').forEach(sel => { sel.addEventListener('change', () => { const probe = sel.dataset.probe; @@ -95,6 +83,63 @@ function renderProbeCards(reading, setpoints) { await saveSetpoint(probe, label, target); }); }); + + cardsInitialized = true; +} + +function updateProbeCards(reading, setpoints) { + const probes = reading?.probes || []; + const unit = reading?.unit || 'F'; + + for (let i = 0; i < 4; i++) { + const card = document.querySelector(`.probe-card[data-probe="${i + 1}"]`); + if (!card) continue; + + const probe = probes[i] || { id: i + 1, name: DEFAULT_PROBE_NAMES[i], temperature: null, connected: false }; + const setpoint = setpoints.find(s => s.probe_number === (i + 1)); + + card.classList.toggle('connected', probe.connected); + card.querySelector('.name').textContent = probe.name || DEFAULT_PROBE_NAMES[i]; + + const targetEl = card.querySelector('.target'); + if (setpoint) { + targetEl.textContent = `Target: ${setpoint.label ? setpoint.label + ' ' : ''}${setpoint.target_temp.toFixed(0)}°${unit}`; + } else { + targetEl.textContent = 'No target set'; + } + + const tempEl = card.querySelector('.temp'); + if (probe.connected) { + tempEl.classList.remove('disconnected'); + tempEl.innerHTML = `${probe.temperature.toFixed(1)}°${unit}`; + } else { + tempEl.classList.add('disconnected'); + tempEl.textContent = '---'; + } + + card.querySelector('.status').textContent = probe.connected ? 'Connected' : 'Disconnected'; + + // Only update setpoint placeholders, never overwrite values while typing. + const customInput = card.querySelector('.custom-target'); + const labelInput = card.querySelector('.target-label'); + const presetSelect = card.querySelector('.preset-select'); + if (!customInput.matches(':focus') && !labelInput.matches(':focus')) { + customInput.placeholder = `Target °${unit}`; + if (!customInput.value && setpoint) { + customInput.value = setpoint.target_temp; + } + if (!labelInput.value && setpoint) { + labelInput.value = setpoint.label || ''; + } + // Update preset option labels with current unit. + Array.from(presetSelect.options).forEach((opt, idx) => { + if (idx > 0 && idx <= PRESETS.length) { + const p = PRESETS[idx - 1]; + opt.textContent = `${p.label} (${p.temp}°${unit})`; + } + }); + } + } } async function saveSetpoint(probeNumber, label, targetTemp) { @@ -105,7 +150,8 @@ async function saveSetpoint(probeNumber, label, targetTemp) { body: JSON.stringify({ probe_number: probeNumber, label, target_temp: targetTemp }), }); await loadSetpoints(); - await refreshDashboard(); + // Do not call refreshDashboard here; it would clear the input the user just used. + // The next periodic poll will pick up the new setpoint from /api/status. } function renderStatusBar(reading) { @@ -161,13 +207,12 @@ async function refreshDashboard() { const status = await api('/api/status'); latestReading = status.latest_reading; - // Add probe names from active session if we had stored them; for now use CYD names. - if (latestReading) { - // The CYD returns probe names; pass them through. + if (!cardsInitialized) { + createProbeCards(); } + updateProbeCards(status.latest_reading, setpoints); renderStatusBar(status.latest_reading); - renderProbeCards(status.latest_reading, setpoints); document.getElementById('last-updated').textContent = `Last updated: ${formatTime(new Date().toISOString())}`; } catch (e) { console.error('Refresh failed:', e); @@ -257,16 +302,12 @@ async function init() { initChart(); await loadSessions(); await loadSetpoints(); + // First call creates the cards; subsequent calls only update values. await refreshDashboard(); await refreshChart(); - setInterval(async () => { - await refreshDashboard(); - }, 5000); - - setInterval(async () => { - if (activeSession) await refreshChart(); - }, 30000); + setInterval(refreshDashboard, 5000); + setInterval(() => { if (activeSession) refreshChart(); }, 30000); } init();