From 981efd45ec58e9b1d9d89b483bd3e46efa523ce9 Mon Sep 17 00:00:00 2001 From: Daryl Lim Date: Sun, 10 Mar 2024 16:52:30 -0700 Subject: [PATCH] feat+refactor(ui): Prevent node drag from triggering dnd flow save + add schemas for types and add to types/schemas --- frontend/src/components/canvas.tsx | 47 +++++++++------------- frontend/src/components/forms/workflow.tsx | 33 +++++++-------- frontend/src/components/workspace.tsx | 14 +------ frontend/src/lib/flow.ts | 2 +- frontend/src/providers/builder.tsx | 24 +++++------ frontend/src/types/schemas.ts | 19 +++++++++ 6 files changed, 67 insertions(+), 72 deletions(-) diff --git a/frontend/src/components/canvas.tsx b/frontend/src/components/canvas.tsx index 7163254f9..709e80899 100644 --- a/frontend/src/components/canvas.tsx +++ b/frontend/src/components/canvas.tsx @@ -17,9 +17,11 @@ import ReactFlow, { import "reactflow/dist/style.css" import { useParams } from "next/navigation" +import { useSession } from "@/providers/session" import { ActionType } from "@/types" -import { saveFlow } from "@/lib/flow" +import { ActionMetadata, WorkflowResponse } from "@/types/schemas" +import { updateDndFlow } from "@/lib/flow" import { useToast } from "@/components/ui/use-toast" import ActionNode, { ActionNodeData } from "@/components/action-node" @@ -34,30 +36,6 @@ const defaultEdgeOptions = { style: { strokeWidth: 2 }, } -type ActionMetadata = { - id: string - workflow_id: string - title: string - description: string -} - -interface ActionResponse { - id: string - title: string - description: string - status: string - inputs: { [key: string]: any } | null -} - -interface WorkflowResponse { - id: string - title: string - description: string - status: string - actions: { [key: string]: ActionResponse[] } - object: { [key: string]: any } | null -} - const WorkflowCanvas: React.FC = () => { const reactFlowWrapper = useRef(null) const [nodes, setNodes, onNodesChange] = useNodesState([]) @@ -68,6 +46,7 @@ const WorkflowCanvas: React.FC = () => { const { setViewport } = useReactFlow() const params = useParams<{ id: string }>() const workflowId = params.id + const session = useSession() const { toast } = useToast() @@ -141,7 +120,7 @@ const WorkflowCanvas: React.FC = () => { (params: Edge | Connection) => { setEdges((eds) => addEdge(params, eds)) }, - [toast, edges, setEdges] + [edges, setEdges] ) const onDragOver = useCallback( @@ -152,6 +131,7 @@ const WorkflowCanvas: React.FC = () => { [nodes] ) + // Adding a new node const onDrop = useCallback( async (event: React.DragEvent) => { event.preventDefault() @@ -222,11 +202,21 @@ const WorkflowCanvas: React.FC = () => { [edges, setEdges] ) + // Saving react flow instance state useEffect(() => { if (workflowId && reactFlowInstance) { - saveFlow(workflowId, reactFlowInstance) + updateDndFlow(session, workflowId, reactFlowInstance) } - }, [nodes, edges]) + }, [edges]) + + const onNodesDragStop = useCallback( + (event: React.MouseEvent, node: Node, nodes: Node[]) => { + if (workflowId && reactFlowInstance) { + updateDndFlow(session, workflowId, reactFlowInstance) + } + }, + [workflowId, reactFlowInstance] + ) return (
@@ -241,6 +231,7 @@ const WorkflowCanvas: React.FC = () => { onInit={setReactFlowInstance} onNodesChange={onNodesChange} onNodesDelete={onNodesDelete} + onNodeDragStop={onNodesDragStop} defaultEdgeOptions={defaultEdgeOptions} nodeTypes={nodeTypes} fitViewOptions={{ maxZoom: 1 }} diff --git a/frontend/src/components/forms/workflow.tsx b/frontend/src/components/forms/workflow.tsx index 2cd2418f6..4ab5d5519 100644 --- a/frontend/src/components/forms/workflow.tsx +++ b/frontend/src/components/forms/workflow.tsx @@ -1,10 +1,11 @@ +import { useSession } from "@/providers/session" import { zodResolver } from "@hookform/resolvers/zod" import { useMutation } from "@tanstack/react-query" -import axios from "axios" import { CircleIcon, Save } from "lucide-react" import { useForm } from "react-hook-form" import { z } from "zod" +import { updateWorkflow } from "@/lib/flow" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { @@ -23,6 +24,7 @@ import { TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip" +import { toast } from "@/components/ui/use-toast" // Define formSchema for type safety const workflowFormSchema = z.object({ @@ -30,6 +32,8 @@ const workflowFormSchema = z.object({ description: z.string(), }) +type WorkflowForm = z.infer + interface WorkflowFormProps { workflowId: string workflowTitle: string @@ -43,7 +47,9 @@ export function WorkflowForm({ workflowDescription, workflowStatus, }: WorkflowFormProps): React.JSX.Element { - const form = useForm>({ + const session = useSession() + console.log("WorkflowForm", session) + const form = useForm({ resolver: zodResolver(workflowFormSchema), defaultValues: { title: workflowTitle || "", @@ -51,23 +57,10 @@ export function WorkflowForm({ }, }) - // Submit form and update Workflow - async function updateWorkflow( - workflowId: string, - values: z.infer - ) { - const response = await axios.post( - `http://localhost:8000/workflows/${workflowId}`, - values - ) - return response.data // Adjust based on what your API returns - } - function useUpdateWorkflow(workflowId: string) { const mutation = useMutation({ - mutationFn: (values: z.infer) => - updateWorkflow(workflowId, values), - // Configure your mutation behavior here + mutationFn: (values: WorkflowForm) => + updateWorkflow(session, workflowId, values), onSuccess: (data, variables, context) => { console.log("Workflow update successful", data) }, @@ -80,8 +73,12 @@ export function WorkflowForm({ } const { mutate } = useUpdateWorkflow(workflowId) - function onSubmit(values: z.infer) { + function onSubmit(values: WorkflowForm) { mutate(values) + toast({ + title: "Saved workflow", + description: "Workflow updated successfully.", + }) } return ( diff --git a/frontend/src/components/workspace.tsx b/frontend/src/components/workspace.tsx index e09541027..8fa46de5d 100644 --- a/frontend/src/components/workspace.tsx +++ b/frontend/src/components/workspace.tsx @@ -2,7 +2,6 @@ import * as React from "react" import { WorkflowBuilderProvider } from "@/providers/builder" -import { useSessionContext } from "@/providers/session" import { Blend, BookText, @@ -38,8 +37,6 @@ import { ActionTiles } from "@/components/action-tiles" import { WorkflowCanvas } from "@/components/canvas" import { WorkflowPanel } from "@/components/panel" -import { Skeleton } from "./ui/skeleton" - interface WorkspaceProps { defaultLayout: number[] | undefined defaultCollapsed?: boolean @@ -51,11 +48,6 @@ export function Workspace({ defaultCollapsed = false, navCollapsedSize, }: WorkspaceProps) { - const { session, isLoading } = useSessionContext() - if (!session) { - throw new Error("Invalid session") - } - const sidePanelRef = React.useRef(null) const [isCollapsed, setIsCollapsed] = React.useState(defaultCollapsed) @@ -88,13 +80,9 @@ export function Workspace({ )}` } - if (isLoading) { - return - } - return ( - + > @@ -19,25 +20,24 @@ const ReactFlowInteractionsContext = createContext< >(undefined) interface ReactFlowInteractionsProviderProps { - session: Session children: ReactNode } export const WorkflowBuilderProvider: React.FC< ReactFlowInteractionsProviderProps -> = ({ session, children }) => { +> = ({ children }) => { + const maybeSession = useSession() const reactFlowInstance = useReactFlow() const params = useParams<{ id: string }>() const workflowId = params.id - const setReactFlowNodes = (nodes: Node[] | ((nodes: Node[]) => Node[])) => { - if (!session) { - console.error("Invalid session: cannot set nodes") - return - } - reactFlowInstance.setNodes(nodes) - saveFlow(session, workflowId, reactFlowInstance) - } + const setReactFlowNodes = useCallback( + (nodes: Node[] | ((nodes: Node[]) => Node[])) => { + reactFlowInstance.setNodes(nodes) + updateDndFlow(maybeSession, workflowId, reactFlowInstance) + }, + [maybeSession, workflowId, reactFlowInstance] + ) return ( +export const actionMetadataSchema = z.object({ + id: z.string(), + workflow_id: z.string(), + title: z.string(), + description: z.string(), +}) +export type ActionMetadata = z.infer + export const workflowMetadataSchema = z.object({ id: z.string(), title: z.string(), @@ -17,3 +25,14 @@ export const workflowMetadataSchema = z.object({ status: z.enum(["online", "offline"]), }) export type WorkflowMetadata = z.infer + +export const workflowResponseSchema = z.object({ + id: z.string(), + title: z.string(), + description: z.string(), + status: z.string(), + actions: z.record(z.array(actionResponseSchema)), + object: z.record(z.any()).nullable(), +}) + +export type WorkflowResponse = z.infer