| import { AppNode, nodeLabel, useEnv, useMessages, useProjectId, useStateStore } from "@/lib/state"; |
| import { Button } from "./ui/button"; |
| import { useCallback, useEffect, useState } from "react"; |
| import { generateDodoConfig } from "@/lib/config"; |
| import { useNodes, useReactFlow } from "@xyflow/react"; |
| import { useToast } from "@/hooks/use-toast"; |
| |
| export function Actions() { |
| const { toast } = useToast(); |
| const store = useStateStore(); |
| const projectId = useProjectId(); |
| const nodes = useNodes<AppNode>(); |
| const env = useEnv(); |
| const messages = useMessages(); |
| const instance = useReactFlow(); |
| const [ok, setOk] = useState(false); |
| const [loading, setLoading] = useState(false); |
| useEffect(() => { |
| setOk(!messages.some((m) => m.type === "FATAL")); |
| }, [messages, setOk]); |
| const monitor = useCallback(async () => { |
| const m = async function () { |
| const resp = await fetch(`/api/project/${projectId}/status`, { |
| method: "GET", |
| headers: { |
| "Content-Type": "application/json", |
| }, |
| }) |
| if (resp.status !== 200) { |
| return; |
| } |
| const data: { type: string, name: string, status: string }[] = await resp.json(); |
| console.log(data); |
| for (const n of nodes) { |
| if (n.type === "network") { |
| continue; |
| } |
| const d = data.find((d) => n.type === d.type && nodeLabel(n) === d.name); |
| if (d !== undefined) { |
| store.updateNodeData(n.id, { |
| state: d?.status, |
| }); |
| } |
| } |
| if (data.find((d) => d.status !== "success" && d.status != "failure") !== undefined) { |
| setTimeout(m, 1000); |
| } |
| }; |
| setTimeout(m, 100); |
| }, [projectId, nodes]); |
| const deploy = useCallback(async () => { |
| if (projectId == null) { |
| return; |
| } |
| setLoading(true); |
| try { |
| const config = generateDodoConfig(nodes, env); |
| if (config == null) { |
| throw new Error("MUST NOT REACH!"); |
| } |
| const resp = await fetch(`/api/project/${projectId}/deploy`, { |
| method: "POST", |
| headers: { |
| "Content-Type": "application/json", |
| }, |
| body: JSON.stringify({ |
| state: JSON.stringify(instance.toObject()), |
| config, |
| }), |
| }); |
| if (resp.ok) { |
| toast({ |
| title: "Deployment succeeded", |
| }); |
| monitor(); |
| } else { |
| toast({ |
| variant: "destructive", |
| title: "Deployment failed", |
| description: await resp.text(), |
| }); |
| } |
| } catch (e) { |
| console.log(e); |
| toast({ |
| variant: "destructive", |
| title: "Deployment failed", |
| }); |
| } finally { |
| setLoading(false); |
| } |
| }, [projectId, instance, nodes, env, setLoading]); |
| const [st, setSt] = useState<string>(); |
| const save = useCallback(async () => { |
| if (projectId == null) { |
| return; |
| } |
| const resp = await fetch(`/api/project/${projectId}/saved`, { |
| method: "POST", |
| headers: { |
| "Content-Type": "application/json", |
| }, |
| body: JSON.stringify(instance.toObject()), |
| }); |
| if (resp.ok) { |
| toast({ |
| title: "Save succeeded", |
| }); |
| } else { |
| toast({ |
| variant: "destructive", |
| title: "Save failed", |
| description: await resp.text(), |
| }); |
| } |
| }, [projectId, instance, setSt]); |
| const restoreSaved = useCallback(async () => { |
| if (projectId == null) { |
| return; |
| } |
| const resp = await fetch(`/api/project/${projectId}/saved`, { |
| method: "GET", |
| }); |
| const inst = await resp.json(); |
| const { x = 0, y = 0, zoom = 1 } = inst.viewport; |
| store.setNodes(inst.nodes || []); |
| store.setEdges(inst.edges || []); |
| instance.setViewport({ x, y, zoom }); |
| }, [projectId, instance, st]); |
| const clear = useCallback(() => { |
| store.setEdges([]); |
| store.setNodes([]); |
| instance.setViewport({ x: 0, y: 0, zoom: 1 }); |
| }, [store]); |
| // TODO(gio): Update store |
| const deleteProject = useCallback(async () => { |
| if (projectId == null) { |
| return; |
| } |
| const resp = await fetch(`/api/project/${projectId}`, { |
| method: "DELETE", |
| }); |
| if (resp.ok) { |
| clear(); |
| store.setProject(undefined); |
| toast({ |
| title: "Save succeeded", |
| }); |
| } else { |
| toast({ |
| variant: "destructive", |
| title: "Save failed", |
| description: await resp.text(), |
| }); |
| } |
| }, [store, clear]); |
| const [props, setProps] = useState({}); |
| useEffect(() => { |
| if (loading) { |
| setProps({ loading: true }); |
| } else if (ok) { |
| setProps({ disabled: false }); |
| } else { |
| setProps({ disabled: true }); |
| } |
| }, [ok, loading, setProps]); |
| return ( |
| <> |
| <Button onClick={deploy} {...props}>Deploy</Button> |
| <Button onClick={save}>Save</Button> |
| <Button onClick={restoreSaved}>Restore</Button> |
| <Button onClick={clear} variant="destructive">Clear</Button> |
| <Button onClick={deleteProject} variant="destructive" disabled={projectId === undefined}>Delete</Button> |
| </> |
| ) |
| } |