diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 000000000..7ace6e89c --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,63 @@ +module.exports = { + root: true, + env: { + browser: true, + es2021: true, + node: true + }, + extends: [ + 'standard', + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended' + ], + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: ['./tsconfig.json', './tsconfig.www.json'] // Include both TypeScript configs + }, + plugins: ['@typescript-eslint'], + rules: { + 'arrow-body-style': ['error', 'as-needed'], + 'func-style': ['error', 'expression'], + semi: ['error', 'always'], + quotes: ['error', 'single'], + indent: ['error', 2], + 'brace-style': ['error', '1tbs'], + 'no-var': 'error', + 'prefer-const': 'error', + '@typescript-eslint/no-explicit-any': 'warn', + 'no-unused-vars': 'off', // Turn off the base rule as it can report incorrect errors + '@typescript-eslint/no-unused-vars': ['warn', { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_' + }], + 'no-empty-function': 'off', + '@typescript-eslint/no-empty-function': 'off' + }, + overrides: [ + { + files: ['.eslintrc.cjs', 'vite.config.js', 'vite.worker.config.js', 'scripts/*', 'www/lib/*'], + env: { + node: true + }, + parserOptions: { + sourceType: 'script', + project: null // Disable TypeScript parsing for these files + } + }, + { + files: ['www/**/*.js', 'www/**/*.ts'], + parserOptions: { + project: './tsconfig.www.json' // Use tsconfig.www.json for files in the www directory + } + }, + { + files: ['src/**/*.ts'], + parserOptions: { + project: './tsconfig.json' // Use main tsconfig.json for files in the src directory + } + } + ] +}; diff --git a/.gitignore b/.gitignore index 1e32602ba..1d19be2e6 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,6 @@ dist/*.min.js dist/tensorflow.js dist/tensorflow.commonjs2.js www/trainingData/*.png -www/trainingData/*.db \ No newline at end of file +www/trainingData/*.db +www/lib/* +!www/lib/d3.v3.min.js \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f7ebc5492..fabdcf13c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,41 +15,592 @@ "regression": "2.0.1" }, "devDependencies": { - "browser-sync": "^3.0.2", + "@types/d3": "^3.5.53", + "@types/node": "^20.0.0", + "@typescript-eslint/eslint-plugin": "^5.0.0", + "@typescript-eslint/parser": "^5.0.0", + "bootstrap": "^5.2.3", + "concurrently": "^9.0.1", + "eslint": "^8.57.0", + "eslint-config-standard": "^17.1.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-n": "^16.6.2", + "eslint-plugin-promise": "^6.1.1", "filemanager-webpack-plugin": "^8.0.0", + "heatmap.js": "^2.0.5", "jsdoc": "^4.0.2", "rimraf": "2.6.3", - "webpack": "^5.75.0", - "webpack-cli": "^5.0.1", + "sweetalert": "^2.1.2", + "typescript": "^5.0.0", + "vite": "^5.4.3", + "vite-plugin-banner": "^0.8.0", "yarnhook": "^0.4.3" } }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz", + "integrity": "sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz", + "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/parser": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.6.tgz", - "integrity": "sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.8.tgz", + "integrity": "sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==", "dev": true, + "dependencies": { + "@babel/types": "^7.25.8" + }, "bin": { "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=6.0.0" + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.8.tgz", + "integrity": "sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.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" + ], + "dev": true, + "license": "MIT", + "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" + ], + "dev": true, + "license": "MIT", + "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" + ], + "dev": true, + "license": "MIT", + "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" + ], + "dev": true, + "license": "MIT", + "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" + ], + "dev": true, + "license": "MIT", + "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" + ], + "dev": true, + "license": "MIT", + "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" + ], + "dev": true, + "license": "MIT", + "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" + ], + "dev": true, + "license": "MIT", + "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" + ], + "dev": true, + "license": "MIT", + "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" + ], + "dev": true, + "license": "MIT", + "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" + ], + "dev": true, + "license": "MIT", + "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" + ], + "dev": true, + "license": "MIT", + "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" + ], + "dev": true, + "license": "MIT", + "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" + ], + "dev": true, + "license": "MIT", + "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" + ], + "dev": true, + "license": "MIT", + "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" + ], + "dev": true, + "license": "MIT", + "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" + ], + "dev": true, + "license": "MIT", + "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" + ], + "dev": true, + "license": "MIT", + "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" + ], + "dev": true, + "license": "MIT", + "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" + ], + "dev": true, + "license": "MIT", + "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" + ], + "dev": true, + "license": "MIT", + "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" + ], + "dev": true, + "license": "MIT", + "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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" } }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, "engines": { - "node": ">=10.0.0" + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", + "dev": true + }, "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==", "dev": true, + "peer": true, "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -64,6 +615,7 @@ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, + "peer": true, "engines": { "node": ">=6.0.0" } @@ -73,6 +625,7 @@ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, + "peer": true, "engines": { "node": ">=6.0.0" } @@ -82,22 +635,25 @@ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "dev": true, + "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "peer": true }, "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==", "dev": true, + "peer": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -150,11 +706,240 @@ "node": ">= 8" } }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", - "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", - "dev": true + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "dev": true, + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.2.tgz", + "integrity": "sha512-fSuPrt0ZO8uXeS+xP3b+yYTCBUd05MoSp2N/MFOgjhhUhMmchXlpTQrTpI8T+YAwAQuK7MafsCOxW7VrPMrJcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.2.tgz", + "integrity": "sha512-xGU5ZQmPlsjQS6tzTTGwMsnKUtu0WVbl0hYpTPauvbRAnmIvpInhJtgjj3mcuJpEiuUw4v1s4BimkdfDWlh7gA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.2.tgz", + "integrity": "sha512-99AhQ3/ZMxU7jw34Sq8brzXqWH/bMnf7ZVhvLk9QU2cOepbQSVTns6qoErJmSiAvU3InRqC2RRZ5ovh1KN0d0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.2.tgz", + "integrity": "sha512-ZbRaUvw2iN/y37x6dY50D8m2BnDbBjlnMPotDi/qITMJ4sIxNY33HArjikDyakhSv0+ybdUxhWxE6kTI4oX26w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.2.tgz", + "integrity": "sha512-ztRJJMiE8nnU1YFcdbd9BcH6bGWG1z+jP+IPW2oDUAPxPjo9dverIOyXz76m6IPA6udEL12reYeLojzW2cYL7w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.2.tgz", + "integrity": "sha512-flOcGHDZajGKYpLV0JNc0VFH361M7rnV1ee+NTeC/BQQ1/0pllYcFmxpagltANYt8FYf9+kL6RSk80Ziwyhr7w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.2.tgz", + "integrity": "sha512-69CF19Kp3TdMopyteO/LJbWufOzqqXzkrv4L2sP8kfMaAQ6iwky7NoXTp7bD6/irKgknDKM0P9E/1l5XxVQAhw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.2.tgz", + "integrity": "sha512-48pD/fJkTiHAZTnZwR0VzHrao70/4MlzJrq0ZsILjLW/Ab/1XlVUStYyGt7tdyIiVSlGZbnliqmult/QGA2O2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.2.tgz", + "integrity": "sha512-cZdyuInj0ofc7mAQpKcPR2a2iu4YM4FQfuUzCVA2u4HI95lCwzjoPtdWjdpDKyHxI0UO82bLDoOaLfpZ/wviyQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.2.tgz", + "integrity": "sha512-RL56JMT6NwQ0lXIQmMIWr1SW28z4E4pOhRRNqwWZeXpRlykRIlEpSWdsgNWJbYBEWD84eocjSGDu/XxbYeCmwg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.2.tgz", + "integrity": "sha512-PMxkrWS9z38bCr3rWvDFVGD6sFeZJw4iQlhrup7ReGmfn7Oukrr/zweLhYX6v2/8J6Cep9IEA/SmjXjCmSbrMQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.2.tgz", + "integrity": "sha512-B90tYAUoLhU22olrafY3JQCFLnT3NglazdwkHyxNDYF/zAxJt5fJUB/yBoWFoIQ7SQj+KLe3iL4BhOMa9fzgpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.2.tgz", + "integrity": "sha512-7twFizNXudESmC9oneLGIUmoHiiLppz/Xs5uJQ4ShvE6234K0VB1/aJYU3f/4g7PhssLGKBVCC37uRkkOi8wjg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.2.tgz", + "integrity": "sha512-9rRero0E7qTeYf6+rFh3AErTNU1VCQg2mn7CQcI44vNUWM9Ze7MSRS/9RFuSsox+vstRt97+x3sOhEey024FRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.2.tgz", + "integrity": "sha512-5rA4vjlqgrpbFVVHX3qkrCo/fZTj1q0Xxpg+Z7yIo3J2AilW7t2+n6Q8Jrx+4MrYpAnjttTYF8rr7bP46BPzRw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.2.tgz", + "integrity": "sha512-6UUxd0+SKomjdzuAcp+HAmxw1FlGBnl1v2yEPSabtx4lBfdXHDVsW7+lQkgz9cNFJGY3AWR7+V8P5BqkD9L9nA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, "node_modules/@tensorflow-models/blazeface": { "version": "0.0.7", @@ -304,26 +1089,18 @@ "@types/readdir-glob": "*" } }, - "node_modules/@types/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "node_modules/@types/d3": { + "version": "3.5.53", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-3.5.53.tgz", + "integrity": "sha512-8yKQA9cAS6+wGsJpBysmnhlaaxlN42Qizqkw+h2nILSlS+MAG2z4JdO6p+PJrJ+ACvimkmLJL281h157e52psQ==", "dev": true }, - "node_modules/@types/cors": { - "version": "2.8.17", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", - "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/eslint": { "version": "8.56.10", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", "dev": true, + "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -334,6 +1111,7 @@ "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "dev": true, + "peer": true, "dependencies": { "@types/eslint": "*", "@types/estree": "*" @@ -351,6 +1129,12 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, "node_modules/@types/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", @@ -400,35 +1184,272 @@ "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.3.0.tgz", "integrity": "sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==" }, - "node_modules/@types/readdir-glob": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@types/readdir-glob/-/readdir-glob-1.1.5.tgz", - "integrity": "sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==", + "node_modules/@types/readdir-glob": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@types/readdir-glob/-/readdir-glob-1.1.5.tgz", + "integrity": "sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/seedrandom": { + "version": "2.4.34", + "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.34.tgz", + "integrity": "sha512-ytDiArvrn/3Xk6/vtylys5tlY6eo7Ane0hvcx++TKo6RxQXuVfW0AF/oeWqAj9dN29SyhtawuXstgmPlwNcv/A==" + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true + }, + "node_modules/@types/webgl-ext": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/webgl-ext/-/webgl-ext-0.0.30.tgz", + "integrity": "sha512-LKVgNmBxN0BbljJrVUwkxwRYqzsAEPcZOe6S2T6ZaBDIrFp0qu4FNlpc5sM1tGbXUYFgdVQIoeLk1Y1UoblyEg==" + }, + "node_modules/@types/webgl2": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/webgl2/-/webgl2-0.0.6.tgz", + "integrity": "sha512-50GQhDVTq/herLMiqSQkdtRu+d5q/cWHn4VvKJtrj4DJAjo1MNkWYa2MA41BaBO1q1HgsUjuQvEOk0QHvlnAaQ==" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", "dev": true, "dependencies": { - "@types/node": "*" + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@types/seedrandom": { - "version": "2.4.34", - "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.34.tgz", - "integrity": "sha512-ytDiArvrn/3Xk6/vtylys5tlY6eo7Ane0hvcx++TKo6RxQXuVfW0AF/oeWqAj9dN29SyhtawuXstgmPlwNcv/A==" - }, - "node_modules/@types/webgl-ext": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/@types/webgl-ext/-/webgl-ext-0.0.30.tgz", - "integrity": "sha512-LKVgNmBxN0BbljJrVUwkxwRYqzsAEPcZOe6S2T6ZaBDIrFp0qu4FNlpc5sM1tGbXUYFgdVQIoeLk1Y1UoblyEg==" - }, - "node_modules/@types/webgl2": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@types/webgl2/-/webgl2-0.0.6.tgz", - "integrity": "sha512-50GQhDVTq/herLMiqSQkdtRu+d5q/cWHn4VvKJtrj4DJAjo1MNkWYa2MA41BaBO1q1HgsUjuQvEOk0QHvlnAaQ==" + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true }, "node_modules/@webassemblyjs/ast": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", "dev": true, + "peer": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6" @@ -438,25 +1459,29 @@ "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "dev": true, + "peer": true, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.11.6", "@webassemblyjs/helper-api-error": "1.11.6", @@ -467,13 +1492,15 @@ "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", "dev": true, + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-buffer": "1.12.1", @@ -486,6 +1513,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "dev": true, + "peer": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -495,6 +1523,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "dev": true, + "peer": true, "dependencies": { "@xtuc/long": "4.2.2" } @@ -503,13 +1532,15 @@ "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", "dev": true, + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-buffer": "1.12.1", @@ -526,6 +1557,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", "dev": true, + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", @@ -539,6 +1571,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", "dev": true, + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-buffer": "1.12.1", @@ -551,6 +1584,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", "dev": true, + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-api-error": "1.11.6", @@ -565,6 +1599,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", "dev": true, + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.12.1", "@xtuc/long": "4.2.2" @@ -575,457 +1610,750 @@ "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.16.tgz", "integrity": "sha512-9E61voMP4+Rze02jlTXud++Htpjyyk8vw5Hyw9FGRrmhHQg2GqbuOfwf5Klrb8vTxc2XWI3EfO7RUHMpxTj26A==" }, - "node_modules/@webpack-cli/configtest": { + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "peer": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "peer": true + }, + "node_modules/acorn": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", + "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "dev": true, + "peer": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", + "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", - "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "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/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "dev": true, + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dev": true, + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "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/archiver-utils/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "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==", + "dev": true, + "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/array-includes": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", + "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" + "node": ">=8" } }, - "node_modules/@webpack-cli/info": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", - "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "node_modules/array.prototype.filter": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.3.tgz", + "integrity": "sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==", "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + }, "engines": { - "node": ">=14.15.0" + "node": ">= 0.4" }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@webpack-cli/serve": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", - "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "node_modules/array.prototype.findlastindex": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.4.tgz", + "integrity": "sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ==", "dev": true, - "engines": { - "node": ">=14.15.0" + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" + "engines": { + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "webpack-dev-server": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", "dev": true, "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", "dev": true, - "bin": { - "acorn": "bin/acorn" + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" }, "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", - "dev": true, - "peerDependencies": { - "acorn": "^8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "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==", "dev": true, "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" + "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": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ajv": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", - "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "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==", "dev": true, "dependencies": { - "fast-deep-equal": "^3.1.3", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.4.1" + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "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/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true, - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true + "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/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "dev": true, "dependencies": { - "fast-deep-equal": "^3.1.3" - }, + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "node_modules/bootstrap": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", + "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], "peerDependencies": { - "ajv": "^8.8.2" + "@popperjs/core": "^2.11.8" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" + "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/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "node_modules/browserslist": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "dev": true, + "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" + } + ], + "peer": true, "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" }, "engines": { - "node": ">= 8" + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/archiver": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", - "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "dependencies": { - "archiver-utils": "^2.1.0", - "async": "^3.2.4", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^2.2.0", - "zip-stream": "^4.1.0" - }, + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, "engines": { - "node": ">= 10" + "node": "*" } }, - "node_modules/archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "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==", "dev": true, - "dependencies": { - "glob": "^7.1.4", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" - }, - "engines": { - "node": ">= 6" - } + "peer": true }, - "node_modules/archiver-utils/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "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==", "dev": true, - "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" + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/archiver-utils/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/archiver-utils/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "node_modules/builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", "dev": true, "dependencies": { - "safe-buffer": "~5.1.0" + "semver": "^7.0.0" } }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/builtins/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, "dependencies": { - "sprintf-js": "~1.0.2" + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "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==", "dev": true, + "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": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true - }, - "node_modules/async-each-series": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/async-each-series/-/async-each-series-0.1.1.tgz", - "integrity": "sha512-p4jj6Fws4Iy2m0iCmI2am2ZNZCgbdgE+P8F/8csmn2vx7ixXrO2zGcuNsD46X5uZSVecmkEy/M06X2vG8KD6dQ==", + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, "engines": { - "node": ">=0.8.0" + "node": ">=6" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "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/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "node_modules/caniuse-lite": { + "version": "1.0.30001621", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001621.tgz", + "integrity": "sha512-+NLXZiviFFKX0fk8Piwv3PfLPGtRqJeq2TiNoUff/qB5KJgwecJTvCXDpmlyP/eCI/GUEmp/h/y5j0yckiiZrA==", "dev": true, "funding": [ { - "type": "github", - "url": "https://github.com/sponsors/feross" + "type": "opencollective", + "url": "https://opencollective.com/browserslist" }, { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" }, { - "type": "consulting", - "url": "https://feross.org/support" + "type": "github", + "url": "https://github.com/sponsors/ai" } - ] + ], + "peer": true }, - "node_modules/base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "node_modules/catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", "dev": true, + "dependencies": { + "lodash": "^4.17.15" + }, "engines": { - "node": "^4.5.0 || >= 5.9" + "node": ">= 10" } }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", - "dev": true - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, + "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": ">=8" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", "dev": true, - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" + "peer": true, + "engines": { + "node": ">=6.0" } }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } }, - "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==", + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, + "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": { - "fill-range": "^7.1.1" + "color-name": "~1.1.4" }, "engines": { - "node": ">=8" + "node": ">=7.0.0" } }, - "node_modules/browser-sync": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/browser-sync/-/browser-sync-3.0.2.tgz", - "integrity": "sha512-PC9c7aWJFVR4IFySrJxOqLwB9ENn3/TaXCXtAa0SzLwocLN3qMjN+IatbjvtCX92BjNXsY6YWg9Eb7F3Wy255g==", - "dev": true, - "dependencies": { - "browser-sync-client": "^3.0.2", - "browser-sync-ui": "^3.0.2", - "bs-recipes": "1.3.4", - "chalk": "4.1.2", - "chokidar": "^3.5.1", - "connect": "3.6.6", - "connect-history-api-fallback": "^1", - "dev-ip": "^1.0.1", - "easy-extender": "^2.3.4", - "eazy-logger": "^4.0.1", - "etag": "^1.8.1", - "fresh": "^0.5.2", - "fs-extra": "3.0.1", - "http-proxy": "^1.18.1", - "immutable": "^3", - "micromatch": "^4.0.2", - "opn": "5.3.0", - "portscanner": "2.2.0", - "raw-body": "^2.3.2", - "resp-modifier": "6.0.2", - "rx": "4.1.0", - "send": "0.16.2", - "serve-index": "1.9.1", - "serve-static": "1.13.2", - "server-destroy": "1.0.1", - "socket.io": "^4.4.1", - "ua-parser-js": "^1.0.33", - "yargs": "^17.3.1" - }, - "bin": { - "browser-sync": "dist/bin.js" + "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/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" }, "engines": { - "node": ">= 8.0.0" + "node": ">= 0.8" } }, - "node_modules/browser-sync-client": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/browser-sync-client/-/browser-sync-client-3.0.2.tgz", - "integrity": "sha512-tBWdfn9L0wd2Pjuz/NWHtNEKthVb1Y67vg8/qyGNtCqetNz5lkDkFnrsx5UhPNPYUO8vci50IWC/BhYaQskDiQ==", + "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==", + "dev": true, + "peer": true + }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", "dev": true, "dependencies": { - "etag": "1.8.1", - "fresh": "0.5.2", - "mitt": "^1.1.3" + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" }, "engines": { - "node": ">=8.0.0" + "node": ">= 10" } }, - "node_modules/browser-sync-ui": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/browser-sync-ui/-/browser-sync-ui-3.0.2.tgz", - "integrity": "sha512-V3FwWAI+abVbFLTyJjXJlCMBwjc3GXf/BPGfwO2fMFACWbIGW9/4SrBOFYEOOtqzCjQE0Di+U3VIb7eES4omNA==", + "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/concurrently": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.0.1.tgz", + "integrity": "sha512-wYKvCd/f54sTXJMSfV6Ln/B8UrfLBKOYa+lzc6CHay3Qek+LorVSBdMVfyewFhRbH0Rbabsk4D+3PL/VjQ5gzg==", "dev": true, "dependencies": { - "async-each-series": "0.1.1", - "chalk": "4.1.2", - "connect-history-api-fallback": "^1", - "immutable": "^3", - "server-destroy": "1.0.1", - "socket.io-client": "^4.4.1", - "stream-throttle": "^0.1.3" + "chalk": "^4.1.2", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, - "node_modules/browser-sync/node_modules/cliui": { + "node_modules/concurrently/node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", @@ -1035,11 +2363,26 @@ "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" }, - "engines": { - "node": ">=12" + "engines": { + "node": ">=12" + } + }, + "node_modules/concurrently/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/browser-sync/node_modules/yargs": { + "node_modules/concurrently/node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", @@ -1057,7 +2400,7 @@ "node": ">=12" } }, - "node_modules/browser-sync/node_modules/yargs-parser": { + "node_modules/concurrently/node_modules/yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", @@ -1066,683 +2409,887 @@ "node": ">=12" } }, - "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "node_modules/core-js": { + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.1.tgz", + "integrity": "sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", "dev": true, - "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.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" - }, "bin": { - "browserslist": "cli.js" + "crc32": "bin/crc32.njs" }, "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": ">=0.8" } }, - "node_modules/bs-recipes": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/bs-recipes/-/bs-recipes-1.3.4.tgz", - "integrity": "sha512-BXvDkqhDNxXEjeGM8LFkSbR+jzmP/CYpCiVKYn+soB1dDldeU15EBNDkwVXndKuX35wnNUaPd0qSoQEAkmQtMw==", - "dev": true + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "dev": true, + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, "engines": { - "node": "*" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "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/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "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==", "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, "engines": { - "node": ">= 0.8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001621", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001621.tgz", - "integrity": "sha512-+NLXZiviFFKX0fk8Piwv3PfLPGtRqJeq2TiNoUff/qB5KJgwecJTvCXDpmlyP/eCI/GUEmp/h/y5j0yckiiZrA==", - "dev": true, - "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/catharsis": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", - "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "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==", "dev": true, "dependencies": { - "lodash": "^4.17.15" + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" }, "engines": { - "node": ">= 10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/del": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", + "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", + "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "globby": "^11.0.1", + "graceful-fs": "^4.2.4", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.2", + "p-map": "^4.0.0", + "rimraf": "^3.0.2", + "slash": "^3.0.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "node_modules/del/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "glob": "^7.1.3" }, - "engines": { - "node": ">= 8.10.0" + "bin": { + "rimraf": "bin.js" }, "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, "engines": { - "node": ">=6.0" + "node": ">=8" } }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, "engines": { - "node": ">=6" + "node": ">=6.0.0" } }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "node_modules/electron-to-chromium": { + "version": "1.4.783", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.783.tgz", + "integrity": "sha512-bT0jEz/Xz1fahQpbZ1D7LgmPYZ3iHVY39NcWWro1+hA2IvjiPeaXtfSqrQ+nXjApMvQRE2ASt1itSLRrebHMRQ==", + "dev": true, + "peer": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "once": "^1.4.0" } }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "node_modules/enhanced-resolve": { + "version": "5.16.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz", + "integrity": "sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==", + "dev": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, "engines": { - "node": ">=6" + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "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" + "node_modules/es-abstract": { + "version": "1.22.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.5.tgz", + "integrity": "sha512-oW69R+4q2wG+Hc3KZePPZxOiisRIqfKBVo/HLx94QcJeWGU/8sZhCvc829rd1kS366vlJbzBfXf9yWwf0+Ko7w==", + "dev": true, + "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", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.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.1", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "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.0", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", + "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.5", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.14" }, "engines": { - "node": ">=7.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", "dev": true }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "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==", + "dev": true, "dependencies": { - "delayed-stream": "~1.0.0" + "get-intrinsic": "^1.2.4" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.4" } }, - "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==", - "dev": true - }, - "node_modules/compress-commons": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", - "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "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==", "dev": true, - "dependencies": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^4.0.2", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" - }, "engines": { - "node": ">= 10" + "node": ">= 0.4" } }, - "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/es-module-lexer": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.3.tgz", + "integrity": "sha512-i1gCgmR9dCl6Vil6UKPI/trA69s08g/syhiDK9TG0Nf1RJjjFI+AzoWW7sPufzkgYAn861skuCwJa0pIIHYxvg==", + "dev": true, + "peer": true }, - "node_modules/connect": { - "version": "3.6.6", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.6.tgz", - "integrity": "sha512-OO7axMmPpu/2XuX1+2Yrg0ddju31B6xLZMWkJ5rYBu4YRmRVlOjvlY6kw2FJKiAzyxGwnrDUAG4s1Pf0sbBMCQ==", + "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==", "dev": true, "dependencies": { - "debug": "2.6.9", - "finalhandler": "1.1.0", - "parseurl": "~1.3.2", - "utils-merge": "1.0.1" + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" }, "engines": { - "node": ">= 0.10.0" + "node": ">= 0.4" } }, - "node_modules/connect-history-api-fallback": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", - "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", "dev": true, - "engines": { - "node": ">=0.8" + "dependencies": { + "hasown": "^2.0.0" } }, - "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "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==", "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, "engines": { - "node": ">= 0.6" - } - }, - "node_modules/core-js": { - "version": "3.37.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.1.tgz", - "integrity": "sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==", - "hasInstallScript": true, + "node": ">= 0.4" + }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "node_modules/es6-object-assign": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", + "integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==", "dev": true }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "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==", "dev": true, - "dependencies": { - "object-assign": "^4", - "vary": "^1" + "hasInstallScript": true, + "license": "MIT", + "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": ">= 0.10" + "node": ">=6" } }, - "node_modules/crc-32": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", - "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, "bin": { - "crc32": "bin/crc32.njs" + "eslint": "bin/eslint.js" }, "engines": { - "node": ">=0.8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/crc32-stream": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", - "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "node_modules/eslint-compat-utils": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.1.2.tgz", + "integrity": "sha512-Jia4JDldWnFNIru1Ehx1H5s9/yxiRHY/TimCuUc0jNexew3cF1gI6CYZil1ociakfWO3rRqFjl1mskBblB3RYg==", "dev": true, - "dependencies": { - "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" - }, "engines": { - "node": ">= 10" + "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" } }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "node_modules/eslint-config-standard": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz", + "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==", "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.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" + } + ], "engines": { - "node": ">= 8" + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": "^8.0.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", + "eslint-plugin-promise": "^6.0.0" } }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", "dev": true, "dependencies": { - "ms": "2.0.0" + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" } }, - "node_modules/del": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", - "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "dependencies": { - "globby": "^11.0.1", - "graceful-fs": "^4.2.4", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.2", - "p-map": "^4.0.0", - "rimraf": "^3.0.2", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "ms": "^2.1.1" } }, - "node_modules/del/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "node_modules/eslint-module-utils": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", + "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", "dev": true, "dependencies": { - "glob": "^7.1.3" + "debug": "^3.2.7" }, - "bin": { - "rimraf": "bin.js" + "engines": { + "node": ">=4" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "node_modules/eslint-plugin-es-x": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.5.0.tgz", + "integrity": "sha512-ODswlDSO0HJDzXU0XvgZ3lF3lS3XAZEossh15Q2UHjwrJggWeBoKqqEsLTZLXl+dh5eOAozG0zRcYtuE35oTuQ==", "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.6.0", + "eslint-compat-utils": "^0.1.2" + }, "engines": { - "node": ">= 0.8" + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": ">=8" } }, - "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==", - "dev": true - }, - "node_modules/dev-ip": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dev-ip/-/dev-ip-1.0.1.tgz", - "integrity": "sha512-LmVkry/oDShEgSZPNgqCIp2/TlqtExeGmymru3uCELnfyjY11IzpAproLYs+1X88fXO6DBoYP3ul2Xo2yz2j6A==", + "node_modules/eslint-plugin-import": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, - "bin": { - "dev-ip": "lib/dev-ip.js" + "dependencies": { + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" } }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" + "ms": "^2.1.1" } }, - "node_modules/easy-extender": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/easy-extender/-/easy-extender-2.3.4.tgz", - "integrity": "sha512-8cAwm6md1YTiPpOvDULYJL4ZS6WfM5/cTeVVh4JsvyYZAoqlRVUpHL9Gr5Fy7HA6xcSZicUia3DeAgO3Us8E+Q==", + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "dependencies": { - "lodash": "^4.17.10" + "esutils": "^2.0.2" }, "engines": { - "node": ">= 4.0.0" + "node": ">=0.10.0" } }, - "node_modules/eazy-logger": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/eazy-logger/-/eazy-logger-4.0.1.tgz", - "integrity": "sha512-2GSFtnnC6U4IEKhEI7+PvdxrmjJ04mdsj3wHZTFiw0tUtG4HCWzTr13ZYTk8XOGnA1xQMaDljoBOYlk3D/MMSw==", + "node_modules/eslint-plugin-n": { + "version": "16.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.6.2.tgz", + "integrity": "sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==", "dev": true, "dependencies": { - "chalk": "4.1.2" + "@eslint-community/eslint-utils": "^4.4.0", + "builtins": "^5.0.1", + "eslint-plugin-es-x": "^7.5.0", + "get-tsconfig": "^4.7.0", + "globals": "^13.24.0", + "ignore": "^5.2.4", + "is-builtin-module": "^3.2.1", + "is-core-module": "^2.12.1", + "minimatch": "^3.1.2", + "resolve": "^1.22.2", + "semver": "^7.5.3" }, "engines": { - "node": ">= 0.8.0" + "node": ">=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=7.0.0" } }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true - }, - "node_modules/electron-to-chromium": { - "version": "1.4.783", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.783.tgz", - "integrity": "sha512-bT0jEz/Xz1fahQpbZ1D7LgmPYZ3iHVY39NcWWro1+hA2IvjiPeaXtfSqrQ+nXjApMvQRE2ASt1itSLRrebHMRQ==", - "dev": true - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "node_modules/eslint-plugin-n/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">= 0.8" + "node": ">=10" } }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "node_modules/eslint-plugin-promise": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", + "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", "dev": true, - "dependencies": { - "once": "^1.4.0" + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" } }, - "node_modules/engine.io": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", - "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "dependencies": { - "@types/cookie": "^0.4.1", - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.4.1", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.11.0" + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" }, "engines": { - "node": ">=10.2.0" + "node": ">=8.0.0" } }, - "node_modules/engine.io-client": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz", - "integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==", + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.11.0", - "xmlhttprequest-ssl": "~2.0.0" + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/engine.io-client/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "dependencies": { - "ms": "2.1.2" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "engines": { - "node": ">=6.0" + "node": ">=10" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/engine.io-client/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==", - "dev": true + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } }, - "node_modules/engine.io-parser": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", - "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "engines": { - "node": ">=10.0.0" + "node": ">=4.0" } }, - "node_modules/engine.io/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "dependencies": { - "ms": "2.1.2" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=6.0" + "node": ">=10" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/engine.io/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==", - "dev": true - }, - "node_modules/enhanced-resolve": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz", - "integrity": "sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==", + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "is-glob": "^4.0.3" }, "engines": { "node": ">=10.13.0" } }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, "engines": { - "node": ">=0.12" + "node": ">=10" }, "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/envinfo": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.13.0.tgz", - "integrity": "sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q==", + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, - "bin": { - "envinfo": "dist/cli.js" + "dependencies": { + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/es-module-lexer": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.3.tgz", - "integrity": "sha512-i1gCgmR9dCl6Vil6UKPI/trA69s08g/syhiDK9TG0Nf1RJjjFI+AzoWW7sPufzkgYAn861skuCwJa0pIIHYxvg==", - "dev": true - }, - "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true - }, - "node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "estraverse": "^5.1.0" }, "engines": { - "node": ">=8.0.0" + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" } }, "node_modules/esrecurse": { @@ -1775,26 +3322,21 @@ "node": ">=4.0" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, "engines": { - "node": ">= 0.6" + "node": ">=0.10.0" } }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, + "peer": true, "engines": { "node": ">=0.8.x" } @@ -1850,14 +3392,11 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true, - "engines": { - "node": ">= 4.9.1" - } + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true }, "node_modules/fastq": { "version": "1.17.1", @@ -1868,6 +3407,18 @@ "reusify": "^1.0.4" } }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, "node_modules/filemanager-webpack-plugin": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/filemanager-webpack-plugin/-/filemanager-webpack-plugin-8.0.0.tgz", @@ -1937,70 +3488,54 @@ "node": ">=8" } }, - "node_modules/finalhandler": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", - "integrity": "sha512-ejnvM9ZXYzp6PUPUyQBMBf0Co5VX2gr5H2VQe2Ui2jWXNlxv+PYZo8wpAymJNJdLsG1R4p+M4aynF8KuoUEwRw==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.1", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "statuses": "~1.3.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/find-parent-dir": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/find-parent-dir/-/find-parent-dir-0.3.1.tgz", "integrity": "sha512-o4UcykWV/XN9wm+jMEtWLPlV8RXCZnMhQI6F6OdHeSez7iiJWePw8ijOlskJZMsaQoGR/b7dH6lO02HhaTN7+A==", "dev": true }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" }, "engines": { - "node": ">=8" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "node_modules/flat-cache/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, "bin": { - "flat": "cli.js" + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "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==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } + "dependencies": { + "is-callable": "^1.1.3" } }, "node_modules/form-data": { @@ -2016,32 +3551,12 @@ "node": ">= 6" } }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "dev": true }, - "node_modules/fs-extra": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", - "integrity": "sha512-V3Z3WZWVUYd8hoCL5xfXJCaHWYzmtwW5XWYSlLgERi8PWd8bx1kUHUk8L1BT57e49oKnDDD180mjfrHc1yA9rg==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^3.0.0", - "universalify": "^0.1.0" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2070,6 +3585,33 @@ "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==", + "dev": true, + "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==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -2078,19 +3620,67 @@ "node": "6.* || 8.* || >= 10.*" } }, + "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==", + "dev": true, + "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-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, "dependencies": { - "pump": "^3.0.0" + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" }, "engines": { - "node": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.3.tgz", + "integrity": "sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, "node_modules/glob": { @@ -2128,7 +3718,38 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true + "dev": true, + "peer": true + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/globby": { "version": "11.1.0", @@ -2150,12 +3771,39 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "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==", "dev": true }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "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==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2164,57 +3812,75 @@ "node": ">=8" } }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "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==", "dev": true, "dependencies": { - "function-bind": "^1.1.2" + "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==", + "dev": true, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "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==", "dev": true, - "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": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "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==", "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, "engines": { - "node": ">= 0.8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" + "function-bind": "^1.1.2" }, "engines": { - "node": ">=8.0.0" + "node": ">= 0.4" } }, + "node_modules/heatmap.js": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/heatmap.js/-/heatmap.js-2.0.5.tgz", + "integrity": "sha512-CG2gYFP5Cv9IQCXEg3ZRxnJDyAilhWnQlAuHYGuWVzv6mFtQelS1bR9iN80IyDmFECbFPbg6I0LR5uAFHgCthw==", + "dev": true + }, "node_modules/human-signals": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", @@ -2224,18 +3890,6 @@ "node": ">=8.12.0" } }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -2270,34 +3924,40 @@ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" }, - "node_modules/immutable": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz", - "integrity": "sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", @@ -2322,25 +3982,89 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "node_modules/interpret": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", - "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "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==", + "dev": true, + "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==", "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, "engines": { - "node": ">=10.13.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "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==", "dev": true, "dependencies": { - "binary-extensions": "^2.0.0" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" }, "engines": { - "node": ">=8" + "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==", + "dev": true, + "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==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-core-module": { @@ -2355,6 +4079,21 @@ "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==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2384,6 +4123,18 @@ "node": ">=0.10.0" } }, + "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==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -2393,13 +4144,19 @@ "node": ">=0.12.0" } }, - "node_modules/is-number-like": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/is-number-like/-/is-number-like-1.0.8.tgz", - "integrity": "sha512-6rZi3ezCyFcn5L71ywzz2bS5b2Igl1En3eTlZlvKjpz1n3IZLAYMbKYAIQgFmEu0GENg92ziU/faEOA/aixjbA==", + "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==", "dev": true, "dependencies": { - "lodash.isfinite": "^3.3.2" + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-path-cwd": { @@ -2420,16 +4177,35 @@ "node": ">=8" } }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "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==", "dev": true, "dependencies": { - "isobject": "^3.0.1" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-stream": { @@ -2444,13 +4220,61 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "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==", "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, "engines": { - "node": ">=4" + "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==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/isarray": { @@ -2465,20 +4289,12 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, + "peer": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -2493,6 +4309,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, + "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -2503,6 +4320,24 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/js-yaml/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "node_modules/js2xmlparser": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", @@ -2541,11 +4376,18 @@ "node": ">=12.0.0" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "dev": true, + "peer": true }, "node_modules/json-schema-traverse": { "version": "1.0.0", @@ -2553,22 +4395,31 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, - "node_modules/jsonfile": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", - "integrity": "sha512-oBko6ZHlubVB5mRFkur5vgYR1UyqX+S6Y/oCfLhqNdcc2fYFlDpIoNc7AfKS1KOGcnNAkvsr0grLck9ANM815w==", + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" } }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, - "engines": { - "node": ">=0.10.0" + "dependencies": { + "json-buffer": "3.0.1" } }, "node_modules/klaw": { @@ -2622,6 +4473,19 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lie": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", @@ -2630,12 +4494,6 @@ "immediate": "~3.0.5" } }, - "node_modules/limiter": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", - "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==", - "dev": true - }, "node_modules/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", @@ -2650,6 +4508,7 @@ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", "dev": true, + "peer": true, "engines": { "node": ">=6.11.5" } @@ -2662,18 +4521,6 @@ "lie": "3.1.1" } }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -2698,18 +4545,18 @@ "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", "dev": true }, - "node_modules/lodash.isfinite": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz", - "integrity": "sha512-7FGG40uhC8Mm633uKW1r58aElFlBlxCrg9JfSi3P6aYiWmfiWF0PgMd86ZUsxE5GwWPdHoS2+48bwTh2VPkIQA==", - "dev": true - }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", "dev": true }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "node_modules/lodash.union": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", @@ -2721,6 +4568,18 @@ "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/markdown-it": { "version": "14.1.0", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", @@ -2800,15 +4659,6 @@ "node": ">=8.6" } }, - "node_modules/mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", - "dev": true, - "bin": { - "mime": "cli.js" - } - }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -2848,11 +4698,14 @@ "node": "*" } }, - "node_modules/mitt": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-1.2.0.tgz", - "integrity": "sha512-r6lj77KlwqLhIUku9UWYes7KJtsczvolZkzp8hbaDPPaE24OmWl5s539Mytlj22siEQKosZ26qCBgda2PKwoJw==", - "dev": true + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/mkdirp": { "version": "1.0.4", @@ -2867,25 +4720,48 @@ } }, "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, "engines": { - "node": ">= 0.6" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/node-fetch": { "version": "2.6.13", @@ -2910,7 +4786,8 @@ "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/normalize-path": { "version": "3.0.0", @@ -2933,69 +4810,104 @@ "node": ">=8" } }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", "dev": true, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "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==", "dev": true, - "dependencies": { - "ee-first": "1.1.1" - }, "engines": { - "node": ">= 0.8" + "node": ">= 0.4" } }, - "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==", + "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==", + "dev": true, "dependencies": { - "wrappy": "1" + "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/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "node_modules/object.fromentries": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", + "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", "dev": true, "dependencies": { - "mimic-fn": "^2.1.0" + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "engines": { - "node": ">=6" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/opn": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", - "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", + "node_modules/object.groupby": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.2.tgz", + "integrity": "sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw==", + "dev": true, + "dependencies": { + "array.prototype.filter": "^1.0.3", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.0.0" + } + }, + "node_modules/object.values": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", + "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", "dev": true, "dependencies": { - "is-wsl": "^1.1.0" + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "engines": { - "node": ">=4" + "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/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "dependencies": { - "p-try": "^2.0.0" + "mimic-fn": "^2.1.0" }, "engines": { "node": ">=6" @@ -3004,16 +4916,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, "dependencies": { - "p-limit": "^2.2.0" + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" }, "engines": { - "node": ">=8" + "node": ">= 0.8.0" } }, "node_modules/p-map": { @@ -3031,24 +4948,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, "engines": { "node": ">=6" } }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3108,39 +5019,51 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "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==", "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/portscanner": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.2.0.tgz", - "integrity": "sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw==", + "node_modules/postcss": { + "version": "8.4.45", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.45.tgz", + "integrity": "sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==", "dev": true, + "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" + } + ], + "license": "MIT", "dependencies": { - "async": "^2.6.0", - "is-number-like": "^1.0.3" + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" }, "engines": { - "node": ">=0.4", - "npm": ">=1.0.0" + "node": "^10 || ^12 || >=14" } }, - "node_modules/portscanner/node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, - "dependencies": { - "lodash": "^4.17.14" + "engines": { + "node": ">= 0.8.0" } }, "node_modules/process-nextick-args": { @@ -3149,6 +5072,12 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, + "node_modules/promise-polyfill": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-6.1.0.tgz", + "integrity": "sha512-g0LWaH0gFsxovsU7R5LrrhHhWAWiHRnh1GPrhXnPgYsDkIqjRYUYSZEsej/wtleDrz5xVSIDbeKfidztp2XHFQ==", + "dev": true + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -3202,34 +5131,11 @@ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, + "peer": true, "dependencies": { "safe-buffer": "^5.1.0" } }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dev": true, - "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/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -3274,35 +5180,29 @@ "node": ">=10" } }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, - "node_modules/rechoir": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", - "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "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==", "dev": true, "dependencies": { - "resolve": "^1.20.0" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" }, "engines": { - "node": ">= 10.13.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" - }, "node_modules/regression": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/regression/-/regression-2.0.1.tgz", @@ -3325,12 +5225,6 @@ "node": ">=0.10.0" } }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, "node_modules/requizzle": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", @@ -3357,38 +5251,13 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/resp-modifier": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/resp-modifier/-/resp-modifier-6.0.2.tgz", - "integrity": "sha512-U1+0kWC/+4ncRFYqQWTx/3qkfE6a4B/h3XXgmXypfa0SPZ3t7cbbaFk297PjQS/yov24R18h6OZe6iZwj3NSLw==", + "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==", "dev": true, - "dependencies": { - "debug": "^2.2.0", - "minimatch": "^3.0.2" - }, - "engines": { - "node": ">= 0.8.0" + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, "node_modules/reusify": { @@ -3407,10 +5276,46 @@ "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, "dependencies": { - "glob": "^7.1.3" + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/rollup": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.2.tgz", + "integrity": "sha512-e3TapAgYf9xjdLvKQCkQTnbTKd4a6jwlpQSJJFokHGaX2IVjoEqkIIhiQfqsi0cdwlOD+tQGuOd5AJkc5RngBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.5" }, "bin": { - "rimraf": "bin.js" + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.21.2", + "@rollup/rollup-android-arm64": "4.21.2", + "@rollup/rollup-darwin-arm64": "4.21.2", + "@rollup/rollup-darwin-x64": "4.21.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.21.2", + "@rollup/rollup-linux-arm-musleabihf": "4.21.2", + "@rollup/rollup-linux-arm64-gnu": "4.21.2", + "@rollup/rollup-linux-arm64-musl": "4.21.2", + "@rollup/rollup-linux-powerpc64le-gnu": "4.21.2", + "@rollup/rollup-linux-riscv64-gnu": "4.21.2", + "@rollup/rollup-linux-s390x-gnu": "4.21.2", + "@rollup/rollup-linux-x64-gnu": "4.21.2", + "@rollup/rollup-linux-x64-musl": "4.21.2", + "@rollup/rollup-win32-arm64-msvc": "4.21.2", + "@rollup/rollup-win32-ia32-msvc": "4.21.2", + "@rollup/rollup-win32-x64-msvc": "4.21.2", + "fsevents": "~2.3.2" } }, "node_modules/run-parallel": { @@ -3436,10 +5341,37 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rx": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", - "integrity": "sha512-CiaiuN6gapkdl+cZUr67W6I8jquN4lkak3vtIsIWCl4XIPP8ffsoyN6/+PuGXnQy8Cu8W2y9Xxh31Rq4M6wUug==", + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "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==", + "dev": true, + "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-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "dev": true }, "node_modules/safe-buffer": { @@ -3461,11 +5393,22 @@ } ] }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "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==", + "dev": true, + "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/schema-utils": { "version": "4.2.0", @@ -3491,73 +5434,13 @@ "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" }, - "node_modules/send": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "statuses": "~1.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/send/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "dev": true, - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/send/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true - }, - "node_modules/send/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "node_modules/send/node_modules/statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "engines": { - "node": ">= 0.6" + "bin": { + "semver": "bin/semver.js" } }, "node_modules/serialize-javascript": { @@ -3565,110 +5448,41 @@ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, + "peer": true, "dependencies": { "randombytes": "^2.1.0" } }, - "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", - "dev": true, - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-index/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "dev": true, - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true - }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "node_modules/serve-index/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-static": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "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==", "dev": true, "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.2", - "send": "0.16.2" + "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.8.0" + "node": ">= 0.4" } }, - "node_modules/server-destroy": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz", - "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==", - "dev": true - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "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==", "dev": true, "dependencies": { - "kind-of": "^6.0.2" + "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": ">=8" + "node": ">= 0.4" } }, "node_modules/shebang-command": { @@ -3692,174 +5506,64 @@ "node": ">=8" } }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/socket.io": { - "version": "4.7.5", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", - "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", - "dev": true, - "dependencies": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "cors": "~2.8.5", - "debug": "~4.3.2", - "engine.io": "~6.5.2", - "socket.io-adapter": "~2.5.2", - "socket.io-parser": "~4.2.4" - }, - "engines": { - "node": ">=10.2.0" - } - }, - "node_modules/socket.io-adapter": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz", - "integrity": "sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==", - "dev": true, - "dependencies": { - "debug": "~4.3.4", - "ws": "~8.11.0" - } - }, - "node_modules/socket.io-adapter/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io-adapter/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==", - "dev": true - }, - "node_modules/socket.io-client": { - "version": "4.7.5", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz", - "integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==", - "dev": true, - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.2", - "engine.io-client": "~6.5.2", - "socket.io-parser": "~4.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/socket.io-client/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io-client/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==", - "dev": true - }, - "node_modules/socket.io-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", - "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", "dev": true, - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" - }, - "engines": { - "node": ">=10.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/socket.io-parser/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "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==", "dev": true, "dependencies": { - "ms": "2.1.2" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" }, "engines": { - "node": ">=6.0" + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/socket.io-parser/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/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, - "node_modules/socket.io/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, - "dependencies": { - "ms": "2.1.2" - }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=8" } }, - "node_modules/socket.io/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==", - "dev": true - }, "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==", "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -3869,6 +5573,7 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, + "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -3879,31 +5584,6 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, - "node_modules/statuses": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "integrity": "sha512-wuTCPGlJONk/a1kqZ4fQM2+908lC7fa7nPYpTC1EhnvqLX/IICbeP1OZGDtA374trpSq68YubKUMo8oRhN46yg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/stream-throttle": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/stream-throttle/-/stream-throttle-0.1.3.tgz", - "integrity": "sha512-889+B9vN9dq7/vLbGyuHeZ6/ctf5sNuGWsDy89uNxkFTAgzy0eK7+w5fL3KLNRTkLle7EgZGvHUphZW0Q26MnQ==", - "dev": true, - "dependencies": { - "commander": "^2.2.0", - "limiter": "^1.0.5" - }, - "bin": { - "throttleproxy": "bin/throttleproxy.js" - }, - "engines": { - "node": ">= 0.10.0" - } - }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -3921,8 +5601,53 @@ "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" }, - "engines": { - "node": ">=8" + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", + "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", + "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", + "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/strip-ansi": { @@ -3936,6 +5661,15 @@ "node": ">=8" } }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -3980,11 +5714,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sweetalert": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/sweetalert/-/sweetalert-2.1.2.tgz", + "integrity": "sha512-iWx7X4anRBNDa/a+AdTmvAzQtkN1+s4j/JJRWlHpYE8Qimkohs8/XnFcWeYHH2lMA8LRCa5tj2d244If3S/hzA==", + "dev": true, + "dependencies": { + "es6-object-assign": "^1.1.0", + "promise-polyfill": "^6.0.2" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "dev": true, + "peer": true, "engines": { "node": ">=6" } @@ -4010,6 +5755,7 @@ "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.0.tgz", "integrity": "sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==", "dev": true, + "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -4028,6 +5774,7 @@ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", "dev": true, + "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", @@ -4062,6 +5809,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -4078,6 +5826,7 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true, + "peer": true, "peerDependencies": { "ajv": "^6.9.1" } @@ -4086,13 +5835,15 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/terser-webpack-plugin/node_modules/schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, + "peer": true, "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -4106,6 +5857,21 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "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==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -4118,41 +5884,167 @@ "node": ">=8.0" } }, - "node_modules/toidentifier": { + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "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==", + "dev": true, + "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/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", "dev": true, + "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.6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + "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==", + "dev": true, + "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/ua-parser-js": { - "version": "1.0.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", - "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", + "node_modules/typed-array-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz", + "integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - }, - { - "type": "github", - "url": "https://github.com/sponsors/faisalman" - } - ], + "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": "*" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" } }, "node_modules/uc.micro": { @@ -4161,6 +6053,21 @@ "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", "dev": true }, + "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==", + "dev": true, + "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/underscore": { "version": "1.13.6", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", @@ -4172,24 +6079,6 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/update-browserslist-db": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", @@ -4209,6 +6098,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "escalade": "^3.1.2", "picocolors": "^1.0.1" @@ -4235,29 +6125,79 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "node_modules/vite": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.3.tgz", + "integrity": "sha512-IH+nl64eq9lJjFqU+/yrRnrHPVTlgy42/+IzbOdaFDVlyLgI/wDlf+FCobXLX1cT0X5+7LMyH1mIy2xJdLfo8Q==", "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, "engines": { - "node": ">= 0.4.0" + "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/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "node_modules/vite-plugin-banner": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/vite-plugin-banner/-/vite-plugin-banner-0.8.0.tgz", + "integrity": "sha512-JpDWDYxtrsytuvUOJCgJcTkBb6XM8yPOidjRtB6F5SW1JSzDd/Y+PD/44wR6ovWKXhSUiyDRqPvx7mMf8+8ELg==", "dev": true, - "engines": { - "node": ">= 0.8" - } + "license": "MIT" }, "node_modules/watchpack": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", "dev": true, + "peer": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -4276,6 +6216,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz", "integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==", "dev": true, + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", @@ -4318,79 +6259,12 @@ } } }, - "node_modules/webpack-cli": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", - "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", - "dev": true, - "dependencies": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.1.1", - "@webpack-cli/info": "^2.0.2", - "@webpack-cli/serve": "^2.0.5", - "colorette": "^2.0.14", - "commander": "^10.0.1", - "cross-spawn": "^7.0.3", - "envinfo": "^7.7.3", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^3.1.1", - "rechoir": "^0.8.0", - "webpack-merge": "^5.7.3" - }, - "bin": { - "webpack-cli": "bin/cli.js" - }, - "engines": { - "node": ">=14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "5.x.x" - }, - "peerDependenciesMeta": { - "@webpack-cli/generators": { - "optional": true - }, - "webpack-bundle-analyzer": { - "optional": true - }, - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/webpack-cli/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", - "dev": true, - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/webpack-sources": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", "dev": true, + "peer": true, "engines": { "node": ">=10.13.0" } @@ -4400,6 +6274,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -4416,6 +6291,7 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true, + "peer": true, "peerDependencies": { "ajv": "^6.9.1" } @@ -4424,13 +6300,15 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/webpack/node_modules/schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, + "peer": true, "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -4468,11 +6346,40 @@ "node": ">= 8" } }, - "node_modules/wildcard": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", - "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", - "dev": true + "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==", + "dev": true, + "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==", + "dev": true, + "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/wrap-ansi": { "version": "7.0.0", @@ -4495,42 +6402,12 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, - "node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "dev": true, - "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/xmlcreate": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", "dev": true }, - "node_modules/xmlhttprequest-ssl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", - "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -4539,6 +6416,12 @@ "node": ">=10" } }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -4577,6 +6460,18 @@ "yarnhook": "index.js" } }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zip-stream": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", diff --git a/package.json b/package.json index 0458f49d1..6a5f01958 100644 --- a/package.json +++ b/package.json @@ -1,24 +1,41 @@ { "name": "webgazer", "version": "3.3.0", + "type": "module", "repository": { "type": "git", "url": "https://github.com/brownhci/WebGazer.git" }, "license": "GPL-3.0-or-later", - "main": "dist/webgazer.js", - "module": "src/index.mjs", + "main": "dist/webgazer.cjs.js", + "module": "dist/webgazer.es.js", + "types": "dist/types/index.d.ts", "scripts": { "clean": "rimraf dist", "prebuild": "npm run clean", - "dev": "webpack --progress --config webpack.config.js --mode development", - "build": "webpack --progress --config webpack.config.js --mode production", - "gen_docs": "jsdoc -c jsdoc.conf.json src/*" + "dev:main": "vite build --watch --mode development", + "dev:worker": "vite build --config vite.worker.config.js --watch --mode development", + "dev:server": "vite", + "dev": "concurrently \"npm run dev:main\" \"npm run dev:worker\" \"npm run dev:server\"", + "typecheck:src": "tsc", + "typecheck:www": "tsc -p tsconfig.www.json", + "build": "npm run typecheck:src && vite build --config vite.worker.config.js && vite build", + "postbuild": "node scripts/create-symlink.mjs && npm run typecheck:www", + "gen_docs": "jsdoc -c jsdoc.conf.json src/*", + "typecheck": "tsc --noEmit", + "lint": "eslint . --ext .ts,.js,.cjs", + "lint:fix": "eslint . --ext .ts,.js,.cjs --fix" }, "files": [ - "/dist", - "/src" + "dist" ], + "exports": { + ".": { + "import": "./dist/webgazer.es.js", + "require": "./dist/webgazer.cjs.js", + "types": "./dist/types/index.d.ts" + } + }, "keywords": [ "webgazer", "eyetracking", @@ -32,12 +49,28 @@ "regression": "2.0.1" }, "devDependencies": { - "browser-sync": "^3.0.2", + "@types/d3": "^3.5.53", + "@types/node": "^20.0.0", + "@typescript-eslint/eslint-plugin": "^5.0.0", + "@typescript-eslint/parser": "^5.0.0", + "bootstrap": "^5.2.3", + "concurrently": "^9.0.1", + "eslint": "^8.57.0", + "eslint-config-standard": "^17.1.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-n": "^16.6.2", + "eslint-plugin-promise": "^6.1.1", "filemanager-webpack-plugin": "^8.0.0", + "heatmap.js": "^2.0.5", "jsdoc": "^4.0.2", "rimraf": "2.6.3", - "webpack": "^5.75.0", - "webpack-cli": "^5.0.1", + "sweetalert": "^2.1.2", + "typescript": "^5.0.0", + "vite": "^5.4.3", + "vite-plugin-banner": "^0.8.0", "yarnhook": "^0.4.3" + }, + "overrides": { + "d3-color": "3.1.0" } } diff --git a/scripts/create-symlink.mjs b/scripts/create-symlink.mjs new file mode 100644 index 000000000..426ffde05 --- /dev/null +++ b/scripts/create-symlink.mjs @@ -0,0 +1,49 @@ +// @ts-check +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const sourceDir = path.resolve(__dirname, '../dist'); +const nodeModulesDir = path.resolve(__dirname, '../node_modules'); +const targetDir = path.resolve(__dirname, '../www/lib'); + +const files = [ + { source: sourceDir, name: 'webgazer.es.js', symlink: 'webgazer.js' }, + { source: sourceDir, name: 'webgazer.es.js.map', symlink: 'webgazer.js.map' }, + { source: sourceDir, name: 'ridgeWorker.worker.js', symlink: 'ridgeWorker.worker.js' }, + { source: path.join(nodeModulesDir, 'bootstrap/dist/js'), name: 'bootstrap.bundle.min.js', symlink: 'bootstrap.bundle.min.js' }, + { source: path.join(nodeModulesDir, 'bootstrap/dist/css'), name: 'bootstrap.min.css', symlink: 'bootstrap.min.css' }, + { source: path.join(nodeModulesDir, 'heatmap.js/build'), name: 'heatmap.min.js', symlink: 'heatmap.min.js' }, + { source: path.join(nodeModulesDir, 'sweetalert/js/build'), name: 'sweetalert.min.js', symlink: 'sweetalert.min.js' }, + { source: path.resolve(sourceDir, 'types'), name: 'index.d.ts', symlink: 'webgazer.d.ts' } +]; + +const createSymlink = (source, target) => { + try { + // Remove existing symlink if it exists + if (fs.existsSync(target)) { + fs.unlinkSync(target); + } + + // Create new symlink + fs.symlinkSync(source, target, 'file'); + console.log(`Symlink created successfully: ${target}`); + } catch (error) { + console.error(`Error creating symlink for ${target}:`, error); + } +}; + +// Ensure the target directories exist +if (!fs.existsSync(targetDir)) { + fs.mkdirSync(targetDir, { recursive: true }); +} + +// Create symlinks for each file +files.forEach(file => { + const source = path.join(file.source, file.name); + const target = path.join(targetDir, file.symlink); + createSymlink(source, target); +}); diff --git a/src/FaceFeedback.ts b/src/FaceFeedback.ts new file mode 100644 index 000000000..d3208d849 --- /dev/null +++ b/src/FaceFeedback.ts @@ -0,0 +1,100 @@ +import type { EyesData } from './facemesh'; + +const overlayStyle: Partial = { + position: 'absolute', + pointerEvents: 'none', + zIndex: '100000' +}; + +const FEEDBACK_BOX_RATIO = { + width: 0.5, + height: 0.7 +}; + +/** + * This class creates a face feedback visualization to help the user position their face in the camera. + */ +export class FaceFeedback { + private faceFeedbackCanvas: HTMLCanvasElement; + private videoElement: HTMLVideoElement; + private mirrorVideo: boolean; + private resizeObserver: ResizeObserver; + + /** + * @param {HTMLVideoElement} videoElement - The video element to use for face feedback + * @param {boolean=} mirrorVideo - Whether to mirror the video feed + */ + constructor (videoElement: HTMLVideoElement, mirrorVideo = false) { + this.videoElement = videoElement; + this.mirrorVideo = mirrorVideo; + + const container = videoElement.parentElement; + if (!container) throw new Error('Video element must have a parent'); + container.style.position = 'relative'; + + this.faceFeedbackCanvas = document.createElement('canvas'); + Object.assign(this.faceFeedbackCanvas.style, overlayStyle); + container.appendChild(this.faceFeedbackCanvas); + + this.resizeObserver = new ResizeObserver(() => this.updateFeedbackBox()); + this.resizeObserver.observe(this.videoElement); + this.updateFeedbackBox(); + } + + private updateFeedbackBox (): void { + const { offsetWidth, offsetHeight, offsetTop, offsetLeft } = this.videoElement; + Object.assign(this.faceFeedbackCanvas.style, { + width: `${offsetWidth}px`, + height: `${offsetHeight}px`, + top: `${offsetTop}px`, + left: `${offsetLeft}px` + }); + this.faceFeedbackCanvas.width = offsetWidth; + this.faceFeedbackCanvas.height = offsetHeight; + } + + /** + * Updates the face feedback visualization + * @param {DOMRect} facePosition - The position of the face + * @param {EyesData} eyePatches - The positions of the eyes + */ + update (eyePatches: EyesData): void { + const { width, height } = this.faceFeedbackCanvas; + const ctx = this.faceFeedbackCanvas.getContext('2d'); + if (ctx) { + ctx.clearRect(0, 0, width, height); + + const { scaleX, scaleY } = eyePatches; + const validationBox = { + x: width * (1 - FEEDBACK_BOX_RATIO.width) / 2, + y: height * (1 - FEEDBACK_BOX_RATIO.height) / 2, + w: width * FEEDBACK_BOX_RATIO.width, + h: height * FEEDBACK_BOX_RATIO.height + }; + const eyesBoxes = [eyePatches.left, eyePatches.right].map(eye => ({ + x: this.mirrorVideo ? width - eye.imagex * scaleX - eye.width * scaleX : eye.imagex * scaleX, + y: eye.imagey * scaleY, + w: eye.width * scaleX, + h: eye.height * scaleY + })); + const eyesInBox = eyesBoxes.every(eye => eye.x >= validationBox.x && + eye.x + eye.w <= validationBox.x + validationBox.w && + eye.y >= validationBox.y && + eye.y + eye.h <= validationBox.y + validationBox.h); + + ctx.strokeStyle = eyesInBox ? '#000080' : '#b00000'; + ctx.lineWidth = 2; + ctx.strokeRect(validationBox.x, validationBox.y, validationBox.w, validationBox.h); + + ctx.fillStyle = eyesInBox ? '#00a000' : '#b00000'; + ctx.globalAlpha = 0.5; // Semi-transparent + eyesBoxes.forEach(eye => ctx.fillRect(eye.x, eye.y, eye.w, eye.h)); + ctx.globalAlpha = 1.0; // Reset alpha + } + } + + dispose (): void { + this.faceFeedbackCanvas.remove(); + this.resizeObserver.disconnect(); + } +} diff --git a/src/GazeDot.ts b/src/GazeDot.ts new file mode 100644 index 000000000..8f815a937 --- /dev/null +++ b/src/GazeDot.ts @@ -0,0 +1,58 @@ +/** + * This class creates a gaze dot that follows the user's gaze. + */ +export class GazeDot { + static style: Partial = { + display: 'block', + position: 'fixed', + top: '0', + left: '0', + width: '100vw', + height: '100vh', + zIndex: '99999', + background: 'red', + borderRadius: '50%', + pointerEvents: 'none', + opacity: '0.7' + }; + + private div: HTMLDivElement = document.createElement('div'); + + /** + * Creates a new GazeDot instance. + * @param dotSize - The size of the gaze dot in pixels. + */ + constructor (dotSize: number) { + Object.assign(this.div.style, GazeDot.style, { + width: `${dotSize}px`, + height: `${dotSize}px` + }); + document.body.appendChild(this.div); + } + + /** + * Updates the position of the gaze dot. + * @param x - The x-coordinate of the new position. + * @param y - The y-coordinate of the new position. + */ + update (x: number, y: number): void { + if (this.isShowing()) { + this.div.style.transform = `translate3d(${x}px, ${y}px, 0)`; + } + } + + /** + * Checks if the gaze dot is currently visible. + * @returns True if the gaze dot is visible, false otherwise. + */ + isShowing (): boolean { + return this.div.style.display === 'block'; + } + + /** + * Removes the gaze dot from the DOM. + */ + dispose (): void { + this.div.remove(); + } +} diff --git a/src/GazeTrail.ts b/src/GazeTrail.ts new file mode 100644 index 000000000..61892e916 --- /dev/null +++ b/src/GazeTrail.ts @@ -0,0 +1,71 @@ +import { DataWindow, Point } from './worker_scripts/util'; + +export class GazeTrail { + static style: Partial = { + position: 'fixed', + top: '0', + left: '0', + width: '100vw', + height: '100vh', + pointerEvents: 'none', + zIndex: '99999' + }; + + private canvas: HTMLCanvasElement = document.createElement('canvas'); + private points: DataWindow = new DataWindow(50); + private animationFrameId: number | null = null; + + constructor (maxPoints = 50) { + document.body.appendChild(this.canvas); + Object.assign(this.canvas.style, GazeTrail.style); + this.canvas.width = window.innerWidth; + this.canvas.height = window.innerHeight; + this.points = new DataWindow(maxPoints); + window.addEventListener('resize', () => { + this.canvas.width = window.innerWidth; + this.canvas.height = window.innerHeight; + this.scheduleRedraw(); + }); + } + + /** + * Add a point to the trail + * @param x - The x coordinate of the point + * @param y - The y coordinate of the point + */ + addPoint (x: number, y: number): void { + this.points.push({ x, y }); + this.scheduleRedraw(); + } + + private scheduleRedraw (): void { + if (this.animationFrameId === null) { + this.animationFrameId = requestAnimationFrame(() => { + this.redraw(); + this.animationFrameId = null; + }); + } + } + + private redraw (): void { + const context2D = this.canvas.getContext('2d'); + if (!context2D) return; + context2D.clearRect(0, 0, this.canvas.width, this.canvas.height); + this.points.forEach((point, index) => { + if (!point) console.log(index, [...this.points.data]); + const age = this.points.data.length - index; + const alpha = 1 - (age / this.points.data.length); + context2D.fillStyle = `rgba(0, 0, 255, ${alpha})`; + context2D.beginPath(); + context2D.arc(point.x, point.y, 3, 0, Math.PI * 2, true); + context2D.fill(); + }); + } + + dispose (): void { + if (this.animationFrameId !== null) { + cancelAnimationFrame(this.animationFrameId); + } + this.canvas.remove(); + } +} diff --git a/src/SaveManager.ts b/src/SaveManager.ts new file mode 100644 index 000000000..c24ba87e3 --- /dev/null +++ b/src/SaveManager.ts @@ -0,0 +1,67 @@ +import localforage from 'localforage'; +import { Ridge } from './worker_scripts/regression'; + +export class SaveManager { + private regression: Ridge; + private autoSaveInterval: ReturnType | undefined = undefined; + saveDelay = 1000; + + /** + * @param regression - The regression object to manage + * @param saveDelay - The delay between auto-saves in milliseconds + */ + constructor (regression: Ridge, saveDelay = 1000) { + this.regression = regression; + this.saveDelay = saveDelay; + } + + private getStorageKey (): string { + return `webgazerGlobalData-${this.regression.name}`; + } + + async saveData (): Promise { + try { + await localforage.setItem(this.getStorageKey(), this.regression.getData()); + } catch (err) { + console.error('Error saving WebGazer data:', err); + } + } + + async loadData (): Promise { + try { + const data = await localforage.getItem(this.getStorageKey()); + if (data) this.regression.setData(data as any); + } catch (err) { + console.error('Error loading WebGazer data:', err); + } + } + + async clearData (): Promise { + try { + await localforage.removeItem(this.getStorageKey()); + } catch (err) { + console.error('Error clearing WebGazer data:', err); + } + } + + /** + * Start auto-saving data at regular intervals + * @param interval - Optional new interval for auto-saving + */ + startAutoSave (interval?: number): void { + if (interval) this.saveDelay = interval; + if (this.autoSaveInterval) return; + this.autoSaveInterval = setInterval(() => this.saveData(), this.saveDelay); + } + + stopAutoSave (): void { + if (this.autoSaveInterval) { + clearInterval(this.autoSaveInterval); + this.autoSaveInterval = undefined; + } + } + + dispose (): void { + this.stopAutoSave(); + } +} diff --git a/src/dom_util.mjs b/src/dom_util.mjs deleted file mode 100644 index ffa790191..000000000 --- a/src/dom_util.mjs +++ /dev/null @@ -1,27 +0,0 @@ -// helper functions - -/** - * Provides requestAnimationFrame in a cross browser way. - */ -window.requestAnimFrame = (function() { - return window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - window.msRequestAnimationFrame || - function(/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) { - return window.setTimeout(callback, 1000/60); - }; -})(); - -/** - * Provides cancelRequestAnimationFrame in a cross browser way. - */ -window.cancelRequestAnimFrame = (function() { - return window.cancelCancelRequestAnimationFrame || - window.webkitCancelRequestAnimationFrame || - window.mozCancelRequestAnimationFrame || - window.oCancelRequestAnimationFrame || - window.msCancelRequestAnimationFrame || - window.clearTimeout; -})(); diff --git a/src/dom_util.ts b/src/dom_util.ts new file mode 100644 index 000000000..878bb8897 --- /dev/null +++ b/src/dom_util.ts @@ -0,0 +1,67 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +// helper functions + +/** + * Provides requestAnimationFrame in a cross browser way. + */ +window.requestAnimationFrame = window.requestAnimationFrame || + // @ts-ignore + window.webkitRequestAnimationFrame || + // @ts-ignore + window.mozRequestAnimationFrame || + // @ts-ignore + window.oRequestAnimationFrame || + // @ts-ignore + window.msRequestAnimationFrame || + /** + * @callback + * @param {FrameRequestCallback} callback + */ + (callback => window.setTimeout(callback, 1000 / 60)); + +/** + * Provides cancelRequestAnimationFrame in a cross browser way. + */ +window.cancelAnimationFrame = window.cancelAnimationFrame || + // @ts-ignore + window.webkitCancelRequestAnimationFrame || + // @ts-ignore + window.mozCancelRequestAnimationFrame || + // @ts-ignore + window.oCancelRequestAnimationFrame || + // @ts-ignore + window.msCancelRequestAnimationFrame || + window.clearTimeout; + +// We need to export this before the polyfill +export const isBrowserCompatible = !!(navigator.mediaDevices.getUserMedia || + // @ts-ignore + navigator.getUserMedia || + // @ts-ignore + navigator.webkitGetUserMedia || + // @ts-ignore + navigator.mozGetUserMedia); + +/** + * Provides mediaDevices.getUserMedia in a cross browser way. + */ +if (navigator.mediaDevices === undefined) { + Object.assign(navigator.mediaDevices, {}); +} +if (navigator.mediaDevices.getUserMedia === undefined) { + navigator.mediaDevices.getUserMedia = async function (constraints) { + // gets the alternative old getUserMedia is possible + // @ts-ignore + const getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia; + + // set an error message if browser doesn't support getUserMedia + if (!getUserMedia) { + return Promise.reject(new Error('Unfortunately, your browser does not support access to the webcam through the getUserMedia API. Try to use the latest version of Google Chrome, Mozilla Firefox, Opera, or Microsoft Edge instead.')); + } + + // uses navigator.getUserMedia for older browsers + return new Promise(function (resolve, reject) { + getUserMedia.call(navigator, constraints, resolve, reject); + }); + }; +} diff --git a/src/facemesh.mjs b/src/facemesh.mjs deleted file mode 100644 index fe5f808fe..000000000 --- a/src/facemesh.mjs +++ /dev/null @@ -1,174 +0,0 @@ -import * as faceLandmarksDetection from '@tensorflow-models/face-landmarks-detection'; - -/** - * Constructor of TFFaceMesh object - * @constructor - * */ -const TFFaceMesh = function() { - //Backend options are webgl, wasm, and CPU. - //For recent laptops WASM is better than WebGL. - this.model = faceLandmarksDetection.load( - faceLandmarksDetection.SupportedPackages.mediapipeFacemesh, - { maxFaces: 1 } - ); - this.predictionReady = false; -}; - -// Global variable for face landmark positions array -TFFaceMesh.prototype.positionsArray = null; - -/** - * Isolates the two patches that correspond to the user's eyes - * @param {Object} video - the video element itself - * @param {Canvas} imageCanvas - canvas corresponding to the webcam stream - * @param {Number} width - of imageCanvas - * @param {Number} height - of imageCanvas - * @return {Object} the two eye-patches, first left, then right eye - */ -TFFaceMesh.prototype.getEyePatches = async function(video, imageCanvas, width, height) { - - if (imageCanvas.width === 0) { - return null; - } - - // Load the MediaPipe facemesh model. - const model = await this.model; - - // Pass in a video stream (or an image, canvas, or 3D tensor) to obtain an - // array of detected faces from the MediaPipe graph. - const predictions = await model.estimateFaces({ - input: video, - returnTensors: false, - flipHorizontal: false, - predictIrises: false, - }); - - if (predictions.length == 0){ - return false; - } - - // Save positions to global variable - this.positionsArray = predictions[0].scaledMesh; - const prediction = predictions[0] - const positions = this.positionsArray; - - const { scaledMesh } = predictions[0]; - // Keypoints indexes are documented at - // https://github.com/tensorflow/tfjs-models/blob/118d4727197d4a21e2d4691e134a7bc30d90deee/face-landmarks-detection/mesh_map.jpg - // https://stackoverflow.com/questions/66649492/how-to-get-specific-landmark-of-face-like-lips-or-eyes-using-tensorflow-js-face - const [leftBBox, rightBBox] = [ - // left - { - eyeTopArc: prediction.annotations.leftEyeUpper0, - eyeBottomArc: prediction.annotations.leftEyeLower0 - }, - // right - { - eyeTopArc: prediction.annotations.rightEyeUpper0, - eyeBottomArc: prediction.annotations.rightEyeLower0 - }, - ].map(({ eyeTopArc, eyeBottomArc }) => { - const topLeftOrigin = { - x: Math.round(Math.min(...eyeTopArc.map(v => v[0]))), - y: Math.round(Math.min(...eyeTopArc.map(v => v[1]))), - }; - const bottomRightOrigin = { - x: Math.round(Math.max(...eyeBottomArc.map(v => v[0]))), - y: Math.round(Math.max(...eyeBottomArc.map(v => v[1]))), - }; - - return { - origin: topLeftOrigin, - width: bottomRightOrigin.x - topLeftOrigin.x, - height: bottomRightOrigin.y - topLeftOrigin.y, - } - }); - var leftOriginX = leftBBox.origin.x; - var leftOriginY = leftBBox.origin.y; - var leftWidth = leftBBox.width; - var leftHeight = leftBBox.height; - var rightOriginX = rightBBox.origin.x; - var rightOriginY = rightBBox.origin.y; - var rightWidth = rightBBox.width; - var rightHeight = rightBBox.height; - - if (leftWidth === 0 || rightWidth === 0){ - console.log('an eye patch had zero width'); - return null; - } - - if (leftHeight === 0 || rightHeight === 0){ - console.log('an eye patch had zero height'); - return null; - } - - // Start building object to be returned - var eyeObjs = {}; - - var leftImageData = imageCanvas.getContext('2d').getImageData(leftOriginX, leftOriginY, leftWidth, leftHeight); - eyeObjs.left = { - patch: leftImageData, - imagex: leftOriginX, - imagey: leftOriginY, - width: leftWidth, - height: leftHeight - }; - - var rightImageData = imageCanvas.getContext('2d').getImageData(rightOriginX, rightOriginY, rightWidth, rightHeight); - eyeObjs.right = { - patch: rightImageData, - imagex: rightOriginX, - imagey: rightOriginY, - width: rightWidth, - height: rightHeight - }; - - this.predictionReady = true; - - return eyeObjs; -}; - -/** - * Returns the positions array corresponding to the last call to getEyePatches. - * Requires that getEyePatches() was called previously, else returns null. - */ -TFFaceMesh.prototype.getPositions = function () { - return this.positionsArray; -} - -/** - * Reset the tracker to default values - */ -TFFaceMesh.prototype.reset = function(){ - console.log( "Unimplemented; Tracking.js has no obvious reset function" ); -} - -/** - * Draw TF_FaceMesh_Overlay - */ -TFFaceMesh.prototype.drawFaceOverlay = function(ctx, keypoints){ - // If keypoints is falsy, don't do anything - if (keypoints) { - ctx.fillStyle = '#32EEDB'; - ctx.strokeStyle = '#32EEDB'; - ctx.lineWidth = 0.5; - - for (let i = 0; i < keypoints.length; i++) { - const x = keypoints[i][0]; - const y = keypoints[i][1]; - - ctx.beginPath(); - ctx.arc(x, y, 1 /* radius */, 0, 2 * Math.PI); - ctx.closePath(); - ctx.fill(); - } - } -} - -/** - * The TFFaceMesh object name - * @type {string} - */ -TFFaceMesh.prototype.name = 'TFFaceMesh'; - -export default TFFaceMesh; diff --git a/src/facemesh.ts b/src/facemesh.ts new file mode 100644 index 000000000..706e65f87 --- /dev/null +++ b/src/facemesh.ts @@ -0,0 +1,234 @@ +import { FaceLandmarksDetector, load, SupportedPackages } from '@tensorflow-models/face-landmarks-detection'; +import { getEyeFeats, type Eye } from './worker_scripts/util'; + +export type TwoEyes = { + left: Eye; + right: Eye; +}; + +export type EyesData = { + left: Eye; + right: Eye; + grayscale: number[]; + facemesh: [number, number, number][]; + /** + * The scale factor for the x-axis between the stream and the videoElement + */ + scaleX: number; + /** + * The scale factor for the y-axis between the stream and the videoElement + */ + scaleY: number; +}; + +const overlayStyle: Partial = { + position: 'absolute', + pointerEvents: 'none', + zIndex: '10000' +}; + +export class TFFaceMesh { + /** @type {Promise} */ + model: Promise; + /** + * The TFFaceMesh object name + * @type {string} + */ + name = 'TFFaceMesh'; + + private mirrorVideo = false; + private canvas: HTMLCanvasElement | null = null; + private videoElement: HTMLVideoElement; + private offscreenContext2D: OffscreenCanvasRenderingContext2D; + private resizeObserver: ResizeObserver; + trackEye: 'left' | 'right' | 'both'; + + /** + * Constructor of TFFaceMesh object + * @param {HTMLVideoElement} videoElement - The video element to track + * @param {MediaStream} stream - The stream to track + * @constructor + * */ + constructor (videoElement: HTMLVideoElement, stream: MediaStream, trackEye: 'left' | 'right' | 'both' = 'both') { + // Backend options are webgl, wasm, and CPU. + // For recent laptops WASM is better than WebGL. + this.model = load( + SupportedPackages.mediapipeFacemesh, + { maxFaces: 1 } + ); + this.videoElement = videoElement; + this.trackEye = trackEye; + // Create OffscreenCanvas with the actual video size + this.resizeObserver = new ResizeObserver(() => this.updateCanvasSize()); + this.resizeObserver.observe(this.videoElement); + const videoTrack = stream.getVideoTracks()[0]; + const { width = videoElement.width, height = videoElement.height } = videoTrack.getSettings(); + const offscreenContext = new OffscreenCanvas(width, height).getContext('2d'); + if (!offscreenContext) throw new Error('No context for the offscreen drawings'); + this.offscreenContext2D = offscreenContext; + this.updateCanvasSize(); + } + + setVideoElement (videoElement: HTMLVideoElement): void { + this.resizeObserver.unobserve(this.videoElement); + this.videoElement = videoElement; + this.resizeObserver.observe(this.videoElement); + } + + /** + * Isolates the two patches that correspond to the user's eyes + * @return {Promise} the data representing the eye patches + */ + getEyePatches = async (): Promise => { + const context2D = this.offscreenContext2D; + if (!context2D || !this.videoElement.width || !this.videoElement.height) return; + + // Draw current frame to offscreen canvas + context2D.drawImage(this.videoElement, 0, 0, context2D.canvas.width, context2D.canvas.height); + + // Load the MediaPipe facemesh model. + const model = await this.model; + + // Pass in a video stream (or an image, canvas, or 3D tensor) to obtain an + // array of detected faces from the MediaPipe graph. + const predictions = await model.estimateFaces({ + input: this.videoElement, + returnTensors: false, + flipHorizontal: false, + predictIrises: false + }); + + if (predictions.length === 0) return; + + // Draw the face overlay if it is enabled + const positions: [number, number, number][] = (predictions[0].scaledMesh as [number, number, number][]).map(([x, y, z]) => [x * this.videoElement.width / this.offscreenContext2D.canvas.width, y * this.videoElement.height / this.offscreenContext2D.canvas.height, z]); + this.drawFaceOverlay(positions); + + // Get the details about the eyes + const prediction = predictions[0]; + if (prediction.kind === 'MediaPipePredictionTensors') return; + const annotations = prediction.annotations; + if (!context2D || !annotations) return; + + const scaleX = this.videoElement.width / this.offscreenContext2D.canvas.width; + const scaleY = this.videoElement.height / this.offscreenContext2D.canvas.height; + + // Keypoints indexes are documented at + // https://github.com/tensorflow/tfjs-models/blob/118d4727197d4a21e2d4691e134a7bc30d90deee/face-landmarks-detection/mesh_map.jpg + // https://stackoverflow.com/questions/66649492/how-to-get-specific-landmark-of-face-like-lips-or-eyes-using-tensorflow-js-face + const [leftEye, rightEye] = ['left', 'right'] + .map(side => { + const [{ x: upperX, y: upperY }, { x: lowerX, y: lowerY }] = ['Upper', 'Lower'] + // We read "leftEyeUpper0", "leftEyeLower0", "rightEyeUpper0", and "rightEyeLower0" from the annotations + .map(edge => annotations[`${side}Eye${edge}0`]) + // Organize the data on x and y + .map(points => ({ x: points.map(point => point[0]), y: points.map(point => point[1]) })); + // Get the bounding box of the eye + const [minUpperX, minUpperY, minLowerX, minLowerY] = [upperX, upperY, lowerX, lowerY].map(values => Math.min(...values)); + const [maxUpperX, maxUpperY, maxLowerX, maxLowerY] = [upperX, upperY, lowerX, lowerY].map(values => Math.max(...values)); + const topLeft = { x: Math.round(Math.min(minUpperX, minLowerX)), y: Math.round(Math.min(minUpperY, minLowerY)) }; + const bottomRight = { x: Math.round(Math.max(maxUpperX, maxLowerX)), y: Math.round(Math.max(maxUpperY, maxLowerY)) }; + // Get the patch of the eye + const patch = context2D.getImageData(topLeft.x, topLeft.y, bottomRight.x - topLeft.x, bottomRight.y - topLeft.y); + return { + patch, + width: bottomRight.x - topLeft.x, + height: bottomRight.y - topLeft.y, + imagex: topLeft.x, + imagey: topLeft.y + }; + }); + + if (leftEye.width === 0 || rightEye.width === 0) { + console.log('an eye patch had zero width'); + return; + } + + if (leftEye.height === 0 || rightEye.height === 0) { + console.log('an eye patch had zero height'); + return; + } + + // Get the grayscale values for the eyes + const grayscale = getEyeFeats({ + left: leftEye, + right: rightEye + }, this.trackEye); + + // Start building object to be returned + return { + grayscale, + left: leftEye, + right: rightEye, + facemesh: positions, + scaleX, + scaleY + }; + }; + + /** + * Reset the tracker to default values + */ + dispose = (): void => { + this.hideFaceOverlay(); + this.resizeObserver.disconnect(); + }; + + showFaceOverlay = (mirrorVideo = false): void => { + if (this.canvas) return; + + const container = this.videoElement.parentElement; + if (!container) throw new Error('Video element must have a parent'); + this.canvas = document.createElement('canvas'); + Object.assign(this.canvas.style, overlayStyle); + container.style.position = 'relative'; + container.appendChild(this.canvas); + this.mirrorVideo = mirrorVideo; + }; + + hideFaceOverlay = (): void => { + if (!this.canvas) return; + this.canvas.remove(); + }; + + private updateCanvasSize (): void { + const { offsetWidth, offsetHeight, offsetTop, offsetLeft } = this.videoElement; + // this.offscreenContext2D.canvas.width = offsetWidth; + // this.offscreenContext2D.canvas.height = offsetHeight; + if (this.canvas) { + Object.assign(this.canvas.style, { + width: `${offsetWidth}px`, + height: `${offsetHeight}px`, + top: `${offsetTop}px`, + left: `${offsetLeft}px` + }); + this.canvas.width = offsetWidth; + this.canvas.height = offsetHeight; + } + } + + /** + * Draw TF_FaceMesh_Overlay + */ + private drawFaceOverlay = (positions: [number, number, number][]): void => { + const ctx = this.canvas?.getContext('2d'); + if (!ctx) return; + + // Clear the canvas + ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); + + // If we have face positions, draw them + if (positions) { + ctx.fillStyle = '#32EEDB'; + ctx.strokeStyle = '#32EEDB'; + ctx.lineWidth = 0.5; + + positions.forEach(([x, y]) => { + ctx.beginPath(); + ctx.arc(this.mirrorVideo ? ctx.canvas.width - x : x, y, 1 /* radius */, 0, 2 * Math.PI); + ctx.closePath(); + ctx.fill(); + }); + } + }; +} diff --git a/src/index.mjs b/src/index.mjs deleted file mode 100644 index ddc21abe8..000000000 --- a/src/index.mjs +++ /dev/null @@ -1,1167 +0,0 @@ -import '@tensorflow/tfjs'; -//import(/* webpackPreload: true */ '@tensorflow/tfjs'); -//import(/* webpackChunkName: 'pageA' */ './vendors~main.js') - -import 'regression'; -import params from './params.mjs'; -import './dom_util.mjs'; -import localforage from 'localforage'; -import TFFaceMesh from './facemesh.mjs'; -import Reg from './ridgeReg.mjs'; -import ridgeRegWeighted from './ridgeWeightedReg.mjs'; -import ridgeRegThreaded from './ridgeRegThreaded.mjs'; -import util from './util.mjs'; - -const webgazer = {}; -webgazer.tracker = {}; -webgazer.tracker.TFFaceMesh = TFFaceMesh; -webgazer.reg = Reg; -webgazer.reg.RidgeWeightedReg = ridgeRegWeighted.RidgeWeightedReg; -webgazer.reg.RidgeRegThreaded = ridgeRegThreaded.RidgeRegThreaded; -webgazer.util = util; -webgazer.params = params; - -//PRIVATE VARIABLES - -//video elements -var videoStream = null; -var videoContainerElement = null; -var videoElement = null; -var videoElementCanvas = null; -var faceOverlay = null; -var faceFeedbackBox = null; -var gazeDot = null; -// Why is this not in webgazer.params ? -var debugVideoLoc = ''; - -/* - * Initialises variables used to store accuracy eigenValues - * This is used by the calibration example file - */ -var xPast50 = new Array(50); -var yPast50 = new Array(50); - -// loop parameters -var clockStart = performance.now(); -var latestEyeFeatures = null; -var latestGazeData = null; -var paused = false; -//registered callback for loop -var nopCallback = function(data, time) {}; -var callback = nopCallback; - -//Types that regression systems should handle -//Describes the source of data so that regression systems may ignore or handle differently the various generating events -var eventTypes = ['click', 'move']; - -//movelistener timeout clock parameters -var moveClock = performance.now(); -//currently used tracker and regression models, defaults to clmtrackr and linear regression -var curTracker = new webgazer.tracker.TFFaceMesh(); -var regs = [new webgazer.reg.RidgeReg()]; -// var blinkDetector = new webgazer.BlinkDetector(); - -//lookup tables -var curTrackerMap = { - 'TFFacemesh': function() { return new webgazer.tracker.TFFaceMesh(); }, -}; -var regressionMap = { - 'ridge': function() { return new webgazer.reg.RidgeReg(); }, - 'weightedRidge': function() { return new webgazer.reg.RidgeWeightedReg(); }, - 'threadedRidge': function() { return new webgazer.reg.RidgeRegThreaded(); }, -}; - -//localstorage name -var localstorageDataLabel = 'webgazerGlobalData'; -var localstorageSettingsLabel = 'webgazerGlobalSettings'; -//settings object for future storage of settings -var settings = {}; -var data = []; -var defaults = { - 'data': [], - 'settings': {} -}; - - -//PRIVATE FUNCTIONS - -/** - * Computes the size of the face overlay validation box depending on the size of the video preview window. - * @returns {Object} The dimensions of the validation box as top, left, width, height. - */ -webgazer.computeValidationBoxSize = function() { - - var vw = videoElement.videoWidth; - var vh = videoElement.videoHeight; - var pw = parseInt(videoElement.style.width); - var ph = parseInt(videoElement.style.height); - - // Find the size of the box. - // Pick the smaller of the two video preview sizes - var smaller = Math.min( vw, vh ); - var larger = Math.max( vw, vh ); - - // Overall scalar - var scalar = ( vw == larger ? pw / vw : ph / vh ); - - // Multiply this by 2/3, then adjust it to the size of the preview - var boxSize = (smaller * webgazer.params.faceFeedbackBoxRatio) * scalar; - - // Set the boundaries of the face overlay validation box based on the preview - var topVal = (ph - boxSize)/2; - var leftVal = (pw - boxSize)/2; - - // top, left, width, height - return [topVal, leftVal, boxSize, boxSize] -} - -/** - * Checks if the pupils are in the position box on the video - */ -function checkEyesInValidationBox() { - - if (faceFeedbackBox != null && latestEyeFeatures) { - var w = videoElement.videoWidth; - var h = videoElement.videoHeight; - - // Find the size of the box. - // Pick the smaller of the two video preview sizes - var smaller = Math.min( w, h ); - var boxSize = smaller * webgazer.params.faceFeedbackBoxRatio; - - // Set the boundaries of the face overlay validation box based on the preview - var topBound = (h - boxSize)/2; - var leftBound = (w - boxSize)/2; - var rightBound = leftBound + boxSize; - var bottomBound = topBound + boxSize; - - //get the x and y positions of the left and right eyes - var eyeLX = latestEyeFeatures.left.imagex; - var eyeLY = latestEyeFeatures.left.imagey; - var eyeRX = latestEyeFeatures.right.imagex; - var eyeRY = latestEyeFeatures.right.imagey; - - - var xPositions = false; - var yPositions = false; - - // check if the x values for the left and right eye are within the - // validation box - // add the width when comparing against the rightBound (which is the left edge on the preview) - if (eyeLX > leftBound && eyeLX + latestEyeFeatures.left.width < rightBound) { - if (eyeRX > leftBound && eyeRX + latestEyeFeatures.right.width < rightBound) { - xPositions = true; - } - } - - //check if the y values for the left and right eye are within the - //validation box - if (eyeLY > topBound && eyeLY + latestEyeFeatures.left.height < bottomBound) { - if (eyeRY > topBound && eyeRY + latestEyeFeatures.right.height < bottomBound) { - yPositions = true; - } - } - - //if the x and y values for both the left and right eye are within - //the validation box then the box border turns green, otherwise if - //the eyes are outside of the box the colour is red - if (xPositions && yPositions){ - faceFeedbackBox.style.border = 'solid green'; - } else { - faceFeedbackBox.style.border = 'solid red'; - } - } - else - faceFeedbackBox.style.border = 'solid black'; -} - -/** - * This draws the point (x,y) onto the canvas in the HTML - * @param {colour} colour - The colour of the circle to plot - * @param {x} x - The x co-ordinate of the desired point to plot - * @param {y} y - The y co-ordinate of the desired point to plot - */ -function drawCoordinates(colour,x,y){ - var ctx = document.getElementById("plotting_canvas").getContext('2d'); - ctx.fillStyle = colour; // Red color - ctx.beginPath(); - ctx.arc(x, y, 5, 0, Math.PI * 2, true); - ctx.fill(); -} - -/** - * Gets the pupil features by following the pipeline which threads an eyes object through each call: - * curTracker gets eye patches -> blink detector -> pupil detection - * @param {Canvas} canvas - a canvas which will have the video drawn onto it - * @param {Number} width - the width of canvas - * @param {Number} height - the height of canvas - */ -function getPupilFeatures(canvas, width, height) { - if (!canvas) { - return; - } - try { - return curTracker.getEyePatches(videoElement, canvas, width, height); - } catch(err) { - console.log("can't get pupil features ", err); - return null; - } -} - -/** - * Gets the most current frame of video and paints it to a resized version of the canvas with width and height - * @param {Canvas} canvas - the canvas to paint the video on to - * @param {Number} width - the new width of the canvas - * @param {Number} height - the new height of the canvas - */ -function paintCurrentFrame(canvas, width, height) { - if (canvas.width != width) { - canvas.width = width; - } - if (canvas.height != height) { - canvas.height = height; - } - - var ctx = canvas.getContext('2d'); - ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height); -} - -/** - * Paints the video to a canvas and runs the prediction pipeline to get a prediction - * @param {Number|undefined} regModelIndex - The prediction index we're looking for - * @returns {*} - */ -async function getPrediction(regModelIndex) { - var predictions = []; - // [20200617 xk] TODO: this call should be made async somehow. will take some work. - latestEyeFeatures = await getPupilFeatures(videoElementCanvas, videoElementCanvas.width, videoElementCanvas.height); - - if (regs.length === 0) { - console.log('regression not set, call setRegression()'); - return null; - } - for (var reg in regs) { - predictions.push(regs[reg].predict(latestEyeFeatures)); - } - if (regModelIndex !== undefined) { - return predictions[regModelIndex] === null ? null : { - 'x' : predictions[regModelIndex].x, - 'y' : predictions[regModelIndex].y, - 'eyeFeatures': latestEyeFeatures - }; - } else { - return predictions.length === 0 || predictions[0] === null ? null : { - 'x' : predictions[0].x, - 'y' : predictions[0].y, - 'eyeFeatures': latestEyeFeatures, - 'all' : predictions - }; - } -} - -/** - * Runs every available animation frame if webgazer is not paused - */ -var smoothingVals = new util.DataWindow(4); -var k = 0; - -async function loop() { - if (!paused) { - - // [20200617 XK] TODO: there is currently lag between the camera input and the face overlay. This behavior - // is not seen in the facemesh demo. probably need to optimize async implementation. I think the issue lies - // in the implementation of getPrediction(). - - // Paint the latest video frame into the canvas which will be analyzed by WebGazer - // [20180729 JT] Why do we need to do this? clmTracker does this itself _already_, which is just duplicating the work. - // Is it because other trackers need a canvas instead of an img/video element? - paintCurrentFrame(videoElementCanvas, videoElementCanvas.width, videoElementCanvas.height); - - // Get gaze prediction (ask clm to track; pass the data to the regressor; get back a prediction) - latestGazeData = getPrediction(); - // Count time - var elapsedTime = performance.now() - clockStart; - - // Draw face overlay - if( webgazer.params.showFaceOverlay ) - { - // Get tracker object - var tracker = webgazer.getTracker(); - faceOverlay.getContext('2d').clearRect( 0, 0, videoElement.videoWidth, videoElement.videoHeight); - tracker.drawFaceOverlay(faceOverlay.getContext('2d'), tracker.getPositions()); - } - - // Feedback box - // Check that the eyes are inside of the validation box - if( webgazer.params.showFaceFeedbackBox ) - checkEyesInValidationBox(); - - latestGazeData = await latestGazeData; - - // [20200623 xk] callback to function passed into setGazeListener(fn) - callback(latestGazeData, elapsedTime); - - if( latestGazeData ) { - // [20200608 XK] Smoothing across the most recent 4 predictions, do we need this with Kalman filter? - smoothingVals.push(latestGazeData); - var x = 0; - var y = 0; - var len = smoothingVals.length; - for (var d in smoothingVals.data) { - x += smoothingVals.get(d).x; - y += smoothingVals.get(d).y; - } - - var pred = util.bound({'x':x/len, 'y':y/len}); - - if (webgazer.params.storingPoints) { - drawCoordinates('blue',pred.x,pred.y); //draws the previous predictions - //store the position of the past fifty occuring tracker preditions - webgazer.storePoints(pred.x, pred.y, k); - k++; - if (k == 50) { - k = 0; - } - } - // GazeDot - if (webgazer.params.showGazeDot) { - gazeDot.style.display = 'block'; - } - gazeDot.style.transform = 'translate3d(' + pred.x + 'px,' + pred.y + 'px,0)'; - } else { - gazeDot.style.display = 'none'; - } - - requestAnimationFrame(loop); - } -} - -//is problematic to test -//because latestEyeFeatures is not set in many cases - -/** - * Records screen position data based on current pupil feature and passes it - * to the regression model. - * @param {Number} x - The x screen position - * @param {Number} y - The y screen position - * @param {String} eventType - The event type to store - * @returns {null} - */ -var recordScreenPosition = function(x, y, eventType) { - if (paused) { - return; - } - if (regs.length === 0) { - console.log('regression not set, call setRegression()'); - return null; - } - for (var reg in regs) { - if( latestEyeFeatures ) - regs[reg].addData(latestEyeFeatures, [x, y], eventType); - } -}; - -/** - * Records click data and passes it to the regression model - * @param {Event} event - The listened event - */ -var clickListener = async function(event) { - recordScreenPosition(event.clientX, event.clientY, eventTypes[0]); // eventType[0] === 'click' - - if (webgazer.params.saveDataAcrossSessions) { - // Each click stores the next data point into localforage. - await setGlobalData(); - - // // Debug line - // console.log('Model size: ' + JSON.stringify(await localforage.getItem(localstorageDataLabel)).length / 1000000 + 'MB'); - } -}; - -/** - * Records mouse movement data and passes it to the regression model - * @param {Event} event - The listened event - */ -var moveListener = function(event) { - if (paused) { - return; - } - - var now = performance.now(); - if (now < moveClock + webgazer.params.moveTickSize) { - return; - } else { - moveClock = now; - } - recordScreenPosition(event.clientX, event.clientY, eventTypes[1]); //eventType[1] === 'move' -}; - -/** - * Add event listeners for mouse click and move. - */ -var addMouseEventListeners = function() { - //third argument set to true so that we get event on 'capture' instead of 'bubbling' - //this prevents a client using event.stopPropagation() preventing our access to the click - document.addEventListener('click', clickListener, true); - document.addEventListener('mousemove', moveListener, true); -}; - -/** - * Remove event listeners for mouse click and move. - */ -var removeMouseEventListeners = function() { - // must set third argument to same value used in addMouseEventListeners - // for this to work. - document.removeEventListener('click', clickListener, true); - document.removeEventListener('mousemove', moveListener, true); -}; - -/** - * Loads the global data and passes it to the regression model - */ -async function loadGlobalData() { - // Get settings object from localforage - // [20200611 xk] still unsure what this does, maybe would be good for Kalman filter settings etc? - settings = await localforage.getItem(localstorageSettingsLabel); - settings = settings || defaults; - - // Get click data from localforage - var loadData = await localforage.getItem(localstorageDataLabel); - loadData = loadData || defaults; - - // Set global var data to newly loaded data - data = loadData; - - // Load data into regression model(s) - for (var reg in regs) { - regs[reg].setData(loadData); - } - - console.log("loaded stored data into regression model"); -} - -/** - * Adds data to localforage - */ -async function setGlobalData() { - // Grab data from regression model - var storeData = regs[0].getData() || data; // Array - - // Store data into localforage - localforage.setItem(localstorageSettingsLabel, settings) // [20200605 XK] is 'settings' ever being used? - localforage.setItem(localstorageDataLabel, storeData); - //TODO data should probably be stored in webgazer object instead of each regression model - // -> requires duplication of data, but is likely easier on regression model implementors -} - -/** - * Clears data from model and global storage - */ -function clearData() { - // Removes data from localforage - localforage.clear(); - - // Removes data from regression model - for (var reg in regs) { - regs[reg].init(); - } -} - -/** - * Initializes all needed dom elements and begins the loop - * @param {URL} stream - The video stream to use - */ -async function init(stream) { - ////////////////////////// - // Video and video preview - ////////////////////////// - var topDist = '0px' - var leftDist = '0px' - - // used for webgazer.stopVideo() and webgazer.setCameraConstraints() - videoStream = stream; - - // create a video element container to enable customizable placement on the page - videoContainerElement = document.createElement('div'); - videoContainerElement.id = webgazer.params.videoContainerId; - - videoContainerElement.style.position = 'fixed'; - videoContainerElement.style.top = topDist; - videoContainerElement.style.left = leftDist; - videoContainerElement.style.width = webgazer.params.videoViewerWidth + 'px'; - videoContainerElement.style.height = webgazer.params.videoViewerHeight + 'px'; - hideVideoElement(videoContainerElement); - - videoElement = document.createElement('video'); - videoElement.setAttribute('playsinline', ''); - videoElement.id = webgazer.params.videoElementId; - videoElement.srcObject = stream; - videoElement.autoplay = true; - videoElement.style.position = 'absolute'; - // We set these to stop the video appearing too large when it is added for the very first time - videoElement.style.width = webgazer.params.videoViewerWidth + 'px'; - videoElement.style.height = webgazer.params.videoViewerHeight + 'px'; - hideVideoElement(videoElement); - // videoElement.style.zIndex="-1"; - - // Canvas for drawing video to pass to clm tracker - videoElementCanvas = document.createElement('canvas'); - videoElementCanvas.id = webgazer.params.videoElementCanvasId; - videoElementCanvas.style.display = 'none'; - - // Face overlay - // Shows the CLM tracking result - faceOverlay = document.createElement('canvas'); - faceOverlay.id = webgazer.params.faceOverlayId; - faceOverlay.style.display = webgazer.params.showFaceOverlay ? 'block' : 'none'; - faceOverlay.style.position = 'absolute'; - - // Mirror video feed - if (webgazer.params.mirrorVideo) { - videoElement.style.setProperty("-moz-transform", "scale(-1, 1)"); - videoElement.style.setProperty("-webkit-transform", "scale(-1, 1)"); - videoElement.style.setProperty("-o-transform", "scale(-1, 1)"); - videoElement.style.setProperty("transform", "scale(-1, 1)"); - videoElement.style.setProperty("filter", "FlipH"); - faceOverlay.style.setProperty("-moz-transform", "scale(-1, 1)"); - faceOverlay.style.setProperty("-webkit-transform", "scale(-1, 1)"); - faceOverlay.style.setProperty("-o-transform", "scale(-1, 1)"); - faceOverlay.style.setProperty("transform", "scale(-1, 1)"); - faceOverlay.style.setProperty("filter", "FlipH"); - } - - // Feedback box - // Lets the user know when their face is in the middle - faceFeedbackBox = document.createElement('canvas'); - faceFeedbackBox.id = webgazer.params.faceFeedbackBoxId; - faceFeedbackBox.style.display = webgazer.params.showFaceFeedbackBox ? 'block' : 'none'; - faceFeedbackBox.style.border = 'solid'; - faceFeedbackBox.style.position = 'absolute'; - - // Gaze dot - // Starts offscreen - gazeDot = document.createElement('div'); - gazeDot.id = webgazer.params.gazeDotId; - gazeDot.style.display = webgazer.params.showGazeDot ? 'block' : 'none'; - gazeDot.style.position = 'fixed'; - gazeDot.style.zIndex = 99999; - gazeDot.style.left = '-5px'; - gazeDot.style.top = '-5px'; - gazeDot.style.background = 'red'; - gazeDot.style.borderRadius = '100%'; - gazeDot.style.opacity = '0.7'; - gazeDot.style.width = '10px'; - gazeDot.style.height = '10px'; - - // Add other preview/feedback elements to the screen once the video has shown and its parameters are initialized - videoContainerElement.appendChild(videoElement); - document.body.appendChild(videoContainerElement); - const videoPreviewSetup = new Promise((res) => { - function setupPreviewVideo(e) { - - // All video preview parts have now been added, so set the size both internally and in the preview window. - setInternalVideoBufferSizes( videoElement.videoWidth, videoElement.videoHeight ); - webgazer.setVideoViewerSize( webgazer.params.videoViewerWidth, webgazer.params.videoViewerHeight ); - - videoContainerElement.appendChild(videoElementCanvas); - videoContainerElement.appendChild(faceOverlay); - videoContainerElement.appendChild(faceFeedbackBox); - document.body.appendChild(gazeDot); - - // Run this only once, so remove the event listener - e.target.removeEventListener(e.type, setupPreviewVideo); - res(); - }; - videoElement.addEventListener('loadeddata', setupPreviewVideo); - }); - - addMouseEventListeners(); - - //BEGIN CALLBACK LOOP - paused = false; - clockStart = performance.now(); - - await videoPreviewSetup; - await loop(); -} - -/** - * Initializes navigator.mediaDevices.getUserMedia - * depending on the browser capabilities - * - * @return Promise - */ -function setUserMediaVariable(){ - - if (navigator.mediaDevices === undefined) { - navigator.mediaDevices = {}; - } - - if (navigator.mediaDevices.getUserMedia === undefined) { - navigator.mediaDevices.getUserMedia = function(constraints) { - - // gets the alternative old getUserMedia is possible - var getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia; - - // set an error message if browser doesn't support getUserMedia - if (!getUserMedia) { - return Promise.reject(new Error("Unfortunately, your browser does not support access to the webcam through the getUserMedia API. Try to use the latest version of Google Chrome, Mozilla Firefox, Opera, or Microsoft Edge instead.")); - } - - // uses navigator.getUserMedia for older browsers - return new Promise(function(resolve, reject) { - getUserMedia.call(navigator, constraints, resolve, reject); - }); - } - } -} - -//PUBLIC FUNCTIONS - CONTROL - -/** - * Starts all state related to webgazer -> dataLoop, video collection, click listener - * If starting fails, call `onFail` param function. - * @param {Function} onFail - Callback to call in case it is impossible to find user camera - * @returns {*} - */ -webgazer.begin = function(onFail) { - if (window.location.protocol !== 'https:' && window.location.hostname !== 'localhost' && window.chrome){ - alert("WebGazer works only over https. If you are doing local development, you need to run a local server."); - } - - // Load model data stored in localforage. - if (webgazer.params.saveDataAcrossSessions) { - loadGlobalData(); - } - - onFail = onFail || function() {console.log('No stream')}; - - if (debugVideoLoc) { - init(debugVideoLoc); - return webgazer; - } - - /////////////////////// - // SETUP VIDEO ELEMENTS - // Sets .mediaDevices.getUserMedia depending on browser - setUserMediaVariable(); - - // Request webcam access under specific constraints - // WAIT for access - return new Promise(async (resolve, reject) => { - let stream; - try { - stream = await navigator.mediaDevices.getUserMedia( webgazer.params.camConstraints ); - await init(stream); - resolve(webgazer); - } catch(err) { - onFail(); - videoElement = null; - stream = null; - reject(err); - } - }); -}; - - -/** - * Checks if webgazer has finished initializing after calling begin() - * [20180729 JT] This seems like a bad idea for how this function should be implemented. - * @returns {boolean} if webgazer is ready - */ -webgazer.isReady = function() { - if (videoElementCanvas === null) { - return false; - } - return videoElementCanvas.width > 0; -}; - -/** - * Stops collection of data and predictions - * @returns {webgazer} this - */ -webgazer.pause = function() { - paused = true; - return webgazer; -}; - -/** - * Resumes collection of data and predictions if paused - * @returns {webgazer} this - */ -webgazer.resume = async function() { - if (!paused) { - return webgazer; - } - paused = false; - await loop(); - return webgazer; -}; - -/** - * stops collection of data and removes dom modifications, must call begin() to reset up - * @return {webgazer} this - */ -webgazer.end = function() { - //loop may run an extra time and fail due to removed elements - paused = true; - - //webgazer.stopVideo(); // uncomment if you want to stop the video from streaming - - //remove video element and canvas - videoContainerElement.remove(); - gazeDot.remove(); - - return webgazer; -}; - -/** - * Stops the video camera from streaming and removes the video outlines - * @return {webgazer} this - */ -webgazer.stopVideo = function() { - // Stops the video from streaming - videoStream.getTracks()[0].stop(); - - // Removes the outline of the face - videoContainerElement.removeChild( faceOverlay ); - - // Removes the box around the face - videoContainerElement.removeChild( faceFeedbackBox ); - - return webgazer; -} - - -//PUBLIC FUNCTIONS - DEBUG - -/** - * Returns if the browser is compatible with webgazer - * @return {boolean} if browser is compatible - */ -webgazer.detectCompatibility = function() { - - var getUserMedia = navigator.mediaDevices.getUserMedia || - navigator.getUserMedia || - navigator.webkitGetUserMedia || - navigator.mozGetUserMedia; - - return getUserMedia !== undefined; -}; - -/** - * Set whether to show any of the video previews (camera, face overlay, feedback box). - * If true: visibility depends on corresponding params (default all true). - * If false: camera, face overlay, feedback box are all hidden - * @param {bool} val - * @return {webgazer} this - */ -webgazer.showVideoPreview = function(val) { - webgazer.params.showVideoPreview = val; - webgazer.showVideo(val && webgazer.params.showVideo); - webgazer.showFaceOverlay(val && webgazer.params.showFaceOverlay); - webgazer.showFaceFeedbackBox(val && webgazer.params.showFaceFeedbackBox); - return webgazer; -} - -/** - * hides a video element (videoElement or videoContainerElement) - * uses display = 'none' for all browsers except Safari and Firefox, which - * use opacity = '1' because they optimize out video element if display = 'none' - * @param {Object} element - * @return {null} - */ -function hideVideoElement(val) { - if ((navigator.vendor && navigator.vendor.indexOf('Apple') > -1) || - (navigator.userAgent && navigator.userAgent.search("Firefox") > -1)) { - val.style.opacity = webgazer.params.showVideo ? '1': '0'; - val.style.display = 'block'; - } else { - val.style.display = webgazer.params.showVideo ? 'block' : 'none'; - } -} - -/** - * Set whether the camera video preview is visible or not (default true). - * @param {*} bool - * @return {webgazer} this - */ -webgazer.showVideo = function(val) { - webgazer.params.showVideo = val; - if (videoElement) { - hideVideoElement(videoElement); - } - if (videoContainerElement) { - hideVideoElement(videoContainerElement); - } - return webgazer; -}; - -/** - * Set whether the face overlay is visible or not (default true). - * @param {*} bool - * @return {webgazer} this - */ -webgazer.showFaceOverlay = function(val) { - webgazer.params.showFaceOverlay = val; - if( faceOverlay ) { - faceOverlay.style.display = val ? 'block' : 'none'; - } - return webgazer; -}; - -/** - * Set whether the face feedback box is visible or not (default true). - * @param {*} bool - * @return {webgazer} this - */ -webgazer.showFaceFeedbackBox = function(val) { - - webgazer.params.showFaceFeedbackBox = val; - if( faceFeedbackBox ) { - faceFeedbackBox.style.display = val ? 'block' : 'none'; - } - return webgazer; -}; - -/** - * Set whether the gaze prediction point(s) are visible or not. - * Multiple because of a trail of past dots. Default true - * @return {webgazer} this - */ -webgazer.showPredictionPoints = function(val) { - webgazer.params.showGazeDot = val; - if( gazeDot ) { - gazeDot.style.display = val ? 'block' : 'none'; - } - return webgazer; -}; - -/** - * Set whether previous calibration data (from localforage) should be loaded. - * Default true. - * - * NOTE: Should be called before webgazer.begin() -- see www/js/main.js for example - * - * @param val - * @returns {webgazer} this - */ -webgazer.saveDataAcrossSessions = function(val) { - webgazer.params.saveDataAcrossSessions = val; - return webgazer; -} - -/** - * Set whether a Kalman filter will be applied to gaze predictions (default true); - * @return {webgazer} this - */ -webgazer.applyKalmanFilter = function(val) { - webgazer.params.applyKalmanFilter = val; - return webgazer; -} - -/** - * Define constraints on the video camera that is used. Useful for non-standard setups. - * This can be set before calling webgazer.begin(), but also mid stream. - * - * @param {Object} constraints Example constraints object: - * { width: { min: 320, ideal: 1280, max: 1920 }, height: { min: 240, ideal: 720, max: 1080 }, facingMode: "user" }; - * - * Follows definition here: - * https://developer.mozilla.org/en-US/docs/Web/API/Media_Streams_API/Constraints - * - * Note: The constraints set here are applied to the video track only. They also _replace_ any constraints, so be sure to set everything you need. - * Warning: Setting a large video resolution will decrease performance, and may require - */ -webgazer.setCameraConstraints = async function(constraints) { - var videoTrack,videoSettings; - webgazer.params.camConstraints = constraints; - - // If the camera stream is already up... - if(videoStream) - { - webgazer.pause(); - videoTrack = videoStream.getVideoTracks()[0]; - try { - await videoTrack.applyConstraints( webgazer.params.camConstraints ); - videoSettings = videoTrack.getSettings(); - setInternalVideoBufferSizes( videoSettings.width, videoSettings.height ); - } catch(err) { - console.log( err ); - return; - } - // Reset and recompute sizes of the video viewer. - // This is only to adjust the feedback box, say, if the aspect ratio of the video has changed. - webgazer.setVideoViewerSize( webgazer.params.videoViewerWidth, webgazer.params.videoViewerHeight ) - webgazer.getTracker().reset(); - await webgazer.resume(); - } -} - - -/** - * Does what it says on the tin. - * @param {*} width - * @param {*} height - */ -function setInternalVideoBufferSizes( width, height ) { - // Re-set the canvas size used by the internal processes - if( videoElementCanvas ) - { - videoElementCanvas.width = width; - videoElementCanvas.height = height; - } - - // Re-set the face overlay canvas size - if( faceOverlay ) - { - faceOverlay.width = width; - faceOverlay.height = height; - } -} - -/** - * Set a static video file to be used instead of webcam video - * @param {String} videoLoc - video file location - * @return {webgazer} this - */ -webgazer.setStaticVideo = function(videoLoc) { - debugVideoLoc = videoLoc; - return webgazer; -}; - -/** - * Set the size of the video viewer - */ -webgazer.setVideoViewerSize = function(w, h) { - - webgazer.params.videoViewerWidth = w; - webgazer.params.videoViewerHeight = h; - - // Change the video viewer - videoElement.style.width = w + 'px'; - videoElement.style.height = h + 'px'; - - // Change video container - videoContainerElement.style.width = w + 'px'; - videoContainerElement.style.height = h + 'px'; - - // Change the face overlay - faceOverlay.style.width = w + 'px'; - faceOverlay.style.height = h + 'px'; - - // Change the feedback box size - // Compute the boundaries of the face overlay validation box based on the video size - var tlwh = webgazer.computeValidationBoxSize() - // Assign them to the object - faceFeedbackBox.style.top = tlwh[0] + 'px'; - faceFeedbackBox.style.left = tlwh[1] + 'px'; - faceFeedbackBox.style.width = tlwh[2] + 'px'; - faceFeedbackBox.style.height = tlwh[3] + 'px'; -}; - -/** - * Add the mouse click and move listeners that add training data. - * @return {webgazer} this - */ -webgazer.addMouseEventListeners = function() { - addMouseEventListeners(); - return webgazer; -}; - -/** - * Remove the mouse click and move listeners that add training data. - * @return {webgazer} this - */ -webgazer.removeMouseEventListeners = function() { - removeMouseEventListeners(); - return webgazer; -}; - -/** - * Records current screen position for current pupil features. - * @param {String} x - position on screen in the x axis - * @param {String} y - position on screen in the y axis - * @param {String} eventType - "click" or "move", as per eventTypes - * @return {webgazer} this - */ -webgazer.recordScreenPosition = function(x, y, eventType) { - // give this the same weight that a click gets. - recordScreenPosition(x, y, eventType || eventTypes[0]); - return webgazer; -}; - -/* - * Stores the position of the fifty most recent tracker preditions - */ -webgazer.storePoints = function(x, y, k) { - xPast50[k] = x; - yPast50[k] = y; -} - -//SETTERS -/** - * Sets the tracking module - * @param {String} name - The name of the tracking module to use - * @return {webgazer} this - */ -webgazer.setTracker = function(name) { - if (curTrackerMap[name] === undefined) { - console.log('Invalid tracker selection'); - console.log('Options are: '); - for (var t in curTrackerMap) { - console.log(t); - } - return webgazer; - } - curTracker = curTrackerMap[name](); - return webgazer; -}; - -/** - * Sets the regression module and clears any other regression modules - * @param {String} name - The name of the regression module to use - * @return {webgazer} this - */ -webgazer.setRegression = function(name) { - if (regressionMap[name] === undefined) { - console.log('Invalid regression selection'); - console.log('Options are: '); - for (var reg in regressionMap) { - console.log(reg); - } - return webgazer; - } - data = regs[0].getData(); - regs = [regressionMap[name]()]; - regs[0].setData(data); - return webgazer; -}; - -/** - * Adds a new tracker module so that it can be used by setTracker() - * @param {String} name - the new name of the tracker - * @param {Function} constructor - the constructor of the curTracker object - * @return {webgazer} this - */ -webgazer.addTrackerModule = function(name, constructor) { - curTrackerMap[name] = function() { - return new constructor(); - }; -}; - -/** - * Adds a new regression module so that it can be used by setRegression() and addRegression() - * @param {String} name - the new name of the regression - * @param {Function} constructor - the constructor of the regression object - */ -webgazer.addRegressionModule = function(name, constructor) { - regressionMap[name] = function() { - return new constructor(); - }; -}; - -/** - * Adds a new regression module to the list of regression modules, seeding its data from the first regression module - * @param {String} name - the string name of the regression module to add - * @return {webgazer} this - */ -webgazer.addRegression = function(name) { - var newReg = regressionMap[name](); - data = regs[0].getData(); - newReg.setData(data); - regs.push(newReg); - return webgazer; -}; - -/** - * Sets a callback to be executed on every gaze event (currently all time steps) - * @param {function} listener - The callback function to call (it must be like function(data, elapsedTime)) - * @return {webgazer} this - */ -webgazer.setGazeListener = function(listener) { - callback = listener; - return webgazer; -}; - -/** - * Removes the callback set by setGazeListener - * @return {webgazer} this - */ -webgazer.clearGazeListener = function() { - callback = nopCallback; - return webgazer; -}; - -/** - * Set the video element canvas; useful if you want to run WebGazer on your own canvas (e.g., on any random image). - * @return The current video element canvas - */ -webgazer.setVideoElementCanvas = function(canvas) { - videoElementCanvas = canvas; - return videoElementCanvas; -} - -/** - * Clear data from localforage and from regs - */ -webgazer.clearData = async function() { - clearData(); -} - - -//GETTERS -/** - * Returns the tracker currently in use - * @return {tracker} an object following the tracker interface - */ -webgazer.getTracker = function() { - return curTracker; -}; - -/** - * Returns the regression currently in use - * @return {Array.} an array of regression objects following the regression interface - */ -webgazer.getRegression = function() { - return regs; -}; - -/** - * Requests an immediate prediction - * @return {object} prediction data object - */ -webgazer.getCurrentPrediction = function(regIndex) { - return getPrediction(regIndex); -}; - -/** - * returns the different event types that may be passed to regressions when calling regression.addData() - * @return {Array} array of strings where each string is an event type - */ -webgazer.params.getEventTypes = function() { - return eventTypes.slice(); -} - -/** - * Get the video element canvas that WebGazer uses internally on which to run its face tracker. - * @return The current video element canvas - */ -webgazer.getVideoElementCanvas = function() { - return videoElementCanvas; -} - -/** - * @return array [a,b] where a is width ratio and b is height ratio - */ -webgazer.getVideoPreviewToCameraResolutionRatio = function() { - return [webgazer.params.videoViewerWidth / videoElement.videoWidth, webgazer.params.videoViewerHeight / videoElement.videoHeight]; -} - -/* - * Gets the fifty most recent tracker predictions - */ -webgazer.getStoredPoints = function() { - return [xPast50, yPast50]; -} - -export default webgazer; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 000000000..1a0ad099d --- /dev/null +++ b/src/index.ts @@ -0,0 +1,465 @@ +// @ts-check +import '@tensorflow/tfjs'; + +import 'regression'; +import './dom_util'; +import { RidgeReg } from './ridgeReg'; +import { RidgeWeightedReg } from './ridgeWeightedReg'; +import { RidgeRegThreaded } from './ridgeRegThreaded'; +import { DataWindow, bound, Point } from './worker_scripts/util'; +import { TFFaceMesh, EyesData } from './facemesh'; +import { GazeDot } from './GazeDot'; +import { FaceFeedback } from './FaceFeedback'; +import { GazeTrail } from './GazeTrail'; +import { SaveManager } from './SaveManager'; +export { isBrowserCompatible } from './dom_util'; + +/** + * Represents the result of a gaze prediction. + */ +export type PredictionResult = { + x: number; + y: number; + eyeFeatures?: EyesData; + all?: (Point | undefined)[]; +}; + +/** + * Options for regression configuration. + */ +export type RegressionOptions = { + /** The regression method to use */ + regression: 'ridgeReg' | 'ridgeWeighted' | 'ridgeThreaded'; + /** Whether to use Kalman filter */ + useKalmanFilter: boolean; + /** Size of move ticks */ + moveTickSize: number; +}; + +/** + * Options for WebGazer configuration. + */ +export type WebGazerOptions = { + /** Constraints for the camera video */ + videoConstraints: MediaTrackConstraints; + /** Gaze dot size in pixels */ + dotSize: number; + /** Whether to save data across sessions */ + saveDataAcrossSessions: boolean; + /** Whether to listen to the mouse click and move events to calibrate the model */ + useCalibration: boolean | 'move' | 'click'; +}; + +/** + * Options for saving data. + */ +export type SaveOptions = { + /** Interval between saves in milliseconds */ + saveInterval: number; +} + +/** + * Options for starting WebGazer. + */ +export type WebGazerStartOptions = { + /** Whether to show the gaze dot */ + gazeDot: boolean; + /** Whether to show the gaze trail */ + gazeTrail: boolean; + /** Constraints for the camera video */ + videoConstraints: MediaTrackConstraints; + /** Which eye to track */ + trackEye: 'left' | 'right' | 'both'; +}; + +export type WebgazerVideoOptions = { + /** Whether to mirror the video feed */ + mirrorVideo: boolean; + /** Whether to show the face feedback */ + faceFeedback: boolean; +} + +const SMOOTHING_PRECISION = 4; + +/** + * Main WebGazer class for eye-tracking functionality. + */ +export class WebGazer { + tracker: TFFaceMesh| null = null; + regression: RidgeRegThreaded | RidgeWeightedReg | RidgeReg; + videoElement: HTMLVideoElement = document.createElement('video'); + stream: MediaStream | null = null; + gazeDot: GazeDot | null = null; + gazeTrail: GazeTrail | null = null; + faceFeedback: FaceFeedback | null = null; + smoothingVals: DataWindow = new DataWindow(SMOOTHING_PRECISION); + paused = false; + listeners: ((data: PredictionResult | undefined, elapsedTime: number) => void)[] = []; + saveManager: SaveManager; + destroyed = false; + clockStart = 0; + mirrorVideo = false; + + params: WebGazerOptions & WebGazerStartOptions = { + gazeDot: true, + gazeTrail: false, + dotSize: 10, + videoConstraints: { width: { min: 320, ideal: 640, max: 1920 }, height: { min: 240, ideal: 480, max: 1080 }, facingMode: 'user' }, + saveDataAcrossSessions: true, + useCalibration: true, + trackEye: 'both' + }; + + lastEyePatches: EyesData | undefined = undefined; + + private mouseMoveClock = performance.now(); + + /** + * Creates a new WebGazer instance. + * @param {Partial} [options] - Configuration options for WebGazer. + */ + constructor (options?: Partial) { + // We duplicate the options object to avoid mutating the original object + const optionsObject = options ? JSON.parse(JSON.stringify(options)) : {}; + const allParams = { ...this.params, ...optionsObject }; + const { regression = 'ridgeReg', useKalmanFilter = true, moveTickSize = 50, saveInterval = 1000, ...otherParams } = allParams; + this.params = { ...this.params, ...otherParams }; + const RegressionClass = regression === 'ridgeThreaded' ? RidgeRegThreaded : regression === 'ridgeWeighted' ? RidgeWeightedReg : RidgeReg; + this.regression = new RegressionClass({ useKalmanFilter, moveTickSize }); + + // Save the data across sessions + this.saveManager = new SaveManager(this.regression, saveInterval); + if (this.params.saveDataAcrossSessions) this.saveManager.loadData(); + } + + get useKalmanFilter (): boolean { + return this.regression.useKalmanFilter; + } + + set useKalmanFilter (value: boolean) { + this.regression.useKalmanFilter = value; + } + + /** + * Sets the regression method and options. + * @param {RegressionOptions['regression']} regression - The regression method to use. + * @param {Partial>=} [options] - Additional regression options. + */ + setRegression (regression: RegressionOptions['regression'], options?: Partial>): void { + this.params = { ...this.params, ...options }; + this.regression.dispose(); + const RegressionClass = regression === 'ridgeThreaded' ? RidgeRegThreaded : regression === 'ridgeWeighted' ? RidgeWeightedReg : RidgeReg; + this.regression = new RegressionClass({ useKalmanFilter: this.regression.useKalmanFilter, moveTickSize: this.regression.moveTickSize }); + this.saveManager = new SaveManager(this.regression, this.saveManager.saveDelay); + } + + /** + * Shows the gaze dot on the screen. + */ + showGazeDot (): void { + this.params.gazeDot = true; + if (!this.gazeDot) this.gazeDot = new GazeDot(this.params.dotSize || 10); + } + + /** + * Hides the gaze dot from the screen. + */ + hideGazeDot (): void { + this.params.gazeDot = false; + if (this.gazeDot) this.gazeDot.dispose(); + this.gazeDot = null; + } + + /** + * Shows face feedback on the screen. + */ + showFaceFeedback (mirrorVideo = false): void { + if (!this.faceFeedback) { + this.faceFeedback = new FaceFeedback(this.videoElement, mirrorVideo); + this.tracker?.showFaceOverlay(mirrorVideo); + } + } + + /** + * Hides face feedback from the screen. + */ + hideFaceFeedback (): void { + if (this.faceFeedback) this.faceFeedback.dispose(); + this.faceFeedback = null; + } + + /** + * Shows the gaze trail on the screen. + * @param {number} [length=50] - The length of the gaze trail. + */ + showGazeTrail (length = 50): void { + this.params.gazeTrail = true; + if (!this.gazeTrail) this.gazeTrail = new GazeTrail(length); + } + + /** + * Hides the gaze trail from the screen. + */ + hideGazeTrail (): void { + this.params.gazeTrail = false; + if (this.gazeTrail) this.gazeTrail.dispose(); + this.gazeTrail = null; + } + + /** + * Adds a gaze listener to receive gaze predictions. + * @param {(data: PredictionResult | undefined, elapsedTime: number) => void} listener - The listener function to add. + */ + addGazeListener (listener: (data: PredictionResult | undefined, elapsedTime: number) => void): void { + this.listeners.push(listener); + } + + /** + * Removes a gaze listener. + * @param {(data: PredictionResult | undefined, elapsedTime: number) => void} listener - The listener function to remove. + */ + removeGazeListener (listener: (data: PredictionResult | undefined, elapsedTime: number) => void): void { + this.listeners.splice(this.listeners.indexOf(listener), 1); + } + + /** + * Stops WebGazer and releases resources. + */ + stop (): void { + if (this.stream) { + this.stream.getTracks().forEach(track => track.stop()); + this.stream = null; + } + + this.videoElement.srcObject = null; + this.hideGazeDot(); + this.hideGazeTrail(); + this.hideFaceFeedback(); + this.saveManager.stopAutoSave(); + // We don't use stopCalibration() because we don't want to set useCalibration to false. This way, the calibration will restart when start() is called again. + document.removeEventListener('click', this.clickListener, true); + document.removeEventListener('mousemove', this.moveListener, true); + } + + /** + * Starts WebGazer + * If no existingStream is provided, it will try to get a new MediaStream from the webcam + * @param {Partial=} options - Options for the start method + * @param {MediaStream=} [existingStream] - Optional existing MediaStream to use + * @returns {Promise} + */ + async start (options?: Partial, existingStream?: MediaStream): Promise { + if (this.destroyed) throw new Error('WebGazer has been destroyed'); + this.params = { ...this.params, ...(options || {}) }; + this.clockStart = performance.now(); + if (existingStream) { + this.stream = existingStream; + } else { + try { + this.stream = await navigator.mediaDevices.getUserMedia({ + video: this.params.videoConstraints, + audio: false + }); + } catch (err) { + console.error('Failed to get user media', err); + throw err; + } + } + + // Get the video track to determine actual size + this.tracker = new TFFaceMesh(this.videoElement, this.stream, this.params.trackEye); + + this.videoElement.srcObject = this.stream; + await this.videoElement.play(); + + if (this.params.gazeDot) this.showGazeDot(); + if (this.params.gazeTrail) this.showGazeTrail(); + if (this.params.saveDataAcrossSessions) this.saveManager.startAutoSave(); + if (this.params.useCalibration === true || this.params.useCalibration === 'click') document.addEventListener('click', this.clickListener, true); + if (this.params.useCalibration === true || this.params.useCalibration === 'move') document.addEventListener('mousemove', this.moveListener, true); + + this.loop(); + } + + /** + * Gets the current gaze prediction. + * @returns {Promise} + */ + async getPrediction (): Promise { + if (!this.stream) return; + const eyeFeatures = this.lastEyePatches; + return eyeFeatures && this.regression.predict(eyeFeatures); + } + + async loop (): Promise { + if (this.paused || !this.stream) return; + + // Make a prediction + const eyeFeatures = await this.tracker?.getEyePatches(); + this.lastEyePatches = eyeFeatures; + if (this.faceFeedback && eyeFeatures) this.faceFeedback.update(eyeFeatures); + const prediction = eyeFeatures && this.regression.predict(eyeFeatures); + if (prediction) { + // Smooth prediction + this.smoothingVals.push(prediction); + const { x, y } = this.smoothingVals.data.reduce((acc, val) => ({ + x: acc.x + val.x, + y: acc.y + val.y + }), { x: 0, y: 0 }); + const len = this.smoothingVals.data.length; + const smoothedPrediction = { x: x / len, y: y / len }; + + // Apply bounds to the smoothed prediction + const boundedPrediction = bound(smoothedPrediction); + + // Update gaze dot position + if (this.gazeDot) this.gazeDot.update(boundedPrediction.x, boundedPrediction.y); + if (this.gazeTrail) this.gazeTrail.addPoint(boundedPrediction.x, boundedPrediction.y); + + // Send the prediction to the listeners + const predictionEvent = { ...boundedPrediction, eyeFeatures, all: [boundedPrediction] }; + this.listeners.forEach(listener => listener(predictionEvent, performance.now() - this.clockStart)); + } + + // Continue the loop + requestAnimationFrame(() => this.loop()); + } + + private clickListener = (event: MouseEvent): void => { + if (this.paused) return; + + const eyesObj = this.lastEyePatches; + if (eyesObj) { + const x = event.clientX; + const y = event.clientY; + this.regression.addData({ eyes: eyesObj, screenPos: [x, y], type: 'click' }); + if (this.params.saveDataAcrossSessions) this.saveManager.saveData(); + } + }; + + private moveListener = (event: MouseEvent): void => { + if (this.paused) return; + + const now = performance.now(); + if (now - this.mouseMoveClock < this.regression.moveTickSize) return; + this.mouseMoveClock = now; + + const eyesObj = this.lastEyePatches; + if (eyesObj) { + const x = event.clientX; + const y = event.clientY; + this.regression.addData({ eyes: eyesObj, screenPos: [x, y], type: 'move' }); + if (this.params.saveDataAcrossSessions) this.saveManager.saveData(); + } + }; + + /** + * Starts the calibration process. + */ + startCalibration (calibration: true | 'click' | 'move' = true): void { + if (this.params.useCalibration) this.stopCalibration(); + this.params.useCalibration = calibration; + if (calibration === true || calibration === 'click') document.addEventListener('click', this.clickListener, true); + if (calibration === true || calibration === 'move') document.addEventListener('mousemove', this.moveListener, true); + } + + /** + * Stops the calibration process. + */ + stopCalibration (): void { + this.params.useCalibration = false; + document.removeEventListener('click', this.clickListener, true); + document.removeEventListener('mousemove', this.moveListener, true); + } + + /** + * Pauses WebGazer. + */ + pause (): void { + this.paused = true; + this.saveManager.stopAutoSave(); + } + + /** + * Resumes WebGazer after being paused. + * @returns {Promise | undefined} + */ + resume (): Promise | undefined { + if (this.destroyed) throw new Error('WebGazer has been destroyed'); + if (!this.paused) return; + this.paused = false; + this.saveManager.startAutoSave(); + return this.loop(); + } + + /** + * Clears all collected data and resets the model. + */ + clearData (): void { + this.regression.setData([]); + this.smoothingVals.clear(); + this.saveManager.clearData(); + } + + /** + * Stops WebGazer and releases all resources. + */ + destroy (): void { + this.destroyed = true; + this.stop(); + this.regression.dispose(); + this.saveManager.dispose(); + this.stopCalibration(); + + // Reset properties + this.listeners = []; + } + + /** + * Shows video feedback on a specified video element. + * @param {HTMLVideoElement} videoElement - The video element to show feedback on. + * @param {boolean} [mirrorVideo] - Whether to mirror the video feed. + * @returns {Promise} + */ + async showVideoFeedback (videoElement: HTMLVideoElement, options?: Partial): Promise { + const { faceFeedback, mirrorVideo } = options || {}; + this.videoElement = videoElement; + this.tracker?.setVideoElement(videoElement); + + if (mirrorVideo) { + this.videoElement.style.setProperty('-moz-transform', 'scale(-1, 1)'); + this.videoElement.style.setProperty('-webkit-transform', 'scale(-1, 1)'); + this.videoElement.style.setProperty('-o-transform', 'scale(-1, 1)'); + this.videoElement.style.setProperty('transform', 'scale(-1, 1)'); + this.videoElement.style.setProperty('filter', 'FlipH'); + } else { + this.videoElement.style.removeProperty('-moz-transform'); + this.videoElement.style.removeProperty('-webkit-transform'); + this.videoElement.style.removeProperty('-o-transform'); + this.videoElement.style.removeProperty('transform'); + this.videoElement.style.removeProperty('filter'); + } + + // Restart the face feedback on the new video element + if (faceFeedback) this.showFaceFeedback(mirrorVideo); + + // Restart the video feed + if (this.stream) { + this.videoElement.srcObject = this.stream; + return this.videoElement.play(); + } + } + + /** + * Stops video feedback. + */ + stopVideoFeedback (): void { + this.hideFaceFeedback(); + this.tracker?.hideFaceOverlay(); + this.videoElement.pause(); + this.videoElement.srcObject = null; + this.videoElement = document.createElement('video'); + this.videoElement.srcObject = this.stream; + this.videoElement.play(); + } +} diff --git a/src/mat.mjs b/src/mat.mjs deleted file mode 100644 index 2957c5de3..000000000 --- a/src/mat.mjs +++ /dev/null @@ -1,431 +0,0 @@ -/** - * Matrix operations, mostly based on WEKA - * @see https://github.com/Waikato/weka-3.8/blob/master/weka/src/main/java/weka/core/matrix/Matrix.java - */ - -/** - * @callback operationCallback - * @param {number} a - an element of matrix A - * @param {number} b - an element of matrix B - * @return {number} a ○ b - */ -/** - * Apply arithmetic operations to every element of A and B: - * X = A ○ B, where ○ can be one of +, -, *, /, etc. - * - * @param {Array.>} A - * @param {Array.>} B - * @param {operationCallback} op - operation to apply, op(a, b) => a ○ b - * @return {Array.>} A ○ B - */ -function applyArithmeticOperation(A, B, op) { - - if (A.length !== B.length || A[0].length !== B[0].length) { - throw new Error('Matrix dimensions must agree.'); - } - - const rows = A.length; - const cols = A[0].length; - - const X = new Array(rows); - - for (let i = 0; i < rows; i++) { - X[i] = new Array(cols); - - for (let j = 0; j < cols; j++) { - X[i][j] = op(A[i][j], B[i][j]); - } - } - - return X; -} - -const mat = { - /** - * Transposes an m*n array - * @param {Array.>} matrix - of 'M x N' dimensionality - * @return {Array.>} transposed matrix - */ - transpose(matrix) { - const rows = matrix.length; - const cols = matrix[0].length; - const transposedMatrix = new Array(cols); - - for (let j = 0; j < cols; j++) { - transposedMatrix[j] = new Array(rows); - - for (let i = 0; i < rows; i++) { - transposedMatrix[j][i] = matrix[i][j]; - } - } - - return transposedMatrix; - }, - - /** - * Get a sub-matrix of matrix - * @param {Array.>} matrix - original matrix - * @param {Array.} r - Array of row indices - * @param {Number} j0 - Initial column index - * @param {Number} j1 - Final column index - * @returns {Array} The sub-matrix matrix(r(:),j0:j1) - */ - getMatrix(matrix, r, j0, j1) { - const X = new Array(r.length); - const m = j1 - j0 + 1; - - for (let i = 0, len = r.length; i < len; i++) { - X[i] = new Array(m); - - for (let j = j0; j <= j1; j++) { - X[i][j - j0] = matrix[r[i]][j]; - } - } - - return X; - }, - - /** - * Get a submatrix of matrix - * @param {Array.>} matrix - original matrix - * @param {Number} i0 - Initial row index - * @param {Number} i1 - Final row index - * @param {Number} j0 - Initial column index - * @param {Number} j1 - Final column index - * @return {Array} The sub-matrix matrix(i0:i1,j0:j1) - */ - getSubMatrix(matrix, i0, i1, j0, j1) { - const size = j1 - j0 + 1; - const X = new Array(i1 - i0 + 1); - - for (let i = i0; i <= i1; i++) { - const subI = i - i0; - X[subI] = new Array(size); - - for (let j = j0; j <= j1; j++) { - X[subI][j - j0] = matrix[i][j]; - } - } - - return X; - }, - - /** - * Linear algebraic matrix multiplication, X = A * B - * @param {Array.>} A - * @param {Array.>} B - * @return {Array.>} Matrix product, A * B - */ - mult(matrix1, matrix2) { - if (matrix2.length != matrix1[0].length){ - console.log('Matrix inner dimensions must agree:'); - } - - var X = new Array(matrix1.length), - Bcolj = new Array(matrix1[0].length); - - for (var j = 0; j < matrix2[0].length; j++){ - for (var k = 0; k < matrix1[0].length; k++){ - Bcolj[k] = matrix2[k][j]; - } - for (var i = 0; i < matrix1.length; i++){ - - if (j === 0) - X[i] = new Array(matrix2[0].length); - - var Arowi = matrix1[i]; - var s = 0; - for (var k = 0; k < matrix1[0].length; k++){ - s += Arowi[k]*Bcolj[k]; - } - X[i][j] = s; - } - } - return X; - }, - - /** - * Multiply a matrix by a scalar, X = s*A - * @param {Array.>} A - matrix - * @param {Number} s - scalar - * @return {Array.>} s*A - */ - multScalar(A, s) { - const rows = A.length; - const cols = A[0].length; - - const X = new Array(rows); - - for (let i = 0; i < rows; i++) { - X[i] = new Array(cols); - - for (let j = 0; j < cols; j++) { - X[i][j] = A[i][j] * s; - } - } - - return X; - }, - - /** - * Linear algebraic matrix addition, X = A + B - * @param {Array.>} A - * @param {Array.>} B - * @return {Array.>} A + B - */ - add(A, B) { - return applyArithmeticOperation(A, B, (a, b) => a + b); - }, - - /** - * Linear algebraic matrix subtraction, X = A - B - * @param {Array.>} A - * @param {Array.>} B - * @return {Array.>} A - B - */ - sub(A, B) { - return applyArithmeticOperation(A, B, (a, b) => a - b); - }, - - /** - * Matrix inverse or pseudoinverse, based on WEKA code - * @param {Array.>} A - original matrix - * @return inverse(A) if A is square, pseudoinverse otherwise. - */ - inv(A) { - return mat.solve(A, mat.identity(A.length, A[0].length)); - }, - - /** - * Generate identity matrix, based on WEKA code - * @param {Number} m - number of rows. - * @param {Number} [n] - number of colums, n = m if undefined. - * @return {Array.>} An m * n matrix with ones on the diagonal and zeros elsewhere. - */ - identity(m, n = m) { - const X = new Array(m); - - for (let i = 0; i < m; i++) { - X[i] = new Array(n); - - for (let j = 0; j < n; j++) { - X[i][j] = (i === j ? 1.0 : 0.0); - } - } - - return X; - }, - - /** - * Solve A*X = B, based on WEKA code - * @param {Array.>} A - left matrix of equation to be solved - * @param {Array.>} B - right matrix of equation to be solved - * @return {Array.>} solution if A is square, least squares solution otherwiseis - */ - solve(A, B) { - if (A.length === A[0].length) { - // A is square - return mat.LUDecomposition(A, B); - } - - return mat.QRDecomposition(A, B); - }, - - /** - * LUDecomposition to solve A*X = B, based on WEKA code - * @param {Array.>} A - left matrix of equation to be solved - * @param {Array.>} B - right matrix of equation to be solved - * @return {Array.>} X so that L*U*X = B(piv,:) - */ - LUDecomposition(A, B) { - var LU = new Array(A.length); - - for (var i = 0; i < A.length; i++){ - LU[i] = new Array(A[0].length); - for (var j = 0; j < A[0].length; j++){ - LU[i][j] = A[i][j]; - } - } - - var m = A.length; - var n = A[0].length; - var piv = new Array(m); - for (var i = 0; i < m; i++){ - piv[i] = i; - } - var pivsign = 1; - var LUrowi = new Array(); - var LUcolj = new Array(m); - // Outer loop. - for (var j = 0; j < n; j++){ - // Make a copy of the j-th column to localize references. - for (var i = 0; i < m; i++){ - LUcolj[i] = LU[i][j]; - } - // Apply previous transformations. - for (var i = 0; i < m; i++){ - LUrowi = LU[i]; - // Most of the time is spent in the following dot product. - var kmax = Math.min(i,j); - var s = 0; - for (var k = 0; k < kmax; k++){ - s += LUrowi[k]*LUcolj[k]; - } - LUrowi[j] = LUcolj[i] -= s; - } - // Find pivot and exchange if necessary. - var p = j; - for (var i = j+1; i < m; i++){ - if (Math.abs(LUcolj[i]) > Math.abs(LUcolj[p])){ - p = i; - } - } - if (p != j){ - for (var k = 0; k < n; k++){ - var t = LU[p][k]; - LU[p][k] = LU[j][k]; - LU[j][k] = t; - } - var k = piv[p]; - piv[p] = piv[j]; - piv[j] = k; - pivsign = -pivsign; - } - // Compute multipliers. - if (j < m & LU[j][j] != 0){ - for (var i = j+1; i < m; i++){ - LU[i][j] /= LU[j][j]; - } - } - } - if (B.length != m){ - console.log('Matrix row dimensions must agree.'); - } - for (var j = 0; j < n; j++){ - if (LU[j][j] === 0){ - console.log('Matrix is singular.') - } - } - var nx = B[0].length; - var X = mat.getMatrix(B,piv,0,nx-1); - // Solve L*Y = B(piv,:) - for (var k = 0; k < n; k++){ - for (var i = k+1; i < n; i++){ - for (var j = 0; j < nx; j++){ - X[i][j] -= X[k][j]*LU[i][k]; - } - } - } - // Solve U*X = Y; - for (var k = n-1; k >= 0; k--){ - for (var j = 0; j < nx; j++){ - X[k][j] /= LU[k][k]; - } - for (var i = 0; i < k; i++){ - for (var j = 0; j < nx; j++){ - X[i][j] -= X[k][j]*LU[i][k]; - } - } - } - return X; - }, - - /** - * Least squares solution of A*X = B, based on WEKA code - * @param {Array.>} A - left side matrix to be solved - * @param {Array.>} B - a matrix with as many rows as A and any number of columns. - * @return {Array.>} X - that minimizes the two norms of QR*X-B. - */ - QRDecomposition(A, B) { - // Initialize. - var QR = new Array(A.length); - - for (var i = 0; i < A.length; i++){ - QR[i] = new Array(A[0].length); - for (var j = 0; j < A[0].length; j++){ - QR[i][j] = A[i][j]; - } - } - var m = A.length; - var n = A[0].length; - var Rdiag = new Array(n); - var nrm; - - // Main loop. - for (var k = 0; k < n; k++){ - // Compute 2-norm of k-th column without under/overflow. - nrm = 0; - for (var i = k; i < m; i++){ - nrm = Math.hypot(nrm,QR[i][k]); - } - if (nrm != 0){ - // Form k-th Householder vector. - if (QR[k][k] < 0){ - nrm = -nrm; - } - for (var i = k; i < m; i++){ - QR[i][k] /= nrm; - } - QR[k][k] += 1; - - // Apply transformation to remaining columns. - for (var j = k+1; j < n; j++){ - var s = 0; - for (var i = k; i < m; i++){ - s += QR[i][k]*QR[i][j]; - } - s = -s/QR[k][k]; - for (var i = k; i < m; i++){ - QR[i][j] += s*QR[i][k]; - } - } - } - Rdiag[k] = -nrm; - } - if (B.length != m){ - console.log('Matrix row dimensions must agree.'); - } - for (var j = 0; j < n; j++){ - if (Rdiag[j] === 0) - console.log('Matrix is rank deficient'); - } - // Copy right hand side - var nx = B[0].length; - var X = new Array(B.length); - for(var i=0; i= 0; k--){ - for (var j = 0; j < nx; j++){ - X[k][j] /= Rdiag[k]; - } - for (var i = 0; i < k; i++){ - for (var j = 0; j < nx; j++){ - X[i][j] -= X[k][j]*QR[i][k]; - } - } - } - return mat.getSubMatrix(X,0,n-1,0,nx-1); - } -}; - -export default mat; diff --git a/src/params.mjs b/src/params.mjs deleted file mode 100644 index cc7690c23..000000000 --- a/src/params.mjs +++ /dev/null @@ -1,29 +0,0 @@ -const params = { - moveTickSize: 50, - videoContainerId: 'webgazerVideoContainer', - videoElementId: 'webgazerVideoFeed', - videoElementCanvasId: 'webgazerVideoCanvas', - faceOverlayId: 'webgazerFaceOverlay', - faceFeedbackBoxId: 'webgazerFaceFeedbackBox', - gazeDotId: 'webgazerGazeDot', - videoViewerWidth: 320, - videoViewerHeight: 240, - faceFeedbackBoxRatio: 0.66, - // View options - showVideo: true, - mirrorVideo: true, - showFaceOverlay: true, - showFaceFeedbackBox: true, - showGazeDot: true, - camConstraints: { video: { width: { min: 320, ideal: 640, max: 1920 }, height: { min: 240, ideal: 480, max: 1080 }, facingMode: "user" } }, - dataTimestep: 50, - showVideoPreview: true, - applyKalmanFilter: true, - saveDataAcrossSessions: true, - // Whether or not to store accuracy eigenValues, used by the calibration example file - storingPoints: false, - - trackEye: 'both', -}; - -export default params; diff --git a/src/pupil.mjs b/src/pupil.mjs deleted file mode 100644 index 29cf4d264..000000000 --- a/src/pupil.mjs +++ /dev/null @@ -1,109 +0,0 @@ -const pupil = {}; - -/** - * Returns intensity value at x,y position of a pixels image - * @param {Array} pixels - array of size width*height - * @param {Number} x - input x value - * @param {Number} y - input y value - * @param {Number} width - width of pixels image - * @returns {Number} - intensity value in [0,255] - */ -const getValue = function (pixels, x, y, width){ - return pixels[y * width + x]; -}; - -/** - * Computes summation area table/integral image of a pixel matrix - * @param {Array} pixels value of eye area - * @param {Number} width - of image in 'pixels' - * @param {Number} height - of image in 'pixels' - * @returns {Array} - integral image - */ -const getSumTable = function (pixels, width, height){ - var integralImage = new Array(width); - var sumx = 0; - var sumy = 0; - - for (var i = 0; i < width; i++){ - integralImage[i] = new Array(height); - sumx += getValue(pixels, i, 0, width); - integralImage[i][0] = sumx; - } - - for (var i = 0; i < height; i++){ - sumy += getValue(pixels, 0, i, width); - integralImage[0][i] = sumy; - } - - for (var x = 1; x < width; x++){ - for (var y = 1; y < height; y++){ - integralImage[x][y] = getValue(pixels, x, y, width) + integralImage[x - 1][y] + integralImage[x][y - 1] - integralImage[x - 1][y - 1]; - } - } - return integralImage; -}; - -/** - * Detects a pupil in a set of pixels - * @param {Array} pixels - patch of pixels to look for pupil into - * @param {Number} width - of pixel patch - * @param {Number} height - of pixel patch - * @return {Array} coordinate of the bottom right corner and width of the best fitted pupil - */ -const getSinglePupil = function (pixels, width, height){ - var summedAreaTable = getSumTable(pixels, width, height); - var bestAvgScore = 999999; //want to minimize this score - var bestPoint = [0, 0]; //bottom right corner of best fitted pupil - var bestHalfWidth = 0; //corresponding half width of the best fitted pupil - var offset = Math.floor(width / 10.0); //padding - //halfWidth could also start at 1, but this makes it faster - for (var halfWidth = Math.floor(height / 10.0); halfWidth < width / 2; halfWidth++){ - //think of a sliding rectangular window of width halfWidth*2 that goes through the whole eye pixel matrix and does the following: - //1) computes the irisArea, which is the total intensity of the iris - //2) computes the scleraIrisArea, which is multiple rows of pixels including the sclera and iris. - //3) computes avg, which is the intensity of the area divided by the number of pixels. - //start at the bottom right of the rectangle!not top left - for (var x = halfWidth; x < width - offset; x++){ - for (var y = halfWidth; y < height - offset; y++){ - //evaluate area by the formula found on wikipedia about the summed area table: I(D)+I(A)-I(B)-I(C) - var irisArea = summedAreaTable[x + offset][y + offset] + summedAreaTable[x + offset - halfWidth][y + offset - halfWidth] - summedAreaTable[x + offset][y + offset - halfWidth] - summedAreaTable[x + offset - halfWidth][y + offset]; - var avgScore = 1.0 * irisArea / ((halfWidth + 1) * (halfWidth + 1)) + 1; - //summation area table again - var scleraIrisArea = ((1.0 * summedAreaTable[width - 1 - offset][y + offset] + summedAreaTable[0 + offset][y + offset - halfWidth] - summedAreaTable[0 + offset][y + offset] - summedAreaTable[width - 1 - offset][y + offset - halfWidth]) - irisArea); - //minimize avgScore/scleraIrisArea. 150 is too high, might have to change since it's closer to white - if ((avgScore) / scleraIrisArea < bestAvgScore && avgScore < 150){ - bestAvgScore = (avgScore) / scleraIrisArea; - bestPoint = [x + offset, y + offset]; - bestHalfWidth = halfWidth; - } - } - } - } - return [bestPoint, bestHalfWidth]; -}; - -/** - * Given an object with two eye patches it finds the location of the detected pupils - * @param {Object} eyesObj - left and right detected eye patches - * @return {Object} eyesObj - updated eye patches with information about pupils' locations - */ -const getPupils = function(eyesObj) { - if (!eyesObj) { - return eyesObj; - } - if (!eyesObj.left.blink) { - eyesObj.left.pupil = getSinglePupil(Array.prototype.slice.call(webgazer.util.grayscale(eyesObj.left.patch.data, eyesObj.left.width, eyesObj.left.height)), eyesObj.left.width, eyesObj.left.height); - eyesObj.left.pupil[0][0] -= eyesObj.left.pupil[1]; - eyesObj.left.pupil[0][1] -= eyesObj.left.pupil[1]; - } - if (!eyesObj.right.blink) { - eyesObj.right.pupil = getSinglePupil(Array.prototype.slice.call(webgazer.util.grayscale(eyesObj.right.patch.data, eyesObj.right.width, eyesObj.right.height)), eyesObj.right.width, eyesObj.right.height); - eyesObj.right.pupil[0][0] -= eyesObj.right.pupil[1]; - eyesObj.right.pupil[0][1] -= eyesObj.right.pupil[1]; - } - return eyesObj; -} - -pupil.getPupils = getPupils; - -export default pupil; diff --git a/src/pupil.ts b/src/pupil.ts new file mode 100644 index 000000000..dc7cc22be --- /dev/null +++ b/src/pupil.ts @@ -0,0 +1,105 @@ +import { grayscale, Pupil } from './worker_scripts/util'; +import type { TwoEyes } from './facemesh'; +import type { Matrix } from './worker_scripts/mat'; + +/** + * Returns intensity value at x,y position of a pixels image + * @param {number[]} pixels - array of size width*height + * @param {number} x - input x value + * @param {number} y - input y value + * @param {number} width - width of pixels image + * @returns {number} - intensity value in [0,255] + */ +const getValue = (pixels: number[], x: number, y: number, width: number): number => pixels[y * width + x]; + +/** + * Computes summation area table/integral image of a pixel matrix + * @param {number[]} pixels value of eye area + * @param {number} width - of image in 'pixels' + * @param {number} height - of image in 'pixels' + * @returns {Matrix} - integral image + */ +const getSumTable = (pixels: number[], width: number, height: number): Matrix => { + const integralImage: Matrix = new Array(width); + let sumx = 0; + let sumy = 0; + + for (let i = 0; i < width; i++) { + integralImage[i] = new Array(height); + sumx += getValue(pixels, i, 0, width); + integralImage[i][0] = sumx; + } + + for (let i = 0; i < height; i++) { + sumy += getValue(pixels, 0, i, width); + integralImage[0][i] = sumy; + } + + for (let x = 1; x < width; x++) { + for (let y = 1; y < height; y++) { + integralImage[x][y] = getValue(pixels, x, y, width) + integralImage[x - 1][y] + integralImage[x][y - 1] - integralImage[x - 1][y - 1]; + } + } + return integralImage; +}; + +/** + * Detects a pupil in a set of pixels + * @param {number[]} pixels - patch of pixels to look for pupil into + * @param {number} width - of pixel patch + * @param {number} height - of pixel patch + * @return {Pupil} coordinate of the bottom right corner and width of the best fitted pupil + */ +const getSinglePupil = (pixels: number[], width: number, height: number): Pupil => { + const summedAreaTable = getSumTable(pixels, width, height); + let bestAvgScore = 999999; // want to minimize this score + let bestPoint: [number, number] = [0, 0]; // bottom right corner of best fitted pupil + let bestHalfWidth = 0; // corresponding half width of the best fitted pupil + const offset = Math.floor(width / 10.0); // padding + // halfWidth could also start at 1, but this makes it faster + for (let halfWidth = Math.floor(height / 10.0); halfWidth < width / 2; halfWidth++) { + // think of a sliding rectangular window of width halfWidth*2 that goes through the whole eye pixel matrix and does the following: + // 1) computes the irisArea, which is the total intensity of the iris + // 2) computes the scleraIrisArea, which is multiple rows of pixels including the sclera and iris. + // 3) computes avg, which is the intensity of the area divided by the number of pixels. + // start at the bottom right of the rectangle!not top left + for (let x = halfWidth; x < width - offset; x++) { + for (let y = halfWidth; y < height - offset; y++) { + // evaluate area by the formula found on wikipedia about the summed area table: I(D)+I(A)-I(B)-I(C) + const irisArea = summedAreaTable[x + offset][y + offset] + summedAreaTable[x + offset - halfWidth][y + offset - halfWidth] - summedAreaTable[x + offset][y + offset - halfWidth] - summedAreaTable[x + offset - halfWidth][y + offset]; + const avgScore = 1.0 * irisArea / ((halfWidth + 1) * (halfWidth + 1)) + 1; + // summation area table again + const scleraIrisArea = ((1.0 * summedAreaTable[width - 1 - offset][y + offset] + summedAreaTable[0 + offset][y + offset - halfWidth] - summedAreaTable[0 + offset][y + offset] - summedAreaTable[width - 1 - offset][y + offset - halfWidth]) - irisArea); + // minimize avgScore/scleraIrisArea. 150 is too high, might have to change since it's closer to white + if ((avgScore) / scleraIrisArea < bestAvgScore && avgScore < 150) { + bestAvgScore = (avgScore) / scleraIrisArea; + bestPoint = [x + offset, y + offset]; + bestHalfWidth = halfWidth; + } + } + } + } + return [bestPoint, bestHalfWidth]; +}; + +/** + * Given an object with two eye patches it finds the location of the detected pupils + * @param {TwoEyes} eyesObj - left and right detected eye patches + * @return {TwoEyes} eyesObj - updated eye patches with information about pupils' locations + */ +export const getPupils = (eyesObj: TwoEyes): TwoEyes => { + if (!eyesObj) { + return eyesObj; + } + if (!eyesObj.left.blink) { + eyesObj.left.pupil = getSinglePupil(Array.from(grayscale(eyesObj.left.patch.data, eyesObj.left.width, eyesObj.left.height)), eyesObj.left.width, eyesObj.left.height); + eyesObj.left.pupil[0][0] -= eyesObj.left.pupil[1]; + eyesObj.left.pupil[0][1] -= eyesObj.left.pupil[1]; + } + if (!eyesObj.right.blink) { + eyesObj.right.pupil = getSinglePupil(Array.from(grayscale(eyesObj.right.patch.data, eyesObj.right.width, eyesObj.right.height)), eyesObj.right.width, eyesObj.right.height); + eyesObj.right.pupil[0][0] -= eyesObj.right.pupil[1]; + eyesObj.right.pupil[0][1] -= eyesObj.right.pupil[1]; + } + return eyesObj; +}; diff --git a/src/ridgeReg.mjs b/src/ridgeReg.mjs deleted file mode 100644 index ee64e16f2..000000000 --- a/src/ridgeReg.mjs +++ /dev/null @@ -1,104 +0,0 @@ -import util from './util.mjs'; -import util_regression from './util_regression.mjs'; -import params from './params.mjs'; - -const reg = {}; - -/** - * Constructor of RidgeReg object, - * this object allow to perform ridge regression - * @constructor - */ -reg.RidgeReg = function() { - this.init(); -}; - -/** - * Initialize new arrays and initialize Kalman filter. - */ -reg.RidgeReg.prototype.init = util_regression.InitRegression - -/** - * Add given data from eyes - * @param {Object} eyes - eyes where extract data to add - * @param {Object} screenPos - The current screen point - * @param {Object} type - The type of performed action - */ -reg.RidgeReg.prototype.addData = util_regression.addData - -/** - * Try to predict coordinates from pupil data - * after apply linear regression on data set - * @param {Object} eyesObj - The current user eyes object - * @returns {Object} - */ -reg.RidgeReg.prototype.predict = function(eyesObj) { - if (!eyesObj || this.eyeFeaturesClicks.length === 0) { - return null; - } - var acceptTime = performance.now() - this.trailTime; - var trailX = []; - var trailY = []; - var trailFeat = []; - for (var i = 0; i < this.trailDataWindow; i++) { - if (this.trailTimes.get(i) > acceptTime) { - trailX.push(this.screenXTrailArray.get(i)); - trailY.push(this.screenYTrailArray.get(i)); - trailFeat.push(this.eyeFeaturesTrail.get(i)); - } - } - - var screenXArray = this.screenXClicksArray.data.concat(trailX); - var screenYArray = this.screenYClicksArray.data.concat(trailY); - var eyeFeatures = this.eyeFeaturesClicks.data.concat(trailFeat); - - var coefficientsX = util_regression.ridge(screenXArray, eyeFeatures, this.ridgeParameter); - var coefficientsY = util_regression.ridge(screenYArray, eyeFeatures, this.ridgeParameter); - - var eyeFeats = util.getEyeFeats(eyesObj); - var predictedX = 0; - for(var i=0; i< eyeFeats.length; i++){ - predictedX += eyeFeats[i] * coefficientsX[i]; - } - var predictedY = 0; - for(var i=0; i< eyeFeats.length; i++){ - predictedY += eyeFeats[i] * coefficientsY[i]; - } - - predictedX = Math.floor(predictedX); - predictedY = Math.floor(predictedY); - - if (params.applyKalmanFilter) { - // Update Kalman model, and get prediction - var newGaze = [predictedX, predictedY]; // [20200607 xk] Should we use a 1x4 vector? - newGaze = this.kalman.update(newGaze); - - return { - x: newGaze[0], - y: newGaze[1] - }; - } else { - return { - x: predictedX, - y: predictedY - }; - } -}; - -reg.RidgeReg.prototype.setData = util_regression.setData; - -/** - * Return the data - * @returns {Array.|*} - */ -reg.RidgeReg.prototype.getData = function() { - return this.dataClicks.data; -} - -/** - * The RidgeReg object name - * @type {string} - */ -reg.RidgeReg.prototype.name = 'ridge'; - -export default reg; diff --git a/src/ridgeReg.ts b/src/ridgeReg.ts new file mode 100644 index 000000000..eb5567ce2 --- /dev/null +++ b/src/ridgeReg.ts @@ -0,0 +1,57 @@ +import { Ridge, ridge } from './worker_scripts/regression'; +import type { EyesData } from './facemesh'; +import type { Matrix } from './worker_scripts/mat'; + +export class RidgeReg extends Ridge { + /** + * Try to predict coordinates from pupil data + * after apply linear regression on data set + * @param {TwoEyes | undefined} eyesObj - The current user eyes object + * @returns {{ x: number; y: number } | undefined} + */ + predict = (eyesObj: EyesData | undefined): { x: number; y: number } | undefined => { + if (!eyesObj || this.eyeFeaturesClicks.length === 0) return; + const acceptTime = performance.now() - this.trailTime; + const trailX: Matrix = []; + const trailY: Matrix = []; + const trailFeat: Matrix = []; + for (let i = 0; i < this.trailDataWindow; i++) { + if (this.trailTimes.get(i) > acceptTime) { + trailX.push(this.screenXTrailArray.get(i)); + trailY.push(this.screenYTrailArray.get(i)); + trailFeat.push(this.eyeFeaturesTrail.get(i)); + } + } + + const screenXArray = this.screenXClicksArray.data.concat(trailX); + const screenYArray = this.screenYClicksArray.data.concat(trailY); + const eyeFeatures = this.eyeFeaturesClicks.data.concat(trailFeat); + + const coefficientsX = ridge(screenXArray, eyeFeatures, this.ridgeParameter); + const coefficientsY = ridge(screenYArray, eyeFeatures, this.ridgeParameter); + + const eyeFeats = eyesObj.grayscale; + let predictedX = 0; + for (let i = 0; i < eyeFeats.length; i++) { + predictedX += eyeFeats[i] * coefficientsX[i]; + } + let predictedY = 0; + for (let i = 0; i < eyeFeats.length; i++) { + predictedY += eyeFeats[i] * coefficientsY[i]; + } + + predictedX = Math.floor(predictedX); + predictedY = Math.floor(predictedY); + + if (this.useKalmanFilter) { + // Update Kalman model, and get prediction + const [x, y] = this.kalman.update([predictedX, predictedY]); + return { x, y }; + } else { + return { + x: predictedX, + y: predictedY + }; + } + }; +} diff --git a/src/ridgeRegThreaded.mjs b/src/ridgeRegThreaded.mjs deleted file mode 100644 index 5b687643d..000000000 --- a/src/ridgeRegThreaded.mjs +++ /dev/null @@ -1,161 +0,0 @@ -import util from './util.mjs'; -import util_regression from './util_regression.mjs'; -import params from './params.mjs'; -import mat from './mat.mjs'; - -const reg = {}; - -var ridgeParameter = Math.pow(10,-5); -var dataWindow = 700; -var weights = {'X':[0],'Y':[0]}; -var trailDataWindow = 10; - - -/** - * Constructor of RidgeRegThreaded object, - * it retrieve data window, and prepare a worker, - * this object allow to perform threaded ridge regression - * @constructor - */ -reg.RidgeRegThreaded = function() { - this.init(); -}; - -/** - * Initialize new arrays and initialize Kalman filter. - */ -reg.RidgeRegThreaded.prototype.init = function() { - this.screenXClicksArray = new util.DataWindow(dataWindow); - this.screenYClicksArray = new util.DataWindow(dataWindow); - this.eyeFeaturesClicks = new util.DataWindow(dataWindow); - - this.screenXTrailArray = new util.DataWindow(trailDataWindow); - this.screenYTrailArray = new util.DataWindow(trailDataWindow); - this.eyeFeaturesTrail = new util.DataWindow(trailDataWindow); - - this.dataClicks = new util.DataWindow(dataWindow); - this.dataTrail = new util.DataWindow(dataWindow); - - // Place the src/ridgeworker.js file into the same directory as your html file. - if (!this.worker) { - this.worker = new Worker('ridgeWorker.mjs'); // [20200708] TODO: Figure out how to make this inline - this.worker.onerror = function(err) { console.log(err.message); }; - this.worker.onmessage = function(evt) { - weights.X = evt.data.X; - weights.Y = evt.data.Y; - }; - console.log('initialized worker'); - } - - // Initialize Kalman filter [20200608 xk] what do we do about parameters? - // [20200611 xk] unsure what to do w.r.t. dimensionality of these matrices. So far at least - // by my own anecdotal observation a 4x1 x vector seems to work alright - var F = [ [1, 0, 1, 0], - [0, 1, 0, 1], - [0, 0, 1, 0], - [0, 0, 0, 1]]; - - //Parameters Q and R may require some fine tuning - var Q = [ [1/4, 0, 1/2, 0], - [0, 1/4, 0, 1/2], - [1/2, 0, 1, 0], - [0, 1/2, 0, 1]];// * delta_t - var delta_t = 1/10; // The amount of time between frames - Q = mat.multScalar(Q, delta_t); - - var H = [ [1, 0, 0, 0, 0, 0], - [0, 1, 0, 0, 0, 0], - [0, 0, 1, 0, 0, 0], - [0, 0, 0, 1, 0, 0]]; - var H = [ [1, 0, 0, 0], - [0, 1, 0, 0]]; - var pixel_error = 47; //We will need to fine tune this value [20200611 xk] I just put a random value here - - //This matrix represents the expected measurement error - var R = mat.multScalar(mat.identity(2), pixel_error); - - var P_initial = mat.multScalar(mat.identity(4), 0.0001); //Initial covariance matrix - var x_initial = [[500], [500], [0], [0]]; // Initial measurement matrix - - this.kalman = new util_regression.KalmanFilter(F, H, Q, R, P_initial, x_initial); -} -/** - * Add given data from eyes - * @param {Object} eyes - eyes where extract data to add - * @param {Object} screenPos - The current screen point - * @param {Object} type - The type of performed action - */ -reg.RidgeRegThreaded.prototype.addData = function(eyes, screenPos, type) { - if (!eyes) { - return; - } - //not doing anything with blink at present - // if (eyes.left.blink || eyes.right.blink) { - // return; - // } - this.worker.postMessage({'eyes':util.getEyeFeats(eyes), 'screenPos':screenPos, 'type':type}); -}; - -/** - * Try to predict coordinates from pupil data - * after apply linear regression on data set - * @param {Object} eyesObj - The current user eyes object - * @returns {Object} - */ -reg.RidgeRegThreaded.prototype.predict = function(eyesObj) { - // console.log('LOGGING..'); - if (!eyesObj) { - return null; - } - var coefficientsX = weights.X; - var coefficientsY = weights.Y; - - var eyeFeats = util.getEyeFeats(eyesObj); - var predictedX = 0, predictedY = 0; - for(var i=0; i< eyeFeats.length; i++){ - predictedX += eyeFeats[i] * coefficientsX[i]; - predictedY += eyeFeats[i] * coefficientsY[i]; - } - - predictedX = Math.floor(predictedX); - predictedY = Math.floor(predictedY); - - if (params.applyKalmanFilter) { - // Update Kalman model, and get prediction - var newGaze = [predictedX, predictedY]; // [20200607 xk] Should we use a 1x4 vector? - newGaze = this.kalman.update(newGaze); - - return { - x: newGaze[0], - y: newGaze[1] - }; - } else { - return { - x: predictedX, - y: predictedY - }; - } -}; - -/** - * Add given data to current data set then, - * replace current data member with given data - * @param {Array.} data - The data to set - */ -reg.RidgeRegThreaded.prototype.setData = util_regression.setData - -/** - * Return the data - * @returns {Array.|*} - */ -reg.RidgeRegThreaded.prototype.getData = function() { - return this.dataClicks.data; -}; - -/** - * The RidgeRegThreaded object name - * @type {string} - */ -reg.RidgeRegThreaded.prototype.name = 'ridge'; - -export default reg; diff --git a/src/ridgeRegThreaded.ts b/src/ridgeRegThreaded.ts new file mode 100644 index 000000000..01b78b3a1 --- /dev/null +++ b/src/ridgeRegThreaded.ts @@ -0,0 +1,90 @@ +// @ts-check +import { Ridge, RidgeOptions } from './worker_scripts/regression'; +import type { Point } from './worker_scripts/util'; +import type { EyesData } from './facemesh'; +import type { EyeData } from './ridgeWorker.worker'; + +export interface Weights { + X: number[]; + Y: number[]; +} + +export class RidgeRegThreaded extends Ridge { + /** + * The RidgeWeightedReg object name + */ + name = 'ridgeThreaded' as const; + + weights: Weights = { X: [0], Y: [0] }; + worker: Worker; + + /** + * Constructor of RidgeRegThreaded object, + * it retrieve data window, and prepare a worker, + * this object allow to perform threaded ridge regression + * @constructor + */ + constructor (options: RidgeOptions) { + super(options); + const workerUrl = new URL('./ridgeWorker.worker.js', import.meta.url); + this.worker = new Worker(workerUrl, { type: 'module' }); + this.worker.onerror = (err: ErrorEvent) => console.log(err.message); + this.worker.onmessage = (evt: MessageEvent) => { + this.weights.X = evt.data.X; + this.weights.Y = evt.data.Y; + }; + } + + /** + * Data to be added + */ + addData = (data: import('./worker_scripts/regression.js').DataSet): void => { + const { eyes, screenPos, type } = data; + if (!eyes) return; + if (type === 'click') this.dataClicks.push(data); + this.worker.postMessage({ eyes: eyes.grayscale, screenPos, type } as EyeData); + }; + + /** + * Return the data + */ + getData = (): import('./worker_scripts/regression.js').DataSet[] => this.dataClicks.data; + + /** + * Try to predict coordinates from pupil data + * after apply linear regression on data set + * @param eyesObj - The current user eyes object + */ + predict = (eyesObj: EyesData | undefined): Point | undefined => { + if (!eyesObj) return; + const coefficientsX = this.weights.X; + const coefficientsY = this.weights.Y; + + const eyeFeats = eyesObj.grayscale; + let predictedX = 0; + let predictedY = 0; + for (let i = 0; i < Math.min(eyeFeats.length, coefficientsX.length, coefficientsY.length); i++) { + predictedX += eyeFeats[i] * coefficientsX[i]; + predictedY += eyeFeats[i] * coefficientsY[i]; + } + + predictedX = Math.floor(predictedX); + predictedY = Math.floor(predictedY); + if (isNaN(predictedX) || isNaN(predictedY)) console.log(coefficientsX, coefficientsY, predictedX, predictedY, eyeFeats); + + if (this.useKalmanFilter) { + // Update Kalman model, and get prediction + const [x, y] = this.kalman.update([predictedX, predictedY]); + return { x, y }; + } else { + return { + x: predictedX, + y: predictedY + }; + } + }; + + dispose = (): void => { + this.worker.terminate(); + }; +} diff --git a/src/ridgeWeightedReg.mjs b/src/ridgeWeightedReg.mjs deleted file mode 100644 index efb2dd470..000000000 --- a/src/ridgeWeightedReg.mjs +++ /dev/null @@ -1,125 +0,0 @@ -import util from './util.mjs'; -import util_regression from './util_regression.mjs'; -import params from './params.mjs'; - -const reg = {}; - -/** - * Constructor of RidgeWeightedReg object - * @constructor - */ -reg.RidgeWeightedReg = function() { - this.init(); -}; - -/** - * Initialize new arrays and initialize Kalman filter. - */ -reg.RidgeWeightedReg.prototype.init = util_regression.InitRegression - -/** - * Add given data from eyes - * @param {Object} eyes - eyes where extract data to add - * @param {Object} screenPos - The current screen point - * @param {Object} type - The type of performed action - */ -reg.RidgeWeightedReg.prototype.addData = util_regression.addData - -/** - * Try to predict coordinates from pupil data - * after apply linear regression on data set - * @param {Object} eyesObj - The current user eyes object - * @returns {Object} - */ -reg.RidgeWeightedReg.prototype.predict = function(eyesObj) { - if (!eyesObj || this.eyeFeaturesClicks.length === 0) { - return null; - } - var acceptTime = performance.now() - this.trailTime; - var trailX = []; - var trailY = []; - var trailFeat = []; - for (var i = 0; i < this.trailDataWindow; i++) { - if (this.trailTimes.get(i) > acceptTime) { - trailX.push(this.screenXTrailArray.get(i)); - trailY.push(this.screenYTrailArray.get(i)); - trailFeat.push(this.eyeFeaturesTrail.get(i)); - } - } - - var len = this.eyeFeaturesClicks.data.length; - var weightedEyeFeats = Array(len); - var weightedXArray = Array(len); - var weightedYArray = Array(len); - for (var i = 0; i < len; i++) { - var weight = Math.sqrt( 1 / (len - i) ); // access from oldest to newest so should start with low weight and increase steadily - //abstraction is leaking... - var trueIndex = this.eyeFeaturesClicks.getTrueIndex(i); - for (var j = 0; j < this.eyeFeaturesClicks.data[trueIndex].length; j++) { - var val = this.eyeFeaturesClicks.data[trueIndex][j] * weight; - if (weightedEyeFeats[trueIndex] !== undefined){ - weightedEyeFeats[trueIndex].push(val); - } else { - weightedEyeFeats[trueIndex] = [val]; - } - } - weightedXArray[i] = this.screenXClicksArray.get(i).slice(0, this.screenXClicksArray.get(i).length); - weightedYArray[i] = this.screenYClicksArray.get(i).slice(0, this.screenYClicksArray.get(i).length); - weightedXArray[i][0] = weightedXArray[i][0] * weight; - weightedYArray[i][0] = weightedYArray[i][0] * weight; - } - - var screenXArray = weightedXArray.concat(trailX); - var screenYArray = weightedYArray.concat(trailY); - var eyeFeatures = weightedEyeFeats.concat(trailFeat); - - var coefficientsX = util_regression.ridge(screenXArray, eyeFeatures, this.ridgeParameter); - var coefficientsY = util_regression.ridge(screenYArray, eyeFeatures, this.ridgeParameter); - - var eyeFeats = util.getEyeFeats(eyesObj); - var predictedX = 0; - for(var i=0; i< eyeFeats.length; i++){ - predictedX += eyeFeats[i] * coefficientsX[i]; - } - var predictedY = 0; - for(var i=0; i< eyeFeats.length; i++){ - predictedY += eyeFeats[i] * coefficientsY[i]; - } - - predictedX = Math.floor(predictedX); - predictedY = Math.floor(predictedY); - - if (params.applyKalmanFilter) { - // Update Kalman model, and get prediction - var newGaze = [predictedX, predictedY]; // [20200607 xk] Should we use a 1x4 vector? - newGaze = this.kalman.update(newGaze); - - return { - x: newGaze[0], - y: newGaze[1] - }; - } else { - return { - x: predictedX, - y: predictedY - }; - } -}; - -reg.RidgeWeightedReg.prototype.setData = util_regression.setData; - -/** - * Return the data - * @returns {Array.|*} - */ -reg.RidgeWeightedReg.prototype.getData = function() { - return this.dataClicks.data; -}; - -/** - * The RidgeWeightedReg object name - * @type {string} - */ -reg.RidgeWeightedReg.prototype.name = 'ridgeWeighted'; - -export default reg; diff --git a/src/ridgeWeightedReg.ts b/src/ridgeWeightedReg.ts new file mode 100644 index 000000000..c2c892d5d --- /dev/null +++ b/src/ridgeWeightedReg.ts @@ -0,0 +1,80 @@ +import { Ridge, ridge } from './worker_scripts/regression'; +import type { Point } from './worker_scripts/util'; +import type { Matrix } from './worker_scripts/mat'; +import type { EyesData } from './facemesh'; + +export class RidgeWeightedReg extends Ridge { + /** + * The RidgeWeightedReg object name + */ + name = 'ridgeWeighted' as const; + + /** + * Try to predict coordinates from pupil data + * after apply linear regression on data set + * @param eyesObj - The current user eyes object + * @returns The predicted point or undefined + */ + predict = (eyesObj: EyesData | undefined): Point | undefined => { + if (!eyesObj || this.eyeFeaturesClicks.length === 0) return; + const acceptTime = performance.now() - this.trailTime; + const trailX: Matrix = []; + const trailY: Matrix = []; + const trailFeat: Matrix = []; + for (let i = 0; i < this.trailDataWindow; i++) { + if (this.trailTimes.get(i) > acceptTime) { + trailX.push(this.screenXTrailArray.get(i)); + trailY.push(this.screenYTrailArray.get(i)); + trailFeat.push(this.eyeFeaturesTrail.get(i)); + } + } + + const len = this.eyeFeaturesClicks.length; + const weightedEyeFeats: Matrix = []; + const weightedXArray: Matrix = []; + const weightedYArray: Matrix = []; + + let i = 0; + for (const eyeFeature of this.eyeFeaturesClicks) { + const weight = Math.sqrt(1 / (len - i)); + const weightedFeature = eyeFeature.map(val => val * weight); + weightedEyeFeats.push(weightedFeature); + + weightedXArray.push(this.screenXClicksArray.get(i).map(x => x * weight)); + weightedYArray.push(this.screenYClicksArray.get(i).map(y => y * weight)); + + i++; + } + + const screenXArray = weightedXArray.concat(trailX); + const screenYArray = weightedYArray.concat(trailY); + const eyeFeatures = weightedEyeFeats.concat(trailFeat); + + const coefficientsX = ridge(screenXArray, eyeFeatures, this.ridgeParameter); + const coefficientsY = ridge(screenYArray, eyeFeatures, this.ridgeParameter); + + const eyeFeats = eyesObj.grayscale; + let predictedX = 0; + for (let i = 0; i < eyeFeats.length; i++) { + predictedX += eyeFeats[i] * coefficientsX[i]; + } + let predictedY = 0; + for (let i = 0; i < eyeFeats.length; i++) { + predictedY += eyeFeats[i] * coefficientsY[i]; + } + + predictedX = Math.floor(predictedX); + predictedY = Math.floor(predictedY); + + if (this.useKalmanFilter) { + // Update Kalman model, and get prediction + const [x, y] = this.kalman.update([predictedX, predictedY]); + return { x, y }; + } else { + return { + x: predictedX, + y: predictedY + }; + } + }; +} diff --git a/src/ridgeWorker.mjs b/src/ridgeWorker.mjs deleted file mode 100644 index f931230d3..000000000 --- a/src/ridgeWorker.mjs +++ /dev/null @@ -1,135 +0,0 @@ -'use strict'; - -console.log('thread starting'); - -// Add src/util.mjs and src/mat.mjs to the same directory as your html file -importScripts('./worker_scripts/util.js', './worker_scripts/mat.js'); // [20200708] Figure out how to make all of this wrap up neatly -var ridgeParameter = Math.pow(10,-5); -var resizeWidth = 10; -var resizeHeight = 6; -var dataWindow = 700; -var trailDataWindow = 10; -var trainInterval = 500; - -var screenXClicksArray = new self.webgazer.util.DataWindow(dataWindow); -var screenYClicksArray = new self.webgazer.util.DataWindow(dataWindow); -var eyeFeaturesClicks = new self.webgazer.util.DataWindow(dataWindow); -var dataClicks = new self.webgazer.util.DataWindow(dataWindow); - -var screenXTrailArray = new self.webgazer.util.DataWindow(trailDataWindow); -var screenYTrailArray = new self.webgazer.util.DataWindow(trailDataWindow); -var eyeFeaturesTrail = new self.webgazer.util.DataWindow(trailDataWindow); -var dataTrail = new self.webgazer.util.DataWindow(trailDataWindow); - -/** - * Performs ridge regression, according to the Weka code. - * @param {Array} y - corresponds to screen coordinates (either x or y) for each of n click events - * @param {Array.>} X - corresponds to gray pixel features (120 pixels for both eyes) for each of n clicks - * @param {Array} k - ridge parameter - * @return{Array} regression coefficients - */ -function ridge(y, X, k){ - var nc = X[0].length; - var m_Coefficients = new Array(nc); - var xt = self.webgazer.mat.transpose(X); - var solution = new Array(); - var success = true; - do{ - var ss = self.webgazer.mat.mult(xt,X); - // Set ridge regression adjustment - for (var i = 0; i < nc; i++) { - ss[i][i] = ss[i][i] + k; - } - - // Carry out the regression - var bb = self.webgazer.mat.mult(xt,y); - for(var i = 0; i < nc; i++) { - m_Coefficients[i] = bb[i][0]; - } - try{ - var n = (m_Coefficients.length !== 0 ? m_Coefficients.length/m_Coefficients.length: 0); - if (m_Coefficients.length*n !== m_Coefficients.length){ - console.log('Array length must be a multiple of m') - } - solution = (ss.length === ss[0].length ? (self.webgazer.mat.LUDecomposition(ss,bb)) : (self.webgazer.mat.QRDecomposition(ss,bb))); - - for (var i = 0; i < nc; i++){ - m_Coefficients[i] = solution[i][0]; - } - success = true; - } - catch (ex){ - k *= 10; - console.log(ex); - success = false; - } - } while (!success); - return m_Coefficients; -} - -//TODO: still usefull ??? -/** - * - * @returns {Number} - */ -function getCurrentFixationIndex() { - var index = 0; - var recentX = this.screenXTrailArray.get(0); - var recentY = this.screenYTrailArray.get(0); - for (var i = this.screenXTrailArray.length - 1; i >= 0; i--) { - var currX = this.screenXTrailArray.get(i); - var currY = this.screenYTrailArray.get(i); - var euclideanDistance = Math.sqrt(Math.pow((currX-recentX),2)+Math.pow((currY-recentY),2)); - if (euclideanDistance > 72){ - return i+1; - } - } - return i; -} - -/** - * Event handler, it store screen position to allow training - * @param {Event} event - the receive event - */ -self.onmessage = function(event) { - var data = event.data; - var screenPos = data['screenPos']; - var eyes = data['eyes']; - var type = data['type']; - if (type === 'click') { - self.screenXClicksArray.push([screenPos[0]]); - self.screenYClicksArray.push([screenPos[1]]); - - self.eyeFeaturesClicks.push(eyes); - } else if (type === 'move') { - self.screenXTrailArray.push([screenPos[0]]); - self.screenYTrailArray.push([screenPos[1]]); - - self.eyeFeaturesTrail.push(eyes); - self.dataTrail.push({'eyes':eyes, 'screenPos':screenPos, 'type':type}); - } - self.needsTraining = true; -}; - -/** - * Compute coefficient from training data - */ -function retrain() { - if (self.screenXClicksArray.length === 0) { - return; - } - if (!self.needsTraining) { - return; - } - var screenXArray = self.screenXClicksArray.data.concat(self.screenXTrailArray.data); - var screenYArray = self.screenYClicksArray.data.concat(self.screenYTrailArray.data); - var eyeFeatures = self.eyeFeaturesClicks.data.concat(self.eyeFeaturesTrail.data); - - var coefficientsX = ridge(screenXArray, eyeFeatures, ridgeParameter); - var coefficientsY = ridge(screenYArray, eyeFeatures, ridgeParameter); - self.postMessage({'X':coefficientsX, 'Y': coefficientsY}); - self.needsTraining = false; -} - -setInterval(retrain, trainInterval); - diff --git a/src/ridgeWorker.worker.ts b/src/ridgeWorker.worker.ts new file mode 100644 index 000000000..04e9c26dd --- /dev/null +++ b/src/ridgeWorker.worker.ts @@ -0,0 +1,70 @@ +'use strict'; + +import { ridge } from './worker_scripts/regression'; +import { DataWindow } from './worker_scripts/util'; + +console.log('thread starting'); + +const DATA_WINDOW_SIZE = 700; +const TRAIL_DATA_WINDOW_SIZE = 10; +const RIDGE_PARAMETER = Math.pow(10, -5); +const TRAIL_TIME = 1000; + +let needsTraining = false; + +const screenXClicksArray = new DataWindow(DATA_WINDOW_SIZE); +const screenYClicksArray = new DataWindow(DATA_WINDOW_SIZE); +const eyeFeaturesClicks = new DataWindow(DATA_WINDOW_SIZE); +const screenXTrailArray = new DataWindow(TRAIL_DATA_WINDOW_SIZE); +const screenYTrailArray = new DataWindow(TRAIL_DATA_WINDOW_SIZE); +const eyeFeaturesTrail = new DataWindow(TRAIL_DATA_WINDOW_SIZE); +const dataTrail = new DataWindow<{ eyes: number[], screenPos: number[], type: 'move' }>(TRAIL_DATA_WINDOW_SIZE); + +export type EyeData = Omit & { + eyes: number[]; + type: 'click' | 'move'; +}; + +/** + * Event handler, it store screen position to allow training + * @param {MessageEvent} event - the receive event + */ +self.onmessage = function (event: MessageEvent) { + const data = event.data; + + // Handle regular data messages + const [x, y] = data.screenPos; + const eyes = data.eyes; + if (data.type === 'click') { + screenXClicksArray.push([x]); + screenYClicksArray.push([y]); + + eyeFeaturesClicks.push(eyes); + } else if (data.type === 'move') { + screenXTrailArray.push([x]); + screenYTrailArray.push([y]); + + eyeFeaturesTrail.push(eyes); + dataTrail.push({ eyes, screenPos: data.screenPos, type: 'move' }); + } + needsTraining = true; +}; + +/** + * Compute coefficient from training data + */ +const retrain = (): void => { + if (screenXClicksArray.length === 0) return; + if (!needsTraining) return; + const screenXArray = screenXClicksArray.data.concat(screenXTrailArray.data); + const screenYArray = screenYClicksArray.data.concat(screenYTrailArray.data); + const eyeFeatures = eyeFeaturesClicks.data.concat(eyeFeaturesTrail.data); + + const coefficientsX = ridge(screenXArray, eyeFeatures, RIDGE_PARAMETER); + const coefficientsY = ridge(screenYArray, eyeFeatures, RIDGE_PARAMETER); + if (coefficientsX.find(isNaN)) console.log(coefficientsX, coefficientsY, screenXArray, screenYArray, eyeFeatures); + self.postMessage({ X: coefficientsX, Y: coefficientsY } as import('./ridgeRegThreaded.js').Weights); + needsTraining = false; +}; + +setInterval(retrain, TRAIL_TIME); diff --git a/src/util.mjs b/src/util.mjs deleted file mode 100644 index 18bbdaecb..000000000 --- a/src/util.mjs +++ /dev/null @@ -1,363 +0,0 @@ -import mat from './mat.mjs'; -import params from './params.mjs'; - -const util = {}; - - -var resizeWidth = 10; -var resizeHeight = 6; - -//not used !? -/** - * Eye class, represents an eye patch detected in the video stream - * @param {ImageData} patch - the image data corresponding to an eye - * @param {Number} imagex - x-axis offset from the top-left corner of the video canvas - * @param {Number} imagey - y-axis offset from the top-left corner of the video canvas - * @param {Number} width - width of the eye patch - * @param {Number} height - height of the eye patch - */ -util.Eye = function(patch, imagex, imagey, width, height) { - this.patch = patch; - this.imagex = imagex; - this.imagey = imagey; - this.width = width; - this.height = height; -}; - -/** - * Compute eyes size as gray histogram - * @param {Object} eyes - The eyes where looking for gray histogram - * @returns {Array.} The eyes gray level histogram - */ -util.getEyeFeats = function(eyes) { - let process = (eye) => { - let resized = this.resizeEye(eye, resizeWidth, resizeHeight); - let gray = this.grayscale(resized.data, resized.width, resized.height); - let hist = []; - this.equalizeHistogram(gray, 5, hist); - return hist; - }; - - if (webgazer.params.trackEye == 'left') { - return process(eyes.left); - } - else if (webgazer.params.trackEye == 'right') { - return process(eyes.right); - } - else { - return [].concat(process(eyes.left), process(eyes.right)); - } -} - -//Data Window class -//operates like an array but 'wraps' data around to keep the array at a fixed windowSize -/** - * DataWindow class - Operates like an array, but 'wraps' data around to keep the array at a fixed windowSize - * @param {Number} windowSize - defines the maximum size of the window - * @param {Array} data - optional data to seed the DataWindow with - **/ -util.DataWindow = function(windowSize, data) { - this.data = []; - this.windowSize = windowSize; - this.index = 0; - this.length = 0; - if(data){ - this.data = data.slice(data.length-windowSize,data.length); - this.length = this.data.length; - } -}; - -/** - * [push description] - * @param {*} entry - item to be inserted. It either grows the DataWindow or replaces the oldest item - * @return {DataWindow} this - */ -util.DataWindow.prototype.push = function(entry) { - if (this.data.length < this.windowSize) { - this.data.push(entry); - this.length = this.data.length; - return this; - } - - //replace oldest entry by wrapping around the DataWindow - this.data[this.index] = entry; - this.index = (this.index + 1) % this.windowSize; - return this; -}; - -/** - * Get the element at the ind position by wrapping around the DataWindow - * @param {Number} ind index of desired entry - * @return {*} - */ -util.DataWindow.prototype.get = function(ind) { - return this.data[this.getTrueIndex(ind)]; -}; - -/** - * Gets the true this.data array index given an index for a desired element - * @param {Number} ind - index of desired entry - * @return {Number} index of desired entry in this.data - */ -util.DataWindow.prototype.getTrueIndex = function(ind) { - if (this.data.length < this.windowSize) { - return ind; - } else { - //wrap around ind so that we can traverse from oldest to newest - return (ind + this.index) % this.windowSize; - } -}; - -/** - * Append all the contents of data - * @param {Array} data - to be inserted - */ -util.DataWindow.prototype.addAll = function(data) { - for (var i = 0; i < data.length; i++) { - this.push(data[i]); - } -}; - - -//Helper functions -/** - * Grayscales an image patch. Can be used for the whole canvas, detected face, detected eye, etc. - * - * Code from tracking.js by Eduardo Lundgren, et al. - * https://github.com/eduardolundgren/tracking.js/blob/master/src/tracking.js - * - * Software License Agreement (BSD License) Copyright (c) 2014, Eduardo A. Lundgren Melo. All rights reserved. - * Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * The name of Eduardo A. Lundgren Melo may not be used to endorse or promote products derived from this software without specific prior written permission of Eduardo A. Lundgren Melo. - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * @param {Array} pixels - image data to be grayscaled - * @param {Number} width - width of image data to be grayscaled - * @param {Number} height - height of image data to be grayscaled - * @return {Array} grayscaledImage - */ -util.grayscale = function(pixels, width, height){ - var gray = new Uint8ClampedArray(pixels.length >> 2); - var p = 0; - var w = 0; - for (var i = 0; i < height; i++) { - for (var j = 0; j < width; j++) { - var value = pixels[w] * 0.299 + pixels[w + 1] * 0.587 + pixels[w + 2] * 0.114; - gray[p++] = value; - - w += 4; - } - } - return gray; -}; - -/** - * Increase contrast of an image. - * - * Code from Martin Tschirsich, Copyright (c) 2012. - * https://github.com/mtschirs/js-objectdetect/blob/gh-pages/js/objectdetect.js - * - * @param {Array} src - grayscale integer array - * @param {Number} step - sampling rate, control performance - * @param {Array} dst - array to hold the resulting image - */ -util.equalizeHistogram = function(src, step, dst) { - var srcLength = src.length; - if (!dst) dst = src; - if (!step) step = 5; - - // Compute histogram and histogram sum: - var hist = Array(256).fill(0); - - for (var i = 0; i < srcLength; i += step) { - ++hist[src[i]]; - } - - // Compute integral histogram: - var norm = 255 * step / srcLength, - prev = 0; - for (var i = 0; i < 256; ++i) { - var h = hist[i]; - prev = h += prev; - hist[i] = h * norm; // For non-integer src: ~~(h * norm + 0.5); - } - - // Equalize image: - for (var i = 0; i < srcLength; ++i) { - dst[i] = hist[src[i]]; - } - return dst; -}; - -//not used !? -util.threshold = function(data, threshold) { - for (let i = 0; i < data.length; i++) { - data[i] = (data[i] > threshold) ? 255 : 0; - } - return data; -}; - -//not used !? -util.correlation = function(data1, data2) { - const length = Math.min(data1.length, data2.length); - let count = 0; - for (let i = 0; i < length; i++) { - if (data1[i] === data2[i]) { - count++; - } - } - return count / Math.max(data1.length, data2.length); -}; - -/** - * Gets an Eye object and resizes it to the desired resolution - * @param {webgazer.util.Eye} eye - patch to be resized - * @param {Number} resizeWidth - desired width - * @param {Number} resizeHeight - desired height - * @return {webgazer.util.Eye} resized eye patch - */ -util.resizeEye = function(eye, resizeWidth, resizeHeight) { - - var canvas = document.createElement('canvas'); - canvas.width = eye.width; - canvas.height = eye.height; - - canvas.getContext('2d').putImageData(eye.patch,0,0); - - var tempCanvas = document.createElement('canvas'); - - tempCanvas.width = resizeWidth; - tempCanvas.height = resizeHeight; - - // save the canvas into temp canvas - tempCanvas.getContext('2d').drawImage(canvas, 0, 0, canvas.width, canvas.height, 0, 0, resizeWidth, resizeHeight); - - return tempCanvas.getContext('2d').getImageData(0, 0, resizeWidth, resizeHeight); -}; - -/** - * Checks if the prediction is within the boundaries of the viewport and constrains it - * @param {Array} prediction [x,y] - predicted gaze coordinates - * @return {Array} constrained coordinates - */ -util.bound = function(prediction){ - if(prediction.x < 0) - prediction.x = 0; - if(prediction.y < 0) - prediction.y = 0; - var w = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); - var h = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); - if(prediction.x > w){ - prediction.x = w; - } - - if(prediction.y > h) - { - prediction.y = h; - } - return prediction; -}; - -//not used !? -/** - * Write statistics in debug paragraph panel - * @param {HTMLElement} para - The

tag where write data - * @param {Object} stats - The stats data to output - */ -function debugBoxWrite(para, stats) { - var str = ''; - for (var key in stats) { - str += key + ': ' + stats[key] + '\n'; - } - para.innerText = str; -} - -//not used !? -/** - * Constructor of DebugBox object, - * it insert an paragraph inside a div to the body, in view to display debug data - * @param {Number} interval - The log interval - * @constructor - */ -util.DebugBox = function(interval) { - this.para = document.createElement('p'); - this.div = document.createElement('div'); - this.div.appendChild(this.para); - document.body.appendChild(this.div); - - this.buttons = {}; - this.canvas = {}; - this.stats = {}; - var updateInterval = interval || 300; - (function(localThis) { - setInterval(function() { - debugBoxWrite(localThis.para, localThis.stats); - }, updateInterval); - }(this)); -}; - -//not used !? -/** - * Add stat data for log - * @param {String} key - The data key - * @param {*} value - The value - */ -util.DebugBox.prototype.set = function(key, value) { - this.stats[key] = value; -}; - -//not used !? -/** - * Initialize stats in case where key does not exist, else - * increment value for key - * @param {String} key - The key to process - * @param {Number} incBy - Value to increment for given key (default: 1) - * @param {Number} init - Initial value in case where key does not exist (default: 0) - */ -util.DebugBox.prototype.inc = function(key, incBy, init) { - if (!this.stats[key]) { - this.stats[key] = init || 0; - } - this.stats[key] += incBy || 1; -}; - -//not used !? -/** - * Create a button and register the given function to the button click event - * @param {String} name - The button name to link - * @param {Function} func - The onClick callback - */ -util.DebugBox.prototype.addButton = function(name, func) { - if (!this.buttons[name]) { - this.buttons[name] = document.createElement('button'); - this.div.appendChild(this.buttons[name]); - } - var button = this.buttons[name]; - this.buttons[name] = button; - button.addEventListener('click', func); - button.innerText = name; -}; - -//not used !? -/** - * Search for a canvas elemenet with name, or create on if not exist. - * Then send the canvas element as callback parameter. - * @param {String} name - The canvas name to send/create - * @param {Function} func - The callback function where send canvas - */ -util.DebugBox.prototype.show = function(name, func) { - if (!this.canvas[name]) { - this.canvas[name] = document.createElement('canvas'); - this.div.appendChild(this.canvas[name]); - } - var canvas = this.canvas[name]; - canvas.getContext('2d').clearRect(0,0, canvas.width, canvas.height); - func(canvas); -}; - -export default util; diff --git a/src/util_regression.mjs b/src/util_regression.mjs deleted file mode 100644 index 54f63b792..000000000 --- a/src/util_regression.mjs +++ /dev/null @@ -1,241 +0,0 @@ -import util from './util.mjs'; -import mat from './mat.mjs'; -import params from './params.mjs'; - -const util_regression = {}; - - -/** - * Initialize new arrays and initialize Kalman filter for regressions. - */ -util_regression.InitRegression = function() { - var dataWindow = 700; - var trailDataWindow = 10; - this.ridgeParameter = Math.pow(10,-5); - this.errorXArray = new util.DataWindow(dataWindow); - this.errorYArray = new util.DataWindow(dataWindow); - - - this.screenXClicksArray = new util.DataWindow(dataWindow); - this.screenYClicksArray = new util.DataWindow(dataWindow); - this.eyeFeaturesClicks = new util.DataWindow(dataWindow); - - //sets to one second worth of cursor trail - this.trailTime = 1000; - this.trailDataWindow = this.trailTime / params.moveTickSize; - this.screenXTrailArray = new util.DataWindow(trailDataWindow); - this.screenYTrailArray = new util.DataWindow(trailDataWindow); - this.eyeFeaturesTrail = new util.DataWindow(trailDataWindow); - this.trailTimes = new util.DataWindow(trailDataWindow); - - this.dataClicks = new util.DataWindow(dataWindow); - this.dataTrail = new util.DataWindow(trailDataWindow); - - // Initialize Kalman filter [20200608 xk] what do we do about parameters? - // [20200611 xk] unsure what to do w.r.t. dimensionality of these matrices. So far at least - // by my own anecdotal observation a 4x1 x vector seems to work alright - var F = [ [1, 0, 1, 0], - [0, 1, 0, 1], - [0, 0, 1, 0], - [0, 0, 0, 1]]; - - //Parameters Q and R may require some fine tuning - var Q = [ [1/4, 0, 1/2, 0], - [0, 1/4, 0, 1/2], - [1/2, 0, 1, 0], - [0, 1/2, 0, 1]];// * delta_t - var delta_t = 1/10; // The amount of time between frames - Q = mat.multScalar(Q, delta_t); - - var H = [ [1, 0, 0, 0, 0, 0], - [0, 1, 0, 0, 0, 0], - [0, 0, 1, 0, 0, 0], - [0, 0, 0, 1, 0, 0]]; - var H = [ [1, 0, 0, 0], - [0, 1, 0, 0]]; - var pixel_error = 47; //We will need to fine tune this value [20200611 xk] I just put a random value here - - //This matrix represents the expected measurement error - var R = mat.multScalar(mat.identity(2), pixel_error); - - var P_initial = mat.multScalar(mat.identity(4), 0.0001); //Initial covariance matrix - var x_initial = [[500], [500], [0], [0]]; // Initial measurement matrix - - this.kalman = new util_regression.KalmanFilter(F, H, Q, R, P_initial, x_initial); -} - -/** - * Kalman Filter constructor - * Kalman filters work by reducing the amount of noise in a models. - * https://blog.cordiner.net/2011/05/03/object-tracking-using-a-kalman-filter-matlab/ - * - * @param {Array.>} F - transition matrix - * @param {Array.>} Q - process noise matrix - * @param {Array.>} H - maps between measurement vector and noise matrix - * @param {Array.>} R - defines measurement error of the device - * @param {Array} P_initial - the initial state - * @param {Array} X_initial - the initial state of the device - */ -util_regression.KalmanFilter = function(F, H, Q, R, P_initial, X_initial) { - this.F = F; // State transition matrix - this.Q = Q; // Process noise matrix - this.H = H; // Transformation matrix - this.R = R; // Measurement Noise - this.P = P_initial; //Initial covariance matrix - this.X = X_initial; //Initial guess of measurement -}; - -/** - * Get Kalman next filtered value and update the internal state - * @param {Array} z - the new measurement - * @return {Array} - */ -util_regression.KalmanFilter.prototype.update = function(z) { - // Here, we define all the different matrix operations we will need - var { - add, sub, mult, inv, identity, transpose, - } = mat; - //TODO cache variables like the transpose of H - - // prediction: X = F * X | P = F * P * F' + Q - var X_p = mult(this.F, this.X); //Update state vector - var P_p = add(mult(mult(this.F,this.P), transpose(this.F)), this.Q); //Predicted covaraince - - //Calculate the update values - z = transpose([z]) - var y = sub(z, mult(this.H, X_p)); // This is the measurement error (between what we expect and the actual value) - var S = add(mult(mult(this.H, P_p), transpose(this.H)), this.R); //This is the residual covariance (the error in the covariance) - - // kalman multiplier: K = P * H' * (H * P * H' + R)^-1 - var K = mult(P_p, mult(transpose(this.H), inv(S))); //This is the Optimal Kalman Gain - - //We need to change Y into it's column vector form - for(var i = 0; i < y.length; i++){ - y[i] = [y[i]]; - } - - //Now we correct the internal values of the model - // correction: X = X + K * (m - H * X) | P = (I - K * H) * P - this.X = add(X_p, mult(K, y)); - this.P = mult(sub(identity(K.length), mult(K,this.H)), P_p); - return transpose(mult(this.H, this.X))[0]; //Transforms the predicted state back into it's measurement form -}; - -/** - * Performs ridge regression, according to the Weka code. - * @param {Array} y - corresponds to screen coordinates (either x or y) for each of n click events - * @param {Array.>} X - corresponds to gray pixel features (120 pixels for both eyes) for each of n clicks - * @param {Array} k - ridge parameter - * @return{Array} regression coefficients - */ -util_regression.ridge = function(y, X, k){ - var nc = X[0].length; - var m_Coefficients = new Array(nc); - var xt = mat.transpose(X); - var solution = new Array(); - var success = true; - do{ - var ss = mat.mult(xt,X); - // Set ridge regression adjustment - for (var i = 0; i < nc; i++) { - ss[i][i] = ss[i][i] + k; - } - - // Carry out the regression - var bb = mat.mult(xt,y); - for(var i = 0; i < nc; i++) { - m_Coefficients[i] = bb[i][0]; - } - try{ - var n = (m_Coefficients.length !== 0 ? m_Coefficients.length/m_Coefficients.length: 0); - if (m_Coefficients.length*n !== m_Coefficients.length){ - console.log('Array length must be a multiple of m') - } - solution = mat.solve(ss, bb); - - for (var i = 0; i < nc; i++){ - m_Coefficients[i] = solution[i]; - } - success = true; - } - catch (ex){ - k *= 10; - console.log(ex); - success = false; - } - } while (!success); - return m_Coefficients; -} - -/** - * Add given data to current data set then, - * replace current data member with given data - * @param {Array.} data - The data to set - */ -util_regression.setData = function(data) { - for (var i = 0; i < data.length; i++) { - // Clone data array - var leftData = new Uint8ClampedArray(data[i].eyes.left.patch.data); - var rightData = new Uint8ClampedArray(data[i].eyes.right.patch.data); - // Duplicate ImageData object - data[i].eyes.left.patch = new ImageData(leftData, data[i].eyes.left.width, data[i].eyes.left.height); - data[i].eyes.right.patch = new ImageData(rightData, data[i].eyes.right.width, data[i].eyes.right.height); - - // Add those data objects to model - this.addData(data[i].eyes, data[i].screenPos, data[i].type); - } -}; - - -//not used ?! -//TODO: still usefull ??? -/** - * - * @returns {Number} - */ -util_regression.getCurrentFixationIndex = function() { - var index = 0; - var recentX = this.screenXTrailArray.get(0); - var recentY = this.screenYTrailArray.get(0); - for (var i = this.screenXTrailArray.length - 1; i >= 0; i--) { - var currX = this.screenXTrailArray.get(i); - var currY = this.screenYTrailArray.get(i); - var euclideanDistance = Math.sqrt(Math.pow((currX-recentX),2)+Math.pow((currY-recentY),2)); - if (euclideanDistance > 72){ - return i+1; - } - } - return i; -} - -util_regression.addData = function(eyes, screenPos, type) { - if (!eyes) { - return; - } - //not doing anything with blink at present - // if (eyes.left.blink || eyes.right.blink) { - // return; - // } - if (type === 'click') { - this.screenXClicksArray.push([screenPos[0]]); - this.screenYClicksArray.push([screenPos[1]]); - this.eyeFeaturesClicks.push(util.getEyeFeats(eyes)); - this.dataClicks.push({'eyes':eyes, 'screenPos':screenPos, 'type':type}); - } else if (type === 'move') { - this.screenXTrailArray.push([screenPos[0]]); - this.screenYTrailArray.push([screenPos[1]]); - - this.eyeFeaturesTrail.push(util.getEyeFeats(eyes)); - this.trailTimes.push(performance.now()); - this.dataTrail.push({'eyes':eyes, 'screenPos':screenPos, 'type':type}); - } - - // [20180730 JT] Why do we do this? It doesn't return anything... - // But as JS is pass by reference, it still affects it. - // - // Causes problems for when we want to call 'addData' twice in a row on the same object, but perhaps with different screenPos or types (think multiple interactions within one video frame) - //eyes.left.patch = Array.from(eyes.left.patch.data); - //eyes.right.patch = Array.from(eyes.right.patch.data); -}; - -export default util_regression; diff --git a/src/worker_scripts/mat.js b/src/worker_scripts/mat.js deleted file mode 100644 index 7b899bc0b..000000000 --- a/src/worker_scripts/mat.js +++ /dev/null @@ -1,435 +0,0 @@ -(function() { - 'use strict'; - - /** - * Matrix operations, mostly based on WEKA - * @see https://github.com/Waikato/weka-3.8/blob/master/weka/src/main/java/weka/core/matrix/Matrix.java - */ - - /** - * @callback operationCallback - * @param {number} a - an element of matrix A - * @param {number} b - an element of matrix B - * @return {number} a ○ b - */ - /** - * Apply arithmetic operations to every element of A and B: - * X = A ○ B, where ○ can be one of +, -, *, /, etc. - * - * @param {Array.>} A - * @param {Array.>} B - * @param {operationCallback} op - operation to apply, op(a, b) => a ○ b - * @return {Array.>} A ○ B - */ - function applyArithmeticOperation(A, B, op) { - if (A.length !== B.length || A[0].length !== B[0].length) { - throw new Error('Matrix dimensions must agree.'); - } - - const rows = A.length; - const cols = A[0].length; - - const X = new Array(rows); - - for (let i = 0; i < rows; i++) { - X[i] = new Array(cols); - - for (let j = 0; j < cols; j++) { - X[i][j] = op(A[i][j], B[i][j]); - } - } - - return X; - } - - const mat = { - /** - * Transposes an m*n array - * @param {Array.>} matrix - of 'M x N' dimensionality - * @return {Array.>} transposed matrix - */ - transpose(matrix) { - const rows = matrix.length; - const cols = matrix[0].length; - const transposedMatrix = new Array(cols); - - for (let j = 0; j < cols; j++) { - transposedMatrix[j] = new Array(rows); - - for (let i = 0; i < rows; i++) { - transposedMatrix[j][i] = matrix[i][j]; - } - } - - return transposedMatrix; - }, - - /** - * Get a sub-matrix of matrix - * @param {Array.>} matrix - original matrix - * @param {Array.} r - Array of row indices - * @param {Number} j0 - Initial column index - * @param {Number} j1 - Final column index - * @returns {Array} The sub-matrix matrix(r(:),j0:j1) - */ - getMatrix(matrix, r, j0, j1) { - const X = new Array(r.length); - const m = j1 - j0 + 1; - - for (let i = 0, len = r.length; i < len; i++) { - X[i] = new Array(m); - - for (let j = j0; j <= j1; j++) { - X[i][j - j0] = matrix[r[i]][j]; - } - } - - return X; - }, - - /** - * Get a submatrix of matrix - * @param {Array.>} matrix - original matrix - * @param {Number} i0 - Initial row index - * @param {Number} i1 - Final row index - * @param {Number} j0 - Initial column index - * @param {Number} j1 - Final column index - * @return {Array} The sub-matrix matrix(i0:i1,j0:j1) - */ - getSubMatrix(matrix, i0, i1, j0, j1) { - const size = j1 - j0 + 1; - const X = new Array(i1 - i0 + 1); - - for (let i = i0; i <= i1; i++) { - const subI = i - i0; - X[subI] = new Array(size); - - for (let j = j0; j <= j1; j++) { - X[subI][j - j0] = matrix[i][j]; - } - } - - return X; - }, - - /** - * Linear algebraic matrix multiplication, X = A * B - * @param {Array.>} A - * @param {Array.>} B - * @return {Array.>} Matrix product, A * B - */ - mult(matrix1, matrix2) { - if (matrix2.length != matrix1[0].length){ - console.log('Matrix inner dimensions must agree:'); - } - - var X = new Array(matrix1.length), - Bcolj = new Array(matrix1[0].length); - - for (var j = 0; j < matrix2[0].length; j++){ - for (var k = 0; k < matrix1[0].length; k++){ - Bcolj[k] = matrix2[k][j]; - } - for (var i = 0; i < matrix1.length; i++){ - - if (j === 0) - X[i] = new Array(matrix2[0].length); - - var Arowi = matrix1[i]; - var s = 0; - for (var k = 0; k < matrix1[0].length; k++){ - s += Arowi[k]*Bcolj[k]; - } - X[i][j] = s; - } - } - return X; - }, - - /** - * Multiply a matrix by a scalar, X = s*A - * @param {Array.>} A - matrix - * @param {Number} s - scalar - * @return {Array.>} s*A - */ - multScalar(A, s) { - const rows = A.length; - const cols = A[0].length; - - const X = new Array(rows); - - for (let i = 0; i < rows; i++) { - X[i] = new Array(cols); - - for (let j = 0; j < cols; j++) { - X[i][j] = A[i][j] * s; - } - } - - return X; - }, - - /** - * Linear algebraic matrix addition, X = A * B - * @param {Array.>} A - * @param {Array.>} B - * @return {Array.>} A + B - */ - add(A, B) { - return applyArithmeticOperation(A, B, (a, b) => a + b); - }, - - /** - * Linear algebraic matrix subtraction, X = A - B - * @param {Array.>} A - * @param {Array.>} B - * @return {Array.>} A - B - */ - sub(A, B) { - return applyArithmeticOperation(A, B, (a, b) => a - b); - }, - - /** - * Matrix inverse or pseudoinverse, based on WEKA code - * @param {Array.>} A - original matrix - * @return inverse(A) if A is square, pseudoinverse otherwise. - */ - inv(A) { - return mat.solve(A, mat.identity(A.length, A[0].length)); - }, - - /** - * Generate identity matrix, based on WEKA code - * @param {Number} m - number of rows. - * @param {Number} [n] - number of colums, n = m if undefined. - * @return {Array.>} An m * n matrix with ones on the diagonal and zeros elsewhere. - */ - identity(m, n = m) { - const X = new Array(m); - - for (let i = 0; i < m; i++) { - X[i] = new Array(n); - - for (let j = 0; j < n; j++) { - X[i][j] = (i === j ? 1.0 : 0.0); - } - } - - return X; - }, - - /** - * Solve A*X = B, based on WEKA code - * @param {Array.>} A - left matrix of equation to be solved - * @param {Array.>} B - right matrix of equation to be solved - * @return {Array.>} solution if A is square, least squares solution otherwiseis - */ - solve(A, B) { - if (A.length === A[0].length) { - // A is square - return mat.LUDecomposition(A, B); - } - - return mat.QRDecomposition(A, B); - }, - - /** - * LUDecomposition to solve A*X = B, based on WEKA code - * @param {Array.>} A - left matrix of equation to be solved - * @param {Array.>} B - right matrix of equation to be solved - * @return {Array.>} X so that L*U*X = B(piv,:) - */ - LUDecomposition(A, B) { - var LU = new Array(A.length); - - for (var i = 0; i < A.length; i++){ - LU[i] = new Array(A[0].length); - for (var j = 0; j < A[0].length; j++){ - LU[i][j] = A[i][j]; - } - } - - var m = A.length; - var n = A[0].length; - var piv = new Array(m); - for (var i = 0; i < m; i++){ - piv[i] = i; - } - var pivsign = 1; - var LUrowi = new Array(); - var LUcolj = new Array(m); - // Outer loop. - for (var j = 0; j < n; j++){ - // Make a copy of the j-th column to localize references. - for (var i = 0; i < m; i++){ - LUcolj[i] = LU[i][j]; - } - // Apply previous transformations. - for (var i = 0; i < m; i++){ - LUrowi = LU[i]; - // Most of the time is spent in the following dot product. - var kmax = Math.min(i,j); - var s = 0; - for (var k = 0; k < kmax; k++){ - s += LUrowi[k]*LUcolj[k]; - } - LUrowi[j] = LUcolj[i] -= s; - } - // Find pivot and exchange if necessary. - var p = j; - for (var i = j+1; i < m; i++){ - if (Math.abs(LUcolj[i]) > Math.abs(LUcolj[p])){ - p = i; - } - } - if (p != j){ - for (var k = 0; k < n; k++){ - var t = LU[p][k]; - LU[p][k] = LU[j][k]; - LU[j][k] = t; - } - var k = piv[p]; - piv[p] = piv[j]; - piv[j] = k; - pivsign = -pivsign; - } - // Compute multipliers. - if (j < m & LU[j][j] != 0){ - for (var i = j+1; i < m; i++){ - LU[i][j] /= LU[j][j]; - } - } - } - if (B.length != m){ - console.log('Matrix row dimensions must agree.'); - } - for (var j = 0; j < n; j++){ - if (LU[j][j] === 0){ - console.log('Matrix is singular.') - } - } - var nx = B[0].length; - var X = mat.getMatrix(B,piv,0,nx-1); - // Solve L*Y = B(piv,:) - for (var k = 0; k < n; k++){ - for (var i = k+1; i < n; i++){ - for (var j = 0; j < nx; j++){ - X[i][j] -= X[k][j]*LU[i][k]; - } - } - } - // Solve U*X = Y; - for (var k = n-1; k >= 0; k--){ - for (var j = 0; j < nx; j++){ - X[k][j] /= LU[k][k]; - } - for (var i = 0; i < k; i++){ - for (var j = 0; j < nx; j++){ - X[i][j] -= X[k][j]*LU[i][k]; - } - } - } - return X; - }, - - /** - * Least squares solution of A*X = B, based on WEKA code - * @param {Array.>} A - left side matrix to be solved - * @param {Array.>} B - a matrix with as many rows as A and any number of columns. - * @return {Array.>} X - that minimizes the two norms of QR*X-B. - */ - QRDecomposition(A, B) { - // Initialize. - var QR = new Array(A.length); - - for (var i = 0; i < A.length; i++){ - QR[i] = new Array(A[0].length); - for (var j = 0; j < A[0].length; j++){ - QR[i][j] = A[i][j]; - } - } - var m = A.length; - var n = A[0].length; - var Rdiag = new Array(n); - var nrm; - - // Main loop. - for (var k = 0; k < n; k++){ - // Compute 2-norm of k-th column without under/overflow. - nrm = 0; - for (var i = k; i < m; i++){ - nrm = Math.hypot(nrm,QR[i][k]); - } - if (nrm != 0){ - // Form k-th Householder vector. - if (QR[k][k] < 0){ - nrm = -nrm; - } - for (var i = k; i < m; i++){ - QR[i][k] /= nrm; - } - QR[k][k] += 1; - - // Apply transformation to remaining columns. - for (var j = k+1; j < n; j++){ - var s = 0; - for (var i = k; i < m; i++){ - s += QR[i][k]*QR[i][j]; - } - s = -s/QR[k][k]; - for (var i = k; i < m; i++){ - QR[i][j] += s*QR[i][k]; - } - } - } - Rdiag[k] = -nrm; - } - if (B.length != m){ - console.log('Matrix row dimensions must agree.'); - } - for (var j = 0; j < n; j++){ - if (Rdiag[j] === 0) - console.log('Matrix is rank deficient'); - } - // Copy right hand side - var nx = B[0].length; - var X = new Array(B.length); - for(var i=0; i= 0; k--){ - for (var j = 0; j < nx; j++){ - X[k][j] /= Rdiag[k]; - } - for (var i = 0; i < k; i++){ - for (var j = 0; j < nx; j++){ - X[i][j] -= X[k][j]*QR[i][k]; - } - } - } - return mat.getSubMatrix(X,0,n-1,0,nx-1); - } - }; - - self.webgazer = self.webgazer || {}; - self.webgazer.mat = self.webgazer.mat || {}; -}()); diff --git a/src/worker_scripts/mat.ts b/src/worker_scripts/mat.ts new file mode 100644 index 000000000..5c9a95430 --- /dev/null +++ b/src/worker_scripts/mat.ts @@ -0,0 +1,373 @@ +/** + * Matrix operations, mostly based on WEKA + * @see https://github.com/Waikato/weka-3.8/blob/master/weka/src/main/java/weka/core/matrix/Matrix.java + */ + +// Equivalent to 0 in JavaScript decimal precision +const EPSILON = 1e-10; + +export type Matrix = number[][]; + +type OperationCallback = (a: number, b: number) => number; + +/** + * Apply arithmetic operations to every element of A and B: + * X = A ○ B, where ○ can be one of +, -, *, /, etc. + */ +const applyArithmeticOperation = (A: Matrix, B: Matrix, op: OperationCallback): Matrix => { + if (A.length !== B.length || A[0].length !== B[0].length) { + throw new Error('Matrix dimensions must agree.'); + } + + const rows = A.length; + const cols = A[0].length; + + const X: Matrix = new Array(rows); + + for (let i = 0; i < rows; i++) { + X[i] = new Array(cols); + + for (let j = 0; j < cols; j++) { + X[i][j] = op(A[i][j], B[i][j]); + } + } + + return X; +}; + +/** + * Transposes an m*n array + */ +export const transpose = (matrix: (number[] | Uint8ClampedArray)[]): Matrix => { + const rows = matrix.length; + const cols = matrix[0].length; + const transposedMatrix: Matrix = new Array(cols); + + for (let j = 0; j < cols; j++) { + transposedMatrix[j] = new Array(rows); + + for (let i = 0; i < rows; i++) { + transposedMatrix[j][i] = matrix[i][j]; + } + } + + return transposedMatrix; +}; + +/** + * Get a sub-matrix of matrix + */ +export const getMatrix = (matrix: Matrix, r: number[], j0: number, j1: number): Matrix => { + const X: Matrix = new Array(r.length); + const m = j1 - j0 + 1; + + for (let i = 0, len = r.length; i < len; i++) { + X[i] = new Array(m); + + for (let j = j0; j <= j1; j++) { + X[i][j - j0] = matrix[r[i]][j]; + } + } + + return X; +}; + +/** + * Get a submatrix of matrix + */ +export const getSubMatrix = (matrix: Matrix, i0: number, i1: number, j0: number, j1: number): Matrix => { + const size = j1 - j0 + 1; + const X: Matrix = new Array(i1 - i0 + 1); + + for (let i = i0; i <= i1; i++) { + const subI = i - i0; + X[subI] = new Array(size); + + for (let j = j0; j <= j1; j++) { + X[subI][j - j0] = matrix[i][j]; + } + } + + return X; +}; + +/** + * Linear algebraic matrix multiplication, X = A * B + */ +export const mult = (matrix1: (number[] | Uint8ClampedArray)[], matrix2: (number[] | Uint8ClampedArray)[]): Matrix => { + if (matrix2.length !== matrix1[0].length) { + throw new Error('Matrix inner dimensions must agree.'); + } + + const X: Matrix = new Array(matrix1.length); + const Bcolj: number[] = new Array(matrix1[0].length); + + for (let j = 0; j < matrix2[0].length; j++) { + for (let k = 0; k < matrix1[0].length; k++) { + Bcolj[k] = matrix2[k][j]; + } + for (let i = 0; i < matrix1.length; i++) { + if (j === 0) { + X[i] = new Array(matrix2[0].length); + } + + const Arowi = matrix1[i]; + let s = 0; + for (let k = 0; k < matrix1[0].length; k++) { + s += Arowi[k] * Bcolj[k]; + } + X[i][j] = s; + } + } + return X; +}; + +/** + * Multiply a matrix by a scalar, X = s*A + */ +export const multScalar = (A: Matrix, s: number): Matrix => { + const rows = A.length; + const cols = A[0].length; + + const X: Matrix = new Array(rows); + + for (let i = 0; i < rows; i++) { + X[i] = new Array(cols); + + for (let j = 0; j < cols; j++) { + X[i][j] = A[i][j] * s; + } + } + + return X; +}; + +/** + * Linear algebraic matrix addition, X = A * B + */ +export const add = (A: Matrix, B: Matrix): Matrix => applyArithmeticOperation(A, B, (a, b) => a + b); + +/** + * Linear algebraic matrix subtraction, X = A - B + */ +export const sub = (A: Matrix, B: Matrix): Matrix => applyArithmeticOperation(A, B, (a, b) => a - b); + +/** + * Matrix inverse or pseudoinverse, based on WEKA code + */ +export const inv = (A: Matrix): Matrix => solve(A, identity(A.length, A[0].length)); + +/** + * Generate identity matrix, based on WEKA code + */ +export const identity = (m: number, n: number = m): Matrix => { + const X: Matrix = new Array(m); + + for (let i = 0; i < m; i++) { + X[i] = new Array(n); + + for (let j = 0; j < n; j++) { + X[i][j] = (i === j ? 1.0 : 0.0); + } + } + + return X; +}; + +/** + * Solve A*X = B, based on WEKA code + */ +export const solve = (A: Matrix, B: Matrix): Matrix => + // Use LU if A is square + A.length === A[0].length ? LUDecomposition(A, B) : QRDecomposition(A, B); + +/** + * LUDecomposition to solve A*X = B, based on WEKA code + */ +const LUDecomposition = (A: Matrix, B: Matrix): Matrix => { + const LU: Matrix = new Array(A.length); + + for (let i = 0; i < A.length; i++) { + LU[i] = new Array(A[0].length); + for (let j = 0; j < A[0].length; j++) { + LU[i][j] = A[i][j]; + } + } + + const m = A.length; + const n = A[0].length; + const piv: number[] = new Array(m); + for (let i = 0; i < m; i++) { + piv[i] = i; + } + let pivsign = 1; + let LUrowi: number[]; + const LUcolj: number[] = new Array(m); + // Outer loop. + for (let j = 0; j < n; j++) { + // Make a copy of the j-th column to localize references. + for (let i = 0; i < m; i++) { + LUcolj[i] = LU[i][j]; + } + // Apply previous transformations. + for (let i = 0; i < m; i++) { + LUrowi = LU[i]; + // Most of the time is spent in the following dot product. + const kmax = Math.min(i, j); + let s = 0; + for (let k = 0; k < kmax; k++) { + s += LUrowi[k] * LUcolj[k]; + } + LUrowi[j] = LUcolj[i] -= s; + } + // Find pivot and exchange if necessary. + let p = j; + for (let i = j + 1; i < m; i++) { + if (Math.abs(LUcolj[i]) > Math.abs(LUcolj[p])) { + p = i; + } + } + if (p !== j) { + for (let k = 0; k < n; k++) { + const t = LU[p][k]; + LU[p][k] = LU[j][k]; + LU[j][k] = t; + } + const k = piv[p]; + piv[p] = piv[j]; + piv[j] = k; + pivsign = -pivsign; + } + // Compute multipliers. + if (j < m && LU[j][j] !== 0) { + for (let i = j + 1; i < m; i++) { + LU[i][j] /= LU[j][j]; + } + } + } + if (B.length !== m) { + console.log('Matrix row dimensions must agree.'); + } + for (let j = 0; j < n; j++) { + if (Math.abs(LU[j][j]) < EPSILON) { + throw new Error('Matrix is singular.'); + } + } + const nx = B[0].length; + const X = getMatrix(B, piv, 0, nx - 1); + // Solve L*Y = B(piv,:) + for (let k = 0; k < n; k++) { + for (let i = k + 1; i < n; i++) { + for (let j = 0; j < nx; j++) { + X[i][j] -= X[k][j] * LU[i][k]; + } + } + } + // Solve U*X = Y; + for (let k = n - 1; k >= 0; k--) { + for (let j = 0; j < nx; j++) { + X[k][j] /= LU[k][k]; + } + for (let i = 0; i < k; i++) { + for (let j = 0; j < nx; j++) { + X[i][j] -= X[k][j] * LU[i][k]; + } + } + } + return X; +}; + +/** + * Least squares solution of A*X = B, based on WEKA code + */ +const QRDecomposition = (A: Matrix, B: Matrix): Matrix => { + // Initialize. + const QR: Matrix = new Array(A.length); + + for (let i = 0; i < A.length; i++) { + QR[i] = new Array(A[0].length); + for (let j = 0; j < A[0].length; j++) { + QR[i][j] = A[i][j]; + } + } + const m = A.length; + const n = A[0].length; + const Rdiag: number[] = new Array(n); + let nrm: number; + + // Main loop. + for (let k = 0; k < n; k++) { + // Compute 2-norm of k-th column without under/overflow. + nrm = 0; + for (let i = k; i < m; i++) { + nrm = Math.hypot(nrm, QR[i][k]); + } + if (nrm !== 0) { + // Form k-th Householder vector. + if (QR[k][k] < 0) { + nrm = -nrm; + } + for (let i = k; i < m; i++) { + QR[i][k] /= nrm; + } + QR[k][k] += 1; + + // Apply transformation to remaining columns. + for (let j = k + 1; j < n; j++) { + let s = 0; + for (let i = k; i < m; i++) { + s += QR[i][k] * QR[i][j]; + } + s = -s / QR[k][k]; + for (let i = k; i < m; i++) { + QR[i][j] += s * QR[i][k]; + } + } + } + Rdiag[k] = -nrm; + } + if (B.length !== m) { + console.log('Matrix row dimensions must agree.'); + } + for (let j = 0; j < n; j++) { + if (Math.abs(Rdiag[j]) < EPSILON) { + throw new Error('Matrix is rank deficient'); + } + } + // Copy right hand side + const nx = B[0].length; + const X: Matrix = new Array(B.length); + for (let i = 0; i < B.length; i++) { + X[i] = new Array(B[0].length); + } + for (let i = 0; i < B.length; i++) { + for (let j = 0; j < B[0].length; j++) { + X[i][j] = B[i][j]; + } + } + // Compute Y = transpose(Q)*B + for (let k = 0; k < n; k++) { + for (let j = 0; j < nx; j++) { + let s = 0.0; + for (let i = k; i < m; i++) { + s += QR[i][k] * X[i][j]; + } + s = -s / QR[k][k]; + for (let i = k; i < m; i++) { + X[i][j] += s * QR[i][k]; + } + } + } + // Solve R*X = Y; + for (let k = n - 1; k >= 0; k--) { + for (let j = 0; j < nx; j++) { + X[k][j] /= Rdiag[k]; + } + for (let i = 0; i < k; i++) { + for (let j = 0; j < nx; j++) { + X[i][j] -= X[k][j] * QR[i][k]; + } + } + } + return getSubMatrix(X, 0, n - 1, 0, nx - 1); +}; diff --git a/src/worker_scripts/regression.ts b/src/worker_scripts/regression.ts new file mode 100644 index 000000000..35d832f14 --- /dev/null +++ b/src/worker_scripts/regression.ts @@ -0,0 +1,203 @@ +import { identity, Matrix, mult, multScalar, solve, transpose } from './mat'; +import { DataWindow, KalmanFilter, Point } from './util'; +import type { EyesData } from '../facemesh'; + +const DATA_WINDOW_SIZE = 700; +const TRAIL_DATA_WINDOW_SIZE = 10; +const RIDGE_PARAMETER = Math.pow(10, -5); +const TRAIL_TIME = 1000; +const KALMAN_PIXEL_ERROR = 47; +const KALMAN_INITIAL_COVARIANCE = 0.0001; +const KALMAN_INITIAL_X = 500; +const KALMAN_INITIAL_Y = 500; +const KALMAN_DELTA_T = 1 / 100; + +/** + * Performs ridge regression, according to the Weka code. + * @param {Matrix} y - corresponds to screen coordinates (either x or y) for each of n click events + * @param {Matrix} X - corresponds to gray pixel features (120 pixels for both eyes) for each of n clicks + * @param {number} k - ridge parameter + * @return {number[]} regression coefficients + */ +export const ridge = (y: Matrix, X: Matrix, k: number): number[] => { + const nc = X[0].length; + const mCoefficients: number[] = new Array(nc); + try { + const xt = transpose(X); + let solution: Matrix = []; + let success = true; + const result: number[] = []; + do { + const ss = mult(xt, X); + // Set ridge regression adjustment + for (let i = 0; i < nc; i++) { + ss[i][i] = ss[i][i] + k; + } + + // Carry out the regression + const bb = mult(xt, y); + for (let i = 0; i < nc; i++) { + mCoefficients[i] = bb[i][0]; + } + try { + const n = (mCoefficients.length !== 0 ? mCoefficients.length / mCoefficients.length : 0); + if (mCoefficients.length * n !== mCoefficients.length) { + console.log('Array length must be a multiple of m'); + } + solution = solve(ss, bb); + + for (let i = 0; i < nc; i++) { + // Should it be solution[i][i] or solution[i][0] + result[i] = solution[i][0]; + } + success = true; + } catch (ex) { + k *= 10; + console.log(ex); + success = false; + } + } while (!success); + return result; + } catch (e) { + console.log('X', X); + throw e; + } +}; + +export interface DataSet { + eyes: EyesData; + screenPos: [number, number]; + type: 'click' | 'move' | 'blink'; +} + +export interface RidgeOptions { + useKalmanFilter?: boolean; + moveTickSize?: number; +} + +export class Ridge { + name: 'ridgeReg' | 'ridgeWeighted' | 'ridgeThreaded' = 'ridgeReg'; + + ridgeParameter: number = RIDGE_PARAMETER; + trailTime: number = TRAIL_TIME; + trailDataWindow: number; + moveTickSize: number; + + screenXClicksArray: DataWindow = new DataWindow(DATA_WINDOW_SIZE); + screenYClicksArray: DataWindow = new DataWindow(DATA_WINDOW_SIZE); + eyeFeaturesClicks: DataWindow = new DataWindow(DATA_WINDOW_SIZE); + + // sets to one second worth of cursor trail + screenXTrailArray: DataWindow = new DataWindow(TRAIL_DATA_WINDOW_SIZE); + screenYTrailArray: DataWindow = new DataWindow(TRAIL_DATA_WINDOW_SIZE); + eyeFeaturesTrail: DataWindow = new DataWindow(TRAIL_DATA_WINDOW_SIZE); + trailTimes: DataWindow = new DataWindow(TRAIL_DATA_WINDOW_SIZE); + + dataClicks: DataWindow = new DataWindow(DATA_WINDOW_SIZE); + dataTrail: DataWindow = new DataWindow(TRAIL_DATA_WINDOW_SIZE); + + kalman: KalmanFilter; + useKalmanFilter: boolean; + + /** + * Constructor of RidgeReg object, + * this object allow to perform ridge regression + * @constructor + * @param {RidgeOptions} options + */ + constructor ({ useKalmanFilter = true, moveTickSize = 10 }: RidgeOptions = {}) { + this.useKalmanFilter = useKalmanFilter; + this.moveTickSize = moveTickSize; + this.trailDataWindow = this.trailTime / moveTickSize; + + // Initialize Kalman filter [20200608 xk] what do we do about parameters? + // [20200611 xk] unsure what to do w.r.t. dimensionality of these matrices. So far at least + // by my own anecdotal observation a 4x1 x vector seems to work alright + const F = [ + [1, 0, 1, 0], + [0, 1, 0, 1], + [0, 0, 1, 0], + [0, 0, 0, 1] + ]; + + // Parameters Q and R may require some fine tuning + let Q = [ + [1 / 4, 0, 1 / 2, 0], + [0, 1 / 4, 0, 1 / 2], + [1 / 2, 0, 1, 0], + [0, 1 / 2, 0, 1] + ]; + Q = multScalar(Q, KALMAN_DELTA_T); + + const H = [ + [1, 0, 0, 0], + [0, 1, 0, 0] + ]; + const pixelError = KALMAN_PIXEL_ERROR; + + // This matrix represents the expected measurement error + const R = multScalar(identity(2), pixelError); + + const PInitial = multScalar(identity(4), KALMAN_INITIAL_COVARIANCE); // Initial covariance matrix + const xInitial = [[KALMAN_INITIAL_X], [KALMAN_INITIAL_Y], [0], [0]]; // Initial measurement matrix + + this.kalman = new KalmanFilter(F, H, Q, R, PInitial, xInitial); + } + + /** + * Add given data to current data set then, + * replace current data member with given data + * @param {DataSet[]} data - The data to set + */ + setData = (data: DataSet[]): void => { + this.dataClicks.clear(); + this.dataTrail.clear(); + this.screenXClicksArray.clear(); + this.screenYClicksArray.clear(); + this.eyeFeaturesClicks.clear(); + this.screenXTrailArray.clear(); + this.screenYTrailArray.clear(); + this.eyeFeaturesTrail.clear(); + data.forEach(d => this.addData(d)); + }; + + /** + * Data to be added + * @param {DataSet} data + * @returns {void} + */ + addData = (data: DataSet): void => { + const { eyes, screenPos, type } = data; + if (!eyes) return; + + if (type === 'click') { + this.screenXClicksArray.push([screenPos[0]]); + this.screenYClicksArray.push([screenPos[1]]); + this.eyeFeaturesClicks.push(eyes.grayscale); + this.dataClicks.push({ eyes, screenPos, type }); + } else if (type === 'move') { + this.screenXTrailArray.push([screenPos[0]]); + this.screenYTrailArray.push([screenPos[1]]); + + this.eyeFeaturesTrail.push(eyes.grayscale); + this.trailTimes.push(performance.now()); + this.dataTrail.push({ eyes, screenPos, type }); + } + }; + + /** + * Return the data + * @returns {DataSet[]} + */ + getData = (): DataSet[] => this.dataClicks.data; + + dispose = (): void => {}; + + /** + * Try to predict coordinates from pupil data + * after apply linear regression on data set + * @param {EyesData | undefined} _eyesObj - The current user eyes object + * @returns {Point | undefined} + */ + predict = (_eyesObj: EyesData | undefined): Point | undefined => undefined; +} diff --git a/src/worker_scripts/util.js b/src/worker_scripts/util.js deleted file mode 100644 index 6aed117e4..000000000 --- a/src/worker_scripts/util.js +++ /dev/null @@ -1,399 +0,0 @@ -'use strict'; -(function() { - - self.webgazer = self.webgazer || {}; - self.webgazer.util = self.webgazer.util || {}; - self.webgazer.mat = self.webgazer.mat || {}; - - /** - * Eye class, represents an eye patch detected in the video stream - * @param {ImageData} patch - the image data corresponding to an eye - * @param {Number} imagex - x-axis offset from the top-left corner of the video canvas - * @param {Number} imagey - y-axis offset from the top-left corner of the video canvas - * @param {Number} width - width of the eye patch - * @param {Number} height - height of the eye patch - */ - self.webgazer.util.Eye = function(patch, imagex, imagey, width, height) { - this.patch = patch; - this.imagex = imagex; - this.imagey = imagey; - this.width = width; - this.height = height; - }; - - - //Data Window class - //operates like an array but 'wraps' data around to keep the array at a fixed windowSize - /** - * DataWindow class - Operates like an array, but 'wraps' data around to keep the array at a fixed windowSize - * @param {Number} windowSize - defines the maximum size of the window - * @param {Array} data - optional data to seed the DataWindow with - **/ - self.webgazer.util.DataWindow = function(windowSize, data) { - this.data = []; - this.windowSize = windowSize; - this.index = 0; - this.length = 0; - if(data){ - this.data = data.slice(data.length-windowSize,data.length); - this.length = this.data.length; - } - }; - - /** - * [push description] - * @param {*} entry - item to be inserted. It either grows the DataWindow or replaces the oldest item - * @return {DataWindow} this - */ - self.webgazer.util.DataWindow.prototype.push = function(entry) { - if (this.data.length < this.windowSize) { - this.data.push(entry); - this.length = this.data.length; - return this; - } - - //replace oldest entry by wrapping around the DataWindow - this.data[this.index] = entry; - this.index = (this.index + 1) % this.windowSize; - return this; - }; - - /** - * Get the element at the ind position by wrapping around the DataWindow - * @param {Number} ind index of desired entry - * @return {*} - */ - self.webgazer.util.DataWindow.prototype.get = function(ind) { - return this.data[this.getTrueIndex(ind)]; - }; - - /** - * Gets the true this.data array index given an index for a desired element - * @param {Number} ind - index of desired entry - * @return {Number} index of desired entry in this.data - */ - self.webgazer.util.DataWindow.prototype.getTrueIndex = function(ind) { - if (this.data.length < this.windowSize) { - return ind; - } else { - //wrap around ind so that we can traverse from oldest to newest - return (ind + this.index) % this.windowSize; - } - }; - - /** - * Append all the contents of data - * @param {Array} data - to be inserted - */ - self.webgazer.util.DataWindow.prototype.addAll = function(data) { - for (var i = 0; i < data.length; i++) { - this.push(data[i]); - } - }; - - - //Helper functions - /** - * Grayscales an image patch. Can be used for the whole canvas, detected face, detected eye, etc. - * - * Code from tracking.js by Eduardo Lundgren, et al. - * https://github.com/eduardolundgren/tracking.js/blob/master/src/tracking.js - * - * Software License Agreement (BSD License) Copyright (c) 2014, Eduardo A. Lundgren Melo. All rights reserved. - * Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * The name of Eduardo A. Lundgren Melo may not be used to endorse or promote products derived from this software without specific prior written permission of Eduardo A. Lundgren Melo. - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * @param {Array} pixels - image data to be grayscaled - * @param {Number} width - width of image data to be grayscaled - * @param {Number} height - height of image data to be grayscaled - * @return {Array} grayscaledImage - */ - self.webgazer.util.grayscale = function(pixels, width, height){ - var gray = new Uint8ClampedArray(pixels.length >> 2); - var p = 0; - var w = 0; - for (var i = 0; i < height; i++) { - for (var j = 0; j < width; j++) { - var value = pixels[w] * 0.299 + pixels[w + 1] * 0.587 + pixels[w + 2] * 0.114; - gray[p++] = value; - - w += 4; - } - } - return gray; - }; - - /** - * Increase contrast of an image. - * - * Code from Martin Tschirsich, Copyright (c) 2012. - * https://github.com/mtschirs/js-objectdetect/blob/gh-pages/js/objectdetect.js - * - * @param {Array} src - grayscale integer array - * @param {Number} step - sampling rate, control performance - * @param {Array} dst - array to hold the resulting image - */ - self.webgazer.util.equalizeHistogram = function(src, step, dst) { - var srcLength = src.length; - if (!dst) dst = src; - if (!step) step = 5; - - // Compute histogram and histogram sum: - var hist = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0]; - - for (var i = 0; i < srcLength; i += step) { - ++hist[src[i]]; - } - - // Compute integral histogram: - var norm = 255 * step / srcLength, - prev = 0; - for (var i = 0; i < 256; ++i) { - var h = hist[i]; - prev = h += prev; - hist[i] = h * norm; // For non-integer src: ~~(h * norm + 0.5); - } - - // Equalize image: - for (var i = 0; i < srcLength; ++i) { - dst[i] = hist[src[i]]; - } - return dst; - }; - - self.webgazer.util.threshold = function(data, threshold) { - for (let i = 0; i < data.length; i++) { - data[i] = (data[i] > threshold) ? 255 : 0; - } - return data; - }; - - self.webgazer.util.correlation = function(data1, data2) { - const length = Math.min(data1.length, data2.length); - let count = 0; - for (let i = 0; i < length; i++) { - if (data1[i] === data2[i]) { - count++; - } - } - return count / Math.max(data1.length, data2.length); - }; - - /** - * Gets an Eye object and resizes it to the desired resolution - * @param {webgazer.util.Eye} eye - patch to be resized - * @param {Number} resizeWidth - desired width - * @param {Number} resizeHeight - desired height - * @return {webgazer.util.Eye} resized eye patch - */ - self.webgazer.util.resizeEye = function(eye, resizeWidth, resizeHeight) { - - var canvas = document.createElement('canvas'); - canvas.width = eye.width; - canvas.height = eye.height; - - canvas.getContext('2d').putImageData(eye.patch,0,0); - - var tempCanvas = document.createElement('canvas'); - - tempCanvas.width = resizeWidth; - tempCanvas.height = resizeHeight; - - // save the canvas into temp canvas - tempCanvas.getContext('2d').drawImage(canvas, 0, 0, canvas.width, canvas.height, 0, 0, resizeWidth, resizeHeight); - - return tempCanvas.getContext('2d').getImageData(0, 0, resizeWidth, resizeHeight); - }; - - /** - * Checks if the prediction is within the boundaries of the viewport and constrains it - * @param {Array} prediction [x,y] - predicted gaze coordinates - * @return {Array} constrained coordinates - */ - self.webgazer.util.bound = function(prediction){ - if(prediction.x < 0) - prediction.x = 0; - if(prediction.y < 0) - prediction.y = 0; - var w = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); - var h = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); - if(prediction.x > w){ - prediction.x = w; - } - - if(prediction.y > h) - { - prediction.y = h; - } - return prediction; - }; - - /** - * Write statistics in debug paragraph panel - * @param {HTMLElement} para - The

tag where write data - * @param {Object} stats - The stats data to output - */ - function debugBoxWrite(para, stats) { - var str = ''; - for (var key in stats) { - str += key + ': ' + stats[key] + '\n'; - } - para.innerText = str; - } - - /** - * Constructor of DebugBox object, - * it insert an paragraph inside a div to the body, in view to display debug data - * @param {Number} interval - The log interval - * @constructor - */ - self.webgazer.util.DebugBox = function(interval) { - this.para = document.createElement('p'); - this.div = document.createElement('div'); - this.div.appendChild(this.para); - document.body.appendChild(this.div); - - this.buttons = {}; - this.canvas = {}; - this.stats = {}; - var updateInterval = interval || 300; - (function(localThis) { - setInterval(function() { - debugBoxWrite(localThis.para, localThis.stats); - }, updateInterval); - }(this)); - }; - - /** - * Add stat data for log - * @param {String} key - The data key - * @param {*} value - The value - */ - self.webgazer.util.DebugBox.prototype.set = function(key, value) { - this.stats[key] = value; - }; - - /** - * Initialize stats in case where key does not exist, else - * increment value for key - * @param {String} key - The key to process - * @param {Number} incBy - Value to increment for given key (default: 1) - * @param {Number} init - Initial value in case where key does not exist (default: 0) - */ - self.webgazer.util.DebugBox.prototype.inc = function(key, incBy, init) { - if (!this.stats[key]) { - this.stats[key] = init || 0; - } - this.stats[key] += incBy || 1; - }; - - /** - * Create a button and register the given function to the button click event - * @param {String} name - The button name to link - * @param {Function} func - The onClick callback - */ - self.webgazer.util.DebugBox.prototype.addButton = function(name, func) { - if (!this.buttons[name]) { - this.buttons[name] = document.createElement('button'); - this.div.appendChild(this.buttons[name]); - } - var button = this.buttons[name]; - this.buttons[name] = button; - button.addEventListener('click', func); - button.innerText = name; - }; - - /** - * Search for a canvas elemenet with name, or create on if not exist. - * Then send the canvas element as callback parameter. - * @param {String} name - The canvas name to send/create - * @param {Function} func - The callback function where send canvas - */ - self.webgazer.util.DebugBox.prototype.show = function(name, func) { - if (!this.canvas[name]) { - this.canvas[name] = document.createElement('canvas'); - this.div.appendChild(this.canvas[name]); - } - var canvas = this.canvas[name]; - canvas.getContext('2d').clearRect(0,0, canvas.width, canvas.height); - func(canvas); - }; - - /** - * Kalman Filter constructor - * Kalman filters work by reducing the amount of noise in a models. - * https://blog.cordiner.net/2011/05/03/object-tracking-using-a-kalman-filter-matlab/ - * - * @param {Array.>} F - transition matrix - * @param {Array.>} Q - process noise matrix - * @param {Array.>} H - maps between measurement vector and noise matrix - * @param {Array.>} R - defines measurement error of the device - * @param {Array} P_initial - the initial state - * @param {Array} X_initial - the initial state of the device - */ - self.webgazer.util.KalmanFilter = function(F, H, Q, R, P_initial, X_initial) { - this.F = F; // State transition matrix - this.Q = Q; // Process noise matrix - this.H = H; // Transformation matrix - this.R = R; // Measurement Noise - this.P = P_initial; //Initial covariance matrix - this.X = X_initial; //Initial guess of measurement - }; - - /** - * Get Kalman next filtered value and update the internal state - * @param {Array} z - the new measurement - * @return {Array} - */ - self.webgazer.util.KalmanFilter.prototype.update = function(z) { - - // Here, we define all the different matrix operations we will need - const { - add, sub, mult, inv, identity, transpose, - } = mat; - //TODO cache variables like the transpose of H - - // prediction: X = F * X | P = F * P * F' + Q - var X_p = mult(this.F, this.X); //Update state vector - var P_p = add(mult(mult(this.F,this.P), transpose(this.F)), this.Q); //Predicted covaraince - - //Calculate the update values - var y = sub(z, mult(this.H, X_p)); // This is the measurement error (between what we expect and the actual value) - var S = add(mult(mult(this.H, P_p), transpose(this.H)), this.R); //This is the residual covariance (the error in the covariance) - - // kalman multiplier: K = P * H' * (H * P * H' + R)^-1 - var K = mult(P_p, mult(transpose(this.H), inv(S))); //This is the Optimal Kalman Gain - - //We need to change Y into it's column vector form - for(var i = 0; i < y.length; i++){ - y[i] = [y[i]]; - } - - //Now we correct the internal values of the model - // correction: X = X + K * (m - H * X) | P = (I - K * H) * P - this.X = add(X_p, mult(K, y)); - this.P = mult(sub(identity(K.length), mult(K,this.H)), P_p); - return transpose(mult(this.H, this.X))[0]; //Transforms the predicted state back into it's measurement form - }; - -}()); diff --git a/src/worker_scripts/util.ts b/src/worker_scripts/util.ts new file mode 100644 index 000000000..136110348 --- /dev/null +++ b/src/worker_scripts/util.ts @@ -0,0 +1,434 @@ +'use strict'; + +import { add, identity, inv, Matrix, mult, sub, transpose } from './mat'; + +// Replace magic numbers with named constants +const RESIZE_WIDTH = 10; +const RESIZE_HEIGHT = 6; +const EQUALIZE_STEP = 5; +const GRAYSCALE_RED_FACTOR = 0.299; +const GRAYSCALE_GREEN_FACTOR = 0.587; +const GRAYSCALE_BLUE_FACTOR = 0.114; + +export interface Point { + x: number; + y: number; +} + +export type Pupil = [[number, number], number]; + +export interface Eye { + patch: ImageData; + imagex: number; + imagey: number; + width: number; + height: number; + blink?: boolean; + pupil?: Pupil; +} + +// Data Window class +export class DataWindow { + data: T[]; + start: number; + private windowSize: number; + + /** + * DataWindow class - Operates like an array, but 'wraps' data around to keep the array at a fixed windowSize + * @param windowSize - defines the maximum size of the window + * @param data - optional data to seed the DataWindow with + **/ + constructor (windowSize: number, data: T[] = []) { + this.data = []; + this.start = 0; + this.windowSize = windowSize; + data.slice(-windowSize).forEach(item => this.push(item)); + } + + get length (): number { + return this.data.length; + } + + clear (): void { + this.data.length = 0; + this.start = 0; + } + + /** + * [push description] + * @param entry - item to be inserted. It either grows the DataWindow or replaces the oldest item + * @return this + */ + push (...entry: T[]): DataWindow { + entry.forEach(item => { + if (this.data.length < this.windowSize) { + this.data.push(item); + } else { + this.data[this.start] = item; + this.start = (this.start + 1) % this.windowSize; + } + }); + return this; + } + + /** + * Get the element at the ind position by wrapping around the DataWindow + * @param ind index of desired entry + */ + get (ind: number): T { + return this.data[(this.start + ind) % this.windowSize]; + } + + /** + * Iterate over the DataWindow + * @param callback + */ + forEach (callback: (item: T, index: number, dataWindow: DataWindow) => void): void { + for (let i = 0; i < this.data.length; i++) { + callback(this.get(i), i, this); + } + } + + toArray (): T[] { + return [...this]; + } + + * [Symbol.iterator] (): IterableIterator { + for (let i = 0; i < this.data.length; i++) { + yield this.get(i); + } + } +} + +interface TwoEyes { + left: Eye; + right: Eye; +} + +/** + * Compute eyes size as gray histogram + * @param eyes - The eyes where looking for gray histogram + * @returns The eyes gray level histogram + */ +export const getEyeFeats = (eyes: TwoEyes, trackEye: 'left' | 'right' | 'both'): number[] => { + /** + * Process an eye and return its gray level histogram + * @param eye - The eye to be processed + * @returns The gray level histogram of the eye + */ + const process = (eye: Eye): number[] => { + const resized = resizeEye(eye, RESIZE_WIDTH, RESIZE_HEIGHT); + const gray = grayscale(resized.data, resized.width, resized.height); + const hist: number[] = []; + equalizeHistogram(gray, EQUALIZE_STEP, hist); + return hist; + }; + + if (trackEye === 'left') { + return process(eyes.left); + } else if (trackEye === 'right') { + return process(eyes.right); + } else { + let ret: number[] = []; + ret = ret.concat(process(eyes.left), process(eyes.right)); + return ret; + } +}; + +// Helper functions +/** + * Grayscales an image patch. Can be used for the whole canvas, detected face, detected eye, etc. + * + * Code from tracking.js by Eduardo Lundgren, et al. + * https://github.com/eduardolundgren/tracking.js/blob/master/src/tracking.js + * + * Software License Agreement (BSD License) Copyright (c) 2014, Eduardo A. Lundgren Melo. All rights reserved. + * Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * The name of Eduardo A. Lundgren Melo may not be used to endorse or promote products derived from this software without specific prior written permission of Eduardo A. Lundgren Melo. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @param pixels - image data to be grayscaled + * @param width - width of image data to be grayscaled + * @param height - height of image data to be grayscaled + * @return grayscaledImage + */ +export const grayscale = (pixels: Uint8ClampedArray, width: number, height: number): Uint8ClampedArray => { + const gray = new Uint8ClampedArray(pixels.length >> 2); + let p = 0; + let w = 0; + for (let i = 0; i < height; i++) { + for (let j = 0; j < width; j++) { + const value = pixels[w] * GRAYSCALE_RED_FACTOR + + pixels[w + 1] * GRAYSCALE_GREEN_FACTOR + + pixels[w + 2] * GRAYSCALE_BLUE_FACTOR; + gray[p++] = value; + + w += 4; + } + } + return gray; +}; + +/** + * Increase contrast of an image. + * + * Code from Martin Tschirsich, Copyright (c) 2012. + * https://github.com/mtschirs/js-objectdetect/blob/gh-pages/js/objectdetect.js + * + * @param src - grayscale integer array + * @param step - sampling rate, control performance + * @param dst - array to hold the resulting image + */ +const equalizeHistogram = (src: Uint8ClampedArray, step: number, dst: number[]): number[] => { + const srcLength = src.length; + if (!dst) dst = Array.from(src); + if (!step) step = 5; + + // Compute histogram and histogram sum: + const hist: number[] = Array(256).fill(0); + + for (let i = 0; i < srcLength; i += step) { + ++hist[src[i]]; + } + + // Compute integral histogram: + const norm = 255 * step / srcLength; + let prev = 0; + for (let i = 0; i < 256; ++i) { + let h = hist[i]; + prev = h += prev; + hist[i] = h * norm; + } + + // Equalize image: + for (let i = 0; i < srcLength; ++i) { + dst[i] = hist[src[i]]; + } + return dst; +}; + +/** + * Evaluate the similarities between 2 list of data + * (should we use levenstein algorithme for more accurate results?) + * @param data1 The first list + * @param data2 The second list + * @returns The ratio of similarities (between 0 and 1) + */ +export const correlation = (data1: number[], data2: number[]): number => { + const length = Math.min(data1.length, data2.length); + let count = 0; + for (let i = 0; i < length; i++) { + if (data1[i] === data2[i]) { + count++; + } + } + return count / Math.max(data1.length, data2.length); +}; + +let resizeCanvas: OffscreenCanvas; +let resizeContext: OffscreenCanvasRenderingContext2D | null; + +let tempCanvas: OffscreenCanvas; +let tempContext: OffscreenCanvasRenderingContext2D | null; + +/** + * Gets an Eye object and resizes it to the desired resolution + * @param eye - patch to be resized + * @param resizeWidth - desired width + * @param resizeHeight - desired height + * @return resized eye patch + */ +const resizeEye = (eye: Eye, resizeWidth: number, resizeHeight: number): ImageData => { + // Create canvas only once and reuse + if (!resizeCanvas) { + resizeCanvas = new OffscreenCanvas(resizeWidth, resizeHeight); + resizeContext = resizeCanvas.getContext('2d'); + } + if (!tempCanvas) { + tempCanvas = new OffscreenCanvas(eye.width, eye.height); + tempContext = tempCanvas.getContext('2d'); + } + + // Ensure canvas is correct size + if (resizeCanvas.width !== resizeWidth || resizeCanvas.height !== resizeHeight) { + resizeCanvas.width = resizeWidth; + resizeCanvas.height = resizeHeight; + } + if (tempCanvas.width !== eye.width || tempCanvas.height !== eye.height) { + tempCanvas.width = eye.width; + tempCanvas.height = eye.height; + } + + if (!resizeContext || !tempContext) return eye.patch; // Theoretically impossible + + // Put the eye patch data onto the temporary canvas + tempContext.putImageData(eye.patch, 0, 0); + + // Clear the resize canvas and draw the resized image + resizeContext.clearRect(0, 0, resizeWidth, resizeHeight); + resizeContext.drawImage(tempCanvas, 0, 0, eye.width, eye.height, 0, 0, resizeWidth, resizeHeight); + + return resizeContext.getImageData(0, 0, resizeWidth, resizeHeight); +}; + +/** + * Checks if the prediction is within the boundaries of the viewport and constrains it + * @param prediction predicted gaze coordinates + * @return constrained coordinates + */ +export const bound = (prediction: Point): Point => { + if (prediction.x < 0) prediction.x = 0; + if (prediction.y < 0) prediction.y = 0; + const w = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); + const h = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); + if (prediction.x > w) prediction.x = w; + if (prediction.y > h) prediction.y = h; + return prediction; +}; + +export class DebugBox { + buttons: Record = {}; + canvas: Record = {}; + stats: Record = {}; + para: HTMLParagraphElement = document.createElement('p'); + div: HTMLDivElement = document.createElement('div'); + + /** + * Constructor of DebugBox object, + * it insert an paragraph inside a div to the body, in view to display debug data + * @param interval - The log interval + */ + constructor (interval?: number) { + this.div.appendChild(this.para); + document.body.appendChild(this.div); + + const updateInterval = interval || 300; + + setInterval(() => { + this.para.innerText = Object.entries(this.stats).map(([key, value]) => `${key}: ${value}`).join('\n'); + }, updateInterval); + } + + /** + * Add stat data for log + * @param key - The data key + * @param value - The value + */ + set = (key: string, value: any): void => { + this.stats[key] = value; + }; + + /** + * Initialize stats in case where key does not exist, else + * increment value for key + * @param key - The key to process + * @param incBy - Value to increment for given key (default: 1) + * @param init - Initial value in case where key does not exist (default: 0) + */ + inc = (key: string, incBy?: number, init?: number): void => { + if (!this.stats[key]) this.stats[key] = init || 0; + this.stats[key] += incBy || 1; + }; + + /** + * Create a button and register the given function to the button click event + * @param name - The button name to link + * @param func - The onClick callback + */ + addButton = (name: string, func: (event: MouseEvent) => void): void => { + if (!this.buttons[name]) { + this.buttons[name] = document.createElement('button'); + this.div.appendChild(this.buttons[name]); + } + const button = this.buttons[name]; + this.buttons[name] = button; + button.addEventListener('click', func); + button.innerText = name; + }; + + /** + * Search for a canvas element with name, or create on if not exist. + * Then send the canvas element as callback parameter. + * @param name - The canvas name to send/create + * @param func - The callback function where send canvas + */ + show = (name: string, func: (canvas: HTMLCanvasElement) => void): void => { + if (!this.canvas[name]) { + this.canvas[name] = document.createElement('canvas'); + this.div.appendChild(this.canvas[name]); + } + const canvas = this.canvas[name]; + const context2D = canvas.getContext('2d'); + if (context2D) context2D.clearRect(0, 0, canvas.width, canvas.height); + func(canvas); + }; +} + +export class KalmanFilter { + F: Matrix; + Q: Matrix; + H: Matrix; + R: Matrix; + P: Matrix; + X: Matrix; + + /** + * Kalman Filter constructor + * Kalman filters work by reducing the amount of noise in a models. + * https://blog.cordiner.net/2011/05/03/object-tracking-using-a-kalman-filter-matlab/ + * @param F - transition matrix + * @param Q - process noise matrix + * @param H - maps between measurement vector and noise matrix + * @param R - defines measurement error of the device + * @param pInitial - the initial state + * @param xInitial - the initial state of the device + */ + constructor ( + F: Matrix, + H: Matrix, + Q: Matrix, + R: Matrix, + pInitial: Matrix, + xInitial: Matrix + ) { + this.F = F; // State transition matrix + this.Q = Q; // Process noise matrix + this.H = H; // Transformation matrix + this.R = R; // Measurement Noise + this.P = pInitial; // Initial covariance matrix + this.X = xInitial; // Initial guess of measurement + } + + /** + * Get Kalman next filtered value and update the internal state + * @param z - the new measurement + * @return + */ + update = (z: [number, number]): [number, number] => { + // TODO cache variables like the transpose of H + + // prediction: X = F * X | P = F * P * F' + Q + const xP = mult(this.F, this.X); // Update state vector + const pP = add(mult(mult(this.F, this.P), transpose(this.F)), this.Q); // Predicted covaraince + + // Calculate the update values + const innovation = sub(transpose([z]), mult(this.H, xP)); // This is the measurement error (between what we expect and the actual value) + const yColumn: Matrix = innovation.map(row => [row[0]]); // Ensure y is a proper column vector + const S = add(mult(mult(this.H, pP), transpose(this.H)), this.R); // This is the residual covariance (the error in the covariance) + + // kalman multiplier: K = P * H' * (H * P * H' + R)^-1 + const K = mult(pP, mult(transpose(this.H), inv(S))); // This is the Optimal Kalman Gain + + // Now we correct the internal values of the model + // correction: X = X + K * (m - H * X) | P = (I - K * H) * P + this.X = add(xP, mult(K, yColumn)); + this.P = mult(sub(identity(K.length), mult(K, this.H)), pP); + const [x, y] = transpose(mult(this.H, this.X))[0]; // Transforms the predicted state back into it's measurement form + return [x, y]; + }; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..3b6215aee --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "outDir": "./dist", + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": false, + "declaration": true, + "emitDeclarationOnly": true, + "declarationDir": "./dist/types", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true, + "esModuleInterop": true + }, + "lib": ["webworker", "es2015"], + "include": ["src/**/*"], + "exclude": ["node_modules", ".eslintrc.cjs", "www", "dist"] +} diff --git a/tsconfig.www.json b/tsconfig.www.json new file mode 100644 index 000000000..abd4bf30e --- /dev/null +++ b/tsconfig.www.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "baseUrl": "./www", + "outDir": "./dist", + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true, + "esModuleInterop": true + }, + "include": ["www/js/**/*"], + "exclude": ["src", "www/lib", "dist", "node_modules"] +} diff --git a/types.d.ts b/types.d.ts new file mode 100644 index 000000000..b33108fdb --- /dev/null +++ b/types.d.ts @@ -0,0 +1,3 @@ +// This allows to import d3 v3 types into a module file (collision.js) +import * as d3 from 'd3'; +export = d3 diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 000000000..2ab67620c --- /dev/null +++ b/vite.config.js @@ -0,0 +1,61 @@ +import { resolve } from 'path'; +import { defineConfig } from 'vite'; +import banner from 'vite-plugin-banner'; +import { exec } from 'child_process'; +import { promisify } from 'util'; + +const execAsync = promisify(exec); + +const bannerString = ` + WebGazer.js: Democratizing Webcam Eye Tracking on the Browser + Copyright (c) 2016, Brown WebGazer Team + Licensed under GPLv3. Companies with a valuation of less than $1M can use WebGazer.js under LGPLv3. + `; + +export default defineConfig({ + root: 'www', + build: { + lib: { + entry: resolve(__dirname, 'src/index.ts'), + name: 'webgazer', + formats: ['es', 'cjs'], + fileName: (format) => `webgazer.${format}.js` + }, + sourcemap: true, + outDir: '../dist', + emptyOutDir: false, + rollupOptions: { + output: { + inlineDynamicImports: true + } + }, + minify: 'terser', + terserOptions: { + keep_classnames: true, + keep_fnames: true, + mangle: false + } + }, + plugins: [ + banner(bannerString), + { + name: 'create-symlinks', + async closeBundle () { + try { + // Run tsc to generate type declarations + await execAsync('tsc --emitDeclarationOnly --declaration --outDir dist/types'); + await execAsync('node scripts/create-symlink.mjs'); + } catch (error) { + console.error('Error in closeBundle:', error); + } + } + } + ], + server: { + port: 8080, + open: true, + watch: { + include: ['src/**', 'www/**'] + } + } +}); diff --git a/vite.worker.config.js b/vite.worker.config.js new file mode 100644 index 000000000..09bccd670 --- /dev/null +++ b/vite.worker.config.js @@ -0,0 +1,25 @@ +import { resolve } from 'path'; +import { defineConfig } from 'vite'; + +export default defineConfig(() => ({ + build: { + lib: { + entry: resolve(__dirname, 'src/ridgeWorker.worker.ts'), + formats: ['es'], + fileName: () => 'ridgeWorker.worker.js' + }, + sourcemap: true, + outDir: 'dist', + emptyOutDir: false, + rollupOptions: { + output: { + inlineDynamicImports: true + } + }, + watch: process.argv.includes('--watch') + ? { + include: ['src/ridgeWorker.worker.ts', 'src/worker_scripts'] + } + : false + } +})); diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100644 index 8c9f890b0..000000000 --- a/webpack.config.js +++ /dev/null @@ -1,106 +0,0 @@ -const path = require('path'); -const webpack = require('webpack'); -const TerserPlugin = require('terser-webpack-plugin'); -const FileManagerPlugin = require('filemanager-webpack-plugin'); - -const bannerString =` - WebGazer.js: Democratizing Webcam Eye Tracking on the Browser - Copyright (c) 2016, Brown WebGazer Team - Licensed under GPLv3. Companies with a valuation of less than $1M can use WebGazer.js under LGPLv3. - `; - -const varConfig = { - entry: './src/index.mjs', - output: { - filename: 'webgazer.js', - library: { - name: 'webgazer', - type: 'var', - export: 'default', - }, - path: path.resolve(__dirname, 'dist'), - }, - module: { - rules: [ - { - test: /\.mjs$/, - type: 'javascript/auto', - exclude: /node_modules/, - resolve: { - fullySpecified: false, - }, - } - ] - }, - resolve: { - extensions: [".mjs", ".webpack.js", ".web.js", ".js", ".json"] - }, - optimization: { - minimizer: [new TerserPlugin({ - extractComments: false, - })], - }, - performance: { - hints: false, - maxEntrypointSize: 512000, - }, - plugins: [ - new webpack.BannerPlugin(bannerString), - new FileManagerPlugin({ - events: { - onEnd: { - copy: [ - { source: './dist/webgazer.js', destination: './www/' }, - { source: './dist/webgazer.js.map', destination: './www/' }, - { source: './dist/webgazer.js', destination: './www/data/src/' }, - { source: './dist/webgazer.js.map', destination: './www/data/src/' }, - ], - }, - }, - }), - ], - devtool: "source-map" -}; - -const commonjs2Config = { - entry: './src/index.mjs', - output: { - filename: 'webgazer.commonjs2.js', - library: { - name: 'webgazer', - type: 'commonjs2', - export: 'default', - }, - path: path.resolve(__dirname, 'dist'), - }, - module: { - rules: [ - { - test: /\.mjs$/, - type: 'javascript/auto', - exclude: /node_modules/, - resolve: { - fullySpecified: false, - }, - } - ] - }, - resolve: { - extensions: [".mjs", ".webpack.js", ".web.js", ".js", ".json"] - }, - optimization: { - minimizer: [new TerserPlugin({ - extractComments: false, - })], - }, - performance: { - hints: false, - maxEntrypointSize: 512000, - }, - plugins: [ - new webpack.BannerPlugin(bannerString), - ], - devtool: "source-map" -}; - -module.exports = [varConfig, commonjs2Config] diff --git a/www/calibration.html b/www/calibration.html index 9d36ed57a..cb08795ab 100644 --- a/www/calibration.html +++ b/www/calibration.html @@ -10,79 +10,83 @@ Instructions on use can be found in the README repository. --> - - - WebGazer Demo - - - - - - - - + + + WebGazer Demo + + + + + - - - - + + + +

+ +
+ + + + + + + + + +
- - -
- - - - - - - - - -
- - -