diff --git a/package.json b/package.json index 30e50b3..32f9b33 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@types/rpi-gpio": "^2.1.1", "@types/rpi-ws281x-native": "^1.0.0", "@types/serialport": "^8.0.2", + "@types/sharp": "^0.31.1", "axios": "^1.2.0", "buffer": "^6.0.3", "cookie-parser": "^1.4.6", @@ -42,7 +43,8 @@ "pug": "^3.0.2", "rpi-gpio": "^2.1.7", "rpi-ws281x-native": "^1.0.4", - "serialport": "^10.5.0" + "serialport": "^10.5.0", + "sharp": "^0.31.3" }, "devDependencies": { "nodemon": "^2.0.20", diff --git a/public/stylesheets/main.css b/public/stylesheets/main.css index 331b251..a96f523 100644 --- a/public/stylesheets/main.css +++ b/public/stylesheets/main.css @@ -8,6 +8,7 @@ grid-template-columns: repeat(3, calc(90% / 3)); grid-template-rows: repeat(2, calc(90% / 2)); grid-gap: 9% 5.5%; + overflow-x: hidden; } @@ -33,9 +34,9 @@ #main .drink:hover { - background-color: rgba(57, 57, 57, 0.8); - width: 100%; - height: 100%; + background-color: rgba(22, 67, 113, 0.8); + /*width: 100%; + height: 100%;*/ } diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 087b0ff..4186d86 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -187,7 +187,7 @@ h2 { #overlay #bottom #menuContainers { height: 100%; display: grid; - grid-template-columns: repeat(14,1fr); + grid-template-columns: repeat(14, 1fr); grid-auto-flow: column; direction: rtl; grid-template-rows: 100%; @@ -195,15 +195,16 @@ h2 { padding: 1% 2% 0.2%; } + #menuContainers .container { text-align: center; border-radius: 0 0 30% 30%; border: 1px solid black; border-top-color: transparent; padding-top: 18%; - background-color: #5b5b9b; + background-color: #5B5B9B; transition: all 1s; - transition: background-color 3s; + transition: background-color 9s; direction: ltr; } @@ -279,28 +280,40 @@ h2 { transition: visibility 0.8s; } + .tooltip { position: relative; display: inline-block; } + .tooltip .tooltiptext { - visibility: hidden; - width: 100px; + opacity: 0; + width: 110px; background-color: #214B74; - color: #fff; + color: #FFFFFF; text-align: center; border-radius: 8px; padding: 5px 0; - /* Position the tooltip */ position: absolute; z-index: 1; - bottom: 45%; + bottom: 40%; left: 50%; - margin-left: -60px; + margin-left: -80px; } + +@keyframes blendIn { + 0% { + opacity: 0; + } + 100% { + opacity: 100%; + } +} + + .tooltip:hover .tooltiptext { - visibility: visible; + animation: blendIn 0.5s ease forwards; } \ No newline at end of file diff --git a/src/Utils.ts b/src/Utils.ts index 57d2adf..0800298 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -3,6 +3,7 @@ import * as fs from "fs"; import * as https from 'https'; import path from "path"; import {Settings} from "./Settings"; +import sharp from "sharp"; export class Utils { public static checkInternet(): Promise { @@ -23,20 +24,26 @@ export class Utils { }) } - static downloadImage(url, filepath) { - + static downloadImage(url, id) { return new Promise((resolve, reject) => { + // "./public/images/" + drink._id + ".png" + let tempPath = "./public/images/" + id + "_pre.png"; + let finalPath = "./public/images/" + id + ".png"; try { - if (!fs.existsSync(path.dirname(filepath))) - fs.mkdirSync(path.dirname(filepath)); + if (!fs.existsSync(path.dirname(tempPath))) + fs.mkdirSync(path.dirname(finalPath)); } catch (e) { } https.get(url, (res) => { if (res.statusCode === 200) { - res.pipe(fs.createWriteStream(filepath)) + res.pipe(fs.createWriteStream(tempPath)) .on('error', reject) - .once('close', () => resolve(filepath)); + .once('close', async () => { + await sharp(tempPath).resize( { width: 400 } ).sharpen().toFile(finalPath); + fs.unlinkSync(tempPath); + resolve(finalPath); + }); } else { // Consume response data to free up memory res.resume(); diff --git a/src/iTender.ts b/src/iTender.ts index 18bcf8c..f4e04c8 100644 --- a/src/iTender.ts +++ b/src/iTender.ts @@ -17,12 +17,10 @@ import axios from "axios"; import {Mixer} from "./Mixer"; - const log = debug("itender:station"); const mixLog = debug("itender:mixer"); - /** * The main class of the itender, here a located all main features of the system, like starting pumps, firing events and stuff */ @@ -42,20 +40,18 @@ export class iTender { */ static sensitivityFactor: number = 1.0; - /** - * Retrieve all drinks in cache - */ - static get drinks(): IDrink[] { - return this._drinks; - } - - /** * Current internal status of itender * @private */ private static _status: iTenderStatus = iTenderStatus.STARTING; + /** + * Returns the internal status of itender app + */ + static get status(): iTenderStatus { + return this._status; + } /** * Current internal connection-status boolean @@ -77,6 +73,13 @@ export class iTender { */ private static _drinks: IDrink[]; + /** + * Retrieve all drinks in cache + */ + static get drinks(): IDrink[] { + return this._drinks; + } + /** * Sets the current itender status and sends it to the client * @param status @@ -88,14 +91,6 @@ export class iTender { log("Status is now " + status); } - /** - * Returns the internal status of itender app - */ - static get status(): iTenderStatus { - return this._status; - } - - /** * This method is fired if the user likes to mix a drink * It starts to calculate the ingredients and amounts of each ingredient @@ -112,6 +107,7 @@ export class iTender { } const job = new Job(); + console.debug(data.drink, data.amount); let amounts: { ingredient: IIngredient, amount: number, container?: IContainer }[] = []; job.completeAmount = 0; @@ -132,11 +128,10 @@ export class iTender { let factor = sum / data.amount; for (let x of (drink.ingredients as { type: IIngredient, amount: number }[])) { - amounts.push({ingredient: x.type, amount: x.amount * factor}) - job.completeAmount += x.amount; + amounts.push({ingredient: x.type, amount: x.amount / factor}) + job.completeAmount += x.amount / factor; } - } else { for (let x of (drink.ingredients as { type: IIngredient, amount: number }[])) { amounts.push({ingredient: x.type, amount: x.amount}); @@ -322,7 +317,7 @@ export class iTender { if (!Utils.checkForImage(remote._id)) { let url = "https://itender.iif.li/images/" + remote._id + ".png"; try { - await Utils.downloadImage(url, "./public/images/" + drink._id + ".png") + await Utils.downloadImage(url, drink._id); log("Drink " + remote.name + "'s Thumbnail downloaded"); } catch (e) { log("Drink " + remote.name + " failed to download thumbnail! (" + url + ") | " + e); diff --git a/src/routes/ws/websocketRoute.ts b/src/routes/ws/websocketRoute.ts index 2539453..79db2c6 100644 --- a/src/routes/ws/websocketRoute.ts +++ b/src/routes/ws/websocketRoute.ts @@ -19,6 +19,7 @@ import * as os from "os"; import {promisify} from "util"; import Drink from "../../database/Drink"; import path from "path"; +import {Utils} from "../../Utils"; const exec = promisify(require('child_process').exec) @@ -361,6 +362,7 @@ router.ws('/', async (ws, req, next) => { await Drink.deleteMany({}); await Ingredient.deleteMany({}); for (let c of (await Container.find())) { + Utils.deleteImage(c._id); c.content = undefined; c.save(); } @@ -369,7 +371,6 @@ router.ws('/', async (ws, req, next) => { WebSocketHandler.answerRequest(msg.data["type"] as RequestType, true); break; } - } break; } diff --git a/src/web/Fill.ts b/src/web/Fill.ts index 81de201..5c6b1e3 100644 --- a/src/web/Fill.ts +++ b/src/web/Fill.ts @@ -41,7 +41,7 @@ export class Fill { let cancelBtn = document.createElement("button"); cancelBtn.classList.add("btn", "btn-danger"); - cancelBtn.innerText = "Abbrechen"; + cancelBtn.innerText = "STOP"; cancelBtn.disabled = true; setTimeout(() => { cancelBtn.disabled = false; @@ -58,14 +58,15 @@ export class Fill { function riseSlowlyUp(lastNumber: number, number: number) { for (let i = lastNumber; i < number; i++) { setTimeout(() => { - ml.innerText = Math.floor(i) + "ml"; + ml.innerText = Math.ceil(i) + "ml"; }, (number - lastNumber / 1000) + i * 4); } } + let interval; modal.open().then(() => { WebWebSocketHandler.request(RequestType.JOB).then((payload) => { - let minus = 0; + let minus = -1; let job = payload.data as IJob; ml.innerText = Math.floor((job.completeAmount / job.estimatedTime) * minus) + "ml"; waterAnimDiv.style.setProperty("--fillTime", job.estimatedTime + "s"); @@ -74,17 +75,25 @@ export class Fill { seconds.innerText = Math.floor(job.estimatedTime) + "s"; let last = 0; - let interval = setInterval(() => { + function updateTimeAndMl() + { minus++; if (minus + 1 > (job.estimatedTime as number)) { - setTimeout( () => clearInterval(interval), 1500 ); + setTimeout(() => clearInterval(interval), 2000); } - seconds.innerText = (Math.floor(job.estimatedTime as number - minus)) + "s"; + let iT = (Math.floor(job.estimatedTime as number - minus)); + if (iT < 0) + iT = 0; + + seconds.innerText = iT + "s"; let calc = Math.floor((job.completeAmount / job.estimatedTime) * minus); riseSlowlyUp(last, calc) last = calc; - }, 1000); + } + + interval = setInterval(updateTimeAndMl, 1000); + updateTimeAndMl(); setTimeout(() => { @@ -105,42 +114,54 @@ export class Fill { return new Promise(async resolve => { let modal = new Modal("fillOptions", drink.name + " Mixen"); let pre = document.createElement("p"); - pre.innerHTML = `Bitte wähle die Größe deines Glas aus
`; + pre.innerHTML = `Bitte wähle die Größe deines Glases aus
`; modal.addContent(pre); let div = document.createElement("div"); + div.style.display = "grid"; + div.style.minHeight = "20vh"; + div.style.gridTemplateRows = "100%"; + div.style.gridTemplateColumns = "repeat(4,auto)"; + div.style.marginTop = "5%"; + div.style.marginBottom = "2%"; - let sizes = [ ["shot", "Shot", 20], ["small", "Klein", 120], ["normal", "Normal", 200], ["large", "Groß", 300] ]; - for( let s of sizes ) - { + let sizes = [["shot", "Shot", 20], ["small", "Klein", 120], ["normal", "Normal", 200], ["large", "Groß", 300]]; + for (let s of sizes) { let glass = document.createElement("div"); let img = document.createElement("img"); img.src = "/static/" + s[0] + ".png"; + img.style.minHeight = "100%"; + img.style.maxHeight = "100%"; + img.alt = "" + s[1]; + let bottom = document.createElement("p"); bottom.style.textAlign = "center"; bottom.innerHTML = `${s[1]} ${s[2]}ml`; - glass.append(img,bottom); + glass.append(img, bottom); div.append(glass); + + glass.onclick = () => { + + WebWebSocketHandler.request(RequestType.STARTFILL, {drink: drink, amount: s[2]}).then((payload) => { + let data = payload.data as { success: boolean, job?: IJob }; + + if (!data.success) { + let modal = new Modal("fill", "Oh nein!"); + let txt = document.createElement("p"); + txt.innerHTML = `Es ist nicht genug Inhalt in den Behältern, um das gewünschte Getränk in der Größe bereitzustellen.

Bitte wende dich an das Wartungspersonal.

`; + modal.addContent(txt); + modal.addButton(ButtonType.SECONDARY, "Schließen", () => modal.close()); + modal.open(); + } + }); + }; } modal.addContent(div); modal.addBR(); modal.addButton(ButtonType.SECONDARY, "Abbrechen", () => modal.close()); - modal.addButton(ButtonType.SUCCESS, "Starten", () => { - WebWebSocketHandler.request(RequestType.STARTFILL, {drink: drink}).then((payload) => { - let data = payload.data as { success: boolean, job?: IJob }; - - if (!data.success) { - let modal = new Modal("fill", "Oh nein!"); - let txt = document.createElement("p"); - txt.innerHTML = `Es ist nicht genug Inhalt in den Behältern, um das gewünschte Getränk in der Größe bereitzustellen.

Bitte wende dich an das Wartungspersonal.

`; - modal.addContent(txt); - modal.addButton(ButtonType.SECONDARY, "Schließen", () => modal.close()); - modal.open(); - } - }); - }); + // modal.addButton(ButtonType.SUCCESS, "Starten", () => {}); await modal.open(); resolve(); }); diff --git a/src/web/WebHandler.ts b/src/web/WebHandler.ts index 6198c23..09e134b 100644 --- a/src/web/WebHandler.ts +++ b/src/web/WebHandler.ts @@ -7,6 +7,7 @@ import {RequestType} from "../RequestType"; import {IJob} from "../database/IJob"; import {Modal} from "./Modal"; import {ButtonType} from "./ButtonType"; +import {Fill} from "./Fill"; export class WebHandler { private static _currentPane: Pane; @@ -44,8 +45,8 @@ export class WebHandler { drinkName.innerText = drink.name; - //drinkEle.onclick = () => Fill.userFillRequest(drink); - drinkEle.onclick = () => { + drinkEle.onclick = () => Fill.userFillRequest(drink); + /*drinkEle.onclick = () => { WebWebSocketHandler.request(RequestType.STARTFILL, {drink: drink}).then((payload) => { let data = payload.data as { success: boolean, job?: IJob }; @@ -59,6 +60,8 @@ export class WebHandler { } }); } + */ + /* let ingredients = "