diff --git a/.vscode/settings.json b/.vscode/settings.json index 8846ac0..3b377ff 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,6 +14,7 @@ "Sider", "Syncable", "Transpiler", + "Voronoi", "Websockets", "antd", "anticon", diff --git a/craco.config.js b/craco.config.js index a3d3698..935b942 100644 --- a/craco.config.js +++ b/craco.config.js @@ -2,6 +2,7 @@ // This file is part of KristWeb 2 under GPL-3.0. // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt const CracoLessPlugin = require("craco-less"); +const AntdDayjsWebpackPlugin = require("antd-dayjs-webpack-plugin"); const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer"); const WebpackBar = require("webpackbar"); @@ -14,6 +15,10 @@ } }, + babel: { + plugins: ["lodash"], + }, + plugins: [ { plugin: CracoLessPlugin, @@ -45,6 +50,11 @@ ...(process.env.NODE_ENV === "development" || process.env.FORCE_ANALYZE ? [new BundleAnalyzerPlugin({ openAnalyzer: false })] : []), + new AntdDayjsWebpackPlugin() ], + + optimization: { + sideEffects: true + } }, }; diff --git a/package.json b/package.json index 69aef78..e3639cb 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "antd": "^4.12.3", "async-mutex": "^0.3.0", "base64-arraybuffer": "^0.2.0", + "chart.js": "^2.9.4", "csv-stringify": "^5.6.1", "dayjs": "^1.10.4", "debug": "^4.3.1", @@ -31,6 +32,7 @@ "i18next-http-backend": "^1.0.20", "lodash-es": "^4.17.21", "react": "^17.0.1", + "react-chartjs-2": "^2.11.1", "react-dom": "^17.0.1", "react-i18next": "^11.8.6", "react-redux": "^7.2.2", @@ -51,7 +53,7 @@ "build": "craco build", "optimise": "gzip -kr build/static", "full-build": "npm run clean; GENERATE_SOURCEMAP=false npm run build; npm run optimise", - "analyze-build": "npm run clean; FORCE_ANALYZE=true npm run build", + "analyze-build": "FORCE_ANALYZE=true npm run build", "test": "craco test" }, "eslintConfig": { @@ -86,6 +88,8 @@ "@types/webpack-env": "^1.16.0", "@typescript-eslint/eslint-plugin": "^4.15.0", "@typescript-eslint/parser": "^4.15.0", + "antd-dayjs-webpack-plugin": "^1.0.6", + "babel-plugin-lodash": "^3.3.4", "craco-less": "^1.17.1", "eslint": "^7.20.0", "eslint-config-prettier": "^7.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4f42bdb..eab5221 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,6 +6,7 @@ antd: 4.12.3_89622fd8e4ec221151a62783d49305af async-mutex: 0.3.0 base64-arraybuffer: 0.2.0 + chart.js: 2.9.4 csv-stringify: 5.6.1 dayjs: 1.10.4 debug: 4.3.1 @@ -15,6 +16,7 @@ i18next-http-backend: 1.1.0 lodash-es: 4.17.21 react: 17.0.1 + react-chartjs-2: 2.11.1_6c446a34f83b2a92e3214f8b711c141a react-dom: 17.0.1_react@17.0.1 react-i18next: 11.8.6_i18next@19.8.7+react@17.0.1 react-redux: 7.2.2_380dc38591053d98779d1f25fc7202b9 @@ -45,6 +47,8 @@ '@types/webpack-env': 1.16.0 '@typescript-eslint/eslint-plugin': 4.15.0_bc16c4564afe16e3219e549b81836acd '@typescript-eslint/parser': 4.15.0_eslint@7.20.0+typescript@4.1.5 + antd-dayjs-webpack-plugin: 1.0.6_dayjs@1.10.4 + babel-plugin-lodash: 3.3.4 craco-less: 1.17.1_8837222c0261ea4582cefd2e7a5e3032 eslint: 7.20.0 eslint-config-prettier: 7.2.0_eslint@7.20.0 @@ -208,13 +212,13 @@ dependencies: '@babel/helper-get-function-arity': 7.12.13 '@babel/template': 7.12.13 - '@babel/types': 7.12.13 + '@babel/types': 7.13.0 dev: true resolution: integrity: sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA== /@babel/helper-get-function-arity/7.12.13: dependencies: - '@babel/types': 7.12.13 + '@babel/types': 7.13.0 dev: true resolution: integrity: sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg== @@ -232,7 +236,7 @@ integrity: sha512-zYoZC1uvebBFmj1wFAlXwt35JLEgecefATtKp20xalwEK8vHAixLBXTGxNrVGEmTT+gzOThUgr8UEdgtalc1BQ== /@babel/helper-module-imports/7.12.13: dependencies: - '@babel/types': 7.12.17 + '@babel/types': 7.13.0 dev: true resolution: integrity: sha512-NGmfvRp9Rqxy0uHSSVP+SRIW1q31a7Ji10cLBcqSDUngGentY4FRiHOFZFE1CLU5eiL0oE8reH7Tg1y99TDM/g== @@ -252,7 +256,7 @@ integrity: sha512-acKF7EjqOR67ASIlDTupwkKM1eUisNAjaSduo5Cz+793ikfnpe7p4Q7B7EWU2PCoSTPWsQkR7hRUWEIZPiVLGA== /@babel/helper-optimise-call-expression/7.12.13: dependencies: - '@babel/types': 7.12.13 + '@babel/types': 7.13.0 dev: true resolution: integrity: sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA== @@ -279,7 +283,7 @@ integrity: sha512-pctAOIAMVStI2TMLhozPKbf5yTEXc0OJa0eENheb4w09SrgOWEs+P4nTOZYJQCqs8JlErGLDPDJTiGIp3ygbLg== /@babel/helper-simple-access/7.12.13: dependencies: - '@babel/types': 7.12.13 + '@babel/types': 7.13.0 dev: true resolution: integrity: sha512-0ski5dyYIHEfwpWGx5GPWhH35j342JaflmCeQmsPWcrOQDtCN6C1zKAVRFVbK53lPW2c9TsuLLSUDf0tIGJ5hA== @@ -291,7 +295,7 @@ integrity: sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA== /@babel/helper-split-export-declaration/7.12.13: dependencies: - '@babel/types': 7.12.13 + '@babel/types': 7.13.0 dev: true resolution: integrity: sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg== @@ -333,6 +337,13 @@ hasBin: true resolution: integrity: sha512-c/+u9cqV6F0+4Hpq01jnJO+GLp2DdT63ppz9Xa+6cHaajM9VFzK/iDXiKK65YtpeVwu+ctfS6iqlMqRgQRzeCw== + /@babel/parser/7.13.0: + dev: true + engines: + node: '>=6.0.0' + hasBin: true + resolution: + integrity: sha512-w80kxEMFhE3wjMOQkfdTvv0CSdRSJZptIlLhU4eU/coNJeWjduspUFz+IRnBbAq6m5XYBFMoT1TNkk9K9yf10g== /@babel/plugin-proposal-async-generator-functions/7.12.13_@babel+core@7.12.3: dependencies: '@babel/core': 7.12.3 @@ -1323,8 +1334,8 @@ /@babel/template/7.12.13: dependencies: '@babel/code-frame': 7.12.13 - '@babel/parser': 7.12.16 - '@babel/types': 7.12.13 + '@babel/parser': 7.13.0 + '@babel/types': 7.13.0 dev: true resolution: integrity: sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA== @@ -1350,14 +1361,14 @@ dev: true resolution: integrity: sha512-oKrdZTld2im1z8bDwTOQvUbxKwE+854zc16qWZQlcTqMN00pWxHQ4ZeOq0yDMnisOpRykH2/5Qqcrk/OlbAjiQ== - /@babel/types/7.12.17: + /@babel/types/7.13.0: dependencies: '@babel/helper-validator-identifier': 7.12.11 lodash: 4.17.21 to-fast-properties: 2.0.0 dev: true resolution: - integrity: sha512-tNMDjcv/4DIcHxErTgwB9q2ZcYyN0sUfgGKUK/mm1FJK7Wz+KstoEekxrl/tBiNDgLK1HGi+sppj1An/1DR4fQ== + integrity: sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA== /@bcoe/v8-coverage/0.2.3: dev: true resolution: @@ -2710,6 +2721,14 @@ node: '>=8' resolution: integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + /antd-dayjs-webpack-plugin/1.0.6_dayjs@1.10.4: + dependencies: + dayjs: 1.10.4 + dev: true + peerDependencies: + dayjs: '*' + resolution: + integrity: sha512-UlK3BfA0iE2c5+Zz/Bd2iPAkT6cICtrKG4/swSik5MZweBHtgmu1aUQCHvICdiv39EAShdZy/edfP6mlkS/xXg== /antd/4.12.3_89622fd8e4ec221151a62783d49305af: dependencies: '@ant-design/colors': 6.0.0 @@ -3104,6 +3123,16 @@ node: '>= 10.14.2' resolution: integrity: sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw== + /babel-plugin-lodash/3.3.4: + dependencies: + '@babel/helper-module-imports': 7.12.13 + '@babel/types': 7.13.0 + glob: 7.1.6 + lodash: 4.17.21 + require-package-name: 2.0.1 + dev: true + resolution: + integrity: sha512-yDZLjK7TCkWl1gpBeBGmuaDIFhZKmkoL+Cu2MUUjv5VxUZx/z7tBGBCBcQs5RI1Bkz5LLmNdjx7paOyQtMovyg== /babel-plugin-macros/2.8.0: dependencies: '@babel/runtime': 7.12.1 @@ -3425,9 +3454,9 @@ integrity: sha512-HI4lPveGKUR0x2StIz+2FXfDk9SfVMrxn6PLh1JeGUwcuoDkdKZebWiyLRJ68iIPDpMI4JLVDf7S7XzslgWOhw== /browserslist/4.16.3: dependencies: - caniuse-lite: 1.0.30001187 + caniuse-lite: 1.0.30001191 colorette: 1.2.1 - electron-to-chromium: 1.3.664 + electron-to-chromium: 1.3.672 escalade: 3.1.1 node-releases: 1.1.70 dev: true @@ -3610,6 +3639,10 @@ dev: true resolution: integrity: sha512-w7/EP1JRZ9552CyrThUnay2RkZ1DXxKe/Q2swTC4+LElLh9RRYrL1Z+27LlakB8kzY0fSmHw9mc7XYDUKAKWMA== + /caniuse-lite/1.0.30001191: + dev: true + resolution: + integrity: sha512-xJJqzyd+7GCJXkcoBiQ1GuxEiOBCLQ0aVW9HMekifZsAVGdj5eJ4mFB9fEhSHipq9IOk/QXFJUiIr9lZT+EsGw== /capture-exit/2.0.0: dependencies: rsvp: 4.8.5 @@ -3660,6 +3693,26 @@ node: '>=10' resolution: integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + /chart.js/2.9.4: + dependencies: + chartjs-color: 2.4.1 + moment: 2.29.1 + dev: false + resolution: + integrity: sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A== + /chartjs-color-string/0.6.0: + dependencies: + color-name: 1.1.4 + dev: false + resolution: + integrity: sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A== + /chartjs-color/2.4.1: + dependencies: + chartjs-color-string: 0.6.0 + color-convert: 1.9.3 + dev: false + resolution: + integrity: sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w== /check-types/11.1.2: dev: true resolution: @@ -4529,7 +4582,7 @@ is-arguments: 1.1.0 is-date-object: 1.0.2 is-regex: 1.1.2 - object-is: 1.1.4 + object-is: 1.1.5 object-keys: 1.1.1 regexp.prototype.flags: 1.3.1 dev: true @@ -4823,6 +4876,10 @@ dev: true resolution: integrity: sha512-yb8LrTQXQnh9yhnaIHLk6CYugF/An50T20+X0h++hjjhVfgSp1DGoMSYycF8/aD5eiqS4QwaNhiduFvK8rifRg== + /electron-to-chromium/1.3.672: + dev: true + resolution: + integrity: sha512-gFQe7HBb0lbOMqK2GAS5/1F+B0IMdYiAgB9OT/w1F4M7lgJK2aNOMNOM622aEax+nS1cTMytkiT0uMOkbtFmHw== /elliptic/6.5.4: dependencies: bn.js: 4.11.9 @@ -7935,7 +7992,6 @@ resolution: integrity: sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== /lodash/4.17.21: - dev: true resolution: integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== /loglevel/1.7.1: @@ -8582,7 +8638,7 @@ /object-inspect/1.9.0: resolution: integrity: sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw== - /object-is/1.1.4: + /object-is/1.1.5: dependencies: call-bind: 1.0.2 define-properties: 1.1.3 @@ -8590,7 +8646,7 @@ engines: node: '>= 0.4' resolution: - integrity: sha512-1ZvAZ4wlF7IyPVOcE1Omikt7UpaFlOQq0HlSti+ZvDH3UiD2brwGMwDbyV43jao2bKJ+4+WdPJHSd7kgzKYVqg== + integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== /object-keys/1.1.1: engines: node: '>= 0.4' @@ -10641,6 +10697,20 @@ node: '>=10' resolution: integrity: sha512-0sF4ny9v/B7s6aoehwze9vJNWcmCemAUYBVasscVr92+UYiEqDXOxfKjXN685mDaMRNF3WdhHQs76oTODMocFA== + /react-chartjs-2/2.11.1_6c446a34f83b2a92e3214f8b711c141a: + dependencies: + chart.js: 2.9.4 + lodash: 4.17.21 + prop-types: 15.7.2 + react: 17.0.1 + react-dom: 17.0.1_react@17.0.1 + dev: false + peerDependencies: + chart.js: ^2.3 + react: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + react-dom: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + resolution: + integrity: sha512-G7cNq/n2Bkh/v4vcI+GKx7Q1xwZexKYhOSj2HmrFXlvNeaURWXun6KlOUpEQwi1cv9Tgs4H3kGywDWMrX2kxfA== /react-dev-utils/11.0.2: dependencies: '@babel/code-frame': 7.10.4 @@ -11166,6 +11236,10 @@ dev: true resolution: integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + /require-package-name/2.0.1: + dev: true + resolution: + integrity: sha1-wR6XJ2tluOKSP3Xav1+y7ww4Qbk= /requires-port/1.0.0: dev: true resolution: @@ -13562,8 +13636,11 @@ '@typescript-eslint/eslint-plugin': ^4.15.0 '@typescript-eslint/parser': ^4.15.0 antd: ^4.12.3 + antd-dayjs-webpack-plugin: ^1.0.6 async-mutex: ^0.3.0 + babel-plugin-lodash: ^3.3.4 base64-arraybuffer: ^0.2.0 + chart.js: ^2.9.4 craco-less: ^1.17.1 csv-stringify: ^5.6.1 dayjs: ^1.10.4 @@ -13579,6 +13656,7 @@ lodash-es: ^4.17.21 prettier: ^2.2.1 react: ^17.0.1 + react-chartjs-2: ^2.11.1 react-dom: ^17.0.1 react-i18next: ^11.8.6 react-redux: ^7.2.2 diff --git a/public/locales/en.json b/public/locales/en.json index 5903e46..0ac9007 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -240,7 +240,8 @@ "blockValueReset": "Resets in <1>{{count, number}} block", "blockValueReset_plural": "Resets in <1>{{count, number}} blocks", - "blockDifficultyCardTitle": "Block Difficulty" + "blockDifficultyCardTitle": "Block Difficulty", + "blockDifficultyError": "There was an error fetching the block difficulty. See the console for details." }, "credits": { diff --git a/src/pages/dashboard/BlockDifficultyCard.tsx b/src/pages/dashboard/BlockDifficultyCard.tsx index eb9c7b5..bb2df05 100644 --- a/src/pages/dashboard/BlockDifficultyCard.tsx +++ b/src/pages/dashboard/BlockDifficultyCard.tsx @@ -1,15 +1,164 @@ // Copyright (c) 2020-2021 Drew Lemmy // This file is part of KristWeb 2 under GPL-3.0. // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import React from "react"; -import { Card } from "antd"; +import React, { useState, useEffect, useMemo } from "react"; +import { Card, Skeleton, Empty } from "antd"; +import { useSelector } from "react-redux"; +import { RootState } from "../../store"; import { useTranslation } from "react-i18next"; +// import { LineCanvas } from "@nivo/line"; +// import { SizeMe } from "react-sizeme"; +import { Line } from "react-chartjs-2"; + +import { APIResponse } from "../../krist/api/types"; + +import { SmallResult } from "../../components/SmallResult"; +import { throttle } from "lodash-es"; + +import Debug from "debug"; +const debug = Debug("kristweb:block-difficulty-card"); + export function BlockDifficultyCard(): JSX.Element { const { t } = useTranslation(); - return + const syncNode = useSelector((s: RootState) => s.node.syncNode); + const lastBlockID = useSelector((s: RootState) => s.node.lastBlockID); + const work = useSelector((s: RootState) => s.node.detailedWork?.work); + const [workOverTime, setWorkOverTime] = useState<{ x: Date; y: number }[] | undefined>(); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(); + + async function _fetchWorkOverTime(): Promise { + try { + debug("fetching work over time"); + + const res = await fetch(syncNode + "/work/day"); + if (!res.ok || res.status !== 200) throw new Error(res.statusText); + + const data: APIResponse<{ work: number[] }> = await res.json(); + if (!data.ok || data.error) throw new Error(data.error); + + const processedWork = data.work.map((work, i, arr) => + ({ x: new Date(Date.now() - ((arr.length - i) * 60000)), y: work })); + + setWorkOverTime(processedWork); + } catch (err) { + console.error(err); + setError(err); + } finally { + setLoading(false); + } + } + + const fetchWorkOverTime = useMemo(() => + throttle(_fetchWorkOverTime, 300, { leading: false, trailing: true }), []); + + useEffect(() => { + if (!syncNode) return; + fetchWorkOverTime(); + }, [syncNode, lastBlockID]); + + function chart(): JSX.Element { + return ; + } + + const isEmpty = !loading && error; + + return + + {error + ? + : (workOverTime + ? chart() + : + )} + ; } diff --git a/src/pages/dashboard/DashboardPage.less b/src/pages/dashboard/DashboardPage.less index c1e83c8..00985a6 100644 --- a/src/pages/dashboard/DashboardPage.less +++ b/src/pages/dashboard/DashboardPage.less @@ -34,6 +34,34 @@ .ant-card-body { padding-top: @padding-sm; } + + &.empty .ant-card-body { + height: 100%; + padding-top: 0 !important; + padding: 0; + + display: flex; + align-items: center; + justify-content: center; + + &::before, &::after { + content: none; + } + + .ant-empty-normal { + margin: 0; + } + + .ant-result { + padding: @padding-sm; + + .ant-result-icon { + margin-bottom: @margin-xs; + + .anticon { font-size: 48px; } + } + } + } } } @@ -156,34 +184,6 @@ } } } - - &.empty .ant-card-body { - height: 100%; - padding-top: 0 !important; - padding: 0; - - display: flex; - align-items: center; - justify-content: center; - - &::before, &::after { - content: none; - } - - .ant-empty-normal { - margin: 0; - } - - .ant-result { - padding: @padding-sm; - - .ant-result-icon { - margin-bottom: @margin-xs; - - .anticon { font-size: 48px; } - } - } - } } .dashboard-card-block-value { @@ -205,6 +205,12 @@ } } + .dashboard-card-block-difficulty { + .ant-card-body { + height: 100%; + } + } + .dashboard-list-item { padding: (@padding-sm / 2) @padding-md; margin-bottom: @margin-xs;