Add pagination support to generic-data-provider (#691)
* feat(#638): add pagination support to data-provider * feat(#638): use infinite rowModelType in market list table * chore(#638): code style fixes * feat(#638): fix data provider post update callbacks, handle market list table empty data * feat(#638): amend variable names to improve code readability
This commit is contained in:
parent
db050c6560
commit
b9aef78447
@ -91,14 +91,15 @@ export const FILTERS_QUERY = gql`
|
||||
const update = (
|
||||
data: SimpleMarkets_markets[],
|
||||
delta: SimpleMarketDataSub_marketData
|
||||
) =>
|
||||
produce(data, (draft) => {
|
||||
) => {
|
||||
return produce(data, (draft) => {
|
||||
const index = draft.findIndex((m) => m.id === delta.market.id);
|
||||
if (index !== -1) {
|
||||
draft[index].data = delta;
|
||||
}
|
||||
// @TODO - else push new market to draft
|
||||
});
|
||||
};
|
||||
|
||||
const getData = (responseData: SimpleMarkets) => responseData.markets;
|
||||
const getDelta = (
|
||||
|
@ -16,11 +16,11 @@ import { ThemeContext } from '@vegaprotocol/react-helpers';
|
||||
import type { MarketState } from '@vegaprotocol/types';
|
||||
import useMarketsFilterData from '../../hooks/use-markets-filter-data';
|
||||
import useColumnDefinitions from '../../hooks/use-column-definitions';
|
||||
import DataProvider from './data-provider';
|
||||
import dataProvider from './data-provider';
|
||||
import * as constants from './constants';
|
||||
import SimpleMarketToolbar from './simple-market-toolbar';
|
||||
import type { SimpleMarkets_markets } from './__generated__/SimpleMarkets';
|
||||
|
||||
import type { SimpleMarketDataSub_marketData } from './__generated__/SimpleMarketDataSub';
|
||||
export type SimpleMarketsType = SimpleMarkets_markets & {
|
||||
percentChange?: number | '-';
|
||||
};
|
||||
@ -44,15 +44,16 @@ const SimpleMarketList = () => {
|
||||
[]
|
||||
);
|
||||
const update = useCallback(
|
||||
(delta) => statusesRef.current[delta.market.id] === delta.market.state,
|
||||
({ delta }: { delta: SimpleMarketDataSub_marketData }) =>
|
||||
statusesRef.current[delta.market.id] === delta.market.state,
|
||||
[statusesRef]
|
||||
);
|
||||
|
||||
const { data, error, loading } = useDataProvider(
|
||||
DataProvider,
|
||||
const { data, error, loading } = useDataProvider({
|
||||
dataProvider,
|
||||
update,
|
||||
variables
|
||||
);
|
||||
variables,
|
||||
});
|
||||
const localData: Array<SimpleMarketsType> = useMarketsFilterData(
|
||||
data || [],
|
||||
params
|
||||
|
@ -55,8 +55,8 @@ export const getId = (
|
||||
const update = (
|
||||
data: Accounts_party_accounts[],
|
||||
delta: AccountSubscribe_accounts
|
||||
) =>
|
||||
produce(data, (draft) => {
|
||||
) => {
|
||||
return produce(data, (draft) => {
|
||||
const id = getId(delta);
|
||||
const index = draft.findIndex((a) => getId(a) === id);
|
||||
if (index !== -1) {
|
||||
@ -65,6 +65,8 @@ const update = (
|
||||
draft.push(delta);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const getData = (responseData: Accounts): Accounts_party_accounts[] | null =>
|
||||
responseData.party ? responseData.party.accounts : null;
|
||||
const getDelta = (
|
||||
|
@ -22,7 +22,7 @@ export const AccountsManager = ({ partyId }: AccountsManagerProps) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const variables = useMemo(() => ({ partyId }), [partyId]);
|
||||
const update = useCallback(
|
||||
(delta: AccountSubscribe_accounts) => {
|
||||
({ delta }: { delta: AccountSubscribe_accounts }) => {
|
||||
const update: Accounts_party_accounts[] = [];
|
||||
const add: Accounts_party_accounts[] = [];
|
||||
if (!gridRef.current) {
|
||||
@ -64,7 +64,7 @@ export const AccountsManager = ({ partyId }: AccountsManagerProps) => {
|
||||
const { data, error, loading } = useDataProvider<
|
||||
Accounts_party_accounts[],
|
||||
AccountSubscribe_accounts
|
||||
>(accountsDataProvider, update, variables);
|
||||
>({ dataProvider: accountsDataProvider, update, variables });
|
||||
return (
|
||||
<AsyncRenderer loading={loading} error={error} data={data}>
|
||||
<AccountsTable ref={gridRef} data={data} />
|
||||
|
@ -1,10 +1,11 @@
|
||||
import produce from 'immer';
|
||||
import { gql } from '@apollo/client';
|
||||
import { makeDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import produce from 'immer';
|
||||
import type { PageInfo, Pagination } from '@vegaprotocol/react-helpers';
|
||||
import type { FillFields } from './__generated__/FillFields';
|
||||
import type {
|
||||
Fills,
|
||||
Fills_party_tradesPaged_edges_node,
|
||||
Fills_party_tradesPaged_edges,
|
||||
} from './__generated__/Fills';
|
||||
import type { FillsSub } from './__generated__/FillsSub';
|
||||
|
||||
@ -16,41 +17,19 @@ const FILL_FRAGMENT = gql`
|
||||
size
|
||||
buyOrder
|
||||
sellOrder
|
||||
aggressor
|
||||
buyer {
|
||||
id
|
||||
}
|
||||
seller {
|
||||
id
|
||||
}
|
||||
buyerFee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
}
|
||||
sellerFee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
}
|
||||
market {
|
||||
id
|
||||
name
|
||||
decimalPlaces
|
||||
positionDecimalPlaces
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
id
|
||||
code
|
||||
product {
|
||||
... on Future {
|
||||
settlementAsset {
|
||||
id
|
||||
symbol
|
||||
decimals
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -88,30 +67,69 @@ export const FILLS_SUB = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
const update = (data: FillFields[], delta: FillFields[]) => {
|
||||
// Add or update incoming trades
|
||||
const update = (data: Fills_party_tradesPaged_edges[], delta: FillFields[]) => {
|
||||
return produce(data, (draft) => {
|
||||
delta.forEach((trade) => {
|
||||
const index = draft.findIndex((t) => t.id === trade.id);
|
||||
if (index === -1) {
|
||||
draft.unshift(trade);
|
||||
delta.forEach((node) => {
|
||||
const index = draft.findIndex((edge) => edge.node.id === node.id);
|
||||
if (index !== -1) {
|
||||
Object.assign(draft[index].node, node);
|
||||
} else {
|
||||
draft[index] = trade;
|
||||
draft.unshift({ node, cursor: '', __typename: 'TradeEdge' });
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const getData = (
|
||||
responseData: Fills
|
||||
): Fills_party_tradesPaged_edges_node[] | null =>
|
||||
responseData.party?.tradesPaged.edges.map((e) => e.node) || null;
|
||||
const getData = (responseData: Fills): Fills_party_tradesPaged_edges[] | null =>
|
||||
responseData.party?.tradesPaged.edges || null;
|
||||
|
||||
const getPageInfo = (responseData: Fills): PageInfo | null =>
|
||||
responseData.party?.tradesPaged.pageInfo || null;
|
||||
|
||||
const getTotalCount = (responseData: Fills): number | undefined =>
|
||||
responseData.party?.tradesPaged.totalCount;
|
||||
|
||||
const getDelta = (subscriptionData: FillsSub) => subscriptionData.trades || [];
|
||||
|
||||
const append = (
|
||||
data: Fills_party_tradesPaged_edges[] | null,
|
||||
pageInfo: PageInfo,
|
||||
insertionData: Fills_party_tradesPaged_edges[] | null,
|
||||
insertionPageInfo: PageInfo | null,
|
||||
pagination?: Pagination
|
||||
) => {
|
||||
if (data && insertionData && insertionPageInfo) {
|
||||
if (pagination?.after) {
|
||||
if (data[data.length - 1].cursor === pagination.after) {
|
||||
return {
|
||||
data: [...data, ...insertionData],
|
||||
pageInfo: { ...pageInfo, endCursor: insertionPageInfo.endCursor },
|
||||
};
|
||||
} else {
|
||||
const cursors = data.map((item) => item.cursor);
|
||||
const startIndex = cursors.lastIndexOf(pagination.after);
|
||||
if (startIndex !== -1) {
|
||||
return {
|
||||
data: [...data.slice(0, startIndex), ...insertionData],
|
||||
pageInfo: { ...pageInfo, endCursor: insertionPageInfo.endCursor },
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return { data, pageInfo };
|
||||
};
|
||||
|
||||
export const fillsDataProvider = makeDataProvider(
|
||||
FILLS_QUERY,
|
||||
FILLS_SUB,
|
||||
update,
|
||||
getData,
|
||||
getDelta
|
||||
getDelta,
|
||||
{
|
||||
getPageInfo,
|
||||
getTotalCount,
|
||||
append,
|
||||
first: 100,
|
||||
}
|
||||
);
|
||||
|
@ -1,13 +1,13 @@
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
import { FillsTable } from './fills-table';
|
||||
import { fillsDataProvider } from './fills-data-provider';
|
||||
import { useCallback, useRef, useMemo } from 'react';
|
||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import type { FillsVariables } from './__generated__/Fills';
|
||||
import type { FillFields } from './__generated__/FillFields';
|
||||
import { FillsTable } from './fills-table';
|
||||
import type { IGetRowsParams } from 'ag-grid-community';
|
||||
|
||||
import { fillsDataProvider as dataProvider } from './fills-data-provider';
|
||||
import type { Fills_party_tradesPaged_edges } from './__generated__/Fills';
|
||||
import type { FillsSub_trades } from './__generated__/FillsSub';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
|
||||
interface FillsManagerProps {
|
||||
partyId: string;
|
||||
@ -15,66 +15,78 @@ interface FillsManagerProps {
|
||||
|
||||
export const FillsManager = ({ partyId }: FillsManagerProps) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const variables = useMemo<FillsVariables>(
|
||||
() => ({
|
||||
partyId,
|
||||
pagination: {
|
||||
last: 300,
|
||||
},
|
||||
}),
|
||||
[partyId]
|
||||
);
|
||||
const update = useCallback((delta: FillsSub_trades[]) => {
|
||||
if (!gridRef.current) {
|
||||
const dataRef = useRef<Fills_party_tradesPaged_edges[] | null>(null);
|
||||
const totalCountRef = useRef<number | undefined>(undefined);
|
||||
|
||||
const update = useCallback(
|
||||
({ data }: { data: Fills_party_tradesPaged_edges[] }) => {
|
||||
if (!gridRef.current?.api) {
|
||||
return false;
|
||||
}
|
||||
const updateRows: FillFields[] = [];
|
||||
const add: FillFields[] = [];
|
||||
|
||||
delta.forEach((d) => {
|
||||
if (!gridRef.current?.api) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rowNode = gridRef.current.api.getRowNode(d.id);
|
||||
|
||||
if (rowNode) {
|
||||
if (!isEqual(d, rowNode.data)) {
|
||||
updateRows.push(d);
|
||||
}
|
||||
} else {
|
||||
add.push(d);
|
||||
}
|
||||
});
|
||||
|
||||
if (updateRows.length || add.length) {
|
||||
gridRef.current.api.applyTransactionAsync({
|
||||
update: updateRows,
|
||||
add,
|
||||
addIndex: 0,
|
||||
});
|
||||
}
|
||||
|
||||
dataRef.current = data;
|
||||
gridRef.current.api.refreshInfiniteCache();
|
||||
return true;
|
||||
}, []);
|
||||
|
||||
const { data, loading, error } = useDataProvider(
|
||||
fillsDataProvider,
|
||||
update,
|
||||
variables
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const fills = useMemo(() => {
|
||||
if (!data?.length) {
|
||||
return [];
|
||||
}
|
||||
const insert = useCallback(
|
||||
({
|
||||
data,
|
||||
totalCount,
|
||||
}: {
|
||||
data: Fills_party_tradesPaged_edges[];
|
||||
totalCount?: number;
|
||||
}) => {
|
||||
dataRef.current = data;
|
||||
totalCountRef.current = totalCount;
|
||||
return true;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return data;
|
||||
}, [data]);
|
||||
const variables = useMemo(() => ({ partyId }), [partyId]);
|
||||
|
||||
const { data, error, loading, load, totalCount } = useDataProvider<
|
||||
Fills_party_tradesPaged_edges[],
|
||||
FillsSub_trades[]
|
||||
>({ dataProvider, update, insert, variables });
|
||||
totalCountRef.current = totalCount;
|
||||
dataRef.current = data;
|
||||
|
||||
const getRows = async ({
|
||||
successCallback,
|
||||
failCallback,
|
||||
startRow,
|
||||
endRow,
|
||||
}: IGetRowsParams) => {
|
||||
try {
|
||||
if (dataRef.current && dataRef.current.length < endRow) {
|
||||
await load({
|
||||
first: endRow - startRow,
|
||||
after: dataRef.current[dataRef.current.length - 1].cursor,
|
||||
});
|
||||
}
|
||||
const rowsThisBlock = dataRef.current
|
||||
? dataRef.current.slice(startRow, endRow).map((edge) => edge.node)
|
||||
: [];
|
||||
let lastRow = -1;
|
||||
if (totalCountRef.current !== undefined) {
|
||||
if (!totalCountRef.current) {
|
||||
lastRow = 0;
|
||||
} else if (totalCountRef.current <= endRow) {
|
||||
lastRow = totalCountRef.current;
|
||||
}
|
||||
}
|
||||
successCallback(rowsThisBlock, lastRow);
|
||||
} catch (e) {
|
||||
failCallback();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AsyncRenderer data={fills} loading={loading} error={error}>
|
||||
<FillsTable ref={gridRef} partyId={partyId} fills={fills} />
|
||||
<AsyncRenderer loading={loading} error={error} data={data}>
|
||||
<FillsTable ref={gridRef} partyId={partyId} datasource={{ getRows }} />
|
||||
</AsyncRenderer>
|
||||
);
|
||||
};
|
||||
|
@ -1,12 +1,25 @@
|
||||
import { render, act, screen, waitFor } from '@testing-library/react';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { getDateTimeFormat } from '@vegaprotocol/react-helpers';
|
||||
import { Side } from '@vegaprotocol/types';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
|
||||
import { FillsTable } from './fills-table';
|
||||
import { generateFill } from './test-helpers';
|
||||
import { generateFill, makeGetRows } from './test-helpers';
|
||||
import type { FillFields } from './__generated__/FillFields';
|
||||
|
||||
const waitForGridToBeInTheDOM = () => {
|
||||
return waitFor(() => {
|
||||
expect(document.querySelector('.ag-root-wrapper')).toBeInTheDocument();
|
||||
});
|
||||
};
|
||||
|
||||
// since our grid starts with no data, when the overlay has gone, data has loaded
|
||||
const waitForDataToHaveLoaded = () => {
|
||||
return waitFor(() => {
|
||||
expect(document.querySelector('.ag-overlay-no-rows-center')).toBeNull();
|
||||
});
|
||||
};
|
||||
|
||||
describe('FillsTable', () => {
|
||||
let defaultFill: PartialDeep<FillFields>;
|
||||
|
||||
@ -34,9 +47,14 @@ describe('FillsTable', () => {
|
||||
});
|
||||
|
||||
it('correct columns are rendered', async () => {
|
||||
await act(async () => {
|
||||
render(<FillsTable partyId="party-id" fills={[generateFill()]} />);
|
||||
});
|
||||
render(
|
||||
<FillsTable
|
||||
partyId="party-id"
|
||||
datasource={{ getRows: makeGetRows([generateFill()]) }}
|
||||
/>
|
||||
);
|
||||
await waitForGridToBeInTheDOM();
|
||||
await waitForDataToHaveLoaded();
|
||||
|
||||
const headers = screen.getAllByRole('columnheader');
|
||||
expect(headers).toHaveLength(7);
|
||||
@ -66,14 +84,14 @@ describe('FillsTable', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { container } = render(
|
||||
<FillsTable partyId={partyId} fills={[buyerFill]} />
|
||||
render(
|
||||
<FillsTable
|
||||
partyId={partyId}
|
||||
datasource={{ getRows: makeGetRows([buyerFill]) }}
|
||||
/>
|
||||
);
|
||||
|
||||
// Check grid has been rendered
|
||||
await waitFor(() => {
|
||||
expect(container.querySelector('.ag-root-wrapper')).toBeInTheDocument();
|
||||
});
|
||||
await waitForGridToBeInTheDOM();
|
||||
await waitForDataToHaveLoaded();
|
||||
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
const expectedValues = [
|
||||
@ -108,14 +126,14 @@ describe('FillsTable', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { container } = render(
|
||||
<FillsTable partyId={partyId} fills={[buyerFill]} />
|
||||
render(
|
||||
<FillsTable
|
||||
partyId={partyId}
|
||||
datasource={{ getRows: makeGetRows([buyerFill]) }}
|
||||
/>
|
||||
);
|
||||
|
||||
// Check grid has been rendered
|
||||
await waitFor(() => {
|
||||
expect(container.querySelector('.ag-root-wrapper')).toBeInTheDocument();
|
||||
});
|
||||
await waitForGridToBeInTheDOM();
|
||||
await waitForDataToHaveLoaded();
|
||||
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
const expectedValues = [
|
||||
@ -144,14 +162,14 @@ describe('FillsTable', () => {
|
||||
aggressor: Side.Sell,
|
||||
});
|
||||
|
||||
const { container, rerender } = render(
|
||||
<FillsTable partyId={partyId} fills={[takerFill]} />
|
||||
const { rerender } = render(
|
||||
<FillsTable
|
||||
partyId={partyId}
|
||||
datasource={{ getRows: makeGetRows([takerFill]) }}
|
||||
/>
|
||||
);
|
||||
|
||||
// Check grid has been rendered
|
||||
await waitFor(() => {
|
||||
expect(container.querySelector('.ag-root-wrapper')).toBeInTheDocument();
|
||||
});
|
||||
await waitForGridToBeInTheDOM();
|
||||
await waitForDataToHaveLoaded();
|
||||
|
||||
expect(
|
||||
screen
|
||||
@ -166,7 +184,14 @@ describe('FillsTable', () => {
|
||||
aggressor: Side.Buy,
|
||||
});
|
||||
|
||||
rerender(<FillsTable partyId={partyId} fills={[makerFill]} />);
|
||||
rerender(
|
||||
<FillsTable
|
||||
partyId={partyId}
|
||||
datasource={{ getRows: makeGetRows([makerFill]) }}
|
||||
/>
|
||||
);
|
||||
await waitForGridToBeInTheDOM();
|
||||
await waitForDataToHaveLoaded();
|
||||
|
||||
expect(
|
||||
screen
|
||||
|
@ -1,7 +1,7 @@
|
||||
import type { Story, Meta } from '@storybook/react';
|
||||
import type { FillsTableProps } from './fills-table';
|
||||
import { FillsTable } from './fills-table';
|
||||
import { generateFills } from './test-helpers';
|
||||
import { generateFills, makeGetRows } from './test-helpers';
|
||||
|
||||
export default {
|
||||
component: FillsTable,
|
||||
@ -14,5 +14,9 @@ export const Default = Template.bind({});
|
||||
const fills = generateFills();
|
||||
Default.args = {
|
||||
partyId: 'party-id',
|
||||
fills: fills.party?.tradesPaged.edges.map((e) => e.node),
|
||||
datasource: {
|
||||
getRows: makeGetRows(
|
||||
fills.party?.tradesPaged.edges.map((e) => e.node) || []
|
||||
),
|
||||
},
|
||||
};
|
||||
|
@ -10,25 +10,26 @@ import { AgGridColumn } from 'ag-grid-react';
|
||||
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
|
||||
import { forwardRef } from 'react';
|
||||
import type { FillFields } from './__generated__/FillFields';
|
||||
import type { ValueFormatterParams } from 'ag-grid-community';
|
||||
import type { ValueFormatterParams, IDatasource } from 'ag-grid-community';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { Side } from '@vegaprotocol/types';
|
||||
|
||||
export interface FillsTableProps {
|
||||
partyId: string;
|
||||
fills: FillFields[];
|
||||
datasource: IDatasource;
|
||||
}
|
||||
|
||||
export const FillsTable = forwardRef<AgGridReact, FillsTableProps>(
|
||||
({ partyId, fills }, ref) => {
|
||||
({ partyId, datasource }, ref) => {
|
||||
return (
|
||||
<AgGrid
|
||||
ref={ref}
|
||||
rowData={fills}
|
||||
overlayNoRowsTemplate={t('No fills')}
|
||||
defaultColDef={{ flex: 1, resizable: true }}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
getRowId={({ data }) => data.id}
|
||||
getRowId={({ data }) => data?.id}
|
||||
rowModelType="infinite"
|
||||
datasource={datasource}
|
||||
>
|
||||
<AgGridColumn headerName={t('Market')} field="market.name" />
|
||||
<AgGridColumn
|
||||
@ -36,9 +37,9 @@ export const FillsTable = forwardRef<AgGridReact, FillsTableProps>(
|
||||
field="size"
|
||||
cellClass={({ data }: { data: FillFields }) => {
|
||||
let className = '';
|
||||
if (data.buyer.id === partyId) {
|
||||
if (data?.buyer.id === partyId) {
|
||||
className = 'text-vega-green';
|
||||
} else if (data.seller.id) {
|
||||
} else if (data?.seller.id) {
|
||||
className = 'text-vega-red';
|
||||
}
|
||||
return className;
|
||||
@ -69,6 +70,9 @@ export const FillsTable = forwardRef<AgGridReact, FillsTableProps>(
|
||||
headerName={t('Date')}
|
||||
field="createdAt"
|
||||
valueFormatter={({ value }: ValueFormatterParams) => {
|
||||
if (value === undefined) {
|
||||
return value;
|
||||
}
|
||||
return getDateTimeFormat().format(new Date(value));
|
||||
}}
|
||||
/>
|
||||
@ -78,56 +82,68 @@ export const FillsTable = forwardRef<AgGridReact, FillsTableProps>(
|
||||
);
|
||||
|
||||
const formatPrice = ({ value, data }: ValueFormatterParams) => {
|
||||
if (value === undefined) {
|
||||
return value;
|
||||
}
|
||||
const asset =
|
||||
data.market.tradableInstrument.instrument.product.settlementAsset.symbol;
|
||||
data?.market.tradableInstrument.instrument.product.settlementAsset.symbol;
|
||||
const valueFormatted = addDecimalsFormatNumber(
|
||||
value,
|
||||
data.market.decimalPlaces
|
||||
data?.market.decimalPlaces
|
||||
);
|
||||
return `${valueFormatted} ${asset}`;
|
||||
};
|
||||
|
||||
const formatSize = (partyId: string) => {
|
||||
return ({ value, data }: ValueFormatterParams) => {
|
||||
if (value === undefined) {
|
||||
return value;
|
||||
}
|
||||
let prefix;
|
||||
if (data.buyer.id === partyId) {
|
||||
if (data?.buyer.id === partyId) {
|
||||
prefix = '+';
|
||||
} else if (data.seller.id) {
|
||||
} else if (data?.seller.id) {
|
||||
prefix = '-';
|
||||
}
|
||||
|
||||
const size = addDecimalsFormatNumber(
|
||||
value,
|
||||
data.market.positionDecimalPlaces
|
||||
data?.market.positionDecimalPlaces
|
||||
);
|
||||
return `${prefix}${size}`;
|
||||
};
|
||||
};
|
||||
|
||||
const formatTotal = ({ value, data }: ValueFormatterParams) => {
|
||||
if (value === undefined) {
|
||||
return value;
|
||||
}
|
||||
const asset =
|
||||
data.market.tradableInstrument.instrument.product.settlementAsset.symbol;
|
||||
data?.market.tradableInstrument.instrument.product.settlementAsset.symbol;
|
||||
const size = new BigNumber(
|
||||
addDecimal(data.size, data.market.positionDecimalPlaces)
|
||||
addDecimal(data?.size, data?.market.positionDecimalPlaces)
|
||||
);
|
||||
const price = new BigNumber(addDecimal(value, data.market.decimalPlaces));
|
||||
const price = new BigNumber(addDecimal(value, data?.market.decimalPlaces));
|
||||
|
||||
const total = size.times(price).toString();
|
||||
const valueFormatted = formatNumber(total, data.market.decimalPlaces);
|
||||
const valueFormatted = formatNumber(total, data?.market.decimalPlaces);
|
||||
return `${valueFormatted} ${asset}`;
|
||||
};
|
||||
|
||||
const formatRole = (partyId: string) => {
|
||||
return ({ value, data }: ValueFormatterParams) => {
|
||||
if (value === undefined) {
|
||||
return value;
|
||||
}
|
||||
const taker = t('Taker');
|
||||
const maker = t('Maker');
|
||||
if (data.buyer.id === partyId) {
|
||||
if (data?.buyer.id === partyId) {
|
||||
if (value === Side.Buy) {
|
||||
return taker;
|
||||
} else {
|
||||
return maker;
|
||||
}
|
||||
} else if (data.seller.id === partyId) {
|
||||
} else if (data?.seller.id === partyId) {
|
||||
if (value === Side.Sell) {
|
||||
return taker;
|
||||
} else {
|
||||
@ -141,12 +157,15 @@ const formatRole = (partyId: string) => {
|
||||
|
||||
const formatFee = (partyId: string) => {
|
||||
return ({ value, data }: ValueFormatterParams) => {
|
||||
if (value === undefined) {
|
||||
return value;
|
||||
}
|
||||
const asset = value.settlementAsset;
|
||||
let feesObj;
|
||||
if (data.buyer.id === partyId) {
|
||||
feesObj = data.buyerFee;
|
||||
} else if (data.seller.id === partyId) {
|
||||
feesObj = data.sellerFee;
|
||||
if (data?.buyer.id === partyId) {
|
||||
feesObj = data?.buyerFee;
|
||||
} else if (data?.seller.id === partyId) {
|
||||
feesObj = data?.sellerFee;
|
||||
} else {
|
||||
return '-';
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Side } from '@vegaprotocol/types';
|
||||
import merge from 'lodash/merge';
|
||||
import type { IGetRowsParams } from 'ag-grid-community';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
import type {
|
||||
Fills,
|
||||
@ -132,3 +133,9 @@ export const generateFill = (
|
||||
|
||||
return merge(defaultFill, override);
|
||||
};
|
||||
|
||||
export const makeGetRows =
|
||||
(data: Fills_party_tradesPaged_edges_node[]) =>
|
||||
({ successCallback }: IGetRowsParams) => {
|
||||
successCallback(data, data.length);
|
||||
};
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
addDecimal,
|
||||
ThemeContext,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import { marketDepthDataProvider } from './market-depth-data-provider';
|
||||
import dataProvider from './market-depth-data-provider';
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
@ -87,7 +87,7 @@ export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => {
|
||||
|
||||
// Apply updates to the table
|
||||
const update = useCallback(
|
||||
(delta: MarketDepthSubscription_marketDepthUpdate) => {
|
||||
({ delta }: { delta: MarketDepthSubscription_marketDepthUpdate }) => {
|
||||
if (!dataRef.current) {
|
||||
return false;
|
||||
}
|
||||
@ -122,11 +122,11 @@ export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => {
|
||||
[]
|
||||
);
|
||||
|
||||
const { data, error, loading } = useDataProvider(
|
||||
marketDepthDataProvider,
|
||||
const { data, error, loading } = useDataProvider({
|
||||
dataProvider,
|
||||
update,
|
||||
variables
|
||||
);
|
||||
variables,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!data) {
|
||||
|
@ -131,3 +131,5 @@ export const marketDepthDataProvider = makeDataProvider(
|
||||
getData,
|
||||
getDelta
|
||||
);
|
||||
|
||||
export default marketDepthDataProvider;
|
||||
|
@ -150,9 +150,9 @@ export const compactRows = (
|
||||
groupedByLevel[price].pop() as PartialOrderbookRowData
|
||||
);
|
||||
row.price = price;
|
||||
let subRow: PartialOrderbookRowData | undefined;
|
||||
// eslint-disable-next-line no-cond-assign
|
||||
while ((subRow = groupedByLevel[price].pop())) {
|
||||
let subRow: PartialOrderbookRowData | undefined =
|
||||
groupedByLevel[price].pop();
|
||||
while (subRow) {
|
||||
row.ask += subRow.ask;
|
||||
row.bid += subRow.bid;
|
||||
if (subRow.ask) {
|
||||
@ -161,6 +161,7 @@ export const compactRows = (
|
||||
if (subRow.bid) {
|
||||
row.bidByLevel[subRow.price] = subRow.bid;
|
||||
}
|
||||
subRow = groupedByLevel[price].pop();
|
||||
}
|
||||
orderbookData.push(row);
|
||||
});
|
||||
|
@ -2,7 +2,7 @@ import throttle from 'lodash/throttle';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import { Orderbook } from './orderbook';
|
||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import { marketDepthDataProvider } from './market-depth-data-provider';
|
||||
import dataProvider from './market-depth-data-provider';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import type { MarketDepthSubscription_marketDepthUpdate } from './__generated__/MarketDepthSubscription';
|
||||
import {
|
||||
@ -46,7 +46,7 @@ export const OrderbookManager = ({ marketId }: OrderbookManagerProps) => {
|
||||
);
|
||||
|
||||
const update = useCallback(
|
||||
(delta: MarketDepthSubscription_marketDepthUpdate) => {
|
||||
({ delta }: { delta: MarketDepthSubscription_marketDepthUpdate }) => {
|
||||
if (!dataRef.current.rows) {
|
||||
return false;
|
||||
}
|
||||
@ -76,11 +76,11 @@ export const OrderbookManager = ({ marketId }: OrderbookManagerProps) => {
|
||||
[]
|
||||
);
|
||||
|
||||
const { data, error, loading, flush } = useDataProvider(
|
||||
marketDepthDataProvider,
|
||||
const { data, error, loading, flush } = useDataProvider({
|
||||
dataProvider,
|
||||
update,
|
||||
variables
|
||||
);
|
||||
variables,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!data) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { forwardRef } from 'react';
|
||||
import type { ValueFormatterParams } from 'ag-grid-community';
|
||||
import type { IDatasource, ValueFormatterParams } from 'ag-grid-community';
|
||||
import {
|
||||
PriceFlashCell,
|
||||
addDecimalsFormatNumber,
|
||||
@ -8,30 +8,22 @@ import {
|
||||
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type {
|
||||
Markets_markets,
|
||||
Markets_markets_data_market,
|
||||
} from '../__generated__/Markets';
|
||||
import type { Markets_markets } from '../__generated__/Markets';
|
||||
|
||||
interface MarketListTableProps {
|
||||
data: Markets_markets[] | null;
|
||||
datasource: IDatasource;
|
||||
onRowClicked: (marketId: string) => void;
|
||||
}
|
||||
|
||||
export const getRowId = ({
|
||||
data,
|
||||
}: {
|
||||
data: Markets_markets | Markets_markets_data_market;
|
||||
}) => data.id;
|
||||
|
||||
export const MarketListTable = forwardRef<AgGridReact, MarketListTableProps>(
|
||||
({ data, onRowClicked }, ref) => {
|
||||
({ datasource, onRowClicked }, ref) => {
|
||||
return (
|
||||
<AgGrid
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
overlayNoRowsTemplate={t('No markets')}
|
||||
rowData={data}
|
||||
getRowId={getRowId}
|
||||
rowModelType="infinite"
|
||||
datasource={datasource}
|
||||
getRowId={({ data }) => data?.id}
|
||||
ref={ref}
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
@ -55,7 +47,9 @@ export const MarketListTable = forwardRef<AgGridReact, MarketListTableProps>(
|
||||
headerName={t('State')}
|
||||
field="data"
|
||||
valueFormatter={({ value }: ValueFormatterParams) =>
|
||||
`${value.market.state} (${value.market.tradingMode})`
|
||||
value === undefined
|
||||
? value
|
||||
: `${value.market.state} (${value.market.tradingMode})`
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
@ -64,7 +58,9 @@ export const MarketListTable = forwardRef<AgGridReact, MarketListTableProps>(
|
||||
type="rightAligned"
|
||||
cellRenderer="PriceFlashCell"
|
||||
valueFormatter={({ value, data }: ValueFormatterParams) =>
|
||||
addDecimalsFormatNumber(value, data.decimalPlaces)
|
||||
value === undefined
|
||||
? value
|
||||
: addDecimalsFormatNumber(value, data.decimalPlaces)
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
@ -72,7 +68,9 @@ export const MarketListTable = forwardRef<AgGridReact, MarketListTableProps>(
|
||||
field="data.bestOfferPrice"
|
||||
type="rightAligned"
|
||||
valueFormatter={({ value, data }: ValueFormatterParams) =>
|
||||
addDecimalsFormatNumber(value, data.decimalPlaces)
|
||||
value === undefined
|
||||
? value
|
||||
: addDecimalsFormatNumber(value, data.decimalPlaces)
|
||||
}
|
||||
cellRenderer="PriceFlashCell"
|
||||
/>
|
||||
@ -82,7 +80,9 @@ export const MarketListTable = forwardRef<AgGridReact, MarketListTableProps>(
|
||||
type="rightAligned"
|
||||
cellRenderer="PriceFlashCell"
|
||||
valueFormatter={({ value, data }: ValueFormatterParams) =>
|
||||
addDecimalsFormatNumber(value, data.decimalPlaces)
|
||||
value === undefined
|
||||
? value
|
||||
: addDecimalsFormatNumber(value, data.decimalPlaces)
|
||||
}
|
||||
/>
|
||||
<AgGridColumn headerName={t('Description')} field="name" />
|
||||
|
@ -1,61 +1,51 @@
|
||||
import { useRef, useCallback } from 'react';
|
||||
import { produce } from 'immer';
|
||||
import merge from 'lodash/merge';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import { MarketListTable, getRowId } from './market-list-table';
|
||||
import { MarketListTable } from './market-list-table';
|
||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { IGetRowsParams } from 'ag-grid-community';
|
||||
import type {
|
||||
Markets_markets,
|
||||
Markets_markets_data,
|
||||
} from '../../components/__generated__/Markets';
|
||||
import { marketsDataProvider } from './markets-data-provider';
|
||||
import { marketsDataProvider as dataProvider } from './markets-data-provider';
|
||||
|
||||
export const MarketsContainer = () => {
|
||||
const { push } = useRouter();
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const update = useCallback(
|
||||
(delta: Markets_markets_data) => {
|
||||
const update: Markets_markets[] = [];
|
||||
const add: Markets_markets[] = [];
|
||||
const dataRef = useRef<Markets_markets[] | null>(null);
|
||||
const update = useCallback(({ data }: { data: Markets_markets[] }) => {
|
||||
if (!gridRef.current?.api) {
|
||||
return false;
|
||||
}
|
||||
const rowNode = gridRef.current.api.getRowNode(
|
||||
getRowId({ data: delta.market })
|
||||
);
|
||||
if (rowNode) {
|
||||
const updatedData = produce<Markets_markets_data>(
|
||||
rowNode.data.data,
|
||||
(draft: Markets_markets_data) => merge(draft, delta)
|
||||
);
|
||||
if (updatedData !== rowNode.data.data) {
|
||||
update.push({ ...rowNode.data, data: updatedData });
|
||||
}
|
||||
}
|
||||
// @TODO - else add new market
|
||||
if (update.length || add.length) {
|
||||
gridRef.current.api.applyTransactionAsync({
|
||||
update,
|
||||
add,
|
||||
addIndex: 0,
|
||||
});
|
||||
}
|
||||
dataRef.current = data;
|
||||
gridRef.current.api.refreshInfiniteCache();
|
||||
return true;
|
||||
},
|
||||
[gridRef]
|
||||
);
|
||||
}, []);
|
||||
const { data, error, loading } = useDataProvider<
|
||||
Markets_markets[],
|
||||
Markets_markets_data
|
||||
>(marketsDataProvider, update);
|
||||
>({ dataProvider, update });
|
||||
dataRef.current = data;
|
||||
|
||||
const getRows = async ({
|
||||
successCallback,
|
||||
startRow,
|
||||
endRow,
|
||||
}: IGetRowsParams) => {
|
||||
const rowsThisBlock = dataRef.current
|
||||
? dataRef.current.slice(startRow, endRow)
|
||||
: [];
|
||||
const lastRow = dataRef.current?.length ?? -1;
|
||||
successCallback(rowsThisBlock, lastRow);
|
||||
};
|
||||
|
||||
return (
|
||||
<AsyncRenderer loading={loading} error={error} data={data}>
|
||||
<MarketListTable
|
||||
datasource={{ getRows }}
|
||||
ref={gridRef}
|
||||
data={data}
|
||||
onRowClicked={(id) => push(`/markets/${id}`)}
|
||||
/>
|
||||
</AsyncRenderer>
|
||||
|
@ -91,14 +91,16 @@ const MARKET_DATA_SUB = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
const update = (data: Markets_markets[], delta: MarketDataSub_marketData) =>
|
||||
produce(data, (draft) => {
|
||||
const update = (data: Markets_markets[], delta: MarketDataSub_marketData) => {
|
||||
return produce(data, (draft) => {
|
||||
const index = draft.findIndex((m) => m.id === delta.market.id);
|
||||
if (index !== -1) {
|
||||
draft[index].data = delta;
|
||||
}
|
||||
// @TODO - else push new market to draft
|
||||
});
|
||||
};
|
||||
|
||||
const getData = (responseData: Markets): Markets_markets[] | null =>
|
||||
responseData.markets;
|
||||
const getDelta = (subscriptionData: MarketDataSub): MarketDataSub_marketData =>
|
||||
|
@ -79,8 +79,8 @@ export const prepareIncomingOrders = (delta: OrderFields[]) => {
|
||||
return incoming;
|
||||
};
|
||||
|
||||
const update = (data: OrderFields[], delta: OrderFields[]) =>
|
||||
produce(data, (draft) => {
|
||||
const update = (data: OrderFields[], delta: OrderFields[]) => {
|
||||
return produce(data, (draft) => {
|
||||
const incoming = prepareIncomingOrders(delta);
|
||||
|
||||
// Add or update incoming orders
|
||||
@ -93,6 +93,7 @@ const update = (data: OrderFields[], delta: OrderFields[]) =>
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const getData = (responseData: Orders): Orders_party_orders[] | null =>
|
||||
responseData?.party?.orders || null;
|
||||
|
@ -3,7 +3,7 @@ import { OrderList } from '../order-list';
|
||||
import type { OrderFields } from '../__generated__/OrderFields';
|
||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
ordersDataProvider,
|
||||
ordersDataProvider as dataProvider,
|
||||
prepareIncomingOrders,
|
||||
sortOrders,
|
||||
} from '../order-data-provider';
|
||||
@ -21,7 +21,7 @@ export const OrderListManager = ({ partyId }: OrderListManagerProps) => {
|
||||
const variables = useMemo(() => ({ partyId }), [partyId]);
|
||||
|
||||
// Apply updates to the table
|
||||
const update = useCallback((delta: OrderSub_orders[]) => {
|
||||
const update = useCallback(({ delta }: { delta: OrderSub_orders[] }) => {
|
||||
if (!gridRef.current) {
|
||||
return false;
|
||||
}
|
||||
@ -57,11 +57,11 @@ export const OrderListManager = ({ partyId }: OrderListManagerProps) => {
|
||||
return true;
|
||||
}, []);
|
||||
|
||||
const { data, error, loading } = useDataProvider(
|
||||
ordersDataProvider,
|
||||
const { data, error, loading } = useDataProvider({
|
||||
dataProvider,
|
||||
update,
|
||||
variables
|
||||
);
|
||||
variables,
|
||||
});
|
||||
|
||||
const orders = useMemo(() => {
|
||||
if (!data) {
|
||||
|
@ -78,8 +78,8 @@ export const POSITIONS_SUB = gql`
|
||||
const update = (
|
||||
data: Positions_party_positions[],
|
||||
delta: PositionSubscribe_positions
|
||||
) =>
|
||||
produce(data, (draft) => {
|
||||
) => {
|
||||
return produce(data, (draft) => {
|
||||
const index = draft.findIndex((m) => m.market.id === delta.market.id);
|
||||
if (index !== -1) {
|
||||
draft[index] = delta;
|
||||
@ -87,6 +87,8 @@ const update = (
|
||||
draft.push(delta);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const getData = (responseData: Positions): Positions_party_positions[] | null =>
|
||||
responseData.party ? responseData.party.positions : null;
|
||||
const getDelta = (
|
||||
|
@ -18,7 +18,7 @@ export const PositionsManager = ({ partyId }: PositionsManagerProps) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const variables = useMemo(() => ({ partyId }), [partyId]);
|
||||
const update = useCallback(
|
||||
(delta: PositionSubscribe_positions) => {
|
||||
({ delta }: { delta: PositionSubscribe_positions }) => {
|
||||
const update: Positions_party_positions[] = [];
|
||||
const add: Positions_party_positions[] = [];
|
||||
if (!gridRef.current?.api) {
|
||||
@ -52,7 +52,7 @@ export const PositionsManager = ({ partyId }: PositionsManagerProps) => {
|
||||
const { data, error, loading } = useDataProvider<
|
||||
Positions_party_positions[],
|
||||
PositionSubscribe_positions
|
||||
>(positionsDataProvider, update, variables);
|
||||
>({ dataProvider: positionsDataProvider, update, variables });
|
||||
return (
|
||||
<AsyncRenderer loading={loading} error={error} data={data}>
|
||||
<PositionsTable ref={gridRef} data={data} />
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import type { OperationVariables } from '@apollo/client';
|
||||
import type { Subscribe } from '../lib/generic-data-provider';
|
||||
import type { Subscribe, Pagination, Load } from '../lib/generic-data-provider';
|
||||
|
||||
/**
|
||||
*
|
||||
@ -10,17 +10,33 @@ import type { Subscribe } from '../lib/generic-data-provider';
|
||||
* @param variables optional
|
||||
* @returns state: data, loading, error, methods: flush (pass updated data to update function without delta), restart: () => void}};
|
||||
*/
|
||||
export function useDataProvider<Data, Delta>(
|
||||
dataProvider: Subscribe<Data, Delta>,
|
||||
update?: (delta: Delta) => boolean,
|
||||
variables?: OperationVariables
|
||||
) {
|
||||
export function useDataProvider<Data, Delta>({
|
||||
dataProvider,
|
||||
update,
|
||||
insert,
|
||||
variables,
|
||||
}: {
|
||||
dataProvider: Subscribe<Data, Delta>;
|
||||
update?: ({ delta, data }: { delta: Delta; data: Data }) => boolean;
|
||||
insert?: ({
|
||||
insertionData,
|
||||
data,
|
||||
totalCount,
|
||||
}: {
|
||||
insertionData: Data;
|
||||
data: Data;
|
||||
totalCount?: number;
|
||||
}) => boolean;
|
||||
variables?: OperationVariables;
|
||||
}) {
|
||||
const client = useApolloClient();
|
||||
const [data, setData] = useState<Data | null>(null);
|
||||
const [totalCount, setTotalCount] = useState<number>();
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<Error | undefined>(undefined);
|
||||
const flushRef = useRef<(() => void) | undefined>(undefined);
|
||||
const reloadRef = useRef<((force?: boolean) => void) | undefined>(undefined);
|
||||
const loadRef = useRef<Load<Data> | undefined>(undefined);
|
||||
const initialized = useRef<boolean>(false);
|
||||
const flush = useCallback(() => {
|
||||
if (flushRef.current) {
|
||||
@ -32,30 +48,48 @@ export function useDataProvider<Data, Delta>(
|
||||
reloadRef.current(force);
|
||||
}
|
||||
}, []);
|
||||
const load = useCallback((pagination: Pagination) => {
|
||||
if (loadRef.current) {
|
||||
return loadRef.current(pagination);
|
||||
}
|
||||
return Promise.reject();
|
||||
}, []);
|
||||
const callback = useCallback(
|
||||
({ data, error, loading, delta }) => {
|
||||
({ data, error, loading, delta, insertionData, totalCount }) => {
|
||||
setError(error);
|
||||
setLoading(loading);
|
||||
if (!error && !loading) {
|
||||
// if update function returns true it means that component handles updates
|
||||
// if update or insert function returns true it means that component handles updates
|
||||
// component can use flush() which will call callback without delta and cause data state update
|
||||
if (!initialized.current || !delta || !update || !update(delta)) {
|
||||
if (initialized.current) {
|
||||
if (delta && update && update({ delta, data })) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
insertionData &&
|
||||
insert &&
|
||||
insert({ insertionData, data, totalCount })
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
initialized.current = true;
|
||||
setTotalCount(totalCount);
|
||||
setData(data);
|
||||
}
|
||||
}
|
||||
},
|
||||
[update]
|
||||
[update, insert]
|
||||
);
|
||||
useEffect(() => {
|
||||
const { unsubscribe, flush, reload } = dataProvider(
|
||||
const { unsubscribe, flush, reload, load } = dataProvider(
|
||||
callback,
|
||||
client,
|
||||
variables
|
||||
);
|
||||
flushRef.current = flush;
|
||||
reloadRef.current = reload;
|
||||
loadRef.current = load;
|
||||
return unsubscribe;
|
||||
}, [client, initialized, dataProvider, callback, variables]);
|
||||
return { data, loading, error, flush, reload };
|
||||
return { data, loading, error, flush, reload, load, totalCount };
|
||||
}
|
||||
|
@ -13,9 +13,30 @@ export interface UpdateCallback<Data, Delta> {
|
||||
data: Data | null;
|
||||
error?: Error;
|
||||
loading: boolean;
|
||||
pageInfo: PageInfo | null;
|
||||
delta?: Delta;
|
||||
insertionData?: Data | null;
|
||||
totalCount?: number;
|
||||
}): void;
|
||||
}
|
||||
|
||||
export interface Load<Data> {
|
||||
(pagination: Pagination): Promise<Data | null>;
|
||||
}
|
||||
|
||||
export interface Pagination {
|
||||
first?: number;
|
||||
after?: string;
|
||||
last?: number;
|
||||
before?: string;
|
||||
}
|
||||
|
||||
export interface PageInfo {
|
||||
startCursor?: string;
|
||||
endCursor?: string;
|
||||
hasNextPage?: boolean;
|
||||
hasPreviousPage?: boolean;
|
||||
}
|
||||
export interface Subscribe<Data, Delta> {
|
||||
(
|
||||
callback: UpdateCallback<Data, Delta>,
|
||||
@ -25,6 +46,7 @@ export interface Subscribe<Data, Delta> {
|
||||
unsubscribe: () => void;
|
||||
reload: (forceReset?: boolean) => void;
|
||||
flush: () => void;
|
||||
load: Load<Data>;
|
||||
};
|
||||
}
|
||||
|
||||
@ -35,8 +57,29 @@ export interface Update<Data, Delta> {
|
||||
(data: Data, delta: Delta, reload: (forceReset?: boolean) => void): Data;
|
||||
}
|
||||
|
||||
export interface Append<Data> {
|
||||
(
|
||||
data: Data | null,
|
||||
pageInfo: PageInfo,
|
||||
insertionData: Data | null,
|
||||
insertionPageInfo: PageInfo | null,
|
||||
pagination?: Pagination
|
||||
): {
|
||||
data: Data | null;
|
||||
pageInfo: PageInfo;
|
||||
};
|
||||
}
|
||||
|
||||
interface GetData<QueryData, Data> {
|
||||
(subscriptionData: QueryData): Data | null;
|
||||
(queryData: QueryData): Data | null;
|
||||
}
|
||||
|
||||
interface GetPageInfo<QueryData> {
|
||||
(queryData: QueryData): PageInfo | null;
|
||||
}
|
||||
|
||||
interface GetTotalCount<QueryData> {
|
||||
(queryData: QueryData): number | undefined;
|
||||
}
|
||||
|
||||
interface GetDelta<SubscriptionData, Delta> {
|
||||
@ -57,6 +100,12 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
|
||||
update: Update<Data, Delta>,
|
||||
getData: GetData<QueryData, Data>,
|
||||
getDelta: GetDelta<SubscriptionData, Delta>,
|
||||
pagination?: {
|
||||
getPageInfo: GetPageInfo<QueryData>;
|
||||
getTotalCount: GetTotalCount<QueryData>;
|
||||
append: Append<Data>;
|
||||
first: number;
|
||||
},
|
||||
fetchPolicy: FetchPolicy = 'no-cache'
|
||||
): Subscribe<Data, Delta> {
|
||||
// list of callbacks passed through subscribe call
|
||||
@ -70,20 +119,60 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
|
||||
let loading = false;
|
||||
let client: ApolloClient<object> | undefined = undefined;
|
||||
let subscription: Subscription | undefined = undefined;
|
||||
let pageInfo: PageInfo | null = null;
|
||||
let totalCount: number | undefined;
|
||||
|
||||
// notify single callback about current state, delta is passes optionally only if notify was invoked onNext
|
||||
const notify = (callback: UpdateCallback<Data, Delta>, delta?: Delta) => {
|
||||
const notify = (
|
||||
callback: UpdateCallback<Data, Delta>,
|
||||
dataUpdate?: { delta?: Delta; insertionData?: Data | null }
|
||||
) => {
|
||||
callback({
|
||||
data,
|
||||
error,
|
||||
loading,
|
||||
delta,
|
||||
pageInfo,
|
||||
totalCount,
|
||||
...dataUpdate,
|
||||
});
|
||||
};
|
||||
|
||||
// notify all callbacks
|
||||
const notifyAll = (delta?: Delta) => {
|
||||
callbacks.forEach((callback) => notify(callback, delta));
|
||||
const notifyAll = (dataUpdate?: {
|
||||
delta?: Delta;
|
||||
insertionData?: Data | null;
|
||||
}) => {
|
||||
callbacks.forEach((callback) => notify(callback, dataUpdate));
|
||||
};
|
||||
|
||||
const load = async (params?: Pagination) => {
|
||||
if (!client || !pagination || !pageInfo) {
|
||||
return Promise.reject();
|
||||
}
|
||||
const paginationVariables: Pagination = params ?? {
|
||||
first: pagination.first,
|
||||
after: pageInfo.endCursor,
|
||||
};
|
||||
const res = await client.query<QueryData>({
|
||||
query,
|
||||
variables: {
|
||||
...variables,
|
||||
pagination: paginationVariables,
|
||||
},
|
||||
fetchPolicy,
|
||||
});
|
||||
const insertionData = getData(res.data);
|
||||
const insertionDataPageInfo = pagination.getPageInfo(res.data);
|
||||
({ data, pageInfo } = pagination.append(
|
||||
data,
|
||||
pageInfo,
|
||||
insertionData,
|
||||
insertionDataPageInfo,
|
||||
paginationVariables
|
||||
));
|
||||
totalCount = pagination.getTotalCount(res.data);
|
||||
notifyAll({ insertionData });
|
||||
return insertionData;
|
||||
};
|
||||
|
||||
const initialFetch = async () => {
|
||||
@ -93,10 +182,16 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
|
||||
try {
|
||||
const res = await client.query<QueryData>({
|
||||
query,
|
||||
variables,
|
||||
variables: pagination
|
||||
? { ...variables, pagination: { first: pagination.first } }
|
||||
: variables,
|
||||
fetchPolicy,
|
||||
});
|
||||
data = getData(res.data);
|
||||
if (pagination) {
|
||||
pageInfo = pagination.getPageInfo(res.data);
|
||||
totalCount = pagination.getTotalCount(res.data);
|
||||
}
|
||||
// if there was some updates received from subscription during initial query loading apply them on just received data
|
||||
if (data && updateQueue && updateQueue.length > 0) {
|
||||
while (updateQueue.length) {
|
||||
@ -165,7 +260,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
|
||||
return;
|
||||
}
|
||||
data = newData;
|
||||
notifyAll(delta);
|
||||
notifyAll({ delta });
|
||||
}
|
||||
},
|
||||
() => reload()
|
||||
@ -205,6 +300,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
|
||||
unsubscribe: () => unsubscribe(callback),
|
||||
reload,
|
||||
flush: () => notify(callback),
|
||||
load,
|
||||
};
|
||||
};
|
||||
}
|
||||
@ -239,7 +335,7 @@ const memoize = <Data, Delta>(
|
||||
* @param update Update<Data, Delta> function that will be executed on each onNext, it should update data base on delta, it can reload data provider
|
||||
* @param getData transforms received query data to format that will be stored in data provider
|
||||
* @param getDelta transforms delta data to format that will be stored in data provider
|
||||
* @param fetchPolicy
|
||||
* @param pagination pagination related functions { getPageInfo, getTotalCount, append, first }
|
||||
* @returns Subscribe<Data, Delta> subscribe function
|
||||
* @example
|
||||
* const marketMidPriceProvider = makeDataProvider<QueryData, Data, SubscriptionData, Delta>(
|
||||
@ -263,6 +359,12 @@ export function makeDataProvider<QueryData, Data, SubscriptionData, Delta>(
|
||||
update: Update<Data, Delta>,
|
||||
getData: GetData<QueryData, Data>,
|
||||
getDelta: GetDelta<SubscriptionData, Delta>,
|
||||
pagination?: {
|
||||
getPageInfo: GetPageInfo<QueryData>;
|
||||
getTotalCount: GetTotalCount<QueryData>;
|
||||
append: Append<Data>;
|
||||
first: number;
|
||||
},
|
||||
fetchPolicy: FetchPolicy = 'no-cache'
|
||||
): Subscribe<Data, Delta> {
|
||||
const getInstance = memoize<Data, Delta>(() =>
|
||||
@ -272,6 +374,7 @@ export function makeDataProvider<QueryData, Data, SubscriptionData, Delta>(
|
||||
update,
|
||||
getData,
|
||||
getDelta,
|
||||
pagination,
|
||||
fetchPolicy
|
||||
)
|
||||
);
|
||||
|
@ -6,7 +6,7 @@ import { useCallback, useMemo, useRef } from 'react';
|
||||
import {
|
||||
MAX_TRADES,
|
||||
sortTrades,
|
||||
tradesDataProvider,
|
||||
tradesDataProvider as dataProvider,
|
||||
} from './trades-data-provider';
|
||||
import { TradesTable } from './trades-table';
|
||||
import type { TradeFields } from './__generated__/TradeFields';
|
||||
@ -22,7 +22,7 @@ export const TradesContainer = ({ marketId }: TradesContainerProps) => {
|
||||
() => ({ marketId, maxTrades: MAX_TRADES }),
|
||||
[marketId]
|
||||
);
|
||||
const update = useCallback((delta: TradeFields[]) => {
|
||||
const update = useCallback(({ delta }: { delta: TradeFields[] }) => {
|
||||
if (!gridRef.current?.api) {
|
||||
return false;
|
||||
}
|
||||
@ -43,11 +43,11 @@ export const TradesContainer = ({ marketId }: TradesContainerProps) => {
|
||||
|
||||
return true;
|
||||
}, []);
|
||||
const { data, error, loading } = useDataProvider(
|
||||
tradesDataProvider,
|
||||
const { data, error, loading } = useDataProvider({
|
||||
dataProvider,
|
||||
update,
|
||||
variables
|
||||
);
|
||||
variables,
|
||||
});
|
||||
|
||||
return (
|
||||
<AsyncRenderer
|
||||
|
@ -53,8 +53,8 @@ export const sortTrades = (trades: TradeFields[]) => {
|
||||
);
|
||||
};
|
||||
|
||||
const update = (data: TradeFields[], delta: TradeFields[]) =>
|
||||
produce(data, (draft) => {
|
||||
const update = (data: TradeFields[], delta: TradeFields[]) => {
|
||||
return produce(data, (draft) => {
|
||||
const incoming = sortTrades(delta);
|
||||
|
||||
// Add new trades to the top
|
||||
@ -65,6 +65,7 @@ const update = (data: TradeFields[], delta: TradeFields[]) =>
|
||||
draft.splice(MAX_TRADES, draft.length - MAX_TRADES);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const getData = (responseData: Trades): TradeFields[] | null =>
|
||||
responseData.market ? responseData.market.trades : null;
|
||||
|
14
yarn.lock
14
yarn.lock
@ -7437,14 +7437,14 @@ aes-js@^3.1.2:
|
||||
integrity sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==
|
||||
|
||||
ag-grid-community@^27.0.1:
|
||||
version "27.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ag-grid-community/-/ag-grid-community-27.1.0.tgz#17f73173444a9efc4faea0f0cd6c5090e698f7ee"
|
||||
integrity sha512-SWzIJTNa7C6Vinizelcoc1FAJQRt1pDn+A8XHQDO2GTQT+VjBnPL8fg94fLJy0EEvqaN5IhDybNS0nD07SKIQw==
|
||||
version "27.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ag-grid-community/-/ag-grid-community-27.3.0.tgz#b1e94a58026aaf2f0cd7920e35833325b5e762c7"
|
||||
integrity sha512-R5oZMXEHXnOLrmhn91J8lR0bv6IAnRcU6maO+wKLMJxffRWaAYFAuw1jt7bdmcKCv8c65F6LEBx4ykSOALa9vA==
|
||||
|
||||
ag-grid-react@^27.0.1:
|
||||
version "27.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ag-grid-react/-/ag-grid-react-27.1.0.tgz#3b08203b9731a2b2d5431dddd69d68dc640c311e"
|
||||
integrity sha512-AfRwH6BL/LribvLJ2594Fq0/MfZf/17WebjGj927bM3vABDr2OBX3qgMIaQE+kpV9mABPb51rlWLMmbCvltv2g==
|
||||
version "27.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ag-grid-react/-/ag-grid-react-27.3.0.tgz#fe06647653f8b0b349b8e613aab8ea2e07915562"
|
||||
integrity sha512-2bs9YfJ/shvBZQLLjny4NFvht+ic6VtpTPO0r3bHHOhlL3Fjx2rGvS6AHSwfvu+kJacHCta30PjaEbX8T3UDyw==
|
||||
dependencies:
|
||||
prop-types "^15.8.1"
|
||||
|
||||
@ -17018,7 +17018,7 @@ nx@13.8.1:
|
||||
object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
|
||||
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
|
||||
|
||||
object-copy@^0.1.0:
|
||||
version "0.1.0"
|
||||
|
Loading…
Reference in New Issue
Block a user