Compare commits

..

7 Commits

Author SHA256 Message Date
91bcb528c1 update todo 2025-07-27 19:45:33 -04:00
d06b32c1b7 update todo 2025-07-27 11:05:44 -04:00
212dea6a7d Move storage commands to planStorage to help with improving UI in future 2025-07-27 11:04:39 -04:00
af89ca96a1 task list/todo 2025-07-27 09:20:24 -04:00
981a736821 fix git url 2025-07-13 21:56:16 -04:00
3dd3f80b04 a mostly ai generated readme 2025-07-13 21:54:36 -04:00
976fd9f647 Add the license
BSD 2 clause
2025-07-14 01:35:03 +00:00
5 changed files with 244 additions and 40 deletions

25
LICENSE Normal file
View File

@@ -0,0 +1,25 @@
BSD 2-Clause License
Copyright (c) 2025, Terrence Ezrol (ezterry)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

66
README.MD Normal file
View File

@@ -0,0 +1,66 @@
# Manual Coffee Roasting Plan
**Project Name:** Manual Coffee Roasting Plan
**Hosting:** [View on Gitea](https://git-hojo.devnull.name/ezterry/Manual-Coffee-Roasting-Plan)
---
### Summary
A tool to track and visualize the manual coffee roasting process, including timer functionality and potential chart-based roasting curve tracking.
---
### Installation
1. Clone the repository.
2. Install dependencies:
```bash
npm install
# or
yarn install
```
---
### Development Environment (VS Code)
1. Open the project in VS Code.
2. Ensure the following extensions are installed:
- **TypeScript** (for `.ts` file support)
- **Parcel** (for bundling)
3. Start the dev server:
```bash
npm start
# or
yarn start
```
This will serve the app at `http://localhost:1234` (default Parcel port).
---
### Deployment
Build the production-ready files:
```bash
npm run build
# or
yarn build
```
The output will be in the `dist/` folder. Deploy this folder to any static hosting service (e.g., GitHub Pages, Netlify, or Gitea's built-in static hosting).
---
### Technologies Used
- **TypeScript** (`src/roast.ts`) for type-safe logic.
- **Chart.js** and **chartjs-plugin-annotation** for potential roasting curve visualizations.
- **Parcel** for bundling and optimization.
- **PostCSS** and **Autoprefixer** for CSS compatibility.
---
### Notes
- The core logic is in toast.ts
- The graph visual is in tempgraph.ts
- roast.html is the main web app, this is all static server content but the typescript may save some local information at your request on your system
---
### Contributing
Pull requests are welcome! For major changes, please open an issue first.

40
TODO.md Normal file
View File

@@ -0,0 +1,40 @@
# TODO List Template for Project
## Overview
This template outlines the current state of the project files and future tasks to improve organization and functionality.
### Current State
#### `roast.ts`
- Manages timer functions (`startTimer`, `stopTimer`).
- Handles event logging (`logEvent`) and system reset.
- Provides plan setting and navigation through temperature buttons.
- Implements CSV export functionality.
- Supports graph display and manipulation.
- Manages saved roast plans (save, load, clear).
#### `roast.html`
- Defines the user interface for coffee roast logging.
- Includes input fields for setting roast plans and saving them.
- Contains control elements to manage events and export data.
#### `roast.css`
- Styles the timer display with phase-specific backgrounds.
- Formats buttons and containers for a cohesive visual layout.
- Ensures responsiveness and accessibility across devices.
### Future Tasks
1. **Enhance User Interface:**
- Update visual styling for better user experience in `roast.css`.
- Implement a popup library window for managing roast plans, allowing users to save, restore, edit, and delete plans.
- Integrate access to hard-coded library plans that can be added to the user's saved list if the online library changes.
2. **Expand Functionality:**
- Add information about the rate of rise during roasting.
- Capture details about the roast (start/end weight).
- Implement a favorite button for quick access to frequently used plans.
3. **Documentation and Testing:**
- Create comprehensive documentation for all features.
- Develop test cases to ensure robustness of functionality.

74
src/planStorage.ts Normal file
View File

@@ -0,0 +1,74 @@
// planStorage.ts
/**
* Saves a roast plan to local storage.
*
* @param name The name of the plan.
* @param temperatures A comma-separated string of temperature targets.
*/
export function savePlan(name: string, temperatures: string): void {
if (!name) {
throw new Error("Please provide a valid name for the roast plan.");
}
const trimmedTemperatures = temperatures.trim();
if (!trimmedTemperatures) {
throw new Error("Temperature list is empty.");
}
// Parse and validate temperature input
const parsedTemps = trimmedTemperatures.split(',').map(x => x.trim()).map(Number);
if (parsedTemps.some(val => isNaN(val))) {
throw new Error("Invalid temperature list. Please enter comma-separated numbers.");
}
localStorage.setItem(`roastplan_${name}`, trimmedTemperatures);
}
/**
* Loads a saved roast plan from local storage.
*
* @param name The name of the plan to load.
* @returns A string containing the temperature targets, or null if not found.
*/
export function retrievePlan(name: string): string | null {
if (!name) return null;
const stored = localStorage.getItem(`roastplan_${name}`);
return stored || null;
}
/**
* Deletes all saved roast plans from local storage.
*/
export function clearAllPlans(): void {
const keysToRemove: string[] = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && key.startsWith("roastplan_")) {
keysToRemove.push(key);
}
}
keysToRemove.forEach(key => localStorage.removeItem(key));
}
/**
* Lists all saved roast plan names from local storage.
*
* @returns An array of strings representing the names of saved plans.
*/
export function listSavedPlans(): string[] {
const savedPlanNames: string[] = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && key.startsWith("roastplan_")) {
const planName = key.replace("roastplan_", "");
savedPlanNames.push(planName);
}
}
return savedPlanNames;
}

View File

@@ -1,4 +1,5 @@
import { loadGraph } from './tempgraph.ts';
import { savePlan, retrievePlan, clearAllPlans, listSavedPlans } from './planStorage.ts';
let timer = 0;
let isRunning = false;
@@ -275,53 +276,61 @@ function saveCurrentPlan(): void {
const planName = nameInput.value.trim();
const rawInput = (document.getElementById("plan-input") as HTMLInputElement).value.trim();
if (!planName) {
alert("Please enter a name to save the plan.");
return;
try {
savePlan(planName, rawInput);
alert(`Plan "${planName}" saved successfully.`);
} catch (error) {
alert(error.message);
}
if (!rawInput) {
alert("Temperature list is empty.");
return;
}
const parsed = rawInput.split(',').map(x => x.trim()).map(Number);
if (parsed.some(val => isNaN(val))) {
alert("Invalid temperature list. Please enter comma-separated numbers.");
return;
}
const tempStr = parsed.join(",");
localStorage.setItem(`roastplan_${planName}`, tempStr);
nameInput.value = "";
updateSavedPlansDropdown();
}
function loadSavedPlan(name: string): void {
if (!name) return;
const stored = localStorage.getItem(`roastplan_${name}`);
const stored = retrievePlan(name);
if (stored) {
targets = stored.split(",").map(Number);
currentTargetIndex = 0;
document.getElementById("controls")!.classList.remove("hidden");
document.getElementById("create-plan")!.classList.add("hidden");
// Set the plan in the text box
console.log("Set plan: " +stored);
const planInput = document.getElementById("plan-input") as HTMLInputElement;
const savedName = document.getElementById("save-name") as HTMLInputElement;
planInput.value = stored;
savedName.value = name;
generateTempButtons();
// Reset the input dropdown to the first (choose prompt) entry
updateSavedPlansDropdown();
} else {
alert(`No plan found with the name "${name}".`);
}
}
function updateSavedPlansDropdown(): void {
const dropdown = document.getElementById("saved-plans") as HTMLSelectElement;
dropdown.innerHTML = '<option value="">-- Select saved plan --</option>';
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && key.startsWith("roastplan_")) {
const name = key.replace("roastplan_", "");
const option = document.createElement("option");
option.value = name;
option.textContent = name;
dropdown.appendChild(option);
}
// Retrieve saved plans from planStorage.ts
const savedPlans = listSavedPlans();
// Reset to default prompt
dropdown.innerHTML = '<option value="">-- Select Saved Plan --</option>';
// Populate the dropdown with saved plans
savedPlans.forEach(planName => {
const option = document.createElement('option');
option.value = planName;
option.textContent = planName;
dropdown.appendChild(option);
});
// Set the first entry as selected if available
if (savedPlans.length > 0) {
dropdown.selectedIndex = 0; // Index 1 corresponds to the first actual plan
}
}
@@ -329,16 +338,7 @@ function clearSavedPlans(): void {
const confirmClear = confirm("Are you sure you want to delete all saved roast plans?");
if (!confirmClear) return;
const keysToRemove: string[] = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && key.startsWith("roastplan_")) {
keysToRemove.push(key);
}
}
keysToRemove.forEach(key => localStorage.removeItem(key));
clearAllPlans();
updateSavedPlansDropdown();
alert("All saved roast plans have been deleted.");
}
@@ -364,5 +364,4 @@ if (typeof window !== 'undefined') {
window.clearSavedPlans = clearSavedPlans;
}
export {};