Migrate integrations to widgets

This commit is contained in:
Meierschlumpf
2022-12-18 21:21:23 +01:00
parent 7cb71eba84
commit e914174e78
14 changed files with 247 additions and 120 deletions

View File

@@ -1,15 +1,14 @@
import { IntegrationsType } from '../../../types/integration'; import calendarDefinition from '../../../widgets/calendar/CalendarTile';
import { CalendarTile } from '../../../widgets/calendar/CalendarTile'; import clockDefinition from '../../../widgets/clock/ClockTile';
import { ClockTile } from '../../../widgets/clock/ClockTile'; import dashDotDefinition from '../../../widgets/dashDot/DashDotTile';
import { DashDotTile } from '../../../widgets/dashDot/DashDotTile'; import useNetDefinition from '../../../widgets/useNet/UseNetTile';
import { UseNetTile } from '../../../widgets/useNet/UseNetTile'; import weatherDefinition from '../../../widgets/weather/WeatherTile';
import { WeatherTile } from '../../../widgets/weather/WeatherTile';
import { EmptyTile } from './EmptyTile'; import { EmptyTile } from './EmptyTile';
import { ServiceTile } from './Service/ServiceTile'; import { ServiceTile } from './Service/ServiceTile';
// TODO: just remove and use service (later app) directly. For widgets the the definition should contain min/max width/height // TODO: just remove and use service (later app) directly. For widgets the the definition should contain min/max width/height
type TileDefinitionProps = { type TileDefinitionProps = {
[key in keyof IntegrationsType | 'service']: { [key in keyof any | 'service']: {
minWidth?: number; minWidth?: number;
minHeight?: number; minHeight?: number;
maxWidth?: number; maxWidth?: number;
@@ -18,7 +17,6 @@ type TileDefinitionProps = {
}; };
}; };
// TODO: change components for other modules
export const Tiles: TileDefinitionProps = { export const Tiles: TileDefinitionProps = {
service: { service: {
component: ServiceTile, component: ServiceTile,
@@ -28,49 +26,49 @@ export const Tiles: TileDefinitionProps = {
maxHeight: 12, maxHeight: 12,
}, },
bitTorrent: { bitTorrent: {
component: EmptyTile, //CalendarTile, component: EmptyTile,
minWidth: 4, minWidth: 4,
maxWidth: 12, maxWidth: 12,
minHeight: 5, minHeight: 5,
maxHeight: 12, maxHeight: 12,
}, },
calendar: { calendar: {
component: CalendarTile, component: calendarDefinition.component,
minWidth: 4, minWidth: 4,
maxWidth: 12, maxWidth: 12,
minHeight: 5, minHeight: 5,
maxHeight: 12, maxHeight: 12,
}, },
clock: { clock: {
component: ClockTile, component: clockDefinition.component,
minWidth: 4, minWidth: 4,
maxWidth: 12, maxWidth: 12,
minHeight: 2, minHeight: 2,
maxHeight: 12, maxHeight: 12,
}, },
dashDot: { dashDot: {
component: DashDotTile, component: dashDotDefinition.component,
minWidth: 4, minWidth: 4,
maxWidth: 9, maxWidth: 9,
minHeight: 5, minHeight: 5,
maxHeight: 14, maxHeight: 14,
}, },
torrentNetworkTraffic: { torrentNetworkTraffic: {
component: EmptyTile, //CalendarTile, component: EmptyTile,
minWidth: 4, minWidth: 4,
maxWidth: 12, maxWidth: 12,
minHeight: 5, minHeight: 5,
maxHeight: 12, maxHeight: 12,
}, },
useNet: { useNet: {
component: UseNetTile, component: useNetDefinition.component,
minWidth: 4, minWidth: 4,
maxWidth: 12, maxWidth: 12,
minHeight: 5, minHeight: 5,
maxHeight: 12, maxHeight: 12,
}, },
weather: { weather: {
component: WeatherTile, component: weatherDefinition.component,
minWidth: 4, minWidth: 4,
maxWidth: 12, maxWidth: 12,
minHeight: 2, minHeight: 2,

View File

@@ -2,6 +2,7 @@ import { Group, Stack } from '@mantine/core';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useConfigContext } from '../../../config/provider'; import { useConfigContext } from '../../../config/provider';
import { useScreenLargerThan } from '../../../tools/hooks/useScreenLargerThan'; import { useScreenLargerThan } from '../../../tools/hooks/useScreenLargerThan';
import { useScreenSmallerThan } from '../../../tools/hooks/useScreenSmallerThan';
import { CategoryType } from '../../../types/category'; import { CategoryType } from '../../../types/category';
import { WrapperType } from '../../../types/wrapper'; import { WrapperType } from '../../../types/wrapper';
import { DashboardCategory } from '../Wrappers/Category/Category'; import { DashboardCategory } from '../Wrappers/Category/Category';
@@ -11,11 +12,11 @@ import { DashboardWrapper } from '../Wrappers/Wrapper/Wrapper';
export const DashboardView = () => { export const DashboardView = () => {
const wrappers = useWrapperItems(); const wrappers = useWrapperItems();
const layoutSettings = useConfigContext()?.config?.settings.customization.layout; const layoutSettings = useConfigContext()?.config?.settings.customization.layout;
const showSidebars = useScreenLargerThan('md'); const doNotShowSidebar = useScreenSmallerThan('md');
return ( return (
<Group align="top" h="100%"> <Group align="top" h="100%">
{layoutSettings?.enabledLeftSidebar && showSidebars ? ( {layoutSettings?.enabledLeftSidebar && !doNotShowSidebar ? (
<DashboardSidebar location="left" /> <DashboardSidebar location="left" />
) : null} ) : null}
<Stack mx={-10} style={{ flexGrow: 1 }}> <Stack mx={-10} style={{ flexGrow: 1 }}>
@@ -27,7 +28,7 @@ export const DashboardView = () => {
) )
)} )}
</Stack> </Stack>
{layoutSettings?.enabledRightSidebar && showSidebars ? ( {layoutSettings?.enabledRightSidebar && !doNotShowSidebar ? (
<DashboardSidebar location="right" /> <DashboardSidebar location="right" />
) : null} ) : null}
</Group> </Group>

View File

@@ -34,7 +34,6 @@ export const DashboardWrapper = ({ wrapper }: DashboardWrapperProps) => {
); );
})} })}
{Object.entries(integrations).map(([k, v]) => { {Object.entries(integrations).map(([k, v]) => {
console.log(k);
const { component: TileComponent, ...tile } = Tiles[k as keyof typeof Tiles]; const { component: TileComponent, ...tile } = Tiles[k as keyof typeof Tiles];
return ( return (

View File

@@ -1,52 +0,0 @@
import { TileBaseType } from './tile';
export interface IntegrationsType {
calendar?: CalendarIntegrationType;
clock?: ClockIntegrationType;
weather?: WeatherIntegrationType;
dashDot?: DashDotIntegrationType;
bitTorrent?: BitTorrentIntegrationType;
useNet?: UseNetIntegrationType;
torrentNetworkTraffic?: TorrentNetworkTrafficIntegrationType;
}
export interface CalendarIntegrationType extends TileBaseType {
properties: {
isWeekStartingAtSunday: boolean;
};
}
export interface ClockIntegrationType extends TileBaseType {
properties: {
is24HoursFormat: boolean;
};
}
export interface WeatherIntegrationType extends TileBaseType {
properties: {
location: string;
isFahrenheit: boolean;
};
}
export interface DashDotIntegrationType extends TileBaseType {
properties: {
graphs: DashDotGraphType[];
isStorageMultiView: boolean;
isCpuMultiView: boolean;
isCompactView: boolean;
url: string;
};
}
export type DashDotGraphType = 'cpu' | 'storage' | 'ram' | 'network' | 'gpu';
export interface BitTorrentIntegrationType extends TileBaseType {
properties: {
hideDownloadedTorrents: boolean;
};
}
export type UseNetIntegrationType = TileBaseType;
export type TorrentNetworkTrafficIntegrationType = TileBaseType;

View File

@@ -1,5 +1,6 @@
import { createStyles, MantineThemeColors, useMantineTheme } from '@mantine/core'; import { createStyles, MantineThemeColors, useMantineTheme } from '@mantine/core';
import { Calendar } from '@mantine/dates'; import { Calendar } from '@mantine/dates';
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 { HomarrCardWrapper } from '../../components/Dashboard/Tiles/HomarrCardWrapper';
@@ -7,15 +8,30 @@ 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';
import { CalendarIntegrationType } from '../../types/integration'; import { defineWidget } from '../helper';
import { IWidget } from '../widgets';
import { CalendarDay } from './CalendarDay'; import { CalendarDay } from './CalendarDay';
import { MediasType } from './type'; import { MediasType } from './type';
const definition = defineWidget({
id: 'calendar',
icon: IconCalendarTime,
options: {
sundayStart: {
type: 'switch',
defaultValue: false,
},
},
component: CalendarTile,
});
export type ICalendarWidget = IWidget<typeof definition['id'], typeof definition>;
interface CalendarTileProps extends BaseTileProps { interface CalendarTileProps extends BaseTileProps {
module: CalendarIntegrationType | undefined; // TODO: change to new type defined through widgetDefinition module: ICalendarWidget;
} }
export const CalendarTile = ({ className, module }: CalendarTileProps) => { function CalendarTile({ className, module }: 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);
@@ -34,8 +50,6 @@ export const CalendarTile = ({ className, module }: CalendarTileProps) => {
).json()) as MediasType, ).json()) as MediasType,
}); });
if (!module) return <></>;
return ( return (
<HomarrCardWrapper className={className} p={6}> <HomarrCardWrapper className={className} p={6}>
<Calendar <Calendar
@@ -44,7 +58,7 @@ export const CalendarTile = ({ className, module }: CalendarTileProps) => {
size="xs" size="xs"
fullWidth fullWidth
onChange={() => {}} onChange={() => {}}
firstDayOfWeek={module.properties?.isWeekStartingAtSunday ? 'sunday' : 'monday'} firstDayOfWeek={module.properties.sundayStart ? 'sunday' : 'monday'}
dayStyle={(date) => ({ dayStyle={(date) => ({
margin: 1, margin: 1,
backgroundColor: isToday(date) backgroundColor: isToday(date)
@@ -67,7 +81,7 @@ export const CalendarTile = ({ className, module }: CalendarTileProps) => {
/> />
</HomarrCardWrapper> </HomarrCardWrapper>
); );
}; }
const useStyles = createStyles((theme, secondaryColor: keyof MantineThemeColors) => ({ const useStyles = createStyles((theme, secondaryColor: keyof MantineThemeColors) => ({
weekend: { weekend: {
@@ -99,3 +113,5 @@ const getReleasedMediasForDate = (medias: MediasType | undefined, date: Date): M
totalCount, totalCount,
}; };
}; };
export default definition;

View File

@@ -5,15 +5,31 @@ import { HomarrCardWrapper } from '../../components/Dashboard/Tiles/HomarrCardWr
import { WidgetsMenu } from '../../components/Dashboard/Tiles/Widgets/WidgetsMenu'; import { WidgetsMenu } from '../../components/Dashboard/Tiles/Widgets/WidgetsMenu';
import { BaseTileProps } from '../../components/Dashboard/Tiles/type'; import { BaseTileProps } from '../../components/Dashboard/Tiles/type';
import { useSetSafeInterval } from '../../tools/hooks/useSetSafeInterval'; import { useSetSafeInterval } from '../../tools/hooks/useSetSafeInterval';
import { ClockIntegrationType } from '../../types/integration'; import { defineWidget } from '../helper';
import { IWidget } from '../widgets';
import { IconClock } from '@tabler/icons';
const definition = defineWidget({
id: 'clock',
icon: IconClock,
options: {
display24HourFormat: {
type: 'switch',
defaultValue: false,
},
},
component: ClockTile,
});
export type IClockWidget = IWidget<typeof definition['id'], typeof definition>;
interface ClockTileProps extends BaseTileProps { interface ClockTileProps extends BaseTileProps {
module: ClockIntegrationType; // TODO: change to new type defined through widgetDefinition module: IClockWidget; // TODO: change to new type defined through widgetDefinition
} }
export const ClockTile = ({ className, module }: ClockTileProps) => { function ClockTile({ className, module }: ClockTileProps) {
const date = useDateState(); const date = useDateState();
const formatString = module?.properties.is24HoursFormat ? 'HH:mm' : 'h:mm A'; const formatString = module.properties.display24HourFormat ? 'HH:mm' : 'h:mm A';
// TODO: add widgetWrapper that is generic and uses the definition // TODO: add widgetWrapper that is generic and uses the definition
return ( return (
@@ -21,7 +37,7 @@ export const ClockTile = ({ className, module }: ClockTileProps) => {
<WidgetsMenu<'clock'> <WidgetsMenu<'clock'>
integration="clock" integration="clock"
module={module} module={module}
options={module?.properties} options={module.properties}
labels={{ is24HoursFormat: 'descriptor.settings.display24HourFormat.label' }} labels={{ is24HoursFormat: 'descriptor.settings.display24HourFormat.label' }}
/> />
<Center style={{ height: '100%' }}> <Center style={{ height: '100%' }}>
@@ -32,7 +48,7 @@ export const ClockTile = ({ className, module }: ClockTileProps) => {
</Center> </Center>
</HomarrCardWrapper> </HomarrCardWrapper>
); );
}; }
/** /**
* State which updates when the minute is changing * State which updates when the minute is changing
@@ -69,3 +85,5 @@ const getMsUntilNextMinute = () => {
); );
return nextMinute.getTime() - now.getTime(); return nextMinute.getTime() - now.getTime();
}; };
export default definition;

View File

@@ -35,10 +35,13 @@ export const DashDotGraph = ({ graph, isCompact, dashDotUrl }: DashDotGraphProps
const useIframeSrc = (dashDotUrl: string, graph: GraphType, isCompact: boolean) => { const useIframeSrc = (dashDotUrl: string, graph: GraphType, isCompact: boolean) => {
const { colorScheme, colors, radius } = useMantineTheme(); const { colorScheme, colors, radius } = useMantineTheme();
const surface = (colorScheme === 'dark' ? colors.dark[7] : colors.gray[0]).substring(1); // removes # from hex value const surface = (colorScheme === 'dark' ? colors.dark[7] : colors.gray[0]).substring(1); // removes # from hex value
const graphId = graph.id === 'memory' ? 'ram' : graph.id;
return ( return (
`${dashDotUrl}` + `${dashDotUrl}` +
`?singleGraphMode=true` + `?singleGraphMode=true` +
`&graph=${graph.id}` + `&graph=${graphId}` +
`&theme=${colorScheme}` + `&theme=${colorScheme}` +
`&surface=${surface}` + `&surface=${surface}` +
`&gap=${isCompact ? 10 : 5}` + `&gap=${isCompact ? 10 : 5}` +

View File

@@ -2,20 +2,52 @@ 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 { DashDotCompactNetwork, DashDotInfo } from './DashDotCompactNetwork';
import { DashDotGraph } from './DashDotGraph';
import { DashDotCompactStorage } from './DashDotCompactStorage';
import { BaseTileProps } from '../../components/Dashboard/Tiles/type';
import { DashDotIntegrationType } from '../../types/integration';
import { WidgetsMenu } from '../../components/Dashboard/Tiles/Widgets/WidgetsMenu';
import { HomarrCardWrapper } from '../../components/Dashboard/Tiles/HomarrCardWrapper'; 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 { IWidget } from '../widgets';
import { DashDotCompactNetwork, DashDotInfo } from './DashDotCompactNetwork';
import { DashDotCompactStorage } from './DashDotCompactStorage';
import { DashDotGraph } from './DashDotGraph';
const definition = defineWidget({
id: 'dashDot',
icon: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/dashdot.png',
options: {
cpuMultiView: {
type: 'switch',
defaultValue: false,
},
storageMultiView: {
type: 'switch',
defaultValue: false,
},
useCompactView: {
type: 'switch',
defaultValue: true,
},
graphs: {
type: 'multi-select',
defaultValue: ['cpu', 'memory'],
data: ['cpu', 'memory', 'storage', 'network', 'gpu'],
},
url: {
type: 'text',
defaultValue: '',
},
},
component: DashDotTile,
});
export type IDashDotTile = IWidget<typeof definition['id'], typeof definition>;
interface DashDotTileProps extends BaseTileProps { interface DashDotTileProps extends BaseTileProps {
module: DashDotIntegrationType; // TODO: change to new type defined through widgetDefinition module: IDashDotTile; // TODO: change to new type defined through widgetDefinition
} }
export const DashDotTile = ({ module, className }: DashDotTileProps) => { function DashDotTile({ module, className }: DashDotTileProps) {
const { classes } = useDashDotTileStyles(); const { classes } = useDashDotTileStyles();
const { t } = useTranslation('modules/dashdot'); const { t } = useTranslation('modules/dashdot');
@@ -25,11 +57,11 @@ export const DashDotTile = ({ module, className }: DashDotTileProps) => {
const graphs = module?.properties.graphs.map((g) => ({ const graphs = module?.properties.graphs.map((g) => ({
id: g, id: g,
name: t(`card.graphs.${g === 'ram' ? 'memory' : g}.title`), name: t(`card.graphs.${g}.title`),
twoSpan: ['network', 'gpu'].includes(g), twoSpan: ['network', 'gpu'].includes(g),
isMultiView: isMultiView:
(g === 'cpu' && module.properties.isCpuMultiView) || (g === 'cpu' && module.properties.cpuMultiView) ||
(g === 'storage' && module.properties.isStorageMultiView), (g === 'storage' && module.properties.storageMultiView),
})); }));
const heading = ( const heading = (
@@ -66,7 +98,7 @@ export const DashDotTile = ({ module, className }: DashDotTileProps) => {
); );
} }
const isCompact = module?.properties.isCompactView ?? false; const isCompact = module?.properties.useCompactView ?? false;
const isCompactStorageVisible = graphs?.some((g) => g.id === 'storage' && isCompact); const isCompactStorageVisible = graphs?.some((g) => g.id === 'storage' && isCompact);
@@ -101,7 +133,7 @@ export const DashDotTile = ({ module, className }: DashDotTileProps) => {
)} )}
</HomarrCardWrapper> </HomarrCardWrapper>
); );
}; }
const useDashDotInfo = () => { const useDashDotInfo = () => {
const { name: configName, config } = useConfigContext(); const { name: configName, config } = useConfigContext();
@@ -139,3 +171,5 @@ export const useDashDotTileStyles = createStyles(() => ({
}, },
}, },
})); }));
export default definition;

View File

@@ -1,7 +1,5 @@
import { DashDotGraphType } from '../../types/integration';
export interface DashDotGraph { export interface DashDotGraph {
id: DashDotGraphType; id: string;
name: string; name: string;
twoSpan: boolean; twoSpan: boolean;
isMultiView: boolean | undefined; isMultiView: boolean | undefined;

10
src/widgets/helper.ts Normal file
View File

@@ -0,0 +1,10 @@
// Method which allows to define the type verry specific and type checks all
import { IWidgetDefinition } from './widgets';
// The options of IWidgetDefinition are so heavily typed that it even used 'true' as type
export const defineWidget = <TKey extends string, TOptions extends IWidgetDefinition<TKey>>(
options: TOptions
) => {
return options;
};

View File

@@ -1,2 +1,4 @@
export {}; import calendar from './calendar/CalendarTile';
export default { calendar };
// TODO: add exports of new IWidgetDefinitions to here // TODO: add exports of new IWidgetDefinitions to here

View File

@@ -9,7 +9,7 @@ import {
Title, Title,
useMantineTheme, useMantineTheme,
} from '@mantine/core'; } from '@mantine/core';
import { IconPlayerPause, IconPlayerPlay } from '@tabler/icons'; import { IconFileDownload, IconPlayerPause, IconPlayerPlay } from '@tabler/icons';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useElementSize } from '@mantine/hooks'; import { useElementSize } from '@mantine/hooks';
@@ -24,14 +24,25 @@ 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 { HomarrCardWrapper } from '../../components/Dashboard/Tiles/HomarrCardWrapper';
import { humanFileSize } from '../../tools/humanFileSize'; import { humanFileSize } from '../../tools/humanFileSize';
import { defineWidget } from '../helper';
import { IWidget } from '../widgets';
dayjs.extend(duration); dayjs.extend(duration);
const downloadServiceTypes: ServiceIntegrationType['type'][] = ['sabnzbd', 'nzbGet']; const downloadServiceTypes: ServiceIntegrationType['type'][] = ['sabnzbd', 'nzbGet'];
const definition = defineWidget({
id: 'useNet',
icon: IconFileDownload,
options: {},
component: UseNetTile,
});
export type IWeatherWidget = IWidget<typeof definition['id'], typeof definition>;
interface UseNetTileProps extends BaseTileProps {} interface UseNetTileProps extends BaseTileProps {}
export const UseNetTile = ({ className }: UseNetTileProps) => { function UseNetTile({ className }: UseNetTileProps) {
const { t } = useTranslation('modules/usenet'); const { t } = useTranslation('modules/usenet');
const { config } = useConfigContext(); const { config } = useConfigContext();
const downloadServices = const downloadServices =
@@ -117,4 +128,6 @@ export const UseNetTile = ({ className }: UseNetTileProps) => {
</Tabs> </Tabs>
</HomarrCardWrapper> </HomarrCardWrapper>
); );
}; }
export default definition;

View File

@@ -1,22 +1,37 @@
import { Center, Group, Skeleton, Stack, Text, Title } from '@mantine/core'; import { Center, Group, Skeleton, Stack, Text, Title } from '@mantine/core';
import { IconArrowDownRight, IconArrowUpRight } from '@tabler/icons'; import { IconArrowDownRight, IconArrowUpRight, IconCloudRain } from '@tabler/icons';
import { HomarrCardWrapper } from '../../components/Dashboard/Tiles/HomarrCardWrapper'; import { HomarrCardWrapper } from '../../components/Dashboard/Tiles/HomarrCardWrapper';
import { WidgetsMenu } from '../../components/Dashboard/Tiles/Widgets/WidgetsMenu';
import { BaseTileProps } from '../../components/Dashboard/Tiles/type'; import { BaseTileProps } from '../../components/Dashboard/Tiles/type';
import { WeatherIntegrationType } from '../../types/integration'; import { WidgetsMenu } from '../../components/Dashboard/Tiles/Widgets/WidgetsMenu';
import { defineWidget } from '../helper';
import { IWidget } from '../widgets';
import { useWeatherForCity } from './useWeatherForCity'; import { useWeatherForCity } from './useWeatherForCity';
import { WeatherIcon } from './WeatherIcon'; import { WeatherIcon } from './WeatherIcon';
const definition = defineWidget({
id: 'weather',
icon: IconCloudRain,
options: {
displayInFahrenheit: {
type: 'switch',
defaultValue: false,
},
location: {
type: 'text',
defaultValue: 'Paris',
},
},
component: WeatherTile,
});
export type IWeatherWidget = IWidget<typeof definition['id'], typeof definition>;
interface WeatherTileProps extends BaseTileProps { interface WeatherTileProps extends BaseTileProps {
module: WeatherIntegrationType; // TODO: change to new type defined through widgetDefinition module: IWeatherWidget;
} }
export const WeatherTile = ({ className, module }: WeatherTileProps) => { function WeatherTile({ className, module }: WeatherTileProps) {
const { const { data: weather, isLoading, isError } = useWeatherForCity(module.properties.location);
data: weather,
isLoading,
isError,
} = useWeatherForCity(module?.properties.location ?? 'Paris');
if (isLoading) { if (isLoading) {
return ( return (
@@ -49,7 +64,7 @@ export const WeatherTile = ({ className, module }: WeatherTileProps) => {
<WidgetsMenu <WidgetsMenu
integration="weather" integration="weather"
module={module} module={module}
options={module?.properties} options={module.properties}
labels={{ labels={{
isFahrenheit: 'descriptor.settings.displayInFahrenheit.label', isFahrenheit: 'descriptor.settings.displayInFahrenheit.label',
location: 'descriptor.settings.location.label', location: 'descriptor.settings.location.label',
@@ -62,7 +77,7 @@ export const WeatherTile = ({ className, module }: WeatherTileProps) => {
<Title order={2}> <Title order={2}>
{getPerferedUnit( {getPerferedUnit(
weather!.current_weather.temperature, weather!.current_weather.temperature,
module?.properties.isFahrenheit module.properties.displayInFahrenheit
)} )}
</Title> </Title>
<Group spacing="xs" noWrap> <Group spacing="xs" noWrap>
@@ -70,7 +85,7 @@ export const WeatherTile = ({ className, module }: WeatherTileProps) => {
<span> <span>
{getPerferedUnit( {getPerferedUnit(
weather!.daily.temperature_2m_max[0], weather!.daily.temperature_2m_max[0],
module?.properties.isFahrenheit module.properties.displayInFahrenheit
)} )}
</span> </span>
<IconArrowUpRight size={16} style={{ right: 15 }} /> <IconArrowUpRight size={16} style={{ right: 15 }} />
@@ -79,7 +94,7 @@ export const WeatherTile = ({ className, module }: WeatherTileProps) => {
<span> <span>
{getPerferedUnit( {getPerferedUnit(
weather!.daily.temperature_2m_min[0], weather!.daily.temperature_2m_min[0],
module?.properties.isFahrenheit module.properties.displayInFahrenheit
)} )}
</span> </span>
<IconArrowDownRight size={16} /> <IconArrowDownRight size={16} />
@@ -90,7 +105,9 @@ export const WeatherTile = ({ className, module }: WeatherTileProps) => {
</Center> </Center>
</HomarrCardWrapper> </HomarrCardWrapper>
); );
}; }
const getPerferedUnit = (value: number, isFahrenheit = false): string => const getPerferedUnit = (value: number, isFahrenheit = false): string =>
isFahrenheit ? `${(value * (9 / 5) + 32).toFixed(1)}°F` : `${value.toFixed(1)}°C`; isFahrenheit ? `${(value * (9 / 5) + 32).toFixed(1)}°F` : `${value.toFixed(1)}°C`;
export default definition;

70
src/widgets/widgets.d.ts vendored Normal file
View File

@@ -0,0 +1,70 @@
import { IconSun, TablerIcon } from '@tabler/icons';
import React from 'react';
import { BaseTileProps } from '../components/Dashboard/Tiles/type';
// Type of widgets which are safed to config
export type IWidget<TKey extends string, TDefinition extends IWidgetDefinition> = {
id: TKey;
properties: {
[key in keyof TDefinition['options']]: MakeLessSpecific<
TDefinition['options'][key]['defaultValue']
>;
};
area: AreaType;
shape: ShapeType;
};
// Makes the type less specific
// For example when the type true is used as input the result is boolean
// By not using this type the definition would always be { property: true }
type MakeLessSpecific<TInput extends IWidgetOptionValue['defaultValue']> = TInput extends boolean
? boolean
: TInput extends number
? number
: TInput extends string[]
? string[]
: TInput extends string
? string
: never;
// Types of options that can be specified for the widget edit modal
export type IWidgetOptionValue =
| IMultiSelectOptionValue
| ISwitchOptionValue
| ITextInputOptionValue
| INumberInputOptionValue;
// will show a multi-select with specified data
export type IMultiSelectOptionValue = {
type: 'multi-select';
defaultValue: string[];
data: string[];
};
// will show a switch
export type ISwitchOptionValue = {
type: 'switch';
defaultValue: boolean;
};
// will show a text-input
export type ITextInputOptionValue = {
type: 'text';
defaultValue: string;
};
// will show a number-input
export type INumberInputOptionValue = {
type: 'number';
defaultValue: string;
};
// is used to type the widget definitions which will be used to display all widgets
export type IWidgetDefinition<TKey extends string = string> = {
id: TKey;
icon: TablerIcon | string;
options: {
[key: string]: IWidgetOptionValue;
};
component: React.ComponentType<any>;
};