From 94134303d7dc6f2d52e4955dcd9d1afbd8d203fb Mon Sep 17 00:00:00 2001 From: Andre Silva <32734153+Aandree5@users.noreply.github.com> Date: Fri, 16 May 2025 15:33:23 +0100 Subject: [PATCH] feat(releases-widget): added auto icon search when editing a repository (#3087) --- packages/forms-collection/src/index.tsx | 1 + ...widget-multiReleasesRepositories-input.tsx | 45 +++++++++++++++++-- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/packages/forms-collection/src/index.tsx b/packages/forms-collection/src/index.tsx index 273e5547d..218655cb5 100644 --- a/packages/forms-collection/src/index.tsx +++ b/packages/forms-collection/src/index.tsx @@ -2,5 +2,6 @@ export * from "./new-app/_app-new-form"; export * from "./new-app/_form"; export * from "./icon-picker/icon-picker"; +export * from "./new-app/icon-matcher"; export * from "./upload-media/upload-media"; diff --git a/packages/widgets/src/_inputs/widget-multiReleasesRepositories-input.tsx b/packages/widgets/src/_inputs/widget-multiReleasesRepositories-input.tsx index 343aadbf4..24313c67e 100644 --- a/packages/widgets/src/_inputs/widget-multiReleasesRepositories-input.tsx +++ b/packages/widgets/src/_inputs/widget-multiReleasesRepositories-input.tsx @@ -1,12 +1,14 @@ "use client"; -import React, { useCallback, useMemo, useState } from "react"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; import { ActionIcon, Button, Divider, Fieldset, Group, Select, Stack, Text, TextInput } from "@mantine/core"; import type { FormErrors } from "@mantine/form"; +import { useDebouncedValue } from "@mantine/hooks"; import { IconEdit, IconTrash, IconTriangleFilled } from "@tabler/icons-react"; import { escapeForRegEx } from "@tiptap/react"; -import { IconPicker } from "@homarr/forms-collection"; +import { clientApi } from "@homarr/api/client"; +import { findBestIconMatch, IconPicker } from "@homarr/forms-collection"; import { createModal, useModalAction } from "@homarr/modals"; import { useScopedI18n } from "@homarr/translation/client"; import { MaskedOrNormalImage } from "@homarr/ui"; @@ -197,6 +199,13 @@ const ReleaseEditModal = createModal(({ innerProps, actions }) const [tempRepository, setTempRepository] = useState(() => ({ ...innerProps.repository })); const [formErrors, setFormErrors] = useState({}); + // Allows user to not select an icon by removing the url from the input, + // will only try and get an icon if the name or identifier changes + const [autoSetIcon, setAutoSetIcon] = useState(false); + + // Debounce the name value with 200ms delay + const [debouncedName] = useDebouncedValue(tempRepository.name, 800); + const handleConfirm = useCallback(() => { setLoading(true); @@ -221,6 +230,25 @@ const ReleaseEditModal = createModal(({ innerProps, actions }) setTempRepository((prev) => ({ ...prev, ...changedValue })); }, []); + // Auto-select icon based on identifier formatted name with debounced search + const { data: iconsData } = clientApi.icon.findIcons.useQuery( + { + searchText: debouncedName, + }, + { + enabled: autoSetIcon && (debouncedName?.length ?? 0) > 3, + }, + ); + + useEffect(() => { + if (autoSetIcon && debouncedName && !tempRepository.iconUrl && iconsData?.icons) { + const bestMatch = findBestIconMatch(debouncedName, iconsData.icons); + if (bestMatch) { + handleChange({ iconUrl: bestMatch }); + } + } + }, [debouncedName, iconsData, tempRepository, handleChange, autoSetIcon]); + return ( @@ -256,6 +284,8 @@ const ReleaseEditModal = createModal(({ innerProps, actions }) identifier: event.currentTarget.value, name, }); + + if (event.currentTarget.value) setAutoSetIcon(true); }} error={formErrors[`${innerProps.fieldPath}.identifier`]} w="100%" @@ -268,6 +298,8 @@ const ReleaseEditModal = createModal(({ innerProps, actions }) value={tempRepository.name ?? ""} onChange={(event) => { handleChange({ name: event.currentTarget.value }); + + if (event.currentTarget.value) setAutoSetIcon(true); }} error={formErrors[`${innerProps.fieldPath}.name`]} style={{ flex: 1, flexBasis: "40%" }} @@ -276,7 +308,14 @@ const ReleaseEditModal = createModal(({ innerProps, actions }) handleChange({ iconUrl: url === "" ? undefined : url })} + onChange={(url) => { + if (url === "") { + setAutoSetIcon(false); + handleChange({ iconUrl: undefined }); + } else { + handleChange({ iconUrl: url }); + } + }} error={formErrors[`${innerProps.fieldPath}.iconUrl`] as string} />