mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-10 07:25:48 +01:00
✨ Add generic menu for tiles
This commit is contained in:
@@ -2,7 +2,14 @@
|
||||
"actions": {
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"ok": "Okay"
|
||||
"ok": "Okay",
|
||||
"edit": "Edit",
|
||||
"changePosition": "Change position",
|
||||
"remove": "Remove"
|
||||
},
|
||||
"sections": {
|
||||
"settings": "Settings",
|
||||
"dangerZone": "Danger zone"
|
||||
},
|
||||
"tip": "Tip: ",
|
||||
"time": {
|
||||
|
||||
@@ -15,7 +15,6 @@ import { useState } from 'react';
|
||||
import PingComponent from '../../modules/ping/PingModule';
|
||||
import { useConfig } from '../../tools/state';
|
||||
import { serviceItem } from '../../tools/types';
|
||||
import { TileMenu } from '../Dashboard/Menu/TileMenu';
|
||||
|
||||
const useStyles = createStyles((theme) => ({
|
||||
item: {
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
import { ActionIcon, Menu, Text } from '@mantine/core';
|
||||
import { openContextModal } from '@mantine/modals';
|
||||
import { showNotification } from '@mantine/notifications';
|
||||
import { IconCheck, IconEdit, IconMenu, IconTrash } from '@tabler/icons';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useConfigContext } from '../../../config/provider';
|
||||
import { useConfigStore } from '../../../config/store';
|
||||
import { useColorTheme } from '../../../tools/color';
|
||||
import { ServiceType } from '../../../types/service';
|
||||
|
||||
interface TileMenuProps {
|
||||
service: ServiceType;
|
||||
}
|
||||
|
||||
export const TileMenu = ({ service }: TileMenuProps) => {
|
||||
const { secondaryColor } = useColorTheme();
|
||||
const { t } = useTranslation('layout/app-shelf-menu');
|
||||
const updateConfig = useConfigStore((x) => x.updateConfig);
|
||||
const { name: configName } = useConfigContext();
|
||||
|
||||
return (
|
||||
<Menu withinPortal width={150} shadow="xl" withArrow radius="md" position="right">
|
||||
<Menu.Target>
|
||||
<ActionIcon>
|
||||
<IconMenu />
|
||||
</ActionIcon>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Label>{t('menu.labels.settings')}</Menu.Label>
|
||||
<Menu.Item
|
||||
color={secondaryColor}
|
||||
icon={<IconEdit />}
|
||||
onClick={() =>
|
||||
openContextModal({
|
||||
modal: 'changeTilePosition',
|
||||
innerProps: {
|
||||
type: 'service',
|
||||
tile: service,
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('menu.actions.edit')}
|
||||
</Menu.Item>
|
||||
<Menu.Label>{t('menu.labels.dangerZone')}</Menu.Label>
|
||||
<Menu.Item
|
||||
color="red"
|
||||
onClick={(e: any) => {
|
||||
if (!configName) {
|
||||
return;
|
||||
}
|
||||
updateConfig(configName, (previousConfig) => ({
|
||||
...previousConfig,
|
||||
services: previousConfig.services.filter((x) => x.id !== service.id),
|
||||
})).then(() => {
|
||||
showNotification({
|
||||
autoClose: 5000,
|
||||
title: (
|
||||
<Text>
|
||||
Service <b>{service.name}</b> removed successfully!
|
||||
</Text>
|
||||
),
|
||||
color: 'green',
|
||||
icon: <IconCheck />,
|
||||
message: undefined,
|
||||
});
|
||||
});
|
||||
}}
|
||||
icon={<IconTrash />}
|
||||
>
|
||||
{t('menu.actions.delete')}
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Card, Center, Stack, Text, Title } from '@mantine/core';
|
||||
import { Center, Stack, Text, Title } from '@mantine/core';
|
||||
import dayjs from 'dayjs';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useSetSafeInterval } from '../../../../tools/hooks/useSetSafeInterval';
|
||||
|
||||
57
src/components/Dashboard/Tiles/GenericTileMenu.tsx
Normal file
57
src/components/Dashboard/Tiles/GenericTileMenu.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import { ActionIcon, Menu } from '@mantine/core';
|
||||
import { IconDots, IconLayoutKanban, IconPencil, IconTrash } from '@tabler/icons';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useEditModeStore } from '../Views/useEditModeStore';
|
||||
|
||||
interface GenericTileMenuProps {
|
||||
handleClickEdit: () => void;
|
||||
handleClickChangePosition: () => void;
|
||||
handleClickDelete: () => void;
|
||||
displayEdit: boolean;
|
||||
}
|
||||
|
||||
export const GenericTileMenu = ({
|
||||
handleClickEdit,
|
||||
handleClickChangePosition,
|
||||
handleClickDelete,
|
||||
displayEdit,
|
||||
}: GenericTileMenuProps) => {
|
||||
const { t } = useTranslation('common');
|
||||
const isEditMode = useEditModeStore((x) => x.enabled);
|
||||
|
||||
if (!isEditMode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Menu withinPortal>
|
||||
<Menu.Target>
|
||||
<ActionIcon pos="absolute" top={4} right={4}>
|
||||
<IconDots />
|
||||
</ActionIcon>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown w={250}>
|
||||
<Menu.Label>{t('sections.settings')}</Menu.Label>
|
||||
{displayEdit && (
|
||||
<Menu.Item icon={<IconPencil size={16} stroke={1.5} />} onClick={handleClickEdit}>
|
||||
{t('actions.edit')}
|
||||
</Menu.Item>
|
||||
)}
|
||||
<Menu.Item
|
||||
icon={<IconLayoutKanban size={16} stroke={1.5} />}
|
||||
onClick={handleClickChangePosition}
|
||||
>
|
||||
{t('actions.changePosition')}
|
||||
</Menu.Item>
|
||||
<Menu.Label>{t('sections.dangerZone')}</Menu.Label>
|
||||
<Menu.Item
|
||||
color="red"
|
||||
icon={<IconTrash size={16} stroke={1.5} color="red" />}
|
||||
onClick={handleClickDelete}
|
||||
>
|
||||
{t('actions.remove')}
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Center,
|
||||
@@ -10,14 +9,10 @@ import {
|
||||
SelectItem,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
} from '@mantine/core';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { ContextModalProps } from '@mantine/modals';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { ClockIntegrationType, IntegrationsType } from '../../../types/integration';
|
||||
import { integrationModuleTranslationsMap } from './IntegrationsEditModal';
|
||||
import { TileBaseType } from '../../../types/tile';
|
||||
import {
|
||||
IconArrowsUpDown,
|
||||
IconCalendarTime,
|
||||
@@ -25,11 +20,14 @@ import {
|
||||
IconCloudRain,
|
||||
IconFileDownload,
|
||||
} from '@tabler/icons';
|
||||
import { ServiceIcon } from './Service/ServiceIcon';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { Tiles } from './tilesDefinitions';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useConfigContext } from '../../../config/provider';
|
||||
import { useConfigStore } from '../../../config/store';
|
||||
import { IntegrationsType } from '../../../types/integration';
|
||||
import { TileBaseType } from '../../../types/tile';
|
||||
import { integrationModuleTranslationsMap } from './IntegrationsEditModal';
|
||||
import { ServiceIcon } from './Service/ServiceIcon';
|
||||
import { Tiles } from './tilesDefinitions';
|
||||
|
||||
export type IntegrationChangePositionModalInnerProps = {
|
||||
integration: keyof IntegrationsType;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { ActionIcon, Menu, Title } from '@mantine/core';
|
||||
import { IconDots, IconLayoutKanban, IconPencil, IconTrash } from '@tabler/icons';
|
||||
import { Title } from '@mantine/core';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { openContextModalGeneric } from '../../../../tools/mantineModalManagerExtensions';
|
||||
import { IntegrationsType } from '../../../../types/integration';
|
||||
import { TileBaseType } from '../../../../types/tile';
|
||||
import { GenericTileMenu } from '../GenericTileMenu';
|
||||
import { IntegrationChangePositionModalInnerProps } from '../IntegrationChangePositionModal';
|
||||
import { IntegrationRemoveModalInnerProps } from '../IntegrationRemoveModal';
|
||||
import {
|
||||
@@ -65,34 +65,11 @@ export const IntegrationsMenu = <TIntegrationKey extends keyof IntegrationsType>
|
||||
};
|
||||
|
||||
return (
|
||||
<Menu withinPortal>
|
||||
<Menu.Target>
|
||||
<ActionIcon pos="absolute" top={4} right={4}>
|
||||
<IconDots />
|
||||
</ActionIcon>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown w={250}>
|
||||
<Menu.Label>Settings</Menu.Label>
|
||||
{options && (
|
||||
<Menu.Item icon={<IconPencil size={16} stroke={1.5} />} onClick={handleEditClick}>
|
||||
Edit
|
||||
</Menu.Item>
|
||||
)}
|
||||
<Menu.Item
|
||||
icon={<IconLayoutKanban size={16} stroke={1.5} />}
|
||||
onClick={handleChangeSizeClick}
|
||||
>
|
||||
Change position
|
||||
</Menu.Item>
|
||||
<Menu.Label>Danger zone</Menu.Label>
|
||||
<Menu.Item
|
||||
color="red"
|
||||
icon={<IconTrash size={16} stroke={1.5} color="red" />}
|
||||
onClick={handleDeleteClick}
|
||||
>
|
||||
Remove
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
<GenericTileMenu
|
||||
handleClickEdit={handleEditClick}
|
||||
handleClickChangePosition={handleChangeSizeClick}
|
||||
handleClickDelete={handleDeleteClick}
|
||||
displayEdit={options !== undefined}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
39
src/components/Dashboard/Tiles/Service/ServiceMenu.tsx
Normal file
39
src/components/Dashboard/Tiles/Service/ServiceMenu.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { openContextModalGeneric } from '../../../../tools/mantineModalManagerExtensions';
|
||||
import { ServiceType } from '../../../../types/service';
|
||||
import { GenericTileMenu } from '../GenericTileMenu';
|
||||
|
||||
interface TileMenuProps {
|
||||
service: ServiceType;
|
||||
}
|
||||
|
||||
export const ServiceMenu = ({ service }: TileMenuProps) => {
|
||||
const handleClickEdit = () => {
|
||||
openContextModalGeneric<{ service: ServiceType }>({
|
||||
modal: 'editService',
|
||||
size: 'xl',
|
||||
innerProps: {
|
||||
service,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleClickChangePosition = () => {
|
||||
openContextModalGeneric({
|
||||
modal: 'changeTilePosition',
|
||||
innerProps: {
|
||||
tile: service,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleClickDelete = () => {};
|
||||
|
||||
return (
|
||||
<GenericTileMenu
|
||||
handleClickEdit={handleClickEdit}
|
||||
handleClickChangePosition={handleClickChangePosition}
|
||||
handleClickDelete={handleClickDelete}
|
||||
displayEdit
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,11 +1,11 @@
|
||||
import { ActionIcon, Card, Center, Text, UnstyledButton } from '@mantine/core';
|
||||
import { Card, Center, Text, UnstyledButton } from '@mantine/core';
|
||||
import { NextLink } from '@mantine/next';
|
||||
import { createStyles } from '@mantine/styles';
|
||||
import { IconDots } from '@tabler/icons';
|
||||
import { ServiceType } from '../../../../types/service';
|
||||
import { useCardStyles } from '../../../layout/useCardStyles';
|
||||
import { useEditModeStore } from '../../Views/useEditModeStore';
|
||||
import { BaseTileProps } from '../type';
|
||||
import { ServiceMenu } from './ServiceMenu';
|
||||
|
||||
interface ServiceTileProps extends BaseTileProps {
|
||||
service: ServiceType;
|
||||
@@ -26,6 +26,7 @@ export const ServiceTile = ({ className, service }: ServiceTileProps) => {
|
||||
{service.name}
|
||||
</Text>
|
||||
<Center style={{ height: '75%', flex: 1 }}>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img className={classes.image} src={service.appearance.iconUrl} alt="" />
|
||||
</Center>
|
||||
</>
|
||||
@@ -34,6 +35,11 @@ export const ServiceTile = ({ className, service }: ServiceTileProps) => {
|
||||
return (
|
||||
<Card className={cx(className, cardClass)} withBorder radius="lg" shadow="md">
|
||||
{/* TODO: add service menu */}
|
||||
|
||||
<div style={{ position: 'absolute', top: 10, right: 10 }}>
|
||||
<ServiceMenu service={service} />
|
||||
</div>
|
||||
|
||||
{!service.url || isEditMode ? (
|
||||
<UnstyledButton
|
||||
className={classes.button}
|
||||
@@ -57,8 +63,7 @@ export const ServiceTile = ({ className, service }: ServiceTileProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const useStyles = createStyles((theme, _params, getRef) => {
|
||||
return {
|
||||
const useStyles = createStyles((theme, _params, getRef) => ({
|
||||
image: {
|
||||
ref: getRef('image'),
|
||||
maxHeight: '80%',
|
||||
@@ -84,5 +89,4 @@ const useStyles = createStyles((theme, _params, getRef) => {
|
||||
// TODO: add styles for service name when hovering card
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Box, createStyles, Group, Header as MantineHeader, Text } from '@mantine/core';
|
||||
import { openContextModal } from '@mantine/modals';
|
||||
import { Box, createStyles, Group, Header as MantineHeader } from '@mantine/core';
|
||||
import { useConfigContext } from '../../../config/provider';
|
||||
import { Logo } from '../Logo';
|
||||
import { useCardStyles } from '../useCardStyles';
|
||||
@@ -23,21 +22,6 @@ export function Header(props: any) {
|
||||
<Logo />
|
||||
</Box>
|
||||
<Group position="right" style={{ maxWidth: 'none' }} noWrap>
|
||||
<Text
|
||||
onClick={() => {
|
||||
openContextModal({
|
||||
modal: 'changeTilePosition',
|
||||
title: 'Change tile position',
|
||||
innerProps: {
|
||||
tile: config?.services[0],
|
||||
},
|
||||
});
|
||||
}}
|
||||
variant="link"
|
||||
>
|
||||
Test
|
||||
</Text>
|
||||
|
||||
<Search />
|
||||
<AddElementAction />
|
||||
<ToggleEditModeAction />
|
||||
|
||||
Reference in New Issue
Block a user