From 8f44532084738445b344b6b3717cacfdb5ce0583 Mon Sep 17 00:00:00 2001 From: Taylor Ho Date: Sat, 15 Aug 2026 00:09:47 -0700 Subject: [PATCH] Polish workflow editor actions Signed-off-by: Taylor Ho --- .../workflows/ui/WorkflowActionsMenu.tsx | 97 +++++++++++++++++++ .../features/workflows/ui/WorkflowCard.tsx | 83 ++-------------- .../features/workflows/ui/WorkflowDialog.tsx | 60 +++++++++++- .../workflows/ui/WorkflowFormBuilder.tsx | 18 +++- .../features/workflows/ui/WorkflowsView.tsx | 4 + 5 files changed, 182 insertions(+), 80 deletions(-) create mode 100644 desktop/src/features/workflows/ui/WorkflowActionsMenu.tsx diff --git a/desktop/src/features/workflows/ui/WorkflowActionsMenu.tsx b/desktop/src/features/workflows/ui/WorkflowActionsMenu.tsx new file mode 100644 index 000000000..92cf3de28 --- /dev/null +++ b/desktop/src/features/workflows/ui/WorkflowActionsMenu.tsx @@ -0,0 +1,97 @@ +import { + Copy, + MoreHorizontal, + Pencil, + Play, + Power, + PowerOff, + Trash2, +} from "lucide-react"; + +import { Button } from "@/shared/ui/button"; +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/shared/ui/dropdown-menu"; +import { Switch } from "@/shared/ui/switch"; + +type WorkflowActionsMenuProps = { + isEnabled: boolean; + isTogglingEnabled?: boolean; + onDelete: () => void; + onDuplicate: () => void; + onEdit: () => void; + onToggleEnabled: () => void; + onTrigger: () => void; +}; + +export function WorkflowActionsMenu({ + isEnabled, + isTogglingEnabled = false, + onDelete, + onDuplicate, + onEdit, + onToggleEnabled, + onTrigger, +}: WorkflowActionsMenuProps) { + return ( + + + + + + + + Trigger + + + + Edit + + + + Duplicate + + { + if (checked !== isEnabled) onToggleEnabled(); + }} + onSelect={(event) => event.preventDefault()} + > + {isEnabled ? ( + + ) : ( + + )} + Enable + + + + + Delete + + + + ); +} diff --git a/desktop/src/features/workflows/ui/WorkflowCard.tsx b/desktop/src/features/workflows/ui/WorkflowCard.tsx index ce90c7229..ecc9350ce 100644 --- a/desktop/src/features/workflows/ui/WorkflowCard.tsx +++ b/desktop/src/features/workflows/ui/WorkflowCard.tsx @@ -2,20 +2,13 @@ import { ArrowRight, CalendarClock, CircleCheckBig, - Copy, GitPullRequest, Hash, MessageCircle, MessageSquare, - MoreHorizontal, - Pencil, - Play, - Power, - PowerOff, Send, SmilePlus, Timer, - Trash2, Webhook, Zap, } from "lucide-react"; @@ -27,16 +20,7 @@ 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, - DropdownMenuCheckboxItem, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from "@/shared/ui/dropdown-menu"; -import { Switch } from "@/shared/ui/switch"; +import { WorkflowActionsMenu } from "./WorkflowActionsMenu"; import { getWorkflowCardLabel, getWorkflowDisplayStatus, @@ -355,62 +339,15 @@ export function WorkflowCard({
- - - - - - onTrigger(workflow.id)}> - - Trigger - - onEdit(workflow)}> - - Edit - - onDuplicate(workflow)}> - - Duplicate - - { - if (checked !== isEnabled) onToggleEnabled(workflow); - }} - onSelect={(event) => event.preventDefault()} - > - {isEnabled ? ( - - ) : ( - - )} - Enable - - - onDelete(workflow)} - > - - Delete - - - + onDelete(workflow)} + onDuplicate={() => onDuplicate(workflow)} + onEdit={() => onEdit(workflow)} + onToggleEnabled={() => onToggleEnabled(workflow)} + onTrigger={() => onTrigger(workflow.id)} + />
diff --git a/desktop/src/features/workflows/ui/WorkflowDialog.tsx b/desktop/src/features/workflows/ui/WorkflowDialog.tsx index 99bf2a0b3..c22527ec9 100644 --- a/desktop/src/features/workflows/ui/WorkflowDialog.tsx +++ b/desktop/src/features/workflows/ui/WorkflowDialog.tsx @@ -32,12 +32,14 @@ import { } from "@/shared/ui/dialog"; import { Input } from "@/shared/ui/input"; import { ChannelCombobox } from "./ChannelCombobox"; +import { WorkflowActionsMenu } from "./WorkflowActionsMenu"; import { WorkflowFormBuilder, type WorkflowEditorMode, type WorkflowFormBuilderHandle, } from "./WorkflowFormBuilder"; import { WorkflowWebhookSecretDialog } from "./WorkflowWebhookSecretDialog"; +import { getWorkflowEnabled } from "./workflowDefinition"; import type { WorkflowEditorPane } from "./workflowEditorPane"; import { DEFAULT_FORM_STATE, @@ -50,8 +52,12 @@ type DialogMode = "create" | "edit" | "duplicate"; type WorkflowDialogProps = { channels: Channel[]; mode: DialogMode; + onDeleteWorkflow: (workflow: Workflow) => void; + onDuplicateWorkflow: (workflowId: string) => void; + onEditWorkflow: (workflowId: string) => void; onEditorPaneChange: (pane: WorkflowEditorPane) => void; onOpenChange: (open: boolean) => void; + onTriggerWorkflow: (workflowId: string) => void; open: boolean; pane: WorkflowEditorPane; workflow?: Workflow | null; @@ -112,6 +118,24 @@ function yamlWithWorkflowName(yaml: string, name: string): string | null { return document.toString(); } +function yamlWithWorkflowEnabled( + yaml: string, + enabled: boolean, +): string | null { + if (!yaml.trim()) { + return formStateToYaml({ ...DEFAULT_FORM_STATE, enabled }); + } + + const document = parseDocument(yaml); + if (document.errors.length > 0 || !isMap(document.contents)) return null; + if (enabled) { + document.delete("enabled"); + } else { + document.set("enabled", false); + } + return document.toString(); +} + function WorkflowNameEditor({ disabled, generating, @@ -214,8 +238,12 @@ function WorkflowNameEditor({ export function WorkflowDialog({ channels, mode, + onDeleteWorkflow, + onDuplicateWorkflow, + onEditWorkflow, onEditorPaneChange, onOpenChange, + onTriggerWorkflow, open, pane, workflow, @@ -419,6 +447,11 @@ export function WorkflowDialog({ const workflowName = visibleWorkflowName(yamlDefinition, workflow?.name); const canEditWorkflowName = !yamlDefinition.trim() || yamlToFormState(yamlDefinition).ok; + const workflowEnabled = parsedDefinition?.ok + ? parsedDefinition.state.enabled + : workflow + ? getWorkflowEnabled(workflow.definition) + : true; const handleWorkflowNameCommit = React.useCallback( (name: string) => { const nextYaml = yamlWithWorkflowName(yamlDefinitionRef.current, name); @@ -430,6 +463,16 @@ export function WorkflowDialog({ }, [mutation.reset], ); + const handleToggleWorkflowEnabled = React.useCallback(() => { + const nextYaml = yamlWithWorkflowEnabled( + yamlDefinitionRef.current, + !workflowEnabled, + ); + if (nextYaml === null) return; + mutation.reset(); + yamlDefinitionRef.current = nextYaml; + setYamlDefinition(nextYaml); + }, [mutation.reset, workflowEnabled]); const showChannelSelector = mode !== "edit"; @@ -465,7 +508,9 @@ export function WorkflowDialog({ />
+ {mode === "edit" && workflow ? ( + onDeleteWorkflow(workflow)} + onDuplicate={() => onDuplicateWorkflow(workflow.id)} + onEdit={() => onEditWorkflow(workflow.id)} + onToggleEnabled={handleToggleWorkflowEnabled} + onTrigger={() => onTriggerWorkflow(workflow.id)} + /> + ) : null}