Took 25 hours 49 minutes
This commit is contained in:
Tobias Hopp 2022-11-15 00:58:59 +01:00
parent a7fac21442
commit a356b39bad
46 changed files with 936 additions and 181 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

BIN
public/images/Gin Tonic.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
public/images/Mezzo Mix.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1020 KiB

BIN
public/images/Mojito.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 433 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

BIN
public/images/Wodka O.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 KiB

BIN
public/images/Zombie.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

View File

@ -5,7 +5,7 @@
:root { :root {
cursor: none; cursor: none !important;
} }
@ -22,7 +22,7 @@ body {
scroll-behavior: smooth; scroll-behavior: smooth;
font-family: "Roboto", serif; font-family: "Roboto", serif;
font-style: normal; font-style: normal;
cursor: none; cursor: none !important;
} }
@ -40,15 +40,35 @@ body {
height: 9%; height: 9%;
background-color: #1F5E5F; background-color: #1F5E5F;
color: white; color: white;
font-size: 2em;
padding-right: 3px;
padding-left: 3px;
} }
#title { #title {
float: right;
width: 100%;
font-size: 1.9em;
text-align: center; text-align: center;
float: left;
width: calc(100% / 3);
}
#left {
font-size: 0.5em;
float: left;
width: calc(100% / 3);
text-align: left;
padding-top: 1.2%;
}
#right {
float: left;
width: calc(100% / 3);
text-align: right;
font-size: 0.5em;
padding-top: 1.2%;
} }
@ -108,7 +128,7 @@ body {
#main { #main {
display: grid; display: grid;
height: 100%; height: 100%;
padding: 1% 2%;
grid-template-columns: repeat(3, calc(90% / 3)); grid-template-columns: repeat(3, calc(90% / 3));
grid-template-rows: repeat(2, calc(90% / 2)); grid-template-rows: repeat(2, calc(90% / 2));
grid-gap: 10% 5%; grid-gap: 10% 5%;
@ -122,26 +142,44 @@ body {
#main .drink { #main .drink {
grid-row: span 1; grid-row: span 1;
grid-column: span 1; grid-column: span 1;
background-color: black; background-color: rgba(57, 57, 57, 0.6);
width: 100%; width: 90%;
height: 100%; height: 97%;
display: grid; display: grid;
grid-template-columns: 100%; grid-template-columns: 100%;
grid-template-rows: repeat(3, calc(100% / 3)); grid-template-rows: repeat(3, calc(100% / 3));
grid-row-gap: 5%; grid-row-gap: 4%;
text-align: center; text-align: center;
border-radius: 30px 10px 30px; border-radius: 30px 10px 30px;
color: black;
/*box-shadow: 3px 3px 3px;*/
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.3), 0 6px 20px 0 rgba(0, 0, 0, 0.29);
transition: 0.4s;
overflow: hidden;
padding-bottom: 1%;
}
#main .drink:hover {
background-color: rgba(57, 57, 57, 0.8);
width: 100%;
height: 100%;
} }
.drink .thumbnail { .drink .thumbnail {
grid-column: span 1; grid-column: span 1;
grid-row: span 2; grid-row: span 2;
margin-left: auto;
margin-right: auto;
max-height: 100%;
overflow: hidden;
transition: 0.5s;
} }
.drink .name { .drink .drinkName {
font-family: Ubuntu, sans-serif;
grid-column: span 1; grid-column: span 1;
grid-row: span 1; grid-row: span 1;
font-size: 140%; font-size: 150%;
} }

View File

@ -50,7 +50,7 @@ export class App {
public loadRoutes( ) : void public loadRoutes( ) : void
{ {
this._app.use( "/", require("./routes/index") ); this._app.use( "/", require("./routes/indexRouter") );
} }

View File

@ -1,6 +1,7 @@
export enum Category { export enum Category {
ALCOHOLIC, ALCOHOLIC = "ALCOHOLIC",
ALCOHOL_FREE, ALCOHOL_FREE = "ALCOHOL_FREE",
SYRUP, SYRUP = "SYRUP",
JUICE JUICE = "JUICE",
SOFTDRINK = "SOFTDRINK"
} }

View File

@ -12,4 +12,11 @@ export class Utils {
}); });
} }
static sleep(ms: number): Promise<void> {
return new Promise(resolve => {
setTimeout(() => resolve(), ms);
})
}
} }

View File

@ -1,7 +1,5 @@
export enum WebSocketEvent { export enum WebSocketEvent {
STATUS_STARTING, STATUS= "STATUS",
STATUS_READY, DRINKS = "DRINKS",
STATUS_FILLING, CONTAINERS = "CONTAINERS",
STATUS_REFRESHING,
STATUS_ERROR
} }

View File

@ -1,4 +1,6 @@
import {WebSocketPayload} from "./WebSocketPayload"; import {WebSocketPayload} from "./WebSocketPayload";
import {WebSocketEvent} from "./WebSocketEvent";
import {iTender} from "./iTender";
export class WebSocketHandler { export class WebSocketHandler {
private static _ws: WebSocket; private static _ws: WebSocket;
@ -14,20 +16,21 @@ export class WebSocketHandler {
public static send(payload: WebSocketPayload): Promise<void> { public static send(payload: WebSocketPayload): Promise<void> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
try { try {
if( this.ws && this.ws.readyState == 1 ) if (this.ws && this.ws.readyState == 1) {
{
await this.ws.send(payload.toString()); await this.ws.send(payload.toString());
resolve(); resolve();
} }
else
{
reject("Websocket is not connected!");
}
} catch (e) { } catch (e) {
reject(e);
} }
}); });
} }
public static sendStatus() {
return new Promise(resolve => {
let payload = new WebSocketPayload(WebSocketEvent.STATUS, false, {status: iTender.status});
WebSocketHandler.send(payload).then(resolve);
});
}
} }

View File

@ -5,40 +5,40 @@ export class WebSocketPayload {
this._event = value; this._event = value;
} }
set status(value: boolean) { set error(value: boolean) {
this._status = value; this._error = value;
} }
set data(value: object | undefined) { set data(value: any | undefined) {
this._data = value; this._data = value;
} }
private _event: WebSocketEvent; private _event: WebSocketEvent;
private _status: boolean; private _error: boolean;
private _data: object | undefined; private _data: any | undefined;
get event(): WebSocketEvent { get event(): WebSocketEvent {
return this._event; return this._event;
} }
get status(): boolean { get error(): boolean {
return this._status; return this._error;
} }
get data(): object | undefined { get data(): any | undefined {
return this._data; return this._data;
} }
constructor(event: WebSocketEvent, status: boolean, data?: object) { constructor(event: WebSocketEvent, error: boolean = false, data?: any) {
this._event = event; this._event = event;
this._status = status; this._error = error;
this._data = data; this._data = data;
} }
public static parseFromJSON(json: string): WebSocketPayload | null { public static parseFromBase64Json(json: string): WebSocketPayload | null {
json = (window) ? atob(json) : Buffer.from(json, "base64").toString(); json = (typeof window != 'undefined') ? atob(json) : Buffer.from(json, "base64").toString();
let rawPayload: { event: string, status: boolean, data: object }; let rawPayload: { event: string, error: boolean, data: any };
try { try {
rawPayload = JSON.parse(json); rawPayload = JSON.parse(json);
} catch (e) { } catch (e) {
@ -47,15 +47,15 @@ export class WebSocketPayload {
let wsEvent = WebSocketEvent[<keyof typeof WebSocketEvent>rawPayload.event]; let wsEvent = WebSocketEvent[<keyof typeof WebSocketEvent>rawPayload.event];
return new WebSocketPayload(wsEvent, rawPayload.status, rawPayload.data); return new WebSocketPayload(wsEvent, rawPayload.error, rawPayload.data);
} }
/** /**
* Returns the payload as base64 encoded json string * Returns the payload as base64 encoded json string
*/ */
public toString(): string { public toString(): string {
let json = JSON.stringify({"event": this._event, status: this._status, data: this._data}); let json = JSON.stringify({"event": this._event, status: this._error, data: this._data});
json = (window) ? btoa(json) : Buffer.from(json).toString("base64"); json = ((typeof window != 'undefined') ? btoa(json) : Buffer.from(json).toString("base64"));
return json; return json;
} }
} }

View File

@ -38,7 +38,7 @@ export class WebsocketApp {
public loadRoutes( ) : void public loadRoutes( ) : void
{ {
this._app.use( "/", require("./routes/ws/websocket") ); this._app.use( "/", require("./routes/ws/websocketRoute") );
} }
public listen(): Promise<void> { public listen(): Promise<void> {

View File

@ -4,8 +4,7 @@ import * as mongoose from "mongoose";
export const DrinkSchema = new mongoose.Schema<IDrink>({ export const DrinkSchema = new mongoose.Schema<IDrink>({
name: {type: String, required: true}, name: {type: String, required: true},
ingredients: [{type: {type: mongoose.Types.ObjectId, ref: "Ingredient", required: true}, amount: { type: Number } }], ingredients: [{type: {type: mongoose.Types.ObjectId, ref: "Ingredient", required: true}, amount: { type: Number } }],
category: String, category: String
recommendedQuantity: {type: Number, default: 200 }
}); });
const Drink = mongoose.model<IDrink>('Drink', DrinkSchema); const Drink = mongoose.model<IDrink>('Drink', DrinkSchema);

View File

@ -12,7 +12,5 @@ export interface IDrink extends mongoose.Document {
// Category of the drink // Category of the drink
category: Category; category: Category;
// Recommended amount in milliliters
recommendedQuantity: number;
} }

10
src/database/IJob.ts Normal file
View File

@ -0,0 +1,10 @@
import mongoose from "mongoose";
import {IDrink} from "./IDrink";
export interface IJob extends mongoose.Document {
drink: IDrink;
amount: number;
startedAt: Date;
endAt: Date;
successful: boolean;
}

15
src/database/Job.ts Normal file
View File

@ -0,0 +1,15 @@
import mongoose from "mongoose";
import {IJob} from "./IJob";
export const JobSchema = new mongoose.Schema<IJob>({
drink: {type: mongoose.Types.ObjectId, ref: "Drink"},
amount: Number,
startedAt: Date,
endAt: Date,
successful: Boolean
});
const Job = mongoose.model<IJob>('Job', JobSchema);
export default Job;

View File

@ -4,15 +4,35 @@ import {HCSR04} from "hc-sr04";
import {IContainer} from "./database/IContainer"; import {IContainer} from "./database/IContainer";
import Drink from "./database/Drink"; import Drink from "./database/Drink";
import {IDrink} from "./database/IDrink"; import {IDrink} from "./database/IDrink";
import debug from "debug";
import {WebSocketHandler} from "./WebSocketHandler";
import {IJob} from "./database/IJob";
const log = debug("itender:station");
export class iTender { export class iTender {
private static _status: iTenderStatus; static get containers(): { container: IContainer; sensor: HCSR04; pump: null }[] {
return this._containers;
}
private static containers: { container: IContainer, sensor: HCSR04, pump: null }[]; static get drinks(): IDrink[] {
private static drinks: IDrink[]; return this._drinks;
}
static set status(value: iTenderStatus) { static get currentJob(): IJob | null {
this._status = value; return this._currentJob;
}
private static _status: iTenderStatus = iTenderStatus.STARTING;
private static _currentJob: IJob | null = null;
private static _containers: { container: IContainer, sensor: HCSR04, pump: null }[] = [];
private static _drinks: IDrink[];
static setStatus(status: iTenderStatus) {
this._status = status;
if (WebSocketHandler.ws && WebSocketHandler.ws.readyState == 1)
WebSocketHandler.sendStatus().then().catch(console.error);
} }
static get status(): iTenderStatus { static get status(): iTenderStatus {
@ -21,32 +41,42 @@ export class iTender {
static startFill() { static startFill() {
// todo Fill method // todo Fill method
// End
this.measureContainers().then();
} }
static stopFill() { static cancelFill() {
// todo Stop fill method // todo Stop fill method
} }
static measureContainers(): Promise<void> { static measureContainers(): Promise<void> {
log("Measuring containers...");
return new Promise(async resolve => { return new Promise(async resolve => {
let containers = await Container.find(); for (let c of this._containers) {
/* try {
public measure() : Number { let dist = c.sensor.distance();
let dist = this.sensor.distance(); c.container.filled = dist * 100 / (c.container.sensorFilledMax + c.container.sensorFilledMin);
return dist * 100 / (this.sensorFilledMax + this.sensorFilledMin); } catch (e) {
c.container.filled = -1;
} }
*/ await c.container.save();
}) }
log("Containers measured!");
resolve();
});
} }
static refreshDrinks(): Promise<void> { static refreshDrinks(): Promise<void> {
this.setStatus(iTenderStatus.REFRESHING);
log("Refreshing drinks...");
return new Promise(async resolve => { return new Promise(async resolve => {
this.drinks = []; this._drinks = [];
for (let d of (await Drink.find().populate("ingredients.type"))) { for (let d of (await Drink.find().populate("ingredients.type"))) {
let drinkAccept = true; let drinkAccept = true;
for (let i of d.ingredients) { for (let i of d.ingredients) {
let c = await Container.findOne({content: i["type"]}); let c = await Container.findOne({content: i["type"]});
@ -54,26 +84,37 @@ export class iTender {
drinkAccept = false; drinkAccept = false;
break; break;
} }
}
if (drinkAccept) {
this._drinks.push(d);
}
} }
if (drinkAccept) log("Drinks refreshed!");
this.drinks.push(d);
} resolve();
this.setStatus(iTenderStatus.READY);
}); });
} }
static refreshContainers(): Promise<void> { static refreshContainers(): Promise<void> {
log("Refreshing containers...");
this.setStatus(iTenderStatus.CALCULATING);
return new Promise(async resolve => { return new Promise(async resolve => {
let containers = await Container.find(); let containers = await Container.find();
for (let c of containers) { for (let c of containers) {
let i = 0; let i = 0;
let found = false; let found = false;
for (let c2 of this.containers) { for (let c2 of this._containers) {
if (c2.container._id == c._id) { if (c2.container._id == c._id) {
this.containers[i] = { let sensor;
try {
sensor = new HCSR04(c.sensorTrigger, c.sensorEcho);
} catch (e) {
}
this._containers[i] = {
container: c, container: c,
sensor: new HCSR04(c.sensorTrigger, c.sensorEcho), sensor: sensor,
pump: null pump: null
}; };
found = true; found = true;
@ -81,10 +122,42 @@ export class iTender {
} }
i++; i++;
} }
if (!found) if (!found) {
this.containers.push({container: c, sensor: new HCSR04(c.sensorTrigger, c.sensorEcho), pump: null}); let sensor;
try {
sensor = new HCSR04(c.sensorTrigger, c.sensorEcho);
} catch (e) {
} }
}) this._containers.push({
container: c,
sensor: sensor,
pump: null
});
}
}
log("Containers refreshed!");
this.measureContainers().then().catch(console.error);
resolve();
this.setStatus(iTenderStatus.READY);
});
}
public static async autoCheckup() {
setInterval(async () => {
if (!this._currentJob)
return;
// Check if startedTime plus 2 mins smaller than now
if (this._currentJob.startedAt.getTime() + 1000 * 60 * 2 <= Date.now()) {
// Job can be declared as stuck!
this._currentJob.successful = false;
this._currentJob.endAt = new Date();
await this._currentJob.save();
this._currentJob = null;
}
}, 30000);
} }

View File

@ -1,14 +1,14 @@
export enum iTenderStatus { export enum iTenderStatus {
// Machine is going to start // Machine is going to start
STARTING, STARTING= "STARTING",
// Machine is ready // Machine is ready
READY, READY = "READY",
// Machine is filling your drink and destroying your leberwurst // Machine is filling your drink and destroying your leberwurst
FILLING, FILLING = "FILLING",
// Drinks will be refreshed from global database (the internet neuland :O) // Drinks will be refreshed from global database (the internet neuland :O)
REFRESHING, REFRESHING = "REFRESHING",
// Drinks will be calculated (check containers and which drinks can be done) // Drinks will be calculated (check containers and which drinks can be done)
CALCULATING, CALCULATING = "CALCULATING",
// An error happened; Oh no :( // An error happened; Oh no :(
ERROR ERROR = "ERROR"
} }

View File

@ -2,10 +2,13 @@ import {App} from "./App";
import debug from "debug"; import debug from "debug";
import {WebsocketApp} from "./WebsocketApp"; import {WebsocketApp} from "./WebsocketApp";
import {Database} from "./database/Database"; import {Database} from "./database/Database";
import Drink from "./database/Drink";
import Ingredient from "./database/Ingredient"; import Ingredient from "./database/Ingredient";
import Container from "./database/Container";
import {iTender} from "./iTender"; import {iTender} from "./iTender";
import {iTenderStatus} from "./iTenderStatus";
import {Utils} from "./Utils";
import {Category} from "./Category";
import Drink from "./database/Drink";
import Container from "./database/Container";
const log = debug("itender:server"); const log = debug("itender:server");
@ -17,9 +20,18 @@ const wsApp = new WebsocketApp();
try { try {
log("Starting..."); log("Starting...");
await Database.connect(); await Database.connect();
//await test();
await app.listen(); await app.listen();
await wsApp.listen(); await wsApp.listen();
iTender.setStatus(iTenderStatus.STARTING);
await Utils.sleep(5000);
await init(); await init();
setInterval(refresh, 1000 * 60 * 10);
iTender.setStatus(iTenderStatus.READY);
} catch (e) { } catch (e) {
console.error("---- ERROR ----") console.error("---- ERROR ----")
console.error(e); console.error(e);
@ -27,19 +39,61 @@ const wsApp = new WebsocketApp();
} }
})(); })();
function init() : Promise<void> { function init(): Promise<void> {
log("Initializing..."); log("Initializing...");
return new Promise(async resolve => { return new Promise(async resolve => {
await iTender.refreshContainers(); await iTender.refreshContainers();
await iTender.refreshDrinks(); await iTender.refreshDrinks();
await iTender.autoCheckup();
resolve(); resolve();
}); });
} }
function refresh(): Promise<void> {
return new Promise(async resolve => {
// If there is a current job, DO NOT REFRESH!
if (iTender.currentJob)
return;
//await iTender.refreshContainers(); Not needed because there is no change in containers?
await iTender.measureContainers();
//await iTender.refreshDrinks(); Not needed because there is no change in drinks?
});
}
async function test() { async function test() {
console.log("Testing fn"); console.log("Testing fn");
let ingredient = await Ingredient.findOne({name: "Cola"}); /*
let cola = new Ingredient();
cola.name = "Cola";
cola.category = Category.SOFTDRINK;
await cola.save();
let sprite = new Ingredient();
sprite.name = "Sprite";
sprite.category = Category.SOFTDRINK;
await sprite.save();
let fanta = new Ingredient();
fanta.name = "Fanta";
fanta.category = Category.SOFTDRINK;
await fanta.save();
let drink = new Drink();
drink.name = "Fanta";
drink.category = Category.ALCOHOL_FREE;
drink.ingredients = [{type: fanta, amount: 200}];
await drink.save();
drink = new Drink();
drink.name = "Mezzo Mix";
drink.category = Category.ALCOHOL_FREE;
drink.ingredients = [{type: fanta, amount: 100}, {type: cola, amount: 100}];
await drink.save();*/
let ingredient = await Ingredient.findOne({name: "Fanta"});
if (!ingredient) if (!ingredient)
return; return;
@ -49,26 +103,26 @@ async function test() {
await drink.save();*/ await drink.save();*/
let drink = await Drink.findOne({name: "Cola"}).populate("ingredients.type"); /* let drink = await Drink.findOne({name: "Cola"}).populate("ingredients.type");
if (!drink) return; if (!drink) return;
console.log(drink); console.log(drink);*/
/*let container = new Container(); let container = new Container();
container.slot = 1; container.slot = 2;
container.volume = 750; container.volume = 750;
container.sensorEcho = 26; container.sensorEcho = 28;
container.sensorTrigger = 27; container.sensorTrigger = 29;
container.content = ingredient; container.content = ingredient;
container.sensorFilledMax = 2; container.sensorFilledMax = 2;
container.sensorFilledMin = 15; container.sensorFilledMin = 15;
await container.save();*/ await container.save();
let container = await Container.findOne({slot: 1}); /* let container = await Container.findOne({slot: 1});
if (!container) return; if (!container) return;
console.log(container); console.log(container);*/
//console.log(drink.ingredients) //console.log(drink.ingredients)

View File

@ -1,33 +0,0 @@
import {WebSocketPayload} from "../../WebSocketPayload";
import debug from "debug";
import {WebSocketHandler} from "../../WebSocketHandler";
const express = require('express');
const router = express.Router();
const log = debug("itender:websocket");
router.ws('/', (ws, req, next) => {
log("Incoming websocket connection")
ws.on('message', async (raw, bool) => {
let msg = WebSocketPayload.parseFromJSON(raw);
// If message is null, close the socket because it could not be decompiled
if (!msg) {
ws.close(1011);
return;
}
switch (msg.event) {
}
});
ws.on('open', (listener) => {
if (WebSocketHandler.ws)
WebSocketHandler.ws.close(1001);
WebSocketHandler.ws = ws;
});
});
module.exports = router;

View File

@ -0,0 +1,59 @@
import {WebSocketPayload} from "../../WebSocketPayload";
import debug from "debug";
import {WebSocketHandler} from "../../WebSocketHandler";
import {iTender} from "../../iTender";
import {iTenderStatus} from "../../iTenderStatus";
import {WebSocketEvent} from "../../WebSocketEvent";
const express = require('express');
const router = express.Router();
const log = debug("itender:websocket");
router.ws('/', async (ws, req, next) => {
log("Incoming websocket connection...");
if (WebSocketHandler.ws)
WebSocketHandler.ws.close(1001);
WebSocketHandler.ws = ws;
await WebSocketHandler.sendStatus();
async function sendWhenReady() {
if (iTender.status != iTenderStatus.READY) {
setTimeout(sendWhenReady, 100);
return;
}
iTender.setStatus(iTenderStatus.REFRESHING);
let payload = new WebSocketPayload(WebSocketEvent.DRINKS, false, iTender.drinks);
await WebSocketHandler.send(payload);
setTimeout(() => {
if (iTender.status == iTenderStatus.REFRESHING)
iTender.setStatus(iTenderStatus.READY);
}, 2000);
}
sendWhenReady().then();
ws.on('message', async (raw, bool) => {
let msg = WebSocketPayload.parseFromBase64Json(raw);
// If message is null, close the socket because it could not be decompiled
if (!msg) {
ws.close(1011);
return;
}
switch (msg.event) {
}
});
});
module.exports = router;

0
src/test.ts Normal file
View File

View File

@ -2,14 +2,19 @@ import {ButtonType} from "./ButtonType";
export class Modal { export class Modal {
private static currentModalId = ""; private static currentModalId: string | undefined = "";
private _title: string = "iTender"; private _title: string = "iTender";
private _content: string | undefined = ""; private _content: string | undefined = "";
private _id: string = ""; private _id: string = "";
private _loader: boolean = false; private _loader: boolean = false;
private _buttons: { type: string, content: string, onclick: Function }[] = []; private _buttons: { type: string, content: string, onclick: Function }[] = [];
private _leftCentered: boolean = false;
set leftCentered(value: boolean) {
this._leftCentered = value;
}
private static modalInClose = false;
constructor(id, title: string, content?: string) { constructor(id, title: string, content?: string) {
this._id = id; this._id = id;
@ -17,6 +22,10 @@ export class Modal {
this._content = content; this._content = content;
} }
public static isModalOpen(): boolean {
return !(!this.currentModalId);
}
set title(value: string) { set title(value: string) {
this._title = value; this._title = value;
@ -34,8 +43,7 @@ export class Modal {
this._loader = value; this._loader = value;
} }
public addButton( type: ButtonType, content: string, onclick: Function ) public addButton(type: ButtonType, content: string, onclick: Function) {
{
this._buttons.push({type: type, content: content, onclick: onclick}); this._buttons.push({type: type, content: content, onclick: onclick});
} }
@ -43,16 +51,39 @@ export class Modal {
if (!this._content) if (!this._content)
this._content = ""; this._content = "";
if( this._leftCentered )
{
this._content = "<div style='text-align: left; padding-left: 2%;'>" + this._content;
}
if (this._loader) if (this._loader)
this._content += "<br><div class=\"lds-ellipsis\">\n" + this._content += "<br><div class=\"lds-ellipsis\">\n" +
" <div></div><div></div><div></div><div></div>\n" + " <div></div><div></div><div></div><div></div>\n" +
"</div>"; "</div>";
for (let btn of this._buttons) { for (let btn of this._buttons) {
this._content += `<button class="btn btn-${btn.type}" onclick="${btn.onclick}">${btn.content}</button>`; this._content += `<button class="btn btn-${btn.type}" onclick="(${btn.onclick})();">${btn.content}</button>`;
} }
Modal.open(this._title, this._content, this._id); if( this._leftCentered )
{
this._content+= "</div>";
}
let title = this._title;
let content = this._content;
let id = this._id;
function tryOpen()
{
if( Modal.modalInClose )
{
setTimeout( tryOpen, 50 );
return;
}
Modal.open(title, content, id );
}
tryOpen();
} }
/** /**
@ -61,6 +92,7 @@ export class Modal {
* @param id * @param id
*/ */
public static open(title: string, content: string, id?: string): void { public static open(title: string, content: string, id?: string): void {
const modal = document.getElementById("modal"); const modal = document.getElementById("modal");
const modalContent = document.getElementById("modalInnerContent"); const modalContent = document.getElementById("modalInnerContent");
@ -82,9 +114,11 @@ export class Modal {
} }
public static close(id?: string): void { public static close(id?: string): void {
if (this.currentModalId != id) if (id && this.currentModalId != id)
return; return;
Modal.modalInClose = true;
const modal = document.getElementById("modal"); const modal = document.getElementById("modal");
const modalContent = document.getElementById("modal-content"); const modalContent = document.getElementById("modal-content");
const modalInnerContent = document.getElementById("modalInnerContent"); const modalInnerContent = document.getElementById("modalInnerContent");
@ -100,6 +134,9 @@ export class Modal {
modalInnerContent.innerHTML = ""; modalInnerContent.innerHTML = "";
modalContent.classList.remove("modalBlendOut"); modalContent.classList.remove("modalBlendOut");
modal.classList.remove("modalBlendOut"); modal.classList.remove("modalBlendOut");
this.modalInClose = false;
}, 800); }, 800);
this.currentModalId = undefined;
} }
} }

54
src/web/WebHandler.ts Normal file
View File

@ -0,0 +1,54 @@
import {WebSocketPayload} from "../WebSocketPayload";
import {IDrink} from "../database/IDrink";
import {Modal} from "./Modal";
export class WebHandler {
public static onDrinkUpdate(payload: WebSocketPayload) {
if (!payload.data) return;
let drinks: IDrink[] = payload.data;
const main = document.getElementById("main");
if (!main) return;
main.style.gridTemplateRows = `repeat(${Math.round(drinks.length / 3)}, calc(90%/2))`;
for (let drink of drinks) {
let drinkEle = document.createElement("div");
drinkEle.classList.add("drink");
let drinkImg = document.createElement("img");
drinkImg.classList.add("thumbnail");
drinkEle.append(drinkImg);
let drinkName = document.createElement("p");
drinkName.classList.add("drinkName");
drinkEle.append(drinkName);
drinkImg.alt = "Foto von " + drink.name;
drinkImg.src = "/images/" + drink.name + ".png";
drinkName.innerText = drink.name;
let ingredients = "<ul style='list-style-type: disc;'>";
for( let i of drink.ingredients )
{
ingredients += "<li>" + i.amount + "ml " + i.type.name + "</li>";
}
ingredients+="</ul>"
drinkEle.onclick = () => {
let modal = new Modal("drink", drink.name );
modal.content = `<strong>Zutaten</strong><br>
${ingredients}`
modal.leftCentered = true;
modal.open();
};
main.append(drinkEle);
}
}
}

View File

@ -1,28 +1,117 @@
import {Modal} from "./Modal"; import {Modal} from "./Modal";
import {WebSocketEvent} from "../WebSocketEvent";
import {WebSocketPayload} from "../WebSocketPayload";
import {ButtonType} from "./ButtonType";
import {iTenderStatus} from "../iTenderStatus";
import {WebHandler} from "./WebHandler";
export class WebWebSocketHandler { export class WebWebSocketHandler {
private socket : WebSocket; private socket: WebSocket;
private static url = (window.location.protocol == "http:" ? "ws://" : "wss://") + window.location.hostname + ":3005";
constructor() { constructor() {
this.socket = new WebSocket((window.location.protocol == "http:" ? "ws://" : "wss://") + window.location.hostname + ":3005"); this.socket = new WebSocket(WebWebSocketHandler.url);
this.socket.onopen = this.onOpen; this.socket.onopen = this.onOpen;
this.socket.onclose = this.onClose; this.socket.onclose = this.onClose;
this.socket.onerror = this.onError; this.socket.onerror = this.onError;
this.socket.onmessage = this.onMessage;
} }
private onOpen( event ) private onMessage(msgEvent: MessageEvent) {
{ console.log("[WS] Incoming message", msgEvent);
let payload = WebSocketPayload.parseFromBase64Json(msgEvent.data);
if (!payload) {
console.log("[WS] Could not parse message: ", msgEvent);
return;
}
console.debug(payload)
console.debug(payload.event);
console.log("[WS] Received " + payload.event + " Event");
switch (payload.event) {
case WebSocketEvent.STATUS: {
let statusElement = document.getElementById("status");
if (statusElement)
statusElement.innerText = payload.data.status;
let status: iTenderStatus = payload.data.status;
switch (status) {
case iTenderStatus.READY: {
Modal.close("start"); Modal.close("start");
Modal.close("refreshing");
break;
}
case iTenderStatus.STARTING: {
let modal = new Modal("start", "Willkommen!", `Einen Augenblick bitte<br>iTender startet...`);
modal.loader = true;
modal.open();
break;
}
case iTenderStatus.REFRESHING: {
let modal = new Modal("refreshing", "Aktualisieren...", `Einen Augenblick bitte<br>iTender aktualisiert die Getränke...`);
modal.loader = true;
modal.open();
break;
}
}
break;
} }
private onClose( event ) { case WebSocketEvent.DRINKS: {
console.error("WS Closed!", event ); WebHandler.onDrinkUpdate(payload);
//openModal("Einen Augenblick...", `Es wurde ein Verbindungsfehler erkannt.\nBitte warten Sie, während der Prozess neu gestartet wird...` ); break;
//window.location.reload(); }
}
} }
private onError( event ) { private onOpen(event) {
console.error("WS Error", event); console.log("[WS] Connected", event);
let connectionElement = document.getElementById("right");
if (connectionElement)
{
connectionElement.innerText = "Verbunden";
connectionElement.style.color = "green";
}
}
private onClose(event) {
console.error("[WS] Closed!", event);
if (event.wasClean) {
let modal = new Modal("socketClosed", "Sitzung beendet!");
modal.content = `Diese Sitzung wurde beendet, da der iTender nun an einem anderen Gerät bzw. an dem Hauptgerät gesteuert wird.<br><br>`;
modal.addButton(ButtonType.SUCCESS, "Sitzung wiederherstellen", () => {
window.location.reload();
});
modal.open();
} else {
let modal = new Modal("socketClosed", "Verbindungsproblem!");
modal.content = `Die Benutzeroberfläche hat die Verbindung mit dem Gerät verloren.<br>Die Verbindung wird wiederhergestellt...<br>`;
modal.loader = true;
modal.open();
setInterval(() => {
window.location.reload();
}, 5000);
}
let connectionElement = document.getElementById("right");
if (connectionElement)
{
connectionElement.innerText = "Getrennt";
connectionElement.style.color = "red";
}
}
private onError(event) {
console.error("[WS] Error", event);
let connectionElement = document.getElementById("right");
if (connectionElement)
connectionElement.innerText = "Fehler";
//openModal("Einen Augenblick...", `Es wurde ein kritischer Fehler festgestellt.\nBitte warten Sie, während der Prozess neu gestartet wird...` ); //openModal("Einen Augenblick...", `Es wurde ein kritischer Fehler festgestellt.\nBitte warten Sie, während der Prozess neu gestartet wird...` );
//window.location.reload(); //window.location.reload();
} }

View File

@ -10,12 +10,13 @@ document.addEventListener("DOMContentLoaded", () => {
let modal = new Modal("start", "iTender"); let modal = new Modal("start", "iTender");
modal.content = "Willkommen"; modal.content = "Willkommen";
modal.loader = true; modal.loader = true;
modal.open(); //modal.open();
connect(); connect();
setTimeout( load, 1000); setTimeout( load, 1000);
}); });
function load() function load()
{ {
if(!main||!time) if(!main||!time)
@ -25,35 +26,10 @@ function load()
let currentDate = new Date(); let currentDate = new Date();
time.innerText = "" + ( currentDate.getHours() < 10 ? "0" + currentDate.getHours() : currentDate.getHours() ) + ":" + ( currentDate.getMinutes() < 10 ? "0" + currentDate.getMinutes() : currentDate.getMinutes() ); time.innerText = "" + ( currentDate.getHours() < 10 ? "0" + currentDate.getHours() : currentDate.getHours() ) + ":" + ( currentDate.getMinutes() < 10 ? "0" + currentDate.getMinutes() : currentDate.getMinutes() );
}, 1000 ); }, 1000 );
let maxI = 20;
main.style.gridTemplateRows = `repeat(${Math.round(maxI/3)}, calc(90%/2))`;
for( let i = 0; i<maxI; i++ )
{
let testDrink = document.createElement("div");
testDrink.classList.add("drink");
let img = document.createElement("img");
img.classList.add("thumbnail");
testDrink.append(img);
let name = document.createElement("p");
name.classList.add("name");
testDrink.append(name);
img.alt="Thumbnail";
name.innerText = "Mixery"
main.append(testDrink);
}
} }
let wsHandler; let wsHandler;
function connect() function connect()
{ {
wsHandler = new WebWebSocketHandler(); wsHandler = new WebWebSocketHandler();
} }

28
staticWeb/index.html Normal file
View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head><title>iTender</title>
<link rel="stylesheet" href="../public/stylesheets/reset.css">
<link rel="stylesheet" href="../public/stylesheets/style.css">
<meta charset="UTF-8">
</head>
<body>
<div class="modal" id="modal">
<div class="modal-content" id="modal-content">
<div class="modalInnerContent" id="modalInnerContent"></div>
</div>
</div>
<div id="overlay">
<div id="top"><span id="left"><strong>Status:</strong> <span id="status"></span></span><span
id="title">iTender</span><span id="right">Verbinden...</span></div>
<div id="bottom">
<button id="menuBtn">Menü</button>
</div>
</div>
<div id="container">
<div id="menu"></div>
<div id="settings"></div>
<div id="main"></div>
</div>
<script src="./web.bundle.js"></script>
</body>
</html>

346
staticWeb/web.bundle.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -4,6 +4,7 @@ html
title iTender title iTender
link(rel='stylesheet', href='/stylesheets/reset.css') link(rel='stylesheet', href='/stylesheets/reset.css')
link(rel='stylesheet', href='/stylesheets/style.css') link(rel='stylesheet', href='/stylesheets/style.css')
meta(charset="UTF-8")
body body
div.modal#modal div.modal#modal
div.modal-content#modal-content div.modal-content#modal-content
@ -11,7 +12,9 @@ html
div#overlay div#overlay
div#top div#top
span#left <strong>Status:</strong> <span id="status"></span>
span#title iTender span#title iTender
span#right Verbinden...
div#bottom div#bottom
button#menuBtn Menü button#menuBtn Menü
div#container div#container
@ -25,7 +28,7 @@ html
block extra block extra
script(src="/web.js") script(src="/web.js")
script. script.
setTimeout( () => // setTimeout( () =>
{ // {
window.location.reload(); // window.location.reload();
}, 120000 ); // }, 120000 );

View File

@ -11,7 +11,7 @@ module.exports = {
{ {
test: /\.tsx?$/, test: /\.tsx?$/,
use: "ts-loader", use: "ts-loader",
exclude: /node_modules/, //exclude: /node_modules/,
}, },
], ],
}, },