feat(trading): market view persistent pane sizes (#3537)
This commit is contained in:
parent
4a4cdaa2b8
commit
7a99ded8e9
@ -27,7 +27,10 @@ import { NO_MARKET } from './constants';
|
|||||||
import { LiquidityContainer } from '../liquidity/liquidity';
|
import { LiquidityContainer } from '../liquidity/liquidity';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import type { PinnedAsset } from '@vegaprotocol/accounts';
|
import type { PinnedAsset } from '@vegaprotocol/accounts';
|
||||||
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
import {
|
||||||
|
usePaneLayout,
|
||||||
|
useScreenDimensions,
|
||||||
|
} from '@vegaprotocol/react-helpers';
|
||||||
import {
|
import {
|
||||||
useMarketClickHandler,
|
useMarketClickHandler,
|
||||||
useMarketLiquidityClickHandler,
|
useMarketLiquidityClickHandler,
|
||||||
@ -83,15 +86,20 @@ interface BottomPanelProps {
|
|||||||
|
|
||||||
const MarketBottomPanel = memo(
|
const MarketBottomPanel = memo(
|
||||||
({ marketId, pinnedAsset }: BottomPanelProps) => {
|
({ marketId, pinnedAsset }: BottomPanelProps) => {
|
||||||
|
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'bottom' });
|
||||||
const { screenSize } = useScreenDimensions();
|
const { screenSize } = useScreenDimensions();
|
||||||
const onMarketClick = useMarketClickHandler(true);
|
const onMarketClick = useMarketClickHandler(true);
|
||||||
const onOrderTypeClick = useMarketLiquidityClickHandler(true);
|
const onOrderTypeClick = useMarketLiquidityClickHandler(true);
|
||||||
|
|
||||||
return 'xxxl' === screenSize ? (
|
return 'xxxl' === screenSize ? (
|
||||||
<ResizableGrid proportionalLayout minSize={200}>
|
<ResizableGrid
|
||||||
|
proportionalLayout
|
||||||
|
minSize={200}
|
||||||
|
onChange={handleOnLayoutChange}
|
||||||
|
>
|
||||||
<ResizableGridPanel
|
<ResizableGridPanel
|
||||||
priority={LayoutPriority.Low}
|
priority={LayoutPriority.Low}
|
||||||
preferredSize="50%"
|
preferredSize={sizes[0] || '50%'}
|
||||||
minSize={50}
|
minSize={50}
|
||||||
>
|
>
|
||||||
<TradeGridChild>
|
<TradeGridChild>
|
||||||
@ -119,7 +127,7 @@ const MarketBottomPanel = memo(
|
|||||||
</ResizableGridPanel>
|
</ResizableGridPanel>
|
||||||
<ResizableGridPanel
|
<ResizableGridPanel
|
||||||
priority={LayoutPriority.Low}
|
priority={LayoutPriority.Low}
|
||||||
preferredSize="50%"
|
preferredSize={sizes[1] || '50%'}
|
||||||
minSize={50}
|
minSize={50}
|
||||||
>
|
>
|
||||||
<TradeGridChild>
|
<TradeGridChild>
|
||||||
@ -186,22 +194,29 @@ MarketBottomPanel.displayName = 'MarketBottomPanel';
|
|||||||
const MainGrid = memo(
|
const MainGrid = memo(
|
||||||
({
|
({
|
||||||
marketId,
|
marketId,
|
||||||
onSelect,
|
|
||||||
pinnedAsset,
|
pinnedAsset,
|
||||||
}: {
|
}: {
|
||||||
marketId: string;
|
marketId: string;
|
||||||
onSelect: (marketId: string, metaKey?: boolean) => void;
|
|
||||||
pinnedAsset?: PinnedAsset;
|
pinnedAsset?: PinnedAsset;
|
||||||
}) => {
|
}) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'top' });
|
||||||
|
const [sizesMiddle, handleOnMiddleLayoutChange] = usePaneLayout({
|
||||||
|
id: 'middle',
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ResizableGrid vertical>
|
<ResizableGrid vertical onChange={handleOnLayoutChange}>
|
||||||
<ResizableGridPanel minSize={75} priority={LayoutPriority.High}>
|
<ResizableGridPanel minSize={75} priority={LayoutPriority.High}>
|
||||||
<ResizableGrid proportionalLayout={false} minSize={200}>
|
<ResizableGrid
|
||||||
|
proportionalLayout={false}
|
||||||
|
minSize={200}
|
||||||
|
onChange={handleOnMiddleLayoutChange}
|
||||||
|
>
|
||||||
<ResizableGridPanel
|
<ResizableGridPanel
|
||||||
priority={LayoutPriority.High}
|
priority={LayoutPriority.High}
|
||||||
minSize={200}
|
minSize={200}
|
||||||
preferredSize="50%"
|
preferredSize={sizesMiddle[0] || '50%'}
|
||||||
>
|
>
|
||||||
<TradeGridChild>
|
<TradeGridChild>
|
||||||
<Tabs storageKey="console-trade-grid-main-left">
|
<Tabs storageKey="console-trade-grid-main-left">
|
||||||
@ -219,7 +234,7 @@ const MainGrid = memo(
|
|||||||
</ResizableGridPanel>
|
</ResizableGridPanel>
|
||||||
<ResizableGridPanel
|
<ResizableGridPanel
|
||||||
priority={LayoutPriority.Low}
|
priority={LayoutPriority.Low}
|
||||||
preferredSize={330}
|
preferredSize={sizesMiddle[1] || 330}
|
||||||
minSize={300}
|
minSize={300}
|
||||||
>
|
>
|
||||||
<TradeGridChild>
|
<TradeGridChild>
|
||||||
@ -238,7 +253,7 @@ const MainGrid = memo(
|
|||||||
</ResizableGridPanel>
|
</ResizableGridPanel>
|
||||||
<ResizableGridPanel
|
<ResizableGridPanel
|
||||||
priority={LayoutPriority.Low}
|
priority={LayoutPriority.Low}
|
||||||
preferredSize={430}
|
preferredSize={sizesMiddle[2] || 430}
|
||||||
minSize={200}
|
minSize={200}
|
||||||
>
|
>
|
||||||
<TradeGridChild>
|
<TradeGridChild>
|
||||||
@ -256,7 +271,7 @@ const MainGrid = memo(
|
|||||||
</ResizableGridPanel>
|
</ResizableGridPanel>
|
||||||
<ResizableGridPanel
|
<ResizableGridPanel
|
||||||
priority={LayoutPriority.Low}
|
priority={LayoutPriority.Low}
|
||||||
preferredSize="25%"
|
preferredSize={sizes[1] || '25%'}
|
||||||
minSize={50}
|
minSize={50}
|
||||||
>
|
>
|
||||||
<MarketBottomPanel marketId={marketId} pinnedAsset={pinnedAsset} />
|
<MarketBottomPanel marketId={marketId} pinnedAsset={pinnedAsset} />
|
||||||
@ -278,11 +293,7 @@ export const TradeGrid = ({
|
|||||||
<TradeMarketHeader market={market} onSelect={onSelect} />
|
<TradeMarketHeader market={market} onSelect={onSelect} />
|
||||||
<OracleBanner marketId={market?.id || ''} />
|
<OracleBanner marketId={market?.id || ''} />
|
||||||
</div>
|
</div>
|
||||||
<MainGrid
|
<MainGrid marketId={market?.id || ''} pinnedAsset={pinnedAsset} />
|
||||||
marketId={market?.id || ''}
|
|
||||||
onSelect={onSelect}
|
|
||||||
pinnedAsset={pinnedAsset}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -7,6 +7,7 @@ import { WithdrawalsContainer } from './withdrawals-container';
|
|||||||
import { FillsContainer } from '@vegaprotocol/fills';
|
import { FillsContainer } from '@vegaprotocol/fills';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
import { usePaneLayout } from '@vegaprotocol/react-helpers';
|
||||||
import { VegaWalletContainer } from '../../components/vega-wallet-container';
|
import { VegaWalletContainer } from '../../components/vega-wallet-container';
|
||||||
import { DepositsContainer } from './deposits-container';
|
import { DepositsContainer } from './deposits-container';
|
||||||
import { LayoutPriority } from 'allotment';
|
import { LayoutPriority } from 'allotment';
|
||||||
@ -34,11 +35,11 @@ export const Portfolio = () => {
|
|||||||
|
|
||||||
const onMarketClick = useMarketClickHandler(true);
|
const onMarketClick = useMarketClickHandler(true);
|
||||||
const onOrderTypeClick = useMarketLiquidityClickHandler(true);
|
const onOrderTypeClick = useMarketLiquidityClickHandler(true);
|
||||||
|
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'portfolio' });
|
||||||
const wrapperClasses = 'h-full max-h-full flex flex-col';
|
const wrapperClasses = 'h-full max-h-full flex flex-col';
|
||||||
return (
|
return (
|
||||||
<div className={wrapperClasses}>
|
<div className={wrapperClasses}>
|
||||||
<ResizableGrid vertical>
|
<ResizableGrid vertical onChange={handleOnLayoutChange}>
|
||||||
<ResizableGridPanel minSize={75}>
|
<ResizableGridPanel minSize={75}>
|
||||||
<PortfolioGridChild>
|
<PortfolioGridChild>
|
||||||
<Tabs storageKey="console-portfolio-top">
|
<Tabs storageKey="console-portfolio-top">
|
||||||
@ -78,7 +79,7 @@ export const Portfolio = () => {
|
|||||||
</ResizableGridPanel>
|
</ResizableGridPanel>
|
||||||
<ResizableGridPanel
|
<ResizableGridPanel
|
||||||
priority={LayoutPriority.Low}
|
priority={LayoutPriority.Low}
|
||||||
preferredSize={300}
|
preferredSize={sizes[1] || 300}
|
||||||
minSize={50}
|
minSize={50}
|
||||||
>
|
>
|
||||||
<PortfolioGridChild>
|
<PortfolioGridChild>
|
||||||
|
@ -17,3 +17,4 @@ export * from './use-yesterday';
|
|||||||
export * from './use-previous';
|
export * from './use-previous';
|
||||||
export * from './use-logger';
|
export * from './use-logger';
|
||||||
export * from './use-bottom-placeholder';
|
export * from './use-bottom-placeholder';
|
||||||
|
export * from './use-pane-layout';
|
||||||
|
19
libs/react-helpers/src/hooks/use-pane-layout.spec.ts
Normal file
19
libs/react-helpers/src/hooks/use-pane-layout.spec.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { renderHook, act, waitFor } from '@testing-library/react';
|
||||||
|
import { usePaneLayout } from './use-pane-layout';
|
||||||
|
|
||||||
|
describe('usePaneLayout', () => {
|
||||||
|
it('should return proper values', () => {
|
||||||
|
const ret = renderHook(() => usePaneLayout({ id: 'testid' }));
|
||||||
|
expect(ret.result.current[0]).toStrictEqual([]);
|
||||||
|
expect(ret.result.current[1]).toStrictEqual(expect.any(Function));
|
||||||
|
});
|
||||||
|
it('setter should change value', async () => {
|
||||||
|
const ret = renderHook(() => usePaneLayout({ id: 'testid' }));
|
||||||
|
await act(() => {
|
||||||
|
ret.result.current[1]([100, 50, 50]);
|
||||||
|
});
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(ret.result.current[0]).toStrictEqual(['50%', '25%', '25%']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
47
libs/react-helpers/src/hooks/use-pane-layout.ts
Normal file
47
libs/react-helpers/src/hooks/use-pane-layout.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
import debounce from 'lodash/debounce';
|
||||||
|
import { create } from 'zustand';
|
||||||
|
import { persist } from 'zustand/middleware';
|
||||||
|
import { immer } from 'zustand/middleware/immer';
|
||||||
|
|
||||||
|
const STORAGE_KEY = 'vega_pane_store';
|
||||||
|
const PANELS_SET_DEBOUNCE_TIME = 300;
|
||||||
|
|
||||||
|
export const usePaneLayoutStore = create<{
|
||||||
|
sizes: Record<string, string[]>;
|
||||||
|
valueSetter: (id: string, value: string[]) => void;
|
||||||
|
}>()(
|
||||||
|
persist(
|
||||||
|
immer((set) => ({
|
||||||
|
sizes: {},
|
||||||
|
valueSetter: (id, value) =>
|
||||||
|
set((state) => {
|
||||||
|
state.sizes[id] = value;
|
||||||
|
return state;
|
||||||
|
}),
|
||||||
|
})),
|
||||||
|
{ name: STORAGE_KEY }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
interface UsePaneLayoutProps {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
export const usePaneLayout = ({
|
||||||
|
id,
|
||||||
|
}: UsePaneLayoutProps): [string[], (sizes: number[]) => void] => {
|
||||||
|
const sizes = usePaneLayoutStore((store) => store.sizes[id]) || [];
|
||||||
|
const valueSetter = usePaneLayoutStore((store) => store.valueSetter);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
const handleOnChange = useCallback(
|
||||||
|
debounce((args) => {
|
||||||
|
if (args.length) {
|
||||||
|
const all = args.reduce((agg: number, item: number) => agg + item, 0);
|
||||||
|
const sizesArr = args.map((arg: number) => `${(arg / all) * 100}%`);
|
||||||
|
valueSetter(id, sizesArr);
|
||||||
|
}
|
||||||
|
}, PANELS_SET_DEBOUNCE_TIME),
|
||||||
|
[valueSetter, id]
|
||||||
|
);
|
||||||
|
return [sizes, handleOnChange];
|
||||||
|
};
|
@ -120,7 +120,11 @@ describe('VegaConnectDialog', () => {
|
|||||||
.mockImplementation(() =>
|
.mockImplementation(() =>
|
||||||
Promise.resolve({ success: true, error: null })
|
Promise.resolve({ success: true, error: null })
|
||||||
);
|
);
|
||||||
|
jest
|
||||||
|
.spyOn(connectors.rest, 'connect')
|
||||||
|
.mockImplementation(() =>
|
||||||
|
Promise.resolve([{ publicKey: 'pubkey', name: 'test key 1' }])
|
||||||
|
);
|
||||||
render(generateJSX());
|
render(generateJSX());
|
||||||
// Switches to rest form
|
// Switches to rest form
|
||||||
fireEvent.click(await screen.findByText('Hosted Fairground wallet'));
|
fireEvent.click(await screen.findByText('Hosted Fairground wallet'));
|
||||||
@ -138,10 +142,11 @@ describe('VegaConnectDialog', () => {
|
|||||||
await act(async () => {
|
await act(async () => {
|
||||||
fireEvent.submit(screen.getByTestId('rest-connector-form'));
|
fireEvent.submit(screen.getByTestId('rest-connector-form'));
|
||||||
});
|
});
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(spy).toHaveBeenCalledWith(fields);
|
||||||
|
|
||||||
expect(spy).toHaveBeenCalledWith(fields);
|
expect(mockCloseVegaDialog).toHaveBeenCalled();
|
||||||
|
});
|
||||||
expect(mockCloseVegaDialog).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles failed connection', async () => {
|
it('handles failed connection', async () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user