Files
Manual-Coffee-Roasting-Plan/src/roast.ts
2025-07-13 14:03:49 -04:00

214 lines
6.2 KiB
TypeScript

import { loadGraph } from './tempgraph.ts';
let timer = 0;
let isRunning = false;
let log: Array<{ time: string, event: string, temp?: number }> = [];
let targets: number[] = [];
let currentTargetIndex = 0;
let interval: NodeJS.Timeout | null = null;
declare global {
interface Window {
startTimer: () => void;
stopTimer: () => void;
logEvent: (eventName: string) => void;
resetSystem: () => void;
setPlan: () => void;
navigateTemp: (direction: number) => void;
logTemp: (temp: number) => void;
exportCSV: () => void;
showGraph: () => void;
hideGraph: () => void;
}
}
function startTimer(): void {
if (isRunning) {
alert("Timer is already running. Click again to reset.");
return;
}
timer = 0;
updateTimerDisplay();
isRunning = true;
interval = setInterval(() => {
timer++;
updateTimerDisplay();
}, 1000);
}
function stopTimer(): void {
if(interval !== null){
clearInterval(interval);
}
isRunning = false;
}
function updateTimerDisplay(): void {
const minutes = Math.floor(timer / 60);
const seconds = timer % 60;
document.getElementById("timer")!.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`;
}
function logEvent(eventName: string): void {
if (eventName === "Charge") {
if (log.length > 0 || isRunning) {
if(isRunning){
stopTimer();
}
log = [];
timer = 0;
}
startTimer();
log.push({ time: formatTime(timer), event: eventName });
updateLog();
return;
}
if (eventName === "Drop") {
stopTimer();
log.push({ time: formatTime(timer), event: eventName });
updateLog();
document.getElementById("create-plan")!.classList.remove("hidden");
const tempButtons = document.getElementById("temp-buttons")!;
tempButtons.innerHTML = "";
return;
}
log.push({ time: formatTime(timer), event: eventName });
updateLog();
}
function resetSystem(): void {
stopTimer();
log = [];
updateLog();
document.getElementById("create-plan")!.classList.remove("hidden");
const tempButtons = document.getElementById("temp-buttons")!;
tempButtons.innerHTML = "";
}
function formatTime(seconds: number): string {
const minutes = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${minutes}:${secs.toString().padStart(2, '0')}`;
}
function setPlan(): void {
const input = (document.getElementById("plan-input") as HTMLInputElement).value;
targets = input.split(',').map(Number);
currentTargetIndex = 0;
document.getElementById("controls")!.classList.remove("hidden");
document.getElementById("create-plan")!.classList.add("hidden");
generateTempButtons();
}
function generateTempButtons(): void {
const container = document.getElementById("temp-buttons")!;
container.innerHTML = "";
if (targets.length === 0) {
container.innerHTML = "<p>No temperature plan set.</p>";
return;
}
const currentTarget = targets[currentTargetIndex];
const buttons: string[] = [];
buttons.push('<button class="nav-btn" onclick="navigateTemp(-1);">&lt;&lt;</button>');
for (let i = -2; i <= 2; i++) {
const temp = currentTarget + i;
buttons.push(`<button onclick="logTemp(${temp})">${temp}</button>`);
}
buttons.push('<button class="nav-btn" onclick="navigateTemp(1);">&gt;&gt;</button>');
container.innerHTML = buttons.join(" ");
}
function navigateTemp(direction: number): void {
currentTargetIndex += direction;
if (currentTargetIndex < 0) {
currentTargetIndex = 0;
} else if (currentTargetIndex >= targets.length) {
currentTargetIndex = targets.length - 1;
}
generateTempButtons();
}
function logTemp(temp: number): void {
log.push({ time: formatTime(timer), event: "Temp", temp });
updateLog();
currentTargetIndex++;
if (currentTargetIndex >= targets.length) {
currentTargetIndex = targets.length - 1;
}
generateTempButtons();
}
function updateLog(): void {
const tbody = document.getElementById("log-body")!;
tbody.innerHTML = log.map(entry => `
<tr>
<td>${entry.time}</td>
<td>${entry.event}</td>
<td>${entry.temp !== null ? entry.temp : ''}</td>
</tr>
`).join("");
}
// src/roast.ts
/**
* Generates an array of CSV strings line by line from log data.
*/
function createCSVData(): string[] {
const header = "Time,Event,Temperature";
const logEntries = log.map(entry => `${entry.time},${entry.event}${entry.temp !== null ? `,${entry.temp}` : ''}`);
return [header].concat(logEntries);
}
/**
* Creates CSV format content as a single string from the array returned by createCSVData().
*/
function generateCSVContent(): string {
return createCSVData().join("\n");
}
function exportCSV(): void {
const csvContent = generateCSVContent();
const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
const link = document.createElement("a");
link.setAttribute("href", URL.createObjectURL(blob));
link.setAttribute("download", "roast_log.csv");
link.style.display = "none";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
function showGraph(): void {
const graphContainer = document.getElementById("graph-container");
if (graphContainer) {
loadGraph(createCSVData());
graphContainer.classList.remove("hidden");
graphContainer.classList.add("graph-display-flex");
}
}
function hideGraph(): void {
const graphContainer = document.getElementById("graph-container");
if (graphContainer) {
graphContainer.classList.add("hidden");
graphContainer.classList.remove("graph-display-flex");
}
}
// Expose functions to the global scope (allowing html direct access)
if (typeof window !== 'undefined') {
window.startTimer = startTimer;
window.stopTimer = stopTimer;
window.logEvent = logEvent;
window.resetSystem = resetSystem;
window.setPlan = setPlan;
window.navigateTemp = navigateTemp;
window.logTemp = logTemp;
window.exportCSV = exportCSV;
window.showGraph = showGraph;
window.hideGraph = hideGraph;
}
export {};