mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-10 23:45:48 +01:00
🚧 Add board, Improve header
This commit is contained in:
@@ -74,7 +74,7 @@ model UserSettings {
|
|||||||
userId String
|
userId String
|
||||||
colorScheme String @default("environment") // environment, light, dark
|
colorScheme String @default("environment") // environment, light, dark
|
||||||
language String @default("en")
|
language String @default("en")
|
||||||
defaultDashboard String @default("default")
|
defaultBoard String @default("default")
|
||||||
searchTemplate String @default("https://google.com/search?q=%s")
|
searchTemplate String @default("https://google.com/search?q=%s")
|
||||||
openSearchInNewTab Boolean @default(true)
|
openSearchInNewTab Boolean @default(true)
|
||||||
disablePingPulse Boolean @default(false)
|
disablePingPulse Boolean @default(false)
|
||||||
|
|||||||
@@ -5,12 +5,16 @@ import { useConfigContext } from '../../config/provider';
|
|||||||
export function Background() {
|
export function Background() {
|
||||||
const { config } = useConfigContext();
|
const { config } = useConfigContext();
|
||||||
|
|
||||||
|
if (!config?.settings.customization.backgroundImageUrl) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Global
|
<Global
|
||||||
styles={{
|
styles={{
|
||||||
body: {
|
body: {
|
||||||
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: 'cover',
|
||||||
backgroundRepeat: 'no-repeat',
|
backgroundRepeat: 'no-repeat',
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { AppShell, createStyles } from '@mantine/core';
|
|||||||
|
|
||||||
import { useConfigContext } from '../../config/provider';
|
import { useConfigContext } from '../../config/provider';
|
||||||
import { Background } from './Background';
|
import { Background } from './Background';
|
||||||
|
import { Head } from './Meta/Head';
|
||||||
import { Header } from './header/Header';
|
import { Header } from './header/Header';
|
||||||
import { Head } from './header/Meta/Head';
|
|
||||||
|
|
||||||
const useStyles = createStyles(() => ({}));
|
const useStyles = createStyles(() => ({}));
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import NextHead from 'next/head';
|
import NextHead from 'next/head';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { useConfigContext } from '../../../../config/provider';
|
import { useConfigContext } from '../../../config/provider';
|
||||||
import { SafariStatusBarStyle } from './SafariStatusBarStyle';
|
import { SafariStatusBarStyle } from './SafariStatusBarStyle';
|
||||||
|
|
||||||
export function Head() {
|
export function Head() {
|
||||||
@@ -1,13 +1,19 @@
|
|||||||
import { AppShell, useMantineTheme } from '@mantine/core';
|
import { AppShell, clsx, useMantineTheme } from '@mantine/core';
|
||||||
|
import { useConfigContext } from '~/config/provider';
|
||||||
|
|
||||||
|
import { Background } from './Background';
|
||||||
|
import { Head } from './Meta/Head';
|
||||||
import { MainHeader } from './new-header/Header';
|
import { MainHeader } from './new-header/Header';
|
||||||
|
|
||||||
type MainLayoutProps = {
|
type MainLayoutProps = {
|
||||||
|
headerActions?: React.ReactNode;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MainLayout = ({ children }: MainLayoutProps) => {
|
export const MainLayout = ({ headerActions, children }: MainLayoutProps) => {
|
||||||
|
const { config } = useConfigContext();
|
||||||
const theme = useMantineTheme();
|
const theme = useMantineTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
styles={{
|
styles={{
|
||||||
@@ -15,9 +21,13 @@ export const MainLayout = ({ children }: MainLayoutProps) => {
|
|||||||
background: theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[1],
|
background: theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[1],
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
header={<MainHeader />}
|
header={<MainHeader headerActions={headerActions} />}
|
||||||
|
className="dashboard-app-shell"
|
||||||
>
|
>
|
||||||
|
<Head />
|
||||||
|
<Background />
|
||||||
{children}
|
{children}
|
||||||
|
<style>{clsx(config?.settings.customization.customCss)}</style>
|
||||||
</AppShell>
|
</AppShell>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,25 +9,29 @@ import {
|
|||||||
IconSun,
|
IconSun,
|
||||||
IconUserCog,
|
IconUserCog,
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { User } from 'next-auth';
|
import { User } from 'next-auth';
|
||||||
import { signOut, useSession } from 'next-auth/react';
|
import { signOut, useSession } from 'next-auth/react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
import { AboutModal } from '~/components/Dashboard/Modals/AboutModal/AboutModal';
|
import { AboutModal } from '~/components/Dashboard/Modals/AboutModal/AboutModal';
|
||||||
import { useColorScheme } from '~/hooks/use-colorscheme';
|
import { useColorScheme } from '~/hooks/use-colorscheme';
|
||||||
|
import { usePackageAttributesStore } from '~/tools/client/zustands/usePackageAttributesStore';
|
||||||
|
|
||||||
|
import { REPO_URL } from '../../../../data/constants';
|
||||||
|
|
||||||
export const AvatarMenu = () => {
|
export const AvatarMenu = () => {
|
||||||
const newVersionAvailable = '0.13.0';
|
|
||||||
const [aboutModalOpened, aboutModal] = useDisclosure(false);
|
const [aboutModalOpened, aboutModal] = useDisclosure(false);
|
||||||
const { data: sessionData } = useSession();
|
const { data: sessionData } = useSession();
|
||||||
const { colorScheme, toggleColorScheme } = useColorScheme();
|
const { colorScheme, toggleColorScheme } = useColorScheme();
|
||||||
|
const newVersionAvailable = useNewVersionAvailable();
|
||||||
|
|
||||||
const Icon = colorScheme === 'dark' ? IconSun : IconMoonStars;
|
const Icon = colorScheme === 'dark' ? IconSun : IconMoonStars;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<UnstyledButton>
|
<UnstyledButton>
|
||||||
<Menu>
|
<Menu width={192}>
|
||||||
<Menu.Target>
|
<Menu.Target>
|
||||||
<CurrentUserAvatar user={sessionData?.user ?? null} />
|
<CurrentUserAvatar user={sessionData?.user ?? null} />
|
||||||
</Menu.Target>
|
</Menu.Target>
|
||||||
@@ -88,6 +92,7 @@ export const AvatarMenu = () => {
|
|||||||
type CurrentUserAvatarProps = {
|
type CurrentUserAvatarProps = {
|
||||||
user: User | null;
|
user: User | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CurrentUserAvatar = forwardRef<HTMLDivElement, CurrentUserAvatarProps>(
|
const CurrentUserAvatar = forwardRef<HTMLDivElement, CurrentUserAvatarProps>(
|
||||||
({ user, ...others }, ref) => {
|
({ user, ...others }, ref) => {
|
||||||
const { primaryColor } = useMantineTheme();
|
const { primaryColor } = useMantineTheme();
|
||||||
@@ -99,3 +104,15 @@ const CurrentUserAvatar = forwardRef<HTMLDivElement, CurrentUserAvatarProps>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const useNewVersionAvailable = () => {
|
||||||
|
const { attributes } = usePackageAttributesStore();
|
||||||
|
const { data } = useQuery({
|
||||||
|
queryKey: ['github/latest'],
|
||||||
|
cacheTime: 1000 * 60 * 60 * 24,
|
||||||
|
staleTime: 1000 * 60 * 60 * 5,
|
||||||
|
queryFn: () =>
|
||||||
|
fetch(`https://api.github.com/repos/${REPO_URL}/releases/latest`).then((res) => res.json()),
|
||||||
|
});
|
||||||
|
return data?.tag_name > `v${attributes.packageVersion}` ? data?.tag_name : undefined;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,24 +1,8 @@
|
|||||||
import {
|
import { Box, Flex, Group, Header, Text, UnstyledButton, useMantineTheme } from '@mantine/core';
|
||||||
Anchor,
|
import { useMediaQuery } from '@mantine/hooks';
|
||||||
Avatar,
|
import { IconAlertTriangle } from '@tabler/icons-react';
|
||||||
Box,
|
|
||||||
Flex,
|
|
||||||
Group,
|
|
||||||
Header,
|
|
||||||
Menu,
|
|
||||||
Text,
|
|
||||||
TextInput,
|
|
||||||
UnstyledButton,
|
|
||||||
} from '@mantine/core';
|
|
||||||
import {
|
|
||||||
IconAlertTriangle,
|
|
||||||
IconDashboard,
|
|
||||||
IconLogout,
|
|
||||||
IconSun,
|
|
||||||
IconUserSearch,
|
|
||||||
} from '@tabler/icons-react';
|
|
||||||
import { signOut } from 'next-auth/react';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import { useScreenLargerThan } from '~/hooks/useScreenLargerThan';
|
||||||
|
|
||||||
import { Logo } from '../Logo';
|
import { Logo } from '../Logo';
|
||||||
import { AvatarMenu } from './AvatarMenu';
|
import { AvatarMenu } from './AvatarMenu';
|
||||||
@@ -27,21 +11,33 @@ import { Search } from './search';
|
|||||||
type MainHeaderProps = {
|
type MainHeaderProps = {
|
||||||
logoHref?: string;
|
logoHref?: string;
|
||||||
showExperimental?: boolean;
|
showExperimental?: boolean;
|
||||||
|
headerActions?: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MainHeader = ({ showExperimental = false, logoHref = '/' }: MainHeaderProps) => {
|
export const MainHeader = ({
|
||||||
const headerHeight = showExperimental ? 60 + 30 : 60;
|
showExperimental = false,
|
||||||
|
logoHref = '/',
|
||||||
|
headerActions,
|
||||||
|
}: MainHeaderProps) => {
|
||||||
|
const { breakpoints } = useMantineTheme();
|
||||||
|
const isSmallerThanMd = useMediaQuery(`(max-width: ${breakpoints.sm})`);
|
||||||
|
const experimentalHeaderNoteHeight = isSmallerThanMd ? 50 : 30;
|
||||||
|
const headerHeight = showExperimental ? 60 + experimentalHeaderNoteHeight : 60;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Header height={headerHeight} pb="sm" pt={0}>
|
<Header height={headerHeight} pb="sm" pt={0}>
|
||||||
<ExperimentalHeaderNote visible={showExperimental} />
|
<ExperimentalHeaderNote visible={showExperimental} height={experimentalHeaderNoteHeight} />
|
||||||
<Group spacing="xl" mt="xs" px="md" position="apart" noWrap>
|
<Group spacing="xl" mt="xs" px="md" position="apart" noWrap>
|
||||||
<UnstyledButton component={Link} href={logoHref}>
|
<UnstyledButton component={Link} href={logoHref} style={{ flex: 1 }}>
|
||||||
<Logo />
|
<Logo />
|
||||||
</UnstyledButton>
|
</UnstyledButton>
|
||||||
|
|
||||||
<Search />
|
<Search />
|
||||||
|
|
||||||
<Group noWrap>
|
<Group noWrap style={{ flex: 1 }} position="right">
|
||||||
|
<Group noWrap spacing={8}>
|
||||||
|
{headerActions}
|
||||||
|
</Group>
|
||||||
<AvatarMenu />
|
<AvatarMenu />
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
@@ -50,16 +46,17 @@ export const MainHeader = ({ showExperimental = false, logoHref = '/' }: MainHea
|
|||||||
};
|
};
|
||||||
|
|
||||||
type ExperimentalHeaderNoteProps = {
|
type ExperimentalHeaderNoteProps = {
|
||||||
|
height?: 30 | 50;
|
||||||
visible?: boolean;
|
visible?: boolean;
|
||||||
};
|
};
|
||||||
const ExperimentalHeaderNote = ({ visible = false }: ExperimentalHeaderNoteProps) => {
|
const ExperimentalHeaderNote = ({ visible = false, height = 30 }: ExperimentalHeaderNoteProps) => {
|
||||||
if (!visible) return null;
|
if (!visible) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box bg="red" h={30} p={3} px={6}>
|
<Box bg="red" h={height} p={3} px={6}>
|
||||||
<Flex h="100%" align="center" columnGap={7}>
|
<Flex h="100%" align="center" columnGap={7}>
|
||||||
<IconAlertTriangle color="white" size="1rem" />
|
<IconAlertTriangle color="white" size="1rem" style={{ minWidth: '1rem' }} />
|
||||||
<Text color="white">
|
<Text color="white" lineClamp={height === 30 ? 1 : 2}>
|
||||||
This is an experimental feature of Homarr. Please report any issues to the official Homarr
|
This is an experimental feature of Homarr. Please report any issues to the official Homarr
|
||||||
team.
|
team.
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -1,18 +1,244 @@
|
|||||||
import { Title } from '@mantine/core';
|
import { Button, ButtonProps, Text, Title, Tooltip } from '@mantine/core';
|
||||||
import { GetServerSideProps } from 'next';
|
import { useHotkeys, useWindowEvent } from '@mantine/hooks';
|
||||||
|
import { openContextModal } from '@mantine/modals';
|
||||||
|
import { hideNotification, showNotification } from '@mantine/notifications';
|
||||||
|
import { IconApps, IconBrandDocker, IconEditCircle, IconEditCircleOff } from '@tabler/icons-react';
|
||||||
|
import Consola from 'consola';
|
||||||
|
import { GetServerSideProps, InferGetServerSidePropsType } from 'next';
|
||||||
|
import { useSession } from 'next-auth/react';
|
||||||
|
import { SSRConfig, Trans, useTranslation } from 'next-i18next';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { Dashboard } from '~/components/Dashboard/Dashboard';
|
||||||
|
import { useEditModeStore } from '~/components/Dashboard/Views/useEditModeStore';
|
||||||
|
import { useNamedWrapperColumnCount } from '~/components/Dashboard/Wrappers/gridstack/store';
|
||||||
import { MainLayout } from '~/components/layout/main';
|
import { MainLayout } from '~/components/layout/main';
|
||||||
|
import { useCardStyles } from '~/components/layout/useCardStyles';
|
||||||
|
import { useInitConfig } from '~/config/init';
|
||||||
|
import { useConfigContext } from '~/config/provider';
|
||||||
|
import { getServerAuthSession } from '~/server/auth';
|
||||||
|
import { prisma } from '~/server/db';
|
||||||
|
import { getFrontendConfig } from '~/tools/config/getFrontendConfig';
|
||||||
|
import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations';
|
||||||
|
import { dashboardNamespaces } from '~/tools/server/translation-namespaces';
|
||||||
|
import { ConfigType } from '~/types/config';
|
||||||
|
import { api } from '~/utils/api';
|
||||||
|
|
||||||
|
export default function BoardPage({
|
||||||
|
config: initialConfig,
|
||||||
|
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
|
||||||
|
useInitConfig(initialConfig);
|
||||||
|
|
||||||
export default function BoardPage() {
|
|
||||||
return (
|
return (
|
||||||
<MainLayout>
|
<MainLayout headerActions={<HeaderActions />}>
|
||||||
<Title order={1}>BoardPage</Title>
|
<Dashboard />
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async () => {
|
type BoardGetServerSideProps = {
|
||||||
console.log('getServerSideProps');
|
config: ConfigType;
|
||||||
|
_nextI18Next?: SSRConfig['_nextI18Next'];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getServerSideProps: GetServerSideProps<BoardGetServerSideProps> = async (ctx) => {
|
||||||
|
const session = await getServerAuthSession(ctx);
|
||||||
|
const currentUserSettings = await prisma.userSettings.findFirst({
|
||||||
|
where: {
|
||||||
|
userId: session?.user?.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const translations = await getServerSideTranslations(
|
||||||
|
dashboardNamespaces,
|
||||||
|
ctx.locale,
|
||||||
|
ctx.req,
|
||||||
|
ctx.res
|
||||||
|
);
|
||||||
|
const boardName = currentUserSettings?.defaultBoard ?? 'default';
|
||||||
|
const config = await getFrontendConfig(boardName as string);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {},
|
props: {
|
||||||
|
config,
|
||||||
|
...translations,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const HeaderActions = () => {
|
||||||
|
const { data: sessionData } = useSession();
|
||||||
|
|
||||||
|
if (!sessionData?.user?.isAdmin) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DockerButton />
|
||||||
|
<ToggleEditModeButton />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DockerButton = () => {
|
||||||
|
const { t } = useTranslation('modules/docker');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip label={t('actionIcon.tooltip')}>
|
||||||
|
<HeaderActionButton component={Link} href="/docker">
|
||||||
|
<IconBrandDocker size={20} stroke={1.5} />
|
||||||
|
</HeaderActionButton>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type SpecificLinkProps = {
|
||||||
|
component: typeof Link;
|
||||||
|
href: string;
|
||||||
|
};
|
||||||
|
type SpecificButtonProps = {
|
||||||
|
onClick: HTMLButtonElement['onclick'];
|
||||||
|
};
|
||||||
|
type HeaderActionButtonProps = Omit<ButtonProps, 'variant' | 'className' | 'h' | 'w' | 'px'> &
|
||||||
|
(SpecificLinkProps | SpecificButtonProps);
|
||||||
|
|
||||||
|
const HeaderActionButton = ({ children, ...props }: HeaderActionButtonProps) => {
|
||||||
|
const { classes } = useCardStyles(true);
|
||||||
|
|
||||||
|
const buttonProps: ButtonProps = {
|
||||||
|
variant: 'default',
|
||||||
|
className: classes.card,
|
||||||
|
h: 38,
|
||||||
|
w: 38,
|
||||||
|
px: 0,
|
||||||
|
...props,
|
||||||
|
};
|
||||||
|
|
||||||
|
if ('component' in props) {
|
||||||
|
return (
|
||||||
|
<Button component={props.component} href={props.href} {...buttonProps}>
|
||||||
|
{children}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Button {...buttonProps}>{children}</Button>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const beforeUnloadEventText = 'Exit the edit mode to save your changes';
|
||||||
|
const editModeNotificationId = 'toggle-edit-mode';
|
||||||
|
|
||||||
|
const ToggleEditModeButton = () => {
|
||||||
|
const { enabled, toggleEditMode } = useEditModeStore();
|
||||||
|
const { classes } = useCardStyles(true);
|
||||||
|
const { config, name: configName } = useConfigContext();
|
||||||
|
const { mutateAsync: saveConfig } = api.config.save.useMutation();
|
||||||
|
const namedWrapperColumnCount = useNamedWrapperColumnCount();
|
||||||
|
const { t } = useTranslation(['layout/header/actions/toggle-edit-mode', 'common']);
|
||||||
|
const translatedSize =
|
||||||
|
namedWrapperColumnCount !== null
|
||||||
|
? t(`common:breakPoints.${namedWrapperColumnCount}`)
|
||||||
|
: t('common:loading');
|
||||||
|
|
||||||
|
useHotkeys([['mod+E', toggleEditMode]]);
|
||||||
|
|
||||||
|
useWindowEvent('beforeunload', (event: BeforeUnloadEvent) => {
|
||||||
|
if (enabled) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
event.returnValue = beforeUnloadEventText;
|
||||||
|
return beforeUnloadEventText;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
const save = async () => {
|
||||||
|
toggleEditMode();
|
||||||
|
if (!config || !configName) return;
|
||||||
|
await saveConfig({ name: configName, config });
|
||||||
|
Consola.log('Saved config to server', configName);
|
||||||
|
hideNotification(editModeNotificationId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const enableEditMode = () => {
|
||||||
|
toggleEditMode();
|
||||||
|
showNotification({
|
||||||
|
styles: (theme) => ({
|
||||||
|
root: {
|
||||||
|
backgroundColor: theme.colors.orange[7],
|
||||||
|
borderColor: theme.colors.orange[7],
|
||||||
|
|
||||||
|
'&::before': { backgroundColor: theme.white },
|
||||||
|
},
|
||||||
|
title: { color: theme.white },
|
||||||
|
description: { color: theme.white },
|
||||||
|
closeButton: {
|
||||||
|
color: theme.white,
|
||||||
|
'&:hover': { backgroundColor: theme.colors.orange[7] },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
radius: 'md',
|
||||||
|
id: 'toggle-edit-mode',
|
||||||
|
autoClose: 10000,
|
||||||
|
title: (
|
||||||
|
<Title order={4}>
|
||||||
|
<Trans
|
||||||
|
i18nKey="layout/header/actions/toggle-edit-mode:popover.title"
|
||||||
|
values={{ size: translatedSize }}
|
||||||
|
components={{
|
||||||
|
1: (
|
||||||
|
<Text
|
||||||
|
component="a"
|
||||||
|
style={{ color: 'inherit', textDecoration: 'underline' }}
|
||||||
|
href="https://homarr.dev/docs/customizations/layout"
|
||||||
|
target="_blank"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Title>
|
||||||
|
),
|
||||||
|
message: <Trans i18nKey="layout/header/actions/toggle-edit-mode:popover.text" />,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (enabled) {
|
||||||
|
return (
|
||||||
|
<Button.Group>
|
||||||
|
<Tooltip label={t('button.disabled')}>
|
||||||
|
<HeaderActionButton onClick={save}>
|
||||||
|
<IconEditCircleOff size={20} stroke={1.5} />
|
||||||
|
</HeaderActionButton>
|
||||||
|
</Tooltip>
|
||||||
|
<AddElementButton />
|
||||||
|
</Button.Group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Tooltip label={t('button.disabled')}>
|
||||||
|
<HeaderActionButton onClick={enableEditMode}>
|
||||||
|
<IconEditCircle size={20} stroke={1.5} />
|
||||||
|
</HeaderActionButton>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AddElementButton = () => {
|
||||||
|
const { t } = useTranslation('layout/element-selector/selector');
|
||||||
|
const { classes } = useCardStyles(true);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip label={t('actionIcon.tooltip')}>
|
||||||
|
<HeaderActionButton
|
||||||
|
onClick={() =>
|
||||||
|
openContextModal({
|
||||||
|
modal: 'selectElement',
|
||||||
|
title: t('modal.title'),
|
||||||
|
size: 'xl',
|
||||||
|
innerProps: {},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconApps size={20} stroke={1.5} />
|
||||||
|
</HeaderActionButton>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user