mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-10 15:35:55 +01:00
✨ Add propagation for service name to service icon
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { Alert, Button, createStyles, Group, Stack, Tabs, Text, ThemeIcon } from '@mantine/core';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { ContextModalProps } from '@mantine/modals';
|
||||
import { hideNotification, showNotification } from '@mantine/notifications';
|
||||
import {
|
||||
IconAccessPoint,
|
||||
IconAdjustments,
|
||||
@@ -11,7 +10,6 @@ import {
|
||||
IconPlug,
|
||||
} from '@tabler/icons';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import Image from 'next/image';
|
||||
import { useState } from 'react';
|
||||
import { useConfigContext } from '../../../../config/provider';
|
||||
import { useConfigStore } from '../../../../config/store';
|
||||
@@ -31,11 +29,13 @@ export const EditServiceModal = ({
|
||||
context,
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<{ service: ServiceType }>) => {
|
||||
}: ContextModalProps<{ service: ServiceType; allowServiceNamePropagation: boolean }>) => {
|
||||
const { t } = useTranslation();
|
||||
const { classes } = useStyles();
|
||||
const { name: configName, config } = useConfigContext();
|
||||
const updateConfig = useConfigStore((store) => store.updateConfig);
|
||||
const [allowServiceNamePropagation, setAllowServiceNamePropagation] = useState<boolean>(
|
||||
innerProps.allowServiceNamePropagation
|
||||
);
|
||||
|
||||
const form = useForm<ServiceType>({
|
||||
initialValues: innerProps.service,
|
||||
@@ -94,29 +94,7 @@ export const EditServiceModal = ({
|
||||
|
||||
const [activeTab, setActiveTab] = useState<EditServiceModalTab>('general');
|
||||
|
||||
const tryCloseModal = () => {
|
||||
if (form.isDirty()) {
|
||||
showNotification({
|
||||
id: 'unsaved-edit-service-modal-changes',
|
||||
title: 'You have unsaved changes',
|
||||
message: (
|
||||
<Stack>
|
||||
<Text color="dimmed">If you close, your changes will be discarded and not saved.</Text>
|
||||
<Button
|
||||
onClick={() => {
|
||||
context.closeModal(id);
|
||||
hideNotification('unsaved-edit-service-modal-changes');
|
||||
}}
|
||||
variant="light"
|
||||
>
|
||||
Close anyway
|
||||
</Button>
|
||||
</Stack>
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const closeModal = () => {
|
||||
context.closeModal(id);
|
||||
};
|
||||
|
||||
@@ -196,12 +174,16 @@ export const EditServiceModal = ({
|
||||
<GeneralTab form={form} openTab={(targetTab) => setActiveTab(targetTab)} />
|
||||
<BehaviourTab form={form} />
|
||||
<NetworkTab form={form} />
|
||||
<AppearanceTab form={form} />
|
||||
<AppearanceTab
|
||||
form={form}
|
||||
disallowServiceNameProgagation={() => setAllowServiceNamePropagation(false)}
|
||||
allowServiceNamePropagation={allowServiceNamePropagation}
|
||||
/>
|
||||
<IntegrationTab form={form} />
|
||||
</Tabs>
|
||||
|
||||
<Group position="right" mt={100}>
|
||||
<Button onClick={tryCloseModal} px={50} variant="light" color="gray">
|
||||
<Button onClick={closeModal} px={50} variant="light" color="gray">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button disabled={!form.isValid()} px={50} type="submit">
|
||||
|
||||
@@ -7,9 +7,15 @@ import { IconSelector } from './IconSelector/IconSelector';
|
||||
|
||||
interface AppearanceTabProps {
|
||||
form: UseFormReturnType<ServiceType, (values: ServiceType) => ServiceType>;
|
||||
disallowServiceNameProgagation: () => void;
|
||||
allowServiceNamePropagation: boolean;
|
||||
}
|
||||
|
||||
export const AppearanceTab = ({ form }: AppearanceTabProps) => {
|
||||
export const AppearanceTab = ({
|
||||
form,
|
||||
disallowServiceNameProgagation,
|
||||
allowServiceNamePropagation,
|
||||
}: AppearanceTabProps) => {
|
||||
const { t } = useTranslation('');
|
||||
const { classes } = useStyles();
|
||||
|
||||
@@ -28,13 +34,16 @@ export const AppearanceTab = ({ form }: AppearanceTabProps) => {
|
||||
{...form.getInputProps('appearance.iconUrl')}
|
||||
/>
|
||||
<IconSelector
|
||||
onChange={(item) =>
|
||||
onChange={(item) => {
|
||||
form.setValues({
|
||||
appearance: {
|
||||
iconUrl: item.url,
|
||||
},
|
||||
})
|
||||
}
|
||||
});
|
||||
disallowServiceNameProgagation();
|
||||
}}
|
||||
allowServiceNamePropagation={allowServiceNamePropagation}
|
||||
form={form}
|
||||
/>
|
||||
</Flex>
|
||||
</Tabs.Panel>
|
||||
|
||||
@@ -13,22 +13,32 @@ import {
|
||||
TextInput,
|
||||
Title,
|
||||
} from '@mantine/core';
|
||||
import { UseFormReturnType } from '@mantine/form';
|
||||
import { useDebouncedValue } from '@mantine/hooks';
|
||||
import { IconSearch, IconX } from '@tabler/icons';
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ICON_PICKER_SLICE_LIMIT } from '../../../../../../../../data/constants';
|
||||
import { useRepositoryIconsQuery } from '../../../../../../../tools/hooks/useRepositoryIconsQuery';
|
||||
import { IconSelectorItem } from '../../../../../../../types/iconSelector/iconSelectorItem';
|
||||
import { WalkxcodeRepositoryIcon } from '../../../../../../../types/iconSelector/repositories/walkxcodeIconRepository';
|
||||
import { ServiceType } from '../../../../../../../types/service';
|
||||
|
||||
interface IconSelectorProps {
|
||||
form: UseFormReturnType<ServiceType, (values: ServiceType) => ServiceType>;
|
||||
onChange: (icon: IconSelectorItem) => void;
|
||||
allowServiceNamePropagation: boolean;
|
||||
}
|
||||
|
||||
export const IconSelector = ({ onChange }: IconSelectorProps) => {
|
||||
export const IconSelector = ({
|
||||
onChange,
|
||||
allowServiceNamePropagation,
|
||||
form,
|
||||
}: IconSelectorProps) => {
|
||||
const { data, isLoading } = useRepositoryIconsQuery<WalkxcodeRepositoryIcon>({
|
||||
url: 'https://api.github.com/repos/walkxcode/Dashboard-Icons/contents/png',
|
||||
converter: (item) => ({
|
||||
url: `https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${item.name}`,
|
||||
fileName: item.name,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -39,8 +49,7 @@ export const IconSelector = ({ onChange }: IconSelectorProps) => {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
const replaceCharacters = (value: string) =>
|
||||
value.toLowerCase().replaceAll(' ', '').replaceAll('-', '');
|
||||
const replaceCharacters = (value: string) => value.toLowerCase().replaceAll('', '-');
|
||||
|
||||
const filteredItems = searchTerm
|
||||
? data.filter((x) => replaceCharacters(x.url).includes(replaceCharacters(searchTerm)))
|
||||
@@ -49,6 +58,24 @@ export const IconSelector = ({ onChange }: IconSelectorProps) => {
|
||||
const isTruncated =
|
||||
slicedFilteredItems.length > 0 && slicedFilteredItems.length !== filteredItems.length;
|
||||
|
||||
const [debouncedValue] = useDebouncedValue(form.values.name, 500);
|
||||
|
||||
useEffect(() => {
|
||||
if (allowServiceNamePropagation !== true) {
|
||||
return;
|
||||
}
|
||||
|
||||
const matchingDebouncedIcon = data.find(
|
||||
(x) => replaceCharacters(x.fileName.split('.')[0]) === replaceCharacters(debouncedValue)
|
||||
);
|
||||
|
||||
if (!matchingDebouncedIcon) {
|
||||
return;
|
||||
}
|
||||
|
||||
form.setFieldValue('appearance.iconUrl', matchingDebouncedIcon.url);
|
||||
}, [debouncedValue]);
|
||||
|
||||
return (
|
||||
<Popover width={310}>
|
||||
<Popover.Target>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Group, Tabs, Text, TextInput } from '@mantine/core';
|
||||
import { Tabs, Text, TextInput } from '@mantine/core';
|
||||
import { UseFormReturnType } from '@mantine/form';
|
||||
import { IconCursorText, IconLink } from '@tabler/icons';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
@@ -27,44 +27,47 @@ export const AvailableElementTypes = ({
|
||||
name="Service"
|
||||
icon={<IconBox size={40} strokeWidth={1.3} />}
|
||||
onClick={() => {
|
||||
openContextModalGeneric<{ service: ServiceType }>({
|
||||
modal: 'editService',
|
||||
innerProps: {
|
||||
service: {
|
||||
id: uuidv4(),
|
||||
name: 'Your service',
|
||||
url: 'https://homarr.dev',
|
||||
appearance: {
|
||||
iconUrl: '/imgs/logo/logo.png',
|
||||
},
|
||||
network: {
|
||||
enabledStatusChecker: false,
|
||||
okStatus: [],
|
||||
},
|
||||
behaviour: {
|
||||
isOpeningNewTab: true,
|
||||
onClickUrl: '',
|
||||
},
|
||||
area: {
|
||||
type: 'sidebar',
|
||||
properties: {
|
||||
location: 'right',
|
||||
},
|
||||
},
|
||||
shape: {
|
||||
location: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
size: {
|
||||
height: 1,
|
||||
width: 1,
|
||||
openContextModalGeneric<{ service: ServiceType; allowServiceNamePropagation: boolean }>(
|
||||
{
|
||||
modal: 'editService',
|
||||
innerProps: {
|
||||
service: {
|
||||
id: uuidv4(),
|
||||
name: 'Your service',
|
||||
url: 'https://homarr.dev',
|
||||
appearance: {
|
||||
iconUrl: '/imgs/logo/logo.png',
|
||||
},
|
||||
network: {
|
||||
enabledStatusChecker: false,
|
||||
okStatus: [],
|
||||
},
|
||||
behaviour: {
|
||||
isOpeningNewTab: true,
|
||||
onClickUrl: '',
|
||||
},
|
||||
area: {
|
||||
type: 'sidebar',
|
||||
properties: {
|
||||
location: 'right',
|
||||
},
|
||||
},
|
||||
shape: {
|
||||
location: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
size: {
|
||||
height: 1,
|
||||
width: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
allowServiceNamePropagation: true,
|
||||
},
|
||||
},
|
||||
size: 'xl',
|
||||
});
|
||||
size: 'xl',
|
||||
}
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<ElementItem
|
||||
|
||||
@@ -8,11 +8,12 @@ interface TileMenuProps {
|
||||
|
||||
export const ServiceMenu = ({ service }: TileMenuProps) => {
|
||||
const handleClickEdit = () => {
|
||||
openContextModalGeneric<{ service: ServiceType }>({
|
||||
openContextModalGeneric<{ service: ServiceType; allowServiceNamePropagation: boolean }>({
|
||||
modal: 'editService',
|
||||
size: 'xl',
|
||||
innerProps: {
|
||||
service,
|
||||
allowServiceNamePropagation: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user