"use client"; import { useState, useCallback, useRef } from "react"; import { Handle, Position, type NodeProps, type Node } from "@xyflow/react"; import { useMutation } from "convex/react"; import { api } from "@/convex/_generated/api"; import type { Id } from "@/convex/_generated/dataModel"; import BaseNodeWrapper from "./base-node-wrapper"; type ImageNodeData = { storageId?: string; url?: string; filename?: string; mimeType?: string; _status?: string; _statusMessage?: string; }; export type ImageNode = Node; export default function ImageNode({ id, data, selected }: NodeProps) { const generateUploadUrl = useMutation(api.storage.generateUploadUrl); const updateData = useMutation(api.nodes.updateData); const fileInputRef = useRef(null); const [isUploading, setIsUploading] = useState(false); const [isDragOver, setIsDragOver] = useState(false); const uploadFile = useCallback( async (file: File) => { if (!file.type.startsWith("image/")) return; setIsUploading(true); try { // 1. Upload-URL generieren const uploadUrl = await generateUploadUrl(); // 2. Datei hochladen const result = await fetch(uploadUrl, { method: "POST", headers: { "Content-Type": file.type }, body: file, }); const { storageId } = await result.json(); // 3. Node-Data mit storageId aktualisieren // Die URL wird serverseitig in der nodes.list Query aufgelöst await updateData({ nodeId: id as Id<"nodes">, data: { storageId, filename: file.name, mimeType: file.type, }, }); } catch (err) { console.error("Upload failed:", err); } finally { setIsUploading(false); } }, [id, generateUploadUrl, updateData], ); // Click-to-Upload const handleClick = useCallback(() => { if (!data.url && !isUploading) { fileInputRef.current?.click(); } }, [data.url, isUploading]); const handleFileChange = useCallback( (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) uploadFile(file); }, [uploadFile], ); // Drag & Drop auf den Node const handleDragOver = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); if (e.dataTransfer.types.includes("Files")) { setIsDragOver(true); e.dataTransfer.dropEffect = "copy"; } }, []); const handleDragLeave = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragOver(false); }, []); const handleDrop = useCallback( (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragOver(false); const file = e.dataTransfer.files?.[0]; if (file && file.type.startsWith("image/")) { uploadFile(file); } }, [uploadFile], ); // Bild ersetzen const handleReplace = useCallback(() => { fileInputRef.current?.click(); }, []); return (
🖼️ Bild
{data.url && ( )}
{isUploading ? (
Wird hochgeladen…
) : data.url ? ( {data.filename ) : (
📁 Klicken oder hierhin ziehen PNG, JPG, WebP
)} {data.filename && data.url && (

{data.filename}

)}
); }