diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f7c8f97 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +.env +backend/.env +.vscode +README.md +node_modules \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..111b6f0 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# notebrook-notes + +Stream of consciousness note taking \ No newline at end of file diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..371167a --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,12 @@ +DB_PATH=/nb/database.db +API_TOKEN=test +UPLOAD_DIR=/nb/uploads/ +DESCRIBE_IMAGES=1 +DESCRIBE_IMAGES_API=ollama +DESCRIBE_IMAGES_PROMPT="Your task is to describe images to your friend in a friendly, detailed but concise manner.\n" +DESCRIBE_IMAGES_TEMPERATURE=0.5 +DESCRIBE_IMAGES_MAX_TOKENS=8192 +OPENAI_API_KEY=sk-blahblahblahblahblahImAnAPIKeyWoopDeeDoo +OPENAI_MODEL=gpt-4o +OLLAMA_URL=http://localhost:11434 +OLLAMA_MODEL=moondream diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..9b1ee42 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,175 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..a7675ae --- /dev/null +++ b/backend/README.md @@ -0,0 +1,15 @@ +# notebrook-backend + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.1.21. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/backend/migrations/1_init.sql b/backend/migrations/1_init.sql new file mode 100644 index 0000000..b569e75 --- /dev/null +++ b/backend/migrations/1_init.sql @@ -0,0 +1,31 @@ +CREATE TABLE IF NOT EXISTS channels ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + createdAt DATETIME DEFAULT CURRENT_TIMESTAMP +); +CREATE TABLE IF NOT EXISTS files ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + channelId INTEGER, + filePath TEXT, + fileType TEXT, + fileSize INTEGER, + originalName TEXT, + createdAt DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (channelId) REFERENCES channels (id) ON DELETE CASCADE +); +CREATE TABLE IF NOT EXISTS messages ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + channelId INTEGER, + content TEXT, + fileId INTEGER NULL, + createdAt DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (channelId) REFERENCES channels (id) ON DELETE CASCADE, + FOREIGN KEY (fileId) REFERENCES files (id) ON DELETE + SET + NULL +); +CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5( + content, + content = 'messages', + content_rowid = 'id' +); \ No newline at end of file diff --git a/backend/migrations/2_localtime.sql b/backend/migrations/2_localtime.sql new file mode 100644 index 0000000..1b0f8cb --- /dev/null +++ b/backend/migrations/2_localtime.sql @@ -0,0 +1,52 @@ +-- 1. Create a backup of the existing tables +CREATE TABLE channels_backup AS SELECT * FROM channels; +CREATE TABLE files_backup AS SELECT * FROM files; +CREATE TABLE messages_backup AS SELECT * FROM messages; + +-- 2. Drop the existing tables +DROP TABLE channels; +DROP TABLE files; +DROP TABLE messages; + +-- 3. Recreate the tables with the updated schema +CREATE TABLE IF NOT EXISTS channels ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + createdAt DATETIME DEFAULT (datetime('now', 'localtime')) +); + +CREATE TABLE IF NOT EXISTS files ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + channelId INTEGER, + filePath TEXT, + fileType TEXT, + fileSize INTEGER, + originalName TEXT, + createdAt DATETIME DEFAULT (datetime('now', 'localtime')), + FOREIGN KEY (channelId) REFERENCES channels (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS messages ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + channelId INTEGER, + content TEXT, + fileId INTEGER NULL, + createdAt DATETIME DEFAULT (datetime('now', 'localtime')), + FOREIGN KEY (channelId) REFERENCES channels (id) ON DELETE CASCADE, + FOREIGN KEY (fileId) REFERENCES files (id) ON DELETE SET NULL +); + +-- 4. Migrate the data back from the backup tables +INSERT INTO channels (id, name, createdAt) +SELECT id, name, createdAt FROM channels_backup; + +INSERT INTO files (id, channelId, filePath, fileType, fileSize, originalName, createdAt) +SELECT id, channelId, filePath, fileType, fileSize, originalName, createdAt FROM files_backup; + +INSERT INTO messages (id, channelId, content, fileId, createdAt) +SELECT id, channelId, content, fileId, createdAt FROM messages_backup; + +-- 5. Drop the backup tables +DROP TABLE channels_backup; +DROP TABLE files_backup; +DROP TABLE messages_backup; diff --git a/backend/package-lock.json b/backend/package-lock.json new file mode 100644 index 0000000..abcbab0 --- /dev/null +++ b/backend/package-lock.json @@ -0,0 +1,2435 @@ +{ + "name": "notebrook-backend", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "notebrook-backend", + "dependencies": { + "@types/better-sqlite3": "^7.6.11", + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/jsonwebtoken": "^9.0.6", + "@types/multer": "^1.4.11", + "@types/ws": "^8.5.12", + "better-sqlite3": "^11.2.1", + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.19.2", + "multer": "^1.4.5-lts.1", + "ollama": "^0.5.8", + "openai": "^4.56.0", + "selfsigned": "^2.4.1", + "sharp": "^0.33.5", + "tsx": "^4.18.0", + "ws": "^8.18.0" + }, + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5.5.4" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz", + "integrity": "sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", + "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", + "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", + "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", + "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", + "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", + "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", + "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", + "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", + "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", + "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", + "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", + "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", + "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", + "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", + "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", + "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", + "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", + "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", + "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", + "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", + "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", + "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", + "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", + "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@types/better-sqlite3": { + "version": "7.6.11", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bun": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.1.8.tgz", + "integrity": "sha512-PIwVFQKPviksiibobyvcWtMvMFMTj91T8dQEh9l1P3Ypr3ZuVn9w7HSr+5mTNrPqD1xpdDLEErzZPU8gqHBu6g==", + "dev": true, + "dependencies": { + "bun-types": "1.1.26" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.21", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.5", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "license": "MIT" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.6", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "license": "MIT" + }, + "node_modules/@types/multer": { + "version": "1.4.11", + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/node": { + "version": "22.2.0", + "license": "MIT", + "dependencies": { + "undici-types": "~6.13.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.11", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.9.15", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.5.12", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agentkeepalive": { + "version": "4.5.0", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/append-field": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/better-sqlite3": { + "version": "11.2.1", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/body-parser": { + "version": "1.20.2", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "license": "MIT" + }, + "node_modules/bun-types": { + "version": "1.1.26", + "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.1.26.tgz", + "integrity": "sha512-n7jDe62LsB2+WE8Q8/mT3azkPaatKlj/2MyP6hi3mKvPz9oPpB6JW/Ll6JHtNLudasFFuvfgklYSE+rreGvBjw==", + "dev": true, + "dependencies": { + "@types/node": "~20.12.8", + "@types/ws": "~8.5.10" + } + }, + "node_modules/bun-types/node_modules/@types/node": { + "version": "20.12.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.14.tgz", + "integrity": "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/bun-types/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/busboy": { + "version": "1.6.0", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "license": "ISC" + }, + "node_modules/color": { + "version": "4.2.3", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", + "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.1", + "@esbuild/android-arm": "0.23.1", + "@esbuild/android-arm64": "0.23.1", + "@esbuild/android-x64": "0.23.1", + "@esbuild/darwin-arm64": "0.23.1", + "@esbuild/darwin-x64": "0.23.1", + "@esbuild/freebsd-arm64": "0.23.1", + "@esbuild/freebsd-x64": "0.23.1", + "@esbuild/linux-arm": "0.23.1", + "@esbuild/linux-arm64": "0.23.1", + "@esbuild/linux-ia32": "0.23.1", + "@esbuild/linux-loong64": "0.23.1", + "@esbuild/linux-mips64el": "0.23.1", + "@esbuild/linux-ppc64": "0.23.1", + "@esbuild/linux-riscv64": "0.23.1", + "@esbuild/linux-s390x": "0.23.1", + "@esbuild/linux-x64": "0.23.1", + "@esbuild/netbsd-x64": "0.23.1", + "@esbuild/openbsd-arm64": "0.23.1", + "@esbuild/openbsd-x64": "0.23.1", + "@esbuild/sunos-x64": "0.23.1", + "@esbuild/win32-arm64": "0.23.1", + "@esbuild/win32-ia32": "0.23.1", + "@esbuild/win32-x64": "0.23.1" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/express": { + "version": "4.19.2", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "license": "MIT" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "license": "MIT", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.6.tgz", + "integrity": "sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "license": "MIT" + }, + "node_modules/gopd": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "license": "MIT" + }, + "node_modules/isarray": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/methods": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, + "node_modules/multer": { + "version": "1.4.5-lts.1", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-abi": { + "version": "3.67.0", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ollama": { + "version": "0.5.8", + "license": "MIT", + "dependencies": { + "whatwg-fetch": "^3.6.20" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openai": { + "version": "4.56.0", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + }, + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/openai/node_modules/@types/node": { + "version": "18.19.45", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/openai/node_modules/@types/node/node_modules/undici-types": { + "version": "5.26.5", + "license": "MIT" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "license": "MIT" + }, + "node_modules/prebuild-install": { + "version": "7.1.2", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pump": { + "version": "3.0.0", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "license": "MIT" + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "license": "MIT" + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.18.0", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static": { + "version": "1.15.0", + "license": "MIT", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "license": "ISC" + }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "license": "MIT" + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream/node_modules/readable-stream": { + "version": "3.6.2", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "optional": true + }, + "node_modules/tsx": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.18.0.tgz", + "integrity": "sha512-a1jaKBSVQkd6yEc1/NI7G6yHFfefIcuf3QJST7ZEyn4oQnxLYrZR5uZAM8UrwUa3Ge8suiZHcNS1gNrEvmobqg==", + "dependencies": { + "esbuild": "~0.23.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.5.4", + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.13.0", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "license": "MIT" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.0", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + } + } +} diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..58c4771 --- /dev/null +++ b/backend/package.json @@ -0,0 +1,35 @@ +{ + "name": "notebrook-backend", + "module": "src/server.ts", + "type": "module", + "scripts": { + "start": "tsx src/server.ts", + "dev": "tsx --watch src/server.ts", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5.5.4" + }, + "dependencies": { + "@types/better-sqlite3": "^7.6.11", + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/jsonwebtoken": "^9.0.6", + "@types/multer": "^1.4.11", + "@types/ws": "^8.5.12", + "better-sqlite3": "^11.2.1", + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.19.2", + "multer": "^1.4.5-lts.1", + "ollama": "^0.5.8", + "openai": "^4.56.0", + "selfsigned": "^2.4.1", + "sharp": "^0.33.5", + "tsx": "^4.18.0", + "ws": "^8.18.0" + } +} diff --git a/backend/schema.sql b/backend/schema.sql new file mode 100644 index 0000000..48077a7 --- /dev/null +++ b/backend/schema.sql @@ -0,0 +1,31 @@ +CREATE TABLE IF NOT EXISTS channels ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + createdAt DATETIME DEFAULT CURRENT_TIMESTAMP +); +CREATE TABLE IF NOT EXISTS files ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + channelId INTEGER, + filePath TEXT, + fileType TEXT, + fileSize INTEGER, + originalName TEXT, + createdAt DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (channelId) REFERENCES channels (id) ON DELETE CASCADE +); +CREATE TABLE IF NOT EXISTS messages ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + channelId INTEGER, + content TEXT, + fileId INTEGER NULL, + createdAt DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (channelId) REFERENCES channels (id) ON DELETE CASCADE, + FOREIGN KEY (fileId) REFERENCES files (id) ON DELETE + SET + NULL +); +CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5( + content, + content = 'messages', + content_rowid = 'id' +); \ No newline at end of file diff --git a/backend/src/app.ts b/backend/src/app.ts new file mode 100644 index 0000000..1b9b56f --- /dev/null +++ b/backend/src/app.ts @@ -0,0 +1,27 @@ +import express from "express"; +import cors from "cors"; +import * as ChannelRoutes from "./routes/channel"; +import * as FileRoutes from "./routes/file"; +import * as MessageRoutes from "./routes/message"; +import * as SearchRoutes from "./routes/search"; +import { authenticate } from "./middleware/auth"; +import { initializeDB } from "./db"; +import { FRONTEND_DIR, UPLOAD_DIR } from "./config"; + + +export const app = express(); + +app.use(express.json()); +app.use(cors()); +app.use('/uploads', express.static(UPLOAD_DIR)); +app.use(express.static(FRONTEND_DIR)); + +app.use("/channels", ChannelRoutes.router); +app.use("/channels/:channelId/messages", MessageRoutes.router); +app.use("/channels/:channelId/messages/:messageId/files", FileRoutes.router); +app.use("/search", SearchRoutes.router); + +app.get('/check-token', authenticate, (req, res) => { + res.json({ message: 'Token is valid' }); +}); + diff --git a/backend/src/config.ts b/backend/src/config.ts new file mode 100644 index 0000000..f854ec9 --- /dev/null +++ b/backend/src/config.ts @@ -0,0 +1,21 @@ +import dotenv from "dotenv"; +dotenv.config(); + +export const DB_PATH = process.env["DB_PATH"] || "/usr/src/app/data/db.sqlite"; +export const SECRET_KEY = process.env["API_TOKEN"] || ""; +export const UPLOAD_DIR = process.env["UPLOAD_DIR"] || "/usr/src/app/data/uploads/"; +export const FRONTEND_DIR = process.env["FRONTEND_DIR"] || "/usr/src/app/backend/public"; +export const DESCRIBE_IMAGES: boolean = process.env["DESCRIBE_IMAGES"] === "1" ? true : false; +export const DESCRIBE_IMAGES_API = process.env["DESCRIBE_IMAGES_API"] || "ollama"; +export const DESCRIBE_IMAGES_PROMPT= process.env["DESCRIBE_IMAGES_PROMPT"] || "Describe this image."; +export const DESCRIBE_IMAGES_TEMPERATURE= parseFloat(process.env["DESCRIBE_IMAGES_TEMPERATURE"]!) || 0.5; +export const DESCRIBE_IMAGES_MAX_TOKENS= parseInt(process.env["DESCRIBE_IMAGES_MAX_TOKENS"]!) || 1024; +export const OPENAI_API_KEY= process.env["OPENAI_API_KEY"] || ""; +export const OPENAI_MODEL = process.env["OPENAI_MODEL"] || "gpt-4o"; +export const OLLAMA_URL= process.env["OLLAMA_URL"] || "http://localhost:11434"; +export const OLLAMA_MODEL= process.env["OLLAMA_MODEL"] || "moondream"; +export const PORT = parseInt(process.env["PORT"]!) || 3000; +export const USE_SSL = process.env["USE_SSL"] === "1" ? true : false; +export const SSL_KEY = process.env["SSL_KEY"] || ""; +export const SSL_CERT = process.env["SSL_CERT"] || ""; +console.log(process.env); \ No newline at end of file diff --git a/backend/src/controllers/channel-controller.ts b/backend/src/controllers/channel-controller.ts new file mode 100644 index 0000000..89e74a5 --- /dev/null +++ b/backend/src/controllers/channel-controller.ts @@ -0,0 +1,62 @@ +import type { Request, Response } from "express"; +import * as ChannelService from "../services/channel-service"; +import { logger } from "../globals"; + +export const createChannel = async (req: Request, res: Response) => { + const { name } = req.body; + if (!name) { + return res.status(400).json({ error: 'Name is required' }); + } + const chan = await ChannelService.createChannel(name); + logger.info(`Channel ${name} created`); + res.json(chan); +} + +export const deleteChannel = async (req: Request, res: Response) => { + const { channelId } = req.params; + if (!channelId) { + return res.status(400).json({ error: 'Channel ID is required' }); + } + const result = await ChannelService.deleteChannel(channelId); + + if (result.changes === 0) { + logger.warn(`Channel ${channelId} not found while deleting`); + return res.status(404).json({ error: 'Channel not found' }); + } + logger.info(`Channel ${channelId} deleted`); + + res.json({ message: 'Channel deleted successfully' }); +} + +export const getChannels = async (req: Request, res: Response) => { + const channels = await ChannelService.getChannels(); + res.json({ channels }); +} + +export const mergeChannel = async (req: Request, res: Response) => { + const { channelId } = req.params; + const { targetChannelId } = req.body; + if (!channelId || !targetChannelId) { + return res.status(400).json({ error: 'Channel ID and targetChannelId are required' }); + } + const result = await ChannelService.mergeChannel(channelId, targetChannelId); + logger.info(`Channel ${targetChannelId} merged into ${channelId}`); + + res.json({ message: 'Channels merged successfully' }); +} + +export const updateChannel = async (req: Request, res: Response) => { + const { channelId } = req.params; + const { name } = req.body; + if (!channelId || !name) { + return res.status(400).json({ error: 'Channel ID and name are required' }); + } + const result = await ChannelService.updateChannel(channelId, name); + + if (result.changes === 0) { + return res.status(404).json({ error: 'Channel not found' }); + } + logger.info(`Channel ${channelId} updated as ${name}`); + + res.json({ message: 'Channel updated successfully' }); +} \ No newline at end of file diff --git a/backend/src/controllers/file-controller.ts b/backend/src/controllers/file-controller.ts new file mode 100644 index 0000000..a67c530 --- /dev/null +++ b/backend/src/controllers/file-controller.ts @@ -0,0 +1,33 @@ +import type { Request, Response } from "express"; +import * as FileService from "../services/file-service"; +import { logger } from "../globals"; + +export const uploadFile = async (req: Request, res: Response) => { + const { channelId, messageId } = req.params; + const filePath = (req.file as Express.Multer.File).path; + const fileType = req.file?.mimetype; + const fileSize = req.file?.size; + const originalName = req.file?.originalname; + + if (!channelId || !messageId) { + return res.status(400).json({ error: 'Channel ID and message ID are required' }); + } + if (!filePath || !fileType || !fileSize || !originalName) { + return res.status(400).json({ error: 'File is required' }); + } + + const result = await FileService.uploadFile(channelId, messageId, filePath, fileType!, fileSize!, originalName!); + logger.info(`File ${originalName} uploaded to message ${messageId} as ${filePath}`); + res.json({ id: result.lastInsertRowid, channelId, messageId, filePath, fileType }); +} + + +export const getFiles = async (req: Request, res: Response) => { + const { messageId } = req.params; + if (!messageId) { + return res.status(400).json({ error: 'Message ID is required' }); + } + const files = await FileService.getFiles(messageId); + res.json({ files }); +} + diff --git a/backend/src/controllers/message-controller.ts b/backend/src/controllers/message-controller.ts new file mode 100644 index 0000000..5f984d8 --- /dev/null +++ b/backend/src/controllers/message-controller.ts @@ -0,0 +1,54 @@ +import type { Request, Response } from "express"; +import * as MessageService from "../services/message-service"; +import { logger } from "../globals"; + +export const createMessage = async (req: Request, res: Response) => { + const { content } = req.body; + const { channelId } = req.params; + if (!content || !channelId) { + return res.status(400).json({ error: 'Content and channel ID are required' }); + } + const messageId = await MessageService.createMessage(channelId, content); + logger.info(`Message ${messageId} created in channel ${channelId}`); + + res.json({ id: messageId, channelId, content, createdAt: new Date().toISOString() }); +}; + +export const updateMessage = async (req: Request, res: Response) => { + const { content } = req.body; + const { messageId } = req.params; + if (!content || !messageId) { + return res.status(400).json({ error: 'Content and message ID are required ' }); + } + const result = await MessageService.updateMessage(messageId, content); + if (result.changes === 0) { + return res.status(404).json({ error: 'Message not found' }); + } + logger.info(`Message ${messageId} updated`); + + res.json({ id: messageId, content }); +} + +export const deleteMessage = async (req: Request, res: Response) => { + const { messageId } = req.params; + if (!messageId) { + return res.status(400).json({ error: 'Message ID is required' }); + } + const result = await MessageService.deleteMessage(messageId); + if (result.changes === 0) { + return res.status(404).json({ error: 'Message not found' }); + } + logger.info(`Message ${messageId} deleted`); + + res.json({ message: 'Message deleted successfully' }); +} + +export const getMessages = async (req: Request, res: Response) => { + const { channelId } = req.params; + if (!channelId) { + return res.status(400).json({ error: 'Channel ID is required' }); + } + const messages = await MessageService.getMessages(channelId); + + res.json({ messages }); +} \ No newline at end of file diff --git a/backend/src/controllers/search-controller.ts b/backend/src/controllers/search-controller.ts new file mode 100644 index 0000000..4da531d --- /dev/null +++ b/backend/src/controllers/search-controller.ts @@ -0,0 +1,13 @@ +import type { Request, Response } from "express"; +import * as SearchService from "../services/search-service"; +import { logger } from "../globals"; + +export const search = async (req: Request, res: Response) => { + const { query, channelId } = req.query; + if (!query) { + return res.status(400).json({ error: 'Query is required' }); + } + const results = await SearchService.search(query as string, channelId as string); + logger.info(`Searched for ${query}`); + res.json({ results }); +} \ No newline at end of file diff --git a/backend/src/controllers/websocket-controller.ts b/backend/src/controllers/websocket-controller.ts new file mode 100644 index 0000000..56479cc --- /dev/null +++ b/backend/src/controllers/websocket-controller.ts @@ -0,0 +1,29 @@ +import { events } from "../globals"; +import { WebSocket } from "ws"; + +export const attachEvents = (ws: WebSocket) => { + events.on('file-uploaded', (id, channelId, messageId, filePath, fileType, fileSize, originalName) => { + ws.send(JSON.stringify({ type: 'file-uploaded', data: {id, channelId, messageId, filePath, fileType, fileSize, originalName }})); + }); + events.on('message-created', (id, channelId, content) => { + ws.send(JSON.stringify({ type: 'message-created', data: {id, channelId, content }})); + }); + events.on('message-updated', (id, content) => { + ws.send(JSON.stringify({ type: 'message-updated', data: {id, content }})); + }); + events.on('message-deleted', (id) => { + ws.send(JSON.stringify({ type: 'message-deleted', data: {id }})); + }); + events.on('channel-created', (channel) => { + ws.send(JSON.stringify({ type: 'channel-created', data: {channel }})); + }); + events.on('channel-deleted', (id) => { + ws.send(JSON.stringify({ type: 'channel-deleted', data: {id} })); + }); + events.on('channel-merged', (channelId, targetChannelId) => { + ws.send(JSON.stringify({ type: 'channel-merged', data: {channelId, targetChannelId }})); + }); + events.on('channel-updated', (id, name) => { + ws.send(JSON.stringify({ type: 'channel-updated', data: {id, name }})); + }); +} \ No newline at end of file diff --git a/backend/src/db.ts b/backend/src/db.ts new file mode 100644 index 0000000..0e1286a --- /dev/null +++ b/backend/src/db.ts @@ -0,0 +1,67 @@ +import Database from 'better-sqlite3'; +import { DB_PATH } from './config'; +import { logger } from './globals'; +import { readdir, readFile } from "fs/promises"; +import { join, dirname } from "path"; + +export let FTS5Enabled = true; + +export const initializeDB = () => { + logger.info("Checking fts"); + const ftstest = db.prepare(`pragma compile_options;`); + const result = ftstest.all() as { compile_options: string }[]; + if (result.find((o) => o["compile_options"].includes("ENABLE_FTS5"))) { + logger.info("FTS5 is enabled"); + } else { + logger.info("FTS5 is not enabled. Attempting to load..."); + try { + db.loadExtension('./fts5'); + } catch (e) { + logger.warn("Failed to load FTS5 extension. Disabling FTS5"); + FTS5Enabled = false; + } + } + + return FTS5Enabled; +} + +export const migrate = async () => { + logger.info(`Checking for migrations...`); + const result = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='meta'`); + if (result.all().length === 0) { + logger.info(`Creating meta table...`); + db.exec(`CREATE TABLE meta (version INTEGER)`); + db.exec(`INSERT INTO meta (version) VALUES (-1)`); + } + + const version = db.prepare(`SELECT version FROM meta`).get() as { version: number }; + logger.info(`Migration version: ${version.version}`); + // we are in bun.js. use its API's to read the file list. + logger.info(`Searching for migrations in ${join("migrations")}`); + const files = await readdir(join("migrations")); + + for (const file of files) { + const [fileVersion, ...rest] = file.split("_"); + logger.info(`Found migration ${fileVersion}`); + if (fileVersion && Number(fileVersion) > version.version) { + logger.info(`Running migration ${file}`); + const sql = new TextDecoder().decode(await readFile(join(`migrations/${file}`))); + db.exec(sql); + const query = db.prepare(`UPDATE meta SET version = ($version)`); + const res = query.run({ version: fileVersion }) + logger.info(`Migration ${file} done`); + } + } + logger.info(`Migrations done`); +} + +logger.info(`Loading database at ${DB_PATH}`); + +export const db = new Database(DB_PATH); + + + + + +initializeDB(); +migrate(); \ No newline at end of file diff --git a/backend/src/globals.ts b/backend/src/globals.ts new file mode 100644 index 0000000..a8eae67 --- /dev/null +++ b/backend/src/globals.ts @@ -0,0 +1,14 @@ +import { EventEmitter } from "events"; +import { Scheduler } from "./utils/scheduler"; +import { jobs } from "./jobs"; +import { Logger } from "./logging/logger"; +import { ConsoleAdapter } from "./logging/adapters/console-adapter"; + +export const events = new EventEmitter(); +export const scheduler = new Scheduler(); +export const logger = new Logger(); +logger.addAdapter(new ConsoleAdapter()); + +jobs.forEach((job) => { + job(); +}); \ No newline at end of file diff --git a/backend/src/image-describe-test.ts b/backend/src/image-describe-test.ts new file mode 100644 index 0000000..67efed2 --- /dev/null +++ b/backend/src/image-describe-test.ts @@ -0,0 +1,6 @@ +import { loadImage, describeWithOpenAI, describeImage } from "./services/image-description"; +import { DESCRIBE_IMAGES_PROMPT, OPENAI_API_KEY } from "./config"; + +(async () => { + console.log(await describeImage("d:/avatar.jpg")); +})(); diff --git a/backend/src/jobs/describe-image.ts b/backend/src/jobs/describe-image.ts new file mode 100644 index 0000000..ba64a1d --- /dev/null +++ b/backend/src/jobs/describe-image.ts @@ -0,0 +1,17 @@ +import type { Message } from "../../types"; +import { events, logger } from "../globals" +import { describeImage } from "../services/image-description"; +import { getMessage, updateMessage } from "../services/message-service"; + +export const describeImageJob = () => { + events.on("file-uploaded", (id, channelId, messageId, filePath, fileType, fileSize, originalName) => { + if (fileType.includes("image")) { + describeImage(filePath).then((description) => { + const msg = getMessage(messageId) as any; + updateMessage(messageId, `${msg.content ? msg.content : ''}\n\n${description}`); + }).catch((e) => { + logger.warn(`Failed to describe image: ${e.message}`); + }); + } + }); +} \ No newline at end of file diff --git a/backend/src/jobs/index.ts b/backend/src/jobs/index.ts new file mode 100644 index 0000000..ff32f4c --- /dev/null +++ b/backend/src/jobs/index.ts @@ -0,0 +1,7 @@ +import { describeImageJob } from "./describe-image"; +import { scheduleVacuum } from "./vacuum"; + +export const jobs = [ + scheduleVacuum, + describeImageJob +] \ No newline at end of file diff --git a/backend/src/jobs/vacuum.ts b/backend/src/jobs/vacuum.ts new file mode 100644 index 0000000..5f3504f --- /dev/null +++ b/backend/src/jobs/vacuum.ts @@ -0,0 +1,9 @@ +import { Scheduler, TimeUnit } from "../utils/scheduler"; +import { scheduler } from "../globals"; +import { db } from "../db"; + +export const scheduleVacuum = () => { + scheduler.register('vacuum', () => { + db.exec('VACUUM'); + }, 1, TimeUnit.DAY); +} diff --git a/backend/src/logging/adapter.ts b/backend/src/logging/adapter.ts new file mode 100644 index 0000000..6a0a088 --- /dev/null +++ b/backend/src/logging/adapter.ts @@ -0,0 +1,15 @@ +import { type LogEntry } from "./log-entry"; + +export abstract class LogAdapter { + public log(message: LogEntry) { + if (this.shouldLog(message)) { + this.logImpl(message); + } + } + + public abstract logImpl(message: LogEntry): boolean; + + public shouldLog(message: LogEntry): boolean { + return true; + } +} \ No newline at end of file diff --git a/backend/src/logging/adapters/console-adapter.ts b/backend/src/logging/adapters/console-adapter.ts new file mode 100644 index 0000000..e895e44 --- /dev/null +++ b/backend/src/logging/adapters/console-adapter.ts @@ -0,0 +1,10 @@ +import { LogAdapter } from "../adapter"; +import { type LogEntry, LogLevel } from "../log-entry"; + +export class ConsoleAdapter extends LogAdapter { + public logImpl(message: LogEntry): boolean { + console.log(`${LogLevel[message.level]}: ${message.message}; ${new Date(message.timestamp).toLocaleString()}:`); + if (message.additionalInfo) console.log(message.additionalInfo); + return true; + } +} \ No newline at end of file diff --git a/backend/src/logging/log-entry.ts b/backend/src/logging/log-entry.ts new file mode 100644 index 0000000..01bcfc5 --- /dev/null +++ b/backend/src/logging/log-entry.ts @@ -0,0 +1,12 @@ +export interface LogEntry { + level: LogLevel; + timestamp: number; + message: string; + additionalInfo?: any; +} + +export enum LogLevel { + info, + warning, + critical +} \ No newline at end of file diff --git a/backend/src/logging/logger.ts b/backend/src/logging/logger.ts new file mode 100644 index 0000000..7df3895 --- /dev/null +++ b/backend/src/logging/logger.ts @@ -0,0 +1,49 @@ +import { LogAdapter } from "./adapter"; +import { type LogEntry, LogLevel } from "./log-entry"; + +export class Logger { + private adapters: LogAdapter[]; + + public constructor() { + this.adapters = []; + } + + public log(message: LogEntry) { + this.adapters.forEach((adapter) => adapter.log(message)); + } + + public info(message: string, additionalInfo?: any) { + this.log({ + level: LogLevel.info, + message, + additionalInfo, + timestamp: Date.now() + }) + } + + public warn(message: string, additionalInfo?: any) { + this.log({ + level: LogLevel.warning, + message, + additionalInfo, + timestamp: Date.now() + }) + } + + public critical(message: string, additionalInfo?: any) { + this.log({ + level: LogLevel.critical, + message, + additionalInfo, + timestamp: Date.now() + }) + } + + public addAdapter(adapter: LogAdapter) { + this.adapters.push(adapter); + } + + public removeAdapter(adapter: LogAdapter) { + this.adapters.slice(this.adapters.indexOf(adapter), 1); + } +} \ No newline at end of file diff --git a/backend/src/middleware/auth.ts b/backend/src/middleware/auth.ts new file mode 100644 index 0000000..e5ac86f --- /dev/null +++ b/backend/src/middleware/auth.ts @@ -0,0 +1,15 @@ +import type { NextFunction, Request, Response } from "express"; +import { SECRET_KEY } from "../config"; +import { logger } from "../globals"; + +export const authenticate = (req: Request, res: Response, next: NextFunction) => { + const token = req.headers['authorization']; + if (!token) { + return res.status(403).json({ error: 'No token provided' }); + } + if (token === SECRET_KEY) { + next(); + } else { + res.status(401).json({ error: "Unauthenticated" }) + } +} diff --git a/backend/src/routes/channel.ts b/backend/src/routes/channel.ts new file mode 100644 index 0000000..3b448f2 --- /dev/null +++ b/backend/src/routes/channel.ts @@ -0,0 +1,10 @@ +import { Router } from 'express'; +import * as ChannelController from '../controllers/channel-controller'; +import { authenticate } from '../middleware/auth'; + +export const router = Router({mergeParams: true}); + +router.post('/', authenticate, ChannelController.createChannel); +router.get('/', authenticate, ChannelController.getChannels); +router.delete('/:channelId', authenticate, ChannelController.deleteChannel); +router.put('/:channelId/merge', authenticate, ChannelController.mergeChannel); diff --git a/backend/src/routes/file.ts b/backend/src/routes/file.ts new file mode 100644 index 0000000..a0b9ef0 --- /dev/null +++ b/backend/src/routes/file.ts @@ -0,0 +1,9 @@ +import { Router } from "express"; +import { upload } from "../utils/multer"; +import * as FileController from "../controllers/file-controller"; +import { authenticate } from "../middleware/auth"; + +export const router = Router({mergeParams: true}); + +router.post("/", authenticate, upload.single("file"), FileController.uploadFile); +router.get("/", authenticate, FileController.getFiles); diff --git a/backend/src/routes/message.ts b/backend/src/routes/message.ts new file mode 100644 index 0000000..b3d3f42 --- /dev/null +++ b/backend/src/routes/message.ts @@ -0,0 +1,11 @@ +import { Router } from 'express'; +import * as MessageController from '../controllers/message-controller'; +import { authenticate } from '../middleware/auth'; + +export const router = Router({mergeParams: true}); + +router.post('/', authenticate, MessageController.createMessage); +router.put('/:messageId', authenticate, MessageController.updateMessage); +router.delete('/:messageId', authenticate, MessageController.deleteMessage); +router.get('/', authenticate, MessageController.getMessages); + diff --git a/backend/src/routes/search.ts b/backend/src/routes/search.ts new file mode 100644 index 0000000..c8c3e8c --- /dev/null +++ b/backend/src/routes/search.ts @@ -0,0 +1,7 @@ +import { Router } from "express"; +import * as SearchController from "../controllers/search-controller"; +import { authenticate } from "../middleware/auth"; + +export const router = Router({mergeParams: true}); + +router.get("/", authenticate, SearchController.search); diff --git a/backend/src/server.ts b/backend/src/server.ts new file mode 100644 index 0000000..58f8e1a --- /dev/null +++ b/backend/src/server.ts @@ -0,0 +1,55 @@ +import { app } from "./app"; +import { createServer } from "http"; +import { WebSocket, WebSocketServer } from "ws"; +import { attachEvents } from "./controllers/websocket-controller"; +import { logger } from "./globals"; +import selfSigned from "selfsigned"; + +const PORT = process.env.PORT || 3000; + +const server = createServer(app); + +const wss = new WebSocketServer({ server }); + +wss.on('connection', (ws: WebSocket) => { + logger.info('Websocket client connected'); + + attachEvents(ws); + + ws.on('message', (message: string) => { + logger.info(`Received message: ${message}`); + }); + + ws.on('close', () => { + logger.info('Websocket client disconnected'); + }); +}); + +server.listen(PORT, () => { + logger.info(`Server is running on http://localhost:${PORT}`); +}); + +const getOrCreateCertificate = async () => { + if (process.env.USE_SSL === '1') { + if (!process.env.SSL_KEY || !process.env.SSL_CERT) { + return await createSelfSignedSSLCert(); + } + return { + key: process.env.SSL_KEY, + cert: process.env.SSL_CERT + }; + } + return null; +} + +const createSelfSignedSSLCert = async () => { + const selfsigned = await import('selfsigned'); + const pems = selfsigned.generate([{ name: 'Notebrook Self Signed Auto Generated Key', value: 'localhost' }], { + keySize: 2048, + days: 365 + }); + return { + key: pems.private, + cert: pems.cert + }; +} \ No newline at end of file diff --git a/backend/src/services/channel-service.ts b/backend/src/services/channel-service.ts new file mode 100644 index 0000000..c53b1eb --- /dev/null +++ b/backend/src/services/channel-service.ts @@ -0,0 +1,37 @@ +import { db } from "../db"; +import { events } from "../globals"; + +export const createChannel = async (name: string) => { + const query = db.prepare(`INSERT INTO channels (name) VALUES ($name)`); + const result = query.run({ name: name }); + events.emit('channel-created', { id: result.lastInsertRowid, name }); + return { id: result.lastInsertRowid, name }; +} + +export const deleteChannel = async (id: string) => { + const query = db.prepare(`DELETE FROM channels WHERE id = ($channelId)`); + const result = query.run({channelId: id}); + // No need to manually delete messages and files as they are set to cascade on delete in the schema + events .emit('channel-deleted', id); + return result; +} + +export const getChannels = async () => { + const query = db.prepare(`SELECT * FROM channels`); + const rows = query.all(); + return rows; +} + +export const mergeChannel = async (channelId: string, targetChannelId: string) => { + const query = db.prepare(`UPDATE messages SET channelId = $targetChannelId WHERE channelId = $channelId`); + const result = query.run({ channelId: channelId, targetChannelId: targetChannelId }); + events.emit('channel-merged', channelId, targetChannelId); + return result; +} + +export const updateChannel = async (id: string, name: string) => { + const query = db.prepare(`UPDATE channels SET name = $name WHERE id = $id`); + const result = query.run({ id: id, name: name }); + events.emit('channel-updated', id, name); + return result; +} \ No newline at end of file diff --git a/backend/src/services/file-service.ts b/backend/src/services/file-service.ts new file mode 100644 index 0000000..3dba4f7 --- /dev/null +++ b/backend/src/services/file-service.ts @@ -0,0 +1,21 @@ +import { db } from "../db"; +import { events } from "../globals"; + +export const uploadFile = async (channelId: string, messageId: string, filePath: string, fileType: string, fileSize: number, originalName: string) => { + const query = db.prepare(`INSERT INTO files (channelId, filePath, fileType, fileSize, originalName) VALUES ($channelId, $filePath, $fileType, $fileSize, $originalName)`); + const result = query.run({ channelId: channelId, filePath: filePath, fileType: fileType, fileSize: fileSize, originalName: originalName }); + + const fileId = result.lastInsertRowid; + + const updateQuery = db.prepare(`UPDATE messages SET fileId = $fileId WHERE id = $messageId`); + const result2 = updateQuery.run({ fileId: fileId, messageId: messageId }); + + events.emit('file-uploaded', result.lastInsertRowid, channelId, messageId, filePath, fileType, fileSize, originalName); + return result2; '' +} + +export const getFiles = async (messageId: string) => { + const query = db.prepare(`SELECT * FROM files WHERE messageId = $messageId`); + const rows = query.all({ messageId: messageId }); + return rows; +} \ No newline at end of file diff --git a/backend/src/services/image-description.ts b/backend/src/services/image-description.ts new file mode 100644 index 0000000..9ef38e1 --- /dev/null +++ b/backend/src/services/image-description.ts @@ -0,0 +1,83 @@ +import { Ollama } from "ollama"; +import OpenAI from "openai"; +import { DESCRIBE_IMAGES_API, DESCRIBE_IMAGES_MAX_TOKENS, DESCRIBE_IMAGES_PROMPT, DESCRIBE_IMAGES_TEMPERATURE, OLLAMA_MODEL, OLLAMA_URL, OPENAI_API_KEY, OPENAI_MODEL } from "../config"; +import { readFile } from "fs/promises"; +import sharp from "sharp"; + +export const describeWithOllama = async (image: Buffer) => { + const client = new Ollama({ host: OLLAMA_URL }); + + const response = await client.chat({ + model: OLLAMA_MODEL, + options: { + temperature: DESCRIBE_IMAGES_TEMPERATURE, + }, + messages: [ + { role: "system", content: DESCRIBE_IMAGES_PROMPT }, + { role: "user", images: [image], content: "Describe this image." }, + ] + }); + return response.message.content; +} + +export const describeWithOpenAI = async (image: Buffer) => { + const client = new OpenAI({ + apiKey: OPENAI_API_KEY, + }); + const response = await client.chat.completions.create({ + model: OPENAI_MODEL, + max_tokens: DESCRIBE_IMAGES_MAX_TOKENS, + temperature: DESCRIBE_IMAGES_TEMPERATURE, + messages: [ + { role: "system", content: DESCRIBE_IMAGES_PROMPT }, + { role: "user", content: [{ type: "text", text: "Describe the following image in a detailed but concise manner." }, { type: "image_url", image_url: { url: imageToBase64URL(image) } }] }, + ] + }) + return response.choices[0].message.content; +} + +export const describeImage = async (filePath: string) => { + const image = await loadImage(filePath); + if (DESCRIBE_IMAGES_API === "ollama") { + return describeWithOllama(image); + } else { + return describeWithOpenAI(image); + } + return ""; +} + +export const loadImage = async (filePath: string) => { + return processImage(filePath); +} + +async function processImage(imagePath: string): Promise { + try { + const image = sharp(imagePath); + const metadata = await image.metadata(); + const maxDimension = 1024; + + // Check if the image needs to be resized + let resizedImage = image; + if (metadata.width && metadata.height && (metadata.width > maxDimension || metadata.height > maxDimension)) { + resizedImage = image.resize({ + width: Math.min(metadata.width, maxDimension), + height: Math.min(metadata.height, maxDimension), + fit: sharp.fit.inside, + withoutEnlargement: true + }); + } + + // Convert the image to JPG + const jpgBuffer = await resizedImage.jpeg().toBuffer(); + + return jpgBuffer; + } catch (error) { + console.error('Error processing the image:', error); + throw new Error('Failed to process the image.'); + } +} + +export const imageToBase64URL = (input: Buffer) => { + return `data:image/jpeg;base64,${input.toString('base64')}`; +} + diff --git a/backend/src/services/message-service.ts b/backend/src/services/message-service.ts new file mode 100644 index 0000000..f27cf38 --- /dev/null +++ b/backend/src/services/message-service.ts @@ -0,0 +1,83 @@ +import { db, FTS5Enabled } from "../db"; +import { events } from "../globals"; + +export const createMessage = async (channelId: string, content: string) => { + const query = db.prepare(`INSERT INTO messages (channelId, content) VALUES ($channelId, $content)`); + const result = query.run({ channelId: channelId, content: content }); + + const messageId = result.lastInsertRowid; + console.log(`Adding message for search with id ${messageId}`); + // Insert into FTS table if FTS is enabled. + if (FTS5Enabled) { + const query2 = db.prepare(`INSERT INTO messages_fts (rowid, content) VALUES ($rowId, $content)`); + const result2 = query2.run({ rowId: messageId, content: content }); + } + + events.emit('message-created', messageId, channelId, content); + return messageId; +} + +export const updateMessage = async (messageId: string, content: string, append: boolean = false) => { + const query = db.prepare(`UPDATE messages SET content = $content WHERE id = $id`); + const result = query.run({ content: content, id: messageId }); + + + + + // Update FTS table if enabled + if (!FTS5Enabled) { + const query2 = db.prepare(`INSERT INTO messages_fts (rowid, content) VALUES ($rowId, $content) ON CONFLICT(rowid) DO UPDATE SET content = excluded.content`); + const result2 = query.run({ rowId: messageId, content: content }); + } + events.emit('message-updated', messageId, content); + return result; +} + +export const deleteMessage = async (messageId: string) => { + const query = db.prepare(`DELETE FROM messages WHERE id = $id`); + const result = query.run({ id: messageId }); + + // Remove from FTS table if enabled + if (FTS5Enabled) { + const query2 = db.prepare(`DELETE FROM messages_fts WHERE rowid = $rowId`); + const result2 = query2.run({ rowId: messageId }); + } + events.emit('message-deleted', messageId); + return result; +} + +export const getMessages = async (channelId: string) => { + const query = db.prepare(` + SELECT + messages.id, messages.channelId, messages.content, messages.createdAt, + files.id as fileId, files.filePath, files.fileType, files.createdAt as fileCreatedAt, files.originalName, files.fileSize + FROM + messages + LEFT JOIN + files + ON + messages.fileId = files.id + WHERE + messages.channelId = $channelId + `); + const rows = query.all({ channelId: channelId }); + return rows; +} + +export const getMessage = async (id: string) => { + const query = db.prepare(` + SELECT + messages.id, messages.channelId, messages.content, messages.createdAt, + files.id as fileId, files.filePath, files.fileType, files.createdAt as fileCreatedAt, files.originalName, files.fileSize + FROM + messages + LEFT JOIN + files + ON + messages.fileId = files.id + WHERE + messages.id = $id + `); + const row = query.get({ id: id }); + return row; +} \ No newline at end of file diff --git a/backend/src/services/search-service.ts b/backend/src/services/search-service.ts new file mode 100644 index 0000000..d0f39aa --- /dev/null +++ b/backend/src/services/search-service.ts @@ -0,0 +1,44 @@ +import { db, FTS5Enabled } from "../db"; + +export const search = async (query: string, channelId?: string) => { + let sql: string; + let params: any; + + if (FTS5Enabled) { + if (channelId) { + sql = ` + SELECT messages.id, messages.channelId, messages.content, messages.createdAt + FROM messages_fts + JOIN messages ON messages_fts.rowid = messages.id + WHERE messages_fts MATCH lower($query) AND messages.channelId = $channelId + `; + params = { channelId: channelId, query: (query || '').toString().toLowerCase() }; + } else { + sql = ` + SELECT messages.id, messages.channelId, messages.content, messages.createdAt + FROM messages_fts + JOIN messages ON messages_fts.rowid = messages.id + WHERE messages_fts MATCH lower($query) + `; + params = { query: (query || '').toString().toLowerCase() }; + } + } else { + console.log("Performing search without FTS5. This might be very slow."); + if (channelId) { + sql = ` + SELECT * FROM messages WHERE LOWER(content) LIKE '%' || LOWER($query) || '%' AND channelId = $channelId + `; + params = { channelId: channelId, query: query }; + } else { + sql = ` + SELECT * FROM messages WHERE LOWER(content) LIKE '%' || LOWER($query) || '%' + `; + params = { query: query }; + } + } + + const sqlquery = db.prepare(sql); + const rows = sqlquery.all(params); + + return rows; +} \ No newline at end of file diff --git a/backend/src/services/websocket-service.ts b/backend/src/services/websocket-service.ts new file mode 100644 index 0000000..e69de29 diff --git a/backend/src/utils/multer.ts b/backend/src/utils/multer.ts new file mode 100644 index 0000000..893681a --- /dev/null +++ b/backend/src/utils/multer.ts @@ -0,0 +1,4 @@ +import multer from "multer"; +import { UPLOAD_DIR } from "../config"; + +export const upload = multer({ dest: UPLOAD_DIR }); diff --git a/backend/src/utils/scheduler.ts b/backend/src/utils/scheduler.ts new file mode 100644 index 0000000..f421209 --- /dev/null +++ b/backend/src/utils/scheduler.ts @@ -0,0 +1,54 @@ +export enum TimeUnit { + SECOND = 1000, + MINUTE = 60 * 1000, + HOUR = 60 * 60 * 1000, + DAY = 24 * 60 * 60 * 1000, + WEEK = 7 * 24 * 60 * 60 * 1000 +} + +export type Task = () => void; + +export interface TaskEntry { + id: Timer; + task: Task; + remainingRuns: number; +} + +export class Scheduler { + private tasks: Map = new Map(); + + static toMilliseconds(time: number, unit: TimeUnit): number { + return time * unit; + } + + register(taskName: string, task: Task, delay: number, unit: TimeUnit, runs: number = Infinity): void { + if (this.tasks.has(taskName)) { + throw new Error(`Task ${taskName} is already registered.`); + } + const performTask = () => { + task(); + const taskEntry = this.tasks.get(taskName); + if (taskEntry) { + taskEntry.remainingRuns--; + if (taskEntry.remainingRuns > 0) { + taskEntry.id = setTimeout(performTask, Scheduler.toMilliseconds(delay, unit)); + } else { + this.tasks.delete(taskName); + } + } + }; + this.tasks.set(taskName, { id: setTimeout(performTask, Scheduler.toMilliseconds(delay, unit)), task, remainingRuns: runs }); + } + + unregister(taskName: string): void { + const taskEntry = this.tasks.get(taskName); + if (taskEntry) { + clearTimeout(taskEntry.id); + this.tasks.delete(taskName); + } + } + + getTasks(): Map { + return this.tasks; + } +} \ No newline at end of file diff --git a/backend/tsconfig.json b/backend/tsconfig.json new file mode 100644 index 0000000..238655f --- /dev/null +++ b/backend/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} diff --git a/backend/types.ts b/backend/types.ts new file mode 100644 index 0000000..43ffd46 --- /dev/null +++ b/backend/types.ts @@ -0,0 +1,21 @@ +export interface Channel { + id: number; + name: string; + created_at: string; +} + +export interface Message { + id: number; + channel_id: number; + content: string; + created_at: string; +} + +export interface File { + id: number; + channel_id: number; + message_id: number; + file_path: string; + file_type: string; + created_at: string; +} \ No newline at end of file diff --git a/dockerfile b/dockerfile new file mode 100644 index 0000000..aff7307 --- /dev/null +++ b/dockerfile @@ -0,0 +1,36 @@ +FROM node:22-slim AS base +WORKDIR /usr/src/app + +FROM base AS install + +COPY backend/package.json backend/package-lock.json /temp/dev/backend/ +COPY frontend/package.json frontend/package-lock.json /temp/dev/frontend/ + +RUN cd /temp/dev/backend && npm install +RUN cd /temp/dev/frontend && npm install + +RUN mkdir -p /temp/prod/backend /temp/prod/frontend +COPY backend/package.json backend/package-lock.json /temp/prod/backend/ +COPY frontend/package.json frontend/package-lock.json /temp/prod/frontend/ + +RUN cd /temp/prod/backend && npm install --production +RUN cd /temp/prod/frontend && npm install --production + +FROM install AS build-frontend +WORKDIR /usr/src/app/frontend +COPY frontend/ . +COPY --from=install /temp/dev/frontend/node_modules node_modules +RUN npm run build + +FROM base AS release +WORKDIR /usr/src/app +COPY backend/ backend/ +COPY --from=install /temp/prod/backend/node_modules backend/node_modules +COPY --from=install /temp/prod/frontend/node_modules frontend/node_modules + +COPY --from=build-frontend /usr/src/app/frontend/dist backend/public + +USER node +WORKDIR /usr/src/app/backend +EXPOSE 3000/tcp +ENTRYPOINT [ "npm", "run", "start"] diff --git a/etc/systemd/backend.service b/etc/systemd/backend.service new file mode 100644 index 0000000..5a4827c --- /dev/null +++ b/etc/systemd/backend.service @@ -0,0 +1,14 @@ +[Unit] +Description=backend Service +After=network.target + +[Service] +User=notebrook +WorkingDirectory=/home/notebrook/backend +ExecStart=/usr/bin/npm start +Restart=always +Environment=PATH=/usr/bin:/usr/local/bin +Environment=NODE_ENV=production + +[Install] +WantedBy=multi-user.target diff --git a/etc/systemd/frontend.service b/etc/systemd/frontend.service new file mode 100644 index 0000000..dd4db92 --- /dev/null +++ b/etc/systemd/frontend.service @@ -0,0 +1,14 @@ +[Unit] +Description=frontend Service notebrook +After=network.target + +[Service] +User=notebrook +WorkingDirectory=/home/notebrook/frontend +ExecStart=/usr/bin/npm run dev -- --host +Restart=always +Environment=PATH=/usr/bin:/usr/local/bin +Environment=NODE_ENV=production + +[Install] +WantedBy=multi-user.target diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..00f7aef --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,75 @@ + + + + + + + Notebrook + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + \ No newline at end of file diff --git a/frontend/manifest.webmanifest b/frontend/manifest.webmanifest new file mode 100644 index 0000000..d19acce --- /dev/null +++ b/frontend/manifest.webmanifest @@ -0,0 +1,31 @@ +{ + "name": "Notebrook", + "short_name": "Notebrook", + "description": "Stream of conciousness accessible note taking", + "start_url": "/", + "display": "standalone", + "background_color": "#ffffff", + "theme_color": "#ffffff", + "icons": [ + { + "src": "/icons/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/icons/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "/icons/apple-touch-icon.png", + "sizes": "180x180", + "type": "image/png" + }, + { + "src": "/icons/mstile-150x150.png", + "sizes": "150x150", + "type": "image/png" + } + ] +} \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..0b53354 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,4949 @@ +{ + "name": "notebrook-frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "notebrook-frontend", + "version": "0.0.0", + "dependencies": { + "idb-keyval": "^6.2.1", + "vite-plugin-pwa": "^0.20.1" + }, + "devDependencies": { + "typescript": "^5.5.3", + "vite": "^5.4.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@apideck/better-ajv-errors": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz", + "integrity": "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==", + "dependencies": { + "json-schema": "^0.4.0", + "jsonpointer": "^5.0.0", + "leven": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "ajv": ">=8" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", + "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.25.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.5.tgz", + "integrity": "sha512-abd43wyLfbWoxC6ahM8xTkqLpGB2iWBVyuKC9/srhFunCd1SDNrV1s72bBpK4hLj8KLzHBBcOblvLQZBNw9r3w==", + "dependencies": { + "@babel/types": "^7.25.4", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz", + "integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==", + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", + "dependencies": { + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.4.tgz", + "integrity": "sha512-ro/bFs3/84MDgDmMwbcHgDa8/E6J3QKNTk4xJJnVeFtGE+tL0K26E3pNxhYz2b67fJpt7Aphw5XcploKXuCvCQ==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/helper-replace-supers": "^7.25.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/traverse": "^7.25.4", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.2.tgz", + "integrity": "sha512-+wqVGP+DFmqwFD3EH6TMTfUNeqDehV3E/dl+Sd54eaXqm17tEUNbEIn4sVivVowbvUpOtIGxdo3GoXyDH9N/9g==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", + "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", + "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", + "dependencies": { + "@babel/traverse": "^7.24.8", + "@babel/types": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", + "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.0.tgz", + "integrity": "sha512-NhavI2eWEIz/H9dbrG0TuOicDhNexze43i5z7lEqwYm0WEZVTwnPpA0EafUTP7+6/W79HWIP2cTe3Z5NiSTVpw==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-wrap-function": "^7.25.0", + "@babel/traverse": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz", + "integrity": "sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/traverse": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", + "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.0.tgz", + "integrity": "sha512-s6Q1ebqutSiZnEjaofc/UKDyC4SbzV5n5SrA2Gq8UawLycr3i04f1dX4OzoQVnexm6aOCh37SQNYlJ/8Ku+PMQ==", + "dependencies": { + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.0", + "@babel/types": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz", + "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==", + "dependencies": { + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.4.tgz", + "integrity": "sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA==", + "dependencies": { + "@babel/types": "^7.25.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.3.tgz", + "integrity": "sha512-wUrcsxZg6rqBXG05HG1FPYgsP6EvwF4WpBbxIpWIIYnH8wG0gzx3yZY3dtEHas4sTAOGkbTsc9EGPxwff8lRoA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.0.tgz", + "integrity": "sha512-Bm4bH2qsX880b/3ziJ8KD711LT7z4u8CFudmjqle65AZj/HNUFhEf90dqYv6O86buWvSBmeQDjv0Tn2aF/bIBA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.0.tgz", + "integrity": "sha512-lXwdNZtTmeVOOFtwM/WDe7yg1PL8sYhRk/XH0FzbR2HDQ0xC+EnQ/JHeoMYSavtU115tnUk0q9CDyq8si+LMAA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz", + "integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.0.tgz", + "integrity": "sha512-tggFrk1AIShG/RUQbEwt2Tr/E+ObkfwrPjR6BjbRvsx24+PSjK8zrq0GWPNCjo8qpRx4DuJzlcvWJqlm+0h3kw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz", + "integrity": "sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", + "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.4.tgz", + "integrity": "sha512-jz8cV2XDDTqjKPwVPJBIjORVEmSGYhdRa8e5k5+vN+uwcjSrSxUaebBRa4ko1jqNF2uxyg8G6XYk30Jv285xzg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-remap-async-to-generator": "^7.25.0", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/traverse": "^7.25.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", + "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", + "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.0.tgz", + "integrity": "sha512-yBQjYoOjXlFv9nlXb3f1casSHOZkWr29NX+zChVanLg5Nc157CrbEX9D7hxxtTpuFy7Q0YzmmWfJxzvps4kXrQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.4.tgz", + "integrity": "sha512-nZeZHyCWPfjkdU5pA/uHiTaDAFUEqkpzf1YoQT2NeSynCGYq9rxfyI3XpQbfx/a0hSnFH6TGlEXvae5Vi7GD8g==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.4", + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz", + "integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.4.tgz", + "integrity": "sha512-oexUfaQle2pF/b6E0dwsxQtAol9TLSO88kQvym6HHBWFliV2lGdrPieX+WgMRLSJDVzdYywk7jXbLPuO2KLTLg==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-replace-supers": "^7.25.0", + "@babel/traverse": "^7.25.4", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", + "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/template": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz", + "integrity": "sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", + "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", + "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.0.tgz", + "integrity": "sha512-YLpb4LlYSc3sCUa35un84poXoraOiQucUTTu8X1j18JV+gNa8E0nyUf/CjZ171IRGr4jEguF+vzJU66QZhn29g==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz", + "integrity": "sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz", + "integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==", + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz", + "integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", + "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.25.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.1.tgz", + "integrity": "sha512-TVVJVdW9RKMNgJJlLtHsKDTydjZAbwIsn6ySBPQaEAUU5+gVvlJt/9nRmqVbsV/IBanRjzWoaAQKLoamWVOUuA==", + "dependencies": { + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz", + "integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.2.tgz", + "integrity": "sha512-HQI+HcTbm9ur3Z2DkO+jgESMAMcYLuN/A7NRw9juzxAezN9AvqvUTnpKP/9kkYANz6u7dFlAyOu44ejuGySlfw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz", + "integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", + "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", + "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", + "dependencies": { + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz", + "integrity": "sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==", + "dependencies": { + "@babel/helper-module-transforms": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-simple-access": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz", + "integrity": "sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==", + "dependencies": { + "@babel/helper-module-transforms": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", + "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", + "dependencies": { + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz", + "integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", + "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz", + "integrity": "sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz", + "integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz", + "integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==", + "dependencies": { + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", + "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz", + "integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz", + "integrity": "sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", + "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.4.tgz", + "integrity": "sha512-ao8BG7E2b/URaUQGqN3Tlsg+M3KlHY6rJ1O1gXAEUnZoyNQnvKyH87Kfg+FoxSeyWUB8ISZZsC91C44ZuBFytw==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.4", + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz", + "integrity": "sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", + "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", + "integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", + "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", + "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", + "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz", + "integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", + "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz", + "integrity": "sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", + "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz", + "integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz", + "integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.4.tgz", + "integrity": "sha512-qesBxiWkgN1Q+31xUE9RcMk79eOXXDCv6tfyGMRSs4RGlioSg2WVyQAm07k726cSE56pa+Kb0y9epX2qaXzTvA==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.2", + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.4.tgz", + "integrity": "sha512-W9Gyo+KmcxjGahtt3t9fb14vFRWvPpu5pT6GBlovAK6BTBcxgjfVMSQCfJl4oi35ODrxP6xx2Wr8LNST57Mraw==", + "dependencies": { + "@babel/compat-data": "^7.25.4", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-option": "^7.24.8", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.3", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.0", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.0", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.0", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.24.7", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.25.4", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoped-functions": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.25.4", + "@babel/plugin-transform-class-static-block": "^7.24.7", + "@babel/plugin-transform-classes": "^7.25.4", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-dotall-regex": "^7.24.7", + "@babel/plugin-transform-duplicate-keys": "^7.24.7", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.0", + "@babel/plugin-transform-dynamic-import": "^7.24.7", + "@babel/plugin-transform-exponentiation-operator": "^7.24.7", + "@babel/plugin-transform-export-namespace-from": "^7.24.7", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.25.1", + "@babel/plugin-transform-json-strings": "^7.24.7", + "@babel/plugin-transform-literals": "^7.25.2", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-member-expression-literals": "^7.24.7", + "@babel/plugin-transform-modules-amd": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-modules-systemjs": "^7.25.0", + "@babel/plugin-transform-modules-umd": "^7.24.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-new-target": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-object-super": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.25.4", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-property-literals": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-reserved-words": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-template-literals": "^7.24.7", + "@babel/plugin-transform-typeof-symbol": "^7.24.8", + "@babel/plugin-transform-unicode-escapes": "^7.24.7", + "@babel/plugin-transform-unicode-property-regex": "^7.24.7", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.25.4", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.6", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.37.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" + }, + "node_modules/@babel/runtime": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.4.tgz", + "integrity": "sha512-DSgLeL/FNcpXuzav5wfYvHCGvynXkJbn3Zvc3823AEe9nPwW9IK4UoCSS5yGymmQzN0pCPvivtgS6/8U2kkm1w==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.4.tgz", + "integrity": "sha512-VJ4XsrD+nOvlXyLzmLzUs/0qjFS4sK30te5yEFlvbbUNEgKaVb2BHZUpAL+ttLPQAHNrsI3zZisbfha5Cvr8vg==", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.4", + "@babel/parser": "^7.25.4", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.4", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.4.tgz", + "integrity": "sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ==", + "dependencies": { + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.2.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", + "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-builtin-module": "^3.2.1", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-terser": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", + "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", + "dependencies": { + "serialize-javascript": "^6.0.1", + "smob": "^1.0.0", + "terser": "^5.17.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.0.tgz", + "integrity": "sha512-WTWD8PfoSAJ+qL87lE7votj3syLavxunWhzCnx3XFxFiI/BA/r3X7MUM8dVrH8rb2r4AiO8jJsr3ZjdaftmnfA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.0.tgz", + "integrity": "sha512-a1sR2zSK1B4eYkiZu17ZUZhmUQcKjk2/j9Me2IDjk1GHW7LB5Z35LEzj9iJch6gtUfsnvZs1ZNyDW2oZSThrkA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.0.tgz", + "integrity": "sha512-zOnKWLgDld/svhKO5PD9ozmL6roy5OQ5T4ThvdYZLpiOhEGY+dp2NwUmxK0Ld91LrbjrvtNAE0ERBwjqhZTRAA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.0.tgz", + "integrity": "sha512-7doS8br0xAkg48SKE2QNtMSFPFUlRdw9+votl27MvT46vo44ATBmdZdGysOevNELmZlfd+NEa0UYOA8f01WSrg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.0.tgz", + "integrity": "sha512-pWJsfQjNWNGsoCq53KjMtwdJDmh/6NubwQcz52aEwLEuvx08bzcy6tOUuawAOncPnxz/3siRtd8hiQ32G1y8VA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.0.tgz", + "integrity": "sha512-efRIANsz3UHZrnZXuEvxS9LoCOWMGD1rweciD6uJQIx2myN3a8Im1FafZBzh7zk1RJ6oKcR16dU3UPldaKd83w==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.0.tgz", + "integrity": "sha512-ZrPhydkTVhyeGTW94WJ8pnl1uroqVHM3j3hjdquwAcWnmivjAwOYjTEAuEDeJvGX7xv3Z9GAvrBkEzCgHq9U1w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.0.tgz", + "integrity": "sha512-cfaupqd+UEFeURmqNP2eEvXqgbSox/LHOyN9/d2pSdV8xTrjdg3NgOFJCtc1vQ/jEke1qD0IejbBfxleBPHnPw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.0.tgz", + "integrity": "sha512-ZKPan1/RvAhrUylwBXC9t7B2hXdpb/ufeu22pG2psV7RN8roOfGurEghw1ySmX/CmDDHNTDDjY3lo9hRlgtaHg==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.0.tgz", + "integrity": "sha512-H1eRaCwd5E8eS8leiS+o/NqMdljkcb1d6r2h4fKSsCXQilLKArq6WS7XBLDu80Yz+nMqHVFDquwcVrQmGr28rg==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.0.tgz", + "integrity": "sha512-zJ4hA+3b5tu8u7L58CCSI0A9N1vkfwPhWd/puGXwtZlsB5bTkwDNW/+JCU84+3QYmKpLi+XvHdmrlwUwDA6kqw==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.0.tgz", + "integrity": "sha512-e2hrvElFIh6kW/UNBQK/kzqMNY5mO+67YtEh9OA65RM5IJXYTWiXjX6fjIiPaqOkBthYF1EqgiZ6OXKcQsM0hg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.0.tgz", + "integrity": "sha512-1vvmgDdUSebVGXWX2lIcgRebqfQSff0hMEkLJyakQ9JQUbLDkEaMsPTLOmyccyC6IJ/l3FZuJbmrBw/u0A0uCQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.0.tgz", + "integrity": "sha512-s5oFkZ/hFcrlAyBTONFY1TWndfyre1wOMwU+6KCpm/iatybvrRgmZVM+vCFwxmC5ZhdlgfE0N4XorsDpi7/4XQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.0.tgz", + "integrity": "sha512-G9+TEqRnAA6nbpqyUqgTiopmnfgnMkR3kMukFBDsiyy23LZvUCpiUwjTRx6ezYCjJODXrh52rBR9oXvm+Fp5wg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.0.tgz", + "integrity": "sha512-2jsCDZwtQvRhejHLfZ1JY6w6kEuEtfF9nzYsZxzSlNVKDX+DpsDJ+Rbjkm74nvg2rdx0gwBS+IMdvwJuq3S9pQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@surma/rollup-plugin-off-main-thread": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", + "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==", + "dependencies": { + "ejs": "^3.1.6", + "json5": "^2.2.0", + "magic-string": "^0.25.0", + "string.prototype.matchall": "^4.0.6" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", + "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.2", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", + "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001653", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001653.tgz", + "integrity": "sha512-XGWQVB8wFQ2+9NZwZ10GxTYC5hk0Fa+q8cSkr0tgvMhYhMHP/QC+WTgrePMDBWiWc/pV+1ik82Al20XOK25Gcw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" + }, + "node_modules/core-js-compat": { + "version": "3.38.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz", + "integrity": "sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==", + "dependencies": { + "browserslist": "^4.23.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz", + "integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==" + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/fast-uri": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", + "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==" + }, + "node_modules/fdir": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.3.0.tgz", + "integrity": "sha512-QOnuT+BOtivR77wYvCWHfGt9s4Pz1VIMbD463vegT5MLqNXy8rYFT/lPVEqf/bhYeT6qmqrNHhsX+rWwe3rOCQ==", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==" + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "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" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==" + }, + "node_modules/idb-keyval": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz", + "integrity": "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==" + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jake/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jake/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jake/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jake/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jake/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jake/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==" + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.41", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", + "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pretty-bytes": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", + "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rollup": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.0.tgz", + "integrity": "sha512-vo+S/lfA2lMS7rZ2Qoubi6I5hwZwzXeUIctILZLbHI+laNtvhhOIon2S1JksA5UEDQ7l3vberd0fxK44lTYjbQ==", + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.21.0", + "@rollup/rollup-android-arm64": "4.21.0", + "@rollup/rollup-darwin-arm64": "4.21.0", + "@rollup/rollup-darwin-x64": "4.21.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.21.0", + "@rollup/rollup-linux-arm-musleabihf": "4.21.0", + "@rollup/rollup-linux-arm64-gnu": "4.21.0", + "@rollup/rollup-linux-arm64-musl": "4.21.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.21.0", + "@rollup/rollup-linux-riscv64-gnu": "4.21.0", + "@rollup/rollup-linux-s390x-gnu": "4.21.0", + "@rollup/rollup-linux-x64-gnu": "4.21.0", + "@rollup/rollup-linux-x64-musl": "4.21.0", + "@rollup/rollup-win32-arm64-msvc": "4.21.0", + "@rollup/rollup-win32-ia32-msvc": "4.21.0", + "@rollup/rollup-win32-x64-msvc": "4.21.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/smob": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz", + "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==" + }, + "node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead" + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", + "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "regexp.prototype.flags": "^1.5.2", + "set-function-name": "^2.0.2", + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", + "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "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==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/tempy": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", + "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", + "dependencies": { + "is-stream": "^2.0.0", + "temp-dir": "^2.0.0", + "type-fest": "^0.16.0", + "unique-string": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terser": { + "version": "5.31.6", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.6.tgz", + "integrity": "sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.5.tgz", + "integrity": "sha512-Dlqgt6h0QkoHttG53/WGADNh9QhcjCAIZMTERAVhdpmIBEejSuLI9ZmGKWzB7tweBjlk30+s/ofi4SLmBeTYhw==", + "dependencies": { + "fdir": "^6.2.0", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "engines": { + "node": ">=4" + } + }, + "node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/type-fest": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.2.tgz", + "integrity": "sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.41", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-plugin-pwa": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.20.1.tgz", + "integrity": "sha512-M6Pk4b18i5ryrhKgiIF8Zud0HGphYiCbEfLsCdlvmwn/CEnS6noVwfIDG/+3V7r6yxpPV/gLiKw+rIlCCiCCoQ==", + "dependencies": { + "debug": "^4.3.4", + "pretty-bytes": "^6.1.1", + "tinyglobby": "^0.2.0", + "workbox-build": "^7.1.0", + "workbox-window": "^7.1.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vite-pwa/assets-generator": "^0.2.4", + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0", + "workbox-build": "^7.1.0", + "workbox-window": "^7.1.0" + }, + "peerDependenciesMeta": { + "@vite-pwa/assets-generator": { + "optional": true + } + } + }, + "node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" + }, + "node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/workbox-background-sync": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.1.0.tgz", + "integrity": "sha512-rMbgrzueVWDFcEq1610YyDW71z0oAXLfdRHRQcKw4SGihkfOK0JUEvqWHFwA6rJ+6TClnMIn7KQI5PNN1XQXwQ==", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "7.1.0" + } + }, + "node_modules/workbox-broadcast-update": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-7.1.0.tgz", + "integrity": "sha512-O36hIfhjej/c5ar95pO67k1GQw0/bw5tKP7CERNgK+JdxBANQhDmIuOXZTNvwb2IHBx9hj2kxvcDyRIh5nzOgQ==", + "dependencies": { + "workbox-core": "7.1.0" + } + }, + "node_modules/workbox-build": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-7.1.1.tgz", + "integrity": "sha512-WdkVdC70VMpf5NBCtNbiwdSZeKVuhTEd5PV3mAwpTQCGAB5XbOny1P9egEgNdetv4srAMmMKjvBk4RD58LpooA==", + "dependencies": { + "@apideck/better-ajv-errors": "^0.3.1", + "@babel/core": "^7.24.4", + "@babel/preset-env": "^7.11.0", + "@babel/runtime": "^7.11.2", + "@rollup/plugin-babel": "^5.2.0", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-replace": "^2.4.1", + "@rollup/plugin-terser": "^0.4.3", + "@surma/rollup-plugin-off-main-thread": "^2.2.3", + "ajv": "^8.6.0", + "common-tags": "^1.8.0", + "fast-json-stable-stringify": "^2.1.0", + "fs-extra": "^9.0.1", + "glob": "^7.1.6", + "lodash": "^4.17.20", + "pretty-bytes": "^5.3.0", + "rollup": "^2.43.1", + "source-map": "^0.8.0-beta.0", + "stringify-object": "^3.3.0", + "strip-comments": "^2.0.1", + "tempy": "^0.6.0", + "upath": "^1.2.0", + "workbox-background-sync": "7.1.0", + "workbox-broadcast-update": "7.1.0", + "workbox-cacheable-response": "7.1.0", + "workbox-core": "7.1.0", + "workbox-expiration": "7.1.0", + "workbox-google-analytics": "7.1.0", + "workbox-navigation-preload": "7.1.0", + "workbox-precaching": "7.1.0", + "workbox-range-requests": "7.1.0", + "workbox-recipes": "7.1.0", + "workbox-routing": "7.1.0", + "workbox-strategies": "7.1.0", + "workbox-streams": "7.1.0", + "workbox-sw": "7.1.0", + "workbox-window": "7.1.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/workbox-build/node_modules/@rollup/plugin-babel": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", + "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", + "dependencies": { + "@babel/helper-module-imports": "^7.10.4", + "@rollup/pluginutils": "^3.1.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@types/babel__core": "^7.1.9", + "rollup": "^1.20.0||^2.0.0" + }, + "peerDependenciesMeta": { + "@types/babel__core": { + "optional": true + } + } + }, + "node_modules/workbox-build/node_modules/@rollup/plugin-replace": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", + "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "magic-string": "^0.25.7" + }, + "peerDependencies": { + "rollup": "^1.20.0 || ^2.0.0" + } + }, + "node_modules/workbox-build/node_modules/@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dependencies": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/workbox-build/node_modules/@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" + }, + "node_modules/workbox-build/node_modules/estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==" + }, + "node_modules/workbox-build/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/workbox-build/node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/workbox-build/node_modules/rollup": { + "version": "2.79.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", + "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/workbox-cacheable-response": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-7.1.0.tgz", + "integrity": "sha512-iwsLBll8Hvua3xCuBB9h92+/e0wdsmSVgR2ZlvcfjepZWwhd3osumQB3x9o7flj+FehtWM2VHbZn8UJeBXXo6Q==", + "dependencies": { + "workbox-core": "7.1.0" + } + }, + "node_modules/workbox-core": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-7.1.0.tgz", + "integrity": "sha512-5KB4KOY8rtL31nEF7BfvU7FMzKT4B5TkbYa2tzkS+Peqj0gayMT9SytSFtNzlrvMaWgv6y/yvP9C0IbpFjV30Q==" + }, + "node_modules/workbox-expiration": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-7.1.0.tgz", + "integrity": "sha512-m5DcMY+A63rJlPTbbBNtpJ20i3enkyOtSgYfv/l8h+D6YbbNiA0zKEkCUaMsdDlxggla1oOfRkyqTvl5Ni5KQQ==", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "7.1.0" + } + }, + "node_modules/workbox-google-analytics": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-7.1.0.tgz", + "integrity": "sha512-FvE53kBQHfVTcZyczeBVRexhh7JTkyQ8HAvbVY6mXd2n2A7Oyz/9fIwnY406ZcDhvE4NFfKGjW56N4gBiqkrew==", + "dependencies": { + "workbox-background-sync": "7.1.0", + "workbox-core": "7.1.0", + "workbox-routing": "7.1.0", + "workbox-strategies": "7.1.0" + } + }, + "node_modules/workbox-navigation-preload": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-7.1.0.tgz", + "integrity": "sha512-4wyAbo0vNI/X0uWNJhCMKxnPanNyhybsReMGN9QUpaePLTiDpKxPqFxl4oUmBNddPwIXug01eTSLVIFXimRG/A==", + "dependencies": { + "workbox-core": "7.1.0" + } + }, + "node_modules/workbox-precaching": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-7.1.0.tgz", + "integrity": "sha512-LyxzQts+UEpgtmfnolo0hHdNjoB7EoRWcF7EDslt+lQGd0lW4iTvvSe3v5JiIckQSB5KTW5xiCqjFviRKPj1zA==", + "dependencies": { + "workbox-core": "7.1.0", + "workbox-routing": "7.1.0", + "workbox-strategies": "7.1.0" + } + }, + "node_modules/workbox-range-requests": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-7.1.0.tgz", + "integrity": "sha512-m7+O4EHolNs5yb/79CrnwPR/g/PRzMFYEdo01LqwixVnc/sbzNSvKz0d04OE3aMRel1CwAAZQheRsqGDwATgPQ==", + "dependencies": { + "workbox-core": "7.1.0" + } + }, + "node_modules/workbox-recipes": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-7.1.0.tgz", + "integrity": "sha512-NRrk4ycFN9BHXJB6WrKiRX3W3w75YNrNrzSX9cEZgFB5ubeGoO8s/SDmOYVrFYp9HMw6sh1Pm3eAY/1gVS8YLg==", + "dependencies": { + "workbox-cacheable-response": "7.1.0", + "workbox-core": "7.1.0", + "workbox-expiration": "7.1.0", + "workbox-precaching": "7.1.0", + "workbox-routing": "7.1.0", + "workbox-strategies": "7.1.0" + } + }, + "node_modules/workbox-routing": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-7.1.0.tgz", + "integrity": "sha512-oOYk+kLriUY2QyHkIilxUlVcFqwduLJB7oRZIENbqPGeBP/3TWHYNNdmGNhz1dvKuw7aqvJ7CQxn27/jprlTdg==", + "dependencies": { + "workbox-core": "7.1.0" + } + }, + "node_modules/workbox-strategies": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-7.1.0.tgz", + "integrity": "sha512-/UracPiGhUNehGjRm/tLUQ+9PtWmCbRufWtV0tNrALuf+HZ4F7cmObSEK+E4/Bx1p8Syx2tM+pkIrvtyetdlew==", + "dependencies": { + "workbox-core": "7.1.0" + } + }, + "node_modules/workbox-streams": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-7.1.0.tgz", + "integrity": "sha512-WyHAVxRXBMfysM8ORwiZnI98wvGWTVAq/lOyBjf00pXFvG0mNaVz4Ji+u+fKa/mf1i2SnTfikoYKto4ihHeS6w==", + "dependencies": { + "workbox-core": "7.1.0", + "workbox-routing": "7.1.0" + } + }, + "node_modules/workbox-sw": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-7.1.0.tgz", + "integrity": "sha512-Hml/9+/njUXBglv3dtZ9WBKHI235AQJyLBV1G7EFmh4/mUdSQuXui80RtjDeVRrXnm/6QWgRUEHG3/YBVbxtsA==" + }, + "node_modules/workbox-window": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.1.0.tgz", + "integrity": "sha512-ZHeROyqR+AS5UPzholQRDttLFqGMwP0Np8MKWAdyxsDETxq3qOAyXvqessc3GniohG6e0mAqSQyKOHmT8zPF7g==", + "dependencies": { + "@types/trusted-types": "^2.0.2", + "workbox-core": "7.1.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..5f38dd3 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,19 @@ +{ + "name": "notebrook-frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "typescript": "^5.5.3", + "vite": "^5.4.0" + }, + "dependencies": { + "idb-keyval": "^6.2.1", + "vite-plugin-pwa": "^0.20.1" + } +} diff --git a/frontend/public/intro.wav b/frontend/public/intro.wav new file mode 100644 index 0000000..bf53d70 Binary files /dev/null and b/frontend/public/intro.wav differ diff --git a/frontend/public/login.wav b/frontend/public/login.wav new file mode 100644 index 0000000..d6af49d Binary files /dev/null and b/frontend/public/login.wav differ diff --git a/frontend/public/sent1.wav b/frontend/public/sent1.wav new file mode 100644 index 0000000..1b4e164 Binary files /dev/null and b/frontend/public/sent1.wav differ diff --git a/frontend/public/sent2.wav b/frontend/public/sent2.wav new file mode 100644 index 0000000..00ffed1 Binary files /dev/null and b/frontend/public/sent2.wav differ diff --git a/frontend/public/sent3.wav b/frontend/public/sent3.wav new file mode 100644 index 0000000..6cfd030 Binary files /dev/null and b/frontend/public/sent3.wav differ diff --git a/frontend/public/sent4.wav b/frontend/public/sent4.wav new file mode 100644 index 0000000..b94156c Binary files /dev/null and b/frontend/public/sent4.wav differ diff --git a/frontend/public/sent5.wav b/frontend/public/sent5.wav new file mode 100644 index 0000000..4f67554 Binary files /dev/null and b/frontend/public/sent5.wav differ diff --git a/frontend/public/sent6.wav b/frontend/public/sent6.wav new file mode 100644 index 0000000..e86d5ad Binary files /dev/null and b/frontend/public/sent6.wav differ diff --git a/frontend/public/uploadfail.wav b/frontend/public/uploadfail.wav new file mode 100644 index 0000000..98f7828 Binary files /dev/null and b/frontend/public/uploadfail.wav differ diff --git a/frontend/public/water1.wav b/frontend/public/water1.wav new file mode 100644 index 0000000..c00a536 Binary files /dev/null and b/frontend/public/water1.wav differ diff --git a/frontend/public/water10.wav b/frontend/public/water10.wav new file mode 100644 index 0000000..86f67ff Binary files /dev/null and b/frontend/public/water10.wav differ diff --git a/frontend/public/water2.wav b/frontend/public/water2.wav new file mode 100644 index 0000000..3bcee7a Binary files /dev/null and b/frontend/public/water2.wav differ diff --git a/frontend/public/water3.wav b/frontend/public/water3.wav new file mode 100644 index 0000000..1c35eb2 Binary files /dev/null and b/frontend/public/water3.wav differ diff --git a/frontend/public/water4.wav b/frontend/public/water4.wav new file mode 100644 index 0000000..6c769ec Binary files /dev/null and b/frontend/public/water4.wav differ diff --git a/frontend/public/water5.wav b/frontend/public/water5.wav new file mode 100644 index 0000000..91bf464 Binary files /dev/null and b/frontend/public/water5.wav differ diff --git a/frontend/public/water6.wav b/frontend/public/water6.wav new file mode 100644 index 0000000..18bc34a Binary files /dev/null and b/frontend/public/water6.wav differ diff --git a/frontend/public/water7.wav b/frontend/public/water7.wav new file mode 100644 index 0000000..42e16d9 Binary files /dev/null and b/frontend/public/water7.wav differ diff --git a/frontend/public/water8.wav b/frontend/public/water8.wav new file mode 100644 index 0000000..9a4c849 Binary files /dev/null and b/frontend/public/water8.wav differ diff --git a/frontend/public/water9.wav b/frontend/public/water9.wav new file mode 100644 index 0000000..2068e18 Binary files /dev/null and b/frontend/public/water9.wav differ diff --git a/frontend/src/api.ts b/frontend/src/api.ts new file mode 100644 index 0000000..1d20a3b --- /dev/null +++ b/frontend/src/api.ts @@ -0,0 +1,103 @@ +import { IChannel } from "./model/channel"; +import { IChannelList } from "./model/channel-list"; +import { IMessage } from "./model/message"; +import { IUnsentMessage } from "./model/unsent-message"; +import { state } from "./state"; + + +export const API = { + token: "", + path: "http://localhost:3000", + + async request(method: string, path: string, body?: any) { + if (!API.token) { + throw new Error("API token was not set."); + } + return fetch(`${API.path}/${path}`, { + method, + headers: { + "Content-Type": "application/json", + "Authorization": API.token + }, + body: JSON.stringify(body), + }); + }, + + async checkToken() { + const response = await API.request("GET", "check-token"); + if (response.status !== 200) { + throw new Error("Invalid token in request"); + } + }, + + async getChannels() { + const response = await API.request("GET", "channels"); + const json = await response.json(); + return json.channels as IChannel[]; + }, + + async getChannel(id: string) { + const response = await API.request("GET", `channels/${id}`); + const json = await response.json(); + return json.channel as IChannel; + }, + + async createChannel(name: string) { + const response = await API.request("POST", "channels", { name }); + const json = await response.json(); + return json as IChannel; + }, + + async deleteChannel(id: string) { + await API.request("DELETE", `channels/${id}`); + }, + + async getMessages(channelId: string) { + const response = await API.request("GET", `channels/${channelId}/messages`); + const json = await response.json(); + return json.messages as IMessage[]; + }, + + async createMessage(channelId: string, content: string) { + const response = await API.request("POST", `channels/${channelId}/messages`, { content }); + const json = await response.json(); + return json as IMessage; + }, + + async deleteMessage(channelId: string, messageId: string) { + await API.request("DELETE", `channels/${channelId}/messages/${messageId}`); + }, + + async uploadFile(channelId: string, messageId: string, file: File | Blob) { + const formData = new FormData(); + formData.append("file", file); + + const response = await fetch(`${API.path}/channels/${channelId}/messages/${messageId}/files`, { + method: "POST", + headers: { + "Authorization": API.token + }, + body: formData, + }); + + const json = await response.json(); + return json; + }, + + async mergeChannels(channelId: string, targetChannelId: string) { + await API.request("PUT", `channels/${channelId}/merge`, { targetChannelId }); + }, + + async search(query: string, channelId?: string) { + const queryPath = channelId ? `search?query=${encodeURIComponent(query)}&channelId=${channelId}` : `search?query=${encodeURIComponent(query)}`; + const response = await API.request("GET", queryPath); + const json = await response.json(); + return json.results as IMessage[]; + }, + + async getFiles(channelId: string, messageId: string) { + const response = await API.request("GET", `channels/${channelId}/messages/${messageId}/files`); + const json = await response.json(); + return json.files as string[]; + } +} \ No newline at end of file diff --git a/frontend/src/chunk-processor.ts b/frontend/src/chunk-processor.ts new file mode 100644 index 0000000..5400f4d --- /dev/null +++ b/frontend/src/chunk-processor.ts @@ -0,0 +1,25 @@ +export class ChunkProcessor { + private chunkSize: number; + + constructor(chunkSize: number = 1000) { + this.chunkSize = chunkSize; + } + + async processArray(array: T[], callback: (chunk: T[]) => void): Promise { + const totalChunks = Math.ceil(array.length / this.chunkSize); + + for (let i = 0; i < totalChunks; i++) { + const chunk = array.slice(i * this.chunkSize, (i + 1) * this.chunkSize); + await this.processChunk(chunk, callback); + } + } + + private async processChunk(chunk: T[], callback: (chunk: T[]) => void): Promise { + return new Promise((resolve) => { + setTimeout(() => { + callback(chunk); + resolve(); + }, 0); + }); + } +} diff --git a/frontend/src/dialogs/channel-dialog.ts b/frontend/src/dialogs/channel-dialog.ts new file mode 100644 index 0000000..8da489e --- /dev/null +++ b/frontend/src/dialogs/channel-dialog.ts @@ -0,0 +1,68 @@ +import { IChannel } from "../model/channel"; +import { showToast } from "../speech"; +import { state } from "../state"; +import { Button, TextInput } from "../ui"; +import { Dialog } from "../ui/dialog"; +import { MergeDialog } from "./merge-dialog"; +import { RemoveDialog } from "./remove-dialog"; + +export class ChannelDialog extends Dialog { + private channel: IChannel; + private nameField: TextInput; + private makeDefault: Button; + private mergeButton: Button; + private deleteButton: Button; + + public constructor(channel: IChannel) { + super("Channel info for " + channel.name); + this.channel = channel; + this.nameField = new TextInput("Channel name"); + this.nameField.setPosition(25, 10, 50, 10); + this.nameField.setValue(channel.name); + this.makeDefault = new Button("Make default"); + this.makeDefault.setPosition(20, 70, 10, 10); + this.makeDefault.onClick(() => { + state.defaultChannelId = this.channel.id; + showToast(`${channel.name} is now the default channel.`); + }); + this.mergeButton = new Button("Merge"); + this.mergeButton.setPosition(40, 70, 10, 10); + this.mergeButton.onClick(() => { + this.mergeChannel(); + }); + if (state.channelList.channels.length === 1) { + this.mergeButton.setDisabled(true); + } + this.deleteButton = new Button("Delete"); + this.deleteButton.setPosition(60, 70, 10, 10); + this.deleteButton.onClick(() => { + this.deleteChannel(); + }); + this.add(this.nameField); + this.add(this.makeDefault); + this.add(this.mergeButton); + this.add(this.deleteButton); + this.setOkAction(() => { + this.channel.name = this.nameField.getValue(); + return this.channel; + }); + } + + private async mergeChannel() { + const res = await new MergeDialog().open(); + if (res) { + this.choose(this.channel); + } else { + return; + } + } + + private async deleteChannel() { + const res = await new RemoveDialog(this.channel.id.toString()).open(); + if (res) { + this.choose(null); + } else { + return; + } + } +} \ No newline at end of file diff --git a/frontend/src/dialogs/create-channel.ts b/frontend/src/dialogs/create-channel.ts new file mode 100644 index 0000000..3e4b3f1 --- /dev/null +++ b/frontend/src/dialogs/create-channel.ts @@ -0,0 +1,22 @@ +import { API } from "../api"; +import { showToast } from "../speech"; +import { TextInput } from "../ui"; +import { Dialog } from "../ui/dialog"; + +export class CreateChannelDialog extends Dialog { + private nameField: TextInput; + + public constructor() { + super("Create new channel"); + this.nameField = new TextInput("Name of new channel"); + this.add(this.nameField); + this.setOkAction(() => { + return this.nameField.getValue(); + }); + this.nameField.onKeyDown((key) => { + if (key === "Enter") { + this.choose(this.nameField.getValue()); + } + }); + } +} \ No newline at end of file diff --git a/frontend/src/dialogs/merge-dialog.ts b/frontend/src/dialogs/merge-dialog.ts new file mode 100644 index 0000000..4fdb13b --- /dev/null +++ b/frontend/src/dialogs/merge-dialog.ts @@ -0,0 +1,51 @@ +import { Button } from "../ui"; +import { Dialog } from "../ui/dialog"; +import { API } from "../api"; +import { Dropdown } from "../ui/dropdown"; +import { state } from "../state"; +import { showToast } from "../speech"; + +export class MergeDialog extends Dialog { + private channelList: Dropdown; + private mergeButton: Button; + protected cancelButton: Button; + + public constructor() { + super("Merge channels", false); + this.channelList = new Dropdown("Target channel", []); + this.channelList.setPosition(10, 10, 80, 20); + this.mergeButton = new Button("Merge"); + this.mergeButton.setPosition(30, 30, 40, 30); + this.mergeButton.onClick(() => this.merge()); + this.cancelButton = new Button("Cancel"); + this.cancelButton.setPosition(30, 70, 40, 30); + this.cancelButton.onClick(() => this.cancel()); + this.add(this.channelList); + this.add(this.mergeButton); + this.add(this.cancelButton); + this.setupChannelList(); + } + + private setupChannelList() { + this.channelList.clearOptions(); + state.channelList.getChannels().forEach((channel) => { + if (channel.id !== state.currentChannel!.id) this.channelList.addOption(channel.id.toString(), channel.name); + }) + } + private async merge() { + const currentChannel = state.currentChannel; + const target = this.channelList.getSelectedValue(); + const targetChannel = state.getChannelById(parseInt(target)); + console.log(currentChannel, targetChannel); + if (!targetChannel || !currentChannel) this.cancel(); + try { + const res = await API.mergeChannels(currentChannel!.id.toString(), target); + currentChannel!.messages = []; + showToast("Channels were merged."); + this.choose(true); + } catch (e) { + showToast("Failed to merge channels: " + e); + this.choose(false); + } + } +} \ No newline at end of file diff --git a/frontend/src/dialogs/message.ts b/frontend/src/dialogs/message.ts new file mode 100644 index 0000000..c42b862 --- /dev/null +++ b/frontend/src/dialogs/message.ts @@ -0,0 +1,50 @@ +import { API } from "../api"; +import { IMessage } from "../model/message"; +import { Button, Container, TextInput} from "../ui"; +import { Dialog } from "../ui/dialog"; +import { Text } from "../ui"; +import { MultilineInput } from "../ui/multiline-input"; +import { state } from "../state"; +export class MessageDialog extends Dialog { + private message: IMessage; + private messageText: MultilineInput; + private deleteButton: Button; + private fileInfoContainer?: Container; + + public constructor(message: IMessage) { + super("Message"); + this.message = message; + this.messageText = new MultilineInput("Message"); + this.messageText.setValue(message.content); + this.messageText.setPosition(10, 10, 80, 20); + + this.deleteButton = new Button("Delete"); + this.deleteButton.setPosition(10, 90, 80, 10); + this.deleteButton.onClick(async () => { + await API.deleteMessage(state.currentChannel!.id.toString(), this.message.id.toString()); + this.choose(null); + }); + this.add(this.messageText); + this.add(this.deleteButton); + if (this.message.fileId !== null) { + this.fileInfoContainer = new Container("File info"); + this.fileInfoContainer.setPosition(10, 50, 30, 80); + this.add(this.fileInfoContainer); + this.handleMessage(); + } + } + + private handleMessage() { + if (this.message?.fileType?.toLowerCase().includes("audio")) { + const audio = new Audio(`${API.path}/${this.message.filePath}`); + audio.autoplay = true; + } + + // display info about files, or the image if it is an image. Also display all metadata. + this.fileInfoContainer?.add(new Text(`File type: ${this.message.fileType}`)); + this.fileInfoContainer?.add(new Text(`File path: ${this.message.filePath}`)); + this.fileInfoContainer?.add(new Text(`File ID: ${this.message.fileId}`)); + this.fileInfoContainer?.add(new Text(`File size: ${this.message.fileSize}`)); + this.fileInfoContainer?.add(new Text(`Original name: ${this.message.originalName}`)); + } +} \ No newline at end of file diff --git a/frontend/src/dialogs/record-audio.ts b/frontend/src/dialogs/record-audio.ts new file mode 100644 index 0000000..6086b2e --- /dev/null +++ b/frontend/src/dialogs/record-audio.ts @@ -0,0 +1,72 @@ +import { Button } from "../ui"; +import { Audio } from "../ui/audio"; +import { AudioRecorder } from "../ui/audio-recorder"; +import { Dialog } from "../ui/dialog"; + +export class RecordAudioDialog extends Dialog { + private audioRecorder: AudioRecorder; + private recordButton: Button; + private stopButton: Button; + private playButton: Button; + private saveButton: Button; + private discardButton: Button; + private audioBlob: Blob | undefined; + private audioPlayer?: Audio; + + constructor() { + super("Record audio", false); + this.audioRecorder = new AudioRecorder("Record from microphone"); + this.audioRecorder.onRecordingComplete(() => { + this.audioBlob = this.audioRecorder.getRecording(); + this.saveButton.setDisabled(false); + }); + this.recordButton = new Button("Record"); + this.recordButton.setPosition(30, 30, 40, 30); + this.recordButton.onClick(() => this.startRecording()); + this.stopButton = new Button("Stop"); + this.stopButton.setPosition(70, 40, 30, 30); + this.stopButton.onClick(() => this.stopRecording()); + this.stopButton.setDisabled(true); + this.saveButton = new Button("Save"); + this.saveButton.setPosition(10, 80, 50, 20); + this.saveButton.onClick(() => this.saveRecording()); + this.saveButton.setDisabled(true); + this.playButton = new Button("Play"); + this.playButton.setPosition(0, 40, 30, 30); + this.playButton.onClick(() => { + if (this.audioBlob) { + this.audioPlayer = new Audio("Recorded audio"); + this.audioPlayer.setSource(URL.createObjectURL(this.audioBlob)); + this.audioPlayer.play(); + } + }); + this.playButton.setDisabled(true); + this.discardButton = new Button("Discard"); + this.discardButton.setPosition(50, 90, 50, 10); + this.discardButton.onClick(() => this.cancel()); + this.add(this.recordButton); + this.add(this.stopButton); + this.add(this.playButton); + this.add(this.saveButton); + this.add(this.discardButton); + } + + private startRecording() { + this.audioRecorder.startRecording(); + this.stopButton.setDisabled(false); + this.recordButton.setDisabled(true); + } + + private stopRecording() { + this.audioRecorder.stopRecording(); + this.recordButton.setDisabled(false); + this.stopButton.setDisabled(true); + this.playButton.setDisabled(false); + } + + private saveRecording() { + if (this.audioBlob) { + this.choose(this.audioBlob); + } + } +} \ No newline at end of file diff --git a/frontend/src/dialogs/remove-dialog.ts b/frontend/src/dialogs/remove-dialog.ts new file mode 100644 index 0000000..4399957 --- /dev/null +++ b/frontend/src/dialogs/remove-dialog.ts @@ -0,0 +1,39 @@ +import { Button } from "../ui"; +import { Dialog } from "../ui/dialog"; +import { Text } from "../ui"; +import { API } from "../api"; +import { state } from "../state"; +import { showToast } from "../speech"; + +export class RemoveDialog extends Dialog { + private content: Text; + private confirmButton: Button; + protected cancelButton: Button; + + public constructor(channelId: string) { + super("Remove channel", false); + this.content = new Text("Are you sure you want to remove this channel?"); + this.confirmButton = new Button("Remove"); + this.confirmButton.setPosition(30, 30, 40, 30); + this.confirmButton.onClick(() => this.doRemove()); + this.cancelButton = new Button("Cancel"); + this.cancelButton.setPosition(30, 70, 40, 30); + this.cancelButton.onClick(() => this.cancel()); + this.add(this.content); + this.add(this.confirmButton); + this.add(this.cancelButton); + } + + private async doRemove() { + try { + const res = await API.deleteChannel(state.currentChannel!.id.toString()); + state.removeChannel(state.currentChannel!); + showToast("Channel was removed."); + this.choose(true); + } catch (e) { + showToast("Failed to remove channel: " + e); + + this.choose(false); + } + } +} \ No newline at end of file diff --git a/frontend/src/dialogs/search.ts b/frontend/src/dialogs/search.ts new file mode 100644 index 0000000..5fa7ad7 --- /dev/null +++ b/frontend/src/dialogs/search.ts @@ -0,0 +1,48 @@ +import { API } from "../api"; +import { IMessage } from "../model/message"; +import { Button, List, ListItem, TextInput } from "../ui"; +import { Dialog } from "../ui/dialog"; + +export class SearchDialog extends Dialog<{channelId: number, messageId: number}> { + private searchField: TextInput; + private searchButton: Button; + private resultsList: List; + private closeButton: Button; + + public constructor() { + super("Search for message", false); + this.searchField = new TextInput("Search query"); + this.searchField.setPosition(5, 5, 80, 20); + this.searchField.onKeyDown((key) => { + if (key === "Enter") { + this.searchButton.click(); + } + }); + this.searchButton = new Button("Search"); + this.searchButton.setPosition(85, 5, 10, 20); + this.searchButton.onClick(async () => { + const messages = await API.search(this.searchField.getValue()); + console.log(messages); + this.renderResults(messages); + }) + this.resultsList = new List("Results"); + this.resultsList.setPosition(5, 20, 90, 70); + this.closeButton = new Button("Close"); + this.closeButton.setPosition(5, 90, 90, 5); + this.closeButton.onClick(() => this.cancel()); + this.add(this.searchField); + this.add(this.searchButton); + this.add(this.resultsList); + this.add(this.closeButton); + } + + private renderResults(messages: IMessage[]) { + this.resultsList.clear(); + messages.forEach((message) => { + const itm = new ListItem(`${message.content}; ${message.createdAt}`); + itm.onClick(() => this.choose({ messageId: message.id, channelId: message.channelId! })); + this.resultsList.add(itm); + }); + this.resultsList.focus(); + } +} \ No newline at end of file diff --git a/frontend/src/dialogs/settings.ts b/frontend/src/dialogs/settings.ts new file mode 100644 index 0000000..5095367 --- /dev/null +++ b/frontend/src/dialogs/settings.ts @@ -0,0 +1,23 @@ +import { Button } from "../ui"; +import { Dialog } from "../ui/dialog"; +import { state } from "../state"; + +export class SettingsDialog extends Dialog { + private resetButton: Button; + + public constructor() { + super("Settings"); + this.resetButton = new Button("Reset frontend"); + this.resetButton.setPosition(30, 20, 30, 30); + this.resetButton.onClick(() => { + this.reset(); + }); + this.add(this.resetButton); + } + + private reset() { + state.clear().then(() => { + window.location.reload(); + }); + } +} \ No newline at end of file diff --git a/frontend/src/dialogs/take-photo.ts b/frontend/src/dialogs/take-photo.ts new file mode 100644 index 0000000..d469ba3 --- /dev/null +++ b/frontend/src/dialogs/take-photo.ts @@ -0,0 +1,30 @@ +import { API } from "../api"; +import { state } from "../state"; +import { Button } from "../ui"; +import { Camera } from "../ui/camera"; +import { Dialog } from "../ui/dialog"; + +export class TakePhotoDialog extends Dialog { + private camera: Camera; + private takePhotoButton: Button; + private discardButton: Button; + + constructor() { + super("Take photo", false); + this.camera = new Camera("Photo camera"); + this.camera.setPosition(10, 15, 80, 75); + this.camera.startCamera(); + this.takePhotoButton = new Button("Take photo"); + this.takePhotoButton.setPosition(10, 90, 80, 10); + this.discardButton = new Button("Cancel"); + this.discardButton.setPosition(5, 5, 10, 10); + this.discardButton.onClick(() => this.cancel()); + this.add(this.camera); + this.add(this.takePhotoButton); + this.add(this.discardButton); + this.takePhotoButton.onClick(async () => { + const photo = await this.camera.savePhotoToBlob(); + if (photo) this.choose(photo); + }); + } +} \ No newline at end of file diff --git a/frontend/src/events/message-events.ts b/frontend/src/events/message-events.ts new file mode 100644 index 0000000..0b5ffaa --- /dev/null +++ b/frontend/src/events/message-events.ts @@ -0,0 +1,28 @@ +export type MessageCreated = { + channelId: string, + id: string, + content: string, +}; + +export type MessageDeleted = { + channelId: string, + messageId: string, +}; + +export type MessageUpdated = { + id: string, + content: string, +}; + +export type ChannelCreated = { + name: string, +}; + +export type ChannelDeleted = { + channelId: string, +}; + +export type ChannelUpdated = { + channelId: string, + name: string, +}; \ No newline at end of file diff --git a/frontend/src/events/messaging-system.ts b/frontend/src/events/messaging-system.ts new file mode 100644 index 0000000..2197443 --- /dev/null +++ b/frontend/src/events/messaging-system.ts @@ -0,0 +1,60 @@ +export type Message = { + type: string, + data?: T, +}; + +export type MessageHandler = (message: Message) => void; + +export class MessagingSystem { + private handlers: Record[]> = {}; + + public registerHandler(type: string, handler: MessageHandler): void { + if (!this.handlers[type]) { + this.handlers[type] = []; + } + if (!this.handlers[type].includes(handler)) { + this.handlers[type].push(handler); + } + } + + public unregisterHandler(type: string, handler: MessageHandler): void { + if (this.handlers[type]) { + this.handlers[type] = this.handlers[type].filter(h => h !== handler); + } + } + + public registerHandlerOnce(type: string, handler: MessageHandler): void { + const wrappedHandler = (message: Message) => { + handler(message); + this.unregisterHandler(type, wrappedHandler); + }; + this.registerHandler(type, wrappedHandler); + } + + public waitForMessage(type: string, timeout?: number): Promise { + return new Promise((resolve, reject) => { + const handler = (message: Message) => { + if (timer) clearTimeout(timer); + resolve(message.data!); + this.unregisterHandler(type, handler); + }; + + this.registerHandler(type, handler); + + let timer: ReturnType | undefined; + if (timeout) { + timer = setTimeout(() => { + this.unregisterHandler(type, handler); + reject(new Error(`Timeout waiting for message of type '${type}'`)); + }, timeout); + } + }); + } + + public sendMessage(message: Message): void { + const handlers = this.handlers[message.type]; + if (handlers) { + handlers.forEach(handler => handler(message)); + } + } +} diff --git a/frontend/src/main.ts b/frontend/src/main.ts new file mode 100644 index 0000000..ed3da4d --- /dev/null +++ b/frontend/src/main.ts @@ -0,0 +1,22 @@ +import './style.css' +import { MainView } from "./views/main"; +import { ViewManager } from './views/view-manager'; +import { AuthorizeView } from './views/authorize'; +import { state } from './state'; +import { API } from './api'; + + +document.addEventListener("DOMContentLoaded", async () => { + await state.load(); + const vm = new ViewManager(); + setInterval(() => { + state.save(); + }, 10000); + + if (state.token === "" || state.apiUrl === "") { + vm.push(new AuthorizeView(vm)); + } else { + vm.push(new MainView(vm)); + } + document.body.appendChild(vm.render() as HTMLElement); +}); \ No newline at end of file diff --git a/frontend/src/model/channel-list.ts b/frontend/src/model/channel-list.ts new file mode 100644 index 0000000..1fadc49 --- /dev/null +++ b/frontend/src/model/channel-list.ts @@ -0,0 +1,46 @@ +import { Channel, IChannel } from "./channel"; + +export interface IChannelList { + channels: IChannel[] +} + +export class ChannelList implements IChannelList { + channels: Channel[] = []; + + constructor(channels?: IChannelList) { + this.channels = channels?.channels?.map((chan) => new Channel(chan)) || []; + } + + public addChannel(channel: Channel): void { + this.channels.push(channel); + } + + public removeChannel(channelId: number): void { + this.channels = this.channels.filter(channel => channel.id !== channelId); + } + + public getChannel(channelId: number): Channel|undefined { + return this.channels.find(channel => channel.id === channelId); + } + + public getChannelByName(channelName: string): IChannel|undefined { + return this.channels.find(channel => channel.name === channelName); + } + + public getChannels(): Channel[] { + return this.channels; + } + + public getChannelIds(): number[] { + return this.channels.map(channel => channel.id); + } + + public getChannelNames(): string[] { + return this.channels.map(channel => channel.name); + } + + public getChannelId(channelName: string): number|undefined { + const channel = this.getChannelByName(channelName); + return channel ? channel.id : undefined; + } +} \ No newline at end of file diff --git a/frontend/src/model/channel.ts b/frontend/src/model/channel.ts new file mode 100644 index 0000000..0581369 --- /dev/null +++ b/frontend/src/model/channel.ts @@ -0,0 +1,60 @@ +import { IMessage, Message } from "./message"; + +export interface IChannel { + id: number; + name: string; + messages: IMessage[]; + createdAt: number; +} + +export class Channel implements IChannel { + id: number; + name: string; + messages: Message[]; + createdAt: number; + private messageToIdMap: Map; + + constructor(channel: IChannel) { + this.id = channel.id; + this.name = channel.name; + this.messages = []; + this.messageToIdMap = new Map(); + channel.messages?.forEach((msg) => this.addMessage(new Message(msg))); + this.createdAt = channel.createdAt; + } + + public addMessage(message: Message): void { + this.messages.push(message); + this.messageToIdMap.set(message.id, message); + } + + public removeMessage(messageId: number): void { + this.messages = this.messages.filter(message => message.id !== messageId); + this.messageToIdMap.delete(messageId); + } + + public getMessage(messageId: number): Message|undefined { + return this.messageToIdMap.get(messageId); + } + + public getMessageByContent(content: string): Message|undefined { + return this.messages.find(message => message.content === content); + } + + public getMessages(): Message[] { + return this.messages; + } + + public getMessageIds(): number[] { + return this.messages.map(message => message.id); + } + + public getMessageContents(): string[] { + return this.messages.map(message => message.content); + } + + public getMessageId(content: string): number|undefined { + const message = this.getMessageByContent(content); + return message ? message.id : undefined; + } +} \ No newline at end of file diff --git a/frontend/src/model/message.ts b/frontend/src/model/message.ts new file mode 100644 index 0000000..9b96ae5 --- /dev/null +++ b/frontend/src/model/message.ts @@ -0,0 +1,33 @@ +export interface IMessage { + id: number; + channelId?: number; + content: string; + fileId?: number; + fileType?: string; + filePath?: string; + fileSize?: number; + originalName?: string; + createdAt: string; +} + +export class Message implements IMessage { + id: number; + content: string; + fileId?: number; + fileType?: string; + filePath?: string; + fileSize?: number; + originalName?: string; + createdAt: string; + + constructor(message: IMessage) { + this.id = message.id; + this.content = message.content; + this.fileId = message.fileId; + this.fileType = message.fileType; + this.filePath = message.filePath; + this.fileSize = message.fileSize; + this.originalName = message.originalName; + this.createdAt = message.createdAt; + } +} \ No newline at end of file diff --git a/frontend/src/model/state.ts b/frontend/src/model/state.ts new file mode 100644 index 0000000..ded2229 --- /dev/null +++ b/frontend/src/model/state.ts @@ -0,0 +1,10 @@ +import { IChannelList } from "./channel-list"; +import { IUnsentMessage } from "./unsent-message"; + +export interface IState { + token: string; + apiUrl: string; + defaultChannelId: number; + channelList: IChannelList; + unsentMessages: IUnsentMessage[]; +} \ No newline at end of file diff --git a/frontend/src/model/unsent-message.ts b/frontend/src/model/unsent-message.ts new file mode 100644 index 0000000..7145ca4 --- /dev/null +++ b/frontend/src/model/unsent-message.ts @@ -0,0 +1,23 @@ +export interface IUnsentMessage { + id: number; + content: string; + blob?: Blob; + createdAt: string; + channelId: number; +} + +export class UnsentMessage implements IUnsentMessage { + id: number; + content: string; + blob?: Blob; + createdAt: string; + channelId: number; + + constructor(message: IUnsentMessage) { + this.id = message.id; + this.content = message.content; + this.blob = message.blob; + this.createdAt = message.createdAt; + this.channelId = message.channelId; + } +} \ No newline at end of file diff --git a/frontend/src/service-worker.ts b/frontend/src/service-worker.ts new file mode 100644 index 0000000..0d6828f --- /dev/null +++ b/frontend/src/service-worker.ts @@ -0,0 +1,62 @@ +const CACHE_NAME = 'notebrook-cache-v1'; +const urlsToCache = [ + '/', + '/index.html', + '/favicon.ico', + '/intro.wav', + '/login.wav', + '/uploadfail.wav', + '/water1.wav', + '/water2.wav', + '/water3.wav', + '/water4.wav', + '/water5.wav', + '/water6.wav', + '/water7.wav', + '/water8.wav', + '/water9.wav', + '/water10.wav', + '/sent1.wav', + '/sent2.wav', + '/sent3.wav', + '/sent4.wav', + '/sent5.wav', + '/sent6.wav', + '/vite.svg', + '/src/main.ts' +]; + +self.addEventListener('install', (event: any) => { + event.waitUntil( + caches.open(CACHE_NAME) + .then(cache => { + return cache.addAll(urlsToCache); + }) + ); +}); + +self.addEventListener('fetch', (event: any) => { + event.respondWith( + caches.match(event.request) + .then(response => { + // Return the cached response if found, otherwise fetch from network + return response || fetch(event.request); + }) + ); +}); + +self.addEventListener('activate', (event: any) => { + const cacheWhitelist = [CACHE_NAME]; + + event.waitUntil( + caches.keys().then(cacheNames => { + return Promise.all( + cacheNames.map(cacheName => { + if (cacheWhitelist.indexOf(cacheName) === -1) { + return caches.delete(cacheName); + } + }) + ); + }) + ); +}); diff --git a/frontend/src/sound.ts b/frontend/src/sound.ts new file mode 100644 index 0000000..fbc9b09 --- /dev/null +++ b/frontend/src/sound.ts @@ -0,0 +1,80 @@ +const audioContext = new AudioContext(); + +const soundFiles = { + intro: 'intro.wav', + login: 'login.wav', + uploadFailed: 'uploadfail.wav' +} as const; + +type SoundName = keyof typeof soundFiles; + +const sounds: Partial> = {}; + +const waterSounds: AudioBuffer[] = []; +const sentSounds: AudioBuffer[] = []; + +async function loadSound(url: string): Promise { + const response = await fetch(url); + const arrayBuffer = await response.arrayBuffer(); + return await audioContext.decodeAudioData(arrayBuffer); +} + +async function loadAllSounds() { + for (const key in soundFiles) { + const soundName = key as SoundName; + sounds[soundName] = await loadSound(soundFiles[soundName]); + } + + for (let i = 1; i <= 10; i++) { + const buffer = await loadSound(`water${i}.wav`); + waterSounds.push(buffer); + } + + for (let i = 1; i <= 6; i++) { + const buffer = await loadSound(`sent${i}.wav`); + sentSounds.push(buffer); + } +} + +function playSoundBuffer(buffer: AudioBuffer) { + if (audioContext.state === 'suspended') { + audioContext.resume(); + } + const source = audioContext.createBufferSource(); + source.buffer = buffer; + source.connect(audioContext.destination); + source.start(0); +} + +export function playSound(name: SoundName) { + const buffer = sounds[name]; + if (buffer) { + playSoundBuffer(buffer); + } else { + console.error(`Sound ${name} not loaded.`); + } +} + +export function playWater() { + if (waterSounds.length > 0) { + const sound = waterSounds[Math.floor(Math.random() * waterSounds.length)]; + playSoundBuffer(sound); + } else { + console.error("Water sounds not loaded."); + } +} + +export function playSent() { + if (sentSounds.length > 0) { + const sound = sentSounds[Math.floor(Math.random() * sentSounds.length)]; + playSoundBuffer(sound); + } else { + console.error("Sent sounds not loaded."); + } +} + +loadAllSounds().then(() => { + console.log('All sounds loaded and ready to play'); +}).catch(error => { + console.error('Error loading sounds:', error); +}); diff --git a/frontend/src/speech.ts b/frontend/src/speech.ts new file mode 100644 index 0000000..7d42070 --- /dev/null +++ b/frontend/src/speech.ts @@ -0,0 +1,14 @@ +import { Toast } from "./toast"; + +export function speak(text: string, interrupt: boolean = false) { + const utterance = new SpeechSynthesisUtterance(text); + if (interrupt) { + speechSynthesis.cancel(); + } + speechSynthesis.speak(utterance); +} + +export function showToast(message: string, timeout: number = 5000) { + const toast = new Toast(timeout); + toast.show(message); +} \ No newline at end of file diff --git a/frontend/src/state.ts b/frontend/src/state.ts new file mode 100644 index 0000000..8bb5fa6 --- /dev/null +++ b/frontend/src/state.ts @@ -0,0 +1,137 @@ +import { MessagingSystem } from "./events/messaging-system"; +import { IChannel, Channel } from "./model/channel"; +import { IChannelList, ChannelList } from "./model/channel-list"; +import { IState } from "./model/state"; +import { IUnsentMessage, UnsentMessage } from "./model/unsent-message"; +import { get, set, clear } from "idb-keyval"; + + +export class State implements IState { + token!: string; + apiUrl!: string; + channelList!: ChannelList; + unsentMessages!: IUnsentMessage[]; + currentChannel!: Channel | null; + defaultChannelId!: number; + public events: MessagingSystem; + + constructor() { + this.token = ""; + this.channelList = new ChannelList(); + this.unsentMessages = []; + this.events = new MessagingSystem(); + } + + public getToken(): string { + return this.token; + } + + public setToken(token: string): void { + this.token = token; + } + + public getChannelList(): IChannelList { + return this.channelList; + } + + public setChannelList(channelList: ChannelList): void { + this.channelList = channelList; + } + + public getUnsentMessages(): IUnsentMessage[] { + return this.unsentMessages; + } + + public setUnsentMessages(unsentMessages: IUnsentMessage[]): void { + this.unsentMessages = unsentMessages; + } + + public async save(): Promise { + // stringify everything here except the currentChannel object. + const { currentChannel, events, ...state } = this; + await set("notebrook", state); + } + + public async load(): Promise { + const saved = await get("notebrook"); + if (saved) { + this.token = saved.token; + this.apiUrl = saved.apiUrl; + this.channelList = new ChannelList( saved.channelList); + this.unsentMessages = saved.unsentMessages.map((message: IUnsentMessage) => new UnsentMessage(message)); + this.defaultChannelId = saved.defaultChannelId; + } + } + + public async clear(): Promise { + this.token = ""; + this.channelList = new ChannelList(); + this.unsentMessages = []; + this.currentChannel = null; + this.defaultChannelId = -1; + + await clear(); + } + + public getChannelById(id: number) { + return this.channelList.getChannel(id); + } + + public getChannelByName(name: string) { + return this.channelList.getChannelByName(name); + } + + public findChannelByQuery(query: string) { + return this.channelList.channels.filter((c) => c.name.toLowerCase().includes(query.toLowerCase())); + } + + public addChannel(channel: Channel) { + if (!this.channelList.channels.find((c) => c.id === channel.id)) this.channelList.channels.push(channel); + } + + public removeChannel(channel: IChannel) { + this.channelList.channels = this.channelList.channels.filter((c) => c.id !== channel.id); + } + + public addUnsentMessage(message: UnsentMessage) { + this.unsentMessages.push(message); + } + + public removeUnsentMessage(message: IUnsentMessage) { + this.unsentMessages = this.unsentMessages.filter((m) => m !== message); + } + + public getChannels() { + return this.channelList.channels; + } + + public getCurrentChannel() { + return this.currentChannel; + } + + public setCurrentChannel(channel: Channel) { + this.currentChannel = channel; + } + + public getDefaultChannelId() { + return this.defaultChannelId; + } + + public setDefaultChannelId(id: number) { + this.defaultChannelId = id; + } + + public getApiUrl() { + return this.apiUrl; + } + + public setApiUrl(url: string) { + this.apiUrl = url; + } + + public getMessageById(id: number) { + return this.currentChannel!.getMessage(id); + } +} + +export const state = new State(); \ No newline at end of file diff --git a/frontend/src/style.css b/frontend/src/style.css new file mode 100644 index 0000000..f9c7350 --- /dev/null +++ b/frontend/src/style.css @@ -0,0 +1,96 @@ +:root { + font-family: Inter, 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; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #3178c6aa); +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} + +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/frontend/src/toast.ts b/frontend/src/toast.ts new file mode 100644 index 0000000..e0c5fc6 --- /dev/null +++ b/frontend/src/toast.ts @@ -0,0 +1,32 @@ +export class Toast { + private container: HTMLElement; + private timeout: number; + + constructor(timeout: number = 3000) { + this.container = document.querySelector('.toast-container') as HTMLElement; + this.timeout = timeout; + } + + public show(message: string): void { + const toast = document.createElement('div'); + toast.className = 'toast'; + toast.textContent = message; + + this.container.appendChild(toast); + + requestAnimationFrame(() => { + toast.classList.add('show'); + }); + + setTimeout(() => { + this.hide(toast); + }, this.timeout); + } + + private hide(toast: HTMLElement): void { + toast.classList.remove('show'); + toast.addEventListener('transitionend', () => { + toast.remove(); + }); + } +} diff --git a/frontend/src/typescript.svg b/frontend/src/typescript.svg new file mode 100644 index 0000000..d91c910 --- /dev/null +++ b/frontend/src/typescript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/ui/audio-recorder.ts b/frontend/src/ui/audio-recorder.ts new file mode 100644 index 0000000..aecd9ce --- /dev/null +++ b/frontend/src/ui/audio-recorder.ts @@ -0,0 +1,76 @@ +import { UINode } from "./node"; + +export class AudioRecorder extends UINode { + private audioElement: HTMLAudioElement; + private mediaRecorder: MediaRecorder | null; + private audioChunks: Blob[]; + private stream: MediaStream | null; + private recording?: Blob; + + public constructor(title: string) { + super(title); + this.audioElement = document.createElement("audio"); + this.mediaRecorder = null; + this.audioChunks = []; + this.stream = null; + + this.audioElement.setAttribute("controls", "true"); + this.audioElement.setAttribute("aria-label", title); + this.element.appendChild(this.audioElement); + + this.setRole("audio-recorder"); + } + + public async startRecording() { + try { + this.stream = await navigator.mediaDevices.getUserMedia({ audio: { autoGainControl: true, channelCount: 2, echoCancellation: false, noiseSuppression: false } }); + this.mediaRecorder = new MediaRecorder(this.stream); + this.mediaRecorder.ondataavailable = (event) => { + this.audioChunks.push(event.data); + }; + this.mediaRecorder.onstop = () => { + const audioBlob = new Blob(this.audioChunks, { type: 'audio/webm' }); + this.recording = audioBlob; + this.audioChunks = []; + const audioUrl = URL.createObjectURL(audioBlob); + this.audioElement.src = audioUrl; + this.triggerRecordingComplete(audioUrl); + }; + this.mediaRecorder.start(); + } catch (error) { + console.error("Error accessing microphone:", error); + } + } + + public stopRecording() { + if (this.mediaRecorder && this.mediaRecorder.state !== "inactive") { + this.mediaRecorder.stop(); + } + if (this.stream) { + this.stream.getTracks().forEach(track => track.stop()); + this.stream = null; + } + } + + public getElement(): HTMLElement { + return this.element; + } + + public onRecordingComplete(callback: (audioUrl: string) => void) { + this.element.addEventListener("recording-complete", (event: Event) => { + const customEvent = event as CustomEvent; + callback(customEvent.detail.audioUrl); + }); + return this; + } + + protected triggerRecordingComplete(audioUrl: string) { + const event = new CustomEvent("recording-complete", { detail: { audioUrl } }); + this.element.dispatchEvent(event); + return this; + } + + public getRecording() { + return this.recording; + } +} diff --git a/frontend/src/ui/audio.ts b/frontend/src/ui/audio.ts new file mode 100644 index 0000000..3169bd6 --- /dev/null +++ b/frontend/src/ui/audio.ts @@ -0,0 +1,66 @@ +import { UINode } from "./node"; + +export class Audio extends UINode { + private audioElement: HTMLAudioElement; + + public constructor(title: string, src: string | MediaStream = "") { + super(title); + this.audioElement = document.createElement("audio"); + if (typeof src === "string") { + this.audioElement.src = src; // Set src if it's a string URL + } else if (src instanceof MediaStream) { + this.audioElement.srcObject = src; // Set srcObject if it's a MediaStream + } + this.audioElement.setAttribute("aria-label", title); + this.element.appendChild(this.audioElement); + this.setRole("audio"); + } + + public getElement(): HTMLElement { + return this.audioElement; + } + + public setSource(src: string | MediaStream) { + if (typeof src === "string") { + this.audioElement.src = src; + } else if (src instanceof MediaStream) { + this.audioElement.srcObject = src; + } + return this; + } + + public play() { + this.audioElement.play(); + return this; + } + + public pause() { + this.audioElement.pause(); + return this; + } + + public setControls(show: boolean) { + this.audioElement.controls = show; + return this; + } + + public setLoop(loop: boolean) { + this.audioElement.loop = loop; + return this; + } + + public setMuted(muted: boolean) { + this.audioElement.muted = muted; + return this; + } + + public setAutoplay(autoplay: boolean) { + this.audioElement.autoplay = autoplay; + return this; + } + + public setVolume(volume: number) { + this.audioElement.volume = volume; + return this; + } +} diff --git a/frontend/src/ui/button.ts b/frontend/src/ui/button.ts new file mode 100644 index 0000000..d11c509 --- /dev/null +++ b/frontend/src/ui/button.ts @@ -0,0 +1,39 @@ +import { UINode } from "./node"; + +export class Button extends UINode { + private buttonElement: HTMLButtonElement; + public constructor(title: string, hasPopup: boolean = false) { + super(title); + this.buttonElement = document.createElement("button"); + this.buttonElement.innerText = title; + if (hasPopup) this.buttonElement.setAttribute("aria-haspopup", "true"); + this.element.appendChild(this.buttonElement); + this.element.setAttribute("aria-label", this.title); + } + + public focus() { + this.buttonElement.focus(); + return this; + } + + public click() { + this.buttonElement.click(); + return this; + } + + public getElement(): HTMLElement { + return this.buttonElement; + } + + public setText(text: string) { + this.title = text; + this.buttonElement.innerText = text; + this.element.setAttribute("aria-label", this.title); + return this; + } + + public setDisabled(val: boolean) { + this.buttonElement.disabled = val; + return this; + } +} \ No newline at end of file diff --git a/frontend/src/ui/camera.ts b/frontend/src/ui/camera.ts new file mode 100644 index 0000000..8193681 --- /dev/null +++ b/frontend/src/ui/camera.ts @@ -0,0 +1,77 @@ +import { UINode } from "./node"; + +export class Camera extends UINode { + private videoElement: HTMLVideoElement; + private canvasElement: HTMLCanvasElement; + private stream: MediaStream | null; + + public constructor(title: string) { + super(title); + this.videoElement = document.createElement("video"); + this.canvasElement = document.createElement("canvas"); + this.stream = null; + + this.videoElement.setAttribute("aria-label", title); + this.element.appendChild(this.videoElement); + this.element.appendChild(this.canvasElement); + + this.setRole("camera"); + } + + public async startCamera() { + try { + this.stream = await navigator.mediaDevices.getUserMedia({ video: true }); + this.videoElement.srcObject = this.stream; + this.videoElement.play(); + } catch (error) { + console.error("Error accessing camera:", error); + } + } + + public stopCamera() { + if (this.stream) { + this.stream.getTracks().forEach(track => track.stop()); + this.stream = null; + } + this.videoElement.pause(); + this.videoElement.srcObject = null; + } + + public takePhoto(): HTMLCanvasElement | null { + if (this.stream) { + const context = this.canvasElement.getContext("2d"); + if (context) { + this.canvasElement.width = this.videoElement.videoWidth; + this.canvasElement.height = this.videoElement.videoHeight; + context.drawImage(this.videoElement, 0, 0, this.canvasElement.width, this.canvasElement.height); + return this.canvasElement; + } + } + return null; + } + + public savePhoto(): string | null { + const photoCanvas = this.takePhoto(); + if (photoCanvas) { + return photoCanvas.toDataURL("image/png"); + } + return null; + } + + public savePhotoToBlob(): Promise { + return new Promise((resolve) => { + const photoCanvas = this.takePhoto(); + if (photoCanvas) { + photoCanvas.toBlob((blob) => { + resolve(blob); + }); + } else { + resolve(null); + } + }); + } + + public getElement(): HTMLElement { + return this.element; + } +} diff --git a/frontend/src/ui/canvas.ts b/frontend/src/ui/canvas.ts new file mode 100644 index 0000000..d8ea3ea --- /dev/null +++ b/frontend/src/ui/canvas.ts @@ -0,0 +1,26 @@ +import { UINode } from "./node"; + +export class Canvas extends UINode { + private canvasElement: HTMLCanvasElement; + public constructor(title: string) { + super(title); + this.canvasElement = document.createElement("canvas"); + + this.canvasElement.setAttribute("tabindex", "-1"); + this.element.appendChild(this.canvasElement); + } + + public focus() { + this.canvasElement.focus(); + return this; + } + + public click() { + this.canvasElement.click(); + return this; + } + + public getElement(): HTMLElement { + return this.canvasElement; + } +} \ No newline at end of file diff --git a/frontend/src/ui/checkbox.ts b/frontend/src/ui/checkbox.ts new file mode 100644 index 0000000..b4731fc --- /dev/null +++ b/frontend/src/ui/checkbox.ts @@ -0,0 +1,50 @@ +import { UINode } from "./node"; + +export class Checkbox extends UINode { + private id: string; + private titleElement: HTMLLabelElement; + private checkboxElement: HTMLInputElement; + public constructor(title: string) { + super(title); + this.id = Math.random().toString(); + this.titleElement = document.createElement("label"); + this.titleElement.id = `chkbx_title_${this.id}`; + this.checkboxElement = document.createElement("input"); + this.checkboxElement.id = `chkbx_${this.id}`; + this.checkboxElement.type = "checkbox"; + this.titleElement.appendChild(this.checkboxElement); + this.titleElement.appendChild(document.createTextNode(this.title)); + this.element.appendChild(this.titleElement); + } + + public focus() { + this.checkboxElement.focus(); + return this; + } + + public click() { + this.checkboxElement.click(); + return this; + } + + public getElement(): HTMLElement { + return this.checkboxElement; + } + + public setText(text: string) { + this.title = text; + this.titleElement.innerText = text; + this.element.setAttribute("aria-label", this.title); + this.element.setAttribute("aria-roledescription", "checkbox"); + return this; + } + + public isChecked(): boolean { + return this.checkboxElement.checked; + } + + public setChecked(value: boolean) { + this.checkboxElement.checked = value; + return this; + } +} \ No newline at end of file diff --git a/frontend/src/ui/collapsable-container.ts b/frontend/src/ui/collapsable-container.ts new file mode 100644 index 0000000..77249fd --- /dev/null +++ b/frontend/src/ui/collapsable-container.ts @@ -0,0 +1,42 @@ +import { Container } from "./container"; + +export class CollapsableContainer extends Container { + private detailsElement: HTMLDetailsElement; + private summaryElement: HTMLElement; + private wrapperElement: HTMLDivElement; + + public constructor(title: string) { + super(title); + this.wrapperElement = document.createElement("div"); + this.detailsElement = document.createElement("details"); + this.summaryElement = document.createElement("summary"); + + this.summaryElement.innerText = title; + this.detailsElement.appendChild(this.summaryElement); + this.detailsElement.appendChild(this.containerElement); + this.wrapperElement.appendChild(this.detailsElement); + } + + public render() { + return this.wrapperElement; + } + + public setTitle(text: string) { + this.title = text; + this.summaryElement.innerText = text; + return this; + } + + public isCollapsed(): boolean { + return this.detailsElement.hasAttribute("open"); + } + + public expand(val: boolean) { + if (val) { + this.detailsElement.setAttribute("open", "true"); + } else { + this.detailsElement.removeAttribute("open"); + } + return this; + } +} diff --git a/frontend/src/ui/container.ts b/frontend/src/ui/container.ts new file mode 100644 index 0000000..c8311cc --- /dev/null +++ b/frontend/src/ui/container.ts @@ -0,0 +1,55 @@ +import { UINode } from "./node"; + +export class Container extends UINode { + public children: UINode[]; + protected containerElement: HTMLDivElement; + private focused: number = 0; + + public constructor(title: string) { + super(title); + this.children = []; + this.containerElement = document.createElement("div"); + this.containerElement.setAttribute("tabindex", "-1"); + this.focused = 0; + } + + public focus() { + this.containerElement.focus(); + return this; + } + + public _onFocus() { + this.children[this.focused].focus(); + } + + public add(node: UINode) { + this.children.push(node); + node._onConnect(); + this.containerElement.appendChild(node.render()); + return this; + } + + public remove(node: UINode) { + this.children.splice(this.children.indexOf(node), 1); + node._onDisconnect(); + this.containerElement.removeChild(node.render()); + return this; + } + + public render() { + return this.containerElement; + } + + public getChildren(): UINode[] { + return this.children; + } + + public getElement() { + return this.containerElement; + } + + public setAriaLabel(text: string) { + this.containerElement.setAttribute("aria-label", text); + return this; + } +} \ No newline at end of file diff --git a/frontend/src/ui/date-picker.ts b/frontend/src/ui/date-picker.ts new file mode 100644 index 0000000..4b5f6f5 --- /dev/null +++ b/frontend/src/ui/date-picker.ts @@ -0,0 +1,44 @@ +import { UINode } from "./node"; + + +export class DatePicker extends UINode { + private id: string; + private titleElement: HTMLLabelElement; + private inputElement: HTMLInputElement; + public constructor(title: string) { + super(title); + this.id = Math.random().toString(); + this.titleElement = document.createElement("label"); + this.titleElement.innerText = title; + this.titleElement.id = `datepicker_title_${this.id}`; + this.inputElement = document.createElement("input"); + this.inputElement.id = `datepicker_${this.id}`; + this.inputElement.type = "date"; + this.titleElement.appendChild(this.inputElement); + this.element.appendChild(this.titleElement); + } + + public focus() { + this.inputElement.focus(); + return this; + } + + public getElement(): HTMLElement { + return this.inputElement; + } + + public setText(text: string) { + this.title = text; + this.titleElement.innerText = text; + return this; + } + + public getValue(): string { + return this.inputElement.value; + } + + public setValue(value: string) { + this.inputElement.value = value; + return this; + } +} \ No newline at end of file diff --git a/frontend/src/ui/dialog.ts b/frontend/src/ui/dialog.ts new file mode 100644 index 0000000..799d4a4 --- /dev/null +++ b/frontend/src/ui/dialog.ts @@ -0,0 +1,78 @@ +import { UIWindow } from "./window"; +import { Button } from "./button"; + +export class Dialog extends UIWindow { + private resolvePromise!: (value: T | PromiseLike) => void; + private rejectPromise!: (reason?: any) => void; + private promise: Promise; + private dialogElement!: HTMLDialogElement; + protected okButton?: Button; + protected cancelButton?: Button; + + private previouslyFocusedElement!: HTMLElement; + + public constructor(title: string, addButtons: boolean = true) { + super(title, "dialog", false); + this.dialogElement = document.createElement("dialog"); + this.promise = new Promise((resolve, reject) => { + this.resolvePromise = resolve; + this.rejectPromise = reject; + }); + + // Automatically add OK and Cancel buttons + if (addButtons) { + this.okButton = new Button("OK"); + this.okButton.setPosition(70, 90, 10, 5); + this.okButton.onClick(() => this.choose(undefined)); + + this.cancelButton = new Button("Cancel"); + this.cancelButton.setPosition(20, 90, 10, 5); + this.cancelButton.onClick(() => this.cancel()); + } + } + + public setOkAction(action: () => T) { + if (!this.okButton) return; + this.okButton.onClick(() => { + const result = action(); + this.choose(result); + }); + return this; + } + + public setCancelAction(action: () => void) { + if (!this.cancelButton) return; + this.cancelButton.onClick(() => { + action(); + this.cancel(); + }); + return this; + } + + public choose(item: T | undefined) { + this.resolvePromise(item as T); + document.body.removeChild(this.dialogElement); + this.hide(); + this.previouslyFocusedElement.focus(); + } + + public cancel(reason?: any) { + this.rejectPromise(reason); + + document.body.removeChild(this.dialogElement); + this.hide(); + this.previouslyFocusedElement.focus(); + } + + public open(): Promise { + this.previouslyFocusedElement = document.activeElement as HTMLElement; + this.dialogElement.appendChild(this.show()!); + if (this.okButton) this.add(this.okButton); + if (this.cancelButton) this.add(this.cancelButton); + document.body.appendChild(this.dialogElement); + this.dialogElement.showModal(); + this.container.focus(); + + return this.promise; + } +} diff --git a/frontend/src/ui/dropdown.ts b/frontend/src/ui/dropdown.ts new file mode 100644 index 0000000..0e7558b --- /dev/null +++ b/frontend/src/ui/dropdown.ts @@ -0,0 +1,77 @@ +import { UINode } from "./node"; + +export class Dropdown extends UINode { + private id: string; + private titleElement: HTMLLabelElement; + private selectElement: HTMLSelectElement; + + public constructor(title: string, options: { key: string; value: string }[]) { + super(title); + this.id = Math.random().toString(); + this.titleElement = document.createElement("label"); + this.titleElement.innerText = title; + this.titleElement.id = `dd_title_${this.id}`; + this.selectElement = document.createElement("select"); + this.selectElement.id = `dd_${this.id}`; + this.titleElement.appendChild(this.selectElement); + this.element.appendChild(this.titleElement); + + this.setOptions(options); + } + + public focus() { + this.selectElement.focus(); + return this; + } + + public getElement(): HTMLElement { + return this.selectElement; + } + + public setText(text: string) { + this.title = text; + this.titleElement.innerText = text; + return this; + } + + public getSelectedValue(): string { + return this.selectElement.value; + } + + public setSelectedValue(value: string) { + this.selectElement.value = value; + return this; + } + + public setOptions(options: { key: string; value: string }[]) { + this.clearOptions(); + options.forEach((option) => { + this.addOption(option.key, option.value); + }); + return this; + } + + public addOption(key: string, value: string) { + const optionElement = document.createElement("option"); + optionElement.value = key; + optionElement.innerText = value; + this.selectElement.appendChild(optionElement); + return this; + } + + public removeOption(key: string) { + const options = Array.from(this.selectElement.options); + const optionToRemove = options.find(option => option.value === key); + if (optionToRemove) { + this.selectElement.removeChild(optionToRemove); + } + return this; + } + + public clearOptions() { + while (this.selectElement.firstChild) { + this.selectElement.removeChild(this.selectElement.firstChild); + } + return this + } +} diff --git a/frontend/src/ui/file-input.ts b/frontend/src/ui/file-input.ts new file mode 100644 index 0000000..0f9754c --- /dev/null +++ b/frontend/src/ui/file-input.ts @@ -0,0 +1,47 @@ +import { UINode } from "./node"; + +export class FileInput extends UINode { + private id: string; + private titleElement: HTMLLabelElement; + private inputElement: HTMLInputElement; + public constructor(title: string, multiple: boolean = false) { + super(title); + this.id = Math.random().toString(); + this.titleElement = document.createElement("label"); + this.titleElement.innerText = title; + this.titleElement.id = `fileinpt_title_${this.id}`; + this.inputElement = document.createElement("input"); + this.inputElement.id = `fileinpt_${this.id}`; + this.inputElement.type = "file"; + if (multiple) { + this.inputElement.multiple = true; + } + this.titleElement.appendChild(this.inputElement); + this.element.appendChild(this.titleElement); + + } + + public focus() { + this.inputElement.focus(); + return this; + } + + public getElement(): HTMLElement { + return this.inputElement; + } + + public setText(text: string) { + this.title = text; + this.titleElement.innerText = text; + return this; + } + + public getFiles(): FileList | null { + return this.inputElement.files; + } + + public setAccept(accept: string) { + this.inputElement.accept = accept; + return this; + } +} \ No newline at end of file diff --git a/frontend/src/ui/image.ts b/frontend/src/ui/image.ts new file mode 100644 index 0000000..b02a374 --- /dev/null +++ b/frontend/src/ui/image.ts @@ -0,0 +1,33 @@ +import { UINode } from "./node"; + +export class Image extends UINode { + private imgElement: HTMLImageElement; + public constructor(title: string, src: string, altText: string = "") { + super(title); + this.imgElement = document.createElement("img"); + this.imgElement.src = src; + this.imgElement.alt = altText; + this.element.appendChild(this.imgElement); + this.element.setAttribute("aria-label", title); + } + + public getElement(): HTMLElement { + return this.imgElement; + } + + public setText(text: string) { + this.title = text; + this.element.setAttribute("aria-label", text); + return this; + } + + public setSource(src: string) { + this.imgElement.src = src; + return this; + } + + public setAltText(altText: string) { + this.imgElement.alt = altText; + return this; + } +} \ No newline at end of file diff --git a/frontend/src/ui/index.ts b/frontend/src/ui/index.ts new file mode 100644 index 0000000..1277631 --- /dev/null +++ b/frontend/src/ui/index.ts @@ -0,0 +1,12 @@ +export { UIWindow } from "./window"; +export { Button } from "./button"; +export { Container } from "./container"; +export { UINode } from "./node"; +export { List } from "./list"; +export { Text } from "./text"; +export { ListItem } from "./list-item"; +export { Checkbox } from "./checkbox"; +export { TextInput } from "./text-input"; +export { TabBar } from "./tab-bar"; +export { TabbedView } from "./tabbed-view"; +export { Canvas } from "./canvas"; \ No newline at end of file diff --git a/frontend/src/ui/list-item.ts b/frontend/src/ui/list-item.ts new file mode 100644 index 0000000..e4c124e --- /dev/null +++ b/frontend/src/ui/list-item.ts @@ -0,0 +1,37 @@ +import { UINode } from "./node"; + +export class ListItem extends UINode { + private listElement: HTMLLIElement; + + public constructor(title: string) { + super(title); + this.listElement = document.createElement("li"); + this.listElement.innerText = this.title; + this.listElement.setAttribute("tabindex", "-1"); + this.element.appendChild(this.listElement); + this.listElement.setAttribute("aria-label", this.title); + this.listElement.setAttribute("role", "option"); + } + + public focus() { + this.listElement.focus(); + return this; + } + + public click() { + this.listElement.click(); + return this; + } + + public getElement(): HTMLElement { + return this.listElement; + } + + public setText(text: string) { + this.title = text; + this.listElement.innerText = text; + this.element.setAttribute("aria-label", this.title); + this.listElement.setAttribute("aria-label", this.title); + return this; + } +} \ No newline at end of file diff --git a/frontend/src/ui/list.ts b/frontend/src/ui/list.ts new file mode 100644 index 0000000..6b221e8 --- /dev/null +++ b/frontend/src/ui/list.ts @@ -0,0 +1,171 @@ +import { UINode } from "./node"; + + +export class List extends UINode { + public children: UINode[]; + protected listElement: HTMLUListElement; + private focused: number; + protected selectCallback?: (id: number) => void; + public constructor(title: string) { + super(title); + this.children = []; + this.listElement = document.createElement("ul"); + this.listElement.setAttribute("role", "listbox"); + this.listElement.style.listStyle = "none"; + this.element.appendChild(this.listElement); + this.element.setAttribute("aria-label", this.title); + this.focused = 0; + } + + public add(node: UINode) { + this.children.push(node); + node._onConnect(); + this.listElement.appendChild(node.render()); + if (this.children.length === 1) this.calculateTabIndex(); + node.onFocus(() => this.calculateFocused(node)); + return this; + } + + public addNodeAtIndex(node: UINode, index: number) { + index = Math.max(0, Math.min(index, this.children.length)); + this.children.splice(index, 0, node); + node._onConnect(); + this.listElement.insertBefore(node.render(), this.listElement.children[index]); + if (this.children.length === 1) this.calculateTabIndex(); + node.onFocus(() => this.calculateFocused(node)); + return this; + } + + public remove(node: UINode) { + const idx = this.children.indexOf(node); + this.children.splice(idx, 1); + node._onDisconnect(); + this.listElement.removeChild(node.render()); + if (idx === this.focused) { + if (this.focused > 0) this.focused--; + this.calculateTabIndex(); + } + return this; + } + + public _onFocus() { + super._onFocus(); + this.children[this.focused].focus(); + return this; + } + + public _onClick() { + // this.children[this.focused]._onClick(); + } + + public _onSelect(id: number) { + if (this.selectCallback) this.selectCallback(id); + } + + protected calculateStyle(): void { + super.calculateStyle(); + this.element.style.overflowY = "scroll"; + this.listElement.style.overflowY = "scroll"; + } + + public _onKeydown(key: string, alt: boolean = false, shift: boolean = false, ctrl: boolean = false): boolean { + switch (key) { + case "ArrowUp": + this.children[this.focused].setTabbable(false); + this.focused = Math.max(0, this.focused - 1); + this.children[this.focused].setTabbable(true); + this.children[this.focused].focus(); + return true; + break; + case "ArrowDown": + this.children[this.focused].setTabbable(false); + this.focused = Math.min(this.children.length - 1, this.focused + 1); + this.children[this.focused].setTabbable(true); + this.children[this.focused].focus(); + return true; + break; + case "Enter": + this.children[this.focused].click(); + return true; + break; + case "Home": + this.children[this.focused].setTabbable(false); + this.focused = 0; + this.children[this.focused].setTabbable(true); + this.children[this.focused].focus(); + return true; + break; + case "End": + this.children[this.focused].setTabbable(false); + this.focused = this.children.length - 1; + this.children[this.focused].setTabbable(true); + this.children[this.focused].focus(); + return true; + break; + default: + return this.children[this.focused]._onKeydown(key); + break; + } + return false; + } + + protected renderAsListItem(node: UINode) { + let li = document.createElement("li"); + li.appendChild(node.render()); + return li; + } + + public getElement(): HTMLElement { + return this.listElement; + } + + public isItemFocused(): boolean { + const has = this.children.find((child) => child.isFocused); + if (has) { + return true; + } + return false; + } + + private calculateTabIndex() { + if (this.children.length < 1) return; + this.children[this.focused].setTabbable(true); + } + + public clear() { + this.children.forEach((child) => this.remove(child)); + this.children = []; + this.listElement.innerHTML = ''; + this.focused = 0; + return this; + } + + public getFocusedChild() { + return this.children[this.focused]; + } + + public getFocus() { + return this.focused; + } + + public onSelect(f: (id: number) => void) { + this.selectCallback = f; + return this; + } + + protected calculateFocused(node: UINode) { + const idx = this.children.indexOf(node); + this._onSelect(idx); + this.focused = idx; + } + + public scrollToBottom() { + this.children.forEach((child) => child.setTabbable(false)); + const node = this.children[this.children.length - 1]; + node.getElement().scrollIntoView(); + // set the focused element for tab index without focusing directly. + this.focused = this.children.length - 1; + this.children[this.focused].setTabbable(true); + return this; + } +} \ No newline at end of file diff --git a/frontend/src/ui/multiline-input.ts b/frontend/src/ui/multiline-input.ts new file mode 100644 index 0000000..0444840 --- /dev/null +++ b/frontend/src/ui/multiline-input.ts @@ -0,0 +1,47 @@ +import { UINode } from "./node"; + +export class MultilineInput extends UINode { + private id: string; + private titleElement: HTMLLabelElement; + private textareaElement: HTMLTextAreaElement; + public constructor(title: string) { + super(title); + this.id = Math.random().toString(); + this.titleElement = document.createElement("label"); + this.titleElement.innerText = title; + this.titleElement.id = `txtarea_title_${this.id}`; + this.textareaElement = document.createElement("textarea"); + this.textareaElement.id = `txtarea_${this.id}`; + this.titleElement.appendChild(this.textareaElement); + this.element.appendChild(this.titleElement); + } + + public focus() { + this.textareaElement.focus(); + return this; + } + + public click() { + this.textareaElement.click(); + return this; + } + + public getElement(): HTMLElement { + return this.textareaElement; + } + + public setText(text: string) { + this.title = text; + this.titleElement.innerText = text; + return this; + } + + public getValue(): string { + return this.textareaElement.value; + } + + public setValue(value: string) { + this.textareaElement.value = value; + return this; + } +} \ No newline at end of file diff --git a/frontend/src/ui/node.ts b/frontend/src/ui/node.ts new file mode 100644 index 0000000..deadb89 --- /dev/null +++ b/frontend/src/ui/node.ts @@ -0,0 +1,161 @@ +import { UITab } from "./tab"; + +export class UINode { + protected title: string; + protected element: HTMLDivElement; + protected position!: {x: number, y: number, width: number, height: number}; + protected positionType: string = "fixed"; + protected calculateOwnStyle: boolean = true; + protected keyDownCallback!: (key: string, alt?: boolean, shift?: boolean, ctrl?: boolean) => void | undefined; + protected focusCallback?: () => void; + protected blurCallback?: () => void; + protected clickCallback?: () => void; + protected globalKeydown: boolean = false; + protected visible: boolean; + public isFocused: boolean; + private userdata: any; + + public constructor(title: string) { + this.title = title; + this.element = document.createElement("div"); + this.element.setAttribute("tabindex", "-1"); + this.visible = false; + this.isFocused = false; + } + + public focus() { + this.element.focus(); + } + + public click() { + this.element.click(); + } + + public _onConnect() { + this.calculateStyle(); + this.addListeners(); + return; + } + + public _onDisconnect() { + return; + } + + public _onFocus() { + if (this.focusCallback) this.focusCallback(); + this.isFocused = true; + return; + } + + public _onBlur() { + if (this.blurCallback) this.blurCallback(); + this.isFocused = false; + return; + } + + public _onClick() { + if (this.clickCallback) this.clickCallback(); + return; + } + + public _onKeydown(key: string, alt: boolean = false, shift: boolean = false, ctrl: boolean = false): boolean { + if (this.keyDownCallback) { + if (this.globalKeydown || (!this.globalKeydown && document.activeElement === this.getElement())) { + this.keyDownCallback(key, alt, shift, ctrl); + return true; + } + } + return false; + } + + public render(): HTMLElement { + this.visible = true; + return this.element; + } + + protected addListeners() { + const elem = this.element; + this.getElement().addEventListener("focus", (e) => this._onFocus()); + elem.addEventListener("blur", (e) => this._onBlur()); + elem.addEventListener("click", (e) => this._onClick()); + elem.addEventListener("keydown", e => this._onKeydown(e.key, e.altKey, e.shiftKey, e.ctrlKey)); + } + + protected calculateStyle() { + if (!this.calculateOwnStyle || !this.position) return; + this.element.style.position = this.positionType; + this.element.style.left = `${this.position.x}%`; + this.element.style.top = `${this.position.y}%`; + this.element.style.width = `${this.position.width}%`; + this.element.style.height = `${this.position.height}%`; + } + + public setPosition(x: number, y: number, width: number, height: number, type: string = "fixed") { + this.position = { + x: x, + y: y, + width: width, + height: height, + }; + this.positionType = type; + this.calculateOwnStyle = true; + this.calculateStyle(); + return this; + } + + public onClick(f: () => void) { + this.clickCallback = f; + return this; + } + + public onFocus(f: () => void) { + this.focusCallback = f; + return this; + } + + public onKeyDown(f: (key: string, alt?: boolean, shift?: boolean, ctrl?: boolean) => void, global: boolean = false) { + this.keyDownCallback = f; + this.globalKeydown = global; + return this; + } + + public onBlur(f: () => void) { + this.blurCallback = f; + return this; + } + + public getElement(): HTMLElement { + return this.element; + } + + public setTabbable(val: boolean) { + this.getElement().setAttribute("tabindex", + (val === true) ? "0" : + "-1"); + return this; + } + + public setAriaLabel(text: string) { + this.element.setAttribute("aria-label", text); + return this; + } + + public setRole(role: string) { + this.getElement().setAttribute("role", role); + return this; + } + + public getUserData(): any { + return this.userdata; + } + + public setUserData(obj: any) { + this.userdata = obj; + return this; + } + + public setAccessKey(key: string) { + this.getElement().accessKey = key; + return this; + } +} \ No newline at end of file diff --git a/frontend/src/ui/progress-bar.ts b/frontend/src/ui/progress-bar.ts new file mode 100644 index 0000000..ab8d3e1 --- /dev/null +++ b/frontend/src/ui/progress-bar.ts @@ -0,0 +1,40 @@ +import { UINode } from "./node"; + +export class ProgressBar extends UINode { + private progressElement: HTMLProgressElement; + public constructor(title: string, max: number) { + super(title); + this.progressElement = document.createElement("progress"); + this.progressElement.max = max; + this.element.appendChild(this.progressElement); + this.element.setAttribute("aria-label", title); + } + + public getElement(): HTMLElement { + return this.progressElement; + } + + public setText(text: string) { + this.title = text; + this.element.setAttribute("aria-label", text); + return this; + } + + public getValue(): number { + return this.progressElement.value; + } + + public setValue(value: number) { + this.progressElement.value = value; + return this; + } + + public getMax(): number { + return this.progressElement.max; + } + + public setMax(max: number) { + this.progressElement.max = max; + return this; + } +} \ No newline at end of file diff --git a/frontend/src/ui/radio-group.ts b/frontend/src/ui/radio-group.ts new file mode 100644 index 0000000..d415fec --- /dev/null +++ b/frontend/src/ui/radio-group.ts @@ -0,0 +1,79 @@ +import { UINode } from "./node"; + +export class RadioGroup extends UINode { + private id: string; + private titleElement: HTMLLegendElement; + private containerElement: HTMLFieldSetElement; + private radioElements: Map; + private radioLabels: Map; + + public constructor(title: string, options: { key: string; value: string }[]) { + super(title); + this.id = Math.random().toString(); + this.titleElement = document.createElement("legend"); + this.titleElement.innerText = title; + this.titleElement.id = `rdgrp_title_${this.id}`; + this.containerElement = document.createElement("fieldset"); + this.containerElement.appendChild(this.titleElement); + this.element.appendChild(this.containerElement); + + this.radioElements = new Map(); + this.radioLabels = new Map(); + + options.forEach((option) => { + const radioId = `rd_${this.id}_${option.key}`; + const radioElement = document.createElement("input"); + radioElement.id = radioId; + radioElement.type = "radio"; + radioElement.name = `rdgrp_${this.id}`; + radioElement.value = option.key; + radioElement.setAttribute("aria-labeledby", `${radioId}_label`); + + const radioLabel = document.createElement("label"); + radioLabel.innerText = option.value; + radioLabel.id = `${radioId}_label`; + radioLabel.setAttribute("for", radioId); + + this.radioElements.set(option.key, radioElement); + this.radioLabels.set(option.key, radioLabel); + + this.containerElement.appendChild(radioElement); + this.containerElement.appendChild(radioLabel); + }); + } + + public focus() { + const firstRadioElement = this.radioElements.values().next().value; + if (firstRadioElement) { + firstRadioElement.focus(); + } + return this; + } + + public getElement(): HTMLElement { + return this.containerElement; + } + + public setText(text: string) { + this.title = text; + this.titleElement.innerText = text; + return this; + } + + public getSelectedValue(): string | null { + for (const [key, radioElement] of this.radioElements.entries()) { + if (radioElement.checked) { + return key; + } + } + return null; + } + + public setSelectedValue(value: string) { + const radioElement = this.radioElements.get(value); + if (radioElement) { + radioElement.checked = true; + } + return this; + } +} \ No newline at end of file diff --git a/frontend/src/ui/slider.ts b/frontend/src/ui/slider.ts new file mode 100644 index 0000000..aab86bd --- /dev/null +++ b/frontend/src/ui/slider.ts @@ -0,0 +1,51 @@ +import { UINode } from "./node"; + +export class Slider extends UINode { + private id: string; + private titleElement: HTMLLabelElement; + private sliderElement: HTMLInputElement; + public constructor(title: string, min: number, max: number, step: number = 1) { + super(title); + this.id = Math.random().toString(); + this.titleElement = document.createElement("label"); + this.titleElement.innerText = title; + this.titleElement.id = `sldr_title_${this.id}`; + this.sliderElement = document.createElement("input"); + this.sliderElement.id = `sldr_${this.id}`; + this.sliderElement.type = "range"; + this.sliderElement.min = min.toString(); + this.sliderElement.max = max.toString(); + this.sliderElement.step = step.toString(); + this.titleElement.appendChild(this.sliderElement); + this.element.appendChild(this.titleElement); + } + + public focus() { + this.sliderElement.focus(); + return this; + } + + public click() { + this.sliderElement.click(); + return this; + } + + public getElement(): HTMLElement { + return this.sliderElement; + } + + public setText(text: string) { + this.title = text; + this.titleElement.innerText = text; + return this; + } + + public getValue(): number { + return parseInt(this.sliderElement.value); + } + + public setValue(value: number) { + this.sliderElement.value = value.toString(); + return this; + } +} \ No newline at end of file diff --git a/frontend/src/ui/tab-bar.ts b/frontend/src/ui/tab-bar.ts new file mode 100644 index 0000000..136a050 --- /dev/null +++ b/frontend/src/ui/tab-bar.ts @@ -0,0 +1,102 @@ +import { UINode } from "./node"; +import { UITab } from "./tab"; + +export class TabBar extends UINode { + private tabs: UITab[]; + private tabBarContainer: HTMLDivElement; + private onTabChangeCallback?: (index: number) => void; + private focused: number; + + public constructor(title: string = "tab bar") { + super(title); + this.tabs = []; + this.tabBarContainer = document.createElement("div"); + this.tabBarContainer.setAttribute("role", "tablist"); + this.tabBarContainer.style.display = "flex"; + this.tabBarContainer.style.alignItems = "center"; + // this.tabBarContainer.style.justifyContent = "space-between"; + this.tabBarContainer.style.overflow = "hidden"; + + this.element.appendChild(this.tabBarContainer); + this.focused = 0; + } + + public _onFocus() { + this.tabs[this.focused].focus(); + return this; + } + + public focus() { + this.tabs[this.focused].focus(); + return this; + } + + public add(title: string) { + const idx = this.tabs.length; + const elem = new UITab(title); + elem.onClick(() => { + this.selectTab(idx); + }); + this.tabs.push(elem); + this.tabBarContainer.appendChild(elem.render()); + elem._onConnect(); + if (this.tabs.length === 1) this.calculateTabIndex(); + return this; + } + + public onTabChange(f: (index: number) => void) { + this.onTabChangeCallback = f; + return this; + } + + private selectTab(idx: number) { + if (idx !== this.focused) { + this.tabs[this.focused].setTabbable(false); + this.focused = idx; + } + if (!this.onTabChangeCallback) return; + this.onTabChangeCallback(idx); + this.tabs[idx].setTabbable(true); + this.tabs[idx].focus(); + this.updateView(); + } + + public _onKeydown(key: string): boolean { + switch (key) { + case "ArrowLeft": + this.tabs[this.focused].setTabbable(false); + this.focused = Math.max(0, this.focused - 1); + this.tabs[this.focused].setTabbable(true); + this.selectTab(this.focused); + return true; + break; + case "ArrowRight": + this.tabs[this.focused].setTabbable(false); + this.focused = Math.min(this.tabs.length - 1, this.focused + 1); + this.tabs[this.focused].setTabbable(true); + this.selectTab(this.focused); + return true; + break; + default: + return false; + break; + } + return false; + } + + private updateView() { + for (let i = 0; i < this.tabs.length; i++) { + this.tabs[i].setSelected(i === this.focused); + } + } + + public getElement(): HTMLElement { + return this.element; + } + + + public calculateTabIndex() { + this.tabs[this.focused].setTabbable(true); + return this; + } +} \ No newline at end of file diff --git a/frontend/src/ui/tab.ts b/frontend/src/ui/tab.ts new file mode 100644 index 0000000..7059dec --- /dev/null +++ b/frontend/src/ui/tab.ts @@ -0,0 +1,44 @@ +import { UINode } from "./node"; + +export class UITab extends UINode { + private textElement: HTMLButtonElement; + private selected: boolean; + + public constructor(title: string) { + super(title); + this.title = title; + this.textElement = document.createElement("button"); + this.textElement.innerText = title; + this.textElement.setAttribute("tabindex", "-1"); + this.textElement.setAttribute("role", "tab"); + this.textElement.setAttribute("aria-selected", "false"); + this.element.appendChild(this.textElement); + this.selected = false; + } + + public focus() { + this.textElement.focus(); + return this; + } + + public click() { + this.textElement.click(); + return this; + } + + public getElement(): HTMLElement { + return this.textElement; + } + + public setText(text: string) { + this.title = text; + this.textElement.innerText = text; + return this; + } + + public setSelected(val: boolean) { + this.selected = val; + this.textElement.setAttribute("aria-selected", this.selected.toString()); + return this; + } +} \ No newline at end of file diff --git a/frontend/src/ui/tabbed-view.ts b/frontend/src/ui/tabbed-view.ts new file mode 100644 index 0000000..47d30e7 --- /dev/null +++ b/frontend/src/ui/tabbed-view.ts @@ -0,0 +1,51 @@ +import { UINode } from "./node"; +import { TabBar } from "./tab-bar"; +import { Container } from "./container"; + + +export class TabbedView extends UINode { + private bar: TabBar; + private containers: Container[]; + private containerElement: HTMLDivElement; + private barAtTop: boolean; + private currentView?: Container; + public constructor(title: string, barAtTop: boolean = true) { + super(title); + this.bar = new TabBar(); + this.bar._onConnect(); + this.bar.onTabChange((index: number) => this.onTabChanged(index)); + this.containers = []; + this.containerElement = document.createElement("div"); + this.element.appendChild(this.bar.render()); + this.element.appendChild(this.containerElement); + this.element.setAttribute("tabindex", "-1"); + this.barAtTop = barAtTop; + } + + public add(name: string, container: Container) { + this.bar.add(name); + container.setRole("tabpanel"); + this.containers.push(container); + return this; + } + + private onTabChanged(idx: number) { + if (this.currentView) { + this.containerElement.removeChild(this.currentView.render()); + } + this.currentView = this.containers[idx]; + this.containerElement.appendChild(this.currentView.render()); + } + + public getElement(): HTMLElement { + return this.containerElement; + } + + protected calculateStyle(): void { + if (this.barAtTop) { + this.bar.setPosition(0, 0, 100, 5); + } else { + this.bar.setPosition(0, 90, 100, 5); + } + } +} \ No newline at end of file diff --git a/frontend/src/ui/text-input.ts b/frontend/src/ui/text-input.ts new file mode 100644 index 0000000..dd5a37b --- /dev/null +++ b/frontend/src/ui/text-input.ts @@ -0,0 +1,48 @@ +import { UINode } from "./node"; + +export class TextInput extends UINode { + private id: string; + private titleElement: HTMLLabelElement; + private inputElement: HTMLInputElement; + public constructor(title: string) { + super(title); + this.id = Math.random().toString(); + this.titleElement = document.createElement("label"); + this.titleElement.innerText = title; + this.titleElement.id = `inpt_title_${this.id}`; + this.inputElement = document.createElement("input"); + this.inputElement.id = `inpt_${this.id}`; + this.inputElement.type = "text"; + this.titleElement.appendChild(this.inputElement); + this.element.appendChild(this.titleElement); + } + + public focus() { + this.inputElement.focus(); + return this; + } + + public click() { + this.inputElement.click(); + return this; + } + + public getElement(): HTMLElement { + return this.inputElement; + } + + public setText(text: string) { + this.title = text; + this.titleElement.innerText = text; + return this; + } + + public getValue(): string { + return this.inputElement.value; + } + + public setValue(value: string) { + this.inputElement.value = value; + return this; + } +} \ No newline at end of file diff --git a/frontend/src/ui/text.ts b/frontend/src/ui/text.ts new file mode 100644 index 0000000..35fc384 --- /dev/null +++ b/frontend/src/ui/text.ts @@ -0,0 +1,32 @@ +import { UINode } from "./node"; + +export class Text extends UINode { + private textElement: HTMLSpanElement; + public constructor(title: string) { + super(title); + this.textElement = document.createElement("span"); + this.textElement.innerText = title; + this.textElement.setAttribute("tabindex", "-1"); + this.element.appendChild(this.textElement); + } + + public focus() { + this.textElement.focus(); + return this; + } + + public click() { + this.textElement.click(); + return this; + } + + public getElement(): HTMLElement { + return this.textElement; + } + + public setText(text: string) { + this.title = text; + this.textElement.innerText = text; + return this; + } +} \ No newline at end of file diff --git a/frontend/src/ui/time-picker.ts b/frontend/src/ui/time-picker.ts new file mode 100644 index 0000000..23eb5f2 --- /dev/null +++ b/frontend/src/ui/time-picker.ts @@ -0,0 +1,43 @@ +import { UINode } from "./node"; + +export class TimePicker extends UINode { + private id: string; + private titleElement: HTMLLabelElement; + private inputElement: HTMLInputElement; + public constructor(title: string) { + super(title); + this.id = Math.random().toString(); + this.titleElement = document.createElement("label"); + this.titleElement.innerText = title; + this.titleElement.id = `timepicker_title_${this.id}`; + this.inputElement = document.createElement("input"); + this.inputElement.id = `timepicker_${this.id}`; + this.inputElement.type = "time"; + this.titleElement.appendChild(this.inputElement); + this.element.appendChild(this.titleElement); + } + + public focus() { + this.inputElement.focus(); + return this; + } + + public getElement(): HTMLElement { + return this.inputElement; + } + + public setText(text: string) { + this.title = text; + this.titleElement.innerText = text; + return this; + } + + public getValue(): string { + return this.inputElement.value; + } + + public setValue(value: string) { + this.inputElement.value = value; + return this; + } +} \ No newline at end of file diff --git a/frontend/src/ui/treelist-item.ts b/frontend/src/ui/treelist-item.ts new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/ui/treelist.ts b/frontend/src/ui/treelist.ts new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/ui/treeview-item.ts b/frontend/src/ui/treeview-item.ts new file mode 100644 index 0000000..87a8c52 --- /dev/null +++ b/frontend/src/ui/treeview-item.ts @@ -0,0 +1,198 @@ +import { UINode } from "./node"; +import { Treeview } from "./treeview"; + +export class TreeviewItem extends UINode { + private listElement: HTMLLIElement; + private childContainer!: HTMLUListElement; + + public children: TreeviewItem[]; + + private expanded!: boolean; + + private focused: number; + + private parent?: TreeviewItem; + + private root!: Treeview; + + private previousItem?: TreeviewItem; + private nextItem?: TreeviewItem; + + public constructor(title: string) { + super(title); + this.listElement = document.createElement("li"); + this.listElement.innerText = this.title; + this.listElement.setAttribute("tabindex", "-1"); + this.listElement.setAttribute("role", "treeitem"); + this.element.appendChild(this.listElement); + this.listElement.setAttribute("aria-label", this.title); + this.children = []; + this.focused = 0; + } + + public focus() { + this.listElement.focus(); + } + + public click() { + this.listElement.click(); + } + + public getElement(): HTMLElement { + return this.listElement; + } + + public setText(text: string) { + this.title = text; + this.listElement.innerText = text; + this.element.setAttribute("aria-label", this.title); + this.listElement.setAttribute("aria-label", this.title); + } + + public add(node: TreeviewItem) { + this.children.push(node); + node.setParent(this); + this.setExpanded(false); + if (this.children.length > 0) { + this.previousItem = this.children[this.children.length - 1]; + this.previousItem.setNextItem(node); + } + } + + public remove(node: TreeviewItem) { + const idx = this.children.indexOf(node); + if (idx > -1) { + this.children.splice(idx, 1); + this.updateShownItems(); + } + } + + public expand() { + if (!this.isExpandable()) return; + if (this.isExpandable() && this.isExpanded() && this.children[this.focused].isExpandable()) { + this.children[this.focused].expand(); + return; + } + this.setExpanded(true); + this.updateShownItems(); + this.children[this.focused].focus(); + } + + public collapse(ignoreFocus: boolean = false) { + if (!this.isExpandable()) { + if (this.getElement() !== document.activeElement && ignoreFocus) return; + this.parent?.collapse(true); + return; + } + this.setExpanded(false); + this.updateShownItems(); + setTimeout(() => this.focus(), 0); + } + + public isExpandable(): boolean { + return this.children.length > 0; + } + + public isExpanded() { + return this.expanded; + } + + private setExpanded(val: boolean) { + this.expanded = val; + if (this.expanded) { + this.listElement.setAttribute("aria-expanded", "true"); + return; + } + this.listElement.setAttribute("aria-expanded", "false"); + } + + private updateShownItems() { + if (this.expanded) { + if (!this.childContainer) { + this.childContainer = document.createElement("ul"); + this.childContainer.setAttribute("role", "group"); + this.children.forEach((child) => this.childContainer.appendChild(child.render())); + this.listElement.appendChild(this.childContainer); + } else { + this.childContainer.hidden = false; + } + } else { + this.childContainer.hidden = true; + // this.listElement.removeChild(this.childContainer); + } + } + + public focusNext(): boolean { + if (this.isExpandable() && this.isExpanded() && this.children[this.focused].isExpandable()) { + return this.children[this.focused].focusNext(); + } + this.children[this.focused].setTabbable(false); + this.focused = Math.min(this.children.length - 1, this.focused + 1); + this.children[this.focused].setTabbable(true); + this.children[this.focused].focus(); + return true; + } + + public focusPrevious(): boolean { + if (this.isExpandable() && this.isExpanded() && this.children[this.focused].isExpandable()) { + return this.children[this.focused].focusPrevious(); + } + this.children[this.focused].setTabbable(false); + this.focused = Math.max(0, this.focused - 1); + this.children[this.focused].setTabbable(true); + this.children[this.focused].focus(); + return true; + } + + public setParent(item: TreeviewItem) { + this.parent = this.parent; + } + + public getParent(): TreeviewItem|undefined { + return this.parent; + } + + public setPrevious(node: TreeviewItem) { + this.previousItem = node; + } + + public setNextItem(node: TreeviewItem) { + this.nextItem = node; + } + + public getPrevious(): TreeviewItem|undefined { + return this.previousItem; + } + + public getNext(): TreeviewItem|undefined { + return this.nextItem; + } + + public _onKeydown(key: string, alt?: boolean, shift?: boolean, ctrl?: boolean): boolean { + switch (key) { + case "ArrowUp": + this.focusPrevious(); + return true; + break; + case "ArrowDown": + this.focusNext(); + return true; + case "ArrowLeft": + this.collapse(); + return true; + case "ArrowRight": + if (this.children[this.focused].isExpandable() && !this.children[this.focused].isExpanded()) { + this.children[this.focused].expand(); + return true; + } + break; + default: + return false; + } + return false; + } + + public focusOnItem() { + this.children[this.focused].focus(); + } +} \ No newline at end of file diff --git a/frontend/src/ui/treeview.ts b/frontend/src/ui/treeview.ts new file mode 100644 index 0000000..727d716 --- /dev/null +++ b/frontend/src/ui/treeview.ts @@ -0,0 +1,151 @@ +import { UINode } from "./node"; +import { TreeviewItem } from "./treeview-item"; + +export class Treeview extends UINode { + public children: TreeviewItem[]; + protected listElement: HTMLUListElement; + private focused: number; + protected selectCallback?: (id: number) => void; + public constructor(title: string) { + super(title); + this.children = []; + this.listElement = document.createElement("ul"); + this.listElement.setAttribute("role", "tree"); + this.listElement.style.listStyle = "none"; + this.element.appendChild(this.listElement); + this.element.setAttribute("aria-label", this.title); + this.focused = 0; + } + + public add(node: TreeviewItem) { + this.children.push(node); + node._onConnect(); + this.listElement.appendChild(node.render()); + if (this.children.length === 1) this.calculateTabIndex(); + node.onFocus(() => this.calculateFocused(node)); + } + + public remove(node: TreeviewItem) { + const idx = this.children.indexOf(node); + this.children.splice(idx, 1); + node._onDisconnect(); + this.listElement.removeChild(node.render()); + if (idx === this.focused) { + if (this.focused > 0) this.focused--; + this.calculateTabIndex(); + } + } + + public _onFocus() { + super._onFocus(); + this.children[this.focused].focus(); + } + + public _onClick() { + this.children[this.focused]._onClick(); + } + + public _onSelect(id: number) { + if (this.selectCallback) this.selectCallback(id); + } + + protected calculateStyle(): void { + super.calculateStyle(); + this.element.style.overflowY = "scroll"; + this.listElement.style.overflowY = "scroll"; + } + + public _onKeydown(key: string, alt: boolean = false, shift: boolean = false, ctrl: boolean = false): boolean { + switch (key) { + case "ArrowUp": + return this.focusPrevious(); + break; + case "ArrowDown": + return this.focusNext(); + break; + case "Enter": + this.children[this.focused].click(); + return true; + break; + case "ArrowLeft": + // this.children[this.focused].collapse(); + return true; + break; + case "ArrowRight": + this.children[this.focused].expand(); + return true; + break; + default: + return this.children[this.focused]._onKeydown(key); + break; + } + return false; + } + + protected renderAsListItem(node: UINode) { + let li = document.createElement("li"); + li.appendChild(node.render()); + return li; + } + + public getElement(): HTMLElement { + return this.listElement; + } + + public isItemFocused(): boolean { + const has = this.children.find((child) => child.isFocused); + if (has) { + return true; + } + return false; + } + + private calculateTabIndex() { + this.children[this.focused].setTabbable(true); + } + + public clear() { + this.children.forEach((child) => this.remove(child)); + this.children = []; + this.listElement.innerHTML = ''; + this.focused = 0; + } + + public getFocusedChild() { + return this.children[this.focused]; + } + + public getFocus() { + return this.focused; + } + + public onSelect(f: (id: number) => void) { + this.selectCallback = f; + } + + protected calculateFocused(node: TreeviewItem) { + const idx = this.children.indexOf(node); + this._onSelect(idx); + } + + public focusPrevious() { + if (this.children[this.focused].isExpanded()) { + // return this.children[this.focused].focusPrevious(); + } else { + this.focused = Math.max(0, this.focused - 1); + this.children[this.focused].focus(); + } + return true; + } + + public focusNext() { + if (this.children[this.focused].isExpanded()) { + // return this.children[this.focused].focusNext(); + } else { + this.focused = Math.min(this.children.length - 1, this.focused + 1); + this.children[this.focused].focus(); + } + + return true; + } +} \ No newline at end of file diff --git a/frontend/src/ui/video.ts b/frontend/src/ui/video.ts new file mode 100644 index 0000000..42fb547 --- /dev/null +++ b/frontend/src/ui/video.ts @@ -0,0 +1,61 @@ +import { UINode } from "./node"; + +export class Video extends UINode { + private videoElement: HTMLVideoElement; + + public constructor(title: string, src: string | MediaStream = "") { + super(title); + this.videoElement = document.createElement("video"); + if (typeof src === "string") { + this.videoElement.src = src; // Set src if it's a string URL + } else if (src instanceof MediaStream) { + this.videoElement.srcObject = src; // Set srcObject if it's a MediaStream + } + this.videoElement.setAttribute("aria-label", title); + this.element.appendChild(this.videoElement); + this.setRole("video"); + } + + public getElement(): HTMLElement { + return this.videoElement; + } + + public setSource(src: string | MediaStream) { + if (typeof src === "string") { + this.videoElement.src = src; + } else if (src instanceof MediaStream) { + this.videoElement.srcObject = src; + } + return this; + } + + public play() { + this.videoElement.play(); + return this; + } + + public pause() { + this.videoElement.pause(); + return this; + } + + public setControls(show: boolean) { + this.videoElement.controls = show; + return this; + } + + public setLoop(loop: boolean) { + this.videoElement.loop = loop; + return this; + } + + public setMuted(muted: boolean) { + this.videoElement.muted = muted; + return this; + } + + public setAutoplay(autoplay: boolean) { + this.videoElement.autoplay = autoplay; + return this; + } +} diff --git a/frontend/src/ui/window.ts b/frontend/src/ui/window.ts new file mode 100644 index 0000000..1ca00bf --- /dev/null +++ b/frontend/src/ui/window.ts @@ -0,0 +1,82 @@ +import { Container } from "./container"; +import { UINode } from "./node"; + +export class UIWindow { + public title: string; + public width!: number; + public height!: number; + public position!: { x: number; y: number; }; + public container: Container; + public visible: boolean; + private element: HTMLDivElement; + private rendered!: boolean; + private keyDown: (e: KeyboardEvent) => void; + + public constructor( + title: string, + classname?: string, + private setTitle: boolean = true + ) { + this.title = title; + this.container = new Container(this.title); + this.container._onConnect(); + this.element = document.createElement("div"); + if (classname) { + this.element.className = classname; + } + this.keyDown = this.onKeyDown.bind(this); + this.visible = false; + } + + public add(node: UINode) { + this.container.add(node); + return this; + } + + public remove(node: UINode) { + if (this.container.children.includes(node)) this.container.remove(node); + return this; + } + + public show(): HTMLElement|undefined { + if (this.visible) return; + if (this.setTitle) document.title = this.title; + if (this.rendered) return this.element; + this.element.appendChild(this.container.render()); + this.element.addEventListener("keydown", this.keyDown); + this.element.focus(); + this.visible = true; + this.rendered = true; + return this.element; + } + + public hide() { + if (!this.visible) return; + this.visible = false; + this.rendered = false; + this.element.replaceChildren(); + this.element.removeEventListener("keydown", this.keyDown); + } + + public onKeyDown(e: KeyboardEvent) { + if (this.container._onKeydown(e.key, e.altKey, e.shiftKey, e.ctrlKey)) { + e.preventDefault(); + } + } + + public onConnect() { + return; + } + + public onDisconnect() { + return; + } + + public getElement(): HTMLElement { + return this.element; + } + + public getContainer(): Container { + return this.container; + } +} \ No newline at end of file diff --git a/frontend/src/views/authorize.ts b/frontend/src/views/authorize.ts new file mode 100644 index 0000000..016a128 --- /dev/null +++ b/frontend/src/views/authorize.ts @@ -0,0 +1,56 @@ +import { showToast } from "../speech"; +import { Button, Text, TextInput } from "../ui"; +import { View } from "./view"; +import { state } from "../state"; +import { API } from "../api"; +import { MainView } from "./main"; +import { playSound } from "../sound"; + +export class AuthorizeView extends View { + private welcomeText!: Text; + private apiURLInput!: TextInput; + private tokenInput!: TextInput; + private loginButton!: Button; + + public onActivate(): void { + playSound("intro"); + } + public onDeactivate(): void { + } + public onCreate(): void { + this.welcomeText = new Text("Welcome to Notebrook!"); + this.welcomeText.setPosition(25, 10, 75, 20); + this.apiURLInput = new TextInput("API URL"); + this.apiURLInput.setPosition(40, 40, 20, 20); + this.tokenInput = new TextInput("Token"); + this.tokenInput.setPosition(40, 60, 20, 10); + this.loginButton = new Button("Login"); + this.loginButton.setPosition(40, 70, 20, 10); + this.window.add(this.welcomeText); + this.window.add(this.apiURLInput); + this.window.add(this.tokenInput); + this.window.add(this.loginButton); + + this.loginButton.onClick(async () => { + const token = this.tokenInput.getValue(); + const apiUrl = this.apiURLInput.getValue(); + API.path = apiUrl; + API.token = token; + try { + await API.checkToken(); + state.token = token; + state.apiUrl = apiUrl; + state.save(); + showToast(`Welcome!`, 2000); + playSound("login"); + this.viewManager.push(new MainView(this.viewManager)); + } catch (e) { + showToast(`Invalid API URL or token provided.`); + playSound("uploadFailed"); + } + }); + } + + public onDestroy(): void { + } +} \ No newline at end of file diff --git a/frontend/src/views/main.ts b/frontend/src/views/main.ts new file mode 100644 index 0000000..8ff0edb --- /dev/null +++ b/frontend/src/views/main.ts @@ -0,0 +1,624 @@ +import { API } from "../api"; +import { ChunkProcessor } from "../chunk-processor"; +import { ChannelDialog } from "../dialogs/channel-dialog"; +import { CreateChannelDialog } from "../dialogs/create-channel"; +import { MessageDialog } from "../dialogs/message"; +import { RecordAudioDialog } from "../dialogs/record-audio"; +import { SearchDialog } from "../dialogs/search"; +import { SettingsDialog } from "../dialogs/settings"; +import { TakePhotoDialog } from "../dialogs/take-photo"; +import { MessageUpdated } from "../events/message-events"; +import { Channel } from "../model/channel"; +import { IMessage, Message } from "../model/message"; +import { UnsentMessage } from "../model/unsent-message"; +import { playSent, playSound, playWater } from "../sound"; +import { showToast } from "../speech"; +import { state } from "../state"; +import { Button, List, ListItem, TextInput, UINode } from "../ui"; +import { Dropdown } from "../ui/dropdown"; +import { FileInput } from "../ui/file-input"; +import { MultilineInput } from "../ui/multiline-input"; +import { connectToWebsocket } from "../websockets"; +import { AuthorizeView } from "./authorize"; +import { View } from "./view"; + +export class MainView extends View { + private settingsButton!: Button; + private channelSwitcher!: Dropdown; + private channelInfoButton!: Button; + private searchButton!: Button; + private fileInput!: FileInput; + private messageInput!: MultilineInput; + private imageInput!: Button; + private voiceMessageInput!: Button; + private messageList!: List; + private updateInterval!: number; + + private messageElementMap: Map = new Map(); + + private hotkeyMap: Map void> = new Map(); + + public onActivate(): void { + if (!state.currentChannel) { + if (state.defaultChannelId) { + this.switchChannel(state.defaultChannelId.toString()); + } else { + if (state.channelList.channels.length > 0) this.switchChannel(state.channelList.channels[0].id.toString()); + } + } + this.renderInitialMessageList(); + this.checkAuthorization(); + this.syncChannels(); + this.updateChannelList(); + this.syncMessages(); + this.updateInterval = setInterval(() => { + this.updateVisibleMessageShownTimestamps(); + }, 10000); + setTimeout(() => this.attemptToSendUnsentMessages(), 2000); + + state.events.registerHandler("message-updated", (message) => { + const { data } = message; + if (!data) return; + const channel = state.currentChannel; + if (!channel) return; + const existing = channel.getMessage(parseInt(data!.id)); + if (!existing) { + return; + } else { + existing.content = data.content; + state.save(); + const renderedMessage = this.messageElementMap.get(existing.id); + if (renderedMessage) { + (renderedMessage as ListItem).setText(`${existing.content}; ${this.convertIsoTimeStringToFriendly(existing.createdAt)}`); + } + } + }); + + document.addEventListener("keydown", (e) => this.handleHotkey(e)); + } + + public onDeactivate(): void { + clearInterval(this.updateInterval); + // unregister hotkey + document.removeEventListener("keydown", (e) => this.handleHotkey(e)); + } + + public onCreate(): void { + this.settingsButton = new Button("Settings") + .setPosition(0, 0, 10, 10) + .onClick(() => this.openSettingsDialog()); + this.channelSwitcher = new Dropdown("Channel", []) + .setPosition(30, 10, 30, 10); + this.channelInfoButton = new Button("Channel info") + .setPosition(60, 10, 30, 10); + this.searchButton = new Button("Search") + .setPosition(90, 10, 10, 10) + .onClick(async () => this.openSearchDialog()); + + this.fileInput = new FileInput("Upload file") + .setPosition(0, 90, 15, 10); + this.imageInput = new Button("Image") + .setPosition(15, 90, 15, 10); + this.messageInput = new MultilineInput("New message") + .setPosition(30, 90, 60, 10); + this.messageInput.getElement().autofocus = true; + this.voiceMessageInput = new Button("Voice message") + .setPosition(70, 90, 30, 10); + + this.messageList = new List("Messages") + .setPosition(30, 30, 60, 50); + this.window.add(this.settingsButton) + .add(this.channelSwitcher) + .add(this.channelInfoButton) + .add(this.searchButton) + .add(this.messageList) + .add(this.messageInput) + .add(this.fileInput) + .add(this.imageInput) + .add(this.voiceMessageInput); + this.channelSwitcher.getElement().addEventListener("change", (e) => this.handleChannelSwitcher(e)); + this.voiceMessageInput.onClick(async () => this.handleVoiceMessageButton()); + + this.messageInput.onKeyDown((key: string, alt: boolean | undefined, shift: boolean | undefined, ctrl: boolean | undefined) => { + if (key === "Enter") { + if (!shift) { + console.log(key, alt, shift, ctrl); + this.sendMessage(); + } + } + }); + this.channelInfoButton.onClick(() => this.handleChannelInfoButton()); + this.imageInput.onClick(async () => this.handleImageButton()); + this.setHotkeys(); + } + + public onDestroy(): void { + + } + + private async syncChannels() { + const channels = await API.getChannels(); + channels.forEach((chan) => state.addChannel(new Channel(chan))); + this.updateChannelList(); + if (!state.currentChannel) { + if (state.defaultChannelId) { + this.switchChannel(state.defaultChannelId.toString()); + } else { + if (state.channelList.channels.length > 0) { + this.switchChannel(state.channelList.channels[0].id.toString()); + } else { + this.createNewChannel(); + } + } + } + state.save(); + } + + private updateChannelList() { + this.channelSwitcher.clearOptions(); + state.getChannels().forEach((chan) => { + this.channelSwitcher.addOption(chan.id.toString(), chan.name); + }); + this.channelSwitcher.addOption("__new__", "Add new channel"); + } + + private checkAuthorization() { + if (!state.token || !state.apiUrl) { + this.viewManager.push(new AuthorizeView(this.viewManager)); + } else { + API.token = state.token; + API.path = state.apiUrl; + connectToWebsocket(); + } + + state.save(); + } + + private async syncMessages() { + if (!state.currentChannel) return; + if (!state.currentChannel.messages) state.currentChannel.messages = []; + const channelId = state.currentChannel.id; + if (channelId) { + const messages = await API.getMessages(channelId.toString()); + // only render new list items, or list items that have changed. + const proc = new ChunkProcessor(100); + proc.processArray(messages, (chunk: IMessage[]) => { + chunk.forEach((message: IMessage) => { + // TODO: this could do with a lot of perf improvements. I'll get to it once this is an issue. + const existing = state.currentChannel!.getMessage(message.id); + if (!existing) { + state.currentChannel!.addMessage(new Message(message)); + this.renderAndAddMessage(message); + } else { + // TODO: this is awful and needs to be updated, but it works for now. + if (existing.content !== message.content || existing.fileId !== message.fileId || existing.filePath !== message.filePath || existing.fileType !== message.fileType || existing.createdAt !== message.createdAt) { + existing.content = message.content; + existing.fileId = message.fileId; + existing.filePath = message.filePath; + existing.fileType = message.fileType; + existing.createdAt = message.createdAt; + existing.fileId = message.fileId; + existing.filePath = message.filePath; + existing.fileSize = message.fileSize; + const renderedMessage = this.messageElementMap.get(message.id); + if (renderedMessage) { + (renderedMessage as ListItem).setText(`${message.content}; ${this.convertIsoTimeStringToFriendly(message.createdAt)}`); + } + } + } + }); + }); + } + state.save(); + } + + public switchChannel(channelId: string) { + if (this.messageList.children.length > 0) this.messageList.clear(); + const chan = state.getChannelById(parseInt(channelId)); + if (!chan) { + throw new Error("Could not find channel " + channelId); + } + state.currentChannel = chan; + state.save(); + } + + private renderMessage(message: IMessage): UINode { + const itm = new ListItem(`${message.content}; ${this.convertIsoTimeStringToFriendly(message.createdAt)}`); + itm.setUserData(message.id); + itm.onClick(() => { + this.openMessageDialog(message); + }) + itm.onKeyDown((key: string, alt: boolean | undefined, shift: boolean | undefined, ctrl: boolean | undefined) => { + if (key === "Delete") { + this.removeMessage(message.id); + } + }); + return itm; + } + + private renderInitialMessageList(reset: boolean = false) { + if (!state.currentChannel) return; + if (!state.currentChannel.messages || state.currentChannel.messages.length < 1) return; + if (this.messageList.children.length > 0 && !reset) { + return; + } else { + this.messageList.clear(); + this.messageElementMap.clear(); + } + state.currentChannel.messages.forEach((message) => { + this.renderAndAddMessage(message); + }); + this.messageList.scrollToBottom(); + } + + private async createNewChannel() { + const name = await new CreateChannelDialog().open(); + if (name) { + const chan = await API.createChannel(name); + state.addChannel(new Channel(chan)); + this.updateChannelList(); + if (state.channelList.channels.length < 2) { + state.defaultChannelId = chan.id; + } + state.save(); + } + } + + private async sendMessage() { + if (this.fileInput && this.fileInput.getFiles() && this.fileInput.getFiles()!.length > 0) { + return this.uploadFile(); + } + if (this.messageInput.getValue().length > 0) { + const messageContent = this.messageInput.getValue(); + this.messageInput.setValue(""); + playWater(); + try { + const message: IMessage = await API.createMessage(state.currentChannel!.id.toString(), messageContent); + this.messageInput.setValue(""); + this.renderAndAddMessage(message); + this.messageList.scrollToBottom(); + playSent(); + state.save(); + } catch (e) { + showToast("Could not post message. Will retry later.", 3000); + playSound("uploadFailed"); + const unsentId = Date.now(); + state.unsentMessages.push(new UnsentMessage({ + channelId: state.currentChannel!.id, + content: messageContent, + createdAt: new Date().toISOString(), + id: unsentId + })); + const tmpMessage: IMessage = new Message({ + id: unsentId, + content: messageContent, + createdAt: new Date().toISOString(), + }); + state.currentChannel!.addMessage(tmpMessage); + this.renderAndAddMessage(tmpMessage); + state.save(); + } + } + } + + private async uploadVoiceMessage(blob: Blob) { + playWater(); + const msgContent = this.messageInput.getValue() !== "" ? this.messageInput.getValue() : "Voice message"; + this.messageInput.setValue(""); + const msg = await API.createMessage(state.currentChannel!.id.toString(), msgContent); + const id = msg.id; + try { + const response: any = await API.uploadFile(state.currentChannel!.id.toString(), id.toString(), blob); + if (msg) { + msg.fileId = response.fileId; + msg.filePath = response.filePath; + msg.fileType = response.fileType; + state.currentChannel!.addMessage(new Message(msg)); + this.renderAndAddMessage(msg); + playSent(); + state.save(); + } else { + showToast("Something went wrong during message file upload."); + playSound("uploadFailed"); + // TODO: Handle the case when no message is found + } + } catch (e) { + playSound("uploadFailed"); + showToast("Unable to send message. Will retry later.", 3000); + state.unsentMessages.push(new UnsentMessage({ + channelId: state.currentChannel!.id, + content: msgContent, + createdAt: new Date().toISOString(), + blob: blob, + id: Date.now() + })); + state.save(); + } + } + + private async uploadFile() { + if (!this.fileInput.getFiles()) return; + if (this.fileInput!.getFiles()!.length < 1) return; + const file = this.fileInput!.getFiles()![0]; + if (file) { + playWater(); + const msgContent = this.messageInput.getValue() !== "" ? this.messageInput.getValue() : "File upload"; + this.messageInput.setValue(""); + try { + const msg = await API.createMessage(state.currentChannel!.id.toString(), msgContent); + const id = msg.id; + const response: any = await API.uploadFile(state.currentChannel!.id.toString(), id.toString(), file); + if (msg) { + msg.fileId = response.fileId; + msg.filePath = response.filePath; + msg.fileType = response.fileType; + state.currentChannel!.addMessage(new Message(msg)); + this.renderAndAddMessage(msg); + playSent(); + this.messageInput.setValue(""); + // reset the file picker + (this.fileInput.getElement() as HTMLInputElement).value = ""; + state.save(); + } else { + showToast("Error while uploading file."); + playSound("uploadFailed"); + // TODO: Handle the case when no message is found + } + } catch (e) { + showToast("Could not post message. Will retry later.", 3000); + playSound("uploadFailed"); + state.unsentMessages.push(new UnsentMessage({ + channelId: state.currentChannel!.id, + content: this.messageInput.getValue(), + createdAt: new Date().toISOString(), + blob: file, + id: Date.now() + })); + state.save(); + } + } + } + + private convertIsoTimeStringToRelative(isoTimeString: string): string { + const date = new Date(isoTimeString); + const now = new Date(); + const diff = now.getTime() - date.getTime(); + if (diff < 1000 * 60) { + return `${Math.floor(diff / 1000)} seconds ago`; + } else if (diff < 1000 * 60 * 60) { + // return both minutes and seconds + return `${Math.floor(diff / (1000 * 60))} minutes ${Math.floor((diff % (1000 * 60)) / 1000)} seconds ago`; + } else if (diff < 1000 * 60 * 60 * 24) { + // return hours, minutes, seconds ago + return `${Math.floor(diff / (1000 * 60 * 60))} hours ${Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60))} minutes ${Math.floor((diff % (1000 * 60)) / 1000)} seconds ago`; + } else { + return date.toLocaleString(); + } + } + + private convertIsoTimeStringToFriendly(isoTimeString: string): string { + const date = new Date(isoTimeString); + const now = new Date(); + if (date.getDate() === now.getDate() && date.getMonth() === now.getMonth() && date.getFullYear() === now.getFullYear()) { + return date.toLocaleTimeString(); + } else { + return date.toLocaleString(); + } + } + private updateVisibleMessageShownTimestamps() { + const lowerIndex = Math.max(this.messageList.getFocus() - 10, 0); + const upperIndex = Math.min(this.messageList.getFocus() + 10, this.messageList.children.length); + for (let i = lowerIndex; i < upperIndex; i++) { + const child = this.messageList.children[i]; + if (!child) break; + const messageId = child.getUserData() as number; + const message = state.currentChannel?.getMessage(messageId); + if (message) { + (child as ListItem).setText(`${message.content}; ${this.convertIsoTimeStringToFriendly(message.createdAt)}`); + } + } + } + + private async attemptToSendUnsentMessages() { + state.unsentMessages.forEach(async (msg) => { + if (msg.blob) { + const apiMsg = await API.createMessage(msg.channelId.toString(), msg.content); + const id = apiMsg.id; + const response: any = await API.uploadFile(msg.channelId.toString(), id.toString(), msg.blob); + if (apiMsg) { + apiMsg.fileId = response.fileId; + apiMsg.filePath = response.filePath; + apiMsg.fileType = response.fileType; + state.currentChannel!.addMessage(new Message(apiMsg)); + this.renderAndAddMessage(apiMsg); + playSent(); + state.unsentMessages = state.unsentMessages.filter((m) => m !== msg); + this.removeSpecificMessageFromList(msg.id); + state.save(); + } + } else { + const apiMsg = await API.createMessage(msg.channelId.toString(), msg.content); + state.currentChannel!.addMessage(new Message(apiMsg)); + this.renderAndAddMessage(apiMsg); + state.unsentMessages = state.unsentMessages.filter((m) => m !== msg); + playSent(); + this.removeSpecificMessageFromList(msg.id); + state.save(); + } + }); + } + + private clearUnsentMessageDisplay() { + this.messageList.children.forEach((msg) => { + const data = msg.getUserData() as IMessage; + if (data.id === -1) { + this.messageList.remove(msg); + } + }) + } + + private removeSpecificMessageFromList(id: number) { + const elem = this.messageElementMap.get(id); + if (elem) { + this.messageList.remove(elem); + this.messageElementMap.delete(id); + } + } + + private async uploadImage(blob: Blob) { + playWater(); + try { + const msg = await API.createMessage(state.currentChannel!.id.toString(), "Image"); + const id = msg.id; + const response: any = await API.uploadFile(state.currentChannel!.id.toString(), id.toString(), blob); + if (msg) { + msg.fileId = response.fileId; + msg.filePath = response.filePath; + msg.fileType = response.fileType; + state.currentChannel!.addMessage(new Message(msg)); + this.renderAndAddMessage(msg); + playSent(); + state.save(); + } else { + showToast("Error while uploading file."); + playSound("uploadFailed"); + // TODO: Handle the case when no message is found + } + } catch (e) { + showToast("Could not post message. Will retry later.", 3000); + playSound("uploadFailed"); + state.unsentMessages.push(new UnsentMessage({ + channelId: state.currentChannel!.id, + content: "Image", + createdAt: new Date().toISOString(), + blob: blob, + id: Date.now() + })); + state.save(); + } + } + + private renderAndAddMessage(message: IMessage) { + const elem = this.renderMessage(message); + this.messageList.add(elem); + this.messageElementMap.set(message.id, elem); + } + + private async openSettingsDialog() { + const d = new SettingsDialog(); + d.open(); + } + + private async openSearchDialog() { + const searchDialog = new SearchDialog(); + const res = await searchDialog.open(); + if (res) { + if (res.channelId && res.messageId) { + if (state.currentChannel?.id !== res.channelId) { + this.switchChannel(res.channelId.toString()); + this.renderInitialMessageList(); + } + const message = state.currentChannel!.getMessage(res.messageId); + if (message) { + this.messageElementMap.get(message.id)?.focus(); + } + } + } + } + + private handleChannelSwitcher(e: Event) { + const target = e.target as HTMLSelectElement; + if (target.value === "__new__") { + this.createNewChannel(); + } else { + this.switchChannel(target.value); + this.renderInitialMessageList(); + this.syncMessages(); + } + } + + private async handleVoiceMessageButton() { + const blob = await new RecordAudioDialog().open(); + if (blob) { + this.uploadVoiceMessage(blob); + } + } + + private async handleChannelInfoButton() { + if (this.channelSwitcher.getSelectedValue() === "__new__") { + this.createNewChannel(); + return; + } + const d = new ChannelDialog(state.currentChannel!); + d.open().then((chan) => { + if (!chan) { + state.removeChannel(state.currentChannel!); + state.currentChannel = null; + this.updateChannelList(); + state.save(); + if (state.channelList.channels.length > 0) { + return this.switchChannel(state.channelList.channels[0].id.toString()); + } else { + return this.createNewChannel(); + } + } + if (chan.messages.length < 1) { + this.renderInitialMessageList(true); + this.syncMessages(); + } + state.save(); + this.updateChannelList(); + }); + } + + private async handleImageButton() { + const photo = await new TakePhotoDialog().open(); + this.uploadImage(photo); + } + + private setHotkeys() { + this.hotkeyMap.set("s", () => this.openSettingsDialog()); + this.hotkeyMap.set("c", () => this.channelSwitcher.focus()); + this.hotkeyMap.set("x", () => this.handleChannelInfoButton()); + this.hotkeyMap.set("f", () => this.openSearchDialog()); + this.hotkeyMap.set("v", () => this.handleVoiceMessageButton()); + this.hotkeyMap.set(" ", () => this.messageInput.focus()); + } + + private handleHotkey(e: KeyboardEvent) { + if (e.ctrlKey && e.shiftKey) { + const action = this.hotkeyMap.get(e.key.toLowerCase()); + if (action) { + e.preventDefault(); + action(); + } + } + } + + private async openMessageDialog(message: IMessage) { + const d = new MessageDialog(message); + const msg = await d.open(); + if (!msg || msg === null) { + state.currentChannel?.removeMessage(message.id); + const node = this.messageElementMap.get(message.id); + if (node) { + this.messageList.remove(node); + this.messageElementMap.delete(message.id); + } + state.save(); + } + } + + private async removeMessage(id: number) { + if (state.currentChannel) { + await API.deleteMessage(state.currentChannel.id.toString(), id.toString()); + state.currentChannel.removeMessage(id); + const node = this.messageElementMap.get(id); + if (node) { + this.messageList.remove(node); + this.messageElementMap.delete(id); + state.save(); + } + } + } +} \ No newline at end of file diff --git a/frontend/src/views/view-manager.ts b/frontend/src/views/view-manager.ts new file mode 100644 index 0000000..34e933d --- /dev/null +++ b/frontend/src/views/view-manager.ts @@ -0,0 +1,74 @@ +import { UIWindow } from "../ui/window"; +import { View } from "./view"; + +export class ViewManager { + private currentView: View | undefined | null; + private views: View[]; + private window: UIWindow; + public constructor() { + this.views = []; + this.window = new UIWindow("Notebrook"); + this.currentView = null; + } + + public add(view: View) { + this.views.push(view); + view.onCreate(); + } + + public remove(view: View) { + this.views.splice(this.views.indexOf(view), 1); + view.onDestroy(); + if (view === this.currentView) this.window.remove(this.currentView.show()); + if (this.currentView) this.currentView.setActive(false); + this.currentView = null; + } + + public switchTo(view: View) { + if (!this.views.includes(view)) { + throw new Error("View not initialized"); + } + if (this.currentView) { + this.currentView.onDeactivate(); + this.currentView.setActive(false); + this.window.remove(this.currentView.show()); + } + this.currentView = view; + this.currentView.setActive(true); + this.currentView.onActivate(); + this.window.add(this.currentView.show()); + } + + public render(): HTMLElement|undefined { + return this.window.show(); + } + + public push(view: View) { + if (this.currentView) { + this.currentView.onDeactivate(); + this.currentView.setActive(false); + this.window.remove(this.currentView.show()); + } + + this.views.unshift(view); + this.currentView = view; + this.currentView.onCreate(); + this.currentView.setActive(true); + this.currentView.onActivate(); + this.window.add(this.currentView.show()); + } + + public pop() { + if (this.currentView) { + this.currentView.onDeactivate(); + this.currentView.setActive(false); + this.window.remove(this.currentView.show()); + this.currentView.onDestroy(); + } + this.views.splice(0, 1); + this.currentView = this.views[0]; + this.currentView.setActive(true); + this.currentView.onActivate(); + this.window.add(this.currentView.show()); + } +} diff --git a/frontend/src/views/view.ts b/frontend/src/views/view.ts new file mode 100644 index 0000000..ec507a3 --- /dev/null +++ b/frontend/src/views/view.ts @@ -0,0 +1,33 @@ +import { Container } from "../ui/container"; +import { UIWindow } from "../ui/window"; +import { ViewManager } from "./view-manager"; + +export abstract class View { + protected viewManager: ViewManager; + protected window: Container; + private active!: boolean; + public constructor(viewManager: ViewManager) { + this.viewManager = viewManager; + this.window = new Container("Base view"); + } + + public show() { + return this.window; + } + + public abstract onActivate(): void; + + public abstract onDeactivate(): void; + + public abstract onCreate(): void; + + public abstract onDestroy(): void; + + public isActive() { + return this.isActive; + } + + public setActive(val: boolean) { + this.active = val; + } +} \ No newline at end of file diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/frontend/src/websockets.ts b/frontend/src/websockets.ts new file mode 100644 index 0000000..3a8cb6f --- /dev/null +++ b/frontend/src/websockets.ts @@ -0,0 +1,18 @@ +import { API } from "./api"; +import { state } from "./state"; + +export const connectToWebsocket = () => { + const ws = new WebSocket(`ws://localhost:3000`); + ws.onopen = () => { + console.log("Connected to websocket server"); + } + ws.onmessage = (data) => { + const message = JSON.parse(data.data.toString()); + state.events.sendMessage(message); + console.log(message); + } + ws.onclose= () => { + console.log("Disconnected from websocket server"); + } + return ws; +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..aab648f --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..004c6cd --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,35 @@ +import { defineConfig } from 'vite'; +import { VitePWA } from 'vite-plugin-pwa'; + + + +export default defineConfig({ + plugins: [ + VitePWA({ + registerType: 'autoUpdate', + includeAssets: ['intro.wav', 'login.wav', 'sent1.wav', 'sent2.wav', 'sent3.wav', 'sent4.wav', 'sent5.wav', 'sent6.wav', 'uploadfail.wav', 'water1.wav', 'water2.wav', 'water3.wav', 'water4.wav', 'water5.wav', 'water6.wav', 'water7.wav', 'water8.wav', 'water9.wav', 'water10.wav', 'index.html'], + manifest: { + name: 'Notebrook', + short_name: 'Notebrook', + description: 'Notebrook, stream of consciousness accessible note taking', + theme_color: '#ffffff', + icons: [ + { + src: 'icons/192x192.png', + sizes: '192x192', + type: 'image/png', + }, + { + src: 'icons/512x512.png', + sizes: '512x512', + type: 'image/png', + }, + ], + }, + workbox: { + + // workbox options for the service worker + }, + }), + ], +}); \ No newline at end of file diff --git a/maybe-dockerfile.txt b/maybe-dockerfile.txt new file mode 100644 index 0000000..240bbfe --- /dev/null +++ b/maybe-dockerfile.txt @@ -0,0 +1,33 @@ +I'll improve the docker build eventually, so I'll keep the planned improvs here. But for now it might be better to use the bigger multistage docker file. + +# Use the official Bun image +FROM oven/bun:1 AS base +WORKDIR /usr/src/app + +# Install dependencies for both backend and frontend +COPY backend/package.json backend/bun.lockb frontend/package.json frontend/bun.lockb /usr/src/app/ +RUN bun install --production --cwd backend && \ + bun install --production --cwd frontend + +# Build the frontend project +COPY frontend/ /usr/src/app/frontend +RUN bun run --cwd frontend build + +# Prepare for final release +FROM oven/bun:1 AS release +WORKDIR /usr/src/app + +# Copy production dependencies +COPY --from=base /usr/src/app/backend/node_modules backend/node_modules +COPY --from=base /usr/src/app/frontend/node_modules frontend/node_modules + +# Copy backend source code +COPY backend/ backend/ + +# Copy the built frontend assets into the backend public directory +COPY --from=base /usr/src/app/frontend/dist backend/public + +# Set the entrypoint to run the backend server +USER bun +EXPOSE 3000/tcp +ENTRYPOINT [ "bun", "run", "backend/src/server.ts" ] diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..3cba980 --- /dev/null +++ b/todo.md @@ -0,0 +1,7 @@ +* () Hotkeys in frontend +* () Electron +* () Whisper +* () Data Export +* () Convert sounds +* () Possibly figure out better sounds +* () Single executable for server