add orderbook grouping ui
This commit is contained in:
parent
9d146f7b02
commit
2fea0ed266
|
@ -0,0 +1,37 @@
|
|||
import { useTheme } from 'next-themes'
|
||||
import { COLORS } from 'styles/colors'
|
||||
|
||||
const OrderbookIcon = ({
|
||||
side,
|
||||
className,
|
||||
}: {
|
||||
side: 'buy' | 'sell'
|
||||
className?: string
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
const oppositeSideColor =
|
||||
theme === 'Light' ? 'rgba(0,0,0,0.2)' : 'rgba(255,255,255,0.2)'
|
||||
const buyColor = side === 'buy' ? COLORS.GREEN[theme] : oppositeSideColor
|
||||
const sellColor = side === 'sell' ? COLORS.RED[theme] : oppositeSideColor
|
||||
return (
|
||||
<svg
|
||||
className={`${className}`}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect x="3" y="3" width="8" height="2" fill={buyColor} />
|
||||
<rect x="3" y="11" width="8" height="2" fill={buyColor} />
|
||||
<rect x="3" y="7" width="8" height="2" fill={buyColor} />
|
||||
<rect x="3" y="15" width="8" height="2" fill={buyColor} />
|
||||
<rect x="3" y="19" width="8" height="2" fill={buyColor} />
|
||||
<rect x="13" y="3" width="8" height="2" fill={sellColor} />
|
||||
<rect x="13" y="11" width="8" height="2" fill={sellColor} />
|
||||
<rect x="13" y="7" width="8" height="2" fill={sellColor} />
|
||||
<rect x="13" y="15" width="8" height="2" fill={sellColor} />
|
||||
<rect x="13" y="19" width="8" height="2" fill={sellColor} />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default OrderbookIcon
|
|
@ -0,0 +1,77 @@
|
|||
import React, { useMemo } from 'react'
|
||||
import { Listbox } from '@headlessui/react'
|
||||
import { ChevronDownIcon } from '@heroicons/react/20/solid'
|
||||
import { isEqual } from '../../utils'
|
||||
|
||||
const GroupSize = ({
|
||||
tickSize,
|
||||
value,
|
||||
onChange,
|
||||
className,
|
||||
}: {
|
||||
tickSize: number
|
||||
value: number
|
||||
onChange: (x: number) => void
|
||||
className?: string
|
||||
}) => {
|
||||
const sizes = useMemo(
|
||||
() => [
|
||||
tickSize,
|
||||
tickSize * 5,
|
||||
tickSize * 10,
|
||||
tickSize * 50,
|
||||
tickSize * 100,
|
||||
],
|
||||
[tickSize]
|
||||
)
|
||||
|
||||
return (
|
||||
<div className={`relative ${className}`}>
|
||||
<Listbox value={value} onChange={onChange}>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Listbox.Button
|
||||
className={`default-transition flex h-6 items-center rounded border border-th-bkg-3 bg-th-bkg-1 py-1 font-normal hover:bg-th-bkg-2 focus:border-th-bkg-4 focus:outline-none`}
|
||||
>
|
||||
<div
|
||||
className={`flex items-center justify-between space-x-1 pr-1 pl-2 font-mono text-xs leading-none`}
|
||||
>
|
||||
<span className="text-th-fgd-2">{value}</span>
|
||||
|
||||
<ChevronDownIcon
|
||||
className={`default-transition h-4 w-4 text-th-fgd-3 ${
|
||||
open ? 'rotate-180 transform' : 'rotate-360 transform'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
</Listbox.Button>
|
||||
{open ? (
|
||||
<Listbox.Options
|
||||
static
|
||||
className={`thin-scroll absolute left-0 top-7 z-20 w-full space-y-2 overflow-auto rounded border border-th-bkg-3 bg-th-bkg-1 p-2 text-th-fgd-2 outline-none`}
|
||||
>
|
||||
{sizes.map((size) => (
|
||||
<Listbox.Option key={size} value={size}>
|
||||
{({ selected }) => (
|
||||
<div
|
||||
className={`default-transition text-right font-mono text-xs text-th-fgd-2 hover:cursor-pointer hover:text-th-primary ${
|
||||
selected && `text-th-primary`
|
||||
}`}
|
||||
>
|
||||
{size}
|
||||
</div>
|
||||
)}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox.Options>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</Listbox>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(GroupSize, (prevProps, nextProps) =>
|
||||
isEqual(prevProps, nextProps, ['tickSize', 'value'])
|
||||
)
|
|
@ -12,6 +12,9 @@ import { floorToDecimal, formatDecimal, getDecimalCount } from 'utils/numbers'
|
|||
import { ORDERBOOK_FLASH_KEY } from 'utils/constants'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Decimal from 'decimal.js'
|
||||
import OrderbookIcon from '@components/icons/OrderbookIcon'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import GroupSize from './GroupSize'
|
||||
|
||||
function decodeBookL2(
|
||||
market: Market,
|
||||
|
@ -154,6 +157,8 @@ const Orderbook = ({ depth = 12 }) => {
|
|||
const [defaultLayout, setDefaultLayout] = useState(true)
|
||||
const [displayCumulativeSize, setDisplayCumulativeSize] = useState(false)
|
||||
const [grouping, setGrouping] = useState(0.01)
|
||||
const [showBuys, setShowBuys] = useState(true)
|
||||
const [showSells, setShowSells] = useState(true)
|
||||
|
||||
const currentOrderbookData = useRef<any>(null)
|
||||
const nextOrderbookData = useRef<any>(null)
|
||||
|
@ -332,6 +337,10 @@ const Orderbook = ({ depth = 12 }) => {
|
|||
}
|
||||
}, [serum3MarketExternal])
|
||||
|
||||
const onGroupSizeChange = (groupSize: number) => {
|
||||
setGrouping(groupSize)
|
||||
}
|
||||
|
||||
if (!serum3MarketExternal) return null
|
||||
|
||||
return (
|
||||
|
@ -339,68 +348,111 @@ const Orderbook = ({ depth = 12 }) => {
|
|||
<div className="sticky top-0 z-20 flex h-[49px] items-center border-b border-th-bkg-3 bg-th-bkg-1 px-4">
|
||||
<h2 className="text-sm text-th-fgd-3">Orderbook</h2>
|
||||
</div>
|
||||
<div className="flex items-center justify-between px-4 py-2 text-xs text-th-fgd-4">
|
||||
<div className="flex items-center justify-between px-4 py-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Tooltip content="Show Buys" placement="top">
|
||||
<button
|
||||
className={`rounded ${
|
||||
showBuys ? 'bg-th-bkg-3' : 'bg-th-bkg-2'
|
||||
} default-transition flex h-6 w-6 items-center justify-center hover:border-th-fgd-4 focus:outline-none disabled:cursor-not-allowed`}
|
||||
onClick={() => setShowBuys(!showBuys)}
|
||||
disabled={!showSells}
|
||||
>
|
||||
<OrderbookIcon className="h-4 w-4" side="buy" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip content="Show Sells" placement="top">
|
||||
<button
|
||||
className={`rounded ${
|
||||
showSells ? 'bg-th-bkg-3' : 'bg-th-bkg-2'
|
||||
} default-transition flex h-6 w-6 items-center justify-center hover:border-th-fgd-4 focus:outline-none disabled:cursor-not-allowed`}
|
||||
onClick={() => setShowSells(!showSells)}
|
||||
disabled={!showBuys}
|
||||
>
|
||||
<OrderbookIcon className="h-4 w-4" side="sell" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Tooltip content="Grouping" placement="top">
|
||||
<GroupSize
|
||||
tickSize={serum3MarketExternal.tickSize}
|
||||
onChange={onGroupSizeChange}
|
||||
value={grouping}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex items-center justify-between px-4 pb-2 text-xs text-th-fgd-4">
|
||||
<div>Size</div>
|
||||
<div>Price</div>
|
||||
</div>
|
||||
<div className="">
|
||||
{orderbookData?.asks.map(
|
||||
({
|
||||
price,
|
||||
size,
|
||||
cumulativeSize,
|
||||
sizePercent,
|
||||
maxSizePercent,
|
||||
}: cumOrderbookSide) => (
|
||||
<OrderbookRow
|
||||
market={serum3MarketExternal}
|
||||
// hasOpenOrder={hasOpenOrderForPriceGroup(
|
||||
// openOrderPrices,
|
||||
// price,
|
||||
// grouping
|
||||
// )}
|
||||
key={price + ''}
|
||||
price={price}
|
||||
size={displayCumulativeSize ? cumulativeSize : size}
|
||||
side="sell"
|
||||
sizePercent={displayCumulativeSize ? maxSizePercent : sizePercent}
|
||||
grouping={grouping}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<div className="my-2 flex justify-between border-y border-th-bkg-2 py-2 px-4 text-xs">
|
||||
<div className="text-th-fgd-3">{t('spread')}</div>
|
||||
<div className="text-th-fgd-1">
|
||||
{orderbookData?.spread.toFixed(2)}
|
||||
{showSells
|
||||
? orderbookData?.asks.map(
|
||||
({
|
||||
price,
|
||||
size,
|
||||
cumulativeSize,
|
||||
sizePercent,
|
||||
maxSizePercent,
|
||||
}: cumOrderbookSide) => (
|
||||
<OrderbookRow
|
||||
market={serum3MarketExternal}
|
||||
// hasOpenOrder={hasOpenOrderForPriceGroup(
|
||||
// openOrderPrices,
|
||||
// price,
|
||||
// grouping
|
||||
// )}
|
||||
key={price + ''}
|
||||
price={price}
|
||||
size={displayCumulativeSize ? cumulativeSize : size}
|
||||
side="sell"
|
||||
sizePercent={
|
||||
displayCumulativeSize ? maxSizePercent : sizePercent
|
||||
}
|
||||
grouping={grouping}
|
||||
/>
|
||||
)
|
||||
)
|
||||
: null}
|
||||
{showBuys && showSells ? (
|
||||
<div className="my-2 flex justify-between border-y border-th-bkg-2 py-2 px-4 text-xs">
|
||||
<div className="text-th-fgd-3">{t('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="text-th-fgd-1">
|
||||
{orderbookData?.spreadPercentage.toFixed(2)}%
|
||||
</div>
|
||||
</div>
|
||||
{orderbookData?.bids.map(
|
||||
({
|
||||
price,
|
||||
size,
|
||||
cumulativeSize,
|
||||
sizePercent,
|
||||
maxSizePercent,
|
||||
}: cumOrderbookSide) => (
|
||||
<OrderbookRow
|
||||
market={serum3MarketExternal}
|
||||
// hasOpenOrder={hasOpenOrderForPriceGroup(
|
||||
// openOrderPrices,
|
||||
// price,
|
||||
// grouping
|
||||
// )}
|
||||
key={price + ''}
|
||||
price={price}
|
||||
size={displayCumulativeSize ? cumulativeSize : size}
|
||||
side="buy"
|
||||
sizePercent={displayCumulativeSize ? maxSizePercent : sizePercent}
|
||||
grouping={grouping}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
) : null}
|
||||
{showBuys
|
||||
? orderbookData?.bids.map(
|
||||
({
|
||||
price,
|
||||
size,
|
||||
cumulativeSize,
|
||||
sizePercent,
|
||||
maxSizePercent,
|
||||
}: cumOrderbookSide) => (
|
||||
<OrderbookRow
|
||||
market={serum3MarketExternal}
|
||||
// hasOpenOrder={hasOpenOrderForPriceGroup(
|
||||
// openOrderPrices,
|
||||
// price,
|
||||
// grouping
|
||||
// )}
|
||||
key={price + ''}
|
||||
price={price}
|
||||
size={displayCumulativeSize ? cumulativeSize : size}
|
||||
side="buy"
|
||||
sizePercent={
|
||||
displayCumulativeSize ? maxSizePercent : sizePercent
|
||||
}
|
||||
grouping={grouping}
|
||||
/>
|
||||
)
|
||||
)
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -187,7 +187,7 @@ const TradeAdvancedPage = () => {
|
|||
containerPadding={[0, 0]}
|
||||
margin={[0, 0]}
|
||||
>
|
||||
<div key="market-header">
|
||||
<div key="market-header" className="z-10">
|
||||
<AdvancedMarketHeader />
|
||||
</div>
|
||||
<div key="tv-chart" className="h-full border border-x-0 border-th-bkg-3">
|
||||
|
|
|
@ -20,3 +20,17 @@ export const retryFn = async (
|
|||
export async function sleep(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
export function isEqual(obj1: any, obj2: any, keys: Array<string>) {
|
||||
if (!keys && Object.keys(obj1).length !== Object.keys(obj2).length) {
|
||||
return false
|
||||
}
|
||||
keys = keys || Object.keys(obj1)
|
||||
for (const k of keys) {
|
||||
if (obj1[k] !== obj2[k]) {
|
||||
// shallow comparison
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue