392 lines
10 KiB
JavaScript
392 lines
10 KiB
JavaScript
import { addDistanceTextToPolygon, getDistance } from '@/util/canvas-util'
|
|
import { useCanvas } from '@/hooks/useCanvas'
|
|
import { fabric } from 'fabric'
|
|
import { v4 as uuidv4 } from 'uuid'
|
|
import { useEffect } from 'react'
|
|
|
|
export default function Roof() {
|
|
const {
|
|
canvas,
|
|
addShape,
|
|
handleUndo,
|
|
handleRedo,
|
|
handleClear,
|
|
handleCopy,
|
|
handleDelete,
|
|
handleSave,
|
|
handlePaste,
|
|
handleRotate,
|
|
attachCustomControlOnPolygon,
|
|
saveImage,
|
|
handleFlip,
|
|
} = useCanvas('canvas')
|
|
|
|
useEffect(() => {
|
|
let circle = new fabric.Circle({
|
|
radius: 40,
|
|
fill: 'rgba(200, 0, 0, 0.3)',
|
|
originX: 'center',
|
|
originY: 'center',
|
|
})
|
|
|
|
let text = new fabric.Textbox('AJLoveChina', {
|
|
originX: 'center',
|
|
originY: 'center',
|
|
textAlign: 'center',
|
|
fontSize: 12,
|
|
})
|
|
|
|
let group = new fabric.Group([circle, text], {
|
|
left: 100,
|
|
top: 100,
|
|
originX: 'center',
|
|
originY: 'center',
|
|
})
|
|
|
|
group.on('mousedblclick', () => {
|
|
// textForEditing is temporary obj,
|
|
// and will be removed after editing
|
|
console.log(text.type)
|
|
let textForEditing = new fabric.Textbox(text.text, {
|
|
originX: 'center',
|
|
originY: 'center',
|
|
textAlign: text.textAlign,
|
|
fontSize: text.fontSize,
|
|
|
|
left: group.left,
|
|
top: group.top,
|
|
})
|
|
|
|
// hide group inside text
|
|
text.visible = false
|
|
// note important, text cannot be hidden without this
|
|
group.addWithUpdate()
|
|
|
|
textForEditing.visible = true
|
|
// do not give controls, do not allow move/resize/rotation on this
|
|
textForEditing.hasConstrols = false
|
|
|
|
// now add this temporary obj to canvas
|
|
canvas.add(textForEditing)
|
|
canvas.setActiveObject(textForEditing)
|
|
// make the cursor showing
|
|
textForEditing.enterEditing()
|
|
textForEditing.selectAll()
|
|
|
|
// editing:exited means you click outside of the textForEditing
|
|
textForEditing.on('editing:exited', () => {
|
|
let newVal = textForEditing.text
|
|
let oldVal = text.text
|
|
|
|
// then we check if text is changed
|
|
if (newVal !== oldVal) {
|
|
text.set({
|
|
text: newVal,
|
|
visible: true,
|
|
})
|
|
|
|
// comment before, you must call this
|
|
group.addWithUpdate()
|
|
|
|
// we do not need textForEditing anymore
|
|
textForEditing.visible = false
|
|
canvas?.remove(textForEditing)
|
|
|
|
// optional, buf for better user experience
|
|
canvas?.setActiveObject(group)
|
|
}
|
|
})
|
|
})
|
|
|
|
canvas?.add(group)
|
|
}, [canvas])
|
|
|
|
const addRect = () => {
|
|
const rect = new fabric.Rect({
|
|
height: 200,
|
|
width: 200,
|
|
top: 10,
|
|
left: 10,
|
|
opacity: 0.4,
|
|
fill: randomColor(),
|
|
stroke: 'red',
|
|
name: uuidv4(),
|
|
})
|
|
|
|
addShape(rect)
|
|
}
|
|
|
|
const addHorizontalLine = () => {
|
|
const { x1, y1, x2, y2 } = { x1: 20, y1: 100, x2: 220, y2: 100 }
|
|
/**
|
|
* 시작X,시작Y,도착X,도착Y 좌표
|
|
*/
|
|
const horizontalLine = new fabric.Line([x1, y1, x2, y2], {
|
|
name: uuidv4(),
|
|
stroke: 'red',
|
|
strokeWidth: 3,
|
|
selectable: true,
|
|
})
|
|
|
|
const text = new fabric.Text(getDistance(x1, y1, x2, y2).toString(), {
|
|
fontSize: 20,
|
|
left: (x2 - x1) / 2,
|
|
top: y1 - 20,
|
|
})
|
|
|
|
const group = new fabric.Group([horizontalLine, text], {
|
|
left: 20,
|
|
top: 20,
|
|
})
|
|
|
|
// addShape(horizontalLine)
|
|
addShape(group)
|
|
console.log(JSON.stringify(canvas))
|
|
}
|
|
|
|
const addVerticalLine = () => {
|
|
const verticalLine = new fabric.Line([10, 10, 10, 100], {
|
|
name: uuidv4(),
|
|
stroke: 'red',
|
|
strokeWidth: 3,
|
|
selectable: true,
|
|
})
|
|
|
|
addShape(verticalLine)
|
|
}
|
|
|
|
const addTriangle = () => {
|
|
const triangle = new fabric.Triangle({
|
|
name: uuidv4(),
|
|
top: 50,
|
|
left: 50,
|
|
width: 100,
|
|
stroke: randomColor(),
|
|
strokeWidth: 3,
|
|
})
|
|
|
|
addShape(triangle)
|
|
}
|
|
|
|
const addTrapezoid = () => {
|
|
const trapezoid = new fabric.Polygon(
|
|
[
|
|
{ x: 100, y: 100 }, // 좌상단
|
|
{ x: 500, y: 100 }, // 우상단
|
|
{ x: 750, y: 700 }, // 우하단
|
|
{ x: 250, y: 400 }, // 좌하단
|
|
],
|
|
{
|
|
name: uuidv4(),
|
|
stroke: 'red',
|
|
opacity: 0.4,
|
|
strokeWidth: 3,
|
|
selectable: true,
|
|
objectCaching: false,
|
|
},
|
|
)
|
|
attachCustomControlOnPolygon(trapezoid)
|
|
const group = addDistanceTextToPolygon(trapezoid)
|
|
addGroupClickEvent(group)
|
|
canvas?.add(group)
|
|
canvas?.renderAll()
|
|
}
|
|
|
|
// group에 클릭 이벤트를 추가하여 클릭 시 group을 제거하고 object들만 남기는 함수
|
|
function addGroupClickEvent(group) {
|
|
group.on('selected', (e) => {
|
|
console.log(e)
|
|
})
|
|
group.on('mousedblclick', (e) => {
|
|
// textForEditing is temporary obj,
|
|
// and will be removed after editing
|
|
const pointer = canvas?.getPointer(e.e) // 마우스 클릭 위치 가져오기
|
|
let minDistance = Infinity
|
|
let closestTextbox = null
|
|
const groupPoint = group.getCenterPoint()
|
|
group.getObjects().forEach(function (object) {
|
|
if (object.type === 'textbox') {
|
|
// 객체가 TextBox인지 확인
|
|
|
|
const objectCenter = object.getCenterPoint() // TextBox 객체의 중심점 가져오기
|
|
const dx = objectCenter.x + groupPoint.x - pointer.x
|
|
const dy = objectCenter.y + groupPoint.y - pointer.y
|
|
const distance = Math.sqrt(dx * dx + dy * dy) // 마우스 클릭 위치와 TextBox 객체 사이의 거리 계산
|
|
|
|
if (distance < minDistance) {
|
|
// 가장 짧은 거리를 가진 TextBox 객체 찾기
|
|
minDistance = distance
|
|
closestTextbox = object
|
|
}
|
|
}
|
|
})
|
|
|
|
let textForEditing = new fabric.Textbox(closestTextbox.text, {
|
|
originX: 'center',
|
|
originY: 'center',
|
|
textAlign: closestTextbox.textAlign,
|
|
fontSize: closestTextbox.fontSize,
|
|
left: closestTextbox.left + groupPoint.x,
|
|
top: closestTextbox.top + groupPoint.y,
|
|
})
|
|
|
|
// hide group inside text
|
|
closestTextbox.visible = false
|
|
// note important, text cannot be hidden without this
|
|
group.addWithUpdate()
|
|
|
|
textForEditing.visible = true
|
|
// do not give controls, do not allow move/resize/rotation on this
|
|
textForEditing.hasConstrols = false
|
|
|
|
// now add this temporary obj to canvas
|
|
canvas?.add(textForEditing)
|
|
canvas?.setActiveObject(textForEditing)
|
|
// make the cursor showing
|
|
textForEditing?.enterEditing()
|
|
textForEditing?.selectAll()
|
|
|
|
// editing:exited means you click outside of the textForEditing
|
|
textForEditing?.on('editing:exited', () => {
|
|
let newVal = textForEditing.text
|
|
|
|
// then we check if text is changed
|
|
closestTextbox.set({
|
|
text: newVal,
|
|
visible: true,
|
|
})
|
|
|
|
// comment before, you must call this
|
|
group.addWithUpdate()
|
|
|
|
// we do not need textForEditing anymore
|
|
textForEditing.visible = false
|
|
canvas?.remove(textForEditing)
|
|
|
|
// optional, buf for better user experience
|
|
canvas?.setActiveObject(group)
|
|
})
|
|
})
|
|
}
|
|
|
|
// IText를 수정할 때 해당 값을 길이로 갖는 다른 polygon을 생성하고 다시 그룹화하는 함수
|
|
function addTextModifiedEvent(text, polygon, index) {
|
|
text.on('editing:exited', function () {})
|
|
}
|
|
|
|
const randomColor = () => {
|
|
return '#' + Math.round(Math.random() * 0xffffff).toString(16)
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<div className="flex justify-center my-8 w-full">
|
|
<button
|
|
className="w-30 mx-2 p-2 rounded bg-blue-500 text-white"
|
|
onClick={addRect}
|
|
>
|
|
ADD RECTANGLE
|
|
</button>
|
|
<button
|
|
className="w-30 mx-2 p-2 rounded bg-blue-500 text-white"
|
|
onClick={addHorizontalLine}
|
|
>
|
|
ADD HORIZONTAL LINE
|
|
</button>
|
|
<button
|
|
className="w-30 mx-2 p-2 rounded bg-blue-500 text-white"
|
|
onClick={addVerticalLine}
|
|
>
|
|
ADD VERTICALITY LINE
|
|
</button>
|
|
<button
|
|
className="w-30 mx-2 p-2 rounded bg-blue-500 text-white"
|
|
onClick={addTriangle}
|
|
>
|
|
ADD TRIANGLE
|
|
</button>
|
|
<button
|
|
className="w-30 mx-2 p-2 rounded bg-blue-500 text-white"
|
|
onClick={addTrapezoid}
|
|
>
|
|
ADD TRAPEZOID
|
|
</button>
|
|
<button
|
|
className="w-30 mx-2 p-2 rounded bg-black text-white"
|
|
onClick={handleCopy}
|
|
>
|
|
COPY shape
|
|
</button>
|
|
<button
|
|
className="w-30 mx-2 p-2 rounded bg-red-500 text-white"
|
|
onClick={handleDelete}
|
|
>
|
|
DELETE
|
|
</button>
|
|
<button
|
|
className="w-30 mx-2 p-2 rounded bg-red-500 text-white"
|
|
onClick={handleClear}
|
|
>
|
|
CLEAR
|
|
</button>
|
|
<button
|
|
className="w-30 mx-2 p-2 rounded bg-green-500 text-white"
|
|
onClick={handleUndo}
|
|
>
|
|
UNDO
|
|
</button>
|
|
<button
|
|
className="w-30 mx-2 p-2 rounded bg-green-300 text-white"
|
|
onClick={handleRedo}
|
|
>
|
|
REDO
|
|
</button>
|
|
<button
|
|
className="w-30 mx-2 p-2 rounded bg-black text-white"
|
|
onClick={handleSave}
|
|
>
|
|
저장
|
|
</button>
|
|
<button
|
|
className="w-30 mx-2 p-2 rounded bg-black text-white"
|
|
onClick={handlePaste}
|
|
>
|
|
붙여넣기
|
|
</button>
|
|
<button
|
|
className="w-30 mx-2 p-2 rounded bg-black text-white"
|
|
onClick={() => handleRotate()}
|
|
>
|
|
45도 회전
|
|
</button>
|
|
<button
|
|
className="w-30 mx-2 p-2 rounded bg-black text-white"
|
|
onClick={() => {
|
|
saveImage('제목')
|
|
}}
|
|
>
|
|
이미지 저장
|
|
</button>
|
|
<button
|
|
className="w-30 mx-2 p-2 rounded bg-black text-white"
|
|
onClick={handleFlip}
|
|
>
|
|
도형반전
|
|
</button>
|
|
</div>
|
|
|
|
<div
|
|
className="flex justify-center"
|
|
style={{
|
|
border: '1px solid',
|
|
width: 1000,
|
|
height: 1000,
|
|
margin: 'auto',
|
|
}}
|
|
>
|
|
<canvas id="canvas" />
|
|
</div>
|
|
</>
|
|
)
|
|
}
|