feat: add improved search (#1051)
* feat: add improved search * wip: add support for sorting, rename use-options to use-query-options, add use-options for local usage, add pages search group * feat: add help links from manage layout to help search mode * feat: add additional search engines * feat: add group search details * refactor: improve users search group type * feat: add apps search group, add disabled search interaction * feat: add integrations and boards for search * wip: hook issue with react * fix: hook issue regarding actions and interactions * chore: address pull request feedback * fix: format issues * feat: add additional global actions to search * chore: remove unused code * fix: search engine short key * fix: typecheck issues * fix: deepsource issues * fix: eslint issue * fix: lint issues * fix: unordered dependencies * chore: address pull request feedback
This commit is contained in:
@@ -1,39 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { Affix, Button, Group, Menu } from "@mantine/core";
|
||||
import { IconCategoryPlus, IconChevronDown, IconFileImport } from "@tabler/icons-react";
|
||||
|
||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||
import { useModalAction } from "@homarr/modals";
|
||||
import { AddBoardModal, ImportBoardModal } from "@homarr/modals-collection";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
import { BetaBadge } from "@homarr/ui";
|
||||
|
||||
interface CreateBoardButtonProps {
|
||||
boardNames: string[];
|
||||
}
|
||||
|
||||
export const CreateBoardButton = ({ boardNames }: CreateBoardButtonProps) => {
|
||||
export const CreateBoardButton = () => {
|
||||
const t = useI18n();
|
||||
const { openModal: openAddModal } = useModalAction(AddBoardModal);
|
||||
const { openModal: openImportModal } = useModalAction(ImportBoardModal);
|
||||
|
||||
const onCreateClick = useCallback(() => {
|
||||
openAddModal({
|
||||
onSettled: async () => {
|
||||
await revalidatePathActionAsync("/manage/boards");
|
||||
},
|
||||
});
|
||||
}, [openAddModal]);
|
||||
|
||||
const onImportClick = useCallback(() => {
|
||||
openImportModal({ boardNames });
|
||||
}, [openImportModal, boardNames]);
|
||||
|
||||
const buttonGroupContent = (
|
||||
<>
|
||||
<Button leftSection={<IconCategoryPlus size="1rem" />} onClick={onCreateClick}>
|
||||
<Button leftSection={<IconCategoryPlus size="1rem" />} onClick={openAddModal}>
|
||||
{t("management.page.board.action.new.label")}
|
||||
</Button>
|
||||
<Menu position="bottom-end">
|
||||
@@ -43,7 +25,7 @@ export const CreateBoardButton = ({ boardNames }: CreateBoardButtonProps) => {
|
||||
</Button>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Item onClick={onImportClick} leftSection={<IconFileImport size="1rem" />}>
|
||||
<Menu.Item onClick={openImportModal} leftSection={<IconFileImport size="1rem" />}>
|
||||
<Group>
|
||||
{t("board.action.oldImport.label")}
|
||||
<BetaBadge size="xs" />
|
||||
|
||||
@@ -39,7 +39,7 @@ export default async function ManageBoardsPage() {
|
||||
<Stack>
|
||||
<Group justify="space-between">
|
||||
<Title mb="md">{t("title")}</Title>
|
||||
<CreateBoardButton boardNames={boards.map((board) => board.name)} />
|
||||
<CreateBoardButton />
|
||||
</Group>
|
||||
|
||||
<Grid mb={{ base: "xl", md: 0 }}>
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { Avatar } from "@mantine/core";
|
||||
import type { MantineSize } from "@mantine/core";
|
||||
|
||||
import { getIconUrl } from "@homarr/definitions";
|
||||
import type { IntegrationKind } from "@homarr/definitions";
|
||||
|
||||
interface IntegrationAvatarProps {
|
||||
size: MantineSize;
|
||||
kind: IntegrationKind | null;
|
||||
}
|
||||
|
||||
export const IntegrationAvatar = ({ kind, size }: IntegrationAvatarProps) => {
|
||||
const url = kind ? getIconUrl(kind) : null;
|
||||
if (!url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <Avatar size={size} src={url} />;
|
||||
};
|
||||
@@ -3,10 +3,10 @@ import { Container, Fieldset, Group, Stack, Title } from "@mantine/core";
|
||||
import { api } from "@homarr/api/server";
|
||||
import { getIntegrationName } from "@homarr/definitions";
|
||||
import { getI18n, getScopedI18n } from "@homarr/translation/server";
|
||||
import { IntegrationAvatar } from "@homarr/ui";
|
||||
|
||||
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
|
||||
import { IntegrationAccessSettings } from "../../_components/integration-access-settings";
|
||||
import { IntegrationAvatar } from "../../_integration-avatar";
|
||||
import { EditIntegrationForm } from "./_integration-edit-form";
|
||||
|
||||
interface EditIntegrationPageProps {
|
||||
|
||||
@@ -8,8 +8,7 @@ import { IconSearch } from "@tabler/icons-react";
|
||||
|
||||
import { getIntegrationName, integrationKinds } from "@homarr/definitions";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
|
||||
import { IntegrationAvatar } from "../_integration-avatar";
|
||||
import { IntegrationAvatar } from "@homarr/ui";
|
||||
|
||||
export const IntegrationCreateDropdownContent = () => {
|
||||
const t = useI18n();
|
||||
|
||||
@@ -4,11 +4,11 @@ import { Container, Group, Stack, Title } from "@mantine/core";
|
||||
import type { IntegrationKind } from "@homarr/definitions";
|
||||
import { getIntegrationName, integrationKinds } from "@homarr/definitions";
|
||||
import { getScopedI18n } from "@homarr/translation/server";
|
||||
import { IntegrationAvatar } from "@homarr/ui";
|
||||
import type { validation } from "@homarr/validation";
|
||||
import { z } from "@homarr/validation";
|
||||
|
||||
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
|
||||
import { IntegrationAvatar } from "../_integration-avatar";
|
||||
import { NewIntegrationForm } from "./_integration-new-form";
|
||||
|
||||
interface NewIntegrationPageProps {
|
||||
|
||||
@@ -34,12 +34,11 @@ import { objectEntries } from "@homarr/common";
|
||||
import type { IntegrationKind } from "@homarr/definitions";
|
||||
import { getIntegrationName } from "@homarr/definitions";
|
||||
import { getScopedI18n } from "@homarr/translation/server";
|
||||
import { CountBadge } from "@homarr/ui";
|
||||
import { CountBadge, IntegrationAvatar } from "@homarr/ui";
|
||||
|
||||
import { ManageContainer } from "~/components/manage/manage-container";
|
||||
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
|
||||
import { ActiveTabAccordion } from "../../../../components/active-tab-accordion";
|
||||
import { IntegrationAvatar } from "./_integration-avatar";
|
||||
import { DeleteIntegrationActionButton } from "./_integration-buttons";
|
||||
import { IntegrationCreateDropdownContent } from "./new/_integration-new-dropdown";
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ export default async function ManageLayout({ children }: PropsWithChildren) {
|
||||
{
|
||||
label: t("items.help.items.documentation"),
|
||||
icon: IconBook2,
|
||||
href: "https://homarr.dev/docs/getting-started/prerequisites",
|
||||
href: "https://homarr.dev/docs/getting-started/",
|
||||
external: true,
|
||||
},
|
||||
{
|
||||
@@ -123,7 +123,7 @@ export default async function ManageLayout({ children }: PropsWithChildren) {
|
||||
external: true,
|
||||
},
|
||||
{
|
||||
label: t("items.tools.items.docker"),
|
||||
label: t("items.help.items.discord"),
|
||||
icon: IconBrandDiscord,
|
||||
href: "https://discord.com/invite/aCsmEV5RgA",
|
||||
external: true,
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { Button, Group, Stack, TextInput } from "@mantine/core";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||
import { useZodForm } from "@homarr/form";
|
||||
import { createModal, useModalAction } from "@homarr/modals";
|
||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||
import { useModalAction } from "@homarr/modals";
|
||||
import { AddGroupModal } from "@homarr/modals-collection";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
import { validation } from "@homarr/validation";
|
||||
|
||||
import { MobileAffixButton } from "~/components/manage/mobile-affix-button";
|
||||
|
||||
@@ -27,50 +22,3 @@ export const AddGroup = () => {
|
||||
</MobileAffixButton>
|
||||
);
|
||||
};
|
||||
|
||||
const AddGroupModal = createModal<void>(({ actions }) => {
|
||||
const t = useI18n();
|
||||
const { mutate, isPending } = clientApi.group.createGroup.useMutation();
|
||||
const form = useZodForm(validation.group.create, {
|
||||
initialValues: {
|
||||
name: "",
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
mutate(values, {
|
||||
onSuccess() {
|
||||
actions.closeModal();
|
||||
void revalidatePathActionAsync("/manage/users/groups");
|
||||
showSuccessNotification({
|
||||
title: t("common.notification.create.success"),
|
||||
message: t("group.action.create.notification.success.message"),
|
||||
});
|
||||
},
|
||||
onError() {
|
||||
showErrorNotification({
|
||||
title: t("common.notification.create.error"),
|
||||
message: t("group.action.create.notification.error.message"),
|
||||
});
|
||||
},
|
||||
});
|
||||
})}
|
||||
>
|
||||
<Stack>
|
||||
<TextInput label={t("group.field.name")} data-autofocus {...form.getInputProps("name")} />
|
||||
<Group justify="right">
|
||||
<Button onClick={actions.closeModal} variant="subtle" color="gray">
|
||||
{t("common.action.cancel")}
|
||||
</Button>
|
||||
<Button loading={isPending} type="submit" color="teal">
|
||||
{t("common.action.create")}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
}).withOptions({
|
||||
defaultTitle: (t) => t("group.action.create.label"),
|
||||
});
|
||||
|
||||
@@ -107,7 +107,7 @@ export const BoardItemMenu = ({
|
||||
}}
|
||||
>
|
||||
{tItem("action.moveResize")}
|
||||
</Menu.Item>{" "}
|
||||
</Menu.Item>
|
||||
<Menu.Item leftSection={<IconCopy size={16} />} onClick={() => duplicateItem({ itemId: item.id })}>
|
||||
{tItem("action.duplicate")}
|
||||
</Menu.Item>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useState } from "react";
|
||||
import { Combobox, Group, Image, InputBase, Skeleton, Text, useCombobox } from "@mantine/core";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
interface IconPickerProps {
|
||||
initialValue?: string;
|
||||
@@ -18,7 +18,8 @@ export const IconPicker = ({ initialValue, onChange, error, onFocus, onBlur }: I
|
||||
const [search, setSearch] = useState(initialValue ?? "");
|
||||
const [previewUrl, setPreviewUrl] = useState<string | null>(initialValue ?? null);
|
||||
|
||||
const t = useScopedI18n("common");
|
||||
const t = useI18n();
|
||||
const tCommon = useScopedI18n("common");
|
||||
|
||||
const { data, isFetching } = clientApi.icon.findIcons.useQuery({
|
||||
searchText: search,
|
||||
@@ -89,13 +90,13 @@ export const IconPicker = ({ initialValue, onChange, error, onFocus, onBlur }: I
|
||||
rightSectionPointerEvents="none"
|
||||
withAsterisk
|
||||
error={error}
|
||||
label={t("iconPicker.label")}
|
||||
label={tCommon("iconPicker.label")}
|
||||
/>
|
||||
</Combobox.Target>
|
||||
|
||||
<Combobox.Dropdown>
|
||||
<Combobox.Header>
|
||||
<Text c="dimmed">{t("iconPicker.header", { countIcons: data?.countIcons })}</Text>
|
||||
<Text c="dimmed">{tCommon("iconPicker.header", { countIcons: data?.countIcons })}</Text>
|
||||
</Combobox.Header>
|
||||
<Combobox.Options mah={350} style={{ overflowY: "auto" }}>
|
||||
{totalOptions > 0 ? (
|
||||
|
||||
@@ -4,13 +4,13 @@ import { TextInput, UnstyledButton } from "@mantine/core";
|
||||
import { IconSearch } from "@tabler/icons-react";
|
||||
|
||||
import { openSpotlight } from "@homarr/spotlight";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
|
||||
import { HeaderButton } from "./button";
|
||||
import classes from "./search.module.css";
|
||||
|
||||
export const DesktopSearchInput = () => {
|
||||
const t = useScopedI18n("common.search");
|
||||
const t = useI18n();
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
@@ -21,7 +21,10 @@ export const DesktopSearchInput = () => {
|
||||
leftSection={<IconSearch size={20} stroke={1.5} />}
|
||||
onClick={openSpotlight}
|
||||
>
|
||||
{t("placeholder")}
|
||||
{t("common.rtl", {
|
||||
value: t("search.placeholder"),
|
||||
symbol: "...",
|
||||
})}
|
||||
</TextInput>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user