125 lines
4.6 KiB
JavaScript
125 lines
4.6 KiB
JavaScript
import { Text, Transformer } from "react-konva";
|
|
import { useRef, useEffect, useState } from "react";
|
|
import useEditorStore from "../useEditorStore";
|
|
import { Html } from "react-konva-utils";
|
|
|
|
export default function TextElement({ 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 [isEditing, setIsEditing] = useState(false);
|
|
const [editValue, setEditValue] = useState(element.text);
|
|
|
|
const isSelected = selectedId === element.id;
|
|
|
|
useEffect(() => {
|
|
if (isSelected && trRef.current && shapeRef.current) {
|
|
trRef.current.nodes([shapeRef.current]);
|
|
trRef.current.getLayer().batchDraw();
|
|
}
|
|
}, [isSelected]);
|
|
|
|
useEffect(() => {
|
|
setEditValue(element.text);
|
|
}, [element.text]);
|
|
|
|
const handleDoubleClick = () => {
|
|
setIsEditing(true);
|
|
setEditValue(element.text);
|
|
};
|
|
|
|
const handleBlur = () => {
|
|
setIsEditing(false);
|
|
updateElement(element.id, { text: editValue });
|
|
};
|
|
|
|
const handleKeyDown = (e) => {
|
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
e.preventDefault();
|
|
handleBlur();
|
|
}
|
|
if (e.key === "Escape") {
|
|
setIsEditing(false);
|
|
setEditValue(element.text);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{isEditing ? (
|
|
<Html
|
|
divProps={{
|
|
style: {
|
|
position: "absolute",
|
|
top: `${element.y}px`,
|
|
left: `${element.x}px`,
|
|
transform: `rotate(${element.rotation}deg)`,
|
|
transformOrigin: "top left",
|
|
},
|
|
}}
|
|
>
|
|
<textarea
|
|
value={editValue}
|
|
onChange={(e) => setEditValue(e.target.value)}
|
|
onBlur={handleBlur}
|
|
onKeyDown={handleKeyDown}
|
|
autoFocus
|
|
style={{
|
|
fontSize: `${element.fontSize}px`,
|
|
fontFamily: element.fontFamily,
|
|
color: element.color,
|
|
textAlign: element.align,
|
|
fontWeight: element.fontWeight || "normal",
|
|
fontStyle: element.italic ? "italic" : "normal",
|
|
textDecoration: element.underline ? "underline" : "none",
|
|
border: "2px solid #2196f3",
|
|
outline: "none",
|
|
padding: "2px 4px",
|
|
background: "rgba(255, 255, 255, 0.95)",
|
|
resize: "none",
|
|
minWidth: "100px",
|
|
minHeight: `${element.fontSize + 10}px`,
|
|
}}
|
|
/>
|
|
</Html>
|
|
) : (
|
|
<Text
|
|
ref={shapeRef}
|
|
text={element.text}
|
|
fontSize={element.fontSize}
|
|
fontFamily={element.fontFamily}
|
|
fill={element.color}
|
|
align={element.align}
|
|
fontStyle={`${element.bold ? "bold" : ""} ${element.italic ? "italic" : ""} ${element.fontWeight || "normal"}`}
|
|
textDecoration={element.underline ? "underline" : ""}
|
|
x={element.x}
|
|
y={element.y}
|
|
rotation={element.rotation}
|
|
draggable
|
|
onClick={() => setSelectedId(element.id)}
|
|
onDblClick={handleDoubleClick}
|
|
onDblTap={handleDoubleClick}
|
|
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;
|
|
updateElement(element.id, {
|
|
rotation: node.rotation(),
|
|
});
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
{isSelected && !isEditing && <Transformer ref={trRef} enabledAnchors={[]} rotateEnabled={true} />}
|
|
</>
|
|
);
|
|
} |