feat(widgets): allow creating and auto-selecting supported integrations directly from widget edit modal
This commit is contained in:
3 files changed
+65
-7
No files matched your search
@@ -1,16 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import { Suspense, use } from "react";
|
||||
import { Suspense, use, useCallback, useMemo, useState } from "react";
|
||||
import { Center, Loader } from "@mantine/core";
|
||||
import { ErrorBoundary } from "react-error-boundary";
|
||||
import type { FallbackProps } from "react-error-boundary";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import type { WidgetKind } from "@homarr/definitions";
|
||||
import { createModal, modalSizeForm } from "@homarr/modals";
|
||||
import { createModal, modalSizeForm, useModalAction } from "@homarr/modals";
|
||||
import { WidgetError } from "@homarr/widgets/errors";
|
||||
import type * as WidgetModalsModule from "@homarr/widgets/modals";
|
||||
import type { WidgetEditModalProps } from "@homarr/widgets/modals";
|
||||
import type { IntegrationSelectOption } from "@homarr/widgets/widget-integration-select";
|
||||
|
||||
import { IntegrationSelectModal } from "~/components/integration/integration-select-modal";
|
||||
import type * as IntegrationEditFormModule from "~/components/integration/embedded-integration-edit-form";
|
||||
import type { EmbeddedIntegrationEditFormProps } from "~/components/integration/embedded-integration-edit-form";
|
||||
|
||||
@@ -80,13 +83,46 @@ interface LazyWidgetEditModalContentProps {
|
||||
|
||||
const LazyWidgetEditModalContent = (props: LazyWidgetEditModalContentProps) => {
|
||||
const { WidgetEditModal } = use(loadWidgetEditModal());
|
||||
const { openModal: openIntegrationModal } = useModalAction(IntegrationSelectModal);
|
||||
const Component = WidgetEditModal.component;
|
||||
const utils = clientApi.useUtils();
|
||||
const [createdIntegrations, setCreatedIntegrations] = useState<IntegrationSelectOption[]>([]);
|
||||
|
||||
const supportedKinds = useMemo(() => {
|
||||
if (!("supportedIntegrations" in props.innerProps.definition)) return [];
|
||||
return (props.innerProps.definition.supportedIntegrations ?? []).filter((k) => k !== "mock");
|
||||
}, [props.innerProps.definition]);
|
||||
|
||||
const handleOpenNewIntegration = useCallback(
|
||||
(onCreated?: (id: string) => void) => {
|
||||
openIntegrationModal({
|
||||
allowedKinds: supportedKinds,
|
||||
onSuccess: (result) => {
|
||||
if (result?.integration) {
|
||||
setCreatedIntegrations((prev) => [...prev, result.integration]);
|
||||
void utils.integration.all.invalidate();
|
||||
onCreated?.(result.integration.id);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
[openIntegrationModal, supportedKinds, utils],
|
||||
);
|
||||
|
||||
const combinedIntegrationData = useMemo(() => {
|
||||
const existing = props.innerProps.integrationData;
|
||||
const additional = createdIntegrations.filter((c) => !existing.some((e) => e.id === c.id));
|
||||
return [...existing, ...additional];
|
||||
}, [props.innerProps.integrationData, createdIntegrations]);
|
||||
|
||||
return (
|
||||
<Component
|
||||
{...props}
|
||||
innerProps={{
|
||||
...props.innerProps,
|
||||
integrationData: combinedIntegrationData,
|
||||
integrationEditForm: LazyIntegrationEditForm,
|
||||
onOpenNewIntegration: supportedKinds.length > 0 ? handleOpenNewIntegration : undefined,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -58,6 +58,7 @@ export interface WidgetEditModalProps<TSort extends WidgetKind> {
|
||||
appId?: string;
|
||||
integrationEditForm?: ComponentType<EmbeddedIntegrationEditFormProps>;
|
||||
onIntegrationSaved?: () => void;
|
||||
onOpenNewIntegration?: (onCreated?: (id: string) => void) => void;
|
||||
}
|
||||
|
||||
export const getSelectedWidgetIntegrations = (
|
||||
@@ -217,6 +218,16 @@ export const WidgetEditModal = createModal<WidgetEditModalProps<WidgetKind>>(({
|
||||
handleSubmit(event);
|
||||
};
|
||||
|
||||
const handleOpenNewIntegration = innerProps.onOpenNewIntegration
|
||||
? () => {
|
||||
innerProps.onOpenNewIntegration?.((newId) => {
|
||||
if (newId) {
|
||||
form.setFieldValue("integrationIds", [...(maxIntegrations > 1 ? form.values.integrationIds : []), newId]);
|
||||
}
|
||||
});
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const widgetFormContent = (
|
||||
<Stack>
|
||||
{canConfigureWidget && innerProps.integrationSupport && (
|
||||
@@ -225,6 +236,7 @@ export const WidgetEditModal = createModal<WidgetEditModalProps<WidgetKind>>(({
|
||||
data={innerProps.integrationData}
|
||||
canSelectMultiple={maxIntegrations > 1}
|
||||
withAsterisk={integrationsRequired}
|
||||
onOpenNewIntegration={handleOpenNewIntegration}
|
||||
{...form.getInputProps("integrationIds")}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -32,6 +32,7 @@ interface WidgetIntegrationSelectProps {
|
||||
canSelectMultiple?: boolean;
|
||||
data: IntegrationSelectOption[];
|
||||
withAsterisk?: boolean;
|
||||
onOpenNewIntegration?: () => void;
|
||||
}
|
||||
export const WidgetIntegrationSelect = ({
|
||||
data,
|
||||
@@ -40,6 +41,7 @@ export const WidgetIntegrationSelect = ({
|
||||
label,
|
||||
canSelectMultiple = true,
|
||||
withAsterisk = false,
|
||||
onOpenNewIntegration,
|
||||
...props
|
||||
}: WidgetIntegrationSelectProps) => {
|
||||
const t = useI18n();
|
||||
@@ -109,11 +111,19 @@ export const WidgetIntegrationSelect = ({
|
||||
<PillsInput
|
||||
inputWrapperOrder={["label", "input", "description", "error"]}
|
||||
description={
|
||||
<Text size="xs" span>
|
||||
<Anchor size="xs" component={Link} target="_blank" href="/manage/integrations">
|
||||
{t("widget.common.integration.manage")}
|
||||
</Anchor>
|
||||
</Text>
|
||||
onOpenNewIntegration ? (
|
||||
<Text size="xs" span>
|
||||
<Anchor size="xs" component="button" type="button" onClick={onOpenNewIntegration}>
|
||||
{t("integration.action.create")}
|
||||
</Anchor>
|
||||
</Text>
|
||||
) : (
|
||||
<Text size="xs" span>
|
||||
<Anchor size="xs" component={Link} target="_blank" href="/manage/integrations">
|
||||
{t("widget.common.integration.manage")}
|
||||
</Anchor>
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
pointer
|
||||
onClick={() => combobox.openDropdown()}
|
||||
|
||||
Reference in new issue
Block a user