chore(trading): add "holding CMD + click" external link (#3273)

Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
This commit is contained in:
Maciek 2023-03-31 15:23:44 +02:00 committed by GitHub
parent 2bfc3abd15
commit 82e5128ba1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 273 additions and 231 deletions

View File

@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo } from 'react';
import React, { useEffect, useMemo } from 'react';
import { addDecimalsFormatNumber, titlefy } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import {
@ -13,6 +13,7 @@ import { useGlobalStore, usePageTitleStore } from '../../stores';
import { TradeGrid, TradePanels } from './trade-grid';
import { useNavigate, useParams } from 'react-router-dom';
import { Links, Routes } from '../../pages/client-router';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
const calculatePrice = (markPrice?: string, decimalPlaces?: number) => {
return markPrice && decimalPlaces
@ -66,14 +67,7 @@ export const MarketPage = () => {
const update = useGlobalStore((store) => store.update);
const lastMarketId = useGlobalStore((store) => store.marketId);
const onSelect = useCallback(
(id: string) => {
if (id && id !== marketId) {
navigate(Links[Routes.MARKET](id));
}
},
[marketId, navigate]
);
const onSelect = useMarketClickHandler();
const { data, error, loading } = useDataProvider({
dataProvider: marketProvider,

View File

@ -8,7 +8,7 @@ import { TradesContainer } from '@vegaprotocol/trades';
import { LayoutPriority } from 'allotment';
import classNames from 'classnames';
import AutoSizer from 'react-virtualized-auto-sizer';
import { memo, useCallback, useState } from 'react';
import { memo, useState } from 'react';
import type { ReactNode, ComponentProps } from 'react';
import { DepthChartContainer } from '@vegaprotocol/market-depth';
import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
@ -27,9 +27,9 @@ import { TradeMarketHeader } from './trade-market-header';
import { NO_MARKET } from './constants';
import { LiquidityContainer } from '../liquidity/liquidity';
import { useNavigate } from 'react-router-dom';
import { Links, Routes } from '../../pages/client-router';
import type { PinnedAsset } from '@vegaprotocol/accounts';
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
type MarketDependantView =
| typeof CandlesChartContainer
@ -66,7 +66,7 @@ type TradingView = keyof typeof TradingViews;
interface TradeGridProps {
market: Market | null;
onSelect: (marketId: string) => void;
onSelect: (marketId: string, metaKey?: boolean) => void;
pinnedAsset?: PinnedAsset;
}
@ -78,15 +78,7 @@ interface BottomPanelProps {
const MarketBottomPanel = memo(
({ marketId, pinnedAsset }: BottomPanelProps) => {
const { screenSize } = useScreenDimensions();
const navigate = useNavigate();
const onMarketClick = useCallback(
(marketId: string) => {
navigate(Links[Routes.MARKET](marketId), {
replace: true,
});
},
[navigate]
);
const onMarketClick = useMarketClickHandler(true);
return 'xxxl' === screenSize ? (
<ResizableGrid proportionalLayout minSize={200}>
@ -189,7 +181,7 @@ const MainGrid = memo(
pinnedAsset,
}: {
marketId: string;
onSelect?: (marketId: string) => void;
onSelect: (marketId: string, metaKey?: boolean) => void;
pinnedAsset?: PinnedAsset;
}) => {
const navigate = useNavigate();
@ -230,12 +222,7 @@ const MainGrid = memo(
/>
</Tab>
<Tab id="info" name={t('Info')}>
<TradingViews.Info
marketId={marketId}
onSelect={(id: string) => {
onSelect?.(id);
}}
/>
<TradingViews.Info marketId={marketId} />
</Tab>
</Tabs>
</TradeGridChild>
@ -304,7 +291,7 @@ const TradeGridChild = ({ children }: TradeGridChildProps) => {
interface TradePanelsProps {
market: Market | null;
onSelect: (marketId: string) => void;
onSelect: (marketId: string, metaKey?: boolean) => void;
onMarketClick?: (marketId: string) => void;
onClickCollateral: () => void;
pinnedAsset?: PinnedAsset;
@ -320,7 +307,7 @@ export const TradePanels = ({
const renderView = () => {
const Component = memo<{
marketId: string;
onSelect: (marketId: string) => void;
onSelect: (marketId: string, metaKey?: boolean) => void;
onMarketClick?: (marketId: string) => void;
onClickCollateral: () => void;
pinnedAsset?: PinnedAsset;

View File

@ -22,7 +22,7 @@ import { MarketState as State } from '@vegaprotocol/types';
interface TradeMarketHeaderProps {
market: Market | null;
onSelect: (marketId: string) => void;
onSelect: (marketId: string, metaKey?: boolean) => void;
}
export const TradeMarketHeader = ({
@ -91,7 +91,6 @@ export const TradeMarketHeader = ({
</HeaderStat>
<HeaderStatMarketTradingMode
marketId={market?.id}
onSelect={onSelect}
initialTradingMode={market?.tradingMode}
/>
<MarketState market={market} />

View File

@ -1,16 +1,7 @@
import { useCallback } from 'react';
import { MarketsContainer } from '@vegaprotocol/market-list';
import { useNavigate } from 'react-router-dom';
import { Links, Routes } from '../../pages/client-router';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
export const Markets = () => {
const navigate = useNavigate();
const handleOnSelect = useCallback(
(marketId: string) => {
navigate(Links[Routes.MARKET](marketId));
},
[navigate]
);
const handleOnSelect = useMarketClickHandler();
return <MarketsContainer onSelect={handleOnSelect} />;
};

View File

@ -19,25 +19,18 @@ import { usePageTitleStore } from '../../stores';
import { LedgerContainer } from '@vegaprotocol/ledger';
import { AccountsContainer } from '../../components/accounts-container';
import { AccountHistoryContainer } from './account-history-container';
import { useNavigate } from 'react-router-dom';
import { Links, Routes } from '../../pages/client-router';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
export const Portfolio = () => {
const { updateTitle } = usePageTitleStore((store) => ({
updateTitle: store.updateTitle,
}));
const navigate = useNavigate();
useEffect(() => {
updateTitle(titlefy([t('Portfolio')]));
}, [updateTitle]);
const onMarketClick = (marketId: string) => {
navigate(Links[Routes.MARKET](marketId), {
replace: true,
});
};
const onMarketClick = useMarketClickHandler(true);
const wrapperClasses = 'h-full max-h-full flex flex-col';
return (

View File

@ -25,7 +25,7 @@ const getTradingModeLabel = (
interface HeaderStatMarketTradingModeProps {
marketId?: string;
onSelect?: (marketId: string) => void;
onSelect?: (marketId: string, metaKey?: boolean) => void;
initialTradingMode?: Schema.MarketTradingMode;
initialTrigger?: Schema.AuctionTrigger;
}
@ -66,7 +66,9 @@ export const MarketTradingMode = ({
return (
<Tooltip
description={<TradingModeTooltip marketId={marketId} skip={!inView} />}
description={
<TradingModeTooltip marketId={marketId} skip={!inView} skipGrid />
}
>
<span ref={ref}>
{getTradingModeLabel(

View File

@ -1,4 +1,4 @@
import type { RefObject } from 'react';
import type { RefObject, MouseEvent } from 'react';
import { FeesCell } from '@vegaprotocol/market-info';
import {
calcCandleHigh,
@ -157,14 +157,14 @@ export const columnHeaders: Column[] = [
];
export type OnCellClickHandler = (
e: React.MouseEvent,
e: MouseEvent,
kind: ColumnKind,
value: string
) => void;
export const columns = (
market: MarketMaybeWithDataAndCandles,
onSelect: (id: string) => void,
onSelect: (id: string, metaKey?: boolean) => void,
onCellClick: OnCellClickHandler,
inViewRoot?: RefObject<HTMLElement>
) => {
@ -174,14 +174,7 @@ export const columns = (
const candleLow = market.candles && calcCandleLow(market.candles);
const candleHigh = market.candles && calcCandleHigh(market.candles);
const candleVolume = market.candles && calcCandleVolume(market.candles);
const handleKeyPress = (
event: React.KeyboardEvent<HTMLAnchorElement>,
id: string
) => {
if (event.key === 'Enter' && onSelect) {
return onSelect(id);
}
};
const selectMarketColumns: Column[] = [
{
kind: ColumnKind.Market,
@ -189,10 +182,10 @@ export const columns = (
<Link
to={Links[Routes.MARKET](market.id)}
data-testid={`market-link-${market.id}`}
onKeyPress={(event) => handleKeyPress(event, market.id)}
onClick={(e) => {
e.preventDefault();
onSelect(market.id);
e.stopPropagation();
onSelect(market.id, e.metaKey);
}}
>
<UILink>{market.tradableInstrument.instrument.code}</UILink>
@ -352,7 +345,7 @@ export const columns = (
export const columnsPositionMarkets = (
market: MarketMaybeWithDataAndCandles,
onSelect: (id: string) => void,
onSelect: (id: string, metaKey?: boolean) => void,
inViewRoot?: RefObject<HTMLElement>,
openVolume?: string,
onCellClick?: OnCellClickHandler
@ -362,14 +355,6 @@ export const columnsPositionMarkets = (
.filter((c: string | undefined): c is CandleClose => !isNil(c));
const candleLow = market.candles && calcCandleLow(market.candles);
const candleHigh = market.candles && calcCandleHigh(market.candles);
const handleKeyPress = (
event: React.KeyboardEvent<HTMLSpanElement>,
id: string
) => {
if (event.key === 'Enter' && onSelect) {
return onSelect(id);
}
};
const candleVolume = market.candles && calcCandleVolume(market.candles);
const selectMarketColumns: Column[] = [
{
@ -378,10 +363,10 @@ export const columnsPositionMarkets = (
<Link
to={Links[Routes.MARKET](market.id)}
data-testid={`market-link-${market.id}`}
onKeyPress={(event) => handleKeyPress(event, market.id)}
onClick={(e) => {
e.preventDefault();
onSelect(market.id);
e.stopPropagation();
onSelect(market.id, e.metaKey);
}}
>
<UILink>{market.tradableInstrument.instrument.code}</UILink>

View File

@ -37,14 +37,14 @@ export const SelectMarketTableRow = ({
}: {
detailed?: boolean;
columns: Column[];
onSelect: (id: string) => void;
onSelect: (id: string, metaKey?: boolean) => void;
marketId: string;
}) => {
return (
<tr
className={`hover:bg-neutral-200 dark:hover:bg-neutral-700 cursor-pointer relative h-[34px]`}
onClick={() => {
onSelect(marketId);
onClick={(ev) => {
onSelect(marketId, ev.metaKey);
}}
data-testid={`market-link-${marketId}`}
>

View File

@ -178,6 +178,6 @@ describe('SelectMarket', () => {
expect(screen.getByText('25.00%')).toBeTruthy(); // price change
expect(container).toHaveTextContent(/1,000/); // volume
fireEvent.click(screen.getAllByTestId(`market-link-1`)[0]);
expect(onSelect).toHaveBeenCalledWith('1');
expect(onSelect).toHaveBeenCalledWith('1', false);
});
});

View File

@ -40,7 +40,7 @@ export const SelectAllMarketsTableBody = ({
markets?: MarketMaybeWithDataAndCandles[] | null;
positions?: PositionFieldsFragment[];
title?: string;
onSelect: (id: string) => void;
onSelect: (id: string, metaKey?: boolean) => void;
onCellClick: OnCellClickHandler;
headers?: Column[];
tableColumns?: (
@ -95,7 +95,7 @@ export const SelectMarketPopover = ({
}: {
marketCode: string;
marketName: string;
onSelect: (id: string) => void;
onSelect: (id: string, metaKey?: boolean) => void;
onCellClick: OnCellClickHandler;
}) => {
const { pubKey } = useVegaWallet();
@ -116,8 +116,8 @@ export const SelectMarketPopover = ({
skip: !pubKey,
});
const onSelectMarket = useCallback(
(marketId: string) => {
onSelect(marketId);
(marketId: string, metaKey?: boolean) => {
onSelect(marketId, metaKey);
setOpen(false);
},
[onSelect]

View File

@ -0,0 +1,21 @@
import { useNavigate, useParams, useLocation } from 'react-router-dom';
import { useCallback } from 'react';
import { Links, Routes } from '../../pages/client-router';
export const useMarketClickHandler = (replace = false) => {
const navigate = useNavigate();
const { marketId } = useParams();
const { pathname } = useLocation();
const isMarketPage = pathname.match(/^\/markets\/(.+)/);
return useCallback(
(selectedId: string, metaKey?: boolean) => {
const link = Links[Routes.MARKET](selectedId);
if (metaKey) {
window.open(`/#${link}`, '_blank');
} else if (selectedId !== marketId || !isMarketPage) {
navigate(link, { replace });
}
},
[navigate, marketId, replace, isMarketPage]
);
};

View File

@ -1,3 +1,5 @@
import { useMemo, useState } from 'react';
import classNames from 'classnames';
import Head from 'next/head';
import type { AppProps } from 'next/app';
import { t } from '@vegaprotocol/i18n';
@ -23,14 +25,12 @@ import {
import './styles.css';
import { useGlobalStore, usePageTitleStore } from '../stores';
import { Footer } from '../components/footer';
import { useMemo, useState } from 'react';
import DialogsContainer from './dialogs-container';
import ToastsManager from './toasts-manager';
import { HashRouter, useLocation, useSearchParams } from 'react-router-dom';
import { Connectors } from '../lib/vega-connectors';
import { ViewingBanner } from '../components/viewing-banner';
import { Banner } from '../components/banner';
import classNames from 'classnames';
import { AppLoader, DynamicLoader } from '../components/app-loader';
import { Navbar } from '../components/navbar';
@ -57,7 +57,7 @@ const Title = () => {
);
};
const TransactionsHandler = () => {
const InitializeHandlers = () => {
useVegaTransactionManager();
useVegaTransactionUpdater();
useEthTransactionManager();
@ -93,7 +93,7 @@ function AppBody({ Component }: AppProps) {
</div>
<DialogsContainer />
<ToastsManager />
<TransactionsHandler />
<InitializeHandlers />
<MaybeConnectEagerly />
</div>
);

View File

@ -9,6 +9,7 @@ export * from './lib/cells/price-change-cell';
export * from './lib/cells/price-flash-cell';
export * from './lib/cells/vol-cell';
export * from './lib/cells/centered-grid-cell';
export * from './lib/cells/market-name-cell';
export * from './lib/filters/date-range-filter';
export * from './lib/filters/set-filter';

View File

@ -0,0 +1,35 @@
import type { MouseEvent } from 'react';
import { useCallback } from 'react';
import get from 'lodash/get';
interface MarketNameCellProps {
value?: string;
data?: { id?: string; marketId?: string; market?: { id: string } };
idPath?: string;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
}
export const MarketNameCell = ({
value,
data,
idPath,
onMarketClick,
}: MarketNameCellProps) => {
const id = data ? get(data, idPath ?? 'id', 'all') : '';
const handleOnClick = useCallback(
(ev: MouseEvent<HTMLButtonElement>) => {
ev.preventDefault();
ev.stopPropagation();
if (onMarketClick) {
onMarketClick(id, ev.metaKey);
}
},
[id, onMarketClick]
);
if (!data) return null;
return (
<button onClick={handleOnClick} tabIndex={0}>
{value}
</button>
);
};

View File

@ -26,7 +26,7 @@ export const compileGridData = (
| 'targetStake'
| 'trigger'
> | null,
onSelect?: (id: string) => void
onSelect?: (id: string, metaKey?: boolean) => void
): { label: ReactNode; value?: ReactNode }[] => {
const grid: SimpleGridProps['grid'] = [];
const isLiquidityMonitoringAuction =
@ -78,7 +78,7 @@ export const compileGridData = (
label: (
<Link
to={`/liquidity/${market.id}`}
onClick={() => onSelect && onSelect(market.id)}
onClick={(ev) => onSelect && onSelect(market.id, ev.metaKey)}
>
<UILink>{t('Current liquidity')}</UILink>
</Link>

View File

@ -12,14 +12,16 @@ import { useMarket, useStaticMarketData } from '@vegaprotocol/market-list';
type TradingModeTooltipProps = {
marketId?: string;
onSelect?: (marketId: string) => void;
onSelect?: (marketId: string, metaKey?: boolean) => void;
skip?: boolean;
skipGrid?: boolean;
};
export const TradingModeTooltip = ({
marketId,
onSelect,
skip,
skipGrid,
}: TradingModeTooltipProps) => {
const { VEGA_DOCS_URL } = useEnvironment();
const { data: market } = useMarket(marketId);
@ -42,7 +44,7 @@ export const TradingModeTooltip = ({
);
const compiledGrid =
onSelect && compileGridData(market, marketData, onSelect);
!skipGrid && compileGridData(market, marketData, onSelect);
switch (marketTradingMode) {
case Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS: {
@ -103,6 +105,7 @@ export const TradingModeTooltip = ({
{VEGA_DOCS_URL && (
<ExternalLink
href={createDocsLinks(VEGA_DOCS_URL).AUCTION_TYPE_OPENING}
className="ml-1"
>
{t('Find out more')}
</ExternalLink>
@ -129,6 +132,7 @@ export const TradingModeTooltip = ({
createDocsLinks(VEGA_DOCS_URL)
.AUCTION_TYPE_LIQUIDITY_MONITORING
}
className="ml-1"
>
{t('Find out more')}
</ExternalLink>
@ -153,6 +157,7 @@ export const TradingModeTooltip = ({
createDocsLinks(VEGA_DOCS_URL)
.AUCTION_TYPE_LIQUIDITY_MONITORING
}
className="ml-1"
>
{t('Find out more')}
</ExternalLink>
@ -175,6 +180,7 @@ export const TradingModeTooltip = ({
createDocsLinks(VEGA_DOCS_URL)
.AUCTION_TYPE_PRICE_MONITORING
}
className="ml-1"
>
{t('Find out more')}
</ExternalLink>

View File

@ -8,7 +8,7 @@ export const FillsContainer = ({
onMarketClick,
}: {
marketId?: string;
onMarketClick?: (marketId: string) => void;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
}) => {
const { pubKey } = useVegaWallet();

View File

@ -11,7 +11,7 @@ import { useBottomPlaceholder } from '@vegaprotocol/react-helpers';
interface FillsManagerProps {
partyId: string;
marketId?: string;
onMarketClick?: (marketId: string) => void;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
}
export const FillsManager = ({

View File

@ -7,6 +7,7 @@ import type { Trade } from './fills-data-provider';
import { FillsTable, getFeesBreakdown } from './fills-table';
import { generateFill } from './test-helpers';
import { MemoryRouter } from 'react-router-dom';
describe('FillsTable', () => {
let defaultFill: PartialDeep<Trade>;
@ -36,7 +37,11 @@ describe('FillsTable', () => {
it('correct columns are rendered', async () => {
await act(async () => {
render(<FillsTable partyId="party-id" rowData={[generateFill()]} />);
render(
<MemoryRouter>
<FillsTable partyId="party-id" rowData={[generateFill()]} />
</MemoryRouter>
);
});
const headers = screen.getAllByRole('columnheader');
@ -67,7 +72,11 @@ describe('FillsTable', () => {
liquidityFee: '2',
},
});
render(<FillsTable partyId={partyId} rowData={[{ ...buyerFill }]} />);
render(
<MemoryRouter>
<FillsTable partyId={partyId} rowData={[{ ...buyerFill }]} />
</MemoryRouter>
);
const cells = screen.getAllByRole('gridcell');
const expectedValues = [
buyerFill.market?.tradableInstrument.instrument.name || '',
@ -100,7 +109,11 @@ describe('FillsTable', () => {
liquidityFee: '1',
},
});
render(<FillsTable partyId={partyId} rowData={[buyerFill]} />);
render(
<MemoryRouter>
<FillsTable partyId={partyId} rowData={[buyerFill]} />
</MemoryRouter>
);
const cells = screen.getAllByRole('gridcell');
const expectedValues = [
@ -129,7 +142,9 @@ describe('FillsTable', () => {
aggressor: Schema.Side.SIDE_SELL,
});
const { rerender } = render(
<MemoryRouter>
<FillsTable partyId={partyId} rowData={[takerFill]} />
</MemoryRouter>
);
expect(
screen
@ -143,7 +158,11 @@ describe('FillsTable', () => {
},
aggressor: Schema.Side.SIDE_BUY,
});
rerender(<FillsTable partyId={partyId} rowData={[makerFill]} />);
rerender(
<MemoryRouter>
<FillsTable partyId={partyId} rowData={[makerFill]} />
</MemoryRouter>
);
expect(
screen
@ -160,7 +179,11 @@ describe('FillsTable', () => {
},
aggressor: Schema.Side.SIDE_SELL,
});
render(<FillsTable partyId={partyId} rowData={[takerFill]} />);
render(
<MemoryRouter>
<FillsTable partyId={partyId} rowData={[takerFill]} />
</MemoryRouter>
);
const feeCell = screen
.getAllByRole('gridcell')

View File

@ -14,16 +14,13 @@ import {
import { t } from '@vegaprotocol/i18n';
import * as Schema from '@vegaprotocol/types';
import { AgGridColumn } from 'ag-grid-react';
import { Link } from '@vegaprotocol/ui-toolkit';
import {
AgGridDynamic as AgGrid,
positiveClassNames,
negativeClassNames,
MarketNameCell,
} from '@vegaprotocol/datagrid';
import type {
VegaICellRendererParams,
VegaValueFormatterParams,
} from '@vegaprotocol/datagrid';
import type { VegaValueFormatterParams } from '@vegaprotocol/datagrid';
import { forwardRef } from 'react';
import BigNumber from 'bignumber.js';
import type { Trade } from './fills-data-provider';
@ -34,7 +31,7 @@ const MAKER = 'MAKER';
export type Props = (AgGridReactProps | AgReactUiProps) & {
partyId: string;
onMarketClick?: (marketId: string) => void;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
};
export const FillsTable = forwardRef<AgGridReact, Props>(
@ -48,30 +45,14 @@ export const FillsTable = forwardRef<AgGridReact, Props>(
getRowId={({ data }) => data?.id}
tooltipShowDelay={0}
tooltipHideDelay={2000}
components={{ MarketNameCell }}
{...props}
>
<AgGridColumn
headerName={t('Market')}
field="market.tradableInstrument.instrument.name"
cellRenderer={({
value,
data,
}: VegaICellRendererParams<
Trade,
'market.tradableInstrument.instrument.name'
>) =>
onMarketClick ? (
<Link
onClick={() =>
data?.market?.id && onMarketClick(data?.market?.id)
}
>
{value}
</Link>
) : (
value
)
}
cellRenderer="MarketNameCell"
cellRendererParams={{ idPath: 'market.id', onMarketClick }}
/>
<AgGridColumn
headerName={t('Size')}

View File

@ -39,12 +39,12 @@ import {
export interface InfoProps {
market: MarketInfoWithDataAndCandles;
onSelect: (id: string) => void;
onSelect?: (id: string, metaKey?: boolean) => void;
}
export interface MarketInfoContainerProps {
marketId: string;
onSelect?: (id: string) => void;
onSelect?: (id: string, metaKey?: boolean) => void;
}
export const MarketInfoContainer = ({
marketId,
@ -73,7 +73,7 @@ export const MarketInfoContainer = ({
<AsyncRenderer data={data} loading={loading} error={error} reload={reload}>
{data ? (
<TinyScroll className="h-full overflow-auto">
<Info market={data} onSelect={(id) => onSelect?.(id)} />
<Info market={data} onSelect={onSelect} />
</TinyScroll>
) : (
<Splash>
@ -169,7 +169,7 @@ export const Info = ({ market, onSelect }: InfoProps) => {
<LiquidityInfoPanel market={market}>
<Link
to={`/liquidity/${market.id}`}
onClick={() => onSelect(market.id)}
onClick={(ev) => onSelect?.(market.id, ev.metaKey)}
data-testid="view-liquidity-link"
>
<UILink>{t('View liquidity provision table')}</UILink>

View File

@ -10,6 +10,7 @@ import type {
import {
AgGridDynamic as AgGrid,
PriceFlashCell,
MarketNameCell,
} from '@vegaprotocol/datagrid';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import { AgGridColumn } from 'ag-grid-react';
@ -24,8 +25,10 @@ export const getRowId = ({ data }: { data: { id: string } }) => data.id;
export const MarketListTable = forwardRef<
AgGridReact,
TypedDataAgGrid<MarketMaybeWithData>
>((props, ref) => {
TypedDataAgGrid<MarketMaybeWithData> & {
onMarketClick: (marketId: string, metaKey?: boolean) => void;
}
>(({ onMarketClick, ...props }, ref) => {
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
return (
<AgGrid
@ -41,22 +44,14 @@ export const MarketListTable = forwardRef<
filterParams: { buttons: ['reset'] },
}}
suppressCellFocus={true}
components={{ PriceFlashCell }}
components={{ PriceFlashCell, MarketNameCell }}
{...props}
>
<AgGridColumn
headerName={t('Market')}
field="tradableInstrument.instrument.code"
cellRenderer={({
value,
data,
}: VegaICellRendererParams<
MarketMaybeWithData,
'tradableInstrument.instrument.code'
>) => {
if (!data) return null;
return <span data-testid={`market-${data.id}`}>{value}</span>;
}}
cellRenderer="MarketNameCell"
cellRendererParams={{ onMarketClick }}
/>
<AgGridColumn
headerName={t('Description')}

View File

@ -1,12 +1,14 @@
import type { MouseEvent } from 'react';
import { t } from '@vegaprotocol/i18n';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { MarketListTable } from './market-list-table';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import type { RowClickedEvent } from 'ag-grid-community';
import type { CellClickedEvent } from 'ag-grid-community';
import { marketsWithDataProvider as dataProvider } from '../../markets-provider';
import type { MarketMaybeWithData } from '../../markets-provider';
interface MarketsContainerProps {
onSelect: (marketId: string) => void;
onSelect: (marketId: string, metaKey?: boolean) => void;
}
export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => {
@ -21,16 +23,23 @@ export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => {
rowData={error ? [] : data}
suppressLoadingOverlay
suppressNoRowsOverlay
onRowClicked={(rowEvent: RowClickedEvent) => {
const { data, event } = rowEvent;
// filters out clicks on the symbol column because it should display asset details
onCellClicked={(cellEvent: CellClickedEvent) => {
const { data, column, event } = cellEvent;
const colId = column.getColId();
if (
(event?.target as HTMLElement).tagName.toUpperCase() === 'BUTTON'
[
'tradableInstrument.instrument.code',
'tradableInstrument.instrument.product.settlementAsset',
].includes(colId)
) {
return;
}
onSelect((data as MarketMaybeWithData).id);
onSelect(
(data as MarketMaybeWithData).id,
(event as unknown as MouseEvent)?.metaKey
);
}}
onMarketClick={onSelect}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer

View File

@ -9,7 +9,7 @@ export const OrderListContainer = ({
enforceBottomPlaceholder,
}: {
marketId?: string;
onMarketClick?: (marketId: string) => void;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
enforceBottomPlaceholder?: boolean;
}) => {
const { pubKey, isReadOnly } = useVegaWallet();

View File

@ -23,7 +23,7 @@ import type { Order, OrderEdge } from '../order-data-provider';
export interface OrderListManagerProps {
partyId: string;
marketId?: string;
onMarketClick?: (marketId: string) => void;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
isReadOnly: boolean;
enforceBottomPlaceholder?: boolean;
}

View File

@ -13,6 +13,7 @@ import {
limitOrder,
marketOrder,
} from '../mocks/generate-orders';
import { MemoryRouter } from 'react-router-dom';
// Mock theme switcher to get around inconsistent mocking of zustand
// stores
@ -36,9 +37,11 @@ const generateJsx = (
) => {
return (
<MockedProvider>
<MemoryRouter>
<VegaWalletContext.Provider value={context as VegaWalletContextShape}>
<OrderListTable {...defaultProps} {...props} />
</VegaWalletContext.Provider>
</MemoryRouter>
</MockedProvider>
);
};

View File

@ -5,7 +5,7 @@ import {
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import * as Schema from '@vegaprotocol/types';
import { ButtonLink, Link } from '@vegaprotocol/ui-toolkit';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import { AgGridColumn } from 'ag-grid-react';
import BigNumber from 'bignumber.js';
import { memo, forwardRef } from 'react';
@ -15,6 +15,7 @@ import {
DateRangeFilter,
negativeClassNames,
positiveClassNames,
MarketNameCell,
} from '@vegaprotocol/datagrid';
import type {
TypedDataAgGrid,
@ -29,7 +30,7 @@ type OrderListProps = TypedDataAgGrid<Order> & { marketId?: string };
export type OrderListTableProps = OrderListProps & {
cancel: (order: Order) => void;
setEditOrder: (order: Order) => void;
onMarketClick?: (marketId: string) => void;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
isReadOnly: boolean;
};
@ -50,30 +51,14 @@ export const OrderListTable = memo(
height: '100%',
}}
getRowId={({ data }) => data.id}
components={{ MarketNameCell }}
{...props}
>
<AgGridColumn
headerName={t('Market')}
field="market.tradableInstrument.instrument.code"
cellRenderer={({
value,
data,
}: VegaICellRendererParams<
Order,
'market.tradableInstrument.instrument.code'
>) =>
onMarketClick ? (
<Link
onClick={() =>
data?.market?.id && onMarketClick(data?.market?.id)
}
>
{value}
</Link>
) : (
value
)
}
cellRenderer="MarketNameCell"
cellRendererParams={{ idPath: 'market.id', onMarketClick }}
minWidth={150}
/>
<AgGridColumn

View File

@ -6,6 +6,7 @@ import type { Position } from './positions-data-providers';
import * as Schema from '@vegaprotocol/types';
import { PositionStatus, PositionStatusMapping } from '@vegaprotocol/types';
import type { ICellRendererParams } from 'ag-grid-community';
import { MemoryRouter } from 'react-router-dom';
const singleRow: Position = {
marketName: 'ETH/BTC (31 july 2022)',
@ -37,7 +38,9 @@ const singleRowData = [singleRow];
it('should render successfully', async () => {
await act(async () => {
const { baseElement } = render(
<MemoryRouter>
<PositionsTable rowData={[]} isReadOnly={false} />
</MemoryRouter>
);
expect(baseElement).toBeTruthy();
});
@ -45,7 +48,11 @@ it('should render successfully', async () => {
it('render correct columns', async () => {
await act(async () => {
render(<PositionsTable rowData={singleRowData} isReadOnly={true} />);
render(
<MemoryRouter>
<PositionsTable rowData={singleRowData} isReadOnly={true} />
</MemoryRouter>
);
});
const headers = screen.getAllByRole('columnheader');
@ -69,7 +76,11 @@ it('render correct columns', async () => {
it('renders market name', async () => {
await act(async () => {
render(<PositionsTable rowData={singleRowData} isReadOnly={false} />);
render(
<MemoryRouter>
<PositionsTable rowData={singleRowData} isReadOnly={false} />
</MemoryRouter>
);
});
expect(screen.getByText('ETH/BTC (31 july 2022)')).toBeTruthy();
});
@ -80,7 +91,11 @@ it('Does not fail if the market name does not match the split pattern', async ()
Object.assign({}, singleRow, { marketName: breakingMarketName }),
];
await act(async () => {
render(<PositionsTable rowData={row} isReadOnly={false} />);
render(
<MemoryRouter>
<PositionsTable rowData={row} isReadOnly={false} />
</MemoryRouter>
);
});
expect(screen.getByText(breakingMarketName)).toBeTruthy();
@ -90,7 +105,9 @@ it('add color and sign to amount, displays positive notional value', async () =>
let result: RenderResult;
await act(async () => {
result = render(
<MemoryRouter>
<PositionsTable rowData={singleRowData} isReadOnly={false} />
</MemoryRouter>
);
});
let cells = screen.getAllByRole('gridcell');
@ -101,10 +118,12 @@ it('add color and sign to amount, displays positive notional value', async () =>
expect(cells[1].textContent).toEqual('1,230.0');
await act(async () => {
result.rerender(
<MemoryRouter>
<PositionsTable
rowData={[{ ...singleRow, openVolume: '-100' }]}
isReadOnly={false}
/>
</MemoryRouter>
);
});
cells = screen.getAllByRole('gridcell');
@ -118,7 +137,9 @@ it('displays mark price', async () => {
let result: RenderResult;
await act(async () => {
result = render(
<MemoryRouter>
<PositionsTable rowData={singleRowData} isReadOnly={false} />
</MemoryRouter>
);
});
@ -127,6 +148,7 @@ it('displays mark price', async () => {
await act(async () => {
result.rerender(
<MemoryRouter>
<PositionsTable
rowData={[
{
@ -137,6 +159,7 @@ it('displays mark price', async () => {
]}
isReadOnly={false}
/>
</MemoryRouter>
);
});
@ -146,7 +169,11 @@ it('displays mark price', async () => {
it('displays leverage', async () => {
await act(async () => {
render(<PositionsTable rowData={singleRowData} isReadOnly={false} />);
render(
<MemoryRouter>
<PositionsTable rowData={singleRowData} isReadOnly={false} />
</MemoryRouter>
);
});
const cells = screen.getAllByRole('gridcell');
expect(cells[6].textContent).toEqual('1.1');
@ -154,7 +181,11 @@ it('displays leverage', async () => {
it('displays allocated margin', async () => {
await act(async () => {
render(<PositionsTable rowData={singleRowData} isReadOnly={false} />);
render(
<MemoryRouter>
<PositionsTable rowData={singleRowData} isReadOnly={false} />
</MemoryRouter>
);
});
const cells = screen.getAllByRole('gridcell');
const cell = cells[7];
@ -163,7 +194,11 @@ it('displays allocated margin', async () => {
it('displays realised and unrealised PNL', async () => {
await act(async () => {
render(<PositionsTable rowData={singleRowData} isReadOnly={false} />);
render(
<MemoryRouter>
<PositionsTable rowData={singleRowData} isReadOnly={false} />
</MemoryRouter>
);
});
const cells = screen.getAllByRole('gridcell');
expect(cells[9].textContent).toEqual('4.56');
@ -172,6 +207,7 @@ it('displays realised and unrealised PNL', async () => {
it('displays close button', async () => {
await act(async () => {
render(
<MemoryRouter>
<PositionsTable
rowData={singleRowData}
onClose={() => {
@ -179,6 +215,7 @@ it('displays close button', async () => {
}}
isReadOnly={false}
/>
</MemoryRouter>
);
});
const cells = screen.getAllByRole('gridcell');
@ -188,6 +225,7 @@ it('displays close button', async () => {
it('do not display close button if openVolume is zero', async () => {
await act(async () => {
render(
<MemoryRouter>
<PositionsTable
rowData={[{ ...singleRow, openVolume: '0' }]}
onClose={() => {
@ -195,6 +233,7 @@ it('do not display close button if openVolume is zero', async () => {
}}
isReadOnly={false}
/>
</MemoryRouter>
);
});
const cells = screen.getAllByRole('gridcell');

View File

@ -14,12 +14,12 @@ import {
PriceFlashCell,
signedNumberCssClass,
signedNumberCssClassRules,
MarketNameCell,
} from '@vegaprotocol/datagrid';
import {
ButtonLink,
Tooltip,
TooltipCellComponent,
Link,
ExternalLink,
Icon,
ProgressBarCell,
@ -43,7 +43,7 @@ import { useEnvironment } from '@vegaprotocol/environment';
interface Props extends TypedDataAgGrid<Position> {
onClose?: (data: Position) => void;
onMarketClick?: (id: string) => void;
onMarketClick?: (id: string, metaKey?: boolean) => void;
style?: CSSProperties;
isReadOnly: boolean;
}
@ -96,26 +96,19 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
filterParams: { buttons: ['reset'] },
tooltipComponent: TooltipCellComponent,
}}
components={{ AmountCell, PriceFlashCell, ProgressBarCell }}
components={{
AmountCell,
PriceFlashCell,
ProgressBarCell,
MarketNameCell,
}}
{...props}
>
<AgGridColumn
headerName={t('Market')}
field="marketName"
cellRenderer={({
value,
data,
}: VegaICellRendererParams<Position, 'marketName'>) =>
onMarketClick ? (
<Link
onClick={() => data?.marketId && onMarketClick(data?.marketId)}
>
{value}
</Link>
) : (
value
)
}
cellRenderer="MarketNameCell"
cellRendererParams={{ idPath: 'marketId', onMarketClick }}
minWidth={190}
/>
<AgGridColumn