Skip to content

Commit

Permalink
chart-tmp
Browse files Browse the repository at this point in the history
  • Loading branch information
isstuev committed Jun 17, 2024
1 parent f54e2e6 commit 4cb28cf
Show file tree
Hide file tree
Showing 17 changed files with 576 additions and 318 deletions.
20 changes: 20 additions & 0 deletions pages/stats/[id].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';

import type { Props } from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs';

const Chart = dynamic(() => import('ui/pages/Chart'), { ssr: false });

const Page: NextPage<Props> = (props: Props) => {
return (
<PageNextJs pathname="/stats/[id]" query={ props.query }>

Check failure on line 12 in pages/stats/[id].tsx

View workflow job for this annotation

GitHub Actions / Code quality

Type '"/stats/[id]"' is not assignable to type '"/" | "/404" | "/account/api-key" | "/account/custom-abi" | "/account/tag-address" | "/account/verified-addresses" | "/account/watchlist" | "/accounts" | "/address/[hash]/contract-verification" | ... 42 more ... | "/withdrawals"'.
<Chart/>
</PageNextJs>
);
};

export default Page;

export { base as getServerSideProps } from 'nextjs/getServerSideProps';
4 changes: 2 additions & 2 deletions theme/components/Tag/Tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ const variants = {
select: definePartsStyle((props) => ({
container: {
bg: mode('gray.100', 'gray.800')(props),
color: 'text_secondary',
color: mode('gray.500', 'whiteAlpha.800')(props),
_hover: {
color: 'link',
color: 'blue.400',
opacity: 0.76,
},
},
Expand Down
100 changes: 100 additions & 0 deletions ui/pages/Chart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// ChartWidgetContainer

import { Button, Flex, Text } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';

import type { StatsIntervalIds } from 'types/client/stats';

import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
import getQueryParamString from 'lib/router/getQueryParamString';
import isCustomAppError from 'ui/shared/AppError/isCustomAppError';
import ChartIntervalSelect from 'ui/shared/chart/ChartIntervalSelect';
import ChartMenu from 'ui/shared/chart/ChartMenu';
import ChartWidgetContent from 'ui/shared/chart/ChartWidgetContent';
import useChartItems from 'ui/shared/chart/useChartItems';
import useZoomReset from 'ui/shared/chart/useZoomReset';
import IconSvg from 'ui/shared/IconSvg';
import PageTitle from 'ui/shared/Page/PageTitle';

const Chart = () => {
const router = useRouter();
const id = getQueryParamString(router.query.id);
const [ interval, setInterval ] = React.useState<StatsIntervalIds>('oneMonth');
const { isZoomResetInitial, handleZoom, handleZoomResetClick } = useZoomReset();

const ref = React.useRef(null);

const { items, lineQuery } = useChartItems(interval, id);

if (lineQuery.isError) {
if (isCustomAppError(lineQuery.error)) {
throwOnResourceLoadError({ resource: 'stats_line', error: lineQuery.error, isError: true });
}
}

const hasItems = (items && items.length > 2) || lineQuery.isPending;

const content = (
<>
<Flex alignItems="center" justifyContent="space-between">
<Flex alignItems="center">
<Text mr={ 2 }>Zoom</Text>
<ChartIntervalSelect interval={ interval } onIntervalChange={ setInterval }/>
{ !isZoomResetInitial && (
<Button
leftIcon={ <IconSvg name="repeat_arrow" w={ 4 } h={ 4 }/> }
colorScheme="blue"
gridColumn={ 2 }
justifySelf="end"
alignSelf="top"
gridRow="1/3"
size="sm"
variant="outline"
onClick={ handleZoomResetClick }
ml={ 6 }
>
Reset zoom
</Button>
) }
</Flex>
{ hasItems && (
<Flex alignItems="center">
{ /* share */ }
<ChartMenu items={ items } title="FIXME" isLoading={ lineQuery.isPending } chartRef={ ref }/>
</Flex>
) }
</Flex>
<Flex
ref={ ref }
flexGrow={ 1 }
h="50vh"
mt={ 3 }
>
<ChartWidgetContent
isError={ lineQuery.isError }
items={ items }
title="title"
// title={ chart.title }
// units={ chart.units || undefined }
isEnlarged
isLoading={ lineQuery.isPending }
isZoomResetInitial={ isZoomResetInitial }
handleZoom={ handleZoom }
/>
</Flex>
</>
);

return (
<>
<PageTitle
title="FIXME"
// withTextAd
/>
{ content }
</>
);
};

export default Chart;
3 changes: 0 additions & 3 deletions ui/pages/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,13 @@ import LatestZkEvmL2Batches from 'ui/home/LatestZkEvmL2Batches';
import Stats from 'ui/home/Stats';
import Transactions from 'ui/home/Transactions';
import AdBanner from 'ui/shared/ad/AdBanner';
import TagGroupSelect from 'ui/shared/tagGroupSelect/TagGroupSelect';
import ProfileMenuDesktop from 'ui/snippets/profileMenu/ProfileMenuDesktop';
import SearchBar from 'ui/snippets/searchBar/SearchBar';
import WalletMenuDesktop from 'ui/snippets/walletMenu/WalletMenuDesktop';

const rollupFeature = config.features.rollup;

const Home = () => {
const onSelect = React.useCallback(() => {}, []);
return (
<Box as="main">
<Box
Expand Down Expand Up @@ -49,7 +47,6 @@ const Home = () => {
</Flex>
<SearchBar isHomepage/>
</Box>
<TagGroupSelect items={ [ '11', '22', '33' ] } defaultValue="1" onChange={ onSelect } my={ 2 }/>
<Stats/>
<ChainIndicators/>
<AdBanner mt={ 6 } mx="auto" display="flex" justifyContent="center"/>
Expand Down
43 changes: 43 additions & 0 deletions ui/shared/chart/ChartIntervalSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Skeleton } from '@chakra-ui/react';
import React from 'react';

import type { StatsInterval, StatsIntervalIds } from 'types/client/stats';

import TagGroupSelect from 'ui/shared/tagGroupSelect/TagGroupSelect';
import { STATS_INTERVALS } from 'ui/stats/constants';
import StatsDropdownMenu from 'ui/stats/StatsDropdownMenu';

const intervalList = Object.keys(STATS_INTERVALS).map((id: string) => ({
id: id,
title: STATS_INTERVALS[id as StatsIntervalIds].title,
})) as Array<StatsInterval>;

const intervalListShort = Object.keys(STATS_INTERVALS).map((id: string) => ({
id: id,
title: STATS_INTERVALS[id as StatsIntervalIds].shortTitle,
})) as Array<StatsInterval>;

type Props = {
interval: StatsIntervalIds;
onIntervalChange: (newInterval: StatsIntervalIds) => void;
isLoading?: boolean;
}

const ChartIntervalSelect = ({ interval, onIntervalChange, isLoading }: Props) => {
return (
<>
<Skeleton display={{ base: 'none', lg: 'flex' }} borderRadius="base" isLoaded={ !isLoading }>
<TagGroupSelect<StatsIntervalIds> items={ intervalListShort } onChange={ onIntervalChange } value={ interval }/>
</Skeleton>
<Skeleton display={{ base: 'block', lg: 'none' }} borderRadius="base" isLoaded={ !isLoading }>
<StatsDropdownMenu
items={ intervalList }
selectedId={ interval }
onSelect={ onIntervalChange }
/>
</Skeleton>
</>
);
};

export default React.memo(ChartIntervalSelect);
146 changes: 146 additions & 0 deletions ui/shared/chart/ChartMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import {
IconButton,
Menu,
MenuButton,
MenuItem,
MenuList,
Skeleton,
useColorModeValue,
VisuallyHidden,
} from '@chakra-ui/react';
import domToImage from 'dom-to-image';
import React from 'react';

import type { TimeChartItem } from './types';

import dayjs from 'lib/date/dayjs';
import saveAsCSV from 'lib/saveAsCSV';
import IconSvg from 'ui/shared/IconSvg';

import FullscreenChartModal from './FullscreenChartModal';

export type Props = {
items?: Array<TimeChartItem>;
title: string;
description?: string;
units?: string;
isLoading: boolean;
chartRef: React.RefObject<HTMLDivElement>;
}

const DOWNLOAD_IMAGE_SCALE = 5;

const ChartMenu = ({ items, title, description, units, isLoading, chartRef }: Props) => {
const pngBackgroundColor = useColorModeValue('white', 'black');
const [ isFullscreen, setIsFullscreen ] = React.useState(false);

const showChartFullscreen = React.useCallback(() => {
setIsFullscreen(true);
}, []);

const clearFullscreenChart = React.useCallback(() => {
setIsFullscreen(false);
}, []);

const handleFileSaveClick = React.useCallback(() => {
// wait for context menu to close
setTimeout(() => {
if (chartRef.current) {
domToImage.toPng(chartRef.current,
{
quality: 100,
bgcolor: pngBackgroundColor,
width: chartRef.current.offsetWidth * DOWNLOAD_IMAGE_SCALE,
height: chartRef.current.offsetHeight * DOWNLOAD_IMAGE_SCALE,
filter: (node) => node.nodeName !== 'BUTTON',
style: {
borderColor: 'transparent',
transform: `scale(${ DOWNLOAD_IMAGE_SCALE })`,
'transform-origin': 'top left',
},
})
.then((dataUrl) => {
const link = document.createElement('a');
link.download = `${ title } (Blockscout chart).png`;
link.href = dataUrl;
link.click();
link.remove();
});
}
}, 100);
}, [ pngBackgroundColor, title, chartRef ]);

const handleSVGSavingClick = React.useCallback(() => {
if (items) {
const headerRows = [
'Date', 'Value',
];
const dataRows = items.map((item) => [
dayjs(item.date).format('YYYY-MM-DD'), String(item.value),
]);

saveAsCSV(headerRows, dataRows, `${ title } (Blockscout stats)`);
}
}, [ items, title ]);

return (
<>
<Menu>
<Skeleton isLoaded={ !isLoading } borderRadius="base">
<MenuButton
w="36px"
h="32px"
icon={ <IconSvg name="dots" boxSize={ 4 } transform="rotate(-90deg)"/> }
colorScheme="gray"
variant="ghost"
as={ IconButton }
>
<VisuallyHidden>
Open chart options menu
</VisuallyHidden>
</MenuButton>
</Skeleton>
<MenuList>
<MenuItem
display="flex"
alignItems="center"
onClick={ showChartFullscreen }
>
<IconSvg name="scope" boxSize={ 5 } mr={ 3 }/>
View fullscreen
</MenuItem>

<MenuItem
display="flex"
alignItems="center"
onClick={ handleFileSaveClick }
>
<IconSvg name="files/image" boxSize={ 5 } mr={ 3 }/>
Save as PNG
</MenuItem>

<MenuItem
display="flex"
alignItems="center"
onClick={ handleSVGSavingClick }
>
<IconSvg name="files/csv" boxSize={ 5 } mr={ 3 }/>
Save as CSV
</MenuItem>
</MenuList>
</Menu>
{ items && (
<FullscreenChartModal
isOpen={ isFullscreen }
items={ items }
title={ title }
description={ description }
onClose={ clearFullscreenChart }
units={ units }
/>
) }
</>
);
};

export default ChartMenu;
Loading

0 comments on commit 4cb28cf

Please sign in to comment.