diff --git a/__pycache__/queries.cpython-311.pyc b/__pycache__/queries.cpython-311.pyc new file mode 100644 index 0000000..9d68442 Binary files /dev/null and b/__pycache__/queries.cpython-311.pyc differ diff --git a/src/components/Map/TokenStreamsMap.tsx b/src/components/Map/TokenStreamsMap.tsx new file mode 100644 index 0000000..b5ee5f3 --- /dev/null +++ b/src/components/Map/TokenStreamsMap.tsx @@ -0,0 +1,143 @@ +import 'reactflow/dist/style.css' +import { table } from 'console' +import { FC, memo, ReactElement, useCallback, useEffect } from 'react' +import ReactFlow, { + Controls, + Background, + Position, + useEdgesState, + useNodesState, + addEdge +} from 'reactflow' +import { Network } from '../../redux/networks' +import UserBlock from './UserBlock' +import { useCall } from 'wagmi' + +interface Edge { + id: string + source: string + target: string + label?: string + animated?: boolean + sourceHandle?: string + targetHandle?: string // Add targetHandle property + type?: string // Add type property +} + +interface Node { + id: string + position: { x: number; y: number } + data: { label: ReactElement } + flowRate?: string + sourcePosition?: Position + targetPosition?: Position +} + +const TokenStreamsMap: FC<{ + network: Network + tableRows: any[] +}> = ({ network, tableRows }): ReactElement => { + // initialize initialNodes and initialEdges with empty arrays + // use placeholder values for nodes + // need 20 nodes and 10 edges + let initialNodes: Node[] = [] + let initialEdges: Edge[] = [] + for (let i = 0; i < 10; i++) { + initialNodes.push({ + id: `${2 * i}`, + position: { x: -200, y: -i * 200 }, + data: { + label: + }, + sourcePosition: Position.Right, + targetPosition: Position.Left + }) + initialNodes.push({ + id: `${2 * i + 1}`, + position: { x: 200, y: -i * 200 }, + data: { + label: + }, + sourcePosition: Position.Right, + targetPosition: Position.Left + }) + initialEdges.push({ + id: `${i}`, + source: `${2 * i}`, + target: `${2 * i + 1}`, + animated: true, + sourceHandle: 'left', + targetHandle: 'right' + }) + } + + const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes) + const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges) + + const onConnect = useCallback( + (params: any) => setEdges((edges) => addEdge(params, edges)), + [setEdges] + ) + + useEffect(() => { + // create newNodes without duplicates + // edges can point to the same node + const newNodes = tableRows + .map((row, index) => [ + { + id: `${row.sender}`, + position: { x: -200, y: -index * 200 }, + data: { + label: + }, + sourcePosition: Position.Right, + targetPosition: Position.Left + }, + { + id: `${row.receiver}`, + position: { x: 200, y: -index * 200 }, + data: { + label: + }, + sourcePosition: Position.Right, + targetPosition: Position.Left + } + ]) + .flat() + const newEdges = tableRows.map((row, index) => { + return { + id: `${row.id}`, + source: `${row.sender}`, + target: `${row.receiver}`, + animated: true, + sourceHandle: 'left', + targetHandle: 'right' + } + }) + setNodes(newNodes) + setEdges(newEdges) + }, [tableRows]) + + console.log('nodes', nodes) + console.log('edges', edges) + + return ( + + + + + ) +} + +export default memo(TokenStreamsMap) diff --git a/src/components/SimpleStream/SimpleStream.tsx b/src/components/SimpleStream/SimpleStream.tsx new file mode 100644 index 0000000..9d929ad --- /dev/null +++ b/src/components/SimpleStream/SimpleStream.tsx @@ -0,0 +1,89 @@ +import { FC, ReactElement, useCallback } from 'react' +import ReactFlow, { + Background, + Controls, + OnConnect, + Position, + addEdge, + useEdgesState, + useNodesState +} from 'reactflow' + +interface StreamProps { + id: string + sender: string + receiver: string + token: string + flowRate: string +} + +const SimpleStream: FC = ({ + id, + sender, + receiver, + token, + flowRate +}): ReactElement => { + const initialNodes = [ + { + id: `${sender}`, + type: 'input', + position: { x: -200, y: 0 }, + data: { label: sender.slice(0, 6) + '...' + sender.slice(-4) }, + sourcePosition: Position.Right, + targetPosition: Position.Left + }, + { + id: `${receiver}`, + position: { x: 150, y: 0 }, + data: { label: receiver.slice(0, 6) + '...' + receiver.slice(-4) }, + sourcePosition: Position.Right, + targetPosition: Position.Left + } + ] + + const initialEdges = [ + { + id: `${id}-a->b`, + source: `${sender}`, + target: `${receiver}`, + animated: true, + sourceHandle: 'left', + targetHandle: 'right', + type: 'straight' + } + ] + + const [nodes, , onNodesChange] = useNodesState(initialNodes) + const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges) + const onConnect: OnConnect = useCallback( + (connection) => setEdges((edges) => addEdge(connection, edges)), + [setEdges] + ) + + console.log('nodes', nodes) + console.log('edges', edges) + console.log('sender', sender) + console.log('receiver', receiver) + + return ( + + + + + ) +} + +export default SimpleStream diff --git a/src/pages/NetworkStreams.tsx b/src/pages/NetworkStreams.tsx index 2b4258c..4c92cf3 100644 --- a/src/pages/NetworkStreams.tsx +++ b/src/pages/NetworkStreams.tsx @@ -6,6 +6,8 @@ import { TableCell, TableFooter, TableRow, + ToggleButton, + ToggleButtonGroup, Typography } from '@mui/material' import { @@ -14,7 +16,7 @@ import { SkipPaging, Stream_OrderBy } from '@superfluid-finance/sdk-core' -import { FC, useState } from 'react' +import React, { FC, useCallback, useEffect, useState } from 'react' import AccountAddress from '../components/Address/AccountAddress' import FlowingBalanceWithToken from '../components/Amount/FlowingBalanceWithToken' @@ -23,6 +25,19 @@ import TableLoader from '../components/Table/TableLoader' import TimeAgo from '../components/TimeAgo/TimeAgo' import { Network } from '../redux/networks' import { sfSubgraph } from '../redux/store' +import SimpleStream from '../components/SimpleStream/SimpleStream' +import ReactFlow, { + OnConnect, + Position, + addEdge, + useEdgesState, + useNodesState +} from 'reactflow' + +export enum ViewMode { + Table, + Map +} export const defaultStreamQueryOrdering: Ordering = { orderBy: 'createdAtTimestamp', @@ -39,6 +54,10 @@ interface NetworkStreamsProps { export const NetworkStreams: FC = ({ network }) => { const [paging, setPaging] = useState(defaultStreamQueryPaging) + const [viewMode, setViewMode] = useState(ViewMode.Table) + + const onViewModeChange = (_event: any, newViewMode: ViewMode) => + setViewMode(newViewMode) const query = sfSubgraph.useStreamsQuery({ chainId: network.chainId, @@ -54,59 +73,127 @@ export const NetworkStreams: FC = ({ network }) => { const streams = query.data?.data ?? [] + const initialNodes = streams + .map((stream, index) => [ + { + id: `${stream.sender}`, + type: 'input', + position: { x: -200, y: 0 }, + data: { + label: stream.sender.slice(0, 6) + '...' + stream.sender.slice(-4) + }, + sourcePosition: Position.Right, + targetPosition: Position.Left + }, + { + id: `${stream.receiver}`, + position: { x: 150, y: 0 }, + data: { + label: stream.receiver.slice(0, 6) + '...' + stream.receiver.slice(-4) + }, + sourcePosition: Position.Right, + targetPosition: Position.Left + } + ]) + .flat() + + const initialEdges = streams + .map((stream, index) => [ + { + id: `${stream.id}`, + source: `${stream.sender}`, + target: `${stream.receiver}`, + animated: true, + sourceHandle: 'left', + targetHandle: 'right', + type: 'straight' + } + ]) + .flat() + + const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes) + const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges) + + useEffect(() => { + // need to update nodes and edges when streams change + }, [paging]) + + const onConnect: OnConnect = useCallback( + (connection) => setEdges((edges) => addEdge(connection, edges)), + [setEdges] + ) + return ( - - - {streams.map((stream) => ( - - - - - - - - - - - - ))} - - - - - {streams.length > 0 && ( - - - - - - - - )} -
+ + {/* + + Table view + Map view + + */} + + + {streams.map((stream) => ( + *': { borderBottom: 'none' } }} + key={stream.id} + > + + + + + + + + + + + ))} + + + + + {streams.length > 0 && ( + + + + + + + + )} +
+
) } diff --git a/src/pages/[_network]/supertokens/[_id]/SuperTokenStreamsTable.tsx b/src/pages/[_network]/supertokens/[_id]/SuperTokenStreamsTable.tsx index 6bb51ee..83b6eca 100644 --- a/src/pages/[_network]/supertokens/[_id]/SuperTokenStreamsTable.tsx +++ b/src/pages/[_network]/supertokens/[_id]/SuperTokenStreamsTable.tsx @@ -30,7 +30,16 @@ import { StreamsQuery } from '@superfluid-finance/sdk-redux' import omit from 'lodash/fp/omit' import set from 'lodash/fp/set' import isEqual from 'lodash/isEqual' -import { ChangeEvent, FC, FormEvent, useEffect, useRef, useState } from 'react' +import { + ChangeEvent, + FC, + FormEvent, + ReactElement, + useCallback, + useEffect, + useRef, + useState +} from 'react' import AccountAddress from '../../../../components/Address/AccountAddress' import FlowRate from '../../../../components/Amount/FlowRate' @@ -44,6 +53,13 @@ import { Network } from '../../../../redux/networks' import { sfSubgraph } from '../../../../redux/store' import { StreamStatus } from '../../accounts/AccountIncomingStreamsTable' import { StreamDetailsDialog } from '../../streams/StreamDetails' +import UserBlock from '../../../../components/Map/UserBlock' +import TokenStreamsMap from '../../../../components/Map/TokenStreamsMap' + +export enum ViewMode { + Table, + Map +} const defaultOrdering = { orderBy: 'createdAtTimestamp', @@ -71,6 +87,11 @@ const SuperTokenStreamsTable: FC = ({ const [streamStatus, setStreamStatus] = useState(null) + const [viewMode, setViewMode] = useState(ViewMode.Table) + + const onViewModeChange = (_event: any, newViewMode: ViewMode) => + setViewMode(newViewMode) + const defaultFilter = { token: tokenAddress } @@ -377,115 +398,132 @@ const SuperTokenStreamsTable: FC = ({ - - - - Sender - Receiver - - - Flow Rate - - - - - - Created - - - - - - - {tableRows.map((stream) => ( - - - - - - - - - - + + + Table view + Map view + + + {viewMode === ViewMode.Table && ( +
+ + + Sender + Receiver - {new Date(stream.createdAtTimestamp * 1000).toLocaleString()} - - - - - {(onClick) => } - + + Flow Rate + + - - ))} - - {queryResult.isSuccess && tableRows.length === 0 && ( - - - - No results - + + + Created + + + + + {tableRows.map((stream) => ( + + + + + + + + + + + + {new Date(stream.createdAtTimestamp * 1000).toLocaleString()} + + + + + {(onClick) => } + + + + ))} + + {queryResult.isSuccess && tableRows.length === 0 && ( + + + + No results + + + + )} + + + + {tableRows.length > 0 && ( + + + + + + + )} - - - - {tableRows.length > 0 && ( - - - - - - - - )} -
+ + )} + {viewMode === ViewMode.Map && ( + + )} ) } diff --git a/src/pages/[_network]/supertokens/[_id]/SuperTokensTable.tsx b/src/pages/[_network]/supertokens/[_id]/SuperTokensTable.tsx index 94598dc..80f1251 100644 --- a/src/pages/[_network]/supertokens/[_id]/SuperTokensTable.tsx +++ b/src/pages/[_network]/supertokens/[_id]/SuperTokensTable.tsx @@ -1,11 +1,15 @@ import FilterListIcon from '@mui/icons-material/FilterList' +import IosShareIcon from '@mui/icons-material/IosShare' import { Box, Button, Chip, + FormControlLabel, IconButton, OutlinedInput, Popover, + Radio, + RadioGroup, Stack, Table, TableBody, @@ -24,9 +28,14 @@ import { createSkipPaging, Ordering, Token_Filter, - Token_OrderBy + Token_OrderBy, + TokenStatistic_OrderBy, + TokenStatistic_Filter } from '@superfluid-finance/sdk-core' -import { TokensQuery } from '@superfluid-finance/sdk-redux' +import { + TokenStatisticsQuery, + TokensQuery +} from '@superfluid-finance/sdk-redux' import isEqual from 'lodash/fp/isEqual' import omit from 'lodash/fp/omit' import set from 'lodash/fp/set' @@ -52,33 +61,60 @@ interface SuperTokensTableProps { } type RequiredTokensQuery = Required> +type RequiredTokenStatisticsQuery = Required< + Omit +> const defaultOrdering = { orderBy: 'isListed', orderDirection: 'desc' } as Ordering +const defaultStatisticsOrdering = { + orderBy: 'token__isListed', + orderDirection: 'desc' +} as Ordering + const defaultFilter: Token_Filter = { isSuperToken: true } +const defaultStatisticsFilter: TokenStatistic_Filter = { + token_: defaultFilter +} + export const defaultPaging = createSkipPaging({ take: 10 }) +export const tokensPaging = createSkipPaging({ + take: 500 +}) + const SuperTokensTable: FC = ({ network }) => { const filterAnchorRef = useRef(null) const [showFilterMenu, setShowFilterMenu] = useState(false) + const exportAnchorRef = useRef(null) + const [showExportMenu, setShowExportMenu] = useState(false) + const [exportValue, setExportValue] = useState('json') + const [listedStatus, setListedStatus] = useState(null) const createDefaultArg = (): RequiredTokensQuery => ({ chainId: network.chainId, filter: defaultFilter, - pagination: defaultPaging, + pagination: tokensPaging, order: defaultOrdering }) + const createDefaultStatisticsArg = (): RequiredTokenStatisticsQuery => ({ + chainId: network.chainId, + filter: defaultStatisticsFilter, + pagination: defaultPaging, + order: defaultStatisticsOrdering + }) + const [queryArg, setQueryArg] = useState(createDefaultArg()) @@ -96,8 +132,27 @@ const SuperTokensTable: FC = ({ network }) => { } }), [queriedTokens.data?.items]); + const [queryStatisticsArg, setQueryStatisticsArg] = + useState(createDefaultStatisticsArg()) + + const [queryStatisticsTrigger, queryStatisticsResult] = + sfSubgraph.useLazyTokenStatisticsQuery() + + // const [queryStatisticsArg, setQueryStatisticsArg] = + // useState(createDefaultStatisticsArg()) + + // const [queryStatisticsTrigger, queryStatisticsResult] = + // sfSubgraph.useLazyTokenStatisticsQuery() + const queryTriggerDebounced = useDebounce(queryTrigger, 250) + const [dailyOutflowRateGte, setDailyOutflowRateGte] = useState('') + const [dailyOutflowRateLte, setDailyOutflowRateLte] = useState('') + const [perSecondOutflowRateGte, setPerSecondOutflowRateGte] = + useState('') + const [perSecondOutflowRateLte, setPerSecondOutflowRateLte] = + useState('') + const onQueryArgsChanged = (newArgs: RequiredTokensQuery) => { setQueryArg(newArgs) @@ -111,86 +166,228 @@ const SuperTokensTable: FC = ({ network }) => { } } + const onQueryStatisticsArgsChanged = ( + newStatisticsArgs: RequiredTokenStatisticsQuery + ) => { + setQueryStatisticsArg(newStatisticsArgs) + queryStatisticsTrigger(newStatisticsArgs, true) + } + useEffect(() => { onQueryArgsChanged(createDefaultArg()) + onQueryStatisticsArgsChanged(createDefaultStatisticsArg()) // eslint-disable-next-line react-hooks/exhaustive-deps }, [network]) - const setPage = (newPage: number) => - onQueryArgsChanged( - set('pagination.skip', (newPage - 1) * queryArg.pagination.take, queryArg) + const setPage = (newPage: number) => { + onQueryStatisticsArgsChanged( + set( + 'pagination.skip', + (newPage - 1) * queryStatisticsArg.pagination.take, + queryStatisticsArg + ) ) + } - const setPageSize = (newPageSize: number) => - onQueryArgsChanged(set('pagination.take', newPageSize, queryArg)) + const setPageSize = (newPageSize: number) => { + onQueryStatisticsArgsChanged( + set('pagination.take', newPageSize, queryStatisticsArg) + ) + } - const onOrderingChanged = (newOrdering: Ordering) => - onQueryArgsChanged({ ...queryArg, order: newOrdering }) + const onStatisticsOrderingChanged = ( + newStatisticsOrdering: Ordering + ) => + onQueryStatisticsArgsChanged({ + ...queryStatisticsArg, + order: newStatisticsOrdering + }) - const onSortClicked = (field: Token_OrderBy) => () => { - if (queryArg.order?.orderBy !== field) { - onOrderingChanged({ + const onStatisticsSortClicked = (field: TokenStatistic_OrderBy) => () => { + if (queryStatisticsArg.order?.orderBy !== field) { + onStatisticsOrderingChanged({ orderBy: field, orderDirection: 'desc' }) - } else if (queryArg.order.orderDirection === 'desc') { - onOrderingChanged({ + } else if (queryStatisticsArg.order.orderDirection === 'desc') { + onStatisticsOrderingChanged({ orderBy: field, orderDirection: 'asc' }) } else { - onOrderingChanged(defaultOrdering) + onStatisticsOrderingChanged(defaultStatisticsOrdering) } } - const onFilterChange = (newFilter: Token_Filter) => { - onQueryArgsChanged({ - ...queryArg, - pagination: { ...queryArg.pagination, skip: 0 }, + const onFilterChange = (newFilter: TokenStatistic_Filter) => { + onQueryStatisticsArgsChanged({ + ...queryStatisticsArg, + pagination: { ...queryStatisticsArg.pagination, skip: 0 }, filter: newFilter }) } const clearFilterField = - (...fields: Array) => - () => - onFilterChange(omit(fields, queryArg.filter)) + (...fields: Array | string[]) => + () => { + if (fields.includes('totalOutflowRate_gte')) { + setDailyOutflowRateGte('') + setPerSecondOutflowRateGte('') + } else if (fields.includes('totalOutflowRate_lte')) { + setDailyOutflowRateLte('') + setPerSecondOutflowRateLte('') + } + onFilterChange(omit(fields, queryStatisticsArg.filter)) + } const onNameChange = (e: ChangeEvent) => { + const { token_, ...newFilter } = queryStatisticsArg.filter if (e.target.value) { onFilterChange({ - ...queryArg.filter, - name_contains_nocase: e.target.value + ...queryStatisticsArg.filter, + token_: { + name_contains_nocase: e.target.value, + isListed: token_?.isListed, + symbol_contains_nocase: token_?.symbol_contains_nocase + } }) } else { - onFilterChange(omit('name_contains_nocase', queryArg.filter)) + onFilterChange( + omit('token_.name_contains_nocase', queryStatisticsArg.filter) + ) } } const onSymbolChange = (e: ChangeEvent) => { + const { token_, ...newFilter } = queryStatisticsArg.filter if (e.target.value) { onFilterChange({ - ...queryArg.filter, - symbol_contains_nocase: e.target.value + ...queryStatisticsArg.filter, + token_: { + symbol_contains_nocase: e.target.value, + isListed: token_?.isListed, + name_contains_nocase: token_?.name_contains_nocase + } }) } else { - onFilterChange(omit('symbol_contains_nocase', queryArg.filter)) + onFilterChange( + omit('token_.symbol_contains_nocase', queryStatisticsArg.filter) + ) } } - const getListedStatusFilter = (status: ListedStatus | null): Token_Filter => { + const onActiveStreamsGteChange = (e: ChangeEvent) => { + if (e.target.value) { + onFilterChange({ + ...queryStatisticsArg.filter, + totalNumberOfActiveStreams_gte: Number(e.target.value) + }) + } else { + onFilterChange( + omit('totalNumberOfActiveStreams_gte', queryStatisticsArg.filter) + ) + } + } + + const onActiveStreamsLteChange = (e: ChangeEvent) => { + if (e.target.value) { + onFilterChange({ + ...queryStatisticsArg.filter, + totalNumberOfActiveStreams_lte: Number(e.target.value) + }) + } else { + onFilterChange( + omit('totalNumberOfActiveStreams_lte', queryStatisticsArg.filter) + ) + } + } + + const queryOutflowRateGte = (normalizedOutflowRate: string) => { + if (normalizedOutflowRate) { + onFilterChange({ + ...queryStatisticsArg.filter, + totalOutflowRate_gte: normalizedOutflowRate + }) + } else { + onFilterChange(omit('totalOutflowRate_gte', queryStatisticsArg.filter)) + } + } + + const onOutflowRateGteChange = (e: ChangeEvent) => { + setDailyOutflowRateGte(e.target.value) + } + + useEffect(() => { + if (dailyOutflowRateGte) { + const dailyOutflowRateGteValue = Number(dailyOutflowRateGte) + if (!isNaN(dailyOutflowRateGteValue)) { + const normalizedOutflowRate = ( + (dailyOutflowRateGteValue * 1e18) / + (60 * 60 * 24) + ).toFixed(0) + setPerSecondOutflowRateGte(normalizedOutflowRate) + queryOutflowRateGte(normalizedOutflowRate) + } + } + }, [dailyOutflowRateGte]) + + const queryOutflowRateLte = (normalizedOutflowRate: string) => { + if (normalizedOutflowRate) { + onFilterChange({ + ...queryStatisticsArg.filter, + totalOutflowRate_lte: normalizedOutflowRate + }) + } else { + onFilterChange(omit('totalOutflowRate_lte', queryStatisticsArg.filter)) + } + } + + const onOutflowRateLteChange = (e: ChangeEvent) => { + setDailyOutflowRateLte(e.target.value) + } + + useEffect(() => { + if (dailyOutflowRateLte) { + const dailyOutflowRateLteValue = Number(dailyOutflowRateLte) + if (!isNaN(dailyOutflowRateLteValue)) { + const normalizedOutflowRate = ( + (dailyOutflowRateLteValue * 1e18) / + (60 * 60 * 24) + ).toFixed(0) + setPerSecondOutflowRateLte(normalizedOutflowRate) + queryOutflowRateLte(normalizedOutflowRate) + } + } + }, [dailyOutflowRateLte]) + + const getListedStatusFilter = ( + status: ListedStatus | null + ): TokenStatistic_Filter => { + const { token_, ...newFilter } = queryStatisticsArg.filter switch (status) { case ListedStatus.Listed: - return { isListed: true } + return { + token_: { + isListed: true, + name_contains_nocase: token_?.name_contains_nocase, + symbol_contains_nocase: token_?.symbol_contains_nocase + } + } case ListedStatus.NotListed: - return { isListed: false } + return { + token_: { + isListed: false, + name_contains_nocase: token_?.name_contains_nocase, + symbol_contains_nocase: token_?.symbol_contains_nocase + } + } default: return {} } } const changeListedStatus = (newStatus: ListedStatus | null) => { - const { isListed, ...newFilter } = queryArg.filter + const { token_, ...newFilter } = queryStatisticsArg.filter setListedStatus(newStatus) onFilterChange({ @@ -214,17 +411,94 @@ const SuperTokensTable: FC = ({ network }) => { const resetFilter = () => { clearListedStatusFilter() - onFilterChange(defaultFilter) + onFilterChange(defaultStatisticsFilter) closeFilter() } const hasNextPage = !!queriedTokens.data?.nextPaging + // const tokens = queryResult.data?.data || [] + + const tokenStatistics = queryStatisticsResult.data?.data || [] + + const resultsToShow = tokenStatistics.map((stat) => ({ + ...stat, + ...tokens.find((token) => token.id === stat.id) + })) + + const { + filter: statisticsFilter, + order: statisticsOrder, + pagination: statisticsPagination + } = queryStatisticsArg + const { filter, order, pagination } = queryArg const { skip = defaultPaging.skip, take = defaultPaging.take } = queriedTokens.data?.paging || {} + const openExport = () => setShowExportMenu(true) + const closeExport = () => setShowExportMenu(false) + const onExportChange = (event: ChangeEvent) => { + setExportValue((event.target as HTMLInputElement).value) + } + + const jsonToCSV = (jsonData: any[]) => { + const keys = Object.keys(jsonData[0]) + const csvRows = [keys.join(',')] + + jsonData.forEach((item) => { + const values = keys.map((key) => item[key]) + csvRows.push(values.join(',')) + }) + + return csvRows.join('\n') + } + + const transformData = (originalData: any[]) => { + return originalData.map((item) => { + const dailyOutflowRateRaw = + (parseFloat(item.totalOutflowRate) * 60 * 60 * 24) / 1e18 + const dailyOutflowRate = + dailyOutflowRateRaw < 0.01 + ? 0 + : parseFloat(dailyOutflowRateRaw.toFixed(2)) + + return { + name: item.name, + symbol: item.symbol, + isListed: item.isListed, + totalNumberOfActiveStreams: item.totalNumberOfActiveStreams, + dailyOutflowRate: dailyOutflowRate, + address: item.id + } + }) + } + + const exportData = () => { + let blob + let fileName + const dataToExport = transformData(resultsToShow) + if (exportValue === 'json') { + const jsonData = JSON.stringify(dataToExport, null, 2) + blob = new Blob([jsonData], { type: 'application/json' }) + fileName = 'tokens.json' + } else { + const csvData = jsonToCSV(dataToExport) + blob = new Blob([csvData], { type: 'text/csv' }) + fileName = 'tokens.csv' + } + const url = window.URL.createObjectURL(blob) + const a = document.createElement('a') + a.setAttribute('hidden', '') + a.setAttribute('href', url) + a.setAttribute('download', `${fileName}`) + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + closeExport() + } + return ( <> @@ -232,30 +506,40 @@ const SuperTokensTable: FC = ({ network }) => { Super tokens + + + + + + - {filter.name_contains_nocase && ( + {statisticsFilter.token_?.name_contains_nocase && ( Name:{' '} - {filter.name_contains_nocase} + + {statisticsFilter.token_?.name_contains_nocase} + } size="small" - onDelete={clearFilterField('name_contains_nocase')} + onDelete={clearFilterField('token_.name_contains_nocase')} /> )} - {filter.symbol_contains_nocase && ( + {statisticsFilter.token_?.symbol_contains_nocase && ( Symbol:{' '} - {filter.symbol_contains_nocase} + + {statisticsFilter.token_?.symbol_contains_nocase} + } size="small" - onDelete={clearFilterField('symbol_contains_nocase')} + onDelete={clearFilterField('token_.symbol_contains_nocase')} /> )} @@ -273,6 +557,62 @@ const SuperTokensTable: FC = ({ network }) => { onDelete={clearListedStatusFilter} /> )} + + {statisticsFilter.totalNumberOfActiveStreams_gte && ( + + Active Streams ≥{' '} + + {statisticsFilter.totalNumberOfActiveStreams_gte} + + + } + size="small" + onDelete={clearFilterField('totalNumberOfActiveStreams_gte')} + /> + )} + + {statisticsFilter.totalNumberOfActiveStreams_lte && ( + + Active Streams ≤{' '} + + {statisticsFilter.totalNumberOfActiveStreams_lte} + + + } + size="small" + onDelete={clearFilterField('totalNumberOfActiveStreams_lte')} + /> + )} + + {statisticsFilter.totalOutflowRate_gte && ( + + Outflow Rate ≥{' '} + {dailyOutflowRateGte} + + } + size="small" + onDelete={clearFilterField('totalOutflowRate_gte')} + /> + )} + + {statisticsFilter.totalOutflowRate_lte && ( + + Outflow Rate ≤{' '} + {dailyOutflowRateLte} + + } + size="small" + onDelete={clearFilterField('totalOutflowRate_lte')} + /> + )} @@ -281,6 +621,59 @@ const SuperTokensTable: FC = ({ network }) => { + + + + + Export as + + + } label="CSV" /> + } + label="JSON" + /> + + + + + + + + + + = ({ network }) => { autoFocus fullWidth size="small" - value={filter.name_contains_nocase || ''} + value={statisticsFilter.token_?.name_contains_nocase || ''} onChange={onNameChange} endAdornment={ - filter.name_contains_nocase && ( + statisticsFilter.token_?.name_contains_nocase && ( ) } @@ -324,12 +717,14 @@ const SuperTokensTable: FC = ({ network }) => { data-cy={'filter-symbol-input'} fullWidth size="small" - value={filter.symbol_contains_nocase || ''} + value={statisticsFilter.token_?.symbol_contains_nocase || ''} onChange={onSymbolChange} endAdornment={ - filter.symbol_contains_nocase && ( + statisticsFilter.token_?.symbol_contains_nocase && ( ) } @@ -363,18 +758,122 @@ const SuperTokensTable: FC = ({ network }) => { + + + Active Streams + + + <>≤ + + ) + } + /> + + + <>≥ + + ) + } + /> + + + + + + Daily Outflow Rate + + + <>≤ + + ) + } + /> + + + <>≥ + + ) + } + /> + + + - {(filter.name_contains_nocase || - filter.symbol_contains_nocase || + {(statisticsFilter.token_?.name_contains_nocase || + statisticsFilter.token_?.symbol_contains_nocase || listedStatus !== null) && ( - - )} + + )} @@ -387,11 +886,13 @@ const SuperTokensTable: FC = ({ network }) => { Token name @@ -399,11 +900,13 @@ const SuperTokensTable: FC = ({ network }) => { Symbol Listed = ({ network }) => { /> + + + Active Streams + + + {/* Holders */} + + + Daily Outflow Rate + + Address - {tokens.map((token) => ( + {resultsToShow.map((token) => ( + {/* {tokens.find((stat) => stat.id === token.id)?.name || ( + <>— + )} */} {token.name || <>—} @@ -446,7 +981,10 @@ const SuperTokensTable: FC = ({ network }) => { –} + label={ + // } // ) // <>— // tokens.find((stat) => stat.id === token.id)?.symbol || ( // { + token.symbol || <>— + } sx={{ cursor: 'pointer', lineHeight: '24px' @@ -455,8 +993,28 @@ const SuperTokensTable: FC = ({ network }) => { + {/* {tokens.find((stat) => stat.id === token.id)?.isListed + ? 'Yes' + : 'No'} */} {token.isListed ? 'Yes' : 'No'} + + {token.totalNumberOfActiveStreams} + + {/* */} + + {(Number(token.totalOutflowRate) * 60 * 60 * 24) / 1e18 > + 0.01 ? ( + <> + {( + (Number(token.totalOutflowRate) * 60 * 60 * 24) / + 1e18 + ).toFixed(2)} + + ) : ( + <>0 + )} + {token.id} ))} @@ -479,10 +1037,10 @@ const SuperTokensTable: FC = ({ network }) => { /> - {tokens.length > 0 && ( + {resultsToShow.length > 0 && ( - +