Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a780cefa8 | ||
|
|
306d256832 | ||
|
|
5ef82748ea |
+57
-17
@@ -310,6 +310,30 @@
|
|||||||
saved locally first, then synced to your account whenever you are
|
saved locally first, then synced to your account whenever you are
|
||||||
online.
|
online.
|
||||||
</p>
|
</p>
|
||||||
|
<div class="field">
|
||||||
|
<span class="field-label">Temperature unit</span>
|
||||||
|
<div
|
||||||
|
class="segmented small"
|
||||||
|
id="temp-unit-toggle"
|
||||||
|
role="radiogroup"
|
||||||
|
aria-label="Temperature unit"
|
||||||
|
>
|
||||||
|
<label class="seg-opt"
|
||||||
|
><input type="radio" name="tempUnit" value="C" checked /><span
|
||||||
|
>°C</span
|
||||||
|
></label
|
||||||
|
>
|
||||||
|
<label class="seg-opt"
|
||||||
|
><input type="radio" name="tempUnit" value="F" /><span
|
||||||
|
>°F</span
|
||||||
|
></label
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<p class="field-note">
|
||||||
|
Changes how Machine Plan and Roast Log temperatures are shown.
|
||||||
|
Plans are always stored in °C, so switching back is lossless.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<p class="drawer-result">
|
<p class="drawer-result">
|
||||||
Install Roast Planner from your browser menu for a focused workspace.
|
Install Roast Planner from your browser menu for a focused workspace.
|
||||||
Updates wait for your confirmation so an in-progress plan is never
|
Updates wait for your confirmation so an in-progress plan is never
|
||||||
@@ -1371,7 +1395,14 @@
|
|||||||
fill="var(--surface-raised)"
|
fill="var(--surface-raised)"
|
||||||
/>
|
/>
|
||||||
<rect class="band" x="52" y="65.4" width="600" height="19.8" />
|
<rect class="band" x="52" y="65.4" width="600" height="19.8" />
|
||||||
<text x="58" y="62.4" class="bandline">
|
<text
|
||||||
|
x="58"
|
||||||
|
y="62.4"
|
||||||
|
class="bandline"
|
||||||
|
data-band-lo="176"
|
||||||
|
data-band-hi="187"
|
||||||
|
data-band-prefix="first-crack band, "
|
||||||
|
>
|
||||||
first-crack band, 176–187 °C
|
first-crack band, 176–187 °C
|
||||||
</text>
|
</text>
|
||||||
<g class="min">
|
<g class="min">
|
||||||
@@ -1428,15 +1459,15 @@
|
|||||||
<line class="ax" x1="52" y1="24" x2="52" y2="294" />
|
<line class="ax" x1="52" y1="24" x2="52" y2="294" />
|
||||||
<line class="ax" x1="52" y1="294" x2="652" y2="294" />
|
<line class="ax" x1="52" y1="294" x2="652" y2="294" />
|
||||||
<line class="ax" x1="652" y1="24" x2="652" y2="294" />
|
<line class="ax" x1="652" y1="24" x2="652" y2="294" />
|
||||||
<g class="al" text-anchor="end">
|
<g class="al curve-axis-temp" text-anchor="end">
|
||||||
<text x="47" y="296.8">60</text>
|
<text data-tempc="60" x="47" y="296.8">60</text>
|
||||||
<text x="47" y="260.8">80</text>
|
<text data-tempc="80" x="47" y="260.8">80</text>
|
||||||
<text x="47" y="224.8">100</text>
|
<text data-tempc="100" x="47" y="224.8">100</text>
|
||||||
<text x="47" y="188.8">120</text>
|
<text data-tempc="120" x="47" y="188.8">120</text>
|
||||||
<text x="47" y="152.8">140</text>
|
<text data-tempc="140" x="47" y="152.8">140</text>
|
||||||
<text x="47" y="116.8">160</text>
|
<text data-tempc="160" x="47" y="116.8">160</text>
|
||||||
<text x="47" y="80.8">180</text>
|
<text data-tempc="180" x="47" y="80.8">180</text>
|
||||||
<text x="47" y="44.8">200</text>
|
<text data-tempc="200" x="47" y="44.8">200</text>
|
||||||
</g>
|
</g>
|
||||||
<g class="al" text-anchor="middle">
|
<g class="al" text-anchor="middle">
|
||||||
<text x="52" y="304">0:00</text>
|
<text x="52" y="304">0:00</text>
|
||||||
@@ -1979,7 +2010,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th style="width: 28mm">Milestone</th>
|
<th style="width: 28mm">Milestone</th>
|
||||||
<th class="num" style="width: 20mm">Time</th>
|
<th class="num" style="width: 20mm">Time</th>
|
||||||
<th class="num" style="width: 18mm">°C</th>
|
<th class="num" style="width: 18mm" id="print-machine-temp-th">°C</th>
|
||||||
<th class="num" style="width: 36mm">Your logged band</th>
|
<th class="num" style="width: 36mm">Your logged band</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -1995,7 +2026,7 @@
|
|||||||
name="temps.charge.tempC"
|
name="temps.charge.tempC"
|
||||||
/><span class="pv"></span>
|
/><span class="pv"></span>
|
||||||
</td>
|
</td>
|
||||||
<td class="num">133–195 °C</td>
|
<td class="num" data-band-lo="133" data-band-hi="195">133–195 °C</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Turning point</td>
|
<td>Turning point</td>
|
||||||
@@ -2012,7 +2043,7 @@
|
|||||||
name="temps.tp.tempC"
|
name="temps.tp.tempC"
|
||||||
/><span class="pv"></span>
|
/><span class="pv"></span>
|
||||||
</td>
|
</td>
|
||||||
<td class="num">77–100 °C @ 0:47–1:15</td>
|
<td class="num" data-band-lo="77" data-band-hi="100" data-band-suffix=" @ 0:47–1:15">77–100 °C @ 0:47–1:15</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Yellow</td>
|
<td>Yellow</td>
|
||||||
@@ -2025,7 +2056,7 @@
|
|||||||
name="temps.yellow.tempC"
|
name="temps.yellow.tempC"
|
||||||
/><span class="pv"></span>
|
/><span class="pv"></span>
|
||||||
</td>
|
</td>
|
||||||
<td class="num">148–166 °C</td>
|
<td class="num" data-band-lo="148" data-band-hi="166">148–166 °C</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>First crack</td>
|
<td>First crack</td>
|
||||||
@@ -2036,7 +2067,7 @@
|
|||||||
name="temps.fc.tempC"
|
name="temps.fc.tempC"
|
||||||
/><span class="pv"></span>
|
/><span class="pv"></span>
|
||||||
</td>
|
</td>
|
||||||
<td class="num">176–187 °C</td>
|
<td class="num" data-band-lo="176" data-band-hi="187">176–187 °C</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Drop</td>
|
<td>Drop</td>
|
||||||
@@ -2047,7 +2078,7 @@
|
|||||||
name="temps.drop.tempC"
|
name="temps.drop.tempC"
|
||||||
/><span class="pv"></span>
|
/><span class="pv"></span>
|
||||||
</td>
|
</td>
|
||||||
<td class="num">183–197 °C light</td>
|
<td class="num" data-band-lo="183" data-band-hi="197" data-band-suffix=" light">183–197 °C light</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -2134,7 +2165,15 @@
|
|||||||
width="600"
|
width="600"
|
||||||
height="19.8"
|
height="19.8"
|
||||||
/>
|
/>
|
||||||
<text x="58" y="62.4" class="alr" style="font-size: 7px">
|
<text
|
||||||
|
x="58"
|
||||||
|
y="62.4"
|
||||||
|
class="alr"
|
||||||
|
style="font-size: 7px"
|
||||||
|
data-band-lo="176"
|
||||||
|
data-band-hi="187"
|
||||||
|
data-band-prefix="your first-crack band, "
|
||||||
|
>
|
||||||
your first-crack band, 176–187 °C
|
your first-crack band, 176–187 °C
|
||||||
</text>
|
</text>
|
||||||
<g class="min">
|
<g class="min">
|
||||||
@@ -2207,6 +2246,7 @@
|
|||||||
class="ttl"
|
class="ttl"
|
||||||
transform="rotate(-90 14 159)"
|
transform="rotate(-90 14 159)"
|
||||||
text-anchor="middle"
|
text-anchor="middle"
|
||||||
|
id="print-curve-axis-title"
|
||||||
>
|
>
|
||||||
BEAN TEMP °C
|
BEAN TEMP °C
|
||||||
</text>
|
</text>
|
||||||
|
|||||||
+80
-3
@@ -26,6 +26,30 @@ let deferredInstallPrompt = null;
|
|||||||
let lotPicker = null;
|
let lotPicker = null;
|
||||||
const BAND_DOMAIN = [60, 220]; // shared °C domain for every band-track in the Machine Plan section
|
const BAND_DOMAIN = [60, 220]; // shared °C domain for every band-track in the Machine Plan section
|
||||||
|
|
||||||
|
// Temperature fields are always stored in state.plan as canonical °C — this is purely a
|
||||||
|
// display-layer preference. tempC/actualBt/expectedBt cover Machine Plan, Roast Log, and
|
||||||
|
// the actuator schedule respectively; every other numeric field (%, g, days) is untouched.
|
||||||
|
const TEMP_UNIT_KEY = "roastPlannerTempUnit.v1";
|
||||||
|
const TEMP_FIELD_RE = /\.(tempC|actualBt|expectedBt)$/;
|
||||||
|
let tempUnit = localStorage.getItem(TEMP_UNIT_KEY) === "F" ? "F" : "C";
|
||||||
|
const cToF = (c) => (c * 9) / 5 + 32;
|
||||||
|
const fToC = (f) => ((f - 32) * 5) / 9;
|
||||||
|
function isTempField(name) {
|
||||||
|
return TEMP_FIELD_RE.test(name);
|
||||||
|
}
|
||||||
|
// Canonical °C string -> what the field should display right now.
|
||||||
|
function tempToDisplay(canonical) {
|
||||||
|
const n = Number.parseFloat(canonical);
|
||||||
|
if (!Number.isFinite(n)) return canonical ?? "";
|
||||||
|
return String(Math.round(tempUnit === "F" ? cToF(n) : n));
|
||||||
|
}
|
||||||
|
// What the user typed (in the current display unit) -> canonical °C string.
|
||||||
|
function tempToCanonical(display) {
|
||||||
|
const n = Number.parseFloat(display);
|
||||||
|
if (!Number.isFinite(n)) return display;
|
||||||
|
return String(Math.round((tempUnit === "F" ? fToC(n) : n) * 10) / 10);
|
||||||
|
}
|
||||||
|
|
||||||
export const state = { plan: blankPlan() };
|
export const state = { plan: blankPlan() };
|
||||||
|
|
||||||
const form = document.getElementById("plan-form");
|
const form = document.getElementById("plan-form");
|
||||||
@@ -201,6 +225,7 @@ function renderActuatorTimeline() {
|
|||||||
function renderActuators() {
|
function renderActuators() {
|
||||||
renderActuatorPrintRows();
|
renderActuatorPrintRows();
|
||||||
renderActuatorTimeline();
|
renderActuatorTimeline();
|
||||||
|
updateTempUnitUI(); // new actuator rows need their expectedBt placeholder set too
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- populate every control in the form from state.plan
|
// ---- populate every control in the form from state.plan
|
||||||
@@ -214,7 +239,7 @@ export function renderFormFromPlan() {
|
|||||||
} else if (el.type === "checkbox") {
|
} else if (el.type === "checkbox") {
|
||||||
el.checked = Boolean(value);
|
el.checked = Boolean(value);
|
||||||
} else {
|
} else {
|
||||||
el.value = value ?? "";
|
el.value = isTempField(name) ? tempToDisplay(value) : (value ?? "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateBlendVisibility(state.plan.fields["2.1"]);
|
updateBlendVisibility(state.plan.fields["2.1"]);
|
||||||
@@ -310,7 +335,8 @@ function renderBandMarkers() {
|
|||||||
const input = row.querySelector('input[name$=".tempC"]');
|
const input = row.querySelector('input[name$=".tempC"]');
|
||||||
const marker = row.querySelector(".band-marker");
|
const marker = row.querySelector(".band-marker");
|
||||||
if (!input || !marker) continue;
|
if (!input || !marker) continue;
|
||||||
const v = Number.parseFloat(input.value);
|
const raw = Number.parseFloat(input.value);
|
||||||
|
const v = Number.isFinite(raw) ? (tempUnit === "F" ? fToC(raw) : raw) : NaN;
|
||||||
if (!Number.isFinite(v)) {
|
if (!Number.isFinite(v)) {
|
||||||
marker.style.display = "none";
|
marker.style.display = "none";
|
||||||
continue;
|
continue;
|
||||||
@@ -587,7 +613,10 @@ function wireForm() {
|
|||||||
const el = e.target;
|
const el = e.target;
|
||||||
if (!el.name) return;
|
if (!el.name) return;
|
||||||
if (el.type === "radio" && !el.checked) return;
|
if (el.type === "radio" && !el.checked) return;
|
||||||
setValueForName(el.name, el.value);
|
setValueForName(
|
||||||
|
el.name,
|
||||||
|
isTempField(el.name) ? tempToCanonical(el.value) : el.value,
|
||||||
|
);
|
||||||
if (el.name === "1.1") cultivarAutofill(el.value);
|
if (el.name === "1.1") cultivarAutofill(el.value);
|
||||||
if (el.name === "2.1") updateBlendVisibility(el.value);
|
if (el.name === "2.1") updateBlendVisibility(el.value);
|
||||||
if (el.name.startsWith("blendComponents.")) updateBlendTotal();
|
if (el.name.startsWith("blendComponents.")) updateBlendTotal();
|
||||||
@@ -595,6 +624,52 @@ function wireForm() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refreshes every unit-facing bit of chrome (labels, placeholders, the toggle itself) after
|
||||||
|
// tempUnit changes. Field *values* are handled separately by renderFormFromPlan().
|
||||||
|
function updateTempUnitUI() {
|
||||||
|
const label = tempUnit === "F" ? "°F" : "°C";
|
||||||
|
for (const input of document.querySelectorAll(
|
||||||
|
'input[name$=".tempC"], input[name$=".actualBt"], input[name$=".expectedBt"]',
|
||||||
|
))
|
||||||
|
input.placeholder = label;
|
||||||
|
for (const span of document.querySelectorAll("#sec-machine .unit-input .unit"))
|
||||||
|
span.textContent = label;
|
||||||
|
const printHeader = document.getElementById("print-machine-temp-th");
|
||||||
|
if (printHeader) printHeader.textContent = label;
|
||||||
|
for (const input of document.querySelectorAll('#temp-unit-toggle input[name="tempUnit"]'))
|
||||||
|
input.checked = input.value === tempUnit;
|
||||||
|
|
||||||
|
// Static reference numbers that live outside the plan (chart axes, band captions, the
|
||||||
|
// print worksheet's "Your logged band" column) — these are fixed constants, not plan
|
||||||
|
// data, so they're driven off data-tempc/data-band-lo/hi rather than valueForName().
|
||||||
|
const conv = (c) => Math.round(tempUnit === "F" ? cToF(c) : c);
|
||||||
|
for (const el of document.querySelectorAll("[data-tempc]"))
|
||||||
|
el.textContent = String(conv(Number(el.dataset.tempc)));
|
||||||
|
for (const el of document.querySelectorAll("[data-band-lo]")) {
|
||||||
|
const lo = conv(Number(el.dataset.bandLo));
|
||||||
|
const hi = conv(Number(el.dataset.bandHi));
|
||||||
|
el.textContent = `${el.dataset.bandPrefix ?? ""}${lo}–${hi} ${label}${el.dataset.bandSuffix ?? ""}`;
|
||||||
|
}
|
||||||
|
const axisTitle = document.getElementById("print-curve-axis-title");
|
||||||
|
if (axisTitle) axisTitle.textContent = `BEAN TEMP ${label}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTempUnit(unit) {
|
||||||
|
tempUnit = unit === "F" ? "F" : "C";
|
||||||
|
localStorage.setItem(TEMP_UNIT_KEY, tempUnit);
|
||||||
|
updateTempUnitUI();
|
||||||
|
renderFormFromPlan();
|
||||||
|
recompute();
|
||||||
|
}
|
||||||
|
|
||||||
|
function wireTempUnitToggle() {
|
||||||
|
const toggle = document.getElementById("temp-unit-toggle");
|
||||||
|
if (!toggle) return;
|
||||||
|
toggle.addEventListener("change", (e) => {
|
||||||
|
if (e.target.name === "tempUnit") setTempUnit(e.target.value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function wireDrawers() {
|
function wireDrawers() {
|
||||||
const overlay = document.getElementById("drawer-overlay");
|
const overlay = document.getElementById("drawer-overlay");
|
||||||
const drawers = [...document.querySelectorAll(".drawer")];
|
const drawers = [...document.querySelectorAll(".drawer")];
|
||||||
@@ -941,6 +1016,8 @@ async function init() {
|
|||||||
initSideNav();
|
initSideNav();
|
||||||
wireDrawers();
|
wireDrawers();
|
||||||
wireToolbar();
|
wireToolbar();
|
||||||
|
wireTempUnitToggle();
|
||||||
|
updateTempUnitUI();
|
||||||
wirePwa();
|
wirePwa();
|
||||||
wireSectionNav();
|
wireSectionNav();
|
||||||
initPrefillPanel({
|
initPrefillPanel({
|
||||||
|
|||||||
Reference in New Issue
Block a user