Commit d9453168 authored by Yuri Mikhin's avatar Yuri Mikhin Committed by Yuri Mikhin

Add fullscreen mode for charts.

parent 2d1d77e9
<svg viewBox="2 2 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.667 3.75c0-.688-.563-1.25-1.25-1.25-.688 0-1.25.563-1.25 1.25 0 .688.562 1.25 1.25 1.25.687 0 1.25-.563 1.25-1.25Zm0 12.5c0-.688-.563-1.25-1.25-1.25-.688 0-1.25.563-1.25 1.25 0 .688.562 1.25 1.25 1.25.687 0 1.25-.563 1.25-1.25Zm0-6.25c0-.688-.563-1.25-1.25-1.25-.688 0-1.25.563-1.25 1.25 0 .688.562 1.25 1.25 1.25.687 0 1.25-.563 1.25-1.25Z" fill="currentColor" fill-opacity=".8"/>
</svg>
...@@ -25,3 +25,8 @@ export type StatsChart = { ...@@ -25,3 +25,8 @@ export type StatsChart = {
description: string; description: string;
apiMethodURL: string; apiMethodURL: string;
} }
export interface ModalChart {
id: string;
title: string;
}
...@@ -18,6 +18,9 @@ const Stats = () => { ...@@ -18,6 +18,9 @@ const Stats = () => {
handleIntervalChange, handleIntervalChange,
debounceFilterCharts, debounceFilterCharts,
displayedCharts, displayedCharts,
showChartFullscreen,
clearFullscreenChart,
fullscreenChart,
} = useStats(); } = useStats();
return ( return (
...@@ -40,6 +43,9 @@ const Stats = () => { ...@@ -40,6 +43,9 @@ const Stats = () => {
<ChartsWidgetsList <ChartsWidgetsList
charts={ displayedCharts } charts={ displayedCharts }
onChartFullscreenClick={ showChartFullscreen }
fullscreenChart={ fullscreenChart }
onModalClose={ clearFullscreenChart }
/> />
</Page> </Page>
); );
......
import { Box, Button, Grid, Heading, Text, useColorModeValue } from '@chakra-ui/react'; import { Box, Button, Grid, Heading, Icon, IconButton, Menu, MenuButton, MenuItem, MenuList, Text, useColorModeValue, VisuallyHidden } from '@chakra-ui/react';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import type { ModalChart } from 'types/client/stats';
import dotsIcon from 'icons/vertical-dots.svg';
import ChartWidgetGraph from './ChartWidgetGraph'; import ChartWidgetGraph from './ChartWidgetGraph';
import { demoChartsData } from './constants/demo-charts-data'; import { demoChartsData } from './constants/demo-charts-data';
type Props = { type Props = {
id: string;
onFullscreenClick: (chart: ModalChart) => void;
apiMethodURL: string; apiMethodURL: string;
title: string; title: string;
description: string; description: string;
} }
const ChartWidget = ({ title, description }: Props) => { const ChartWidget = ({ id, title, description, onFullscreenClick }: Props) => {
const [ isZoomResetInitial, setIsZoomResetInitial ] = React.useState(true); const [ isZoomResetInitial, setIsZoomResetInitial ] = React.useState(true);
const handleZoom = useCallback(() => { const handleZoom = useCallback(() => {
...@@ -21,6 +27,10 @@ const ChartWidget = ({ title, description }: Props) => { ...@@ -21,6 +27,10 @@ const ChartWidget = ({ title, description }: Props) => {
setIsZoomResetInitial(true); setIsZoomResetInitial(true);
}, []); }, []);
const handleFullscreenClick = useCallback(() => {
onFullscreenClick({ id, title });
}, [ id, title, onFullscreenClick ]);
return ( return (
<Box <Box
padding={{ base: 3, md: 4 }} padding={{ base: 3, md: 4 }}
...@@ -28,7 +38,10 @@ const ChartWidget = ({ title, description }: Props) => { ...@@ -28,7 +38,10 @@ const ChartWidget = ({ title, description }: Props) => {
border="1px" border="1px"
borderColor={ useColorModeValue('gray.200', 'gray.600') } borderColor={ useColorModeValue('gray.200', 'gray.600') }
> >
<Grid> <Grid
gridTemplateColumns="auto auto 36px"
gridColumnGap={ 4 }
>
<Heading <Heading
mb={ 1 } mb={ 1 }
size={{ base: 'xs', md: 'sm' }} size={{ base: 'xs', md: 'sm' }}
...@@ -50,7 +63,7 @@ const ChartWidget = ({ title, description }: Props) => { ...@@ -50,7 +63,7 @@ const ChartWidget = ({ title, description }: Props) => {
<Button <Button
gridColumn={ 2 } gridColumn={ 2 }
justifySelf="end" justifySelf="end"
alignSelf="center" alignSelf="top"
gridRow="1/3" gridRow="1/3"
size="sm" size="sm"
variant="outline" variant="outline"
...@@ -59,6 +72,26 @@ const ChartWidget = ({ title, description }: Props) => { ...@@ -59,6 +72,26 @@ const ChartWidget = ({ title, description }: Props) => {
Reset zoom Reset zoom
</Button> </Button>
) } ) }
<Menu>
<MenuButton
gridColumn={ 3 }
gridRow="1/3"
justifySelf="end"
w="36px"
h="32px"
icon={ <Icon as={ dotsIcon } w={ 4 } h={ 4 } color={ useColorModeValue('black', 'white') }/> }
colorScheme="transparent"
as={ IconButton }
>
<VisuallyHidden>
Open chart options menu
</VisuallyHidden>
</MenuButton>
<MenuList>
<MenuItem onClick={ handleFullscreenClick }>View fullscreen</MenuItem>
</MenuList>
</Menu>
</Grid> </Grid>
<ChartWidgetGraph <ChartWidgetGraph
......
...@@ -20,7 +20,7 @@ interface Props { ...@@ -20,7 +20,7 @@ interface Props {
isZoomResetInitial: boolean; isZoomResetInitial: boolean;
} }
const CHART_MARGIN = { bottom: 20, left: 65, right: 30, top: 10 }; const CHART_MARGIN = { bottom: 20, left: 52, right: 30, top: 10 };
const ChartWidgetGraph = ({ items, onZoom, isZoomResetInitial, title }: Props) => { const ChartWidgetGraph = ({ items, onZoom, isZoomResetInitial, title }: Props) => {
const ref = React.useRef<SVGSVGElement>(null); const ref = React.useRef<SVGSVGElement>(null);
......
import { Grid, GridItem, Heading, List, ListItem } from '@chakra-ui/react'; import { Box, Grid, GridItem, Heading, List, ListItem } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { StatsSection } from 'types/client/stats'; import type { ModalChart, StatsSection } from 'types/client/stats';
import { apos } from 'lib/html-entities'; import { apos } from 'lib/html-entities';
import EmptySearchResult from '../apps/EmptySearchResult'; import EmptySearchResult from '../apps/EmptySearchResult';
import ChartWidget from './ChartWidget'; import ChartWidget from './ChartWidget';
import FullscreenChartModal from './FullscreenChartModal';
type Props = { type Props = {
charts: Array<StatsSection>; charts: Array<StatsSection>;
onChartFullscreenClick: (chart: ModalChart) => void;
fullscreenChart: ModalChart | null;
onModalClose: () => void;
} }
const ChartsWidgetsList = ({ charts }: Props) => { const ChartsWidgetsList = ({ charts, onChartFullscreenClick, fullscreenChart, onModalClose }: Props) => {
const isAnyChartDisplayed = charts.some((section) => section.charts.some(chart => chart.visible)); const isAnyChartDisplayed = charts.some((section) => section.charts.some(chart => chart.visible));
return isAnyChartDisplayed ? ( return (
<List> <Box>
{ { isAnyChartDisplayed ? (
charts.map((section) => ( <List>
<ListItem {
display={ section.charts.every((chart) => !chart.visible) ? 'none' : 'block' } charts.map((section) => (
key={ section.id } <ListItem
mb={ 8 } display={ section.charts.every((chart) => !chart.visible) ? 'none' : 'block' }
_last={{ key={ section.id }
marginBottom: 0, mb={ 8 }
}} _last={{
> marginBottom: 0,
<Heading }}
size="md" >
mb={ 4 } <Heading
> size="md"
{ section.title } mb={ 4 }
</Heading> >
{ section.title }
</Heading>
<Grid <Grid
templateColumns={{ templateColumns={{
sm: 'repeat(2, 1fr)', sm: 'repeat(2, 1fr)',
}} }}
gap={ 4 } gap={ 4 }
>
{ section.charts.map((chart) => (
<GridItem
key={ chart.id }
display={ chart.visible ? 'block' : 'none' }
> >
<ChartWidget { section.charts.map((chart) => (
apiMethodURL={ chart.apiMethodURL } <GridItem
title={ chart.title } key={ chart.id }
description={ chart.description } display={ chart.visible ? 'block' : 'none' }
/> >
</GridItem> <ChartWidget
)) } id={ chart.id }
</Grid> onFullscreenClick={ onChartFullscreenClick }
</ListItem> apiMethodURL={ chart.apiMethodURL }
)) title={ chart.title }
} description={ chart.description }
</List> />
) : ( </GridItem>
<EmptySearchResult text={ `Couldn${ apos }t find a chart that matches your filter query.` }/> )) }
</Grid>
</ListItem>
))
}
</List>
) : (
<EmptySearchResult text={ `Couldn${ apos }t find a chart that matches your filter query.` }/>
) }
{ fullscreenChart && (
<FullscreenChartModal
id={ fullscreenChart.id }
title={ fullscreenChart.title }
onClose={ onModalClose }
/>
) }
</Box>
); );
}; };
......
import { Button, Flex, Heading, Modal, ModalBody, ModalCloseButton, ModalContent, ModalHeader, ModalOverlay } from '@chakra-ui/react';
import React, { useCallback } from 'react';
import ChartWidgetGraph from './ChartWidgetGraph';
import { demoChartsData } from './constants/demo-charts-data';
type Props = {
id: string;
title: string;
onClose: () => void;
}
const FullscreenChartModal = ({
id,
title,
onClose,
}: Props) => {
const [ isZoomResetInitial, setIsZoomResetInitial ] = React.useState(true);
const handleZoom = useCallback(() => {
setIsZoomResetInitial(false);
}, []);
const handleZoomResetClick = useCallback(() => {
setIsZoomResetInitial(true);
}, []);
return (
<Modal
isOpen={ Boolean(id) }
onClose={ onClose }
size="full"
isCentered
>
<ModalOverlay/>
<ModalContent>
<ModalHeader>
<Flex
alignItems="center"
>
<Heading
as="h2"
gridColumn={ 2 }
fontSize={{ base: '2xl', sm: '3xl' }}
fontWeight="medium"
lineHeight={ 1 }
color="blue.600"
>
{ title }
</Heading>
{ !isZoomResetInitial && (
<Button
ml="auto"
gridColumn={ 2 }
justifySelf="end"
alignSelf="top"
gridRow="1/3"
size="md"
variant="outline"
onClick={ handleZoomResetClick }
>
Reset zoom
</Button>
) }
</Flex>
</ModalHeader>
<ModalCloseButton/>
<ModalBody
h="100%"
>
<ChartWidgetGraph
items={ demoChartsData }
onZoom={ handleZoom }
isZoomResetInitial={ isZoomResetInitial }
title="test"
/>
</ModalBody>
</ModalContent>
</Modal>
);
};
export default FullscreenChartModal;
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import type { StatsChart, StatsIntervalIds, StatsSection, StatsSectionIds } from 'types/client/stats'; import type { ModalChart, StatsChart, StatsIntervalIds, StatsSection, StatsSectionIds } from 'types/client/stats';
import { statsChartsScheme } from './constants/charts-scheme'; import { statsChartsScheme } from './constants/charts-scheme';
...@@ -17,6 +17,7 @@ export default function useStats() { ...@@ -17,6 +17,7 @@ export default function useStats() {
const [ displayedCharts, setDisplayedCharts ] = useState<Array<StatsSection>>(statsChartsScheme); const [ displayedCharts, setDisplayedCharts ] = useState<Array<StatsSection>>(statsChartsScheme);
const [ section, setSection ] = useState<StatsSectionIds>('all'); const [ section, setSection ] = useState<StatsSectionIds>('all');
const [ interval, setInterval ] = useState<StatsIntervalIds>('all'); const [ interval, setInterval ] = useState<StatsIntervalIds>('all');
const [ fullscreenChart, setFullscreenChart ] = useState<ModalChart | null>(null);
const [ filterQuery, setFilterQuery ] = useState(''); const [ filterQuery, setFilterQuery ] = useState('');
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
...@@ -47,6 +48,22 @@ export default function useStats() { ...@@ -47,6 +48,22 @@ export default function useStats() {
setInterval(newInterval); setInterval(newInterval);
}, []); }, []);
const showChartFullscreen = useCallback((chart: ModalChart) => {
setFullscreenChart(chart);
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen();
}
}, []);
const clearFullscreenChart = useCallback(() => {
setFullscreenChart(null);
if (document.fullscreenElement) {
document.exitFullscreen();
}
}, []);
useEffect(() => { useEffect(() => {
filterCharts(filterQuery, section); filterCharts(filterQuery, section);
}, [ filterQuery, section, filterCharts ]); }, [ filterQuery, section, filterCharts ]);
...@@ -58,6 +75,9 @@ export default function useStats() { ...@@ -58,6 +75,9 @@ export default function useStats() {
handleIntervalChange, handleIntervalChange,
debounceFilterCharts, debounceFilterCharts,
displayedCharts, displayedCharts,
showChartFullscreen,
clearFullscreenChart,
fullscreenChart,
}), [ }), [
section, section,
handleSectionChange, handleSectionChange,
...@@ -65,5 +85,8 @@ export default function useStats() { ...@@ -65,5 +85,8 @@ export default function useStats() {
handleIntervalChange, handleIntervalChange,
debounceFilterCharts, debounceFilterCharts,
displayedCharts, displayedCharts,
showChartFullscreen,
clearFullscreenChart,
fullscreenChart,
]); ]);
} }
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