🎨 Improve code structure of dns hole summary

This commit is contained in:
Meier Lukas
2023-08-11 20:38:13 +02:00
parent a14a9d4601
commit 71272c982e
3 changed files with 129 additions and 121 deletions

View File

@@ -109,57 +109,52 @@ function App(
<Head> <Head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
</Head> </Head>
<PersistQueryClientProvider <ColorSchemeProvider colorScheme={colorScheme} toggleColorScheme={toggleColorScheme}>
client={queryClient} <ColorTheme.Provider value={colorTheme}>
persistOptions={{ persister: asyncStoragePersister }} <MantineProvider
> theme={{
<ColorSchemeProvider colorScheme={colorScheme} toggleColorScheme={toggleColorScheme}> ...theme,
<ColorTheme.Provider value={colorTheme}> components: {
<MantineProvider Checkbox: {
theme={{ styles: {
...theme, input: { cursor: 'pointer' },
components: { label: { cursor: 'pointer' },
Checkbox: {
styles: {
input: { cursor: 'pointer' },
label: { cursor: 'pointer' },
},
},
Switch: {
styles: {
input: { cursor: 'pointer' },
label: { cursor: 'pointer' },
},
}, },
}, },
primaryColor, Switch: {
primaryShade, styles: {
colorScheme, input: { cursor: 'pointer' },
}} label: { cursor: 'pointer' },
withGlobalStyles },
withNormalizeCSS },
> },
<ConfigProvider {...props.pageProps}> primaryColor,
<Notifications limit={4} position="bottom-left" /> primaryShade,
<ModalsProvider colorScheme,
modals={{ }}
editApp: EditAppModal, withGlobalStyles
selectElement: SelectElementModal, withNormalizeCSS
integrationOptions: WidgetsEditModal, >
integrationRemove: WidgetsRemoveModal, <ConfigProvider {...props.pageProps}>
categoryEditModal: CategoryEditModal, <Notifications limit={4} position="bottom-left" />
changeAppPositionModal: ChangeAppPositionModal, <ModalsProvider
changeIntegrationPositionModal: ChangeWidgetPositionModal, modals={{
}} editApp: EditAppModal,
> selectElement: SelectElementModal,
<Component {...pageProps} /> integrationOptions: WidgetsEditModal,
</ModalsProvider> integrationRemove: WidgetsRemoveModal,
</ConfigProvider> categoryEditModal: CategoryEditModal,
</MantineProvider> changeAppPositionModal: ChangeAppPositionModal,
</ColorTheme.Provider> changeIntegrationPositionModal: ChangeWidgetPositionModal,
</ColorSchemeProvider> }}
<ReactQueryDevtools initialIsOpen={false} /> >
</PersistQueryClientProvider> <Component {...pageProps} />
</ModalsProvider>
</ConfigProvider>
</MantineProvider>
</ColorTheme.Provider>
</ColorSchemeProvider>
<ReactQueryDevtools initialIsOpen={false} />
</> </>
); );
} }

View File

@@ -16,3 +16,7 @@ export const formatNumber = (n: number, decimalPlaces: number) => {
} }
return n.toFixed(decimalPlaces); return n.toFixed(decimalPlaces);
}; };
export const formatPercentage = (n: number, decimalPlaces: number) => {
return `${(n * 100).toFixed(decimalPlaces)}%`;
};

View File

@@ -1,4 +1,4 @@
import { Card, Center, Container, Flex, Text } from '@mantine/core'; import { Box, Card, Center, Container, Flex, Text } from '@mantine/core';
import { useElementSize } from '@mantine/hooks'; import { useElementSize } from '@mantine/hooks';
import { import {
IconAd, IconAd,
@@ -6,17 +6,21 @@ import {
IconPercentage, IconPercentage,
IconSearch, IconSearch,
IconWorldWww, IconWorldWww,
TablerIconsProps,
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import React from 'react'; import React from 'react';
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
import { api } from '~/utils/api'; import { RouterOutputs, api } from '~/utils/api';
import { formatNumber } from '../../tools/client/math'; import { formatNumber, formatPercentage } from '../../tools/client/math';
import { defineWidget } from '../helper'; import { defineWidget } from '../helper';
import { WidgetLoading } from '../loading'; import { WidgetLoading } from '../loading';
import { IWidget } from '../widgets'; import { IWidget } from '../widgets';
const availableLayouts = ['grid', 'row', 'column'] as const;
type AvailableLayout = (typeof availableLayouts)[number];
const definition = defineWidget({ const definition = defineWidget({
id: 'dns-hole-summary', id: 'dns-hole-summary',
icon: IconAd, icon: IconAd,
@@ -27,8 +31,8 @@ const definition = defineWidget({
}, },
layout: { layout: {
type: 'select', type: 'select',
defaultValue: 'grid', defaultValue: 'grid' as AvailableLayout,
data: [{ value: 'grid' }, { value: 'row' }, { value: 'column' }], data: availableLayouts.map((x) => ({ value: x })),
}, },
}, },
gridstack: { gridstack: {
@@ -47,60 +51,54 @@ interface DnsHoleSummaryWidgetProps {
} }
function DnsHoleSummaryWidgetTile({ widget }: DnsHoleSummaryWidgetProps) { function DnsHoleSummaryWidgetTile({ widget }: DnsHoleSummaryWidgetProps) {
const { t } = useTranslation('modules/dns-hole-summary');
const { isInitialLoading, data } = useDnsHoleSummeryQuery(); const { isInitialLoading, data } = useDnsHoleSummeryQuery();
const flexLayout = widget.properties.layout as 'row' | 'column';
if (isInitialLoading || !data) { if (isInitialLoading || !data) {
return <WidgetLoading />; return <WidgetLoading />;
} }
return ( return (
<Container <Container h="100%" p={0} style={constructContainerStyle(widget.properties.layout)}>
h="100%" {stats.map((item) => (
p={0} <StatCard item={item} usePiHoleColors={widget.properties.usePiHoleColors} data={data} />
style={{ ))}
gridTemplateColumns: '1fr 1fr',
gridTemplateRows: '1fr 1fr',
display: flexLayout?.includes('grid') ? 'grid' : 'flex',
flexDirection: flexLayout,
}}
>
<StatCard
icon={<IconBarrierBlock />}
number={formatNumber(data.adsBlockedToday, 2)}
label={t('card.metrics.queriesBlockedToday') as string}
color={
widget.properties.usePiHoleColors ? 'rgba(240, 82, 60, 0.4)' : 'rgba(96, 96, 96, 0.1)'
}
/>
<StatCard
icon={<IconPercentage />}
number={(data.adsBlockedTodayPercentage * 100).toFixed(2) + '%'}
color={
widget.properties.usePiHoleColors ? 'rgba(255, 165, 20, 0.4)' : 'rgba(96, 96, 96, 0.1)'
}
/>
<StatCard
icon={<IconSearch />}
number={formatNumber(data.dnsQueriesToday, 2)}
label={t('card.metrics.queriesToday') as string}
color={
widget.properties.usePiHoleColors ? 'rgba(0, 175, 218, 0.4)' : 'rgba(96, 96, 96, 0.1)'
}
/>
<StatCard
icon={<IconWorldWww />}
number={formatNumber(data.domainsBeingBlocked, 2)}
label={t('card.metrics.domainsOnAdlist') as string}
color={
widget.properties.usePiHoleColors ? 'rgba(0, 176, 96, 0.4)' : 'rgba(96, 96, 96, 0.1)'
}
/>
</Container> </Container>
); );
} }
const stats = [
{
icon: IconBarrierBlock,
value: (x) => formatNumber(x.adsBlockedToday, 2),
label: 'card.metrics.queriesBlockedToday',
color: 'rgba(240, 82, 60, 0.4)',
},
{
icon: IconPercentage,
value: (x) => formatPercentage(x.adsBlockedTodayPercentage, 2),
color: 'rgba(255, 165, 20, 0.4)',
},
{
icon: IconSearch,
value: (x) => formatNumber(x.dnsQueriesToday, 2),
label: 'card.metrics.queriesToday',
color: 'rgba(0, 175, 218, 0.4)',
},
{
icon: IconWorldWww,
value: (x) => formatNumber(x.domainsBeingBlocked, 2),
label: 'card.metrics.domainsOnAdlist',
color: 'rgba(0, 176, 96, 0.4)',
},
] satisfies StatItem[];
type StatItem = {
icon: (props: TablerIconsProps) => JSX.Element;
value: (x: RouterOutputs['dnsHole']['summary']) => string;
label?: string;
color: string;
};
export const useDnsHoleSummeryQuery = () => { export const useDnsHoleSummeryQuery = () => {
const { name: configName } = useConfigContext(); const { name: configName } = useConfigContext();
@@ -114,23 +112,23 @@ export const useDnsHoleSummeryQuery = () => {
); );
}; };
interface StatCardProps { type StatCardProps = {
icon: JSX.Element; item: StatItem;
number: string; data: RouterOutputs['dnsHole']['summary'];
label?: string; usePiHoleColors: boolean;
color?: string; };
} const StatCard = ({ item, data, usePiHoleColors }: StatCardProps) => {
const { t } = useTranslation('modules/dns-hole-summary');
const StatCard = ({ icon, number, label, color }: StatCardProps) => {
const { ref, height, width } = useElementSize(); const { ref, height, width } = useElementSize();
return ( return (
<Card <Card
ref={ref} ref={ref}
m="0.4rem" m="0.4rem"
p="0.2rem" p="0.2rem"
sx={{ bg={usePiHoleColors ? item.color : 'rgba(96, 96, 96, 0.1)'}
backgroundColor: color, style={{
flex: '1', flex: 1,
}} }}
withBorder withBorder
> >
@@ -142,31 +140,42 @@ const StatCard = ({ icon, number, label, color }: StatCardProps) => {
justify="space-evenly" justify="space-evenly"
direction={width > height + 20 ? 'row' : 'column'} direction={width > height + 20 ? 'row' : 'column'}
> >
{React.cloneElement(icon, { <item.icon size={30} style={{ margin: '0 10' }} />
size: 30, <Flex
style: { margin: '0 10' } justify="center"
})} direction="column"
<div
style={{ style={{
flex: '1', flex: 1,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
}} }}
> >
<Text align="center" lh={1.2} size="md" weight="bold"> <Text align="center" lh={1.2} size="md" weight="bold">
{number} {item.value(data)}
</Text> </Text>
{label && ( {item.label && (
<Text align="center" lh={1.2} size="0.75rem"> <Text align="center" lh={1.2} size="0.75rem">
{label} {t<string>(item.label)}
</Text> </Text>
)} )}
</div> </Flex>
</Flex> </Flex>
</Center> </Center>
</Card> </Card>
); );
}; };
const constructContainerStyle = (flexLayout: (typeof availableLayouts)[number]) => {
if (flexLayout === 'grid') {
return {
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gridTemplateRows: '1fr 1fr',
};
}
return {
display: 'flex',
flexDirection: flexLayout,
};
};
export default definition; export default definition;