Commit 957b2e7f authored by isstuev's avatar isstuev

design fixes and tests

parent a44d803f
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 30 30"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 30 30">
<path fill="#4A5568" fill-rule="evenodd" d="M7.904 22.831c.335.102.681.153 1.029.152a3.59 3.59 0 0 0 1.907-.583 4.14 4.14 0 0 0 1.17-1.155 4.666 4.666 0 0 0 .68-1.57l.165-.688h4.287l.128.704c.132.569.363 1.102.68 1.57.319.468.716.86 1.171 1.155a3.61 3.61 0 0 0 1.444.548 3.483 3.483 0 0 0 1.521-.117c1.011-.32 1.87-1.07 2.39-2.092.52-1.021.66-2.23.39-3.366L23.2 10.31a4.69 4.69 0 0 0-.682-1.569 4.16 4.16 0 0 0-1.169-1.156 3.61 3.61 0 0 0-1.444-.548 3.483 3.483 0 0 0-1.52.117 3.793 3.793 0 0 0-1.442.832 4.34 4.34 0 0 0-1.017 1.413H14.07a4.357 4.357 0 0 0-1.018-1.411 3.814 3.814 0 0 0-1.44-.834 3.483 3.483 0 0 0-1.521-.117 3.6 3.6 0 0 0-1.444.548 4.14 4.14 0 0 0-1.173 1.162 4.668 4.668 0 0 0-.678 1.58L5.132 17.39a4.944 4.944 0 0 0 .393 3.354c.518 1.017 1.372 1.767 2.379 2.088ZM9.36 8.967c.375-.24.798-.366 1.23-.368.216 0 .433.033.642.096.39.123.747.349 1.042.657.296.309.522.691.659 1.117l.171.527h3.787l.171-.527a2.88 2.88 0 0 1 .655-1.116c.294-.309.65-.534 1.039-.658a2.22 2.22 0 0 1 .963-.073 2.3 2.3 0 0 1 .916.345c.29.19.544.442.746.742.203.3.35.643.432 1.008l1.665 7.064a3.162 3.162 0 0 1-.237 2.16c-.33.657-.88 1.141-1.527 1.348-.314.095-.642.12-.964.073a2.298 2.298 0 0 1-.916-.345 2.675 2.675 0 0 1-.749-.742 3.016 3.016 0 0 1-.437-1.008l-.435-1.878h-6.43l-.444 1.878a2.982 2.982 0 0 1-.435 1.01 2.64 2.64 0 0 1-.75.74 2.28 2.28 0 0 1-.912.346 2.199 2.199 0 0 1-.96-.074c-.648-.209-1.196-.693-1.527-1.35a3.179 3.179 0 0 1-.245-2.158l1.664-7.064c.173-.736.6-1.365 1.186-1.75ZM20 12.287a.714.714 0 1 1-1.43.001.714.714 0 0 1 1.43 0ZM9.918 14.906a1.43 1.43 0 1 0 1.588-2.377 1.43 1.43 0 0 0-1.588 2.377Zm9.367.955a.714.714 0 1 0 0-1.429.714.714 0 0 0 0 1.43Zm-.714-2.143a.714.714 0 1 1-1.429 0 .714.714 0 0 1 1.429 0Zm2.143.714a.714.714 0 1 0 0-1.429.714.714 0 0 0 0 1.43Z" clip-rule="evenodd"/> <path fill="currentColor" d="M6.485 23.997a4.23 4.23 0 0 0 1.234.183 4.31 4.31 0 0 0 2.29-.7c.545-.353 1.022-.824 1.403-1.386s.66-1.202.817-1.884l.197-.825h5.144l.155.844c.157.682.435 1.323.816 1.885s.859 1.032 1.404 1.385a4.334 4.334 0 0 0 1.733.658 4.18 4.18 0 0 0 1.825-.14c1.214-.384 2.244-1.285 2.868-2.51a5.933 5.933 0 0 0 .467-4.04l-1.997-8.496c-.16-.682-.437-1.322-.818-1.883s-.858-1.033-1.403-1.387a4.333 4.333 0 0 0-1.733-.657 4.18 4.18 0 0 0-1.825.14c-.632.194-1.221.534-1.73.998s-.923 1.042-1.22 1.696h-2.228c-.298-.653-.714-1.23-1.222-1.694s-1.096-.804-1.728-1a4.18 4.18 0 0 0-1.825-.14c-.61.088-1.2.312-1.733.657A4.969 4.969 0 0 0 5.97 7.095a5.602 5.602 0 0 0-.813 1.895l-1.998 8.477a5.932 5.932 0 0 0 .472 4.025c.621 1.22 1.646 2.12 2.855 2.505zM8.234 7.36a2.752 2.752 0 0 1 1.474-.441c.26 0 .52.04.772.115.467.148.895.418 1.25.788.355.37.626.83.79 1.34l.206.634h4.544l.206-.633c.163-.51.432-.97.786-1.34s.78-.64 1.246-.789c.377-.114.77-.144 1.156-.088.387.056.76.197 1.099.414.348.228.653.53.896.891.243.36.419.772.518 1.21l1.998 8.476c.214.872.112 1.804-.285 2.592s-1.055 1.37-1.833 1.618c-.376.114-.77.144-1.156.088a2.761 2.761 0 0 1-1.098-.414c-.35-.228-.655-.531-.9-.892s-.422-.77-.524-1.208l-.523-2.254H11.14l-.532 2.254c-.1.438-.277.85-.522 1.21-.244.361-.55.663-.9.89a2.738 2.738 0 0 1-1.095.414 2.64 2.64 0 0 1-1.152-.088c-.777-.25-1.435-.832-1.832-1.62a3.815 3.815 0 0 1-.294-2.59L6.81 9.46c.208-.883.72-1.638 1.424-2.1zm12.765 3.985a.857.857 0 1 1-1.714 0 .857.857 0 0 1 1.714 0zm-12.098 3.14a1.715 1.715 0 1 0 1.906-2.85 1.715 1.715 0 0 0-1.906 2.85zm11.241 1.147a.857.857 0 1 0 0-1.714.857.857 0 0 0 0 1.714zm-.857-2.572a.857.857 0 1 1-1.714 0 .857.857 0 0 1 1.714 0zm2.572.857a.857.857 0 1 0 0-1.714.857.857 0 0 0 0 1.714z" fill-rule="evenodd" clip-rule="evenodd"/>
<path fill="#000" fill-opacity=".2" fill-rule="evenodd" d="M7.904 22.831c.335.102.681.153 1.029.152a3.59 3.59 0 0 0 1.907-.583 4.14 4.14 0 0 0 1.17-1.155 4.666 4.666 0 0 0 .68-1.57l.165-.688h4.287l.128.704c.132.569.363 1.102.68 1.57.319.468.716.86 1.171 1.155a3.61 3.61 0 0 0 1.444.548 3.483 3.483 0 0 0 1.521-.117c1.011-.32 1.87-1.07 2.39-2.092.52-1.021.66-2.23.39-3.366L23.2 10.31a4.69 4.69 0 0 0-.682-1.569 4.16 4.16 0 0 0-1.169-1.156 3.61 3.61 0 0 0-1.444-.548 3.483 3.483 0 0 0-1.52.117 3.793 3.793 0 0 0-1.442.832 4.34 4.34 0 0 0-1.017 1.413H14.07a4.357 4.357 0 0 0-1.018-1.411 3.814 3.814 0 0 0-1.44-.834 3.483 3.483 0 0 0-1.521-.117 3.6 3.6 0 0 0-1.444.548 4.14 4.14 0 0 0-1.173 1.162 4.668 4.668 0 0 0-.678 1.58L5.132 17.39a4.944 4.944 0 0 0 .393 3.354c.518 1.017 1.372 1.767 2.379 2.088ZM9.36 8.967c.375-.24.798-.366 1.23-.368.216 0 .433.033.642.096.39.123.747.349 1.042.657.296.309.522.691.659 1.117l.171.527h3.787l.171-.527a2.88 2.88 0 0 1 .655-1.116c.294-.309.65-.534 1.039-.658a2.22 2.22 0 0 1 .963-.073 2.3 2.3 0 0 1 .916.345c.29.19.544.442.746.742.203.3.35.643.432 1.008l1.665 7.064a3.162 3.162 0 0 1-.237 2.16c-.33.657-.88 1.141-1.527 1.348-.314.095-.642.12-.964.073a2.298 2.298 0 0 1-.916-.345 2.675 2.675 0 0 1-.749-.742 3.016 3.016 0 0 1-.437-1.008l-.435-1.878h-6.43l-.444 1.878a2.982 2.982 0 0 1-.435 1.01 2.64 2.64 0 0 1-.75.74 2.28 2.28 0 0 1-.912.346 2.199 2.199 0 0 1-.96-.074c-.648-.209-1.196-.693-1.527-1.35a3.179 3.179 0 0 1-.245-2.158l1.664-7.064c.173-.736.6-1.365 1.186-1.75ZM20 12.287a.714.714 0 1 1-1.43.001.714.714 0 0 1 1.43 0ZM9.918 14.906a1.43 1.43 0 1 0 1.588-2.377 1.43 1.43 0 0 0-1.588 2.377Zm9.367.955a.714.714 0 1 0 0-1.429.714.714 0 0 0 0 1.43Zm-.714-2.143a.714.714 0 1 1-1.429 0 .714.714 0 0 1 1.429 0Zm2.143.714a.714.714 0 1 0 0-1.429.714.714 0 0 0 0 1.43Z" clip-rule="evenodd"/>
</svg> </svg>
/* eslint-disable max-len */
import type { AddressMudRecord, AddressMudRecords, AddressMudRecordsItem, AddressMudTables } from 'types/api/address';
import type { MudWorldSchema, MudWorldTable } from 'types/api/mudWorlds';
export const table1: MudWorldTable = {
table_full_name: 'tb.store.Tables',
table_id: '0x746273746f72650000000000000000005461626c657300000000000000000000',
table_name: 'Tables',
table_namespace: 'store',
table_type: 'onchain',
};
export const table2: MudWorldTable = {
table_full_name: 'ot.world.FunctionSignatur',
table_id: '0x6f74776f726c6400000000000000000046756e6374696f6e5369676e61747572',
table_name: 'FunctionSignatur',
table_namespace: 'world',
table_type: 'offchain',
};
export const schema1: MudWorldSchema = {
key_names: [ 'moduleAddress', 'argumentsHash' ],
key_types: [ 'address', 'bytes32' ],
value_names: [ 'fieldLayout', 'keySchema', 'valueSchema', 'abiEncodedKeyNames', 'abiEncodedFieldNames' ],
value_types: [ 'bytes32', 'bytes32', 'bytes32', 'bytes', 'bytes' ],
};
export const schema2: MudWorldSchema = {
key_names: [],
key_types: [],
value_names: [ 'value' ],
value_types: [ 'address' ],
};
export const mudTables: AddressMudTables = {
items: [
{
table: table1,
schema: schema1,
},
{
table: table2,
schema: schema2,
},
],
next_page_params: {
items_count: 50,
table_id: '1',
},
};
const record: AddressMudRecordsItem = {
decoded: {
abiEncodedFieldNames: '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000006706c617965720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000576616c7565000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000974696d657374616d700000000000000000000000000000000000000000000000',
abiEncodedKeyNames: '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000026964000000000000000000000000000000000000000000000000000000000000',
goldCosts: [ '100000', '150000', '200000', '250000', '400000', '550000', '700000' ],
prototypeIds: [
'0x53776f7264736d616e0000000000000000000000000000000000000000000000',
'0x50696b656d616e00000000000000000000000000000000000000000000000000',
'0x50696b656d616e00000000000000000000000000000000000000000000000000',
'0x4172636865720000000000000000000000000000000000000000000000000000',
'0x4b6e696768740000000000000000000000000000000000000000000000000000',
],
keySchema: '0x002001001f000000000000000000000000000000000000000000000000000000',
tableId: '0x6f74000000000000000000000000000044726177557064617465000000000000',
valueSchema: '0x00540300611f1f00000000000000000000000000000000000000000000000000',
},
id: '0x007a651a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007',
is_deleted: false,
timestamp: '2024-05-09T15:14:32.000000Z',
};
export const mudRecords: AddressMudRecords = {
items: [ record, record ],
next_page_params: {
items_count: 50,
key0: '1',
key1: '2',
key_bytes: '3',
},
schema: {
key_names: [ 'tableId' ],
key_types: [ 'bytes32' ],
value_names: [ 'prototypeIds', 'goldCosts', 'keySchema', 'valueSchema', 'abiEncodedKeyNames', 'abiEncodedFieldNames' ],
value_types: [ 'bytes32[]', 'int32[]', 'bytes32', 'bytes32', 'bytes32', 'bytes', 'bytes' ],
},
table: table1,
};
export const mudRecord: AddressMudRecord = {
record,
schema: mudRecords.schema,
table: table1,
};
import type { MudWorldsResponse } from 'types/api/mudWorlds';
import { withName, withoutName } from 'mocks/address/address';
export const mudWorlds: MudWorldsResponse = {
items: [
{
address: withName,
coin_balance: '300000000000000000',
tx_count: 3938,
},
{
address: withoutName,
coin_balance: '0',
tx_count: 0,
},
{
address: withoutName,
coin_balance: '0',
tx_count: 0,
},
],
next_page_params: {
items_count: 50,
world: '0x18f01f12ca21b6fc97b917c3e32f671f8a933caa',
},
};
...@@ -230,7 +230,7 @@ export type AddressMudRecords = { ...@@ -230,7 +230,7 @@ export type AddressMudRecords = {
} }
export type AddressMudRecordsItem = { export type AddressMudRecordsItem = {
decoded: Record<string, string>; decoded: Record<string, string | Array<string>>;
id: string; id: string;
is_deleted: boolean; is_deleted: boolean;
timestamp: string; timestamp: string;
......
import { Box, useColorModeValue, chakra, HStack } from '@chakra-ui/react'; import { Box, useColorModeValue, chakra, Grid } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
...@@ -8,6 +8,8 @@ import CopyToClipboard from 'ui/shared/CopyToClipboard'; ...@@ -8,6 +8,8 @@ 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 LinkInternal from 'ui/shared/links/LinkInternal';
import useAddressQuery from '../utils/useAddressQuery';
type TableViewProps = { type TableViewProps = {
scrollRef?: React.RefObject<HTMLDivElement>; scrollRef?: React.RefObject<HTMLDivElement>;
className?: string; className?: string;
...@@ -40,7 +42,7 @@ const BreadcrumbItem = ({ text, href, isLast, scrollRef }: BreadcrumbItemProps) ...@@ -40,7 +42,7 @@ const BreadcrumbItem = ({ text, href, isLast, scrollRef }: BreadcrumbItemProps)
if (isLast) { if (isLast) {
return ( return (
<HStack gap={ 2 } overflow="hidden"> <Grid gap={ 2 } overflow="hidden" templateColumns="auto 24px">
<Box <Box
overflow="hidden" overflow="hidden"
whiteSpace="nowrap" whiteSpace="nowrap"
...@@ -49,12 +51,12 @@ const BreadcrumbItem = ({ text, href, isLast, scrollRef }: BreadcrumbItemProps) ...@@ -49,12 +51,12 @@ const BreadcrumbItem = ({ text, href, isLast, scrollRef }: BreadcrumbItemProps)
{ text } { text }
</Box> </Box>
<CopyToClipboard text={ href } type="link" mx={ 0 } color="text_secondary"/> <CopyToClipboard text={ href } type="link" mx={ 0 } color="text_secondary"/>
</HStack> </Grid>
); );
} }
return ( return (
<HStack gap={ 2 } overflow="hidden"> <Grid gap={ 2 } overflow="hidden" templateColumns="auto 24px">
<LinkInternal <LinkInternal
href={ href } href={ href }
onClick={ onLinkClick } onClick={ onLinkClick }
...@@ -65,7 +67,7 @@ const BreadcrumbItem = ({ text, href, isLast, scrollRef }: BreadcrumbItemProps) ...@@ -65,7 +67,7 @@ const BreadcrumbItem = ({ text, href, isLast, scrollRef }: BreadcrumbItemProps)
{ text } { text }
</LinkInternal> </LinkInternal>
{ !isLast && <IconSvg name="arrows/east" boxSize={ 6 } color={ iconColor }/> } { !isLast && <IconSvg name="arrows/east" boxSize={ 6 } color={ iconColor }/> }
</HStack> </Grid>
); );
}; };
...@@ -73,6 +75,8 @@ const AddressMudBreadcrumbs = (props: TableViewProps | RecordViewProps) => { ...@@ -73,6 +75,8 @@ const AddressMudBreadcrumbs = (props: TableViewProps | RecordViewProps) => {
const queryParams = { tab: 'mud', hash: props.hash }; const queryParams = { tab: 'mud', hash: props.hash };
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const addressQuery = useAddressQuery({ hash: props.hash });
return ( return (
<Box <Box
display={ isMobile ? 'flex' : 'grid' } display={ isMobile ? 'flex' : 'grid' }
...@@ -82,8 +86,9 @@ const AddressMudBreadcrumbs = (props: TableViewProps | RecordViewProps) => { ...@@ -82,8 +86,9 @@ const AddressMudBreadcrumbs = (props: TableViewProps | RecordViewProps) => {
alignItems="center" alignItems="center"
className={ props.className } className={ props.className }
width="fit-content" width="fit-content"
fontSize="sm"
> >
<IconSvg name="MUD" boxSize={ 5 } color="green.500"/> <IconSvg name="MUD" boxSize={ 5 } color={ addressQuery.data?.is_verified ? 'green.500' : 'text_secondary' }/>
<BreadcrumbItem <BreadcrumbItem
text="MUD World" text="MUD World"
href={ route({ pathname: '/address/[hash]', query: queryParams }) } href={ route({ pathname: '/address/[hash]', query: queryParams }) }
......
import { Box } from '@chakra-ui/react';
import React from 'react';
import { mudRecord } from 'mocks/mud/mudTables';
import { test, expect, devices } from 'playwright/lib';
import AddressMudRecord from './AddressMudRecord';
const ADDRESS_HASH = 'hash';
const TABLE_ID = '123';
const RECORD_ID = '234';
const hooksConfig = {
router: {
query: { hash: ADDRESS_HASH },
},
};
test('base view', async({ render, mockApiResponse }) => {
await mockApiResponse('address_mud_record', mudRecord, { pathParams: { hash: ADDRESS_HASH, table_id: TABLE_ID, record_id: RECORD_ID } });
const component = await render(
<Box pt={{ base: '134px', lg: 6 }}>
<AddressMudRecord tableId={ TABLE_ID } recordId={ RECORD_ID }/>
</Box>,
{ hooksConfig },
);
await expect(component).toHaveScreenshot();
});
test.describe('mobile', () => {
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
test('base view', async({ render, mockApiResponse }) => {
await mockApiResponse('address_mud_record', mudRecord, { pathParams: { hash: ADDRESS_HASH, table_id: TABLE_ID, record_id: RECORD_ID } });
const component = await render(
<Box pt={{ base: '134px', lg: 6 }}>
<AddressMudRecord tableId={ TABLE_ID } recordId={ RECORD_ID }/>
</Box>,
{ hooksConfig },
);
await expect(component).toHaveScreenshot();
});
});
...@@ -10,6 +10,7 @@ import TruncatedValue from 'ui/shared/TruncatedValue'; ...@@ -10,6 +10,7 @@ import TruncatedValue from 'ui/shared/TruncatedValue';
import AddressMudBreadcrumbs from './AddressMudBreadcrumbs'; import AddressMudBreadcrumbs from './AddressMudBreadcrumbs';
import AddressMudRecordValues from './AddressMudRecordValues'; import AddressMudRecordValues from './AddressMudRecordValues';
import { getValueString } from './utils';
type Props ={ type Props ={
scrollRef?: React.RefObject<HTMLDivElement>; scrollRef?: React.RefObject<HTMLDivElement>;
...@@ -55,12 +56,12 @@ const AddressMudRecord = ({ tableId, recordId, isQueryEnabled = true, scrollRef ...@@ -55,12 +56,12 @@ const AddressMudRecord = ({ tableId, recordId, isQueryEnabled = true, scrollRef
<Table borderRadius="8px" style={{ tableLayout: 'auto' }} width="100%" overflow="hidden"> <Table 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' }> <Tr key={ keyName } borderBottomStyle={ index === data.schema.key_names.length - 1 ? 'hidden' : 'solid' }>
<Td fontWeight={ 600 } whiteSpace="nowrap"> <Td fontWeight={ 600 } whiteSpace="nowrap" fontSize="sm">
{ keyName } ({ data.schema.key_types[index] }) { keyName } ({ data.schema.key_types[index] })
</Td> </Td>
<Td colSpan={ 2 }> <Td colSpan={ 2 } fontSize="sm">
<Flex justifyContent="space-between"> <Flex justifyContent="space-between">
<TruncatedValue value={ 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> </Td>
...@@ -72,12 +73,12 @@ const AddressMudRecord = ({ tableId, recordId, isQueryEnabled = true, scrollRef ...@@ -72,12 +73,12 @@ const AddressMudRecord = ({ tableId, recordId, isQueryEnabled = true, scrollRef
<Hide above="lg" ssr={ false }> <Hide above="lg" ssr={ false }>
<> <>
{ 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"> <VStack gap={ 1 } key={ keyName } alignItems="start" fontSize="sm">
<Divider/> <Divider/>
<Text fontWeight={ 600 } whiteSpace="nowrap"> <Text fontWeight={ 600 } whiteSpace="nowrap">
{ keyName } ({ data.schema.key_types[index] }) { keyName } ({ data.schema.key_types[index] })
</Text> </Text>
<Text wordBreak="break-all">{ data.record.decoded[keyName] }</Text> <Text wordBreak="break-word">{ getValueString(data.record.decoded[keyName]) }</Text>
{ 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>
)) } )) }
......
...@@ -3,6 +3,8 @@ import React from 'react'; ...@@ -3,6 +3,8 @@ import React from 'react';
import type { AddressMudRecord } from 'types/api/address'; import type { AddressMudRecord } from 'types/api/address';
import { getValueString } from './utils';
type Props ={ type Props ={
data?: AddressMudRecord; data?: AddressMudRecord;
} }
...@@ -16,19 +18,19 @@ const AddressMudRecordValues = ({ data }: Props) => { ...@@ -16,19 +18,19 @@ const AddressMudRecordValues = ({ data }: Props) => {
return ( return (
<> <>
<Tr backgroundColor={ valuesBgColor } borderBottomStyle="hidden"> <Tr backgroundColor={ valuesBgColor } borderBottomStyle="hidden" >
<Td fontWeight={ 600 }>Field</Td> <Td fontWeight={ 600 } w="100px" fontSize="sm">Field</Td>
<Td fontWeight={ 600 }>Type</Td> <Td fontWeight={ 600 } w="90px" fontSize="sm">Type</Td>
<Td fontWeight={ 600 } w="100%" wordBreak="break-all">Value</Td> <Td fontWeight={ 600 } fontSize="sm">Value</Td>
</Tr> </Tr>
{ {
data?.schema.value_names.map((valName, index) => ( data?.schema.value_names.map((valName, index) => (
<Tr key={ valName } backgroundColor={ valuesBgColor } borderBottomStyle="hidden"> <Tr key={ valName } backgroundColor={ valuesBgColor } borderBottomStyle="hidden">
<Td whiteSpace="nowrap" py={ 0 } pb={ 4 }>{ valName }</Td> <Td fontSize="sm" w="100px" py={ 0 } pb={ 4 } pr={ 0 }wordBreak="break-all">{ valName }</Td>
<Td py={ 0 } pb={ 4 }>{ data.schema.value_types[index] }</Td> <Td fontSize="sm" w="90px" py={ 0 } pb={ 4 } wordBreak="break-all">{ data.schema.value_types[index] }</Td>
<Td w="100%" wordBreak="break-all" py={ 0 } pb={ 4 }> <Td fontSize="sm" wordBreak="break-word" py={ 0 } pb={ 4 }>
<Box> <Box>
{ data.record.decoded[valName] } { getValueString(data.record.decoded[valName]) }
</Box> </Box>
</Td> </Td>
</Tr> </Tr>
......
import type { StyleProps } from '@chakra-ui/react'; import type { StyleProps } from '@chakra-ui/react';
import { Box, Link, Table, Tbody, Td, Th, Tr, Flex, useColorModeValue, useBoolean } 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';
import type { AddressMudRecords, AddressMudRecordsFilter, AddressMudRecordsSorting } from 'types/api/address'; import type { AddressMudRecords, AddressMudRecordsFilter, AddressMudRecordsSorting } from 'types/api/address';
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 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 { default as Thead } from 'ui/shared/TheadSticky';
import AddressMudRecordsKeyFilter from './AddressMudRecordsKeyFilter'; import AddressMudRecordsKeyFilter from './AddressMudRecordsKeyFilter';
import { getNameTypeText } from './utils'; import { getNameTypeText, getValueString } from './utils';
const COL_MIN_WIDTH = 180; const COL_MIN_WIDTH = 180;
const COL_MIN_WIDTH_MOBILE = 140; const COL_MIN_WIDTH_MOBILE = 140;
...@@ -28,6 +31,7 @@ type Props = { ...@@ -28,6 +31,7 @@ type Props = {
filters: AddressMudRecordsFilter; filters: AddressMudRecordsFilter;
toggleTableHasHorisontalScroll: () => void; toggleTableHasHorisontalScroll: () => void;
scrollRef?: React.RefObject<HTMLDivElement>; scrollRef?: React.RefObject<HTMLDivElement>;
hash: string;
} }
const AddressMudRecordsTable = ({ const AddressMudRecordsTable = ({
...@@ -39,6 +43,7 @@ const AddressMudRecordsTable = ({ ...@@ -39,6 +43,7 @@ const AddressMudRecordsTable = ({
setFilters, setFilters,
toggleTableHasHorisontalScroll, toggleTableHasHorisontalScroll,
scrollRef, scrollRef,
hash,
}: Props) => { }: Props) => {
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);
...@@ -46,23 +51,32 @@ const AddressMudRecordsTable = ({ ...@@ -46,23 +51,32 @@ const AddressMudRecordsTable = ({
const [ isOpened, setIsOpened ] = useBoolean(false); const [ isOpened, setIsOpened ] = useBoolean(false);
const [ hasCut, setHasCut ] = useBoolean(isMobile ? totalColsCut > MIN_CUT_COUNT : true); const [ hasCut, setHasCut ] = useBoolean(isMobile ? totalColsCut > MIN_CUT_COUNT : true);
const containerRef = React.useRef<HTMLTableElement>(null);
const tableRef = React.useRef<HTMLTableElement>(null); const tableRef = React.useRef<HTMLTableElement>(null);
const router = useRouter(); const router = useRouter();
const toggleIsOpen = React.useCallback(() => { const toggleIsOpen = React.useCallback(() => {
isOpened && tableRef.current?.scroll({ left: 0 });
setIsOpened.toggle(); setIsOpened.toggle();
toggleTableHasHorisontalScroll(); toggleTableHasHorisontalScroll();
}, [ setIsOpened, toggleTableHasHorisontalScroll ]); }, [ setIsOpened, toggleTableHasHorisontalScroll, isOpened ]);
const onRecordClick = React.useCallback((e: React.MouseEvent) => { const onRecordClick = React.useCallback((e: React.MouseEvent) => {
const newQuery = { if (e.metaKey || e.ctrlKey) {
...router.query, // Allow opening in a new tab/window with right-click or ctrl/cmd+click
record_id: e.currentTarget.getAttribute('data-id') as string, return;
}; }
router.push({ pathname: router.pathname, query: newQuery }, undefined, { shallow: true });
e.preventDefault();
router.push(
{ pathname: '/address/[hash]', query: { hash, tab: 'mud', table_id: data.table.table_id, record_id: e.currentTarget.getAttribute('data-id') as string } },
undefined,
{ shallow: true },
);
scrollRef?.current?.scrollIntoView(); scrollRef?.current?.scrollIntoView();
}, [ router, scrollRef ]); }, [ router, scrollRef, hash, data.table.table_id ]);
const handleFilterChange = React.useCallback((field: keyof AddressMudRecordsFilter) => (val: string) => { const handleFilterChange = React.useCallback((field: keyof AddressMudRecordsFilter) => (val: string) => {
setFilters(prev => { setFilters(prev => {
...@@ -80,8 +94,8 @@ const AddressMudRecordsTable = ({ ...@@ -80,8 +94,8 @@ const AddressMudRecordsTable = ({
const keyBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); const keyBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
React.useEffect(() => { React.useEffect(() => {
if (hasCut && !colsCutCount && tableRef.current) { if (hasCut && !colsCutCount && containerRef.current) {
const count = Math.floor((tableRef.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 > 2 && count - 1 < totalColsCut) { if (totalColsCut > 2 && count - 1 < totalColsCut) {
setColsCutCount(count - 1); setColsCutCount(count - 1);
} else { } else {
...@@ -90,22 +104,15 @@ const AddressMudRecordsTable = ({ ...@@ -90,22 +104,15 @@ const AddressMudRecordsTable = ({
} }
}, [ colsCutCount, data.schema, hasCut, setHasCut, totalColsCut ]); }, [ colsCutCount, data.schema, hasCut, setHasCut, totalColsCut ]);
const cutWidth = `${ CUT_COL_WIDTH }px `;
const colW = isMobile ? COL_MIN_WIDTH_MOBILE : COL_MIN_WIDTH; const colW = isMobile ? COL_MIN_WIDTH_MOBILE : COL_MIN_WIDTH;
const tdStyles: StyleProps = { const tdStyles: StyleProps = {
wordBreak: 'break-all',
whiteSpace: 'normal',
minW: `${ colW }px`,
w: `${ colW }px`,
};
const thStyles: StyleProps = {
wordBreak: 'break-word', wordBreak: 'break-word',
whiteSpace: 'normal', whiteSpace: 'normal',
minW: `${ colW }px`, minW: `${ colW }px`,
w: `${ colW }px`, w: `${ colW }px`,
verticalAlign: 'baseline',
lineHeight: '20px',
}; };
const keys = (isOpened || !hasCut) ? data.schema.key_names : data.schema.key_names.slice(0, colsCutCount); const keys = (isOpened || !hasCut) ? data.schema.key_names : data.schema.key_names.slice(0, colsCutCount);
...@@ -113,16 +120,28 @@ const AddressMudRecordsTable = ({ ...@@ -113,16 +120,28 @@ const AddressMudRecordsTable = ({
const hasHorizontalScroll = isMobile || isOpened; const hasHorizontalScroll = isMobile || isOpened;
if (hasCut && !colsCutCount) {
return <Box w="100%" ref={ containerRef }></Box>;
}
const cutButton = (
<Th width={ `${ CUT_COL_WIDTH }px ` } verticalAlign="baseline">
<Tooltip label={ isOpened ? 'Hide columns' : 'Show all columns' }>
<Link onClick={ toggleIsOpen } aria-label="show/hide columns">...</Link>
</Tooltip>
</Th>
);
return ( return (
// can't implement both horisontal table scroll and sticky header // can't implement both horisontal table scroll and sticky header
<Box maxW="100%" overflowX={ hasHorizontalScroll ? 'scroll' : 'unset' } whiteSpace="nowrap"> <Box maxW="100%" overflowX={ hasHorizontalScroll ? 'scroll' : 'unset' } whiteSpace="nowrap" ref={ tableRef }>
<Table variant="simple" size="sm" style={{ tableLayout: 'fixed' }} ref={ tableRef }> <Table variant="simple" size="sm" style={{ tableLayout: 'fixed' }}>
<Thead top={ hasHorizontalScroll ? 0 : top } display={ hasHorizontalScroll ? 'table' : 'table-header-group' } w="100%"> <Thead top={ hasHorizontalScroll ? 0 : top } display={ hasHorizontalScroll ? 'table' : 'table-header-group' } w="100%">
<Tr > <Tr >
{ 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 } { ...thStyles }> <Th key={ keyName } { ...tdStyles }>
{ index < 2 ? ( { index < 2 ? (
<Flex> <Flex>
<Link onClick={ onKeySortClick } data-id={ index } display="flex" alignItems="start" lineHeight="20px" mr={ 2 }> <Link onClick={ onKeySortClick } data-id={ index } display="flex" alignItems="start" lineHeight="20px" mr={ 2 }>
...@@ -143,13 +162,13 @@ const AddressMudRecordsTable = ({ ...@@ -143,13 +162,13 @@ const AddressMudRecordsTable = ({
); );
}) } }) }
{ values.map((valName, index) => ( { values.map((valName, index) => (
<Th key={ valName } { ...thStyles }> <Th key={ valName } { ...tdStyles }>
{ capitalizeFirstLetter(valName) } ({ data.schema.value_types[index] }) { capitalizeFirstLetter(valName) } ({ data.schema.value_types[index] })
</Th> </Th>
)) } )) }
{ hasCut && !isOpened && <Th width={ cutWidth }><Link onClick={ toggleIsOpen }>...</Link></Th> } { hasCut && !isOpened && cutButton }
<Th { ...thStyles }>Modified</Th> <Th { ...tdStyles }>Modified</Th>
{ hasCut && isOpened && <Th width={ cutWidth }><Link onClick={ toggleIsOpen }>...</Link></Th> } { hasCut && isOpened && cutButton }
</Tr> </Tr>
</Thead> </Thead>
<Tbody display={ hasHorizontalScroll ? 'table' : 'table-row-group' } w="100%"> <Tbody display={ hasHorizontalScroll ? 'table' : 'table-row-group' } w="100%">
...@@ -157,17 +176,23 @@ const AddressMudRecordsTable = ({ ...@@ -157,17 +176,23 @@ const AddressMudRecordsTable = ({
<Tr key={ item.id }> <Tr key={ item.id }>
{ keys.map((keyName, index) => ( { keys.map((keyName, index) => (
<Td key={ keyName } backgroundColor={ keyBgColor } { ...tdStyles }> <Td key={ keyName } backgroundColor={ keyBgColor } { ...tdStyles }>
{ index === 0 ? { index === 0 ? (
<Link onClick={ onRecordClick } data-id={ item.id } fontWeight={ 700 }>{ item.decoded[keyName].toString() }</Link> : <LinkInternal
item.decoded[keyName].toString() onClick={ onRecordClick }
} data-id={ item.id }
fontWeight={ 700 }
href={ route({ pathname: '/address/[hash]', query: { hash, tab: 'mud', table_id: data.table.table_id, record_id: item.id } }) }
>
{ getValueString(item.decoded[keyName]) }
</LinkInternal>
) : getValueString(item.decoded[keyName]) }
</Td> </Td>
)) } )) }
{ values.map((valName) => { values.map((valName) =>
<Td key={ valName } { ...tdStyles }>{ item.decoded[valName].toString() }</Td>) } <Td key={ valName } { ...tdStyles }>{ getValueString(item.decoded[valName]) }</Td>) }
{ hasCut && !isOpened && <Td width={ cutWidth }></Td> } { hasCut && !isOpened && <Td width={ `${ CUT_COL_WIDTH }px ` }></Td> }
<Td { ...tdStyles } color="text_secondary">{ dayjs(item.timestamp).format('lll') }</Td> <Td { ...tdStyles } color="text_secondary">{ dayjs(item.timestamp).format('lll') }</Td>
{ hasCut && isOpened && <Td width={ cutWidth }></Td> } { hasCut && isOpened && <Td width={ `${ CUT_COL_WIDTH }px ` }></Td> }
</Tr> </Tr>
)) } )) }
</Tbody> </Tbody>
......
import { Box } from '@chakra-ui/react';
import React from 'react';
import { mudRecords } from 'mocks/mud/mudTables';
import { test, expect } from 'playwright/lib';
import AddressMudTable from './AddressMudTable';
const ADDRESS_HASH = 'hash';
const TABLE_ID = '123';
const hooksConfig = {
router: {
query: { hash: ADDRESS_HASH },
},
};
test('base view +@mobile', async({ render, mockApiResponse }) => {
await mockApiResponse('address_mud_records', mudRecords, { pathParams: { hash: ADDRESS_HASH, table_id: TABLE_ID } });
const component = await render(
<Box pt={{ base: '134px', lg: 6 }}>
<AddressMudTable tableId={ TABLE_ID }/>
</Box>,
{ hooksConfig },
);
await expect(component).toHaveScreenshot();
});
test('expanded view +@mobile', async({ render, mockApiResponse }) => {
await mockApiResponse('address_mud_records', mudRecords, { pathParams: { hash: ADDRESS_HASH, table_id: TABLE_ID } });
const component = await render(
<Box pt={{ base: '134px', lg: 6 }}>
<AddressMudTable tableId={ TABLE_ID }/>
</Box>,
{ hooksConfig },
);
await component.locator('a[aria-label="show/hide columns"]').first().click();
await expect(component).toHaveScreenshot();
});
...@@ -125,6 +125,7 @@ const AddressMudTable = ({ scrollRef, tableId, isQueryEnabled = true }: Props) = ...@@ -125,6 +125,7 @@ const AddressMudTable = ({ scrollRef, tableId, isQueryEnabled = true }: Props) =
filters={ filters } filters={ filters }
toggleTableHasHorisontalScroll={ setTableHasHorisontalScroll.toggle } toggleTableHasHorisontalScroll={ setTableHasHorisontalScroll.toggle }
scrollRef={ scrollRef } scrollRef={ scrollRef }
hash={ hash }
/> />
) : null; ) : null;
......
import { Box } from '@chakra-ui/react';
import React from 'react';
import { mudTables } from 'mocks/mud/mudTables';
import { test, expect } from 'playwright/lib';
import AddressMudTables from './AddressMudTables';
const ADDRESS_HASH = 'hash';
const hooksConfig = {
router: {
query: { hash: ADDRESS_HASH, q: 'o' },
},
};
test('base view +@mobile', async({ render, mockApiResponse }) => {
await mockApiResponse('address_mud_tables', mudTables, { pathParams: { hash: ADDRESS_HASH }, queryParams: { q: 'o' } });
const component = await render(
<Box pt={{ base: '134px', lg: 6 }}>
<AddressMudTables/>
</Box>,
{ hooksConfig },
);
await expect(component).toHaveScreenshot();
});
test('with schema opened +@mobile', async({ render, mockApiResponse }) => {
await mockApiResponse('address_mud_tables', mudTables, { pathParams: { hash: ADDRESS_HASH }, queryParams: { q: 'o' } });
const component = await render(
<Box pt={{ base: '134px', lg: 6 }}>
<AddressMudTables/>
</Box>,
{ hooksConfig },
);
await component.locator('div[aria-label="View schema"]').first().click();
await expect(component).toHaveScreenshot();
});
...@@ -2,6 +2,7 @@ import { Hide, Show } from '@chakra-ui/react'; ...@@ -2,6 +2,7 @@ import { Hide, Show } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import useDebounce from 'lib/hooks/useDebounce';
import useIsInitialLoading from 'lib/hooks/useIsInitialLoading'; import useIsInitialLoading from 'lib/hooks/useIsInitialLoading';
import { apos } from 'lib/html-entities'; import { apos } from 'lib/html-entities';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
...@@ -27,11 +28,12 @@ const AddressMudTables = ({ scrollRef, isQueryEnabled = true }: Props) => { ...@@ -27,11 +28,12 @@ const AddressMudTables = ({ scrollRef, isQueryEnabled = true }: Props) => {
const hash = getQueryParamString(router.query.hash); const hash = getQueryParamString(router.query.hash);
const q = getQueryParamString(router.query.q); const q = getQueryParamString(router.query.q);
const [ searchTerm, setSearchTerm ] = React.useState<string>(q || ''); const [ searchTerm, setSearchTerm ] = React.useState<string>(q || '');
const debouncedSearchTerm = useDebounce(searchTerm, 300);
const { data, isPlaceholderData, isError, pagination } = useQueryWithPages({ const { data, isPlaceholderData, isError, pagination } = useQueryWithPages({
resourceName: 'address_mud_tables', resourceName: 'address_mud_tables',
pathParams: { hash }, pathParams: { hash },
filters: { q: searchTerm }, filters: { q: debouncedSearchTerm },
scrollRef, scrollRef,
options: { options: {
enabled: isQueryEnabled, enabled: isQueryEnabled,
...@@ -71,6 +73,7 @@ const AddressMudTables = ({ scrollRef, isQueryEnabled = true }: Props) => { ...@@ -71,6 +73,7 @@ const AddressMudTables = ({ scrollRef, isQueryEnabled = true }: Props) => {
isLoading={ isPlaceholderData } isLoading={ isPlaceholderData }
top={ ACTION_BAR_HEIGHT_DESKTOP } top={ ACTION_BAR_HEIGHT_DESKTOP }
scrollRef={ scrollRef } scrollRef={ scrollRef }
hash={ hash }
/> />
</Hide> </Hide>
<Show below="lg" ssr={ false }> <Show below="lg" ssr={ false }>
...@@ -79,6 +82,7 @@ const AddressMudTables = ({ scrollRef, isQueryEnabled = true }: Props) => { ...@@ -79,6 +82,7 @@ const AddressMudTables = ({ scrollRef, isQueryEnabled = true }: Props) => {
key={ item.table.table_id + (isPlaceholderData ? String(index) : '') } key={ item.table.table_id + (isPlaceholderData ? String(index) : '') }
item={ item } item={ item }
isLoading={ isPlaceholderData } isLoading={ isPlaceholderData }
hash={ hash }
/> />
)) } )) }
</Show> </Show>
......
...@@ -4,30 +4,41 @@ import React from 'react'; ...@@ -4,30 +4,41 @@ import React from 'react';
import type { AddressMudTableItem } from 'types/api/address'; import type { AddressMudTableItem } from 'types/api/address';
import { route } from 'nextjs-routes';
import Tag from 'ui/shared/chakra/Tag'; import Tag from 'ui/shared/chakra/Tag';
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 = {
item: AddressMudTableItem; item: AddressMudTableItem;
isLoading: boolean; isLoading: boolean;
scrollRef?: React.RefObject<HTMLDivElement>; scrollRef?: React.RefObject<HTMLDivElement>;
hash: string;
}; };
const AddressMudTablesListItem = ({ item, isLoading, scrollRef }: Props) => { const AddressMudTablesListItem = ({ item, isLoading, scrollRef, hash }: Props) => {
const [ isOpened, setIsOpened ] = useBoolean(false); const [ isOpened, setIsOpened ] = useBoolean(false);
const router = useRouter(); const router = useRouter();
const onTableClick = React.useCallback((e: React.MouseEvent) => { const onTableClick = React.useCallback((e: React.MouseEvent) => {
const newQuery = { if (e.metaKey || e.ctrlKey) {
...router.query, // Allow opening in a new tab/window with right-click or ctrl/cmd+click
table_id: e.currentTarget.getAttribute('data-id') as string, return;
}; }
router.push({ pathname: router.pathname, query: newQuery }, undefined, { shallow: true });
e.preventDefault();
router.push(
{ pathname: '/address/[hash]', query: { hash, tab: 'mud', table_id: e.currentTarget.getAttribute('data-id') as string } },
undefined,
{ shallow: true },
);
scrollRef?.current?.scrollIntoView(); scrollRef?.current?.scrollIntoView();
}, [ router, scrollRef ]); }, [ router, scrollRef, hash ]);
return ( return (
<ListItemMobile rowGap={ 3 } fontSize="sm" py={ 3 }> <ListItemMobile rowGap={ 3 } fontSize="sm" py={ 3 }>
...@@ -41,15 +52,21 @@ const AddressMudTablesListItem = ({ item, isLoading, scrollRef }: Props) => { ...@@ -41,15 +52,21 @@ const AddressMudTablesListItem = ({ item, isLoading, scrollRef }: Props) => {
cursor="pointer" cursor="pointer"
onClick={ setIsOpened.toggle } onClick={ setIsOpened.toggle }
transitionDuration="faster" transitionDuration="faster"
aria-label="View schema"
/> />
</Link> </Link>
</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 isLoaded={ !isLoading }>
<Link onClick={ onTableClick } data-id={ item.table.table_id } fontWeight={ 500 }> <LinkInternal
onClick={ onTableClick }
data-id={ item.table.table_id }
fontWeight={ 500 }
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 }
</Link> </LinkInternal>
</Skeleton> </Skeleton>
<Skeleton isLoaded={ !isLoading } color="text_secondary"> <Skeleton isLoaded={ !isLoading } color="text_secondary">
{ item.table.table_type } { item.table.table_type }
......
...@@ -12,10 +12,11 @@ type Props = { ...@@ -12,10 +12,11 @@ type Props = {
isLoading: boolean; isLoading: boolean;
top: number; top: number;
scrollRef?: React.RefObject<HTMLDivElement>; scrollRef?: React.RefObject<HTMLDivElement>;
hash: string;
} }
//sorry for the naming //sorry for the naming
const AddressMudTablesTable = ({ items, isLoading, top, scrollRef }: Props) => { const AddressMudTablesTable = ({ items, isLoading, top, scrollRef, hash }: Props) => {
return ( return (
<Table variant="simple" size="sm" style={{ tableLayout: 'auto' }}> <Table variant="simple" size="sm" style={{ tableLayout: 'auto' }}>
<Thead top={ top }> <Thead top={ top }>
...@@ -33,6 +34,7 @@ const AddressMudTablesTable = ({ items, isLoading, top, scrollRef }: Props) => { ...@@ -33,6 +34,7 @@ const AddressMudTablesTable = ({ items, isLoading, top, scrollRef }: Props) => {
item={ item } item={ item }
isLoading={ isLoading } isLoading={ isLoading }
scrollRef={ scrollRef } scrollRef={ scrollRef }
hash={ hash }
/> />
)) } )) }
</Tbody> </Tbody>
......
...@@ -4,28 +4,39 @@ import React from 'react'; ...@@ -4,28 +4,39 @@ import React from 'react';
import type { AddressMudTableItem } from 'types/api/address'; import type { AddressMudTableItem } from 'types/api/address';
import { route } from 'nextjs-routes';
import Tag from 'ui/shared/chakra/Tag'; import Tag from 'ui/shared/chakra/Tag';
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;
isLoading: boolean; isLoading: boolean;
scrollRef?: React.RefObject<HTMLDivElement>; scrollRef?: React.RefObject<HTMLDivElement>;
hash: string;
}; };
const AddressMudTablesTableItem = ({ item, isLoading, scrollRef }: Props) => { const AddressMudTablesTableItem = ({ item, isLoading, scrollRef, hash }: Props) => {
const [ isOpened, setIsOpened ] = useBoolean(false); const [ isOpened, setIsOpened ] = useBoolean(false);
const router = useRouter(); const router = useRouter();
const onTableClick = React.useCallback((e: React.MouseEvent) => { const onTableClick = React.useCallback((e: React.MouseEvent) => {
const newQuery = { if (e.metaKey || e.ctrlKey) {
...router.query, // Allow opening in a new tab/window with right-click or ctrl/cmd+click
table_id: e.currentTarget.getAttribute('data-id') as string, return;
}; }
router.push({ pathname: router.pathname, query: newQuery }, undefined, { shallow: true });
e.preventDefault();
router.push(
{ pathname: '/address/[hash]', query: { hash, tab: 'mud', table_id: e.currentTarget.getAttribute('data-id') as string } },
undefined,
{ shallow: true },
);
scrollRef?.current?.scrollIntoView(); scrollRef?.current?.scrollIntoView();
}, [ router, scrollRef ]); }, [ router, scrollRef, hash ]);
return ( return (
<> <>
...@@ -40,15 +51,21 @@ const AddressMudTablesTableItem = ({ item, isLoading, scrollRef }: Props) => { ...@@ -40,15 +51,21 @@ const AddressMudTablesTableItem = ({ item, isLoading, scrollRef }: Props) => {
cursor="pointer" cursor="pointer"
onClick={ setIsOpened.toggle } onClick={ setIsOpened.toggle }
transitionDuration="faster" transitionDuration="faster"
aria-label="View schema"
/> />
</Link> </Link>
</Skeleton> </Skeleton>
</Td> </Td>
<Td verticalAlign="middle"> <Td verticalAlign="middle">
<Skeleton isLoaded={ !isLoading }> <Skeleton isLoaded={ !isLoading }>
<Link onClick={ onTableClick } data-id={ item.table.table_id }> <LinkInternal
href={ route({ pathname: '/address/[hash]', query: { hash, tab: 'mud', table_id: item.table.table_id } }) }
data-id={ item.table.table_id }
onClick={ onTableClick }
fontWeight={ 700 }
>
{ item.table.table_full_name } { item.table.table_full_name }
</Link> </LinkInternal>
</Skeleton> </Skeleton>
</Td> </Td>
<Td verticalAlign="middle"> <Td verticalAlign="middle">
...@@ -69,7 +86,7 @@ const AddressMudTablesTableItem = ({ item, isLoading, scrollRef }: Props) => { ...@@ -69,7 +86,7 @@ const AddressMudTablesTableItem = ({ item, isLoading, scrollRef }: Props) => {
<Table> <Table>
{ Boolean(item.schema.key_names.length) && ( { Boolean(item.schema.key_names.length) && (
<Tr> <Tr>
<Td width="80px" fontSize="sm" fontWeight={ 600 } py={ 2 }>Key</Td> <Td width="80px" fontSize="sm" fontWeight={ 600 } py={ 2 } verticalAlign="middle">Key</Td>
<Td py={ 2 }> <Td py={ 2 }>
<VStack gap={ 1 } alignItems="start"> <VStack gap={ 1 } alignItems="start">
{ item.schema.key_names.map((name, index) => ( { item.schema.key_names.map((name, index) => (
......
...@@ -8,3 +8,11 @@ export const SORT_SEQUENCE: Record<'key0' | 'key1', Array<'desc' | 'asc' | undef ...@@ -8,3 +8,11 @@ export const SORT_SEQUENCE: Record<'key0' | 'key1', Array<'desc' | 'asc' | undef
export const getNameTypeText = (name: string, type: string) => { export const getNameTypeText = (name: string, type: string) => {
return capitalizeFirstLetter(name) + ' (' + type + ')'; return capitalizeFirstLetter(name) + ' (' + type + ')';
}; };
export const getValueString = (value: string | Array<string>) => {
if (Array.isArray(value)) {
return value.join(', ');
}
return value;
};
...@@ -7,15 +7,9 @@ import type { MudWorldItem } from 'types/api/mudWorlds'; ...@@ -7,15 +7,9 @@ import type { MudWorldItem } from 'types/api/mudWorlds';
import config from 'configs/app'; import config from 'configs/app';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
const mudFrameworkFeature = config.features.mudFramework;
type Props = { item: MudWorldItem; isLoading?: boolean }; type Props = { item: MudWorldItem; isLoading?: boolean };
const MudWorldsTableItem = ({ item, isLoading }: Props) => { const MudWorldsTableItem = ({ item, isLoading }: Props) => {
if (!mudFrameworkFeature.isEnabled) {
return null;
}
const addressBalance = BigNumber(item.coin_balance).div(BigNumber(10 ** config.chain.currency.decimals)); const addressBalance = BigNumber(item.coin_balance).div(BigNumber(10 ** config.chain.currency.decimals));
const addressBalanceChunks = addressBalance.dp(8).toFormat().split('.'); const addressBalanceChunks = addressBalance.dp(8).toFormat().split('.');
......
...@@ -272,16 +272,21 @@ const AddressPageContent = () => { ...@@ -272,16 +272,21 @@ const AddressPageContent = () => {
<RoutedTabs tabs={ tabs } tabListProps={{ mt: 6 }} isLoading={ isTabsLoading }/>; <RoutedTabs tabs={ tabs } tabListProps={{ mt: 6 }} isLoading={ isTabsLoading }/>;
const backLink = React.useMemo(() => { const backLink = React.useMemo(() => {
const hasGoBackLink = appProps.referrer && appProps.referrer.includes('/accounts'); if (appProps.referrer && appProps.referrer.includes('/accounts')) {
return {
label: 'Back to top accounts list',
url: appProps.referrer,
};
}
if (!hasGoBackLink) { if (appProps.referrer && appProps.referrer.includes('/mud-worlds')) {
return; return {
label: 'Back to MUD worlds list',
url: appProps.referrer,
};
} }
return { return;
label: 'Back to top accounts list',
url: appProps.referrer,
};
}, [ appProps.referrer ]); }, [ appProps.referrer ]);
const titleSecondRow = ( const titleSecondRow = (
......
import React from 'react';
import { mudWorlds } from 'mocks/mud/mudWorlds';
import { test, expect } from 'playwright/lib';
import MudWorlds from './MudWorlds';
test('default view +@mobile', async({ mockTextAd, mockApiResponse, render }) => {
await mockTextAd();
await mockApiResponse('mud_worlds', mudWorlds);
const component = await render(<MudWorlds/>);
await expect(component).toHaveScreenshot();
});
...@@ -40,7 +40,7 @@ const TableColumnFilterWrapper = ({ columnName, isActive, className, children, i ...@@ -40,7 +40,7 @@ const TableColumnFilterWrapper = ({ columnName, isActive, className, children, i
variant="ghost" variant="ghost"
w="20px" w="20px"
h="20px" h="20px"
icon={ <IconSvg name="filter" w="20px" h="20px"/> } icon={ <IconSvg name="filter" w="19px" h="19px"/> }
isActive={ isActive } isActive={ isActive }
isDisabled={ isLoading } isDisabled={ isLoading }
borderRadius="4px" borderRadius="4px"
......
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