🎨 Migrate all wrapper to use WidgetWrapper

This commit is contained in:
Meierschlumpf
2022-12-19 18:26:04 +01:00
parent 8fa9cfaccf
commit c2186c2525
9 changed files with 153 additions and 202 deletions

View File

@@ -36,11 +36,7 @@ export const AppTile = ({ className, app }: AppTileProps) => {
return ( return (
<HomarrCardWrapper className={className}> <HomarrCardWrapper className={className}>
{/* TODO: add app menu */} <AppMenu app={app} />
<div style={{ position: 'absolute', top: 10, right: 10 }}>
<AppMenu app={app} />
</div>
{!app.url || isEditMode ? ( {!app.url || isEditMode ? (
<UnstyledButton <UnstyledButton

View File

@@ -3,6 +3,7 @@ import { MutableRefObject, RefObject } from 'react';
import { AppType } from '../../../types/app'; import { AppType } from '../../../types/app';
import Widgets from '../../../widgets'; import Widgets from '../../../widgets';
import { IWidget, IWidgetDefinition } from '../../../widgets/widgets'; import { IWidget, IWidgetDefinition } from '../../../widgets/widgets';
import { WidgetWrapper } from '../../../widgets/WidgetWrapper';
import { Tiles } from '../Tiles/tilesDefinitions'; import { Tiles } from '../Tiles/tilesDefinitions';
import { GridstackTileWrapper } from '../Tiles/TileWrapper'; import { GridstackTileWrapper } from '../Tiles/TileWrapper';
@@ -51,7 +52,9 @@ export const WrapperContent = ({ apps, refs, widgets }: WrapperContentProps) =>
{...widget.shape.location} {...widget.shape.location}
{...widget.shape.size} {...widget.shape.size}
> >
<definition.component className="grid-stack-item-content" widget={widget} /> <WidgetWrapper className="grid-stack-item-content" widget={widget} widgetId={widget.id}>
<definition.component className="grid-stack-item-content" widget={widget} />
</WidgetWrapper>
</GridstackTileWrapper> </GridstackTileWrapper>
); );
})} })}

View File

@@ -1,6 +1,4 @@
import { IconClock, IconFileDownload } from '@tabler/icons'; import { IconFileDownload } from '@tabler/icons';
import { HomarrCardWrapper } from '../../components/Dashboard/Tiles/HomarrCardWrapper';
import { BaseTileProps } from '../../components/Dashboard/Tiles/type';
import { defineWidget } from '../helper'; import { defineWidget } from '../helper';
import { IWidget } from '../widgets'; import { IWidget } from '../widgets';
@@ -19,12 +17,12 @@ const definition = defineWidget({
export type IBitTorrent = IWidget<typeof definition['id'], typeof definition>; export type IBitTorrent = IWidget<typeof definition['id'], typeof definition>;
interface BitTorrentTileProps extends BaseTileProps { interface BitTorrentTileProps {
widget: IBitTorrent; // TODO: change to new type defined through widgetDefinition widget: IBitTorrent;
} }
function BitTorrentTile({ className, widget }: BitTorrentTileProps) { function BitTorrentTile({ widget }: BitTorrentTileProps) {
return <HomarrCardWrapper>Bit Torrent</HomarrCardWrapper>; return null;
} }
export default definition; export default definition;

View File

@@ -3,8 +3,6 @@ import { Calendar } from '@mantine/dates';
import { IconCalendarTime } from '@tabler/icons'; import { IconCalendarTime } from '@tabler/icons';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useState } from 'react'; import { useState } from 'react';
import { HomarrCardWrapper } from '../../components/Dashboard/Tiles/HomarrCardWrapper';
import { BaseTileProps } from '../../components/Dashboard/Tiles/type';
import { useConfigContext } from '../../config/provider'; import { useConfigContext } from '../../config/provider';
import { useColorTheme } from '../../tools/color'; import { useColorTheme } from '../../tools/color';
import { isToday } from '../../tools/isToday'; import { isToday } from '../../tools/isToday';
@@ -33,11 +31,11 @@ const definition = defineWidget({
export type ICalendarWidget = IWidget<typeof definition['id'], typeof definition>; export type ICalendarWidget = IWidget<typeof definition['id'], typeof definition>;
interface CalendarTileProps extends BaseTileProps { interface CalendarTileProps {
widget: ICalendarWidget; widget: ICalendarWidget;
} }
function CalendarTile({ className, widget }: CalendarTileProps) { function CalendarTile({ widget }: CalendarTileProps) {
const { secondaryColor } = useColorTheme(); const { secondaryColor } = useColorTheme();
const { name: configName } = useConfigContext(); const { name: configName } = useConfigContext();
const { classes, cx } = useStyles(secondaryColor); const { classes, cx } = useStyles(secondaryColor);
@@ -57,35 +55,33 @@ function CalendarTile({ className, widget }: CalendarTileProps) {
}); });
return ( return (
<HomarrCardWrapper className={className} p={6}> <Calendar
<Calendar month={month}
month={month} onMonthChange={setMonth}
onMonthChange={setMonth} size="xs"
size="xs" fullWidth
fullWidth onChange={() => {}}
onChange={() => {}} firstDayOfWeek={widget.properties.sundayStart ? 'sunday' : 'monday'}
firstDayOfWeek={widget.properties.sundayStart ? 'sunday' : 'monday'} dayStyle={(date) => ({
dayStyle={(date) => ({ margin: 1,
margin: 1, backgroundColor: isToday(date)
backgroundColor: isToday(date) ? colorScheme === 'dark'
? colorScheme === 'dark' ? colors.dark[5]
? colors.dark[5] : colors.gray[0]
: colors.gray[0] : undefined,
: undefined, })}
})} styles={{
styles={{ calendarHeader: {
calendarHeader: { marginRight: 40,
marginRight: 40, marginLeft: 40,
marginLeft: 40, },
}, }}
}} allowLevelChange={false}
allowLevelChange={false} dayClassName={(_, modifiers) => cx({ [classes.weekend]: modifiers.weekend })}
dayClassName={(_, modifiers) => cx({ [classes.weekend]: modifiers.weekend })} renderDay={(date) => (
renderDay={(date) => ( <CalendarDay date={date} medias={getReleasedMediasForDate(medias, date)} />
<CalendarDay date={date} medias={getReleasedMediasForDate(medias, date)} /> )}
)} />
/>
</HomarrCardWrapper>
); );
} }

View File

@@ -2,9 +2,6 @@ import { createStyles, Group, Title } from '@mantine/core';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import axios from 'axios'; import axios from 'axios';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { HomarrCardWrapper } from '../../components/Dashboard/Tiles/HomarrCardWrapper';
import { BaseTileProps } from '../../components/Dashboard/Tiles/type';
import { WidgetsMenu } from '../../components/Dashboard/Tiles/Widgets/WidgetsMenu';
import { useConfigContext } from '../../config/provider'; import { useConfigContext } from '../../config/provider';
import { defineWidget } from '../helper'; import { defineWidget } from '../helper';
import { IWidget } from '../widgets'; import { IWidget } from '../widgets';
@@ -49,11 +46,11 @@ const definition = defineWidget({
export type IDashDotTile = IWidget<typeof definition['id'], typeof definition>; export type IDashDotTile = IWidget<typeof definition['id'], typeof definition>;
interface DashDotTileProps extends BaseTileProps { interface DashDotTileProps {
widget: IDashDotTile; // TODO: change to new type defined through widgetDefinition widget: IDashDotTile;
} }
function DashDotTile({ widget, className }: DashDotTileProps) { function DashDotTile({ widget }: DashDotTileProps) {
const { classes } = useDashDotTileStyles(); const { classes } = useDashDotTileStyles();
const { t } = useTranslation('modules/dashdot'); const { t } = useTranslation('modules/dashdot');
@@ -76,23 +73,6 @@ function DashDotTile({ widget, className }: DashDotTileProps) {
</Title> </Title>
); );
const menu = (
// TODO: add widgetWrapper that is generic and uses the definition
<WidgetsMenu widget={widget} integration={definition.id} />
);
if (!dashDotUrl) {
return (
<HomarrCardWrapper className={className}>
{menu}
<div>
{heading}
<p>{t('card.errors.noApp')}</p>
</div>
</HomarrCardWrapper>
);
}
const isCompact = widget?.properties.useCompactView ?? false; const isCompact = widget?.properties.useCompactView ?? false;
const isCompactStorageVisible = graphs?.some((g) => g.id === 'storage' && isCompact); const isCompactStorageVisible = graphs?.some((g) => g.id === 'storage' && isCompact);
@@ -104,8 +84,7 @@ function DashDotTile({ widget, className }: DashDotTileProps) {
); );
return ( return (
<HomarrCardWrapper className={className}> <>
{menu}
{heading} {heading}
{!info && <p>{t('card.errors.noInformation')}</p>} {!info && <p>{t('card.errors.noInformation')}</p>}
{info && ( {info && (
@@ -126,7 +105,7 @@ function DashDotTile({ widget, className }: DashDotTileProps) {
</Group> </Group>
</div> </div>
)} )}
</HomarrCardWrapper> </>
); );
} }

View File

@@ -1,13 +1,10 @@
import { Center, Stack, Text, Title } from '@mantine/core'; import { Center, Stack, Text, Title } from '@mantine/core';
import { IconClock } from '@tabler/icons';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { HomarrCardWrapper } from '../../components/Dashboard/Tiles/HomarrCardWrapper';
import { WidgetsMenu } from '../../components/Dashboard/Tiles/Widgets/WidgetsMenu';
import { BaseTileProps } from '../../components/Dashboard/Tiles/type';
import { useSetSafeInterval } from '../../tools/hooks/useSetSafeInterval'; import { useSetSafeInterval } from '../../tools/hooks/useSetSafeInterval';
import { defineWidget } from '../helper'; import { defineWidget } from '../helper';
import { IWidget } from '../widgets'; import { IWidget } from '../widgets';
import { IconClock } from '@tabler/icons';
const definition = defineWidget({ const definition = defineWidget({
id: 'date', id: 'date',
@@ -29,25 +26,21 @@ const definition = defineWidget({
export type IDateWidget = IWidget<typeof definition['id'], typeof definition>; export type IDateWidget = IWidget<typeof definition['id'], typeof definition>;
interface DateTileProps extends BaseTileProps { interface DateTileProps {
widget: IDateWidget; // TODO: change to new type defined through widgetDefinition widget: IDateWidget;
} }
function DateTile({ className, widget }: DateTileProps) { function DateTile({ widget }: DateTileProps) {
const date = useDateState(); const date = useDateState();
const formatString = widget.properties.display24HourFormat ? 'HH:mm' : 'h:mm A'; const formatString = widget.properties.display24HourFormat ? 'HH:mm' : 'h:mm A';
// TODO: add widgetWrapper that is generic and uses the definition
return ( return (
<HomarrCardWrapper className={className}> <Center style={{ height: '100%' }}>
<WidgetsMenu integration={definition.id} widget={widget} /> <Stack spacing="xs">
<Center style={{ height: '100%' }}> <Title>{dayjs(date).format(formatString)}</Title>
<Stack spacing="xs"> <Text size="lg">{dayjs(date).format('dddd, MMMM D')}</Text>
<Title>{dayjs(date).format(formatString)}</Title> </Stack>
<Text size="lg">{dayjs(date).format('dddd, MMMM D')}</Text> </Center>
</Stack>
</Center>
</HomarrCardWrapper>
); );
} }

View File

@@ -1,6 +1,4 @@
import { IconArrowsUpDown, IconClock } from '@tabler/icons'; import { IconArrowsUpDown } from '@tabler/icons';
import { HomarrCardWrapper } from '../../components/Dashboard/Tiles/HomarrCardWrapper';
import { BaseTileProps } from '../../components/Dashboard/Tiles/type';
import { defineWidget } from '../helper'; import { defineWidget } from '../helper';
import { IWidget } from '../widgets'; import { IWidget } from '../widgets';
@@ -20,12 +18,12 @@ const definition = defineWidget({
export type ITorrentNetworkTraffic = IWidget<typeof definition['id'], typeof definition>; export type ITorrentNetworkTraffic = IWidget<typeof definition['id'], typeof definition>;
interface TorrentNetworkTrafficTileProps extends BaseTileProps { interface TorrentNetworkTrafficTileProps {
widget: ITorrentNetworkTraffic; // TODO: change to new type defined through widgetDefinition widget: ITorrentNetworkTraffic;
} }
function TorrentNetworkTrafficTile({ className, widget }: TorrentNetworkTrafficTileProps) { function TorrentNetworkTrafficTile({ widget }: TorrentNetworkTrafficTileProps) {
return <HomarrCardWrapper>TorrentNetworkTraffic</HomarrCardWrapper>; return null;
} }
export default definition; export default definition;

View File

@@ -16,16 +16,14 @@ import { useElementSize } from '@mantine/hooks';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration'; import duration from 'dayjs/plugin/duration';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { UsenetQueueList } from './UsenetQueueList';
import { UsenetHistoryList } from './UsenetHistoryList';
import { BaseTileProps } from '../../components/Dashboard/Tiles/type';
import { AppIntegrationType } from '../../types/app';
import { useConfigContext } from '../../config/provider'; import { useConfigContext } from '../../config/provider';
import { useGetUsenetInfo, usePauseUsenetQueue, useResumeUsenetQueue } from '../../tools/hooks/api'; import { useGetUsenetInfo, usePauseUsenetQueue, useResumeUsenetQueue } from '../../tools/hooks/api';
import { HomarrCardWrapper } from '../../components/Dashboard/Tiles/HomarrCardWrapper';
import { humanFileSize } from '../../tools/humanFileSize'; import { humanFileSize } from '../../tools/humanFileSize';
import { AppIntegrationType } from '../../types/app';
import { defineWidget } from '../helper'; import { defineWidget } from '../helper';
import { IWidget } from '../widgets'; import { IWidget } from '../widgets';
import { UsenetHistoryList } from './UsenetHistoryList';
import { UsenetQueueList } from './UsenetQueueList';
dayjs.extend(duration); dayjs.extend(duration);
@@ -46,15 +44,14 @@ const definition = defineWidget({
export type IWeatherWidget = IWidget<typeof definition['id'], typeof definition>; export type IWeatherWidget = IWidget<typeof definition['id'], typeof definition>;
interface UseNetTileProps extends BaseTileProps {} interface UseNetTileProps {}
function UseNetTile({ className }: UseNetTileProps) { function UseNetTile({}: UseNetTileProps) {
const { t } = useTranslation('modules/usenet'); const { t } = useTranslation('modules/usenet');
const { config } = useConfigContext(); const { config } = useConfigContext();
const downloadApps = const downloadApps =
config?.apps.filter( config?.apps.filter((x) => x.integration && downloadAppTypes.includes(x.integration.type)) ??
(x) => x.integration && downloadAppTypes.includes(x.integration.type) [];
) ?? [];
const [selectedAppId, setSelectedApp] = useState<string | null>(downloadApps[0]?.id); const [selectedAppId, setSelectedApp] = useState<string | null>(downloadApps[0]?.id);
const { data } = useGetUsenetInfo({ appId: selectedAppId! }); const { data } = useGetUsenetInfo({ appId: selectedAppId! });
@@ -70,14 +67,12 @@ function UseNetTile({ className }: UseNetTileProps) {
if (downloadApps.length === 0) { if (downloadApps.length === 0) {
return ( return (
<HomarrCardWrapper className={className}> <Stack>
<Stack> <Title order={3}>{t('card.errors.noDownloadClients.title')}</Title>
<Title order={3}>{t('card.errors.noDownloadClients.title')}</Title> <Group>
<Group> <Text>{t('card.errors.noDownloadClients.text')}</Text>
<Text>{t('card.errors.noDownloadClients.text')}</Text> </Group>
</Group> </Stack>
</Stack>
</HomarrCardWrapper>
); );
} }
@@ -89,50 +84,48 @@ function UseNetTile({ className }: UseNetTileProps) {
const MIN_WIDTH_MOBILE = useMantineTheme().breakpoints.xs; const MIN_WIDTH_MOBILE = useMantineTheme().breakpoints.xs;
return ( return (
<HomarrCardWrapper className={className}> <Tabs keepMounted={false} defaultValue="queue">
<Tabs keepMounted={false} defaultValue="queue"> <Tabs.List ref={ref} mb="md" style={{ flex: 1 }} grow>
<Tabs.List ref={ref} mb="md" style={{ flex: 1 }} grow> <Tabs.Tab value="queue">{t('tabs.queue')}</Tabs.Tab>
<Tabs.Tab value="queue">{t('tabs.queue')}</Tabs.Tab> <Tabs.Tab value="history">{t('tabs.history')}</Tabs.Tab>
<Tabs.Tab value="history">{t('tabs.history')}</Tabs.Tab> {data && (
{data && ( <Group position="right" ml="auto">
<Group position="right" ml="auto"> {width > MIN_WIDTH_MOBILE && (
{width > MIN_WIDTH_MOBILE && ( <>
<> <Badge>{humanFileSize(data?.speed)}/s</Badge>
<Badge>{humanFileSize(data?.speed)}/s</Badge> <Badge>
<Badge> {t('info.sizeLeft')}: {humanFileSize(data?.sizeLeft)}
{t('info.sizeLeft')}: {humanFileSize(data?.sizeLeft)} </Badge>
</Badge> </>
</> )}
)} </Group>
</Group>
)}
</Tabs.List>
{downloadApps.length > 1 && (
<Select
value={selectedAppId}
onChange={setSelectedApp}
ml="xs"
data={downloadApps.map((app) => ({ value: app.id, label: app.name }))}
/>
)} )}
<Tabs.Panel value="queue"> </Tabs.List>
<UsenetQueueList appId={selectedAppId} /> {downloadApps.length > 1 && (
{!data ? null : data.paused ? ( <Select
<Button uppercase onClick={() => resume()} radius="xl" size="xs" fullWidth mt="sm"> value={selectedAppId}
<IconPlayerPlay size={12} style={{ marginRight: 5 }} /> {t('info.paused')} onChange={setSelectedApp}
</Button> ml="xs"
) : ( data={downloadApps.map((app) => ({ value: app.id, label: app.name }))}
<Button uppercase onClick={() => pause()} radius="xl" size="xs" fullWidth mt="sm"> />
<IconPlayerPause size={12} style={{ marginRight: 5 }} />{' '} )}
{dayjs.duration(data.eta, 's').format('HH:mm')} <Tabs.Panel value="queue">
</Button> <UsenetQueueList appId={selectedAppId} />
)} {!data ? null : data.paused ? (
</Tabs.Panel> <Button uppercase onClick={() => resume()} radius="xl" size="xs" fullWidth mt="sm">
<Tabs.Panel value="history" style={{ display: 'flex', flexDirection: 'column' }}> <IconPlayerPlay size={12} style={{ marginRight: 5 }} /> {t('info.paused')}
<UsenetHistoryList appId={selectedAppId} /> </Button>
</Tabs.Panel> ) : (
</Tabs> <Button uppercase onClick={() => pause()} radius="xl" size="xs" fullWidth mt="sm">
</HomarrCardWrapper> <IconPlayerPause size={12} style={{ marginRight: 5 }} />{' '}
{dayjs.duration(data.eta, 's').format('HH:mm')}
</Button>
)}
</Tabs.Panel>
<Tabs.Panel value="history" style={{ display: 'flex', flexDirection: 'column' }}>
<UsenetHistoryList appId={selectedAppId} />
</Tabs.Panel>
</Tabs>
); );
} }

View File

@@ -32,16 +32,16 @@ const definition = defineWidget({
export type IWeatherWidget = IWidget<typeof definition['id'], typeof definition>; export type IWeatherWidget = IWidget<typeof definition['id'], typeof definition>;
interface WeatherTileProps extends BaseTileProps { interface WeatherTileProps {
widget: IWeatherWidget; widget: IWeatherWidget;
} }
function WeatherTile({ className, widget }: WeatherTileProps) { function WeatherTile({ widget }: WeatherTileProps) {
const { data: weather, isLoading, isError } = useWeatherForCity(widget.properties.location); const { data: weather, isLoading, isError } = useWeatherForCity(widget.properties.location);
if (isLoading) { if (isLoading) {
return ( return (
<HomarrCardWrapper className={className}> <>
<Skeleton height={40} width={100} mb="xl" /> <Skeleton height={40} width={100} mb="xl" />
<Group noWrap> <Group noWrap>
<Skeleton height={50} circle /> <Skeleton height={50} circle />
@@ -50,58 +50,53 @@ function WeatherTile({ className, widget }: WeatherTileProps) {
<Skeleton height={25} width={70} /> <Skeleton height={25} width={70} />
</Group> </Group>
</Group> </Group>
</HomarrCardWrapper> </>
); );
} }
if (isError) { if (isError) {
return ( return (
<HomarrCardWrapper className={className}> <Center>
<Center> <Text weight={500}>An error occured</Text>
<Text weight={500}>An error occured</Text> </Center>
</Center>
</HomarrCardWrapper>
); );
} }
// TODO: add widgetWrapper that is generic and uses the definition // TODO: add widgetWrapper that is generic and uses the definition
return ( return (
<HomarrCardWrapper className={className}> <Center style={{ height: '100%' }}>
<WidgetsMenu integration={definition.id} widget={widget} /> <Group spacing="md" noWrap align="center">
<Center style={{ height: '100%' }}> <WeatherIcon code={weather!.current_weather.weathercode} />
<Group spacing="md" noWrap align="center"> <Stack p={0} spacing={4}>
<WeatherIcon code={weather!.current_weather.weathercode} /> <Title order={2}>
<Stack p={0} spacing={4}> {getPerferedUnit(
<Title order={2}> weather!.current_weather.temperature,
{getPerferedUnit( widget.properties.displayInFahrenheit
weather!.current_weather.temperature, )}
widget.properties.displayInFahrenheit </Title>
)} <Group spacing="xs" noWrap>
</Title> <div>
<Group spacing="xs" noWrap> <span>
<div> {getPerferedUnit(
<span> weather!.daily.temperature_2m_max[0],
{getPerferedUnit( widget.properties.displayInFahrenheit
weather!.daily.temperature_2m_max[0], )}
widget.properties.displayInFahrenheit </span>
)} <IconArrowUpRight size={16} style={{ right: 15 }} />
</span> </div>
<IconArrowUpRight size={16} style={{ right: 15 }} /> <div>
</div> <span>
<div> {getPerferedUnit(
<span> weather!.daily.temperature_2m_min[0],
{getPerferedUnit( widget.properties.displayInFahrenheit
weather!.daily.temperature_2m_min[0], )}
widget.properties.displayInFahrenheit </span>
)} <IconArrowDownRight size={16} />
</span> </div>
<IconArrowDownRight size={16} /> </Group>
</div> </Stack>
</Group> </Group>
</Stack> </Center>
</Group>
</Center>
</HomarrCardWrapper>
); );
} }