import React, { useState, useRef, useEffect } from "react"; import { useAppStore } from "../store"; import { TextNote } from "../types"; import { AlignLeft, AlignCenter, AlignRight, Bold, Italic, Underline, List, ListOrdered, Link, } from "lucide-react"; export const TextNoteNode = ({ note }: { note: TextNote }) => { const { setTextNotes, zoom, pan, isAnnotationMode, isClickThrough, selectedNodeIds, setSelectedNodeIds, updateSelectedNodes, } = useAppStore(); const [isDragging, setIsDragging] = useState(false); const [isEditing, setIsEditing] = useState(false); const [localText, setLocalText] = useState(note.text); const contentEditableRef = useRef(null); const isSelected = selectedNodeIds.includes(note.id); useEffect(() => { if (isEditing && contentEditableRef.current) { if (contentEditableRef.current.innerHTML !== localText) { contentEditableRef.current.innerHTML = localText; } contentEditableRef.current.focus(); } }, [isEditing]); const updateNote = (changes: Partial) => { setTextNotes((prev) => prev.map((n) => (n.id === note.id ? { ...n, ...changes } : n)), ); }; const handlePointerDown = (e: React.PointerEvent) => { if (isClickThrough || isAnnotationMode) return; if (isEditing) return; // let user click inside text if ((e.target as HTMLElement).tagName.toLowerCase() === "a") { // let the link click happen e.stopPropagation(); return; } e.stopPropagation(); if (e.button === 2) { return; } if (!isSelected) { setTextNotes((prev) => { let idsToSelect = [note.id]; if (note.groupId) { idsToSelect = prev .filter((n) => n.groupId === note.groupId) .map((n) => n.id); } if (e.shiftKey) { setSelectedNodeIds((sel) => Array.from(new Set([...sel, ...idsToSelect])), ); } else { setSelectedNodeIds(idsToSelect); } return prev; }); } setIsDragging(true); e.currentTarget.setPointerCapture(e.pointerId); }; const handlePointerMoveRoot = (e: React.PointerEvent) => { if (isResizing) { handleResizeMove(e); } else if (isDragging) { e.stopPropagation(); updateSelectedNodes(e.movementX / zoom, e.movementY / zoom, note.id); } }; const handlePointerUpRoot = (e: React.PointerEvent) => { if (isResizing) { handleResizeEnd(e); } else if (isDragging) { setIsDragging(false); e.currentTarget.releasePointerCapture(e.pointerId); } }; const handleDoubleClick = (e: React.MouseEvent) => { e.stopPropagation(); setIsEditing(true); }; const handleBlur = () => { setIsEditing(false); const newText = contentEditableRef.current?.innerHTML || ""; setLocalText(newText); updateNote({ text: newText }); }; const handleKeyDown = (e: React.KeyboardEvent) => { e.stopPropagation(); // prevent app-level shortcuts if (e.key === "Escape") { handleBlur(); } }; const [isResizing, setIsResizing] = useState(false); const handleResizeStart = (e: React.PointerEvent) => { e.stopPropagation(); setIsResizing(true); e.currentTarget.setPointerCapture(e.pointerId); }; const handleResizeMove = (e: React.PointerEvent) => { if (isResizing) { e.stopPropagation(); updateNote({ width: Math.max(100, note.width + e.movementX / zoom), height: Math.max(50, (note.height || 80) + e.movementY / zoom), }); } }; const handleResizeEnd = (e: React.PointerEvent) => { if (isResizing) { setIsResizing(false); e.currentTarget.releasePointerCapture(e.pointerId); } }; const colors = ["#FFFFFF", "#FFD60A", "#FF453A", "#32D74B", "#0A84FF"]; const fonts = ["Inter", "Courier New", "Times New Roman", "Georgia", "Arial"]; // Apply default styles const alignment = note.alignment || "left"; const isBold = note.isBold || false; const isItalic = note.isItalic || false; const isUnderline = note.isUnderline || false; const color = note.color || "#FFFFFF"; const bgColor = note.bgColor || "rgba(0,0,0,0.6)"; const fontSize = note.fontSize || 14; const fontFamily = note.fontFamily || "Inter"; return (
e.preventDefault()} > {/* Floating Toolbar */} {isEditing && (
{ e.preventDefault(); e.stopPropagation(); }} >
{fontSize}
{colors.map((c) => (
)}
{isEditing ? (
e.stopPropagation()} /> ) : (
Double click to edit text', }} /> )} {/* Delete button (only visible on hover) */} {!isEditing && ( )} {/* Resize Handle (only visible on hover/editing) */}
); };