feat(trading): persist state of chart intervals studies and overlays (#3261)
This commit is contained in:
parent
462bf6b8b5
commit
8863209342
28
apps/trading-e2e/src/integration/trading-chart.cy.ts
Normal file
28
apps/trading-e2e/src/integration/trading-chart.cy.ts
Normal file
@ -0,0 +1,28 @@
|
||||
describe('chart', { tags: '@smoke' }, () => {
|
||||
beforeEach(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
});
|
||||
it('config should persist', () => {
|
||||
cy.getByTestId('Chart').click();
|
||||
cy.get('[data-testid="tab-chart"] button').as('control-buttons');
|
||||
cy.get('@control-buttons').each(($button) => {
|
||||
cy.wrap($button).click();
|
||||
cy.get(
|
||||
'[role="menuitemradio"]:first, [role="menuitemcheckbox"]:first'
|
||||
).click();
|
||||
});
|
||||
cy.getByTestId('Depth').click();
|
||||
cy.getByTestId('Chart').click();
|
||||
cy.get('@control-buttons').each(($button) => {
|
||||
cy.wrap($button).click();
|
||||
cy.get('[role="menuitemradio"]:first, [role="menuitemcheckbox"]:first')
|
||||
.within(($lastMenuItem) => {
|
||||
expect($lastMenuItem.data('state')).to.equal('checked');
|
||||
})
|
||||
.click();
|
||||
});
|
||||
});
|
||||
});
|
@ -12,9 +12,13 @@ import {
|
||||
} from 'pennant';
|
||||
import { VegaDataSource } from './data-source';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
useThemeSwitcher,
|
||||
getValidItem,
|
||||
getValidSubset,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
@ -28,6 +32,54 @@ import {
|
||||
import type { IconName } from '@blueprintjs/icons';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { immer } from 'zustand/middleware/immer';
|
||||
|
||||
interface StoredSettings {
|
||||
interval?: Interval;
|
||||
type?: ChartType;
|
||||
overlays?: Overlay[];
|
||||
studies?: Study[];
|
||||
}
|
||||
|
||||
export const useCandlesChartSettings = create<
|
||||
StoredSettings & {
|
||||
merge: (settings: StoredSettings) => void;
|
||||
setType: (type: ChartType) => void;
|
||||
setInterval: (interval: Interval) => void;
|
||||
setOverlays: (overlays: Overlay[]) => void;
|
||||
setStudies: (studies: Study[]) => void;
|
||||
}
|
||||
>()(
|
||||
persist(
|
||||
immer((set) => ({
|
||||
merge: (settings: StoredSettings) =>
|
||||
set((state) => {
|
||||
Object.assign(state, settings);
|
||||
}),
|
||||
setType: (type: ChartType) =>
|
||||
set((state) => {
|
||||
state.type = type;
|
||||
}),
|
||||
setInterval: (interval: Interval) =>
|
||||
set((state) => {
|
||||
state.interval = interval;
|
||||
}),
|
||||
setOverlays: (overlays: Overlay[]) =>
|
||||
set((state) => {
|
||||
state.overlays = overlays;
|
||||
}),
|
||||
setStudies: (studies: Study[]) =>
|
||||
set((state) => {
|
||||
state.studies = studies;
|
||||
}),
|
||||
})),
|
||||
{
|
||||
name: 'console-candles',
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const chartTypeIcon = new Map<ChartType, IconName>([
|
||||
[ChartType.AREA, IconNames.TIMELINE_AREA_CHART],
|
||||
@ -47,10 +99,31 @@ export const CandlesChartContainer = ({
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { theme } = useThemeSwitcher();
|
||||
|
||||
const [interval, setInterval] = useState<Interval>(Interval.I15M);
|
||||
const [chartType, setChartType] = useState<ChartType>(ChartType.CANDLE);
|
||||
const [overlays, setOverlays] = useState<Overlay[]>([]);
|
||||
const [studies, setStudies] = useState<Study[]>([Study.VOLUME]);
|
||||
const settings = useCandlesChartSettings();
|
||||
|
||||
const interval: Interval = getValidItem(
|
||||
settings.interval,
|
||||
Object.values(Interval),
|
||||
Interval.I15M
|
||||
);
|
||||
|
||||
const chartType: ChartType = getValidItem(
|
||||
settings.type,
|
||||
Object.values(ChartType),
|
||||
ChartType.CANDLE
|
||||
);
|
||||
|
||||
const overlays: Overlay[] = getValidSubset(
|
||||
settings.overlays,
|
||||
Object.values(Overlay),
|
||||
[]
|
||||
);
|
||||
|
||||
const studies: Study[] = getValidSubset(
|
||||
settings.studies,
|
||||
Object.values(Study),
|
||||
[Study.VOLUME]
|
||||
);
|
||||
|
||||
const dataSource = useMemo(() => {
|
||||
return new VegaDataSource(client, marketId, pubKey);
|
||||
@ -70,7 +143,7 @@ export const CandlesChartContainer = ({
|
||||
<DropdownMenuRadioGroup
|
||||
value={interval}
|
||||
onValueChange={(value) => {
|
||||
setInterval(value as Interval);
|
||||
settings.setInterval(value as Interval);
|
||||
}}
|
||||
>
|
||||
{Object.values(Interval).map((timeInterval) => (
|
||||
@ -97,7 +170,7 @@ export const CandlesChartContainer = ({
|
||||
<DropdownMenuRadioGroup
|
||||
value={chartType}
|
||||
onValueChange={(value) => {
|
||||
setChartType(value as ChartType);
|
||||
settings.setType(value as ChartType);
|
||||
}}
|
||||
>
|
||||
{Object.values(ChartType).map((type) => (
|
||||
@ -125,7 +198,7 @@ export const CandlesChartContainer = ({
|
||||
? newOverlays.splice(index, 1)
|
||||
: newOverlays.push(overlay);
|
||||
|
||||
setOverlays(newOverlays);
|
||||
settings.setOverlays(newOverlays);
|
||||
}}
|
||||
>
|
||||
{overlayLabels[overlay]}
|
||||
@ -150,7 +223,7 @@ export const CandlesChartContainer = ({
|
||||
? newStudies.splice(index, 1)
|
||||
: newStudies.push(study);
|
||||
|
||||
setStudies(newStudies);
|
||||
settings.setStudies(newStudies);
|
||||
}}
|
||||
>
|
||||
{studyLabels[study]}
|
||||
@ -176,8 +249,10 @@ export const CandlesChartContainer = ({
|
||||
interval={interval}
|
||||
theme={theme}
|
||||
onOptionsChanged={(options) => {
|
||||
setOverlays(options.overlays ?? []);
|
||||
setStudies(options.studies ?? []);
|
||||
settings.merge({
|
||||
overlays: options.overlays,
|
||||
studies: options.studies,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
@ -2,3 +2,4 @@ export * from './__generated__/ChainId';
|
||||
export * from './ag-grid-update';
|
||||
export * from './format';
|
||||
export * from './grid';
|
||||
export * from './validation';
|
||||
|
17
libs/react-helpers/src/lib/validation.ts
Normal file
17
libs/react-helpers/src/lib/validation.ts
Normal file
@ -0,0 +1,17 @@
|
||||
export const getValidItem = <T>(
|
||||
value: T | null | undefined,
|
||||
set: T[],
|
||||
defaultValue: T
|
||||
) =>
|
||||
value !== null && value !== undefined && set.includes(value)
|
||||
? value
|
||||
: defaultValue;
|
||||
|
||||
export const getValidSubset = <T>(
|
||||
value: T[] | null | undefined,
|
||||
set: T[],
|
||||
defaultValue: T[]
|
||||
) =>
|
||||
value !== null && value !== undefined
|
||||
? value.filter((item) => set.includes(item))
|
||||
: defaultValue;
|
@ -1,5 +1,8 @@
|
||||
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
||||
import { useLocalStorageSnapshot } from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
useLocalStorageSnapshot,
|
||||
getValidItem,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import classNames from 'classnames';
|
||||
import type { ReactElement, ReactNode } from 'react';
|
||||
import { Children, isValidElement, useState } from 'react';
|
||||
@ -93,12 +96,22 @@ export const Tab = ({ children, ...props }: TabProps) => {
|
||||
|
||||
export const LocalStoragePersistTabs = ({
|
||||
storageKey,
|
||||
children,
|
||||
...props
|
||||
}: Omit<TabsProps, 'value' | 'onValueChange'> & { storageKey: string }) => {
|
||||
const [value, onValueChange] = useLocalStorageSnapshot(
|
||||
`active-tab-${storageKey}`
|
||||
);
|
||||
return (
|
||||
<Tabs {...props} value={value || undefined} onValueChange={onValueChange} />
|
||||
<Tabs
|
||||
{...props}
|
||||
children={children}
|
||||
value={getValidItem(
|
||||
value,
|
||||
Children.map(children, (child) => child.props.id),
|
||||
undefined
|
||||
)}
|
||||
onValueChange={onValueChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user