write doc
Took 2 hours 59 minutes
This commit is contained in:
parent
82ab47e9fd
commit
7d9dfca62e
5
ToDo.md
5
ToDo.md
@ -5,4 +5,7 @@
|
||||
- [ ] Fix Fehler, wenn keine Getränke hinzugefügt worden sind
|
||||
- [ ] Schriftarten Lokal machen
|
||||
- [x] Probleme beim Laden der Container im Setup
|
||||
-
|
||||
- [ ] Container option "Auto tare" hinzufügen
|
||||
- Heißt, sobald ein Inhalt in einen Container eingestellt wird und das Volumen angegeben ist, werden die Sensoren auf den aktuellen Füllstand als 100% gesetzt
|
||||
- [ ] Nach Speichern des Setups und bei Einmessen auf Schließen, Setup verlassen
|
||||
- [ ]
|
80
doc/Notes.md
80
doc/Notes.md
@ -1,4 +1,5 @@
|
||||
# Notes und kleine Dokumentation
|
||||
|
||||
Was haben wir bereits am iTender Projekt gemacht?
|
||||
|
||||
<hr>
|
||||
@ -6,26 +7,30 @@ Was haben wir bereits am iTender Projekt gemacht?
|
||||
## Konzept-Erstellung
|
||||
|
||||
#### Ideen
|
||||
|
||||
- Grund-Ideen
|
||||
- Smarten Cocktail-Mischer
|
||||
- 4 Getränke Behälter (mit Saft, Sirup oder Likör bzw. Schnapps)
|
||||
- 4 Pumpen (Peristaltik Pumpe)
|
||||
- Raspberry Pi als Prozessoreinheit
|
||||
- Display in der Front mit Benutzeroberfläche
|
||||
- Automatisches filtern von Getränken, je nachdem welche "Zutaten" in den Behältern sind
|
||||
- Messung der aktuellen Füllmenge der Behälter, basierend auf Gewicht (mittels Wägezelle) oder Abstand zur Wasseroberfläche (mittels Ultraschall-Sensor)
|
||||
- Smarten Cocktail-Mischer
|
||||
- 4 Getränke Behälter (mit Saft, Sirup oder Likör bzw. Schnapps)
|
||||
- 4 Pumpen (Peristaltik Pumpe)
|
||||
- Raspberry Pi als Prozessoreinheit
|
||||
- Display in der Front mit Benutzeroberfläche
|
||||
- Automatisches filtern von Getränken, je nachdem welche "Zutaten" in den Behältern sind
|
||||
- Messung der aktuellen Füllmenge der Behälter, basierend auf Gewicht (mittels Wägezelle) oder Abstand zur
|
||||
Wasseroberfläche (mittels Ultraschall-Sensor)
|
||||
|
||||
- Nice to have
|
||||
- LED-Stripes für schöne Beleuchtung, basierend auf dem aktuellen Status der Maschine
|
||||
- Extra Schlauch für weitere außenstehende Getränke
|
||||
- Mit Bier-Fass Adapter?
|
||||
- Kühlung der Container mittels Peltierelement und Lüftern
|
||||
- LED-Stripes für schöne Beleuchtung, basierend auf dem aktuellen Status der Maschine
|
||||
- Extra Schlauch für weitere außenstehende Getränke
|
||||
- Mit Bier-Fass Adapter?
|
||||
- Kühlung der Container mittels Peltierelement und Lüftern
|
||||
|
||||
#### Erstes 3D-Modell
|
||||
|
||||
<img src="./Screenshot_Model1.1_FrontTopRight.png" width="50%">
|
||||
<img src="./Screenshot_Model1.1_BackDownLeft.png" width="50%">
|
||||
|
||||
#### Neues 3D-Modell
|
||||
|
||||
<img src="./Screenshot_Model1.2_Front.png" width="50%">
|
||||
<img src="./Screenshot_Model1.2_Back.png" width="50%">
|
||||
|
||||
@ -35,12 +40,23 @@ Was haben wir bereits am iTender Projekt gemacht?
|
||||
## Das Programm
|
||||
|
||||
#### Aufbau
|
||||
|
||||
- Das Programm ist aufgebaut in eine Client-Seite und eine Server-Seite
|
||||
- Diese sind zur Sicherheit des Geräts voneinander getrennt
|
||||
- Server und Client kommunizieren über einen WebSocket, welchen man sich als eine Art Chat-Kanal vorstellen kann
|
||||
- Client und Server haben bestimmte Status, ein Status ist beispielsweise READY oder FILLING
|
||||
- Die Oberfläche ist sowohl über das Display, aber auch über ein Tablet steuerbar
|
||||
- Oberfläche sendet Befehle an den Server → Server verarbeitet und gibt ggfs. eine Antwort
|
||||
- Oberfläche sendet Befehle an den Server → Server verarbeitet und gibt ggf. eine Antwort
|
||||
- Bei jedem start wird der Status des Netzwerkes erfasst
|
||||
- Sollte Netzwerk zu verfügung stehen, versucht iTender die Getränke vom Hauptserver (im Internet) zu aktualisieren
|
||||
- Somit kommen auch beim bestehenden Produkt immer neue Getränke und mögliche Zutaten dazu
|
||||
- Nach jedem mischen und alle 5 minuten werden die Füllstände der Behälter erfasst
|
||||
- Danach werden auch die verfügbaren Cocktails berechnet, diese werden dann im Webinterface angezeigt
|
||||
|
||||
#### Code fakten
|
||||
|
||||
- Inzwischen hat der Programmiercode 14492 Zeilen
|
||||
- In Stunden wurde das Projekt (Stand 29.11) ~80 Stunden programmiert
|
||||
|
||||
#### Fotos des Webinterfaces (Stand 21.11)
|
||||
|
||||
@ -48,27 +64,55 @@ Was haben wir bereits am iTender Projekt gemacht?
|
||||
<br>
|
||||
Die Main Pane ist der Hauptteil und direkt die Einstiegsseite des iTenders<br>
|
||||
Hier können Getränke ausgewählt werden, welche dann "gemacht" werden
|
||||
<img src="./v1Main.png">
|
||||
|
||||
<img src="./v1Main.png" width="50%">
|
||||
<br><br>
|
||||
<strong>Menu</strong><br>
|
||||
Das Menü ist das Navigationsherz, von hier aus können alle anderen Panels erreicht werden<br>
|
||||
<img src="./v1Menu.png">
|
||||
<img src="./v1Menu.png" width="50%">
|
||||
<br><br>
|
||||
<strong>Containers</strong><br>
|
||||
Hier können die Behälter inhalte aktualisiert werden<br>
|
||||
Man wählt die "Zutat" aus und danach wie voll der Behälter nun ist<br>
|
||||
In der Regel kann das auch automatisch eingemessen werden, wenn alle Sensoren eingestellt sind<br>
|
||||
<br>Trotzdem sollte das hier eingestellt werden
|
||||
<img src="./v1Containers.png">
|
||||
|
||||
<img src="./v1Containers.png" width="50%">
|
||||
|
||||
<br><br>
|
||||
<strong>Fill</strong><br>
|
||||
Einfach ein "Popup" welches anzeigt dass das Getränk gefüllt wird
|
||||
<img src="./v1Fill.png">
|
||||
|
||||
<img src="./v1Fill.png" width="50%">
|
||||
|
||||
<br><br>
|
||||
<strong>Setup</strong><br>
|
||||
Das Setup ist das erste menü was nach dem ersten einrichten erscheint<br>
|
||||
es dient zur Grundkonfiguration
|
||||
<img src="./v1Setup.png">
|
||||
|
||||
<img src="./v1Setup.png" width="50%">
|
||||
|
||||
<br><br>
|
||||
|
||||
|
||||
<hr>
|
||||
|
||||
<br><br>
|
||||
|
||||
### Erklärung der Dateien und Modulen
|
||||
|
||||
| Context | Datei | Beschreibung |
|
||||
|-----------|---------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| iTender | main | Die Hauptdatei, sie ist der Einstiegspunkt des Programms, von hier aus wird die Datenbank verbunden und der Webserver gestartet |
|
||||
| iTender | MyGPIO | Die eigene GPIO Library von Tobias Hopp. Sie dient zum Steuern von GPIO-Pins am Raspberry Pi |
|
||||
| iTender | Utils | Hier befinden sich einige schnelle Funktionen, welche des öfteren von mehreren Methoden im Programm genutzt werden |
|
||||
| iTender | RejectReason, SensorType, RequestType | Ein paar Klassen welche Enums (sogenannte feste Platzhalter für Variablen) bereitstellen |
|
||||
| iTender | SensorType | Die Art des Sensors, aktuell sind Ultraschallsensoren und Wäge sensoren unterstützt |
|
||||
| Webserver | App | Die App ist die Instanz des Webservers, welcher für die Oberfläche genutzt wird, dieser wird vom iTender selbst, sowie etwaigen anderen Geräten aufgerufen |
|
||||
| Webserver | WebsocketApp | Ähnlich wie die normale App, nur spezifisch für die Direktverbindung zwischen Oberfläche und Server |
|
||||
| iTender | LEDHandler | Übernimmt die Steuerung der WS2812b LEDs |
|
||||
| iTender | Category | Kategorie des Getränks |
|
||||
| iTender | Settings | Die Einstellungen des iTenders, werden gespeichert in der config.json |
|
||||
| Webseite | error.pug, index.pug, layout.pug | Die statische Webseite für den iTender, diese Oberfläche liest die JavaScript Dateien ein und verbindet sich dann mit dem WebSocket (mit dem iTender) |
|
||||
| Compiler | dist/ Ordner | Hier sind alle kompilierten Dateien zu finden |
|
||||
| Webserver | web/main | Der Einstiegspunkt für die Weboberfläche, von hier aus wird mit dem WebSocket verbunden |
|
||||
| | | |
|
||||
|
@ -48,6 +48,32 @@ body {
|
||||
user-select: none; /* Standard syntax */
|
||||
}
|
||||
|
||||
#blockPanel {
|
||||
z-index: 999;
|
||||
position: fixed;
|
||||
top:0;
|
||||
left:0;
|
||||
background-color: rgba(0,0,0,0.9);
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
;
|
||||
transition: opacity 0.4s;;
|
||||
}
|
||||
|
||||
.opacityOutDisplayNone {
|
||||
animation: opacityOutDisplayNone 0.4s linear forwards;
|
||||
|
||||
}
|
||||
|
||||
@keyframes opacityOutDisplayNone {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.74em;
|
||||
|
@ -42,6 +42,8 @@ export class Utils {
|
||||
reject(new Error(`Request Failed With a Status Code: ${res.statusCode}`));
|
||||
|
||||
}
|
||||
}).on("error", (e) => {
|
||||
reject(new Error("Request failed " + e))
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -8,6 +8,6 @@ export enum WebSocketEvent {
|
||||
SETUP = "SETUP",
|
||||
REQUEST = "REQUEST",
|
||||
RESPONSE = "RESPONSE",
|
||||
FILL = "FILL",
|
||||
CANCEL = "CANCEL"
|
||||
CANCEL = "CANCEL",
|
||||
ERROR = "ERROR",
|
||||
}
|
@ -17,7 +17,6 @@ import {IIngredient} from "./database/IIngredient";
|
||||
import Ingredient from "./database/Ingredient";
|
||||
import {clearInterval} from "timers";
|
||||
import {RejectReason} from "./RejectReason";
|
||||
import {Settings} from "./Settings";
|
||||
import axios from "axios";
|
||||
import GPIO from "rpi-gpio";
|
||||
import {MyGPIO} from "./MyGPIO";
|
||||
@ -27,6 +26,10 @@ const isPI = require("detect-rpi");
|
||||
const log = debug("itender:station");
|
||||
const mixLog = debug("itender:mix");
|
||||
|
||||
|
||||
/**
|
||||
* The main class of the itender, here a located all main features of the system, like starting pumps, firing events and stuff
|
||||
*/
|
||||
export class iTender {
|
||||
private static secondsPer100ml: number = 35.3335;
|
||||
|
||||
@ -34,6 +37,9 @@ export class iTender {
|
||||
return this._drinks;
|
||||
}
|
||||
|
||||
/**
|
||||
* The current job of the itender
|
||||
*/
|
||||
static get currentJob(): IJob | null {
|
||||
return this._currentJob;
|
||||
}
|
||||
@ -43,6 +49,9 @@ export class iTender {
|
||||
private static _jobCheckInterval: NodeJS.Timer;
|
||||
private static _internetConnection: boolean = false;
|
||||
|
||||
/**
|
||||
* Returns true if internet connection is active
|
||||
*/
|
||||
static get internetConnection(): boolean {
|
||||
return this._internetConnection;
|
||||
}
|
||||
@ -60,6 +69,11 @@ export class iTender {
|
||||
return this._status;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method is fired if the user likes to mix a drink
|
||||
* @param data
|
||||
*/
|
||||
static onReceiveFill(data: { drink: IDrink, amounts?: { ingredient: String, amount: number }[], amount?: number }): Promise<IJob> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
mixLog("Receiving fill");
|
||||
@ -71,6 +85,7 @@ export class iTender {
|
||||
|
||||
const job = new Job();
|
||||
|
||||
|
||||
let amounts: { ingredient: IIngredient, amount: number, container?: IContainer }[] = [];
|
||||
job.completeAmount = 0;
|
||||
if (data.amounts) {
|
||||
@ -121,8 +136,7 @@ export class iTender {
|
||||
console.log(amounts);
|
||||
job.drink = drink
|
||||
job.amounts = amounts as { ingredient: IIngredient, amount: number, container: IContainer }[];
|
||||
if( job.estimatedTime < 0.5 )
|
||||
{
|
||||
if (job.estimatedTime < 0.5) {
|
||||
job.estimatedTime = 1;
|
||||
}
|
||||
await job.save()
|
||||
@ -134,6 +148,11 @@ export class iTender {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Start the internal fill method
|
||||
* @param job
|
||||
*/
|
||||
static async startFill(job: IJob) {
|
||||
job.startedAt = new Date();
|
||||
await job.populate([{path: "amounts.ingredient"}, {path: "amounts.container"}, {path: "drink"}]);
|
||||
@ -149,7 +168,7 @@ export class iTender {
|
||||
try {
|
||||
await MyGPIO.setup(x.container.pumpPin, GPIO.DIR_OUT)
|
||||
|
||||
await MyGPIO.write(x.container.pumpPin, true );
|
||||
await MyGPIO.write(x.container.pumpPin, true);
|
||||
} catch (e) {
|
||||
if (isPI()) {
|
||||
log("[ERROR] GPIO I/O Error " + e);
|
||||
@ -177,7 +196,7 @@ export class iTender {
|
||||
mixLog(`Stopping output of pump ${x.container.pumpPin}`);
|
||||
// Stop pump here
|
||||
try {
|
||||
await MyGPIO.write(x.container.pumpPin, false );
|
||||
await MyGPIO.write(x.container.pumpPin, false);
|
||||
} catch (e) {
|
||||
if (isPI()) {
|
||||
log("[ERROR] GPIO I/O Error " + e);
|
||||
@ -204,11 +223,15 @@ export class iTender {
|
||||
job.successful = true;
|
||||
await job.save();
|
||||
mixLog("Job successful");
|
||||
setTimeout( () => iTender.setStatus(iTenderStatus.READY), 3000 )
|
||||
setTimeout(() => iTender.setStatus(iTenderStatus.READY), 3000)
|
||||
|
||||
}, 500);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cancel the fill
|
||||
*/
|
||||
static async cancelFill() {
|
||||
if (!this._currentJob || this.status != iTenderStatus.FILLING)
|
||||
return;
|
||||
@ -221,7 +244,7 @@ export class iTender {
|
||||
for (let x of this._currentJob.amounts) {
|
||||
// stop pump pin
|
||||
try {
|
||||
await MyGPIO.write(x.container.pumpPin, false );
|
||||
await MyGPIO.write(x.container.pumpPin, false);
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
@ -231,6 +254,10 @@ export class iTender {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Measure all containers based on their sensor values
|
||||
*/
|
||||
static measureContainers(): Promise<void> {
|
||||
log("Measuring containers...");
|
||||
|
||||
@ -269,6 +296,11 @@ export class iTender {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Refresh the drinks to the local variable
|
||||
* Check which drinks can be done, based on the current container ingredients
|
||||
*/
|
||||
static refreshDrinks(): Promise<void> {
|
||||
log("Refreshing drinks...");
|
||||
return new Promise(async resolve => {
|
||||
@ -326,6 +358,10 @@ export class iTender {
|
||||
try {
|
||||
const requestIngredients = await axios.get("https://itender.iif.li/api/ingredients");
|
||||
let serverIngredients = requestIngredients.data as IIngredient[];
|
||||
if (serverIngredients.length == 0) {
|
||||
log("Got 0 ingredients from the server... aborting.");
|
||||
throw new Error("Got 0 ingredients from the server, invalid");
|
||||
}
|
||||
log("Got " + serverIngredients.length + " ingredients from server");
|
||||
|
||||
let localIngredients = await Ingredient.find();
|
||||
@ -362,6 +398,10 @@ export class iTender {
|
||||
|
||||
const requestDrinks = await axios.get("https://itender.iif.li/api/drinks");
|
||||
let serverDrinks = requestDrinks.data as IDrink[];
|
||||
if (serverDrinks.length == 0) {
|
||||
log("Got 0 drinks from the server... aborting.");
|
||||
throw new Error("Got 0 drinks from the server, invalid");
|
||||
}
|
||||
log("Got " + serverDrinks.length + " drinks from server");
|
||||
|
||||
|
||||
@ -405,16 +445,16 @@ export class iTender {
|
||||
log("Drink " + remote.name + " failed to download thumbnail! (" + url + ") | " + e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
console.error("Could not refresh drinks " + e);
|
||||
await WebSocketHandler.send(new WebSocketPayload(WebSocketEvent.ERROR, false, "Beim aktualisieren der Getränke ist ein Netzwerk-Fehler aufgetreten.<br>Bitte später erneut versuchen!"));
|
||||
}
|
||||
|
||||
iTender.setStatus(iTenderStatus.READY);
|
||||
resolve();
|
||||
iTender.refreshDrinks();
|
||||
await iTender.refreshDrinks();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,10 @@ import {WebSocketPayload} from "../WebSocketPayload";
|
||||
import {WebSocketEvent} from "../WebSocketEvent";
|
||||
|
||||
export class Containers {
|
||||
|
||||
/**
|
||||
* Open the menu for the container ingredient setup
|
||||
*/
|
||||
static openMenu() {
|
||||
let modal = new Modal("containers", "Behälter aktualisieren");
|
||||
let txt = document.createElement("p");
|
||||
@ -24,6 +28,7 @@ export class Containers {
|
||||
btnSave.disabled = true;
|
||||
|
||||
let containerVolumes: Record<any, number> = {};
|
||||
let containers: Record<string, IContainer> = {};
|
||||
|
||||
let volume = document.createElement("span");
|
||||
volume.innerText = "";
|
||||
@ -35,6 +40,7 @@ export class Containers {
|
||||
volumeSlider.style.visibility = "hidden";
|
||||
volumeSlider.id = "containers_volumeSlider"
|
||||
|
||||
// When volume slider is changed
|
||||
function onChange() {
|
||||
volume.innerText = volumeSlider.value + " ml ";
|
||||
txt.innerText = "Speichern zum abschließen"
|
||||
@ -52,6 +58,8 @@ export class Containers {
|
||||
let selectIngredient = document.createElement("select");
|
||||
selectIngredient.style.visibility = "hidden";
|
||||
selectIngredient.classList.add("input");
|
||||
|
||||
// When ingredient is changed
|
||||
selectIngredient.onchange = () => {
|
||||
if (selectIngredient.value == "null") {
|
||||
volumeSlider.value = "0";
|
||||
@ -78,27 +86,40 @@ export class Containers {
|
||||
|
||||
let selectContainer = document.createElement("select");
|
||||
selectContainer.classList.add("input");
|
||||
//let containers : IContainer[] = [];
|
||||
selectContainer.onchange = () => {
|
||||
// Enable select ingredient field and set max and min to the slider
|
||||
selectIngredient.style.visibility = "visible";
|
||||
volumeSlider.max = String(containerVolumes[selectContainer.value]);
|
||||
volumeSlider.min = String(0);
|
||||
volumeSlider.value = String(containerVolumes[selectContainer.value] / 2);
|
||||
txt.innerText = "Ingredient des Behälters auswählen";
|
||||
|
||||
// When content of container is filled, preselect the ingredient selector
|
||||
if (containers[selectContainer.value].content) {
|
||||
selectIngredient.value = containers[selectContainer.value].content?._id;
|
||||
let event = new Event('change', {bubbles: true});
|
||||
selectIngredient.dispatchEvent(event);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
selectContainer.append(nonSelect.cloneNode(true));
|
||||
selectContainer.selectedIndex = 0;
|
||||
|
||||
|
||||
WebWebSocketHandler.request(RequestType.CONTAINERS).then((payload) => {
|
||||
for (let container of (payload.data["content"] as IContainer[])) {
|
||||
containerVolumes[container._id] = container.volume;
|
||||
let option = document.createElement("option");
|
||||
option.value = container._id;
|
||||
option.innerText = "Behälter Slot " + container.slot + "[" + (container.content && container.content.name ? container.content.name : "Kein Inhalt") + "]";
|
||||
option.innerText = "Behälter Slot " + (container.slot+1) + "[" + (container.content && container.content.name ? container.content.name : "Kein Inhalt") + "]";
|
||||
selectContainer.append(option);
|
||||
|
||||
containers[container._id] = container;
|
||||
}
|
||||
//containers = payload.data["content"] as IContainer[];
|
||||
});
|
||||
WebWebSocketHandler.request(RequestType.INGREDIENTS).then((payload) => {
|
||||
for (let ingredient of (payload.data["content"] as IIngredient[])) {
|
||||
@ -134,7 +155,14 @@ export class Containers {
|
||||
ingredient: (selectIngredient.value == "null") ? null : selectIngredient.value,
|
||||
filled: volumeSlider.value
|
||||
});
|
||||
WebWebSocketHandler.send(payload).then(() => modal.close());
|
||||
|
||||
WebWebSocketHandler.send(payload).then(() => {
|
||||
selectContainer.value = "-1";
|
||||
selectIngredient.value = "-1";
|
||||
let event = new Event('change', {bubbles: true});
|
||||
selectContainer.dispatchEvent(event);
|
||||
selectIngredient.dispatchEvent(event);
|
||||
});
|
||||
};
|
||||
|
||||
modal.open();
|
||||
|
101
src/web/Fill.ts
Normal file
101
src/web/Fill.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import {WebSocketPayload} from "../WebSocketPayload";
|
||||
import {Modal} from "./Modal";
|
||||
import {WebSocketEvent} from "../WebSocketEvent";
|
||||
import {RequestType} from "../RequestType";
|
||||
import {IJob} from "../database/IJob";
|
||||
import {WebWebSocketHandler} from "./WebWebSocketHandler";
|
||||
|
||||
export class Fill {
|
||||
static onFillEvent(payload: WebSocketPayload) {
|
||||
let modal = new Modal("fill", "Cocktail wird zubereitet");
|
||||
let header = document.createElement("h2");
|
||||
header.innerText = "";
|
||||
|
||||
modal.addContent(header);
|
||||
|
||||
let txt = document.createElement("p");
|
||||
txt.innerHTML = `Der Cocktail wird gerade zubereitet`;
|
||||
txt.id = "main_fillTxt";
|
||||
|
||||
let waterAnimDiv = document.createElement("div");
|
||||
waterAnimDiv.classList.add("water");
|
||||
modal.addContent(txt);
|
||||
|
||||
modal.addContent(waterAnimDiv);
|
||||
|
||||
let seconds = document.createElement("span");
|
||||
seconds.innerText = "60s";
|
||||
seconds.style.marginRight = "3%";
|
||||
|
||||
modal.addContent(seconds);
|
||||
|
||||
let ml = document.createElement("span");
|
||||
ml.innerText = "200ml";
|
||||
modal.addContent(ml);
|
||||
|
||||
modal.addContent(document.createElement("br"));
|
||||
modal.addContent(document.createElement("br"));
|
||||
|
||||
|
||||
let cancelBtn = document.createElement("button");
|
||||
cancelBtn.classList.add("btn", "btn-danger");
|
||||
cancelBtn.innerText = "Abbrechen";
|
||||
cancelBtn.disabled = true;
|
||||
setTimeout(() => {
|
||||
cancelBtn.disabled = false;
|
||||
}, 1000);
|
||||
cancelBtn.onclick = () => {
|
||||
cancelBtn.disabled = true;
|
||||
txt.innerHTML = "Der Vorgang wird abgebrochen...";
|
||||
waterAnimDiv.classList.add("waterCancel");
|
||||
|
||||
WebWebSocketHandler.send(new WebSocketPayload(WebSocketEvent.CANCEL));
|
||||
};
|
||||
modal.addContent(cancelBtn);
|
||||
|
||||
function riseSlowlyUp(lastNumber: number, number: number) {
|
||||
for (let i = lastNumber; i < number; i++) {
|
||||
setTimeout(() => {
|
||||
ml.innerText = Math.floor(i) + "ml";
|
||||
}, (number - lastNumber / 1000) + i * 4);
|
||||
}
|
||||
}
|
||||
|
||||
modal.open().then(() => {
|
||||
WebWebSocketHandler.request(RequestType.JOB).then((payload) => {
|
||||
let minus = 0;
|
||||
let job = payload.data.content as IJob;
|
||||
ml.innerText = Math.floor((job.completeAmount / job.estimatedTime) * minus) + "ml";
|
||||
waterAnimDiv.style.setProperty("--fillTime", job.estimatedTime + "s");
|
||||
waterAnimDiv.style.backgroundImage = `url("/images/${job.drink._id}.png")`;
|
||||
header.innerText = job.drink.name;
|
||||
seconds.innerText = Math.floor(job.estimatedTime) + "s";
|
||||
|
||||
let last = 0;
|
||||
let interval = setInterval(() => {
|
||||
minus++;
|
||||
if (minus + 1 > (job.estimatedTime as number)) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
|
||||
seconds.innerText = (Math.floor(job.estimatedTime as number - minus)) + "s";
|
||||
let calc = Math.floor((job.completeAmount / job.estimatedTime) * minus);
|
||||
riseSlowlyUp(last, calc)
|
||||
last = calc;
|
||||
}, 1000);
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
txt.innerHTML = "Bitte entnehme den Cocktail";
|
||||
modal.title.innerHTML = "Cocktail fertig gestellt"
|
||||
|
||||
cancelBtn.classList.add("btn-blendout");
|
||||
waterAnimDiv.classList.add("waterFinished");
|
||||
cancelBtn.onclick = () => {
|
||||
modal.close();
|
||||
}
|
||||
}, job.estimatedTime * 1000);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@ -1,10 +1,13 @@
|
||||
import {ButtonType} from "./ButtonType";
|
||||
|
||||
export class Modal {
|
||||
get title(): HTMLHeadingElement {
|
||||
return this._title;
|
||||
}
|
||||
|
||||
private static currentModalId: string | undefined = "";
|
||||
|
||||
private _title: string = "iTender";
|
||||
private _title: HTMLHeadingElement;
|
||||
private _id: string = "";
|
||||
private _loader: boolean = false;
|
||||
private _buttons: { type: string, content: string, onclick: Function }[] = [];
|
||||
@ -19,11 +22,10 @@ export class Modal {
|
||||
|
||||
constructor(id, title: string) {
|
||||
this._id = id;
|
||||
this._title = title;
|
||||
|
||||
let t = document.createElement("h1");
|
||||
t.innerText = title;
|
||||
this._elements.push(t);
|
||||
this._title = document.createElement("h1") as HTMLHeadingElement;
|
||||
this._title.innerText = title;
|
||||
this._elements.push(this._title);
|
||||
}
|
||||
|
||||
public static isModalOpen(): boolean {
|
||||
@ -91,6 +93,10 @@ export class Modal {
|
||||
});
|
||||
}
|
||||
|
||||
public setTitle(title) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param elements
|
||||
* @param id
|
||||
|
@ -7,7 +7,7 @@ import {WebHandler} from "./WebHandler";
|
||||
import {Setup} from "./Setup";
|
||||
import {Pane} from "./Pane";
|
||||
import {RequestType} from "../RequestType";
|
||||
import {IJob} from "../database/IJob";
|
||||
import {Fill} from "./Fill";
|
||||
|
||||
export class WebWebSocketHandler {
|
||||
private static socket: WebSocket;
|
||||
@ -29,8 +29,7 @@ export class WebWebSocketHandler {
|
||||
}
|
||||
|
||||
public static registerForEvent(event: WebSocketEvent, fn: (payload: WebSocketPayload) => void) {
|
||||
for( let e of WebWebSocketHandler.eventRegister )
|
||||
{
|
||||
for (let e of WebWebSocketHandler.eventRegister) {
|
||||
if (e.fn == fn) {
|
||||
console.log("Event fn already registered");
|
||||
return;
|
||||
@ -55,11 +54,27 @@ export class WebWebSocketHandler {
|
||||
|
||||
switch (payload.event) {
|
||||
case WebSocketEvent.CONFIG: {
|
||||
// Incoming WebSocketStatus
|
||||
Setup.onConfigUpdate(payload);
|
||||
break;
|
||||
}
|
||||
|
||||
case WebSocketEvent.DRINKS: {
|
||||
WebHandler.onDrinkUpdate(payload);
|
||||
break;
|
||||
}
|
||||
|
||||
case WebSocketEvent.ERROR: {
|
||||
let modal = new Modal("error", "Aww crap!");
|
||||
let txt = document.createElement("p");
|
||||
txt.innerHTML = payload.data;
|
||||
modal.addContent(txt);
|
||||
modal.addContent(document.createElement("br"));
|
||||
modal.addButton(ButtonType.PRIMARY, "Schließen", () => modal.close() );
|
||||
modal.open();
|
||||
break;
|
||||
}
|
||||
|
||||
// Incoming WebSocketStatus
|
||||
case WebSocketEvent.STATUS: {
|
||||
let statusElement = document.getElementById("status");
|
||||
if (statusElement)
|
||||
@ -90,19 +105,16 @@ export class WebWebSocketHandler {
|
||||
case iTenderStatus.DOWNLOADING: {
|
||||
let modal = new Modal("download", "Aktualisieren");
|
||||
let txt = document.createElement("p");
|
||||
txt.innerHTML = `Einen Augenblick bitte<br>iTender aktualisiert die Getränke vom Server.`;
|
||||
txt.innerHTML = `Einen Augenblick bitte<br>iTender aktualisiert die Getränke vom Server...`;
|
||||
modal.addContent(txt);
|
||||
modal.loader = true;
|
||||
modal.open();
|
||||
break;
|
||||
}
|
||||
case iTenderStatus.REFRESHING: {
|
||||
/* let modal = new Modal("refreshing", "Aktualisieren...");
|
||||
let txt = document.createElement("p");
|
||||
txt.innerHTML = `Einen Augenblick bitte<br>iTender aktualisiert die Getränke...`;
|
||||
modal.addContent(txt);
|
||||
modal.loader = true;
|
||||
modal.open();*/
|
||||
setTimeout( () => {
|
||||
if( txt )
|
||||
{
|
||||
txt.innerHTML = txt.innerHTML + "<br><br>Der Vorgang dauert länger als gewöhnlich.<br>Überprüfe deine Internetverbindung!"
|
||||
}
|
||||
}, 1000 * 15 )
|
||||
break;
|
||||
}
|
||||
case iTenderStatus.SETUP: {
|
||||
@ -111,111 +123,16 @@ export class WebWebSocketHandler {
|
||||
break;
|
||||
}
|
||||
case iTenderStatus.FILLING: {
|
||||
|
||||
let modal = new Modal("fill", "Getränk wird ausgegeben");
|
||||
let header = document.createElement("h2");
|
||||
header.innerText = "";
|
||||
|
||||
modal.addContent(header);
|
||||
|
||||
let txt = document.createElement("p");
|
||||
txt.innerHTML = `Dein Cocktail wird gerade zubereitet`;
|
||||
txt.id = "main_fillTxt";
|
||||
|
||||
let waterAnimDiv = document.createElement("div");
|
||||
waterAnimDiv.classList.add("water");
|
||||
modal.addContent(txt);
|
||||
|
||||
modal.addContent(waterAnimDiv);
|
||||
|
||||
let seconds = document.createElement("span");
|
||||
seconds.innerText = "60s";
|
||||
seconds.style.marginRight = "3%";
|
||||
|
||||
modal.addContent(seconds);
|
||||
|
||||
let ml = document.createElement("span");
|
||||
ml.innerText = "200ml";
|
||||
modal.addContent(ml);
|
||||
|
||||
modal.addContent(document.createElement("br"));
|
||||
modal.addContent(document.createElement("br"));
|
||||
|
||||
|
||||
let cancelBtn = document.createElement("button");
|
||||
cancelBtn.classList.add("btn", "btn-danger");
|
||||
cancelBtn.innerText = "Abbrechen";
|
||||
cancelBtn.disabled = true;
|
||||
setTimeout(() => {
|
||||
cancelBtn.disabled = false;
|
||||
}, 1000);
|
||||
cancelBtn.onclick = () => {
|
||||
cancelBtn.disabled = true;
|
||||
txt.innerHTML = "Der Vorgang wird abgebrochen...";
|
||||
waterAnimDiv.classList.add("waterCancel");
|
||||
|
||||
WebWebSocketHandler.send(new WebSocketPayload(WebSocketEvent.CANCEL));
|
||||
};
|
||||
modal.addContent(cancelBtn);
|
||||
|
||||
function riseSlowlyUp(lastNumber:number, number: number) {
|
||||
for (let i = lastNumber; i < number; i++) {
|
||||
setTimeout(() => {
|
||||
ml.innerText = i + "ml";
|
||||
}, (number-lastNumber/1000)+i*4);
|
||||
}
|
||||
}
|
||||
|
||||
modal.open().then(() => {
|
||||
WebWebSocketHandler.request(RequestType.JOB).then((payload) => {
|
||||
let minus = 0;
|
||||
let job = payload.data.content as IJob;
|
||||
ml.innerText = Math.floor((job.completeAmount / job.estimatedTime) * minus) + "ml";
|
||||
waterAnimDiv.style.setProperty("--fillTime", job.estimatedTime + "s");
|
||||
waterAnimDiv.style.backgroundImage = `url("/images/${job.drink._id}.png")`;
|
||||
header.innerText = job.drink.name;
|
||||
seconds.innerText = job.estimatedTime + "s";
|
||||
|
||||
let last = 0;
|
||||
let interval = setInterval(() => {
|
||||
minus++;
|
||||
if (minus + 1 > (job.estimatedTime as number)) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
|
||||
seconds.innerText = (Math.floor(job.estimatedTime as number - minus)) + "s";
|
||||
let calc = Math.floor((job.completeAmount / job.estimatedTime) * minus);
|
||||
riseSlowlyUp(last, calc)
|
||||
last = calc;
|
||||
//ml.innerText = + "ml";
|
||||
}, 1000);
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
txt.innerHTML = "Bitte entnehme den Cocktail!";
|
||||
/*cancelBtn.classList.add("btn-primary");
|
||||
cancelBtn.classList.remove("btn-danger");
|
||||
cancelBtn.innerText = "Schließen";*/
|
||||
cancelBtn.classList.add("btn-blendout");
|
||||
waterAnimDiv.classList.add("waterFinished");
|
||||
cancelBtn.onclick = () => {
|
||||
modal.close();
|
||||
}
|
||||
//setTimeout(() => modal.close(), 1000 * 4.5);
|
||||
}, job.estimatedTime * 1000);
|
||||
});
|
||||
});
|
||||
Fill.onFillEvent(payload);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
console.log("Unknown to handle " + status);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case WebSocketEvent.DRINKS: {
|
||||
WebHandler.onDrinkUpdate(payload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,12 +140,8 @@ export class WebWebSocketHandler {
|
||||
private onOpen(event) {
|
||||
console.log("[WS] Connected", event);
|
||||
|
||||
/*let connectionElement = document.getElementById("right");
|
||||
if (connectionElement) {
|
||||
connectionElement.innerText = "Verbunden";
|
||||
connectionElement.style.color = "green";
|
||||
}*/
|
||||
|
||||
const blockPanel = document.getElementById("blockPanel") as HTMLDivElement;
|
||||
blockPanel.classList.add("opacityOutDisplayNone");
|
||||
}
|
||||
|
||||
private onClose(event) {
|
||||
|
@ -7,6 +7,7 @@ html
|
||||
link(rel='stylesheet', href='/stylesheets/style.css')
|
||||
meta(charset="UTF-8")
|
||||
body
|
||||
div#blockPanel
|
||||
div.modal#modal
|
||||
div.modal-content#modal-content
|
||||
div.modalInnerContent#modalInnerContent
|
||||
|
Loading…
x
Reference in New Issue
Block a user