596 lines
17 KiB
JavaScript

import { useCanvas } from '@/hooks/useCanvas'
import { useEffect, useState } from 'react'
import { Mode, useMode } from '@/hooks/useMode'
import { Button } from '@nextui-org/react'
import RangeSlider from './ui/RangeSlider'
import { useRecoilState, useRecoilValue } from 'recoil'
import { canvasSizeState, fontSizeState, roofMaterialState, sortedPolygonArray, templateTypeState } from '@/store/canvasAtom'
import { QLine } from '@/components/fabric/QLine'
import { getCanvasState, insertCanvasState } from '@/lib/canvas'
import { calculateIntersection } from '@/util/canvas-util'
import { QPolygon } from '@/components/fabric/QPolygon'
import offsetPolygon from '@/util/qpolygon-utils'
export default function Roof2() {
const { canvas, handleRedo, handleUndo, setCanvasBackgroundWithDots, saveImage, addCanvas } = useCanvas('canvas')
//canvas 기본 사이즈
const [canvasSize, setCanvasSize] = useRecoilState(canvasSizeState)
//canvas 가로 사이즈
const [verticalSize, setVerticalSize] = useState(canvasSize.vertical)
//canvas 세로 사이즈
const [horizontalSize, setHorizontalSize] = useState(canvasSize.horizontal)
// 글자크기
const [fontSize, setFontSize] = useRecoilState(fontSizeState)
const [sortedArray] = useRecoilState(sortedPolygonArray)
const [angle, setAngle] = useState(0)
const [showControl, setShowControl] = useState(false)
//지붕재
const roofMaterial = useRecoilValue(roofMaterialState)
const [templateType, setTemplateType] = useRecoilState(templateTypeState)
const {
mode,
setMode,
changeMode,
handleClear,
fillCellInPolygon,
zoomIn,
zoomOut,
zoom,
togglePolygonLine,
handleOuterlinesTest,
handleOuterlinesTest2,
applyTemplateB,
makeRoofPatternPolygon,
createRoofRack,
} = useMode()
// const [canvasState, setCanvasState] = useRecoilState(canvasAtom)
useEffect(() => {
if (!canvas) {
return
}
changeMode(canvas, mode)
}, [canvas, mode])
const makeLine = () => {
if (canvas) {
const line = new QLine([50, 50, 200, 50], {
stroke: 'black',
strokeWidth: 2,
fontSize: fontSize,
})
canvas?.add(line)
}
}
const makePolygon = () => {
if (canvas) {
const polygon = new QPolygon(
[
{ x: 100, y: 100 },
{ x: 600, y: 200 },
{ x: 700, y: 800 },
{ x: 100, y: 800 },
],
{
fill: 'transparent',
stroke: 'black',
strokeWidth: 2,
selectable: true,
fontSize: fontSize,
},
)
canvas?.add(polygon)
// polygon.fillCell({ width: 50, height: 30, padding: 10 })
}
}
useEffect(() => {
setCanvasSize({ ...canvasSize, vertical: parseInt(verticalSize), horizontal: parseInt(horizontalSize) })
}, [verticalSize, horizontalSize])
/**
* 값 변경시
*/
// useEffect(() => {
// canvasSizeMode()
// }, [verticalSize, horizontalSize])
useEffect(() => {
const { vertical, horizontal } = canvasSize
if (vertical !== verticalSize || horizontal !== horizontalSize) {
canvas?.setWidth(horizontalSize)
canvas?.setHeight(verticalSize)
canvas?.renderAll()
}
}, [canvasSize, canvas])
const makeQPolygon = () => {
const type1 = [
{ x: 100, y: 100 },
{ x: 850, y: 100 },
{ x: 850, y: 800 },
{ x: 500, y: 800 },
{ x: 500, y: 400 },
{ x: 100, y: 400 },
]
const type2 = [
{ x: 200, y: 100 },
{ x: 200, y: 1000 },
{ x: 1100, y: 1000 },
{ x: 1100, y: 600 },
{ x: 650, y: 600 },
{ x: 650, y: 100 },
]
const type3 = [
{ x: 200, y: 100 },
{ x: 200, y: 800 },
{ x: 500, y: 800 },
{ x: 500, y: 300 },
{ x: 800, y: 300 },
{ x: 800, y: 100 },
]
const type4 = [
{ x: 150, y: 450 },
{ x: 150, y: 800 },
{ x: 750, y: 800 },
{ x: 750, y: 300 },
{ x: 550, y: 300 },
{ x: 550, y: 450 },
]
const type1A = [
{ x: 67, y: 81 },
{ x: 67, y: 660 },
{ x: 437, y: 660 },
{ x: 437, y: 1190 },
{ x: 858, y: 1190 },
{ x: 858, y: 81 },
]
const type1B = [
{ x: 137, y: 42 },
{ x: 137, y: 621 },
{ x: 667, y: 621 },
{ x: 667, y: 991 },
{ x: 1088, y: 991 },
{ x: 1088, y: 42 },
]
const eightPoint = [
{ x: 240.1111, y: 130.1111 },
{ x: 240.1111, y: 630.1111 },
{ x: 640.1111, y: 630.1111 },
{ x: 640.1111, y: 480.1111 },
{ x: 440.1111, y: 480.1111 },
{ x: 440.1111, y: 280.1111 },
{ x: 740.1111, y: 280.1111 },
{ x: 740.1111, y: 130.1111 },
]
const eightPoint2 = [
{ x: 197, y: 215 },
{ x: 197, y: 815 },
{ x: 397, y: 815 },
{ x: 397, y: 1115 },
{ x: 697, y: 1115 },
{ x: 697, y: 815 },
{ x: 897, y: 815 },
{ x: 897, y: 215 },
]
const eightPoint3 = [
{ x: 190, y: 147 },
{ x: 190, y: 747 },
{ x: 490, y: 747 },
{ x: 490, y: 497 },
{ x: 640, y: 497 },
{ x: 640, y: 747 },
{ x: 1090, y: 747 },
{ x: 1090, y: 147 },
]
const eightPoint4 = [
{ x: 228, y: 92 },
{ x: 228, y: 592 },
{ x: 478, y: 592 },
{ x: 478, y: 342 },
{ x: 728, y: 342 },
{ x: 728, y: 592 },
{ x: 1078, y: 592 },
{ x: 1078, y: 92 },
]
const twelvePoint = [
{ x: 195, y: 166 },
{ x: 195, y: 466 },
{ x: 395, y: 466 },
{ x: 395, y: 766 },
{ x: 545, y: 766 },
{ x: 545, y: 466 },
{ x: 695, y: 466 },
{ x: 695, y: 666 },
{ x: 845, y: 666 },
{ x: 845, y: 466 },
{ x: 995, y: 466 },
{ x: 995, y: 166 },
]
const complicatedType = [
{ x: 100, y: 100 },
{ x: 100, y: 1100 },
{ x: 400, y: 1100 },
{ x: 400, y: 800 },
{ x: 700, y: 800 },
{ x: 700, y: 1100 },
{ x: 1000, y: 1100 },
{ x: 1000, y: 600 },
{ x: 700, y: 600 },
{ x: 700, y: 300 },
{ x: 1000, y: 300 },
{ x: 1000, y: 100 },
]
const types = [type1, type2, type3, type4, type1A, type1B, eightPoint, eightPoint2, eightPoint3, eightPoint4, twelvePoint]
const newP = [
{ x: 450, y: 450 },
{ x: 650, y: 250 },
{ x: 675, y: 275 },
{ x: 450, y: 850 },
]
const polygon = new QPolygon(type2, {
fill: 'transparent',
stroke: 'black',
strokeWidth: 1,
selectable: false,
fontSize: fontSize,
name: 'wall',
})
canvas?.add(polygon)
handleOuterlinesTest2(polygon, 50)
setTemplateType(1)
}
const rotateShape = () => {
if (canvas) {
const activeObject = canvas?.getActiveObject()
if (activeObject) {
activeObject.rotate(angle)
canvas?.renderAll()
}
}
}
const makeQLine = () => {
if (canvas) {
const line = new QLine([50, 250, 900, 250], {
stroke: 'black',
strokeWidth: 5,
fontSize: fontSize,
selectable: true,
})
const line2 = new QLine([450, 450, 821, 78], {
stroke: 'black',
strokeWidth: 5,
fontSize: fontSize,
selectable: true,
})
canvas?.add(line)
canvas?.add(line2)
const interSectionPoint = calculateIntersection(line, line2)
if (interSectionPoint) {
const circle = new fabric.Circle({
radius: 5,
fill: 'red',
left: interSectionPoint.x - 5,
top: interSectionPoint.y - 5,
})
canvas?.add(circle)
}
}
}
const addBackgroundInPolygon = (polygon) => {
fabric.Image.fromURL('assets/img/check2.png', function (img) {
// 패턴 객체를 생성합니다.
const pattern = new fabric.Pattern({
source: img.getElement(),
repeat: 'repeat',
})
polygon.fillBackground(pattern)
})
}
function PolygonToLine() {
const polygon = canvas?.getActiveObject()
if (polygon.type !== 'QPolygon') {
return
}
const lines = togglePolygonLine(polygon)
}
/**
* canvas 내용 저장하기
*/
const handleSaveCanvas = async () => {
// const jsonStr = JSON.stringify(canvas?.toDatalessJSON(['type', 'fontSize']))
const jsonObj = JSON.stringify(canvas?.toDatalessJSON(['type', 'fontSize', 'lines']))
console.log(jsonObj)
const param = {
loginId: 'test',
canvas: jsonObj,
}
console.log(param)
await insertCanvasState(param)
handleClear()
}
const drawRoofMaterial = () => {
const { width, height, roofStyle } = roofMaterial
const wallPolygon = canvas?.getObjects().find((obj) => obj.name === 'wall')
wallPolygon.set('strokeDashArray', [10, 5, 2, 5])
wallPolygon.set('stroke', 'blue')
wallPolygon.set('strokeWidth', 1)
const roofs = canvas?.getObjects().filter((obj) => obj.name === 'roof')
roofs.forEach((roof) => {
let maxLengthLine = roof.lines.reduce((acc, cur) => {
return acc.length > cur.length ? acc : cur
})
const roofRatio = window.devicePixelRatio || 1
// 패턴 소스를 위한 임시 캔버스 생성
const patternSourceCanvas = document.createElement('canvas')
if (roofStyle === 1) {
if (maxLengthLine.direction === 'right' || maxLengthLine.direction === 'left') {
patternSourceCanvas.width = width * roofRatio
patternSourceCanvas.height = height * roofRatio
} else {
patternSourceCanvas.width = height * roofRatio
patternSourceCanvas.height = width * roofRatio
}
} else if (roofStyle === 2) {
if (maxLengthLine.direction === 'right' || maxLengthLine.direction === 'left') {
patternSourceCanvas.width = width * 2
patternSourceCanvas.height = height * 2
} else {
patternSourceCanvas.width = height * 2
patternSourceCanvas.height = width * 2
}
}
const ctx = patternSourceCanvas.getContext('2d')
ctx.scale(roofRatio, roofRatio)
ctx.strokeStyle = 'green'
ctx.lineWidth = 0.4
// 벽돌 패턴 그리기
if (roofStyle === 1) {
ctx.strokeRect(0, 0, 50, 30)
} else if (roofStyle === 2) {
// 지그재그
ctx.strokeRect(0, 0, 200, 100)
ctx.strokeRect(100, 100, 200, 100)
}
// 패턴 생성
const pattern = new fabric.Pattern({
source: patternSourceCanvas,
repeat: 'repeat',
})
roof.set('fill', null)
roof.set('fill', pattern)
canvas?.renderAll()
})
}
/**
* canvas 내용 불러오기
*/
const handleLoadCanvas = async () => {
const canvasStates = await getCanvasState()
console.log(JSON.parse(canvasStates.canvas))
canvas?.loadFromJSON(JSON.parse(canvasStates.canvas))
}
/**
* 컨트롤러 보이기/숨기기
*/
const handleShowController = () => {
setShowControl(!showControl)
}
const drawRoofPatterns = (roofStyle) => {
makeRoofPatternPolygon(roofStyle)
}
const deleteCell = () => {
const selectedCells = canvas?.getObjects().filter((obj) => obj.name === 'cell' && obj.selected)
selectedCells.forEach((cell) => {
canvas?.remove(cell)
})
}
return (
<>
{canvas && (
<>
<div className=" my-8 w-full text:pretty">
<Button className="m-1 p-2" color={`${mode === Mode.DEFAULT ? 'primary' : 'default'}`} onClick={fillCellInPolygon}>
모드 DEFAULT
</Button>
<Button className="m-1 p-2" color={`${mode === Mode.DRAW_LINE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.DRAW_LINE)}>
기준선 긋기 모드
</Button>
<Button className="m-1 p-2" color={`${mode === Mode.EDIT ? 'primary' : 'default'}`} onClick={() => setMode(Mode.EDIT)}>
에디팅모드
</Button>
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.TEMPLATE)}>
템플릿(기둥)
</Button>
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.PATTERNA)}>
템플릿(A 패턴)
</Button>
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.PATTERNB)}>
템플릿(B 패턴)
</Button>
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => drawRoofPatterns(1)}>
지붕패턴1
</Button>
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => drawRoofPatterns(2)}>
지붕패턴2
</Button>
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.ROOF_TRESTLE)}>
지붕가대생성
</Button>
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.FILL_CELLS)}>
태양광셀생성
</Button>
<Button className="m-1 p-2" color={`${mode === Mode.TEXTBOX ? 'primary' : 'default'}`} onClick={() => setMode(Mode.TEXTBOX)}>
텍스트박스 모드
</Button>
{/*<Button className="m-1 p-2" onClick={handleUndo}>
Undo
</Button>
<Button className="m-1 p-2" onClick={handleRedo}>
Redo
</Button>*/}
<Button className="m-1 p-2" onClick={handleClear}>
clear
</Button>
{/*<Button className="m-1 p-2" onClick={zoomIn}>
확대
</Button>
<Button className="m-1 p-2" onClick={zoomOut}>
축소
</Button>*/}
현재 : {zoom}%
<Button className="m-1 p-2" onClick={makeLine}>
추가
</Button>
<Button className="m-1 p-2" onClick={makePolygon}>
다각형 추가
</Button>
{templateType === 0 && (
<>
<Button
className="m-1 p-2"
onClick={() => {
setCanvasBackgroundWithDots(canvas, 10)
}}
>
점선 추가
</Button>
<Button
className="m-1 p-2"
onClick={() => {
setCanvasBackgroundWithDots(canvas, 20)
}}
>
점선 추가
</Button>
<Button className="m-1 p-2" onClick={makeQPolygon}>
QPolygon
</Button>
</>
)}
<Button className="m-1 p-2" onClick={saveImage}>
저장
</Button>
{/*<Button className="m-1 p-2" onClick={rotateShape}>
회전
</Button>*/}
{/*<Button className="m-1 p-2" onClick={makeQLine}>
QLine
</Button>
<Button className="m-1 p-2" onClick={PolygonToLine}>
PolygonToLine
</Button>
<Button className="m-1 p-2" onClick={addCanvas}>
캔버스 추가
</Button>*/}
{templateType === 1 && (
<>
<Button className="m-1 p-2" onClick={drawRoofMaterial}>
지붕타입 지붕재
</Button>
<Button className="m-1 p-2" onClick={createRoofRack}>
지붕가대
</Button>
</>
)}
<Button className="m-1 p-2" onClick={deleteCell}>
선택 지우기
</Button>
<Button className="m-1 p-2" color={`${showControl ? 'primary' : 'default'}`} onClick={handleShowController}>
canvas 컨트롤러 {`${showControl ? '숨기기' : '보이기'}`}
</Button>
</div>
<div className={showControl ? `flex justify-center flex-col items-center` : `hidden`}>
<div className="m-2 p-2 w-80">
<RangeSlider title={`각도${angle}`} initValue={angle} min="0" step="1" max="360" onchange={setAngle} />
</div>
<div className="m-2 p-2 w-80">
<RangeSlider
title={`canvas 가로 사이즈${horizontalSize}`}
initValue={horizontalSize}
min="500"
step="100"
max="2000"
onchange={setHorizontalSize}
/>
</div>
<div className="m-2 p-2 w-80">
<RangeSlider
title={`canvas 세로 사이즈${verticalSize}`}
initValue={verticalSize}
min="500"
step="100"
max="2000"
onchange={setVerticalSize}
/>
</div>
<div className="m-2 p-2 w-80">
<RangeSlider title={`글자 크기${fontSize}`} initValue={fontSize} onchange={setFontSize} />
</div>
</div>
</>
)}
<div className="flex justify-start my-8 mx-2 w-full">
<canvas id="canvas" style={{ border: '1px solid black' }} />
</div>
</>
)
}