From d7bec26ee21b448acba66473e685e6e6b821cc8c Mon Sep 17 00:00:00 2001 From: Manuel Ruwe Date: Mon, 5 Dec 2022 21:43:47 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20icon=20picker=20for=20service?= =?UTF-8?q?=20icon?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Meier Lukas --- data/constants.ts | 1 + .../Modals/EditService/EditServiceModal.tsx | 6 +- .../Tabs/AppereanceTab/AppereanceTab.tsx | 36 ++++-- .../IconSelector/IconSelector.tsx | 108 ++++++++++++++++++ .../Tabs/BehaviourTab/BehaviourTab.tsx | 24 +--- src/tools/hooks/useRepositoryIconsQuery.ts | 26 +++++ src/types/iconSelector/iconSelectorItem.ts | 3 + .../repositories/walkxcodeIconRepository.ts | 3 + 8 files changed, 172 insertions(+), 35 deletions(-) create mode 100644 src/components/Dashboard/Modals/EditService/Tabs/AppereanceTab/IconSelector/IconSelector.tsx create mode 100644 src/tools/hooks/useRepositoryIconsQuery.ts create mode 100644 src/types/iconSelector/iconSelectorItem.ts create mode 100644 src/types/iconSelector/repositories/walkxcodeIconRepository.ts diff --git a/data/constants.ts b/data/constants.ts index 8ca22cd0b..1db7f949d 100644 --- a/data/constants.ts +++ b/data/constants.ts @@ -1,2 +1,3 @@ export const REPO_URL = 'ajnart/homarr'; export const CURRENT_VERSION = 'v0.10.7'; +export const ICON_PICKER_SLICE_LIMIT = 36; diff --git a/src/components/Dashboard/Modals/EditService/EditServiceModal.tsx b/src/components/Dashboard/Modals/EditService/EditServiceModal.tsx index ba2f86161..7eb11d9ba 100644 --- a/src/components/Dashboard/Modals/EditService/EditServiceModal.tsx +++ b/src/components/Dashboard/Modals/EditService/EditServiceModal.tsx @@ -1,7 +1,7 @@ -import Image from 'next/image'; import { Button, createStyles, Group, Stack, Tabs, Text } from '@mantine/core'; import { useForm } from '@mantine/form'; -import { closeModal, ContextModalProps } from '@mantine/modals'; +import { ContextModalProps } from '@mantine/modals'; +import { hideNotification, showNotification } from '@mantine/notifications'; import { IconAccessPoint, IconAdjustments, @@ -12,7 +12,7 @@ import { IconPlug, } from '@tabler/icons'; import { useTranslation } from 'next-i18next'; -import { hideNotification, showNotification } from '@mantine/notifications'; +import Image from 'next/image'; import { ServiceType } from '../../../../types/service'; import { AppearanceTab } from './Tabs/AppereanceTab/AppereanceTab'; import { BehaviourTab } from './Tabs/BehaviourTab/BehaviourTab'; diff --git a/src/components/Dashboard/Modals/EditService/Tabs/AppereanceTab/AppereanceTab.tsx b/src/components/Dashboard/Modals/EditService/Tabs/AppereanceTab/AppereanceTab.tsx index 1066a28df..048c2ed8f 100644 --- a/src/components/Dashboard/Modals/EditService/Tabs/AppereanceTab/AppereanceTab.tsx +++ b/src/components/Dashboard/Modals/EditService/Tabs/AppereanceTab/AppereanceTab.tsx @@ -1,8 +1,9 @@ -import { Tabs, TextInput, createStyles } from '@mantine/core'; +import { createStyles, Flex, Tabs, TextInput } from '@mantine/core'; import { UseFormReturnType } from '@mantine/form'; import { IconPhoto } from '@tabler/icons'; import { useTranslation } from 'next-i18next'; import { ServiceType } from '../../../../../../types/service'; +import { IconSelector } from './IconSelector/IconSelector'; interface AppearanceTabProps { form: UseFormReturnType ServiceType>; @@ -24,20 +25,35 @@ export const AppearanceTab = ({ form }: AppearanceTabProps) => { return ( - } - label="Service Icon" - variant="default" - defaultValue={form.values.appearance.iconUrl} - {...form.getInputProps('appearance.iconUrl')} - withAsterisk - required - /> + + } + label="Service Icon" + variant="default" + withAsterisk + required + {...form.getInputProps('appearance.iconUrl')} + /> + + form.setValues({ + appearance: { + iconUrl: item.url, + }, + }) + } + /> + ); }; const useStyles = createStyles(() => ({ + textInput: { + flexGrow: 1, + }, iconImage: { objectFit: 'contain', width: 20, diff --git a/src/components/Dashboard/Modals/EditService/Tabs/AppereanceTab/IconSelector/IconSelector.tsx b/src/components/Dashboard/Modals/EditService/Tabs/AppereanceTab/IconSelector/IconSelector.tsx new file mode 100644 index 000000000..e6cbc82e9 --- /dev/null +++ b/src/components/Dashboard/Modals/EditService/Tabs/AppereanceTab/IconSelector/IconSelector.tsx @@ -0,0 +1,108 @@ +/* eslint-disable @next/next/no-img-element */ +import { + ActionIcon, + createStyles, + Divider, + Flex, + Loader, + Popover, + ScrollArea, + Stack, + Text, + TextInput, + Title, +} from '@mantine/core'; +import { IconFlame, IconSearch, IconX } from '@tabler/icons'; +import { 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'; + +interface IconSelectorProps { + onChange: (icon: IconSelectorItem) => void; +} + +export const IconSelector = ({ onChange }: IconSelectorProps) => { + const { data, isLoading } = useRepositoryIconsQuery({ + 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}`, + }), + }); + + const [searchTerm, setSearchTerm] = useState(''); + const { classes } = useStyles(); + + if (isLoading || !data) { + return ; + } + + const filteredItems = searchTerm ? data.filter((x) => x.url.includes(searchTerm)) : data; + const slicedFilteredItems = filteredItems.slice(0, ICON_PICKER_SLICE_LIMIT); + const isTruncated = + slicedFilteredItems.length > 0 && slicedFilteredItems.length !== filteredItems.length; + + return ( + + + + + + + + + setSearchTerm(event.currentTarget.value)} + placeholder="Search for icons..." + variant="filled" + rightSection={ + setSearchTerm('')}> + + + } + /> + + + + {slicedFilteredItems.map((item) => ( + onChange(item)} size={40} p={3}> + icon from repository + + ))} + + + {isTruncated && ( + + + + + Search is limited to {ICON_PICKER_SLICE_LIMIT} icons + + + To keep things snappy and fast, the search is limited to {ICON_PICKER_SLICE_LIMIT}{' '} + icons. Use the search box to find more icons. + + + )} + + + + + ); +}; + +const useStyles = createStyles(() => ({ + flameIcon: { + margin: '0 auto', + }, + icon: { + width: '100%', + height: '100%', + objectFit: 'contain', + }, + actionIcon: { + alignSelf: 'end', + }, +})); diff --git a/src/components/Dashboard/Modals/EditService/Tabs/BehaviourTab/BehaviourTab.tsx b/src/components/Dashboard/Modals/EditService/Tabs/BehaviourTab/BehaviourTab.tsx index 839c0fe41..19e39aed0 100644 --- a/src/components/Dashboard/Modals/EditService/Tabs/BehaviourTab/BehaviourTab.tsx +++ b/src/components/Dashboard/Modals/EditService/Tabs/BehaviourTab/BehaviourTab.tsx @@ -18,30 +18,10 @@ export const BehaviourTab = ({ form }: BehaviourTabProps) => { placeholder="Override the default service url when clicking on the service" variant="default" mb="md" - {...form.getInputProps('onClickUrl')} + {...form.getInputProps('behaviour.onClickUrl')} /> - - Disables the direct movement of the tile - - } - mb="md" - {...form.getInputProps('isEditModeMovingDisabled')} - /> - - Disables the movement of the tile when moving others - - } - {...form.getInputProps('isEditModeTileFreezed')} - /> + ); }; diff --git a/src/tools/hooks/useRepositoryIconsQuery.ts b/src/tools/hooks/useRepositoryIconsQuery.ts new file mode 100644 index 000000000..e08e096ad --- /dev/null +++ b/src/tools/hooks/useRepositoryIconsQuery.ts @@ -0,0 +1,26 @@ +import { useQuery } from '@tanstack/react-query'; +import { IconSelectorItem } from '../../types/iconSelector/iconSelectorItem'; + +export const useRepositoryIconsQuery = ({ + url, + converter, +}: { + url: string; + converter: (value: TRepositoryIcon) => IconSelectorItem; +}) => + useQuery({ + queryKey: ['repository-icons', { url }], + queryFn: async () => fetchRepositoryIcons(url), + select(data) { + return data.map(x => converter(x)); + }, + refetchOnWindowFocus: false, + }); + +const fetchRepositoryIcons = + async (url: string): Promise => { + const response = await fetch( + 'https://api.github.com/repos/walkxcode/Dashboard-Icons/contents/png' + ); + return response.json(); +}; diff --git a/src/types/iconSelector/iconSelectorItem.ts b/src/types/iconSelector/iconSelectorItem.ts new file mode 100644 index 000000000..6a38b1850 --- /dev/null +++ b/src/types/iconSelector/iconSelectorItem.ts @@ -0,0 +1,3 @@ +export interface IconSelectorItem { + url: string; +} diff --git a/src/types/iconSelector/repositories/walkxcodeIconRepository.ts b/src/types/iconSelector/repositories/walkxcodeIconRepository.ts new file mode 100644 index 000000000..70aab46aa --- /dev/null +++ b/src/types/iconSelector/repositories/walkxcodeIconRepository.ts @@ -0,0 +1,3 @@ +export interface WalkxcodeRepositoryIcon { + name: string; +}