diff --git a/src/components/Dashboard/Tiles/tilesDefinitions.tsx b/src/components/Dashboard/Tiles/tilesDefinitions.tsx index 20e3cf023..a3d950748 100644 --- a/src/components/Dashboard/Tiles/tilesDefinitions.tsx +++ b/src/components/Dashboard/Tiles/tilesDefinitions.tsx @@ -1,15 +1,14 @@ -import { IntegrationsType } from '../../../types/integration'; -import { CalendarTile } from '../../../widgets/calendar/CalendarTile'; -import { ClockTile } from '../../../widgets/clock/ClockTile'; -import { DashDotTile } from '../../../widgets/dashDot/DashDotTile'; -import { UseNetTile } from '../../../widgets/useNet/UseNetTile'; -import { WeatherTile } from '../../../widgets/weather/WeatherTile'; +import calendarDefinition from '../../../widgets/calendar/CalendarTile'; +import clockDefinition from '../../../widgets/clock/ClockTile'; +import dashDotDefinition from '../../../widgets/dashDot/DashDotTile'; +import useNetDefinition from '../../../widgets/useNet/UseNetTile'; +import weatherDefinition from '../../../widgets/weather/WeatherTile'; import { EmptyTile } from './EmptyTile'; import { AppTile } from './Apps/AppTile'; // TODO: just remove and use app (later app) directly. For widgets the the definition should contain min/max width/height type TileDefinitionProps = { - [key in keyof IntegrationsType | 'app']: { + [key in keyof any | 'app']: { minWidth?: number; minHeight?: number; maxWidth?: number; @@ -18,7 +17,6 @@ type TileDefinitionProps = { }; }; -// TODO: change components for other modules export const Tiles: TileDefinitionProps = { app: { component: AppTile, @@ -28,49 +26,49 @@ export const Tiles: TileDefinitionProps = { maxHeight: 12, }, bitTorrent: { - component: EmptyTile, //CalendarTile, + component: EmptyTile, minWidth: 4, maxWidth: 12, minHeight: 5, maxHeight: 12, }, calendar: { - component: CalendarTile, + component: calendarDefinition.component, minWidth: 4, maxWidth: 12, minHeight: 5, maxHeight: 12, }, clock: { - component: ClockTile, + component: clockDefinition.component, minWidth: 4, maxWidth: 12, minHeight: 2, maxHeight: 12, }, dashDot: { - component: DashDotTile, + component: dashDotDefinition.component, minWidth: 4, maxWidth: 9, minHeight: 5, maxHeight: 14, }, torrentNetworkTraffic: { - component: EmptyTile, //CalendarTile, + component: EmptyTile, minWidth: 4, maxWidth: 12, minHeight: 5, maxHeight: 12, }, useNet: { - component: UseNetTile, + component: useNetDefinition.component, minWidth: 4, maxWidth: 12, minHeight: 5, maxHeight: 12, }, weather: { - component: WeatherTile, + component: weatherDefinition.component, minWidth: 4, maxWidth: 12, minHeight: 2, diff --git a/src/components/Dashboard/Views/DashboardView.tsx b/src/components/Dashboard/Views/DashboardView.tsx index 9cb6342ac..bff1f0f04 100644 --- a/src/components/Dashboard/Views/DashboardView.tsx +++ b/src/components/Dashboard/Views/DashboardView.tsx @@ -2,6 +2,7 @@ import { Group, Stack } from '@mantine/core'; import { useMemo } from 'react'; import { useConfigContext } from '../../../config/provider'; import { useScreenLargerThan } from '../../../tools/hooks/useScreenLargerThan'; +import { useScreenSmallerThan } from '../../../tools/hooks/useScreenSmallerThan'; import { CategoryType } from '../../../types/category'; import { WrapperType } from '../../../types/wrapper'; import { DashboardCategory } from '../Wrappers/Category/Category'; @@ -11,11 +12,11 @@ import { DashboardWrapper } from '../Wrappers/Wrapper/Wrapper'; export const DashboardView = () => { const wrappers = useWrapperItems(); const layoutSettings = useConfigContext()?.config?.settings.customization.layout; - const showSidebars = useScreenLargerThan('md'); + const doNotShowSidebar = useScreenSmallerThan('md'); return ( - {layoutSettings?.enabledLeftSidebar && showSidebars ? ( + {layoutSettings?.enabledLeftSidebar && !doNotShowSidebar ? ( ) : null} @@ -27,7 +28,7 @@ export const DashboardView = () => { ) )} - {layoutSettings?.enabledRightSidebar && showSidebars ? ( + {layoutSettings?.enabledRightSidebar && !doNotShowSidebar ? ( ) : null} diff --git a/src/components/Dashboard/Wrappers/Wrapper/Wrapper.tsx b/src/components/Dashboard/Wrappers/Wrapper/Wrapper.tsx index 013eb2eb2..7c07a72e3 100644 --- a/src/components/Dashboard/Wrappers/Wrapper/Wrapper.tsx +++ b/src/components/Dashboard/Wrappers/Wrapper/Wrapper.tsx @@ -34,7 +34,6 @@ export const DashboardWrapper = ({ wrapper }: DashboardWrapperProps) => { ); })} {Object.entries(integrations).map(([k, v]) => { - console.log(k); const { component: TileComponent, ...tile } = Tiles[k as keyof typeof Tiles]; return ( diff --git a/src/types/integration.ts b/src/types/integration.ts deleted file mode 100644 index cddd9ca26..000000000 --- a/src/types/integration.ts +++ /dev/null @@ -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; diff --git a/src/widgets/calendar/CalendarTile.tsx b/src/widgets/calendar/CalendarTile.tsx index a9760c869..a23ac7c6d 100644 --- a/src/widgets/calendar/CalendarTile.tsx +++ b/src/widgets/calendar/CalendarTile.tsx @@ -1,5 +1,6 @@ import { createStyles, MantineThemeColors, useMantineTheme } from '@mantine/core'; import { Calendar } from '@mantine/dates'; +import { IconCalendarTime } from '@tabler/icons'; import { useQuery } from '@tanstack/react-query'; import { useState } from 'react'; import { HomarrCardWrapper } from '../../components/Dashboard/Tiles/HomarrCardWrapper'; @@ -7,15 +8,30 @@ import { BaseTileProps } from '../../components/Dashboard/Tiles/type'; import { useConfigContext } from '../../config/provider'; import { useColorTheme } from '../../tools/color'; import { isToday } from '../../tools/isToday'; -import { CalendarIntegrationType } from '../../types/integration'; +import { defineWidget } from '../helper'; +import { IWidget } from '../widgets'; import { CalendarDay } from './CalendarDay'; import { MediasType } from './type'; +const definition = defineWidget({ + id: 'calendar', + icon: IconCalendarTime, + options: { + sundayStart: { + type: 'switch', + defaultValue: false, + }, + }, + component: CalendarTile, +}); + +export type ICalendarWidget = IWidget; + 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 { name: configName } = useConfigContext(); const { classes, cx } = useStyles(secondaryColor); @@ -34,8 +50,6 @@ export const CalendarTile = ({ className, module }: CalendarTileProps) => { ).json()) as MediasType, }); - if (!module) return <>; - return ( { size="xs" fullWidth onChange={() => {}} - firstDayOfWeek={module.properties?.isWeekStartingAtSunday ? 'sunday' : 'monday'} + firstDayOfWeek={module.properties.sundayStart ? 'sunday' : 'monday'} dayStyle={(date) => ({ margin: 1, backgroundColor: isToday(date) @@ -67,7 +81,7 @@ export const CalendarTile = ({ className, module }: CalendarTileProps) => { /> ); -}; +} const useStyles = createStyles((theme, secondaryColor: keyof MantineThemeColors) => ({ weekend: { @@ -99,3 +113,5 @@ const getReleasedMediasForDate = (medias: MediasType | undefined, date: Date): M totalCount, }; }; + +export default definition; diff --git a/src/widgets/clock/ClockTile.tsx b/src/widgets/clock/ClockTile.tsx index 0866e8e36..bd90ae1c3 100644 --- a/src/widgets/clock/ClockTile.tsx +++ b/src/widgets/clock/ClockTile.tsx @@ -5,15 +5,31 @@ import { HomarrCardWrapper } from '../../components/Dashboard/Tiles/HomarrCardWr import { WidgetsMenu } from '../../components/Dashboard/Tiles/Widgets/WidgetsMenu'; import { BaseTileProps } from '../../components/Dashboard/Tiles/type'; 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; 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 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 return ( @@ -21,7 +37,7 @@ export const ClockTile = ({ className, module }: ClockTileProps) => { integration="clock" module={module} - options={module?.properties} + options={module.properties} labels={{ is24HoursFormat: 'descriptor.settings.display24HourFormat.label' }} />
@@ -32,7 +48,7 @@ export const ClockTile = ({ className, module }: ClockTileProps) => {
); -}; +} /** * State which updates when the minute is changing @@ -69,3 +85,5 @@ const getMsUntilNextMinute = () => { ); return nextMinute.getTime() - now.getTime(); }; + +export default definition; diff --git a/src/widgets/dashDot/DashDotGraph.tsx b/src/widgets/dashDot/DashDotGraph.tsx index 70280bbc7..7ad600ed3 100644 --- a/src/widgets/dashDot/DashDotGraph.tsx +++ b/src/widgets/dashDot/DashDotGraph.tsx @@ -35,10 +35,13 @@ export const DashDotGraph = ({ graph, isCompact, dashDotUrl }: DashDotGraphProps const useIframeSrc = (dashDotUrl: string, graph: GraphType, isCompact: boolean) => { const { colorScheme, colors, radius } = useMantineTheme(); 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 ( `${dashDotUrl}` + `?singleGraphMode=true` + - `&graph=${graph.id}` + + `&graph=${graphId}` + `&theme=${colorScheme}` + `&surface=${surface}` + `&gap=${isCompact ? 10 : 5}` + diff --git a/src/widgets/dashDot/DashDotTile.tsx b/src/widgets/dashDot/DashDotTile.tsx index fea8273d2..5c25143d6 100644 --- a/src/widgets/dashDot/DashDotTile.tsx +++ b/src/widgets/dashDot/DashDotTile.tsx @@ -2,20 +2,52 @@ import { createStyles, Group, Title } from '@mantine/core'; import { useQuery } from '@tanstack/react-query'; import axios from 'axios'; 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 { BaseTileProps } from '../../components/Dashboard/Tiles/type'; +import { WidgetsMenu } from '../../components/Dashboard/Tiles/Widgets/WidgetsMenu'; 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; 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 { t } = useTranslation('modules/dashdot'); @@ -25,11 +57,11 @@ export const DashDotTile = ({ module, className }: DashDotTileProps) => { const graphs = module?.properties.graphs.map((g) => ({ id: g, - name: t(`card.graphs.${g === 'ram' ? 'memory' : g}.title`), + name: t(`card.graphs.${g}.title`), twoSpan: ['network', 'gpu'].includes(g), isMultiView: - (g === 'cpu' && module.properties.isCpuMultiView) || - (g === 'storage' && module.properties.isStorageMultiView), + (g === 'cpu' && module.properties.cpuMultiView) || + (g === 'storage' && module.properties.storageMultiView), })); 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); @@ -101,7 +133,7 @@ export const DashDotTile = ({ module, className }: DashDotTileProps) => { )} ); -}; +} const useDashDotInfo = () => { const { name: configName, config } = useConfigContext(); @@ -139,3 +171,5 @@ export const useDashDotTileStyles = createStyles(() => ({ }, }, })); + +export default definition; diff --git a/src/widgets/dashDot/types.ts b/src/widgets/dashDot/types.ts index 16123735c..b11e4e520 100644 --- a/src/widgets/dashDot/types.ts +++ b/src/widgets/dashDot/types.ts @@ -1,7 +1,5 @@ -import { DashDotGraphType } from '../../types/integration'; - export interface DashDotGraph { - id: DashDotGraphType; + id: string; name: string; twoSpan: boolean; isMultiView: boolean | undefined; diff --git a/src/widgets/helper.ts b/src/widgets/helper.ts new file mode 100644 index 000000000..1344584bb --- /dev/null +++ b/src/widgets/helper.ts @@ -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 = >( + options: TOptions +) => { + return options; +}; diff --git a/src/widgets/index.ts b/src/widgets/index.ts index 56c87d6ff..82126275b 100644 --- a/src/widgets/index.ts +++ b/src/widgets/index.ts @@ -1,2 +1,4 @@ -export {}; +import calendar from './calendar/CalendarTile'; + +export default { calendar }; // TODO: add exports of new IWidgetDefinitions to here diff --git a/src/widgets/useNet/UseNetTile.tsx b/src/widgets/useNet/UseNetTile.tsx index d6dbe6122..b05287acc 100644 --- a/src/widgets/useNet/UseNetTile.tsx +++ b/src/widgets/useNet/UseNetTile.tsx @@ -9,7 +9,7 @@ import { Title, useMantineTheme, } from '@mantine/core'; -import { IconPlayerPause, IconPlayerPlay } from '@tabler/icons'; +import { IconFileDownload, IconPlayerPause, IconPlayerPlay } from '@tabler/icons'; import { useEffect, useState } from 'react'; import { useElementSize } from '@mantine/hooks'; @@ -24,14 +24,25 @@ import { useConfigContext } from '../../config/provider'; import { useGetUsenetInfo, usePauseUsenetQueue, useResumeUsenetQueue } from '../../tools/hooks/api'; import { HomarrCardWrapper } from '../../components/Dashboard/Tiles/HomarrCardWrapper'; import { humanFileSize } from '../../tools/humanFileSize'; +import { defineWidget } from '../helper'; +import { IWidget } from '../widgets'; dayjs.extend(duration); const downloadAppTypes: AppIntegrationType['type'][] = ['sabnzbd', 'nzbGet']; +const definition = defineWidget({ + id: 'useNet', + icon: IconFileDownload, + options: {}, + component: UseNetTile, +}); + +export type IWeatherWidget = IWidget; + interface UseNetTileProps extends BaseTileProps {} -export const UseNetTile = ({ className }: UseNetTileProps) => { +function UseNetTile({ className }: UseNetTileProps) { const { t } = useTranslation('modules/usenet'); const { config } = useConfigContext(); const downloadApps = @@ -117,4 +128,6 @@ export const UseNetTile = ({ className }: UseNetTileProps) => { ); -}; +} + +export default definition; diff --git a/src/widgets/weather/WeatherTile.tsx b/src/widgets/weather/WeatherTile.tsx index 342a8d1d4..c768c5705 100644 --- a/src/widgets/weather/WeatherTile.tsx +++ b/src/widgets/weather/WeatherTile.tsx @@ -1,22 +1,37 @@ 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 { WidgetsMenu } from '../../components/Dashboard/Tiles/Widgets/WidgetsMenu'; 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 { 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; + interface WeatherTileProps extends BaseTileProps { - module: WeatherIntegrationType; // TODO: change to new type defined through widgetDefinition + module: IWeatherWidget; } -export const WeatherTile = ({ className, module }: WeatherTileProps) => { - const { - data: weather, - isLoading, - isError, - } = useWeatherForCity(module?.properties.location ?? 'Paris'); +function WeatherTile({ className, module }: WeatherTileProps) { + const { data: weather, isLoading, isError } = useWeatherForCity(module.properties.location); if (isLoading) { return ( @@ -49,7 +64,7 @@ export const WeatherTile = ({ className, module }: WeatherTileProps) => { { {getPerferedUnit( weather!.current_weather.temperature, - module?.properties.isFahrenheit + module.properties.displayInFahrenheit )} @@ -70,7 +85,7 @@ export const WeatherTile = ({ className, module }: WeatherTileProps) => { {getPerferedUnit( weather!.daily.temperature_2m_max[0], - module?.properties.isFahrenheit + module.properties.displayInFahrenheit )} @@ -79,7 +94,7 @@ export const WeatherTile = ({ className, module }: WeatherTileProps) => { {getPerferedUnit( weather!.daily.temperature_2m_min[0], - module?.properties.isFahrenheit + module.properties.displayInFahrenheit )} @@ -90,7 +105,9 @@ export const WeatherTile = ({ className, module }: WeatherTileProps) => { ); -}; +} const getPerferedUnit = (value: number, isFahrenheit = false): string => isFahrenheit ? `${(value * (9 / 5) + 32).toFixed(1)}°F` : `${value.toFixed(1)}°C`; + +export default definition; diff --git a/src/widgets/widgets.d.ts b/src/widgets/widgets.d.ts new file mode 100644 index 000000000..a02f7bf1a --- /dev/null +++ b/src/widgets/widgets.d.ts @@ -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 = { + 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 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 = { + id: TKey; + icon: TablerIcon | string; + options: { + [key: string]: IWidgetOptionValue; + }; + component: React.ComponentType; +};