fix: stop probe card re-render from clearing form inputs
- Split renderProbeCards into createProbeCards (once) and updateProbeCards (mutates only the values: name, target, temp, status, dot color). - Skip refreshing setpoint input values while a field has focus so typing is not interrupted by the 5s poll. - Stop calling refreshDashboard after saveSetpoint to avoid clobbering input.
This commit is contained in:
Binary file not shown.
+78
-37
@@ -11,6 +11,7 @@ let activeSession = null;
|
|||||||
let latestReading = null;
|
let latestReading = null;
|
||||||
let setpoints = [];
|
let setpoints = [];
|
||||||
let chartInstance = null;
|
let chartInstance = null;
|
||||||
|
let cardsInitialized = false;
|
||||||
|
|
||||||
async function api(path, options = {}) {
|
async function api(path, options = {}) {
|
||||||
const resp = await fetch(path, options);
|
const resp = await fetch(path, options);
|
||||||
@@ -28,46 +29,33 @@ function statusClass(connected) {
|
|||||||
return connected ? 'ok' : 'danger';
|
return connected ? 'ok' : 'danger';
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderProbeCards(reading, setpoints) {
|
function createProbeCards() {
|
||||||
const container = document.getElementById('probe-cards');
|
const container = document.getElementById('probe-cards');
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
|
|
||||||
const probes = reading?.probes || [];
|
|
||||||
const unit = reading?.unit || 'F';
|
|
||||||
|
|
||||||
for (let i = 0; i < 4; i++) {
|
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');
|
const card = document.createElement('div');
|
||||||
card.className = `probe-card ${probe.connected ? 'connected' : ''}`;
|
card.className = 'probe-card';
|
||||||
|
card.dataset.probe = i + 1;
|
||||||
const tempDisplay = probe.connected
|
|
||||||
? `<div class="temp">${probe.temperature.toFixed(1)}<span>°${unit}</span></div>`
|
|
||||||
: `<div class="temp disconnected">---</div>`;
|
|
||||||
|
|
||||||
const targetDisplay = setpoint
|
|
||||||
? `<div class="target">Target: ${setpoint.label || ''} ${setpoint.target_temp.toFixed(0)}°${unit}</div>`
|
|
||||||
: `<div class="target">No target set</div>`;
|
|
||||||
|
|
||||||
card.innerHTML = `
|
card.innerHTML = `
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<span class="name">${probe.name || DEFAULT_PROBE_NAMES[i]}</span>
|
<span class="name">${DEFAULT_PROBE_NAMES[i]}</span>
|
||||||
<span>#${i + 1}</span>
|
<span class="number">#${i + 1}</span>
|
||||||
</div>
|
</div>
|
||||||
${targetDisplay}
|
<div class="target">No target set</div>
|
||||||
${tempDisplay}
|
<div class="temp">---</div>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<span>${probe.connected ? 'Connected' : 'Disconnected'}</span>
|
<span class="status">Disconnected</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="setpoint-editor">
|
<div class="setpoint-editor">
|
||||||
<select class="preset-select" data-probe="${i + 1}">
|
<select class="preset-select" data-probe="${i + 1}">
|
||||||
<option value="">Preset...</option>
|
<option value="">Preset...</option>
|
||||||
${PRESETS.map(p => `<option value="${p.temp}">${p.label} (${p.temp}°${unit})</option>`).join('')}
|
${PRESETS.map(p => `<option value="${p.temp}">${p.label}</option>`).join('')}
|
||||||
<option value="custom">Custom...</option>
|
<option value="custom">Custom...</option>
|
||||||
</select>
|
</select>
|
||||||
<input type="number" class="custom-target" data-probe="${i + 1}" placeholder="Target °${unit}" value="${setpoint ? setpoint.target_temp : ''}">
|
<input type="number" class="custom-target" data-probe="${i + 1}" placeholder="Target">
|
||||||
<input type="text" class="target-label" data-probe="${i + 1}" placeholder="Label" value="${setpoint ? setpoint.label : ''}">
|
<input type="text" class="target-label" data-probe="${i + 1}" placeholder="Label">
|
||||||
<button class="save-setpoint" data-probe="${i + 1}">Save</button>
|
<button class="save-setpoint" data-probe="${i + 1}">Save</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -75,7 +63,7 @@ function renderProbeCards(reading, setpoints) {
|
|||||||
container.appendChild(card);
|
container.appendChild(card);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wire up preset selectors and save buttons.
|
// Wire up preset selectors and save buttons once.
|
||||||
document.querySelectorAll('.preset-select').forEach(sel => {
|
document.querySelectorAll('.preset-select').forEach(sel => {
|
||||||
sel.addEventListener('change', () => {
|
sel.addEventListener('change', () => {
|
||||||
const probe = sel.dataset.probe;
|
const probe = sel.dataset.probe;
|
||||||
@@ -95,6 +83,63 @@ function renderProbeCards(reading, setpoints) {
|
|||||||
await saveSetpoint(probe, label, target);
|
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)}<span>°${unit}</span>`;
|
||||||
|
} 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) {
|
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 }),
|
body: JSON.stringify({ probe_number: probeNumber, label, target_temp: targetTemp }),
|
||||||
});
|
});
|
||||||
await loadSetpoints();
|
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) {
|
function renderStatusBar(reading) {
|
||||||
@@ -161,13 +207,12 @@ async function refreshDashboard() {
|
|||||||
const status = await api('/api/status');
|
const status = await api('/api/status');
|
||||||
latestReading = status.latest_reading;
|
latestReading = status.latest_reading;
|
||||||
|
|
||||||
// Add probe names from active session if we had stored them; for now use CYD names.
|
if (!cardsInitialized) {
|
||||||
if (latestReading) {
|
createProbeCards();
|
||||||
// The CYD returns probe names; pass them through.
|
|
||||||
}
|
}
|
||||||
|
updateProbeCards(status.latest_reading, setpoints);
|
||||||
|
|
||||||
renderStatusBar(status.latest_reading);
|
renderStatusBar(status.latest_reading);
|
||||||
renderProbeCards(status.latest_reading, setpoints);
|
|
||||||
document.getElementById('last-updated').textContent = `Last updated: ${formatTime(new Date().toISOString())}`;
|
document.getElementById('last-updated').textContent = `Last updated: ${formatTime(new Date().toISOString())}`;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Refresh failed:', e);
|
console.error('Refresh failed:', e);
|
||||||
@@ -257,16 +302,12 @@ async function init() {
|
|||||||
initChart();
|
initChart();
|
||||||
await loadSessions();
|
await loadSessions();
|
||||||
await loadSetpoints();
|
await loadSetpoints();
|
||||||
|
// First call creates the cards; subsequent calls only update values.
|
||||||
await refreshDashboard();
|
await refreshDashboard();
|
||||||
await refreshChart();
|
await refreshChart();
|
||||||
|
|
||||||
setInterval(async () => {
|
setInterval(refreshDashboard, 5000);
|
||||||
await refreshDashboard();
|
setInterval(() => { if (activeSession) refreshChart(); }, 30000);
|
||||||
}, 5000);
|
|
||||||
|
|
||||||
setInterval(async () => {
|
|
||||||
if (activeSession) await refreshChart();
|
|
||||||
}, 30000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|||||||
Reference in New Issue
Block a user