|
|
import { useEffect, useRef } from 'react'; |
|
|
import { Text as KonvaText } from 'react-konva'; |
|
|
import { TextObject } from '../../types/canvas.types'; |
|
|
import Konva from 'konva'; |
|
|
|
|
|
|
|
|
function getFontStyle(bold: boolean, italic: boolean, fontWeight?: 'normal' | 'bold' | 'black'): string { |
|
|
const weight = fontWeight === 'black' ? '900' : bold ? 'bold' : 'normal'; |
|
|
const style = italic ? 'italic' : ''; |
|
|
return `${weight} ${style}`.trim(); |
|
|
} |
|
|
|
|
|
interface TextEditableProps { |
|
|
object: TextObject; |
|
|
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.Text | null) => void) | React.RefObject<Konva.Text>; |
|
|
} |
|
|
|
|
|
export default function TextEditable({ |
|
|
object, |
|
|
isSelected, |
|
|
onSelect, |
|
|
onDragStart, |
|
|
onDragMove, |
|
|
onDragEnd, |
|
|
onTransformEnd, |
|
|
onEditingChange, |
|
|
onMouseEnter, |
|
|
onMouseLeave, |
|
|
shapeRef |
|
|
}: TextEditableProps) { |
|
|
const textNodeRef = useRef<Konva.Text | null>(null); |
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
if (object.text === '' && isSelected && !object.isFixedSize && !object.isEditing) { |
|
|
|
|
|
const timer = setTimeout(() => { |
|
|
onEditingChange(object.id, true); |
|
|
}, 100); |
|
|
return () => clearTimeout(timer); |
|
|
} |
|
|
}, [object.text, isSelected, object.isFixedSize, object.isEditing, object.id, onEditingChange]); |
|
|
|
|
|
|
|
|
const handleDoubleClick = (e: Konva.KonvaEventObject<MouseEvent>) => { |
|
|
if (!object.isEditing) { |
|
|
const stage = e.target.getStage(); |
|
|
const pointerPos = stage?.getPointerPosition(); |
|
|
if (pointerPos) { |
|
|
|
|
|
const clickX = pointerPos.x - object.x; |
|
|
const clickY = pointerPos.y - object.y; |
|
|
onEditingChange(object.id, true, clickX, clickY); |
|
|
} else { |
|
|
onEditingChange(object.id, true); |
|
|
} |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const calculateFitFontSize = (): number => { |
|
|
if (!object.isFixedSize) return object.fontSize; |
|
|
if (!object.text) return object.fontSize; |
|
|
|
|
|
try { |
|
|
|
|
|
const tempText = new Konva.Text({ |
|
|
text: object.text || 'M', |
|
|
fontSize: object.fontSize, |
|
|
fontFamily: object.fontFamily, |
|
|
fontStyle: getFontStyle(object.bold, object.italic, object.fontWeight), |
|
|
width: object.width, |
|
|
height: object.height |
|
|
}); |
|
|
|
|
|
let fontSize = object.fontSize; |
|
|
const maxWidth = object.width; |
|
|
const maxHeight = object.height; |
|
|
|
|
|
|
|
|
while (fontSize > 10) { |
|
|
tempText.fontSize(fontSize); |
|
|
const size = tempText.measureSize(object.text || 'M'); |
|
|
|
|
|
if (size.width <= maxWidth && size.height <= maxHeight) { |
|
|
break; |
|
|
} |
|
|
fontSize -= 1; |
|
|
} |
|
|
|
|
|
tempText.destroy(); |
|
|
return fontSize; |
|
|
} catch (error) { |
|
|
console.error('Error calculating fit font size:', error); |
|
|
return object.fontSize; |
|
|
} |
|
|
}; |
|
|
|
|
|
const displayFontSize = object.isFixedSize ? calculateFitFontSize() : object.fontSize; |
|
|
|
|
|
return ( |
|
|
<KonvaText |
|
|
id={object.id} |
|
|
ref={(node) => { |
|
|
// Call the callback ref if it's a function |
|
|
if (typeof shapeRef === 'function') { |
|
|
shapeRef(node); |
|
|
} else if (shapeRef) { |
|
|
// Set the RefObject if it's an object |
|
|
(shapeRef as React.MutableRefObject<Konva.Text | null>).current = node; |
|
|
} |
|
|
// Also store internally for our own use |
|
|
textNodeRef.current = node; |
|
|
}} |
|
|
x={object.x} |
|
|
y={object.y} |
|
|
width={object.width} |
|
|
height={object.height} |
|
|
text={object.text} |
|
|
fontSize={displayFontSize} |
|
|
fontFamily={object.fontFamily} |
|
|
fill={object.fill} |
|
|
fontStyle={getFontStyle(object.bold, object.italic, object.fontWeight)} |
|
|
align={object.align || 'left'} |
|
|
verticalAlign="top" |
|
|
rotation={object.rotation} |
|
|
padding={0} |
|
|
lineHeight={1} |
|
|
draggable={true} |
|
|
onClick={(e) => onSelect(e)} |
|
|
onTap={(e) => onSelect(e)} |
|
|
onDblClick={handleDoubleClick} |
|
|
onDblTap={handleDoubleClick} |
|
|
onDragStart={onDragStart} |
|
|
onDragMove={onDragMove} |
|
|
onDragEnd={onDragEnd} |
|
|
onTransformEnd={onTransformEnd} |
|
|
onMouseEnter={onMouseEnter} |
|
|
onMouseLeave={onMouseLeave} |
|
|
opacity={object.isEditing ? 0 : 1} |
|
|
listening={!object.isEditing} |
|
|
/> |
|
|
); |
|
|
} |
|
|
|