diff --git a/apps/console-lite/src/app/components/deal-ticket/use-order-validation.tsx b/apps/console-lite/src/app/components/deal-ticket/use-order-validation.tsx index b657ff34e..8ad27a917 100644 --- a/apps/console-lite/src/app/components/deal-ticket/use-order-validation.tsx +++ b/apps/console-lite/src/app/components/deal-ticket/use-order-validation.tsx @@ -215,7 +215,9 @@ export const useOrderValidation = ({ {t('This market is in auction until it reaches')}{' '} } + description={ + + } > {t('sufficient liquidity')} @@ -237,7 +239,9 @@ export const useOrderValidation = ({ {t('This market is in auction due to')}{' '} } + description={ + + } > {t('high price volatility')} @@ -276,7 +280,9 @@ export const useOrderValidation = ({ {t('This market is in auction until it reaches')}{' '} } + description={ + + } > {t('sufficient liquidity')} @@ -300,7 +306,9 @@ export const useOrderValidation = ({ {t('This market is in auction due to')}{' '} } + description={ + + } > {t('high price volatility')} diff --git a/apps/console-lite/src/app/components/simple-market-list/__generated__/CandleLive.ts b/apps/console-lite/src/app/components/simple-market-list/__generated__/CandleLive.ts deleted file mode 100644 index 6c390a93d..000000000 --- a/apps/console-lite/src/app/components/simple-market-list/__generated__/CandleLive.ts +++ /dev/null @@ -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; -} diff --git a/apps/console-lite/src/app/components/simple-market-list/__generated___/Candles.ts b/apps/console-lite/src/app/components/simple-market-list/__generated___/Candles.ts deleted file mode 100644 index 0a71e9633..000000000 --- a/apps/console-lite/src/app/components/simple-market-list/__generated___/Candles.ts +++ /dev/null @@ -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) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useSubscription(CandleLiveDocument, options); - } -export type CandleLiveSubscriptionHookResult = ReturnType; -export type CandleLiveSubscriptionResult = Apollo.SubscriptionResult; \ No newline at end of file diff --git a/apps/liquidity-provision-dashboard/src/app/components/detail/last-24h-volume/last-24h-volume.tsx b/apps/liquidity-provision-dashboard/src/app/components/detail/last-24h-volume/last-24h-volume.tsx index 3a416fe8e..6b4a96c83 100644 --- a/apps/liquidity-provision-dashboard/src/app/components/detail/last-24h-volume/last-24h-volume.tsx +++ b/apps/liquidity-provision-dashboard/src/app/components/detail/last-24h-volume/last-24h-volume.tsx @@ -15,7 +15,7 @@ import { import type { Candle } from '@vegaprotocol/market-list'; import { marketCandlesProvider } from '@vegaprotocol/market-list'; -const DEBOUNCE_UPDATE_TIME = 500; +const THROTTLE_UPDATE_TIME = 500; export const Last24hVolume = ({ marketId, @@ -54,7 +54,7 @@ export const Last24hVolume = ({ const throttledSetCandles = useRef( throttle((data: Candle[]) => { setCandleVolume(calcDayVolume(data)); - }, DEBOUNCE_UPDATE_TIME) + }, THROTTLE_UPDATE_TIME) ).current; const update = useCallback( @@ -78,7 +78,7 @@ export const Last24hVolume = ({ throttle((candles: Candle[]) => { const candle24hAgo = candles?.[0]; setVolumeChange(getChange(data || [], candle24hAgo?.close)); - }, DEBOUNCE_UPDATE_TIME) + }, THROTTLE_UPDATE_TIME) ).current; const updateCandle24hAgo = useCallback( diff --git a/apps/trading/assets/.gitkeep b/apps/trading/assets/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/trading/assets/env-config.js b/apps/trading/assets/env-config.js deleted file mode 100644 index b8be63bb5..000000000 --- a/apps/trading/assets/env-config.js +++ /dev/null @@ -1 +0,0 @@ -window._env_ = {}; diff --git a/apps/trading/assets/green-cloud.png b/apps/trading/assets/green-cloud.png deleted file mode 100644 index 08eeb94a5..000000000 Binary files a/apps/trading/assets/green-cloud.png and /dev/null differ diff --git a/apps/trading/assets/poster-image.jpg b/apps/trading/assets/poster-image.jpg deleted file mode 100644 index e11b2801c..000000000 Binary files a/apps/trading/assets/poster-image.jpg and /dev/null differ diff --git a/apps/trading/client-pages/market/trade-market-header.tsx b/apps/trading/client-pages/market/trade-market-header.tsx index 494c56e5b..629241077 100644 --- a/apps/trading/client-pages/market/trade-market-header.tsx +++ b/apps/trading/client-pages/market/trade-market-header.tsx @@ -16,7 +16,7 @@ import { MarketMarkPrice } from '../../components/market-mark-price'; import { Last24hPriceChange } from '../../components/last-24h-price-change'; import { Last24hVolume } from '../../components/last-24h-volume'; 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 { MarketState as State } from '@vegaprotocol/types'; @@ -64,22 +64,35 @@ export const TradeMarketHeader = ({ > - + + + + + + + + + - - - {asset ? ( ; } export const Last24hPriceChange = ({ marketId, decimalPlaces, initialValue, - isHeader = false, - noUpdate = false, + inViewRoot, }: Props) => { - const [candlesClose, setCandlesClose] = useState( - initialValue || [] - ); + const [ref, inView] = useInView({ root: inViewRoot?.current }); 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( () => ({ marketId: marketId, interval: Schema.Interval.INTERVAL_I1H, - since: yTimestamp, + since: new Date(yesterday).toISOString(), }), - [marketId, yTimestamp] + [marketId, yesterday] ); - const throttledSetCandles = useRef( - throttle((data: Candle[]) => { - if (!noUpdate) { - const candlesClose: string[] = data - .map((candle) => candle?.close) - .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; + const { data, error } = useThrottledDataProvider( + { + dataProvider: marketCandlesProvider, + variables, + skip: !marketId || !inView, }, - [throttledSetCandles] + THROTTLE_UPDATE_TIME ); - const { error } = useDataProvider({ - dataProvider: marketCandlesProvider, - update, - variables, - skip: noUpdate || !marketId, - }); + const candles = + data + ?.map((candle) => candle?.close) + .filter((c): c is CandleClose => c !== null) || initialValue; - const content = useMemo(() => { - if (error || !isNumeric(decimalPlaces)) { - return <>-; - } - return ( - - ); - }, [candlesClose, decimalPlaces, error]); - - return isHeader ? ( - - {content} - - ) : ( - content + if (error || !isNumeric(decimalPlaces)) { + return -; + } + return ( + ); }; diff --git a/apps/trading/components/last-24h-volume/last-24h-volume.tsx b/apps/trading/components/last-24h-volume/last-24h-volume.tsx index b5fa4d052..d1011463f 100644 --- a/apps/trading/components/last-24h-volume/last-24h-volume.tsx +++ b/apps/trading/components/last-24h-volume/last-24h-volume.tsx @@ -1,102 +1,65 @@ +import type { RefObject } from 'react'; +import { useInView } from 'react-intersection-observer'; import { calcCandleVolume, marketCandlesProvider, } from '@vegaprotocol/market-list'; import { addDecimalsFormatNumber, - t, - useDataProvider, + useThrottledDataProvider, useYesterday, isNumeric, } from '@vegaprotocol/react-helpers'; import * as Schema from '@vegaprotocol/types'; -import throttle from 'lodash/throttle'; -import { useCallback, useMemo, useRef, useState } from 'react'; -import * as constants from '../constants'; -import { HeaderStat } from '../header'; +import { useMemo } from 'react'; import type { Candle } from '@vegaprotocol/market-list'; +import { THROTTLE_UPDATE_TIME } from '../constants'; interface Props { marketId?: string; positionDecimalPlaces?: number; - noUpdate?: boolean; - isHeader?: boolean; + formatDecimals?: number; + inViewRoot?: RefObject; initialValue?: string; } export const Last24hVolume = ({ marketId, positionDecimalPlaces, - noUpdate = false, - isHeader = false, + formatDecimals, + inViewRoot, initialValue, }: Props) => { - const [candleVolume, setCandleVolume] = useState(initialValue || ''); 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 [ref, inView] = useInView({ root: inViewRoot?.current }); const variables = useMemo( () => ({ marketId: marketId, interval: Schema.Interval.INTERVAL_I1H, - since: yTimestamp, + since: new Date(yesterday).toISOString(), }), - [marketId, yTimestamp] + [marketId, yesterday] ); - const throttledSetCandles = useRef( - throttle((data: Candle[]) => { - noUpdate || setCandleVolume(calcCandleVolume(data) || ''); - }, constants.DEBOUNCE_UPDATE_TIME) - ).current; - const update = useCallback( - ({ data }: { data: Candle[] | null }) => { - if (data) { - throttledSetCandles(data); - } - return true; + const { data } = useThrottledDataProvider( + { + dataProvider: marketCandlesProvider, + variables, + skip: !(inView && marketId), }, - [throttledSetCandles] + THROTTLE_UPDATE_TIME ); - - const { error } = useDataProvider({ - dataProvider: marketCandlesProvider, - update, - variables, - skip: noUpdate || !marketId, - }); - - const formatDecimals = isHeader ? positionDecimalPlaces || 0 : 2; - const content = useMemo(() => { - return ( - <> - {!error && candleVolume && isNumeric(positionDecimalPlaces) - ? addDecimalsFormatNumber( - candleVolume, - positionDecimalPlaces, - formatDecimals - ) - : '-'} - - ); - }, [error, candleVolume, positionDecimalPlaces, formatDecimals]); - return isHeader ? ( - - {content} - - ) : ( - content + const candleVolume = data ? calcCandleVolume(data) : initialValue; + return ( + + {candleVolume && isNumeric(positionDecimalPlaces) + ? addDecimalsFormatNumber( + candleVolume, + positionDecimalPlaces, + formatDecimals + ) + : '-'} + ); }; diff --git a/apps/trading/components/market-mark-price/market-mark-price.tsx b/apps/trading/components/market-mark-price/market-mark-price.tsx index 3f45eebc1..423b23ed3 100644 --- a/apps/trading/components/market-mark-price/market-mark-price.tsx +++ b/apps/trading/components/market-mark-price/market-mark-price.tsx @@ -1,10 +1,10 @@ -import { useCallback, useMemo, useRef, useState } from 'react'; -import throttle from 'lodash/throttle'; +import type { RefObject } from 'react'; +import { useMemo } from 'react'; +import { useInView } from 'react-intersection-observer'; import { addDecimalsFormatNumber, - t, PriceCell, - useDataProvider, + useThrottledDataProvider, isNumeric, } from '@vegaprotocol/react-helpers'; import type { @@ -12,14 +12,13 @@ import type { MarketDataUpdateFieldsFragment, } from '@vegaprotocol/market-list'; import { marketDataProvider } from '@vegaprotocol/market-list'; -import { HeaderStat } from '../header'; -import * as constants from '../constants'; +import { THROTTLE_UPDATE_TIME } from '../constants'; interface Props { marketId?: string; decimalPlaces?: number; - isHeader?: boolean; - noUpdate?: boolean; + asPriceCell?: boolean; + inViewRoot?: RefObject; initialValue?: string; } @@ -27,58 +26,39 @@ export const MarketMarkPrice = ({ marketId, decimalPlaces, initialValue, - isHeader = false, - noUpdate = false, + inViewRoot, + asPriceCell, }: Props) => { - const [marketPrice, setMarketPrice] = useState( - initialValue || null - ); - const variables = useMemo( - () => ({ - marketId: marketId, - }), - [marketId] - ); + const [ref, inView] = useInView({ root: inViewRoot?.current }); + const variables = useMemo(() => ({ marketId }), [marketId]); - const throttledSetMarketPrice = useRef( - throttle((price: string) => { - noUpdate || setMarketPrice(price); - }, constants.DEBOUNCE_UPDATE_TIME) - ).current; - const update = useCallback( - ({ data: marketData }: { data: MarketData | null }) => { - throttledSetMarketPrice(marketData?.markPrice || ''); - return true; + const { data } = useThrottledDataProvider< + MarketData, + MarketDataUpdateFieldsFragment + >( + { + dataProvider: marketDataProvider, + variables, + skip: !inView, }, - [throttledSetMarketPrice] + THROTTLE_UPDATE_TIME ); - useDataProvider({ - dataProvider: marketDataProvider, - update, - variables, - skip: noUpdate || !marketId, - }); + const marketPrice = data?.markPrice || initialValue; - const content = useMemo(() => { - if (!marketPrice || !isNumeric(decimalPlaces)) { - return <>-; - } - return isHeader ? ( -
{addDecimalsFormatNumber(marketPrice, decimalPlaces)}
- ) : ( + if (!marketPrice || !isNumeric(decimalPlaces)) { + return -; + } + if (asPriceCell) { + return ( ); - }, [marketPrice, decimalPlaces, isHeader]); - - return isHeader ? ( - - {content} - - ) : ( - content + } + return ( + {addDecimalsFormatNumber(marketPrice, decimalPlaces)} ); }; diff --git a/apps/trading/components/market-state/market-state.tsx b/apps/trading/components/market-state/market-state.tsx index 4e94e97b6..4c1d91ce0 100644 --- a/apps/trading/components/market-state/market-state.tsx +++ b/apps/trading/components/market-state/market-state.tsx @@ -23,7 +23,7 @@ export const MarketState = ({ const throttledSetMarketState = useRef( throttle((state: Schema.MarketState) => { setMarketState(state); - }, constants.DEBOUNCE_UPDATE_TIME) + }, constants.THROTTLE_UPDATE_TIME) ).current; const update = useCallback( diff --git a/apps/trading/components/market-trading-mode/market-trading-mode.tsx b/apps/trading/components/market-trading-mode/market-trading-mode.tsx index fb25d3b9a..ccb551e79 100644 --- a/apps/trading/components/market-trading-mode/market-trading-mode.tsx +++ b/apps/trading/components/market-trading-mode/market-trading-mode.tsx @@ -1,110 +1,78 @@ -import { useCallback, useMemo, useState } from 'react'; -import { t, useDataProvider } from '@vegaprotocol/react-helpers'; -import type { MarketDealTicket } from '@vegaprotocol/market-list'; -import { compileGridData, TradingModeTooltip } from '@vegaprotocol/deal-ticket'; +import type { RefObject } from 'react'; +import { t } from '@vegaprotocol/react-helpers'; +import { TradingModeTooltip } from '@vegaprotocol/deal-ticket'; +import { useInView } from 'react-intersection-observer'; import * as Schema from '@vegaprotocol/types'; -import type { - MarketData, - MarketDataUpdateFieldsFragment, - SingleMarketFieldsFragment, -} from '@vegaprotocol/market-list'; -import { marketDataProvider, marketProvider } from '@vegaprotocol/market-list'; +import { useStaticMarketData } from '@vegaprotocol/market-list'; import { HeaderStat } from '../header'; import { Tooltip } from '@vegaprotocol/ui-toolkit'; -interface Props { - marketId?: string; - onSelect?: (marketId: string) => void; - isHeader?: boolean; - noUpdate?: boolean; - initialMode?: Schema.MarketTradingMode; - initialTrigger?: Schema.AuctionTrigger; -} - -export const MarketTradingMode = ({ - marketId, - onSelect, - isHeader = false, - noUpdate = false, - initialMode, - initialTrigger, -}: Props) => { - const [tradingMode, setTradingMode] = - useState(initialMode || null); - const [trigger, setTrigger] = useState( - initialTrigger || null - ); - const [market, setMarket] = useState(null); - const variables = useMemo( - () => ({ - marketId: marketId, - }), - [marketId] - ); - - const { data } = useDataProvider({ - 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({ - dataProvider: marketDataProvider, - update, - variables, - skip: noUpdate || !marketId || !data, - }); - - const content = +const getTradingModeLabel = ( + tradingMode?: Schema.MarketTradingMode, + trigger?: Schema.AuctionTrigger +) => { + return ( (tradingMode === Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION && trigger && trigger !== Schema.AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED ? `${Schema.MarketTradingModeMapping[tradingMode]} - ${Schema.AuctionTriggerMapping[trigger]}` : Schema.MarketTradingModeMapping[ 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 ( - ) + } testId="market-trading-mode" > -
{content}
+
{getTradingModeLabel(tradingMode, trigger)}
- ) : ( + ); +}; + +export const MarketTradingMode = ({ + marketId, + initialTradingMode, + initialTrigger, + inViewRoot, +}: Omit & { + inViewRoot?: RefObject; +}) => { + const [ref, inView] = useInView({ root: inViewRoot?.current }); + const data = useStaticMarketData(marketId, !inView); + + return ( - ) - } + description={} > - {content} + + {getTradingModeLabel( + data?.marketTradingMode ?? initialTradingMode, + data?.trigger ?? initialTrigger + )} + ); }; diff --git a/apps/trading/components/market-volume/market-volume.tsx b/apps/trading/components/market-volume/market-volume.tsx index 4e2974211..ea5c14baf 100644 --- a/apps/trading/components/market-volume/market-volume.tsx +++ b/apps/trading/components/market-volume/market-volume.tsx @@ -31,7 +31,7 @@ export const MarketVolume = ({ marketId }: { marketId: string }) => { const throttledSetMarketVolume = useRef( throttle((volume: string) => { setMarketVolume(volume); - }, constants.DEBOUNCE_UPDATE_TIME) + }, constants.THROTTLE_UPDATE_TIME) ).current; const update = useCallback( ({ data: marketData }: { data: MarketData | null }) => { diff --git a/apps/trading/components/select-market/select-market-columns.tsx b/apps/trading/components/select-market/select-market-columns.tsx index 86e6ceabd..7c3a97818 100644 --- a/apps/trading/components/select-market/select-market-columns.tsx +++ b/apps/trading/components/select-market/select-market-columns.tsx @@ -1,3 +1,4 @@ +import type { RefObject } from 'react'; import { FeesCell } from '@vegaprotocol/market-info'; import { calcCandleHigh, @@ -173,7 +174,7 @@ export const columns = ( market: Market, onSelect: (id: string) => void, onCellClick: OnCellClickHandler, - activeMarketId?: string | null + inViewRoot?: RefObject ) => { const candlesClose = market.candles ?.map((candle) => candle?.close) @@ -189,7 +190,6 @@ export const columns = ( return onSelect(id); } }; - const noUpdate = !activeMarketId || market.id !== activeMarketId; const selectMarketColumns: Column[] = [ { kind: ColumnKind.Market, @@ -221,8 +221,9 @@ export const columns = ( ), className: `${cellClassNames} max-w-[100px]`, @@ -234,7 +235,7 @@ export const columns = ( ), @@ -317,7 +318,8 @@ export const columns = ( marketId={market.id} positionDecimalPlaces={market.positionDecimalPlaces} initialValue={candleVolume} - noUpdate={noUpdate} + inViewRoot={inViewRoot} + formatDecimals={2} /> ), className: `${cellClassNames} hidden lg:table-cell font-mono`, @@ -329,8 +331,8 @@ export const columns = ( value: ( ), @@ -359,9 +361,9 @@ export const columns = ( export const columnsPositionMarkets = ( market: Market, onSelect: (id: string) => void, + inViewRoot?: RefObject, openVolume?: string, - onCellClick?: OnCellClickHandler, - activeMarketId?: string | null + onCellClick?: OnCellClickHandler ) => { const candlesClose = market.candles ?.map((candle) => candle?.close) @@ -377,7 +379,6 @@ export const columnsPositionMarkets = ( } }; const candleVolume = market.candles && calcCandleVolume(market.candles); - const noUpdate = !activeMarketId || market.id !== activeMarketId; const selectMarketColumns: Column[] = [ { kind: ColumnKind.Market, @@ -409,8 +410,9 @@ export const columnsPositionMarkets = ( ), className: cellClassNames, @@ -422,7 +424,7 @@ export const columnsPositionMarkets = ( ), @@ -503,9 +505,10 @@ export const columnsPositionMarkets = ( value: ( ), className: `${cellClassNames} hidden lg:table-cell font-mono`, @@ -517,8 +520,8 @@ export const columnsPositionMarkets = ( value: ( ), diff --git a/apps/trading/components/select-market/select-market.tsx b/apps/trading/components/select-market/select-market.tsx index 008c72cf8..f2477af20 100644 --- a/apps/trading/components/select-market/select-market.tsx +++ b/apps/trading/components/select-market/select-market.tsx @@ -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 { positionsDataProvider } from '@vegaprotocol/positions'; import { t, useDataProvider } from '@vegaprotocol/react-helpers'; @@ -27,7 +28,6 @@ import { TOKEN_NEW_MARKET_PROPOSAL, useLinks, } from '@vegaprotocol/environment'; -import { useGlobalStore } from '../../stores'; export type Market = MarketWithCandles & MarketWithData; @@ -36,19 +36,22 @@ export const SelectAllMarketsTableBody = ({ positions, onSelect, onCellClick, - activeMarketId, + inViewRoot, headers = columnHeaders, - tableColumns = (market) => - columns(market, onSelect, onCellClick, activeMarketId), + tableColumns = (market) => columns(market, onSelect, onCellClick, inViewRoot), }: { markets?: Market[] | null; positions?: PositionFieldsFragment[]; title?: string; onSelect: (id: string) => void; onCellClick: OnCellClickHandler; - activeMarketId?: string | null; headers?: Column[]; - tableColumns?: (market: Market, openVolume?: string) => Column[]; + tableColumns?: ( + market: Market, + inViewRoot?: RefObject, + openVolume?: string + ) => Column[]; + inViewRoot?: RefObject; }) => { const tokenLink = useLinks(DApp.Token); if (!markets) return null; @@ -68,6 +71,7 @@ export const SelectAllMarketsTableBody = ({ onSelect={onSelect} columns={tableColumns( market, + inViewRoot, positions && positions.find((p) => p.market.id === market.id)?.openVolume )} @@ -95,11 +99,11 @@ export const SelectMarketPopover = ({ onSelect: (id: string) => void; onCellClick: OnCellClickHandler; }) => { - const activeMarketId = useGlobalStore((store) => store.marketId); 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'; const { pubKey } = useVegaWallet(); const [open, setOpen] = useState(false); + const inViewRoot = useRef(null); const { data, loading: marketsLoading, @@ -155,6 +159,7 @@ export const SelectMarketPopover = ({
{marketsLoading || (pubKey && positionsLoading) ? (
@@ -167,6 +172,7 @@ export const SelectMarketPopover = ({ <> {t('My markets')} edge.node) @@ -174,13 +180,13 @@ export const SelectMarketPopover = ({ onSelect={onSelectMarket} onCellClick={onCellClick} headers={columnHeadersPositionMarkets} - tableColumns={(market, openVolume) => + tableColumns={(market, inViewRoot, openVolume) => columnsPositionMarkets( market, onSelectMarket, + inViewRoot, openVolume, - onCellClick, - activeMarketId + onCellClick ) } /> @@ -188,10 +194,10 @@ export const SelectMarketPopover = ({ ) : null} {t('All markets')} )} diff --git a/apps/trading/setup-tests.ts b/apps/trading/setup-tests.ts index 17e7e61cb..ebaab45f9 100644 --- a/apps/trading/setup-tests.ts +++ b/apps/trading/setup-tests.ts @@ -1,7 +1,9 @@ import '@testing-library/jest-dom'; import 'jest-canvas-mock'; import ResizeObserver from 'resize-observer-polyfill'; +import { defaultFallbackInView } from 'react-intersection-observer'; +defaultFallbackInView(true); global.ResizeObserver = ResizeObserver; // Required by radix-ui/react-tooltip diff --git a/libs/deal-ticket/src/components/deal-ticket/time-in-force-selector.tsx b/libs/deal-ticket/src/components/deal-ticket/time-in-force-selector.tsx index f56fac56e..a64969d90 100644 --- a/libs/deal-ticket/src/components/deal-ticket/time-in-force-selector.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/time-in-force-selector.tsx @@ -78,7 +78,11 @@ export const TimeInForceSelector = ({ return ( {t('This market is in auction until it reaches')}{' '} - }> + + } + > {t('sufficient liquidity')} {'. '} @@ -93,7 +97,11 @@ export const TimeInForceSelector = ({ return ( {t('This market is in auction due to')}{' '} - }> + + } + > {t('high price volatility')} {'. '} diff --git a/libs/deal-ticket/src/components/deal-ticket/type-selector.tsx b/libs/deal-ticket/src/components/deal-ticket/type-selector.tsx index 973c2c5ff..c6add5c08 100644 --- a/libs/deal-ticket/src/components/deal-ticket/type-selector.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/type-selector.tsx @@ -33,7 +33,11 @@ export const TypeSelector = ({ return ( {t('This market is in auction until it reaches')}{' '} - }> + + } + > {t('sufficient liquidity')} {'. '} @@ -46,7 +50,11 @@ export const TypeSelector = ({ return ( {t('This market is in auction due to')}{' '} - }> + + } + > {t('high price volatility')} {'. '} diff --git a/libs/deal-ticket/src/components/trading-mode-tooltip/compile-grid-data.tsx b/libs/deal-ticket/src/components/trading-mode-tooltip/compile-grid-data.tsx index 1ce35ceb9..60a2e5271 100644 --- a/libs/deal-ticket/src/components/trading-mode-tooltip/compile-grid-data.tsx +++ b/libs/deal-ticket/src/components/trading-mode-tooltip/compile-grid-data.tsx @@ -8,17 +8,31 @@ import * as Schema from '@vegaprotocol/types'; import { Link as UILink } from '@vegaprotocol/ui-toolkit'; import type { ReactNode } from 'react'; import { Link } from 'react-router-dom'; -import type { MarketDealTicket } from '@vegaprotocol/market-list'; +import type { Market, MarketData } from '@vegaprotocol/market-list'; 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 ): { label: ReactNode; value?: ReactNode }[] => { const grid: DataGridProps['grid'] = []; const isLiquidityMonitoringAuction = - market.data.marketTradingMode === + marketData.marketTradingMode === 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 formattedValue = addDecimalsFormatNumber( @@ -30,19 +44,17 @@ export const compileGridData = ( return `${formattedValue} ${asset}`; }; - if (!market.data) return grid; + if (!marketData) return grid; - if (market.data.auctionStart) { + if (marketData.auctionStart) { grid.push({ label: t('Auction start'), - value: getDateTimeFormat().format(new Date(market.data.auctionStart)), + value: getDateTimeFormat().format(new Date(marketData.auctionStart)), }); } - if (market.data.auctionEnd) { - const endDate = getDateTimeFormat().format( - new Date(market.data.auctionEnd) - ); + if (marketData.auctionEnd) { + const endDate = getDateTimeFormat().format(new Date(marketData.auctionEnd)); grid.push({ label: isLiquidityMonitoringAuction ? t('Est. auction end') @@ -51,14 +63,14 @@ export const compileGridData = ( }); } - if (isLiquidityMonitoringAuction && market.data.targetStake) { + if (isLiquidityMonitoringAuction && marketData.targetStake) { grid.push({ label: t('Target liquidity'), - value: formatStake(market.data.targetStake), + value: formatStake(marketData.targetStake), }); } - if (isLiquidityMonitoringAuction && market.data.suppliedStake) { + if (isLiquidityMonitoringAuction && marketData.suppliedStake) { grid.push({ label: ( {t('Current liquidity')} ), - value: formatStake(market.data.suppliedStake), + value: formatStake(marketData.suppliedStake), }); } - if (market.data.indicativePrice) { + if (marketData.indicativePrice) { grid.push({ label: t('Est. uncrossing price'), value: - market.data.indicativePrice && market.data.indicativePrice !== '0' + marketData.indicativePrice && marketData.indicativePrice !== '0' ? `~ ${addDecimalsFormatNumber( - market.data.indicativePrice, + marketData.indicativePrice, market.decimalPlaces )}` : '-', }); } - if (market.data.indicativeVolume) { + if (marketData.indicativeVolume) { grid.push({ label: t('Est. uncrossing vol'), value: - market.data.indicativeVolume && market.data.indicativeVolume !== '0' + marketData.indicativeVolume && marketData.indicativeVolume !== '0' ? '~' + addDecimalsFormatNumber( - market.data.indicativeVolume, + marketData.indicativeVolume, market.positionDecimalPlaces ) : '-', diff --git a/libs/deal-ticket/src/components/trading-mode-tooltip/trading-mode-tooltip.tsx b/libs/deal-ticket/src/components/trading-mode-tooltip/trading-mode-tooltip.tsx index d60b52e8a..a6a4042f1 100644 --- a/libs/deal-ticket/src/components/trading-mode-tooltip/trading-mode-tooltip.tsx +++ b/libs/deal-ticket/src/components/trading-mode-tooltip/trading-mode-tooltip.tsx @@ -1,23 +1,35 @@ -import type { ReactNode } from 'react'; import classNames from 'classnames'; import { useEnvironment } from '@vegaprotocol/environment'; import { DataGrid, t } from '@vegaprotocol/react-helpers'; import * as Schema from '@vegaprotocol/types'; import { ExternalLink } from '@vegaprotocol/ui-toolkit'; import { createDocsLinks } from '@vegaprotocol/react-helpers'; +import { compileGridData } from './compile-grid-data'; +import { useMarket, useStaticMarketData } from '@vegaprotocol/market-list'; type TradingModeTooltipProps = { - tradingMode: Schema.MarketTradingMode | null; - trigger: Schema.AuctionTrigger | null; - compiledGrid?: { label: ReactNode; value?: ReactNode }[]; + marketId?: string; + onSelect?: (marketId: string) => void; + skip?: boolean; }; export const TradingModeTooltip = ({ - tradingMode, - trigger, - compiledGrid, + marketId, + onSelect, + skip, }: TradingModeTooltipProps) => { 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) { case Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS: { return ( diff --git a/libs/market-list/src/lib/market-data-provider.ts b/libs/market-list/src/lib/market-data-provider.ts index df65cb219..a2a5288fb 100644 --- a/libs/market-list/src/lib/market-data-provider.ts +++ b/libs/market-list/src/lib/market-data-provider.ts @@ -1,4 +1,9 @@ import produce from 'immer'; +import { useMemo } from 'react'; +import { + makeDerivedDataProvider, + useDataProvider, +} from '@vegaprotocol/react-helpers'; import { makeDataProvider } from '@vegaprotocol/react-helpers'; import { MarketDataDocument, @@ -39,3 +44,51 @@ export const marketDataProvider = makeDataProvider< getData, 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; + 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; +}; diff --git a/libs/market-list/src/lib/markets-provider.ts b/libs/market-list/src/lib/markets-provider.ts index 77e091890..494c5a201 100644 --- a/libs/market-list/src/lib/markets-provider.ts +++ b/libs/market-list/src/lib/markets-provider.ts @@ -35,6 +35,28 @@ export const marketsProvider = makeDataProvider< fetchPolicy: 'cache-first', }); +const marketProvider = makeDerivedDataProvider< + Market, + never, + { marketId: string } +>( + [marketsProvider], + ([markets], variables) => + ((markets as ReturnType) || []).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( [marketsProvider], ([markets]) => filterAndSortMarkets(markets) diff --git a/libs/react-helpers/src/lib/format/number.spec.tsx b/libs/react-helpers/src/lib/format/number.spec.tsx index 5a599d519..0dccbc44b 100644 --- a/libs/react-helpers/src/lib/format/number.spec.tsx +++ b/libs/react-helpers/src/lib/format/number.spec.tsx @@ -86,6 +86,8 @@ describe('isNumeric', () => { { i: '--123.01', o: false }, { i: '123.', 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.123), o: true }, { i: new BigNumber(123.123).toString(), o: true }, @@ -98,7 +100,7 @@ describe('isNumeric', () => { i, o, }: { - i: number | string | undefined | null | BigNumber; + i: number | string | undefined | null | BigNumber | bigint; o: boolean; }) => { expect(isNumeric(i)).toStrictEqual(o); diff --git a/libs/react-helpers/src/lib/format/number.tsx b/libs/react-helpers/src/lib/format/number.tsx index 51f0aea76..14f4be2e3 100644 --- a/libs/react-helpers/src/lib/format/number.tsx +++ b/libs/react-helpers/src/lib/format/number.tsx @@ -138,7 +138,7 @@ export const useNumberParts = ( }; export const isNumeric = ( - value?: string | number | BigNumber | null + value?: string | number | BigNumber | bigint | null ): value is NonNullable => /^-?\d*\.?\d+$/.test(String(value)); const INFINITY = '∞'; diff --git a/libs/react-helpers/src/lib/generic-data-provider.ts b/libs/react-helpers/src/lib/generic-data-provider.ts index 11e45d337..391236db1 100644 --- a/libs/react-helpers/src/lib/generic-data-provider.ts +++ b/libs/react-helpers/src/lib/generic-data-provider.ts @@ -560,7 +560,8 @@ export type CombineDerivedData< Variables extends OperationVariables = OperationVariables > = ( data: DerivedPart['data'][], - variables?: Variables + variables: Variables | undefined, + prevData: Data | null ) => Data | null; export type CombineDerivedDelta< @@ -641,7 +642,8 @@ function makeDerivedDataProviderInternal< const newData = newLoaded ? combineData( parts.map((part) => part.data), - variables + variables, + data ) : data; if ( @@ -655,7 +657,7 @@ function makeDerivedDataProviderInternal< loaded = newLoaded; const previousData = data; data = newData; - if (newLoaded) { + if (loaded) { const updatedPart = parts[updatedPartIndex]; if (updatedPart.isUpdate) { isUpdate = true; diff --git a/libs/react-helpers/src/lib/grid/price-cell.tsx b/libs/react-helpers/src/lib/grid/price-cell.tsx index 09945c91a..15a141dac 100644 --- a/libs/react-helpers/src/lib/grid/price-cell.tsx +++ b/libs/react-helpers/src/lib/grid/price-cell.tsx @@ -1,37 +1,41 @@ -import React from 'react'; -import { getDecimalSeparator } from '../format'; +import { memo, forwardRef } from 'react'; +import { getDecimalSeparator, isNumeric } from '../format'; export interface IPriceCellProps { value: number | bigint | null | undefined; valueFormatted: string; testId?: string; } -export const PriceCell = React.memo( - ({ value, valueFormatted, testId }: IPriceCellProps) => { - if ( - (!value && value !== 0) || - (typeof value === 'number' && isNaN(Number(value))) - ) { - return -; +export const PriceCell = memo( + forwardRef( + ({ value, valueFormatted, testId }: IPriceCellProps, ref) => { + if (!isNumeric(value)) { + return ( + + - + + ); + } + const decimalSeparator = getDecimalSeparator(); + const valueSplit: string[] = decimalSeparator + ? valueFormatted.split(decimalSeparator).map((v) => `${v}`) + : [`${value}`]; + return ( + + {valueSplit[0]} + {valueSplit[1] ? decimalSeparator : null} + {valueSplit[1] ? ( + {valueSplit[1]} + ) : null} + + ); } - const decimalSeparator = getDecimalSeparator(); - const valueSplit: string[] = decimalSeparator - ? valueFormatted.split(decimalSeparator).map((v) => `${v}`) - : [`${value}`]; - return ( - - {valueSplit[0]} - {valueSplit[1] ? decimalSeparator : null} - {valueSplit[1] ? ( - {valueSplit[1]} - ) : null} - - ); - } + ) ); PriceCell.displayName = 'PriceCell'; diff --git a/libs/ui-toolkit/src/components/price-change/price-change-cell.tsx b/libs/ui-toolkit/src/components/price-change/price-change-cell.tsx index dfd9830f2..dbda4080b 100644 --- a/libs/ui-toolkit/src/components/price-change/price-change-cell.tsx +++ b/libs/ui-toolkit/src/components/price-change/price-change-cell.tsx @@ -3,7 +3,7 @@ import { formatNumberPercentage, } from '@vegaprotocol/react-helpers'; import BigNumber from 'bignumber.js'; -import React from 'react'; +import { memo, forwardRef } from 'react'; import { signedNumberCssClass } from '@vegaprotocol/react-helpers'; import { Arrow } from '../arrows/arrow'; @@ -36,30 +36,33 @@ export const priceChange = (candles: string[]) => { : 0; }; -export const PriceCellChange = React.memo( - ({ candles, decimalPlaces }: PriceChangeCellProps) => { - const change = priceChange(candles); - const changePercentage = priceChangePercentage(candles); - return ( - - - - {formatNumberPercentage( - new BigNumber(changePercentage.toString()), - 2 - )} -   +export const PriceCellChange = memo( + forwardRef( + ({ candles, decimalPlaces }: PriceChangeCellProps, ref) => { + const change = priceChange(candles); + const changePercentage = priceChangePercentage(candles); + return ( + + + + {formatNumberPercentage( + new BigNumber(changePercentage.toString()), + 2 + )} +   + + + {addDecimalsFormatNumber(change.toString(), decimalPlaces ?? 0, 3)} + - - {addDecimalsFormatNumber(change.toString(), decimalPlaces ?? 0, 3)} - - - ); - } + ); + } + ) ); PriceCellChange.displayName = 'PriceCellChange';