import { ArrowRight, CalendarClock, CircleCheckBig, Copy, GitPullRequest, Hash, MessageCircle, MessageSquare, MoreHorizontal, Pencil, Play, Power, PowerOff, Send, SmilePlus, Timer, Trash2, Webhook, Zap, } from "lucide-react"; import type { LucideIcon } from "lucide-react"; import { useCustomEmoji } from "@/features/custom-emoji/hooks"; import { reactionEmojiUrl } from "@/shared/api/customEmoji"; import type { Workflow } from "@/shared/api/types"; import { cn } from "@/shared/lib/cn"; import { emojiDisplayName } from "@/shared/lib/emojiName"; import { rewriteRelayUrl } from "@/shared/lib/mediaUrl"; import { Button } from "@/shared/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/shared/ui/dropdown-menu"; import { getWorkflowCardLabel, getWorkflowDescription, getWorkflowDisplayStatus, getWorkflowEnabled, getWorkflowPrimaryAction, getWorkflowPrimaryActionEmoji, getWorkflowTriggerConfig, getWorkflowTriggerSummary, getWorkflowTriggerType, } from "./workflowDefinition"; import { TriggerDescriptionText } from "./WorkflowNodeDescriptions"; import { useWorkflowTriggerPresentation } from "./useWorkflowTriggerPresentation"; type WorkflowCardProps = { workflow: Workflow; channelName?: string; isActive?: boolean; isTogglingEnabled?: boolean; onTrigger: (workflowId: string) => void; onToggleEnabled: (workflow: Workflow) => void; onEdit: (workflow: Workflow) => void; onDuplicate: (workflow: Workflow) => void; onDelete: (workflow: Workflow) => void; }; const TRIGGER_ICONS: Record = { diff_posted: GitPullRequest, message_posted: MessageSquare, reaction_added: SmilePlus, schedule: CalendarClock, webhook: Webhook, }; const ACTION_ICONS: Record = { add_reaction: SmilePlus, call_webhook: Webhook, delay: Timer, request_approval: CircleCheckBig, send_dm: MessageCircle, send_message: Send, set_channel_topic: Hash, }; const TRIGGER_THEMES: Record = { diff_posted: "border-violet-400/30 bg-violet-600 text-white", message_posted: "border-blue-400/30 bg-blue-600 text-white", reaction_added: "border-pink-400/30 bg-pink-600 text-white", schedule: "border-emerald-400/30 bg-emerald-600 text-white", webhook: "border-orange-300/30 bg-orange-500 text-white", }; function StatusBadge({ status }: { status: Workflow["status"] }) { return ( {status} ); } function ReactionGlyph({ className, emoji, testId, url, }: { className: string; emoji: string; testId: string; url?: string; }) { return url ? ( {emoji} ) : ( {emoji} ); } function ReactionLabelText({ reactions, text, }: { reactions: ReadonlyArray<{ emoji: string; url?: string }>; text: string; }) { const candidates = [...reactions].sort( (left, right) => right.emoji.length - left.emoji.length, ); const parts: React.ReactNode[] = []; let cursor = 0; while (cursor < text.length) { let nextIndex = -1; let nextReaction: (typeof candidates)[number] | undefined; for (const reaction of candidates) { const matchIndex = text.indexOf(reaction.emoji, cursor); if (matchIndex >= 0 && (nextIndex < 0 || matchIndex < nextIndex)) { nextIndex = matchIndex; nextReaction = reaction; } } if (!nextReaction) break; if (nextIndex > cursor) parts.push(text.slice(cursor, nextIndex)); parts.push( nextReaction.url ? ( ) : ( nextReaction.emoji ), ); cursor = nextIndex + nextReaction.emoji.length; } if (cursor < text.length) parts.push(text.slice(cursor)); return parts.length > 0 ? parts : text; } function TriggerCardText({ authorLoading, messageLoading, reactions, text, }: { authorLoading?: boolean; messageLoading?: boolean; reactions: ReadonlyArray<{ emoji: string; url?: string }>; text: string; }) { return authorLoading || messageLoading ? ( ) : ( ); } export function WorkflowCard({ workflow, channelName, isActive = false, isTogglingEnabled = false, onTrigger, onToggleEnabled, onEdit, onDuplicate, onDelete, }: WorkflowCardProps) { const customEmoji = useCustomEmoji(); const isEnabled = getWorkflowEnabled(workflow.definition); const displayStatus = getWorkflowDisplayStatus(workflow); const description = getWorkflowDescription(workflow.definition); const configuredTrigger = getWorkflowTriggerConfig(workflow.definition); const trigger = configuredTrigger ?? { on: "message_posted" as const }; const triggerPresentation = useWorkflowTriggerPresentation({ trigger, workflowChannelId: workflow.channelId, }); const triggerSummary = configuredTrigger && ["diff_posted", "message_posted", "reaction_added"].includes(trigger.on) ? triggerPresentation.description : getWorkflowTriggerSummary(workflow.definition); const triggerType = getWorkflowTriggerType(workflow.definition); const actionType = getWorkflowPrimaryAction(workflow.definition); const actionReaction = getWorkflowPrimaryActionEmoji(workflow.definition); const cardLabel = getWorkflowCardLabel(workflow.definition, { triggerDescription: configuredTrigger && triggerPresentation.description !== getWorkflowTriggerSummary({ trigger: { on: configuredTrigger.on } }) ? triggerPresentation.description : undefined, triggerReaction: triggerPresentation.emoji, }); const cardReactions = [ ...new Set([triggerPresentation.emoji, actionReaction]), ] .filter((emoji): emoji is string => Boolean(emoji)) .map((emoji) => ({ emoji, url: reactionEmojiUrl(emoji, customEmoji), })); const TriggerIcon = triggerType ? TRIGGER_ICONS[triggerType] : undefined; const ActionIcon = actionType ? ACTION_ICONS[actionType] : undefined; const triggerReaction = cardReactions.find( ({ emoji }) => emoji === triggerPresentation.emoji, ); const actionReactionPresentation = cardReactions.find( ({ emoji }) => emoji === actionReaction, ); const theme = triggerType ? TRIGGER_THEMES[triggerType] : undefined; return (
); }