"use client"; import { useCallback, useRef } from "react"; import { Handle, Position, NodeProps, Node } from "@xyflow/react"; import { BaseNode } from "./BaseNode"; import { useAnnotationStore } from "@/store/annotationStore"; import { useWorkflowStore } from "@/store/workflowStore"; import { AnnotationNodeData } from "@/types"; import { useAdaptiveImageSrc } from "@/hooks/useAdaptiveImageSrc"; type AnnotationNodeType = Node; export function AnnotationNode({ id, data, selected }: NodeProps) { const nodeData = data; const openModal = useAnnotationStore((state) => state.openModal); const updateNodeData = useWorkflowStore((state) => state.updateNodeData); const fileInputRef = useRef(null); const handleFileChange = useCallback( (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; if (!file.type.match(/^image\/(png|jpeg|webp)$/)) { alert("Unsupported format. Use PNG, JPG, or WebP."); return; } if (file.size > 10 * 1024 * 1024) { alert("Image too large. Maximum size is 10MB."); return; } const reader = new FileReader(); reader.onload = (event) => { const base64 = event.target?.result as string; updateNodeData(id, { sourceImage: base64, sourceImageRef: undefined, outputImage: null, outputImageRef: undefined, annotations: [], }); }; reader.readAsDataURL(file); }, [id, updateNodeData] ); const handleDrop = useCallback( (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); const file = e.dataTransfer.files?.[0]; if (!file) return; const dt = new DataTransfer(); dt.items.add(file); if (fileInputRef.current) { fileInputRef.current.files = dt.files; fileInputRef.current.dispatchEvent(new Event("change", { bubbles: true })); } }, [] ); const handleDragOver = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); }, []); const handleEdit = useCallback(() => { const imageToEdit = nodeData.sourceImage || nodeData.outputImage; if (!imageToEdit) { alert("No image available. Connect an image or load one manually."); return; } openModal(id, imageToEdit, nodeData.annotations); }, [id, nodeData, openModal]); const handleRemove = useCallback(() => { updateNodeData(id, { sourceImage: null, sourceImageRef: undefined, outputImage: null, outputImageRef: undefined, annotations: [], }); }, [id, updateNodeData]); const displayImage = nodeData.outputImage || nodeData.sourceImage; const adaptiveDisplayImage = useAdaptiveImageSrc(displayImage, id); return ( {displayImage ? (
Annotated
{nodeData.annotations.length > 0 ? `Edit (${nodeData.annotations.length})` : "Add annotations"}
) : (
fileInputRef.current?.click()} onDrop={handleDrop} onDragOver={handleDragOver} className="w-full h-full bg-neutral-900/40 flex flex-col items-center justify-center cursor-pointer hover:bg-neutral-800/60 transition-colors" > Drop, click, or connect
)}
); }