feat: improved structure of settings

This commit is contained in:
Manuel Ruwe
2022-12-06 21:22:37 +01:00
parent b28547777f
commit 4d45805bce
20 changed files with 111 additions and 99 deletions

View File

@@ -1,3 +1,3 @@
export const REPO_URL = 'ajnart/homarr'; export const REPO_URL = 'ajnart/homarr';
export const CURRENT_VERSION = 'v0.10.7'; export const CURRENT_VERSION = 'v0.11';
export const ICON_PICKER_SLICE_LIMIT = 36; export const ICON_PICKER_SLICE_LIMIT = 36;

View File

@@ -1,10 +1,10 @@
import { Space, Stack, Text } from '@mantine/core'; import { Space, Stack, Text } from '@mantine/core';
import { useConfigContext } from '../../config/provider'; import { useConfigContext } from '../../../config/provider';
import ConfigChanger from '../Config/ConfigChanger'; import ConfigChanger from '../../Config/ConfigChanger';
import ConfigActions from './Common/ConfigActions'; import ConfigActions from './Config/ConfigActions';
import LanguageSelect from './Common/LanguageSelect'; import LanguageSelect from './Language/LanguageSelect';
import { SearchEngineSelector } from './Common/SearchEngineSelector'; import { SearchEngineSelector } from './SearchEngine/SearchEngineSelector';
import { SearchNewTabSwitch } from './Common/SearchNewTabSwitch'; import { SearchNewTabSwitch } from './SearchNewTabSwitch';
export default function CommonSettings() { export default function CommonSettings() {
const { config } = useConfigContext(); const { config } = useConfigContext();

View File

@@ -1,13 +1,13 @@
import { Button, Center, Group } from '@mantine/core'; import { ActionIcon, Center, createStyles, Flex, Text, useMantineTheme } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks'; import { useDisclosure } from '@mantine/hooks';
import { showNotification } from '@mantine/notifications'; import { showNotification } from '@mantine/notifications';
import { IconCheck, IconDownload, IconPlus, IconTrash, IconX } from '@tabler/icons'; import { IconCheck, IconCopy, IconDownload, IconTrash, IconX } from '@tabler/icons';
import { useMutation } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query';
import fileDownload from 'js-file-download'; import fileDownload from 'js-file-download';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useConfigContext } from '../../../config/provider'; import { useConfigContext } from '../../../../config/provider';
import Tip from '../../layout/Tip'; import Tip from '../../../layout/Tip';
import { CreateConfigCopyModal } from './ConfigActions/CreateCopyModal'; import { CreateConfigCopyModal } from './CreateCopyModal';
export default function ConfigActions() { export default function ConfigActions() {
const { t } = useTranslation(['settings/general/config-changer', 'settings/common']); const { t } = useTranslation(['settings/general/config-changer', 'settings/common']);
@@ -26,6 +26,9 @@ export default function ConfigActions() {
await mutateAsync(); await mutateAsync();
}; };
const { classes } = useStyles();
const { colors } = useMantineTheme();
return ( return (
<> <>
<CreateConfigCopyModal <CreateConfigCopyModal
@@ -33,32 +36,25 @@ export default function ConfigActions() {
closeModal={createCopyModal.close} closeModal={createCopyModal.close}
initialConfigName={config.configProperties.name} initialConfigName={config.configProperties.name}
/> />
<Group spacing="xs" position="center"> <Flex gap="xs" justify="stretch">
<Button <ActionIcon className={classes.actionIcon} onClick={handleDownload} variant="default">
size="xs" <IconDownload size={20} />
leftIcon={<IconDownload size={18} />} <Text>{t('buttons.download')}</Text>
variant="default" </ActionIcon>
onClick={handleDownload} <ActionIcon
> className={classes.actionIcon}
{t('buttons.download')}
</Button>
<Button
size="xs"
leftIcon={<IconTrash size={18} />}
variant="default"
onClick={handleDeletion} onClick={handleDeletion}
color="red"
variant="light"
> >
{t('buttons.delete.text')} <IconTrash color={colors.red[2]} size={20} />
</Button> <Text>{t('buttons.delete.text')}</Text>
<Button </ActionIcon>
size="xs" <ActionIcon className={classes.actionIcon} onClick={createCopyModal.open} variant="default">
leftIcon={<IconPlus size={18} />} <IconCopy size={20} />
variant="default" <Text>{t('buttons.saveCopy')}</Text>
onClick={createCopyModal.open} </ActionIcon>
> </Flex>
{t('buttons.saveCopy')}
</Button>
</Group>
<Center> <Center>
<Tip>{t('settings/common:tips.configTip')}</Tip> <Tip>{t('settings/common:tips.configTip')}</Tip>
@@ -67,6 +63,23 @@ export default function ConfigActions() {
); );
} }
const useStyles = createStyles(() => ({
actionIcon: {
width: 'auto',
height: 'auto',
maxWidth: 'auto',
maxHeight: 'auto',
flexGrow: 1,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
textAlign: 'center',
rowGap: 10,
padding: 10,
},
}));
const useDeleteConfigMutation = (configName: string) => { const useDeleteConfigMutation = (configName: string) => {
const { t } = useTranslation(['settings/general/config-changer']); const { t } = useTranslation(['settings/general/config-changer']);

View File

@@ -2,7 +2,7 @@ import { Group, ActionIcon, Anchor, Text } from '@mantine/core';
import { IconBrandDiscord, IconBrandGithub } from '@tabler/icons'; import { IconBrandDiscord, IconBrandGithub } from '@tabler/icons';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { CURRENT_VERSION } from '../../../data/constants'; import { CURRENT_VERSION } from '../../../../data/constants';
export default function Credits() { export default function Credits() {
const { t } = useTranslation('settings/common'); const { t } = useTranslation('settings/common');

View File

@@ -5,7 +5,7 @@ import { forwardRef, useState } from 'react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { getCookie, setCookie } from 'cookies-next'; import { getCookie, setCookie } from 'cookies-next';
import { getLanguageByCode, Language } from '../../../tools/language'; import { getLanguageByCode, Language } from '../../../../tools/language';
export default function LanguageSelect() { export default function LanguageSelect() {
const { t, i18n } = useTranslation('settings/general/internationalization'); const { t, i18n } = useTranslation('settings/general/internationalization');

View File

@@ -2,13 +2,12 @@ import { Alert, Paper, SegmentedControl, Space, Stack, TextInput, Title } from '
import { IconInfoCircle } from '@tabler/icons'; import { IconInfoCircle } from '@tabler/icons';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { ChangeEventHandler, useState } from 'react'; import { ChangeEventHandler, useState } from 'react';
import { useConfigContext } from '../../../config/provider'; import { useConfigContext } from '../../../../config/provider';
import { useConfigStore } from '../../../config/store'; import { useConfigStore } from '../../../../config/store';
import { import {
CommonSearchEngineCommonSettingsType, CommonSearchEngineCommonSettingsType,
SearchEngineCommonSettingsType, SearchEngineCommonSettingsType,
} from '../../../types/settings'; } from '../../../../types/settings';
import Tip from '../../layout/Tip';
import { SearchNewTabSwitch } from './SearchNewTabSwitch'; import { SearchNewTabSwitch } from './SearchNewTabSwitch';
interface Props { interface Props {

View File

@@ -1,9 +1,9 @@
import { Switch } from '@mantine/core'; import { Switch } from '@mantine/core';
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 { SearchEngineCommonSettingsType } from '../../../types/settings'; import { SearchEngineCommonSettingsType } from '../../../../types/settings';
interface SearchNewTabSwitchProps { interface SearchNewTabSwitchProps {
defaultValue: boolean | undefined; defaultValue: boolean | undefined;

View File

@@ -1,15 +1,15 @@
import { Stack } from '@mantine/core'; import { Stack } from '@mantine/core';
import { useConfigContext } from '../../config/provider'; import { useConfigContext } from '../../../config/provider';
import { ColorSelector } from './Customization/ColorSelector'; import { ColorSelector } from './Theme/ColorSelector';
import { BackgroundChanger } from './Customization/BackgroundChanger'; import { BackgroundChanger } from './Meta/BackgroundChanger';
import { CustomCssChanger } from './Customization/CustomCssChanger'; import { CustomCssChanger } from './Theme/CustomCssChanger';
import { FaviconChanger } from './Customization/FaviconChanger'; import { FaviconChanger } from './Meta/FaviconChanger';
import { LogoImageChanger } from './Customization/LogoImageChanger'; import { LogoImageChanger } from './Meta/LogoImageChanger';
import { MetaTitleChanger } from './Customization/MetaTitleChanger'; import { MetaTitleChanger } from './Meta/MetaTitleChanger';
import { PageTitleChanger } from './Customization/PageTitleChanger'; import { PageTitleChanger } from './Meta/PageTitleChanger';
import { OpacitySelector } from './Customization/OpacitySelector'; import { OpacitySelector } from './Theme/OpacitySelector';
import { ShadeSelector } from './Customization/ShadeSelector'; import { ShadeSelector } from './Theme/ShadeSelector';
import { LayoutSelector } from './Customization/LayoutSelector'; import { LayoutSelector } from './Layout/LayoutSelector';
export default function CustomizationSettings() { export default function CustomizationSettings() {
const { config } = useConfigContext(); const { config } = useConfigContext();

View File

@@ -11,10 +11,10 @@ import {
} from '@mantine/core'; } from '@mantine/core';
import { IconBrandDocker, IconLayout, IconSearch } from '@tabler/icons'; import { IconBrandDocker, IconLayout, IconSearch } from '@tabler/icons';
import { ChangeEvent, Dispatch, SetStateAction, useState } from 'react'; import { ChangeEvent, Dispatch, SetStateAction, useState } from 'react';
import { useConfigContext } from '../../../config/provider'; import { useConfigContext } from '../../../../config/provider';
import { useConfigStore } from '../../../config/store'; import { useConfigStore } from '../../../../config/store';
import { CustomizationSettingsType } from '../../../types/settings'; import { CustomizationSettingsType } from '../../../../types/settings';
import { Logo } from '../../layout/Logo'; import { Logo } from '../../../layout/Logo';
interface LayoutSelectorProps { interface LayoutSelectorProps {
defaultLayout: CustomizationSettingsType['layout'] | undefined; defaultLayout: CustomizationSettingsType['layout'] | undefined;

View File

@@ -1,8 +1,8 @@
import { TextInput } from '@mantine/core'; import { TextInput } from '@mantine/core';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { ChangeEventHandler, useState } from 'react'; import { ChangeEventHandler, useState } from 'react';
import { useConfigContext } from '../../../config/provider'; import { useConfigContext } from '../../../../config/provider';
import { useConfigStore } from '../../../config/store'; import { useConfigStore } from '../../../../config/store';
interface BackgroundChangerProps { interface BackgroundChangerProps {
defaultValue: string | undefined; defaultValue: string | undefined;
@@ -17,7 +17,7 @@ export const BackgroundChanger = ({ defaultValue }: BackgroundChangerProps) => {
if (!configName) return null; if (!configName) return null;
const handleChange: ChangeEventHandler<HTMLInputElement> = (ev) => { const handleChange: ChangeEventHandler<HTMLInputElement> = (ev) => {
const value = ev.currentTarget.value; const { value } = ev.currentTarget;
const backgroundImageUrl = value.trim().length === 0 ? undefined : value; const backgroundImageUrl = value.trim().length === 0 ? undefined : value;
setBackgroundImageUrl(backgroundImageUrl); setBackgroundImageUrl(backgroundImageUrl);
updateConfig(configName, (prev) => ({ updateConfig(configName, (prev) => ({

View File

@@ -1,8 +1,8 @@
import { TextInput } from '@mantine/core'; import { TextInput } from '@mantine/core';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { ChangeEventHandler, useState } from 'react'; import { ChangeEventHandler, useState } from 'react';
import { useConfigContext } from '../../../config/provider'; import { useConfigContext } from '../../../../config/provider';
import { useConfigStore } from '../../../config/store'; import { useConfigStore } from '../../../../config/store';
interface FaviconChangerProps { interface FaviconChangerProps {
defaultValue: string | undefined; defaultValue: string | undefined;
@@ -17,7 +17,7 @@ export const FaviconChanger = ({ defaultValue }: FaviconChangerProps) => {
if (!configName) return null; if (!configName) return null;
const handleChange: ChangeEventHandler<HTMLInputElement> = (ev) => { const handleChange: ChangeEventHandler<HTMLInputElement> = (ev) => {
const value = ev.currentTarget.value; const { value } = ev.currentTarget;
const faviconUrl = value.trim().length === 0 ? undefined : value; const faviconUrl = value.trim().length === 0 ? undefined : value;
setFaviconUrl(faviconUrl); setFaviconUrl(faviconUrl);
updateConfig(configName, (prev) => ({ updateConfig(configName, (prev) => ({

View File

@@ -1,8 +1,8 @@
import { TextInput } from '@mantine/core'; import { TextInput } from '@mantine/core';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { ChangeEventHandler, useState } from 'react'; import { ChangeEventHandler, useState } from 'react';
import { useConfigContext } from '../../../config/provider'; import { useConfigContext } from '../../../../config/provider';
import { useConfigStore } from '../../../config/store'; import { useConfigStore } from '../../../../config/store';
interface LogoImageChangerProps { interface LogoImageChangerProps {
defaultValue: string | undefined; defaultValue: string | undefined;
@@ -17,7 +17,7 @@ export const LogoImageChanger = ({ defaultValue }: LogoImageChangerProps) => {
if (!configName) return null; if (!configName) return null;
const handleChange: ChangeEventHandler<HTMLInputElement> = (ev) => { const handleChange: ChangeEventHandler<HTMLInputElement> = (ev) => {
const value = ev.currentTarget.value; const { value } = ev.currentTarget;
const logoImageSrc = value.trim().length === 0 ? undefined : value; const logoImageSrc = value.trim().length === 0 ? undefined : value;
setLogoImageSrc(logoImageSrc); setLogoImageSrc(logoImageSrc);
updateConfig(configName, (prev) => ({ updateConfig(configName, (prev) => ({

View File

@@ -1,8 +1,8 @@
import { TextInput } from '@mantine/core'; import { TextInput } from '@mantine/core';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { ChangeEventHandler, useState } from 'react'; import { ChangeEventHandler, useState } from 'react';
import { useConfigContext } from '../../../config/provider'; import { useConfigContext } from '../../../../config/provider';
import { useConfigStore } from '../../../config/store'; import { useConfigStore } from '../../../../config/store';
interface MetaTitleChangerProps { interface MetaTitleChangerProps {
defaultValue: string | undefined; defaultValue: string | undefined;
@@ -18,7 +18,7 @@ export const MetaTitleChanger = ({ defaultValue }: MetaTitleChangerProps) => {
if (!configName) return null; if (!configName) return null;
const handleChange: ChangeEventHandler<HTMLInputElement> = (ev) => { const handleChange: ChangeEventHandler<HTMLInputElement> = (ev) => {
const value = ev.currentTarget.value; const { value } = ev.currentTarget;
const metaTitle = value.trim().length === 0 ? undefined : value; const metaTitle = value.trim().length === 0 ? undefined : value;
setMetaTitle(metaTitle); setMetaTitle(metaTitle);
updateConfig(configName, (prev) => ({ updateConfig(configName, (prev) => ({

View File

@@ -1,8 +1,8 @@
import { TextInput } from '@mantine/core'; import { TextInput } from '@mantine/core';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { ChangeEventHandler, useState } from 'react'; import { ChangeEventHandler, useState } from 'react';
import { useConfigContext } from '../../../config/provider'; import { useConfigContext } from '../../../../config/provider';
import { useConfigStore } from '../../../config/store'; import { useConfigStore } from '../../../../config/store';
interface PageTitleChangerProps { interface PageTitleChangerProps {
defaultValue: string | undefined; defaultValue: string | undefined;
@@ -18,7 +18,7 @@ export const PageTitleChanger = ({ defaultValue }: PageTitleChangerProps) => {
if (!configName) return null; if (!configName) return null;
const handleChange: ChangeEventHandler<HTMLInputElement> = (ev) => { const handleChange: ChangeEventHandler<HTMLInputElement> = (ev) => {
const value = ev.currentTarget.value; const { value } = ev.currentTarget;
const pageTitle = value.trim().length === 0 ? undefined : value; const pageTitle = value.trim().length === 0 ? undefined : value;
setPageTitle(pageTitle); setPageTitle(pageTitle);
updateConfig(configName, (prev) => ({ updateConfig(configName, (prev) => ({

View File

@@ -1,4 +1,3 @@
import React, { useState } from 'react';
import { import {
ColorSwatch, ColorSwatch,
Grid, Grid,
@@ -8,11 +7,12 @@ import {
Text, Text,
useMantineTheme, useMantineTheme,
} from '@mantine/core'; } from '@mantine/core';
import { useTranslation } from 'next-i18next';
import { useColorTheme } from '../../../tools/color';
import { useDisclosure } from '@mantine/hooks'; import { useDisclosure } from '@mantine/hooks';
import { useConfigStore } from '../../../config/store'; import { useTranslation } from 'next-i18next';
import { useConfigContext } from '../../../config/provider'; import { useState } from 'react';
import { useConfigContext } from '../../../../config/provider';
import { useConfigStore } from '../../../../config/store';
import { useColorTheme } from '../../../../tools/color';
interface ColorControlProps { interface ColorControlProps {
defaultValue: MantineTheme['primaryColor'] | undefined; defaultValue: MantineTheme['primaryColor'] | undefined;
@@ -40,7 +40,7 @@ export function ColorSelector({ type, defaultValue }: ColorControlProps) {
if (type === 'primary') setPrimaryColor(color); if (type === 'primary') setPrimaryColor(color);
else setSecondaryColor(color); else setSecondaryColor(color);
updateConfig(configName, (prev) => { updateConfig(configName, (prev) => {
const colors = prev.settings.customization.colors; const { colors } = prev.settings.customization;
colors[type] = color; colors[type] = color;
return { return {
...prev, ...prev,

View File

@@ -1,8 +1,8 @@
import { Textarea } from '@mantine/core'; import { Textarea } from '@mantine/core';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { ChangeEventHandler, useState } from 'react'; import { ChangeEventHandler, useState } from 'react';
import { useConfigContext } from '../../../config/provider'; import { useConfigContext } from '../../../../config/provider';
import { useConfigStore } from '../../../config/store'; import { useConfigStore } from '../../../../config/store';
interface CustomCssChangerProps { interface CustomCssChangerProps {
defaultValue: string | undefined; defaultValue: string | undefined;
@@ -17,7 +17,7 @@ export const CustomCssChanger = ({ defaultValue }: CustomCssChangerProps) => {
if (!configName) return null; if (!configName) return null;
const handleChange: ChangeEventHandler<HTMLTextAreaElement> = (ev) => { const handleChange: ChangeEventHandler<HTMLTextAreaElement> = (ev) => {
const value = ev.currentTarget.value; const { value } = ev.currentTarget;
const customCss = value.trim().length === 0 ? undefined : value; const customCss = value.trim().length === 0 ? undefined : value;
setCustomCss(customCss); setCustomCss(customCss);
updateConfig(configName, (prev) => ({ updateConfig(configName, (prev) => ({

View File

@@ -1,8 +1,8 @@
import React, { useState } from 'react'; import { Slider, Stack, Text } from '@mantine/core';
import { Text, Slider, Stack } from '@mantine/core';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useConfigContext } from '../../../config/provider'; import { useState } from 'react';
import { useConfigStore } from '../../../config/store'; import { useConfigContext } from '../../../../config/provider';
import { useConfigStore } from '../../../../config/store';
interface OpacitySelectorProps { interface OpacitySelectorProps {
defaultValue: number | undefined; defaultValue: number | undefined;

View File

@@ -1,19 +1,19 @@
import React, { useState } from 'react';
import { import {
ColorSwatch, ColorSwatch,
Grid,
Group, Group,
MantineTheme,
Popover, Popover,
Stack,
Text, Text,
useMantineTheme, useMantineTheme,
MantineTheme,
Stack,
Grid,
} from '@mantine/core'; } from '@mantine/core';
import { useTranslation } from 'next-i18next';
import { useColorTheme } from '../../../tools/color';
import { useDisclosure } from '@mantine/hooks'; import { useDisclosure } from '@mantine/hooks';
import { useConfigStore } from '../../../config/store'; import { useTranslation } from 'next-i18next';
import { useConfigContext } from '../../../config/provider'; import { useState } from 'react';
import { useConfigContext } from '../../../../config/provider';
import { useConfigStore } from '../../../../config/store';
import { useColorTheme } from '../../../../tools/color';
interface ShadeSelectorProps { interface ShadeSelectorProps {
defaultValue: MantineTheme['primaryShade'] | undefined; defaultValue: MantineTheme['primaryShade'] | undefined;

View File

@@ -4,9 +4,9 @@ import { useState } from 'react';
import { IconSettings } from '@tabler/icons'; import { IconSettings } from '@tabler/icons';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import CustomizationSettings from './CustomizationSettings'; import CustomizationSettings from './Customization/CustomizationSettings';
import CommonSettings from './CommonSettings'; import CommonSettings from './Common/CommonSettings';
import Credits from './Credits'; import Credits from './Common/Credits';
function SettingsMenu() { function SettingsMenu() {
const { t } = useTranslation('settings/common'); const { t } = useTranslation('settings/common');