Image properties customization (#1590)

This commit is contained in:
Manuel
2023-11-12 13:37:32 +01:00
committed by GitHub
parent e900a7b07e
commit 27037c6f50
7 changed files with 78 additions and 10 deletions

View File

@@ -18,6 +18,29 @@
"background": { "background": {
"label": "Background" "label": "Background"
}, },
"backgroundImageAttachment": {
"label": "Background image attachment",
"options": {
"fixed": "Fixed - Background stays in the same position (recommended)",
"scroll": "Scroll - Background scrolls with your mouse"
}
},
"backgroundImageSize": {
"label": "Background image size",
"options": {
"cover": "Cover - Scales the image as small as possible to cover the entire window by cropping excessive space. (recommended)",
"contain": "Contain - Scales the image as large as possible within its container without cropping or stretching the image."
}
},
"backgroundImageRepeat": {
"label": "Background image attachment",
"options": {
"repeat": "Repeat - The image is repeated as much as needed to cover the whole background image painting area.",
"no-repeat": "No repeat - The image is not repeated any may not fill the entire space (recommended)",
"repeat-x": "Repeat X - Same as 'Repeat' but only on horizontal axis.",
"repeat-y": "Repeat Y - Same as 'Repeat' but only on vertical axis."
}
},
"customCSS": { "customCSS": {
"label": "Custom CSS", "label": "Custom CSS",
"description": "Further, customize your dashboard using CSS, only recommended for experienced users", "description": "Further, customize your dashboard using CSS, only recommended for experienced users",

View File

@@ -4,9 +4,9 @@ import {
Group, Group,
Input, Input,
MantineTheme, MantineTheme,
Select,
Slider, Slider,
Stack, Stack,
Text,
TextInput, TextInput,
createStyles, createStyles,
rem, rem,
@@ -16,6 +16,7 @@ import { useTranslation } from 'next-i18next';
import { highlight, languages } from 'prismjs'; import { highlight, languages } from 'prismjs';
import Editor from 'react-simple-code-editor'; import Editor from 'react-simple-code-editor';
import { useColorTheme } from '~/tools/color'; import { useColorTheme } from '~/tools/color';
import { BackgroundImageAttachment, BackgroundImageRepeat, BackgroundImageSize } from '~/types/settings';
import { useBoardCustomizationFormContext } from '../form'; import { useBoardCustomizationFormContext } from '../form';
@@ -30,6 +31,32 @@ export const AppearanceCustomization = () => {
placeholder="/imgs/backgrounds/background.png" placeholder="/imgs/backgrounds/background.png"
{...form.getInputProps('appearance.backgroundSrc')} {...form.getInputProps('appearance.backgroundSrc')}
/> />
<Select
label={t('backgroundImageAttachment.label')}
data={BackgroundImageAttachment.map((attachment) => ({
value: attachment,
label: t(`backgroundImageAttachment.options.${attachment}`) as string,
}))}
{...form.getInputProps('appearance.backgroundImageAttachment')}
/>
<Select
label={t('backgroundImageSize.label')}
data={BackgroundImageSize.map((size) => ({
value: size,
label: t(`backgroundImageSize.options.${size}`) as string,
}))}
{...form.getInputProps('appearance.backgroundImageSize')}
/>
<Select
label={t('backgroundImageRepeat.label')}
data={BackgroundImageRepeat.map((repeat) => ({
value: repeat,
label: t(`backgroundImageRepeat.options.${repeat}`) as string,
}))}
{...form.getInputProps('appearance.backgroundImageRepeat')}
/>
<ColorSelector type="primaryColor" /> <ColorSelector type="primaryColor" />
<ColorSelector type="secondaryColor" /> <ColorSelector type="secondaryColor" />
<ShadeSelector /> <ShadeSelector />

View File

@@ -4,10 +4,9 @@ import { openContextModal } from '@mantine/modals';
import { hideNotification, showNotification } from '@mantine/notifications'; import { hideNotification, showNotification } from '@mantine/notifications';
import { import {
IconApps, IconApps,
IconBrandDocker,
IconEditCircle, IconEditCircle,
IconEditCircleOff, IconEditCircleOff,
IconSettings, IconSettings
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import Consola from 'consola'; import Consola from 'consola';
import { useSession } from 'next-auth/react'; import { useSession } from 'next-auth/react';
@@ -19,11 +18,10 @@ import { useNamedWrapperColumnCount } from '~/components/Dashboard/Wrappers/grid
import { BoardHeadOverride } from '~/components/layout/Meta/BoardHeadOverride'; import { BoardHeadOverride } from '~/components/layout/Meta/BoardHeadOverride';
import { HeaderActionButton } from '~/components/layout/header/ActionButton'; import { HeaderActionButton } from '~/components/layout/header/ActionButton';
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
import { useScreenLargerThan } from '~/hooks/useScreenLargerThan';
import { api } from '~/utils/api'; import { api } from '~/utils/api';
import { MainLayout } from './MainLayout';
import { env } from 'process'; import { env } from 'process';
import { MainLayout } from './MainLayout';
type BoardLayoutProps = { type BoardLayoutProps = {
children: React.ReactNode; children: React.ReactNode;
@@ -205,8 +203,9 @@ const BackgroundImage = () => {
minHeight: '100vh', minHeight: '100vh',
backgroundImage: `url('${config?.settings.customization.backgroundImageUrl}')`, backgroundImage: `url('${config?.settings.customization.backgroundImageUrl}')`,
backgroundPosition: 'center center', backgroundPosition: 'center center',
backgroundSize: 'cover', backgroundSize: config?.settings.customization.backgroundImageSize ?? 'cover',
backgroundRepeat: 'no-repeat', backgroundRepeat: config?.settings.customization.backgroundImageRepeat ?? 'no-repeat',
backgroundAttachment: config?.settings.customization.backgroundImageAttachment ?? 'fixed'
}, },
}} }}
/> />

View File

@@ -66,7 +66,7 @@ export default function CustomizationPage({
refetchOnMount: false, refetchOnMount: false,
} }
); );
const { mutateAsync: saveCusomization, isLoading } = api.config.saveCusomization.useMutation(); const { mutateAsync: saveCustomization, isLoading } = api.config.saveCustomization.useMutation();
const { i18nZodResolver } = useI18nZodResolver(); const { i18nZodResolver } = useI18nZodResolver();
const { t } = useTranslation('boards/customize'); const { t } = useTranslation('boards/customize');
const form = useBoardCustomizationForm({ const form = useBoardCustomizationForm({
@@ -86,6 +86,9 @@ export default function CustomizationPage({
shade: (config?.settings.customization.colors.shade as number | undefined) ?? 8, shade: (config?.settings.customization.colors.shade as number | undefined) ?? 8,
opacity: config?.settings.customization.appOpacity ?? 50, opacity: config?.settings.customization.appOpacity ?? 50,
customCss: config?.settings.customization.customCss ?? '', customCss: config?.settings.customization.customCss ?? '',
backgroundImageAttachment: config?.settings.customization.backgroundImageAttachment ?? 'fixed',
backgroundImageRepeat: config?.settings.customization.backgroundImageRepeat ?? 'no-repeat',
backgroundImageSize: config?.settings.customization.backgroundImageSize ?? 'cover',
}, },
gridstack: { gridstack: {
sm: config?.settings.customization.gridstack?.columnCountSmall ?? 3, sm: config?.settings.customization.gridstack?.columnCountSmall ?? 3,
@@ -114,7 +117,7 @@ export default function CustomizationPage({
message: t('notifications.pending.message'), message: t('notifications.pending.message'),
loading: true, loading: true,
}); });
await saveCusomization( await saveCustomization(
{ {
name: query.slug, name: query.slug,
...values, ...values,

View File

@@ -177,7 +177,7 @@ export const configRouter = createTRPCRouter({
return await getFrontendConfig(input.name); return await getFrontendConfig(input.name);
}), }),
saveCusomization: adminProcedure saveCustomization: adminProcedure
.input(boardCustomizationSchema.and(z.object({ name: configNameSchema }))) .input(boardCustomizationSchema.and(z.object({ name: configNameSchema })))
.mutation(async ({ input }) => { .mutation(async ({ input }) => {
const previousConfig = getConfig(input.name); const previousConfig = getConfig(input.name);
@@ -193,6 +193,9 @@ export const configRouter = createTRPCRouter({
...previousConfig.settings.customization, ...previousConfig.settings.customization,
appOpacity: input.appearance.opacity, appOpacity: input.appearance.opacity,
backgroundImageUrl: input.appearance.backgroundSrc, backgroundImageUrl: input.appearance.backgroundSrc,
backgroundImageAttachment: input.appearance.backgroundImageAttachment,
backgroundImageRepeat: input.appearance.backgroundImageRepeat,
backgroundImageSize: input.appearance.backgroundImageSize,
colors: { colors: {
primary: input.appearance.primaryColor, primary: input.appearance.primaryColor,
secondary: input.appearance.secondaryColor, secondary: input.appearance.secondaryColor,

View File

@@ -45,6 +45,9 @@ export interface CustomizationSettingsType {
logoImageUrl?: string; logoImageUrl?: string;
faviconUrl?: string; faviconUrl?: string;
backgroundImageUrl?: string; backgroundImageUrl?: string;
backgroundImageAttachment?: typeof BackgroundImageAttachment[number];
backgroundImageSize?: typeof BackgroundImageSize[number];
backgroundImageRepeat?: typeof BackgroundImageRepeat[number];
customCss?: string; customCss?: string;
colors: ColorsCustomizationSettingsType; colors: ColorsCustomizationSettingsType;
appOpacity?: number; appOpacity?: number;
@@ -52,6 +55,12 @@ export interface CustomizationSettingsType {
accessibility: AccessibilitySettings; accessibility: AccessibilitySettings;
} }
export const BackgroundImageAttachment = ['fixed', 'scroll'] as const;
export const BackgroundImageSize = ['cover', 'contain'] as const;
export const BackgroundImageRepeat = ['no-repeat', 'repeat', 'repeat-x', 'repeat-y'] as const;
export interface AccessibilitySettings { export interface AccessibilitySettings {
disablePingPulse: boolean; disablePingPulse: boolean;
replacePingDotsWithIcons: boolean; replacePingDotsWithIcons: boolean;

View File

@@ -1,5 +1,6 @@
import { DEFAULT_THEME, MANTINE_COLORS, MantineColor } from '@mantine/core'; import { DEFAULT_THEME, MANTINE_COLORS, MantineColor } from '@mantine/core';
import { z } from 'zod'; import { z } from 'zod';
import { BackgroundImageAttachment, BackgroundImageRepeat, BackgroundImageSize } from '~/types/settings';
export const createBoardSchemaValidation = z.object({ export const createBoardSchemaValidation = z.object({
name: z.string().min(2).max(25), name: z.string().min(2).max(25),
@@ -27,6 +28,9 @@ export const boardCustomizationSchema = z.object({
}), }),
appearance: z.object({ appearance: z.object({
backgroundSrc: z.string(), backgroundSrc: z.string(),
backgroundImageAttachment: z.enum(BackgroundImageAttachment),
backgroundImageSize: z.enum(BackgroundImageSize),
backgroundImageRepeat: z.enum(BackgroundImageRepeat),
primaryColor: z.custom<MantineColor>( primaryColor: z.custom<MantineColor>(
(value) => typeof value === 'string' && MANTINE_COLORS.includes(value) (value) => typeof value === 'string' && MANTINE_COLORS.includes(value)
), ),