diff --git a/public/images/Blue Lagoon.png b/public/images/Blue Lagoon.png new file mode 100644 index 0000000..a546cca Binary files /dev/null and b/public/images/Blue Lagoon.png differ diff --git a/public/images/Caipirinha.png b/public/images/Caipirinha.png new file mode 100644 index 0000000..a261129 Binary files /dev/null and b/public/images/Caipirinha.png differ diff --git a/public/images/Cosmopolitan.png b/public/images/Cosmopolitan.png new file mode 100644 index 0000000..32dc501 Binary files /dev/null and b/public/images/Cosmopolitan.png differ diff --git a/public/images/Cubralibre.png b/public/images/Cubralibre.png new file mode 100644 index 0000000..1cb6b1e Binary files /dev/null and b/public/images/Cubralibre.png differ diff --git a/public/images/Gin Tonic.png b/public/images/Gin Tonic.png new file mode 100644 index 0000000..9b79df1 Binary files /dev/null and b/public/images/Gin Tonic.png differ diff --git a/public/images/Malibu Beach.png b/public/images/Malibu Beach.png new file mode 100644 index 0000000..78ff35d Binary files /dev/null and b/public/images/Malibu Beach.png differ diff --git a/public/images/Mezzo Mix.png b/public/images/Mezzo Mix.png new file mode 100644 index 0000000..be5fe95 Binary files /dev/null and b/public/images/Mezzo Mix.png differ diff --git a/public/images/Mojito.png b/public/images/Mojito.png new file mode 100644 index 0000000..3ec43d6 Binary files /dev/null and b/public/images/Mojito.png differ diff --git a/public/images/Moscow Mule.png b/public/images/Moscow Mule.png new file mode 100644 index 0000000..465b52e Binary files /dev/null and b/public/images/Moscow Mule.png differ diff --git a/public/images/Pina Colada.png b/public/images/Pina Colada.png new file mode 100644 index 0000000..163482f Binary files /dev/null and b/public/images/Pina Colada.png differ diff --git a/public/images/Sex on the Beach.png b/public/images/Sex on the Beach.png new file mode 100644 index 0000000..9711fa2 Binary files /dev/null and b/public/images/Sex on the Beach.png differ diff --git a/public/images/Swimming Pool.png b/public/images/Swimming Pool.png new file mode 100644 index 0000000..04f86c1 Binary files /dev/null and b/public/images/Swimming Pool.png differ diff --git a/public/images/Tequila Sunrise.png b/public/images/Tequila Sunrise.png new file mode 100644 index 0000000..61db825 Binary files /dev/null and b/public/images/Tequila Sunrise.png differ diff --git a/public/images/Wodka Bull.png b/public/images/Wodka Bull.png new file mode 100644 index 0000000..045fa93 Binary files /dev/null and b/public/images/Wodka Bull.png differ diff --git a/public/images/Wodka Cola.png b/public/images/Wodka Cola.png new file mode 100644 index 0000000..530eb8b Binary files /dev/null and b/public/images/Wodka Cola.png differ diff --git a/public/images/Wodka Fanta.png b/public/images/Wodka Fanta.png new file mode 100644 index 0000000..b91d9d7 Binary files /dev/null and b/public/images/Wodka Fanta.png differ diff --git a/public/images/Wodka Lemon.png b/public/images/Wodka Lemon.png new file mode 100644 index 0000000..a8d17f6 Binary files /dev/null and b/public/images/Wodka Lemon.png differ diff --git a/public/images/Wodka O.png b/public/images/Wodka O.png new file mode 100644 index 0000000..4d04285 Binary files /dev/null and b/public/images/Wodka O.png differ diff --git a/public/images/Zombie.png b/public/images/Zombie.png new file mode 100644 index 0000000..ec50688 Binary files /dev/null and b/public/images/Zombie.png differ diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 6845fe6..2a030fa 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -5,7 +5,7 @@ :root { - cursor: none; + cursor: none !important; } @@ -22,7 +22,7 @@ body { scroll-behavior: smooth; font-family: "Roboto", serif; font-style: normal; - cursor: none; + cursor: none !important; } @@ -40,15 +40,35 @@ body { height: 9%; background-color: #1F5E5F; color: white; + font-size: 2em; + padding-right: 3px; + padding-left: 3px; + } - #title { - float: right; - width: 100%; - font-size: 1.9em; 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 { display: grid; height: 100%; - + padding: 1% 2%; grid-template-columns: repeat(3, calc(90% / 3)); grid-template-rows: repeat(2, calc(90% / 2)); grid-gap: 10% 5%; @@ -122,26 +142,44 @@ body { #main .drink { grid-row: span 1; grid-column: span 1; - background-color: black; - width: 100%; - height: 100%; + background-color: rgba(57, 57, 57, 0.6); + width: 90%; + height: 97%; display: grid; grid-template-columns: 100%; grid-template-rows: repeat(3, calc(100% / 3)); - grid-row-gap: 5%; + grid-row-gap: 4%; text-align: center; 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 { grid-column: span 1; 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-row: span 1; - font-size: 140%; + font-size: 150%; } \ No newline at end of file diff --git a/src/App.ts b/src/App.ts index 7504133..275470e 100644 --- a/src/App.ts +++ b/src/App.ts @@ -50,7 +50,7 @@ export class App { public loadRoutes( ) : void { - this._app.use( "/", require("./routes/index") ); + this._app.use( "/", require("./routes/indexRouter") ); } diff --git a/src/Category.ts b/src/Category.ts index 1115b77..cb7f82d 100644 --- a/src/Category.ts +++ b/src/Category.ts @@ -1,6 +1,7 @@ export enum Category { - ALCOHOLIC, - ALCOHOL_FREE, - SYRUP, - JUICE + ALCOHOLIC = "ALCOHOLIC", + ALCOHOL_FREE = "ALCOHOL_FREE", + SYRUP = "SYRUP", + JUICE = "JUICE", + SOFTDRINK = "SOFTDRINK" } \ No newline at end of file diff --git a/src/Utils.ts b/src/Utils.ts index 258af84..ba0e9da 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -12,4 +12,11 @@ export class Utils { }); } + + static sleep(ms: number): Promise { + return new Promise(resolve => { + setTimeout(() => resolve(), ms); + }) + } + } \ No newline at end of file diff --git a/src/WebSocketEvent.ts b/src/WebSocketEvent.ts index bfd9d87..5dfcfdc 100644 --- a/src/WebSocketEvent.ts +++ b/src/WebSocketEvent.ts @@ -1,7 +1,5 @@ export enum WebSocketEvent { - STATUS_STARTING, - STATUS_READY, - STATUS_FILLING, - STATUS_REFRESHING, - STATUS_ERROR + STATUS= "STATUS", + DRINKS = "DRINKS", + CONTAINERS = "CONTAINERS", } \ No newline at end of file diff --git a/src/WebSocketHandler.ts b/src/WebSocketHandler.ts index b319489..f7d6641 100644 --- a/src/WebSocketHandler.ts +++ b/src/WebSocketHandler.ts @@ -1,4 +1,6 @@ import {WebSocketPayload} from "./WebSocketPayload"; +import {WebSocketEvent} from "./WebSocketEvent"; +import {iTender} from "./iTender"; export class WebSocketHandler { private static _ws: WebSocket; @@ -14,20 +16,21 @@ export class WebSocketHandler { public static send(payload: WebSocketPayload): Promise { return new Promise(async (resolve, reject) => { try { - if( this.ws && this.ws.readyState == 1 ) - { + if (this.ws && this.ws.readyState == 1) { await this.ws.send(payload.toString()); resolve(); } - else - { - reject("Websocket is not connected!"); - } - } 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); + }); + } + } \ No newline at end of file diff --git a/src/WebSocketPayload.ts b/src/WebSocketPayload.ts index 147b6da..0c15ab3 100644 --- a/src/WebSocketPayload.ts +++ b/src/WebSocketPayload.ts @@ -5,40 +5,40 @@ export class WebSocketPayload { this._event = value; } - set status(value: boolean) { - this._status = value; + set error(value: boolean) { + this._error = value; } - set data(value: object | undefined) { + set data(value: any | undefined) { this._data = value; } private _event: WebSocketEvent; - private _status: boolean; - private _data: object | undefined; + private _error: boolean; + private _data: any | undefined; get event(): WebSocketEvent { return this._event; } - get status(): boolean { - return this._status; + get error(): boolean { + return this._error; } - get data(): object | undefined { + get data(): any | undefined { return this._data; } - constructor(event: WebSocketEvent, status: boolean, data?: object) { + constructor(event: WebSocketEvent, error: boolean = false, data?: any) { this._event = event; - this._status = status; + this._error = error; this._data = data; } - public static parseFromJSON(json: string): WebSocketPayload | null { - json = (window) ? atob(json) : Buffer.from(json, "base64").toString(); - let rawPayload: { event: string, status: boolean, data: object }; + public static parseFromBase64Json(json: string): WebSocketPayload | null { + json = (typeof window != 'undefined') ? atob(json) : Buffer.from(json, "base64").toString(); + let rawPayload: { event: string, error: boolean, data: any }; try { rawPayload = JSON.parse(json); } catch (e) { @@ -47,15 +47,15 @@ export class WebSocketPayload { let wsEvent = 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 */ public toString(): string { - let json = JSON.stringify({"event": this._event, status: this._status, data: this._data}); - json = (window) ? btoa(json) : Buffer.from(json).toString("base64"); + let json = JSON.stringify({"event": this._event, status: this._error, data: this._data}); + json = ((typeof window != 'undefined') ? btoa(json) : Buffer.from(json).toString("base64")); return json; } } \ No newline at end of file diff --git a/src/WebsocketApp.ts b/src/WebsocketApp.ts index 7e85d74..b7540fd 100644 --- a/src/WebsocketApp.ts +++ b/src/WebsocketApp.ts @@ -38,7 +38,7 @@ export class WebsocketApp { public loadRoutes( ) : void { - this._app.use( "/", require("./routes/ws/websocket") ); + this._app.use( "/", require("./routes/ws/websocketRoute") ); } public listen(): Promise { diff --git a/src/database/Drink.ts b/src/database/Drink.ts index a57ec0d..a04fbfa 100644 --- a/src/database/Drink.ts +++ b/src/database/Drink.ts @@ -4,8 +4,7 @@ import * as mongoose from "mongoose"; export const DrinkSchema = new mongoose.Schema({ name: {type: String, required: true}, ingredients: [{type: {type: mongoose.Types.ObjectId, ref: "Ingredient", required: true}, amount: { type: Number } }], - category: String, - recommendedQuantity: {type: Number, default: 200 } + category: String }); const Drink = mongoose.model('Drink', DrinkSchema); diff --git a/src/database/IDrink.ts b/src/database/IDrink.ts index c19367a..ca58dcc 100644 --- a/src/database/IDrink.ts +++ b/src/database/IDrink.ts @@ -12,7 +12,5 @@ export interface IDrink extends mongoose.Document { // Category of the drink category: Category; - // Recommended amount in milliliters - recommendedQuantity: number; } \ No newline at end of file diff --git a/src/database/IJob.ts b/src/database/IJob.ts new file mode 100644 index 0000000..a5ab748 --- /dev/null +++ b/src/database/IJob.ts @@ -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; +} \ No newline at end of file diff --git a/src/database/Job.ts b/src/database/Job.ts new file mode 100644 index 0000000..654977e --- /dev/null +++ b/src/database/Job.ts @@ -0,0 +1,15 @@ +import mongoose from "mongoose"; +import {IJob} from "./IJob"; + + +export const JobSchema = new mongoose.Schema({ + drink: {type: mongoose.Types.ObjectId, ref: "Drink"}, + amount: Number, + startedAt: Date, + endAt: Date, + successful: Boolean +}); + + +const Job = mongoose.model('Job', JobSchema); +export default Job; \ No newline at end of file diff --git a/src/iTender.ts b/src/iTender.ts index 90a7ef4..9a00f21 100644 --- a/src/iTender.ts +++ b/src/iTender.ts @@ -4,15 +4,35 @@ import {HCSR04} from "hc-sr04"; import {IContainer} from "./database/IContainer"; import Drink from "./database/Drink"; 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 { - 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 }[]; - private static drinks: IDrink[]; + static get drinks(): IDrink[] { + return this._drinks; + } - static set status(value: iTenderStatus) { - this._status = value; + static get currentJob(): IJob | null { + 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 { @@ -21,32 +41,42 @@ export class iTender { static startFill() { // todo Fill method + + + // End + this.measureContainers().then(); } - static stopFill() { + static cancelFill() { // todo Stop fill method } static measureContainers(): Promise { + log("Measuring containers..."); + return new Promise(async resolve => { - let containers = await Container.find(); - /* - public measure() : Number { - let dist = this.sensor.distance(); - return dist * 100 / (this.sensorFilledMax + this.sensorFilledMin); - } - */ - }) + for (let c of this._containers) { + try { + let dist = c.sensor.distance(); + c.container.filled = dist * 100 / (c.container.sensorFilledMax + c.container.sensorFilledMin); + } catch (e) { + c.container.filled = -1; + } + await c.container.save(); + } + log("Containers measured!"); + resolve(); + }); } static refreshDrinks(): Promise { + this.setStatus(iTenderStatus.REFRESHING); + log("Refreshing drinks..."); return new Promise(async resolve => { - this.drinks = []; + this._drinks = []; for (let d of (await Drink.find().populate("ingredients.type"))) { - let drinkAccept = true; - for (let i of d.ingredients) { let c = await Container.findOne({content: i["type"]}); @@ -54,26 +84,37 @@ export class iTender { drinkAccept = false; break; } - } - if (drinkAccept) - this.drinks.push(d); + if (drinkAccept) { + this._drinks.push(d); + } + } + log("Drinks refreshed!"); + + resolve(); + this.setStatus(iTenderStatus.READY); }); } static refreshContainers(): Promise { - + log("Refreshing containers..."); + this.setStatus(iTenderStatus.CALCULATING); return new Promise(async resolve => { let containers = await Container.find(); for (let c of containers) { let i = 0; let found = false; - for (let c2 of this.containers) { + for (let c2 of this._containers) { 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, - sensor: new HCSR04(c.sensorTrigger, c.sensorEcho), + sensor: sensor, pump: null }; found = true; @@ -81,10 +122,42 @@ export class iTender { } i++; } - if (!found) - this.containers.push({container: c, sensor: new HCSR04(c.sensorTrigger, c.sensorEcho), pump: null}); + if (!found) { + 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); } diff --git a/src/iTenderStatus.ts b/src/iTenderStatus.ts index e2b1821..4704b3e 100644 --- a/src/iTenderStatus.ts +++ b/src/iTenderStatus.ts @@ -1,14 +1,14 @@ export enum iTenderStatus { // Machine is going to start - STARTING, + STARTING= "STARTING", // Machine is ready - READY, + READY = "READY", // Machine is filling your drink and destroying your leberwurst - FILLING, + FILLING = "FILLING", // 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) - CALCULATING, + CALCULATING = "CALCULATING", // An error happened; Oh no :( - ERROR + ERROR = "ERROR" } \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index b5d8346..f395251 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,10 +2,13 @@ import {App} from "./App"; import debug from "debug"; import {WebsocketApp} from "./WebsocketApp"; import {Database} from "./database/Database"; -import Drink from "./database/Drink"; import Ingredient from "./database/Ingredient"; -import Container from "./database/Container"; 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"); @@ -17,9 +20,18 @@ const wsApp = new WebsocketApp(); try { log("Starting..."); await Database.connect(); + //await test(); await app.listen(); await wsApp.listen(); + + iTender.setStatus(iTenderStatus.STARTING); + await Utils.sleep(5000); + await init(); + + setInterval(refresh, 1000 * 60 * 10); + + iTender.setStatus(iTenderStatus.READY); } catch (e) { console.error("---- ERROR ----") console.error(e); @@ -27,19 +39,61 @@ const wsApp = new WebsocketApp(); } })(); -function init() : Promise { +function init(): Promise { log("Initializing..."); return new Promise(async resolve => { await iTender.refreshContainers(); await iTender.refreshDrinks(); + await iTender.autoCheckup(); + resolve(); }); } +function refresh(): Promise { + 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() { 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) return; @@ -49,26 +103,26 @@ async function test() { await drink.save();*/ - let drink = await Drink.findOne({name: "Cola"}).populate("ingredients.type"); - if (!drink) return; + /* let drink = await Drink.findOne({name: "Cola"}).populate("ingredients.type"); + if (!drink) return; - console.log(drink); + console.log(drink);*/ - /*let container = new Container(); - container.slot = 1; + let container = new Container(); + container.slot = 2; container.volume = 750; - container.sensorEcho = 26; - container.sensorTrigger = 27; + container.sensorEcho = 28; + container.sensorTrigger = 29; container.content = ingredient; container.sensorFilledMax = 2; container.sensorFilledMin = 15; - await container.save();*/ + await container.save(); - let container = await Container.findOne({slot: 1}); - if (!container) return; + /* let container = await Container.findOne({slot: 1}); + if (!container) return; - console.log(container); + console.log(container);*/ //console.log(drink.ingredients) diff --git a/src/routes/index.ts b/src/routes/indexRouter.ts similarity index 100% rename from src/routes/index.ts rename to src/routes/indexRouter.ts diff --git a/src/routes/ws/websocket.ts b/src/routes/ws/websocket.ts deleted file mode 100644 index 1517f7d..0000000 --- a/src/routes/ws/websocket.ts +++ /dev/null @@ -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; diff --git a/src/routes/ws/websocketRoute.ts b/src/routes/ws/websocketRoute.ts new file mode 100644 index 0000000..2125de9 --- /dev/null +++ b/src/routes/ws/websocketRoute.ts @@ -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; diff --git a/src/test.ts b/src/test.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/web/Modal.ts b/src/web/Modal.ts index af2fa9c..abe77c9 100644 --- a/src/web/Modal.ts +++ b/src/web/Modal.ts @@ -2,14 +2,19 @@ import {ButtonType} from "./ButtonType"; export class Modal { - private static currentModalId = ""; + private static currentModalId: string | undefined = ""; private _title: string = "iTender"; private _content: string | undefined = ""; private _id: string = ""; private _loader: boolean = false; 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) { this._id = id; @@ -17,6 +22,10 @@ export class Modal { this._content = content; } + public static isModalOpen(): boolean { + return !(!this.currentModalId); + } + set title(value: string) { this._title = value; @@ -34,8 +43,7 @@ export class Modal { 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}); } @@ -43,16 +51,39 @@ export class Modal { if (!this._content) this._content = ""; + if( this._leftCentered ) + { + this._content = "
" + this._content; + } + if (this._loader) this._content += "
\n" + "
\n" + "
"; for (let btn of this._buttons) { - this._content += ``; + this._content += ``; } - Modal.open(this._title, this._content, this._id); + if( this._leftCentered ) + { + this._content+= "
"; + } + + 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 */ public static open(title: string, content: string, id?: string): void { + const modal = document.getElementById("modal"); const modalContent = document.getElementById("modalInnerContent"); @@ -82,9 +114,11 @@ export class Modal { } public static close(id?: string): void { - if (this.currentModalId != id) + if (id && this.currentModalId != id) return; + Modal.modalInClose = true; + const modal = document.getElementById("modal"); const modalContent = document.getElementById("modal-content"); const modalInnerContent = document.getElementById("modalInnerContent"); @@ -100,6 +134,9 @@ export class Modal { modalInnerContent.innerHTML = ""; modalContent.classList.remove("modalBlendOut"); modal.classList.remove("modalBlendOut"); + this.modalInClose = false; }, 800); + + this.currentModalId = undefined; } } \ No newline at end of file diff --git a/src/web/WebHandler.ts b/src/web/WebHandler.ts new file mode 100644 index 0000000..0b7f544 --- /dev/null +++ b/src/web/WebHandler.ts @@ -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 = "
    "; + for( let i of drink.ingredients ) + { + ingredients += "
  • " + i.amount + "ml " + i.type.name + "
  • "; + } + ingredients+="
" + + drinkEle.onclick = () => { + let modal = new Modal("drink", drink.name ); + modal.content = `Zutaten
+${ingredients}` + modal.leftCentered = true; + modal.open(); + }; + + main.append(drinkEle); + } + + } + + +} \ No newline at end of file diff --git a/src/web/WebWebSocketHandler.ts b/src/web/WebWebSocketHandler.ts index 3af1523..013387b 100644 --- a/src/web/WebWebSocketHandler.ts +++ b/src/web/WebWebSocketHandler.ts @@ -1,28 +1,117 @@ 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 { - private socket : WebSocket; + private socket: WebSocket; + private static url = (window.location.protocol == "http:" ? "ws://" : "wss://") + window.location.hostname + ":3005"; 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.onclose = this.onClose; this.socket.onerror = this.onError; + this.socket.onmessage = this.onMessage; } - private onOpen( event ) - { - Modal.close("start"); + 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("refreshing"); + break; + } + case iTenderStatus.STARTING: { + let modal = new Modal("start", "Willkommen!", `Einen Augenblick bitte
iTender startet...`); + modal.loader = true; + modal.open(); + break; + } + case iTenderStatus.REFRESHING: { + let modal = new Modal("refreshing", "Aktualisieren...", `Einen Augenblick bitte
iTender aktualisiert die Getränke...`); + modal.loader = true; + modal.open(); + break; + + } + } + break; + } + + case WebSocketEvent.DRINKS: { + WebHandler.onDrinkUpdate(payload); + break; + } + } } - private onClose( event ) { - console.error("WS Closed!", event ); - //openModal("Einen Augenblick...", `Es wurde ein Verbindungsfehler erkannt.\nBitte warten Sie, während der Prozess neu gestartet wird...` ); - //window.location.reload(); + private onOpen(event) { + console.log("[WS] Connected", event); + + let connectionElement = document.getElementById("right"); + if (connectionElement) + { + connectionElement.innerText = "Verbunden"; + connectionElement.style.color = "green"; + } + } - private onError( event ) { - console.error("WS Error", event); + 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.

`; + 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.
Die Verbindung wird wiederhergestellt...
`; + 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...` ); //window.location.reload(); } diff --git a/src/web/main.ts b/src/web/main.ts index 5444e4b..b2a4df9 100644 --- a/src/web/main.ts +++ b/src/web/main.ts @@ -10,12 +10,13 @@ document.addEventListener("DOMContentLoaded", () => { let modal = new Modal("start", "iTender"); modal.content = "Willkommen"; modal.loader = true; - modal.open(); + //modal.open(); connect(); setTimeout( load, 1000); }); + function load() { if(!main||!time) @@ -25,35 +26,10 @@ function load() let currentDate = new Date(); time.innerText = "" + ( currentDate.getHours() < 10 ? "0" + currentDate.getHours() : currentDate.getHours() ) + ":" + ( currentDate.getMinutes() < 10 ? "0" + currentDate.getMinutes() : currentDate.getMinutes() ); }, 1000 ); - - let maxI = 20; - - main.style.gridTemplateRows = `repeat(${Math.round(maxI/3)}, calc(90%/2))`; - - - for( let i = 0; i + +iTender + + + + + + +
+
Status: iTenderVerbinden...
+
+ +
+
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/staticWeb/web.bundle.js b/staticWeb/web.bundle.js new file mode 100644 index 0000000..21e3fef --- /dev/null +++ b/staticWeb/web.bundle.js @@ -0,0 +1,346 @@ +/******/ (() => { // webpackBootstrap +/******/ "use strict"; +/******/ var __webpack_modules__ = ({ + +/***/ "./src/WebSocketEvent.ts": +/*!*******************************!*\ + !*** ./src/WebSocketEvent.ts ***! + \*******************************/ +/***/ ((__unused_webpack_module, exports) => { + + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.WebSocketEvent = void 0; +var WebSocketEvent; +(function (WebSocketEvent) { + WebSocketEvent["STATUS"] = "STATUS"; +})(WebSocketEvent = exports.WebSocketEvent || (exports.WebSocketEvent = {})); + + +/***/ }), + +/***/ "./src/WebSocketPayload.ts": +/*!*********************************!*\ + !*** ./src/WebSocketPayload.ts ***! + \*********************************/ +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.WebSocketPayload = void 0; +const WebSocketEvent_1 = __webpack_require__(/*! ./WebSocketEvent */ "./src/WebSocketEvent.ts"); +class WebSocketPayload { + constructor(event, error = false, data) { + this._event = event; + this._error = error; + this._data = data; + } + set event(value) { + this._event = value; + } + set error(value) { + this._error = value; + } + set data(value) { + this._data = value; + } + get event() { + return this._event; + } + get error() { + return this._error; + } + get data() { + return this._data; + } + static parseFromBase64Json(json) { + json = (typeof window != 'undefined') ? atob(json) : Buffer.from(json, "base64").toString(); + let rawPayload; + try { + rawPayload = JSON.parse(json); + } + catch (e) { + return null; + } + let wsEvent = WebSocketEvent_1.WebSocketEvent[rawPayload.event]; + return new WebSocketPayload(wsEvent, rawPayload.error, rawPayload.data); + } + toString() { + let json = JSON.stringify({ "event": this._event, status: this._error, data: this._data }); + json = ((typeof window != 'undefined') ? btoa(json) : Buffer.from(json).toString("base64")); + return json; + } +} +exports.WebSocketPayload = WebSocketPayload; + + +/***/ }), + +/***/ "./src/web/ButtonType.ts": +/*!*******************************!*\ + !*** ./src/web/ButtonType.ts ***! + \*******************************/ +/***/ ((__unused_webpack_module, exports) => { + + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.ButtonType = void 0; +var ButtonType; +(function (ButtonType) { + ButtonType["SUCCESS"] = "success"; + ButtonType["ERROR"] = "error"; +})(ButtonType = exports.ButtonType || (exports.ButtonType = {})); + + +/***/ }), + +/***/ "./src/web/Modal.ts": +/*!**************************!*\ + !*** ./src/web/Modal.ts ***! + \**************************/ +/***/ ((__unused_webpack_module, exports) => { + + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.Modal = void 0; +class Modal { + constructor(id, title, content) { + this._title = "iTender"; + this._content = ""; + this._id = ""; + this._loader = false; + this._buttons = []; + this._id = id; + this._title = title; + this._content = content; + } + set title(value) { + this._title = value; + } + set content(value) { + this._content = value; + } + set id(value) { + this._id = value; + } + set loader(value) { + this._loader = value; + } + addButton(type, content, onclick) { + this._buttons.push({ type: type, content: content, onclick: onclick }); + } + open() { + if (!this._content) + this._content = ""; + if (this._loader) + this._content += "
\n" + + "
\n" + + "
"; + for (let btn of this._buttons) { + this._content += ``; + } + Modal.open(this._title, this._content, this._id); + } + static open(title, content, id) { + const modal = document.getElementById("modal"); + const modalContent = document.getElementById("modalInnerContent"); + if (!modal || !modalContent) + return; + modalContent.classList.add("modalBlendIn"); + modal.classList.add("modalBlendIn"); + setTimeout(() => { + modalContent.classList.remove("modalBlendIn"); + modal.classList.remove("modalBlendIn"); + }, 800); + modalContent.innerHTML = `

${title}

${content}`; + modal.style.display = "block"; + this.currentModalId = id ? id : "null"; + } + static close(id) { + if (this.currentModalId != id) + return; + const modal = document.getElementById("modal"); + const modalContent = document.getElementById("modal-content"); + const modalInnerContent = document.getElementById("modalInnerContent"); + if (!modal || !modalContent || !modalInnerContent) + return; + modalContent.classList.add("modalBlendOut"); + modal.classList.add("modalBlendOut"); + setTimeout(() => { + modal.style.display = "none"; + modalInnerContent.innerHTML = ""; + modalContent.classList.remove("modalBlendOut"); + modal.classList.remove("modalBlendOut"); + }, 800); + } +} +exports.Modal = Modal; +Modal.currentModalId = ""; + + +/***/ }), + +/***/ "./src/web/WebWebSocketHandler.ts": +/*!****************************************!*\ + !*** ./src/web/WebWebSocketHandler.ts ***! + \****************************************/ +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.WebWebSocketHandler = void 0; +const Modal_1 = __webpack_require__(/*! ./Modal */ "./src/web/Modal.ts"); +const WebSocketEvent_1 = __webpack_require__(/*! ../WebSocketEvent */ "./src/WebSocketEvent.ts"); +const WebSocketPayload_1 = __webpack_require__(/*! ../WebSocketPayload */ "./src/WebSocketPayload.ts"); +const ButtonType_1 = __webpack_require__(/*! ./ButtonType */ "./src/web/ButtonType.ts"); +class WebWebSocketHandler { + constructor() { + this.socket = new WebSocket(WebWebSocketHandler.url); + this.socket.onopen = this.onOpen; + this.socket.onclose = this.onClose; + this.socket.onerror = this.onError; + this.socket.onmessage = this.onMessage; + } + onMessage(msgEvent) { + console.log("[WS] Incoming message", msgEvent); + let payload = WebSocketPayload_1.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_1.WebSocketEvent.STATUS: { + let statusElement = document.getElementById("status"); + if (statusElement) + statusElement.innerText = payload.data.status; + } + } + } + onOpen(event) { + console.log("[WS] Connected", event); + Modal_1.Modal.close("start"); + let connectionElement = document.getElementById("right"); + if (connectionElement) + connectionElement.innerText = "Verbunden"; + } + onClose(event) { + console.error("[WS] Closed!", event); + if (event.wasClean) { + let modal = new Modal_1.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.

`; + modal.addButton(ButtonType_1.ButtonType.SUCCESS, "Sitzung wiederherstellen", () => { + window.location.reload(); + }); + modal.open(); + } + else { + let modal = new Modal_1.Modal("socketClosed", "Verbindungsproblem!"); + modal.content = `Die Benutzeroberfläche hat die Verbindung mit dem Gerät verloren.
Die Verbindung wird wiederhergestellt...
`; + modal.loader = true; + modal.open(); + setInterval(() => { + window.location.reload(); + }, 5000); + } + let connectionElement = document.getElementById("right"); + if (connectionElement) + connectionElement.innerText = "Keine Verbindung"; + } + onError(event) { + console.error("[WS] Error", event); + let connectionElement = document.getElementById("right"); + if (connectionElement) + connectionElement.innerText = "Fehler"; + } +} +exports.WebWebSocketHandler = WebWebSocketHandler; +WebWebSocketHandler.url = (window.location.protocol == "http:" ? "ws://" : "wss://") + window.location.hostname + ":3005"; + + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ var cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +var __webpack_exports__ = {}; +// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk. +(() => { +var exports = __webpack_exports__; +/*!*************************!*\ + !*** ./src/web/main.ts ***! + \*************************/ + +Object.defineProperty(exports, "__esModule", ({ value: true })); +const WebWebSocketHandler_1 = __webpack_require__(/*! ./WebWebSocketHandler */ "./src/web/WebWebSocketHandler.ts"); +const Modal_1 = __webpack_require__(/*! ./Modal */ "./src/web/Modal.ts"); +const main = document.getElementById("main"); +const time = document.getElementById("title"); +document.addEventListener("DOMContentLoaded", () => { + console.log("DOM Loaded"); + let modal = new Modal_1.Modal("start", "iTender"); + modal.content = "Willkommen"; + modal.loader = true; + modal.open(); + connect(); + setTimeout(load, 1000); +}); +function load() { + if (!main || !time) + return; + setInterval(() => { + let currentDate = new Date(); + time.innerText = "" + (currentDate.getHours() < 10 ? "0" + currentDate.getHours() : currentDate.getHours()) + ":" + (currentDate.getMinutes() < 10 ? "0" + currentDate.getMinutes() : currentDate.getMinutes()); + }, 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; +function connect() { + wsHandler = new WebWebSocketHandler_1.WebWebSocketHandler(); +} + +})(); + +/******/ })() +; +//# sourceMappingURL=data:application/json;charset=utf-8;base64,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QUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUV0QixJQUFJLElBQUksR0FBRyxRQUFRLENBQUMsYUFBYSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ3ZDLElBQUksQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQzNCLFNBQVMsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUM7UUFFdkIsR0FBRyxDQUFDLEdBQUcsR0FBQyxXQUFXLENBQUM7UUFDcEIsSUFBSSxDQUFDLFNBQVMsR0FBRyxRQUFRO1FBRXpCLElBQUksQ0FBQyxNQUFNLENBQUMsU0FBUyxDQUFDLENBQUM7S0FDekI7QUFDSixDQUFDO0FBRUQsSUFBSSxTQUFTLENBQUM7QUFDZCxTQUFTLE9BQU87SUFFYixTQUFTLEdBQUcsSUFBSSx5Q0FBbUIsRUFBRSxDQUFDO0FBRXpDLENBQUMiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9pdGVuZGVyLy4vc3JjL1dlYlNvY2tldEV2ZW50LnRzIiwid2VicGFjazovL2l0ZW5kZXIvLi9zcmMvV2ViU29ja2V0UGF5bG9hZC50cyIsIndlYnBhY2s6Ly9pdGVuZGVyLy4vc3JjL3dlYi9CdXR0b25UeXBlLnRzIiwid2VicGFjazovL2l0ZW5kZXIvLi9zcmMvd2ViL01vZGFsLnRzIiwid2VicGFjazovL2l0ZW5kZXIvLi9zcmMvd2ViL1dlYldlYlNvY2tldEhhbmRsZXIudHMiLCJ3ZWJwYWNrOi8vaXRlbmRlci93ZWJwYWNrL2Jvb3RzdHJhcCIsIndlYnBhY2s6Ly9pdGVuZGVyLy4vc3JjL3dlYi9tYWluLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBlbnVtIFdlYlNvY2tldEV2ZW50IHtcbiAgICBTVEFUVVM9IFwiU1RBVFVTXCJcbn0iLCJpbXBvcnQge1dlYlNvY2tldEV2ZW50fSBmcm9tIFwiLi9XZWJTb2NrZXRFdmVudFwiO1xuXG5leHBvcnQgY2xhc3MgV2ViU29ja2V0UGF5bG9hZCB7XG4gICAgc2V0IGV2ZW50KHZhbHVlOiBXZWJTb2NrZXRFdmVudCkge1xuICAgICAgICB0aGlzLl9ldmVudCA9IHZhbHVlO1xuICAgIH1cblxuICAgIHNldCBlcnJvcih2YWx1ZTogYm9vbGVhbikge1xuICAgICAgICB0aGlzLl9lcnJvciA9IHZhbHVlO1xuICAgIH1cblxuICAgIHNldCBkYXRhKHZhbHVlOiBhbnkgfCB1bmRlZmluZWQpIHtcbiAgICAgICAgdGhpcy5fZGF0YSA9IHZhbHVlO1xuICAgIH1cblxuICAgIHByaXZhdGUgX2V2ZW50OiBXZWJTb2NrZXRFdmVudDtcbiAgICBwcml2YXRlIF9lcnJvcjogYm9vbGVhbjtcbiAgICBwcml2YXRlIF9kYXRhOiBhbnkgfCB1bmRlZmluZWQ7XG5cblxuICAgIGdldCBldmVudCgpOiBXZWJTb2NrZXRFdmVudCB7XG4gICAgICAgIHJldHVybiB0aGlzLl9ldmVudDtcbiAgICB9XG5cbiAgICBnZXQgZXJyb3IoKTogYm9vbGVhbiB7XG4gICAgICAgIHJldHVybiB0aGlzLl9lcnJvcjtcbiAgICB9XG5cbiAgICBnZXQgZGF0YSgpOiBhbnkgfCB1bmRlZmluZWQge1xuICAgICAgICByZXR1cm4gdGhpcy5fZGF0YTtcbiAgICB9XG5cbiAgICBjb25zdHJ1Y3RvcihldmVudDogV2ViU29ja2V0RXZlbnQsIGVycm9yOiBib29sZWFuID0gZmFsc2UsIGRhdGE/OiBhbnkpIHtcbiAgICAgICAgdGhpcy5fZXZlbnQgPSBldmVudDtcbiAgICAgICAgdGhpcy5fZXJyb3IgPSBlcnJvcjtcbiAgICAgICAgdGhpcy5fZGF0YSA9IGRhdGE7XG4gICAgfVxuXG4gICAgcHVibGljIHN0YXRpYyBwYXJzZUZyb21CYXNlNjRKc29uKGpzb246IHN0cmluZyk6IFdlYlNvY2tldFBheWxvYWQgfCBudWxsIHtcbiAgICAgICAganNvbiA9ICh0eXBlb2Ygd2luZG93ICE9ICd1bmRlZmluZWQnKSA/IGF0b2IoanNvbikgOiBCdWZmZXIuZnJvbShqc29uLCBcImJhc2U2NFwiKS50b1N0cmluZygpO1xuICAgICAgICBsZXQgcmF3UGF5bG9hZDogeyBldmVudDogc3RyaW5nLCBlcnJvcjogYm9vbGVhbiwgZGF0YTogYW55IH07XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICByYXdQYXlsb2FkID0gSlNPTi5wYXJzZShqc29uKTtcbiAgICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICAgIH1cblxuICAgICAgICBsZXQgd3NFdmVudCA9IFdlYlNvY2tldEV2ZW50WzxrZXlvZiB0eXBlb2YgV2ViU29ja2V0RXZlbnQ+cmF3UGF5bG9hZC5ldmVudF07XG5cbiAgICAgICAgcmV0dXJuIG5ldyBXZWJTb2NrZXRQYXlsb2FkKHdzRXZlbnQsIHJhd1BheWxvYWQuZXJyb3IsIHJhd1BheWxvYWQuZGF0YSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0aGUgcGF5bG9hZCBhcyBiYXNlNjQgZW5jb2RlZCBqc29uIHN0cmluZ1xuICAgICAqL1xuICAgIHB1YmxpYyB0b1N0cmluZygpOiBzdHJpbmcge1xuICAgICAgICBsZXQganNvbiA9IEpTT04uc3RyaW5naWZ5KHtcImV2ZW50XCI6IHRoaXMuX2V2ZW50LCBzdGF0dXM6IHRoaXMuX2Vycm9yLCBkYXRhOiB0aGlzLl9kYXRhfSk7XG4gICAgICAgIGpzb24gPSAoKHR5cGVvZiB3aW5kb3cgIT0gJ3VuZGVmaW5lZCcpID8gYnRvYShqc29uKSA6IEJ1ZmZlci5mcm9tKGpzb24pLnRvU3RyaW5nKFwiYmFzZTY0XCIpKTtcbiAgICAgICAgcmV0dXJuIGpzb247XG4gICAgfVxufSIsImV4cG9ydCBlbnVtIEJ1dHRvblR5cGUge1xuICAgIFNVQ0NFU1MgPSBcInN1Y2Nlc3NcIixcbiAgICBFUlJPUiA9IFwiZXJyb3JcIixcblxufSIsImltcG9ydCB7QnV0dG9uVHlwZX0gZnJvbSBcIi4vQnV0dG9uVHlwZVwiO1xuXG5leHBvcnQgY2xhc3MgTW9kYWwge1xuXG4gICAgcHJpdmF0ZSBzdGF0aWMgY3VycmVudE1vZGFsSWQgPSBcIlwiO1xuXG4gICAgcHJpdmF0ZSBfdGl0bGU6IHN0cmluZyA9IFwiaVRlbmRlclwiO1xuICAgIHByaXZhdGUgX2NvbnRlbnQ6IHN0cmluZyB8IHVuZGVmaW5lZCA9IFwiXCI7XG4gICAgcHJpdmF0ZSBfaWQ6IHN0cmluZyA9IFwiXCI7XG4gICAgcHJpdmF0ZSBfbG9hZGVyOiBib29sZWFuID0gZmFsc2U7XG4gICAgcHJpdmF0ZSBfYnV0dG9uczogeyB0eXBlOiBzdHJpbmcsIGNvbnRlbnQ6IHN0cmluZywgb25jbGljazogRnVuY3Rpb24gfVtdID0gW107XG5cblxuICAgIGNvbnN0cnVjdG9yKGlkLCB0aXRsZTogc3RyaW5nLCBjb250ZW50Pzogc3RyaW5nKSB7XG4gICAgICAgIHRoaXMuX2lkID0gaWQ7XG4gICAgICAgIHRoaXMuX3RpdGxlID0gdGl0bGU7XG4gICAgICAgIHRoaXMuX2NvbnRlbnQgPSBjb250ZW50O1xuICAgIH1cblxuXG4gICAgc2V0IHRpdGxlKHZhbHVlOiBzdHJpbmcpIHtcbiAgICAgICAgdGhpcy5fdGl0bGUgPSB2YWx1ZTtcbiAgICB9XG5cbiAgICBzZXQgY29udGVudCh2YWx1ZTogc3RyaW5nIHwgdW5kZWZpbmVkKSB7XG4gICAgICAgIHRoaXMuX2NvbnRlbnQgPSB2YWx1ZTtcbiAgICB9XG5cbiAgICBzZXQgaWQodmFsdWU6IHN0cmluZykge1xuICAgICAgICB0aGlzLl9pZCA9IHZhbHVlO1xuICAgIH1cblxuICAgIHNldCBsb2FkZXIodmFsdWU6IGJvb2xlYW4pIHtcbiAgICAgICAgdGhpcy5fbG9hZGVyID0gdmFsdWU7XG4gICAgfVxuXG4gICAgcHVibGljIGFkZEJ1dHRvbiggdHlwZTogQnV0dG9uVHlwZSwgY29udGVudDogc3RyaW5nLCBvbmNsaWNrOiBGdW5jdGlvbiApXG4gICAge1xuICAgICAgICB0aGlzLl9idXR0b25zLnB1c2goe3R5cGU6IHR5cGUsIGNvbnRlbnQ6IGNvbnRlbnQsIG9uY2xpY2s6IG9uY2xpY2t9KTtcbiAgICB9XG5cbiAgICBwdWJsaWMgb3BlbigpIHtcbiAgICAgICAgaWYgKCF0aGlzLl9jb250ZW50KVxuICAgICAgICAgICAgdGhpcy5fY29udGVudCA9IFwiXCI7XG5cbiAgICAgICAgaWYgKHRoaXMuX2xvYWRlcilcbiAgICAgICAgICAgIHRoaXMuX2NvbnRlbnQgKz0gXCI8YnI+PGRpdiBjbGFzcz1cXFwibGRzLWVsbGlwc2lzXFxcIj5cXG5cIiArXG4gICAgICAgICAgICAgICAgXCIgIDxkaXY+PC9kaXY+PGRpdj48L2Rpdj48ZGl2PjwvZGl2PjxkaXY+PC9kaXY+XFxuXCIgK1xuICAgICAgICAgICAgICAgIFwiPC9kaXY+XCI7XG5cbiAgICAgICAgZm9yIChsZXQgYnRuIG9mIHRoaXMuX2J1dHRvbnMpIHtcbiAgICAgICAgICAgIHRoaXMuX2NvbnRlbnQgKz0gYDxidXR0b24gY2xhc3M9XCJidG4gYnRuLSR7YnRuLnR5cGV9XCIgb25jbGljaz1cIigke2J0bi5vbmNsaWNrfSkoKTtcIj4ke2J0bi5jb250ZW50fTwvYnV0dG9uPmA7XG4gICAgICAgIH1cblxuICAgICAgICBNb2RhbC5vcGVuKHRoaXMuX3RpdGxlLCB0aGlzLl9jb250ZW50LCB0aGlzLl9pZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQHBhcmFtIHRpdGxlXG4gICAgICogQHBhcmFtIGNvbnRlbnRcbiAgICAgKiBAcGFyYW0gaWRcbiAgICAgKi9cbiAgICBwdWJsaWMgc3RhdGljIG9wZW4odGl0bGU6IHN0cmluZywgY29udGVudDogc3RyaW5nLCBpZD86IHN0cmluZyk6IHZvaWQge1xuICAgICAgICBjb25zdCBtb2RhbCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKFwibW9kYWxcIik7XG4gICAgICAgIGNvbnN0IG1vZGFsQ29udGVudCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKFwibW9kYWxJbm5lckNvbnRlbnRcIik7XG5cbiAgICAgICAgaWYgKCFtb2RhbCB8fCAhbW9kYWxDb250ZW50KVxuICAgICAgICAgICAgcmV0dXJuO1xuXG4gICAgICAgIG1vZGFsQ29udGVudC5jbGFzc0xpc3QuYWRkKFwibW9kYWxCbGVuZEluXCIpO1xuICAgICAgICBtb2RhbC5jbGFzc0xpc3QuYWRkKFwibW9kYWxCbGVuZEluXCIpO1xuXG4gICAgICAgIHNldFRpbWVvdXQoKCkgPT4ge1xuICAgICAgICAgICAgbW9kYWxDb250ZW50LmNsYXNzTGlzdC5yZW1vdmUoXCJtb2RhbEJsZW5kSW5cIik7XG4gICAgICAgICAgICBtb2RhbC5jbGFzc0xpc3QucmVtb3ZlKFwibW9kYWxCbGVuZEluXCIpO1xuICAgICAgICB9LCA4MDApO1xuXG4gICAgICAgIG1vZGFsQ29udGVudC5pbm5lckhUTUwgPSBgPGgxIGlkPVwibW9kYWxUaXRsZVwiPiR7dGl0bGV9PC9oMT4ke2NvbnRlbnR9YDtcbiAgICAgICAgbW9kYWwuc3R5bGUuZGlzcGxheSA9IFwiYmxvY2tcIjtcblxuICAgICAgICB0aGlzLmN1cnJlbnRNb2RhbElkID0gaWQgPyBpZCA6IFwibnVsbFwiO1xuICAgIH1cblxuICAgIHB1YmxpYyBzdGF0aWMgY2xvc2UoaWQ/OiBzdHJpbmcpOiB2b2lkIHtcbiAgICAgICAgaWYgKHRoaXMuY3VycmVudE1vZGFsSWQgIT0gaWQpXG4gICAgICAgICAgICByZXR1cm47XG5cbiAgICAgICAgY29uc3QgbW9kYWwgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChcIm1vZGFsXCIpO1xuICAgICAgICBjb25zdCBtb2RhbENvbnRlbnQgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChcIm1vZGFsLWNvbnRlbnRcIik7XG4gICAgICAgIGNvbnN0IG1vZGFsSW5uZXJDb250ZW50ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoXCJtb2RhbElubmVyQ29udGVudFwiKTtcblxuICAgICAgICBpZiAoIW1vZGFsIHx8ICFtb2RhbENvbnRlbnQgfHwgIW1vZGFsSW5uZXJDb250ZW50KVxuICAgICAgICAgICAgcmV0dXJuO1xuXG4gICAgICAgIG1vZGFsQ29udGVudC5jbGFzc0xpc3QuYWRkKFwibW9kYWxCbGVuZE91dFwiKTtcbiAgICAgICAgbW9kYWwuY2xhc3NMaXN0LmFkZChcIm1vZGFsQmxlbmRPdXRcIik7XG5cbiAgICAgICAgc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICAgICAgICBtb2RhbC5zdHlsZS5kaXNwbGF5ID0gXCJub25lXCI7XG4gICAgICAgICAgICBtb2RhbElubmVyQ29udGVudC5pbm5lckhUTUwgPSBcIlwiO1xuICAgICAgICAgICAgbW9kYWxDb250ZW50LmNsYXNzTGlzdC5yZW1vdmUoXCJtb2RhbEJsZW5kT3V0XCIpO1xuICAgICAgICAgICAgbW9kYWwuY2xhc3NMaXN0LnJlbW92ZShcIm1vZGFsQmxlbmRPdXRcIik7XG4gICAgICAgIH0sIDgwMCk7XG4gICAgfVxufSIsImltcG9ydCB7TW9kYWx9IGZyb20gXCIuL01vZGFsXCI7XG5pbXBvcnQge1dlYlNvY2tldEV2ZW50fSBmcm9tIFwiLi4vV2ViU29ja2V0RXZlbnRcIjtcbmltcG9ydCB7V2ViU29ja2V0UGF5bG9hZH0gZnJvbSBcIi4uL1dlYlNvY2tldFBheWxvYWRcIjtcbmltcG9ydCB7QnV0dG9uVHlwZX0gZnJvbSBcIi4vQnV0dG9uVHlwZVwiO1xuXG5leHBvcnQgY2xhc3MgV2ViV2ViU29ja2V0SGFuZGxlciB7XG4gICAgcHJpdmF0ZSBzb2NrZXQ6IFdlYlNvY2tldDtcbiAgICBwcml2YXRlIHN0YXRpYyB1cmwgPSAod2luZG93LmxvY2F0aW9uLnByb3RvY29sID09IFwiaHR0cDpcIiA/IFwid3M6Ly9cIiA6IFwid3NzOi8vXCIpICsgd2luZG93LmxvY2F0aW9uLmhvc3RuYW1lICsgXCI6MzAwNVwiO1xuXG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMuc29ja2V0ID0gbmV3IFdlYlNvY2tldChXZWJXZWJTb2NrZXRIYW5kbGVyLnVybCk7XG4gICAgICAgIHRoaXMuc29ja2V0Lm9ub3BlbiA9IHRoaXMub25PcGVuO1xuICAgICAgICB0aGlzLnNvY2tldC5vbmNsb3NlID0gdGhpcy5vbkNsb3NlO1xuICAgICAgICB0aGlzLnNvY2tldC5vbmVycm9yID0gdGhpcy5vbkVycm9yO1xuICAgICAgICB0aGlzLnNvY2tldC5vbm1lc3NhZ2UgPSB0aGlzLm9uTWVzc2FnZTtcbiAgICB9XG5cbiAgICBwcml2YXRlIG9uTWVzc2FnZShtc2dFdmVudDogTWVzc2FnZUV2ZW50KSB7XG4gICAgICAgIGNvbnNvbGUubG9nKFwiW1dTXSBJbmNvbWluZyBtZXNzYWdlXCIsIG1zZ0V2ZW50KTtcbiAgICAgICAgbGV0IHBheWxvYWQgPSBXZWJTb2NrZXRQYXlsb2FkLnBhcnNlRnJvbUJhc2U2NEpzb24obXNnRXZlbnQuZGF0YSk7XG4gICAgICAgIGlmICghcGF5bG9hZCkge1xuICAgICAgICAgICAgY29uc29sZS5sb2coXCJbV1NdIENvdWxkIG5vdCBwYXJzZSBtZXNzYWdlOiBcIiwgbXNnRXZlbnQpO1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgICAgIGNvbnNvbGUuZGVidWcocGF5bG9hZClcblxuICAgICAgICBjb25zb2xlLmRlYnVnKHBheWxvYWQuZXZlbnQpO1xuXG4gICAgICAgIGNvbnNvbGUubG9nKFwiW1dTXSBSZWNlaXZlZCBcIiArIHBheWxvYWQuZXZlbnQgKyBcIiBFdmVudFwiKTtcblxuICAgICAgICBzd2l0Y2ggKHBheWxvYWQuZXZlbnQpIHtcbiAgICAgICAgICAgIGNhc2UgV2ViU29ja2V0RXZlbnQuU1RBVFVTOiB7XG4gICAgICAgICAgICAgICAgbGV0IHN0YXR1c0VsZW1lbnQgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChcInN0YXR1c1wiKTtcbiAgICAgICAgICAgICAgICBpZiAoc3RhdHVzRWxlbWVudClcbiAgICAgICAgICAgICAgICAgICAgc3RhdHVzRWxlbWVudC5pbm5lclRleHQgPSBwYXlsb2FkLmRhdGEuc3RhdHVzO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBvbk9wZW4oZXZlbnQpIHtcbiAgICAgICAgY29uc29sZS5sb2coXCJbV1NdIENvbm5lY3RlZFwiLCBldmVudCk7XG4gICAgICAgIE1vZGFsLmNsb3NlKFwic3RhcnRcIik7XG4gICAgICAgIGxldCBjb25uZWN0aW9uRWxlbWVudCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKFwicmlnaHRcIik7XG4gICAgICAgIGlmIChjb25uZWN0aW9uRWxlbWVudClcbiAgICAgICAgICAgIGNvbm5lY3Rpb25FbGVtZW50LmlubmVyVGV4dCA9IFwiVmVyYnVuZGVuXCI7XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBvbkNsb3NlKGV2ZW50KSB7XG4gICAgICAgIGNvbnNvbGUuZXJyb3IoXCJbV1NdIENsb3NlZCFcIiwgZXZlbnQpO1xuICAgICAgICBpZiggZXZlbnQud2FzQ2xlYW4gKVxuICAgICAgICB7XG4gICAgICAgICAgICBsZXQgbW9kYWwgPSBuZXcgTW9kYWwoXCJzb2NrZXRDbG9zZWRcIiwgXCJTaXR6dW5nIGJlZW5kZXQhXCIpO1xuICAgICAgICAgICAgbW9kYWwuY29udGVudCA9IGBEaWVzZSBTaXR6dW5nIHd1cmRlIGJlZW5kZXQsIGRhIGRlciBpVGVuZGVyIG51biBhbiBlaW5lbSBhbmRlcmVuIEdlcsOkdCBiencuIGFuIGRlbSBIYXVwdGdlcsOkdCBnZXN0ZXVlcnQgd2lyZC48YnI+PGJyPmA7XG4gICAgICAgICAgICBtb2RhbC5hZGRCdXR0b24oQnV0dG9uVHlwZS5TVUNDRVNTLCBcIlNpdHp1bmcgd2llZGVyaGVyc3RlbGxlblwiLCAoKSA9PiB7XG4gICAgICAgICAgICAgICAgd2luZG93LmxvY2F0aW9uLnJlbG9hZCgpO1xuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICBtb2RhbC5vcGVuKCk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZVxuICAgICAgICB7XG4gICAgICAgICAgICBsZXQgbW9kYWwgPSBuZXcgTW9kYWwoXCJzb2NrZXRDbG9zZWRcIiwgXCJWZXJiaW5kdW5nc3Byb2JsZW0hXCIpO1xuICAgICAgICAgICAgbW9kYWwuY29udGVudCA9IGBEaWUgQmVudXR6ZXJvYmVyZmzDpGNoZSBoYXQgZGllIFZlcmJpbmR1bmcgbWl0IGRlbSBHZXLDpHQgdmVybG9yZW4uPGJyPkRpZSBWZXJiaW5kdW5nIHdpcmQgd2llZGVyaGVyZ2VzdGVsbHQuLi48YnI+YDtcbiAgICAgICAgICAgIG1vZGFsLmxvYWRlciA9IHRydWU7XG4gICAgICAgICAgICBtb2RhbC5vcGVuKCk7XG4gICAgICAgICAgICBzZXRJbnRlcnZhbCggKCkgPT4ge1xuICAgICAgICAgICAgICAgIHdpbmRvdy5sb2NhdGlvbi5yZWxvYWQoKTtcbiAgICAgICAgICAgIH0sIDUwMDAgKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGxldCBjb25uZWN0aW9uRWxlbWVudCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKFwicmlnaHRcIik7XG4gICAgICAgIGlmIChjb25uZWN0aW9uRWxlbWVudClcbiAgICAgICAgICAgIGNvbm5lY3Rpb25FbGVtZW50LmlubmVyVGV4dCA9IFwiS2VpbmUgVmVyYmluZHVuZ1wiO1xuICAgIH1cblxuICAgIHByaXZhdGUgb25FcnJvcihldmVudCkge1xuICAgICAgICBjb25zb2xlLmVycm9yKFwiW1dTXSBFcnJvclwiLCBldmVudCk7XG4gICAgICAgIGxldCBjb25uZWN0aW9uRWxlbWVudCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKFwicmlnaHRcIik7XG4gICAgICAgIGlmIChjb25uZWN0aW9uRWxlbWVudClcbiAgICAgICAgICAgIGNvbm5lY3Rpb25FbGVtZW50LmlubmVyVGV4dCA9IFwiRmVobGVyXCI7XG4gICAgICAgIC8vb3Blbk1vZGFsKFwiRWluZW4gQXVnZW5ibGljay4uLlwiLCBgRXMgd3VyZGUgZWluIGtyaXRpc2NoZXIgRmVobGVyIGZlc3RnZXN0ZWxsdC5cXG5CaXR0ZSB3YXJ0ZW4gU2llLCB3w6RocmVuZCBkZXIgUHJvemVzcyBuZXUgZ2VzdGFydGV0IHdpcmQuLi5gICk7XG4gICAgICAgIC8vd2luZG93LmxvY2F0aW9uLnJlbG9hZCgpO1xuICAgIH1cbn0iLCIvLyBUaGUgbW9kdWxlIGNhY2hlXG52YXIgX193ZWJwYWNrX21vZHVsZV9jYWNoZV9fID0ge307XG5cbi8vIFRoZSByZXF1aXJlIGZ1bmN0aW9uXG5mdW5jdGlvbiBfX3dlYnBhY2tfcmVxdWlyZV9fKG1vZHVsZUlkKSB7XG5cdC8vIENoZWNrIGlmIG1vZHVsZSBpcyBpbiBjYWNoZVxuXHR2YXIgY2FjaGVkTW9kdWxlID0gX193ZWJwYWNrX21vZHVsZV9jYWNoZV9fW21vZHVsZUlkXTtcblx0aWYgKGNhY2hlZE1vZHVsZSAhPT0gdW5kZWZpbmVkKSB7XG5cdFx0cmV0dXJuIGNhY2hlZE1vZHVsZS5leHBvcnRzO1xuXHR9XG5cdC8vIENyZWF0ZSBhIG5ldyBtb2R1bGUgKGFuZCBwdXQgaXQgaW50byB0aGUgY2FjaGUpXG5cdHZhciBtb2R1bGUgPSBfX3dlYnBhY2tfbW9kdWxlX2NhY2hlX19bbW9kdWxlSWRdID0ge1xuXHRcdC8vIG5vIG1vZHVsZS5pZCBuZWVkZWRcblx0XHQvLyBubyBtb2R1bGUubG9hZGVkIG5lZWRlZFxuXHRcdGV4cG9ydHM6IHt9XG5cdH07XG5cblx0Ly8gRXhlY3V0ZSB0aGUgbW9kdWxlIGZ1bmN0aW9uXG5cdF9fd2VicGFja19tb2R1bGVzX19bbW9kdWxlSWRdKG1vZHVsZSwgbW9kdWxlLmV4cG9ydHMsIF9fd2VicGFja19yZXF1aXJlX18pO1xuXG5cdC8vIFJldHVybiB0aGUgZXhwb3J0cyBvZiB0aGUgbW9kdWxlXG5cdHJldHVybiBtb2R1bGUuZXhwb3J0cztcbn1cblxuIiwiaW1wb3J0IHtXZWJXZWJTb2NrZXRIYW5kbGVyfSBmcm9tIFwiLi9XZWJXZWJTb2NrZXRIYW5kbGVyXCI7XG5pbXBvcnQge01vZGFsfSBmcm9tIFwiLi9Nb2RhbFwiO1xuXG5jb25zdCBtYWluID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoXCJtYWluXCIpO1xuY29uc3QgdGltZSA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKFwidGl0bGVcIik7XG5cbmRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoXCJET01Db250ZW50TG9hZGVkXCIsICgpID0+IHtcbiAgIGNvbnNvbGUubG9nKFwiRE9NIExvYWRlZFwiKTtcblxuICAgbGV0IG1vZGFsID0gbmV3IE1vZGFsKFwic3RhcnRcIiwgXCJpVGVuZGVyXCIpO1xuICAgbW9kYWwuY29udGVudCA9IFwiV2lsbGtvbW1lblwiO1xuICAgbW9kYWwubG9hZGVyID0gdHJ1ZTtcbiAgIG1vZGFsLm9wZW4oKTtcbiAgIGNvbm5lY3QoKTtcblxuICAgc2V0VGltZW91dCggbG9hZCwgMTAwMCk7XG59KTtcblxuZnVuY3Rpb24gbG9hZCgpXG57XG4gICBpZighbWFpbnx8IXRpbWUpXG4gICAgICByZXR1cm47XG5cbiAgIHNldEludGVydmFsKCAoKSA9PiB7XG4gICAgICBsZXQgY3VycmVudERhdGUgPSBuZXcgRGF0ZSgpO1xuICAgICAgdGltZS5pbm5lclRleHQgPSBcIlwiICsgKCBjdXJyZW50RGF0ZS5nZXRIb3VycygpIDwgMTAgPyBcIjBcIiArIGN1cnJlbnREYXRlLmdldEhvdXJzKCkgOiBjdXJyZW50RGF0ZS5nZXRIb3VycygpICkgKyBcIjpcIiArICggY3VycmVudERhdGUuZ2V0TWludXRlcygpIDwgMTAgPyBcIjBcIiArIGN1cnJlbnREYXRlLmdldE1pbnV0ZXMoKSA6IGN1cnJlbnREYXRlLmdldE1pbnV0ZXMoKSApO1xuICAgfSwgMTAwMCApO1xuXG4gICBsZXQgbWF4SSA9IDIwO1xuXG4gICBtYWluLnN0eWxlLmdyaWRUZW1wbGF0ZVJvd3MgPSBgcmVwZWF0KCR7TWF0aC5yb3VuZChtYXhJLzMpfSwgY2FsYyg5MCUvMikpYDtcblxuXG4gICBmb3IoIGxldCBpID0gMDsgaTxtYXhJOyBpKysgKVxuICAge1xuICAgICAgbGV0IHRlc3REcmluayA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoXCJkaXZcIik7XG4gICAgICB0ZXN0RHJpbmsuY2xhc3NMaXN0LmFkZChcImRyaW5rXCIpO1xuXG4gICAgICBsZXQgaW1nID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcImltZ1wiKTtcbiAgICAgIGltZy5jbGFzc0xpc3QuYWRkKFwidGh1bWJuYWlsXCIpO1xuICAgICAgdGVzdERyaW5rLmFwcGVuZChpbWcpO1xuXG4gICAgICBsZXQgbmFtZSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoXCJwXCIpO1xuICAgICAgbmFtZS5jbGFzc0xpc3QuYWRkKFwibmFtZVwiKTtcbiAgICAgIHRlc3REcmluay5hcHBlbmQobmFtZSk7XG5cbiAgICAgIGltZy5hbHQ9XCJUaHVtYm5haWxcIjtcbiAgICAgIG5hbWUuaW5uZXJUZXh0ID0gXCJNaXhlcnlcIlxuXG4gICAgICBtYWluLmFwcGVuZCh0ZXN0RHJpbmspO1xuICAgfVxufVxuXG5sZXQgd3NIYW5kbGVyO1xuZnVuY3Rpb24gY29ubmVjdCgpXG57XG4gICB3c0hhbmRsZXIgPSBuZXcgV2ViV2ViU29ja2V0SGFuZGxlcigpO1xuXG59XG4iXSwibmFtZXMiOltdLCJzb3VyY2VSb290IjoiIn0= \ No newline at end of file diff --git a/views/layout.pug b/views/layout.pug index e99ea90..f17103f 100644 --- a/views/layout.pug +++ b/views/layout.pug @@ -4,6 +4,7 @@ html title iTender link(rel='stylesheet', href='/stylesheets/reset.css') link(rel='stylesheet', href='/stylesheets/style.css') + meta(charset="UTF-8") body div.modal#modal div.modal-content#modal-content @@ -11,7 +12,9 @@ html div#overlay div#top + span#left Status: span#title iTender + span#right Verbinden... div#bottom button#menuBtn Menü div#container @@ -25,7 +28,7 @@ html block extra script(src="/web.js") script. - setTimeout( () => - { - window.location.reload(); - }, 120000 ); + // setTimeout( () => + // { + // window.location.reload(); + // }, 120000 ); diff --git a/webpack.config.js b/webpack.config.js index 7bf85f4..99ac5ea 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -11,7 +11,7 @@ module.exports = { { test: /\.tsx?$/, use: "ts-loader", - exclude: /node_modules/, + //exclude: /node_modules/, }, ], },