Skip to content

Commit

Permalink
Yet another prototype with debug, README update underway
Browse files Browse the repository at this point in the history
  • Loading branch information
TanukiSharp committed Apr 1, 2024
1 parent 20edd05 commit e998142
Show file tree
Hide file tree
Showing 14 changed files with 292 additions and 209 deletions.
25 changes: 8 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
# Overview

> [!CAUTION]
> If you ran into this project by mistake, DO NTO use it, it has a severe issue.
This project is a web-based blood alcohol content calculator.

Most of information needed to understand the (easy) math can be found here: https://en.wikipedia.org/wiki/Blood_alcohol_content
Most of the information needed to understand the (easy) math can be found here: https://en.wikipedia.org/wiki/Blood_alcohol_content

⚠️ Please have a look at the warning section at the end of this document.

Expand All @@ -15,37 +12,31 @@ The web application can be found here: https://tanukisharp.github.io/blood-alcoh

## How to run (for developers)

When working locally for development purpose, just spawn a local web server where the `index.js` is located and open a browser to the hosting address and port.
When working locally for development purpose, just spawn a local web server where the `index.js` file is located and open a browser to the hosting address and port.

## How to use (for users)

### Settings

The first thing to do is to do a bit of setup. For that, click on the settings button.

![Settings button](./docs/settings-button.png)
The first thing to do is to do a bit of setup. For that, click on the settings button. (⚙️)

Then set your `Body weight`, and your `Rho factor`. The women average is 0.58 and the men average is 0.71, as indicated on the settings page.
Then set your `Body weight`, and your `Rho factor`. The women Rho factor average is 0.58 L/kg and the men Rho factor average is 0.71 L/Kg, as indicated on the settings page.

As for the `Alcohol elimination rate`, you can let the default value, unless you know what you are doing.
As for the `Alcohol elimination rate`, you can let the default value (0.148 g/L/hour), unless you know what you are doing.

You can also set the `Driving limit` to the regulation value where you drive, but feel free to set it lower if you want.

Your settings are stored in the browser's local storage, so if you refresh the page or close it and reopen it later, your settings are maintained, but if you use another browser or uninstall it and reinstall it, or clean it's local storage data, you will have to setup the application again.
Your settings are stored in the browser's local storage, so if you refresh the page or close it and reopen it later, your settings remain, but if you use another browser or uninstall the browser and reinstall it, or clean it's local storage data, you will have to setup the application again.

### Main page

Then add a drink by clicking on the following button:

![Add drink button](./docs/add-drink-button.png)
Add a drink by clicking on the add drink (➕🍺) button.

A drink entry appears below. Set the `Drink quantity`, in liters, and the `Alcohol percentage` in percentage of alcohol contained in the drink.

When a drink is added, the `Started at` date and time are automatically set to now, but you can tweak it if you started to drink before entering values.

Click on the crossed swords button to remove a drink.

![Remove drink button](./docs/remove-drink-button.png)
Click on the crossed swords (⚔️) button to remove a drink.

On a drink entry, the `Time to zero` is the progression of the alcohol elimination. When the bar is full, the alcohol contained in the drink is completely eliminated by your body, and the entry turns green. At this moment, you can remove it, because it does not affect your alcohol concentration anymore.

Expand Down
87 changes: 47 additions & 40 deletions components/drink.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { addEventListener, bindToLabel, MILLISECONDS_PER_HOUR } from '../lib/utils.js';
import { addEventListener, bindToLabel } from '../lib/utils.js';
import { Time } from '../lib/time.js';

export class DrinkComponent {
constructor(quantity, alcoholPercentage, startedAt, userRemoveFunc, valueChangedFunc) {
Expand All @@ -12,7 +13,10 @@ export class DrinkComponent {
this._alcoholPercentageElement.value = alcoholPercentage;
this._startedAtElement.value = startedAt;

this.setTimeToZero(0, 0);
this.setBloodAlcoholConcentration(0);
this.setIsEffective(false);

this.evaluateTimeAndEffectiveness(startedAt);
}

get quantity() {
Expand All @@ -27,11 +31,15 @@ export class DrinkComponent {
return this._startedAtElement.value;
}

get isEffective() {
return this._isEffective;
}

get rootElement() {
return this._rootContainerElement;
}

isValid(now) {
_isValid(now) {
if (this.quantity <= 0 || this.alcoholPercentage <= 0) {
return false;
}
Expand All @@ -45,42 +53,37 @@ export class DrinkComponent {
return true;
}

SET_ENDS_AT(dt) {
this._endsAt = dt;

this._bloodAlcoholConcentration.innerText = `${this._concentration} - ${this._isEffective} - ${new Date(this._endsAt)}`;
}

setDrinkNumber(num) {
this._quantityLabelElement.innerText = `Drink ${num} quantity:`;
}

evaluateParameters(now) {
if (this.isValid(now) === false) {
this._rootContainerElement.style.backgroundColor = '#edf';
return false;
} else {
this._rootContainerElement.style.removeProperty('background-color');
return true;
}
setBloodAlcoholConcentration(concentration) {
this._concentration = concentration;

this._bloodAlcoholConcentration.innerText = `${this._concentration} - ${this._isEffective} - ${new Date(this._endsAt)}`;
}

setEliminationRatio(ratio) {
// this._timeToZeroProgressElement.style.width = `${ratio * 100}%`;
setIsEffective(isEffective) {
this._isEffective = isEffective;

// if (ratio >= 1) {
// this._rootContainerElement.classList.add('done');
// } else {
// this._rootContainerElement.classList.remove('done');
// }
this._bloodAlcoholConcentration.innerText = `${this._concentration} - ${this._isEffective} - ${new Date(this._endsAt)}`;
}

setTimeToZero(now, timeToZero) {
// const startedAt = new Date(this._startedAtElement.value).getTime();
evaluateTimeAndEffectiveness(now) {
this._rootContainerElement.classList.remove('is-invalid');
this._rootContainerElement.classList.remove('is-effective');

// const endOfZero = now + timeToZero * MILLISECONDS_PER_HOUR;
// const ratioOfZero = Math.min((now - startedAt) / (endOfZero - startedAt), 1);
// this._timeToZeroProgressElement.style.width = `${ratioOfZero * 100}%`;

// if (ratioOfZero >= 1) {
// this._rootContainerElement.classList.add('done');
// } else {
// this._rootContainerElement.classList.remove('done');
// }
if (this._isValid(now) === false) {
this._rootContainerElement.classList.add('is-invalid');
} else if (this._isEffective) {
this._rootContainerElement.classList.add('is-effective');
}
}

_delete() {
Expand Down Expand Up @@ -157,23 +160,27 @@ export class DrinkComponent {
this._startedAtElement.className = 'started-at value';
this._startedAtElement.type = 'datetime-local';
bindToLabel(startedAtLabelElement, this._startedAtElement);
addEventListener(this._disposeFunctions, this._startedAtElement, 'input', () => this._valueChangedFunc?.());
addEventListener(this._disposeFunctions, this._startedAtElement, 'input', () => {
this.evaluateTimeAndEffectiveness(Time.now());
this._valueChangedFunc?.();
});
this._rootContainerElement.appendChild(this._startedAtElement);

// ---

const timeToZeroLabelElement = document.createElement('span');
timeToZeroLabelElement.className = 'time-to zero label';
timeToZeroLabelElement.innerText = 'Time to zero:';
this._rootContainerElement.appendChild(timeToZeroLabelElement);
const bloodAlcoholConcentrationLabelElement = document.createElement('span');
bloodAlcoholConcentrationLabelElement.className = 'blood-alcohol-concentration label';
bloodAlcoholConcentrationLabelElement.innerText = 'Blood alcohol concentration:';
this._rootContainerElement.appendChild(bloodAlcoholConcentrationLabelElement);

const timeToZeroProgressContainerElement = document.createElement('div');
timeToZeroProgressContainerElement.className = 'time-to zero progress-container';
this._rootContainerElement.appendChild(timeToZeroProgressContainerElement);
this._bloodAlcoholConcentration = document.createElement('div');
this._bloodAlcoholConcentration.className = 'blood-alcohol-concentration value';
this._rootContainerElement.appendChild(this._bloodAlcoholConcentration);

this._timeToZeroProgressElement = document.createElement('div');
this._timeToZeroProgressElement.className = 'time-to zero progress-bar';
timeToZeroProgressContainerElement.appendChild(this._timeToZeroProgressElement);
const bloodAlcoholConcentrationHintElement = document.createElement('span');
bloodAlcoholConcentrationHintElement.className = 'blood-alcohol-concentration hint';
bloodAlcoholConcentrationHintElement.innerText = 'in g/L';
this._rootContainerElement.appendChild(bloodAlcoholConcentrationHintElement);

// ---

Expand Down
76 changes: 23 additions & 53 deletions components/main.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,23 @@
import { DrinkComponent } from './drink.js';
import { localStorageEx } from '../lib/localStorage.js';
import { toDateTimeInputElementString, round, toHumanReadableTime, toDateTime } from '../lib/utils.js';
import { toDateTimeInputElementString, round, toHumanReadableTime, addHoursToDate } from '../lib/utils.js';
import { Drink, Options, computeBloodAlcoholConcentration } from '../lib/bac.js';
import { settingsComponent } from './settings.js';
import { pagesController } from '../pagesController.js';
import { Time } from '../lib/time.js';

const DEBUG = false;
import { Debug } from '../lib/debug.js';
const debug = new Debug();

class MainComponent {
constructor() {
const debugCanvas = document.querySelector('.page.main > .debug');
if (DEBUG) {
this._debug = debugCanvas.getContext('2d');
this._debugTime = 0;
this._debugPrevBac = -1;
this._debug.canvas.width = this._debug.canvas.clientWidth;
this._debug.canvas.height = this._debug.canvas.clientHeight;
} else {
debugCanvas.style.display = 'none';
}

this._lastBloodAlcoholConcentration = 0;
this._drinks = [];
this._setupElements();
this._loadDrinks();
this._recompute();

if (DEBUG) {
this._debugNow = (new Date(2024, 2, 10, 22, 16, 55)).getTime();
for (let i = 0; i < 800; i += 1) {
this._recompute();
this._debugNow += 1000;
}
} else {
this._debugNow = Date.now();
setInterval(() => this._recompute(), 1000);
}
setInterval(() => this._recompute(), 1000);
}

_setupElements() {
Expand Down Expand Up @@ -70,7 +52,7 @@ class MainComponent {
let drink;

const onRemove = (cancellable) => {
if (DEBUG === false && this._lastBloodAlcoholConcentration > 0) {
if (drink.isEffective) {
if (prompt('Are you sure you want to delete this drink ?\nIt may end up in a completely wrong computation.\n\nType \'yes\' to confirm drink deletion.')?.toLocaleLowerCase() !== 'yes') {
cancellable.isCancelled = true;
return;
Expand Down Expand Up @@ -116,9 +98,9 @@ class MainComponent {
_updateTimeToLimitHint() {
if (settingsComponent.drivingLimit > 0) {
this._timeToLimitHintElement.innerText = `(${settingsComponent.drivingLimit} g/L)`;
this._timeToLimitContainerElement.style.removeProperty('display');
this._timeToLimitContainerElement.classList.remove('collapsed');
} else {
this._timeToLimitContainerElement.style.display = 'none';
this._timeToLimitContainerElement.classList.add('collapsed');
}
}

Expand All @@ -139,23 +121,19 @@ class MainComponent {
drivingLimit
);

let now;
if (DEBUG) {
now = this._debugNow;
} else {
now = Date.now();
}
const now = Time.now();

const drinks = [];

for (const drinkComponent of this._drinks) {
drinkComponent.evaluateParameters(now);
drinkComponent.setEliminationRatio(0);
drinkComponent.setBloodAlcoholConcentration(0);
drinkComponent.setIsEffective(false);
drinkComponent.evaluateTimeAndEffectiveness(now);

drinks.push(new Drink(
drinkComponent.quantity,
drinkComponent.alcoholPercentage,
new Date(drinkComponent.startedAt).getTime(),
new Date(drinkComponent.startedAt).getTime()
));
}

Expand All @@ -165,37 +143,29 @@ class MainComponent {
return;
}

for (let i = 0; i < result.drinkEliminationRatios.length; i += 1) {
this._drinks[i].setEliminationRatio(result.drinkEliminationRatios[i]);
}

if (DEBUG) {
if (this._debugPrevBac < 0) {
this._debugPrevBac = result.bloodAlcoholConcentration;
}
for (const drinkInfo of result.perDrinkInfo) {
const sourceDrink = this._drinks[drinkInfo.index];

const bacResolution = 200;
this._debug.moveTo(this._debugTime, this._debug.canvas.clientHeight - this._debugPrevBac * bacResolution - 1);
this._debugTime += 1.5;
this._debug.lineTo(this._debugTime, this._debug.canvas.clientHeight - result.bloodAlcoholConcentration * bacResolution - 1);
this._debug.stroke();
this._debugPrevBac = result.bloodAlcoholConcentration;
sourceDrink.setBloodAlcoholConcentration(round(drinkInfo.bloodAlcoholConcentration, 3));
sourceDrink.setIsEffective(drinkInfo.isEffective);
sourceDrink.evaluateTimeAndEffectiveness(now);
sourceDrink.SET_ENDS_AT(drinkInfo.endsAt);
}

this._lastBloodAlcoholConcentration = result.bloodAlcoholConcentration;

this._alcoholBloodConcentrationValueElement.innerText = round(result.bloodAlcoholConcentration, 2);

this._timeToLimitValueElement.innerText = this._timeToDisplayString(result.timeToLimit);
this._timeToZeroValueElement.innerText = this._timeToDisplayString(result.timeToZero);
this._timeToLimitValueElement.innerText = this._timeToDisplayString(now, result.timeToLimit);
this._timeToZeroValueElement.innerText = this._timeToDisplayString(now, result.timeToZero);
}

_timeToDisplayString(timeTo) {
_timeToDisplayString(date, timeTo) {
if (timeTo <= 0) {
return '-';
}

return `${toHumanReadableTime(timeTo)} (at ${toDateTime(timeTo)})`
return `${toHumanReadableTime(timeTo)} (at ${addHoursToDate(date, timeTo)})`
}

_numberDrinks() {
Expand Down
Binary file removed docs/add-drink-button.png
Binary file not shown.
Binary file modified docs/main-page.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed docs/remove-drink-button.png
Binary file not shown.
Binary file removed docs/settings-button.png
Binary file not shown.
3 changes: 2 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
</head>
<body>
<div class="page main">
<canvas class="debug"></canvas>
<canvas class="debug canvas" width="500" height="200"></canvas>
<div class="toolbar container">
<input class="debug time" type="datetime-local"/>
<button class="show-settings" title="Settings..." type="button"><div>⚙️</div></button>
<button class="add-drink" title="Add a drink" type="button"><div>➕🍺</div></button>
</div>
Expand Down
Loading

0 comments on commit e998142

Please sign in to comment.