blob: 699fb04e901a98d5a39b0953206fa548a0469242 [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";
giobc47f9f2025-05-12 08:31:07 +00009import { Separator } from "./components/ui/separator";
10import { Plus } from "lucide-react";
gio5f2f1002025-03-20 18:38:48 +040011
gio880de162025-05-11 07:26:00 +000012export function ProjectSelect() {
giod0026612025-05-08 13:00:36 +000013 const { toast } = useToast();
14 const store = useStateStore();
15 const [projects, setProjects] = useState<Project[]>([]);
gio7461e502025-05-12 10:11:55 +000016 const projectId = useProjectId();
17
giod0026612025-05-08 13:00:36 +000018 const refreshProjects = useCallback(async () => {
19 try {
20 const resp = await fetch("/api/project");
gio7461e502025-05-12 10:11:55 +000021 const projectList = await resp.json();
22 const sortedProjects = [...projectList].sort((a, b) =>
23 a.name.localeCompare(b.name, undefined, { sensitivity: "base" }),
24 );
25 setProjects(sortedProjects);
giod0026612025-05-08 13:00:36 +000026 } catch (e) {
27 console.log(e);
28 }
29 }, [setProjects]);
gio7461e502025-05-12 10:11:55 +000030
31 useEffect(() => {
32 if (projects.length > 0 && (projectId == null || !projects.some((p) => p.id === projectId))) {
33 store.setProject(projects[0].id);
34 }
35 }, [projectId, projects, store]);
giod0026612025-05-08 13:00:36 +000036 useEffect(() => {
37 refreshProjects();
38 }, [refreshProjects]);
gio7461e502025-05-12 10:11:55 +000039
giod0026612025-05-08 13:00:36 +000040 const project = useProjectId();
41 const [createNewOpen, setCreateNewOpen] = useState(false);
42 const onSelect = useCallback(
43 (projectId: string) => {
44 if (projectId === "create-new") {
45 setCreateNewOpen(true);
46 } else {
47 store.setProject(projectId);
48 }
49 },
50 [store],
51 );
52 const instance = useReactFlow();
53 const restoreSaved = useCallback(
54 async (projectId: string) => {
55 const resp = await fetch(`/api/project/${projectId}/saved`, {
56 method: "GET",
57 });
58 const inst = await resp.json();
59 const { x = 0, y = 0, zoom = 1 } = inst.viewport;
60 instance.setNodes(inst.nodes || []);
61 instance.setEdges(inst.edges || []);
62 instance.setViewport({ x, y, zoom });
63 },
64 [instance],
65 );
66 useEffect(() => {
67 if (project == null) {
68 return;
69 }
70 restoreSaved(project);
71 }, [project, restoreSaved]);
72 const [name, setName] = useState<string | undefined>(undefined);
73 const updateName = useCallback(
74 (e: ChangeEvent<HTMLInputElement>) => {
75 setName(e.target.value);
76 },
77 [setName],
78 );
79 const createNew = useCallback(() => {
80 console.log(name);
81 if (!name) {
82 return;
83 }
84 fetch("/api/project", {
85 method: "POST",
86 headers: {
87 "Content-Type": "application/json",
88 },
89 body: JSON.stringify({
90 name: name,
91 }),
92 })
93 .then(async (resp) => {
94 if (!resp.ok) {
95 return;
96 }
97 const { id } = await resp.json();
98 await refreshProjects();
99 store.setProject(id as string);
100 setCreateNewOpen(false);
101 toast({
102 title: `Created project: ${name}`,
103 });
104 })
105 .catch((e) => {
106 console.log(e);
107 toast({
108 variant: "destructive",
109 title: `Failed to create project: ${name}`,
110 });
111 });
112 }, [name, setCreateNewOpen, toast, store, refreshProjects]);
113 return (
gio880de162025-05-11 07:26:00 +0000114 <Select onValueChange={onSelect} value={project}>
giobc47f9f2025-05-12 08:31:07 +0000115 <SelectTrigger className="w-[200px] !border-none !shadow-none !focus:ring-0 !focus:ring-offset-0">
gio880de162025-05-11 07:26:00 +0000116 <SelectValue placeholder="Choose Project" defaultValue={project} />
117 </SelectTrigger>
118 <SelectContent>
giobc47f9f2025-05-12 08:31:07 +0000119 {projects.map((p) => (
120 <SelectItem key={p.id} value={p.id}>
121 {p.name}
122 </SelectItem>
123 ))}
124 <Separator />
gio880de162025-05-11 07:26:00 +0000125 <SelectItem value={"create-new"}>
126 <Dialog open={createNewOpen} onOpenChange={setCreateNewOpen}>
giobc47f9f2025-05-12 08:31:07 +0000127 <DialogTrigger className="flex flex-row items-center">
128 <Plus />
129 Create New
130 </DialogTrigger>
gio880de162025-05-11 07:26:00 +0000131 <DialogContent>
132 <Input type="text" placeholder="Name" onChange={updateName} />
133 <Button onClick={createNew}>Create New</Button>
134 </DialogContent>
135 </Dialog>
136 </SelectItem>
gio880de162025-05-11 07:26:00 +0000137 </SelectContent>
138 </Select>
giod0026612025-05-08 13:00:36 +0000139 );
140}