Commit e4a5483f authored by tom's avatar tom

mud address tab

parent 67f0e1c2
...@@ -4,12 +4,12 @@ import React from 'react'; ...@@ -4,12 +4,12 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs'; import PageNextJs from 'nextjs/PageNextJs';
// const MudWorlds = dynamic(() => import('ui/pages/MudWorlds'), { ssr: false }); const MudWorlds = dynamic(() => import('ui/pages/MudWorlds'), { ssr: false });
const Page: NextPage = () => { const Page: NextPage = () => {
return ( return (
<PageNextJs pathname="/mud-worlds"> <PageNextJs pathname="/mud-worlds">
{ /* <MudWorlds/> */ } <MudWorlds/>
</PageNextJs> </PageNextJs>
); );
}; };
......
...@@ -5,7 +5,7 @@ import { Skeleton } from './skeleton'; ...@@ -5,7 +5,7 @@ import { Skeleton } from './skeleton';
export interface IconButtonProps extends ButtonProps {} export interface IconButtonProps extends ButtonProps {}
// TODO @tom2drum variants for icon buttons: prev-next, top-bar, copy-to-clipboard // TODO @tom2drum variants for icon buttons: prev-next, top-bar, copy-to-clipboard, filter column
// TODO @tom2drum fix loading state for outlined variant // TODO @tom2drum fix loading state for outlined variant
export const IconButton = React.forwardRef<HTMLDivElement, IconButtonProps>( export const IconButton = React.forwardRef<HTMLDivElement, IconButtonProps>(
......
...@@ -51,6 +51,19 @@ export const PopoverCloseTrigger = React.forwardRef< ...@@ -51,6 +51,19 @@ export const PopoverCloseTrigger = React.forwardRef<
); );
}); });
export const PopoverCloseTriggerWrapper = React.forwardRef<
HTMLButtonElement,
ChakraPopover.CloseTriggerProps
>(function PopoverCloseTriggerWrapper(props, ref) {
return (
<ChakraPopover.CloseTrigger
ref={ ref }
{ ...props }
asChild
/>
);
});
export const PopoverRoot = (props: ChakraPopover.RootProps) => { export const PopoverRoot = (props: ChakraPopover.RootProps) => {
const positioning = { const positioning = {
placement: 'bottom-start' as const, placement: 'bottom-start' as const,
......
...@@ -95,13 +95,13 @@ export default function useContractTabs(data: Address | undefined, isPlaceholder ...@@ -95,13 +95,13 @@ export default function useContractTabs(data: Address | undefined, isPlaceholder
title: 'Custom ABI', title: 'Custom ABI',
component: <ContractMethodsCustom isLoading={ contractQuery.isPlaceholderData }/>, component: <ContractMethodsCustom isLoading={ contractQuery.isPlaceholderData }/>,
}, },
// hasMudTab && { hasMudTab && {
// id: 'mud_system' as const, id: 'mud_system' as const,
// title: 'MUD System', title: 'MUD System',
// component: mudSystemsQuery.isPlaceholderData ? component: mudSystemsQuery.isPlaceholderData ?
// <ContentLoader/> : <ContentLoader/> :
// <ContractMethodsMudSystem items={ mudSystemsQuery.data?.items ?? [] }/>, <ContractMethodsMudSystem items={ mudSystemsQuery.data?.items ?? [] }/>,
// }, },
].filter(Boolean), ].filter(Boolean),
isLoading: contractQuery.isPlaceholderData, isLoading: contractQuery.isPlaceholderData,
}; };
......
import { Box, useColorModeValue, chakra, Grid } from '@chakra-ui/react'; import { Box, chakra, Grid } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import isBrowser from 'lib/isBrowser'; import isBrowser from 'lib/isBrowser';
import { Link } from 'toolkit/chakra/link';
import CopyToClipboard from 'ui/shared/CopyToClipboard'; import CopyToClipboard from 'ui/shared/CopyToClipboard';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import LinkInternal from 'ui/shared/links/LinkInternal';
import useAddressQuery from '../utils/useAddressQuery'; import useAddressQuery from '../utils/useAddressQuery';
...@@ -17,9 +17,11 @@ type TableViewProps = { ...@@ -17,9 +17,11 @@ type TableViewProps = {
hash: string; hash: string;
tableId: string; tableId: string;
tableName: string; tableName: string;
recordId?: never;
recordName?: never;
}; };
type RecordViewProps = TableViewProps & { type RecordViewProps = Omit<TableViewProps, 'recordId' | 'recordName'> & {
recordId: string; recordId: string;
recordName: string; recordName: string;
}; };
...@@ -32,8 +34,6 @@ type BreadcrumbItemProps = { ...@@ -32,8 +34,6 @@ type BreadcrumbItemProps = {
}; };
const BreadcrumbItem = ({ text, href, isLast, scrollRef }: BreadcrumbItemProps) => { const BreadcrumbItem = ({ text, href, isLast, scrollRef }: BreadcrumbItemProps) => {
const iconColor = useColorModeValue('gray.300', 'gray.600');
const currentUrl = isBrowser() ? window.location.href : ''; const currentUrl = isBrowser() ? window.location.href : '';
const onLinkClick = React.useCallback(() => { const onLinkClick = React.useCallback(() => {
...@@ -60,7 +60,7 @@ const BreadcrumbItem = ({ text, href, isLast, scrollRef }: BreadcrumbItemProps) ...@@ -60,7 +60,7 @@ const BreadcrumbItem = ({ text, href, isLast, scrollRef }: BreadcrumbItemProps)
return ( return (
<Grid gap={ 2 } overflow="hidden" templateColumns="auto 24px" alignItems="center"> <Grid gap={ 2 } overflow="hidden" templateColumns="auto 24px" alignItems="center">
<LinkInternal <Link
href={ href } href={ href }
onClick={ onLinkClick } onClick={ onLinkClick }
overflow="hidden" overflow="hidden"
...@@ -68,8 +68,8 @@ const BreadcrumbItem = ({ text, href, isLast, scrollRef }: BreadcrumbItemProps) ...@@ -68,8 +68,8 @@ const BreadcrumbItem = ({ text, href, isLast, scrollRef }: BreadcrumbItemProps)
whiteSpace="nowrap" whiteSpace="nowrap"
> >
{ text } { text }
</LinkInternal> </Link>
{ !isLast && <IconSvg name="arrows/east" boxSize={ 6 } color={ iconColor }/> } { !isLast && <IconSvg name="arrows/east" boxSize={ 6 } color={{ _light: 'gray.300', _dark: 'gray.600' }}/> }
</Grid> </Grid>
); );
}; };
...@@ -103,7 +103,7 @@ const AddressMudBreadcrumbs = (props: TableViewProps | RecordViewProps) => { ...@@ -103,7 +103,7 @@ const AddressMudBreadcrumbs = (props: TableViewProps | RecordViewProps) => {
isLast={ !('recordId' in props) } isLast={ !('recordId' in props) }
scrollRef={ props.scrollRef } scrollRef={ props.scrollRef }
/> />
{ ('recordId' in props) && ( { ('recordId' in props && typeof props.recordId === 'string') && ('recordName' in props && typeof props.recordName === 'string') && (
<BreadcrumbItem <BreadcrumbItem
text={ props.recordName } text={ props.recordName }
href={ route({ pathname: '/address/[hash]', query: { ...queryParams, table_id: props.tableId, record_id: props.recordId } }) } href={ route({ pathname: '/address/[hash]', query: { ...queryParams, table_id: props.tableId, record_id: props.recordId } }) }
......
import { Box, Td, Tr, Flex, Text, Table, Show, Hide, Divider, VStack } from '@chakra-ui/react'; import { Box, Flex, Separator, Text, VStack } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import { TableRoot, TableRow, TableCell } from 'toolkit/chakra/table';
import ContentLoader from 'ui/shared/ContentLoader'; import ContentLoader from 'ui/shared/ContentLoader';
import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DataFetchAlert from 'ui/shared/DataFetchAlert';
import TruncatedValue from 'ui/shared/TruncatedValue'; import TruncatedValue from 'ui/shared/TruncatedValue';
...@@ -53,29 +54,29 @@ const AddressMudRecord = ({ tableId, recordId, isQueryEnabled = true, scrollRef ...@@ -53,29 +54,29 @@ const AddressMudRecord = ({ tableId, recordId, isQueryEnabled = true, scrollRef
scrollRef={ scrollRef } scrollRef={ scrollRef }
/> />
) } ) }
<Show above="lg" ssr={ false }> <Box hideBelow="lg">
<Table borderRadius="8px" style={{ tableLayout: 'auto' }} width="100%" overflow="hidden"> <TableRoot borderRadius="8px" style={{ tableLayout: 'auto' }} width="100%" overflow="hidden">
{ data?.schema.key_names.length && data?.schema.key_names.map((keyName, index) => ( { data?.schema.key_names.length && data?.schema.key_names.map((keyName, index) => (
<Tr key={ keyName } borderBottomStyle={ index === data.schema.key_names.length - 1 ? 'hidden' : 'solid' }> <TableRow key={ keyName } borderBottomStyle={ index === data.schema.key_names.length - 1 ? 'hidden' : 'solid' }>
<Td fontWeight={ 600 } whiteSpace="nowrap" fontSize="sm"> <TableCell fontWeight={ 600 } whiteSpace="nowrap" fontSize="sm">
{ keyName } ({ data.schema.key_types[index] }) { keyName } ({ data.schema.key_types[index] })
</Td> </TableCell>
<Td colSpan={ 2 } fontSize="sm"> <TableCell colSpan={ 2 } fontSize="sm">
<Flex justifyContent="space-between"> <Flex justifyContent="space-between">
<TruncatedValue value={ getValueString(data.record.decoded[keyName]) } mr={ 2 }/> <TruncatedValue value={ getValueString(data.record.decoded[keyName]) } mr={ 2 }/>
{ index === 0 && <Box color="text_secondary">{ dayjs(data.record.timestamp).format('lll') }</Box> } { index === 0 && <Box color="text_secondary">{ dayjs(data.record.timestamp).format('lll') }</Box> }
</Flex> </Flex>
</Td> </TableCell>
</Tr> </TableRow>
)) } )) }
<AddressMudRecordValues data={ data }/> <AddressMudRecordValues data={ data }/>
</Table> </TableRoot>
</Show> </Box>
<Hide above="lg" ssr={ false }> <Box hideFrom="lg">
<> <>
{ data?.schema.key_names.length && data?.schema.key_names.map((keyName, index) => ( { data?.schema.key_names.length && data?.schema.key_names.map((keyName, index) => (
<VStack gap={ 1 } key={ keyName } alignItems="start" fontSize="sm"> <VStack gap={ 1 } key={ keyName } alignItems="start" fontSize="sm">
<Divider/> <Separator/>
<Text fontWeight={ 600 } whiteSpace="nowrap"> <Text fontWeight={ 600 } whiteSpace="nowrap">
{ keyName } ({ data.schema.key_types[index] }) { keyName } ({ data.schema.key_types[index] })
</Text> </Text>
...@@ -83,11 +84,11 @@ const AddressMudRecord = ({ tableId, recordId, isQueryEnabled = true, scrollRef ...@@ -83,11 +84,11 @@ const AddressMudRecord = ({ tableId, recordId, isQueryEnabled = true, scrollRef
{ index === 0 && <Box color="text_secondary">{ dayjs(data.record.timestamp).format('lll') }</Box> } { index === 0 && <Box color="text_secondary">{ dayjs(data.record.timestamp).format('lll') }</Box> }
</VStack> </VStack>
)) } )) }
<Table borderRadius="8px" style={{ tableLayout: 'auto' }} width="100%" mt={ 2 } overflow="hidden"> <TableRoot borderRadius="8px" style={{ tableLayout: 'auto' }} width="100%" mt={ 2 } overflow="hidden">
<AddressMudRecordValues data={ data }/> <AddressMudRecordValues data={ data }/>
</Table> </TableRoot>
</> </>
</Hide> </Box>
</> </>
); );
}; };
......
import { Box, Td, Tr, useColorModeValue } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { AddressMudRecord } from 'types/api/address'; import type { AddressMudRecord } from 'types/api/address';
import { TableCell, TableRow } from 'toolkit/chakra/table';
import { getValueString } from './utils'; import { getValueString } from './utils';
type Props = { type Props = {
...@@ -10,7 +12,7 @@ type Props = { ...@@ -10,7 +12,7 @@ type Props = {
}; };
const AddressMudRecordValues = ({ data }: Props) => { const AddressMudRecordValues = ({ data }: Props) => {
const valuesBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); const valuesBgColor = { _light: 'blackAlpha.50', _dark: 'whiteAlpha.50' };
if (!data?.schema.value_names.length) { if (!data?.schema.value_names.length) {
return null; return null;
...@@ -18,22 +20,22 @@ const AddressMudRecordValues = ({ data }: Props) => { ...@@ -18,22 +20,22 @@ const AddressMudRecordValues = ({ data }: Props) => {
return ( return (
<> <>
<Tr backgroundColor={ valuesBgColor } borderBottomStyle="hidden" > <TableRow backgroundColor={ valuesBgColor } borderBottomStyle="hidden" >
<Td fontWeight={ 600 } w="100px" fontSize="sm">Field</Td> <TableCell fontWeight={ 600 } w="100px" fontSize="sm">Field</TableCell>
<Td fontWeight={ 600 } w="90px" fontSize="sm">Type</Td> <TableCell fontWeight={ 600 } w="90px" fontSize="sm">Type</TableCell>
<Td fontWeight={ 600 } fontSize="sm">Value</Td> <TableCell fontWeight={ 600 } fontSize="sm">Value</TableCell>
</Tr> </TableRow>
{ {
data?.schema.value_names.map((valName, index) => ( data?.schema.value_names.map((valName, index) => (
<Tr key={ valName } backgroundColor={ valuesBgColor } borderBottomStyle="hidden"> <TableRow key={ valName } backgroundColor={ valuesBgColor } borderBottomStyle="hidden">
<Td fontWeight={ 400 } w="100px" py={ 0 } pb={ 4 } pr={ 0 }wordBreak="break-all">{ valName }</Td> <TableCell fontWeight={ 400 } w="100px" py={ 0 } pb={ 4 } pr={ 0 } wordBreak="break-all">{ valName }</TableCell>
<Td fontWeight={ 400 } w="90px" py={ 0 } pb={ 4 } wordBreak="break-all">{ data.schema.value_types[index] }</Td> <TableCell fontWeight={ 400 } w="90px" py={ 0 } pb={ 4 } wordBreak="break-all">{ data.schema.value_types[index] }</TableCell>
<Td fontWeight={ 400 } wordBreak="break-word" py={ 0 } pb={ 4 }> <TableCell fontWeight={ 400 } wordBreak="break-word" py={ 0 } pb={ 4 }>
<Box> <Box>
{ getValueString(data.record.decoded[valName]) } { getValueString(data.record.decoded[valName]) }
</Box> </Box>
</Td> </TableCell>
</Tr> </TableRow>
)) ))
} }
</> </>
......
...@@ -16,7 +16,7 @@ const AddressMudRecordsKeyFilter = ({ value = '', handleFilterChange, columnName ...@@ -16,7 +16,7 @@ const AddressMudRecordsKeyFilter = ({ value = '', handleFilterChange, columnName
return ( return (
<TableColumnFilterWrapper <TableColumnFilterWrapper
columnName={ columnName } columnName={ columnName }
isActive={ Boolean(value) } selected={ Boolean(value) }
isLoading={ isLoading } isLoading={ isLoading }
w="350px" w="350px"
> >
......
...@@ -8,10 +8,9 @@ type Props = { ...@@ -8,10 +8,9 @@ type Props = {
handleFilterChange: (val: string) => void; handleFilterChange: (val: string) => void;
title: string; title: string;
columnName: string; columnName: string;
onClose?: () => void;
}; };
const AddressMudRecordsKeyFilter = ({ value = '', handleFilterChange, columnName, title, onClose }: Props) => { const AddressMudRecordsKeyFilterContent = ({ value = '', handleFilterChange, columnName, title }: Props) => {
const [ filterValue, setFilterValue ] = React.useState<string>(value); const [ filterValue, setFilterValue ] = React.useState<string>(value);
const onFilter = React.useCallback(() => { const onFilter = React.useCallback(() => {
...@@ -23,11 +22,10 @@ const AddressMudRecordsKeyFilter = ({ value = '', handleFilterChange, columnName ...@@ -23,11 +22,10 @@ const AddressMudRecordsKeyFilter = ({ value = '', handleFilterChange, columnName
title={ title } title={ title }
isFilled={ filterValue !== value } isFilled={ filterValue !== value }
onFilter={ onFilter } onFilter={ onFilter }
onClose={ onClose }
> >
<FilterInput <FilterInput
initialValue={ value } initialValue={ value }
size="xs" size="sm"
onChange={ setFilterValue } onChange={ setFilterValue }
placeholder={ columnName } placeholder={ columnName }
/> />
...@@ -35,4 +33,4 @@ const AddressMudRecordsKeyFilter = ({ value = '', handleFilterChange, columnName ...@@ -35,4 +33,4 @@ const AddressMudRecordsKeyFilter = ({ value = '', handleFilterChange, columnName
); );
}; };
export default AddressMudRecordsKeyFilter; export default AddressMudRecordsKeyFilterContent;
import type { StyleProps } from '@chakra-ui/react'; import { Box, Flex } from '@chakra-ui/react';
import { Box, Link, Table, Tbody, Td, Th, Tr, Flex, useColorModeValue, useBoolean, Tooltip } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
...@@ -10,10 +9,13 @@ import { route } from 'nextjs-routes'; ...@@ -10,10 +9,13 @@ import { route } from 'nextjs-routes';
import capitalizeFirstLetter from 'lib/capitalizeFirstLetter'; import capitalizeFirstLetter from 'lib/capitalizeFirstLetter';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import { middot } from 'lib/html-entities';
import { Link } from 'toolkit/chakra/link';
import { TableBody, TableCell, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
import type { TableColumnHeaderProps } from 'toolkit/chakra/table';
import { Tooltip } from 'toolkit/chakra/tooltip';
import CopyToClipboard from 'ui/shared/CopyToClipboard'; import CopyToClipboard from 'ui/shared/CopyToClipboard';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import LinkInternal from 'ui/shared/links/LinkInternal';
import { default as Thead } from 'ui/shared/TheadSticky';
import AddressMudRecordsKeyFilter from './AddressMudRecordsKeyFilter'; import AddressMudRecordsKeyFilter from './AddressMudRecordsKeyFilter';
import { getNameTypeText, getValueString } from './utils'; import { getNameTypeText, getValueString } from './utils';
...@@ -49,8 +51,8 @@ const AddressMudRecordsTable = ({ ...@@ -49,8 +51,8 @@ const AddressMudRecordsTable = ({
const totalColsCut = data.schema.key_names.length + data.schema.value_names.length; const totalColsCut = data.schema.key_names.length + data.schema.value_names.length;
const isMobile = useIsMobile(false); const isMobile = useIsMobile(false);
const [ colsCutCount, setColsCutCount ] = React.useState<number>(isMobile ? MIN_CUT_COUNT : 0); const [ colsCutCount, setColsCutCount ] = React.useState<number>(isMobile ? MIN_CUT_COUNT : 0);
const [ isOpened, setIsOpened ] = useBoolean(false); const [ isOpened, setIsOpened ] = React.useState(false);
const [ hasCut, setHasCut ] = useBoolean(isMobile ? totalColsCut > MIN_CUT_COUNT : true); const [ hasCut, setHasCut ] = React.useState(isMobile ? totalColsCut > MIN_CUT_COUNT : true);
const containerRef = React.useRef<HTMLTableElement>(null); const containerRef = React.useRef<HTMLTableElement>(null);
const tableRef = React.useRef<HTMLTableElement>(null); const tableRef = React.useRef<HTMLTableElement>(null);
...@@ -59,7 +61,7 @@ const AddressMudRecordsTable = ({ ...@@ -59,7 +61,7 @@ const AddressMudRecordsTable = ({
const toggleIsOpen = React.useCallback(() => { const toggleIsOpen = React.useCallback(() => {
isOpened && tableRef.current?.scroll({ left: 0 }); isOpened && tableRef.current?.scroll({ left: 0 });
setIsOpened.toggle(); setIsOpened((prev) => !prev);
toggleTableHasHorizontalScroll(); toggleTableHasHorizontalScroll();
}, [ setIsOpened, toggleTableHasHorizontalScroll, isOpened ]); }, [ setIsOpened, toggleTableHasHorizontalScroll, isOpened ]);
...@@ -95,15 +97,13 @@ const AddressMudRecordsTable = ({ ...@@ -95,15 +97,13 @@ const AddressMudRecordsTable = ({
[ toggleSorting ], [ toggleSorting ],
); );
const keyBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
React.useEffect(() => { React.useEffect(() => {
if (hasCut && !colsCutCount && containerRef.current) { if (hasCut && !colsCutCount && containerRef.current) {
const count = Math.floor((containerRef.current.getBoundingClientRect().width - CUT_COL_WIDTH) / COL_MIN_WIDTH); const count = Math.floor((containerRef.current.getBoundingClientRect().width - CUT_COL_WIDTH) / COL_MIN_WIDTH);
if (totalColsCut > MIN_CUT_COUNT && count - 1 < totalColsCut) { if (totalColsCut > MIN_CUT_COUNT && count - 1 < totalColsCut) {
setColsCutCount(count - 1); setColsCutCount(count - 1);
} else { } else {
setHasCut.off(); setHasCut(false);
} }
} }
}, [ colsCutCount, data.schema, hasCut, setHasCut, totalColsCut ]); }, [ colsCutCount, data.schema, hasCut, setHasCut, totalColsCut ]);
...@@ -114,7 +114,7 @@ const AddressMudRecordsTable = ({ ...@@ -114,7 +114,7 @@ const AddressMudRecordsTable = ({
const values = (isOpened || !hasCut) ? data.schema.value_names : data.schema.value_names.slice(0, colsCutCount - data.schema.key_names.length); const values = (isOpened || !hasCut) ? data.schema.value_names : data.schema.value_names.slice(0, colsCutCount - data.schema.key_names.length);
const colsCount = (isOpened || !hasCut) ? totalColsCut : colsCutCount; const colsCount = (isOpened || !hasCut) ? totalColsCut : colsCutCount;
const tdStyles: StyleProps = { const tdStyles: TableColumnHeaderProps = {
wordBreak: 'break-word', wordBreak: 'break-word',
whiteSpace: 'normal', whiteSpace: 'normal',
minW: `${ colW }px`, minW: `${ colW }px`,
...@@ -130,23 +130,23 @@ const AddressMudRecordsTable = ({ ...@@ -130,23 +130,23 @@ const AddressMudRecordsTable = ({
} }
const cutButton = ( const cutButton = (
<Th width={ `${ CUT_COL_WIDTH }px ` } verticalAlign="baseline"> <TableColumnHeader width={ `${ CUT_COL_WIDTH }px ` } verticalAlign="baseline">
<Tooltip label={ isOpened ? 'Hide columns' : 'Show all columns' }> <Tooltip content={ isOpened ? 'Hide columns' : 'Show all columns' }>
<Link onClick={ toggleIsOpen } aria-label="show/hide columns">...</Link> <Link onClick={ toggleIsOpen } aria-label="show/hide columns">{ middot }{ middot }{ middot }</Link>
</Tooltip> </Tooltip>
</Th> </TableColumnHeader>
); );
return ( return (
// can't implement both horizontal table scroll and sticky header // can't implement both horizontal table scroll and sticky header
<Box maxW="100%" overflowX={ hasHorizontalScroll ? 'scroll' : 'unset' } whiteSpace="nowrap" ref={ tableRef }> <Box maxW="100%" overflowX={ hasHorizontalScroll ? 'scroll' : 'unset' } whiteSpace="nowrap" ref={ tableRef }>
<Table style={{ tableLayout: 'fixed' }}> <TableRoot style={{ tableLayout: 'fixed' }}>
<Thead top={ hasHorizontalScroll ? 0 : top } display={ hasHorizontalScroll ? 'table' : 'table-header-group' } w="100%"> <TableHeaderSticky top={ hasHorizontalScroll ? 0 : top } display={ hasHorizontalScroll ? 'table' : 'table-header-group' } w="100%">
<Tr > <TableRow>
{ keys.map((keyName, index) => { { keys.map((keyName, index) => {
const text = getNameTypeText(keyName, data.schema.key_types[index]); const text = getNameTypeText(keyName, data.schema.key_types[index]);
return ( return (
<Th key={ keyName } { ...tdStyles }> <TableColumnHeader key={ keyName } { ...tdStyles }>
{ index < 2 ? ( { index < 2 ? (
<Flex alignItems="center"> <Flex alignItems="center">
<Link <Link
...@@ -178,46 +178,47 @@ const AddressMudRecordsTable = ({ ...@@ -178,46 +178,47 @@ const AddressMudRecordsTable = ({
</Box> </Box>
</Flex> </Flex>
) : text } ) : text }
</Th> </TableColumnHeader>
); );
}) } }) }
{ values.map((valName, index) => ( { values.map((valName, index) => (
<Th key={ valName } { ...tdStyles }> <TableColumnHeader key={ valName } { ...tdStyles }>
{ capitalizeFirstLetter(valName) } ({ data.schema.value_types[index] }) { capitalizeFirstLetter(valName) } ({ data.schema.value_types[index] })
</Th> </TableColumnHeader>
)) } )) }
{ hasCut && !isOpened && cutButton } { hasCut && !isOpened && cutButton }
<Th { ...tdStyles } w={ `${ colW }px` }>Modified</Th> <TableColumnHeader { ...tdStyles } w={ `${ colW }px` }>Modified</TableColumnHeader>
{ hasCut && isOpened && cutButton } { hasCut && isOpened && cutButton }
</Tr> </TableRow>
</Thead> </TableHeaderSticky>
<Tbody display={ hasHorizontalScroll ? 'table' : 'table-row-group' } w="100%"> <TableBody display={ hasHorizontalScroll ? 'table' : 'table-row-group' } w="100%">
{ data.items.map((item) => ( { data.items.map((item) => (
<Tr key={ item.id }> <TableRow key={ item.id }>
{ keys.map((keyName, index) => ( { keys.map((keyName, index) => (
<Td key={ keyName } backgroundColor={ keyBgColor } { ...tdStyles }> <TableCell key={ keyName } backgroundColor={{ _light: 'blackAlpha.50', _dark: 'whiteAlpha.50' }} { ...tdStyles }>
{ index === 0 ? ( { index === 0 ? (
<LinkInternal <Link
onClick={ onRecordClick } onClick={ onRecordClick }
data-id={ item.id } data-id={ item.id }
fontWeight={ 700 } fontWeight={ 700 }
href={ route({ pathname: '/address/[hash]', query: { hash, tab: 'mud', table_id: data.table.table_id, record_id: item.id } }) } href={ route({ pathname: '/address/[hash]', query: { hash, tab: 'mud', table_id: data.table.table_id, record_id: item.id } }) }
display="inline"
> >
{ getValueString(item.decoded[keyName]) } { getValueString(item.decoded[keyName]) }
</LinkInternal> </Link>
) : getValueString(item.decoded[keyName]) } ) : getValueString(item.decoded[keyName]) }
<CopyToClipboard text={ item.decoded[keyName] }/> <CopyToClipboard text={ String(item.decoded[keyName]) }/>
</Td> </TableCell>
)) } )) }
{ values.map((valName) => { values.map((valName) =>
<Td key={ valName } { ...tdStyles }>{ getValueString(item.decoded[valName]) }</Td>) } <TableCell key={ valName } { ...tdStyles }>{ getValueString(item.decoded[valName]) }</TableCell>) }
{ hasCut && !isOpened && <Td width={ `${ CUT_COL_WIDTH }px ` }></Td> } { hasCut && !isOpened && <TableCell width={ `${ CUT_COL_WIDTH }px ` }></TableCell> }
<Td { ...tdStyles } color="text_secondary" w={ `${ colW }px` }>{ dayjs(item.timestamp).format('lll') }</Td> <TableCell { ...tdStyles } color="text.secondary" w={ `${ colW }px` }>{ dayjs(item.timestamp).format('lll') }</TableCell>
{ hasCut && isOpened && <Td width={ `${ CUT_COL_WIDTH }px ` }></Td> } { hasCut && isOpened && <TableCell width={ `${ CUT_COL_WIDTH }px ` }></TableCell> }
</Tr> </TableRow>
)) } )) }
</Tbody> </TableBody>
</Table> </TableRoot>
</Box> </Box>
); );
}; };
......
import { Box, HStack, Tag, TagCloseButton, chakra, useBoolean } from '@chakra-ui/react'; import { Box, HStack, chakra } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
...@@ -7,6 +7,7 @@ import type { AddressMudRecordsFilter, AddressMudRecordsSorting } from 'types/ap ...@@ -7,6 +7,7 @@ import type { AddressMudRecordsFilter, AddressMudRecordsSorting } from 'types/ap
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import { apos, nbsp } from 'lib/html-entities'; import { apos, nbsp } from 'lib/html-entities';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import { Tag } from 'toolkit/chakra/tag';
import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar';
import ContentLoader from 'ui/shared/ContentLoader'; import ContentLoader from 'ui/shared/ContentLoader';
import DataListDisplay from 'ui/shared/DataListDisplay'; import DataListDisplay from 'ui/shared/DataListDisplay';
...@@ -36,7 +37,7 @@ const AddressMudTable = ({ scrollRef, tableId, isQueryEnabled = true }: Props) = ...@@ -36,7 +37,7 @@ const AddressMudTable = ({ scrollRef, tableId, isQueryEnabled = true }: Props) =
React.useState<AddressMudRecordsSorting | undefined>(getSortParamsFromQuery<AddressMudRecordsSorting>(router.query, SORT_SEQUENCE)); React.useState<AddressMudRecordsSorting | undefined>(getSortParamsFromQuery<AddressMudRecordsSorting>(router.query, SORT_SEQUENCE));
const [ filters, setFilters ] = React.useState<AddressMudRecordsFilter>({}); const [ filters, setFilters ] = React.useState<AddressMudRecordsFilter>({});
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const [ tableHasHorizontalScroll, setTableHasHorizontalScroll ] = useBoolean(isMobile); const [ tableHasHorizontalScroll, setTableHasHorizontalScroll ] = React.useState(isMobile);
const hash = getQueryParamString(router.query.hash); const hash = getQueryParamString(router.query.hash);
...@@ -52,6 +53,10 @@ const AddressMudTable = ({ scrollRef, tableId, isQueryEnabled = true }: Props) = ...@@ -52,6 +53,10 @@ const AddressMudTable = ({ scrollRef, tableId, isQueryEnabled = true }: Props) =
}, },
}); });
const handleTableHasHorizontalScroll = React.useCallback(() => {
setTableHasHorizontalScroll((prev) => !prev);
}, []);
const toggleSorting = React.useCallback((val: AddressMudRecordsSorting['sort']) => { const toggleSorting = React.useCallback((val: AddressMudRecordsSorting['sort']) => {
const newSorting = { sort: val, order: getNextOrderValue(sorting?.sort === val ? sorting.order : undefined) }; const newSorting = { sort: val, order: getNextOrderValue(sorting?.sort === val ? sorting.order : undefined) };
setSorting(newSorting); setSorting(newSorting);
...@@ -83,15 +88,22 @@ const AddressMudTable = ({ scrollRef, tableId, isQueryEnabled = true }: Props) = ...@@ -83,15 +88,22 @@ const AddressMudTable = ({ scrollRef, tableId, isQueryEnabled = true }: Props) =
{ Object.entries(filters).map(([ key, value ]) => { { Object.entries(filters).map(([ key, value ]) => {
const index = key as FilterKeys === 'filter_key0' ? 0 : 1; const index = key as FilterKeys === 'filter_key0' ? 0 : 1;
return ( return (
<Tag display="inline-flex" key={ key } maxW="360px" colorScheme="blue"> <Tag
<chakra.span color="text_secondary" >{ display="inline-flex"
key={ key }
maxW="360px"
// TODO @tom2drum style filter tags
colorScheme="blue"
closable
onClose={ onRemoveFilterClick(key as FilterKeys) }
>
<chakra.span color="text.secondary" >{
getNameTypeText(data?.schema.key_names[index] || '', data?.schema.key_types[index] || '') } getNameTypeText(data?.schema.key_names[index] || '', data?.schema.key_types[index] || '') }
</chakra.span> </chakra.span>
<chakra.span color="text" overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap"> <chakra.span color="text" overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap">
{ nbsp } { nbsp }
{ value } { value }
</chakra.span> </chakra.span>
<TagCloseButton onClick={ onRemoveFilterClick(key as FilterKeys) }/>
</Tag> </Tag>
); );
}) } }) }
...@@ -126,7 +138,7 @@ const AddressMudTable = ({ scrollRef, tableId, isQueryEnabled = true }: Props) = ...@@ -126,7 +138,7 @@ const AddressMudTable = ({ scrollRef, tableId, isQueryEnabled = true }: Props) =
toggleSorting={ toggleSorting } toggleSorting={ toggleSorting }
setFilters={ setFilters } setFilters={ setFilters }
filters={ filters } filters={ filters }
toggleTableHasHorizontalScroll={ setTableHasHorizontalScroll.toggle } toggleTableHasHorizontalScroll={ handleTableHasHorizontalScroll }
scrollRef={ scrollRef } scrollRef={ scrollRef }
hash={ hash } hash={ hash }
/> />
...@@ -146,17 +158,18 @@ const AddressMudTable = ({ scrollRef, tableId, isQueryEnabled = true }: Props) = ...@@ -146,17 +158,18 @@ const AddressMudTable = ({ scrollRef, tableId, isQueryEnabled = true }: Props) =
) } ) }
<DataListDisplay <DataListDisplay
isError={ isError } isError={ isError }
items={ data?.items } itemsNum={ data?.items.length }
emptyText={ emptyText } emptyText={ emptyText }
filterProps={{ filterProps={{
emptyFilteredText: `Couldn${ apos }t find records that match your filter query.`, emptyFilteredText: `Couldn${ apos }t find records that match your filter query.`,
hasActiveFilters: Object.values(filters).some(Boolean), hasActiveFilters: Object.values(filters).some(Boolean),
}} }}
content={ content }
actionBar={ actionBar } actionBar={ actionBar }
showActionBarIfEmpty={ !isMobile } showActionBarIfEmpty={ !isMobile }
mt={ data?.items.length ? 0 : 2 } mt={ data?.items.length ? 0 : 2 }
/> >
{ content }
</DataListDisplay>
</> </>
); );
}; };
......
import { Hide, Show } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
...@@ -50,11 +50,11 @@ const AddressMudTables = ({ scrollRef, isQueryEnabled = true }: Props) => { ...@@ -50,11 +50,11 @@ const AddressMudTables = ({ scrollRef, isQueryEnabled = true }: Props) => {
<FilterInput <FilterInput
w={{ base: '100%', lg: '360px' }} w={{ base: '100%', lg: '360px' }}
minW={{ base: 'auto', lg: '250px' }} minW={{ base: 'auto', lg: '250px' }}
size="xs" size="sm"
onChange={ setSearchTerm } onChange={ setSearchTerm }
placeholder="Search by name, namespace or table ID..." placeholder="Search by name, namespace or table ID..."
initialValue={ searchTerm } initialValue={ searchTerm }
isLoading={ isInitialLoading } loading={ isInitialLoading }
/> />
); );
...@@ -67,7 +67,7 @@ const AddressMudTables = ({ scrollRef, isQueryEnabled = true }: Props) => { ...@@ -67,7 +67,7 @@ const AddressMudTables = ({ scrollRef, isQueryEnabled = true }: Props) => {
const content = data?.items ? ( const content = data?.items ? (
<> <>
<Hide below="lg" ssr={ false }> <Box hideBelow="lg">
<AddressMudTablesTable <AddressMudTablesTable
items={ data.items } items={ data.items }
isLoading={ isPlaceholderData } isLoading={ isPlaceholderData }
...@@ -75,8 +75,8 @@ const AddressMudTables = ({ scrollRef, isQueryEnabled = true }: Props) => { ...@@ -75,8 +75,8 @@ const AddressMudTables = ({ scrollRef, isQueryEnabled = true }: Props) => {
scrollRef={ scrollRef } scrollRef={ scrollRef }
hash={ hash } hash={ hash }
/> />
</Hide> </Box>
<Show below="lg" ssr={ false }> <Box hideFrom="lg">
{ data.items.map((item, index) => ( { data.items.map((item, index) => (
<AddressMudTablesListItem <AddressMudTablesListItem
key={ item.table.table_id + (isPlaceholderData ? String(index) : '') } key={ item.table.table_id + (isPlaceholderData ? String(index) : '') }
...@@ -85,22 +85,23 @@ const AddressMudTables = ({ scrollRef, isQueryEnabled = true }: Props) => { ...@@ -85,22 +85,23 @@ const AddressMudTables = ({ scrollRef, isQueryEnabled = true }: Props) => {
hash={ hash } hash={ hash }
/> />
)) } )) }
</Show> </Box>
</> </>
) : null; ) : null;
return ( return (
<DataListDisplay <DataListDisplay
isError={ isError } isError={ isError }
items={ data?.items } itemsNum={ data?.items?.length }
emptyText="There are no tables for this address." emptyText="There are no tables for this address."
filterProps={{ filterProps={{
emptyFilteredText: `Couldn${ apos }t find tables that match your filter query.`, emptyFilteredText: `Couldn${ apos }t find tables that match your filter query.`,
hasActiveFilters: Boolean(searchTerm), hasActiveFilters: Boolean(searchTerm),
}} }}
content={ content }
actionBar={ actionBar } actionBar={ actionBar }
/> >
{ content }
</DataListDisplay>
); );
}; };
......
import { Divider, Text, useBoolean, Flex, Link, VStack, chakra, Box, Grid, GridItem } from '@chakra-ui/react'; import { Text, Flex, VStack, chakra, Box, Grid, GridItem, Separator } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
...@@ -6,11 +6,11 @@ import type { AddressMudTableItem } from 'types/api/address'; ...@@ -6,11 +6,11 @@ import type { AddressMudTableItem } from 'types/api/address';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import Skeleton from 'ui/shared/chakra/Skeleton'; import { Badge } from 'toolkit/chakra/badge';
import Tag from 'ui/shared/chakra/Tag'; import { Link } from 'toolkit/chakra/link';
import { Skeleton } from 'toolkit/chakra/skeleton';
import HashStringShorten from 'ui/shared/HashStringShorten'; import HashStringShorten from 'ui/shared/HashStringShorten';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import LinkInternal from 'ui/shared/links/LinkInternal';
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
type Props = { type Props = {
...@@ -21,10 +21,14 @@ type Props = { ...@@ -21,10 +21,14 @@ type Props = {
}; };
const AddressMudTablesListItem = ({ item, isLoading, scrollRef, hash }: Props) => { const AddressMudTablesListItem = ({ item, isLoading, scrollRef, hash }: Props) => {
const [ isOpened, setIsOpened ] = useBoolean(false); const [ isOpened, setIsOpened ] = React.useState(false);
const router = useRouter(); const router = useRouter();
const handleIconClick = React.useCallback(() => {
setIsOpened((prev) => !prev);
}, []);
const onTableClick = React.useCallback((e: React.MouseEvent) => { const onTableClick = React.useCallback((e: React.MouseEvent) => {
if (e.metaKey || e.ctrlKey) { if (e.metaKey || e.ctrlKey) {
// Allow opening in a new tab/window with right-click or ctrl/cmd+click // Allow opening in a new tab/window with right-click or ctrl/cmd+click
...@@ -48,14 +52,14 @@ const AddressMudTablesListItem = ({ item, isLoading, scrollRef, hash }: Props) = ...@@ -48,14 +52,14 @@ const AddressMudTablesListItem = ({ item, isLoading, scrollRef, hash }: Props) =
return ( return (
<ListItemMobile rowGap={ 3 } fontSize="sm" py={ 3 }> <ListItemMobile rowGap={ 3 } fontSize="sm" py={ 3 }>
<Flex w="100%"> <Flex w="100%">
<Skeleton isLoaded={ !isLoading }> <Skeleton loading={ isLoading }>
<Link display="block"> <Link display="block">
<IconSvg <IconSvg
name="arrows/east-mini" name="arrows/east-mini"
transform={ isOpened ? 'rotate(270deg)' : 'rotate(180deg)' } transform={ isOpened ? 'rotate(270deg)' : 'rotate(180deg)' }
boxSize={ 6 } boxSize={ 6 }
cursor="pointer" cursor="pointer"
onClick={ setIsOpened.toggle } onClick={ handleIconClick }
transitionDuration="faster" transitionDuration="faster"
aria-label="View schema" aria-label="View schema"
/> />
...@@ -63,21 +67,21 @@ const AddressMudTablesListItem = ({ item, isLoading, scrollRef, hash }: Props) = ...@@ -63,21 +67,21 @@ const AddressMudTablesListItem = ({ item, isLoading, scrollRef, hash }: Props) =
</Skeleton> </Skeleton>
<Box flexGrow="1"> <Box flexGrow="1">
<Flex justifyContent="space-between" height={ 6 } alignItems="center" mb={ 3 }> <Flex justifyContent="space-between" height={ 6 } alignItems="center" mb={ 3 }>
<Skeleton isLoaded={ !isLoading }> <Skeleton loading={ isLoading }>
<LinkInternal <Link
onClick={ onTableClick } onClick={ onTableClick }
data-id={ item.table.table_id } data-id={ item.table.table_id }
fontWeight={ 500 } fontWeight={ 500 }
href={ route({ pathname: '/address/[hash]', query: { hash, tab: 'mud', table_id: item.table.table_id } }) } href={ route({ pathname: '/address/[hash]', query: { hash, tab: 'mud', table_id: item.table.table_id } }) }
> >
{ item.table.table_full_name } { item.table.table_full_name }
</LinkInternal> </Link>
</Skeleton> </Skeleton>
<Skeleton isLoaded={ !isLoading } color="text_secondary"> <Skeleton loading={ isLoading } color="text.secondary">
{ item.table.table_type } { item.table.table_type }
</Skeleton> </Skeleton>
</Flex> </Flex>
<Skeleton isLoaded={ !isLoading } color="text_secondary"> <Skeleton loading={ isLoading } color="text.secondary">
<HashStringShorten hash={ item.table.table_id } type="long"/> <HashStringShorten hash={ item.table.table_id } type="long"/>
</Skeleton> </Skeleton>
</Box> </Box>
...@@ -90,14 +94,14 @@ const AddressMudTablesListItem = ({ item, isLoading, scrollRef, hash }: Props) = ...@@ -90,14 +94,14 @@ const AddressMudTablesListItem = ({ item, isLoading, scrollRef, hash }: Props) =
<Text lineHeight="24px">Key</Text> <Text lineHeight="24px">Key</Text>
<VStack gap={ 1 } alignItems="start"> <VStack gap={ 1 } alignItems="start">
{ item.schema.key_names.map((name, index) => ( { item.schema.key_names.map((name, index) => (
<Tag key={ name }> <Badge key={ name }>
<chakra.span fontWeight={ 700 }>{ item.schema.key_types[index] }</chakra.span> { name } <chakra.span fontWeight={ 700 }>{ item.schema.key_types[index] }</chakra.span> { name }
</Tag> </Badge>
)) } )) }
</VStack> </VStack>
</> </>
) } ) }
<GridItem colSpan={ 2 }><Divider/></GridItem> <GridItem colSpan={ 2 }><Separator/></GridItem>
<Text lineHeight="24px">Value</Text> <Text lineHeight="24px">Value</Text>
<VStack gap={ 1 } alignItems="start"> <VStack gap={ 1 } alignItems="start">
{ item.schema.value_names.map((name, index) => ( { item.schema.value_names.map((name, index) => (
......
import { Table, Tbody, Th, Tr } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { AddressMudTables } from 'types/api/address'; import type { AddressMudTables } from 'types/api/address';
import { default as Thead } from 'ui/shared/TheadSticky'; import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
import AddressMudTablesTableItem from './AddressMudTablesTableItem'; import AddressMudTablesTableItem from './AddressMudTablesTableItem';
...@@ -18,16 +17,16 @@ type Props = { ...@@ -18,16 +17,16 @@ type Props = {
//sorry for the naming //sorry for the naming
const AddressMudTablesTable = ({ items, isLoading, top, scrollRef, hash }: Props) => { const AddressMudTablesTable = ({ items, isLoading, top, scrollRef, hash }: Props) => {
return ( return (
<Table style={{ tableLayout: 'auto' }}> <TableRoot style={{ tableLayout: 'auto' }}>
<Thead top={ top }> <TableHeaderSticky top={ top }>
<Tr> <TableRow>
<Th width="24px"></Th> <TableColumnHeader width="24px"></TableColumnHeader>
<Th>Full name</Th> <TableColumnHeader>Full name</TableColumnHeader>
<Th>Table ID</Th> <TableColumnHeader>Table ID</TableColumnHeader>
<Th>Type</Th> <TableColumnHeader>Type</TableColumnHeader>
</Tr> </TableRow>
</Thead> </TableHeaderSticky>
<Tbody> <TableBody>
{ items.map((item, index) => ( { items.map((item, index) => (
<AddressMudTablesTableItem <AddressMudTablesTableItem
key={ item.table.table_id + (isLoading ? String(index) : '') } key={ item.table.table_id + (isLoading ? String(index) : '') }
...@@ -37,8 +36,8 @@ const AddressMudTablesTable = ({ items, isLoading, top, scrollRef, hash }: Props ...@@ -37,8 +36,8 @@ const AddressMudTablesTable = ({ items, isLoading, top, scrollRef, hash }: Props
hash={ hash } hash={ hash }
/> />
)) } )) }
</Tbody> </TableBody>
</Table> </TableRoot>
); );
}; };
......
import { Td, Tr, Text, useBoolean, Link, Table, VStack, chakra } from '@chakra-ui/react'; import { Text, VStack, chakra } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
...@@ -6,10 +6,11 @@ import type { AddressMudTableItem } from 'types/api/address'; ...@@ -6,10 +6,11 @@ import type { AddressMudTableItem } from 'types/api/address';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import Skeleton from 'ui/shared/chakra/Skeleton'; import { Badge } from 'toolkit/chakra/badge';
import Tag from 'ui/shared/chakra/Tag'; import { Link } from 'toolkit/chakra/link';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { TableBody, TableCell, TableRoot, TableRow } from 'toolkit/chakra/table';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import LinkInternal from 'ui/shared/links/LinkInternal';
type Props = { type Props = {
item: AddressMudTableItem; item: AddressMudTableItem;
...@@ -19,10 +20,14 @@ type Props = { ...@@ -19,10 +20,14 @@ type Props = {
}; };
const AddressMudTablesTableItem = ({ item, isLoading, scrollRef, hash }: Props) => { const AddressMudTablesTableItem = ({ item, isLoading, scrollRef, hash }: Props) => {
const [ isOpened, setIsOpened ] = useBoolean(false); const [ isOpened, setIsOpened ] = React.useState(false);
const router = useRouter(); const router = useRouter();
const handleIconClick = React.useCallback(() => {
setIsOpened((prev) => !prev);
}, []);
const onTableClick = React.useCallback((e: React.MouseEvent) => { const onTableClick = React.useCallback((e: React.MouseEvent) => {
if (e.metaKey || e.ctrlKey) { if (e.metaKey || e.ctrlKey) {
// Allow opening in a new tab/window with right-click or ctrl/cmd+click // Allow opening in a new tab/window with right-click or ctrl/cmd+click
...@@ -44,79 +49,81 @@ const AddressMudTablesTableItem = ({ item, isLoading, scrollRef, hash }: Props) ...@@ -44,79 +49,81 @@ const AddressMudTablesTableItem = ({ item, isLoading, scrollRef, hash }: Props)
return ( return (
<> <>
<Tr borderBottomStyle={ isOpened ? 'hidden' : 'unset' }> <TableRow borderBottomStyle={ isOpened ? 'hidden' : 'unset' }>
<Td verticalAlign="middle"> <TableCell verticalAlign="middle">
<Skeleton isLoaded={ !isLoading }> <Skeleton loading={ isLoading }>
<Link display="block"> <Link display="block">
<IconSvg <IconSvg
name="arrows/east-mini" name="arrows/east-mini"
transform={ isOpened ? 'rotate(270deg)' : 'rotate(180deg)' } transform={ isOpened ? 'rotate(270deg)' : 'rotate(180deg)' }
boxSize={ 6 } boxSize={ 6 }
cursor="pointer" cursor="pointer"
onClick={ setIsOpened.toggle } onClick={ handleIconClick }
transitionDuration="faster" transitionDuration="faster"
aria-label="View schema" aria-label="View schema"
/> />
</Link> </Link>
</Skeleton> </Skeleton>
</Td> </TableCell>
<Td verticalAlign="middle"> <TableCell verticalAlign="middle">
<Skeleton isLoaded={ !isLoading }> <Skeleton loading={ isLoading }>
<LinkInternal <Link
href={ route({ pathname: '/address/[hash]', query: { hash, tab: 'mud', table_id: item.table.table_id } }) } href={ route({ pathname: '/address/[hash]', query: { hash, tab: 'mud', table_id: item.table.table_id } }) }
data-id={ item.table.table_id } data-id={ item.table.table_id }
onClick={ onTableClick } onClick={ onTableClick }
fontWeight={ 700 } fontWeight={ 700 }
> >
{ item.table.table_full_name } { item.table.table_full_name }
</LinkInternal> </Link>
</Skeleton> </Skeleton>
</Td> </TableCell>
<Td verticalAlign="middle"> <TableCell verticalAlign="middle">
<Skeleton isLoaded={ !isLoading }> <Skeleton loading={ isLoading }>
{ item.table.table_id } { item.table.table_id }
</Skeleton> </Skeleton>
</Td> </TableCell>
<Td verticalAlign="middle"> <TableCell verticalAlign="middle">
<Skeleton isLoaded={ !isLoading }> <Skeleton loading={ isLoading }>
{ item.table.table_type } { item.table.table_type }
</Skeleton> </Skeleton>
</Td> </TableCell>
</Tr> </TableRow>
{ isOpened && ( { isOpened && (
<Tr> <TableRow>
<Td pt={ 0 }></Td> <TableCell pt={ 0 }></TableCell>
<Td colSpan={ 3 } pt={ 0 }> <TableCell colSpan={ 3 } pt={ 0 }>
<Table> <TableRoot>
{ Boolean(item.schema.key_names.length) && ( <TableBody>
<Tr> { Boolean(item.schema.key_names.length) && (
<Td width="80px" fontSize="sm" fontWeight={ 600 } py={ 2 } pl={ 0 } verticalAlign="middle">Key</Td> <TableRow>
<Td py={ 2 }> <TableCell width="80px" fontSize="sm" fontWeight={ 600 } py={ 2 } pl={ 0 } verticalAlign="middle">Key</TableCell>
<TableCell py={ 2 }>
<VStack gap={ 1 } alignItems="start">
{ item.schema.key_names.map((name, index) => (
<Badge key={ name }>
<chakra.span fontWeight={ 700 }>{ item.schema.key_types[index] }</chakra.span> { name }
</Badge>
)) }
</VStack>
</TableCell>
</TableRow>
) }
<TableRow borderBottomStyle="hidden">
<TableCell width="80px" fontSize="sm" fontWeight={ 600 } py={ 2 } pl={ 0 } >Value</TableCell>
<TableCell fontSize="sm" py={ 2 }>
<VStack gap={ 1 } alignItems="start"> <VStack gap={ 1 } alignItems="start">
{ item.schema.key_names.map((name, index) => ( { item.schema.value_names.map((name, index) => (
<Tag key={ name }> <Text key={ name }>
<chakra.span fontWeight={ 700 }>{ item.schema.key_types[index] }</chakra.span> { name } <chakra.span fontWeight={ 700 }>{ item.schema.value_types[index] }</chakra.span> { name }
</Tag> </Text>
)) } )) }
</VStack> </VStack>
</Td> </TableCell>
</Tr> </TableRow>
) } </TableBody>
<Tr borderBottomStyle="hidden"> </TableRoot>
<Td width="80px" fontSize="sm" fontWeight={ 600 } py={ 2 } pl={ 0 } >Value</Td> </TableCell>
<Td fontSize="sm" py={ 2 }> </TableRow>
<VStack gap={ 1 } alignItems="start">
{ item.schema.value_names.map((name, index) => (
<Text key={ name }>
<chakra.span fontWeight={ 700 }>{ item.schema.value_types[index] }</chakra.span> { name }
</Text>
)) }
</VStack>
</Td>
</Tr>
</Table>
</Td>
</Tr>
) } ) }
</> </>
); );
......
...@@ -6,7 +6,7 @@ import type { MudWorldItem } from 'types/api/mudWorlds'; ...@@ -6,7 +6,7 @@ import type { MudWorldItem } from 'types/api/mudWorlds';
import config from 'configs/app'; import config from 'configs/app';
import { currencyUnits } from 'lib/units'; import { currencyUnits } from 'lib/units';
import Skeleton from 'ui/shared/chakra/Skeleton'; import { Skeleton } from 'toolkit/chakra/skeleton';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
...@@ -31,15 +31,15 @@ const MudWorldsListItem = ({ ...@@ -31,15 +31,15 @@ const MudWorldsListItem = ({
mr={ 2 } mr={ 2 }
truncation="constant_long" truncation="constant_long"
/> />
<HStack spacing={ 3 } maxW="100%" alignItems="flex-start"> <HStack gap={ 3 } maxW="100%" alignItems="flex-start">
<Skeleton isLoaded={ !isLoading } fontSize="sm" fontWeight={ 500 } flexShrink={ 0 }>{ `Balance ${ currencyUnits.ether }` }</Skeleton> <Skeleton loading={ isLoading } fontSize="sm" fontWeight={ 500 } flexShrink={ 0 }>{ `Balance ${ currencyUnits.ether }` }</Skeleton>
<Skeleton isLoaded={ !isLoading } fontSize="sm" color="text_secondary" minW="0" whiteSpace="pre-wrap"> <Skeleton loading={ isLoading } fontSize="sm" color="text_secondary" minW="0" whiteSpace="pre-wrap">
<span>{ addressBalance.dp(8).toFormat() }</span> <span>{ addressBalance.dp(8).toFormat() }</span>
</Skeleton> </Skeleton>
</HStack> </HStack>
<HStack spacing={ 3 }> <HStack gap={ 3 }>
<Skeleton isLoaded={ !isLoading } fontSize="sm" fontWeight={ 500 }>Txn count</Skeleton> <Skeleton loading={ isLoading } fontSize="sm" fontWeight={ 500 }>Txn count</Skeleton>
<Skeleton isLoaded={ !isLoading } fontSize="sm" color="text_secondary"> <Skeleton loading={ isLoading } fontSize="sm" color="text_secondary">
<span>{ Number(item.transaction_count).toLocaleString() }</span> <span>{ Number(item.transaction_count).toLocaleString() }</span>
</Skeleton> </Skeleton>
</HStack> </HStack>
......
import { Table, Tbody, Th, Tr } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { MudWorldItem } from 'types/api/mudWorlds'; import type { MudWorldItem } from 'types/api/mudWorlds';
import { currencyUnits } from 'lib/units'; import { currencyUnits } from 'lib/units';
import { default as Thead } from 'ui/shared/TheadSticky'; import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
import MudWorldsTableItem from './MudWorldsTableItem'; import MudWorldsTableItem from './MudWorldsTableItem';
...@@ -16,15 +15,15 @@ type Props = { ...@@ -16,15 +15,15 @@ type Props = {
const MudWorldsTable = ({ items, top, isLoading }: Props) => { const MudWorldsTable = ({ items, top, isLoading }: Props) => {
return ( return (
<Table style={{ tableLayout: 'auto' }}> <TableRoot style={{ tableLayout: 'auto' }}>
<Thead top={ top }> <TableHeaderSticky top={ top }>
<Tr> <TableRow>
<Th>Address</Th> <TableColumnHeader>Address</TableColumnHeader>
<Th isNumeric>{ `Balance ${ currencyUnits.ether }` }</Th> <TableColumnHeader isNumeric>{ `Balance ${ currencyUnits.ether }` }</TableColumnHeader>
<Th isNumeric>Txn count</Th> <TableColumnHeader isNumeric>Txn count</TableColumnHeader>
</Tr> </TableRow>
</Thead> </TableHeaderSticky>
<Tbody> <TableBody>
{ items.map((item, index) => ( { items.map((item, index) => (
<MudWorldsTableItem <MudWorldsTableItem
key={ String(item.address.hash) + (isLoading ? index : '') } key={ String(item.address.hash) + (isLoading ? index : '') }
...@@ -32,8 +31,8 @@ const MudWorldsTable = ({ items, top, isLoading }: Props) => { ...@@ -32,8 +31,8 @@ const MudWorldsTable = ({ items, top, isLoading }: Props) => {
isLoading={ isLoading } isLoading={ isLoading }
/> />
)) } )) }
</Tbody> </TableBody>
</Table> </TableRoot>
); );
}; };
......
import { Text, Td, Tr } from '@chakra-ui/react'; import { Text } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
import type { MudWorldItem } from 'types/api/mudWorlds'; import type { MudWorldItem } from 'types/api/mudWorlds';
import config from 'configs/app'; import config from 'configs/app';
import Skeleton from 'ui/shared/chakra/Skeleton'; import { Skeleton } from 'toolkit/chakra/skeleton';
import { TableCell, TableRow } from 'toolkit/chakra/table';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
type Props = { item: MudWorldItem; isLoading?: boolean }; type Props = { item: MudWorldItem; isLoading?: boolean };
...@@ -15,22 +16,22 @@ const MudWorldsTableItem = ({ item, isLoading }: Props) => { ...@@ -15,22 +16,22 @@ const MudWorldsTableItem = ({ item, isLoading }: Props) => {
const addressBalanceChunks = addressBalance.dp(8).toFormat().split('.'); const addressBalanceChunks = addressBalance.dp(8).toFormat().split('.');
return ( return (
<Tr> <TableRow>
<Td verticalAlign="middle"> <TableCell verticalAlign="middle">
<AddressEntity address={ item.address } isLoading={ isLoading } fontWeight={ 700 }/> <AddressEntity address={ item.address } isLoading={ isLoading } fontWeight={ 700 }/>
</Td> </TableCell>
<Td isNumeric> <TableCell isNumeric>
<Skeleton isLoaded={ !isLoading } display="inline-block" maxW="100%"> <Skeleton loading={ isLoading } display="inline-block" maxW="100%">
<Text lineHeight="24px" as="span">{ addressBalanceChunks[0] + (addressBalanceChunks[1] ? '.' : '') }</Text> <Text lineHeight="24px" as="span">{ addressBalanceChunks[0] + (addressBalanceChunks[1] ? '.' : '') }</Text>
<Text lineHeight="24px" variant="secondary" as="span">{ addressBalanceChunks[1] }</Text> <Text lineHeight="24px" color="text.secondary" as="span">{ addressBalanceChunks[1] }</Text>
</Skeleton> </Skeleton>
</Td> </TableCell>
<Td isNumeric> <TableCell isNumeric>
<Skeleton isLoaded={ !isLoading } display="inline-block" lineHeight="24px"> <Skeleton loading={ isLoading } display="inline-block" lineHeight="24px">
{ Number(item.transaction_count).toLocaleString() } { Number(item.transaction_count).toLocaleString() }
</Skeleton> </Skeleton>
</Td> </TableCell>
</Tr> </TableRow>
); );
}; };
......
...@@ -153,12 +153,12 @@ const AddressPageContent = () => { ...@@ -153,12 +153,12 @@ const AddressPageContent = () => {
const tabs: Array<TabItemRegular> = React.useMemo(() => { const tabs: Array<TabItemRegular> = React.useMemo(() => {
return [ return [
// config.features.mudFramework.isEnabled && mudTablesCountQuery.data && mudTablesCountQuery.data > 0 && { config.features.mudFramework.isEnabled && mudTablesCountQuery.data && mudTablesCountQuery.data > 0 && {
// id: 'mud', id: 'mud',
// title: 'MUD', title: 'MUD',
// count: mudTablesCountQuery.data, count: mudTablesCountQuery.data,
// component: <AddressMud scrollRef={ tabsScrollRef } shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>, component: <AddressMud scrollRef={ tabsScrollRef } shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
// }, },
{ {
id: 'txs', id: 'txs',
title: 'Transactions', title: 'Transactions',
......
import { Hide, Show } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { MUD_WORLD } from 'stubs/mud'; import { MUD_WORLD } from 'stubs/mud';
...@@ -30,7 +30,7 @@ const MudWorlds = () => { ...@@ -30,7 +30,7 @@ const MudWorlds = () => {
const content = data?.items ? ( const content = data?.items ? (
<> <>
<Show below="lg" ssr={ false }> <Box hideFrom="lg">
{ data.items.map(((item, index) => ( { data.items.map(((item, index) => (
<MudWorldsListItem <MudWorldsListItem
key={ item.address.hash + (isPlaceholderData ? String(index) : '') } key={ item.address.hash + (isPlaceholderData ? String(index) : '') }
...@@ -38,10 +38,10 @@ const MudWorlds = () => { ...@@ -38,10 +38,10 @@ const MudWorlds = () => {
isLoading={ isPlaceholderData } isLoading={ isPlaceholderData }
/> />
))) } ))) }
</Show> </Box>
<Hide below="lg" ssr={ false }> <Box hideBelow="lg">
<MudWorldsTable items={ data.items } top={ pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 } isLoading={ isPlaceholderData }/> <MudWorldsTable items={ data.items } top={ pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 } isLoading={ isPlaceholderData }/>
</Hide> </Box>
</> </>
) : null; ) : null;
...@@ -56,11 +56,12 @@ const MudWorlds = () => { ...@@ -56,11 +56,12 @@ const MudWorlds = () => {
<PageTitle title="MUD worlds" withTextAd/> <PageTitle title="MUD worlds" withTextAd/>
<DataListDisplay <DataListDisplay
isError={ isError } isError={ isError }
items={ data?.items } itemsNum={ data?.items.length }
emptyText="There are no MUD worlds." emptyText="There are no MUD worlds."
content={ content }
actionBar={ actionBar } actionBar={ actionBar }
/> >
{ content }
</DataListDisplay>
</> </>
); );
}; };
......
...@@ -21,6 +21,7 @@ const ClearButton = ({ onClick, isDisabled, isVisible = true, className }: Props ...@@ -21,6 +21,7 @@ const ClearButton = ({ onClick, isDisabled, isVisible = true, className }: Props
size="sm" size="sm"
onClick={ onClick } onClick={ onClick }
opacity={ isVisible ? 1 : 0 } opacity={ isVisible ? 1 : 0 }
visibility={ isVisible ? 'visible' : 'hidden' }
> >
<IconSvg <IconSvg
name="status/error" name="status/error"
......
...@@ -2,11 +2,13 @@ import { ...@@ -2,11 +2,13 @@ import {
chakra, chakra,
Flex, Flex,
Text, Text,
Link,
Button,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { Button } from 'toolkit/chakra/button';
import { Link } from 'toolkit/chakra/link';
import { PopoverCloseTriggerWrapper } from 'toolkit/chakra/popover';
type Props = { type Props = {
title: string; title: string;
isFilled?: boolean; isFilled?: boolean;
...@@ -14,19 +16,17 @@ type Props = { ...@@ -14,19 +16,17 @@ type Props = {
hasReset?: boolean; hasReset?: boolean;
onFilter: () => void; onFilter: () => void;
onReset?: () => void; onReset?: () => void;
onClose?: () => void;
children: React.ReactNode; children: React.ReactNode;
}; };
const TableColumnFilter = ({ title, isFilled, isTouched, hasReset, onFilter, onReset, onClose, children }: Props) => { const TableColumnFilter = ({ title, isFilled, isTouched, hasReset, onFilter, onReset, children }: Props) => {
const onFilterClick = React.useCallback(() => { const onFilterClick = React.useCallback(() => {
onClose && onClose();
onFilter(); onFilter();
}, [ onClose, onFilter ]); }, [ onFilter ]);
return ( return (
<> <>
<Flex alignItems="center" justifyContent="space-between"> <Flex alignItems="center" justifyContent="space-between">
<Text color="text_secondary" fontWeight="600">{ title }</Text> <Text color="text.secondary" fontWeight="600">{ title }</Text>
{ hasReset && ( { hasReset && (
<Link <Link
onClick={ onReset } onClick={ onReset }
...@@ -41,13 +41,15 @@ const TableColumnFilter = ({ title, isFilled, isTouched, hasReset, onFilter, onR ...@@ -41,13 +41,15 @@ const TableColumnFilter = ({ title, isFilled, isTouched, hasReset, onFilter, onR
) } ) }
</Flex> </Flex>
{ children } { children }
<Button <PopoverCloseTriggerWrapper>
isDisabled={ !isTouched } <Button
onClick={ onFilterClick } disabled={ !isTouched }
w="fit-content" onClick={ onFilterClick }
> w="fit-content"
Filter >
</Button> Filter
</Button>
</PopoverCloseTriggerWrapper>
</> </>
); );
}; };
......
import { import { chakra } from '@chakra-ui/react';
PopoverTrigger,
PopoverContent,
PopoverBody,
useDisclosure,
chakra,
Portal,
Button,
} from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import Popover from 'ui/shared/chakra/Popover'; import { Button } from 'toolkit/chakra/button';
import { PopoverBody, PopoverContent, PopoverRoot, PopoverTrigger } from 'toolkit/chakra/popover';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
interface Props { interface Props {
columnName: string; columnName: string;
isActive?: boolean;
isLoading?: boolean; isLoading?: boolean;
selected?: boolean;
className?: string; className?: string;
children: React.ReactNode; children: React.ReactNode;
value?: string; value?: string;
} }
const TableColumnFilterWrapper = ({ columnName, isActive, className, children, isLoading, value }: Props) => { const TableColumnFilterWrapper = ({ columnName, className, children, isLoading, selected, value }: Props) => {
const { isOpen, onToggle, onClose } = useDisclosure();
const content = React.Children.only(children) as React.ReactElement & {
ref?: React.Ref<React.ReactNode>;
};
const modifiedContent = React.cloneElement(
content,
{ onClose },
);
return ( return (
<Popover isOpen={ isOpen } onClose={ onClose } placement="bottom-start" isLazy lazyBehavior="unmount"> <PopoverRoot>
<PopoverTrigger> <PopoverTrigger>
<Button <Button
onClick={ onToggle }
aria-label={ `filter by ${ columnName }` } aria-label={ `filter by ${ columnName }` }
variant="ghost" variant="dropdown"
borderWidth="0"
h="20px" h="20px"
isActive={ Boolean(value) || isActive } minW="auto"
isDisabled={ isLoading } disabled={ isLoading }
selected={ selected }
borderRadius="4px" borderRadius="4px"
color="text_secondary" size="sm"
fontSize="sm"
fontWeight={ 500 } fontWeight={ 500 }
leftIcon={ <IconSvg name="filter" w="19px" h="19px"/> }
padding={ 0 } padding={ 0 }
sx={{ css={{
'span:only-child': { 'span:only-child': {
mx: 0, mx: 0,
}, },
...@@ -58,17 +39,16 @@ const TableColumnFilterWrapper = ({ columnName, isActive, className, children, i ...@@ -58,17 +39,16 @@ const TableColumnFilterWrapper = ({ columnName, isActive, className, children, i
}, },
}} }}
> >
<IconSvg name="filter" w="19px" h="19px"/>
{ Boolean(value) && <chakra.span>{ value }</chakra.span> } { Boolean(value) && <chakra.span>{ value }</chakra.span> }
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<Portal> <PopoverContent className={ className }>
<PopoverContent className={ className }> <PopoverBody display="flex" flexDir="column" rowGap={ 3 }>
<PopoverBody px={ 4 } py={ 6 } display="flex" flexDir="column" rowGap={ 3 }> { children }
{ modifiedContent } </PopoverBody>
</PopoverBody> </PopoverContent>
</PopoverContent> </PopoverRoot>
</Portal>
</Popover>
); );
}; };
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment