layout toggle for orderbook
This commit is contained in:
parent
36b3b62a95
commit
dbf6e7087f
|
@ -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)}
|
||||
%
|
||||
|
|
|
@ -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,12 +174,138 @@ 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`}>
|
||||
<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-right`}>Price ({quoteCurrency})</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
|
||||
|
@ -129,7 +318,15 @@ export default function Orderbook({ depth = 7 }) {
|
|||
onSizeClick={() => handleSizeClick(size)}
|
||||
/>
|
||||
))}
|
||||
<MarkPriceComponent markPrice={markPrice} />
|
||||
<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 + ''}
|
||||
|
@ -142,6 +339,24 @@ export default function Orderbook({ depth = 7 }) {
|
|||
/>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -73,9 +73,7 @@ const TradePageGrid = () => {
|
|||
</FloatingElement>
|
||||
</div>
|
||||
<div key="orderbook">
|
||||
<FloatingElement>
|
||||
<Orderbook />
|
||||
</FloatingElement>
|
||||
</div>
|
||||
<div key="tradeForm">
|
||||
<TradeForm />
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue