feat(trading): persist state of chart intervals studies and overlays (#3261)

This commit is contained in:
Bartłomiej Głownia 2023-03-28 21:03:53 +02:00 committed by GitHub
parent 462bf6b8b5
commit 8863209342
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 148 additions and 14 deletions

View 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();
});
});
});

View File

@ -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>

View File

@ -2,3 +2,4 @@ export * from './__generated__/ChainId';
export * from './ag-grid-update';
export * from './format';
export * from './grid';
export * from './validation';

View 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;

View File

@ -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}
/>
);
};