diff --git a/desktop/src/features/workflows/ui/WorkflowCard.tsx b/desktop/src/features/workflows/ui/WorkflowCard.tsx index 2f5462186..d62def974 100644 --- a/desktop/src/features/workflows/ui/WorkflowCard.tsx +++ b/desktop/src/features/workflows/ui/WorkflowCard.tsx @@ -20,8 +20,12 @@ import { } 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, @@ -34,9 +38,13 @@ import { getWorkflowDescription, getWorkflowDisplayStatus, getWorkflowPrimaryAction, + getWorkflowPrimaryActionChannel, + getWorkflowPrimaryActionEmoji, + getWorkflowTriggerConfig, getWorkflowTriggerSummary, getWorkflowTriggerType, } from "./workflowDefinition"; +import { useWorkflowTriggerPresentation } from "./useWorkflowTriggerPresentation"; type WorkflowCardProps = { workflow: Workflow; @@ -89,6 +97,87 @@ function StatusBadge({ status }: { status: Workflow["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; +} + export function WorkflowCard({ workflow, channelName, @@ -98,14 +187,54 @@ export function WorkflowCard({ onDuplicate, onDelete, }: WorkflowCardProps) { + const customEmoji = useCustomEmoji(); const displayStatus = getWorkflowDisplayStatus(workflow); const description = getWorkflowDescription(workflow.definition); - const triggerSummary = getWorkflowTriggerSummary(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 cardLabel = getWorkflowCardLabel(workflow.definition); + const actionChannel = getWorkflowPrimaryActionChannel(workflow.definition); + const actionReaction = getWorkflowPrimaryActionEmoji(workflow.definition); + const actionChannelLabel = + channelName && (!actionChannel || actionChannel === workflow.channelId) + ? channelName + : undefined; + const cardLabel = getWorkflowCardLabel(workflow.definition, { + actionChannelLabel, + 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 ( @@ -134,7 +263,14 @@ export function WorkflowCard({