vega-frontend-monorepo/libs/market-depth/src/lib/depth-chart.tsx
Bartłomiej Głownia b9aef78447
Add pagination support to generic-data-provider (#691)
* feat(#638): add pagination support to data-provider

* feat(#638): use infinite rowModelType in market list table

* chore(#638): code style fixes

* feat(#638): fix data provider post update callbacks, handle market list table empty data

* feat(#638): amend variable names to improve code readability
2022-07-05 15:33:50 +02:00

162 lines
4.4 KiB
TypeScript

import { DepthChart } from 'pennant';
import throttle from 'lodash/throttle';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import {
useDataProvider,
addDecimal,
ThemeContext,
} from '@vegaprotocol/react-helpers';
import dataProvider from './market-depth-data-provider';
import {
useCallback,
useEffect,
useMemo,
useRef,
useState,
useContext,
} from 'react';
import type {
MarketDepthSubscription_marketDepthUpdate_buy,
MarketDepthSubscription_marketDepthUpdate_sell,
MarketDepthSubscription_marketDepthUpdate,
} from './__generated__/MarketDepthSubscription';
import type {
MarketDepth_market_depth_buy,
MarketDepth_market_depth_sell,
} from './__generated__/MarketDepth';
import type { DepthChartProps } from 'pennant';
interface DepthChartManagerProps {
marketId: string;
}
interface PriceLevel {
price: number;
volume: number;
}
const formatLevel = (
priceLevel: MarketDepth_market_depth_buy | MarketDepth_market_depth_sell,
decimalPlaces: number
): PriceLevel => ({
price: Number(addDecimal(priceLevel.price, decimalPlaces)),
volume: Number(priceLevel.volume),
});
const updateLevels = (
levels: PriceLevel[],
updates: (
| MarketDepthSubscription_marketDepthUpdate_buy
| MarketDepthSubscription_marketDepthUpdate_sell
)[],
decimalPlaces: number
) => {
updates.forEach((update) => {
const updateLevel = formatLevel(update, decimalPlaces);
let index = levels.findIndex((level) => level.price === updateLevel.price);
if (index !== -1) {
if (update.volume === '0') {
levels.splice(index, 1);
} else {
Object.assign(levels[index], updateLevel);
}
} else if (update.volume !== '0') {
index = levels.findIndex((level) => level.price > updateLevel.price);
if (index !== -1) {
levels.splice(index, 0, updateLevel);
} else {
levels.push(updateLevel);
}
}
});
return levels;
};
const formatMidPrice = (midPrice: string, decimalPlaces: number) =>
Number(addDecimal(midPrice, decimalPlaces));
type DepthData = Pick<DepthChartProps, 'data' | 'midPrice'>;
export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => {
const theme = useContext(ThemeContext);
const variables = useMemo(() => ({ marketId }), [marketId]);
const [depthData, setDepthData] = useState<DepthData | null>(null);
const decimalPlacesRef = useRef<number>(0);
const dataRef = useRef<DepthData | null>(null);
const setDepthDataThrottledRef = useRef(throttle(setDepthData, 1000));
// Apply updates to the table
const update = useCallback(
({ delta }: { delta: MarketDepthSubscription_marketDepthUpdate }) => {
if (!dataRef.current) {
return false;
}
dataRef.current = {
...dataRef.current,
midPrice: delta.market.data?.staticMidPrice
? formatMidPrice(
delta.market.data?.staticMidPrice,
decimalPlacesRef.current
)
: undefined,
data: {
buy: delta.buy
? updateLevels(
dataRef.current.data.buy,
delta.buy,
decimalPlacesRef.current
)
: dataRef.current.data.buy,
sell: delta.sell
? updateLevels(
dataRef.current.data.sell,
delta.sell,
decimalPlacesRef.current
)
: dataRef.current.data.sell,
},
};
setDepthDataThrottledRef.current(dataRef.current);
return true;
},
[]
);
const { data, error, loading } = useDataProvider({
dataProvider,
update,
variables,
});
useEffect(() => {
if (!data) {
dataRef.current = null;
setDepthData(dataRef.current);
return;
}
dataRef.current = {
midPrice: data.data?.staticMidPrice
? formatMidPrice(data.data?.staticMidPrice, data.decimalPlaces)
: undefined,
data: {
buy:
data.depth.buy?.map((priceLevel) =>
formatLevel(priceLevel, data.decimalPlaces)
) ?? [],
sell:
data.depth.sell?.map((priceLevel) =>
formatLevel(priceLevel, data.decimalPlaces)
) ?? [],
},
};
setDepthData(dataRef.current);
decimalPlacesRef.current = data.decimalPlaces;
}, [data]);
return (
<AsyncRenderer loading={loading} error={error} data={data}>
{depthData && <DepthChart {...depthData} theme={theme} />}
</AsyncRenderer>
);
};