diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..1fe0a72 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,20 @@ +{ + "env": { + "browser": true, + "es2021": true + }, + "extends": [ + "eslint:recommended", + "plugin:prettier/recommended" + ], + "plugins": ["prettier"], + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "rules": { + "prettier/prettier": "error", + "no-unused-vars": "warn", + "no-console": "off" + } +} diff --git a/.gitignore b/.gitignore index 06362ca..3d2fec6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ dev/train/trainData/mnist/mnistTraindata_large.js dev/train/trainData/mnist/mnist_train.csv +node_modules +dist +build +*.min.js \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..904225f --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/prettier.xml b/.idea/prettier.xml new file mode 100644 index 0000000..ea0f3cf --- /dev/null +++ b/.idea/prettier.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..bae74ff --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "printWidth": 100, + "tabWidth": 4, + "trailingComma": "all", + "singleQuote": true, + "semi": true +} \ No newline at end of file diff --git a/dev/train/Trainer.js b/dev/train/Trainer.js index 3594d8f..0434c50 100644 --- a/dev/train/Trainer.js +++ b/dev/train/Trainer.js @@ -1,27 +1,44 @@ -import { NeuralNetworkBase } from "../../src/core/NeuralNetworkBase.js"; +import { NeuralNetworkBase } from '../../src/core/NeuralNetworkBase.ts'; // matrix operations -import {matrixMultiply, transposeMatrix} from "../../src/core/ops/matrixOps.js"; +import { matrixMultiply, transposeMatrix } from '../../src/core/ops/matrixOps.ts'; export class Trainer extends NeuralNetworkBase { - train(inputs, targets){ + train(inputs, targets) { // CNN operations const { finalOutputs, hiddenOutputs } = this.feedForward(inputs); // calculate errors - const output_errors = targets.map((v, idx) => v - finalOutputs[idx]).map(v => [v]); // 출력 계층의 오차를 목표값 - 출력값으로 지정 + const output_errors = targets.map((v, idx) => v - finalOutputs[idx]).map((v) => [v]); // 출력 계층의 오차를 목표값 - 출력값으로 지정 const hidden_errors = matrixMultiply(transposeMatrix(this.W_hiddenToOutput), output_errors); // 은닉 계층의 오차를 은닉-> 출력 계층의 가중치값과(W_hiddenToOutput.T) 출력 계층의 오차들을 재조합하여 계산 // activation function derivative - const activationDerivative_HtO = finalOutputs.map(v => 1.0 - v).map((v, idx) => v * finalOutputs[idx]); // hidden to output derivative - const outputGradient = activationDerivative_HtO.map((v, idx) => v * output_errors[idx]).map(v => [v]); - const W_HtO_Update = matrixMultiply(outputGradient, transposeMatrix(hiddenOutputs)).map(array => array.map(v => v * this.learningRate)); // 오차값을 이용해 은닉 계층과 출력 계층간의 가중치 업데이트 + const activationDerivative_HtO = finalOutputs + .map((v) => 1.0 - v) + .map((v, idx) => v * finalOutputs[idx]); // hidden to output derivative + const outputGradient = activationDerivative_HtO + .map((v, idx) => v * output_errors[idx]) + .map((v) => [v]); + const W_HtO_Update = matrixMultiply(outputGradient, transposeMatrix(hiddenOutputs)).map( + (array) => array.map((v) => v * this.learningRate), + ); // 오차값을 이용해 은닉 계층과 출력 계층간의 가중치 업데이트 - const activationDerivative_ItH = hiddenOutputs.map(v => 1.0 - v).map((v, idx) => v * hiddenOutputs[idx]); - const hiddenGradient = activationDerivative_ItH.map((v, idx) => v * hidden_errors[idx]).map(v => [v]); - const W_ItH_update = matrixMultiply(hiddenGradient, transposeMatrix(inputs.map(v => [v]))).map(array => array.map(v => v * this.learningRate)); + const activationDerivative_ItH = hiddenOutputs + .map((v) => 1.0 - v) + .map((v, idx) => v * hiddenOutputs[idx]); + const hiddenGradient = activationDerivative_ItH + .map((v, idx) => v * hidden_errors[idx]) + .map((v) => [v]); + const W_ItH_update = matrixMultiply( + hiddenGradient, + transposeMatrix(inputs.map((v) => [v])), + ).map((array) => array.map((v) => v * this.learningRate)); // update weights - this.W_hiddenToOutput = this.W_hiddenToOutput.map((row, i)=> row.map((v, j) => v + W_HtO_Update[i][j])); - this.W_inputToHidden = this.W_inputToHidden.map((row, i)=> row.map((v, j) => v + W_ItH_update[i][j])); + this.W_hiddenToOutput = this.W_hiddenToOutput.map((row, i) => + row.map((v, j) => v + W_HtO_Update[i][j]), + ); + this.W_inputToHidden = this.W_inputToHidden.map((row, i) => + row.map((v, j) => v + W_ItH_update[i][j]), + ); } -} \ No newline at end of file +} diff --git a/public/index.html b/index.html similarity index 67% rename from public/index.html rename to index.html index 7df7fa3..3e0130a 100644 --- a/public/index.html +++ b/index.html @@ -6,9 +6,8 @@ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> Neural Network Visualization - - - + + @@ -21,9 +20,7 @@
- - - +
diff --git a/jsconfig.json b/jsconfig.json index 8ecfcad..1286948 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,9 +1,8 @@ { "compilerOptions": { - "baseUrl": "src", + "baseUrl": "./src", "paths": { - "@controller/*": ["controller/*"], - "@view/*": ["view/*"] + "@/*": ["*"] } }, "include": ["src"] diff --git a/package.json b/package.json index 15fecff..eba51c4 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,22 @@ "version": "1.0.0", "type": "module", "description": "", - "main": "public/index.html", + "main": "index.html", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "format": "prettier --write ." }, - "private": true -} + "private": true, + "devDependencies": { + "eslint": "^9.32.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.3", + "prettier": "3.6.2", + "vite": "^7.0.6" + } +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..9c498a0 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1328 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + eslint: + specifier: ^9.32.0 + version: 9.32.0 + eslint-config-prettier: + specifier: ^10.1.8 + version: 10.1.8(eslint@9.32.0) + eslint-plugin-prettier: + specifier: ^5.5.3 + version: 5.5.3(eslint-config-prettier@10.1.8(eslint@9.32.0))(eslint@9.32.0)(prettier@3.6.2) + prettier: + specifier: 3.6.2 + version: 3.6.2 + vite: + specifier: ^7.0.6 + version: 7.0.6 + +packages: + + '@esbuild/aix-ppc64@0.25.8': + resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.8': + resolution: {integrity: sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.8': + resolution: {integrity: sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.8': + resolution: {integrity: sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.8': + resolution: {integrity: sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.8': + resolution: {integrity: sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.8': + resolution: {integrity: sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.8': + resolution: {integrity: sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.8': + resolution: {integrity: sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.8': + resolution: {integrity: sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.8': + resolution: {integrity: sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.8': + resolution: {integrity: sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.8': + resolution: {integrity: sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.8': + resolution: {integrity: sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.8': + resolution: {integrity: sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.8': + resolution: {integrity: sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.8': + resolution: {integrity: sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.8': + resolution: {integrity: sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.8': + resolution: {integrity: sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.8': + resolution: {integrity: sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.8': + resolution: {integrity: sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.8': + resolution: {integrity: sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.8': + resolution: {integrity: sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.8': + resolution: {integrity: sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.8': + resolution: {integrity: sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.8': + resolution: {integrity: sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.7.0': + resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.0': + resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.3.0': + resolution: {integrity: sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.15.1': + resolution: {integrity: sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.32.0': + resolution: {integrity: sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.3.4': + resolution: {integrity: sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@pkgr/core@0.2.9': + resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + + '@rollup/rollup-android-arm-eabi@4.46.2': + resolution: {integrity: sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.46.2': + resolution: {integrity: sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.46.2': + resolution: {integrity: sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.46.2': + resolution: {integrity: sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.46.2': + resolution: {integrity: sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.46.2': + resolution: {integrity: sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.46.2': + resolution: {integrity: sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.46.2': + resolution: {integrity: sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.46.2': + resolution: {integrity: sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.46.2': + resolution: {integrity: sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.46.2': + resolution: {integrity: sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.46.2': + resolution: {integrity: sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.46.2': + resolution: {integrity: sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.46.2': + resolution: {integrity: sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.46.2': + resolution: {integrity: sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.46.2': + resolution: {integrity: sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.46.2': + resolution: {integrity: sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.46.2': + resolution: {integrity: sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.46.2': + resolution: {integrity: sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.46.2': + resolution: {integrity: sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==} + cpu: [x64] + os: [win32] + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + esbuild@0.25.8: + resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==} + engines: {node: '>=18'} + hasBin: true + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-prettier@10.1.8: + resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-prettier@5.5.3: + resolution: {integrity: sha512-NAdMYww51ehKfDyDhv59/eIItUVzU0Io9H2E8nHNGKEeeqlnci+1gCvrHib6EmZdf6GxF+LCV5K7UC65Ezvw7w==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.32.0: + resolution: {integrity: sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fdir@6.4.6: + resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} + hasBin: true + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + rollup@4.46.2: + resolution: {integrity: sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + synckit@0.11.11: + resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} + engines: {node: ^14.18.0 || >=16.0.0} + + tinyglobby@0.2.14: + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + engines: {node: '>=12.0.0'} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + vite@7.0.6: + resolution: {integrity: sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@esbuild/aix-ppc64@0.25.8': + optional: true + + '@esbuild/android-arm64@0.25.8': + optional: true + + '@esbuild/android-arm@0.25.8': + optional: true + + '@esbuild/android-x64@0.25.8': + optional: true + + '@esbuild/darwin-arm64@0.25.8': + optional: true + + '@esbuild/darwin-x64@0.25.8': + optional: true + + '@esbuild/freebsd-arm64@0.25.8': + optional: true + + '@esbuild/freebsd-x64@0.25.8': + optional: true + + '@esbuild/linux-arm64@0.25.8': + optional: true + + '@esbuild/linux-arm@0.25.8': + optional: true + + '@esbuild/linux-ia32@0.25.8': + optional: true + + '@esbuild/linux-loong64@0.25.8': + optional: true + + '@esbuild/linux-mips64el@0.25.8': + optional: true + + '@esbuild/linux-ppc64@0.25.8': + optional: true + + '@esbuild/linux-riscv64@0.25.8': + optional: true + + '@esbuild/linux-s390x@0.25.8': + optional: true + + '@esbuild/linux-x64@0.25.8': + optional: true + + '@esbuild/netbsd-arm64@0.25.8': + optional: true + + '@esbuild/netbsd-x64@0.25.8': + optional: true + + '@esbuild/openbsd-arm64@0.25.8': + optional: true + + '@esbuild/openbsd-x64@0.25.8': + optional: true + + '@esbuild/openharmony-arm64@0.25.8': + optional: true + + '@esbuild/sunos-x64@0.25.8': + optional: true + + '@esbuild/win32-arm64@0.25.8': + optional: true + + '@esbuild/win32-ia32@0.25.8': + optional: true + + '@esbuild/win32-x64@0.25.8': + optional: true + + '@eslint-community/eslint-utils@4.7.0(eslint@9.32.0)': + dependencies: + eslint: 9.32.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.21.0': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.1 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.3.0': {} + + '@eslint/core@0.15.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.1 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.32.0': {} + + '@eslint/object-schema@2.1.6': {} + + '@eslint/plugin-kit@0.3.4': + dependencies: + '@eslint/core': 0.15.1 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@pkgr/core@0.2.9': {} + + '@rollup/rollup-android-arm-eabi@4.46.2': + optional: true + + '@rollup/rollup-android-arm64@4.46.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.46.2': + optional: true + + '@rollup/rollup-darwin-x64@4.46.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.46.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.46.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.46.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.46.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.46.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.46.2': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.46.2': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.46.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.46.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.46.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.46.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.46.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.46.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.46.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.46.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.46.2': + optional: true + + '@types/estree@1.0.8': {} + + '@types/json-schema@7.0.15': {} + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + argparse@2.0.1: {} + + balanced-match@1.0.2: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + callsites@3.1.0: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + concat-map@0.0.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@4.4.1: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + esbuild@0.25.8: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.8 + '@esbuild/android-arm': 0.25.8 + '@esbuild/android-arm64': 0.25.8 + '@esbuild/android-x64': 0.25.8 + '@esbuild/darwin-arm64': 0.25.8 + '@esbuild/darwin-x64': 0.25.8 + '@esbuild/freebsd-arm64': 0.25.8 + '@esbuild/freebsd-x64': 0.25.8 + '@esbuild/linux-arm': 0.25.8 + '@esbuild/linux-arm64': 0.25.8 + '@esbuild/linux-ia32': 0.25.8 + '@esbuild/linux-loong64': 0.25.8 + '@esbuild/linux-mips64el': 0.25.8 + '@esbuild/linux-ppc64': 0.25.8 + '@esbuild/linux-riscv64': 0.25.8 + '@esbuild/linux-s390x': 0.25.8 + '@esbuild/linux-x64': 0.25.8 + '@esbuild/netbsd-arm64': 0.25.8 + '@esbuild/netbsd-x64': 0.25.8 + '@esbuild/openbsd-arm64': 0.25.8 + '@esbuild/openbsd-x64': 0.25.8 + '@esbuild/openharmony-arm64': 0.25.8 + '@esbuild/sunos-x64': 0.25.8 + '@esbuild/win32-arm64': 0.25.8 + '@esbuild/win32-ia32': 0.25.8 + '@esbuild/win32-x64': 0.25.8 + + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@10.1.8(eslint@9.32.0): + dependencies: + eslint: 9.32.0 + + eslint-plugin-prettier@5.5.3(eslint-config-prettier@10.1.8(eslint@9.32.0))(eslint@9.32.0)(prettier@3.6.2): + dependencies: + eslint: 9.32.0 + prettier: 3.6.2 + prettier-linter-helpers: 1.0.0 + synckit: 0.11.11 + optionalDependencies: + eslint-config-prettier: 10.1.8(eslint@9.32.0) + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.32.0: + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.21.0 + '@eslint/config-helpers': 0.3.0 + '@eslint/core': 0.15.1 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.32.0 + '@eslint/plugin-kit': 0.3.4 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.1 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fdir@6.4.6(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + fsevents@2.3.3: + optional: true + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + has-flag@4.0.0: {} + + ignore@5.3.2: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + isexe@2.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 + + prettier@3.6.2: {} + + punycode@2.3.1: {} + + resolve-from@4.0.0: {} + + rollup@4.46.2: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.46.2 + '@rollup/rollup-android-arm64': 4.46.2 + '@rollup/rollup-darwin-arm64': 4.46.2 + '@rollup/rollup-darwin-x64': 4.46.2 + '@rollup/rollup-freebsd-arm64': 4.46.2 + '@rollup/rollup-freebsd-x64': 4.46.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.46.2 + '@rollup/rollup-linux-arm-musleabihf': 4.46.2 + '@rollup/rollup-linux-arm64-gnu': 4.46.2 + '@rollup/rollup-linux-arm64-musl': 4.46.2 + '@rollup/rollup-linux-loongarch64-gnu': 4.46.2 + '@rollup/rollup-linux-ppc64-gnu': 4.46.2 + '@rollup/rollup-linux-riscv64-gnu': 4.46.2 + '@rollup/rollup-linux-riscv64-musl': 4.46.2 + '@rollup/rollup-linux-s390x-gnu': 4.46.2 + '@rollup/rollup-linux-x64-gnu': 4.46.2 + '@rollup/rollup-linux-x64-musl': 4.46.2 + '@rollup/rollup-win32-arm64-msvc': 4.46.2 + '@rollup/rollup-win32-ia32-msvc': 4.46.2 + '@rollup/rollup-win32-x64-msvc': 4.46.2 + fsevents: 2.3.3 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + source-map-js@1.2.1: {} + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + synckit@0.11.11: + dependencies: + '@pkgr/core': 0.2.9 + + tinyglobby@0.2.14: + dependencies: + fdir: 6.4.6(picomatch@4.0.3) + picomatch: 4.0.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + vite@7.0.6: + dependencies: + esbuild: 0.25.8 + fdir: 6.4.6(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.46.2 + tinyglobby: 0.2.14 + optionalDependencies: + fsevents: 2.3.3 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + yocto-queue@0.1.0: {} diff --git a/src/controller/AppContoller.js b/src/controller/AppContoller.js deleted file mode 100644 index 22fd9c7..0000000 --- a/src/controller/AppContoller.js +++ /dev/null @@ -1,35 +0,0 @@ -import WeightManager from "../core/WeightManager.js"; -import NeuralNetworkBase from "../core/NeuralNetworkBase.js"; -import QueryProcessController from "./queryPipeline/QueryProcessController.js"; -import UserInputCanvas from "../view/components/userQuery/UserInputCanvas.js"; -import PathTrackingCanvas from "../view/components/userQuery/PathTrackingCanvas.js"; -import AlignCanvas from "../view/components/userQuery/AlignCanvas.js"; -import ResizeCanvas from "../view/components/userQuery/ResizeCanvas.js"; -import { DataStore } from "./DataStore.js"; - -import eventBus from "./EventBus.js"; -import { DATA_EVENTS, HANDLER_EVENTS } from "./constants/events.js"; -import { NETWORK_CONFIG } from "./constants/networkConfig.js"; - -class AppController { - constructor({ dataStore }) { - this.dataStore = dataStore; - } - - async initialize() { - const $WM = new WeightManager(NETWORK_CONFIG); - const $NN = new NeuralNetworkBase(await $WM.getWeights()); - const $DS = new DataStore(); - const $QC = new QueryProcessController({ - userInputCanvas: new UserInputCanvas(), - trackingCanvas: new PathTrackingCanvas(), - alignCanvas: new AlignCanvas(), - resizeCanvas: new ResizeCanvas(), - $NN: $NN, - }); - eventBus.emit(HANDLER_EVENTS.APP_READY, this.dataStore); - eventBus.on(DATA_EVENTS.RESULT_CHANGED, (data) => {console.log("RESULT CHANGED", data)}); - }; -} - -export default AppController; \ No newline at end of file diff --git a/src/controller/AppController.ts b/src/controller/AppController.ts new file mode 100644 index 0000000..1d24875 --- /dev/null +++ b/src/controller/AppController.ts @@ -0,0 +1,43 @@ +import WeightManager from '@/core/WeightManager.ts'; +import NeuralNetworkBase from '@/core/NeuralNetworkBase.ts'; +import QueryProcessController from './queryPipeline/QueryProcessController.ts'; +import UserInputCanvas from '@/view/components/userQuery/UserInputCanvas.ts'; +import PathTrackingCanvas from '@/view/components/userQuery/PathTrackingCanvas.ts'; +import AlignCanvas from '@/view/components/userQuery/AlignCanvas.ts'; +import ResizeCanvas from '@/view/components/userQuery/ResizeCanvas.ts'; +import DataStore from './DataStore.ts'; + +import eventBus from './EventBus.ts'; +import { DATA_EVENTS, HANDLER_EVENTS } from './constants/events.ts'; +import { NETWORK_CONFIG } from './constants/networkConfig.ts'; +import DrawingEventHandler from './queryPipeline/DrawingEventHandler.js'; +import { IWeightManager } from '@/core/types/WeightManager.ts'; +import { INeuralNetworkBase } from '@/core/types/NeuralNetworkBase.ts'; +import { IDataStore } from './types/DataStore.ts'; +import { Matrix2D } from '@/core/ops/types/OpsType.ts'; + +class AppController { + private $WM: IWeightManager; + private $NN: INeuralNetworkBase; + private $DS: IDataStore; + + async initialize() { + const $WM = new WeightManager(NETWORK_CONFIG); + const $NN = new NeuralNetworkBase(await $WM.getWeights()); + const $DS = new DataStore(); + new DrawingEventHandler(); + const $QC = new QueryProcessController({ + userInputCanvas: new UserInputCanvas(), + trackingCanvas: new PathTrackingCanvas(), + alignCanvas: new AlignCanvas(), + resizeCanvas: new ResizeCanvas(), + $NN: $NN, + $DS: $DS, + }); + eventBus.on(DATA_EVENTS.RESULT_CHANGED, (data: Matrix2D): void => { + console.log('RESULT CHANGED', data); + }); + } +} + +export default AppController; diff --git a/src/controller/DataStore.ts b/src/controller/DataStore.ts new file mode 100644 index 0000000..556ab6c --- /dev/null +++ b/src/controller/DataStore.ts @@ -0,0 +1,51 @@ +import eventBus from './EventBus.js'; +import { DATA_EVENTS } from './constants/events.js'; + +import { nodeState } from './types/DataStore'; +import { Matrix2D } from '@/core/ops/types/OpsType.ts'; + +class DataStore { + private queryInfo: number[] | null = null; + private nodeState: nodeState | null = null; + private queryResult: Matrix2D | null = null; + + constructor() { + this.queryInfo = null; + this.nodeState = null; + this.queryResult = null; + } + + setQueryInfo(queryInfo: number[]): void { + if (queryInfo === null) throw new Error('No query info found'); + this.queryInfo = queryInfo; + eventBus.emit(DATA_EVENTS.QUERY_CHANGED, this.queryInfo); + } + getQueryInfo(): number[] { + return this.queryInfo; + } + + setNodeState(nodeState: nodeState): void { + if (nodeState === null) throw new Error('No node state found'); + this.nodeState = nodeState; + } + getNodeState(): nodeState { + return this.nodeState; + } + + setQueryResult(queryResult: Matrix2D): void { + if (queryResult === null) throw new Error('No query result found'); + this.queryResult = queryResult; + eventBus.emit(DATA_EVENTS.RESULT_CHANGED, queryResult); + } + getQueryResult() { + return this.queryResult; + } + + reset() { + this.queryInfo = null; + this.nodeState = null; + this.queryResult = null; + } +} + +export default DataStore; diff --git a/src/controller/EventBus.js b/src/controller/EventBus.js deleted file mode 100644 index e8d182a..0000000 --- a/src/controller/EventBus.js +++ /dev/null @@ -1,20 +0,0 @@ -const EventBus = { - _events: {}, - - on(eventName, subscriber) { - if(!this._events[eventName]) this._events[eventName] = []; - this._events[eventName].push(subscriber); - }, - - off(eventName, subscriber) { - if(!this._events[eventName]) return; - this._events[eventName] = this._events[eventName].filter(fn => fn !== subscriber); - }, - - emit(eventName, payload) { - if(!this._events[eventName]) return; - this._events[eventName].forEach(fn => fn(payload)); - } -} - -export default EventBus; \ No newline at end of file diff --git a/src/controller/EventBus.ts b/src/controller/EventBus.ts new file mode 100644 index 0000000..ffe271f --- /dev/null +++ b/src/controller/EventBus.ts @@ -0,0 +1,33 @@ +import { IEventBus } from './types/eventBus'; +import { eventPayloads } from './types/eventBus'; + +const EventBus: IEventBus = { + events: {}, + + on( + eventName: K, + subscriber: (payload: eventPayloads[K]) => any, + ): void { + if (!this.events[eventName]) this.events[eventName] = []; + this.events[eventName].push(subscriber); + }, + + off( + eventName: K, + subscriber: (payload: eventPayloads[K]) => any, + ): void { + if (!this.events[eventName]) return; + this.events[eventName] = this.events[eventName].filter( + (fn: (payload: eventPayloads[K]) => void): boolean => fn !== subscriber, + ); + }, + + emit(eventName: K, payload: eventPayloads[K]): void { + if (!this.events[eventName]) return; + this.events[eventName].forEach((fn: (payload: eventPayloads[K]) => any): any => + fn(payload), + ); + }, +}; + +export default EventBus; diff --git a/src/controller/constants/canvasConfig.js b/src/controller/constants/canvasConfig.ts similarity index 53% rename from src/controller/constants/canvasConfig.js rename to src/controller/constants/canvasConfig.ts index 23eef1c..dbca9a0 100644 --- a/src/controller/constants/canvasConfig.js +++ b/src/controller/constants/canvasConfig.ts @@ -2,4 +2,6 @@ export const CANVAS_CONFIG = Object.freeze({ lineWidth: 20, lineCap: 'round', lineJoin: 'round', -}); \ No newline at end of file +} as const); + +export type CanvasConfig = (typeof CANVAS_CONFIG)[keyof typeof CANVAS_CONFIG]; diff --git a/src/controller/constants/events.js b/src/controller/constants/events.ts similarity index 52% rename from src/controller/constants/events.js rename to src/controller/constants/events.ts index edebe68..bf0e08c 100644 --- a/src/controller/constants/events.js +++ b/src/controller/constants/events.ts @@ -4,18 +4,23 @@ export const DATA_EVENTS = Object.freeze({ NODE_UPDATE: 'node:update', NODE_CHANGED: 'node:changed', RESULT_UPDATE: 'result:update', - RESULT_CHANGED: 'result:changed' -}); + RESULT_CHANGED: 'result:changed', +} as const); export const HANDLER_EVENTS = Object.freeze({ APP_READY: 'app:ready', NN_INITIALIZE: 'nn:initialize', ICH_INITIALIZE: 'ich:initialize', -}) +} as const); export const DRAWING_EVENTS = Object.freeze({ - START_DRAW: 'draw: start', - DRAW: 'draw: drawing', - END_DRAW: 'draw: end', + START_DRAW: 'draw:start', + DRAW: 'draw:drawing', + END_DRAW: 'draw:end', + CLEAR_DRAW: 'draw:clear', BOUNDINGBOX_UPDATE: 'boundingbox:update', -}) \ No newline at end of file +} as const); + +export type DataEvent = (typeof DATA_EVENTS)[keyof typeof DATA_EVENTS]; +export type HandlerEvent = (typeof DATA_EVENTS)[keyof typeof DATA_EVENTS]; +export type DrawingEvent = (typeof DATA_EVENTS)[keyof typeof DATA_EVENTS]; diff --git a/src/controller/constants/networkConfig.js b/src/controller/constants/networkConfig.js deleted file mode 100644 index 8d6a6c8..0000000 --- a/src/controller/constants/networkConfig.js +++ /dev/null @@ -1,6 +0,0 @@ -export const NETWORK_CONFIG = { - inputNodes: 784, - hiddenNodes: 200, - outputNodes: 10, - learningRate: 0.15, -} \ No newline at end of file diff --git a/src/controller/constants/networkConfig.ts b/src/controller/constants/networkConfig.ts new file mode 100644 index 0000000..fe022fd --- /dev/null +++ b/src/controller/constants/networkConfig.ts @@ -0,0 +1,8 @@ +import { networkConfig } from './types/networkConfig'; + +export const NETWORK_CONFIG: networkConfig = { + inputNodes: 784, + hiddenNodes: 200, + outputNodes: 10, + learningRate: 0.15, +}; diff --git a/src/view/components/perceptron/Perceptron.js b/src/controller/constants/types/events.ts similarity index 100% rename from src/view/components/perceptron/Perceptron.js rename to src/controller/constants/types/events.ts diff --git a/src/controller/constants/types/networkConfig.ts b/src/controller/constants/types/networkConfig.ts new file mode 100644 index 0000000..f099979 --- /dev/null +++ b/src/controller/constants/types/networkConfig.ts @@ -0,0 +1,6 @@ +export interface networkConfig { + inputNodes: number; + hiddenNodes: number; + outputNodes: number; + learningRate: number; +} diff --git a/src/controller/dataStore.js b/src/controller/dataStore.js deleted file mode 100644 index 434e342..0000000 --- a/src/controller/dataStore.js +++ /dev/null @@ -1,42 +0,0 @@ -import eventBus from "./EventBus.js"; -import { DATA_EVENTS } from "./constants/events.js" - -export class DataStore { - constructor() { - this._queryInfo = null; - this._nodeState = {}; - this._queryResult = null; - - eventBus.on(DATA_EVENTS.NODE_UPDATE, this.setQueryInfo.bind(this)); - eventBus.on(DATA_EVENTS.RESULT_UPDATE, this.setQueryResult.bind(this)); - } - - setQueryInfo(queryInfo) { - this._queryInfo = queryInfo; - eventBus.emit(DATA_EVENTS.QUERY_CHANGED, queryInfo) - } - getQueryInfo() { - return this._queryInfo; - } - - setNodeState(nodeState) { - this._nodeState = nodeState; - } - getNodeState() { - return this._nodeState; - } - - setQueryResult(queryResult) { - this._queryResult = queryResult; - eventBus.emit(DATA_EVENTS.QUERY_CHANGED, queryResult); - } - getQueryResult() { - return this._queryResult; - } - - reset() { - this._queryInfo = null; - this._nodeState = {}; - this._queryResult = null; - } -} \ No newline at end of file diff --git a/src/controller/queryPipeline/DrawingEventHandler.js b/src/controller/queryPipeline/DrawingEventHandler.js deleted file mode 100644 index 05671c2..0000000 --- a/src/controller/queryPipeline/DrawingEventHandler.js +++ /dev/null @@ -1,64 +0,0 @@ -import BoundingBox from "../../view/components/canvasUtils/BoundingBox.js"; -import eventBus from "../EventBus.js"; -import { DRAWING_EVENTS } from "../constants/events.js"; - -class DrawingEventHandler { - constructor() { - this.inputCanvas = document.getElementById('userInputCanvas'); - this.registerEvents(); - } - - registerEvents() { - const startDraw = (x, y) => { - this.isDrawing = true; - eventBus.emit(DRAWING_EVENTS.START_DRAW, {x, y}); - } - - const draw = (x, y) => { - if (!this.isDrawing) return; - BoundingBox.update(x, y); - eventBus.emit(DRAWING_EVENTS.DRAW, {x, y}) - } - - const endDraw = () => { - this.isDrawing = false; - BoundingBox.log(); - eventBus.emit(DRAWING_EVENTS.END_DRAW); - } - - const getTouchPosition = (e) => { - e.preventDefault(); - const touch = e.touches[0]; - const rect = this.inputCanvas.getBoundingClientRect(); - return { - x: touch.clientX - rect.left, - y: touch.clientY - rect.top, - } - } - - this.inputCanvas.addEventListener("mousedown", (e) => { - startDraw(e.offsetX, e.offsetY); - }); - this.inputCanvas.addEventListener("touchstart", (e) => { - let {x, y} = getTouchPosition(e); - startDraw(x, y); - }); - - this.inputCanvas.addEventListener("mousemove", (e) => { - draw(e.offsetX, e.offsetY); - }); - this.inputCanvas.addEventListener("touchmove", (e) => { - let {x, y} = getTouchPosition(e); - draw(x, y); - }); - - this.inputCanvas.addEventListener("mouseup", endDraw); - this.inputCanvas.addEventListener("mouseout", endDraw); - this.inputCanvas.addEventListener("touchend", (e) => { - e.preventDefault(); - endDraw(); - }); - } -} - -export default DrawingEventHandler; diff --git a/src/controller/queryPipeline/DrawingEventHandler.ts b/src/controller/queryPipeline/DrawingEventHandler.ts new file mode 100644 index 0000000..d64076c --- /dev/null +++ b/src/controller/queryPipeline/DrawingEventHandler.ts @@ -0,0 +1,92 @@ +import BoundingBox from '@/view/components/canvasUtils/BoundingBox.ts'; +import eventBus from '../EventBus.ts'; +import { DRAWING_EVENTS } from '../constants/events.ts'; + +class DrawingEventHandler { + private inputCanvas: HTMLCanvasElement | null; + private clearButton: HTMLButtonElement | null; + private isDrawing: boolean; + + constructor() { + this.inputCanvas = document.getElementById('userInputCanvas') as HTMLCanvasElement; + this.clearButton = document.getElementById('clear') as HTMLButtonElement; + this.isDrawing = false; + this.registerEvents(); + } + + registerEvents(): void { + const bindings: [string, EventListener][] = [ + ['mousedown', this.handleStartDraw], + ['touchstart', this.handleStartDraw], + ['mousemove', this.handleDraw], + ['touchmove', this.handleDraw], + ['mouseup', this.endDraw], + ['mouseout', this.endDraw], + [ + 'touchend', + (e: TouchEvent): void => { + e.preventDefault(); + this.endDraw(); + }, + ], + ]; + + bindings.forEach(([event, handler]: [string, EventListener]): void => + this.inputCanvas.addEventListener(event, handler), + ); + + this.clearButton.addEventListener('click', () => { + eventBus.emit(DRAWING_EVENTS.CLEAR_DRAW, null); + }); + } + + // eventBus로 입력 event 전송 + startDraw = (x: number, y: number): void => { + this.isDrawing = true; + eventBus.emit(DRAWING_EVENTS.START_DRAW, { x, y }); + }; + + draw = (x: number, y: number): void => { + if (!this.isDrawing) return; + BoundingBox.update(x, y); + eventBus.emit(DRAWING_EVENTS.DRAW, { x, y }); + }; + + endDraw = (): void => { + this.isDrawing = false; + BoundingBox.log(); + eventBus.emit(DRAWING_EVENTS.END_DRAW, null); + }; + + // 캔버스에서의 터치 위치 반환 + getTouchPosition = (e: TouchEvent): { x: number; y: number } => { + e.preventDefault(); + const touch = e.touches[0]; + const rect = this.inputCanvas.getBoundingClientRect(); + return { + x: touch.clientX - rect.left, + y: touch.clientY - rect.top, + }; + }; + + // 마우스 클릭, 터치 이벤트에 대한 입력 이벤트 발생 + handleStartDraw = (e: MouseEvent | TouchEvent): void => { + if (e instanceof MouseEvent) { + this.startDraw(e.offsetX, e.offsetY); + } else if (e instanceof TouchEvent) { + let { x, y } = this.getTouchPosition(e); + this.startDraw(x, y); + } + }; + + handleDraw = (e: MouseEvent | TouchEvent): void => { + if (e instanceof MouseEvent) { + this.draw(e.offsetX, e.offsetY); + } else if (e instanceof TouchEvent) { + let { x, y } = this.getTouchPosition(e); + this.draw(x, y); + } + }; +} + +export default DrawingEventHandler; diff --git a/src/controller/queryPipeline/QueryProcessController.js b/src/controller/queryPipeline/QueryProcessController.js deleted file mode 100644 index 0bc3697..0000000 --- a/src/controller/queryPipeline/QueryProcessController.js +++ /dev/null @@ -1,48 +0,0 @@ -import eventBus from "../EventBus.js"; -import { DATA_EVENTS, HANDLER_EVENTS, DRAWING_EVENTS } from "../constants/events.js"; -import DrawingEventHandler from "./DrawingEventHandler.js"; -import {pixelExtractor} from "../queryPipeline/pixelExtractor.js"; -import {throttle} from "../queryPipeline/throttle.js"; - - -class QueryProcessController { - constructor({ userInputCanvas, trackingCanvas, alignCanvas, resizeCanvas, $NN }) { - const drawingEventHandler = new DrawingEventHandler(); - this.userInputCanvas = userInputCanvas; - this.trackingCanvas = trackingCanvas; - this.alignCanvas = alignCanvas; - this.resizeCanvas = resizeCanvas; - - this.$NN = $NN; - this.drawingEvent(); - } - - drawingEvent() { - const throttleQuery = throttle((inputs) => { - const result = this.$NN.query(inputs); - if(result) { - eventBus.emit(DATA_EVENTS.RESULT_CHANGED, result); - // Array 중 가장 값이 큰 값의 index number가 추론 결과 - } - },100); - - eventBus.on(DRAWING_EVENTS.START_DRAW, ({x, y}) => { - this.userInputCanvas.startPath(x, y); - this.trackingCanvas.startPath(x, y); - }); - eventBus.on(DRAWING_EVENTS.DRAW, ({x, y}) => { - this.userInputCanvas.drawPath(x, y); - this.trackingCanvas.drawPath(x, y); - this.alignCanvas.updateCanvasScale(); - this.alignCanvas.centralize(this.trackingCanvas.canvas); - this.resizeCanvas.downScale(this.alignCanvas.canvas); - throttleQuery(pixelExtractor(this.resizeCanvas)); - }); - eventBus.on(DRAWING_EVENTS.END_DRAW, () => { - this.userInputCanvas.endPath(); - this.trackingCanvas.endPath(); - }); - } -} - -export default QueryProcessController; \ No newline at end of file diff --git a/src/controller/queryPipeline/QueryProcessController.ts b/src/controller/queryPipeline/QueryProcessController.ts new file mode 100644 index 0000000..eddbb2f --- /dev/null +++ b/src/controller/queryPipeline/QueryProcessController.ts @@ -0,0 +1,85 @@ +import eventBus from '../EventBus.js'; +import { DATA_EVENTS, DRAWING_EVENTS } from '../constants/events.js'; +import { pixelExtractor } from './pixelExtractor.js'; +import { throttle } from './throttle'; +import { IQueryProcessControllerProps } from './types/QueryProcessController.js'; +import { + ICanvasBase, + IUserInputCanvas, + IPathTrackingCanvas, + IAlignCanvas, + IResizeCanvas, +} from '@/view/components/userQuery/types/canvas'; +import { INeuralNetworkBase } from '@/core/types/NeuralNetworkBase'; +import { IDataStore } from '../types/DataStore'; +import { Matrix2D } from '@/core/ops/types/OpsType.ts'; +import BoundingBox from '@/view/components/canvasUtils/BoundingBox.ts'; + +class QueryProcessController { + private readonly userInputCanvas: ICanvasBase & IUserInputCanvas; + private readonly trackingCanvas: ICanvasBase & IPathTrackingCanvas; + private readonly alignCanvas: ICanvasBase & IAlignCanvas; + private readonly resizeCanvas: ICanvasBase & IResizeCanvas; + private readonly $NN: INeuralNetworkBase; + private readonly $DS: IDataStore; + private readonly queryFrequencyMs: number; + //TODO: Canvas clear시 Skeleton 에니메이션 적용하기 + + constructor({ + userInputCanvas, + trackingCanvas, + alignCanvas, + resizeCanvas, + $NN, + $DS, + }: IQueryProcessControllerProps) { + this.userInputCanvas = userInputCanvas; + this.trackingCanvas = trackingCanvas; + this.alignCanvas = alignCanvas; + this.resizeCanvas = resizeCanvas; + + this.$NN = $NN; + this.$DS = $DS; + this.registerDrawingEvent(); + this.query(); + this.queryFrequencyMs = 200; + } + + registerDrawingEvent(): void { + eventBus.on(DRAWING_EVENTS.START_DRAW, ({ x, y }) => { + this.userInputCanvas.startPath(x, y); + this.trackingCanvas.startPath(x, y); + }); + eventBus.on(DRAWING_EVENTS.DRAW, ({ x, y }) => { + this.userInputCanvas.drawPath(x, y); + this.trackingCanvas.drawPath(x, y); + this.alignCanvas.updateCanvasScale(); + this.alignCanvas.centralize(this.trackingCanvas.canvas); + this.resizeCanvas.downScale(this.alignCanvas.canvas); + this.$DS.setQueryInfo(pixelExtractor(this.resizeCanvas)); + }); + eventBus.on(DRAWING_EVENTS.END_DRAW, () => { + this.userInputCanvas.endPath(); + this.trackingCanvas.endPath(); + }); + eventBus.on(DRAWING_EVENTS.CLEAR_DRAW, () => { + this.userInputCanvas.clear(); + this.trackingCanvas.clear(); + this.alignCanvas.clear(); + this.resizeCanvas.clear(); + BoundingBox.reset(); + }); + } + + query() { + const throttleQuery = throttle((inputs) => { + const result: Matrix2D = this.$NN.query(inputs); + if (result) eventBus.emit(DATA_EVENTS.RESULT_CHANGED, result); + }, this.queryFrequencyMs); + + eventBus.on(DATA_EVENTS.QUERY_CHANGED, (inputs: number[]) => throttleQuery(inputs)); + // TODO: type interface 추가하기, 이벤트버스 구조와 쿼리 구조 다시 생각해보기, TS 마이그레이션 + } +} + +export default QueryProcessController; diff --git a/src/controller/queryPipeline/pixelExtractor.js b/src/controller/queryPipeline/pixelExtractor.js deleted file mode 100644 index d4156c7..0000000 --- a/src/controller/queryPipeline/pixelExtractor.js +++ /dev/null @@ -1,28 +0,0 @@ -export const pixelExtractor = (path) => { - const pathToMatrix = (width, height, data) => { - return Array.from({ length: height }, (_, y) => { - return Array.from({ length: width }, (_, x) => { - const i = (y * width + x) * 4; - const [r, g, b, a] = data.slice(i, i + 4); - return { r, g, b, a }; - }); - }); - } - - const grayscaleMatrix = (matrixRGB) => { - return matrixRGB.flatMap(pixelObject => - pixelObject.map(v => { - const RGB_sum = v.r + v.g + v.b; - return Math.round(RGB_sum/3); - })); - } - - const normalize = (grayscaleMatrix) => { - return grayscaleMatrix.map(v => (v/255)*0.99+0.01); - } - - const {width, height, data} = path.ctx.getImageData(0, 0, path.canvas.width, path.canvas.height); - const matrixRGB = pathToMatrix(width, height, data); - const grayScale = grayscaleMatrix(matrixRGB); - return normalize(grayScale); -}; \ No newline at end of file diff --git a/src/controller/queryPipeline/pixelExtractor.ts b/src/controller/queryPipeline/pixelExtractor.ts new file mode 100644 index 0000000..3e37ef1 --- /dev/null +++ b/src/controller/queryPipeline/pixelExtractor.ts @@ -0,0 +1,42 @@ +import { IResizeCanvas, ICanvasBase } from '@/view/components/userQuery/types/canvas'; + +export const pixelExtractor = (path: IResizeCanvas & ICanvasBase) => { + const pathToMatrix = (width: number, height: number, data: Uint8ClampedArray): object[] => { + return Array.from( + { length: height }, + (_: unknown, y: number): { r: number; g: number; b: number; a: number }[] => { + return Array.from( + { length: width }, + (_: unknown, x: number): { r: number; g: number; b: number; a: number } => { + const i: number = (y * width + x) * 4; + const [r, g, b, a] = data.slice(i, i + 4); + return { r, g, b, a }; + }, + ); + }, + ); + }; + + const grayscaleMatrix = (matrixRGB: object[]): number[] => { + return matrixRGB.flatMap((pixelObject: object[]) => + pixelObject.map((v: { r: number; g: number; b: number; a: number }): number => { + const RGB_sum: number = v.r + v.g + v.b; + return Math.round(RGB_sum / 3); + }), + ); + }; + + const normalize = (grayscaleMatrix: number[]): number[] => { + return grayscaleMatrix.map((v) => (v / 255) * 0.99 + 0.01); + }; + + const { width, height, data } = path.ctx.getImageData( + 0, + 0, + path.canvas.width, + path.canvas.height, + ); + const matrixRGB: object[] = pathToMatrix(width, height, data); + const grayScale = grayscaleMatrix(matrixRGB); + return normalize(grayScale); +}; diff --git a/src/controller/queryPipeline/throttle.js b/src/controller/queryPipeline/throttle.ts similarity index 51% rename from src/controller/queryPipeline/throttle.js rename to src/controller/queryPipeline/throttle.ts index 8d25156..bc966df 100644 --- a/src/controller/queryPipeline/throttle.js +++ b/src/controller/queryPipeline/throttle.ts @@ -1,6 +1,8 @@ -export const throttle = (fn, delay) => { +export const throttle = any> ( + fn: T, delay: number +): (...args: Parameters) => ReturnType => { let pause = false; - return (...args) => { + return (...args: Parameters): ReturnType => { if(!pause) { const result = fn(...args); pause = true; diff --git a/src/controller/queryPipeline/types/DrawingEventHandler.ts b/src/controller/queryPipeline/types/DrawingEventHandler.ts new file mode 100644 index 0000000..d4a36b5 --- /dev/null +++ b/src/controller/queryPipeline/types/DrawingEventHandler.ts @@ -0,0 +1,12 @@ +interface DrawingEventHandlerParams { + startDraw(x: number, y: number): void; + draw(x: number, y: number): void; + endDraw(): void; + getTouchPosition(e: TouchEvent): { x: number; y: number }; + + addEventListener(listener: EventListener): void; +} + +interface DrawingEventHandlerParams { + registerEvents: DrawingEventHandlerParams; +} diff --git a/src/controller/queryPipeline/types/QueryProcessController.ts b/src/controller/queryPipeline/types/QueryProcessController.ts new file mode 100644 index 0000000..e43c0bb --- /dev/null +++ b/src/controller/queryPipeline/types/QueryProcessController.ts @@ -0,0 +1,18 @@ +import { + ICanvasBase, + IUserInputCanvas, + IPathTrackingCanvas, + IAlignCanvas, + IResizeCanvas, +} from '@/view/components/userQuery/types/canvas'; +import { INeuralNetworkBase } from '@/core/types/NeuralNetworkBase'; +import { IDataStore } from '@/controller/types/DataStore'; + +export interface IQueryProcessControllerProps { + userInputCanvas: ICanvasBase & IUserInputCanvas; + trackingCanvas: ICanvasBase & IPathTrackingCanvas; + alignCanvas: ICanvasBase & IAlignCanvas; + resizeCanvas: ICanvasBase & IResizeCanvas; + $NN: INeuralNetworkBase; + $DS: IDataStore; +} diff --git a/src/controller/types/DataStore.ts b/src/controller/types/DataStore.ts new file mode 100644 index 0000000..cff00ae --- /dev/null +++ b/src/controller/types/DataStore.ts @@ -0,0 +1,18 @@ +import { Matrix2D } from '@/core/ops/types/OpsType.ts'; + +export interface IDataStore { + setQueryInfo(queryInfo: number[]): void; + getQueryInfo(): number[]; + + setNodeState(nodeState: nodeState): void; + getNodeState(): nodeState; + + setQueryResult(queryResult: Matrix2D): void; + getQueryResult(): Matrix2D; +} + +export interface nodeState { + inputNodes: number[]; + hiddenNodes: number[]; + outputNodes: number[]; +} diff --git a/src/controller/types/eventBus.ts b/src/controller/types/eventBus.ts new file mode 100644 index 0000000..7c302ac --- /dev/null +++ b/src/controller/types/eventBus.ts @@ -0,0 +1,32 @@ +import { Matrix2D } from '@/core/ops/types/OpsType'; + +export interface eventPayloads { + // Data handling event + 'query:changed': number[]; + 'node:changed': { inputNodes: number[]; hiddenNodes: number[]; outputNodes: number[] }; + 'result:changed': Matrix2D; + + // Canvas handling event + 'draw:start': { x: number; y: number }; + 'draw:drawing': { x: number; y: number }; + 'draw:end': void; + 'draw:clear': void; + 'bondingbox:update': { X: number; y: number }; + + 'node:update': { hiddenInputs: Matrix2D; hiddenOutputs: Matrix2D; finalOutputs: Matrix2D }; +} + +export interface IEventBus { + events: object; + on( + eventName: K, + subscriber: (payload: eventPayloads[K]) => void, + ): void; + + off( + eventName: K, + subscriber: (payload: eventPayloads[K]) => void, + ): void; + + emit(eventName: K, payload: eventPayloads[K]): void; +} diff --git a/src/controller/types/weights.ts b/src/controller/types/weights.ts new file mode 100644 index 0000000..a149746 --- /dev/null +++ b/src/controller/types/weights.ts @@ -0,0 +1,4 @@ +export interface weights { + W_inputToHidden: number[][]; + W_hiddenToOutput: number[][]; +} diff --git a/src/core/NeuralNetworkBase.js b/src/core/NeuralNetworkBase.js deleted file mode 100644 index adb4bfa..0000000 --- a/src/core/NeuralNetworkBase.js +++ /dev/null @@ -1,35 +0,0 @@ -import activationFunction from "./ops/activationOps.js"; -import { matrixMultiply } from "./ops/matrixOps.js"; -import eventBus from "../controller/EventBus.js"; -import { DATA_EVENTS } from "../controller/constants/events.js"; -import { NETWORK_CONFIG } from "../controller/constants/networkConfig.js"; - -class NeuralNetworkBase { - constructor(weights) { - this.inputNodes = NETWORK_CONFIG.inputNodes; - this.hiddenNodes = NETWORK_CONFIG.hiddenNodes; - this.outputNodes = NETWORK_CONFIG.outputNodes; - this.learningRate = NETWORK_CONFIG.learningRate; - - this.W_inputToHidden = weights?.W_inputToHidden ?? null; - this.W_hiddenToOutput = weights?.W_hiddenToOutput ?? null; - } - - // CNN operations - feedForward(inputs) { - console.log("inputs", inputs); - const hiddenInputs = matrixMultiply(this.W_inputToHidden, inputs.map(v => [v])); //신경망 출력 결과를 Nx1 형태의 행렬곱으로 변환. - const hiddenOutputs = activationFunction(hiddenInputs); - const finalInputs = matrixMultiply(this.W_hiddenToOutput, hiddenOutputs); - const finalOutputs = activationFunction(finalInputs); - return { hiddenInputs, hiddenOutputs, finalInputs, finalOutputs }; - } - - query(inputs){ - const { hiddenInputs, hiddenOutputs , finalOutputs } = this.feedForward(inputs); - eventBus.emit(DATA_EVENTS.NODE_UPDATE, {hiddenInputs, hiddenOutputs, finalOutputs}); - return finalOutputs; - } -} - -export default NeuralNetworkBase; \ No newline at end of file diff --git a/src/core/NeuralNetworkBase.ts b/src/core/NeuralNetworkBase.ts new file mode 100644 index 0000000..3f1cc6e --- /dev/null +++ b/src/core/NeuralNetworkBase.ts @@ -0,0 +1,42 @@ +import activationFunction from './ops/activationOps.js'; +import { matrixMultiply } from './ops/matrixOps.js'; +import eventBus from '@/controller/EventBus.js'; +import { DATA_EVENTS } from '@/controller/constants/events.js'; + +import { weights } from '@/controller/types/weights'; +import { Matrix2D } from './ops/types/OpsType'; + +class NeuralNetworkBase { + private readonly W_inputToHidden: number[][] | null; + private readonly W_hiddenToOutput: number[][] | null; + + constructor(weights: Partial) { + // weight를 optional하게 처리하기 위해 Partial 사용 + this.W_inputToHidden = weights?.W_inputToHidden ?? null; + this.W_hiddenToOutput = weights?.W_hiddenToOutput ?? null; + } + + // CNN operations + feedForward(inputs: number[]): { + hiddenInputs: Matrix2D; + hiddenOutputs: Matrix2D; + finalOutputs: Matrix2D; + } { + const hiddenInputs: number[][] = matrixMultiply( + this.W_inputToHidden, + inputs.map((v) => [v]), + ); //신경망 출력 결과를 Nx1 형태의 행렬곱으로 변환. + const hiddenOutputs: Matrix2D = activationFunction(hiddenInputs); + const finalInputs: Matrix2D = matrixMultiply(this.W_hiddenToOutput, hiddenOutputs); + const finalOutputs: Matrix2D = activationFunction(finalInputs); + return { hiddenInputs, hiddenOutputs, finalOutputs }; + } + + query(inputs: number[]): Matrix2D { + const { hiddenInputs, hiddenOutputs, finalOutputs } = this.feedForward(inputs); + eventBus.emit(DATA_EVENTS.NODE_UPDATE, { hiddenInputs, hiddenOutputs, finalOutputs }); + return finalOutputs; + } +} + +export default NeuralNetworkBase; diff --git a/src/core/WeightManager.js b/src/core/WeightManager.js deleted file mode 100644 index 40b49ef..0000000 --- a/src/core/WeightManager.js +++ /dev/null @@ -1,33 +0,0 @@ -import { createRandomWeight } from "./utils/createRandomWeights.js"; -import { loadPretrainedWeights } from "./utils/loadPretrainedWeights.js"; - -class WeightManager { - _cache = null; - - constructor(networkConfig) { - this.config = networkConfig; - this.path = "assets/preTrainedWeights_h200_lr15p.json"; - } - - async getWeights() { - if(this._cache) return this._cache - try{ - const json = await loadPretrainedWeights(this.path); - this._cache = { - W_inputToHidden: json.W_inputToHidden, - W_hiddenToOutput: json.W_hiddenToOutput, - }; - }catch(err){ - console.warn(`[WeightManager] Using random weights due to error: ${err.message}`); - this._cache = { - W_inputToHidden: createRandomWeight(this.config.hiddenNodes, this.config.inputNodes), - W_hiddenToOutput: createRandomWeight(this.config.outputNodes, this.config.hiddenNodes), - }; - } - console.log("Network weights fetched"); - return this._cache; - } - -} - -export default WeightManager; \ No newline at end of file diff --git a/src/core/WeightManager.ts b/src/core/WeightManager.ts new file mode 100644 index 0000000..29aa62d --- /dev/null +++ b/src/core/WeightManager.ts @@ -0,0 +1,43 @@ +import { createRandomWeight } from './utils/createRandomWeights.js'; +import { loadPretrainedWeights } from './utils/loadPretrainedWeights.js'; +import { networkConfig } from '@/controller/constants/types/networkConfig'; + +import { weights } from '@/controller/types/weights'; + +class WeightManager { + private _cache: null | weights = null; + private config: networkConfig; + private readonly path: string; + + constructor(networkConfig: networkConfig) { + this.config = networkConfig; + this.path = 'assets/preTrainedWeights_h200_lr15p.json'; + } + + async getWeights(): Promise { + if (this._cache) return this._cache; + try { + const json = await loadPretrainedWeights(this.path); + this._cache = { + W_inputToHidden: json.W_inputToHidden, + W_hiddenToOutput: json.W_hiddenToOutput, + }; + } catch (err) { + console.warn(`[WeightManager] Using random weights due to error: ${err.message}`); + this._cache = { + W_inputToHidden: createRandomWeight( + this.config.hiddenNodes, + this.config.inputNodes, + ), + W_hiddenToOutput: createRandomWeight( + this.config.outputNodes, + this.config.hiddenNodes, + ), + }; + } + console.log('Network weights fetched'); + return this._cache; + } +} + +export default WeightManager; diff --git a/src/core/ops/activationOps.js b/src/core/ops/activationOps.js deleted file mode 100644 index 9dfb928..0000000 --- a/src/core/ops/activationOps.js +++ /dev/null @@ -1,17 +0,0 @@ -const activationFunction = matrix => - matrix.map(array => - array.map(v => { - const value = sigmoid(v) - return parseFloat(value.toFixed(5)) //활성화 함수를 적용시킨 값을 소숫점 5자리로 반올림 - }) - ) - -const sigmoid = ($x) => { - return 1 / (1 + Math.exp(-$x)); -}; - -const ReLU = ($x) => { - return Math.max(0, $x); -} - -export default activationFunction; \ No newline at end of file diff --git a/src/core/ops/activationOps.ts b/src/core/ops/activationOps.ts new file mode 100644 index 0000000..3d92e4a --- /dev/null +++ b/src/core/ops/activationOps.ts @@ -0,0 +1,19 @@ +import { Matrix2D } from './types/OpsType.ts'; + +const activationFunction = (matrix: Matrix2D): Matrix2D => + matrix.map((array: number[]): number[] => + array.map((v: number): number => { + const value: number = sigmoid(v); + return parseFloat(value.toFixed(5)); //활성화 함수를 적용시킨 값을 소숫점 5자리로 반올림 + }), + ); + +const sigmoid = ($x: number): number => { + return 1 / (1 + Math.exp(-$x)); +}; + +const ReLU = ($x: number): number => { + return Math.max(0, $x); +}; + +export default activationFunction; diff --git a/src/core/ops/matrixOps.js b/src/core/ops/matrixOps.js deleted file mode 100644 index 41247f3..0000000 --- a/src/core/ops/matrixOps.js +++ /dev/null @@ -1,17 +0,0 @@ -export const matrixMultiply = (A, B) => { - A = Array.isArray(A[0]) ? A : [A]; - - if (!Array.isArray(A) || !Array.isArray(B)) throw new Error('matrixA, B must be an array'); - else if (A[0].length !== B.length) throw new Error('rows and columns length doesn\'t match'); - - return A.map((row, i) => - B[0].map((_, j) => - row.reduce((sum, _, k) => { - return sum + A[i][k]*B[k][j]; - }, 0) - ) - ) -} - -export const transposeMatrix = matrix => - matrix[0].map((_, idx)=> matrix.map(row => row[idx])) \ No newline at end of file diff --git a/src/core/ops/matrixOps.ts b/src/core/ops/matrixOps.ts new file mode 100644 index 0000000..f6eda6e --- /dev/null +++ b/src/core/ops/matrixOps.ts @@ -0,0 +1,25 @@ +import { Matrix2D } from './types/OpsType'; + +const ensure2DArray = (matrix: number[] | number[][]): number[][] => { + return Array.isArray(matrix[0]) ? (matrix as number[][]) : [matrix as unknown as number[]]; +}; + +export const matrixMultiply = (A: Matrix2D, B: Matrix2D): Matrix2D => { + A = ensure2DArray(A); + + if (!Array.isArray(A) || !Array.isArray(B)) throw new Error('matrixA, B must be an array'); + else if (A[0].length !== B.length) throw new Error("rows and columns length doesn't match"); + + return A.map((row: number[], i: number): number[] => + B[0].map((_: unknown, j: number): number => + row.reduce((sum: number, _: unknown, k: number): number => { + return sum + A[i][k] * B[k][j]; + }, 0), + ), + ); +}; + +export const transposeMatrix = (matrix: Matrix2D): Matrix2D => + matrix[0].map((_: unknown, idx: number): number[] => + matrix.map((row: number[]): number => row[idx]), + ); diff --git a/src/core/ops/types/OpsType.ts b/src/core/ops/types/OpsType.ts new file mode 100644 index 0000000..5f6ec6d --- /dev/null +++ b/src/core/ops/types/OpsType.ts @@ -0,0 +1 @@ +export type Matrix2D = number[][]; diff --git a/src/core/types/NeuralNetworkBase.ts b/src/core/types/NeuralNetworkBase.ts new file mode 100644 index 0000000..0c15bab --- /dev/null +++ b/src/core/types/NeuralNetworkBase.ts @@ -0,0 +1,10 @@ +import { Matrix2D } from '@/core/ops/types/OpsType.ts'; + +export interface INeuralNetworkBase { + feedForward(inputs: number[]): { + hiddenInputs: Matrix2D; + hiddenOutputs: Matrix2D; + finalOutputs: Matrix2D; + }; + query(inputs: number[]): Matrix2D; +} diff --git a/src/core/types/WeightManager.ts b/src/core/types/WeightManager.ts new file mode 100644 index 0000000..5fd417f --- /dev/null +++ b/src/core/types/WeightManager.ts @@ -0,0 +1,9 @@ +import { weights } from '@/controller/types/weights.ts'; +import { networkConfig } from '@/controller/constants/types/networkConfig.ts'; + +export interface IWeightManager { + _cache: null | weights; + config: networkConfig; + path: string; + getWeights(): Promise; +} diff --git a/src/core/utils/createRandomWeights.js b/src/core/utils/createRandomWeights.js deleted file mode 100644 index 8ff2a1f..0000000 --- a/src/core/utils/createRandomWeights.js +++ /dev/null @@ -1,7 +0,0 @@ -export const createRandomWeight = (col, row) => - Array.from({ length: col }, () => - Array.from({ length: row }, () => { - const value = (Math.random() - .5) * 1.99999; - return value; - }) - ); \ No newline at end of file diff --git a/src/core/utils/createRandomWeights.ts b/src/core/utils/createRandomWeights.ts new file mode 100644 index 0000000..b5d3086 --- /dev/null +++ b/src/core/utils/createRandomWeights.ts @@ -0,0 +1,7 @@ +export const createRandomWeight = (col: number, row: number): number[][] => + Array.from({ length: col }, (): number[] => + Array.from({ length: row }, (): number => { + const value: number = (Math.random() - 0.5) * 1.99999; + return value; + }), + ); diff --git a/src/core/utils/loadPretrainedWeights.js b/src/core/utils/loadPretrainedWeights.js deleted file mode 100644 index 9eab7ae..0000000 --- a/src/core/utils/loadPretrainedWeights.js +++ /dev/null @@ -1,5 +0,0 @@ -export async function loadPretrainedWeights(path) { - const response = await fetch(path); - if(!response.ok) throw new Error(`Failed to load weight datas from ${path}`); - return await response.json(); -} \ No newline at end of file diff --git a/src/core/utils/loadPretrainedWeights.ts b/src/core/utils/loadPretrainedWeights.ts new file mode 100644 index 0000000..3e89e03 --- /dev/null +++ b/src/core/utils/loadPretrainedWeights.ts @@ -0,0 +1,7 @@ +import { weights } from '@/controller/types/weights'; + +export async function loadPretrainedWeights(path: string): Promise { + const response: Response = await fetch(path); + if (!response.ok) throw new Error(`Failed to load weight data from ${path}`); + return await response.json(); +} diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 807ff3c..0000000 --- a/src/index.js +++ /dev/null @@ -1,6 +0,0 @@ -import AppController from './controller/AppContoller.js'; -import { DataStore } from "./controller/DataStore.js"; - -const App = new AppController({DataStore}); -await App.initialize(); -console.log("App initialized!"); \ No newline at end of file diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..70a5ce8 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,5 @@ +import AppController from './controller/AppController.js'; + +const App = new AppController(); +await App.initialize(); +console.log('App initialized!'); diff --git a/src/view/DataPresnter.ts b/src/view/DataPresnter.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/view/ViewHandler.js b/src/view/ViewHandler.ts similarity index 100% rename from src/view/ViewHandler.js rename to src/view/ViewHandler.ts diff --git a/src/view/components/CanvasComponentBase.js b/src/view/components/CanvasComponentBase.js index bf544ff..2af8a95 100644 --- a/src/view/components/CanvasComponentBase.js +++ b/src/view/components/CanvasComponentBase.js @@ -30,7 +30,6 @@ class CanvasComponentBase { clear() { this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); } - } -export default CanvasComponentBase; \ No newline at end of file +export default CanvasComponentBase; diff --git a/src/view/components/canvasUtils/BoundingBox.js b/src/view/components/canvasUtils/BoundingBox.js deleted file mode 100644 index 7fe4911..0000000 --- a/src/view/components/canvasUtils/BoundingBox.js +++ /dev/null @@ -1,38 +0,0 @@ -class BoundingBox { - constructor() { - this.initialCoords = { - minX: Infinity, - minY: Infinity, - maxX: -Infinity, - maxY: -Infinity, - } - this.coordinate = {...this.initialCoords}; - this.originalObject = {}; - } - - update(currentX, currentY) { - this.coordinate = { - minX: Math.round(Math.min(this.coordinate.minX, currentX)), - minY: Math.round(Math.min(this.coordinate.minY, currentY)), - maxX: Math.round(Math.max(this.coordinate.maxX, currentX)), - maxY: Math.round(Math.max(this.coordinate.maxY, currentY)), - } - Object.assign(this.originalObject, - { - x: this.coordinate.minX-20, - y: this.coordinate.minY-20, - width: this.coordinate.maxX+40 - this.coordinate.minX, - height: this.coordinate.maxY+40 - this.coordinate.minY,}); - } - - log() { - console.log(this.coordinate); - console.log(this.originalObject); - } - - reset() { - this.coordinate = {...this.initialCoords}; - } -} - -export default new BoundingBox; \ No newline at end of file diff --git a/src/view/components/canvasUtils/BoundingBox.ts b/src/view/components/canvasUtils/BoundingBox.ts new file mode 100644 index 0000000..bd7b1b1 --- /dev/null +++ b/src/view/components/canvasUtils/BoundingBox.ts @@ -0,0 +1,53 @@ +import { IinitialCoords, originalObject } from './types'; + +class BoundingBox { + private readonly initialCoords: IinitialCoords; + private coordinate: IinitialCoords; + private originalObject: originalObject; + + constructor() { + this.initialCoords = { + minX: Infinity, + minY: Infinity, + maxX: -Infinity, + maxY: -Infinity, + }; + this.coordinate = { ...this.initialCoords }; + this.originalObject = { + x: 0, + y: 0, + width: 0, + height: 0, + }; + } + + getOriginalObject(): Readonly { + return { ...this.originalObject }; + } + + update(currentX: number, currentY: number): void { + this.coordinate = { + minX: Math.round(Math.min(this.coordinate.minX, currentX)), + minY: Math.round(Math.min(this.coordinate.minY, currentY)), + maxX: Math.round(Math.max(this.coordinate.maxX, currentX)), + maxY: Math.round(Math.max(this.coordinate.maxY, currentY)), + }; + Object.assign(this.originalObject, { + x: this.coordinate.minX - 20, + y: this.coordinate.minY - 20, + width: this.coordinate.maxX + 40 - this.coordinate.minX, + height: this.coordinate.maxY + 40 - this.coordinate.minY, + }); + } + + log(): void { + console.log(this.coordinate); + console.log(this.originalObject); + } + + reset(): void { + this.coordinate = { ...this.initialCoords }; + } +} + +export default new BoundingBox(); diff --git a/src/view/components/canvasUtils/types.ts b/src/view/components/canvasUtils/types.ts new file mode 100644 index 0000000..b9e8646 --- /dev/null +++ b/src/view/components/canvasUtils/types.ts @@ -0,0 +1,19 @@ +export interface IinitialCoords { + minX: number; + minY: number; + maxX: number; + maxY: number; +} + +export interface originalObject { + x: number; + y: number; + width: number; + height: number; +} + +interface BoundingBox { + update(currentX: number, currentY: number): void; + log(): void; + reset(): void; +} diff --git a/src/view/components/perceptron/Perceptron.ts b/src/view/components/perceptron/Perceptron.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/view/components/userQuery/AlignCanvas.js b/src/view/components/userQuery/AlignCanvas.js deleted file mode 100644 index c5ae35e..0000000 --- a/src/view/components/userQuery/AlignCanvas.js +++ /dev/null @@ -1,43 +0,0 @@ -import BoundingBox from "../canvasUtils/BoundingBox.js"; - -class AlignCanvas { - constructor() { - // this.canvas = document.createElement('alignCanvas'); - this.canvas = document.getElementById('alignCanvas'); - this.ctx = this.canvas.getContext('2d', { willReadFrequently: true }); - this.canvas.style.border = '2px solid red'; - - const { minX, minY, maxX, maxY } = BoundingBox.coordinate; - this.setupCanvas(); - } - setupCanvas() { - this.ctx.fillStyle = 'rgba(0, 0, 0)'; - this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); - this.updateCanvasScale(); - } - updateCanvasScale() { - if(BoundingBox.originalObject.width > BoundingBox.originalObject.height){ - this.canvas.width = BoundingBox.originalObject.width*1.3; - this.canvas.height = BoundingBox.originalObject.width*1.3; - } else { - this.canvas.width = BoundingBox.originalObject.height*1.3; - this.canvas.height = BoundingBox.originalObject.height*1.3; - } - } - - centralize(path) { - this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); - const alignStartPosition = { - x: (this.canvas.width - BoundingBox.originalObject.width) / 2, - y: (this.canvas.height - BoundingBox.originalObject.height) / 2, - } - this.ctx.drawImage(path, - BoundingBox.originalObject.x, BoundingBox.originalObject.y, - BoundingBox.originalObject.width, BoundingBox.originalObject.height, - alignStartPosition.x, alignStartPosition.y, - BoundingBox.originalObject.width, BoundingBox.originalObject.height - ); - } -} - -export default AlignCanvas; \ No newline at end of file diff --git a/src/view/components/userQuery/AlignCanvas.ts b/src/view/components/userQuery/AlignCanvas.ts new file mode 100644 index 0000000..691f45c --- /dev/null +++ b/src/view/components/userQuery/AlignCanvas.ts @@ -0,0 +1,53 @@ +import BoundingBox from '../canvasUtils/BoundingBox.ts'; + +class AlignCanvas { + public readonly canvas: HTMLCanvasElement | null; + public readonly ctx: CanvasRenderingContext2D; + + constructor() { + this.canvas = document.createElement('canvas') as HTMLCanvasElement; + this.ctx = this.canvas.getContext('2d', { willReadFrequently: true }); + this.canvas.style.border = '2px solid red'; + this.setupCanvas(); + } + setupCanvas(): void { + this.ctx.fillStyle = 'rgba(0, 0, 0)'; + this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); + this.updateCanvasScale(); + } + updateCanvasScale(): void { + if (BoundingBox.getOriginalObject().width > BoundingBox.getOriginalObject().height) { + this.canvas.width = BoundingBox.getOriginalObject().width * 1.3; + this.canvas.height = BoundingBox.getOriginalObject().width * 1.3; + } else { + this.canvas.width = BoundingBox.getOriginalObject().height * 1.3; + this.canvas.height = BoundingBox.getOriginalObject().height * 1.3; + } + } + + centralize(path: HTMLCanvasElement): void { + this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); + const alignStartPosition = { + x: (this.canvas.width - BoundingBox.getOriginalObject().width) / 2, + y: (this.canvas.height - BoundingBox.getOriginalObject().height) / 2, + }; + this.ctx.drawImage( + path, + BoundingBox.getOriginalObject().x, + BoundingBox.getOriginalObject().y, + BoundingBox.getOriginalObject().width, + BoundingBox.getOriginalObject().height, + alignStartPosition.x, + alignStartPosition.y, + BoundingBox.getOriginalObject().width, + BoundingBox.getOriginalObject().height, + ); + } + + clear(): void { + this.canvas.width = this.canvas.height = 1; + this.setupCanvas(); + } +} + +export default AlignCanvas; diff --git a/src/view/components/userQuery/PathTrackingCanvas.js b/src/view/components/userQuery/PathTrackingCanvas.ts similarity index 55% rename from src/view/components/userQuery/PathTrackingCanvas.js rename to src/view/components/userQuery/PathTrackingCanvas.ts index 2f0248e..bd2b4b2 100644 --- a/src/view/components/userQuery/PathTrackingCanvas.js +++ b/src/view/components/userQuery/PathTrackingCanvas.ts @@ -1,16 +1,18 @@ -import { CANVAS_CONFIG } from "../../../controller/constants/canvasConfig.js"; +import { CANVAS_CONFIG } from '@/controller/constants/canvasConfig.ts'; class PathTrackingCanvas { + public readonly canvas: HTMLCanvasElement | null; + public readonly ctx: CanvasRenderingContext2D; + constructor() { - // this.strokeCanvas = document.createElement('pathTrackingCanvas'); - this.canvas = document.getElementById('pathTrackingCanvas'); + this.canvas = document.createElement('canvas') as HTMLCanvasElement; this.ctx = this.canvas.getContext('2d', { willReadFrequently: true }); - this.setupCanvas() + this.setupCanvas(); } - setupCanvas() { + setupCanvas(): void { this.canvas.width = window.innerWidth; - this.canvas.height = window.innerHeight / 3; + this.canvas.height = window.innerHeight / 3; this.ctx.fillStyle = 'rgba(0, 0, 0)'; this.ctx.strokeStyle = 'rgba(255, 255, 255)'; @@ -22,19 +24,23 @@ class PathTrackingCanvas { this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); } - startPath(x, y) { + startPath(x: number, y: number): void { this.ctx.beginPath(); this.ctx.moveTo(x, y); } - drawPath(x, y) { + drawPath(x: number, y: number): void { this.ctx.lineTo(x, y); this.ctx.stroke(); } - endPath() { + endPath(): void { this.ctx.closePath(); } + + clear(): void { + this.setupCanvas(); + } } -export default PathTrackingCanvas; \ No newline at end of file +export default PathTrackingCanvas; diff --git a/src/view/components/userQuery/ResizeCanvas.js b/src/view/components/userQuery/ResizeCanvas.js deleted file mode 100644 index c424a57..0000000 --- a/src/view/components/userQuery/ResizeCanvas.js +++ /dev/null @@ -1,15 +0,0 @@ -class ResizeCanvas { - constructor() { - this.canvas = document.getElementById("resizeCanvas"); - this.ctx = this.canvas.getContext("2d", { willReadFrequently: true }); - this.canvas.width = this.canvas.height = 28; - } - - downScale(path) { - this.ctx.fillStyle = 'rgba(255,255,255)'; - this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); - this.ctx.drawImage(path, 0, 0, path.width, path.height, 0, 0, this.canvas.width, this.canvas.height); - } -} - -export default ResizeCanvas; \ No newline at end of file diff --git a/src/view/components/userQuery/ResizeCanvas.ts b/src/view/components/userQuery/ResizeCanvas.ts new file mode 100644 index 0000000..96a4275 --- /dev/null +++ b/src/view/components/userQuery/ResizeCanvas.ts @@ -0,0 +1,37 @@ +class ResizeCanvas { + public readonly canvas: HTMLCanvasElement | null; + public readonly ctx: CanvasRenderingContext2D; + + constructor() { + this.canvas = document.createElement('canvas') as HTMLCanvasElement; + this.ctx = this.canvas.getContext('2d', { willReadFrequently: true }); + this.setupCanvas(); + } + + setupCanvas(): void { + this.canvas.width = this.canvas.height = 28; + this.ctx.fillStyle = 'rgba(255,255,255)'; + this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); + } + + downScale(path: HTMLCanvasElement): void { + this.ctx.drawImage( + path, + 0, + 0, + path.width, + path.height, + 0, + 0, + this.canvas.width, + this.canvas.height, + ); + } + + clear(): void { + this.setupCanvas(); + // this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); + } +} + +export default ResizeCanvas; diff --git a/src/view/components/userQuery/UserInputCanvas.js b/src/view/components/userQuery/UserInputCanvas.ts similarity index 72% rename from src/view/components/userQuery/UserInputCanvas.js rename to src/view/components/userQuery/UserInputCanvas.ts index 6693753..564819a 100644 --- a/src/view/components/userQuery/UserInputCanvas.js +++ b/src/view/components/userQuery/UserInputCanvas.ts @@ -1,8 +1,12 @@ -import { CANVAS_CONFIG } from "../../../controller/constants/canvasConfig.js"; +import { CANVAS_CONFIG } from '@/controller/constants/canvasConfig.ts'; class UserInputCanvas { + public readonly canvas: HTMLCanvasElement | null; + public readonly ctx: CanvasRenderingContext2D; + private isDrawing: boolean; + constructor() { - this.canvas = document.getElementById('userInputCanvas'); + this.canvas = document.getElementById('userInputCanvas') as HTMLCanvasElement; this.ctx = this.canvas.getContext('2d'); this.isDrawing = false; this.setupCanvas(); @@ -13,11 +17,13 @@ class UserInputCanvas { this.ctx.lineJoin = CANVAS_CONFIG.lineJoin; } - clear = () => { + clear = (): void => { + this.ctx.fillStyle = 'rgba(40,40,40)'; this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); - } + this.drawGridDots(); + }; - setupCanvas() { + setupCanvas(): void { this.canvas.width = window.innerWidth; this.canvas.height = window.innerHeight / 3; @@ -33,7 +39,7 @@ class UserInputCanvas { this.ctx.fillStyle = 'rgba(255,255,255,0.3)'; this.ctx.arc(x, y, 1, 0, 2 * Math.PI); this.ctx.fill(); - } + }; for (let x = 5; x < this.canvas.width; x += 12) { for (let y = 5; y < this.canvas.height; y += 12) { @@ -42,19 +48,19 @@ class UserInputCanvas { } } - startPath(x, y) { + startPath(x: number, y: number): void { this.ctx.beginPath(); this.ctx.moveTo(x, y); } - drawPath(x, y) { + drawPath(x: number, y: number): void { this.ctx.lineTo(x, y); this.ctx.stroke(); } - endPath() { + endPath(): void { this.ctx.closePath(); } } -export default UserInputCanvas; \ No newline at end of file +export default UserInputCanvas; diff --git a/src/view/components/userQuery/types/canvas.ts b/src/view/components/userQuery/types/canvas.ts new file mode 100644 index 0000000..7d0936a --- /dev/null +++ b/src/view/components/userQuery/types/canvas.ts @@ -0,0 +1,23 @@ +export interface ICanvasBase { + setupCanvas(): void; + clear(): void; + canvas: HTMLCanvasElement; + ctx: CanvasRenderingContext2D; +} + +export interface IUserInputCanvas extends IPathTrackingCanvas { + drawGridDots(): void; +} +export interface IPathTrackingCanvas { + startPath(x: number, y: number): void; + drawPath(x: number, y: number): void; + endPath(): void; +} +export interface IAlignCanvas { + updateCanvasScale(): void; + centralize(path: HTMLCanvasElement): void; +} + +export interface IResizeCanvas { + downScale(path: HTMLCanvasElement): void; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e5e1ea1 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ES2022", + "moduleResolution": "Node", + "allowImportingTsExtensions": true, + "noEmit": true, + "outDir": "./dist", + "rootDir": "./src", + "strict": false, + "allowJs": true, + "checkJs": false, + "esModuleInterop": true, + "baseUrl": "./src", + "paths": { + "@/*": ["*"] + } + }, + "include": ["src/**/*"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..1f6b4b0 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + root: './src', + resolve: { + alias: [{ find: '@', replacement: path.resolve(__dirname, 'src') }], + }, + publicDir: 'public', + build: { + outDir: 'dist', + target: 'esnext', + }, + server: { + port: 5173, + open: true, + }, +});