diff --git a/package.json b/package.json index 6f4e247..68c5be4 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,13 @@ "license": "MIT", "devDependencies": { "@types/node": "^22.15.14", + "concurrently": "^9.1.2", "typescript": "^5.8.3" + }, + "scripts": { + "dev": "concurrently \"npm run --prefix web dev\" \"npm run --prefix server watchTS\"" + }, + "dependencies": { + "nodemon": "^3.1.10" } } diff --git a/server/.env b/server/.env new file mode 100644 index 0000000..06f6ae7 --- /dev/null +++ b/server/.env @@ -0,0 +1 @@ +DEBUG=itender:* \ No newline at end of file diff --git a/server/package.json b/server/package.json index aea0bfc..647ac12 100644 --- a/server/package.json +++ b/server/package.json @@ -1,15 +1,16 @@ { - "name": "itender", + "name": "itender-server", "version": "2.2.8", "private": true, "author": "Tobias Hopp ", "license": "UNLICENSED", + "main": "dist/main.js", "scripts": { "start": "node --trace-warnings ./dist/main.js", "compile": "tsc && webpack", "compileStart": "yarn run compile && yarn start", "watchTS": "tsc --watch", - "watchWP": "webpack --watch", + "watch": "nodemon -w src/ -e ts src/main.ts", "doc": "typedoc" }, "dependencies": { @@ -19,6 +20,7 @@ "cookie-parser": "^1.4.6", "debug": "^4.3.4", "detect-rpi": "^1.4.0", + "dotenv": "^16.5.0", "express": "5.0.0", "express-ws": "^5.0.2", "hc-sr04": "^0.0.1", diff --git a/server/src/ContainerHelper.ts b/server/src/ContainerHelper.ts index 43876ab..8f41bcc 100644 --- a/server/src/ContainerHelper.ts +++ b/server/src/ContainerHelper.ts @@ -14,51 +14,84 @@ export class ContainerHelper { /** * Measure all containers based on their sensor values */ - static measureContainers(): Promise { + static async measureContainers() { log("Measuring containers..."); - return new Promise(async resolve => { - for (let c of (await Container.find({}))) { - if (c.sensorType != SensorType.NONE) { + for (let c of (await Container.find({}))) { + if (c.sensorType != SensorType.NONE) { - let weight; - try { - weight = await SensorHelper.measureRaw(c); - } catch (e) { - await WebSocketHandler.send(new WebSocketPayload(WebSocketEvent.ERROR, "Ein Sensor hat beim Austarieren einen ungültigen Wert zurückgegeben.
Dies weist auf eine Fehlkonfiguration oder kaputten Sensor hin.
Aus Sicherheitsgründen wurde der Sensor für diesen Behälter deaktiviert.")); - continue; - } - - - // V2: New calculation method - /* - (WEIGHT_VAL - NO_WEIGHT_VAL) / Sensitivitätsfaktor = Gewicht in Gramm - (WEIGHT_VAL - NO_WEIGHT_VAL) / (100g_val /100) ist die Formel zur Berechnung des Gewichts in Gramm nach der Kalibrierung mit einem 100 Gramm Gewicht. - 100g_val /100 gibt den Sensitivitätsfaktor an, der angibt, wie viel sich der Sensorwert ändert, wenn sich das Gewicht um ein Gramm ändert. - Durch die Division von 100g_val durch 100 wird der Sensitivitätsfaktor berechnet, und durch die Division von (WEIGHT_VAL - NO_WEIGHT_VAL) durch den Sensitivitätsfaktor wird das Gewicht in Gramm berechnet. - - Beispiel: - (WEIGHT_VAL - NO_WEIGHT_VAL) / (100g_val /100) = Gewicht in Gramm - (2400 - 2000) / (2450 /100) = 80 Gramm - */ - let newFilled = weight - c.sensorDelta / iTender.sensitivityFactor; - - if (newFilled <= 3 && c.filled != -1) { - c.filled = -1; - // Container is empty! - } else { - // Container > 2 - c.filled = newFilled; - } - - await c.save(); + let weight: number; + try { + weight = await SensorHelper.measureRaw(c); + } catch (e) { + log(e); + await WebSocketHandler.send(new WebSocketPayload(WebSocketEvent.ERROR, "Ein Sensor hat beim Austarieren einen ungültigen Wert zurückgegeben.
Dies weist auf eine Fehlkonfiguration oder kaputten Sensor hin.
Aus Sicherheitsgründen wurde der Sensor für diesen Behälter deaktiviert.")); + continue; } - } - log("Containers measured!"); - resolve(); - await WebSocketHandler.sendContainers(); - }); + // V2: New calculation method + /* + (WEIGHT_VAL - NO_WEIGHT_VAL) / Sensitivitätsfaktor = Gewicht in Gramm + (WEIGHT_VAL - NO_WEIGHT_VAL) / (100g_val /100) ist die Formel zur Berechnung des Gewichts in Gramm nach der Kalibrierung mit einem 100 Gramm Gewicht. + 100g_val /100 gibt den Sensitivitätsfaktor an, der angibt, wie viel sich der Sensorwert ändert, wenn sich das Gewicht um ein Gramm ändert. + Durch die Division von 100g_val durch 100 wird der Sensitivitätsfaktor berechnet, und durch die Division von (WEIGHT_VAL - NO_WEIGHT_VAL) durch den Sensitivitätsfaktor wird das Gewicht in Gramm berechnet. + + Beispiel: + (WEIGHT_VAL - NO_WEIGHT_VAL) / (100g_val /100) = Gewicht in Gramm + (2400 - 2000) / (2450 /100) = 80 Gramm + */ + let newFilled = weight - c.sensorDelta / iTender.sensitivityFactor; + + if (newFilled <= 3 && c.filled != -1) { + c.filled = -1; + // Container is empty! + } else { + // Container > 2 + c.filled = newFilled; + } + + await c.save(); + } + + } + await WebSocketHandler.sendContainers(); + log("Containers measured!"); + + } + + static async tare() { + // Start TARE + + for (let c of await Container.find({})) { + if (c.sensorType != SensorType.NONE) { + c.sensorTare = 0; + await c.save(); + } + } + + let timeouts: Array = []; + + async function measureAndSave() { + await SensorHelper.measureAllRaw(); + for (let c of await Container.find({})) { + if (c.sensorType != SensorType.NONE) { + c.sensorTare += c.rawData; + } + } + } + + timeouts.push(setTimeout(measureAndSave, 500)); + timeouts.push(setTimeout(measureAndSave, 1000)); + timeouts.push(setTimeout(measureAndSave, 2000)); + timeouts.push(setTimeout(measureAndSave, 3000)); + + await Promise.all(timeouts); + for (let c of await Container.find({})) { + if (c.sensorType != SensorType.NONE) { + c.sensorTare = c.sensorTare / 4; + await c.save(); + } + } } } \ No newline at end of file diff --git a/server/src/RequestType.ts b/server/src/RequestType.ts index 977fb66..492f046 100644 --- a/server/src/RequestType.ts +++ b/server/src/RequestType.ts @@ -1,6 +1,6 @@ export enum RequestType { - CONTAINERS = "CONTAINERS", - INGREDIENTS = "INGREDIENTS", + CONTAINERS = "CONTAINERS", // duplicated, try to use from event + INGREDIENTS = "INGREDIENTS", // duplicated, try to use from event STATS = "STATS", JOB = "JOB", STARTFILL = "STARTFILL", diff --git a/server/src/WebSocketEvent.ts b/server/src/WebSocketEvent.ts index 713a988..dea0f76 100644 --- a/server/src/WebSocketEvent.ts +++ b/server/src/WebSocketEvent.ts @@ -1,10 +1,14 @@ +/** + * File for backend + */ + export enum WebSocketEvent { STATUS= "STATUS", DRINKS = "DRINKS", CONTAINERS = "CONTAINERS", + INGREDIENTS = "INGREDIENTS", CONTAINER_UPDATE = "CONTAINER_UPDATE", CONFIG = "CONFIG", - TARE = "TARE", SETUP = "SETUP", REQUEST = "REQUEST", RESPONSE = "RESPONSE", diff --git a/server/src/WebSocketHandler.ts b/server/src/WebSocketHandler.ts index abd7156..8a7a351 100644 --- a/server/src/WebSocketHandler.ts +++ b/server/src/WebSocketHandler.ts @@ -12,29 +12,26 @@ import debug from "debug"; const log = debug("itender:WShandler"); export class WebSocketHandler { - private static _ws: WebSocket|any; + private static _ws: WebSocket | any; static get ws(): WebSocket { return this._ws; } - static set ws(value: WebSocket|any) { + static set ws(value: WebSocket | any) { this._ws = value; } - public static send(payload: WebSocketPayload): Promise { - return new Promise(async (resolve, reject) => { - try { - if (this.ws && this.ws.readyState == 1) { - log("Sending " + payload.event); - - await this.ws.send(payload.toString()); - resolve(); - } - } catch (e) { + public static send(payload: WebSocketPayload) { + try { + if (this.ws && this.ws.readyState == 1) { + log("Sending " + payload.event); + this.ws.send(payload.toString()); } - }); + } catch { + log("There was no open ws client, so no data sent (" + payload.event + ")"); + } } public static answerRequest(type: RequestType, data: any) { @@ -45,70 +42,68 @@ export class WebSocketHandler { } public static sendStatus() { - return new Promise(resolve => { - let payload = new WebSocketPayload(WebSocketEvent.STATUS, {status: iTender.status}); - WebSocketHandler.send(payload).then(resolve); - }); + log("Sending status " + iTender.status); + let payload = new WebSocketPayload(WebSocketEvent.STATUS, {status: iTender.status}); + WebSocketHandler.send(payload); } static sendRunningConfig() { - return new Promise(resolve => { - let payload = new WebSocketPayload(WebSocketEvent.CONFIG, Settings.json); - WebSocketHandler.send(payload).then(resolve); - }); + + let payload = new WebSocketPayload(WebSocketEvent.CONFIG, Settings.json); + WebSocketHandler.send(payload) } - static sendStats() { - return new Promise(async resolve => { + static async sendStats() { - let counts: any[] = []; - let drinks = await Drink.find(); - for (let drink of drinks) { - console.log(drink._id); - console.log( (await Job.countDocuments( )) ); - let count = await Job.countDocuments({drink: drink._id}); - console.log(count); + let counts: any[] = []; + let drinks = await Drink.find(); + for (let drink of drinks) { + console.log(drink._id); + console.log((await Job.countDocuments())); + let count = await Job.countDocuments({drink: drink._id}); + console.log(count); - counts.push([drink, count]); - } + counts.push([drink, count]); + } - counts = counts.sort((a, b) => { - if (a[1] > b[1]) - return -1; - else if (a[1] < b[1]) - return 1; - else - return 0; - }); - - - - let stats = { - "drinks_finished": (await Job.countDocuments({successful: true})), - "drink_most": (counts.length == 0) ? "Keiner" : counts[0][0].name, - "count_ingredients": (await Ingredient.countDocuments()), - "count_cocktails": (await Drink.countDocuments()) - }; - let payload = new WebSocketPayload(WebSocketEvent.RESPONSE, { - type: RequestType.STATS, - data: stats - }); - WebSocketHandler.send(payload).then(resolve); + counts = counts.sort((a, b) => { + if (a[1] > b[1]) + return -1; + else if (a[1] < b[1]) + return 1; + else + return 0; }); + + + let stats = { + "drinks_finished": (await Job.countDocuments({successful: true})), + "drink_most": (counts.length == 0) ? "Keiner" : counts[0][0].name, + "count_ingredients": (await Ingredient.countDocuments()), + "count_cocktails": (await Drink.countDocuments()) + }; + let payload = new WebSocketPayload(WebSocketEvent.RESPONSE, { + type: RequestType.STATS, + data: stats + }); + WebSocketHandler.send(payload) + } - static sendContainers() { - return new Promise(async resolve => { - let payload = new WebSocketPayload(WebSocketEvent.CONTAINERS, (await Container.find().populate("content"))); - WebSocketHandler.send(payload).then(resolve); - }) + public static async sendContainers() { + let payload = new WebSocketPayload(WebSocketEvent.CONTAINERS, (await Container.find().sort({"slot": 1}).populate("content"))); + WebSocketHandler.send(payload); + } + + public static async sendIngredients() { + let payload = new WebSocketPayload(WebSocketEvent.INGREDIENTS, (await Ingredient.find().sort({"name": 1}))); + WebSocketHandler.send(payload); } static sendDrinks() { - return new Promise(async resolve => { - let payload = new WebSocketPayload(WebSocketEvent.DRINKS, iTender.drinks); - WebSocketHandler.send(payload).then(resolve); - }) + let payload = new WebSocketPayload(WebSocketEvent.DRINKS, iTender.drinks); + WebSocketHandler.send(payload); + } } \ No newline at end of file diff --git a/server/src/WebSocketPayload.ts b/server/src/WebSocketPayload.ts index c9d7d66..3a94680 100644 --- a/server/src/WebSocketPayload.ts +++ b/server/src/WebSocketPayload.ts @@ -1,5 +1,4 @@ import {WebSocketEvent} from "./WebSocketEvent"; -import {Buffer} from "buffer"; export class WebSocketPayload { set event(value: WebSocketEvent) { diff --git a/server/src/database/Container.ts b/server/src/database/Container.ts index 793fd8f..52f9a22 100644 --- a/server/src/database/Container.ts +++ b/server/src/database/Container.ts @@ -13,7 +13,7 @@ export const ContainerSchema = new Mongoose.Schema({ content: {type: mongoose.Types.ObjectId, ref: "Ingredient"}, sensorDelta: Number, // V2: Now sensorDelta - Differenz, welche beim Einstellen der Zutat aus Gewicht(Sensor) - Volumen errechnet wird sensorTare: Number, // V2: Now sensorTare - useProxy: { type: Boolean, default: false }, + useProxy: {type: Boolean, default: false}, filled: Number, enabled: {type: Boolean, default: false}, }); diff --git a/server/src/database/IContainer.ts b/server/src/database/IContainer.ts index 092443a..57c52f7 100644 --- a/server/src/database/IContainer.ts +++ b/server/src/database/IContainer.ts @@ -28,6 +28,7 @@ export interface IContainer extends mongoose.Document { rawData: number; pumpPin: number; + // Filled up to? filled: number; enabled: boolean; } \ No newline at end of file diff --git a/server/src/database/IDrink.ts b/server/src/database/IDrink.ts index 6ef1149..77e38b1 100644 --- a/server/src/database/IDrink.ts +++ b/server/src/database/IDrink.ts @@ -6,7 +6,7 @@ export interface IDrink extends mongoose.Document { name: string; // Ingredients - ingredients: { type: IIngredient, amount: Number }[]; + ingredients: { type: IIngredient, amount: number }[]; } \ No newline at end of file diff --git a/server/src/iTender.ts b/server/src/iTender.ts index cd94a1f..8b31389 100644 --- a/server/src/iTender.ts +++ b/server/src/iTender.ts @@ -15,6 +15,7 @@ import Ingredient from "./database/Ingredient"; import {RejectReason} from "./RejectReason"; import axios from "axios"; import {Mixer} from "./Mixer"; +import mongoose from "mongoose"; const log = debug("itender:station"); @@ -97,12 +98,12 @@ export class iTender { * also calculates the amount of time, the drink will need to be done * @param data */ - static onReceiveFill(data: { drink: IDrink, amounts?: { ingredient: String, amount: number }[], amount?: number }): Promise { + static onReceiveFill(data: { drink: IDrink, amounts?: { ingredient: mongoose.Types.ObjectId, amount: number }[], amount?: number }): Promise { return new Promise(async (resolve, reject) => { mixLog("Receiving fill"); let drink = await Drink.findById(data.drink).populate("ingredients.type"); if (!drink) { - reject(); + reject(new Error("Drink could not be found")); return; } @@ -145,7 +146,7 @@ export class iTender { let c = await Container.findOne({$and: [{enabled: true}, {filled: {$gt: x.amount + tolerance}}, {content: x.ingredient}]}); if (!c) { mixLog("Not enough ingredients!"); - reject(RejectReason.NOT_ENOUGH_INGREDIENTS); + reject(new Error(RejectReason.NOT_ENOUGH_INGREDIENTS)); return; } x.container = c; @@ -166,8 +167,6 @@ export class iTender { await Mixer.startFill(job); }); - - } diff --git a/server/src/main.ts b/server/src/main.ts index 797d7c9..e940ece 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -1,4 +1,7 @@ -import {App} from "./App"; +require('dotenv').config(); +console.log("iTender - Node " + process.version); + + import debug from "debug"; import {WebsocketApp} from "./WebsocketApp"; import {Database} from "./database/Database"; @@ -12,14 +15,10 @@ import {ContainerHelper} from "./ContainerHelper"; import {ArduinoProxy} from "./ArduinoProxy"; import path from "path"; import {ErrorHandler, InternalError} from "./ErrorHandler"; -//import {LEDHandler} from "./LEDHandler"; -import {AppMaintenance} from "./maintenance/AppMaintenance"; - const log = debug("itender:server"); -const maintenance = new AppMaintenance(); -const app = new App(); + const wsApp = new WebsocketApp(); @@ -46,22 +45,22 @@ process.on("unhandledRejection", (reason, promise) => { if (Settings.get("arduino_proxy_enabled") as boolean) { try { await ArduinoProxy.connect(); - } catch (e) { + } catch { Settings.set("arduino_proxy_enabled", false); Settings.setupDone = false; log("Force iTender to setup, because proxy not connected!"); } } - //await test(); + /*//await test(); try { - await maintenance.listen(); + //await maintenance.listen(); } catch( e ) { log("Could not start maintenance web app"); console.error(e); - } + }*/ - await app.listen(); + //await app.listen(); await wsApp.listen(); diff --git a/server/src/routes/ws/websocketRoute.ts b/server/src/routes/ws/websocketRoute.ts index fa5679c..eb616ae 100644 --- a/server/src/routes/ws/websocketRoute.ts +++ b/server/src/routes/ws/websocketRoute.ts @@ -22,6 +22,7 @@ import {ErrorHandler, InternalError} from "../../ErrorHandler"; import {NextFunction, Request} from "express"; import WebsocketContainers from "./WebsocketContainers"; import WebsocketContainerUpdate from "./WebsocketContainerUpdate"; +import {ContainerHelper} from "../../ContainerHelper"; const exec = promisify(require('child_process').exec) @@ -38,11 +39,12 @@ export async function handleWebSocket(ws: WebSocket | any, req: Request, next: N await WebSocketHandler.sendRunningConfig(); await WebSocketHandler.sendContainers(); + await WebSocketHandler.sendIngredients(); await WebSocketHandler.sendStatus(); await WebSocketHandler.sendDrinks(); - ws.on('message', async (raw, bool) => { + ws.on('message', async (raw: any, bool: any) => { try { let msg = WebSocketPayload.parseFromBase64Json(raw); // If message is null, close the socket because it could not be decompiled @@ -79,11 +81,9 @@ export async function handleWebSocket(ws: WebSocket | any, req: Request, next: N case WebSocketEvent.SETUP: { if ((msg.data as boolean)) { iTender.setStatus(iTenderStatus.SETUP); - } else { - if (Settings.setupDone) { - iTender.setStatus(iTenderStatus.READY); - await WebSocketHandler.sendRunningConfig(); - } + } else if (Settings.setupDone) { + iTender.setStatus(iTenderStatus.READY); + await WebSocketHandler.sendRunningConfig(); } await WebSocketHandler.sendContainers(); break; @@ -103,11 +103,11 @@ export async function handleWebSocket(ws: WebSocket | any, req: Request, next: N break; } case RequestType.CONTAINERS: { - WebSocketHandler.answerRequest(msg.data["type"] as RequestType, (await Container.find().sort({"slot": 1}).populate("content"))); + await WebSocketHandler.sendContainers(); break; } case RequestType.INGREDIENTS: { - WebSocketHandler.answerRequest(msg.data["type"] as RequestType, (await Ingredient.find().sort({"name": 1}))); + await WebSocketHandler.sendIngredients(); break; } case RequestType.STARTFILL: { @@ -154,8 +154,8 @@ export async function handleWebSocket(ws: WebSocket | any, req: Request, next: N if (conf["arduino_proxy_enabled"]) { try { ArduinoProxy.disconnect(); - } catch (e) { - + } catch { + //ignored } try { await ArduinoProxy.connect(); @@ -189,50 +189,15 @@ export async function handleWebSocket(ws: WebSocket | any, req: Request, next: N } case RequestType.TARE: { let type = msg.data["type"]; - // Start TARE - let success = true; - for (let c of await Container.find({})) { - if (c.sensorType != SensorType.NONE) { - c.sensorTare = 0; - await c.save(); - } + try { + await ContainerHelper.tare() + WebSocketHandler.answerRequest(type, {success: true, msg: "OK"}); + + } catch (e) { + console.error(e); + WebSocketHandler.answerRequest(type, {success: false, msg: "failed" + e}); } - - let timeouts: Array = []; - - async function measureAndSafe() { - try { - await SensorHelper.measureAllRaw(); - for (let c of await Container.find({})) { - if (c.sensorType != SensorType.NONE) { - c.sensorTare += c.rawData; - } - } - } catch (e) { - WebSocketHandler.answerRequest(type, {success: false, msg: e}); - success = false; - for (let t of timeouts) - clearTimeout(t); - } - } - - timeouts.push(setTimeout(measureAndSafe, 500)); - timeouts.push(setTimeout(measureAndSafe, 1000)); - timeouts.push(setTimeout(measureAndSafe, 2000)); - timeouts.push(setTimeout(measureAndSafe, 3000)); - - setTimeout(async () => { - if (success) { - for (let c of await Container.find({})) { - if (c.sensorType != SensorType.NONE) { - c.sensorTare = c.sensorTare / 4; - await c.save(); - } - } - WebSocketHandler.answerRequest(type, {success: true, msg: "OK"}); - } - }, 4000); break; } @@ -269,8 +234,7 @@ export async function handleWebSocket(ws: WebSocket | any, req: Request, next: N case RequestType.INFO: { let nets = os.networkInterfaces(); let net = nets["wlan0"]; - if (!net) - net = nets["wlp0s20f3"]; + net ??= nets["wlp0s20f3"]; let ipAddr: string = ""; if (net) @@ -284,7 +248,7 @@ export async function handleWebSocket(ws: WebSocket | any, req: Request, next: N try { wifi = (await exec("iwgetid")).stdout uptime = (await exec("uptime -p")).stdout; - } catch (e) { + } catch { } wifi = wifi.substring(wifi.indexOf('"') + 1, wifi.length - 2); diff --git a/server/src/web/Setup.ts b/server/src/web/Setup.ts index 6869d04..a851249 100644 --- a/server/src/web/Setup.ts +++ b/server/src/web/Setup.ts @@ -9,7 +9,6 @@ import {IContainer} from "../database/IContainer"; import {SensorType} from "../SensorType"; import {RequestType} from "../RequestType"; - export class Setup { public static arduinoProxyCheckboxes: HTMLInputElement[] = []; @@ -277,7 +276,7 @@ Die Gewichtssensoren werden beim Bestätigen austariert

Zum fortfahren Ta ul = document.createElement("ul"); modal.addContent(ul); - let tareInterval: NodeJS.Timer | number = 0; + let tareInterval: NodeJS.Timeout | number = 0; let btn = document.createElement("button"); btn.classList.add("btn", "btn-primary"); @@ -439,9 +438,9 @@ Mindestens ein Sensor konnte nicht kalibriert werden.
${data.msg}
`; buttonCalibrateSeconds.innerText = "Füllzeit Kalibrieren"; containerDiv.append(buttonCalibrateSeconds); buttonCalibrateSeconds.onclick = () => { - /* WebWebSocketHandler.send(new WebSocketPayload()).then(() => { + /* WebWebSocketHandler.send(new WebSocketPayload()).then(() => { - });*/ + });*/ let modal = new Modal("calibration", "Kalibrierung"); let txt = document.createElement("p"); txt.innerHTML = `Während der Kalibrierung wird der Behälter solange entleert, bis die 100ml erreicht wurden.
diff --git a/server/src/web/main.ts b/server/src/web/main.ts index 4e25479..47d2d49 100644 --- a/server/src/web/main.ts +++ b/server/src/web/main.ts @@ -156,7 +156,7 @@ function setupOnClickEvents() { } -let wsHandler; +let wsHandler: WebWebSocketHandler; function connect(): Promise { return new Promise(resolve => { diff --git a/server/tsconfig.json b/server/tsconfig.json index 9a018ae..86aa8c8 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -24,6 +24,7 @@ "./src/" ], "exclude": [ - "../node_modules" + "../node_modules", + "./src/web" ] } \ No newline at end of file diff --git a/server/webpack.config.js b/server/webpack.config.js_disabled similarity index 100% rename from server/webpack.config.js rename to server/webpack.config.js_disabled diff --git a/server/yarn.lock b/server/yarn.lock index adcaa62..f4fc62b 100644 --- a/server/yarn.lock +++ b/server/yarn.lock @@ -1008,6 +1008,11 @@ doctypes@^1.1.0: resolved "https://registry.yarnpkg.com/doctypes/-/doctypes-1.1.0.tgz#ea80b106a87538774e8a3a4a5afe293de489e0a9" integrity sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ== +dotenv@^16.5.0: + version "16.5.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.5.0.tgz#092b49f25f808f020050051d1ff258e404c78692" + integrity sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg== + dunder-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" diff --git a/web/index.html b/web/index.html index 5b325c5..23a8f6d 100644 --- a/web/index.html +++ b/web/index.html @@ -5,8 +5,8 @@ iTender - - + + diff --git a/web/package.json b/web/package.json index 485574b..72fd810 100644 --- a/web/package.json +++ b/web/package.json @@ -1,8 +1,11 @@ { - "name": "my-vite-app", + "name": "itender-web", "private": true, - "version": "0.0.0", + "version": "0.0.1", "type": "module", + "author": "Tobias Hopp ", + "license": "UNLICENSED", + "proxy": "http://localhost:3000", "scripts": { "dev": "vite", "build": "tsc -b && vite build", @@ -10,6 +13,11 @@ "preview": "vite preview" }, "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@fontsource/roboto": "^5.2.5", + "@mui/icons-material": "^7.1.0", + "@mui/material": "^7.1.0", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router": "^7.5.3" @@ -17,8 +25,8 @@ "devDependencies": { "@eslint/js": "^9.22.0", "@types/node": "^22.15.3", - "@types/react": "^19.0.10", - "@types/react-dom": "^19.0.4", + "@types/react": "^19.1.2", + "@types/react-dom": "^19.1.3", "@types/react-router": "^5.1.20", "@vitejs/plugin-react": "^4.3.4", "eslint": "^9.22.0", diff --git a/web/src/App.tsx b/web/src/App.tsx index c6decd0..b9e36e9 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,19 +1,78 @@ -import {Route, Routes} from 'react-router'; -import './App.css' -import {BrowserRouter} from "react-router"; +import {Route, Routes, useNavigate} from 'react-router'; import {Layout} from "./components/Layout.tsx"; +import {PaneMain} from "./routes/PaneMain.tsx"; +import {PaneMenu} from "./routes/PaneMenu.tsx"; +import {WebSocketHandler} from "./utils/WebSocketHandler.ts"; +import {useEffect, useRef, useState} from "react"; +import {iTenderStatus} from "./interfaces/iTenderStatus.ts"; +import {Pane} from "./interfaces/Pane.ts"; +import {PaneStartup} from "./routes/PaneStartup.tsx"; +import {WebSocketEvent} from "./interfaces/WebSocketEvent.ts"; function App() { + const navigate = useNavigate(); + const [currentStatus, setCurrentStatus] = useState(iTenderStatus.CONNECTING); + const [currentPane, setCurrentPane] = useState(Pane.NONE); + const cleanupHook = useRef void)>(undefined); - return ( - + const connect = () => { + console.log("[App.tsx] Connecting..."); + WebSocketHandler.connect(() => { + // now open + if (cleanupHook.current) + cleanupHook.current(); + cleanupHook.current = WebSocketHandler.registerForEvent(WebSocketEvent.STATUS, (payload) => { + setCurrentStatus(payload.data.status); + }); + + /*console.log("setting pane main"); + setCurrentPane(Pane.MAIN);*/ + }, (wasClean: boolean) => { + setCurrentStatus(iTenderStatus.CONNECTING); + if (wasClean) { + // loop??????????????????? + console.log("[App] WS was closed clean, trying to reconnect in 1s"); + setTimeout(connect, 1000); + } + }); + } + + useEffect(() => { + connect(); + + }, []); + + useEffect(() => { + switch (currentStatus) { + case iTenderStatus.CONNECTING: + setCurrentPane(Pane.NONE); + break; + case iTenderStatus.READY: + setCurrentPane(Pane.MAIN); + break; + } + }, [currentStatus]); + + useEffect(() => { + console.log("Changing pane", currentPane); + if (currentPane == Pane.NONE) { + setCurrentStatus(iTenderStatus.STARTING); + navigate("/"); + } else + navigate("panes/" + currentPane); + }, [currentPane]); + + + return ( - }> - - - - - ) + }> + connect()}/>}/> + + }> + }/> + }/> + + ) } export default App diff --git a/web/src/assets/stylesheets/main.css b/web/src/assets/stylesheets/main.css index 150d648..88c434e 100644 --- a/web/src/assets/stylesheets/main.css +++ b/web/src/assets/stylesheets/main.css @@ -1,8 +1,8 @@ -#main::-webkit-scrollbar { - display: none; -} - #main { + #main::-webkit-scrollbar { + display: none; + } + display: grid; grid-template-columns: repeat(3, calc(90% / 3)); grid-template-rows: repeat(2, calc(90% / 2)); @@ -77,60 +77,63 @@ body { margin: auto auto 2%; color: white; font-size: 1.5em; -} -.water::before { - content: ""; - width: 200%; - height: 200%; - background-color: #ececec; - position: absolute; - top: -90%; - left: -50%; - border-radius: 40%; - animation: animWater 7s linear infinite, animFillIn var(--fillTime) linear forwards; -} -.water::after { - content: ""; - width: 204%; - height: 204%; - background-color: #ececec80; - position: absolute; - top: -100%; - left: -52%; - border-radius: 40%; - animation: animWater 7s linear infinite, animFillIn var(--fillTime) linear forwards; - animation-delay: 0.4s; -} + .water::before { + content: ""; + width: 200%; + height: 200%; + background-color: #ececec; + position: absolute; + top: -90%; + left: -50%; + border-radius: 40%; + animation: animWater 7s linear infinite, animFillIn var(--fillTime) linear forwards; + } -.waterCancel { - background-color: red; - transition: background-color 1s; -} -.waterFinished { - background-color: green; - transition: background-color 2s; -} - -@keyframes animFillIn { - 0% { + .water::after { + content: ""; + width: 204%; + height: 204%; + background-color: #ececec80; + position: absolute; top: -100%; + left: -52%; + border-radius: 40%; + animation: animWater 7s linear infinite, animFillIn var(--fillTime) linear forwards; + animation-delay: 0.4s; } - 100% { - top: -188%; + + .waterCancel { + background-color: red; + transition: background-color 1s; + } + + .waterFinished { + background-color: green; + transition: background-color 2s; + } + + @keyframes animFillIn { + 0% { + top: -100%; + } + 100% { + top: -188%; + } + } + + @keyframes animWater { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } } } -@keyframes animWater { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} #main_fillTxt { margin-bottom: 1.3%; diff --git a/web/src/assets/stylesheets/style.css b/web/src/assets/stylesheets/style.css index 392e7c3..dd86f50 100644 --- a/web/src/assets/stylesheets/style.css +++ b/web/src/assets/stylesheets/style.css @@ -101,6 +101,7 @@ h2 { #overlay { #top { display: grid; + grid-template-columns: calc(100%/3) calc(100%/3) calc(100%/3); grid-gap: 0; position: fixed; top: 0; @@ -114,27 +115,31 @@ h2 { padding-top: 5px; background-color: #167FCC; box-shadow: inset 11px 45px 50px 3px rgba(181, 15, 15, 0.66); + align-items: center; #title { text-align: center; margin-top: 0.6%; grid-row: 4; + grid-row-end: 4; } #left { font-size: 0.5em; - grid-row: 4; text-align: left; padding-top: 1.6%; padding-left: 10px; + grid-row: 4; + grid-row-end: 4; } #right { - grid-row: 4; text-align: right; font-size: 0.8em; padding-top: 1.2%; padding-right: 10px; + grid-row: 4; + grid-row-end: 4; } } @@ -213,7 +218,7 @@ h2 { top: 50px; left: 0; right: 0; - height:calc(100vh - 100px); + height: calc(100vh - 100px); color: white; background-color: #0E1F31; @@ -282,24 +287,27 @@ h2 { position: relative; display: inline-block; overflow: visible; -} + .tooltiptext { + opacity: 0; -.tooltip .tooltiptext { - opacity: 0; + background-color: #214B74; + color: #FFFFFF; + text-align: center; + border-radius: 9px; + padding: 5px 0; + /* Position the tooltip */ + position: absolute; + z-index: 1; + bottom: -55%; + left: 50%; + margin-left: -80px; - background-color: #214B74; - color: #FFFFFF; - text-align: center; - border-radius: 9px; - padding: 5px 0; - /* Position the tooltip */ - position: absolute; - z-index: 1; - bottom: -55%; - left: 50%; - margin-left: -80px; + } + &:hover .tooltiptext { + animation: blendIn 0.5s ease forwards; + } } @@ -313,6 +321,3 @@ h2 { } -.tooltip:hover .tooltiptext { - animation: blendIn 0.5s ease forwards; -} \ No newline at end of file diff --git a/web/src/components/Layout.tsx b/web/src/components/Layout.tsx index 2814dd4..3ed053f 100644 --- a/web/src/components/Layout.tsx +++ b/web/src/components/Layout.tsx @@ -1,8 +1,72 @@ import {Outlet} from "react-router"; +import {useEffect, useState} from "react"; +import {WebSocketHandler} from "../utils/WebSocketHandler.ts"; +import {WebSocketEvent} from "../interfaces/WebSocketEvent.ts"; +import IContainer from "../interfaces/db/IContainer.ts"; -export function Layout () { +export function Layout() { + const [currentTime, setCurrentTime] = useState("00:00"); + const [status, setStatus] = useState("Booting up..."); + const [containers, setContainers] = useState([]); + + useEffect(() => { + console.log("hooks applied") + let currentTimeInterval = setInterval(() => { + let date = new Date(); + setCurrentTime((date.getHours() < 10 ? "0" + date.getHours() : date.getHours()) + ":" + (date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes())); + }, 1000); + const statusHook = WebSocketHandler.registerForEvent(WebSocketEvent.STATUS, (payload) => { + console.log(payload); + setStatus(payload.data.status); + }); + const containerHook = WebSocketHandler.registerForEvent(WebSocketEvent.CONTAINERS, (payload) => { + setContainers(payload.data); + }); + + return () => { + clearInterval(currentTimeInterval); + statusHook(); + containerHook(); + } + }, []); + return ( - + <> +
+
+ + Status: + {status} + + iTender + {currentTime} +
+
+
+ +
+ +
+ {containers.map((container) => ( +
+
+ {!container.content ? "-" : Math.round(container.filled * 100 / container.volume)}% + {container.content ? container.content.name : "-"} {Math.ceil(container.filled)}ml +
+ +
+ ))} + {/*
+ 3% +
*/} +
+
+
+
+ +
+ ) } \ No newline at end of file diff --git a/web/src/index.css b/web/src/index.css deleted file mode 100644 index 08a3ac9..0000000 --- a/web/src/index.css +++ /dev/null @@ -1,68 +0,0 @@ -:root { - font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/web/src/interfaces/Pane.ts b/web/src/interfaces/Pane.ts new file mode 100644 index 0000000..3da71bf --- /dev/null +++ b/web/src/interfaces/Pane.ts @@ -0,0 +1,8 @@ +export enum Pane { + MAIN= "paneMain", + MENU = "paneMenu", + SETTINGS = "paneSettings", + SETUP = "paneSetup", + NONE = "paneNone", + CONTAINERS = "paneContainers", +} \ No newline at end of file diff --git a/web/src/interfaces/RequestType.ts b/web/src/interfaces/RequestType.ts new file mode 100644 index 0000000..52e1004 --- /dev/null +++ b/web/src/interfaces/RequestType.ts @@ -0,0 +1,40 @@ +import {IJob} from "./db/IJob.ts"; + +export enum RequestType { + STATS = "STATS", + JOB = "JOB", + STARTFILL = "STARTFILL", + STOPFILL = "STOPFILL", + DOWNLOAD_DRINKS = "DOWNLOAD_DRINKS", + TARE = "TARE", + CHECK = "CHECK", + UPDATE = "UPDATE", + INFO = "INFO", + CLEAR_DB = "CLEAR_DB", + SHUTDOWN = "SHUTDOWN", +} + +// Define a mapping from RequestType to the expected data type of the RESPONSE payload +// You need to define these types based on the data your backend sends back for each request type +export interface RequestTypeResponseMap { + [RequestType.STATS]: any; + [RequestType.JOB]: IJob; + [RequestType.STARTFILL]: { success: boolean, job: IJob }; + [RequestType.STOPFILL]: any; + [RequestType.DOWNLOAD_DRINKS]: string; + [RequestType.TARE]: { success: boolean, msg: string }; + [RequestType.CHECK]: { success: boolean, msg: string }; + [RequestType.UPDATE]: boolean; + [RequestType.INFO]: { + internet: boolean, + ip: string, + network: string, + uptime: string, + version: string, + author: string, + contact: string + }; + [RequestType.CLEAR_DB]: true; + [RequestType.SHUTDOWN]: undefined; + // Add other request types and their response data types as needed +} diff --git a/web/src/interfaces/WebSocketEvent.ts b/web/src/interfaces/WebSocketEvent.ts new file mode 100644 index 0000000..23a7962 --- /dev/null +++ b/web/src/interfaces/WebSocketEvent.ts @@ -0,0 +1,35 @@ +/** + * File for web + */ +import {iTenderStatus} from "./iTenderStatus.ts"; +import {IDrink} from "./db/IDrink.ts"; +import IContainer from "./db/IContainer.ts"; + +export enum WebSocketEvent { + STATUS= "STATUS", + DRINKS = "DRINKS", + CONTAINERS = "CONTAINERS", + INGREDIENTS = "INGREDIENTS", + CONTAINER_UPDATE = "CONTAINER_UPDATE", + CONFIG = "CONFIG", + SETUP = "SETUP", + REQUEST = "REQUEST", + RESPONSE = "RESPONSE", + CANCEL = "CANCEL", + ERROR = "ERROR", +} + +export interface WebSocketEventPayloadMap { + [WebSocketEvent.STATUS]: {status: iTenderStatus}; // Example: STATUS event carries a string status message + [WebSocketEvent.DRINKS]: IDrink[]; // Example: DRINKS event carries an array of drinks + [WebSocketEvent.CONTAINERS]: IContainer[]; // Example: CONTAINERS event carries an array of containers + [WebSocketEvent.CONTAINER_UPDATE]: any; // Example: CONTAINER_UPDATE carries a single container update + [WebSocketEvent.CONFIG]: any; // Example: CONFIG carries configuration data + [WebSocketEvent.INGREDIENTS]: any; // Example: TARE carries tare data + [WebSocketEvent.SETUP]: any; // Example: SETUP carries setup data + [WebSocketEvent.REQUEST]: any; // REQUEST payload structure (type and data) + [WebSocketEvent.RESPONSE]: any; // RESPONSE payload structure (type and data) + [WebSocketEvent.CANCEL]: any; // CANCEL payload + [WebSocketEvent.ERROR]: string; // ERROR event carries an error message + // Add other events and their types as needed +} \ No newline at end of file diff --git a/web/src/interfaces/WebSocketPayload.ts b/web/src/interfaces/WebSocketPayload.ts new file mode 100644 index 0000000..654e21b --- /dev/null +++ b/web/src/interfaces/WebSocketPayload.ts @@ -0,0 +1,55 @@ +import {WebSocketEvent} from "./WebSocketEvent.ts"; + +export class WebSocketPayload { + set event(value: WebSocketEvent) { + this._event = value; + } + + + set data(value: T) { + this._data = value; + } + + private _event: WebSocketEvent; + private _data: T; + + + get event(): WebSocketEvent { + return this._event; + } + + + + get data(): T { + return this._data; + } + + constructor(event: WebSocketEvent, data?: any) { + this._event = event; + this._data = data; + } + + public static parseFromBase64Json(json: any): WebSocketPayload | null { + //json = (typeof window != 'undefined') ? atob(json).toString() : Buffer.from(json, "base64").toString("utf-8"); + json = atob(json); + let rawPayload: { event: string, data: any }; + try { + rawPayload = JSON.parse(json); + } catch { + return null; + } + let wsEvent = WebSocketEvent[rawPayload.event]; + + return new WebSocketPayload(wsEvent, rawPayload.data); + } + + /** + * Returns the payload as base64 encoded json string + */ + public toString(): string { + let json = JSON.stringify({"event": this._event, 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/web/src/interfaces/db/IContainer.ts b/web/src/interfaces/db/IContainer.ts new file mode 100644 index 0000000..16c3091 --- /dev/null +++ b/web/src/interfaces/db/IContainer.ts @@ -0,0 +1,33 @@ +import IIngredient from "./IIngredient.ts"; +import {ISensorType} from "./ISensorType.ts"; + +export default interface IContainer { + _id: string, + slot: number; + content: IIngredient | undefined; + volume: number; + sensorDelta: number; + sensorTare: number; + + // Sensor Type + sensorType: ISensorType; + + /** + * HX711 DATA-Pin + */ + sensorPin1: number; + /** + * HX711 CLOCK-Pin + */ + sensorPin2: number; + + /** + * Is the arduino used as proxy? + */ + useProxy: boolean + rawData: number; + pumpPin: number; + + filled: number; + enabled: boolean; +} \ No newline at end of file diff --git a/web/src/interfaces/db/IDrink.ts b/web/src/interfaces/db/IDrink.ts new file mode 100644 index 0000000..4ed3024 --- /dev/null +++ b/web/src/interfaces/db/IDrink.ts @@ -0,0 +1,15 @@ +import IIngredient from "./IIngredient.ts"; + +/** + * IDrink interface for web + * + */ +export interface IDrink { + // Name for the Drink + name: string; + + // Ingredients + ingredients: { type: IIngredient, amount: number }[]; + + +} \ No newline at end of file diff --git a/web/src/interfaces/db/IIngredient.ts b/web/src/interfaces/db/IIngredient.ts new file mode 100644 index 0000000..abd2830 --- /dev/null +++ b/web/src/interfaces/db/IIngredient.ts @@ -0,0 +1,14 @@ +/** + * IIngredient interface for web + * + */ + +export default interface IIngredient { + _id: string; + + // Name of the ingredient + name: string; + + // Category of the ingredient + category: Category; +} \ No newline at end of file diff --git a/web/src/interfaces/db/IJob.ts b/web/src/interfaces/db/IJob.ts new file mode 100644 index 0000000..5d1253b --- /dev/null +++ b/web/src/interfaces/db/IJob.ts @@ -0,0 +1,13 @@ +import {IDrink} from "./IDrink.ts"; +import IIngredient from "./IIngredient.ts"; +import IContainer from "./IContainer.ts"; + +export interface IJob { + drink: IDrink; + amounts: { ingredient: IIngredient, amount: number, container: IContainer }[]; + completeAmount: number + startedAt: Date; + endAt: Date; + estimatedTime : number; + successful: boolean; +} \ No newline at end of file diff --git a/web/src/interfaces/db/ISensorType.ts b/web/src/interfaces/db/ISensorType.ts new file mode 100644 index 0000000..7162862 --- /dev/null +++ b/web/src/interfaces/db/ISensorType.ts @@ -0,0 +1,5 @@ +export enum ISensorType { + NONE="NONE", + ULTRASOUND="ULTRASOUND", + LOADCELL="LOADCELL" +} \ No newline at end of file diff --git a/web/src/interfaces/iTenderStatus.ts b/web/src/interfaces/iTenderStatus.ts new file mode 100644 index 0000000..cba2362 --- /dev/null +++ b/web/src/interfaces/iTenderStatus.ts @@ -0,0 +1,19 @@ +export enum iTenderStatus { + CONNECTING = "CONNECTING", + // Machine is going to start + STARTING= "STARTING", + // Machine is ready + READY = "READY", + // Machine is filling your drink and destroying your leberwurst + FILLING = "FILLING", + // Drinks will be refreshed + REFRESHING = "REFRESHING", + // Drinks will be calculated (check containers and which drinks can be done) + CALCULATING = "CALCULATING", + // Download drinks from the world wide web + DOWNLOADING = "DOWNLOADING", + // Device is in setup mode for first setup + SETUP = "SETUP", + // An error happened; Oh no :( + ERROR = "ERROR" +} \ No newline at end of file diff --git a/web/src/main.tsx b/web/src/main.tsx index 2f92060..15272fe 100644 --- a/web/src/main.tsx +++ b/web/src/main.tsx @@ -1,11 +1,14 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './index.css' +import {StrictMode} from 'react' +import {createRoot} from 'react-dom/client' import App from './App.tsx' +import './assets/stylesheets/style.css' +import {BrowserRouter} from "react-router"; createRoot(document.getElementById('root')!).render( - - - , + + + + + , ) diff --git a/web/src/routes/PaneMain.tsx b/web/src/routes/PaneMain.tsx new file mode 100644 index 0000000..9de726a --- /dev/null +++ b/web/src/routes/PaneMain.tsx @@ -0,0 +1,16 @@ +import { useNavigate } from "react-router"; +import {useEffect} from "react"; + + +export function PaneMain () { + + const navigate = useNavigate(); + + useEffect(() => { + + }, []); + + return ( +
hallo
+ ) +} \ No newline at end of file diff --git a/web/src/routes/PaneMenu.tsx b/web/src/routes/PaneMenu.tsx new file mode 100644 index 0000000..d27feae --- /dev/null +++ b/web/src/routes/PaneMenu.tsx @@ -0,0 +1,5 @@ +export function PaneMenu () { + return ( +
hallo ich bin menu
+ ) +} \ No newline at end of file diff --git a/web/src/routes/PaneStartup.tsx b/web/src/routes/PaneStartup.tsx new file mode 100644 index 0000000..f2e9da5 --- /dev/null +++ b/web/src/routes/PaneStartup.tsx @@ -0,0 +1,39 @@ +import {Button, Typography} from "@mui/material"; +import {useEffect, useState} from "react"; + +interface StartupProps { + onRetryClick: () => void; +} + +export function PaneStartup({onRetryClick}: Readonly) { + + const [connectionIssue, setConnectionIssue] = useState(false); + + const init = () => { + setConnectionIssue(false); + return setTimeout(() => { + setConnectionIssue(true); + }, 10000); + } + + useEffect(() => { + let t = init(); + return () => clearTimeout(t); + }, []); + + return ( + <> + +
+ Willkommen + {connectionIssue ? "Verbindungsfehler" : "iTender wird gestartet..."} + {connectionIssue && } +
+ + ) +} \ No newline at end of file diff --git a/web/src/utils/WebSocketHandler.ts b/web/src/utils/WebSocketHandler.ts new file mode 100644 index 0000000..dc3c11f --- /dev/null +++ b/web/src/utils/WebSocketHandler.ts @@ -0,0 +1,295 @@ +import {WebSocketEvent, WebSocketEventPayloadMap} from "../interfaces/WebSocketEvent.ts"; +import {WebSocketPayload} from "../interfaces/WebSocketPayload.ts"; +import {RequestType, RequestTypeResponseMap} from "../interfaces/RequestType.ts"; + +// Cleanup function +type cleanup = () => void; + +export class WebSocketHandler { + public static isConnected: boolean = false; + private static socket: WebSocket; + private static readonly url = (window.location.protocol == "http:" ? "ws://" : "wss://") + window.location.hostname + ":3005"; + private static eventRegister: { event: WebSocketEvent, fn: (payload: WebSocketPayload) => void }[] = []; + + public static connect(onConnect: (() => void), onDisconnect: (wasClean: boolean) => void) { + if (this.socket) { + try { + this.socket.close(0, "reconnect"); + } catch { + // ignored + } + } + + console.log("[WS] Connecting..."); + WebSocketHandler.socket = new WebSocket(WebSocketHandler.url); + WebSocketHandler.socket.onopen = (x) => { + this.isConnected = true; + this.onOpen(x); + onConnect(); + }; + WebSocketHandler.socket.onclose = (ev) => { + this.isConnected = false; + this.onClose(); + onDisconnect(ev.wasClean); + } + WebSocketHandler.socket.onerror = this.onError; + WebSocketHandler.socket.onmessage = this.onMessage; + + } + + + /** + * Registers for an event, returns a cleanup function + * The payload type is inferred from the WebSocketEventPayloadMap + * @param event The WebSocketEvent to register for + * @param fn The callback function to handle the event payload + * @return Cleanup-Function + */ + public static registerForEvent( + event: E, + fn: (payload: WebSocketPayload) => void + ): cleanup { + // The type assertion 'as any' is necessary here because the eventRegister array + // is typed to accept WebSocketPayload for flexibility in the onMessage handler. + // TypeScript cannot statically guarantee that the payload type for a specific event + // matches the generic constraint E at this point, but we handle the type safety + // at the call site of registerForEvent. + let obj = {event: event, fn: fn as any}; + WebSocketHandler.eventRegister.push(obj); + + /** + * cleanup function + */ + return () => { + WebSocketHandler.eventRegister = WebSocketHandler.eventRegister.filter((e) => e != obj); + } + } + + /** + * Request and response + * The response payload data type is inferred from the RequestTypeResponseMap + * @return Promise> A promise that resolves with the response payload data + * @param type The RequestType + * @param content The request content + * @param timeout Time in seconds for timeout + */ + public static request( + type: T, + content: object | any = null, + timeout: number = 30 + ): Promise { // Use the mapped response type here + console.log("[WS] Request to " + type) + return new Promise((resolve, reject) => { + let cancel = setTimeout(() => { + // Check if cleanup is defined before calling it + if (cleanup) + cleanup(); + reject(new Error("timeout")); + }, timeout * 1000); + // Use registerForEvent with the specific RESPONSE event type + let cleanup = WebSocketHandler.registerForEvent(WebSocketEvent.RESPONSE, (payload) => { + // The payload.data here is typed as WebSocketEventPayloadMap[WebSocketEvent.RESPONSE] (which is 'any' in our current map) + // We rely on the check `(payload.data["type"] as RequestType) == type` to match the request type + // and then assert the data type based on the RequestTypeResponseMap. + if (payload.data && (payload.data["type"] as RequestType) == type) { + clearTimeout(cancel); + // Check if cleanup is defined before calling it + if (cleanup) + cleanup(); + // Assert the data type based on the RequestTypeResponseMap + resolve(payload.data.data as RequestTypeResponseMap[T]); // Resolve with the actual data part of the response + } + }); + WebSocketHandler.send(new WebSocketPayload(WebSocketEvent.REQUEST, { + type: type, + data: content + })).catch(reject); + }); + } + + + public static async send(payload: WebSocketPayload): Promise { + console.log("[WS] Sending " + payload.event + " Event", payload); + if (this.socket && this.socket.readyState == 1) { + this.socket.send(payload.toString()); + } else { + console.warn("[WS] No socket or readyState is not 1"); + } + } + + private static checkConnection(): Promise { + return new Promise(async resolve => { + const xhr = new XMLHttpRequest(); + xhr.open("GET", '/status', true); + + //Send the proper header information along with the request + xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + + xhr.onreadystatechange = () => { // Call a function when the state changes. + if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { + resolve(true); + } else if (xhr.readyState == XMLHttpRequest.DONE) { + resolve(false); + } + } + + try { + xhr.send(); + } catch (e) { + resolve(false); + } + + }); + } + + private static onMessage(msgEvent: MessageEvent) { + let payload = WebSocketPayload.parseFromBase64Json(msgEvent.data); + if (!payload) { + console.log("[WS] Could not parse message: ", msgEvent); + return; + } + console.log("[WS] Received " + payload.event + " Event", payload); + + for (let evReg of WebSocketHandler.eventRegister) { + if (evReg.event == payload.event) + evReg.fn(payload); + } + + /*switch (payload.event) { + case WebSocketEvent.CONFIG: { + Setup.onConfigUpdate(payload); + break; + } + + case WebSocketEvent.DRINKS: { + WebHandler.onDrinkUpdate(payload); + break; + } + + case WebSocketEvent.ERROR: { + /!* let modal = new Modal("error", "Aww crap!"); + let txt = document.createElement("p"); + txt.innerHTML = payload.data; + modal.addContent(txt); + modal.addContent(document.createElement("br")); + modal.addButton(ButtonType.SECONDARY, "Schließen", () => modal.close()); + modal.open(); + Settings.inUpdate = false;*!/ + console.error(payload); + break; + } + + // Incoming WebSocketStatus + 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("setup"); + Modal.close("fill"); + Modal.close("download"); + if (WebHandler.currentPane != Pane.MENU) + WebHandler.openPane(Pane.MAIN); + (document.getElementById("menuBtn") as HTMLButtonElement).disabled = false; + break; + } + case iTenderStatus.STARTING: { + let modal = new Modal("start", "Willkommen!"); + let txt = document.createElement("p"); + txt.innerHTML = `Einen Augenblick bitte
iTender startet...`; + modal.addContent(txt); + modal.loader = true; + modal.open(); + break; + } + case iTenderStatus.DOWNLOADING: { + let modal = new Modal("download", "Aktualisieren"); + let txt = document.createElement("p"); + txt.innerHTML = `Einen Augenblick bitte
iTender synchronisiert die Datenbank mit der Cloud...`; + modal.addContent(txt); + modal.loader = true; + modal.open(); + setTimeout(() => { + if (txt) { + txt.innerHTML = txt.innerHTML + "

Der Vorgang dauert länger als gewöhnlich.
Überprüfe deine Internetverbindung!" + } + }, 1000 * 15) + break; + } + case iTenderStatus.SETUP: { + Modal.close("start"); + Setup.openSetup(); + break; + } + case iTenderStatus.FILLING: { + Fill.onFillEvent(payload); + break; + } + default: { + console.log("Unknown to handle " + status); + } + } + break; + } + + }*/ + } + + private static onOpen(event: Event) { + console.log("[WS] Connected", event); + + } + + private static onClose() { + console.error("[WS] Closed!"); + + /*if (event.wasClean) { + let modal = new Modal("socketClosed", "Sitzung beendet!"); + let txt = document.createElement("p"); + txt.innerHTML = `Diese Sitzung wurde beendet, da der iTender nun an einem anderen Gerät bzw. an dem Hauptgerät gesteuert wird.

`; + modal.addContent(txt); + modal.addButton(ButtonType.PRIMARY, "Sitzung wiederherstellen", () => { + window.location.reload(); + }); + modal.open(); + } else { + setInterval(async () => { + if ((await WebWebSocketHandler.checkConnection())) + window.location.reload(); + }, 2000); + + if (Settings.inUpdate) + return; + + let modal = new Modal("socketClosed", "Verbindungsproblem!"); + let txt = document.createElement("p"); + txt.innerHTML = `Die Benutzeroberfläche hat die Verbindung mit dem Gerät verloren.
Die Verbindung wird wiederhergestellt...
`; + modal.addContent(txt); + modal.loader = true; + modal.open(); + } + + /!* let connectionElement = document.getElementById("right"); + if (connectionElement) { + connectionElement.innerText = "Getrennt"; + connectionElement.style.color = "red"; + }*!/*/ + } + + private static onError(event: any) { + 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(); + } + + +} \ No newline at end of file diff --git a/web/tsconfig.app.json b/web/tsconfig.app.json index 358ca9b..a67d962 100644 --- a/web/tsconfig.app.json +++ b/web/tsconfig.app.json @@ -17,8 +17,8 @@ /* Linting */ "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, + "noUnusedLocals": false, + "noUnusedParameters": false, "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true }, diff --git a/web/tsconfig.node.json b/web/tsconfig.node.json index d70c1ce..96c8ea6 100644 --- a/web/tsconfig.node.json +++ b/web/tsconfig.node.json @@ -16,8 +16,8 @@ /* Linting */ "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, + "noUnusedLocals": false, + "noUnusedParameters": false, "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true }, diff --git a/web/yarn.lock b/web/yarn.lock index 6f9038f..d6c9473 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -10,7 +10,7 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" -"@babel/code-frame@^7.26.2": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.26.2": version "7.26.2" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz" integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== @@ -24,7 +24,7 @@ resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz" integrity sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ== -"@babel/core@^7.26.10": +"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.26.10": version "7.26.10" resolved "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz" integrity sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ== @@ -67,7 +67,7 @@ lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-module-imports@^7.25.9": +"@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.25.9": version "7.25.9" resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz" integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw== @@ -133,6 +133,11 @@ dependencies: "@babel/helper-plugin-utils" "^7.25.9" +"@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.27.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz" + integrity sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog== + "@babel/template@^7.26.9", "@babel/template@^7.27.0": version "7.27.0" resolved "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz" @@ -163,125 +168,112 @@ "@babel/helper-string-parser" "^7.25.9" "@babel/helper-validator-identifier" "^7.25.9" -"@esbuild/aix-ppc64@0.25.3": - version "0.25.3" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz#014180d9a149cffd95aaeead37179433f5ea5437" - integrity sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ== +"@emotion/babel-plugin@^11.13.5": + version "11.13.5" + resolved "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz" + integrity sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ== + dependencies: + "@babel/helper-module-imports" "^7.16.7" + "@babel/runtime" "^7.18.3" + "@emotion/hash" "^0.9.2" + "@emotion/memoize" "^0.9.0" + "@emotion/serialize" "^1.3.3" + babel-plugin-macros "^3.1.0" + convert-source-map "^1.5.0" + escape-string-regexp "^4.0.0" + find-root "^1.1.0" + source-map "^0.5.7" + stylis "4.2.0" -"@esbuild/android-arm64@0.25.3": - version "0.25.3" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.3.tgz#649e47e04ddb24a27dc05c395724bc5f4c55cbfe" - integrity sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ== +"@emotion/cache@^11.13.5", "@emotion/cache@^11.14.0": + version "11.14.0" + resolved "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz" + integrity sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA== + dependencies: + "@emotion/memoize" "^0.9.0" + "@emotion/sheet" "^1.4.0" + "@emotion/utils" "^1.4.2" + "@emotion/weak-memoize" "^0.4.0" + stylis "4.2.0" -"@esbuild/android-arm@0.25.3": - version "0.25.3" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.3.tgz#8a0f719c8dc28a4a6567ef7328c36ea85f568ff4" - integrity sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A== +"@emotion/hash@^0.9.2": + version "0.9.2" + resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz" + integrity sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g== -"@esbuild/android-x64@0.25.3": - version "0.25.3" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.3.tgz#e2ab182d1fd06da9bef0784a13c28a7602d78009" - integrity sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ== +"@emotion/is-prop-valid@^1.3.0": + version "1.3.1" + resolved "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz" + integrity sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw== + dependencies: + "@emotion/memoize" "^0.9.0" -"@esbuild/darwin-arm64@0.25.3": - version "0.25.3" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.3.tgz#c7f3166fcece4d158a73dcfe71b2672ca0b1668b" - integrity sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w== +"@emotion/memoize@^0.9.0": + version "0.9.0" + resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz" + integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ== -"@esbuild/darwin-x64@0.25.3": - version "0.25.3" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.3.tgz#d8c5342ec1a4bf4b1915643dfe031ba4b173a87a" - integrity sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A== +"@emotion/react@^11.0.0-rc.0", "@emotion/react@^11.14.0", "@emotion/react@^11.4.1", "@emotion/react@^11.5.0": + version "11.14.0" + resolved "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz" + integrity sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA== + dependencies: + "@babel/runtime" "^7.18.3" + "@emotion/babel-plugin" "^11.13.5" + "@emotion/cache" "^11.14.0" + "@emotion/serialize" "^1.3.3" + "@emotion/use-insertion-effect-with-fallbacks" "^1.2.0" + "@emotion/utils" "^1.4.2" + "@emotion/weak-memoize" "^0.4.0" + hoist-non-react-statics "^3.3.1" -"@esbuild/freebsd-arm64@0.25.3": - version "0.25.3" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.3.tgz#9f7d789e2eb7747d4868817417cc968ffa84f35b" - integrity sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw== +"@emotion/serialize@^1.3.3": + version "1.3.3" + resolved "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz" + integrity sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA== + dependencies: + "@emotion/hash" "^0.9.2" + "@emotion/memoize" "^0.9.0" + "@emotion/unitless" "^0.10.0" + "@emotion/utils" "^1.4.2" + csstype "^3.0.2" -"@esbuild/freebsd-x64@0.25.3": - version "0.25.3" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.3.tgz#8ad35c51d084184a8e9e76bb4356e95350a64709" - integrity sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q== +"@emotion/sheet@^1.4.0": + version "1.4.0" + resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz" + integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg== -"@esbuild/linux-arm64@0.25.3": - version "0.25.3" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.3.tgz#3af0da3d9186092a9edd4e28fa342f57d9e3cd30" - integrity sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A== +"@emotion/styled@^11.14.0", "@emotion/styled@^11.3.0": + version "11.14.0" + resolved "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz" + integrity sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA== + dependencies: + "@babel/runtime" "^7.18.3" + "@emotion/babel-plugin" "^11.13.5" + "@emotion/is-prop-valid" "^1.3.0" + "@emotion/serialize" "^1.3.3" + "@emotion/use-insertion-effect-with-fallbacks" "^1.2.0" + "@emotion/utils" "^1.4.2" -"@esbuild/linux-arm@0.25.3": - version "0.25.3" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.3.tgz#e91cafa95e4474b3ae3d54da12e006b782e57225" - integrity sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ== +"@emotion/unitless@^0.10.0": + version "0.10.0" + resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz" + integrity sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg== -"@esbuild/linux-ia32@0.25.3": - version "0.25.3" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.3.tgz#81025732d85b68ee510161b94acdf7e3007ea177" - integrity sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw== +"@emotion/use-insertion-effect-with-fallbacks@^1.2.0": + version "1.2.0" + resolved "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz" + integrity sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg== -"@esbuild/linux-loong64@0.25.3": - version "0.25.3" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.3.tgz#3c744e4c8d5e1148cbe60a71a11b58ed8ee5deb8" - integrity sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g== +"@emotion/utils@^1.4.2": + version "1.4.2" + resolved "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz" + integrity sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA== -"@esbuild/linux-mips64el@0.25.3": - version "0.25.3" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.3.tgz#1dfe2a5d63702db9034cc6b10b3087cc0424ec26" - integrity sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag== - -"@esbuild/linux-ppc64@0.25.3": - version "0.25.3" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.3.tgz#2e85d9764c04a1ebb346dc0813ea05952c9a5c56" - integrity sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg== - -"@esbuild/linux-riscv64@0.25.3": - version "0.25.3" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.3.tgz#a9ea3334556b09f85ccbfead58c803d305092415" - integrity sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA== - -"@esbuild/linux-s390x@0.25.3": - version "0.25.3" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.3.tgz#f6a7cb67969222b200974de58f105dfe8e99448d" - integrity sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ== - -"@esbuild/linux-x64@0.25.3": - version "0.25.3" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.3.tgz#a237d3578ecdd184a3066b1f425e314ade0f8033" - integrity sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA== - -"@esbuild/netbsd-arm64@0.25.3": - version "0.25.3" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.3.tgz#4c15c68d8149614ddb6a56f9c85ae62ccca08259" - integrity sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA== - -"@esbuild/netbsd-x64@0.25.3": - version "0.25.3" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.3.tgz#12f6856f8c54c2d7d0a8a64a9711c01a743878d5" - integrity sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g== - -"@esbuild/openbsd-arm64@0.25.3": - version "0.25.3" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.3.tgz#ca078dad4a34df192c60233b058db2ca3d94bc5c" - integrity sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ== - -"@esbuild/openbsd-x64@0.25.3": - version "0.25.3" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.3.tgz#c9178adb60e140e03a881d0791248489c79f95b2" - integrity sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w== - -"@esbuild/sunos-x64@0.25.3": - version "0.25.3" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.3.tgz#03765eb6d4214ff27e5230af779e80790d1ee09f" - integrity sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA== - -"@esbuild/win32-arm64@0.25.3": - version "0.25.3" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.3.tgz#f1c867bd1730a9b8dfc461785ec6462e349411ea" - integrity sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ== - -"@esbuild/win32-ia32@0.25.3": - version "0.25.3" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.3.tgz#77491f59ef6c9ddf41df70670d5678beb3acc322" - integrity sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew== +"@emotion/weak-memoize@^0.4.0": + version "0.4.0" + resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz" + integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg== "@esbuild/win32-x64@0.25.3": version "0.25.3" @@ -336,7 +328,7 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@9.25.1", "@eslint/js@^9.22.0": +"@eslint/js@^9.22.0", "@eslint/js@9.25.1": version "9.25.1" resolved "https://registry.npmjs.org/@eslint/js/-/js-9.25.1.tgz" integrity sha512-dEIwmjntEx8u3Uvv+kr3PDeeArL8Hw07H9kyYxCjnM9pBjfEhk6uLXSchxxzgiwtRhhzVzqmUSDFBOi1TuZ7qg== @@ -354,6 +346,11 @@ "@eslint/core" "^0.13.0" levn "^0.4.1" +"@fontsource/roboto@^5.2.5": + version "5.2.5" + resolved "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.2.5.tgz" + integrity sha512-70r2UZ0raqLn5W+sPeKhqlf8wGvUXFWlofaDlcbt/S3d06+17gXKr3VNqDODB0I1ASme3dGT5OJj9NABt7OTZQ== + "@humanfs/core@^0.19.1": version "0.19.1" resolved "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz" @@ -414,6 +411,90 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@mui/core-downloads-tracker@^7.1.0": + version "7.1.0" + resolved "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.1.0.tgz" + integrity sha512-E0OqhZv548Qdc0PwWhLVA2zmjJZSTvaL4ZhoswmI8NJEC1tpW2js6LLP827jrW9MEiXYdz3QS6+hask83w74yQ== + +"@mui/icons-material@^7.1.0": + version "7.1.0" + resolved "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.1.0.tgz" + integrity sha512-1mUPMAZ+Qk3jfgL5ftRR06ATH/Esi0izHl1z56H+df6cwIlCWG66RXciUqeJCttbOXOQ5y2DCjLZI/4t3Yg3LA== + dependencies: + "@babel/runtime" "^7.27.1" + +"@mui/material@^7.1.0": + version "7.1.0" + resolved "https://registry.npmjs.org/@mui/material/-/material-7.1.0.tgz" + integrity sha512-ahUJdrhEv+mCp4XHW+tHIEYzZMSRLg8z4AjUOsj44QpD1ZaMxQoVOG2xiHvLFdcsIPbgSRx1bg1eQSheHBgvtg== + dependencies: + "@babel/runtime" "^7.27.1" + "@mui/core-downloads-tracker" "^7.1.0" + "@mui/system" "^7.1.0" + "@mui/types" "^7.4.2" + "@mui/utils" "^7.1.0" + "@popperjs/core" "^2.11.8" + "@types/react-transition-group" "^4.4.12" + clsx "^2.1.1" + csstype "^3.1.3" + prop-types "^15.8.1" + react-is "^19.1.0" + react-transition-group "^4.4.5" + +"@mui/private-theming@^7.1.0": + version "7.1.0" + resolved "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.1.0.tgz" + integrity sha512-4Kck4jxhqF6YxNwJdSae1WgDfXVg0lIH6JVJ7gtuFfuKcQCgomJxPvUEOySTFRPz1IZzwz5OAcToskRdffElDA== + dependencies: + "@babel/runtime" "^7.27.1" + "@mui/utils" "^7.1.0" + prop-types "^15.8.1" + +"@mui/styled-engine@^7.1.0": + version "7.1.0" + resolved "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.1.0.tgz" + integrity sha512-m0mJ0c6iRC+f9hMeRe0W7zZX1wme3oUX0+XTVHjPG7DJz6OdQ6K/ggEOq7ZdwilcpdsDUwwMfOmvO71qDkYd2w== + dependencies: + "@babel/runtime" "^7.27.1" + "@emotion/cache" "^11.13.5" + "@emotion/serialize" "^1.3.3" + "@emotion/sheet" "^1.4.0" + csstype "^3.1.3" + prop-types "^15.8.1" + +"@mui/system@^7.1.0": + version "7.1.0" + resolved "https://registry.npmjs.org/@mui/system/-/system-7.1.0.tgz" + integrity sha512-iedAWgRJMCxeMHvkEhsDlbvkK+qKf9me6ofsf7twk/jfT4P1ImVf7Rwb5VubEA0sikrVL+1SkoZM41M4+LNAVA== + dependencies: + "@babel/runtime" "^7.27.1" + "@mui/private-theming" "^7.1.0" + "@mui/styled-engine" "^7.1.0" + "@mui/types" "^7.4.2" + "@mui/utils" "^7.1.0" + clsx "^2.1.1" + csstype "^3.1.3" + prop-types "^15.8.1" + +"@mui/types@^7.4.2": + version "7.4.2" + resolved "https://registry.npmjs.org/@mui/types/-/types-7.4.2.tgz" + integrity sha512-edRc5JcLPsrlNFYyTPxds+d5oUovuUxnnDtpJUbP6WMeV4+6eaX/mqai1ZIWT62lCOe0nlrON0s9HDiv5en5bA== + dependencies: + "@babel/runtime" "^7.27.1" + +"@mui/utils@^7.1.0": + version "7.1.0" + resolved "https://registry.npmjs.org/@mui/utils/-/utils-7.1.0.tgz" + integrity sha512-/OM3S8kSHHmWNOP+NH9xEtpYSG10upXeQ0wLZnfDgmgadTAk5F4MQfFLyZ5FCRJENB3eRzltMmaNl6UtDnPovw== + dependencies: + "@babel/runtime" "^7.27.1" + "@mui/types" "^7.4.2" + "@types/prop-types" "^15.7.14" + clsx "^2.1.1" + prop-types "^15.8.1" + react-is "^19.1.0" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" @@ -422,7 +503,7 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": +"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": version "2.0.5" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== @@ -435,100 +516,10 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@rollup/rollup-android-arm-eabi@4.40.1": - version "4.40.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.1.tgz#e1562d360bca73c7bef6feef86098de3a2f1d442" - integrity sha512-kxz0YeeCrRUHz3zyqvd7n+TVRlNyTifBsmnmNPtk3hQURUyG9eAB+usz6DAwagMusjx/zb3AjvDUvhFGDAexGw== - -"@rollup/rollup-android-arm64@4.40.1": - version "4.40.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.1.tgz#37ba63940211673e15dcc5f469a78e34276dbca7" - integrity sha512-PPkxTOisoNC6TpnDKatjKkjRMsdaWIhyuMkA4UsBXT9WEZY4uHezBTjs6Vl4PbqQQeu6oION1w2voYZv9yquCw== - -"@rollup/rollup-darwin-arm64@4.40.1": - version "4.40.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.1.tgz#58b1eb86d997d71dabc5b78903233a3c27438ca0" - integrity sha512-VWXGISWFY18v/0JyNUy4A46KCFCb9NVsH+1100XP31lud+TzlezBbz24CYzbnA4x6w4hx+NYCXDfnvDVO6lcAA== - -"@rollup/rollup-darwin-x64@4.40.1": - version "4.40.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.1.tgz#5e22dab3232b1e575d930ce891abb18fe19c58c9" - integrity sha512-nIwkXafAI1/QCS7pxSpv/ZtFW6TXcNUEHAIA9EIyw5OzxJZQ1YDrX+CL6JAIQgZ33CInl1R6mHet9Y/UZTg2Bw== - -"@rollup/rollup-freebsd-arm64@4.40.1": - version "4.40.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.1.tgz#04c892d9ff864d66e31419634726ab0bebb33707" - integrity sha512-BdrLJ2mHTrIYdaS2I99mriyJfGGenSaP+UwGi1kB9BLOCu9SR8ZpbkmmalKIALnRw24kM7qCN0IOm6L0S44iWw== - -"@rollup/rollup-freebsd-x64@4.40.1": - version "4.40.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.1.tgz#f4b1e091f7cf5afc9e3a029d70128ad56409ecfb" - integrity sha512-VXeo/puqvCG8JBPNZXZf5Dqq7BzElNJzHRRw3vjBE27WujdzuOPecDPc/+1DcdcTptNBep3861jNq0mYkT8Z6Q== - -"@rollup/rollup-linux-arm-gnueabihf@4.40.1": - version "4.40.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.1.tgz#c8814bb5ce047a81b1fe4a33628dfd4ac52bd864" - integrity sha512-ehSKrewwsESPt1TgSE/na9nIhWCosfGSFqv7vwEtjyAqZcvbGIg4JAcV7ZEh2tfj/IlfBeZjgOXm35iOOjadcg== - -"@rollup/rollup-linux-arm-musleabihf@4.40.1": - version "4.40.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.1.tgz#5b4e7bd83cbebbf5ffe958802dcfd4ee34bf73a3" - integrity sha512-m39iO/aaurh5FVIu/F4/Zsl8xppd76S4qoID8E+dSRQvTyZTOI2gVk3T4oqzfq1PtcvOfAVlwLMK3KRQMaR8lg== - -"@rollup/rollup-linux-arm64-gnu@4.40.1": - version "4.40.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.1.tgz#141c848e53cee011e82a11777b8a51f1b3e8d77c" - integrity sha512-Y+GHnGaku4aVLSgrT0uWe2o2Rq8te9hi+MwqGF9r9ORgXhmHK5Q71N757u0F8yU1OIwUIFy6YiJtKjtyktk5hg== - -"@rollup/rollup-linux-arm64-musl@4.40.1": - version "4.40.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.1.tgz#22ebeaf2fa301aa4aa6c84b760e6cd1d1ac7eb1e" - integrity sha512-jEwjn3jCA+tQGswK3aEWcD09/7M5wGwc6+flhva7dsQNRZZTe30vkalgIzV4tjkopsTS9Jd7Y1Bsj6a4lzz8gQ== - -"@rollup/rollup-linux-loongarch64-gnu@4.40.1": - version "4.40.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.1.tgz#20b77dc78e622f5814ff8e90c14c938ceb8043bc" - integrity sha512-ySyWikVhNzv+BV/IDCsrraOAZ3UaC8SZB67FZlqVwXwnFhPihOso9rPOxzZbjp81suB1O2Topw+6Ug3JNegejQ== - -"@rollup/rollup-linux-powerpc64le-gnu@4.40.1": - version "4.40.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.1.tgz#2c90f99c987ef1198d4f8d15d754c286e1f07b13" - integrity sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg== - -"@rollup/rollup-linux-riscv64-gnu@4.40.1": - version "4.40.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.1.tgz#9336fd5e47d7f4760d02aa85f76976176eef53ca" - integrity sha512-EQSP+8+1VuSulm9RKSMKitTav89fKbHymTf25n5+Yr6gAPZxYWpj3DzAsQqoaHAk9YX2lwEyAf9S4W8F4l3VBQ== - -"@rollup/rollup-linux-riscv64-musl@4.40.1": - version "4.40.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.1.tgz#d75b4d54d46439bb5c6c13762788f57e798f5670" - integrity sha512-n/vQ4xRZXKuIpqukkMXZt9RWdl+2zgGNx7Uda8NtmLJ06NL8jiHxUawbwC+hdSq1rrw/9CghCpEONor+l1e2gA== - -"@rollup/rollup-linux-s390x-gnu@4.40.1": - version "4.40.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.1.tgz#e9f09b802f1291839247399028beaef9ce034c81" - integrity sha512-h8d28xzYb98fMQKUz0w2fMc1XuGzLLjdyxVIbhbil4ELfk5/orZlSTpF/xdI9C8K0I8lCkq+1En2RJsawZekkg== - -"@rollup/rollup-linux-x64-gnu@4.40.1": - version "4.40.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.1.tgz#0413169dc00470667dea8575c1129d4e7a73eb29" - integrity sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ== - -"@rollup/rollup-linux-x64-musl@4.40.1": - version "4.40.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.1.tgz#c76fd593323c60ea219439a00da6c6d33ffd0ea6" - integrity sha512-2BRORitq5rQ4Da9blVovzNCMaUlyKrzMSvkVR0D4qPuOy/+pMCrh1d7o01RATwVy+6Fa1WBw+da7QPeLWU/1mQ== - -"@rollup/rollup-win32-arm64-msvc@4.40.1": - version "4.40.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.1.tgz#c7724c386eed0bda5ae7143e4081c1910cab349b" - integrity sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg== - -"@rollup/rollup-win32-ia32-msvc@4.40.1": - version "4.40.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.1.tgz#7749e1b65cb64fe6d41ad1ad9e970a0ccc8ac350" - integrity sha512-DfcogW8N7Zg7llVEfpqWMZcaErKfsj9VvmfSyRjCyo4BI3wPEfrzTtJkZG6gKP/Z92wFm6rz2aDO7/JfiR/whA== +"@popperjs/core@^2.11.8": + version "2.11.8" + resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz" + integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== "@rollup/rollup-win32-x64-msvc@4.40.1": version "4.40.1" @@ -568,14 +559,14 @@ dependencies: "@babel/types" "^7.20.7" -"@types/estree@1.0.7", "@types/estree@^1.0.6": +"@types/estree@^1.0.6", "@types/estree@1.0.7": version "1.0.7" resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz" integrity sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ== "@types/history@^4.7.11": version "4.7.11" - resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64" + resolved "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz" integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA== "@types/json-schema@^7.0.15": @@ -583,27 +574,42 @@ resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== -"@types/node@^22.15.3": +"@types/node@^18.0.0 || ^20.0.0 || >=22.0.0", "@types/node@^22.15.3": version "22.15.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.3.tgz#b7fb9396a8ec5b5dfb1345d8ac2502060e9af68b" + resolved "https://registry.npmjs.org/@types/node/-/node-22.15.3.tgz" integrity sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw== dependencies: undici-types "~6.21.0" -"@types/react-dom@^19.0.4": - version "19.1.2" - resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.2.tgz" - integrity sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw== +"@types/parse-json@^4.0.0": + version "4.0.2" + resolved "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz" + integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== + +"@types/prop-types@^15.7.14": + version "15.7.14" + resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz" + integrity sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ== + +"@types/react-dom@^19.1.3": + version "19.1.3" + resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.3.tgz" + integrity sha512-rJXC08OG0h3W6wDMFxQrZF00Kq6qQvw0djHRdzl3U5DnIERz0MRce3WVc7IS6JYBwtaP/DwYtRRjVlvivNveKg== "@types/react-router@^5.1.20": version "5.1.20" - resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.20.tgz#88eccaa122a82405ef3efbcaaa5dcdd9f021387c" + resolved "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz" integrity sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q== dependencies: "@types/history" "^4.7.11" "@types/react" "*" -"@types/react@*", "@types/react@^19.0.10": +"@types/react-transition-group@^4.4.12": + version "4.4.12" + resolved "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz" + integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w== + +"@types/react@*", "@types/react@^17.0.0 || ^18.0.0 || ^19.0.0", "@types/react@^19.0.0", "@types/react@^19.1.2": version "19.1.2" resolved "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz" integrity sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw== @@ -625,7 +631,7 @@ natural-compare "^1.4.0" ts-api-utils "^2.0.1" -"@typescript-eslint/parser@8.31.1": +"@typescript-eslint/parser@^8.0.0 || ^8.0.0-alpha.0", "@typescript-eslint/parser@8.31.1": version "8.31.1" resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.31.1.tgz" integrity sha512-oU/OtYVydhXnumd0BobL9rkJg7wFJ9bFFPmSmB/bf/XWN85hlViji59ko6bSKBXyseT9V8l+CN1nwmlbiN0G7Q== @@ -707,7 +713,7 @@ acorn-jsx@^5.3.2: resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn@^8.14.0: +"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.14.0: version "8.14.1" resolved "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz" integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg== @@ -734,6 +740,15 @@ argparse@^2.0.1: resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== +babel-plugin-macros@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz" + integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== + dependencies: + "@babel/runtime" "^7.12.5" + cosmiconfig "^7.0.0" + resolve "^1.19.0" + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" @@ -761,7 +776,7 @@ braces@^3.0.3: dependencies: fill-range "^7.1.1" -browserslist@^4.24.0: +browserslist@^4.24.0, "browserslist@>= 4.21.0": version "4.24.4" resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz" integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A== @@ -789,6 +804,11 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +clsx@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz" + integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== + color-convert@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" @@ -806,6 +826,11 @@ concat-map@0.0.1: resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +convert-source-map@^1.5.0: + version "1.9.0" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + convert-source-map@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz" @@ -813,9 +838,20 @@ convert-source-map@^2.0.0: cookie@^1.0.1: version "1.0.2" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.0.2.tgz#27360701532116bd3f1f9416929d176afe1e4610" + resolved "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz" integrity sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA== +cosmiconfig@^7.0.0: + version "7.1.0" + resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz" + integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + cross-spawn@^7.0.6: version "7.0.6" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz" @@ -825,7 +861,7 @@ cross-spawn@^7.0.6: shebang-command "^2.0.0" which "^2.0.1" -csstype@^3.0.2: +csstype@^3.0.2, csstype@^3.1.3: version "3.1.3" resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz" integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== @@ -842,11 +878,26 @@ deep-is@^0.1.3: resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== +dom-helpers@^5.0.1: + version "5.2.1" + resolved "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + electron-to-chromium@^1.5.73: version "1.5.144" resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.144.tgz" integrity sha512-eJIaMRKeAzxfBSxtjYnoIAw/tdD6VIH6tHBZepZnAbE3Gyqqs5mGN87DvcldPUbVkIljTK8pY0CMcUljP64lfQ== +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + esbuild@^0.25.0: version "0.25.3" resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz" @@ -916,7 +967,7 @@ eslint-visitor-keys@^4.2.0: resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz" integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== -eslint@^9.22.0: +"eslint@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^8.57.0 || ^9.0.0", eslint@^9.22.0, eslint@>=8.40: version "9.25.1" resolved "https://registry.npmjs.org/eslint/-/eslint-9.25.1.tgz" integrity sha512-E6Mtz9oGQWDCpV12319d59n4tx9zOTXSTmc8BLVxBx+G/0RdM5MvEEJLU9c0+aleoePYYgVTOsRblx433qmhWQ== @@ -1042,6 +1093,11 @@ fill-range@^7.1.1: dependencies: to-regex-range "^5.0.1" +find-root@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz" + integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== + find-up@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" @@ -1063,10 +1119,10 @@ flatted@^3.2.9: resolved "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz" integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== -fsevents@~2.3.2, fsevents@~2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== gensync@^1.0.0-beta.2: version "1.0.0-beta.2" @@ -1112,6 +1168,20 @@ has-flag@^4.0.0: resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +hoist-non-react-statics@^3.3.1: + version "3.3.2" + resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + ignore@^5.2.0, ignore@^5.3.1: version "5.3.2" resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz" @@ -1130,6 +1200,18 @@ imurmurhash@^0.1.4: resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-core-module@^2.16.0: + version "2.16.1" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" @@ -1152,7 +1234,7 @@ isexe@^2.0.0: resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -js-tokens@^4.0.0: +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== @@ -1174,6 +1256,11 @@ json-buffer@3.0.1: resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz" integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" @@ -1204,6 +1291,11 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + locate-path@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" @@ -1216,6 +1308,13 @@ lodash.merge@^4.6.2: resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" @@ -1270,6 +1369,11 @@ node-releases@^2.0.19: resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz" integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + optionator@^0.9.3: version "0.9.4" resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz" @@ -1303,6 +1407,16 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" +parse-json@^5.0.0: + version "5.2.0" + resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" @@ -1313,6 +1427,16 @@ path-key@^3.1.0: resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + picocolors@^1.0.0, picocolors@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" @@ -1323,7 +1447,7 @@ picomatch@^2.3.1: resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -picomatch@^4.0.2: +"picomatch@^3 || ^4", picomatch@^4.0.2: version "4.0.2" resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz" integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== @@ -1342,6 +1466,15 @@ prelude-ls@^1.2.1: resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prop-types@^15.6.2, prop-types@^15.8.1: + version "15.8.1" + resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + punycode@^2.1.0: version "2.3.1" resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" @@ -1352,13 +1485,28 @@ queue-microtask@^1.2.2: resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -react-dom@^19.0.0: +"react-dom@^17.0.0 || ^18.0.0 || ^19.0.0", react-dom@^19.0.0, react-dom@>=16.6.0, react-dom@>=18: version "19.1.0" resolved "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz" integrity sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g== dependencies: scheduler "^0.26.0" +react-is@^16.13.1: + version "16.13.1" + resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-is@^16.7.0: + version "16.13.1" + resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-is@^19.1.0: + version "19.1.0" + resolved "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz" + integrity sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg== + react-refresh@^0.17.0: version "0.17.0" resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz" @@ -1366,14 +1514,24 @@ react-refresh@^0.17.0: react-router@^7.5.3: version "7.5.3" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-7.5.3.tgz#9e5420832af8c3690740c1797d4fa54613fea06d" + resolved "https://registry.npmjs.org/react-router/-/react-router-7.5.3.tgz" integrity sha512-3iUDM4/fZCQ89SXlDa+Ph3MevBrozBAI655OAfWQlTm9nBR0IKlrmNwFow5lPHttbwvITZfkeeeZFP6zt3F7pw== dependencies: cookie "^1.0.1" set-cookie-parser "^2.6.0" turbo-stream "2.4.0" -react@^19.0.0: +react-transition-group@^4.4.5: + version "4.4.5" + resolved "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + +"react@^17.0.0 || ^18.0.0 || ^19.0.0", react@^19.0.0, react@^19.1.0, react@>=16.6.0, react@>=16.8.0, react@>=18: version "19.1.0" resolved "https://registry.npmjs.org/react/-/react-19.1.0.tgz" integrity sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg== @@ -1383,6 +1541,15 @@ resolve-from@^4.0.0: resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +resolve@^1.19.0: + version "1.22.10" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz" + integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== + dependencies: + is-core-module "^2.16.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + reusify@^1.0.4: version "1.1.0" resolved "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz" @@ -1441,7 +1608,7 @@ semver@^7.6.0: set-cookie-parser@^2.6.0: version "2.7.1" - resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz#3016f150072202dfbe90fadee053573cc89d2943" + resolved "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz" integrity sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ== shebang-command@^2.0.0: @@ -1461,11 +1628,21 @@ source-map-js@^1.2.1: resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz" integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== +source-map@^0.5.7: + version "0.5.7" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz" + integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== + strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +stylis@4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz" + integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== + supports-color@^7.1.0: version "7.2.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" @@ -1473,6 +1650,11 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + tinyglobby@^0.2.13: version "0.2.13" resolved "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz" @@ -1495,7 +1677,7 @@ ts-api-utils@^2.0.1: turbo-stream@2.4.0: version "2.4.0" - resolved "https://registry.yarnpkg.com/turbo-stream/-/turbo-stream-2.4.0.tgz#1e4fca6725e90fa14ac4adb782f2d3759a5695f0" + resolved "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz" integrity sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g== type-check@^0.4.0, type-check@~0.4.0: @@ -1514,14 +1696,14 @@ typescript-eslint@^8.26.1: "@typescript-eslint/parser" "8.31.1" "@typescript-eslint/utils" "8.31.1" -typescript@~5.7.2: +typescript@>=4.8.4, "typescript@>=4.8.4 <5.9.0", typescript@~5.7.2: version "5.7.3" resolved "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz" integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw== undici-types@~6.21.0: version "6.21.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz" integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== update-browserslist-db@^1.1.1: @@ -1539,7 +1721,7 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -vite@^6.3.1: +"vite@^4.2.0 || ^5.0.0 || ^6.0.0", vite@^6.3.1: version "6.3.3" resolved "https://registry.npmjs.org/vite/-/vite-6.3.3.tgz" integrity sha512-5nXH+QsELbFKhsEfWLkHrvgRpTdGJzqOZ+utSdmPTvwHmvU6ITTm3xx+mRusihkcI8GeC7lCDyn3kDtiki9scw== @@ -1570,6 +1752,16 @@ yallist@^3.0.2: resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== +yaml@^1.10.0: + version "1.10.2" + resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + +yaml@^2.4.2: + version "2.7.1" + resolved "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz" + integrity sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ== + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" diff --git a/yarn.lock b/yarn.lock index d17ff0a..9c0ab2e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9,12 +9,384 @@ dependencies: undici-types "~6.21.0" +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chokidar@^3.5.2: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +concurrently@^9.1.2: + version "9.1.2" + resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-9.1.2.tgz#22d9109296961eaee773e12bfb1ce9a66bc9836c" + integrity sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ== + dependencies: + chalk "^4.1.2" + lodash "^4.17.21" + rxjs "^7.8.1" + shell-quote "^1.8.1" + supports-color "^8.1.1" + tree-kill "^1.2.2" + yargs "^17.7.2" + +debug@^4: + version "4.4.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" + integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== + dependencies: + ms "^2.1.3" + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +escalade@^3.1.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +ignore-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +nodemon@^3.1.10: + version "3.1.10" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.1.10.tgz#5015c5eb4fffcb24d98cf9454df14f4fecec9bc1" + integrity sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw== + dependencies: + chokidar "^3.5.2" + debug "^4" + ignore-by-default "^1.0.1" + minimatch "^3.1.2" + pstree.remy "^1.1.8" + semver "^7.5.3" + simple-update-notifier "^2.0.0" + supports-color "^5.5.0" + touch "^3.1.0" + undefsafe "^2.0.5" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +picomatch@^2.0.4, picomatch@^2.2.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pstree.remy@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" + integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +rxjs@^7.8.1: + version "7.8.2" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b" + integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== + dependencies: + tslib "^2.1.0" + +semver@^7.5.3: + version "7.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" + integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== + +shell-quote@^1.8.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.2.tgz#d2d83e057959d53ec261311e9e9b8f51dcb2934a" + integrity sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA== + +simple-update-notifier@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb" + integrity sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w== + dependencies: + semver "^7.5.3" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +supports-color@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +touch@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.1.tgz#097a23d7b161476435e5c1344a95c0f75b4a5694" + integrity sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA== + +tree-kill@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" + integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== + +tslib@^2.1.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + typescript@^5.8.3: version "5.8.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== +undefsafe@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" + integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== + undici-types@~6.21.0: version "6.21.0" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1"