Commit 7bc58a3f authored by tom's avatar tom

pagination for blocks page (w/o sticky)

parent 2ad39b1d
import { Text, Show, Hide, Skeleton, Alert } from '@chakra-ui/react';
import { Text, Show, Hide, Alert } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import { useQueryClient } from '@tanstack/react-query';
import React from 'react';
......@@ -6,7 +7,7 @@ import type { SocketMessage } from 'lib/socket/types';
import type { BlockType, BlocksResponse } from 'types/api/block';
import { QueryKeys } from 'types/client/queries';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import useIsMobile from 'lib/hooks/useIsMobile';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import BlocksList from 'ui/blocks/BlocksList';
......@@ -15,25 +16,25 @@ import BlocksTable from 'ui/blocks/BlocksTable';
import ActionBar from 'ui/shared/ActionBar';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import Pagination from 'ui/shared/Pagination';
import type { Props as PaginationProps } from 'ui/shared/Pagination';
import SkeletonTable from 'ui/shared/SkeletonTable';
type QueryResult = UseQueryResult<BlocksResponse> & {
pagination: PaginationProps;
};
interface Props {
type?: BlockType;
query: QueryResult;
}
const BlocksContent = ({ type }: Props) => {
const BlocksContent = ({ type, query }: Props) => {
const queryClient = useQueryClient();
const isMobile = useIsMobile();
const [ socketAlert, setSocketAlert ] = React.useState('');
const { data, isLoading, isError, pagination } = useQueryWithPages({
apiPath: '/node-api/blocks',
queryName: QueryKeys.blocks,
filters: { type },
});
const isPaginatorHidden = !isLoading && !isError && pagination.page === 1 && !pagination.hasNextPage;
const handleNewBlockMessage: SocketMessage.NewBlock['handler'] = React.useCallback((payload) => {
queryClient.setQueryData([ QueryKeys.blocks, { page: pagination.page, filters: { type } } ], (prevData: BlocksResponse | undefined) => {
queryClient.setQueryData([ QueryKeys.blocks, { page: query.pagination.page, filters: { type } } ], (prevData: BlocksResponse | undefined) => {
const shouldAddToList = !type || type === payload.block.type;
if (!prevData) {
......@@ -44,7 +45,7 @@ const BlocksContent = ({ type }: Props) => {
}
return shouldAddToList ? { ...prevData, items: [ payload.block, ...prevData.items ] } : prevData;
});
}, [ pagination.page, queryClient, type ]);
}, [ query.pagination.page, queryClient, type ]);
const handleSocketClose = React.useCallback(() => {
setSocketAlert('Connection is lost. Please click here to load new blocks.');
......@@ -58,7 +59,7 @@ const BlocksContent = ({ type }: Props) => {
topic: 'blocks:new_block',
onSocketClose: handleSocketClose,
onSocketError: handleSocketError,
isDisabled: isLoading || isError || pagination.page !== 1,
isDisabled: query.isLoading || query.isError || query.pagination.page !== 1,
});
useSocketMessage({
channel,
......@@ -67,7 +68,7 @@ const BlocksContent = ({ type }: Props) => {
});
const content = (() => {
if (isLoading) {
if (query.isLoading) {
return (
<>
<Show below="lg" key="skeleton-mobile">
......@@ -80,37 +81,31 @@ const BlocksContent = ({ type }: Props) => {
);
}
if (isError) {
if (query.isError) {
return <DataFetchAlert/>;
}
if (data.items.length === 0) {
if (query.data.items.length === 0) {
return <Text as="span">There are no blocks.</Text>;
}
return (
<>
{ socketAlert && <Alert status="warning" mb={ 6 } as="a" href={ window.document.location.href }>{ socketAlert }</Alert> }
<Show below="lg" key="content-mobile"><BlocksList data={ data.items }/></Show>
<Hide below="lg" key="content-desktop"><BlocksTable data={ data.items } top={ isPaginatorHidden ? 0 : 80 } page={ pagination.page }/></Hide>
<Show below="lg" key="content-mobile"><BlocksList data={ query.data.items }/></Show>
<Hide below="lg" key="content-desktop"><BlocksTable data={ query.data.items } top={ 0 } page={ 1 }/></Hide>
</>
);
})();
const totalText = data?.items.length ?
<Text mb={{ base: 0, lg: 6 }}>Total of { data.items[0].height.toLocaleString() } blocks</Text> :
null;
const isPaginatorHidden = !query.isLoading && !query.isError && query.pagination.page === 1 && !query.pagination.hasNextPage;
return (
<>
{ !isLoading ?
totalText :
<Skeleton h="24px" w="200px" mb={{ base: 0, lg: 6 }}/>
}
{ !isPaginatorHidden && (
{ isMobile && !isPaginatorHidden && (
<ActionBar>
<Pagination ml="auto" { ...pagination }/>
<Pagination ml="auto" { ...query.pagination }/>
</ActionBar>
) }
{ content }
......@@ -118,4 +113,4 @@ const BlocksContent = ({ type }: Props) => {
);
};
export default BlocksContent;
export default React.memo(BlocksContent);
import { Flex, Box, Text, Skeleton } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import React from 'react';
import type { HomeStats } from 'types/api/stats';
import { QueryKeys } from 'types/client/queries';
import useFetch from 'lib/hooks/useFetch';
import useIsMobile from 'lib/hooks/useIsMobile';
import { nbsp } from 'lib/html-entities';
import type { Props as PaginationProps } from 'ui/shared/Pagination';
import Pagination from 'ui/shared/Pagination';
interface Props {
pagination: PaginationProps;
}
const BlocksTabSlot = ({ pagination }: Props) => {
const isMobile = useIsMobile();
const fetch = useFetch();
const statsQuery = useQuery<unknown, unknown, HomeStats>(
[ QueryKeys.homeStats ],
() => fetch('/node-api/stats'),
);
if (isMobile) {
return null;
}
return (
<Flex alignItems="center" columnGap={ 8 }>
{ statsQuery.isLoading && <Skeleton w="175px" h="24px"/> }
{ statsQuery.data?.network_utilization_percentage !== undefined && (
<Box>
<Text as="span" fontSize="sm">
Network utilization:{ nbsp }
</Text>
<Text as="span" fontSize="sm" color="blue.400" fontWeight={ 600 }>
{ statsQuery.data.network_utilization_percentage.toFixed(2) }%
</Text>
</Box>
) }
<Pagination my={ 1 } { ...pagination }/>
</Flex>
);
};
export default BlocksTabSlot;
import { useRouter } from 'next/router';
import React from 'react';
import type { BlockType } from 'types/api/block';
import { QueryKeys } from 'types/client/queries';
import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import BlocksContent from 'ui/blocks/BlocksContent';
import BlocksTabSlot from 'ui/blocks/BlocksTabSlot';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
const TABS: Array<RoutedTab> = [
{ id: 'blocks', title: 'All', component: <BlocksContent/> },
{ id: 'reorgs', title: 'Forked', component: <BlocksContent type="reorg"/> },
{ id: 'uncles', title: 'Uncles', component: <BlocksContent type="uncle"/> },
];
const TAB_TO_TYPE: Record<string, BlockType> = {
blocks: 'block',
reorgs: 'reorg',
uncles: 'uncle',
};
const BlocksPageContent = () => {
const router = useRouter();
const type = router.query.tab && !Array.isArray(router.query.tab) ? TAB_TO_TYPE[router.query.tab] : undefined;
const blocksQuery = useQueryWithPages({
apiPath: '/node-api/blocks',
queryName: QueryKeys.blocks,
filters: { type },
});
const tabs: Array<RoutedTab> = [
{ id: 'blocks', title: 'All', component: <BlocksContent query={ blocksQuery }/> },
{ id: 'reorgs', title: 'Forked', component: <BlocksContent type="reorg" query={ blocksQuery }/> },
{ id: 'uncles', title: 'Uncles', component: <BlocksContent type="uncle" query={ blocksQuery }/> },
];
return (
<Page>
<PageTitle text="Blocks"/>
<RoutedTabs tabs={ TABS } tabListMarginBottom={{ base: 6, lg: 8 }}/>
<RoutedTabs
tabs={ tabs }
tabListMarginBottom={{ base: 6, lg: 8 }}
rightSlot={ <BlocksTabSlot pagination={ blocksQuery.pagination }/> }
/>
</Page>
);
};
......
......@@ -5,6 +5,7 @@ import {
TabList,
TabPanel,
TabPanels,
Box,
} from '@chakra-ui/react';
import type { StyleProps } from '@chakra-ui/styled-system';
import { useRouter } from 'next/router';
......@@ -27,11 +28,13 @@ const hiddenItemStyles: StyleProps = {
interface Props {
tabs: Array<RoutedTab>;
tabListMarginBottom?: ChakraProps['marginBottom'];
rightSlot?: React.ReactNode;
}
const RoutedTabs = ({ tabs, tabListMarginBottom }: Props) => {
const RoutedTabs = ({ tabs, tabListMarginBottom, rightSlot }: Props) => {
const router = useRouter();
const [ activeTabIndex, setActiveTabIndex ] = useState<number>(tabs.length + 1);
useEffect(() => {
if (router.isReady) {
let tabIndex = 0;
......@@ -46,7 +49,7 @@ const RoutedTabs = ({ tabs, tabListMarginBottom }: Props) => {
}, [ tabs, router ]);
const isMobile = useIsMobile();
const { tabsCut, tabsList, tabsRefs, listRef } = useAdaptiveTabs(tabs, isMobile);
const { tabsCut, tabsList, tabsRefs, listRef, rightSlotRef } = useAdaptiveTabs(tabs, isMobile);
const handleTabChange = React.useCallback((index: number) => {
const nextTab = tabs[index];
......@@ -59,7 +62,7 @@ const RoutedTabs = ({ tabs, tabListMarginBottom }: Props) => {
}, [ tabs, router ]);
return (
<Tabs variant="soft-rounded" colorScheme="blue" isLazy onChange={ handleTabChange } index={ activeTabIndex }>
<Tabs variant="soft-rounded" colorScheme="blue" isLazy onChange={ handleTabChange } index={ activeTabIndex } position="relative">
<TabList
marginBottom={ tabListMarginBottom }
flexWrap="nowrap"
......@@ -112,6 +115,7 @@ const RoutedTabs = ({ tabs, tabListMarginBottom }: Props) => {
);
}) }
</TabList>
{ rightSlot ? <Box position="absolute" top={ 0 } right={ 0 } ref={ rightSlotRef }>{ rightSlot }</Box> : null }
<TabPanels>
{ tabsList.map((tab) => <TabPanel padding={ 0 } key={ tab.id }>{ tab.component }</TabPanel>) }
</TabPanels>
......
......@@ -11,9 +11,11 @@ export default function useAdaptiveTabs(tabs: Array<RoutedTab>, disabled?: boole
const [ tabsCut, setTabsCut ] = React.useState(disabled ? tabs.length : 0);
const [ tabsRefs, setTabsRefs ] = React.useState<Array<React.RefObject<HTMLButtonElement>>>([]);
const listRef = React.useRef<HTMLDivElement>(null);
const rightSlotRef = React.useRef<HTMLDivElement>(null);
const calculateCut = React.useCallback(() => {
const listWidth = listRef.current?.getBoundingClientRect().width;
const rightSlotWidth = rightSlotRef.current?.getBoundingClientRect().width || 0;
const tabWidths = tabsRefs.map((tab) => tab.current?.getBoundingClientRect().width);
const menuWidth = tabWidths[tabWidths.length - 1];
......@@ -26,11 +28,11 @@ export default function useAdaptiveTabs(tabs: Array<RoutedTab>, disabled?: boole
return result;
}
if (result.accWidth + item <= listWidth - menuWidth) {
if (result.accWidth + item <= listWidth - rightSlotWidth - menuWidth) {
return { visibleNum: result.visibleNum + 1, accWidth: result.accWidth + item };
}
if (result.accWidth + item <= listWidth && index === tabWidths.length - 2) {
if (result.accWidth + item <= listWidth - rightSlotWidth && index === tabWidths.length - 2) {
return { visibleNum: result.visibleNum + 1, accWidth: result.accWidth + item };
}
......@@ -82,6 +84,7 @@ export default function useAdaptiveTabs(tabs: Array<RoutedTab>, disabled?: boole
tabsList,
tabsRefs,
listRef,
rightSlotRef,
};
}, [ tabsList, tabsCut, tabsRefs, listRef ]);
}
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