From 9f5ae802684908167fd6d012b4513102553498d3 Mon Sep 17 00:00:00 2001 From: Erika Date: Wed, 14 Aug 2024 11:42:41 +0200 Subject: [PATCH] feat(ui): sidebar charts --- package-lock.json | 52 +++- package.json | 1 + src/containers/SideBar/Miner/Miner.tsx | 2 - src/containers/SideBar/SideBar.tsx | 21 +- .../SideBar/components/HashRateChart.tsx | 108 ++++++++ .../SideBar/components/MinerUptimeChart.tsx | 106 ++++++++ src/containers/SideBar/styles.ts | 256 +++++++++--------- 7 files changed, 406 insertions(+), 140 deletions(-) create mode 100644 src/containers/SideBar/components/HashRateChart.tsx create mode 100644 src/containers/SideBar/components/MinerUptimeChart.tsx diff --git a/package-lock.json b/package-lock.json index a7f90816..d52def94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@emotion/styled": "^11.13.0", "@mui/material": "^5.16.7", "@tauri-apps/api": "^1", + "echarts-for-react": "^3.0.2", "globals": "^15.9.0", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -2709,6 +2710,31 @@ "csstype": "^3.0.2" } }, + "node_modules/echarts": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.5.1.tgz", + "integrity": "sha512-Fce8upazaAXUVUVsjgV6mBnGuqgO+JNDlcgF79Dksy4+wgGpQB2lmYoO4TSweFg/mZITdpGHomw/cNBJZj1icA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "2.3.0", + "zrender": "5.6.0" + } + }, + "node_modules/echarts-for-react": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/echarts-for-react/-/echarts-for-react-3.0.2.tgz", + "integrity": "sha512-DRwIiTzx8JfwPOVgGttDytBqdp5VzCSyMRIxubgU/g2n9y3VLUmF2FK7Icmg/sNVkv4+rktmrLN9w22U2yy3fA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "size-sensor": "^1.0.1" + }, + "peerDependencies": { + "echarts": "^3.0.0 || ^4.0.0 || ^5.0.0", + "react": "^15.0.0 || >=16.0.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.829", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.829.tgz", @@ -3228,8 +3254,7 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-glob": { "version": "3.3.2", @@ -4899,6 +4924,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/size-sensor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/size-sensor/-/size-sensor-1.0.2.tgz", + "integrity": "sha512-2NCmWxY7A9pYKGXNBfteo4hy14gWu47rg5692peVMst6lQLPKrVjhY+UTEsPI5ceFRJSl3gVgMYaUi/hKuaiKw==", + "license": "ISC" + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -5099,6 +5130,13 @@ "typescript": ">=4.2.0" } }, + "node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD", + "peer": true + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -5470,6 +5508,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zrender": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.0.tgz", + "integrity": "sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "tslib": "2.3.0" + } + }, "node_modules/zustand": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.4.tgz", diff --git a/package.json b/package.json index 2975d7f3..2ec298df 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@emotion/styled": "^11.13.0", "@mui/material": "^5.16.7", "@tauri-apps/api": "^1", + "echarts-for-react": "^3.0.2", "globals": "^15.9.0", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/src/containers/SideBar/Miner/Miner.tsx b/src/containers/SideBar/Miner/Miner.tsx index 8cef0cb0..cebc9de6 100644 --- a/src/containers/SideBar/Miner/Miner.tsx +++ b/src/containers/SideBar/Miner/Miner.tsx @@ -1,7 +1,6 @@ import Tile from './components/Tile.tsx'; import { MinerContainer, TileContainer } from './styles.ts'; import AutoMiner from './components/AutoMiner.tsx'; -import Scheduler from './components/Scheduler.tsx'; import ModeSelect from './components/ModeSelect.tsx'; import { useAppStatusStore } from '../../../store/useAppStatusStore.ts'; @@ -36,7 +35,6 @@ function Miner() { return ( - diff --git a/src/containers/SideBar/SideBar.tsx b/src/containers/SideBar/SideBar.tsx index 6f650177..cbb52502 100644 --- a/src/containers/SideBar/SideBar.tsx +++ b/src/containers/SideBar/SideBar.tsx @@ -2,16 +2,17 @@ import Miner from './Miner/Miner'; import Wallet from './components/Wallet'; import Heading from './components/Heading'; import Milestones from './components/Milestone'; +import MinerUptimeChart from './components/MinerUptimeChart.tsx'; +import HashRateChart from './components/HashRateChart.tsx'; import { SideBarContainer, SideBarInner, HeadingContainer, BottomContainer, } from './styles'; -import TestButtons from './TestButtons'; -import {useTheme} from '@mui/material/styles'; - -import {useUIStore} from '../../store/useUIStore.ts'; +import { useTheme } from '@mui/material/styles'; +import { useUIStore } from '../../store/useUIStore.ts'; +import { Divider } from '@mui/material'; function SideBar() { const theme = useTheme(); @@ -19,15 +20,17 @@ function SideBar() { return ( - + - - - {/**/} + + + + - + + ); diff --git a/src/containers/SideBar/components/HashRateChart.tsx b/src/containers/SideBar/components/HashRateChart.tsx new file mode 100644 index 00000000..74f583e8 --- /dev/null +++ b/src/containers/SideBar/components/HashRateChart.tsx @@ -0,0 +1,108 @@ +import ReactECharts from 'echarts-for-react'; +import { useTheme } from '@mui/material/styles'; +import { Typography, Stack } from '@mui/material'; + +interface TooltipParams { + dataIndex: number; +} + +function formatHash(number: number) { + const suffixes = ['', 'K', 'M', 'G', 'T', 'P']; + let suffixIndex = 0; + while (number >= 1000 && suffixIndex < suffixes.length - 1) { + number /= 1000; + suffixIndex++; + } + return number.toFixed(1) + ' ' + suffixes[suffixIndex] + 'H'; +} + +const HashRateChartGannt = () => { + const theme = useTheme(); + const chartHeight = 150; + + const data = [ + { hashRate: 2725148000, blockNo: 8703 }, + { hashRate: 2425148500, blockNo: 8704 }, + { hashRate: 2325149000, blockNo: 8705 }, + { hashRate: 2725149500, blockNo: 8706 }, + { hashRate: 2625150000, blockNo: 8707 }, + { hashRate: 2825150500, blockNo: 8708 }, + { hashRate: 2725151000, blockNo: 8709 }, + { hashRate: 2325151500, blockNo: 8710 }, + { hashRate: 2425152000, blockNo: 8711 }, + { hashRate: 2925152500, blockNo: 8712 }, + { hashRate: 2925153000, blockNo: 8713 }, + { hashRate: 3325153500, blockNo: 8714 }, + { hashRate: 3625154000, blockNo: 8715 }, + { hashRate: 3225154500, blockNo: 8716 }, + { hashRate: 3425155000, blockNo: 8717 }, + { hashRate: 3325155500, blockNo: 8718 }, + { hashRate: 2725156000, blockNo: 8719 }, + { hashRate: 3025156500, blockNo: 8720 }, + { hashRate: 3125157000, blockNo: 8721 }, + ]; + + const option = { + height: chartHeight, + tooltip: { + trigger: 'axis', + formatter: (params: TooltipParams[]) => { + const { hashRate, blockNo } = data[params[0].dataIndex]; + return `${formatHash(hashRate)} at
block ${blockNo}`; + }, + }, + xAxis: { + type: 'category', + data: data?.map((item) => item.blockNo), + axisLabel: { + show: false, + }, + }, + yAxis: { + type: 'value', + axisLabel: { + formatter: formatHash, + }, + }, + grid: { + left: '0', + right: '0', + bottom: '20', + top: '10', + containLabel: true, + }, + series: [ + { + name: 'Hash Rate', + type: 'line', + data: data?.map((item) => item.hashRate), + smooth: false, + itemStyle: { + color: '#F3B927', + }, + }, + ], + }; + + return ( + + + Network Hash Rate + + + 2M + {' '} + AVG block time + + + + + ); +}; + +export default HashRateChartGannt; diff --git a/src/containers/SideBar/components/MinerUptimeChart.tsx b/src/containers/SideBar/components/MinerUptimeChart.tsx new file mode 100644 index 00000000..eb2a7428 --- /dev/null +++ b/src/containers/SideBar/components/MinerUptimeChart.tsx @@ -0,0 +1,106 @@ +import React from 'react'; +import ReactECharts from 'echarts-for-react'; +import { useTheme } from '@mui/material/styles'; +import { Typography, Stack } from '@mui/material'; + +const MinerUptimeChart: React.FC = () => { + const theme = useTheme(); + const chartHeight = 25; + + const uptime = [ + { status: 'on', time: '00:00:00' }, + { status: 'off', time: '01:00:00' }, + { status: 'off', time: '02:00:00' }, + { status: 'on', time: '03:00:00' }, + { status: 'on', time: '04:00:00' }, + { status: 'off', time: '05:00:00' }, + { status: 'on', time: '06:00:00' }, + { status: 'on', time: '07:00:00' }, + { status: 'on', time: '08:00:00' }, + { status: 'off', time: '09:00:00' }, + { status: 'on', time: '10:00:00' }, + { status: 'off', time: '11:00:00' }, + { status: 'on', time: '12:00:00' }, + { status: 'on', time: '13:00:00' }, + { status: 'on', time: '14:00:00' }, + { status: 'off', time: '15:00:00' }, + { status: 'on', time: '16:00:00' }, + { status: 'off', time: '17:00:00' }, + { status: 'on', time: '18:00:00' }, + { status: 'on', time: '19:00:00' }, + { status: 'off', time: '20:00:00' }, + { status: 'off', time: '21:00:00' }, + { status: 'on', time: '22:00:00' }, + { status: 'off', time: '23:00:00' }, + ]; + + const data = uptime.map(({ status }) => ({ + value: 1, + itemStyle: { + color: + status === 'on' + ? theme.palette.success.main + : theme.palette.divider, + }, + })); + + const option = { + height: chartHeight, + xAxis: { + type: 'category', + data: uptime.map((_, index) => index + 1), + show: false, + }, + yAxis: { + type: 'value', + min: 0, + max: 1, + show: false, + }, + series: [ + { + data: data, + type: 'bar', + itemStyle: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + color: (params: any) => + params.value === 'on' + ? theme.palette.success.main + : theme.palette.divider, + borderRadius: 3, + }, + }, + ], + grid: { + left: '0', + right: '0', + bottom: '0', + top: '0', + containLabel: false, + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow', + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + formatter: (params: any) => { + const { status, time } = uptime[params[0].dataIndex]; + const color = + status === 'on' + ? theme.palette.success.main + : theme.palette.divider; + return ` ${status === 'on' ? 'Online' : 'Offline'}
at ${time}`; + }, + }, + }; + + return ( + + 24h Miner Uptime + + + ); +}; + +export default MinerUptimeChart; diff --git a/src/containers/SideBar/styles.ts b/src/containers/SideBar/styles.ts index 67a4dab9..4f48426e 100644 --- a/src/containers/SideBar/styles.ts +++ b/src/containers/SideBar/styles.ts @@ -5,60 +5,60 @@ import cardbg from '../../assets/images/card.png'; import { keyframes } from '@emotion/react'; interface SideBarContainerProps { - sidebaropen: boolean; + sidebaropen: boolean; } interface BalanceChangeProps { - direction: 'up' | 'down'; + direction: 'up' | 'down'; } // SideBar export const SideBarContainer = styled(Box, { - shouldForwardProp: (prop) => prop !== 'sidebaropen', + shouldForwardProp: (prop) => prop !== 'sidebaropen', })(({ theme, sidebaropen }) => ({ - backgroundColor: theme.palette.background.paper, - borderRadius: theme.shape.borderRadius, - marginLeft: theme.spacing(1), - marginRight: theme.spacing(1), - marginBottom: theme.spacing(1), - marginTop: 0, - position: 'absolute', - top: headerHeight, - left: 0, - height: `calc(100vh - ${headerHeight} - ${theme.spacing(1)})`, - width: sidebaropen ? `calc(100% - ${theme.spacing(2)})` : sidebarWidth, - zIndex: 100, - transition: 'width 0.5s ease-in-out', - display: 'flex', - flexDirection: 'column', - gap: theme.spacing(2), - justifyContent: 'flex-start', - overflow: 'hidden', + backgroundColor: theme.palette.background.paper, + borderRadius: theme.shape.borderRadius, + marginLeft: theme.spacing(1), + marginRight: theme.spacing(1), + marginBottom: theme.spacing(1), + marginTop: 0, + position: 'absolute', + top: headerHeight, + left: 0, + height: `calc(100vh - ${headerHeight} - ${theme.spacing(1)})`, + width: sidebaropen ? `calc(100% - ${theme.spacing(2)})` : sidebarWidth, + zIndex: 100, + transition: 'width 0.5s ease-in-out', + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(2), + justifyContent: 'flex-start', + overflow: 'hidden', })); export const SideBarInner = styled(Box)(({ theme }) => ({ - display: 'flex', - flexDirection: 'column', - gap: theme.spacing(2), - height: '100%', - overflowY: 'auto', - paddingLeft: theme.spacing(2), - paddingRight: theme.spacing(2), + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(2), + height: '100%', + overflowY: 'auto', + paddingLeft: theme.spacing(2), + paddingRight: theme.spacing(2), })); export const BottomContainer = styled(Box)(({ theme }) => ({ - paddingLeft: theme.spacing(2), - paddingRight: theme.spacing(2), - paddingBottom: theme.spacing(2), - display: 'flex', - flexDirection: 'column', - gap: theme.spacing(1), + paddingLeft: theme.spacing(2), + paddingRight: theme.spacing(2), + paddingBottom: theme.spacing(2), + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(1), })); export const HeadingContainer = styled(Box)(({ theme }) => ({ - paddingLeft: theme.spacing(2), - paddingRight: theme.spacing(2), - paddingTop: theme.spacing(2), + paddingLeft: theme.spacing(2), + paddingRight: theme.spacing(2), + paddingTop: theme.spacing(2), })); const fadeIn = keyframes` @@ -85,119 +85,121 @@ const fadeOut = keyframes` // Wallet export const WalletContainer = styled(Box)(({ theme }) => ({ - display: 'flex', - flexDirection: 'column', - justifyContent: 'space-between', - alignItems: 'stretch', - gap: theme.spacing(0), - backgroundImage: `url(${cardbg})`, - backgroundRepeat: 'no-repeat', - backgroundPosition: 'top left', - padding: theme.spacing(1), - borderRadius: '20px', - width: `calc(${sidebarWidth} - ${theme.spacing(4)})`, - boxShadow: '4px 4px 10px 0px rgba(0, 0, 0, 0.30)', - '&:hover .hover-stack': { display: 'flex', - animation: `${fadeIn} 0.8s ease-in-out forwards`, - }, - '&:not(:hover) .hover-stack': { - opacity: 0, - maxHeight: 0, - animation: `${fadeOut} 0.8s ease-in-out forwards`, - }, + flexDirection: 'column', + justifyContent: 'space-between', + alignItems: 'stretch', + gap: theme.spacing(0), + backgroundImage: `url(${cardbg})`, + backgroundRepeat: 'no-repeat', + backgroundPosition: 'top left', + padding: theme.spacing(1), + borderRadius: '20px', + width: `calc(${sidebarWidth} - ${theme.spacing(4)})`, + boxShadow: '4px 4px 10px 0px rgba(0, 0, 0, 0.30)', + '&:hover .hover-stack': { + display: 'flex', + animation: `${fadeIn} 0.8s ease-in-out forwards`, + }, + '&:not(:hover) .hover-stack': { + opacity: 0, + maxHeight: 0, + animation: `${fadeOut} 0.8s ease-in-out forwards`, + }, })); export const HoverStack = styled(Box)(() => ({ - display: 'flex', - opacity: 0, - maxHeight: 0, - overflow: 'hidden', - width: '100%', - transition: 'opacity 0.8s ease-in-out, max-height 0.8s ease-in-out', - '&.active': { - opacity: 1, - maxHeight: '100px', - }, + display: 'flex', + opacity: 0, + maxHeight: 0, + overflow: 'hidden', + width: '100%', + transition: 'opacity 0.8s ease-in-out, max-height 0.8s ease-in-out', + '&.active': { + opacity: 1, + maxHeight: '100px', + }, })); export const AddressBox = styled(Box)(({ theme }) => ({ - backgroundColor: 'rgba(255,255,255,0.05)', - border: `1px solid ${theme.palette.divider}`, - borderRadius: theme.spacing(2), - padding: '5px 10px', - display: 'flex', - alignItems: 'center', - gap: theme.spacing(0.5), - transition: 'color 0.5s ease-in-out', + backgroundColor: 'rgba(255,255,255,0.05)', + border: `1px solid ${theme.palette.divider}`, + borderRadius: theme.spacing(2), + padding: '5px 10px', + display: 'flex', + alignItems: 'center', + gap: theme.spacing(0.5), + transition: 'color 0.5s ease-in-out', })); export const Handle = styled(Box)(({ theme }) => ({ - backgroundColor: theme.palette.text.secondary, - width: '52px', - height: '3px', - borderRadius: '2px', + backgroundColor: theme.palette.text.secondary, + width: '52px', + height: '3px', + borderRadius: '2px', })); export const BalanceChangeChip = styled(Chip, { - shouldForwardProp: (prop) => prop !== 'direction', + shouldForwardProp: (prop) => prop !== 'direction', })(({ theme, direction }) => ({ - backgroundColor: 'rgba(255,255,255,0.05)', - border: `1px solid ${theme.palette.divider}`, - color: - direction === 'up' ? theme.palette.success.main : theme.palette.error.main, - borderRadius: theme.spacing(2), - display: 'flex', - alignItems: 'center', - gap: theme.spacing(0.5), - transition: 'color 0.5s ease-in-out', - '& .MuiChip-icon': { + backgroundColor: 'rgba(255,255,255,0.05)', + border: `1px solid ${theme.palette.divider}`, color: - direction === 'up' - ? theme.palette.success.main - : theme.palette.error.main, - transform: direction === 'up' ? 'rotate(0deg)' : 'rotate(180deg)', - transition: 'color 0.5s ease-in-out, transform 0.5s ease-in-out', - }, - '& .MuiChip-label': { - paddingLeft: '6px', - paddingRight: '6px', - fontSize: '12px', - }, + direction === 'up' + ? theme.palette.success.main + : theme.palette.error.main, + borderRadius: theme.spacing(2), + display: 'flex', + alignItems: 'center', + gap: theme.spacing(0.5), + transition: 'color 0.5s ease-in-out', + '& .MuiChip-icon': { + color: + direction === 'up' + ? theme.palette.success.main + : theme.palette.error.main, + transform: direction === 'up' ? 'rotate(0deg)' : 'rotate(180deg)', + transition: 'color 0.5s ease-in-out, transform 0.5s ease-in-out', + }, + '& .MuiChip-label': { + paddingLeft: '6px', + paddingRight: '6px', + fontSize: '12px', + }, })); export const WalletButton = styled(Button)(({ theme }) => ({ - backgroundColor: 'rgba(255,255,255,0.05)', - color: theme.palette.primary.contrastText, - borderRadius: '20px', - border: `1px solid ${theme.palette.divider}`, - padding: '10px', - height: '34px', - '&:hover': { - backgroundColor: theme.palette.divider, - }, + backgroundColor: 'rgba(255,255,255,0.05)', + color: theme.palette.primary.contrastText, + borderRadius: '20px', + border: `1px solid ${theme.palette.divider}`, + padding: '10px', + height: '34px', + '&:hover': { + backgroundColor: theme.palette.divider, + }, })); // Milestones export const ProgressBox = styled(Box)(({ theme }) => ({ - backgroundColor: theme.palette.background.paper, - padding: '3px', - borderRadius: '10px', - width: '100%', - border: `1px solid ${theme.palette.divider}`, - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - gap: theme.spacing(0.1), + backgroundColor: theme.palette.background.paper, + padding: '3px', + borderRadius: '10px', + width: '100%', + border: `1px solid ${theme.palette.divider}`, + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + gap: theme.spacing(0.1), })); export const StyledLinearProgress = styled(LinearProgress)(() => ({ - '& .MuiLinearProgress-bar': { - background: 'linear-gradient(90deg, #FF7D45 0%, #FFB660 99.49%)', - borderRadius: '5px', - }, - backgroundColor: 'transparent', - padding: '3px', - borderRadius: '10px', - flexGrow: 1, + '& .MuiLinearProgress-bar': { + background: 'linear-gradient(90deg, #FF7D45 0%, #FFB660 99.49%)', + borderRadius: '5px', + }, + backgroundColor: 'transparent', + padding: '3px', + borderRadius: '10px', + flexGrow: 1, }));