* 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
88 lines
2.5 KiB
TypeScript
88 lines
2.5 KiB
TypeScript
"use client";
|
|
|
|
import type { PropsWithChildren } from "react";
|
|
import { Suspense } from "react";
|
|
import { Flex, Text, Tooltip, UnstyledButton } from "@mantine/core";
|
|
import combineClasses from "clsx";
|
|
|
|
import { clientApi } from "@homarr/api/client";
|
|
import { parseAppHrefWithVariablesClient } from "@homarr/common/client";
|
|
import { useI18n } from "@homarr/translation/client";
|
|
|
|
import type { WidgetComponentProps } from "../definition";
|
|
import classes from "./app.module.css";
|
|
import { PingDot } from "./ping/ping-dot";
|
|
import { PingIndicator } from "./ping/ping-indicator";
|
|
|
|
export default function AppWidget({ options, isEditMode }: WidgetComponentProps<"app">) {
|
|
const t = useI18n();
|
|
const [app] = clientApi.app.byId.useSuspenseQuery(
|
|
{
|
|
id: options.appId,
|
|
},
|
|
{
|
|
refetchOnMount: false,
|
|
refetchOnWindowFocus: false,
|
|
refetchOnReconnect: false,
|
|
retry: false,
|
|
},
|
|
);
|
|
|
|
return (
|
|
<AppLink
|
|
href={parseAppHrefWithVariablesClient(app.href ?? "")}
|
|
openInNewTab={options.openInNewTab}
|
|
enabled={Boolean(app.href) && !isEditMode}
|
|
>
|
|
<Tooltip.Floating
|
|
label={app.description}
|
|
position="right-start"
|
|
multiline
|
|
disabled={!options.showDescriptionTooltip || !app.description}
|
|
styles={{ tooltip: { maxWidth: 300 } }}
|
|
>
|
|
<Flex
|
|
className={combineClasses("app-flex-wrapper", app.name, app.id)}
|
|
h="100%"
|
|
w="100%"
|
|
direction="column"
|
|
p="7.5cqmin"
|
|
justify="center"
|
|
align="center"
|
|
>
|
|
{options.showTitle && (
|
|
<Text className="app-title" fw={700} ta="center" size="12.5cqmin">
|
|
{app.name}
|
|
</Text>
|
|
)}
|
|
<img src={app.iconUrl} alt={app.name} className={combineClasses(classes.appIcon, "app-icon")} />
|
|
</Flex>
|
|
</Tooltip.Floating>
|
|
{options.pingEnabled && app.href ? (
|
|
<Suspense
|
|
fallback={
|
|
<PingDot color="blue" tooltip={t("common.rtl", { symbol: "…", value: t("common.action.loading") })} />
|
|
}
|
|
>
|
|
<PingIndicator href={app.href} />
|
|
</Suspense>
|
|
) : null}
|
|
</AppLink>
|
|
);
|
|
}
|
|
|
|
interface AppLinkProps {
|
|
href: string;
|
|
openInNewTab: boolean;
|
|
enabled: boolean;
|
|
}
|
|
|
|
const AppLink = ({ href, openInNewTab, enabled, children }: PropsWithChildren<AppLinkProps>) =>
|
|
enabled ? (
|
|
<UnstyledButton component="a" href={href} target={openInNewTab ? "_blank" : undefined} h="100%" w="100%">
|
|
{children}
|
|
</UnstyledButton>
|
|
) : (
|
|
children
|
|
);
|