Files
certimate/ui/src/components/workflow/WorkflowElement.tsx

177 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useTranslation } from "react-i18next";
import { CloseCircleOutlined as CloseCircleOutlinedIcon, EllipsisOutlined as EllipsisOutlinedIcon } from "@ant-design/icons";
import { Avatar, Button, Card, Dropdown, Popover, Space, Typography } from "antd";
import { produce } from "immer";
import Show from "@/components/Show";
import { deployProvidersMap } from "@/domain/provider";
import { notifyChannelsMap } from "@/domain/settings";
import {
WORKFLOW_TRIGGERS,
type WorkflowNode,
type WorkflowNodeConfigAsApply,
type WorkflowNodeConfigAsDeploy,
type WorkflowNodeConfigAsNotify,
type WorkflowNodeConfigAsStart,
WorkflowNodeType,
} from "@/domain/workflow";
import { useZustandShallowSelector } from "@/hooks";
import { useWorkflowStore } from "@/stores/workflow";
import PanelBody from "./PanelBody";
import { usePanel } from "./PanelProvider";
import AddNode from "./node/AddNode";
export type NodeProps = {
node: WorkflowNode;
disabled?: boolean;
};
const WorkflowElement = ({ node, disabled }: NodeProps) => {
const { t } = useTranslation();
const { updateNode, removeNode } = useWorkflowStore(useZustandShallowSelector(["updateNode", "removeNode"]));
const { showPanel } = usePanel();
const renderNodeContent = () => {
if (!node.validated) {
return <Typography.Link>{t("workflow_node.action.configure_node")}</Typography.Link>;
}
switch (node.type) {
case WorkflowNodeType.Start: {
const config = (node.config as WorkflowNodeConfigAsStart) ?? {};
return (
<div className="flex items-center justify-between space-x-2">
<Typography.Text className="truncate">
{config.trigger === WORKFLOW_TRIGGERS.AUTO
? t("workflow.props.trigger.auto")
: config.trigger === WORKFLOW_TRIGGERS.MANUAL
? t("workflow.props.trigger.manual")
: " "}
</Typography.Text>
<Typography.Text className="truncate" type="secondary">
{config.trigger === WORKFLOW_TRIGGERS.AUTO ? config.triggerCron : ""}
</Typography.Text>
</div>
);
}
case WorkflowNodeType.Apply: {
const config = (node.config as WorkflowNodeConfigAsApply) ?? {};
return <Typography.Text className="truncate">{config.domains || " "}</Typography.Text>;
}
case WorkflowNodeType.Deploy: {
const config = (node.config as WorkflowNodeConfigAsDeploy) ?? {};
const provider = deployProvidersMap.get(config.provider);
return (
<Space>
<Avatar src={provider?.icon} size="small" />
<Typography.Text className="truncate">{t(provider?.name ?? "")}</Typography.Text>
</Space>
);
}
case WorkflowNodeType.Notify: {
const config = (node.config as WorkflowNodeConfigAsNotify) ?? {};
const channel = notifyChannelsMap.get(config.channel as string);
return (
<div className="flex items-center justify-between space-x-2">
<Typography.Text className="truncate">{t(channel?.name ?? " ")}</Typography.Text>
<Typography.Text className="truncate" type="secondary">
{config.subject ?? ""}
</Typography.Text>
</div>
);
}
default: {
return <></>;
}
}
};
const handleNodeNameBlur = (e: React.FocusEvent<HTMLDivElement>) => {
const oldName = node.name;
const newName = e.target.innerText.trim();
if (oldName === newName) {
return;
}
updateNode(
produce(node, (draft) => {
draft.name = newName;
})
);
};
const handleNodeClick = () => {
if (disabled) return;
showPanel({
name: node.name,
children: <PanelBody data={node} />,
});
};
return (
<>
<Popover
arrow={false}
content={
<Show when={node.type !== WorkflowNodeType.Start}>
<Dropdown
menu={{
items: [
{
key: "delete",
disabled: disabled,
label: t("workflow_node.action.delete_node"),
icon: <CloseCircleOutlinedIcon />,
danger: true,
onClick: () => {
if (disabled) return;
removeNode(node.id);
},
},
],
}}
trigger={["click"]}
>
<Button color="primary" icon={<EllipsisOutlinedIcon />} variant="text" />
</Dropdown>
</Show>
}
overlayClassName="shadow-md"
overlayInnerStyle={{ padding: 0 }}
placement="rightTop"
>
<Card className="relative w-[256px] overflow-hidden shadow-md" styles={{ body: { padding: 0 } }} hoverable>
<div className="bg-primary flex h-[48px] flex-col items-center justify-center truncate px-4 py-2 text-white">
<div
className="focus:bg-background focus:text-foreground w-full overflow-hidden text-center outline-none focus:rounded-sm"
contentEditable
suppressContentEditableWarning
onBlur={handleNodeNameBlur}
>
{node.name}
</div>
</div>
<div className="flex flex-col justify-center px-4 py-2">
<div className="cursor-pointer text-sm" onClick={handleNodeClick}>
{renderNodeContent()}
</div>
</div>
</Card>
</Popover>
<AddNode node={node} disabled={disabled} />
</>
);
};
export default WorkflowElement;