| import { Rect, Image as KonvaImage, Group } from 'react-konva'; | |
| import { CanvasObject, LogoPlaceholderObject } from '../../types/canvas.types'; | |
| import { useEffect, useRef, useState } from 'react'; | |
| import Konva from 'konva'; | |
| import TextEditable from './TextEditable'; | |
| interface CanvasObjectProps { | |
| object: CanvasObject; | |
| isSelected: boolean; | |
| onSelect: (e?: Konva.KonvaEventObject<MouseEvent>) => void; | |
| onDragStart?: (e: Konva.KonvaEventObject<DragEvent>) => void; | |
| onDragMove?: (e: Konva.KonvaEventObject<DragEvent>) => void; | |
| onDragEnd?: (e: Konva.KonvaEventObject<DragEvent>) => void; | |
| onTransformEnd?: (e: Konva.KonvaEventObject<Event>) => void; | |
| onEditingChange?: (id: string, isEditing: boolean, clickX?: number, clickY?: number) => void; | |
| onMouseEnter?: () => void; | |
| onMouseLeave?: () => void; | |
| shapeRef?: ((node: Konva.Node | null) => void) | React.RefObject<Konva.Node>; | |
| } | |
| export default function CanvasObjectRenderer({ | |
| object, | |
| isSelected, | |
| onSelect, | |
| onDragStart, | |
| onDragMove, | |
| onDragEnd, | |
| onTransformEnd, | |
| onEditingChange, | |
| onMouseEnter, | |
| onMouseLeave, | |
| shapeRef | |
| }: CanvasObjectProps) { | |
| switch (object.type) { | |
| case 'rect': | |
| return ( | |
| <Rect | |
| id={object.id} | |
| ref={(node) => { | |
| if (typeof shapeRef === 'function') { | |
| shapeRef(node); | |
| } else if (shapeRef) { | |
| (shapeRef as React.MutableRefObject<Konva.Rect | null>).current = node; | |
| } | |
| }} | |
| x={object.x} | |
| y={object.y} | |
| width={object.width} | |
| height={object.height} | |
| fill={object.fill} | |
| stroke={object.stroke} | |
| strokeWidth={object.strokeWidth} | |
| rotation={object.rotation} | |
| draggable | |
| onClick={(e) => onSelect(e)} | |
| onTap={(e) => onSelect(e)} | |
| onDragStart={onDragStart} | |
| onDragMove={onDragMove} | |
| onDragEnd={onDragEnd} | |
| onTransformEnd={onTransformEnd} | |
| onMouseEnter={onMouseEnter} | |
| onMouseLeave={onMouseLeave} | |
| /> | |
| ); | |
| case 'image': | |
| case 'huggy': | |
| return ( | |
| <ImageRenderer | |
| object={object} | |
| onSelect={onSelect} | |
| onDragStart={onDragStart} | |
| onDragMove={onDragMove} | |
| onDragEnd={onDragEnd} | |
| onTransformEnd={onTransformEnd} | |
| onMouseEnter={onMouseEnter} | |
| onMouseLeave={onMouseLeave} | |
| shapeRef={shapeRef} | |
| /> | |
| ); | |
| case 'text': | |
| return ( | |
| <TextEditable | |
| object={object} | |
| isSelected={isSelected} | |
| onSelect={onSelect} | |
| onDragStart={onDragStart} | |
| onDragMove={onDragMove} | |
| onDragEnd={onDragEnd} | |
| onTransformEnd={onTransformEnd} | |
| onEditingChange={onEditingChange || (() => {})} | |
| onMouseEnter={onMouseEnter} | |
| onMouseLeave={onMouseLeave} | |
| shapeRef={shapeRef as React.RefObject<Konva.Text>} | |
| /> | |
| ); | |
| case 'logoPlaceholder': | |
| return ( | |
| <LogoPlaceholderRenderer | |
| object={object} | |
| onSelect={onSelect} | |
| onDragStart={onDragStart} | |
| onDragMove={onDragMove} | |
| onDragEnd={onDragEnd} | |
| onTransformEnd={onTransformEnd} | |
| onMouseEnter={onMouseEnter} | |
| onMouseLeave={onMouseLeave} | |
| shapeRef={shapeRef} | |
| /> | |
| ); | |
| default: | |
| return null; | |
| } | |
| } | |
| // Image renderer with image loading | |
| function ImageRenderer({ | |
| object, | |
| onSelect, | |
| onDragStart, | |
| onDragMove, | |
| onDragEnd, | |
| onTransformEnd, | |
| onMouseEnter, | |
| onMouseLeave, | |
| shapeRef | |
| }: Omit<CanvasObjectProps, 'isSelected' | 'onEditingChange'>) { | |
| const [image, setImage] = useState<HTMLImageElement | null>(null); | |
| const imageRef = useRef<HTMLImageElement | null>(null); | |
| const nodeRef = useRef<Konva.Image | null>(null); | |
| useEffect(() => { | |
| const img = new window.Image(); | |
| img.src = object.type === 'image' || object.type === 'huggy' ? object.src : ''; | |
| img.crossOrigin = 'anonymous'; | |
| img.onload = () => { | |
| setImage(img); | |
| imageRef.current = img; | |
| // Force a small delay to ensure ref is registered and transformer can update | |
| setTimeout(() => { | |
| if (nodeRef.current) { | |
| // Trigger a layer redraw to ensure transformer picks up the node | |
| const layer = nodeRef.current.getLayer(); | |
| if (layer) { | |
| layer.batchDraw(); | |
| } | |
| } | |
| }, 10); | |
| }; | |
| return () => { | |
| imageRef.current = null; | |
| }; | |
| }, [object]); | |
| return ( | |
| <> | |
| {image && ( | |
| <KonvaImage | |
| id={object.id} | |
| ref={(node) => { | |
| nodeRef.current = node; | |
| if (typeof shapeRef === 'function') { | |
| shapeRef(node); | |
| } else if (shapeRef) { | |
| (shapeRef as React.MutableRefObject<Konva.Image | null>).current = node; | |
| } | |
| }} | |
| x={object.x} | |
| y={object.y} | |
| width={object.width} | |
| height={object.height} | |
| image={image} | |
| rotation={object.rotation} | |
| draggable | |
| onClick={(e) => onSelect(e)} | |
| onTap={(e) => onSelect(e)} | |
| onDragStart={onDragStart} | |
| onDragMove={onDragMove} | |
| onDragEnd={onDragEnd} | |
| onTransformEnd={onTransformEnd} | |
| onMouseEnter={onMouseEnter} | |
| onMouseLeave={onMouseLeave} | |
| /> | |
| )} | |
| </> | |
| ); | |
| } | |
| // Logo placeholder renderer with blur effect | |
| function LogoPlaceholderRenderer({ | |
| object, | |
| onSelect, | |
| onDragStart, | |
| onDragMove, | |
| onDragEnd, | |
| onTransformEnd, | |
| onMouseEnter, | |
| onMouseLeave, | |
| shapeRef | |
| }: Omit<CanvasObjectProps, 'isSelected' | 'onEditingChange'>) { | |
| const rectRef = useRef<Konva.Rect>(null); | |
| useEffect(() => { | |
| if (rectRef.current && object.type === 'logoPlaceholder') { | |
| rectRef.current.cache(); | |
| rectRef.current.filters([Konva.Filters.Blur]); | |
| rectRef.current.blurRadius(object.blurRadius); | |
| } | |
| }, [object]); | |
| if (object.type !== 'logoPlaceholder') return null; | |
| return ( | |
| <Rect | |
| id={object.id} | |
| ref={(node) => { | |
| rectRef.current = node; | |
| if (typeof shapeRef === 'function') { | |
| shapeRef(node); | |
| } else if (shapeRef) { | |
| (shapeRef as React.MutableRefObject<Konva.Rect | null>).current = node; | |
| } | |
| }} | |
| x={object.x} | |
| y={object.y} | |
| width={object.width} | |
| height={object.height} | |
| fill="rgba(255, 255, 255, 0.3)" | |
| stroke="rgba(200, 200, 200, 0.5)" | |
| strokeWidth={2} | |
| rotation={object.rotation} | |
| draggable | |
| onClick={(e) => onSelect(e)} | |
| onTap={(e) => onSelect(e)} | |
| onDragStart={onDragStart} | |
| onDragMove={onDragMove} | |
| onDragEnd={onDragEnd} | |
| onTransformEnd={onTransformEnd} | |
| onMouseEnter={onMouseEnter} | |
| onMouseLeave={onMouseLeave} | |
| /> | |
| ); | |
| } | |