add orderbook grouping ui

This commit is contained in:
saml33 2022-09-18 22:53:28 +10:00
parent 9d146f7b02
commit 2fea0ed266
5 changed files with 238 additions and 58 deletions

View File

@ -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

View File

@ -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'])
)

View File

@ -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>
)

View File

@ -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">

View File

@ -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
}