Skip to content

Commit

Permalink
drastically improves performance (#320)
Browse files Browse the repository at this point in the history
* rendering of the tables was very expensive for large amounts of data
* this version uses react window to only render the data in the viewport
* styled the tables to match the previous look
  • Loading branch information
schmanu authored Dec 4, 2021
1 parent ad4edb9 commit 18c88ce
Show file tree
Hide file tree
Showing 9 changed files with 287 additions and 111 deletions.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
"react-dropzone": "^11.4.2",
"react-scripts": "^4.0.3",
"react-svg": "^14.1.3",
"react-virtualized-auto-sizer": "^1.0.6",
"react-window": "^1.8.6",
"styled-components": "^5.3.3",
"tslib": "^2.3.1",
"typescript": "~4.4.4"
Expand All @@ -52,6 +54,8 @@
"@types/node": "^16.11.11",
"@types/react": "^17.0.37",
"@types/react-dom": "^17.0.11",
"@types/react-virtualized-auto-sizer": "^1.0.1",
"@types/react-window": "^1.8.5",
"@types/styled-components": "^5.1.16",
"babel-eslint": "^10.1.0",
"chai": "^4.3.4",
Expand Down
6 changes: 5 additions & 1 deletion src/components/Receiver.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const Container = styled.div`
justify-content: start;
align-items: center;
gap: 8px;
padding: 16px;
min-width: 285px;
`;

export const Receiver = (props: ReceiverProps) => {
Expand All @@ -28,6 +30,8 @@ export const Receiver = (props: ReceiverProps) => {
</Tooltip>
</Container>
) : (
<Text size="md">{receiverAddress}</Text>
<Container>
<Text size="md">{receiverAddress}</Text>
</Container>
);
};
4 changes: 2 additions & 2 deletions src/components/Summary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const Summary = (props: SummaryProps): JSX.Element => {
</div>
</AccordionSummary>
<AccordionDetails>
<AssetTransferTable transferContent={assetTransfers} />
{assetTransfers.length > 0 && <AssetTransferTable transferContent={assetTransfers} />}
</AccordionDetails>
</Accordion>
<Accordion disabled={collectibleTxCount === 0} compact style={{ maxWidth: 1400 }}>
Expand All @@ -66,7 +66,7 @@ export const Summary = (props: SummaryProps): JSX.Element => {
</div>
</AccordionSummary>
<AccordionDetails>
<CollectiblesTransferTable transferContent={collectibleTransfers} />
{collectibleTransfers.length > 0 && <CollectiblesTransferTable transferContent={collectibleTransfers} />}
</AccordionDetails>
</Accordion>
</>
Expand Down
114 changes: 87 additions & 27 deletions src/components/assets/AssetTransferTable.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Table, Text } from "@gnosis.pm/safe-react-components";
import React from "react";
import { Text } from "@gnosis.pm/safe-react-components";
import React, { memo } from "react";
import AutoSizer from "react-virtualized-auto-sizer";
import { areEqual, FixedSizeList as List } from "react-window";

import { AssetTransfer } from "../../parser/csvParser";
import { Receiver } from "../Receiver";
Expand All @@ -10,34 +12,92 @@ type TransferTableProps = {
transferContent: AssetTransfer[];
};

type RowProps = {
index: number;
style: any;
data: AssetTransfer[];
};

type ListHeaderProps = {
width: number;
};

const ListHeader = (props: ListHeaderProps) => {
const { width } = props;
return (
<div
style={{
width,
height: 60,
display: "flex",
flexDirection: "row",
borderBottom: "1px solid rgba(224, 224, 224, 1)",
overflow: "hidden",
}}
>
<div style={{ flex: 1, padding: 16, minWidth: 285 }}>
<Text size="lg">Token</Text>
</div>
<div style={{ flex: 1, padding: 16, minWidth: 285 }}>
<Text size="lg">Receiver</Text>
</div>
<div style={{ flex: 1, padding: 16, minWidth: 80 }}>
<Text size="lg">Amount</Text>
</div>
</div>
);
};

const Row = memo((props: RowProps) => {
const { index, style, data } = props;
const row = data[index];
return (
<div style={style}>
<div
style={{
display: "flex",
flexDirection: "row",
borderBottom: "1px solid rgba(224, 224, 224, 1)",
alignItems: "center",
}}
>
<ERC20Token tokenAddress={row.tokenAddress} symbol={row.symbol} />
<Receiver receiverAddress={row.receiver} receiverEnsName={row.receiverEnsName} />
<div style={{ flex: "1", padding: 16, minWidth: 80 }}>
<Text size="md">{row.amount?.toFixed()}</Text>
</div>
</div>
</div>
);
}, areEqual);

export const AssetTransferTable = (props: TransferTableProps) => {
const { transferContent } = props;
return (
<div style={{ flex: "1" }}>
<Table
isStickyHeader={true}
maxHeight={500}
headers={[
{ id: "position", label: "#" },
{ id: "token", label: "Token" },
{ id: "receiver", label: "Receiver" },
{ id: "value", label: "Value" },
]}
rows={transferContent.map((row, index) => {
return {
id: "" + index,
cells: [
{ id: "position", content: row.position },
{ id: "token", content: <ERC20Token tokenAddress={row.tokenAddress} symbol={row.symbol} /> },
{
id: "receiver",
content: <Receiver receiverAddress={row.receiver} receiverEnsName={row.receiverEnsName} />,
},
{ id: "value", content: <Text size="md">{row.amount.toString()}</Text> },
],
};
})}
/>
<div
style={{
flex: 1,
boxShadow:
"rgb(247, 245, 245) 0px 3px 3px -2px, rgb(247, 245, 245) 0px 3px 4px 0px, rgb(247, 245, 245) 0px 1px 8px 0px",
}}
>
<AutoSizer disableHeight>
{({ width }) => (
// List Header?
<>
<ListHeader width={width} />
<List
height={Math.min(460, transferContent.length * 55)}
itemCount={transferContent.length}
itemSize={55}
width={width}
itemData={transferContent}
>
{Row}
</List>
</>
)}
</AutoSizer>
</div>
);
};
7 changes: 5 additions & 2 deletions src/components/assets/CSVForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useSafeAppsSDK } from "@gnosis.pm/safe-apps-react-sdk";
import { Text } from "@gnosis.pm/safe-react-components";
import { ethers } from "ethers";
import debounce from "lodash.debounce";
import React, { useContext, useMemo, useState } from "react";
import React, { useContext, useEffect, useMemo, useState } from "react";
import styled from "styled-components";

import { MessageContext } from "../../contexts/MessageContextProvider";
Expand Down Expand Up @@ -41,7 +41,6 @@ export const CSVForm = (props: CSVFormProps): JSX.Element => {

const onChangeTextHandler = (csvText: string) => {
setCsvText(csvText);
parseAndValidateCSV(csvText);
};

const parseAndValidateCSV = useMemo(
Expand Down Expand Up @@ -104,6 +103,10 @@ export const CSVForm = (props: CSVFormProps): JSX.Element => {
],
);

useEffect(() => {
parseAndValidateCSV(csvText);
}, [csvText, parseAndValidateCSV]);

return (
<Form>
<Text size="xl">
Expand Down
144 changes: 105 additions & 39 deletions src/components/assets/CollectiblesTransferTable.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Table, Text } from "@gnosis.pm/safe-react-components";
import React from "react";
import { Text } from "@gnosis.pm/safe-react-components";
import React, { memo } from "react";
import AutoSizer from "react-virtualized-auto-sizer";
import { areEqual, FixedSizeList as List } from "react-window";

import { CollectibleTransfer } from "../../parser/csvParser";
import { Receiver } from "../Receiver";
Expand All @@ -10,46 +12,110 @@ type TransferTableProps = {
transferContent: CollectibleTransfer[];
};

type RowProps = {
index: number;
style: any;
data: CollectibleTransfer[];
};

type ListHeaderProps = {
width: number;
};

export const ListHeader = (props: ListHeaderProps) => {
const { width } = props;
return (
<>
<div
style={{
width,
height: 60,
display: "flex",
flexDirection: "row",
borderBottom: "1px solid rgba(224, 224, 224, 1)",
overflow: "hidden",
}}
>
<div style={{ flex: 1, padding: 16, minWidth: 285 }}>
<Text size="lg">Token</Text>
</div>
<div style={{ flex: 1, padding: 16, minWidth: 80 }}>
<Text size="lg">Type</Text>
</div>
<div style={{ flex: 1, padding: 16, minWidth: 285 }}>
<Text size="lg">Receiver</Text>
</div>
<div style={{ flex: 1, padding: 16, minWidth: 80 }}>
<Text size="lg">Value</Text>
</div>
<div style={{ flex: 1, padding: 16, minWidth: 80 }}>
<Text size="lg">Id</Text>
</div>
</div>
</>
);
};

export const Row = memo((props: RowProps) => {
const { index, style, data } = props;
const row = data[index];
return (
<div style={style}>
<div
style={{
display: "flex",
flexDirection: "row",
borderBottom: "1px solid rgba(224, 224, 224, 1)",
alignItems: "center",
}}
>
<ERC721Token
tokenAddress={row.tokenAddress}
id={row.tokenId}
token_type={row.token_type}
hasMetaData={row.hasMetaData}
/>
<div style={{ flex: "1", padding: 16, minWidth: 80 }}>
<Text size="md">{row.token_type.toUpperCase()}</Text>
</div>
<Receiver receiverAddress={row.receiver} receiverEnsName={row.receiverEnsName} />
<div style={{ flex: "1", padding: 16, minWidth: 80 }}>
<Text size="md">{row.value?.toFixed()}</Text>
</div>
<div style={{ flex: "1", padding: 16, minWidth: 80 }}>
<Text size="md">{row.tokenId.toFixed()}</Text>
</div>
</div>
</div>
);
}, areEqual);

export const CollectiblesTransferTable = (props: TransferTableProps) => {
const { transferContent } = props;
return (
<div style={{ flex: "1" }}>
<Table
isStickyHeader={true}
maxHeight={500}
headers={[
{ id: "token", label: "Token" },
{ id: "type", label: "Type" },
{ id: "receiver", label: "Receiver" },
{ id: "value", label: "Value" },
{ id: "id", label: "ID" },
]}
rows={transferContent.map((row, index) => {
return {
id: "" + index,
cells: [
{
id: "token",
content: (
<ERC721Token
tokenAddress={row.tokenAddress}
id={row.tokenId}
token_type={row.token_type}
hasMetaData={row.hasMetaData}
/>
),
},
{ id: "type", content: row.token_type.toUpperCase() },
{
id: "receiver",
content: <Receiver receiverAddress={row.receiver} receiverEnsName={row.receiverEnsName} />,
},
{ id: "value", content: <Text size="md">{row.value?.toString()}</Text> },
{ id: "id", content: <Text size="md">{row.tokenId.toString()}</Text> },
],
};
})}
/>
<div
style={{
flex: 1,
boxShadow:
"rgb(247, 245, 245) 0px 3px 3px -2px, rgb(247, 245, 245) 0px 3px 4px 0px, rgb(247, 245, 245) 0px 1px 8px 0px",
}}
>
<AutoSizer disableHeight>
{({ width }) => (
<>
<ListHeader width={width} />
<List
height={Math.min(460, transferContent.length * 55)}
itemCount={transferContent.length}
itemSize={55}
width={width}
itemData={transferContent}
>
{Row}
</List>
</>
)}
</AutoSizer>
</div>
);
};
4 changes: 3 additions & 1 deletion src/components/assets/ERC20Token.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const Container = styled.div`
justify-content: start;
align-items: center;
gap: 8px;
padding: 16px;
min-width: 285px;
`;

export const ERC20Token = (props: TokenProps) => {
Expand All @@ -23,7 +25,7 @@ export const ERC20Token = (props: TokenProps) => {
return (
<Container>
{tokenList.get(tokenAddress) && (
<img /* TODO - alt doesn't really work here */
<img
alt={""}
src={tokenList.get(tokenAddress)?.logoURI}
style={{
Expand Down
Loading

0 comments on commit 18c88ce

Please sign in to comment.