-
Notifications
You must be signed in to change notification settings - Fork 485
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
576 additions
and
318 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 GitHub Actions / Code quality
|
||
<Chart/> | ||
</PageNextJs> | ||
); | ||
}; | ||
|
||
export default Page; | ||
|
||
export { base as getServerSideProps } from 'nextjs/getServerSideProps'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.