Files
Homarr/src/components/Dashboard/Modals/EditAppModal/EditAppModal.tsx

240 lines
7.3 KiB
TypeScript
Raw Normal View History

import { Alert, Button, Group, Popover, Stack, Tabs, Text, ThemeIcon } from '@mantine/core';
2022-12-04 21:19:40 +01:00
import { useForm } from '@mantine/form';
import { useDisclosure } from '@mantine/hooks';
import { ContextModalProps } from '@mantine/modals';
2022-12-08 20:42:12 +01:00
import {
IconAccessPoint,
IconAdjustments,
IconAlertTriangle,
IconBrush,
IconClick,
IconPlug,
} from '@tabler/icons-react';
2022-12-04 21:19:40 +01:00
import { useTranslation } from 'next-i18next';
import { useState } from 'react';
2023-07-21 18:08:40 +09:00
import { useConfigContext } from '../../../../config/provider';
import { useConfigStore } from '../../../../config/store';
import { AppType } from '../../../../types/app';
import { DebouncedImage } from '../../../IconSelector/DebouncedImage';
import { useEditModeStore } from '../../Views/useEditModeStore';
2022-12-04 21:19:40 +01:00
import { AppearanceTab } from './Tabs/AppereanceTab/AppereanceTab';
import { BehaviourTab } from './Tabs/BehaviourTab/BehaviourTab';
import { GeneralTab } from './Tabs/GeneralTab/GeneralTab';
import { IntegrationTab } from './Tabs/IntegrationTab/IntegrationTab';
import { NetworkTab } from './Tabs/NetworkTab/NetworkTab';
import { EditAppModalTab } from './Tabs/type';
const appUrlRegex =
'(https?://(?:www.|(?!www))\\[?[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\]?.[^\\s]{2,}|www.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9].[^\\s]{2,}|https?://(?:www.|(?!www))\\[?[a-zA-Z0-9]+\\]?.[^\\s]{2,}|www.[a-zA-Z0-9]+.[^\\s]{2,})';
2022-12-04 21:19:40 +01:00
export const EditAppModal = ({
2022-12-04 21:19:40 +01:00
context,
id,
innerProps,
}: ContextModalProps<{ app: AppType; allowAppNamePropagation: boolean }>) => {
2022-12-20 11:34:07 +09:00
const { t } = useTranslation(['layout/modals/add-app', 'common']);
const { name: configName, config } = useConfigContext();
const updateConfig = useConfigStore((store) => store.updateConfig);
const { enabled: isEditMode } = useEditModeStore();
const [allowAppNamePropagation, setAllowAppNamePropagation] = useState<boolean>(
innerProps.allowAppNamePropagation
);
2022-12-04 21:19:40 +01:00
const form = useForm<AppType>({
initialValues: innerProps.app,
validate: {
name: (name) => (!name ? 'Name is required' : null),
url: (url) => {
if (!url) {
return 'Url is required';
}
if (!url.match(appUrlRegex)) {
return 'Value is not a valid url';
}
return null;
},
2022-12-08 20:42:12 +01:00
appearance: {
iconUrl: (url: string) => {
if (url.length < 1) {
return 'This field is required';
}
return null;
2022-12-08 20:42:12 +01:00
},
},
behaviour: {
2022-12-22 11:29:51 +09:00
externalUrl: (url: string) => {
2022-12-08 20:42:12 +01:00
if (url === undefined || url.length < 1) {
return null;
}
if (!url.match(appUrlRegex)) {
2022-12-08 20:42:12 +01:00
return 'Uri override is not a valid uri';
}
2022-12-08 20:42:12 +01:00
return null;
},
},
},
validateInputOnChange: true,
2022-12-04 21:19:40 +01:00
});
const onSubmit = (values: AppType) => {
if (!configName) {
return;
}
updateConfig(
configName,
(previousConfig) => ({
...previousConfig,
apps: [
...previousConfig.apps.filter((x) => x.id !== values.id),
{
...values,
},
],
}),
true,
!isEditMode
);
// also close the parent modal
context.closeAll();
2022-12-04 21:19:40 +01:00
};
const [activeTab, setActiveTab] = useState<EditAppModalTab>('general');
const closeModal = () => {
2022-12-04 21:19:40 +01:00
context.closeModal(id);
};
2022-12-08 20:42:12 +01:00
const validationErrors = Object.keys(form.errors);
const ValidationErrorIndicator = ({ keys }: { keys: string[] }) => {
const relevantErrors = validationErrors.filter((x) => keys.includes(x));
return (
2022-12-11 21:01:13 +01:00
<ThemeIcon
opacity={relevantErrors.length === 0 ? 0 : 1}
color="red"
size={18}
variant="light"
>
2022-12-08 20:42:12 +01:00
<IconAlertTriangle size={15} />
</ThemeIcon>
);
};
2022-12-04 21:19:40 +01:00
return (
<>
{configName === undefined ||
(config === undefined && (
<Alert color="red">
There was an unexpected problem loading the configuration. Functionality might be
restricted. Please report this incident.
</Alert>
))}
2022-12-04 21:19:40 +01:00
<Stack spacing={0} align="center" my="lg">
<DebouncedImage src={form.values.appearance.iconUrl} width={120} height={120} />
2022-12-04 21:19:40 +01:00
<Text align="center" weight="bold" size="lg" mt="md">
{form.values.name ?? 'New App'}
2022-12-04 21:19:40 +01:00
</Text>
</Stack>
2022-12-08 20:42:12 +01:00
2022-12-04 21:19:40 +01:00
<form onSubmit={form.onSubmit(onSubmit)}>
2022-12-20 11:34:07 +09:00
<Stack
justify="space-between"
style={{
minHeight: 300,
}}
>
2022-12-20 11:34:07 +09:00
<Tabs
value={activeTab}
onTabChange={(tab) => setActiveTab(tab as EditAppModalTab)}
defaultValue="general"
radius="md"
>
<Tabs.List grow>
<Tabs.Tab
rightSection={<ValidationErrorIndicator keys={['name', 'url']} />}
icon={<IconAdjustments size={14} />}
value="general"
>
{t('tabs.general')}
</Tabs.Tab>
<Tabs.Tab
2022-12-22 11:29:51 +09:00
rightSection={<ValidationErrorIndicator keys={['behaviour.externalUrl']} />}
2022-12-20 11:34:07 +09:00
icon={<IconClick size={14} />}
value="behaviour"
>
{t('tabs.behaviour')}
</Tabs.Tab>
<Tabs.Tab
rightSection={<ValidationErrorIndicator keys={[]} />}
icon={<IconAccessPoint size={14} />}
value="network"
>
{t('tabs.network')}
</Tabs.Tab>
<Tabs.Tab
rightSection={<ValidationErrorIndicator keys={['appearance.iconUrl']} />}
icon={<IconBrush size={14} />}
value="appearance"
>
{t('tabs.appearance')}
</Tabs.Tab>
<Tabs.Tab
rightSection={<ValidationErrorIndicator keys={[]} />}
icon={<IconPlug size={14} />}
value="integration"
>
{t('tabs.integration')}
</Tabs.Tab>
</Tabs.List>
<GeneralTab form={form} openTab={(targetTab) => setActiveTab(targetTab)} />
<BehaviourTab form={form} />
<NetworkTab form={form} />
<AppearanceTab
form={form}
disallowAppNameProgagation={() => setAllowAppNamePropagation(false)}
allowAppNamePropagation={allowAppNamePropagation}
/>
<IntegrationTab form={form} />
</Tabs>
<Group position="right" mt="md">
<Button onClick={closeModal} px={50} variant="light" color="gray">
2022-12-20 16:54:22 +09:00
{t('common:cancel')}
2022-12-20 11:34:07 +09:00
</Button>
<SaveButton formIsValid={form.isValid()} />
2022-12-20 11:34:07 +09:00
</Group>
</Stack>
2022-12-04 21:19:40 +01:00
</form>
</>
);
};
const SaveButton = ({ formIsValid }: { formIsValid: boolean }) => {
const [opened, { close, open }] = useDisclosure(false);
const { t } = useTranslation(['layout/modals/add-app', 'common']);
return (
2023-01-10 21:42:56 +01:00
<Popover opened={opened && !formIsValid} width={300} withArrow withinPortal>
<Popover.Target>
<div onMouseEnter={open} onMouseLeave={close}>
<Button disabled={!formIsValid} px={50} type="submit">
{t('common:save')}
</Button>
</div>
</Popover.Target>
<Popover.Dropdown sx={{ pointerEvents: 'none' }}>{t('validation.popover')}</Popover.Dropdown>
</Popover>
);
};