♻️ Remove unused fields on board customization page

This commit is contained in:
Meier Lukas
2023-10-21 12:44:48 +02:00
parent a01d7c5798
commit 75934558a1
9 changed files with 46 additions and 295 deletions

View File

@@ -1,13 +1,9 @@
{
"text": "Customizations allow you to configure and adjust your experience with Homarr to your preferences.",
"accordeon": {
"layout": {
"name": "Layout",
"description": "Enable and disable elements on your header and dashboard tiles"
},
"gridstack": {
"name": "Gridstack",
"description": "Customize the behaviour and columns of your dashboard area"
"network": {
"name": "Network",
"description": "Enable or disable pings for this board"
},
"pageMetadata": {
"name": "Page Metadata",
@@ -17,13 +13,9 @@
"name": "Appearance",
"description": "Customize the background, colors and apps appearance"
},
"accessibility": {
"name": "Accessibility",
"description": "Configure Homarr for disabled and handicapped users"
},
"access": {
"name": "Acccess",
"description": "Configure who has access to your board"
}
}
}
}

View File

@@ -1,37 +0,0 @@
import { Input, Slider } from '@mantine/core';
import { useTranslation } from 'next-i18next';
import { GridstackBreakpoints } from '~/constants/gridstack-breakpoints';
import { useBoardCustomizationFormContext } from '../form';
export const GridstackCustomization = () => {
const { t } = useTranslation('settings/customization/gridstack');
const form = useBoardCustomizationFormContext();
return (
<>
<Input.Wrapper
label={t('columnsCount.labelPreset', { size: t('common:breakPoints.small') })}
description={t('columnsCount.descriptionPreset', { pixels: GridstackBreakpoints.medium })}
mb="md"
>
<Slider min={1} max={8} mt="xs" {...form.getInputProps('gridstack.sm')} />
</Input.Wrapper>
<Input.Wrapper
label={t('columnsCount.labelPreset', { size: t('common:breakPoints.medium') })}
description={t('columnsCount.descriptionPreset', { pixels: GridstackBreakpoints.large })}
mb="md"
>
<Slider min={3} max={16} mt="xs" {...form.getInputProps('gridstack.md')} />
</Input.Wrapper>
<Input.Wrapper
label={t('columnsCount.labelPreset', { size: t('common:breakPoints.large') })}
description={t('columnsCount.descriptionExceedsPreset', {
pixels: GridstackBreakpoints.large,
})}
>
<Slider min={5} max={20} mt="xs" {...form.getInputProps('gridstack.lg')} />
</Input.Wrapper>
</>
);
};

View File

@@ -1,42 +0,0 @@
import { Checkbox, Grid, Stack } from '@mantine/core';
import { useTranslation } from 'next-i18next';
import { useBoardCustomizationFormContext } from '../form';
import { LayoutPreview } from './LayoutPreview';
export const LayoutCustomization = () => {
const { t } = useTranslation('settings/common');
const form = useBoardCustomizationFormContext();
return (
<Grid gutter="xl" align="stretch">
<Grid.Col span={12} sm={6}>
<LayoutPreview
showLeftSidebar={form.values.layout.leftSidebarEnabled}
showRightSidebar={form.values.layout.rightSidebarEnabled}
showPings={form.values.layout.pingsEnabled}
/>
</Grid.Col>
<Grid.Col span={12} sm={6}>
<Stack spacing="sm" h="100%" justify="space-between">
<Stack spacing="xs">
<Checkbox
label={t('layout.enablelsidebar')}
description={t('layout.enablelsidebardesc')}
{...form.getInputProps('layout.leftSidebarEnabled', { type: 'checkbox' })}
/>
<Checkbox
label={t('layout.enablersidebar')}
description={t('layout.enablersidebardesc')}
{...form.getInputProps('layout.rightSidebarEnabled', { type: 'checkbox' })}
/>
<Checkbox
label={t('layout.enableping')}
{...form.getInputProps('layout.pingsEnabled', { type: 'checkbox' })}
/>
</Stack>
</Stack>
</Grid.Col>
</Grid>
);
};

View File

@@ -1,124 +0,0 @@
import { Flex, Group, Indicator, Paper, Stack, createStyles } from '@mantine/core';
import { Logo } from '~/components/layout/Common/Logo';
import { createDummyArray } from '~/tools/client/arrays';
type LayoutPreviewProps = {
showLeftSidebar: boolean;
showRightSidebar: boolean;
showPings: boolean;
};
export const LayoutPreview = ({
showLeftSidebar,
showRightSidebar,
showPings,
}: LayoutPreviewProps) => {
const { classes } = useStyles();
return (
<Stack spacing="xs">
<Paper px="xs" py={4} withBorder>
<Group position="apart">
<div style={{ flex: 1 }}>
<Logo size="xs" />
</div>
<BaseElement width={60} height={10} />
<Group style={{ flex: 1 }} position="right">
<BaseElement width={10} height={10} />
</Group>
</Group>
</Paper>
<Flex gap={6}>
{showLeftSidebar && (
<Paper className={classes.secondaryWrapper} p="xs" withBorder>
<Flex gap={5} wrap="wrap" align="end" w={65}>
{createDummyArray(5).map((_item, index) => (
<PlaceholderElement
height={index % 4 === 0 ? 60 + 5 : 30}
width={30}
key={`example-item-right-sidebard-${index}`}
index={index}
showPing={showPings}
/>
))}
</Flex>
</Paper>
)}
<Paper className={classes.primaryWrapper} p="xs" withBorder>
<Flex gap={5} wrap="wrap">
{createDummyArray(10).map((_item, index) => (
<PlaceholderElement
height={30}
width={index % 5 === 0 ? 60 + 5 : 30}
key={`example-item-main-${index}`}
index={index}
showPing={showPings}
/>
))}
</Flex>
</Paper>
{showRightSidebar && (
<Paper className={classes.secondaryWrapper} p="xs" withBorder>
<Flex gap={5} align="start" wrap="wrap" w={65}>
{createDummyArray(5).map((_item, index) => (
<PlaceholderElement
height={30}
width={index % 4 === 0 ? 60 + 5 : 30}
key={`example-item-right-sidebard-${index}`}
index={index}
showPing={showPings}
/>
))}
</Flex>
</Paper>
)}
</Flex>
</Stack>
);
};
const useStyles = createStyles((theme) => ({
primaryWrapper: {
flexGrow: 2,
},
secondaryWrapper: {
flexGrow: 1,
maxWidth: 100,
},
}));
const BaseElement = ({ height, width }: { height: number; width: number }) => (
<Paper
sx={(theme) => ({
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.gray[8] : theme.colors.gray[1],
})}
h={height}
p={2}
w={width}
/>
);
type PlaceholderElementProps = {
height: number;
width: number;
showPing: boolean;
index: number;
};
const PlaceholderElement = ({ height, width, showPing, index }: PlaceholderElementProps) => {
if (showPing) {
return (
<Indicator
position="bottom-end"
size={5}
offset={10}
color={index % 4 === 0 ? 'red' : 'green'}
>
<BaseElement width={width} height={height} />
</Indicator>
);
}
return <BaseElement width={width} height={height} />;
};

View File

@@ -0,0 +1,18 @@
import { Stack, Switch } from '@mantine/core';
import { useTranslation } from 'next-i18next';
import { useBoardCustomizationFormContext } from '../form';
export const NetworkCustomization = () => {
const { t } = useTranslation('settings/common');
const form = useBoardCustomizationFormContext();
return (
<Stack>
<Switch
label={t('layout.enableping')}
{...form.getInputProps('network.pingsEnabled', { type: 'checkbox' })}
/>
</Stack>
);
};

View File

@@ -3,6 +3,7 @@ import {
Button,
Card,
Container,
Grid,
Group,
Paper,
Stack,
@@ -13,17 +14,16 @@ import {
} from '@mantine/core';
import { showNotification, updateNotification } from '@mantine/notifications';
import {
IconAccessPoint,
IconArrowLeft,
IconBrush,
IconChartCandle,
IconCheck,
IconDragDrop,
IconLayout,
IconLock,
IconX,
TablerIconsProps,
} from '@tabler/icons-react';
import { GetServerSideProps, GetServerSidePropsContext, InferGetServerSidePropsType } from 'next';
import { GetServerSidePropsContext, InferGetServerSidePropsType } from 'next';
import { useTranslation } from 'next-i18next';
import Head from 'next/head';
import Link from 'next/link';
@@ -32,8 +32,7 @@ import { ReactNode } from 'react';
import { z } from 'zod';
import { AccessCustomization } from '~/components/Board/Customize/Access/AccessCustomization';
import { AppearanceCustomization } from '~/components/Board/Customize/Appearance/AppearanceCustomization';
import { GridstackCustomization } from '~/components/Board/Customize/Gridstack/GridstackCustomization';
import { LayoutCustomization } from '~/components/Board/Customize/Layout/LayoutCustomization';
import { NetworkCustomization } from '~/components/Board/Customize/Network/NetworkCustomization';
import { PageMetadataCustomization } from '~/components/Board/Customize/PageMetadata/PageMetadataCustomization';
import {
BoardCustomizationFormProvider,
@@ -71,9 +70,7 @@ export default function CustomizationPage({
access: {
allowGuests: board.allowGuests ?? false,
},
layout: {
leftSidebarEnabled: false,
rightSidebarEnabled: false,
network: {
pingsEnabled: board.isPingEnabled ?? false,
},
appearance: {
@@ -84,11 +81,6 @@ export default function CustomizationPage({
opacity: board.appOpacity ?? 50,
customCss: board.customCss ?? '',
},
gridstack: {
sm: 3, //config?.settings.customization.gridstack?.columnCountSmall ?? 3,
md: 6, //config?.settings.customization.gridstack?.columnCountMedium ?? 6,
lg: 12, //config?.settings.customization.gridstack?.columnCountLarge ?? 12,
},
pageMetadata: {
pageTitle: board.pageTitle ?? '',
metaTitle: board.metaTitle ?? '',
@@ -219,22 +211,24 @@ export default function CustomizationPage({
</Group>
<BoardCustomizationFormProvider form={form}>
<Stack spacing="xl">
<Stack spacing="xs">
<SectionTitle type="layout" icon={IconLayout} />
<LayoutCustomization />
</Stack>
<Stack spacing="xs">
<SectionTitle type="access" icon={IconLock} />
<AccessCustomization />
</Stack>
<Stack spacing="xs">
<SectionTitle type="gridstack" icon={IconDragDrop} />
<GridstackCustomization />
</Stack>
<Stack spacing="xs">
<SectionTitle type="pageMetadata" icon={IconChartCandle} />
<PageMetadataCustomization />
</Stack>
<Grid>
<Grid.Col span={12} sm={6}>
<Stack spacing="xs">
<SectionTitle type="access" icon={IconLock} />
<AccessCustomization />
</Stack>
</Grid.Col>
<Grid.Col span={12} sm={6}>
<Stack spacing="xs">
<SectionTitle type="network" icon={IconAccessPoint} />
<NetworkCustomization />
</Stack>
</Grid.Col>
</Grid>
<Stack spacing="xs">
<SectionTitle type="appereance" icon={IconBrush} />
<AppearanceCustomization />
@@ -249,7 +243,7 @@ export default function CustomizationPage({
}
type SectionTitleProps = {
type: 'layout' | 'gridstack' | 'pageMetadata' | 'appereance' | 'access';
type: 'network' | 'pageMetadata' | 'appereance' | 'access';
icon: (props: TablerIconsProps) => ReactNode;
};

View File

@@ -195,7 +195,7 @@ export const boardRouter = createTRPCRouter({
.update(boards)
.set({
allowGuests: input.customization.access.allowGuests,
isPingEnabled: input.customization.layout.pingsEnabled,
isPingEnabled: input.customization.network.pingsEnabled,
appOpacity: input.customization.appearance.opacity,
backgroundImageUrl: input.customization.appearance.backgroundSrc,
primaryColor: input.customization.appearance.primaryColor,

View File

@@ -5,12 +5,12 @@ import fs from 'fs';
import path from 'path';
import { z } from 'zod';
import { configExists } from '~/tools/config/configExists';
import { getConfig } from '~/tools/config/getConfig';
import { getFrontendConfig } from '~/tools/config/getFrontendConfig';
import { BackendConfigType, ConfigType } from '~/types/config';
import { boardCustomizationSchema } from '~/validations/boards';
import { IRssWidget } from '~/widgets/rss/RssWidgetTile';
import { getConfig } from '~/tools/config/getConfig';
import { adminProcedure, createTRPCRouter, publicProcedure } from '../trpc';
export const configNameSchema = z.string().regex(/^[a-zA-Z0-9-_]+$/);
@@ -70,7 +70,7 @@ export const configRouter = createTRPCRouter({
if (process.env.DISABLE_EDIT_MODE?.toLowerCase() === 'true') {
throw new TRPCError({
code: 'METHOD_NOT_SUPPORTED',
message: 'Edit is not allowed, because edit mode is disabled'
message: 'Edit is not allowed, because edit mode is disabled',
});
}
Consola.info(`Saving updated configuration of '${input.name}' config.`);
@@ -183,47 +183,4 @@ export const configRouter = createTRPCRouter({
return await getFrontendConfig(input.name);
}),
saveCusomization: adminProcedure
.input(boardCustomizationSchema.and(z.object({ name: configNameSchema })))
.mutation(async ({ input }) => {
const previousConfig = getConfig(input.name);
const newConfig = {
...previousConfig,
settings: {
...previousConfig.settings,
access: {
...previousConfig.settings.access,
allowGuests: input.access.allowGuests,
},
customization: {
...previousConfig.settings.customization,
appOpacity: input.appearance.opacity,
backgroundImageUrl: input.appearance.backgroundSrc,
colors: {
primary: input.appearance.primaryColor,
secondary: input.appearance.secondaryColor,
shade: input.appearance.shade as MantineTheme['primaryShade'],
},
customCss: input.appearance.customCss,
faviconUrl: input.pageMetadata.faviconSrc,
gridstack: {
columnCountSmall: input.gridstack.sm,
columnCountMedium: input.gridstack.md,
columnCountLarge: input.gridstack.lg,
},
layout: {
...previousConfig.settings.customization.layout,
enabledLeftSidebar: input.layout.leftSidebarEnabled,
enabledRightSidebar: input.layout.rightSidebarEnabled,
enabledPing: input.layout.pingsEnabled,
},
logoImageUrl: input.pageMetadata.logoSrc,
metaTitle: input.pageMetadata.metaTitle,
pageTitle: input.pageMetadata.pageTitle,
},
},
} satisfies BackendConfigType;
const targetPath = path.join('data/configs', `${input.name}.json`);
fs.writeFileSync(targetPath, JSON.stringify(newConfig, null, 2), 'utf8');
}),
});

View File

@@ -9,16 +9,9 @@ export const boardCustomizationSchema = z.object({
access: z.object({
allowGuests: z.boolean(),
}),
layout: z.object({
leftSidebarEnabled: z.boolean(),
rightSidebarEnabled: z.boolean(),
network: z.object({
pingsEnabled: z.boolean(),
}),
gridstack: z.object({
sm: z.number().min(1).max(8),
md: z.number().min(3).max(16),
lg: z.number().min(5).max(20),
}),
pageMetadata: z.object({
pageTitle: z.string().min(1),
metaTitle: z.string(),