💄 Prettier codebase

This commit is contained in:
ajnart
2023-10-25 15:29:45 +02:00
parent 01ab01d159
commit 5ab0e5207b
99 changed files with 375 additions and 358 deletions

View File

@@ -7,7 +7,11 @@ export const AccessCustomization = () => {
const form = useBoardCustomizationFormContext(); const form = useBoardCustomizationFormContext();
return ( return (
<Stack> <Stack>
<Switch label={t('allowGuests.label')} description={t('allowGuests.description')} {...form.getInputProps('access.allowGuests', { type: 'checkbox' })} /> <Switch
label={t('allowGuests.label')}
description={t('allowGuests.description')}
{...form.getInputProps('access.allowGuests', { type: 'checkbox' })}
/>
</Stack> </Stack>
) );
} };

View File

@@ -1,9 +1,9 @@
import { ActionIcon, Space, createStyles } from '@mantine/core'; import { ActionIcon, Space, createStyles } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks'; import { useDisclosure } from '@mantine/hooks';
import { IconChevronLeft, IconChevronRight } from '@tabler/icons-react'; import { IconChevronLeft, IconChevronRight } from '@tabler/icons-react';
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
import { useScreenLargerThan } from '~/hooks/useScreenLargerThan'; import { useScreenLargerThan } from '~/hooks/useScreenLargerThan';
import { MobileRibbonSidebarDrawer } from './MobileRibbonSidebarDrawer'; import { MobileRibbonSidebarDrawer } from './MobileRibbonSidebarDrawer';
export const MobileRibbons = () => { export const MobileRibbons = () => {

View File

@@ -1,9 +1,9 @@
import { SelectItem } from '@mantine/core'; import { SelectItem } from '@mantine/core';
import { ContextModalProps, closeModal } from '@mantine/modals'; import { ContextModalProps, closeModal } from '@mantine/modals';
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
import { useConfigStore } from '~/config/store'; import { useConfigStore } from '~/config/store';
import { AppType } from '~/types/app'; import { AppType } from '~/types/app';
import { useGridstackStore, useWrapperColumnCount } from '../../Wrappers/gridstack/store'; import { useGridstackStore, useWrapperColumnCount } from '../../Wrappers/gridstack/store';
import { ChangePositionModal } from './ChangePositionModal'; import { ChangePositionModal } from './ChangePositionModal';

View File

@@ -1,7 +1,6 @@
import { Button, Flex, Grid, NumberInput, Select, SelectItem } from '@mantine/core'; import { Button, Flex, Grid, NumberInput, Select, SelectItem } from '@mantine/core';
import { useForm } from '@mantine/form'; import { useForm } from '@mantine/form';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
interface ChangePositionModalProps { interface ChangePositionModalProps {

View File

@@ -1,8 +1,8 @@
import { SelectItem } from '@mantine/core'; import { SelectItem } from '@mantine/core';
import { ContextModalProps, closeModal } from '@mantine/modals'; import { ContextModalProps, closeModal } from '@mantine/modals';
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
import { useConfigStore } from '~/config/store'; import { useConfigStore } from '~/config/store';
import widgets from '../../../../widgets'; import widgets from '../../../../widgets';
import { WidgetChangePositionModalInnerProps } from '../../Tiles/Widgets/WidgetsMenu'; import { WidgetChangePositionModalInnerProps } from '../../Tiles/Widgets/WidgetsMenu';
import { useGridstackStore, useWrapperColumnCount } from '../../Wrappers/gridstack/store'; import { useGridstackStore, useWrapperColumnCount } from '../../Wrappers/gridstack/store';

View File

@@ -12,10 +12,10 @@ import {
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useState } from 'react'; import { useState } from 'react';
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
import { useConfigStore } from '~/config/store'; import { useConfigStore } from '~/config/store';
import { AppType } from '~/types/app'; import { AppType } from '~/types/app';
import { DebouncedImage } from '../../../IconSelector/DebouncedImage'; import { DebouncedImage } from '../../../IconSelector/DebouncedImage';
import { useEditModeStore } from '../../Views/useEditModeStore'; import { useEditModeStore } from '../../Views/useEditModeStore';
import { AppearanceTab } from './Tabs/AppereanceTab/AppereanceTab'; import { AppearanceTab } from './Tabs/AppereanceTab/AppereanceTab';

View File

@@ -3,9 +3,8 @@ import { UseFormReturnType } from '@mantine/form';
import { useDebouncedValue } from '@mantine/hooks'; import { useDebouncedValue } from '@mantine/hooks';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { AppType } from '~/types/app';
import { IconSelector } from '~/components/IconSelector/IconSelector'; import { IconSelector } from '~/components/IconSelector/IconSelector';
import { AppType } from '~/types/app';
interface AppearanceTabProps { interface AppearanceTabProps {
form: UseFormReturnType<AppType, (values: AppType) => AppType>; form: UseFormReturnType<AppType, (values: AppType) => AppType>;
@@ -83,7 +82,8 @@ export const AppearanceTab = ({
data={[ data={[
{ {
value: 'column', value: 'column',
label: t('appearance.positionAppName.dropdown.top') as string }, label: t('appearance.positionAppName.dropdown.top') as string,
},
{ {
value: 'row-reverse', value: 'row-reverse',
label: t('appearance.positionAppName.dropdown.right') as string, label: t('appearance.positionAppName.dropdown.right') as string,
@@ -94,7 +94,8 @@ export const AppearanceTab = ({
}, },
{ {
value: 'row', value: 'row',
label: t('appearance.positionAppName.dropdown.left') as string }, label: t('appearance.positionAppName.dropdown.left') as string,
},
]} ]}
{...form.getInputProps('appearance.positionAppName')} {...form.getInputProps('appearance.positionAppName')}
onChange={(value) => { onChange={(value) => {

View File

@@ -1,9 +1,18 @@
import { Text, TextInput, Tooltip, Stack, Switch, Tabs, Group, useMantineTheme, HoverCard } from '@mantine/core'; import {
Group,
HoverCard,
Stack,
Switch,
Tabs,
Text,
TextInput,
Tooltip,
useMantineTheme,
} from '@mantine/core';
import { UseFormReturnType } from '@mantine/form'; import { UseFormReturnType } from '@mantine/form';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { InfoCard } from '~/components/InfoCard/InfoCard';
import { AppType } from '~/types/app'; import { AppType } from '~/types/app';
import { InfoCard } from '~/components/InfoCard/InfoCard'
interface BehaviourTabProps { interface BehaviourTabProps {
form: UseFormReturnType<AppType, (values: AppType) => AppType>; form: UseFormReturnType<AppType, (values: AppType) => AppType>;
@@ -19,7 +28,7 @@ export const BehaviourTab = ({ form }: BehaviourTabProps) => {
<Switch <Switch
label={t('behaviour.isOpeningNewTab.label')} label={t('behaviour.isOpeningNewTab.label')}
description={t('behaviour.isOpeningNewTab.description')} description={t('behaviour.isOpeningNewTab.description')}
styles={{ label: { fontWeight: 500, }, description: { marginTop: 0, }, }} styles={{ label: { fontWeight: 500 }, description: { marginTop: 0 } }}
{...form.getInputProps('behaviour.isOpeningNewTab', { type: 'checkbox' })} {...form.getInputProps('behaviour.isOpeningNewTab', { type: 'checkbox' })}
/> />
<Stack spacing="0.25rem"> <Stack spacing="0.25rem">
@@ -27,13 +36,11 @@ export const BehaviourTab = ({ form }: BehaviourTabProps) => {
<Text size="0.875rem" weight={500}> <Text size="0.875rem" weight={500}>
{t('behaviour.tooltipDescription.label')} {t('behaviour.tooltipDescription.label')}
</Text> </Text>
<InfoCard message={t('behaviour.tooltipDescription.description')}/> <InfoCard message={t('behaviour.tooltipDescription.description')} />
</Group> </Group>
<TextInput <TextInput {...form.getInputProps('behaviour.tooltipDescription')} />
{...form.getInputProps('behaviour.tooltipDescription')}
/>
</Stack> </Stack>
</Stack> </Stack>
</Tabs.Panel> </Tabs.Panel>
); );
}; };

View File

@@ -2,8 +2,8 @@ import { Stack, Tabs, Text, TextInput } from '@mantine/core';
import { UseFormReturnType } from '@mantine/form'; import { UseFormReturnType } from '@mantine/form';
import { IconClick, IconCursorText, IconLink } from '@tabler/icons-react'; import { IconClick, IconCursorText, IconLink } from '@tabler/icons-react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { AppType } from '~/types/app'; import { AppType } from '~/types/app';
import { EditAppModalTab } from '../type'; import { EditAppModalTab } from '../type';
interface GeneralTabProps { interface GeneralTabProps {
@@ -51,7 +51,7 @@ export const GeneralTab = ({ form, openTab }: GeneralTabProps) => {
<Text color="red" mt="sm" size="sm"> <Text color="red" mt="sm" size="sm">
{t('behaviour.customProtocolWarning')} {t('behaviour.customProtocolWarning')}
</Text> </Text>
)} )}
</Stack> </Stack>
</Tabs.Panel> </Tabs.Panel>
); );

View File

@@ -15,7 +15,6 @@ import {
import { Icon } from '@tabler/icons-react'; import { Icon } from '@tabler/icons-react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useState } from 'react'; import { useState } from 'react';
import { AppIntegrationPropertyAccessabilityType } from '~/types/app'; import { AppIntegrationPropertyAccessabilityType } from '~/types/app';
interface GenericSecretInputProps { interface GenericSecretInputProps {

View File

@@ -3,7 +3,6 @@ import { Group, Image, Select, SelectItem, Text } from '@mantine/core';
import { UseFormReturnType } from '@mantine/form'; import { UseFormReturnType } from '@mantine/form';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { forwardRef } from 'react'; import { forwardRef } from 'react';
import { import {
AppIntegrationPropertyType, AppIntegrationPropertyType,
AppIntegrationType, AppIntegrationType,

View File

@@ -1,7 +1,6 @@
import { Stack } from '@mantine/core'; import { Stack } from '@mantine/core';
import { UseFormReturnType } from '@mantine/form'; import { UseFormReturnType } from '@mantine/form';
import { IconKey } from '@tabler/icons-react'; import { IconKey } from '@tabler/icons-react';
import { import {
AppIntegrationPropertyType, AppIntegrationPropertyType,
AppType, AppType,
@@ -9,6 +8,7 @@ import {
integrationFieldDefinitions, integrationFieldDefinitions,
integrationFieldProperties, integrationFieldProperties,
} from '~/types/app'; } from '~/types/app';
import { GenericSecretInput } from '../InputElements/GenericSecretInput'; import { GenericSecretInput } from '../InputElements/GenericSecretInput';
interface IntegrationOptionsRendererProps { interface IntegrationOptionsRendererProps {

View File

@@ -2,8 +2,8 @@ import { Alert, Divider, Tabs, Text } from '@mantine/core';
import { UseFormReturnType } from '@mantine/form'; import { UseFormReturnType } from '@mantine/form';
import { IconAlertTriangle } from '@tabler/icons-react'; import { IconAlertTriangle } from '@tabler/icons-react';
import { Trans, useTranslation } from 'next-i18next'; import { Trans, useTranslation } from 'next-i18next';
import { AppType } from '~/types/app'; import { AppType } from '~/types/app';
import { IntegrationSelector } from './Components/InputElements/IntegrationSelector'; import { IntegrationSelector } from './Components/InputElements/IntegrationSelector';
import { IntegrationOptionsRenderer } from './Components/IntegrationOptionsRenderer/IntegrationOptionsRenderer'; import { IntegrationOptionsRenderer } from './Components/IntegrationOptionsRenderer/IntegrationOptionsRenderer';

View File

@@ -1,7 +1,6 @@
import { MultiSelect, Stack, Switch, Tabs } from '@mantine/core'; import { MultiSelect, Stack, Switch, Tabs } from '@mantine/core';
import { UseFormReturnType } from '@mantine/form'; import { UseFormReturnType } from '@mantine/form';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { StatusCodes } from '~/tools/acceptableStatusCodes'; import { StatusCodes } from '~/tools/acceptableStatusCodes';
import { AppType } from '~/types/app'; import { AppType } from '~/types/app';
@@ -20,7 +19,7 @@ export const NetworkTab = ({ form }: NetworkTabProps) => {
<Switch <Switch
label={t('network.statusChecker.label')} label={t('network.statusChecker.label')}
description={t('network.statusChecker.description')} description={t('network.statusChecker.description')}
styles={{ label: { fontWeight: 500, }, description: { marginTop: 0, }, }} styles={{ label: { fontWeight: 500 }, description: { marginTop: 0 } }}
defaultChecked={form.values.network.enabledStatusChecker} defaultChecked={form.values.network.enabledStatusChecker}
{...form.getInputProps('network.enabledStatusChecker')} {...form.getInputProps('network.enabledStatusChecker')}
/> />

View File

@@ -3,10 +3,10 @@ import { showNotification } from '@mantine/notifications';
import { Icon, IconChecks } from '@tabler/icons-react'; import { Icon, IconChecks } from '@tabler/icons-react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
import { useConfigStore } from '~/config/store'; import { useConfigStore } from '~/config/store';
import { IWidget, IWidgetDefinition } from '~/widgets/widgets'; import { IWidget, IWidgetDefinition } from '~/widgets/widgets';
import { useEditModeStore } from '../../../../Views/useEditModeStore'; import { useEditModeStore } from '../../../../Views/useEditModeStore';
import { GenericAvailableElementType } from '../Shared/GenericElementType'; import { GenericAvailableElementType } from '../Shared/GenericElementType';

View File

@@ -2,6 +2,7 @@ import { useConfigContext } from '~/config/provider';
import { useConfigStore } from '~/config/store'; import { useConfigStore } from '~/config/store';
import { openContextModalGeneric } from '~/tools/mantineModalManagerExtensions'; import { openContextModalGeneric } from '~/tools/mantineModalManagerExtensions';
import { AppType } from '~/types/app'; import { AppType } from '~/types/app';
import { GenericTileMenu } from '../GenericTileMenu'; import { GenericTileMenu } from '../GenericTileMenu';
interface TileMenuProps { interface TileMenuProps {

View File

@@ -4,10 +4,9 @@ import Consola from 'consola';
import { TargetAndTransition, Transition, motion } from 'framer-motion'; import { TargetAndTransition, Transition, motion } from 'framer-motion';
import { useSession } from 'next-auth/react'; import { useSession } from 'next-auth/react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { RouterOutputs, api } from '~/utils/api';
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
import { AppType } from '~/types/app'; import { AppType } from '~/types/app';
import { RouterOutputs, api } from '~/utils/api';
interface AppPingProps { interface AppPingProps {
app: AppType; app: AppType;

View File

@@ -2,8 +2,8 @@ import { Affix, Box, Text, Tooltip, UnstyledButton } from '@mantine/core';
import { createStyles, useMantineTheme } from '@mantine/styles'; import { createStyles, useMantineTheme } from '@mantine/styles';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import Link from 'next/link'; import Link from 'next/link';
import { AppType } from '~/types/app'; import { AppType } from '~/types/app';
import { useEditModeStore } from '../../Views/useEditModeStore'; import { useEditModeStore } from '../../Views/useEditModeStore';
import { HomarrCardWrapper } from '../HomarrCardWrapper'; import { HomarrCardWrapper } from '../HomarrCardWrapper';
import { BaseTileProps } from '../type'; import { BaseTileProps } from '../type';
@@ -111,7 +111,7 @@ const useStyles = createStyles((theme, _params, getRef) => ({
overflow: 'visible', overflow: 'visible',
flexGrow: 5, flexGrow: 5,
}, },
appImage:{ appImage: {
maxHeight: '100%', maxHeight: '100%',
maxWidth: '100%', maxWidth: '100%',
overflow: 'auto', overflow: 'auto',

View File

@@ -1,8 +1,8 @@
import { ActionIcon, Menu } from '@mantine/core'; import { ActionIcon, Menu } from '@mantine/core';
import { IconLayoutKanban, IconPencil, IconSettings, IconTrash } from '@tabler/icons-react'; import { IconLayoutKanban, IconPencil, IconSettings, IconTrash } from '@tabler/icons-react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useColorTheme } from '~/tools/color'; import { useColorTheme } from '~/tools/color';
import { useEditModeStore } from '../Views/useEditModeStore'; import { useEditModeStore } from '../Views/useEditModeStore';
interface GenericTileMenuProps { interface GenericTileMenuProps {
@@ -12,14 +12,12 @@ interface GenericTileMenuProps {
displayEdit: boolean; displayEdit: boolean;
} }
export const GenericTileMenu = ( export const GenericTileMenu = ({
{ handleClickEdit,
handleClickEdit, handleClickChangePosition,
handleClickChangePosition, handleClickDelete,
handleClickDelete, displayEdit,
displayEdit, }: GenericTileMenuProps) => {
}: GenericTileMenuProps
) => {
const { t } = useTranslation('common'); const { t } = useTranslation('common');
const isEditMode = useEditModeStore((x) => x.enabled); const isEditMode = useEditModeStore((x) => x.enabled);

View File

@@ -3,7 +3,6 @@ import { useDisclosure } from '@mantine/hooks';
import { IconChevronDown, IconGripVertical } from '@tabler/icons-react'; import { IconChevronDown, IconGripVertical } from '@tabler/icons-react';
import { Reorder, useDragControls } from 'framer-motion'; import { Reorder, useDragControls } from 'framer-motion';
import { FC, useEffect, useRef } from 'react'; import { FC, useEffect, useRef } from 'react';
import { IDraggableEditableListInputValue } from '~/widgets/widgets'; import { IDraggableEditableListInputValue } from '~/widgets/widgets';
interface DraggableListProps { interface DraggableListProps {

View File

@@ -3,7 +3,6 @@ import { useDisclosure } from '@mantine/hooks';
import { IconChevronDown, IconGripVertical } from '@tabler/icons-react'; import { IconChevronDown, IconGripVertical } from '@tabler/icons-react';
import { Reorder, useDragControls } from 'framer-motion'; import { Reorder, useDragControls } from 'framer-motion';
import { FC, ReactNode, useEffect, useRef } from 'react'; import { FC, ReactNode, useEffect, useRef } from 'react';
import { IDraggableListInputValue } from '~/widgets/widgets'; import { IDraggableListInputValue } from '~/widgets/widgets';
const useStyles = createStyles((theme) => ({ const useStyles = createStyles((theme) => ({

View File

@@ -18,13 +18,13 @@ import { ContextModalProps } from '@mantine/modals';
import { IconAlertTriangle, IconPlaylistX, IconPlus } from '@tabler/icons-react'; import { IconAlertTriangle, IconPlaylistX, IconPlus } from '@tabler/icons-react';
import { Trans, useTranslation } from 'next-i18next'; import { Trans, useTranslation } from 'next-i18next';
import { FC, useState } from 'react'; import { FC, useState } from 'react';
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
import { useConfigStore } from '~/config/store'; import { useConfigStore } from '~/config/store';
import { mapObject } from '~/tools/client/objects'; import { mapObject } from '~/tools/client/objects';
import Widgets from '../../../../widgets';
import type { IDraggableListInputValue, IWidgetOptionValue } from '~/widgets/widgets'; import type { IDraggableListInputValue, IWidgetOptionValue } from '~/widgets/widgets';
import { IWidget } from '~/widgets/widgets'; import { IWidget } from '~/widgets/widgets';
import Widgets from '../../../../widgets';
import { InfoCard } from '../../../InfoCard/InfoCard'; import { InfoCard } from '../../../InfoCard/InfoCard';
import { DraggableList } from './Inputs/DraggableList'; import { DraggableList } from './Inputs/DraggableList';
import { LocationSelection } from './Inputs/LocationSelection'; import { LocationSelection } from './Inputs/LocationSelection';

View File

@@ -1,9 +1,9 @@
import { Title } from '@mantine/core'; import { Title } from '@mantine/core';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { openContextModalGeneric } from '~/tools/mantineModalManagerExtensions'; import { openContextModalGeneric } from '~/tools/mantineModalManagerExtensions';
import WidgetsDefinitions from '../../../../widgets';
import { IWidget } from '~/widgets/widgets'; import { IWidget } from '~/widgets/widgets';
import WidgetsDefinitions from '../../../../widgets';
import { useWrapperColumnCount } from '../../Wrappers/gridstack/store'; import { useWrapperColumnCount } from '../../Wrappers/gridstack/store';
import { GenericTileMenu } from '../GenericTileMenu'; import { GenericTileMenu } from '../GenericTileMenu';
import { WidgetEditModalInnerProps } from './WidgetsEditModal'; import { WidgetEditModalInnerProps } from './WidgetsEditModal';

View File

@@ -1,7 +1,6 @@
import { Button, Group, Stack, Text } from '@mantine/core'; import { Button, Group, Stack, Text } from '@mantine/core';
import { ContextModalProps } from '@mantine/modals'; import { ContextModalProps } from '@mantine/modals';
import { Trans, useTranslation } from 'next-i18next'; import { Trans, useTranslation } from 'next-i18next';
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
import { useConfigStore } from '~/config/store'; import { useConfigStore } from '~/config/store';

View File

@@ -1,11 +1,11 @@
import { Group, Stack } from '@mantine/core'; import { Group, Stack } from '@mantine/core';
import { useEffect, useMemo, useRef } from 'react'; import { useEffect, useMemo, useRef } from 'react';
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
import { useResize } from '~/hooks/use-resize'; import { useResize } from '~/hooks/use-resize';
import { useScreenLargerThan } from '~/hooks/useScreenLargerThan'; import { useScreenLargerThan } from '~/hooks/useScreenLargerThan';
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';
import { DashboardSidebar } from '../Wrappers/Sidebar/Sidebar'; import { DashboardSidebar } from '../Wrappers/Sidebar/Sidebar';
import { DashboardWrapper } from '../Wrappers/Wrapper/Wrapper'; import { DashboardWrapper } from '../Wrappers/Wrapper/Wrapper';

View File

@@ -1,8 +1,8 @@
import { ActionIcon, Button, Text, Tooltip } from '@mantine/core'; import { ActionIcon, Button, Text, Tooltip } from '@mantine/core';
import { IconEdit, IconEditOff } from '@tabler/icons-react'; import { IconEdit, IconEditOff } from '@tabler/icons-react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useScreenLargerThan } from '~/hooks/useScreenLargerThan'; import { useScreenLargerThan } from '~/hooks/useScreenLargerThan';
import { useEditModeStore } from './useEditModeStore'; import { useEditModeStore } from './useEditModeStore';
export const ViewToggleButton = () => { export const ViewToggleButton = () => {

View File

@@ -13,9 +13,9 @@ import { useLocalStorage } from '@mantine/hooks';
import { modals } from '@mantine/modals'; import { modals } from '@mantine/modals';
import { IconDotsVertical, IconShare3 } from '@tabler/icons-react'; import { IconDotsVertical, IconShare3 } from '@tabler/icons-react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
import { CategoryType } from '~/types/category'; import { CategoryType } from '~/types/category';
import { useCardStyles } from '../../../layout/Common/useCardStyles'; import { useCardStyles } from '../../../layout/Common/useCardStyles';
import { useEditModeStore } from '../../Views/useEditModeStore'; import { useEditModeStore } from '../../Views/useEditModeStore';
import { WrapperContent } from '../WrapperContent'; import { WrapperContent } from '../WrapperContent';

View File

@@ -8,11 +8,11 @@ import {
IconTransitionTop, IconTransitionTop,
IconTrash, IconTrash,
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import { useTranslation } from 'next-i18next';
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
import { CategoryType } from '~/types/category'; import { CategoryType } from '~/types/category';
import { useCategoryActions } from './useCategoryActions'; import { useCategoryActions } from './useCategoryActions';
import { useTranslation } from 'next-i18next';
interface CategoryEditMenuProps { interface CategoryEditMenuProps {
category: CategoryType; category: CategoryType;
@@ -22,7 +22,7 @@ export const CategoryEditMenu = ({ category }: CategoryEditMenuProps) => {
const { name: configName } = useConfigContext(); const { name: configName } = useConfigContext();
const { addCategoryAbove, addCategoryBelow, moveCategoryUp, moveCategoryDown, edit, remove } = const { addCategoryAbove, addCategoryBelow, moveCategoryUp, moveCategoryDown, edit, remove } =
useCategoryActions(configName, category); useCategoryActions(configName, category);
const { t } = useTranslation(['layout/common','common']); const { t } = useTranslation(['layout/common', 'common']);
return ( return (
<Menu withinPortal withArrow> <Menu withinPortal withArrow>
@@ -33,28 +33,24 @@ export const CategoryEditMenu = ({ category }: CategoryEditMenuProps) => {
</Menu.Target> </Menu.Target>
<Menu.Dropdown> <Menu.Dropdown>
<Menu.Item icon={<IconEdit size={20} />} onClick={edit}> <Menu.Item icon={<IconEdit size={20} />} onClick={edit}>
{t('common:edit')} {t('common:edit')}
</Menu.Item> </Menu.Item>
<Menu.Item icon={<IconTrash size={20} />} onClick={remove}> <Menu.Item icon={<IconTrash size={20} />} onClick={remove}>
{t('common:remove')} {t('common:remove')}
</Menu.Item> </Menu.Item>
<Menu.Label> <Menu.Label>{t('common:changePosition')}</Menu.Label>
{t('common:changePosition')}
</Menu.Label>
<Menu.Item icon={<IconTransitionTop size={20} />} onClick={moveCategoryUp}> <Menu.Item icon={<IconTransitionTop size={20} />} onClick={moveCategoryUp}>
{t('menu.moveUp')} {t('menu.moveUp')}
</Menu.Item> </Menu.Item>
<Menu.Item icon={<IconTransitionBottom size={20} />} onClick={moveCategoryDown}> <Menu.Item icon={<IconTransitionBottom size={20} />} onClick={moveCategoryDown}>
{t('menu.moveDown')} {t('menu.moveDown')}
</Menu.Item> </Menu.Item>
<Menu.Label> <Menu.Label>{t('menu.addCategory', { location: '' })}</Menu.Label>
{t('menu.addCategory',{location: ''})}
</Menu.Label>
<Menu.Item icon={<IconRowInsertTop size={20} />} onClick={addCategoryAbove}> <Menu.Item icon={<IconRowInsertTop size={20} />} onClick={addCategoryAbove}>
{t('menu.addCategory',{location: t('menu.addAbove')})} {t('menu.addCategory', { location: t('menu.addAbove') })}
</Menu.Item> </Menu.Item>
<Menu.Item icon={<IconRowInsertBottom size={20} />} onClick={addCategoryBelow}> <Menu.Item icon={<IconRowInsertBottom size={20} />} onClick={addCategoryBelow}>
{t('menu.addCategory',{location: t('menu.addBelow')})} {t('menu.addCategory', { location: t('menu.addBelow') })}
</Menu.Item> </Menu.Item>
</Menu.Dropdown> </Menu.Dropdown>
</Menu> </Menu>

View File

@@ -2,7 +2,6 @@ import { Button, Group, TextInput } from '@mantine/core';
import { useForm } from '@mantine/form'; import { useForm } from '@mantine/form';
import { ContextModalProps } from '@mantine/modals'; import { ContextModalProps } from '@mantine/modals';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
import { useConfigStore } from '~/config/store'; import { useConfigStore } from '~/config/store';
import { CategoryType } from '~/types/category'; import { CategoryType } from '~/types/category';

View File

@@ -1,11 +1,11 @@
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { useConfigStore } from '~/config/store'; import { useConfigStore } from '~/config/store';
import { openContextModalGeneric } from '~/tools/mantineModalManagerExtensions'; import { openContextModalGeneric } from '~/tools/mantineModalManagerExtensions';
import { AppType } from '~/types/app'; import { AppType } from '~/types/app';
import { CategoryType } from '~/types/category'; import { CategoryType } from '~/types/category';
import { WrapperType } from '~/types/wrapper'; import { WrapperType } from '~/types/wrapper';
import { IWidget } from '~/widgets/widgets'; import { IWidget } from '~/widgets/widgets';
import { CategoryEditModalInnerProps } from './CategoryEditModal'; import { CategoryEditModalInnerProps } from './CategoryEditModal';
export const useCategoryActions = (configName: string | undefined, category: CategoryType) => { export const useCategoryActions = (configName: string | undefined, category: CategoryType) => {

View File

@@ -1,4 +1,5 @@
import { WrapperType } from '~/types/wrapper'; import { WrapperType } from '~/types/wrapper';
import { useEditModeStore } from '../../Views/useEditModeStore'; import { useEditModeStore } from '../../Views/useEditModeStore';
import { WrapperContent } from '../WrapperContent'; import { WrapperContent } from '../WrapperContent';
import { useGridstack } from '../gridstack/use-gridstack'; import { useGridstack } from '../gridstack/use-gridstack';

View File

@@ -1,10 +1,10 @@
import { GridStack } from 'fily-publish-gridstack'; import { GridStack } from 'fily-publish-gridstack';
import { MutableRefObject, RefObject } from 'react'; import { MutableRefObject, RefObject } from 'react';
import { AppType } from '~/types/app'; import { AppType } from '~/types/app';
import Widgets from '../../../widgets';
import { WidgetWrapper } from '~/widgets/WidgetWrapper'; import { WidgetWrapper } from '~/widgets/WidgetWrapper';
import { IWidget, IWidgetDefinition } from '~/widgets/widgets'; import { IWidget, IWidgetDefinition } from '~/widgets/widgets';
import Widgets from '../../../widgets';
import { appTileDefinition } from '../Tiles/Apps/AppTile'; import { appTileDefinition } from '../Tiles/Apps/AppTile';
import { GridstackTileWrapper } from '../Tiles/TileWrapper'; import { GridstackTileWrapper } from '../Tiles/TileWrapper';
import { useGridstackStore } from './gridstack/store'; import { useGridstackStore } from './gridstack/store';

View File

@@ -1,6 +1,5 @@
import { GridItemHTMLElement, GridStack, GridStackNode } from 'fily-publish-gridstack'; import { GridItemHTMLElement, GridStack, GridStackNode } from 'fily-publish-gridstack';
import { MutableRefObject, RefObject } from 'react'; import { MutableRefObject, RefObject } from 'react';
import { AppType } from '~/types/app'; import { AppType } from '~/types/app';
import { ShapeType } from '~/types/shape'; import { ShapeType } from '~/types/shape';
import { IWidget } from '~/widgets/widgets'; import { IWidget } from '~/widgets/widgets';

View File

@@ -1,5 +1,4 @@
import { createWithEqualityFn } from 'zustand/traditional'; import { createWithEqualityFn } from 'zustand/traditional';
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
import { GridstackBreakpoints } from '~/constants/gridstack-breakpoints'; import { GridstackBreakpoints } from '~/constants/gridstack-breakpoints';

View File

@@ -1,11 +1,11 @@
import { GridStack, GridStackNode } from 'fily-publish-gridstack'; import { GridStack, GridStackNode } from 'fily-publish-gridstack';
import { MutableRefObject, RefObject, createRef, useEffect, useMemo, useRef } from 'react'; import { MutableRefObject, RefObject, createRef, useEffect, useMemo, useRef } from 'react';
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
import { useConfigStore } from '~/config/store'; import { useConfigStore } from '~/config/store';
import { AppType } from '~/types/app'; import { AppType } from '~/types/app';
import { AreaType } from '~/types/area'; import { AreaType } from '~/types/area';
import { IWidget } from '~/widgets/widgets'; import { IWidget } from '~/widgets/widgets';
import { useEditModeStore } from '../../Views/useEditModeStore'; import { useEditModeStore } from '../../Views/useEditModeStore';
import { TileWithUnknownLocation, initializeGridstack } from './init-gridstack'; import { TileWithUnknownLocation, initializeGridstack } from './init-gridstack';
import { useGridstackStore, useWrapperColumnCount } from './store'; import { useGridstackStore, useWrapperColumnCount } from './store';

View File

@@ -15,9 +15,9 @@ import {
import { IconSearch } from '@tabler/icons-react'; import { IconSearch } from '@tabler/icons-react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { forwardRef, useImperativeHandle, useState } from 'react'; import { forwardRef, useImperativeHandle, useState } from 'react';
import { humanFileSize } from '~/tools/humanFileSize';
import { api } from '~/utils/api'; import { api } from '~/utils/api';
import { humanFileSize } from '~/tools/humanFileSize';
import { DebouncedImage } from './DebouncedImage'; import { DebouncedImage } from './DebouncedImage';
export const IconSelector = forwardRef( export const IconSelector = forwardRef(
@@ -83,9 +83,7 @@ export const IconSelector = forwardRef(
} }
icon={<DebouncedImage src={value ?? currentValue} width={20} height={20} />} icon={<DebouncedImage src={value ?? currentValue} width={20} height={20} />}
rightSection={ rightSection={
(value ?? currentValue).length > 0 ? ( (value ?? currentValue).length > 0 ? <CloseButton onClick={() => onChange('')} /> : null
<CloseButton onClick={() => onChange("")} />
) : null
} }
itemComponent={AutoCompleteItem} itemComponent={AutoCompleteItem}
className={classes.textInput} className={classes.textInput}

View File

@@ -23,7 +23,9 @@ interface InfoCardProps {
export const InfoCard = ({ bg, cardProp, message, link, hoverProp, position }: InfoCardProps) => { export const InfoCard = ({ bg, cardProp, message, link, hoverProp, position }: InfoCardProps) => {
const { colorScheme } = useMantineTheme(); const { colorScheme } = useMantineTheme();
const { t } = useTranslation('common'); const { t } = useTranslation('common');
const content = link? message + ` <a href=\"${link}\" target=\"_blank\">${t('seeMore')}</a>` : message; const content = link
? message + ` <a href=\"${link}\" target=\"_blank\">${t('seeMore')}</a>`
: message;
const editor = useEditor({ const editor = useEditor({
content, content,
editable: false, editable: false,

View File

@@ -23,7 +23,12 @@ export const ReviewInputStep = ({ values, prevStep, nextStep }: ReviewInputStepP
const { t } = useTranslation('manage/users/create'); const { t } = useTranslation('manage/users/create');
const utils = api.useContext(); const utils = api.useContext();
const { mutateAsync: createAsync, isLoading, isError, error } = api.user.create.useMutation({ const {
mutateAsync: createAsync,
isLoading,
isError,
error,
} = api.user.create.useMutation({
onSettled: () => { onSettled: () => {
void utils.user.all.invalidate(); void utils.user.all.invalidate();
}, },

View File

@@ -57,8 +57,10 @@ export const StepCreateAccount = ({
Create your administrator account Create your administrator account
</Title> </Title>
<Text> <Text>
Your administrator account <b>must be secure</b>, that's why we have so many rules surrounding it. Your administrator account <b>must be secure</b>, that's why we have so many rules
<br/>Try not to make it adminadmin this time... surrounding it.
<br />
Try not to make it adminadmin this time...
</Text> </Text>
<form onSubmit={form.onSubmit(handleSubmit)}> <form onSubmit={form.onSubmit(handleSubmit)}>
<Stack> <Stack>

View File

@@ -39,7 +39,7 @@ services:
const added = { color: 'green', label: '+' }; const added = { color: 'green', label: '+' };
export const StepUpdatePathMappings = ({ next }: { next: () => void }) => { export const StepUpdatePathMappings = ({ next }: { next: () => void }) => {
const [selectedTab, setSelectedTab] = useState<TabsValue>("standard_docker"); const [selectedTab, setSelectedTab] = useState<TabsValue>('standard_docker');
return ( return (
<OnboardingStepWrapper> <OnboardingStepWrapper>
<Title order={2} align="center" mb="md"> <Title order={2} align="center" mb="md">
@@ -140,7 +140,9 @@ export const StepUpdatePathMappings = ({ next }: { next: () => void }) => {
{dockerComposeCommand} {dockerComposeCommand}
</Prism> </Prism>
</List.Item> </List.Item>
<List.Item>Run <Code>docker compose up</Code>.</List.Item> <List.Item>
Run <Code>docker compose up</Code>.
</List.Item>
<List.Item>Refresh this page and click on "continue"</List.Item> <List.Item>Refresh this page and click on "continue"</List.Item>
</List> </List>
</Tabs.Panel> </Tabs.Panel>

View File

@@ -23,7 +23,6 @@ function getStrength(password: string) {
score += 1; score += 1;
} }
return (score / goal) * 100; return (score / goal) * 100;
} }
export const PasswordRequirements = ({ value }: { value: string }) => { export const PasswordRequirements = ({ value }: { value: string }) => {

View File

@@ -1,23 +1,14 @@
import { import { ActionIcon, ActionIconProps } from '@mantine/core';
ActionIcon,
ActionIconProps,
} from '@mantine/core';
import { useColorScheme } from '~/hooks/use-colorscheme';
import { IconMoonStars, IconSun } from '@tabler/icons-react'; import { IconMoonStars, IconSun } from '@tabler/icons-react';
import { useColorScheme } from '~/hooks/use-colorscheme';
export const ThemeSchemeToggle = (props : Partial<ActionIconProps>) => { export const ThemeSchemeToggle = (props: Partial<ActionIconProps>) => {
const { colorScheme, toggleColorScheme } = useColorScheme(); const { colorScheme, toggleColorScheme } = useColorScheme();
const Icon = colorScheme === 'dark' ? IconSun : IconMoonStars; const Icon = colorScheme === 'dark' ? IconSun : IconMoonStars;
return ( return (
<ActionIcon <ActionIcon size={50} variant="outline" radius="md" onClick={toggleColorScheme} {...props}>
size={50} <Icon size="66%" />
variant="outline"
radius="md"
onClick={toggleColorScheme}
{...props}
>
<Icon size="66%"/>
</ActionIcon> </ActionIcon>
); );
}; };

View File

@@ -17,7 +17,7 @@ export const PolkaElement = ({
<Box <Box
style={{ style={{
transform: `rotate(${rotation}deg)`, transform: `rotate(${rotation}deg)`,
pointerEvents: 'none' pointerEvents: 'none',
}} }}
className="polka" className="polka"
pos="absolute" pos="absolute"

View File

@@ -1,7 +1,7 @@
import { Group, Image, Text } from '@mantine/core'; import { Group, Image, Text } from '@mantine/core';
import { useConfigContext } from '~/config/provider';
import { useScreenLargerThan } from '~/hooks/useScreenLargerThan'; import { useScreenLargerThan } from '~/hooks/useScreenLargerThan';
import { useConfigContext } from '~/config/provider';
import { usePrimaryGradient } from './useGradient'; import { usePrimaryGradient } from './useGradient';
interface LogoProps { interface LogoProps {

View File

@@ -1,5 +1,4 @@
import { createStyles } from '@mantine/core'; import { createStyles } from '@mantine/core';
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
export const useCardStyles = (isCategory: boolean) => { export const useCardStyles = (isCategory: boolean) => {

View File

@@ -1,5 +1,4 @@
import { MantineGradient } from '@mantine/core'; import { MantineGradient } from '@mantine/core';
import { useColorTheme } from '~/tools/color'; import { useColorTheme } from '~/tools/color';
export const usePrimaryGradient = () => { export const usePrimaryGradient = () => {

View File

@@ -1,8 +1,7 @@
import Head from 'next/head'; import Head from 'next/head';
import React from 'react'; import React from 'react';
import { firstUpperCase } from '~/tools/shared/strings';
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
import { firstUpperCase } from '~/tools/shared/strings';
export const BoardHeadOverride = () => { export const BoardHeadOverride = () => {
const { config, name } = useConfigContext(); const { config, name } = useConfigContext();

View File

@@ -7,8 +7,8 @@ import {
Grid, Grid,
Group, Group,
HoverCard, HoverCard,
Kbd,
Image, Image,
Kbd,
Modal, Modal,
Table, Table,
Text, Text,
@@ -31,11 +31,11 @@ import { motion } from 'framer-motion';
import { InitOptions } from 'i18next'; import { InitOptions } from 'i18next';
import { Trans, i18n, useTranslation } from 'next-i18next'; import { Trans, i18n, useTranslation } from 'next-i18next';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
import { useConfigStore } from '~/config/store'; import { useConfigStore } from '~/config/store';
import { usePackageAttributesStore } from '~/tools/client/zustands/usePackageAttributesStore'; import { usePackageAttributesStore } from '~/tools/client/zustands/usePackageAttributesStore';
import { useColorTheme } from '~/tools/color'; import { useColorTheme } from '~/tools/color';
import { usePrimaryGradient } from '../../Common/useGradient'; import { usePrimaryGradient } from '../../Common/useGradient';
import Credits from './Credits'; import Credits from './Credits';
import Tip from './Tip'; import Tip from './Tip';
@@ -75,13 +75,7 @@ export const AboutModal = ({ opened, closeModal, newVersionAvailable }: AboutMod
opened={opened} opened={opened}
title={ title={
<Group spacing="sm"> <Group spacing="sm">
<Image <Image alt="Homarr logo" src="/imgs/logo/logo.png" width={30} height={30} fit="contain" />
alt="Homarr logo"
src="/imgs/logo/logo.png"
width={30}
height={30}
fit="contain"
/>
<Title order={3} variant="gradient" gradient={colorGradiant}> <Title order={3} variant="gradient" gradient={colorGradiant}>
{t('about')} Homarr {t('about')} Homarr
</Title> </Title>
@@ -271,13 +265,17 @@ const useInformationTableItems = (newVersionAvailable?: string): InformationTabl
transition={{ duration: 0.8, ease: 'easeInOut' }} transition={{ duration: 0.8, ease: 'easeInOut' }}
> >
<Badge color="green" variant="filled"> <Badge color="green" variant="filled">
{t('version.new',{ newVersion: newVersionAvailable})} {t('version.new', { newVersion: newVersionAvailable })}
</Badge> </Badge>
</motion.div> </motion.div>
</HoverCard.Target> </HoverCard.Target>
<HoverCard.Dropdown> <HoverCard.Dropdown>
<Text> <Text>
{t('version.dropdown', {currentVersion: attributes.packageVersion}).split('{{newVersion}}')[0]} {
t('version.dropdown', { currentVersion: attributes.packageVersion }).split(
'{{newVersion}}'
)[0]
}
<b> <b>
<Anchor <Anchor
target="_blank" target="_blank"
@@ -286,7 +284,11 @@ const useInformationTableItems = (newVersionAvailable?: string): InformationTabl
{newVersionAvailable} {newVersionAvailable}
</Anchor> </Anchor>
</b> </b>
{t('version.dropdown', {currentVersion: attributes.packageVersion}).split('{{newVersion}}')[1]} {
t('version.dropdown', { currentVersion: attributes.packageVersion }).split(
'{{newVersion}}'
)[1]
}
</Text> </Text>
</HoverCard.Dropdown> </HoverCard.Dropdown>
</HoverCard> </HoverCard>

View File

@@ -1,7 +1,6 @@
import { Anchor, Box, Collapse, Flex, Table, Text } from '@mantine/core'; import { Anchor, Box, Collapse, Flex, Table, Text } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks'; import { useDisclosure } from '@mantine/hooks';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { usePackageAttributesStore } from '~/tools/client/zustands/usePackageAttributesStore'; import { usePackageAttributesStore } from '~/tools/client/zustands/usePackageAttributesStore';
export default function Credits() { export default function Credits() {

View File

@@ -1,6 +1,6 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { ConfigType } from '~/types/config'; import { ConfigType } from '~/types/config';
import { useConfigContext } from './provider'; import { useConfigContext } from './provider';
import { useConfigStore } from './store'; import { useConfigStore } from './store';

View File

@@ -1,8 +1,8 @@
import { ReactNode, createContext, useContext, useEffect, useState } from 'react'; import { ReactNode, createContext, useContext, useEffect, useState } from 'react';
import { shallow } from 'zustand/shallow'; import { shallow } from 'zustand/shallow';
import { useColorTheme } from '~/tools/color'; import { useColorTheme } from '~/tools/color';
import { ConfigType } from '~/types/config'; import { ConfigType } from '~/types/config';
import { useConfigStore } from './store'; import { useConfigStore } from './store';
export type ConfigContextType = { export type ConfigContextType = {

View File

@@ -1,7 +1,6 @@
import { createWithEqualityFn } from 'zustand/traditional'; import { createWithEqualityFn } from 'zustand/traditional';
import { trcpProxyClient } from '~/utils/api';
import { ConfigType } from '~/types/config'; import { ConfigType } from '~/types/config';
import { trcpProxyClient } from '~/utils/api';
export const useConfigStore = createWithEqualityFn<UseConfigStoreType>( export const useConfigStore = createWithEqualityFn<UseConfigStoreType>(
(set, get) => ({ (set, get) => ({

View File

@@ -48,7 +48,7 @@ const shouldRedirectToOnboard = async (): Promise<boolean> => {
const cacheAndGetUserCount = async () => { const cacheAndGetUserCount = async () => {
cachedUserCount = await client.user.count.query(); cachedUserCount = await client.user.count.query();
return cachedUserCount === 0; return cachedUserCount === 0;
} };
if (!env.DATABASE_URL?.startsWith('file:')) { if (!env.DATABASE_URL?.startsWith('file:')) {
return await cacheAndGetUserCount(); return await cacheAndGetUserCount();

View File

@@ -2,9 +2,9 @@ import { Badge, Button, Group, Image, Stack, Text, Title } from '@mantine/core';
import { IconDownload, IconExternalLink, IconPlayerPlay } from '@tabler/icons-react'; import { IconDownload, IconExternalLink, IconPlayerPlay } from '@tabler/icons-react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useState } from 'react'; import { useState } from 'react';
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
import { useColorTheme } from '~/tools/color'; import { useColorTheme } from '~/tools/color';
import { RequestModal } from '../overseerr/RequestModal'; import { RequestModal } from '../overseerr/RequestModal';
import { Result } from '../overseerr/SearchResult'; import { Result } from '../overseerr/SearchResult';

View File

@@ -5,9 +5,9 @@ import Consola from 'consola';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useState } from 'react'; import { useState } from 'react';
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
import { useColorTheme } from '~/tools/color';
import { api } from '~/utils/api'; import { api } from '~/utils/api';
import { useColorTheme } from '~/tools/color';
import { MovieResult } from './Movie.d'; import { MovieResult } from './Movie.d';
import { Result } from './SearchResult'; import { Result } from './SearchResult';
import { TvShowResult, TvShowResultSeason } from './TvShow.d'; import { TvShowResult, TvShowResultSeason } from './TvShow.d';

View File

@@ -5,9 +5,8 @@ import Head from 'next/head';
import Image from 'next/image'; import Image from 'next/image';
import Link from 'next/link'; import Link from 'next/link';
import pageNotFoundImage from '~/images/undraw_page_not_found_re_e9o6.svg'; import pageNotFoundImage from '~/images/undraw_page_not_found_re_e9o6.svg';
import { pageNotFoundNamespaces } from '~/tools/server/translation-namespaces';
import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations'; import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations';
import { pageNotFoundNamespaces } from '~/tools/server/translation-namespaces';
export default function Custom404() { export default function Custom404() {
const { classes } = useStyles(); const { classes } = useStyles();

View File

@@ -2,11 +2,11 @@ import {
Box, Box,
Card, Card,
Group, Group,
Image,
SimpleGrid, SimpleGrid,
Stack, Stack,
Text, Text,
Title, Title,
Image,
UnstyledButton, UnstyledButton,
createStyles, createStyles,
} from '@mantine/core'; } from '@mantine/core';

View File

@@ -10,54 +10,60 @@ import { AppType } from '~/types/app';
import { createTRPCRouter, publicProcedure } from '../trpc'; import { createTRPCRouter, publicProcedure } from '../trpc';
export const appRouter = createTRPCRouter({ export const appRouter = createTRPCRouter({
ping: publicProcedure.input(z.object({ ping: publicProcedure
id: z.string(), .input(
configName: z.string() z.object({
})).query(async ({ input }) => { id: z.string(),
const agent = new https.Agent({ rejectUnauthorized: false }); configName: z.string(),
const config = getConfig(input.configName); })
const app = config.apps.find((app) => app.id === input.id); )
.query(async ({ input }) => {
const agent = new https.Agent({ rejectUnauthorized: false });
const config = getConfig(input.configName);
const app = config.apps.find((app) => app.id === input.id);
if (!app?.url) { if (!app?.url) {
Consola.error(`App ${input} not found`); Consola.error(`App ${input} not found`);
throw new TRPCError({
code: 'NOT_FOUND',
cause: input,
message: `App ${input.id} was not found`,
});
}
const res = await axios
.get(app.url, { httpsAgent: agent, timeout: 10000 })
.then((response) => ({
status: response.status,
statusText: response.statusText,
state: isStatusOk(app as AppType, response.status) ? 'online' : 'offline'
}))
.catch((error: AxiosError) => {
if (error.response) {
return {
state: isStatusOk(app as AppType, error.response.status) ? 'online' : 'offline',
status: error.response.status,
statusText: error.response.statusText,
};
}
if (error.code === 'ECONNABORTED') {
Consola.error(`Ping timed out for app with id '${input.id}' in config '${input.configName}' -> url: ${app.url})`);
throw new TRPCError({
code: 'TIMEOUT',
cause: input,
message: `Ping timed out`,
});
}
Consola.error(`Unexpected response: ${error.message}`);
throw new TRPCError({ throw new TRPCError({
code: 'UNPROCESSABLE_CONTENT', code: 'NOT_FOUND',
cause: input, cause: input,
message: `Unexpected response: ${error.message}`, message: `App ${input.id} was not found`,
}); });
}); }
return res; const res = await axios
}), .get(app.url, { httpsAgent: agent, timeout: 10000 })
.then((response) => ({
status: response.status,
statusText: response.statusText,
state: isStatusOk(app as AppType, response.status) ? 'online' : 'offline',
}))
.catch((error: AxiosError) => {
if (error.response) {
return {
state: isStatusOk(app as AppType, error.response.status) ? 'online' : 'offline',
status: error.response.status,
statusText: error.response.statusText,
};
}
if (error.code === 'ECONNABORTED') {
Consola.error(
`Ping timed out for app with id '${input.id}' in config '${input.configName}' -> url: ${app.url})`
);
throw new TRPCError({
code: 'TIMEOUT',
cause: input,
message: `Ping timed out`,
});
}
Consola.error(`Unexpected response: ${error.message}`);
throw new TRPCError({
code: 'UNPROCESSABLE_CONTENT',
cause: input,
message: `Unexpected response: ${error.message}`,
});
});
return res;
}),
}); });

View File

@@ -1,13 +1,13 @@
import Consola from 'consola'; import Consola from 'consola';
import { removeTrailingSlash } from 'next/dist/shared/lib/router/utils/remove-trailing-slash';
import { z } from 'zod'; import { z } from 'zod';
import { checkIntegrationsType } from '~/tools/client/app-properties'; import { checkIntegrationsType } from '~/tools/client/app-properties';
import { getConfig } from '~/tools/config/getConfig'; import { getConfig } from '~/tools/config/getConfig';
import { MediaRequestListWidget } from '~/widgets/media-requests/MediaRequestListTile'; import { MediaRequestListWidget } from '~/widgets/media-requests/MediaRequestListTile';
import { MediaRequestStatsWidget } from '~/widgets/media-requests/MediaRequestStatsTile';
import { MediaRequest, Users } from '~/widgets/media-requests/media-request-types'; import { MediaRequest, Users } from '~/widgets/media-requests/media-request-types';
import { createTRPCRouter, publicProcedure } from '../trpc'; import { createTRPCRouter, publicProcedure } from '../trpc';
import { MediaRequestStatsWidget } from '~/widgets/media-requests/MediaRequestStatsTile';
import { removeTrailingSlash } from 'next/dist/shared/lib/router/utils/remove-trailing-slash';
export const mediaRequestsRouter = createTRPCRouter({ export const mediaRequestsRouter = createTRPCRouter({
allMedia: publicProcedure allMedia: publicProcedure
@@ -33,9 +33,11 @@ export const mediaRequestsRouter = createTRPCRouter({
}) })
.then(async (response) => { .then(async (response) => {
const body = (await response.json()) as OverseerrResponse; const body = (await response.json()) as OverseerrResponse;
let appUrl = input.widget.properties.replaceLinksWithExternalHost && app.behaviour.externalUrl?.length > 0 let appUrl =
? app.behaviour.externalUrl input.widget.properties.replaceLinksWithExternalHost &&
: app.url; app.behaviour.externalUrl?.length > 0
? app.behaviour.externalUrl
: app.url;
appUrl = removeTrailingSlash(appUrl); appUrl = removeTrailingSlash(appUrl);
@@ -163,7 +165,7 @@ const retrieveDetailsForItem = async (
backdropPath: series.backdropPath, backdropPath: series.backdropPath,
posterPath: series.backdropPath, posterPath: series.backdropPath,
}; };
}; }
const movieResponse = await fetch(`${baseUrl}/api/v1/movie/${id}`, { const movieResponse = await fetch(`${baseUrl}/api/v1/movie/${id}`, {
headers, headers,

View File

@@ -2,21 +2,18 @@ import { Jellyfin } from '@jellyfin/sdk';
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models'; import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models';
import { getSessionApi } from '@jellyfin/sdk/lib/utils/api/session-api'; import { getSessionApi } from '@jellyfin/sdk/lib/utils/api/session-api';
import { getSystemApi } from '@jellyfin/sdk/lib/utils/api/system-api'; import { getSystemApi } from '@jellyfin/sdk/lib/utils/api/system-api';
import Consola from 'consola'; import Consola from 'consola';
import { z } from 'zod'; import { z } from 'zod';
import { createTRPCRouter, publicProcedure } from '../trpc';
import { ConfigAppType } from '~/types/app';
import { checkIntegrationsType, findAppProperty } from '~/tools/client/app-properties'; import { checkIntegrationsType, findAppProperty } from '~/tools/client/app-properties';
import { getConfig } from '~/tools/config/getConfig'; import { getConfig } from '~/tools/config/getConfig';
import { PlexClient } from '~/tools/server/sdk/plex/plexClient';
import { trimStringEnding } from '~/tools/shared/strings'; import { trimStringEnding } from '~/tools/shared/strings';
import { GenericMediaServer } from '~/types/api/media-server/media-server'; import { GenericMediaServer } from '~/types/api/media-server/media-server';
import { MediaServersResponseType } from '~/types/api/media-server/response'; import { MediaServersResponseType } from '~/types/api/media-server/response';
import { GenericCurrentlyPlaying, GenericSessionInfo } from '~/types/api/media-server/session-info'; import { GenericCurrentlyPlaying, GenericSessionInfo } from '~/types/api/media-server/session-info';
import { PlexClient } from '~/tools/server/sdk/plex/plexClient'; import { ConfigAppType } from '~/types/app';
import { createTRPCRouter, publicProcedure } from '../trpc';
const jellyfin = new Jellyfin({ const jellyfin = new Jellyfin({
clientInfo: { clientInfo: {
@@ -108,7 +105,13 @@ const handleServer = async (app: ConfigAppType): Promise<GenericMediaServer | un
return { return {
type: 'jellyfin', type: 'jellyfin',
appId: app.id, appId: app.id,
serverAddress: trimStringEnding(app.url, ['/web/index.html#!/home.html', '/web', '/web/index.html', '/web/', '/web/index.html#']), serverAddress: trimStringEnding(app.url, [
'/web/index.html#!/home.html',
'/web',
'/web/index.html',
'/web/',
'/web/index.html#',
]),
version: infoApi.data.Version ?? undefined, version: infoApi.data.Version ?? undefined,
sessions: sessions sessions: sessions
.filter((session) => session.NowPlayingItem) .filter((session) => session.NowPlayingItem)
@@ -180,7 +183,13 @@ const handleServer = async (app: ConfigAppType): Promise<GenericMediaServer | un
if (!apiKey) { if (!apiKey) {
return { return {
serverAddress: trimStringEnding(app.url, ['/web', '/web/index.html', '/web/index.html#!', '/web/index.html#!/settings/web/general', '/web/']), serverAddress: trimStringEnding(app.url, [
'/web',
'/web/index.html',
'/web/index.html#!',
'/web/index.html#!/settings/web/general',
'/web/',
]),
sessions: [], sessions: [],
type: 'plex', type: 'plex',
appId: app.id, appId: app.id,

View File

@@ -70,7 +70,7 @@ export const rssRouter = createTRPCRouter({
} }
if (input.feedUrls.length === 0) { if (input.feedUrls.length === 0) {
return [] return [];
} }
const result = await Promise.all( const result = await Promise.all(

View File

@@ -1,18 +1,19 @@
import { z } from 'zod'; import { z } from 'zod';
const GeoTz = require('browser-geo-tz/dist/geotz.js');
import { createTRPCRouter, publicProcedure } from '../trpc'; import { createTRPCRouter, publicProcedure } from '../trpc';
const GeoTz = require('browser-geo-tz/dist/geotz.js');
export const timezoneRouter = createTRPCRouter({ export const timezoneRouter = createTRPCRouter({
at: publicProcedure at: publicProcedure
.input( .input(
z.object({ z.object({
longitude: z.number(), longitude: z.number(),
latitude: z.number(), latitude: z.number(),
}) })
) )
.query(async ({ input }) => { .query(async ({ input }) => {
const timezone = GeoTz.find(input.latitude,input.longitude); const timezone = GeoTz.find(input.latitude, input.longitude);
return Array.isArray(timezone) ? timezone[0] : timezone; return Array.isArray(timezone) ? timezone[0] : timezone;
}), }),
}) });

View File

@@ -1,7 +1,7 @@
import Consola from 'consola'; import Consola from 'consola';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { BackendConfigType, ConfigType } from '~/types/config'; import { BackendConfigType, ConfigType } from '~/types/config';
import { configExists } from './configExists'; import { configExists } from './configExists';
import { getFallbackConfig } from './getFallbackConfig'; import { getFallbackConfig } from './getFallbackConfig';
import { readConfig } from './readConfig'; import { readConfig } from './readConfig';

View File

@@ -1,4 +1,5 @@
import { ConfigType } from '~/types/config'; import { ConfigType } from '~/types/config';
import defaultConfig from '../../../data/configs/default.json'; import defaultConfig from '../../../data/configs/default.json';
export const getFallbackConfig = (name?: string) => ({ export const getFallbackConfig = (name?: string) => ({
@@ -11,7 +12,7 @@ export const getFallbackConfig = (name?: string) => ({
export const getStaticFallbackConfig = (name: string): ConfigType => ({ export const getStaticFallbackConfig = (name: string): ConfigType => ({
schemaVersion: 1, schemaVersion: 1,
configProperties: { configProperties: {
name: name name: name,
}, },
categories: [ categories: [
{ {
@@ -55,7 +56,7 @@ export const getStaticFallbackConfig = (name: string): ConfigType => ({
}, },
accessibility: { accessibility: {
disablePingPulse: false, disablePingPulse: false,
replacePingDotsWithIcons: false replacePingDotsWithIcons: false,
}, },
pageTitle: 'Homarr ⭐️', pageTitle: 'Homarr ⭐️',
logoImageUrl: '/imgs/logo/logo.png', logoImageUrl: '/imgs/logo/logo.png',

View File

@@ -2,8 +2,8 @@ import Consola from 'consola';
import fs from 'fs'; import fs from 'fs';
import { fetchCity } from '~/server/api/routers/weather'; import { fetchCity } from '~/server/api/routers/weather';
import { IntegrationField } from '~/types/app'; import { IntegrationField } from '~/types/app';
import { BackendConfigType, ConfigType } from '~/types/config'; import { BackendConfigType, ConfigType } from '~/types/config';
import { getConfig } from './getConfig'; import { getConfig } from './getConfig';
export const getFrontendConfig = async (name: string): Promise<ConfigType> => { export const getFrontendConfig = async (name: string): Promise<ConfigType> => {
@@ -139,11 +139,11 @@ const migrateAppConfigs = (config: BackendConfigType) => {
...app, ...app,
appearance: { appearance: {
...app.appearance, ...app.appearance,
appNameStatus: app.appearance.appNameStatus?? 'normal', appNameStatus: app.appearance.appNameStatus ?? 'normal',
positionAppName: app.appearance.positionAppName?? 'column', positionAppName: app.appearance.positionAppName ?? 'column',
appNameFontSize: app.appearance.appNameFontSize?? 16, appNameFontSize: app.appearance.appNameFontSize ?? 16,
lineClampAppName: app.appearance.lineClampAppName?? 1, lineClampAppName: app.appearance.lineClampAppName ?? 1,
} },
})) })),
} };
} };

View File

@@ -1,6 +1,6 @@
import fs from 'fs'; import fs from 'fs';
import { BackendConfigType } from '~/types/config'; import { BackendConfigType } from '~/types/config';
import { generateConfigPath } from './generateConfigPath'; import { generateConfigPath } from './generateConfigPath';
export function writeConfig(config: BackendConfigType) { export function writeConfig(config: BackendConfigType) {

View File

@@ -10,7 +10,7 @@ import { ParsedUrlQuery } from 'querystring';
export const checkForSessionOrAskForLogin = ( export const checkForSessionOrAskForLogin = (
context: GetServerSidePropsContext<ParsedUrlQuery, PreviewData>, context: GetServerSidePropsContext<ParsedUrlQuery, PreviewData>,
session: Session | null, session: Session | null,
accessCallback: () => boolean, accessCallback: () => boolean
): GetServerSidePropsResult<any> | undefined => { ): GetServerSidePropsResult<any> | undefined => {
if (!session?.user) { if (!session?.user) {
console.log('detected logged out user!'); console.log('detected logged out user!');
@@ -26,8 +26,8 @@ export const checkForSessionOrAskForLogin = (
if (!accessCallback()) { if (!accessCallback()) {
return { return {
props: {}, props: {},
notFound: true notFound: true,
} };
} }
return undefined; return undefined;

View File

@@ -2,6 +2,7 @@ import axios from 'axios';
import Consola from 'consola'; import Consola from 'consola';
import { z } from 'zod'; import { z } from 'zod';
import { trimStringEnding } from '~/tools/shared/strings'; import { trimStringEnding } from '~/tools/shared/strings';
import { import {
adGuardApiFilteringStatusSchema, adGuardApiFilteringStatusSchema,
adGuardApiStatsResponseSchema, adGuardApiStatsResponseSchema,

View File

@@ -171,7 +171,7 @@ describe('PiHole API client', () => {
}); });
} }
if (request.url === 'http://pi.hole/admin/api.php?summaryRaw&auth=nice'){ if (request.url === 'http://pi.hole/admin/api.php?summaryRaw&auth=nice') {
countTriedRequests += 1; countTriedRequests += 1;
return JSON.stringify({ return JSON.stringify({
status: 'enabled', status: 'enabled',
@@ -213,9 +213,9 @@ describe('PiHole API client', () => {
}); });
} }
if (request.url === 'http://pi.hole/admin/api.php?summaryRaw&auth=nice'){ if (request.url === 'http://pi.hole/admin/api.php?summaryRaw&auth=nice') {
countTriedRequests += 1; countTriedRequests += 1;
if(countTriedRequests < 10) { if (countTriedRequests < 10) {
return JSON.stringify({ return JSON.stringify({
status: 'disabled', status: 'disabled',
}); });
@@ -261,7 +261,7 @@ describe('PiHole API client', () => {
}); });
} }
if (request.url === 'http://pi.hole/admin/api.php?summaryRaw&auth=nice'){ if (request.url === 'http://pi.hole/admin/api.php?summaryRaw&auth=nice') {
countTriedRequests += 1; countTriedRequests += 1;
return JSON.stringify({ return JSON.stringify({
status: 'disabled', status: 'disabled',
@@ -303,9 +303,9 @@ describe('PiHole API client', () => {
}); });
} }
if (request.url === 'http://pi.hole/admin/api.php?summaryRaw&auth=nice'){ if (request.url === 'http://pi.hole/admin/api.php?summaryRaw&auth=nice') {
countTriedRequests += 1; countTriedRequests += 1;
if(countTriedRequests < 10) { if (countTriedRequests < 10) {
return JSON.stringify({ return JSON.stringify({
status: 'enabled', status: 'enabled',
}); });
@@ -351,7 +351,7 @@ describe('PiHole API client', () => {
}); });
} }
if (request.url === 'http://pi.hole/admin/api.php?summaryRaw&auth=nice'){ if (request.url === 'http://pi.hole/admin/api.php?summaryRaw&auth=nice') {
countTriedRequests += 1; countTriedRequests += 1;
return JSON.stringify({ return JSON.stringify({
status: 'disabled', status: 'disabled',

View File

@@ -1,4 +1,5 @@
import { trimStringEnding } from '~/tools/shared/strings'; import { trimStringEnding } from '~/tools/shared/strings';
import { PiHoleApiStatusChangeResponse, PiHoleApiSummaryResponse } from './piHole.type'; import { PiHoleApiStatusChangeResponse, PiHoleApiSummaryResponse } from './piHole.type';
export class PiHoleClient { export class PiHoleClient {
@@ -62,18 +63,18 @@ export class PiHoleClient {
); );
} }
for(let loops = 0; loops < 10; loops++){ for (let loops = 0; loops < 10; loops++) {
const summary = await this.getSummary() const summary = await this.getSummary();
if (summary.status === action + 'd'){ if (summary.status === action + 'd') {
return { status: summary.status } as PiHoleApiStatusChangeResponse; return { status: summary.status } as PiHoleApiStatusChangeResponse;
} }
await new Promise ((resolve) => { setTimeout(resolve, 50)}); await new Promise((resolve) => {
setTimeout(resolve, 50);
});
} }
return Promise.reject( return Promise.reject(
new Error( new Error(`Although PiHole received the command, it failed to update it's status: ${json}`)
`Although PiHole received the command, it failed to update it's status: ${json}` );
)
)
} }
} }

View File

@@ -1,9 +1,5 @@
import { Element, xml2js } from 'xml-js'; import { Element, xml2js } from 'xml-js';
import { GenericCurrentlyPlaying, GenericSessionInfo } from '~/types/api/media-server/session-info';
import {
GenericCurrentlyPlaying,
GenericSessionInfo,
} from '~/types/api/media-server/session-info';
export class PlexClient { export class PlexClient {
constructor( constructor(
@@ -44,7 +40,6 @@ export class PlexClient {
const { videoCodec, videoFrameRate, audioCodec, audioChannels, height, width, bitrate } = const { videoCodec, videoFrameRate, audioCodec, audioChannels, height, width, bitrate } =
mediaElement; mediaElement;
return { return {
id: sessionElement?.id as string | undefined, id: sessionElement?.id as string | undefined,
username: userElement?.title ?? ('Anonymous' as string), username: userElement?.title ?? ('Anonymous' as string),

View File

@@ -11,7 +11,7 @@ export const generateDefaultApp = (wrapperId: string): AppType =>
appNameStatus: 'normal', appNameStatus: 'normal',
positionAppName: 'column', positionAppName: 'column',
lineClampAppName: 1, lineClampAppName: 1,
appNameFontSize: 16 appNameFontSize: 16,
}, },
network: { network: {
enabledStatusChecker: true, enabledStatusChecker: true,

View File

@@ -1,5 +1,4 @@
import { NormalizedTorrent } from '@ctrl/shared-torrent'; import { NormalizedTorrent } from '@ctrl/shared-torrent';
import { UsenetQueueItem } from '~/widgets/useNet/types'; import { UsenetQueueItem } from '~/widgets/useNet/types';
export type NormalizedDownloadQueueResponse = { export type NormalizedDownloadQueueResponse = {

View File

@@ -1,5 +1,5 @@
import { Icon, IconKey, IconPassword, IconUser } from '@tabler/icons-react'; import { Icon, IconKey, IconPassword, IconUser } from '@tabler/icons-react';
import { Property } from 'csstype' import { Property } from 'csstype';
import { TileBaseType } from './tile'; import { TileBaseType } from './tile';
@@ -34,7 +34,7 @@ interface AppNetworkType {
interface AppAppearanceType { interface AppAppearanceType {
iconUrl: string; iconUrl: string;
appNameStatus: "normal"|"hover"|"hidden"; appNameStatus: 'normal' | 'hover' | 'hidden';
positionAppName: Property.FlexDirection; positionAppName: Property.FlexDirection;
appNameFontSize: number; appNameFontSize: number;
lineClampAppName: number; lineClampAppName: number;

View File

@@ -1,4 +1,5 @@
import { IWidget } from '~/widgets/widgets'; import { IWidget } from '~/widgets/widgets';
import { AppType, ConfigAppType } from './app'; import { AppType, ConfigAppType } from './app';
import { CategoryType } from './category'; import { CategoryType } from './category';
import { SettingsType } from './settings'; import { SettingsType } from './settings';

View File

@@ -1,4 +1,4 @@
import { randomUUID } from "crypto"; import { randomUUID } from 'crypto';
export const fromDate = (seconds: number, date = Date.now()) => { export const fromDate = (seconds: number, date = Date.now()) => {
return new Date(date + seconds * 1000); return new Date(date + seconds * 1000);
@@ -8,4 +8,3 @@ export const fromDate = (seconds: number, date = Date.now()) => {
export const generateSessionToken = () => { export const generateSessionToken = () => {
return randomUUID(); return randomUUID();
}; };

View File

@@ -1,8 +1,8 @@
import { ComponentType } from 'react'; import { ComponentType } from 'react';
import Widgets from '.';
import { HomarrCardWrapper } from '~/components/Dashboard/Tiles/HomarrCardWrapper'; import { HomarrCardWrapper } from '~/components/Dashboard/Tiles/HomarrCardWrapper';
import { WidgetsMenu } from '~/components/Dashboard/Tiles/Widgets/WidgetsMenu'; import { WidgetsMenu } from '~/components/Dashboard/Tiles/Widgets/WidgetsMenu';
import Widgets from '.';
import ErrorBoundary from './boundary'; import ErrorBoundary from './boundary';
import { IWidget } from './widgets'; import { IWidget } from './widgets';

View File

@@ -3,11 +3,11 @@ import {
Box, Box,
Button, Button,
Card, Card,
Divider,
Flex, Flex,
Group, Group,
Image, Image,
ScrollArea, ScrollArea,
Divider,
Stack, Stack,
Switch, Switch,
Text, Text,
@@ -27,12 +27,12 @@ import {
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useEffect } from 'react'; import { useEffect } from 'react';
import React from 'react';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { z } from 'zod'; import { z } from 'zod';
import React from 'react';
import { useEditModeStore } from '~/components/Dashboard/Views/useEditModeStore'; import { useEditModeStore } from '~/components/Dashboard/Views/useEditModeStore';
import { IconSelector } from '~/components/IconSelector/IconSelector'; import { IconSelector } from '~/components/IconSelector/IconSelector';
import { defineWidget } from '../helper'; import { defineWidget } from '../helper';
import { IDraggableEditableListInputValue, IWidget } from '../widgets'; import { IDraggableEditableListInputValue, IWidget } from '../widgets';
@@ -54,7 +54,7 @@ const definition = defineWidget({
type: 'text', type: 'text',
defaultValue: '', defaultValue: '',
info: true, info: true,
infoLink: "https://homarr.dev/docs/widgets/bookmarks/", infoLink: 'https://homarr.dev/docs/widgets/bookmarks/',
}, },
items: { items: {
type: 'draggable-editable-list', type: 'draggable-editable-list',
@@ -84,11 +84,11 @@ const definition = defineWidget({
return undefined; return undefined;
} }
return t('item.validation.length', {shortest: "1", longest: "100"}); return t('item.validation.length', { shortest: '1', longest: '100' });
}, },
href: (value) => { href: (value) => {
if (!z.string().min(1).max(200).safeParse(value).success) { if (!z.string().min(1).max(200).safeParse(value).success) {
return t('item.validation.length', {shortest: "1", longest: "200"}); return t('item.validation.length', { shortest: '1', longest: '200' });
} }
if (!z.string().url().safeParse(value).success) { if (!z.string().url().safeParse(value).success) {
@@ -102,7 +102,7 @@ const definition = defineWidget({
return undefined; return undefined;
} }
return t('item.validation.length', {shortest: "1", longest: "400"}); return t('item.validation.length', { shortest: '1', longest: '400' });
}, },
}, },
validateInputOnChange: true, validateInputOnChange: true,
@@ -174,11 +174,7 @@ const definition = defineWidget({
} satisfies IDraggableEditableListInputValue<BookmarkItem>, } satisfies IDraggableEditableListInputValue<BookmarkItem>,
layout: { layout: {
type: 'select', type: 'select',
data: [ data: [{ value: 'autoGrid' }, { value: 'horizontal' }, { value: 'vertical' }],
{ value: 'autoGrid', },
{ value: 'horizontal', },
{ value: 'vertical', },
],
defaultValue: 'autoGrid', defaultValue: 'autoGrid',
}, },
}, },
@@ -223,10 +219,12 @@ function BookmarkWidgetTile({ widget }: BookmarkWidgetTileProps) {
case 'autoGrid': case 'autoGrid':
return ( return (
<Stack h="100%" spacing={0}> <Stack h="100%" spacing={0}>
<Title size="h4" px="0.25rem">{widget.properties.name}</Title> <Title size="h4" px="0.25rem">
{widget.properties.name}
</Title>
<Box <Box
className={classes.grid} className={classes.grid}
mr={isEditModeEnabled && widget.properties.name === "" ? 'xl' : undefined} mr={isEditModeEnabled && widget.properties.name === '' ? 'xl' : undefined}
h="100%" h="100%"
> >
{widget.properties.items.map((item: BookmarkItem, index) => ( {widget.properties.items.map((item: BookmarkItem, index) => (
@@ -239,10 +237,12 @@ function BookmarkWidgetTile({ widget }: BookmarkWidgetTileProps) {
href={item.href} href={item.href}
target={item.openNewTab ? '_blank' : undefined} target={item.openNewTab ? '_blank' : undefined}
withBorder withBorder
bg={colorScheme === 'dark' ? colors.dark[5].concat('80') : colors.blue[0].concat('80')} bg={
colorScheme === 'dark' ? colors.dark[5].concat('80') : colors.blue[0].concat('80')
}
sx={{ sx={{
'&:hover': { backgroundColor: fn.primaryColor().concat('40'), }, //'40' = 25% opacity '&:hover': { backgroundColor: fn.primaryColor().concat('40') }, //'40' = 25% opacity
flex:'1 1 auto', flex: '1 1 auto',
}} }}
display="flex" display="flex"
> >
@@ -265,29 +265,29 @@ function BookmarkWidgetTile({ widget }: BookmarkWidgetTileProps) {
type="auto" type="auto"
h="100%" h="100%"
offsetScrollbars offsetScrollbars
mr={isEditModeEnabled && widget.properties.name === ""? 'xl' : undefined} mr={isEditModeEnabled && widget.properties.name === '' ? 'xl' : undefined}
styles={{ styles={{
viewport:{ viewport: {
//mantine being mantine again... this might break. Needed for taking 100% of widget space //mantine being mantine again... this might break. Needed for taking 100% of widget space
'& div[style="min-width: 100%; display: table;"]':{ '& div[style="min-width: 100%; display: table;"]': {
display: 'flex !important', display: 'flex !important',
height:'100%', height: '100%',
}, },
}, },
}} }}
> >
<Flex <Flex direction={flexDirection} gap="0" h="100%" w="100%">
direction={flexDirection}
gap="0"
h="100%"
w="100%"
>
{widget.properties.items.map((item: BookmarkItem, index) => ( {widget.properties.items.map((item: BookmarkItem, index) => (
<div key={index} style={{ display: 'flex', flex: '1', flexDirection: flexDirection, }}> <div
key={index}
style={{ display: 'flex', flex: '1', flexDirection: flexDirection }}
>
<Divider <Divider
m="3px" m="3px"
orientation={ widget.properties.layout !== 'vertical' ? 'vertical' : 'horizontal' } orientation={
color={index === 0 ? "transparent" : undefined} widget.properties.layout !== 'vertical' ? 'vertical' : 'horizontal'
}
color={index === 0 ? 'transparent' : undefined}
/> />
<Card <Card
px="md" px="md"
@@ -298,13 +298,13 @@ function BookmarkWidgetTile({ widget }: BookmarkWidgetTileProps) {
radius="md" radius="md"
bg="transparent" bg="transparent"
sx={{ sx={{
'&:hover': { backgroundColor: fn.primaryColor().concat('40'),}, //'40' = 25% opacity '&:hover': { backgroundColor: fn.primaryColor().concat('40') }, //'40' = 25% opacity
flex:'1 1 auto', flex: '1 1 auto',
overflow: 'unset', overflow: 'unset',
}} }}
display="flex" display="flex"
> >
<BookmarkItemContent item={item}/> <BookmarkItemContent item={item} />
</Card> </Card>
</div> </div>
))} ))}
@@ -320,26 +320,28 @@ function BookmarkWidgetTile({ widget }: BookmarkWidgetTileProps) {
const BookmarkItemContent = ({ item }: { item: BookmarkItem }) => { const BookmarkItemContent = ({ item }: { item: BookmarkItem }) => {
const { colorScheme } = useMantineTheme(); const { colorScheme } = useMantineTheme();
return ( return (
<Group spacing="0rem 1rem"> <Group spacing="0rem 1rem">
<Image <Image
hidden={item.hideIcon} hidden={item.hideIcon}
src={item.iconUrl} src={item.iconUrl}
width={47} width={47}
height={47} height={47}
fit="contain" fit="contain"
withPlaceholder /> withPlaceholder
<Stack spacing={0}> />
<Text size="md">{item.name}</Text> <Stack spacing={0}>
<Text <Text size="md">{item.name}</Text>
color={colorScheme === 'dark' ? "gray.6" : "gray.7"} <Text
size="sm" color={colorScheme === 'dark' ? 'gray.6' : 'gray.7'}
hidden={item.hideHostname} size="sm"
> hidden={item.hideHostname}
{new URL(item.href).hostname} >
</Text> {new URL(item.href).hostname}
</Stack> </Text>
</Group> </Stack>
)}; </Group>
);
};
const useStyles = createStyles(() => ({ const useStyles = createStyles(() => ({
grid: { grid: {

View File

@@ -4,8 +4,8 @@ import { IconBrandGithub, IconBug, IconInfoCircle, IconRefresh } from '@tabler/i
import Consola from 'consola'; import Consola from 'consola';
import { withTranslation } from 'next-i18next'; import { withTranslation } from 'next-i18next';
import React, { ReactNode } from 'react'; import React, { ReactNode } from 'react';
import { WidgetsMenu } from '~/components/Dashboard/Tiles/Widgets/WidgetsMenu'; import { WidgetsMenu } from '~/components/Dashboard/Tiles/Widgets/WidgetsMenu';
import { IWidget } from './widgets'; import { IWidget } from './widgets';
type ErrorBoundaryState = { type ErrorBoundaryState = {

View File

@@ -1,13 +1,13 @@
import { Divider, ScrollArea, createStyles } from '@mantine/core'; import { Divider, ScrollArea, createStyles } from '@mantine/core';
import { useViewportSize } from '@mantine/hooks'; import { useViewportSize } from '@mantine/hooks';
import React from 'react'; import React from 'react';
import { import {
LidarrMediaDisplay, LidarrMediaDisplay,
RadarrMediaDisplay, RadarrMediaDisplay,
ReadarrMediaDisplay, ReadarrMediaDisplay,
SonarrMediaDisplay, SonarrMediaDisplay,
} from '~/modules/common'; } from '~/modules/common';
import { MediasType } from './type'; import { MediasType } from './type';
interface MediaListProps { interface MediaListProps {

View File

@@ -1,5 +1,4 @@
import { ColorScheme, useMantineTheme } from '@mantine/core'; import { ColorScheme, useMantineTheme } from '@mantine/core';
import { isToday } from '~/tools/shared/time/date.tool'; import { isToday } from '~/tools/shared/time/date.tool';
export const getBgColorByDateAndTheme = (colorScheme: ColorScheme, date: Date) => { export const getBgColorByDateAndTheme = (colorScheme: ColorScheme, date: Date) => {

View File

@@ -1,9 +1,8 @@
import { Group, Stack, Text } from '@mantine/core'; import { Group, Stack, Text } from '@mantine/core';
import { IconArrowNarrowDown, IconArrowNarrowUp } from '@tabler/icons-react'; import { IconArrowNarrowDown, IconArrowNarrowUp } from '@tabler/icons-react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { RouterOutputs } from '~/utils/api';
import { bytes } from '~/tools/bytesHelper'; import { bytes } from '~/tools/bytesHelper';
import { RouterOutputs } from '~/utils/api';
interface DashDotCompactNetworkProps { interface DashDotCompactNetworkProps {
info: DashDotInfo; info: DashDotInfo;

View File

@@ -1,9 +1,9 @@
import { Group, Stack, Text } from '@mantine/core'; import { Group, Stack, Text } from '@mantine/core';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { api } from '~/utils/api';
import { bytes } from '~/tools/bytesHelper'; import { bytes } from '~/tools/bytesHelper';
import { percentage } from '~/tools/shared/math/percentage.tool'; import { percentage } from '~/tools/shared/math/percentage.tool';
import { api } from '~/utils/api';
import { DashDotInfo } from './DashDotCompactNetwork'; import { DashDotInfo } from './DashDotCompactNetwork';
interface DashDotCompactStorageProps { interface DashDotCompactStorageProps {

View File

@@ -1,6 +1,13 @@
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
import { RouterInputs, api } from '~/utils/api'; import { RouterInputs, api } from '~/utils/api';
import { UsenetHistoryRequestParams, UsenetInfoRequestParams, UsenetPauseRequestParams, UsenetQueueRequestParams, UsenetResumeRequestParams } from '../useNet/types';
import {
UsenetHistoryRequestParams,
UsenetInfoRequestParams,
UsenetPauseRequestParams,
UsenetQueueRequestParams,
UsenetResumeRequestParams,
} from '../useNet/types';
const POLLING_INTERVAL = 2000; const POLLING_INTERVAL = 2000;

View File

@@ -6,10 +6,10 @@ import timezones from 'dayjs/plugin/timezone';
import utc from 'dayjs/plugin/utc'; import utc from 'dayjs/plugin/utc';
import { useSession } from 'next-auth/react'; import { useSession } from 'next-auth/react';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { useSetSafeInterval } from '~/hooks/useSetSafeInterval';
import { getLanguageByCode } from '~/tools/language'; import { getLanguageByCode } from '~/tools/language';
import { api } from '~/utils/api'; import { api } from '~/utils/api';
import { useSetSafeInterval } from '~/hooks/useSetSafeInterval';
import { defineWidget } from '../helper'; import { defineWidget } from '../helper';
import { IWidget } from '../widgets'; import { IWidget } from '../widgets';
@@ -108,8 +108,8 @@ function DateTile({ widget }: DateTileProps) {
); );
} }
const useStyles = createStyles(()=>({ const useStyles = createStyles(() => ({
wrapper:{ wrapper: {
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
justifyContent: 'space-evenly', justifyContent: 'space-evenly',
@@ -117,17 +117,17 @@ const useStyles = createStyles(()=>({
height: '100%', height: '100%',
gap: 0, gap: 0,
}, },
clock:{ clock: {
lineHeight: '1', lineHeight: '1',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
fontWeight: 700, fontWeight: 700,
fontSize: '2.125rem', fontSize: '2.125rem',
}, },
extras:{ extras: {
lineHeight: '1', lineHeight: '1',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
} },
})) }));
/** /**
* State which updates when the minute is changing * State which updates when the minute is changing
@@ -142,7 +142,7 @@ const useDateState = (location?: { latitude: number; longitude: number }) => {
const { data: userWithSettings } = api.user.withSettings.useQuery(undefined, { const { data: userWithSettings } = api.user.withSettings.useQuery(undefined, {
enabled: !!sessionData?.user, enabled: !!sessionData?.user,
}); });
const userLanguage = userWithSettings?.settings.language; const userLanguage = userWithSettings?.settings.language;
const [date, setDate] = useState(getNewDate(timezone)); const [date, setDate] = useState(getNewDate(timezone));
const setSafeInterval = useSetSafeInterval(); const setSafeInterval = useSetSafeInterval();
const timeoutRef = useRef<NodeJS.Timeout>(); // reference for initial timeout until first minute change const timeoutRef = useRef<NodeJS.Timeout>(); // reference for initial timeout until first minute change

View File

@@ -16,9 +16,9 @@ import { useElementSize } from '@mantine/hooks';
import { IconDeviceGamepad, IconPlayerPlay, IconPlayerStop } from '@tabler/icons-react'; import { IconDeviceGamepad, IconPlayerPlay, IconPlayerStop } from '@tabler/icons-react';
import { useSession } from 'next-auth/react'; import { useSession } from 'next-auth/react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useConfigContext } from '~/config/provider';
import { api } from '~/utils/api'; import { api } from '~/utils/api';
import { useConfigContext } from '~/config/provider';
import { defineWidget } from '../helper'; import { defineWidget } from '../helper';
import { WidgetLoading } from '../loading'; import { WidgetLoading } from '../loading';
import { IWidget } from '../widgets'; import { IWidget } from '../widgets';
@@ -84,15 +84,19 @@ function DnsHoleControlsWidgetTile({ widget }: DnsHoleControlsWidgetProps) {
} }
if (data.status.length === 0) { if (data.status.length === 0) {
return( return (
<Center h="100%"> <Center h="100%">
<Stack align="center"> <Stack align="center">
<IconDeviceGamepad size={40} strokeWidth={1}/> <IconDeviceGamepad size={40} strokeWidth={1} />
<Title align="center" order={6}>{t('modules/dns-hole-controls:descriptor.errors.general.title')}</Title> <Title align="center" order={6}>
<Text align="center">{t('modules/dns-hole-controls:descriptor.errors.general.text')}</Text> {t('modules/dns-hole-controls:descriptor.errors.general.title')}
</Title>
<Text align="center">
{t('modules/dns-hole-controls:descriptor.errors.general.text')}
</Text>
</Stack> </Stack>
</Center> </Center>
) );
} }
console.log(data); console.log(data);
@@ -136,15 +140,18 @@ function DnsHoleControlsWidgetTile({ widget }: DnsHoleControlsWidgetProps) {
> >
<Button <Button
onClick={async () => { onClick={async () => {
await mutateAsync({ await mutateAsync(
action: 'enable', {
configName, action: 'enable',
appsToChange: getDnsStatus()?.disabled, configName,
},{ appsToChange: getDnsStatus()?.disabled,
onSettled: () => { },
reFetchSummaryDns(); {
onSettled: () => {
reFetchSummaryDns();
},
} }
}); );
}} }}
disabled={getDnsStatus()?.disabled.length === 0 || fetchingDnsSummary || changingStatus} disabled={getDnsStatus()?.disabled.length === 0 || fetchingDnsSummary || changingStatus}
leftIcon={<IconPlayerPlay size={20} />} leftIcon={<IconPlayerPlay size={20} />}
@@ -156,15 +163,18 @@ function DnsHoleControlsWidgetTile({ widget }: DnsHoleControlsWidgetProps) {
</Button> </Button>
<Button <Button
onClick={async () => { onClick={async () => {
await mutateAsync({ await mutateAsync(
action: 'disable', {
configName, action: 'disable',
appsToChange: getDnsStatus()?.enabled, configName,
},{ appsToChange: getDnsStatus()?.enabled,
onSettled: () => { },
reFetchSummaryDns(); {
onSettled: () => {
reFetchSummaryDns();
},
} }
}); );
}} }}
disabled={getDnsStatus()?.enabled.length === 0 || fetchingDnsSummary || changingStatus} disabled={getDnsStatus()?.enabled.length === 0 || fetchingDnsSummary || changingStatus}
leftIcon={<IconPlayerStop size={20} />} leftIcon={<IconPlayerStop size={20} />}

View File

@@ -10,9 +10,9 @@ import {
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
import { formatNumber, formatPercentage } from '~/tools/client/math';
import { RouterOutputs, api } from '~/utils/api'; import { RouterOutputs, api } from '~/utils/api';
import { formatNumber, formatPercentage } from '~/tools/client/math';
import { defineWidget } from '../helper'; import { defineWidget } from '../helper';
import { WidgetLoading } from '../loading'; import { WidgetLoading } from '../loading';
import { IWidget } from '../widgets'; import { IWidget } from '../widgets';

View File

@@ -15,17 +15,17 @@ import { Datum, ResponsiveLine, Serie } from '@nivo/line';
import { IconDownload, IconUpload } from '@tabler/icons-react'; import { IconDownload, IconUpload } from '@tabler/icons-react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { AppAvatar } from '~/components/AppAvatar'; import { AppAvatar } from '~/components/AppAvatar';
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
import { useGetDownloadClientsQueue } from './useGetNetworkSpeed';
import { useColorTheme } from '~/tools/color'; import { useColorTheme } from '~/tools/color';
import { humanFileSize } from '~/tools/humanFileSize'; import { humanFileSize } from '~/tools/humanFileSize';
import { import {
NormalizedDownloadQueueResponse, NormalizedDownloadQueueResponse,
TorrentTotalDownload, TorrentTotalDownload,
} from '~/types/api/downloads/queue/NormalizedDownloadQueueResponse'; } from '~/types/api/downloads/queue/NormalizedDownloadQueueResponse';
import definition, { ITorrentNetworkTraffic } from './TorrentNetworkTrafficTile'; import definition, { ITorrentNetworkTraffic } from './TorrentNetworkTrafficTile';
import { useGetDownloadClientsQueue } from './useGetNetworkSpeed';
interface TorrentNetworkTrafficTileProps { interface TorrentNetworkTrafficTileProps {
widget: ITorrentNetworkTraffic; widget: ITorrentNetworkTraffic;

View File

@@ -9,12 +9,12 @@ import iframe from './iframe/IFrameTile';
import mediaRequestsList from './media-requests/MediaRequestListTile'; import mediaRequestsList from './media-requests/MediaRequestListTile';
import mediaRequestsStats from './media-requests/MediaRequestStatsTile'; import mediaRequestsStats from './media-requests/MediaRequestStatsTile';
import mediaServer from './media-server/MediaServerTile'; import mediaServer from './media-server/MediaServerTile';
import notebook from './notebook/NotebookWidgetTile';
import rss from './rss/RssWidgetTile'; import rss from './rss/RssWidgetTile';
import torrent from './torrent/TorrentTile'; import torrent from './torrent/TorrentTile';
import usenet from './useNet/UseNetTile'; import usenet from './useNet/UseNetTile';
import videoStream from './video/VideoStreamTile'; import videoStream from './video/VideoStreamTile';
import weather from './weather/WeatherTile'; import weather from './weather/WeatherTile';
import notebook from './notebook/NotebookWidgetTile';
export default { export default {
calendar, calendar,

View File

@@ -57,7 +57,7 @@ function MediaRequestStatsTile({ widget }: MediaRequestStatsWidgetProps) {
const { const {
data: usersData, data: usersData,
isFetching: usersFetching, isFetching: usersFetching,
isLoading: usersLoading isLoading: usersLoading,
} = useUsersQuery(widget); } = useUsersQuery(widget);
const { ref, height } = useElementSize(); const { ref, height } = useElementSize();
const { colorScheme } = useMantineTheme(); const { colorScheme } = useMantineTheme();
@@ -128,7 +128,7 @@ function MediaRequestStatsTile({ widget }: MediaRequestStatsWidgetProps) {
p={0} p={0}
component="a" component="a"
href={user.userLink} href={user.userLink}
target={widget.properties.openInNewTab ? "_blank" : "_self"} target={widget.properties.openInNewTab ? '_blank' : '_self'}
mah={95} mah={95}
mih={55} mih={55}
radius="md" radius="md"

View File

@@ -1,9 +1,10 @@
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
import { MediaRequestListWidget } from './MediaRequestListTile';
import { MediaRequestStatsWidget } from './MediaRequestStatsTile';
import { api } from '~/utils/api'; import { api } from '~/utils/api';
export const useMediaRequestQuery = (widget: MediaRequestListWidget|MediaRequestStatsWidget) => { import { MediaRequestListWidget } from './MediaRequestListTile';
import { MediaRequestStatsWidget } from './MediaRequestStatsTile';
export const useMediaRequestQuery = (widget: MediaRequestListWidget | MediaRequestStatsWidget) => {
const { name: configName } = useConfigContext(); const { name: configName } = useConfigContext();
return api.mediaRequest.allMedia.useQuery( return api.mediaRequest.allMedia.useQuery(
{ configName: configName!, widget: widget }, { configName: configName!, widget: widget },
@@ -13,7 +14,7 @@ export const useMediaRequestQuery = (widget: MediaRequestListWidget|MediaRequest
); );
}; };
export const useUsersQuery = (widget: MediaRequestListWidget|MediaRequestStatsWidget) => { export const useUsersQuery = (widget: MediaRequestListWidget | MediaRequestStatsWidget) => {
const { name: configName } = useConfigContext(); const { name: configName } = useConfigContext();
return api.mediaRequest.users.useQuery( return api.mediaRequest.users.useQuery(
{ configName: configName!, widget: widget }, { configName: configName!, widget: widget },

View File

@@ -11,13 +11,13 @@ import {
} from '@mantine/core'; } from '@mantine/core';
import { IconAlertTriangle, IconMovie } from '@tabler/icons-react'; import { IconAlertTriangle, IconMovie } from '@tabler/icons-react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { AppAvatar } from '~/components/AppAvatar'; import { AppAvatar } from '~/components/AppAvatar';
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
import { useGetMediaServers } from './useGetMediaServers';
import { defineWidget } from '../helper'; import { defineWidget } from '../helper';
import { IWidget } from '../widgets'; import { IWidget } from '../widgets';
import { TableRow } from './TableRow'; import { TableRow } from './TableRow';
import { useGetMediaServers } from './useGetMediaServers';
const definition = defineWidget({ const definition = defineWidget({
id: 'media-server', id: 'media-server',

View File

@@ -6,11 +6,9 @@ import {
IconQuestionMark, IconQuestionMark,
IconVideo, IconVideo,
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import { GenericSessionInfo } from '~/types/api/media-server/session-info'; import { GenericSessionInfo } from '~/types/api/media-server/session-info';
export const NowPlayingDisplay = ({ session }: { session: GenericSessionInfo }) => { export const NowPlayingDisplay = ({ session }: { session: GenericSessionInfo }) => {
if (!session.currentlyPlaying) { if (!session.currentlyPlaying) {
return null; return null;
} }

View File

@@ -18,10 +18,10 @@ import duration from 'dayjs/plugin/duration';
import relativeTime from 'dayjs/plugin/relativeTime'; import relativeTime from 'dayjs/plugin/relativeTime';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useCardStyles } from '~/components/layout/Common/useCardStyles'; import { useCardStyles } from '~/components/layout/Common/useCardStyles';
import { MIN_WIDTH_MOBILE } from '~/constants/constants'; import { MIN_WIDTH_MOBILE } from '~/constants/constants';
import { NormalizedDownloadQueueResponse } from '~/types/api/downloads/queue/NormalizedDownloadQueueResponse'; import { NormalizedDownloadQueueResponse } from '~/types/api/downloads/queue/NormalizedDownloadQueueResponse';
import { AppIntegrationType } from '~/types/app'; import { AppIntegrationType } from '~/types/app';
import { useGetDownloadClientsQueue } from '../download-speed/useGetNetworkSpeed'; import { useGetDownloadClientsQueue } from '../download-speed/useGetNetworkSpeed';
import { defineWidget } from '../helper'; import { defineWidget } from '../helper';
import { IWidget } from '../widgets'; import { IWidget } from '../widgets';

View File

@@ -6,11 +6,11 @@ import duration from 'dayjs/plugin/duration';
import { useSession } from 'next-auth/react'; import { useSession } from 'next-auth/react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
import { MIN_WIDTH_MOBILE } from '~/constants/constants'; import { MIN_WIDTH_MOBILE } from '~/constants/constants';
import { humanFileSize } from '~/tools/humanFileSize'; import { humanFileSize } from '~/tools/humanFileSize';
import { AppIntegrationType } from '~/types/app'; import { AppIntegrationType } from '~/types/app';
import { import {
useGetUsenetInfo, useGetUsenetInfo,
usePauseUsenetQueueMutation, usePauseUsenetQueueMutation,

View File

@@ -22,7 +22,7 @@ interface WeatherIconProps {
* @param code weather code from api * @param code weather code from api
* @returns weather tile component * @returns weather tile component
*/ */
export const WeatherIcon = ({ code, size=50 }: WeatherIconProps) => { export const WeatherIcon = ({ code, size = 50 }: WeatherIconProps) => {
const { t } = useTranslation('modules/weather'); const { t } = useTranslation('modules/weather');
const { icon: Icon, name } = const { icon: Icon, name } =

View File

@@ -6,12 +6,12 @@ import {
IconCloudRain, IconCloudRain,
IconMapPin, IconMapPin,
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import { useTranslation } from 'react-i18next';
import { api } from '~/utils/api'; import { api } from '~/utils/api';
import { defineWidget } from '../helper'; import { defineWidget } from '../helper';
import { IWidget } from '../widgets'; import { IWidget } from '../widgets';
import { WeatherIcon } from './WeatherIcon'; import { WeatherIcon } from './WeatherIcon';
import { useTranslation } from 'react-i18next';
const definition = defineWidget({ const definition = defineWidget({
id: 'weather', id: 'weather',

View File

@@ -8,7 +8,6 @@ import {
} from '@mantine/core'; } from '@mantine/core';
import { Icon } from '@tabler/icons-react'; import { Icon } from '@tabler/icons-react';
import React from 'react'; import React from 'react';
import { AreaType } from '~/types/area'; import { AreaType } from '~/types/area';
import { ShapeType } from '~/types/shape'; import { ShapeType } from '~/types/shape';

View File

@@ -1,20 +1,15 @@
import { NextApiRequest, NextApiResponse } from 'next'; import { NextApiRequest, NextApiResponse } from 'next';
import { SSRConfig } from 'next-i18next'; import { SSRConfig } from 'next-i18next';
import { ParsedUrlQuery } from 'querystring'; import { ParsedUrlQuery } from 'querystring';
import { describe, expect, it, vitest } from 'vitest'; import { describe, expect, it, vitest } from 'vitest';
import * as serverAuthModule from '~/server/auth';
import { ConfigType } from '~/types/config';
import { getServerSideProps } from '../../../src/pages/board/[slug]'; import { getServerSideProps } from '../../../src/pages/board/[slug]';
import * as configExistsModule from '../../../src/tools/config/configExists'; import * as configExistsModule from '../../../src/tools/config/configExists';
import * as getFrontendConfigModule from '../../../src/tools/config/getFrontendConfig'; import * as getFrontendConfigModule from '../../../src/tools/config/getFrontendConfig';
import * as getServerSideTranslationsModule from '../../../src/tools/server/getServerSideTranslations'; import * as getServerSideTranslationsModule from '../../../src/tools/server/getServerSideTranslations';
import * as serverAuthModule from '~/server/auth';
import { ConfigType } from '~/types/config';
vitest.mock('./../../server/auth.ts', () => ({ vitest.mock('./../../server/auth.ts', () => ({
getServerAuthSession: () => null, getServerAuthSession: () => null,
})); }));
@@ -159,8 +154,8 @@ describe('[slug] page', () => {
// assert // assert
expect(response).toEqual({ expect(response).toEqual({
redirect: { redirect: {
destination: "/auth/login?redirectAfterLogin=/board/my-authentication-board", destination: '/auth/login?redirectAfterLogin=/board/my-authentication-board',
permanent: false permanent: false,
}, },
props: {}, props: {},
}); });