feat: update market data components when they are in view (#2607)

This commit is contained in:
Bartłomiej Głownia 2023-01-16 18:51:30 +01:00 committed by GitHub
parent 4f7b589cbd
commit 5a0da4e158
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 443 additions and 473 deletions

View File

@ -215,7 +215,9 @@ export const useOrderValidation = ({
<span> <span>
{t('This market is in auction until it reaches')}{' '} {t('This market is in auction until it reaches')}{' '}
<Tooltip <Tooltip
description={<DataGrid grid={compileGridData(market)} />} description={
<DataGrid grid={compileGridData(market, market.data)} />
}
> >
<span>{t('sufficient liquidity')}</span> <span>{t('sufficient liquidity')}</span>
</Tooltip> </Tooltip>
@ -237,7 +239,9 @@ export const useOrderValidation = ({
<span> <span>
{t('This market is in auction due to')}{' '} {t('This market is in auction due to')}{' '}
<Tooltip <Tooltip
description={<DataGrid grid={compileGridData(market)} />} description={
<DataGrid grid={compileGridData(market, market.data)} />
}
> >
<span>{t('high price volatility')}</span> <span>{t('high price volatility')}</span>
</Tooltip> </Tooltip>
@ -276,7 +280,9 @@ export const useOrderValidation = ({
<span> <span>
{t('This market is in auction until it reaches')}{' '} {t('This market is in auction until it reaches')}{' '}
<Tooltip <Tooltip
description={<DataGrid grid={compileGridData(market)} />} description={
<DataGrid grid={compileGridData(market, market.data)} />
}
> >
<span>{t('sufficient liquidity')}</span> <span>{t('sufficient liquidity')}</span>
</Tooltip> </Tooltip>
@ -300,7 +306,9 @@ export const useOrderValidation = ({
<span> <span>
{t('This market is in auction due to')}{' '} {t('This market is in auction due to')}{' '}
<Tooltip <Tooltip
description={<DataGrid grid={compileGridData(market)} />} description={
<DataGrid grid={compileGridData(market, market.data)} />
}
> >
<span>{t('high price volatility')}</span> <span>{t('high price volatility')}</span>
</Tooltip> </Tooltip>

View File

@ -1,27 +0,0 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
// ====================================================
// GraphQL subscription operation: CandleLive
// ====================================================
export interface CandleLive_candles {
__typename: "Candle";
/**
* Close price (uint64)
*/
close: string;
}
export interface CandleLive {
/**
* Subscribe to the candles updates
*/
candles: CandleLive_candles;
}
export interface CandleLiveVariables {
marketId: string;
}

View File

@ -1,43 +0,0 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type CandleLiveSubscriptionVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
}>;
export type CandleLiveSubscription = { __typename?: 'Subscription', candles: { __typename?: 'Candle', close: string } };
export const CandleLiveDocument = gql`
subscription CandleLive($marketId: ID!) {
candles(marketId: $marketId, interval: INTERVAL_I1H) {
close
}
}
`;
/**
* __useCandleLiveSubscription__
*
* To run a query within a React component, call `useCandleLiveSubscription` and pass it any options that fit your needs.
* When your component renders, `useCandleLiveSubscription` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the subscription, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useCandleLiveSubscription({
* variables: {
* marketId: // value for 'marketId'
* },
* });
*/
export function useCandleLiveSubscription(baseOptions: Apollo.SubscriptionHookOptions<CandleLiveSubscription, CandleLiveSubscriptionVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useSubscription<CandleLiveSubscription, CandleLiveSubscriptionVariables>(CandleLiveDocument, options);
}
export type CandleLiveSubscriptionHookResult = ReturnType<typeof useCandleLiveSubscription>;
export type CandleLiveSubscriptionResult = Apollo.SubscriptionResult<CandleLiveSubscription>;

View File

@ -15,7 +15,7 @@ import {
import type { Candle } from '@vegaprotocol/market-list'; import type { Candle } from '@vegaprotocol/market-list';
import { marketCandlesProvider } from '@vegaprotocol/market-list'; import { marketCandlesProvider } from '@vegaprotocol/market-list';
const DEBOUNCE_UPDATE_TIME = 500; const THROTTLE_UPDATE_TIME = 500;
export const Last24hVolume = ({ export const Last24hVolume = ({
marketId, marketId,
@ -54,7 +54,7 @@ export const Last24hVolume = ({
const throttledSetCandles = useRef( const throttledSetCandles = useRef(
throttle((data: Candle[]) => { throttle((data: Candle[]) => {
setCandleVolume(calcDayVolume(data)); setCandleVolume(calcDayVolume(data));
}, DEBOUNCE_UPDATE_TIME) }, THROTTLE_UPDATE_TIME)
).current; ).current;
const update = useCallback( const update = useCallback(
@ -78,7 +78,7 @@ export const Last24hVolume = ({
throttle((candles: Candle[]) => { throttle((candles: Candle[]) => {
const candle24hAgo = candles?.[0]; const candle24hAgo = candles?.[0];
setVolumeChange(getChange(data || [], candle24hAgo?.close)); setVolumeChange(getChange(data || [], candle24hAgo?.close));
}, DEBOUNCE_UPDATE_TIME) }, THROTTLE_UPDATE_TIME)
).current; ).current;
const updateCandle24hAgo = useCallback( const updateCandle24hAgo = useCallback(

View File

@ -1 +0,0 @@
window._env_ = {};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

View File

@ -16,7 +16,7 @@ import { MarketMarkPrice } from '../../components/market-mark-price';
import { Last24hPriceChange } from '../../components/last-24h-price-change'; import { Last24hPriceChange } from '../../components/last-24h-price-change';
import { Last24hVolume } from '../../components/last-24h-volume'; import { Last24hVolume } from '../../components/last-24h-volume';
import { MarketState } from '../../components/market-state'; import { MarketState } from '../../components/market-state';
import { MarketTradingMode } from '../../components/market-trading-mode'; import { HeaderStatMarketTradingMode } from '../../components/market-trading-mode';
import { MarketLiquiditySupplied } from '../../components/liquidity-supplied'; import { MarketLiquiditySupplied } from '../../components/liquidity-supplied';
import { MarketState as State } from '@vegaprotocol/types'; import { MarketState as State } from '@vegaprotocol/types';
@ -64,22 +64,35 @@ export const TradeMarketHeader = ({
> >
<ExpiryLabel market={market} /> <ExpiryLabel market={market} />
</HeaderStat> </HeaderStat>
<MarketMarkPrice <HeaderStat heading={t('Price')} testId="market-price">
<MarketMarkPrice
marketId={market?.id}
decimalPlaces={market?.decimalPlaces}
/>
</HeaderStat>
<HeaderStat heading={t('Change (24h)')} testId="market-change">
<Last24hPriceChange
marketId={market?.id}
decimalPlaces={market?.decimalPlaces}
/>
</HeaderStat>
<HeaderStat
heading={t('Volume (24h)')}
testId="market-volume"
description={t(
'The total amount of assets traded in the last 24 hours.'
)}
>
<Last24hVolume
marketId={market?.id}
positionDecimalPlaces={market?.positionDecimalPlaces}
/>
</HeaderStat>
<HeaderStatMarketTradingMode
marketId={market?.id} marketId={market?.id}
decimalPlaces={market?.decimalPlaces} onSelect={onSelect}
isHeader initialTradingMode={market?.tradingMode}
/> />
<Last24hPriceChange
marketId={market?.id}
decimalPlaces={market?.decimalPlaces}
isHeader
/>
<Last24hVolume
marketId={market?.id}
positionDecimalPlaces={market?.positionDecimalPlaces}
isHeader
/>
<MarketTradingMode marketId={market?.id} onSelect={onSelect} isHeader />
<MarketState market={market} /> <MarketState market={market} />
{asset ? ( {asset ? (
<HeaderStat <HeaderStat

View File

@ -1,5 +1,5 @@
import { t } from '@vegaprotocol/react-helpers'; import { t } from '@vegaprotocol/react-helpers';
export const DEBOUNCE_UPDATE_TIME = 500; export const THROTTLE_UPDATE_TIME = 500;
export const RISK_ACCEPTED_KEY = 'vega-risk-accepted'; export const RISK_ACCEPTED_KEY = 'vega-risk-accepted';
export const MAINNET_WELCOME_HEADER = t( export const MAINNET_WELCOME_HEADER = t(
'Trade cash settled futures on the fully decentralised Vega network.' 'Trade cash settled futures on the fully decentralised Vega network.'

View File

@ -1,9 +1,9 @@
import { useCallback, useMemo, useRef, useState } from 'react'; import type { RefObject } from 'react';
import throttle from 'lodash/throttle'; import { useMemo } from 'react';
import { useInView } from 'react-intersection-observer';
import { import {
isNumeric, isNumeric,
t, useThrottledDataProvider,
useDataProvider,
useYesterday, useYesterday,
} from '@vegaprotocol/react-helpers'; } from '@vegaprotocol/react-helpers';
import { PriceCellChange } from '@vegaprotocol/ui-toolkit'; import { PriceCellChange } from '@vegaprotocol/ui-toolkit';
@ -11,8 +11,7 @@ import * as Schema from '@vegaprotocol/types';
import type { CandleClose } from '@vegaprotocol/types'; import type { CandleClose } from '@vegaprotocol/types';
import type { Candle } from '@vegaprotocol/market-list'; import type { Candle } from '@vegaprotocol/market-list';
import { marketCandlesProvider } from '@vegaprotocol/market-list'; import { marketCandlesProvider } from '@vegaprotocol/market-list';
import { HeaderStat } from '../header'; import { THROTTLE_UPDATE_TIME } from '../constants';
import * as constants from '../constants';
interface Props { interface Props {
marketId?: string; marketId?: string;
@ -20,75 +19,48 @@ interface Props {
initialValue?: string[]; initialValue?: string[];
isHeader?: boolean; isHeader?: boolean;
noUpdate?: boolean; noUpdate?: boolean;
inViewRoot?: RefObject<Element>;
} }
export const Last24hPriceChange = ({ export const Last24hPriceChange = ({
marketId, marketId,
decimalPlaces, decimalPlaces,
initialValue, initialValue,
isHeader = false, inViewRoot,
noUpdate = false,
}: Props) => { }: Props) => {
const [candlesClose, setCandlesClose] = useState<string[]>( const [ref, inView] = useInView({ root: inViewRoot?.current });
initialValue || []
);
const yesterday = useYesterday(); const yesterday = useYesterday();
// Cache timestamp for yesterday to prevent full unmount of market page when
// a rerender occurs
const yTimestamp = useMemo(() => {
return new Date(yesterday).toISOString();
}, [yesterday]);
const variables = useMemo( const variables = useMemo(
() => ({ () => ({
marketId: marketId, marketId: marketId,
interval: Schema.Interval.INTERVAL_I1H, interval: Schema.Interval.INTERVAL_I1H,
since: yTimestamp, since: new Date(yesterday).toISOString(),
}), }),
[marketId, yTimestamp] [marketId, yesterday]
); );
const throttledSetCandles = useRef( const { data, error } = useThrottledDataProvider<Candle[], Candle>(
throttle((data: Candle[]) => { {
if (!noUpdate) { dataProvider: marketCandlesProvider,
const candlesClose: string[] = data variables,
.map((candle) => candle?.close) skip: !marketId || !inView,
.filter((c): c is CandleClose => c !== null);
setCandlesClose(candlesClose);
}
}, constants.DEBOUNCE_UPDATE_TIME)
).current;
const update = useCallback(
({ data }: { data: Candle[] | null }) => {
if (data) {
throttledSetCandles(data);
}
return true;
}, },
[throttledSetCandles] THROTTLE_UPDATE_TIME
); );
const { error } = useDataProvider<Candle[], Candle>({ const candles =
dataProvider: marketCandlesProvider, data
update, ?.map((candle) => candle?.close)
variables, .filter((c): c is CandleClose => c !== null) || initialValue;
skip: noUpdate || !marketId,
});
const content = useMemo(() => { if (error || !isNumeric(decimalPlaces)) {
if (error || !isNumeric(decimalPlaces)) { return <span ref={ref}>-</span>;
return <>-</>; }
} return (
return ( <PriceCellChange
<PriceCellChange candles={candlesClose} decimalPlaces={decimalPlaces} /> candles={candles || []}
); decimalPlaces={decimalPlaces}
}, [candlesClose, decimalPlaces, error]); ref={ref}
/>
return isHeader ? (
<HeaderStat heading={t('Change (24h)')} testId="market-change">
{content}
</HeaderStat>
) : (
content
); );
}; };

View File

@ -1,102 +1,65 @@
import type { RefObject } from 'react';
import { useInView } from 'react-intersection-observer';
import { import {
calcCandleVolume, calcCandleVolume,
marketCandlesProvider, marketCandlesProvider,
} from '@vegaprotocol/market-list'; } from '@vegaprotocol/market-list';
import { import {
addDecimalsFormatNumber, addDecimalsFormatNumber,
t, useThrottledDataProvider,
useDataProvider,
useYesterday, useYesterday,
isNumeric, isNumeric,
} from '@vegaprotocol/react-helpers'; } from '@vegaprotocol/react-helpers';
import * as Schema from '@vegaprotocol/types'; import * as Schema from '@vegaprotocol/types';
import throttle from 'lodash/throttle'; import { useMemo } from 'react';
import { useCallback, useMemo, useRef, useState } from 'react';
import * as constants from '../constants';
import { HeaderStat } from '../header';
import type { Candle } from '@vegaprotocol/market-list'; import type { Candle } from '@vegaprotocol/market-list';
import { THROTTLE_UPDATE_TIME } from '../constants';
interface Props { interface Props {
marketId?: string; marketId?: string;
positionDecimalPlaces?: number; positionDecimalPlaces?: number;
noUpdate?: boolean; formatDecimals?: number;
isHeader?: boolean; inViewRoot?: RefObject<Element>;
initialValue?: string; initialValue?: string;
} }
export const Last24hVolume = ({ export const Last24hVolume = ({
marketId, marketId,
positionDecimalPlaces, positionDecimalPlaces,
noUpdate = false, formatDecimals,
isHeader = false, inViewRoot,
initialValue, initialValue,
}: Props) => { }: Props) => {
const [candleVolume, setCandleVolume] = useState<string>(initialValue || '');
const yesterday = useYesterday(); const yesterday = useYesterday();
// Cache timestamp for yesterday to prevent full unmount of market page when const [ref, inView] = useInView({ root: inViewRoot?.current });
// a rerender occurs
const yTimestamp = useMemo(() => {
return new Date(yesterday).toISOString();
}, [yesterday]);
const variables = useMemo( const variables = useMemo(
() => ({ () => ({
marketId: marketId, marketId: marketId,
interval: Schema.Interval.INTERVAL_I1H, interval: Schema.Interval.INTERVAL_I1H,
since: yTimestamp, since: new Date(yesterday).toISOString(),
}), }),
[marketId, yTimestamp] [marketId, yesterday]
); );
const throttledSetCandles = useRef( const { data } = useThrottledDataProvider<Candle[], Candle>(
throttle((data: Candle[]) => { {
noUpdate || setCandleVolume(calcCandleVolume(data) || ''); dataProvider: marketCandlesProvider,
}, constants.DEBOUNCE_UPDATE_TIME) variables,
).current; skip: !(inView && marketId),
const update = useCallback(
({ data }: { data: Candle[] | null }) => {
if (data) {
throttledSetCandles(data);
}
return true;
}, },
[throttledSetCandles] THROTTLE_UPDATE_TIME
); );
const candleVolume = data ? calcCandleVolume(data) : initialValue;
const { error } = useDataProvider<Candle[], Candle>({ return (
dataProvider: marketCandlesProvider, <span ref={ref}>
update, {candleVolume && isNumeric(positionDecimalPlaces)
variables, ? addDecimalsFormatNumber(
skip: noUpdate || !marketId, candleVolume,
}); positionDecimalPlaces,
formatDecimals
const formatDecimals = isHeader ? positionDecimalPlaces || 0 : 2; )
const content = useMemo(() => { : '-'}
return ( </span>
<>
{!error && candleVolume && isNumeric(positionDecimalPlaces)
? addDecimalsFormatNumber(
candleVolume,
positionDecimalPlaces,
formatDecimals
)
: '-'}
</>
);
}, [error, candleVolume, positionDecimalPlaces, formatDecimals]);
return isHeader ? (
<HeaderStat
heading={t('Volume (24h)')}
testId="market-volume"
description={
error && candleVolume && positionDecimalPlaces
? t('The total amount of assets traded in the last 24 hours.')
: null
}
>
{content}
</HeaderStat>
) : (
content
); );
}; };

View File

@ -1,10 +1,10 @@
import { useCallback, useMemo, useRef, useState } from 'react'; import type { RefObject } from 'react';
import throttle from 'lodash/throttle'; import { useMemo } from 'react';
import { useInView } from 'react-intersection-observer';
import { import {
addDecimalsFormatNumber, addDecimalsFormatNumber,
t,
PriceCell, PriceCell,
useDataProvider, useThrottledDataProvider,
isNumeric, isNumeric,
} from '@vegaprotocol/react-helpers'; } from '@vegaprotocol/react-helpers';
import type { import type {
@ -12,14 +12,13 @@ import type {
MarketDataUpdateFieldsFragment, MarketDataUpdateFieldsFragment,
} from '@vegaprotocol/market-list'; } from '@vegaprotocol/market-list';
import { marketDataProvider } from '@vegaprotocol/market-list'; import { marketDataProvider } from '@vegaprotocol/market-list';
import { HeaderStat } from '../header'; import { THROTTLE_UPDATE_TIME } from '../constants';
import * as constants from '../constants';
interface Props { interface Props {
marketId?: string; marketId?: string;
decimalPlaces?: number; decimalPlaces?: number;
isHeader?: boolean; asPriceCell?: boolean;
noUpdate?: boolean; inViewRoot?: RefObject<Element>;
initialValue?: string; initialValue?: string;
} }
@ -27,58 +26,39 @@ export const MarketMarkPrice = ({
marketId, marketId,
decimalPlaces, decimalPlaces,
initialValue, initialValue,
isHeader = false, inViewRoot,
noUpdate = false, asPriceCell,
}: Props) => { }: Props) => {
const [marketPrice, setMarketPrice] = useState<string | null>( const [ref, inView] = useInView({ root: inViewRoot?.current });
initialValue || null const variables = useMemo(() => ({ marketId }), [marketId]);
);
const variables = useMemo(
() => ({
marketId: marketId,
}),
[marketId]
);
const throttledSetMarketPrice = useRef( const { data } = useThrottledDataProvider<
throttle((price: string) => { MarketData,
noUpdate || setMarketPrice(price); MarketDataUpdateFieldsFragment
}, constants.DEBOUNCE_UPDATE_TIME) >(
).current; {
const update = useCallback( dataProvider: marketDataProvider,
({ data: marketData }: { data: MarketData | null }) => { variables,
throttledSetMarketPrice(marketData?.markPrice || ''); skip: !inView,
return true;
}, },
[throttledSetMarketPrice] THROTTLE_UPDATE_TIME
); );
useDataProvider<MarketData, MarketDataUpdateFieldsFragment>({ const marketPrice = data?.markPrice || initialValue;
dataProvider: marketDataProvider,
update,
variables,
skip: noUpdate || !marketId,
});
const content = useMemo(() => { if (!marketPrice || !isNumeric(decimalPlaces)) {
if (!marketPrice || !isNumeric(decimalPlaces)) { return <span ref={ref}>-</span>;
return <>-</>; }
} if (asPriceCell) {
return isHeader ? ( return (
<div>{addDecimalsFormatNumber(marketPrice, decimalPlaces)}</div>
) : (
<PriceCell <PriceCell
ref={ref}
value={Number(marketPrice)} value={Number(marketPrice)}
valueFormatted={addDecimalsFormatNumber(marketPrice, decimalPlaces, 2)} valueFormatted={addDecimalsFormatNumber(marketPrice, decimalPlaces, 2)}
/> />
); );
}, [marketPrice, decimalPlaces, isHeader]); }
return (
return isHeader ? ( <span ref={ref}>{addDecimalsFormatNumber(marketPrice, decimalPlaces)}</span>
<HeaderStat heading={t('Price')} testId="market-price">
{content}
</HeaderStat>
) : (
content
); );
}; };

View File

@ -23,7 +23,7 @@ export const MarketState = ({
const throttledSetMarketState = useRef( const throttledSetMarketState = useRef(
throttle((state: Schema.MarketState) => { throttle((state: Schema.MarketState) => {
setMarketState(state); setMarketState(state);
}, constants.DEBOUNCE_UPDATE_TIME) }, constants.THROTTLE_UPDATE_TIME)
).current; ).current;
const update = useCallback( const update = useCallback(

View File

@ -1,110 +1,78 @@
import { useCallback, useMemo, useState } from 'react'; import type { RefObject } from 'react';
import { t, useDataProvider } from '@vegaprotocol/react-helpers'; import { t } from '@vegaprotocol/react-helpers';
import type { MarketDealTicket } from '@vegaprotocol/market-list'; import { TradingModeTooltip } from '@vegaprotocol/deal-ticket';
import { compileGridData, TradingModeTooltip } from '@vegaprotocol/deal-ticket'; import { useInView } from 'react-intersection-observer';
import * as Schema from '@vegaprotocol/types'; import * as Schema from '@vegaprotocol/types';
import type { import { useStaticMarketData } from '@vegaprotocol/market-list';
MarketData,
MarketDataUpdateFieldsFragment,
SingleMarketFieldsFragment,
} from '@vegaprotocol/market-list';
import { marketDataProvider, marketProvider } from '@vegaprotocol/market-list';
import { HeaderStat } from '../header'; import { HeaderStat } from '../header';
import { Tooltip } from '@vegaprotocol/ui-toolkit'; import { Tooltip } from '@vegaprotocol/ui-toolkit';
interface Props { const getTradingModeLabel = (
marketId?: string; tradingMode?: Schema.MarketTradingMode,
onSelect?: (marketId: string) => void; trigger?: Schema.AuctionTrigger
isHeader?: boolean; ) => {
noUpdate?: boolean; return (
initialMode?: Schema.MarketTradingMode;
initialTrigger?: Schema.AuctionTrigger;
}
export const MarketTradingMode = ({
marketId,
onSelect,
isHeader = false,
noUpdate = false,
initialMode,
initialTrigger,
}: Props) => {
const [tradingMode, setTradingMode] =
useState<Schema.MarketTradingMode | null>(initialMode || null);
const [trigger, setTrigger] = useState<Schema.AuctionTrigger | null>(
initialTrigger || null
);
const [market, setMarket] = useState<MarketDealTicket | null>(null);
const variables = useMemo(
() => ({
marketId: marketId,
}),
[marketId]
);
const { data } = useDataProvider<SingleMarketFieldsFragment, never>({
dataProvider: marketProvider,
variables,
skip: !marketId,
});
const update = useCallback(
({ data: marketData }: { data: MarketData | null }) => {
if (!noUpdate && marketData) {
setTradingMode(marketData.marketTradingMode);
setTrigger(marketData.trigger);
setMarket({
...data,
data: marketData,
} as MarketDealTicket);
}
return true;
},
[noUpdate, data]
);
useDataProvider<MarketData, MarketDataUpdateFieldsFragment>({
dataProvider: marketDataProvider,
update,
variables,
skip: noUpdate || !marketId || !data,
});
const content =
(tradingMode === Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION && (tradingMode === Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
trigger && trigger &&
trigger !== Schema.AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED trigger !== Schema.AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED
? `${Schema.MarketTradingModeMapping[tradingMode]} - ${Schema.AuctionTriggerMapping[trigger]}` ? `${Schema.MarketTradingModeMapping[tradingMode]} - ${Schema.AuctionTriggerMapping[trigger]}`
: Schema.MarketTradingModeMapping[ : Schema.MarketTradingModeMapping[
tradingMode as Schema.MarketTradingMode tradingMode as Schema.MarketTradingMode
]) || '-'; ]) || '-'
);
};
return isHeader ? ( interface HeaderStatMarketTradingModeProps {
marketId?: string;
onSelect?: (marketId: string) => void;
initialTradingMode?: Schema.MarketTradingMode;
initialTrigger?: Schema.AuctionTrigger;
}
export const HeaderStatMarketTradingMode = ({
marketId,
onSelect,
initialTradingMode,
initialTrigger,
}: HeaderStatMarketTradingModeProps) => {
const data = useStaticMarketData(marketId);
const tradingMode = data?.marketTradingMode ?? initialTradingMode;
const trigger = data?.trigger ?? initialTrigger;
return (
<HeaderStat <HeaderStat
heading={t('Trading mode')} heading={t('Trading mode')}
description={ description={
market && ( <TradingModeTooltip marketId={marketId} onSelect={onSelect} />
<TradingModeTooltip
tradingMode={tradingMode}
trigger={trigger}
compiledGrid={compileGridData(market, onSelect)}
/>
)
} }
testId="market-trading-mode" testId="market-trading-mode"
> >
<div>{content}</div> <div>{getTradingModeLabel(tradingMode, trigger)}</div>
</HeaderStat> </HeaderStat>
) : ( );
};
export const MarketTradingMode = ({
marketId,
initialTradingMode,
initialTrigger,
inViewRoot,
}: Omit<HeaderStatMarketTradingModeProps, 'onUpdate'> & {
inViewRoot?: RefObject<Element>;
}) => {
const [ref, inView] = useInView({ root: inViewRoot?.current });
const data = useStaticMarketData(marketId, !inView);
return (
<Tooltip <Tooltip
description={ description={<TradingModeTooltip marketId={marketId} skip={!inView} />}
tradingMode &&
trigger && (
<TradingModeTooltip tradingMode={tradingMode} trigger={trigger} />
)
}
> >
<span>{content}</span> <span ref={ref}>
{getTradingModeLabel(
data?.marketTradingMode ?? initialTradingMode,
data?.trigger ?? initialTrigger
)}
</span>
</Tooltip> </Tooltip>
); );
}; };

View File

@ -31,7 +31,7 @@ export const MarketVolume = ({ marketId }: { marketId: string }) => {
const throttledSetMarketVolume = useRef( const throttledSetMarketVolume = useRef(
throttle((volume: string) => { throttle((volume: string) => {
setMarketVolume(volume); setMarketVolume(volume);
}, constants.DEBOUNCE_UPDATE_TIME) }, constants.THROTTLE_UPDATE_TIME)
).current; ).current;
const update = useCallback( const update = useCallback(
({ data: marketData }: { data: MarketData | null }) => { ({ data: marketData }: { data: MarketData | null }) => {

View File

@ -1,3 +1,4 @@
import type { RefObject } from 'react';
import { FeesCell } from '@vegaprotocol/market-info'; import { FeesCell } from '@vegaprotocol/market-info';
import { import {
calcCandleHigh, calcCandleHigh,
@ -173,7 +174,7 @@ export const columns = (
market: Market, market: Market,
onSelect: (id: string) => void, onSelect: (id: string) => void,
onCellClick: OnCellClickHandler, onCellClick: OnCellClickHandler,
activeMarketId?: string | null inViewRoot?: RefObject<HTMLElement>
) => { ) => {
const candlesClose = market.candles const candlesClose = market.candles
?.map((candle) => candle?.close) ?.map((candle) => candle?.close)
@ -189,7 +190,6 @@ export const columns = (
return onSelect(id); return onSelect(id);
} }
}; };
const noUpdate = !activeMarketId || market.id !== activeMarketId;
const selectMarketColumns: Column[] = [ const selectMarketColumns: Column[] = [
{ {
kind: ColumnKind.Market, kind: ColumnKind.Market,
@ -221,8 +221,9 @@ export const columns = (
<MarketMarkPrice <MarketMarkPrice
marketId={market.id} marketId={market.id}
decimalPlaces={market?.decimalPlaces} decimalPlaces={market?.decimalPlaces}
initialValue={market.data?.markPrice.toString()} initialValue={market.data?.markPrice}
noUpdate={noUpdate} inViewRoot={inViewRoot}
asPriceCell
/> />
), ),
className: `${cellClassNames} max-w-[100px]`, className: `${cellClassNames} max-w-[100px]`,
@ -234,7 +235,7 @@ export const columns = (
<Last24hPriceChange <Last24hPriceChange
marketId={market.id} marketId={market.id}
decimalPlaces={market?.decimalPlaces} decimalPlaces={market?.decimalPlaces}
noUpdate={noUpdate} inViewRoot={inViewRoot}
initialValue={candlesClose} initialValue={candlesClose}
/> />
), ),
@ -317,7 +318,8 @@ export const columns = (
marketId={market.id} marketId={market.id}
positionDecimalPlaces={market.positionDecimalPlaces} positionDecimalPlaces={market.positionDecimalPlaces}
initialValue={candleVolume} initialValue={candleVolume}
noUpdate={noUpdate} inViewRoot={inViewRoot}
formatDecimals={2}
/> />
), ),
className: `${cellClassNames} hidden lg:table-cell font-mono`, className: `${cellClassNames} hidden lg:table-cell font-mono`,
@ -329,8 +331,8 @@ export const columns = (
value: ( value: (
<MarketTradingMode <MarketTradingMode
marketId={market?.id} marketId={market?.id}
noUpdate={noUpdate} inViewRoot={inViewRoot}
initialMode={market.tradingMode} initialTradingMode={market.tradingMode}
initialTrigger={market.data?.trigger} initialTrigger={market.data?.trigger}
/> />
), ),
@ -359,9 +361,9 @@ export const columns = (
export const columnsPositionMarkets = ( export const columnsPositionMarkets = (
market: Market, market: Market,
onSelect: (id: string) => void, onSelect: (id: string) => void,
inViewRoot?: RefObject<HTMLElement>,
openVolume?: string, openVolume?: string,
onCellClick?: OnCellClickHandler, onCellClick?: OnCellClickHandler
activeMarketId?: string | null
) => { ) => {
const candlesClose = market.candles const candlesClose = market.candles
?.map((candle) => candle?.close) ?.map((candle) => candle?.close)
@ -377,7 +379,6 @@ export const columnsPositionMarkets = (
} }
}; };
const candleVolume = market.candles && calcCandleVolume(market.candles); const candleVolume = market.candles && calcCandleVolume(market.candles);
const noUpdate = !activeMarketId || market.id !== activeMarketId;
const selectMarketColumns: Column[] = [ const selectMarketColumns: Column[] = [
{ {
kind: ColumnKind.Market, kind: ColumnKind.Market,
@ -409,8 +410,9 @@ export const columnsPositionMarkets = (
<MarketMarkPrice <MarketMarkPrice
marketId={market.id} marketId={market.id}
decimalPlaces={market?.decimalPlaces} decimalPlaces={market?.decimalPlaces}
initialValue={market.data?.markPrice.toString()} inViewRoot={inViewRoot}
noUpdate={noUpdate} initialValue={market.data?.markPrice}
asPriceCell
/> />
), ),
className: cellClassNames, className: cellClassNames,
@ -422,7 +424,7 @@ export const columnsPositionMarkets = (
<Last24hPriceChange <Last24hPriceChange
marketId={market.id} marketId={market.id}
decimalPlaces={market?.decimalPlaces} decimalPlaces={market?.decimalPlaces}
noUpdate={noUpdate} inViewRoot={inViewRoot}
initialValue={candlesClose} initialValue={candlesClose}
/> />
), ),
@ -503,9 +505,10 @@ export const columnsPositionMarkets = (
value: ( value: (
<Last24hVolume <Last24hVolume
marketId={market.id} marketId={market.id}
inViewRoot={inViewRoot}
positionDecimalPlaces={market.positionDecimalPlaces} positionDecimalPlaces={market.positionDecimalPlaces}
initialValue={candleVolume} initialValue={candleVolume}
noUpdate={noUpdate} formatDecimals={2}
/> />
), ),
className: `${cellClassNames} hidden lg:table-cell font-mono`, className: `${cellClassNames} hidden lg:table-cell font-mono`,
@ -517,8 +520,8 @@ export const columnsPositionMarkets = (
value: ( value: (
<MarketTradingMode <MarketTradingMode
marketId={market?.id} marketId={market?.id}
noUpdate={noUpdate} inViewRoot={inViewRoot}
initialMode={market.tradingMode} initialTradingMode={market.tradingMode}
initialTrigger={market.data?.trigger} initialTrigger={market.data?.trigger}
/> />
), ),

View File

@ -1,4 +1,5 @@
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState, useRef } from 'react';
import type { RefObject } from 'react';
import { useMarketList } from '@vegaprotocol/market-list'; import { useMarketList } from '@vegaprotocol/market-list';
import { positionsDataProvider } from '@vegaprotocol/positions'; import { positionsDataProvider } from '@vegaprotocol/positions';
import { t, useDataProvider } from '@vegaprotocol/react-helpers'; import { t, useDataProvider } from '@vegaprotocol/react-helpers';
@ -27,7 +28,6 @@ import {
TOKEN_NEW_MARKET_PROPOSAL, TOKEN_NEW_MARKET_PROPOSAL,
useLinks, useLinks,
} from '@vegaprotocol/environment'; } from '@vegaprotocol/environment';
import { useGlobalStore } from '../../stores';
export type Market = MarketWithCandles & MarketWithData; export type Market = MarketWithCandles & MarketWithData;
@ -36,19 +36,22 @@ export const SelectAllMarketsTableBody = ({
positions, positions,
onSelect, onSelect,
onCellClick, onCellClick,
activeMarketId, inViewRoot,
headers = columnHeaders, headers = columnHeaders,
tableColumns = (market) => tableColumns = (market) => columns(market, onSelect, onCellClick, inViewRoot),
columns(market, onSelect, onCellClick, activeMarketId),
}: { }: {
markets?: Market[] | null; markets?: Market[] | null;
positions?: PositionFieldsFragment[]; positions?: PositionFieldsFragment[];
title?: string; title?: string;
onSelect: (id: string) => void; onSelect: (id: string) => void;
onCellClick: OnCellClickHandler; onCellClick: OnCellClickHandler;
activeMarketId?: string | null;
headers?: Column[]; headers?: Column[];
tableColumns?: (market: Market, openVolume?: string) => Column[]; tableColumns?: (
market: Market,
inViewRoot?: RefObject<HTMLDivElement>,
openVolume?: string
) => Column[];
inViewRoot?: RefObject<HTMLDivElement>;
}) => { }) => {
const tokenLink = useLinks(DApp.Token); const tokenLink = useLinks(DApp.Token);
if (!markets) return null; if (!markets) return null;
@ -68,6 +71,7 @@ export const SelectAllMarketsTableBody = ({
onSelect={onSelect} onSelect={onSelect}
columns={tableColumns( columns={tableColumns(
market, market,
inViewRoot,
positions && positions &&
positions.find((p) => p.market.id === market.id)?.openVolume positions.find((p) => p.market.id === market.id)?.openVolume
)} )}
@ -95,11 +99,11 @@ export const SelectMarketPopover = ({
onSelect: (id: string) => void; onSelect: (id: string) => void;
onCellClick: OnCellClickHandler; onCellClick: OnCellClickHandler;
}) => { }) => {
const activeMarketId = useGlobalStore((store) => store.marketId);
const triggerClasses = const triggerClasses =
'sm:text-lg md:text-xl lg:text-2xl flex items-center gap-2 whitespace-nowrap hover:text-neutral-500 dark:hover:text-neutral-300 mt-1'; 'sm:text-lg md:text-xl lg:text-2xl flex items-center gap-2 whitespace-nowrap hover:text-neutral-500 dark:hover:text-neutral-300 mt-1';
const { pubKey } = useVegaWallet(); const { pubKey } = useVegaWallet();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const inViewRoot = useRef<HTMLDivElement>(null);
const { const {
data, data,
loading: marketsLoading, loading: marketsLoading,
@ -155,6 +159,7 @@ export const SelectMarketPopover = ({
<div <div
className="w-[90vw] max-h-[80vh] overflow-y-auto" className="w-[90vw] max-h-[80vh] overflow-y-auto"
data-testid="select-market-list" data-testid="select-market-list"
ref={inViewRoot}
> >
{marketsLoading || (pubKey && positionsLoading) ? ( {marketsLoading || (pubKey && positionsLoading) ? (
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
@ -167,6 +172,7 @@ export const SelectMarketPopover = ({
<> <>
<TableTitle>{t('My markets')}</TableTitle> <TableTitle>{t('My markets')}</TableTitle>
<SelectAllMarketsTableBody <SelectAllMarketsTableBody
inViewRoot={inViewRoot}
markets={markets} markets={markets}
positions={party?.positionsConnection?.edges positions={party?.positionsConnection?.edges
?.filter((edge) => edge.node) ?.filter((edge) => edge.node)
@ -174,13 +180,13 @@ export const SelectMarketPopover = ({
onSelect={onSelectMarket} onSelect={onSelectMarket}
onCellClick={onCellClick} onCellClick={onCellClick}
headers={columnHeadersPositionMarkets} headers={columnHeadersPositionMarkets}
tableColumns={(market, openVolume) => tableColumns={(market, inViewRoot, openVolume) =>
columnsPositionMarkets( columnsPositionMarkets(
market, market,
onSelectMarket, onSelectMarket,
inViewRoot,
openVolume, openVolume,
onCellClick, onCellClick
activeMarketId
) )
} }
/> />
@ -188,10 +194,10 @@ export const SelectMarketPopover = ({
) : null} ) : null}
<TableTitle>{t('All markets')}</TableTitle> <TableTitle>{t('All markets')}</TableTitle>
<SelectAllMarketsTableBody <SelectAllMarketsTableBody
inViewRoot={inViewRoot}
markets={data} markets={data}
onSelect={onSelectMarket} onSelect={onSelectMarket}
onCellClick={onCellClick} onCellClick={onCellClick}
activeMarketId={activeMarketId}
/> />
</table> </table>
)} )}

View File

@ -1,7 +1,9 @@
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import 'jest-canvas-mock'; import 'jest-canvas-mock';
import ResizeObserver from 'resize-observer-polyfill'; import ResizeObserver from 'resize-observer-polyfill';
import { defaultFallbackInView } from 'react-intersection-observer';
defaultFallbackInView(true);
global.ResizeObserver = ResizeObserver; global.ResizeObserver = ResizeObserver;
// Required by radix-ui/react-tooltip // Required by radix-ui/react-tooltip

View File

@ -78,7 +78,11 @@ export const TimeInForceSelector = ({
return ( return (
<span> <span>
{t('This market is in auction until it reaches')}{' '} {t('This market is in auction until it reaches')}{' '}
<Tooltip description={<DataGrid grid={compileGridData(market)} />}> <Tooltip
description={
<DataGrid grid={compileGridData(market, market.data)} />
}
>
<span>{t('sufficient liquidity')}</span> <span>{t('sufficient liquidity')}</span>
</Tooltip> </Tooltip>
{'. '} {'. '}
@ -93,7 +97,11 @@ export const TimeInForceSelector = ({
return ( return (
<span> <span>
{t('This market is in auction due to')}{' '} {t('This market is in auction due to')}{' '}
<Tooltip description={<DataGrid grid={compileGridData(market)} />}> <Tooltip
description={
<DataGrid grid={compileGridData(market, market.data)} />
}
>
<span>{t('high price volatility')}</span> <span>{t('high price volatility')}</span>
</Tooltip> </Tooltip>
{'. '} {'. '}

View File

@ -33,7 +33,11 @@ export const TypeSelector = ({
return ( return (
<span> <span>
{t('This market is in auction until it reaches')}{' '} {t('This market is in auction until it reaches')}{' '}
<Tooltip description={<DataGrid grid={compileGridData(market)} />}> <Tooltip
description={
<DataGrid grid={compileGridData(market, market.data)} />
}
>
<span>{t('sufficient liquidity')}</span> <span>{t('sufficient liquidity')}</span>
</Tooltip> </Tooltip>
{'. '} {'. '}
@ -46,7 +50,11 @@ export const TypeSelector = ({
return ( return (
<span> <span>
{t('This market is in auction due to')}{' '} {t('This market is in auction due to')}{' '}
<Tooltip description={<DataGrid grid={compileGridData(market)} />}> <Tooltip
description={
<DataGrid grid={compileGridData(market, market.data)} />
}
>
<span>{t('high price volatility')}</span> <span>{t('high price volatility')}</span>
</Tooltip> </Tooltip>
{'. '} {'. '}

View File

@ -8,17 +8,31 @@ import * as Schema from '@vegaprotocol/types';
import { Link as UILink } from '@vegaprotocol/ui-toolkit'; import { Link as UILink } from '@vegaprotocol/ui-toolkit';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import type { MarketDealTicket } from '@vegaprotocol/market-list'; import type { Market, MarketData } from '@vegaprotocol/market-list';
export const compileGridData = ( export const compileGridData = (
market: MarketDealTicket, market: Pick<
Market,
'tradableInstrument' | 'id' | 'decimalPlaces' | 'positionDecimalPlaces'
>,
marketData: Pick<
MarketData,
| 'marketTradingMode'
| 'auctionStart'
| 'auctionEnd'
| 'indicativePrice'
| 'indicativeVolume'
| 'suppliedStake'
| 'targetStake'
| 'trigger'
>,
onSelect?: (id: string) => void onSelect?: (id: string) => void
): { label: ReactNode; value?: ReactNode }[] => { ): { label: ReactNode; value?: ReactNode }[] => {
const grid: DataGridProps['grid'] = []; const grid: DataGridProps['grid'] = [];
const isLiquidityMonitoringAuction = const isLiquidityMonitoringAuction =
market.data.marketTradingMode === marketData.marketTradingMode ===
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION && Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
market.data.trigger === Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY; marketData.trigger === Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY;
const formatStake = (value: string) => { const formatStake = (value: string) => {
const formattedValue = addDecimalsFormatNumber( const formattedValue = addDecimalsFormatNumber(
@ -30,19 +44,17 @@ export const compileGridData = (
return `${formattedValue} ${asset}`; return `${formattedValue} ${asset}`;
}; };
if (!market.data) return grid; if (!marketData) return grid;
if (market.data.auctionStart) { if (marketData.auctionStart) {
grid.push({ grid.push({
label: t('Auction start'), label: t('Auction start'),
value: getDateTimeFormat().format(new Date(market.data.auctionStart)), value: getDateTimeFormat().format(new Date(marketData.auctionStart)),
}); });
} }
if (market.data.auctionEnd) { if (marketData.auctionEnd) {
const endDate = getDateTimeFormat().format( const endDate = getDateTimeFormat().format(new Date(marketData.auctionEnd));
new Date(market.data.auctionEnd)
);
grid.push({ grid.push({
label: isLiquidityMonitoringAuction label: isLiquidityMonitoringAuction
? t('Est. auction end') ? t('Est. auction end')
@ -51,14 +63,14 @@ export const compileGridData = (
}); });
} }
if (isLiquidityMonitoringAuction && market.data.targetStake) { if (isLiquidityMonitoringAuction && marketData.targetStake) {
grid.push({ grid.push({
label: t('Target liquidity'), label: t('Target liquidity'),
value: formatStake(market.data.targetStake), value: formatStake(marketData.targetStake),
}); });
} }
if (isLiquidityMonitoringAuction && market.data.suppliedStake) { if (isLiquidityMonitoringAuction && marketData.suppliedStake) {
grid.push({ grid.push({
label: ( label: (
<Link <Link
@ -68,31 +80,31 @@ export const compileGridData = (
<UILink>{t('Current liquidity')}</UILink> <UILink>{t('Current liquidity')}</UILink>
</Link> </Link>
), ),
value: formatStake(market.data.suppliedStake), value: formatStake(marketData.suppliedStake),
}); });
} }
if (market.data.indicativePrice) { if (marketData.indicativePrice) {
grid.push({ grid.push({
label: t('Est. uncrossing price'), label: t('Est. uncrossing price'),
value: value:
market.data.indicativePrice && market.data.indicativePrice !== '0' marketData.indicativePrice && marketData.indicativePrice !== '0'
? `~ ? `~
${addDecimalsFormatNumber( ${addDecimalsFormatNumber(
market.data.indicativePrice, marketData.indicativePrice,
market.decimalPlaces market.decimalPlaces
)}` )}`
: '-', : '-',
}); });
} }
if (market.data.indicativeVolume) { if (marketData.indicativeVolume) {
grid.push({ grid.push({
label: t('Est. uncrossing vol'), label: t('Est. uncrossing vol'),
value: value:
market.data.indicativeVolume && market.data.indicativeVolume !== '0' marketData.indicativeVolume && marketData.indicativeVolume !== '0'
? '~' + ? '~' +
addDecimalsFormatNumber( addDecimalsFormatNumber(
market.data.indicativeVolume, marketData.indicativeVolume,
market.positionDecimalPlaces market.positionDecimalPlaces
) )
: '-', : '-',

View File

@ -1,23 +1,35 @@
import type { ReactNode } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { useEnvironment } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { DataGrid, t } from '@vegaprotocol/react-helpers'; import { DataGrid, t } from '@vegaprotocol/react-helpers';
import * as Schema from '@vegaprotocol/types'; import * as Schema from '@vegaprotocol/types';
import { ExternalLink } from '@vegaprotocol/ui-toolkit'; import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { createDocsLinks } from '@vegaprotocol/react-helpers'; import { createDocsLinks } from '@vegaprotocol/react-helpers';
import { compileGridData } from './compile-grid-data';
import { useMarket, useStaticMarketData } from '@vegaprotocol/market-list';
type TradingModeTooltipProps = { type TradingModeTooltipProps = {
tradingMode: Schema.MarketTradingMode | null; marketId?: string;
trigger: Schema.AuctionTrigger | null; onSelect?: (marketId: string) => void;
compiledGrid?: { label: ReactNode; value?: ReactNode }[]; skip?: boolean;
}; };
export const TradingModeTooltip = ({ export const TradingModeTooltip = ({
tradingMode, marketId,
trigger, onSelect,
compiledGrid, skip,
}: TradingModeTooltipProps) => { }: TradingModeTooltipProps) => {
const { VEGA_DOCS_URL } = useEnvironment(); const { VEGA_DOCS_URL } = useEnvironment();
const market = useMarket(marketId);
const marketData = useStaticMarketData(marketId, skip);
if (!market || !marketData) {
return null;
}
const compiledGrid =
onSelect && compileGridData(market, marketData, onSelect);
const { marketTradingMode: tradingMode, trigger } = marketData;
switch (tradingMode) { switch (tradingMode) {
case Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS: { case Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS: {
return ( return (

View File

@ -1,4 +1,9 @@
import produce from 'immer'; import produce from 'immer';
import { useMemo } from 'react';
import {
makeDerivedDataProvider,
useDataProvider,
} from '@vegaprotocol/react-helpers';
import { makeDataProvider } from '@vegaprotocol/react-helpers'; import { makeDataProvider } from '@vegaprotocol/react-helpers';
import { import {
MarketDataDocument, MarketDataDocument,
@ -39,3 +44,51 @@ export const marketDataProvider = makeDataProvider<
getData, getData,
getDelta, getDelta,
}); });
export type StaticMarketData = Pick<
MarketData,
| 'marketTradingMode'
| 'auctionStart'
| 'auctionEnd'
| 'indicativePrice'
| 'indicativeVolume'
| 'suppliedStake'
| 'targetStake'
| 'trigger'
>;
export const staticMarketDataProvider = makeDerivedDataProvider<
StaticMarketData,
never
>([marketDataProvider], (parts, variables, prevData) => {
const marketData = parts[0] as ReturnType<typeof getData>;
if (!marketData) {
return marketData;
}
const data: StaticMarketData = {
marketTradingMode: marketData.marketTradingMode,
auctionStart: marketData.auctionStart,
auctionEnd: marketData.auctionEnd,
indicativePrice: marketData.indicativePrice,
indicativeVolume: marketData.indicativeVolume,
suppliedStake: marketData.suppliedStake,
targetStake: marketData.targetStake,
trigger: marketData.trigger,
};
if (!prevData) {
return data;
}
return produce(prevData, (draft) => {
Object.assign(draft, data);
});
});
export const useStaticMarketData = (marketId?: string, skip?: boolean) => {
const variables = useMemo(() => ({ marketId }), [marketId]);
const { data } = useDataProvider({
dataProvider: staticMarketDataProvider,
variables,
skip: skip || !marketId,
});
return data;
};

View File

@ -35,6 +35,28 @@ export const marketsProvider = makeDataProvider<
fetchPolicy: 'cache-first', fetchPolicy: 'cache-first',
}); });
const marketProvider = makeDerivedDataProvider<
Market,
never,
{ marketId: string }
>(
[marketsProvider],
([markets], variables) =>
((markets as ReturnType<typeof getData>) || []).find(
(market) => market.id === variables?.marketId
) || null
);
export const useMarket = (marketId?: string) => {
const variables = useMemo(() => ({ marketId: marketId || '' }), [marketId]);
const { data } = useDataProvider({
dataProvider: marketProvider,
variables,
skip: !marketId,
});
return data;
};
export const activeMarketsProvider = makeDerivedDataProvider<Market[], never>( export const activeMarketsProvider = makeDerivedDataProvider<Market[], never>(
[marketsProvider], [marketsProvider],
([markets]) => filterAndSortMarkets(markets) ([markets]) => filterAndSortMarkets(markets)

View File

@ -86,6 +86,8 @@ describe('isNumeric', () => {
{ i: '--123.01', o: false }, { i: '--123.01', o: false },
{ i: '123.', o: false }, { i: '123.', o: false },
{ i: '123.1.1', o: false }, { i: '123.1.1', o: false },
{ i: BigInt(123), o: true },
{ i: BigInt(-1), o: true },
{ i: new BigNumber(123), o: true }, { i: new BigNumber(123), o: true },
{ i: new BigNumber(123.123), o: true }, { i: new BigNumber(123.123), o: true },
{ i: new BigNumber(123.123).toString(), o: true }, { i: new BigNumber(123.123).toString(), o: true },
@ -98,7 +100,7 @@ describe('isNumeric', () => {
i, i,
o, o,
}: { }: {
i: number | string | undefined | null | BigNumber; i: number | string | undefined | null | BigNumber | bigint;
o: boolean; o: boolean;
}) => { }) => {
expect(isNumeric(i)).toStrictEqual(o); expect(isNumeric(i)).toStrictEqual(o);

View File

@ -138,7 +138,7 @@ export const useNumberParts = (
}; };
export const isNumeric = ( export const isNumeric = (
value?: string | number | BigNumber | null value?: string | number | BigNumber | bigint | null
): value is NonNullable<number | string> => /^-?\d*\.?\d+$/.test(String(value)); ): value is NonNullable<number | string> => /^-?\d*\.?\d+$/.test(String(value));
const INFINITY = '∞'; const INFINITY = '∞';

View File

@ -560,7 +560,8 @@ export type CombineDerivedData<
Variables extends OperationVariables = OperationVariables Variables extends OperationVariables = OperationVariables
> = ( > = (
data: DerivedPart<Variables>['data'][], data: DerivedPart<Variables>['data'][],
variables?: Variables variables: Variables | undefined,
prevData: Data | null
) => Data | null; ) => Data | null;
export type CombineDerivedDelta< export type CombineDerivedDelta<
@ -641,7 +642,8 @@ function makeDerivedDataProviderInternal<
const newData = newLoaded const newData = newLoaded
? combineData( ? combineData(
parts.map((part) => part.data), parts.map((part) => part.data),
variables variables,
data
) )
: data; : data;
if ( if (
@ -655,7 +657,7 @@ function makeDerivedDataProviderInternal<
loaded = newLoaded; loaded = newLoaded;
const previousData = data; const previousData = data;
data = newData; data = newData;
if (newLoaded) { if (loaded) {
const updatedPart = parts[updatedPartIndex]; const updatedPart = parts[updatedPartIndex];
if (updatedPart.isUpdate) { if (updatedPart.isUpdate) {
isUpdate = true; isUpdate = true;

View File

@ -1,37 +1,41 @@
import React from 'react'; import { memo, forwardRef } from 'react';
import { getDecimalSeparator } from '../format'; import { getDecimalSeparator, isNumeric } from '../format';
export interface IPriceCellProps { export interface IPriceCellProps {
value: number | bigint | null | undefined; value: number | bigint | null | undefined;
valueFormatted: string; valueFormatted: string;
testId?: string; testId?: string;
} }
export const PriceCell = React.memo( export const PriceCell = memo(
({ value, valueFormatted, testId }: IPriceCellProps) => { forwardRef<HTMLSpanElement, IPriceCellProps>(
if ( ({ value, valueFormatted, testId }: IPriceCellProps, ref) => {
(!value && value !== 0) || if (!isNumeric(value)) {
(typeof value === 'number' && isNaN(Number(value))) return (
) { <span data-testid="price" ref={ref}>
return <span data-testid="price">-</span>; -
</span>
);
}
const decimalSeparator = getDecimalSeparator();
const valueSplit: string[] = decimalSeparator
? valueFormatted.split(decimalSeparator).map((v) => `${v}`)
: [`${value}`];
return (
<span
ref={ref}
className="font-mono relative text-black dark:text-white whitespace-nowrap overflow-hidden text-ellipsis text-right rtl-dir"
data-testid={testId || 'price'}
title={valueFormatted}
>
{valueSplit[0]}
{valueSplit[1] ? decimalSeparator : null}
{valueSplit[1] ? (
<span className="opacity-60">{valueSplit[1]}</span>
) : null}
</span>
);
} }
const decimalSeparator = getDecimalSeparator(); )
const valueSplit: string[] = decimalSeparator
? valueFormatted.split(decimalSeparator).map((v) => `${v}`)
: [`${value}`];
return (
<span
className="font-mono relative text-black dark:text-white whitespace-nowrap overflow-hidden text-ellipsis text-right rtl-dir"
data-testid={testId || 'price'}
title={valueFormatted}
>
{valueSplit[0]}
{valueSplit[1] ? decimalSeparator : null}
{valueSplit[1] ? (
<span className="opacity-60">{valueSplit[1]}</span>
) : null}
</span>
);
}
); );
PriceCell.displayName = 'PriceCell'; PriceCell.displayName = 'PriceCell';

View File

@ -3,7 +3,7 @@ import {
formatNumberPercentage, formatNumberPercentage,
} from '@vegaprotocol/react-helpers'; } from '@vegaprotocol/react-helpers';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import { memo, forwardRef } from 'react';
import { signedNumberCssClass } from '@vegaprotocol/react-helpers'; import { signedNumberCssClass } from '@vegaprotocol/react-helpers';
import { Arrow } from '../arrows/arrow'; import { Arrow } from '../arrows/arrow';
@ -36,30 +36,33 @@ export const priceChange = (candles: string[]) => {
: 0; : 0;
}; };
export const PriceCellChange = React.memo( export const PriceCellChange = memo(
({ candles, decimalPlaces }: PriceChangeCellProps) => { forwardRef<HTMLSpanElement, PriceChangeCellProps>(
const change = priceChange(candles); ({ candles, decimalPlaces }: PriceChangeCellProps, ref) => {
const changePercentage = priceChangePercentage(candles); const change = priceChange(candles);
return ( const changePercentage = priceChangePercentage(candles);
<span return (
className={`${signedNumberCssClass( <span
change ref={ref}
)} flex items-center gap-2 justify-end font-mono text-ui-small`} className={`${signedNumberCssClass(
> change
<Arrow value={change} /> )} flex items-center gap-2 justify-end font-mono text-ui-small`}
<span data-testid="price-change-percentage"> >
{formatNumberPercentage( <Arrow value={change} />
new BigNumber(changePercentage.toString()), <span data-testid="price-change-percentage">
2 {formatNumberPercentage(
)} new BigNumber(changePercentage.toString()),
&nbsp; 2
)}
&nbsp;
</span>
<span data-testid="price-change">
{addDecimalsFormatNumber(change.toString(), decimalPlaces ?? 0, 3)}
</span>
</span> </span>
<span data-testid="price-change"> );
{addDecimalsFormatNumber(change.toString(), decimalPlaces ?? 0, 3)} }
</span> )
</span>
);
}
); );
PriceCellChange.displayName = 'PriceCellChange'; PriceCellChange.displayName = 'PriceCellChange';