From e47d55b582fee2257d5aa247017f4150d9fce552 Mon Sep 17 00:00:00 2001 From: Reckless_Satoshi <90936742+Reckless-Satoshi@users.noreply.github.com> Date: Tue, 27 Sep 2022 15:52:00 +0000 Subject: [PATCH] Book functional component (#256) * Add bookTable functional component * Implement responsive booktable * Fix unwanted scroll bar on chromium browsers * Add column self-organization and 3 new columns * Add responsive behaviour on depth chart * Run prettier * Add minimum pageSize (book must at least be 1 row height) * Adjust circular spinner div height * Add order ID column style * Refactor window resize event listener * Add depth chart outline * Review fixes --- frontend/src/components/BookPage.js | 474 ++------------ frontend/src/components/BookTable.tsx | 598 ++++++++++++++++++ .../components/Charts/DepthChart/index.tsx | 210 +++--- .../src/components/Dialogs/UpdateClient.tsx | 4 +- frontend/src/components/HomePage.js | 19 +- frontend/src/components/OrderPage.js | 18 +- .../components/Robots/RobotAvatar/index.tsx | 4 +- frontend/src/utils/checkVer.ts | 4 +- frontend/src/utils/hexToRgb.js | 14 + frontend/src/utils/statusBadgeColor.ts | 9 + frontend/static/css/index.css | 9 +- 11 files changed, 800 insertions(+), 563 deletions(-) create mode 100644 frontend/src/components/BookTable.tsx create mode 100644 frontend/src/utils/hexToRgb.js create mode 100644 frontend/src/utils/statusBadgeColor.ts diff --git a/frontend/src/components/BookPage.js b/frontend/src/components/BookPage.js index a2718c3c..a4621ec1 100644 --- a/frontend/src/components/BookPage.js +++ b/frontend/src/components/BookPage.js @@ -1,38 +1,27 @@ import React, { Component } from 'react'; import { withTranslation } from 'react-i18next'; import { - Tooltip, - Stack, - Paper, Button, ToggleButtonGroup, ToggleButton, - ListItemButton, Typography, Grid, Select, MenuItem, FormControl, FormHelperText, - ListItemText, - ListItemAvatar, IconButton, ButtonGroup, } from '@mui/material'; import { Link } from 'react-router-dom'; -import { DataGrid } from '@mui/x-data-grid'; import currencyDict from '../../static/assets/currencies.json'; - -import MediaQuery from 'react-responsive'; import FlagWithProps from './FlagWithProps'; -import { pn, amountToString } from '../utils/prettyNumbers'; -import PaymentText from './PaymentText'; import DepthChart from './Charts/DepthChart'; -import RobotAvatar from './Robots/RobotAvatar'; import { apiClient } from '../services/api/index'; // Icons import { BarChart, FormatListBulleted, Refresh } from '@mui/icons-material'; +import BookTable from './BookTable'; class BookPage extends Component { constructor(props) { @@ -43,11 +32,11 @@ class BookPage extends Component { }; } - componentDidMount() { - this.getOrderDetails(2, 0); - } + componentDidMount = () => { + this.getOrderDetails(); + }; - getOrderDetails(type, currency) { + getOrderDetails() { this.props.setAppState({ bookLoading: true }); apiClient.get('/api/book/').then((data) => this.props.setAppState({ @@ -79,378 +68,6 @@ class BookPage extends Component { } } - // Colors for the status badges - statusBadgeColor(status) { - if (status === 'Active') { - return 'success'; - } - if (status === 'Seen recently') { - return 'warning'; - } - if (status === 'Inactive') { - return 'error'; - } - } - - dataGridLocaleText = () => { - const { t } = this.props; - return { - MuiTablePagination: { labelRowsPerPage: t('Orders per page:') }, - noRowsLabel: t('No rows'), - noResultsOverlayLabel: t('No results found.'), - errorOverlayDefaultLabel: t('An error occurred.'), - toolbarColumns: t('Columns'), - toolbarColumnsLabel: t('Select columns'), - columnsPanelTextFieldLabel: t('Find column'), - columnsPanelTextFieldPlaceholder: t('Column title'), - columnsPanelDragIconLabel: t('Reorder column'), - columnsPanelShowAllButton: t('Show all'), - columnsPanelHideAllButton: t('Hide all'), - filterPanelAddFilter: t('Add filter'), - filterPanelDeleteIconLabel: t('Delete'), - filterPanelLinkOperator: t('Logic operator'), - filterPanelOperators: t('Operator'), // TODO v6: rename to filterPanelOperator - filterPanelOperatorAnd: t('And'), - filterPanelOperatorOr: t('Or'), - filterPanelColumns: t('Columns'), - filterPanelInputLabel: t('Value'), - filterPanelInputPlaceholder: t('Filter value'), - filterOperatorContains: t('contains'), - filterOperatorEquals: t('equals'), - filterOperatorStartsWith: t('starts with'), - filterOperatorEndsWith: t('ends with'), - filterOperatorIs: t('is'), - filterOperatorNot: t('is not'), - filterOperatorAfter: t('is after'), - filterOperatorOnOrAfter: t('is on or after'), - filterOperatorBefore: t('is before'), - filterOperatorOnOrBefore: t('is on or before'), - filterOperatorIsEmpty: t('is empty'), - filterOperatorIsNotEmpty: t('is not empty'), - filterOperatorIsAnyOf: t('is any of'), - filterValueAny: t('any'), - filterValueTrue: t('true'), - filterValueFalse: t('false'), - columnMenuLabel: t('Menu'), - columnMenuShowColumns: t('Show columns'), - columnMenuFilter: t('Filter'), - columnMenuHideColumn: t('Hide'), - columnMenuUnsort: t('Unsort'), - columnMenuSortAsc: t('Sort by ASC'), - columnMenuSortDesc: t('Sort by DESC'), - columnHeaderFiltersLabel: t('Show filters'), - columnHeaderSortIconLabel: t('Sort'), - booleanCellTrueLabel: t('yes'), - booleanCellFalseLabel: t('no'), - }; - }; - - bookListTableDesktop = () => { - const { t } = this.props; - return ( -
- - (order.type == this.props.type || this.props.type == null) && - (order.currency == this.props.currency || this.props.currency == 0), - )} - loading={this.props.bookLoading} - columns={[ - // { field: 'id', headerName: 'ID', width: 40 }, - { - field: 'maker_nick', - headerName: t('Robot'), - width: 240, - renderCell: (params) => { - return ( - - - - - - - ); - }, - }, - { - field: 'type', - headerName: t('Is'), - width: 60, - renderCell: (params) => (params.row.type ? t('Seller') : t('Buyer')), - }, - { - field: 'amount', - headerName: t('Amount'), - type: 'number', - width: 90, - renderCell: (params) => { - return ( -
- {amountToString( - params.row.amount, - params.row.has_range, - params.row.min_amount, - params.row.max_amount, - )} -
- ); - }, - }, - { - field: 'currency', - headerName: t('Currency'), - width: 100, - renderCell: (params) => { - const currencyCode = this.getCurrencyCode(params.row.currency); - return ( -
- {currencyCode + ' '} - -
- ); - }, - }, - { - field: 'payment_method', - headerName: t('Payment Method'), - width: 180, - renderCell: (params) => { - return ( -
- -
- ); - }, - }, - { - field: 'price', - headerName: t('Price'), - type: 'number', - width: 140, - renderCell: (params) => { - const currencyCode = this.getCurrencyCode(params.row.currency); - return ( -
- {pn(params.row.price) + ' ' + currencyCode + '/BTC'} -
- ); - }, - }, - { - field: 'premium', - headerName: t('Premium'), - type: 'number', - width: 100, - renderCell: (params) => { - return ( -
- {parseFloat(parseFloat(params.row.premium).toFixed(4)) + '%'} -
- ); - }, - }, - ]} - components={{ - NoRowsOverlay: () => ( - -
- {this.NoOrdersFound()} - - ), - NoResultsOverlay: () => ( - - {t('Filter has no results')} - - ), - }} - pageSize={this.props.bookLoading ? 0 : this.state.pageSize} - rowsPerPageOptions={[0, 6, 20, 50]} - onPageSizeChange={(newPageSize) => this.setState({ pageSize: newPageSize })} - onRowClick={(params) => this.handleRowClick(params.row.id)} // Whole row is clickable, but the mouse only looks clickly in some places. - /> -
- ); - }; - - bookListTablePhone = () => { - const { t } = this.props; - return ( -
- - (order.type == this.props.type || this.props.type == null) && - (order.currency == this.props.currency || this.props.currency == 0), - )} - columns={[ - // { field: 'id', headerName: 'ID', width: 40 }, - { - field: 'maker_nick', - headerName: t('Robot'), - width: 64, - renderCell: (params) => { - return ( -
- -
- ); - }, - }, - { - field: 'amount', - headerName: t('Amount'), - type: 'number', - width: 84, - renderCell: (params) => { - return ( - -
- {amountToString( - params.row.amount, - params.row.has_range, - params.row.min_amount, - params.row.max_amount, - )} -
-
- ); - }, - }, - { - field: 'currency', - headerName: t('Currency'), - width: 85, - renderCell: (params) => { - const currencyCode = this.getCurrencyCode(params.row.currency); - return ( -
- {currencyCode + ' '} - -
- ); - }, - }, - { field: 'payment_method', headerName: t('Payment Method'), width: 180, hide: 'true' }, - { - field: 'payment_icons', - headerName: t('Pay'), - width: 75, - renderCell: (params) => { - return ( -
- -
- ); - }, - }, - { - field: 'price', - headerName: t('Price'), - type: 'number', - width: 140, - hide: 'true', - renderCell: (params) => { - return ( -
- {pn(params.row.price) + ' ' + params.row.currency + '/BTC'} -
- ); - }, - }, - { - field: 'premium', - headerName: t('Premium'), - type: 'number', - width: 85, - renderCell: (params) => { - return ( - -
- {parseFloat(parseFloat(params.row.premium).toFixed(4)) + '%'} -
-
- ); - }, - }, - ]} - components={{ - NoRowsOverlay: () => ( - -
- {this.NoOrdersFound()} - - ), - NoResultsOverlay: () => ( - - {t('Local filter returns no result')} - - ), - }} - pageSize={this.props.bookLoading ? 0 : this.state.pageSize} - rowsPerPageOptions={[0, 6, 20, 50]} - onPageSizeChange={(newPageSize) => this.setState({ pageSize: newPageSize })} - onRowClick={(params) => this.handleRowClick(params.row.id)} // Whole row is clickable, but the mouse only looks clickly in some places. - /> -
- ); - }; - handleTypeChange = (mouseEvent, val) => { this.props.setAppState({ type: val }); }; @@ -495,45 +112,32 @@ class BookPage extends Component { return this.NoOrdersFound(); } - const components = - this.state.view == 'depth' - ? [ - , - , - ] - : [this.bookListTableDesktop(), this.bookListTablePhone()]; - - return ( - <> - {/* Desktop */} - - -
{components[0]}
-
-
- {/* Smartphone */} - - -
{components[1]}
-
-
- - ); + if (this.state.view === 'depth') { + return ( + + ); + } else { + return ( + + ); + } }; getTitle = () => { @@ -562,7 +166,7 @@ class BookPage extends Component { this.setState({ loading: true }) & this.getOrderDetails(2, 0)} + onClick={() => this.setState({ loading: true }) & this.getOrderDetails()} > @@ -631,15 +235,11 @@ class BookPage extends Component { - {this.props.bookNotFound ? ( - <> - ) : ( - - - {this.getTitle()} - - - )} + + + {this.getTitle()} + + {this.mainView()} diff --git a/frontend/src/components/BookTable.tsx b/frontend/src/components/BookTable.tsx new file mode 100644 index 00000000..2d409747 --- /dev/null +++ b/frontend/src/components/BookTable.tsx @@ -0,0 +1,598 @@ +import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useHistory } from 'react-router-dom'; +import { + Box, + Typography, + Paper, + Stack, + ListItemButton, + ListItemText, + ListItemAvatar, + useTheme, + CircularProgress, +} from '@mui/material'; +import { DataGrid } from '@mui/x-data-grid'; +import currencyDict from '../../static/assets/currencies.json'; +import { Order } from '../models/Order.model'; + +import FlagWithProps from './FlagWithProps'; +import { pn, amountToString } from '../utils/prettyNumbers'; +import PaymentText from './PaymentText'; +import RobotAvatar from './Robots/RobotAvatar'; +import hexToRgb from '../utils/hexToRgb'; +import statusBadgeColor from '../utils/statusBadgeColor'; + +interface Props { + loading: boolean; + orders: Order[]; + type: number; + currency: number; + maxWidth: number; + maxHeight: number; +} + +const BookTable = ({ + loading, + orders, + type, + currency, + maxWidth, + maxHeight, +}: Props): JSX.Element => { + const { t } = useTranslation(); + const theme = useTheme(); + const history = useHistory(); + + const fontSize = theme.typography.fontSize; + + // all sizes in 'em' + const verticalHeightFrame = 6.9075; + const verticalHeightRow = 3.25; + const defaultPageSize = Math.max( + Math.floor((maxHeight - verticalHeightFrame) / verticalHeightRow), + 1, + ); + const height = defaultPageSize * verticalHeightRow + verticalHeightFrame; + + const [pageSize, setPageSize] = useState(0); + const [useDefaultPageSize, setUseDefaultPageSize] = useState(true); + useEffect(() => { + if (useDefaultPageSize) { + setPageSize(defaultPageSize); + } + }); + + const premiumColor = function (baseColor: string, accentColor: string, point: number) { + const baseRGB = hexToRgb(baseColor); + const accentRGB = hexToRgb(accentColor); + const redDiff = accentRGB[0] - baseRGB[0]; + const red = baseRGB[0] + redDiff * point; + const greenDiff = accentRGB[1] - baseRGB[1]; + const green = baseRGB[1] + greenDiff * point; + const blueDiff = accentRGB[2] - baseRGB[2]; + const blue = baseRGB[2] + blueDiff * point; + return `rgb(${Math.round(red)}, ${Math.round(green)}, ${Math.round(blue)}, ${ + 0.7 + point * 0.3 + })`; + }; + + const localeText = { + MuiTablePagination: { labelRowsPerPage: t('Orders per page:') }, + noRowsLabel: t('No rows'), + noResultsOverlayLabel: t('No results found.'), + errorOverlayDefaultLabel: t('An error occurred.'), + toolbarColumns: t('Columns'), + toolbarColumnsLabel: t('Select columns'), + columnsPanelTextFieldLabel: t('Find column'), + columnsPanelTextFieldPlaceholder: t('Column title'), + columnsPanelDragIconLabel: t('Reorder column'), + columnsPanelShowAllButton: t('Show all'), + columnsPanelHideAllButton: t('Hide all'), + filterPanelAddFilter: t('Add filter'), + filterPanelDeleteIconLabel: t('Delete'), + filterPanelLinkOperator: t('Logic operator'), + filterPanelOperators: t('Operator'), + filterPanelOperatorAnd: t('And'), + filterPanelOperatorOr: t('Or'), + filterPanelColumns: t('Columns'), + filterPanelInputLabel: t('Value'), + filterPanelInputPlaceholder: t('Filter value'), + filterOperatorContains: t('contains'), + filterOperatorEquals: t('equals'), + filterOperatorStartsWith: t('starts with'), + filterOperatorEndsWith: t('ends with'), + filterOperatorIs: t('is'), + filterOperatorNot: t('is not'), + filterOperatorAfter: t('is after'), + filterOperatorOnOrAfter: t('is on or after'), + filterOperatorBefore: t('is before'), + filterOperatorOnOrBefore: t('is on or before'), + filterOperatorIsEmpty: t('is empty'), + filterOperatorIsNotEmpty: t('is not empty'), + filterOperatorIsAnyOf: t('is any of'), + filterValueAny: t('any'), + filterValueTrue: t('true'), + filterValueFalse: t('false'), + columnMenuLabel: t('Menu'), + columnMenuShowColumns: t('Show columns'), + columnMenuFilter: t('Filter'), + columnMenuHideColumn: t('Hide'), + columnMenuUnsort: t('Unsort'), + columnMenuSortAsc: t('Sort by ASC'), + columnMenuSortDesc: t('Sort by DESC'), + columnHeaderFiltersLabel: t('Show filters'), + columnHeaderSortIconLabel: t('Sort'), + booleanCellTrueLabel: t('yes'), + booleanCellFalseLabel: t('no'), + }; + + const robotObj = function (width: number, hide: boolean) { + return { + hide, + field: 'maker_nick', + headerName: t('Robot'), + width: width * fontSize, + renderCell: (params) => { + return ( + + + + + + + ); + }, + }; + }; + + const robotSmallObj = function (width: number, hide: boolean) { + return { + hide, + field: 'maker_nick', + headerName: t('Robot'), + width: width * fontSize, + renderCell: (params) => { + return ( +
+ + + +
+ ); + }, + }; + }; + + const typeObj = function (width: number, hide: boolean) { + return { + hide, + field: 'type', + headerName: t('Is'), + width: width * fontSize, + renderCell: (params) => (params.row.type ? t('Seller') : t('Buyer')), + }; + }; + + const amountObj = function (width: number, hide: boolean) { + return { + hide, + field: 'amount', + headerName: t('Amount'), + type: 'number', + width: width * fontSize, + renderCell: (params) => { + return ( +
+ {amountToString( + params.row.amount, + params.row.has_range, + params.row.min_amount, + params.row.max_amount, + )} +
+ ); + }, + }; + }; + + const currencyObj = function (width: number, hide: boolean) { + return { + hide, + field: 'currency', + headerName: t('Currency'), + width: width * fontSize, + renderCell: (params) => { + const currencyCode = currencyDict[params.row.currency.toString()]; + return ( +
+ {currencyCode + ' '} + +
+ ); + }, + }; + }; + + const paymentObj = function (width: number, hide: boolean) { + return { + hide, + field: 'payment_method', + headerName: t('Payment Method'), + width: width * fontSize, + renderCell: (params) => { + return ( +
+ +
+ ); + }, + }; + }; + + const paymentSmallObj = function (width: number, hide: boolean) { + return { + hide, + field: 'payment_icons', + headerName: t('Pay'), + width: width * fontSize, + renderCell: (params) => { + return ( +
+ +
+ ); + }, + }; + }; + + const priceObj = function (width: number, hide: boolean) { + return { + hide, + field: 'price', + headerName: t('Price'), + type: 'number', + width: width * fontSize, + renderCell: (params) => { + const currencyCode = currencyDict[params.row.currency.toString()]; + return ( +
{`${pn(params.row.price)} ${currencyCode}/BTC`}
+ ); + }, + }; + }; + + const premiumObj = function (width: number, hide: boolean) { + // coloring premium texts based on 4 params: + // Hardcoded: a sell order at 0% is an outstanding premium + // Hardcoded: a buy order at 10% is an outstanding premium + const sellStandardPremium = 10; + const buyOutstandingPremium = 10; + return { + hide, + field: 'premium', + headerName: t('Premium'), + type: 'number', + width: width * fontSize, + renderCell: (params) => { + let fontColor = `rgb(0,0,0)`; + if (params.row.type === 0) { + var premiumPoint = params.row.premium / buyOutstandingPremium; + premiumPoint = premiumPoint < 0 ? 0 : premiumPoint > 1 ? 1 : premiumPoint; + fontColor = premiumColor( + theme.palette.text.primary, + theme.palette.secondary.dark, + premiumPoint, + ); + } else { + var premiumPoint = (sellStandardPremium - params.row.premium) / sellStandardPremium; + premiumPoint = premiumPoint < 0 ? 0 : premiumPoint > 1 ? 1 : premiumPoint; + fontColor = premiumColor( + theme.palette.text.primary, + theme.palette.primary.dark, + premiumPoint, + ); + } + const fontWeight = 400 + Math.round(premiumPoint * 5) * 100; + return ( +
+ + {parseFloat(parseFloat(params.row.premium).toFixed(4)) + '%'} + +
+ ); + }, + }; + }; + + const timerObj = function (width: number, hide: boolean) { + return { + hide, + field: 'escrow_duration', + headerName: t('Timer'), + type: 'number', + width: width * fontSize, + renderCell: (params) => { + const hours = Math.round(params.row.escrow_duration / 3600); + const minutes = Math.round((params.row.escrow_duration - hours * 3600) / 60); + return
{hours > 0 ? `${hours}h` : `${minutes}m`}
; + }, + }; + }; + + const expiryObj = function (width: number, hide: boolean) { + return { + hide, + field: 'expires_at', + headerName: t('Expiry'), + type: 'string', + width: width * fontSize, + renderCell: (params) => { + const expiresAt = new Date(params.row.expires_at); + const timeToExpiry = Math.abs(expiresAt - new Date()); + const percent = Math.round((timeToExpiry / (24 * 60 * 60 * 1000)) * 100); + const hours = Math.round(timeToExpiry / (3600 * 1000)); + const minutes = Math.round((timeToExpiry - hours * (3600 * 1000)) / 60000); + return ( + + + + + {hours > 0 ? `${hours}h` : `${minutes}m`} + + + + ); + }, + }; + }; + + const satoshisObj = function (width: number, hide: boolean) { + return { + hide, + field: 'satoshis_now', + headerName: t('Sats now'), + type: 'number', + width: width * fontSize, + renderCell: (params) => { + return ( +
+ {`${pn(Math.round(params.row.satoshis_now / 1000))}K`} +
+ ); + }, + }; + }; + + const idObj = function (width: number, hide: boolean) { + return { + hide, + field: 'id', + headerName: 'Order ID', + width: width * fontSize, + renderCell: (params) => { + return ( +
+ + {`#${params.row.id}`} + +
+ ); + }, + }; + }; + + const columnSpecs = { + amount: { + priority: 1, + order: 4, + normal: { + width: 6.5, + object: amountObj, + }, + }, + currency: { + priority: 2, + order: 5, + normal: { + width: 5.8, + object: currencyObj, + }, + }, + premium: { + priority: 3, + order: 11, + normal: { + width: 6, + object: premiumObj, + }, + }, + robot: { + priority: 4, + order: 1, + normal: { + width: 17.14, + object: robotObj, + }, + small: { + width: 4.3, + object: robotSmallObj, + }, + }, + paymentMethod: { + priority: 5, + order: 6, + normal: { + width: 12.85, + object: paymentObj, + }, + small: { + width: 5.8, + object: paymentSmallObj, + }, + }, + price: { + priority: 6, + order: 10, + normal: { + width: 10, + object: priceObj, + }, + }, + expires_at: { + priority: 7, + order: 7, + normal: { + width: 5.8, + object: expiryObj, + }, + }, + escrow_duration: { + priority: 8, + order: 8, + normal: { + width: 3.8, + object: timerObj, + }, + }, + satoshisNow: { + priority: 9, + order: 9, + normal: { + width: 6, + object: satoshisObj, + }, + }, + type: { + priority: 10, + order: 2, + normal: { + width: 4.3, + object: typeObj, + }, + }, + id: { + priority: 11, + order: 12, + normal: { + width: 4.8, + object: idObj, + }, + }, + }; + + const filteredColumns = function (maxWidth: number) { + const useSmall = maxWidth < 70; + const selectedColumns: object[] = []; + let width: number = 0; + + for (const [key, value] of Object.entries(columnSpecs)) { + const colWidth = useSmall && value.small ? value.small.width : value.normal.width; + const colObject = useSmall && value.small ? value.small.object : value.normal.object; + + if (width + colWidth < maxWidth || selectedColumns.length < 2) { + width = width + colWidth; + selectedColumns.push([colObject(colWidth, false), value.order]); + } else { + selectedColumns.push([colObject(colWidth, true), value.order]); + } + } + + // sort columns by column.order value + selectedColumns.sort(function (first, second) { + return first[1] - second[1]; + }); + + const columns = selectedColumns.map(function (item) { + return item[0]; + }); + + return [columns, width * 0.875 + 0.15]; + }; + + const [columns, width] = filteredColumns(maxWidth); + + return ( + + + (order.type == type || type == null) && (order.currency == currency || currency == 0), + )} + loading={loading} + columns={columns} + components={{ + NoResultsOverlay: () => ( + + {t('Filter has no results')} + + ), + }} + pageSize={loading ? 0 : pageSize} + rowsPerPageOptions={[0, pageSize, defaultPageSize * 2, 50, 100]} + onPageSizeChange={(newPageSize) => { + setPageSize(newPageSize); + setUseDefaultPageSize(false); + }} + onRowClick={(params) => history.push('/order/' + params.row.id)} // Whole row is clickable, but the mouse only looks clickly in some places. + /> + + ); +}; + +export default BookTable; diff --git a/frontend/src/components/Charts/DepthChart/index.tsx b/frontend/src/components/Charts/DepthChart/index.tsx index 9c4c45da..8f4051f0 100644 --- a/frontend/src/components/Charts/DepthChart/index.tsx +++ b/frontend/src/components/Charts/DepthChart/index.tsx @@ -30,6 +30,7 @@ import PaymentText from '../../PaymentText'; import getNivoScheme from '../NivoScheme'; import median from '../../../utils/match'; import { apiClient } from '../../../services/api/index'; +import statusBadgeColor from '../../../utils/statusBadgeColor'; interface DepthChartProps { bookLoading: boolean; @@ -38,7 +39,8 @@ interface DepthChartProps { currency: number; setAppState: (state: object) => void; limits: LimitList; - compact?: boolean; + maxWidth: number; + maxHeight: number; } const DepthChart: React.FC = ({ @@ -48,7 +50,8 @@ const DepthChart: React.FC = ({ currency, setAppState, limits, - compact, + maxWidth, + maxHeight, }) => { const { t } = useTranslation(); const history = useHistory(); @@ -61,6 +64,9 @@ const DepthChart: React.FC = ({ const [currencyCode, setCurrencyCode] = useState(1); const [center, setCenter] = useState(); + const height = maxHeight < 20 ? 20 : maxHeight; + const width = maxWidth < 20 ? 20 : maxWidth > 72.8 ? 72.8 : maxWidth; + useEffect(() => { if (Object.keys(limits).length === 0) { apiClient.get('/api/limits/').then((data) => { @@ -216,17 +222,6 @@ const DepthChart: React.FC = ({ /> ); - const statusBadgeColor = (status: string) => { - if (status === 'Active') { - return 'success'; - } - if (status === 'Seen recently') { - return 'warning'; - } - - return 'error'; - }; - const generateTooltip: React.FunctionComponent = ( pointTooltip: PointTooltipProps, ) => { @@ -293,94 +288,109 @@ const DepthChart: React.FC = ({ history.push('/order/' + point.data?.order?.id); }; - return bookLoading || center == undefined || enrichedOrders.length < 1 ? ( -
- -
- ) : ( - - - - - - - - - - setXRange(xRange + rangeSteps)}> - - + return ( + + + {bookLoading || center == undefined || enrichedOrders.length < 1 ? ( +
+ +
+ ) : ( + + + + + + + + + + setXRange(xRange + rangeSteps)}> + + + + + + {xType === 'base_amount' + ? `${center} ${currencyDict[currencyCode]}` + : `${center}%`} + + + + setXRange(xRange - rangeSteps)} disabled={xRange <= 1}> + + + + + + + Number(value).toFixed(0)} + lineWidth={3} + theme={getNivoScheme(theme)} + colors={[theme.palette.secondary.main, theme.palette.primary.main]} + xScale={{ + type: 'linear', + min: center - xRange, + max: center + xRange, + }} + layers={['axes', 'areas', 'crosshair', 'lines', centerLine, 'slices', 'mesh']} + /> + - - - {xType === 'base_amount' ? `${center} ${currencyDict[currencyCode]}` : `${center}%`} - - - - setXRange(xRange - rangeSteps)} disabled={xRange <= 1}> - - - -
-
- - Number(value).toFixed(0)} - lineWidth={3} - theme={getNivoScheme(theme)} - colors={[theme.palette.secondary.main, theme.palette.primary.main]} - xScale={{ - type: 'linear', - min: center - xRange, - max: center + xRange, - }} - layers={['axes', 'areas', 'crosshair', 'lines', centerLine, 'slices', 'mesh']} - /> - -
+ )} + + ); }; diff --git a/frontend/src/components/Dialogs/UpdateClient.tsx b/frontend/src/components/Dialogs/UpdateClient.tsx index 562e54ea..548f7cee 100644 --- a/frontend/src/components/Dialogs/UpdateClient.tsx +++ b/frontend/src/components/Dialogs/UpdateClient.tsx @@ -46,7 +46,7 @@ const UpdateClientDialog = ({ {t( 'The RoboSats coordinator is on version {{coordinatorVersion}}, but your client app is {{clientVersion}}. This version mismatch might lead to a bad user experience.', - { coordinatorVersion: coordinatorVersion, clientVersion: clientVersion }, + { coordinatorVersion, clientVersion }, )} @@ -63,7 +63,7 @@ const UpdateClientDialog = ({ diff --git a/frontend/src/components/HomePage.js b/frontend/src/components/HomePage.js index 725d3ec3..7a949402 100644 --- a/frontend/src/components/HomePage.js +++ b/frontend/src/components/HomePage.js @@ -31,6 +31,23 @@ export default class HomePage extends Component { }; } + componentDidMount = () => { + if (typeof window !== undefined) { + this.setState({ windowWidth: window.innerWidth, windowHeight: window.innerHeight }); + window.addEventListener('resize', this.onResize); + } + }; + + componentWillUnmount = () => { + if (typeof window !== undefined) { + window.removeEventListener('resize', this.onResize); + } + }; + + onResize = () => { + this.setState({ windowWidth: window.innerWidth, windowHeight: window.innerHeight }); + }; + setAppState = (newState) => { this.setState(newState); }; @@ -106,7 +123,7 @@ export default class HomePage extends Component {
{ const { t } = this.props; return ( @@ -694,7 +682,7 @@ class OrderPage extends Component { void; } -const RobotAvatar: React.FC = ({ +const RobotAvatar: React.FC = ({ nickname, orderType, statusColor, diff --git a/frontend/src/utils/checkVer.ts b/frontend/src/utils/checkVer.ts index a157d0f5..771157c8 100644 --- a/frontend/src/utils/checkVer.ts +++ b/frontend/src/utils/checkVer.ts @@ -15,8 +15,8 @@ export const checkVer: ( const patchAvailable = !updateAvailable && patch > Number(semver[2]); return { - updateAvailable: updateAvailable, - patchAvailable: patchAvailable, + updateAvailable, + patchAvailable, coordinatorVersion: `v${major}.${minor}.${patch}`, clientVersion: `v${semver[0]}.${semver[1]}.${semver[2]}`, }; diff --git a/frontend/src/utils/hexToRgb.js b/frontend/src/utils/hexToRgb.js new file mode 100644 index 00000000..e88d7f82 --- /dev/null +++ b/frontend/src/utils/hexToRgb.js @@ -0,0 +1,14 @@ +export default function hexToRgb(c) { + if (c.includes('rgb')) { + const vals = c.split('(')[1].split(')')[0]; + return vals.split(','); + } + if (/^#([a-f0-9]{3}){1,2}$/.test(c)) { + if (c.length == 4) { + c = '#' + [c[1], c[1], c[2], c[2], c[3], c[3]].join(''); + } + c = '0x' + c.substring(1); + return [(c >> 16) & 255, (c >> 8) & 255, c & 255]; + } + return ''; +} diff --git a/frontend/src/utils/statusBadgeColor.ts b/frontend/src/utils/statusBadgeColor.ts new file mode 100644 index 00000000..8c571e91 --- /dev/null +++ b/frontend/src/utils/statusBadgeColor.ts @@ -0,0 +1,9 @@ +export default function statusBadgeColor(status: string) { + if (status === 'Active') { + return 'success'; + } + if (status === 'Seen recently') { + return 'warning'; + } + return 'error'; +} diff --git a/frontend/static/css/index.css b/frontend/static/css/index.css index a115f0df..350a46d3 100644 --- a/frontend/static/css/index.css +++ b/frontend/static/css/index.css @@ -169,11 +169,12 @@ input[type='number'] { width: auto !important; } -@media (max-width: 929px) { +@media (max-height: 725px) { .appCenter:has(> div.MuiGrid-root:first-child, > div.MuiBox-root:first-child) { - overflow-y: scroll; - margin-top: 12px; - padding-bottom: 25px; + overflow-y: auto; + margin-top: 1em; + padding-bottom: 3em; height: 100%; + width: 100%; } }