Squashed commit of the following:
commit d9209a78cff1c05be3e6a87e27cd1a5a4d5f91c5 Author: GuillaumeSD <47183782+GuillaumeSD@users.noreply.github.com> Date: Wed Jul 24 11:55:35 2024 +0200 style : UI analysis panel adjustment commit 3c2e19bdb9d97f3bb7e8ceaefd630aad64d755c4 Author: GuillaumeSD <47183782+GuillaumeSD@users.noreply.github.com> Date: Wed Jul 24 11:10:07 2024 +0200 feat : graph dot color match move classification commit 4a99ccb2fe19d3806ff320370ebc55af984d719a Author: GuillaumeSD <47183782+GuillaumeSD@users.noreply.github.com> Date: Wed Jul 24 11:09:35 2024 +0200 fix : load pgn with no moves commit 9eeb0e7f2869e544700b7da963b74f707fa6ea2f Author: GuillaumeSD <47183782+GuillaumeSD@users.noreply.github.com> Date: Wed Jul 24 00:09:03 2024 +0200 feat : add current move reference line in graph commit febb9962a0b366aeac1dc266e0470b75bd619e68 Author: GuillaumeSD <47183782+GuillaumeSD@users.noreply.github.com> Date: Tue Jul 23 23:08:17 2024 +0200 fix : handle tab change on new game commit a105239a728dc05211a0ae99d8fd56f179108a0e Author: GuillaumeSD <47183782+GuillaumeSD@users.noreply.github.com> Date: Tue Jul 23 03:46:49 2024 +0200 style : small chart UI tweaks commit 4878ebf87b4ddbac75db70619fe452a3a317ca09 Author: GuillaumeSD <47183782+GuillaumeSD@users.noreply.github.com> Date: Tue Jul 23 03:38:40 2024 +0200 feat : add eval graph commit 29c5a001da03ee288d2a2c133426b1d2ca435930 Author: GuillaumeSD <47183782+GuillaumeSD@users.noreply.github.com> Date: Tue Jul 23 00:30:25 2024 +0200 refacto : analysis directory commit a8b966cc07152bb117b8c68f54af3498ca2a5d2f Author: GuillaumeSD <47183782+GuillaumeSD@users.noreply.github.com> Date: Tue Jul 23 00:07:07 2024 +0200 style : add settings floating button commit 7edc54f09ce7d4b4c4beb310a9c7f985363ff5ee Author: GuillaumeSD <47183782+GuillaumeSD@users.noreply.github.com> Date: Sun Jul 21 22:29:48 2024 +0200 feat : tab analysis panel
This commit is contained in:
304
package-lock.json
generated
304
package-lock.json
generated
@@ -23,7 +23,8 @@
|
||||
"next": "14.2.5",
|
||||
"react": "18.2.0",
|
||||
"react-chessboard": "^4.6.0",
|
||||
"react-dom": "18.2.0"
|
||||
"react-dom": "18.2.0",
|
||||
"recharts": "^2.12.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "20.3.0",
|
||||
@@ -1699,6 +1700,69 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-array": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
|
||||
"integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-color": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
|
||||
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-ease": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
|
||||
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-interpolate": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
|
||||
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/d3-color": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-path": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz",
|
||||
"integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-scale": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz",
|
||||
"integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/d3-time": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-shape": {
|
||||
"version": "3.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz",
|
||||
"integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/d3-path": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-time": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz",
|
||||
"integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-timer": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
|
||||
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/json-schema": {
|
||||
"version": "7.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||
@@ -2430,6 +2494,127 @@
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
|
||||
},
|
||||
"node_modules/d3-array": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"internmap": "1 - 2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-color": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
|
||||
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-ease": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
|
||||
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-format": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
|
||||
"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-interpolate": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
|
||||
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-color": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-path": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
|
||||
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-scale": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
|
||||
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-array": "2.10.0 - 3",
|
||||
"d3-format": "1 - 3",
|
||||
"d3-interpolate": "1.2.0 - 3",
|
||||
"d3-time": "2.1.1 - 3",
|
||||
"d3-time-format": "2 - 4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-shape": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
|
||||
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-path": "^3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-time": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
|
||||
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-array": "2 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-time-format": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
|
||||
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-time": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-timer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
|
||||
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/damerau-levenshtein": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
|
||||
@@ -2503,6 +2688,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/decimal.js-light": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
|
||||
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/deep-equal": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz",
|
||||
@@ -3405,6 +3596,12 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
@@ -3416,6 +3613,15 @@
|
||||
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fast-equals": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz",
|
||||
"integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
|
||||
@@ -3947,6 +4153,15 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/internmap": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
|
||||
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/is-arguments": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
|
||||
@@ -4507,6 +4722,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.camelcase": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
||||
@@ -5155,6 +5376,21 @@
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
|
||||
},
|
||||
"node_modules/react-smooth": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.1.tgz",
|
||||
"integrity": "sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-equals": "^5.0.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-transition-group": "^4.4.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-transition-group": {
|
||||
"version": "4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||
@@ -5170,6 +5406,44 @@
|
||||
"react-dom": ">=16.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/recharts": {
|
||||
"version": "2.12.7",
|
||||
"resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.7.tgz",
|
||||
"integrity": "sha512-hlLJMhPQfv4/3NBSAyq3gzGg4h2v69RJh6KU7b3pXYNNAELs9kEoXOjbkxdXpALqKBoVmVptGfLpxdaVYqjmXQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"clsx": "^2.0.0",
|
||||
"eventemitter3": "^4.0.1",
|
||||
"lodash": "^4.17.21",
|
||||
"react-is": "^16.10.2",
|
||||
"react-smooth": "^4.0.0",
|
||||
"recharts-scale": "^0.4.4",
|
||||
"tiny-invariant": "^1.3.1",
|
||||
"victory-vendor": "^36.6.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/recharts-scale": {
|
||||
"version": "0.4.5",
|
||||
"resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz",
|
||||
"integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"decimal.js-light": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/recharts/node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/redux": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
|
||||
@@ -5738,6 +6012,12 @@
|
||||
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/tiny-invariant": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
||||
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
@@ -5941,6 +6221,28 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/victory-vendor": {
|
||||
"version": "36.9.2",
|
||||
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",
|
||||
"integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==",
|
||||
"license": "MIT AND ISC",
|
||||
"dependencies": {
|
||||
"@types/d3-array": "^3.0.3",
|
||||
"@types/d3-ease": "^3.0.0",
|
||||
"@types/d3-interpolate": "^3.0.1",
|
||||
"@types/d3-scale": "^4.0.2",
|
||||
"@types/d3-shape": "^3.1.0",
|
||||
"@types/d3-time": "^3.0.0",
|
||||
"@types/d3-timer": "^3.0.0",
|
||||
"d3-array": "^3.1.6",
|
||||
"d3-ease": "^3.0.1",
|
||||
"d3-interpolate": "^3.0.1",
|
||||
"d3-scale": "^4.0.2",
|
||||
"d3-shape": "^3.1.0",
|
||||
"d3-time": "^3.0.0",
|
||||
"d3-timer": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/websocket-driver": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
"next": "14.2.5",
|
||||
"react": "18.2.0",
|
||||
"react-chessboard": "^4.6.0",
|
||||
"react-dom": "18.2.0"
|
||||
"react-dom": "18.2.0",
|
||||
"recharts": "^2.12.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "20.3.0",
|
||||
|
||||
@@ -11,10 +11,11 @@ import { useChessActions } from "@/hooks/useChessActions";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Color, MoveClassification } from "@/types/enums";
|
||||
import { Chess } from "chess.js";
|
||||
import { getSquareRenderer, moveClassificationColors } from "./squareRenderer";
|
||||
import { getSquareRenderer } from "./squareRenderer";
|
||||
import { CurrentPosition } from "@/types/eval";
|
||||
import EvaluationBar from "./evaluationBar";
|
||||
import CapturedPieces from "./capturedPieces";
|
||||
import { moveClassificationColors } from "@/lib/chess";
|
||||
|
||||
export interface Props {
|
||||
id: string;
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
CustomSquareProps,
|
||||
Square,
|
||||
} from "react-chessboard/dist/chessboard/types";
|
||||
import { moveClassificationColors } from "@/lib/chess";
|
||||
|
||||
export interface Props {
|
||||
currentPositionAtom: PrimitiveAtom<CurrentPosition>;
|
||||
@@ -74,18 +75,6 @@ export function getSquareRenderer({
|
||||
return squareRenderer;
|
||||
}
|
||||
|
||||
export const moveClassificationColors: Record<MoveClassification, string> = {
|
||||
[MoveClassification.Book]: "#d5a47d",
|
||||
[MoveClassification.Brilliant]: "#26c2a3",
|
||||
[MoveClassification.Great]: "#4099ed",
|
||||
[MoveClassification.Best]: "#3aab18",
|
||||
[MoveClassification.Excellent]: "#3aab18",
|
||||
[MoveClassification.Good]: "#81b64c",
|
||||
[MoveClassification.Inaccuracy]: "#f7c631",
|
||||
[MoveClassification.Mistake]: "#ffa459",
|
||||
[MoveClassification.Blunder]: "#fa412d",
|
||||
};
|
||||
|
||||
const rightClickSquareStyle: CSSProperties = {
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
|
||||
@@ -37,6 +37,15 @@ export const useChessActions = (chessAtom: PrimitiveAtom<Chess>) => {
|
||||
|
||||
const copyGame = useCallback(() => {
|
||||
const newGame = new Chess();
|
||||
|
||||
if (game.history().length === 0) {
|
||||
const pgnSplitted = game.pgn().split("]");
|
||||
if (pgnSplitted.at(-1)?.includes("1-0")) {
|
||||
newGame.loadPgn(pgnSplitted.slice(0, -1).join("]") + "]");
|
||||
return newGame;
|
||||
}
|
||||
}
|
||||
|
||||
newGame.loadPgn(game.pgn());
|
||||
return newGame;
|
||||
}, [game]);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { EvaluateGameParams, PositionEval } from "@/types/eval";
|
||||
import { EvaluateGameParams, LineEval, PositionEval } from "@/types/eval";
|
||||
import { Game } from "@/types/game";
|
||||
import { Chess, PieceSymbol, Square } from "chess.js";
|
||||
import { getPositionWinPercentage } from "./engine/helpers/winPercentage";
|
||||
import { Color } from "@/types/enums";
|
||||
import { Color, MoveClassification } from "@/types/enums";
|
||||
|
||||
export const getEvaluateGameParams = (game: Chess): EvaluateGameParams => {
|
||||
const history = game.history({ verbose: true });
|
||||
@@ -315,3 +315,29 @@ export const getCapturedPieces = (
|
||||
|
||||
return capturedPieces;
|
||||
};
|
||||
|
||||
export const getLineEvalLabel = (
|
||||
line: Pick<LineEval, "cp" | "mate">
|
||||
): string => {
|
||||
if (line.cp !== undefined) {
|
||||
return `${line.cp > 0 ? "+" : ""}${(line.cp / 100).toFixed(2)}`;
|
||||
}
|
||||
|
||||
if (line.mate) {
|
||||
return `${line.mate > 0 ? "+" : "-"}M${Math.abs(line.mate)}`;
|
||||
}
|
||||
|
||||
return "?";
|
||||
};
|
||||
|
||||
export const moveClassificationColors: Record<MoveClassification, string> = {
|
||||
[MoveClassification.Book]: "#d5a47d",
|
||||
[MoveClassification.Brilliant]: "#26c2a3",
|
||||
[MoveClassification.Great]: "#4099ed",
|
||||
[MoveClassification.Best]: "#3aab18",
|
||||
[MoveClassification.Excellent]: "#3aab18",
|
||||
[MoveClassification.Good]: "#81b64c",
|
||||
[MoveClassification.Inaccuracy]: "#f7c631",
|
||||
[MoveClassification.Mistake]: "#ffa459",
|
||||
[MoveClassification.Blunder]: "#fa412d",
|
||||
};
|
||||
|
||||
@@ -1,28 +1,42 @@
|
||||
import { useChessActions } from "@/hooks/useChessActions";
|
||||
import Board from "@/sections/analysis/board";
|
||||
import ReviewPanelBody from "@/sections/analysis/reviewPanelBody";
|
||||
import ReviewPanelHeader from "@/sections/analysis/reviewPanelHeader";
|
||||
import ReviewPanelToolBar from "@/sections/analysis/reviewPanelToolbar";
|
||||
import PanelHeader from "@/sections/analysis/panelHeader";
|
||||
import PanelToolBar from "@/sections/analysis/panelToolbar";
|
||||
import AnalysisTab from "@/sections/analysis/panelBody/analysisTab";
|
||||
import ClassificationTab from "@/sections/analysis/panelBody/classificationTab";
|
||||
import {
|
||||
boardAtom,
|
||||
boardOrientationAtom,
|
||||
gameAtom,
|
||||
gameEvalAtom,
|
||||
} from "@/sections/analysis/states";
|
||||
import { Divider, Grid, useMediaQuery, useTheme } from "@mui/material";
|
||||
import {
|
||||
Box,
|
||||
Divider,
|
||||
Grid,
|
||||
Tab,
|
||||
Tabs,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { Chess } from "chess.js";
|
||||
import { useAtom, useSetAtom } from "jotai";
|
||||
import { useAtom, useAtomValue, useSetAtom } from "jotai";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
import ClassificationPanel from "@/sections/analysis/reviewPanelBody/classificationPanel";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Icon } from "@iconify/react";
|
||||
import EngineSettingsButton from "@/sections/engineSettings/engineSettingsButton";
|
||||
import GraphTab from "@/sections/analysis/panelBody/graphTab";
|
||||
|
||||
export default function GameReport() {
|
||||
export default function GameReview() {
|
||||
const theme = useTheme();
|
||||
const [tab, setTab] = useState(0);
|
||||
const isLgOrGreater = useMediaQuery(theme.breakpoints.up("lg"));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
|
||||
const { reset: resetBoard } = useChessActions(boardAtom);
|
||||
const { setPgn: setGamePgn } = useChessActions(gameAtom);
|
||||
const [gameEval, setGameEval] = useAtom(gameEvalAtom);
|
||||
const game = useAtomValue(gameAtom);
|
||||
const setBoardOrientation = useSetAtom(boardOrientationAtom);
|
||||
|
||||
const router = useRouter();
|
||||
@@ -37,13 +51,22 @@ export default function GameReport() {
|
||||
}
|
||||
}, [gameId, setGameEval, setBoardOrientation, resetBoard, setGamePgn]);
|
||||
|
||||
const isGameLoaded = game.history().length > 0;
|
||||
|
||||
useEffect(() => {
|
||||
if (tab === 1 && !isGameLoaded) setTab(0);
|
||||
if (tab === 2 && !gameEval) setTab(0);
|
||||
}, [isGameLoaded, gameEval, tab]);
|
||||
|
||||
return (
|
||||
<Grid container gap={4} justifyContent="space-evenly" alignItems="center">
|
||||
<Grid container gap={4} justifyContent="space-evenly" alignItems="start">
|
||||
<Board />
|
||||
|
||||
<Grid
|
||||
container
|
||||
item
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
borderRadius={2}
|
||||
border={1}
|
||||
borderColor={"secondary.main"}
|
||||
@@ -59,52 +82,113 @@ export default function GameReport() {
|
||||
style={{
|
||||
maxWidth: "1200px",
|
||||
}}
|
||||
rowGap={2}
|
||||
maxHeight={{ lg: "calc(95vh - 130px)", xs: "900px" }}
|
||||
display="grid"
|
||||
gridTemplateRows="repeat(4, auto) fit-content(100%)"
|
||||
marginTop={isLgOrGreater && window.innerHeight > 780 ? 4 : 0}
|
||||
>
|
||||
<Grid
|
||||
container
|
||||
item
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
xs={12}
|
||||
rowGap={2}
|
||||
maxHeight={{ lg: "calc(95vh - 130px)", xs: "900px" }}
|
||||
display="grid"
|
||||
gridTemplateRows="repeat(4, auto) fit-content(100%)"
|
||||
{isLgOrGreater ? (
|
||||
<PanelHeader key="analysis-panel-header" />
|
||||
) : (
|
||||
<PanelToolBar key="review-panel-toolbar" />
|
||||
)}
|
||||
|
||||
{!isLgOrGreater && !gameEval && <Divider sx={{ marginX: "5%" }} />}
|
||||
{!isLgOrGreater && !gameEval && (
|
||||
<PanelHeader key="analysis-panel-header" />
|
||||
)}
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
borderBottom: 1,
|
||||
borderColor: "divider",
|
||||
marginX: { sm: "5%", xs: undefined },
|
||||
}}
|
||||
>
|
||||
{isLgOrGreater ? (
|
||||
<>
|
||||
<ReviewPanelHeader key="analysis-panel-header" />
|
||||
<Tabs
|
||||
value={tab}
|
||||
onChange={(_, newValue) => setTab(newValue)}
|
||||
aria-label="basic tabs example"
|
||||
variant="fullWidth"
|
||||
>
|
||||
<Tab
|
||||
label="Analysis"
|
||||
id="tab0"
|
||||
icon={
|
||||
<Icon
|
||||
icon="mdi:magnify"
|
||||
color="#27f019"
|
||||
height={isMobile ? 15 : 20}
|
||||
/>
|
||||
}
|
||||
iconPosition="start"
|
||||
sx={{ textTransform: "none", minHeight: 20, paddingX: 0 }}
|
||||
disableFocusRipple
|
||||
/>
|
||||
|
||||
<Divider sx={{ marginX: "5%" }} />
|
||||
<Tab
|
||||
label="Moves"
|
||||
id="tab1"
|
||||
icon={
|
||||
<Icon
|
||||
icon="mdi:format-list-bulleted"
|
||||
color="#27f019"
|
||||
height={isMobile ? 15 : 20}
|
||||
/>
|
||||
}
|
||||
iconPosition="start"
|
||||
sx={{
|
||||
textTransform: "none",
|
||||
minHeight: 20,
|
||||
display: isGameLoaded ? undefined : "none",
|
||||
paddingX: 0,
|
||||
}}
|
||||
disableFocusRipple
|
||||
/>
|
||||
|
||||
<ReviewPanelBody key="review-panel-body" />
|
||||
<Tab
|
||||
label="Graph"
|
||||
id="tab2"
|
||||
icon={
|
||||
<Icon
|
||||
icon="mdi:chart-line"
|
||||
color="#27f019"
|
||||
height={isMobile ? 15 : 20}
|
||||
/>
|
||||
}
|
||||
iconPosition="start"
|
||||
sx={{
|
||||
textTransform: "none",
|
||||
minHeight: 20,
|
||||
display: gameEval ? undefined : "none",
|
||||
paddingX: 0,
|
||||
}}
|
||||
disableFocusRipple
|
||||
/>
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
<ClassificationPanel key="review-panel-class" />
|
||||
<AnalysisTab role="tabpanel" hidden={tab !== 0} id="tabContent0" />
|
||||
|
||||
<Divider sx={{ marginX: "5%" }} />
|
||||
<ClassificationTab
|
||||
role="tabpanel"
|
||||
hidden={tab !== 1}
|
||||
id="tabContent1"
|
||||
/>
|
||||
|
||||
<ReviewPanelToolBar key="review-panel-toolbar" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ReviewPanelToolBar key="review-panel-toolbar" />
|
||||
<GraphTab role="tabpanel" hidden={tab !== 2} id="tabContent2" />
|
||||
|
||||
<Divider sx={{ marginX: "5%" }} />
|
||||
{isLgOrGreater && <Divider sx={{ marginX: "5%" }} />}
|
||||
{isLgOrGreater && <PanelToolBar key="review-panel-toolbar" />}
|
||||
|
||||
{!gameEval && <ReviewPanelHeader key="analysis-panel-header" />}
|
||||
{!gameEval && <Divider sx={{ marginX: "5%" }} />}
|
||||
|
||||
<ReviewPanelBody key="review-panel-body" />
|
||||
|
||||
<ClassificationPanel key="review-panel-class" />
|
||||
|
||||
{gameEval && <Divider sx={{ marginX: "5%" }} />}
|
||||
{gameEval && <ReviewPanelHeader key="analysis-panel-header" />}
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
{!isLgOrGreater && gameEval && <Divider sx={{ marginX: "5%" }} />}
|
||||
{!isLgOrGreater && gameEval && (
|
||||
<PanelHeader key="analysis-panel-header" />
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
<EngineSettingsButton />
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -50,7 +50,13 @@ export const useCurrentPosition = (engineName?: EngineName) => {
|
||||
|
||||
setCurrentPosition(position);
|
||||
|
||||
if (!position.eval && engine?.isReady() && engineName) {
|
||||
if (
|
||||
!position.eval &&
|
||||
engine?.isReady() &&
|
||||
engineName &&
|
||||
!board.isCheckmate() &&
|
||||
!board.isStalemate()
|
||||
) {
|
||||
const getFenEngineEval = async (
|
||||
fen: string,
|
||||
setPartialEval?: (positionEval: PositionEval) => void
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Grid, Typography } from "@mui/material";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { gameEvalAtom } from "../states";
|
||||
import { gameEvalAtom } from "../../states";
|
||||
|
||||
export default function Accuracies() {
|
||||
const gameEval = useAtomValue(gameEvalAtom);
|
||||
@@ -1,21 +1,19 @@
|
||||
import { Icon } from "@iconify/react";
|
||||
import { Grid, List, Typography } from "@mui/material";
|
||||
import { Grid, GridProps, List, Typography } from "@mui/material";
|
||||
import { useAtomValue } from "jotai";
|
||||
import {
|
||||
boardAtom,
|
||||
engineMultiPvAtom,
|
||||
engineNameAtom,
|
||||
gameAtom,
|
||||
} from "../states";
|
||||
} from "../../states";
|
||||
import LineEvaluation from "./lineEvaluation";
|
||||
import { useCurrentPosition } from "../hooks/useCurrentPosition";
|
||||
import { useCurrentPosition } from "../../hooks/useCurrentPosition";
|
||||
import { LineEval } from "@/types/eval";
|
||||
import EngineSettingsButton from "@/sections/engineSettings/engineSettingsButton";
|
||||
import Accuracies from "./accuracies";
|
||||
import MoveInfo from "./moveInfo";
|
||||
import Opening from "./opening";
|
||||
|
||||
export default function ReviewPanelBody() {
|
||||
export default function AnalysisTab(props: GridProps) {
|
||||
const linesNumber = useAtomValue(engineMultiPvAtom);
|
||||
const engineName = useAtomValue(engineNameAtom);
|
||||
const position = useCurrentPosition(engineName);
|
||||
@@ -45,41 +43,16 @@ export default function ReviewPanelBody() {
|
||||
container
|
||||
xs={12}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
alignItems="start"
|
||||
height="100%"
|
||||
rowGap={1.2}
|
||||
{...props}
|
||||
sx={
|
||||
props.hidden
|
||||
? { display: "none" }
|
||||
: { overflow: "hidden", overflowY: "auto", ...props.sx }
|
||||
}
|
||||
>
|
||||
<Grid
|
||||
item
|
||||
container
|
||||
xs={12}
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
>
|
||||
<Grid item xs={1} />
|
||||
|
||||
<Grid
|
||||
item
|
||||
container
|
||||
xs
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
columnGap={1}
|
||||
>
|
||||
<Icon
|
||||
icon="pepicons-pop:star-filled-circle"
|
||||
color="#27f019"
|
||||
height={25}
|
||||
/>
|
||||
<Typography variant="h6" align="center" lineHeight="25px">
|
||||
Engine evaluation
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
||||
<Grid item container xs={1} justifyContent="center">
|
||||
<EngineSettingsButton />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Accuracies />
|
||||
|
||||
<MoveInfo />
|
||||
@@ -1,8 +1,8 @@
|
||||
import { LineEval } from "@/types/eval";
|
||||
import { ListItem, Skeleton, Typography } from "@mui/material";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { boardAtom } from "../states";
|
||||
import { moveLineUciToSan } from "@/lib/chess";
|
||||
import { boardAtom } from "../../states";
|
||||
import { getLineEvalLabel, moveLineUciToSan } from "@/lib/chess";
|
||||
|
||||
interface Props {
|
||||
line: LineEval;
|
||||
@@ -10,12 +10,7 @@ interface Props {
|
||||
|
||||
export default function LineEvaluation({ line }: Props) {
|
||||
const board = useAtomValue(boardAtom);
|
||||
const lineLabel =
|
||||
line.cp !== undefined
|
||||
? `${line.cp > 0 ? "+" : ""}${(line.cp / 100).toFixed(2)}`
|
||||
: line.mate
|
||||
? `${line.mate > 0 ? "+" : "-"}M${Math.abs(line.mate)}`
|
||||
: "?";
|
||||
const lineLabel = getLineEvalLabel(line);
|
||||
|
||||
const isBlackCp =
|
||||
(line.cp !== undefined && line.cp < 0) ||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Grid, Typography } from "@mui/material";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { boardAtom, currentPositionAtom } from "../states";
|
||||
import { boardAtom, currentPositionAtom } from "../../states";
|
||||
import { useMemo } from "react";
|
||||
import { moveLineUciToSan } from "@/lib/chess";
|
||||
import { MoveClassification } from "@/types/enums";
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCurrentPosition } from "../hooks/useCurrentPosition";
|
||||
import { useCurrentPosition } from "../../hooks/useCurrentPosition";
|
||||
import { Grid, Typography } from "@mui/material";
|
||||
|
||||
export default function Opening() {
|
||||
24
src/sections/analysis/panelBody/classificationTab/index.tsx
Normal file
24
src/sections/analysis/panelBody/classificationTab/index.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Grid, GridProps } from "@mui/material";
|
||||
import MovesPanel from "./movesPanel";
|
||||
import MovesClassificationsRecap from "./movesClassificationsRecap";
|
||||
|
||||
export default function ClassificationTab(props: GridProps) {
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
item
|
||||
justifyContent="center"
|
||||
alignItems="start"
|
||||
height="100%"
|
||||
maxHeight="18rem"
|
||||
{...props}
|
||||
sx={
|
||||
props.hidden ? { display: "none" } : { overflow: "hidden", ...props.sx }
|
||||
}
|
||||
>
|
||||
<MovesPanel />
|
||||
|
||||
<MovesClassificationsRecap />
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
@@ -3,10 +3,10 @@ import { Grid, Typography } from "@mui/material";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { boardAtom, gameAtom, gameEvalAtom } from "../../../states";
|
||||
import { useMemo } from "react";
|
||||
import { moveClassificationColors } from "@/components/board/squareRenderer";
|
||||
import Image from "next/image";
|
||||
import { capitalize } from "@/lib/helpers";
|
||||
import { useChessActions } from "@/hooks/useChessActions";
|
||||
import { moveClassificationColors } from "@/lib/chess";
|
||||
|
||||
interface Props {
|
||||
classification: MoveClassification;
|
||||
@@ -1,12 +1,12 @@
|
||||
import { MoveClassification } from "@/types/enums";
|
||||
import { Grid, Typography } from "@mui/material";
|
||||
import { moveClassificationColors } from "@/components/board/squareRenderer";
|
||||
import Image from "next/image";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { boardAtom, currentPositionAtom, gameAtom } from "../../../states";
|
||||
import { useChessActions } from "@/hooks/useChessActions";
|
||||
import { useEffect } from "react";
|
||||
import { isInViewport } from "@/lib/helpers";
|
||||
import { moveClassificationColors } from "@/lib/chess";
|
||||
|
||||
interface Props {
|
||||
san: string;
|
||||
39
src/sections/analysis/panelBody/graphTab/dot.tsx
Normal file
39
src/sections/analysis/panelBody/graphTab/dot.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { DotProps } from "recharts";
|
||||
import { ChartItemData } from "./types";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { boardAtom, gameAtom } from "../../states";
|
||||
import { useChessActions } from "@/hooks/useChessActions";
|
||||
import { moveClassificationColors } from "@/lib/chess";
|
||||
|
||||
export default function CustomDot({
|
||||
cx,
|
||||
cy,
|
||||
r,
|
||||
payload,
|
||||
}: DotProps & { payload?: ChartItemData }) {
|
||||
const { goToMove } = useChessActions(boardAtom);
|
||||
const game = useAtomValue(gameAtom);
|
||||
|
||||
const handleDotClick = () => {
|
||||
if (!payload) return;
|
||||
goToMove(payload.moveNb, game);
|
||||
};
|
||||
|
||||
const moveColor = payload?.moveClassification
|
||||
? moveClassificationColors[payload.moveClassification]
|
||||
: "grey";
|
||||
|
||||
return (
|
||||
<circle
|
||||
cx={cx}
|
||||
cy={cy}
|
||||
r={r}
|
||||
stroke={moveColor}
|
||||
strokeWidth={5}
|
||||
fill={moveColor}
|
||||
fillOpacity={1}
|
||||
onClick={handleDotClick}
|
||||
cursor="pointer"
|
||||
/>
|
||||
);
|
||||
}
|
||||
135
src/sections/analysis/panelBody/graphTab/index.tsx
Normal file
135
src/sections/analysis/panelBody/graphTab/index.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
import { Box, Grid, GridProps } from "@mui/material";
|
||||
import { useAtomValue } from "jotai";
|
||||
import {
|
||||
Area,
|
||||
AreaChart,
|
||||
ReferenceLine,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from "recharts";
|
||||
import { currentPositionAtom, gameEvalAtom } from "../../states";
|
||||
import { useMemo } from "react";
|
||||
import CustomTooltip from "./tooltip";
|
||||
import { ChartItemData } from "./types";
|
||||
import { PositionEval } from "@/types/eval";
|
||||
import { moveClassificationColors } from "@/lib/chess";
|
||||
import CustomDot from "./dot";
|
||||
|
||||
export default function GraphTab(props: GridProps) {
|
||||
const gameEval = useAtomValue(gameEvalAtom);
|
||||
const currentPosition = useAtomValue(currentPositionAtom);
|
||||
|
||||
const chartData: ChartItemData[] = useMemo(
|
||||
() => gameEval?.positions.map(formatEvalToChartData) ?? [],
|
||||
[gameEval]
|
||||
);
|
||||
|
||||
const boardMoveColor = currentPosition.eval?.moveClassification
|
||||
? moveClassificationColors[currentPosition.eval.moveClassification]
|
||||
: "grey";
|
||||
|
||||
if (!gameEval) return null;
|
||||
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
item
|
||||
justifyContent="center"
|
||||
alignItems="start"
|
||||
height="100%"
|
||||
{...props}
|
||||
sx={
|
||||
props.hidden
|
||||
? { display: "none" }
|
||||
: { marginY: 1, overflow: "hidden", overflowY: "auto", ...props.sx }
|
||||
}
|
||||
>
|
||||
<Box
|
||||
width="max(35rem, 90%)"
|
||||
maxWidth="100%"
|
||||
height="max(8rem, 100%)"
|
||||
maxHeight="15rem"
|
||||
sx={{
|
||||
backgroundColor: "#2e2e2e",
|
||||
borderRadius: "15px",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<AreaChart
|
||||
width={500}
|
||||
height={400}
|
||||
data={chartData}
|
||||
margin={{ top: 0, left: 0, right: 0, bottom: 0 }}
|
||||
>
|
||||
<XAxis dataKey="moveNb" hide stroke="red" />
|
||||
<YAxis domain={[0, 20]} hide />
|
||||
<Tooltip
|
||||
content={<CustomTooltip />}
|
||||
isAnimationActive={false}
|
||||
cursor={{
|
||||
stroke: "grey",
|
||||
strokeWidth: 2,
|
||||
strokeOpacity: 0.3,
|
||||
}}
|
||||
/>
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="value"
|
||||
stroke="none"
|
||||
fill="#ffffff"
|
||||
fillOpacity={1}
|
||||
activeDot={<CustomDot />}
|
||||
isAnimationActive={false}
|
||||
/>
|
||||
<ReferenceLine
|
||||
y={10}
|
||||
stroke="grey"
|
||||
strokeWidth={2}
|
||||
strokeOpacity={0.4}
|
||||
/>
|
||||
<ReferenceLine
|
||||
x={currentPosition.currentMoveIdx}
|
||||
stroke={boardMoveColor}
|
||||
strokeWidth={4}
|
||||
strokeOpacity={0.6}
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</Box>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
const formatEvalToChartData = (
|
||||
position: PositionEval,
|
||||
index: number
|
||||
): ChartItemData => {
|
||||
const line = position.lines[0];
|
||||
|
||||
const chartItem: ChartItemData = {
|
||||
moveNb: index,
|
||||
value: 10,
|
||||
cp: line.cp,
|
||||
mate: line.mate,
|
||||
moveClassification: position.moveClassification,
|
||||
};
|
||||
|
||||
if (line.mate) {
|
||||
return {
|
||||
...chartItem,
|
||||
value: line.mate > 0 ? 20 : 0,
|
||||
};
|
||||
}
|
||||
|
||||
if (line.cp) {
|
||||
return {
|
||||
...chartItem,
|
||||
value: Math.max(Math.min(line.cp / 100, 10), -10) + 10,
|
||||
};
|
||||
}
|
||||
|
||||
return chartItem;
|
||||
};
|
||||
27
src/sections/analysis/panelBody/graphTab/tooltip.tsx
Normal file
27
src/sections/analysis/panelBody/graphTab/tooltip.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { TooltipProps } from "recharts";
|
||||
import { ChartItemData } from "./types";
|
||||
import { getLineEvalLabel } from "@/lib/chess";
|
||||
|
||||
export default function CustomTooltip({
|
||||
active,
|
||||
payload,
|
||||
}: TooltipProps<number, number>) {
|
||||
if (!active || !payload?.length) return null;
|
||||
|
||||
const data = payload[0].payload as ChartItemData;
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: "#f0f0f0",
|
||||
padding: 5,
|
||||
color: "black",
|
||||
opacity: 0.9,
|
||||
border: "1px solid black",
|
||||
borderRadius: 3,
|
||||
}}
|
||||
>
|
||||
{getLineEvalLabel(data)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
9
src/sections/analysis/panelBody/graphTab/types.ts
Normal file
9
src/sections/analysis/panelBody/graphTab/types.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { MoveClassification } from "@/types/enums";
|
||||
|
||||
export interface ChartItemData {
|
||||
moveNb: number;
|
||||
value: number;
|
||||
cp?: number;
|
||||
mate?: number;
|
||||
moveClassification?: MoveClassification;
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import LinearProgressBar from "@/components/LinearProgressBar";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { evaluationProgressAtom } from "../states";
|
||||
|
||||
export default function ReviewPanelHeader() {
|
||||
export default function PanelHeader() {
|
||||
const evaluationProgress = useAtomValue(evaluationProgressAtom);
|
||||
|
||||
return (
|
||||
@@ -30,7 +30,7 @@ export default function ReviewPanelHeader() {
|
||||
<Icon icon="streamline:clipboard-check" height={24} />
|
||||
|
||||
<Typography variant="h5" align="center">
|
||||
Game Analysis
|
||||
Game Review
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
||||
@@ -10,7 +10,7 @@ import SaveButton from "./saveButton";
|
||||
import { useEffect } from "react";
|
||||
import { getStartingFen } from "@/lib/chess";
|
||||
|
||||
export default function ReviewPanelToolBar() {
|
||||
export default function PanelToolBar() {
|
||||
const board = useAtomValue(boardAtom);
|
||||
const { reset: resetBoard, undoMove: undoBoardMove } =
|
||||
useChessActions(boardAtom);
|
||||
@@ -1,31 +0,0 @@
|
||||
import { Divider, Grid } from "@mui/material";
|
||||
import MovesPanel from "./movesPanel";
|
||||
import MovesClassificationsRecap from "./movesClassificationsRecap";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { gameAtom } from "../../states";
|
||||
|
||||
export default function ClassificationPanel() {
|
||||
const game = useAtomValue(gameAtom);
|
||||
|
||||
if (!game.history().length) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Divider sx={{ marginX: "5%" }} />
|
||||
|
||||
<Grid
|
||||
container
|
||||
item
|
||||
justifyContent="center"
|
||||
alignItems="start"
|
||||
height="100%"
|
||||
minHeight={{ lg: "50px", xs: undefined }}
|
||||
sx={{ overflow: "hidden" }}
|
||||
>
|
||||
<MovesPanel />
|
||||
|
||||
<MovesClassificationsRecap />
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IconButton, Tooltip } from "@mui/material";
|
||||
import { Fab } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import EngineSettingsDialog from "./engineSettingsDialog";
|
||||
import { Icon } from "@iconify/react";
|
||||
@@ -8,11 +8,21 @@ export default function EngineSettingsButton() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip title="Engine settings">
|
||||
<IconButton onClick={() => setOpenDialog(true)} sx={{ paddingY: 0.3 }}>
|
||||
<Icon icon="ri:settings-3-line" height={20} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Fab
|
||||
title="Engine settings"
|
||||
color="secondary"
|
||||
size="small"
|
||||
sx={{
|
||||
top: "auto",
|
||||
right: 16,
|
||||
bottom: 16,
|
||||
left: "auto",
|
||||
position: "fixed",
|
||||
}}
|
||||
onClick={() => setOpenDialog(true)}
|
||||
>
|
||||
<Icon icon="mdi:settings" height={20} />
|
||||
</Fab>
|
||||
|
||||
<EngineSettingsDialog
|
||||
open={openDialog}
|
||||
|
||||
@@ -58,8 +58,7 @@ export default function EngineSettingsDialog({ open, onClose }: Props) {
|
||||
<Typography>
|
||||
Stockfish 16 Lite (HCE) is the default engine. It offers the best
|
||||
balance between speed and strength. Stockfish 16 is the strongest
|
||||
engine available, but please note that it requires a one time download
|
||||
of 40MB.
|
||||
engine available, note that it requires a one time download of 40MB.
|
||||
</Typography>
|
||||
<Grid
|
||||
marginTop={4}
|
||||
|
||||
Reference in New Issue
Block a user