layout toggle for orderbook

This commit is contained in:
saml33 2021-04-26 22:53:27 +10:00
parent 36b3b62a95
commit dbf6e7087f
6 changed files with 319 additions and 83 deletions

View File

@ -73,17 +73,23 @@ export default function MarginBalances() {
{selectedMangoGroup ? ( {selectedMangoGroup ? (
<table className={`min-w-full`}> <table className={`min-w-full`}>
<thead> <thead>
<tr className={`text-center text-th-fgd-4 mb-2`}> <tr className={`text-center text-th-fgd-4 mb-2 text-xs`}>
<th scope="col" className={`flex-auto font-normal`}> <th scope="col" className={`flex-auto font-normal text-left`}>
Assets Assets
</th> </th>
<th scope="col" className={`flex-auto font-normal`}> <th
scope="col"
className={`flex-auto font-normal text-right px-2`}
>
Deposits Deposits
</th> </th>
<th scope="col" className={`flex-auto font-normal`}> <th
scope="col"
className={`flex-auto font-normal text-right px-2`}
>
Borrows Borrows
</th> </th>
<th scope="col" className={`flex-auto font-normal`}> <th scope="col" className={`flex-auto font-normal text-right`}>
Interest (APY) Interest (APY)
</th> </th>
</tr> </tr>
@ -101,7 +107,7 @@ export default function MarginBalances() {
/> />
<span>{name}</span> <span>{name}</span>
</td> </td>
<td className={`text-center`}> <td className={`text-right px-2`}>
{selectedMarginAccount {selectedMarginAccount
? floorToDecimal( ? floorToDecimal(
selectedMarginAccount.getUiDeposit( selectedMarginAccount.getUiDeposit(
@ -112,14 +118,14 @@ export default function MarginBalances() {
).toFixed(tokenPrecision[name]) ).toFixed(tokenPrecision[name])
: (0).toFixed(tokenPrecision[name])} : (0).toFixed(tokenPrecision[name])}
</td> </td>
<td className={`text-center`}> <td className={`text-right px-2`}>
{selectedMarginAccount {selectedMarginAccount
? selectedMarginAccount ? selectedMarginAccount
.getUiBorrow(selectedMangoGroup, i) .getUiBorrow(selectedMangoGroup, i)
.toFixed(tokenPrecision[name]) .toFixed(tokenPrecision[name])
: (0).toFixed(tokenPrecision[name])} : (0).toFixed(tokenPrecision[name])}
</td> </td>
<td className={`text-center`}> <td className={`text-right`}>
<span className={`text-th-green`}> <span className={`text-th-green`}>
+{(selectedMangoGroup.getDepositRate(i) * 100).toFixed(2)} +{(selectedMangoGroup.getDepositRate(i) * 100).toFixed(2)}
% %

View File

@ -1,48 +1,74 @@
import React, { useRef, useEffect, useState } from 'react' import React, { useRef, useEffect, useState } from 'react'
import styled from '@emotion/styled' import styled from '@emotion/styled'
import { css, keyframes } from '@emotion/react'
import useInterval from '../hooks/useInterval' import useInterval from '../hooks/useInterval'
import usePrevious from '../hooks/usePrevious' import usePrevious from '../hooks/usePrevious'
import { isEqual, getDecimalCount } from '../utils/' import { isEqual, getDecimalCount } from '../utils/'
import { ArrowUpIcon, ArrowDownIcon } from '@heroicons/react/solid' import {
ArrowUpIcon,
ArrowDownIcon,
SwitchHorizontalIcon,
} from '@heroicons/react/solid'
import useMarkPrice from '../hooks/useMarkPrice' import useMarkPrice from '../hooks/useMarkPrice'
import useOrderbook from '../hooks/useOrderbook' import useOrderbook from '../hooks/useOrderbook'
import useMarket from '../hooks/useMarket' import useMarket from '../hooks/useMarket'
import { ElementTitle } from './styles' import { ElementTitle } from './styles'
import useMangoStore from '../stores/useMangoStore' import useMangoStore from '../stores/useMangoStore'
import Tooltip from './Tooltip'
import FloatingElement from './FloatingElement'
const Line = styled.div<any>` const Line = styled.div<any>`
text-align: ${(props) => (props.invert ? 'left' : 'right')}; text-align: ${(props) => (props.invert ? 'left' : 'right')};
float: ${(props) => (props.invert ? 'left' : 'right')};
height: 100%; height: 100%;
filter: opacity(70%); filter: opacity(40%);
${(props) => props['data-width'] && `width: ${props['data-width']};`} ${(props) => props['data-width'] && `width: ${props['data-width']};`}
` `
const fadeIn = keyframes`
function getCumulativeOrderbookSide( from {
orders, opacity: 0;
totalSize,
depth,
backwards = false
) {
let cumulative = orders
.slice(0, depth)
.reduce((cumulative, [price, size], i) => {
const cumulativeSize = (cumulative[i - 1]?.cumulativeSize || 0) + size
cumulative.push({
price,
size,
cumulativeSize,
sizePercent: Math.round((cumulativeSize / (totalSize || 1)) * 100),
})
return cumulative
}, [])
if (backwards) {
cumulative = cumulative.reverse()
} }
return cumulative
}
export default function Orderbook({ depth = 7 }) { to {
opacity: 1;
}
`
const FlipCard = styled.div`
background-color: transparent;
height: 100%;
perspective: 1000px;
`
const FlipCardInner = styled.div<any>`
position: relative;
width: 100%;
height: 100%;
text-align: center;
transition: transform 0.8s ease-out;
transform-style: preserve-3d;
transform: ${({ flip }) => (flip ? 'rotateY(0deg)' : 'rotateY(180deg)')};
`
const FlipCardFront = styled.div`
position: absolute;
width: 100%;
height: 100%;
`
const FlipCardBack = styled.div`
position: absolute;
width: 100%;
height: 100%;
transform: rotateY(180deg);
`
const StyledFloatingElement = styled(FloatingElement)`
animation: ${css`
${fadeIn} 1s linear
`};
`
export default function Orderbook({ depth = 8 }) {
const markPrice = useMarkPrice() const markPrice = useMarkPrice()
const [orderbook] = useOrderbook() const [orderbook] = useOrderbook()
const { baseCurrency, quoteCurrency } = useMarket() const { baseCurrency, quoteCurrency } = useMarket()
@ -52,6 +78,8 @@ export default function Orderbook({ depth = 7 }) {
const lastOrderbookData = useRef(null) const lastOrderbookData = useRef(null)
const [orderbookData, setOrderbookData] = useState(null) const [orderbookData, setOrderbookData] = useState(null)
const [defaultLayout, setDefaultLayout] = useState(true)
const [loading, setLoading] = useState(true)
useInterval(() => { useInterval(() => {
if ( if (
@ -72,19 +100,30 @@ export default function Orderbook({ depth = 7 }) {
depth, depth,
false false
) )
const asksToDisplay = getCumulativeOrderbookSide( const asksToDisplay = defaultLayout
asks, ? getCumulativeOrderbookSide(asks, totalSize, depth, false)
totalSize, : getCumulativeOrderbookSide(asks, totalSize, depth, true)
depth,
true
)
currentOrderbookData.current = { currentOrderbookData.current = {
bids: orderbook?.bids, bids: orderbook?.bids,
asks: orderbook?.asks, asks: orderbook?.asks,
} }
if (bidsToDisplay[0] && asksToDisplay[0]) {
const bid = bidsToDisplay[0].price
const ask = defaultLayout
? asksToDisplay[0].price
: asksToDisplay[7].price
const spread = ask - bid
const spreadPercentage = (spread / ask) * 100
setOrderbookData({ bids: bidsToDisplay, asks: asksToDisplay }) setOrderbookData({
bids: bidsToDisplay,
asks: asksToDisplay,
spread: spread,
spreadPercentage: spreadPercentage,
})
setLoading(false)
}
} }
}, 250) }, 250)
@ -95,6 +134,30 @@ export default function Orderbook({ depth = 7 }) {
} }
}, [orderbook]) }, [orderbook])
const getCumulativeOrderbookSide = (
orders,
totalSize,
depth,
backwards = false
) => {
let cumulative = orders
.slice(0, depth)
.reduce((cumulative, [price, size], i) => {
const cumulativeSize = (cumulative[i - 1]?.cumulativeSize || 0) + size
cumulative.push({
price,
size,
cumulativeSize,
sizePercent: Math.round((cumulativeSize / (totalSize || 1)) * 100),
})
return cumulative
}, [])
if (backwards) {
cumulative = cumulative.reverse()
}
return cumulative
}
const handlePriceClick = (price) => { const handlePriceClick = (price) => {
console.log('price click') console.log('price click')
@ -111,36 +174,188 @@ export default function Orderbook({ depth = 7 }) {
}) })
} }
const handleLayoutChange = () => {
setLoading(true)
setDefaultLayout(!defaultLayout)
}
return ( return (
<> <>
<ElementTitle>Orderbook</ElementTitle> <FlipCard>
<div className={`text-th-fgd-4 flex justify-between mb-2`}> <FlipCardInner flip={defaultLayout}>
<div className={`text-left`}>Size ({baseCurrency})</div> {defaultLayout ? (
<div className={`text-right`}>Price ({quoteCurrency})</div> <FlipCardFront>
</div> <StyledFloatingElement>
{orderbookData?.asks.map(({ price, size, sizePercent }) => ( <div className="flex items-center justify-between pb-2.5">
<OrderbookRow <div className="w-8 h-8" />
key={price + ''} <ElementTitle noMarignBottom>Orderbook</ElementTitle>
price={price} <div className="flex relative">
size={size} <Tooltip content={'Switch Layout'} className="text-xs py-1">
side={'sell'} <button
sizePercent={sizePercent} onClick={() => handleLayoutChange()}
onPriceClick={() => handlePriceClick(price)} className="flex items-center justify-center rounded-full bg-th-bkg-3 w-8 h-8 hover:text-th-primary focus:outline-none"
onSizeClick={() => handleSizeClick(size)} >
/> <SwitchHorizontalIcon className="w-5 h-5" />
))} </button>
<MarkPriceComponent markPrice={markPrice} /> </Tooltip>
{orderbookData?.bids.map(({ price, size, sizePercent }) => ( </div>
<OrderbookRow </div>
key={price + ''} <MarkPriceComponent markPrice={markPrice} />
price={price} {!loading ? (
size={size} <>
side={'buy'} <div
sizePercent={sizePercent} className={`text-th-fgd-4 flex justify-between mb-2 text-xs`}
onPriceClick={() => handlePriceClick(price)} >
onSizeClick={() => handleSizeClick(size)} <div className={`text-left`}>Size ({baseCurrency})</div>
/> <div className={`text-center`}>
))} Price ({quoteCurrency})
</div>
<div className={`text-right`}>Size ({baseCurrency})</div>
</div>
<div className="flex">
<div className="w-1/2">
{orderbookData?.bids.map(
({ price, size, sizePercent }) => (
<OrderbookRow
key={price + ''}
price={price}
size={size}
side={'buy'}
sizePercent={sizePercent}
onPriceClick={() => handlePriceClick(price)}
onSizeClick={() => handleSizeClick(size)}
/>
)
)}
</div>
<div className="w-1/2">
{/* <div
className={`text-th-fgd-4 flex justify-between mb-2`}
>
<div className={`text-left text-xs px-1`}>
Ask ({quoteCurrency})
</div>
<div className={`text-right text-xs`}>
Size ({baseCurrency})
</div>
</div> */}
{orderbookData?.asks.map(
({ price, size, sizePercent }) => (
<OrderbookRow
invert
key={price + ''}
price={price}
size={size}
side={'sell'}
sizePercent={sizePercent}
onPriceClick={() => handlePriceClick(price)}
onSizeClick={() => handleSizeClick(size)}
/>
)
)}
</div>
</div>
<div className="flex justify-between bg-th-bkg-1 p-2 mt-4 rounded-md text-xs">
<div className="text-th-fgd-3">Spread</div>
<div className="text-th-fgd-1">
{orderbookData?.spread.toFixed(2)}
</div>
<div className="text-th-fgd-1">
{orderbookData?.spreadPercentage.toFixed(2)}%
</div>
</div>
</>
) : (
<div className="animate-pulse space-y-3 w-full">
<div className="h-5 w-full bg-th-bkg-3 rounded" />
<div className="h-5 w-full bg-th-bkg-3 rounded" />
<div className="h-5 w-full bg-th-bkg-3 rounded" />
<div className="h-5 w-full bg-th-bkg-3 rounded" />
<div className="h-5 w-full bg-th-bkg-3 rounded" />
<div className="h-5 w-full bg-th-bkg-3 rounded" />
<div className="h-5 w-full bg-th-bkg-3 rounded" />
<div className="h-5 w-full bg-th-bkg-3 rounded" />
</div>
)}
</StyledFloatingElement>
</FlipCardFront>
) : (
<FlipCardBack>
<StyledFloatingElement>
<div className="flex items-center justify-between pb-2.5">
<div className="w-8 h-8" />
<ElementTitle noMarignBottom>Orderbook</ElementTitle>
<div className="flex relative">
<Tooltip content={'Switch Layout'} className="text-xs py-1">
<button
onClick={() => handleLayoutChange()}
className="flex items-center justify-center rounded-full bg-th-bkg-3 w-8 h-8 hover:text-th-primary focus:outline-none"
>
<SwitchHorizontalIcon className="w-5 h-5" />
</button>
</Tooltip>
</div>
</div>
<MarkPriceComponent markPrice={markPrice} />
{!loading ? (
<>
<div className={`text-th-fgd-4 flex justify-between mb-2`}>
<div className={`text-left text-xs`}>
Size ({baseCurrency})
</div>
<div className={`text-right text-xs`}>
Price ({quoteCurrency})
</div>
</div>
{orderbookData?.asks.map(({ price, size, sizePercent }) => (
<OrderbookRow
key={price + ''}
price={price}
size={size}
side={'sell'}
sizePercent={sizePercent}
onPriceClick={() => handlePriceClick(price)}
onSizeClick={() => handleSizeClick(size)}
/>
))}
<div className="flex justify-between bg-th-bkg-1 p-2 my-2 rounded-md text-xs">
<div className="text-th-fgd-3">Spread</div>
<div className="text-th-fgd-1">
{orderbookData?.spread.toFixed(2)}
</div>
<div className="text-th-fgd-1">
{orderbookData?.spreadPercentage.toFixed(2)}%
</div>
</div>
{orderbookData?.bids.map(({ price, size, sizePercent }) => (
<OrderbookRow
key={price + ''}
price={price}
size={size}
side={'buy'}
sizePercent={sizePercent}
onPriceClick={() => handlePriceClick(price)}
onSizeClick={() => handleSizeClick(size)}
/>
))}
</>
) : (
<div className="animate-pulse space-y-3 w-full">
<div className="h-5 w-full bg-th-bkg-3 rounded" />
<div className="h-5 w-full bg-th-bkg-3 rounded" />
<div className="h-5 w-full bg-th-bkg-3 rounded" />
<div className="h-5 w-full bg-th-bkg-3 rounded" />
<div className="h-5 w-full bg-th-bkg-3 rounded" />
<div className="h-5 w-full bg-th-bkg-3 rounded" />
<div className="h-5 w-full bg-th-bkg-3 rounded" />
<div className="h-5 w-full bg-th-bkg-3 rounded" />
</div>
)}
</StyledFloatingElement>
</FlipCardBack>
)}
</FlipCardInner>
</FlipCard>
</> </>
) )
} }
@ -176,14 +391,21 @@ const OrderbookRow = React.memo<any>(
<div className={`flex text-sm leading-7 justify-between`} ref={element}> <div className={`flex text-sm leading-7 justify-between`} ref={element}>
{invert ? ( {invert ? (
<> <>
<div className={`text-left`}> <div className={`text-left relative flex-1`}>
<Line <Line
invert invert
data-width={sizePercent + '%'} data-width={sizePercent + '%'}
side={side} side={side}
className={`${side === 'buy' ? `bg-th-green` : `bg-th-red`}`} className={`absolute inset-y-0 left-0 ${
side === 'buy' ? `bg-th-green` : `bg-th-red`
}`}
/> />
<div onClick={onPriceClick}>{formattedPrice}</div> <div
onClick={onPriceClick}
className="z-30 relative text-th-fgd-1 px-1"
>
{formattedPrice}
</div>
</div> </div>
<div className={`text-right`} onClick={onSizeClick}> <div className={`text-right`} onClick={onSizeClick}>
{formattedSize} {formattedSize}
@ -206,7 +428,7 @@ const OrderbookRow = React.memo<any>(
side={side} side={side}
/> />
<div <div
className={`z-30 relative text-th-fgd-1`} className={`z-30 relative text-th-fgd-1 px-1`}
onClick={onPriceClick} onClick={onPriceClick}
> >
{formattedPrice} {formattedPrice}
@ -233,7 +455,7 @@ const MarkPriceComponent = React.memo<{ markPrice: number }>(
return ( return (
<div <div
className={`flex justify-center items-center font-bold p-1 ${ className={`flex justify-center items-center font-bold text-lg pb-4 ${
markPrice > previousMarkPrice markPrice > previousMarkPrice
? `text-th-green` ? `text-th-green`
: markPrice < previousMarkPrice : markPrice < previousMarkPrice
@ -242,10 +464,10 @@ const MarkPriceComponent = React.memo<{ markPrice: number }>(
}`} }`}
> >
{markPrice > previousMarkPrice && ( {markPrice > previousMarkPrice && (
<ArrowUpIcon className={`h-4 w-4 mr-1 text-th-green`} /> <ArrowUpIcon className={`h-5 w-5 mr-1 text-th-green`} />
)} )}
{markPrice < previousMarkPrice && ( {markPrice < previousMarkPrice && (
<ArrowDownIcon className={`h-4 w-4 mr-1 text-th-red`} /> <ArrowDownIcon className={`h-5 w-5 mr-1 text-th-red`} />
)} )}
{formattedMarkPrice || '----'} {formattedMarkPrice || '----'}
</div> </div>

View File

@ -35,7 +35,7 @@ export default function PublicTrades() {
return ( return (
<FloatingElement> <FloatingElement>
<ElementTitle>Recent Trades</ElementTitle> <ElementTitle>Recent Trades</ElementTitle>
<div className={`grid grid-cols-3 text-th-fgd-4 mb-2`}> <div className={`grid grid-cols-3 text-th-fgd-4 mb-2 text-xs`}>
<div>Price ({quoteCurrency}) </div> <div>Price ({quoteCurrency}) </div>
<div className={`text-right`}>Size ({baseCurrency})</div> <div className={`text-right`}>Size ({baseCurrency})</div>
<div className={`text-right`}>Time</div> <div className={`text-right`}>Time</div>

View File

@ -259,7 +259,7 @@ export default function TradeForm() {
disabled={tradeType === 'Market'} disabled={tradeType === 'Market'}
prefix={'Price'} prefix={'Price'}
suffix={quoteCurrency} suffix={quoteCurrency}
className="rounded-none" className="rounded-r-none"
wrapperClassName="w-3/5" wrapperClassName="w-3/5"
/> />
<TradeType <TradeType
@ -275,7 +275,7 @@ export default function TradeForm() {
step={market?.minOrderSize || 1} step={market?.minOrderSize || 1}
onChange={(e) => onSetBaseSize(parseFloat(e.target.value))} onChange={(e) => onSetBaseSize(parseFloat(e.target.value))}
value={baseSize} value={baseSize}
className="rounded-none" className="rounded-r-none"
wrapperClassName="w-3/5" wrapperClassName="w-3/5"
prefix={'Size'} prefix={'Size'}
suffix={baseCurrency} suffix={baseCurrency}

View File

@ -73,9 +73,7 @@ const TradePageGrid = () => {
</FloatingElement> </FloatingElement>
</div> </div>
<div key="orderbook"> <div key="orderbook">
<FloatingElement> <Orderbook />
<Orderbook />
</FloatingElement>
</div> </div>
<div key="tradeForm"> <div key="tradeForm">
<TradeForm /> <TradeForm />

View File

@ -1,3 +1,5 @@
@import url('https://fonts.googleapis.com/css2?family=Lato:wght@300;400&display=swap');
:root:not(.theme-dark) { :root:not(.theme-dark) {
--black: #fff; --black: #fff;
--primary: #ff9c24; --primary: #ff9c24;
@ -48,6 +50,10 @@
/* light theme */ /* light theme */
html .chart-page {
font-family: 'Lato', sans-serif;
}
html .loading-indicator { html .loading-indicator {
background: transparent; background: transparent;
} }
@ -371,6 +377,10 @@ html .item-stVdeCwG.interactive-3E0jwVyG:active {
/* dark theme */ /* dark theme */
html.theme-dark .chart-page {
font-family: 'Lato', sans-serif;
}
html.theme-dark .loading-indicator { html.theme-dark .loading-indicator {
background: transparent; background: transparent;
} }