mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-10 23:45:48 +01:00
Merge branch 'gridstack' of https://github.com/manuel-rw/homarr into gridstack
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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 (
|
||||
<Group align="top" h="100%">
|
||||
{layoutSettings?.enabledLeftSidebar && showSidebars ? (
|
||||
{layoutSettings?.enabledLeftSidebar && !doNotShowSidebar ? (
|
||||
<DashboardSidebar location="left" />
|
||||
) : null}
|
||||
<Stack mx={-10} style={{ flexGrow: 1 }}>
|
||||
@@ -27,7 +28,7 @@ export const DashboardView = () => {
|
||||
)
|
||||
)}
|
||||
</Stack>
|
||||
{layoutSettings?.enabledRightSidebar && showSidebars ? (
|
||||
{layoutSettings?.enabledRightSidebar && !doNotShowSidebar ? (
|
||||
<DashboardSidebar location="right" />
|
||||
) : null}
|
||||
</Group>
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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 { 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<typeof definition['id'], typeof definition>;
|
||||
|
||||
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 (
|
||||
<HomarrCardWrapper className={className} p={6}>
|
||||
<Calendar
|
||||
@@ -44,7 +58,7 @@ export const CalendarTile = ({ className, module }: CalendarTileProps) => {
|
||||
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) => {
|
||||
/>
|
||||
</HomarrCardWrapper>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const useStyles = createStyles((theme, secondaryColor: keyof MantineThemeColors) => ({
|
||||
weekend: {
|
||||
@@ -99,3 +113,5 @@ const getReleasedMediasForDate = (medias: MediasType | undefined, date: Date): M
|
||||
totalCount,
|
||||
};
|
||||
};
|
||||
|
||||
export default definition;
|
||||
|
||||
@@ -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<typeof definition['id'], typeof definition>;
|
||||
|
||||
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) => {
|
||||
<WidgetsMenu<'clock'>
|
||||
integration="clock"
|
||||
module={module}
|
||||
options={module?.properties}
|
||||
options={module.properties}
|
||||
labels={{ is24HoursFormat: 'descriptor.settings.display24HourFormat.label' }}
|
||||
/>
|
||||
<Center style={{ height: '100%' }}>
|
||||
@@ -32,7 +48,7 @@ export const ClockTile = ({ className, module }: ClockTileProps) => {
|
||||
</Center>
|
||||
</HomarrCardWrapper>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* State which updates when the minute is changing
|
||||
@@ -69,3 +85,5 @@ const getMsUntilNextMinute = () => {
|
||||
);
|
||||
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 { 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}` +
|
||||
|
||||
@@ -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<typeof definition['id'], typeof definition>;
|
||||
|
||||
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) => {
|
||||
)}
|
||||
</HomarrCardWrapper>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const useDashDotInfo = () => {
|
||||
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 {
|
||||
id: DashDotGraphType;
|
||||
id: string;
|
||||
name: string;
|
||||
twoSpan: boolean;
|
||||
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
|
||||
|
||||
@@ -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<typeof definition['id'], typeof definition>;
|
||||
|
||||
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) => {
|
||||
</Tabs>
|
||||
</HomarrCardWrapper>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default definition;
|
||||
|
||||
@@ -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<typeof definition['id'], typeof definition>;
|
||||
|
||||
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) => {
|
||||
<WidgetsMenu
|
||||
integration="weather"
|
||||
module={module}
|
||||
options={module?.properties}
|
||||
options={module.properties}
|
||||
labels={{
|
||||
isFahrenheit: 'descriptor.settings.displayInFahrenheit.label',
|
||||
location: 'descriptor.settings.location.label',
|
||||
@@ -62,7 +77,7 @@ export const WeatherTile = ({ className, module }: WeatherTileProps) => {
|
||||
<Title order={2}>
|
||||
{getPerferedUnit(
|
||||
weather!.current_weather.temperature,
|
||||
module?.properties.isFahrenheit
|
||||
module.properties.displayInFahrenheit
|
||||
)}
|
||||
</Title>
|
||||
<Group spacing="xs" noWrap>
|
||||
@@ -70,7 +85,7 @@ export const WeatherTile = ({ className, module }: WeatherTileProps) => {
|
||||
<span>
|
||||
{getPerferedUnit(
|
||||
weather!.daily.temperature_2m_max[0],
|
||||
module?.properties.isFahrenheit
|
||||
module.properties.displayInFahrenheit
|
||||
)}
|
||||
</span>
|
||||
<IconArrowUpRight size={16} style={{ right: 15 }} />
|
||||
@@ -79,7 +94,7 @@ export const WeatherTile = ({ className, module }: WeatherTileProps) => {
|
||||
<span>
|
||||
{getPerferedUnit(
|
||||
weather!.daily.temperature_2m_min[0],
|
||||
module?.properties.isFahrenheit
|
||||
module.properties.displayInFahrenheit
|
||||
)}
|
||||
</span>
|
||||
<IconArrowDownRight size={16} />
|
||||
@@ -90,7 +105,9 @@ export const WeatherTile = ({ className, module }: WeatherTileProps) => {
|
||||
</Center>
|
||||
</HomarrCardWrapper>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const getPerferedUnit = (value: number, isFahrenheit = false): string =>
|
||||
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