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

View File

@ -1,48 +1,74 @@
import React, { useRef, useEffect, useState } from 'react'
import styled from '@emotion/styled'
import { css, keyframes } from '@emotion/react'
import useInterval from '../hooks/useInterval'
import usePrevious from '../hooks/usePrevious'
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 useOrderbook from '../hooks/useOrderbook'
import useMarket from '../hooks/useMarket'
import { ElementTitle } from './styles'
import useMangoStore from '../stores/useMangoStore'
import Tooltip from './Tooltip'
import FloatingElement from './FloatingElement'
const Line = styled.div<any>`
text-align: ${(props) => (props.invert ? 'left' : 'right')};
float: ${(props) => (props.invert ? 'left' : 'right')};
height: 100%;
filter: opacity(70%);
filter: opacity(40%);
${(props) => props['data-width'] && `width: ${props['data-width']};`}
`
function 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()
const fadeIn = keyframes`
from {
opacity: 0;
}
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 [orderbook] = useOrderbook()
const { baseCurrency, quoteCurrency } = useMarket()
@ -52,6 +78,8 @@ export default function Orderbook({ depth = 7 }) {
const lastOrderbookData = useRef(null)
const [orderbookData, setOrderbookData] = useState(null)
const [defaultLayout, setDefaultLayout] = useState(true)
const [loading, setLoading] = useState(true)
useInterval(() => {
if (
@ -72,19 +100,30 @@ export default function Orderbook({ depth = 7 }) {
depth,
false
)
const asksToDisplay = getCumulativeOrderbookSide(
asks,
totalSize,
depth,
true
)
const asksToDisplay = defaultLayout
? getCumulativeOrderbookSide(asks, totalSize, depth, false)
: getCumulativeOrderbookSide(asks, totalSize, depth, true)
currentOrderbookData.current = {
bids: orderbook?.bids,
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)
@ -95,6 +134,30 @@ export default function Orderbook({ depth = 7 }) {
}
}, [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) => {
console.log('price click')
@ -111,36 +174,188 @@ export default function Orderbook({ depth = 7 }) {
})
}
const handleLayoutChange = () => {
setLoading(true)
setDefaultLayout(!defaultLayout)
}
return (
<>
<ElementTitle>Orderbook</ElementTitle>
<div className={`text-th-fgd-4 flex justify-between mb-2`}>
<div className={`text-left`}>Size ({baseCurrency})</div>
<div className={`text-right`}>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)}
/>
))}
<MarkPriceComponent markPrice={markPrice} />
{orderbookData?.bids.map(({ price, size, sizePercent }) => (
<OrderbookRow
key={price + ''}
price={price}
size={size}
side={'buy'}
sizePercent={sizePercent}
onPriceClick={() => handlePriceClick(price)}
onSizeClick={() => handleSizeClick(size)}
/>
))}
<FlipCard>
<FlipCardInner flip={defaultLayout}>
{defaultLayout ? (
<FlipCardFront>
<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 text-xs`}
>
<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}>
{invert ? (
<>
<div className={`text-left`}>
<div className={`text-left relative flex-1`}>
<Line
invert
data-width={sizePercent + '%'}
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 className={`text-right`} onClick={onSizeClick}>
{formattedSize}
@ -206,7 +428,7 @@ const OrderbookRow = React.memo<any>(
side={side}
/>
<div
className={`z-30 relative text-th-fgd-1`}
className={`z-30 relative text-th-fgd-1 px-1`}
onClick={onPriceClick}
>
{formattedPrice}
@ -233,7 +455,7 @@ const MarkPriceComponent = React.memo<{ markPrice: number }>(
return (
<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
? `text-th-green`
: markPrice < previousMarkPrice
@ -242,10 +464,10 @@ const MarkPriceComponent = React.memo<{ markPrice: number }>(
}`}
>
{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 && (
<ArrowDownIcon className={`h-4 w-4 mr-1 text-th-red`} />
<ArrowDownIcon className={`h-5 w-5 mr-1 text-th-red`} />
)}
{formattedMarkPrice || '----'}
</div>

View File

@ -35,7 +35,7 @@ export default function PublicTrades() {
return (
<FloatingElement>
<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 className={`text-right`}>Size ({baseCurrency})</div>
<div className={`text-right`}>Time</div>

View File

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

View File

@ -73,9 +73,7 @@ const TradePageGrid = () => {
</FloatingElement>
</div>
<div key="orderbook">
<FloatingElement>
<Orderbook />
</FloatingElement>
<Orderbook />
</div>
<div key="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) {
--black: #fff;
--primary: #ff9c24;
@ -48,6 +50,10 @@
/* light theme */
html .chart-page {
font-family: 'Lato', sans-serif;
}
html .loading-indicator {
background: transparent;
}
@ -371,6 +377,10 @@ html .item-stVdeCwG.interactive-3E0jwVyG:active {
/* dark theme */
html.theme-dark .chart-page {
font-family: 'Lato', sans-serif;
}
html.theme-dark .loading-indicator {
background: transparent;
}