figmatic-print-builder/elements/ImageElement.jsx

191 lines
6.3 KiB
React
Raw Normal View History

2026-06-16 12:58:18 +05:30
import { Image as KonvaImage, Transformer, Rect, Text } from "react-konva";
import { useRef, useEffect, useState } from "react";
import useEditorStore from "../useEditorStore";
export default function ImageElement({ element }) {
const updateElement = useEditorStore((state) => state.updateElement);
const setSelectedId = useEditorStore((state) => state.setSelectedId);
const selectedId = useEditorStore((state) => state.selectedId);
const shapeRef = useRef();
const trRef = useRef();
const [image, setImage] = useState(null);
const [isHovered, setIsHovered] = useState(false);
const isSelected = selectedId === element.id;
useEffect(() => {
if (element.src) {
const img = new window.Image();
img.src = element.src;
img.onload = () => {
setImage(img);
};
} else {
setImage(null);
}
}, [element.src]);
useEffect(() => {
if (isSelected && trRef.current && shapeRef.current) {
trRef.current.nodes([shapeRef.current]);
trRef.current.getLayer().batchDraw();
}
}, [isSelected]);
const handleImageUpload = () => {
const input = document.createElement("input");
input.type = "file";
input.accept = "image/*";
input.onchange = (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (event) => {
updateElement(element.id, { src: event.target.result });
};
reader.readAsDataURL(file);
}
};
input.click();
};
const handleDeleteImage = (e) => {
e.cancelBubble = true;
updateElement(element.id, { src: null });
};
if (!image) {
// Placeholder when no image
return (
<>
<Rect
ref={shapeRef}
x={element.x}
y={element.y}
width={element.width}
height={element.height}
fill="#f0f0f0"
stroke="#ddd"
strokeWidth={2}
dash={[5, 5]}
rotation={element.rotation}
draggable
onClick={() => {
setSelectedId(element.id);
handleImageUpload();
}}
onDragEnd={(e) => {
const snap = (v) => Math.round(v / 10) * 10;
updateElement(element.id, {
x: snap(e.target.x()),
y: snap(e.target.y()),
});
}}
/>
<Text
text="📷 Click to Upload"
x={element.x + element.width / 2 - 60}
y={element.y + element.height / 2 - 10}
fontSize={14}
fill="#999"
listening={false}
/>
{isSelected && (
<Transformer
ref={trRef}
rotateEnabled={true}
boundBoxFunc={(oldBox, newBox) => {
if (newBox.width < 20 || newBox.height < 20) {
return oldBox;
}
return newBox;
}}
/>
)}
</>
);
}
return (
<>
<KonvaImage
ref={shapeRef}
image={image}
x={element.x}
y={element.y}
width={element.width}
height={element.height}
rotation={element.rotation}
draggable
onClick={() => setSelectedId(element.id)}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
onDragEnd={(e) => {
const snap = (v) => Math.round(v / 10) * 10;
updateElement(element.id, {
x: snap(e.target.x()),
y: snap(e.target.y()),
});
}}
onTransformEnd={() => {
const node = shapeRef.current;
const scaleX = node.scaleX();
const scaleY = node.scaleY();
node.scaleX(1);
node.scaleY(1);
updateElement(element.id, {
x: node.x(),
y: node.y(),
width: Math.max(5, node.width() * scaleX),
height: Math.max(5, node.height() * scaleY),
rotation: node.rotation(),
});
}}
/>
{/* Delete button on hover */}
{isHovered && (
<Rect
x={element.x + element.width - 30}
y={element.y + 5}
width={25}
height={25}
fill="#ff4444"
cornerRadius={4}
onClick={handleDeleteImage}
onTap={handleDeleteImage}
/>
)}
{isHovered && (
<Text
text="×"
x={element.x + element.width - 22}
y={element.y + 8}
fontSize={18}
fill="#fff"
fontStyle="bold"
onClick={handleDeleteImage}
onTap={handleDeleteImage}
/>
)}
{isSelected && (
<Transformer
ref={trRef}
rotateEnabled={true}
boundBoxFunc={(oldBox, newBox) => {
// Limit resize
if (newBox.width < 20 || newBox.height < 20) {
return oldBox;
}
return newBox;
}}
/>
)}
</>
);
}