ChunDe's picture
feat: Add responsive UI scaling and fix Bison font loading
d06267b
raw
history blame
6.77 kB
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}
/>
);
}