| gio | 5f2f100 | 2025-03-20 18:38:48 +0400 | [diff] [blame] | 1 | import { Button } from "./components/ui/button"; |
| gio | 3304672 | 2025-05-16 14:49:55 +0000 | [diff] [blame] | 2 | import { AppNode, AppState, Message, nodeLabel, useMessages, useProjectId } from "./lib/state"; |
| gio | 5f2f100 | 2025-03-20 18:38:48 +0400 | [diff] [blame] | 3 | import { useCallback, useEffect, useState } from "react"; |
| 4 | import { useNodes } from "@xyflow/react"; |
| 5 | import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "./components/ui/accordion"; |
| 6 | import { Badge } from "./components/ui/badge"; |
| gio | 9fc37c7 | 2025-05-16 12:17:18 +0000 | [diff] [blame] | 7 | import { CircleAlert, CircleX } from "lucide-react"; |
| gio | 5f2f100 | 2025-03-20 18:38:48 +0400 | [diff] [blame] | 8 | |
| 9 | export function Messages() { |
| gio | d002661 | 2025-05-08 13:00:36 +0000 | [diff] [blame] | 10 | const nodes = useNodes<AppNode>(); |
| gio | 3304672 | 2025-05-16 14:49:55 +0000 | [diff] [blame] | 11 | const projectId = useProjectId(); |
| gio | d002661 | 2025-05-08 13:00:36 +0000 | [diff] [blame] | 12 | const [nodeMap, setNodeMap] = useState<Map<string, AppNode>>(new Map()); |
| 13 | useEffect(() => { |
| 14 | setNodeMap(new Map(nodes.map((n) => [n.id, n]))); |
| 15 | }, [nodes, setNodeMap]); |
| gio | 9fc37c7 | 2025-05-16 12:17:18 +0000 | [diff] [blame] | 16 | const onClick = useCallback((_?: (state: AppState) => void) => { |
| 17 | return () => { |
| 18 | // TODO(gio): visual hints |
| 19 | // if (fn) { |
| 20 | // fn(store); |
| 21 | // } |
| 22 | }; |
| 23 | }, []); |
| gio | d002661 | 2025-05-08 13:00:36 +0000 | [diff] [blame] | 24 | const messages = useMessages(); |
| 25 | const [grouped, setGrouped] = useState<Map<string, Message[]>>(new Map()); |
| 26 | useEffect(() => { |
| 27 | const g = new Map<string, Message[]>(); |
| gio | 3304672 | 2025-05-16 14:49:55 +0000 | [diff] [blame] | 28 | if (projectId == null) { |
| 29 | g.set("global", [ |
| 30 | { |
| 31 | id: "global", |
| 32 | nodeId: undefined, |
| 33 | message: "Create a new project or select existing one to get started", |
| 34 | type: "FATAL", |
| 35 | }, |
| 36 | ]); |
| 37 | } else { |
| 38 | messages.forEach((m) => { |
| 39 | const id = m.nodeId || "global"; |
| 40 | const existing: Message[] = g.get(id) || []; |
| 41 | existing.push(m); |
| 42 | g.set(id, existing); |
| 43 | }); |
| 44 | } |
| gio | d002661 | 2025-05-08 13:00:36 +0000 | [diff] [blame] | 45 | setGrouped(g); |
| gio | 3304672 | 2025-05-16 14:49:55 +0000 | [diff] [blame] | 46 | }, [projectId, messages, setGrouped]); |
| gio | d002661 | 2025-05-08 13:00:36 +0000 | [diff] [blame] | 47 | const [open, setOpen] = useState<string[]>([...grouped.keys()]); |
| 48 | useEffect(() => { |
| 49 | // TODO(gio): do not reopen closed ones |
| 50 | setOpen([...grouped.keys()]); |
| 51 | }, [grouped, setOpen]); |
| 52 | return ( |
| gio | 8cadbc7 | 2025-05-16 07:51:02 +0000 | [diff] [blame] | 53 | <Accordion type="multiple" value={open} onValueChange={(v) => setOpen(v)} className="h-full max-h-full"> |
| gio | d002661 | 2025-05-08 13:00:36 +0000 | [diff] [blame] | 54 | {[...grouped.entries()].map(([id, messages]) => ( |
| 55 | <AccordionItem key={id} value={id}> |
| gio | 617b1dd | 2025-05-13 05:45:41 +0000 | [diff] [blame] | 56 | <AccordionTrigger className="flex flex-row-reverse !gap-1 !justify-end !h-fit !py-0"> |
| gio | da12043 | 2025-06-02 09:42:26 +0000 | [diff] [blame^] | 57 | <Badge className="h-5 min-w-5 rounded-full px-2 font-mono tabular-nums"> |
| 58 | {messages.length} |
| 59 | </Badge> |
| gio | d002661 | 2025-05-08 13:00:36 +0000 | [diff] [blame] | 60 | <div>{id === "global" ? "Global" : nodeLabel(nodeMap.get(id)!)}</div> |
| 61 | </AccordionTrigger> |
| gio | 9fc37c7 | 2025-05-16 12:17:18 +0000 | [diff] [blame] | 62 | <AccordionContent className="flex flex-col !px-1"> |
| 63 | {messages.map((m) => ( |
| 64 | <Button |
| 65 | key={m.id} |
| 66 | variant="ghost" |
| 67 | className="justify-start !h-fit !py-0 flex flex-row gap-1 items-center" |
| 68 | onMouseOver={onClick(m.onHighlight)} |
| 69 | onMouseLeave={onClick(m.onLooseHighlight)} |
| 70 | onClick={onClick(m.onClick)} |
| 71 | > |
| 72 | {m.type === "WARNING" && <CircleAlert className="w-4 h-4" color="brown" />} |
| 73 | {m.type === "FATAL" && <CircleX className="w-4 h-4" color="red" />} |
| 74 | {m.type === "INFO" && <div className="w-4 h-4" />} |
| 75 | <div>{m.message}</div> |
| 76 | </Button> |
| 77 | ))} |
| gio | d002661 | 2025-05-08 13:00:36 +0000 | [diff] [blame] | 78 | </AccordionContent> |
| 79 | </AccordionItem> |
| 80 | ))} |
| 81 | </Accordion> |
| 82 | ); |
| 83 | } |