mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-09 23:15:46 +01:00
✨ Migrate integrations to widgets
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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}` +
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
10
src/widgets/helper.ts
Normal 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;
|
||||||
|
};
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
70
src/widgets/widgets.d.ts
vendored
Normal 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>;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user