blob: 83c658c179503ff3c1a433ba1ff3bf8c9cce86c1 [file] [log] [blame]
gio5f2f1002025-03-20 18:38:48 +04001import { ChangeEvent, useCallback, useEffect, useState } from "react";
2import { Project, useProjectId, useStateStore } from "./lib/state";
3import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./components/ui/select";
4import { useReactFlow } from "@xyflow/react";
5import { Input } from "./components/ui/input";
6import { Button } from "./components/ui/button";
7import { Dialog, DialogContent, DialogTrigger } from "./components/ui/dialog";
8import { useToast } from "@/hooks/use-toast";
9
10export function Header() {
11 const { toast } = useToast();
12 const store = useStateStore();
13 const [projects, setProjects] = useState<Project[]>([]);
gio5f2f1002025-03-20 18:38:48 +040014 const refreshProjects = useCallback(async () => {
15 try {
16 const resp = await fetch("/api/project");
17 setProjects(await resp.json());
18 } catch (e) {
19 console.log(e);
20 }
21 }, [setProjects]);
22 useEffect(() => {
23 refreshProjects();
24 }, [refreshProjects]);
25 const project = useProjectId();
26 const [createNewOpen, setCreateNewOpen] = useState(false);
27 const onSelect = useCallback((projectId: string) => {
28 if (projectId === "create-new") {
29 setCreateNewOpen(true);
30 } else {
31 store.setProject(projectId);
32 }
33 }, [store]);
34 const instance = useReactFlow();
35 const restoreSaved = useCallback(async (projectId: string) => {
36 const resp = await fetch(`/api/project/${projectId}/saved`, {
37 method: "GET",
38 });
39 const inst = await resp.json();
40 const { x = 0, y = 0, zoom = 1 } = inst.viewport;
41 instance.setNodes(inst.nodes || []);
42 instance.setEdges(inst.edges || []);
43 instance.setViewport({ x, y, zoom });
44 }, [instance]);
45 useEffect(() => {
46 if (project == null) {
47 return;
48 }
49 restoreSaved(project)
50 }, [project, restoreSaved]);
51 const [name, setName] = useState<string | undefined>(undefined);
52 const updateName = useCallback((e: ChangeEvent<HTMLInputElement>) => {
53 setName(e.target.value);
54 }, [setName]);
55 const createNew = useCallback(() => {
56 console.log(name);
57 if (!name) {
58 return;
59 }
60 fetch("/api/project", {
61 method: "POST",
62 headers: {
63 "Content-Type": "application/json",
64 },
65 body: JSON.stringify({
66 name: name,
67 }),
68 }).then(async (resp) => {
69 if (!resp.ok) {
70 return;
71 }
72 const { id } = await resp.json();
73 await refreshProjects();
74 store.setProject(id as string);
75 setCreateNewOpen(false);
76 toast({
77 title: `Created project: ${name}`,
78 });
79 }).catch((e) => {
80 console.log(e);
81 toast({
82 variant: "destructive",
83 title: `Failed to create project: ${name}`,
84 });
85 });
gio6cf8c272025-05-08 09:01:38 +000086 }, [name, setCreateNewOpen, toast, store, refreshProjects]);
gio5f2f1002025-03-20 18:38:48 +040087 return (
88 <div className="flex flex-row h-9">
89 <Select onValueChange={onSelect} value={project}>
90 <SelectTrigger>
91 <SelectValue placeholder="Choose Project" defaultValue={project} />
92 </SelectTrigger>
93 <SelectContent>
94 {projects.map((p) => (
95 <SelectItem value={p.id}>{p.name}</SelectItem>
96 ))}
97 <SelectItem value={"create-new"}>
98 <Dialog open={createNewOpen} onOpenChange={setCreateNewOpen}>
99 <DialogTrigger>Create New</DialogTrigger>
100 <DialogContent>
101 <Input type="text" placeholder="Name" onChange={updateName} />
102 <Button onClick={createNew}>Create New</Button>
103 </DialogContent>
104 </Dialog>
105 </SelectItem>
106 </SelectContent>
107 </Select>
gio6cf8c272025-05-08 09:01:38 +0000108
gio5f2f1002025-03-20 18:38:48 +0400109 </div>
110 );
111}