Merge branch 'dev' of https://git.jetbrains.space/nalpari/q-cast-iii/qcast-front into dev-ck
This commit is contained in:
commit
c226644556
@ -11,6 +11,7 @@ import { useRecoilState, useRecoilValue } from 'recoil'
|
|||||||
import {
|
import {
|
||||||
canvasSizeState,
|
canvasSizeState,
|
||||||
compassState,
|
compassState,
|
||||||
|
currentObjectState,
|
||||||
fontSizeState,
|
fontSizeState,
|
||||||
roofMaterialState,
|
roofMaterialState,
|
||||||
roofState,
|
roofState,
|
||||||
@ -27,6 +28,8 @@ import QContextMenu from './common/context-menu/QContextMenu'
|
|||||||
import { modalContent, modalState } from '@/store/modalAtom'
|
import { modalContent, modalState } from '@/store/modalAtom'
|
||||||
import SettingsModal from './SettingsModal'
|
import SettingsModal from './SettingsModal'
|
||||||
import { useAxios } from '@/hooks/useAxios'
|
import { useAxios } from '@/hooks/useAxios'
|
||||||
|
import QPolygonContextMenu from '@/components/common/context-menu/QPolygonContextMenu'
|
||||||
|
import QLineContextMenu from '@/components/common/context-menu/QLineContextMenu'
|
||||||
|
|
||||||
export default function Roof2(props) {
|
export default function Roof2(props) {
|
||||||
const { name, userId, email, isLoggedIn } = props
|
const { name, userId, email, isLoggedIn } = props
|
||||||
@ -67,6 +70,7 @@ export default function Roof2(props) {
|
|||||||
const [contents, setContent] = useRecoilState(modalContent)
|
const [contents, setContent] = useRecoilState(modalContent)
|
||||||
|
|
||||||
const [scale, setScale] = useState(1)
|
const [scale, setScale] = useState(1)
|
||||||
|
const currentObject = useRecoilValue(currentObjectState)
|
||||||
|
|
||||||
//canvas 썸네일
|
//canvas 썸네일
|
||||||
const [thumbnails, setThumbnails] = useState([])
|
const [thumbnails, setThumbnails] = useState([])
|
||||||
@ -80,7 +84,6 @@ export default function Roof2(props) {
|
|||||||
setMode,
|
setMode,
|
||||||
changeMode,
|
changeMode,
|
||||||
handleClear,
|
handleClear,
|
||||||
fillCellInPolygon,
|
|
||||||
zoomIn,
|
zoomIn,
|
||||||
zoomOut,
|
zoomOut,
|
||||||
zoom,
|
zoom,
|
||||||
@ -126,6 +129,7 @@ export default function Roof2(props) {
|
|||||||
if (canvas) {
|
if (canvas) {
|
||||||
const line = new QLine([50, 50, 200, 50], {
|
const line = new QLine([50, 50, 200, 50], {
|
||||||
stroke: 'black',
|
stroke: 'black',
|
||||||
|
selectable: true,
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
})
|
})
|
||||||
@ -580,7 +584,7 @@ export default function Roof2(props) {
|
|||||||
>
|
>
|
||||||
그리드 설정
|
그리드 설정
|
||||||
</Button>
|
</Button>
|
||||||
<Button className="m-1 p-2" color={`${mode === Mode.DEFAULT ? 'primary' : 'default'}`} onClick={fillCellInPolygon}>
|
<Button className="m-1 p-2" color={`${mode === Mode.DEFAULT ? 'primary' : 'default'}`} onClick={() => setMode(Mode.DEFAULT)}>
|
||||||
모드 DEFAULT
|
모드 DEFAULT
|
||||||
</Button>
|
</Button>
|
||||||
<Button className="m-1 p-2" color={`${mode === Mode.DRAW_LINE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.DRAW_LINE)}>
|
<Button className="m-1 p-2" color={`${mode === Mode.DRAW_LINE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.DRAW_LINE)}>
|
||||||
@ -758,7 +762,13 @@ export default function Roof2(props) {
|
|||||||
<ThumbnailList {...thumbnailProps} />
|
<ThumbnailList {...thumbnailProps} />
|
||||||
<div className="flex justify-start my-8 mx-2 w-full">
|
<div className="flex justify-start my-8 mx-2 w-full">
|
||||||
<canvas ref={canvasRef} id="canvas" style={{ border: '1px solid black' }} />
|
<canvas ref={canvasRef} id="canvas" style={{ border: '1px solid black' }} />
|
||||||
{canvas !== undefined && <QContextMenu contextRef={canvasRef} canvasProps={canvas} />}
|
{!canvas ? null : currentObject?.type === 'QPolygon' ? (
|
||||||
|
<QPolygonContextMenu contextRef={canvasRef} canvasProps={canvas} />
|
||||||
|
) : currentObject?.type === 'QLine' ? (
|
||||||
|
<QLineContextMenu contextRef={canvasRef} canvasProps={canvas} />
|
||||||
|
) : (
|
||||||
|
<QContextMenu contextRef={canvasRef} canvasProps={canvas} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -9,69 +9,50 @@ export default function SettingsModal(props) {
|
|||||||
const { canvasProps } = props
|
const { canvasProps } = props
|
||||||
const [isCustomGridSetting, setIsCustomGridSetting] = useState(true)
|
const [isCustomGridSetting, setIsCustomGridSetting] = useState(true)
|
||||||
const [gridCheckedValue, setGridCheckValue] = useState([])
|
const [gridCheckedValue, setGridCheckValue] = useState([])
|
||||||
const [ratioValue, setRatioValue] = useState('원치수')
|
const [ratioValue, setRatioValue] = useState('1')
|
||||||
const moduleLength = useRef(90) //모듈 mm 길이 입력
|
const moduleLength = useRef(null) //모듈 mm 길이 입력
|
||||||
|
const customModuleHoriLength = useRef(null)
|
||||||
|
const customModuleVertLength = useRef(null)
|
||||||
|
|
||||||
const [open, setOpen] = useRecoilState(modalState)
|
const [open, setOpen] = useRecoilState(modalState)
|
||||||
const [guideLine, setGuideLine] = useRecoilState(guideLineState)
|
const [guideLine, setGuideLine] = useRecoilState(guideLineState)
|
||||||
|
|
||||||
|
const gridSettingArray = []
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
moduleLength.current.value = 90
|
||||||
|
customModuleHoriLength.current.value = 90
|
||||||
|
customModuleVertLength.current.value = 90
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsCustomGridSetting(ratioValue !== 'custom')
|
setIsCustomGridSetting(ratioValue !== 'custom')
|
||||||
}, [ratioValue])
|
}, [ratioValue])
|
||||||
|
|
||||||
const drawGridSettings = () => {
|
const drawGridSettings = () => {
|
||||||
const gridSettingArray = []
|
//기존에 선택된 데이터가 있으면 그 데이터를 포함한다
|
||||||
|
if (!(Object.keys(guideLine).length === 0 && guideLine.constructor === Object)) {
|
||||||
|
gridSettingArray.push(...guideLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
let moduleHoriLength = moduleLength.current.value //가로 간격
|
||||||
|
let moduleVertLength = moduleLength.current.value //새로 간격
|
||||||
|
|
||||||
|
if (ratioValue === 'custom') {
|
||||||
|
moduleHoriLength = customModuleHoriLength.current.value
|
||||||
|
moduleVertLength = customModuleVertLength.current.value
|
||||||
|
} else {
|
||||||
|
moduleHoriLength = moduleHoriLength / ratioValue
|
||||||
|
moduleVertLength = moduleVertLength / ratioValue
|
||||||
|
}
|
||||||
|
|
||||||
if (gridCheckedValue.includes('line')) {
|
if (gridCheckedValue.includes('line')) {
|
||||||
// fabric.GuideRect = fabric.util.createClass(fabric.Rect, {
|
|
||||||
// _render: function (ctx) {
|
|
||||||
// this.callSuper('_render', ctx)
|
|
||||||
// ctx.strokeStyle = this.stroke || 'blue'
|
|
||||||
// ctx.lineWidth = this.strokeWidth || 1
|
|
||||||
|
|
||||||
// ctx.beginPath()
|
|
||||||
// ctx.moveTo(-this.width / 2, -this.height / 2)
|
|
||||||
// ctx.lineTo(this.width / 2, -this.height / 2)
|
|
||||||
// ctx.lineTo(this.width / 2, this.height / 2)
|
|
||||||
// ctx.stroke()
|
|
||||||
// },
|
|
||||||
// })
|
|
||||||
|
|
||||||
// const guideRect = new fabric.GuideRect({
|
|
||||||
// width: canvasProps.width,
|
|
||||||
// height: canvasProps.height,
|
|
||||||
// stroke: 'blue',
|
|
||||||
// strokeWidth: 1,
|
|
||||||
// selectable: false,
|
|
||||||
// fill: 'transparent',
|
|
||||||
// name: 'guideRect',
|
|
||||||
// })
|
|
||||||
|
|
||||||
// const patternSourceCanvas = new fabric.StaticCanvas(null, {
|
|
||||||
// width: moduleLength.current.value,
|
|
||||||
// height: moduleLength.current.value,
|
|
||||||
// backgroundColor: 'white',
|
|
||||||
// })
|
|
||||||
|
|
||||||
// patternSourceCanvas.add(guideRect)
|
|
||||||
// patternSourceCanvas.renderAll()
|
|
||||||
|
|
||||||
// const pattern = new fabric.Pattern({
|
|
||||||
// source: patternSourceCanvas.getElement(),
|
|
||||||
// repeat: 'repeat',
|
|
||||||
// })
|
|
||||||
|
|
||||||
const horizontalLineArray = []
|
const horizontalLineArray = []
|
||||||
const verticalLineArray = []
|
const verticalLineArray = []
|
||||||
|
|
||||||
for (let i = 0; i < canvasProps.height / moduleLength.current.value + 1; i++) {
|
for (let i = 0; i < canvasProps.height / moduleVertLength + 1; i++) {
|
||||||
const horizontalLine = new fabric.Line(
|
const horizontalLine = new fabric.Line(
|
||||||
[
|
[0, i * moduleVertLength - moduleVertLength / 2, canvasProps.width, i * moduleVertLength - moduleVertLength / 2],
|
||||||
0,
|
|
||||||
i * moduleLength.current.value - moduleLength.current.value / 2,
|
|
||||||
canvasProps.width,
|
|
||||||
i * moduleLength.current.value - moduleLength.current.value / 2,
|
|
||||||
],
|
|
||||||
{
|
{
|
||||||
stroke: 'gray',
|
stroke: 'gray',
|
||||||
strokeWidth: 1,
|
strokeWidth: 1,
|
||||||
@ -83,14 +64,9 @@ export default function SettingsModal(props) {
|
|||||||
horizontalLineArray.push(horizontalLine)
|
horizontalLineArray.push(horizontalLine)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < canvasProps.width / moduleLength.current.value + 1; i++) {
|
for (let i = 0; i < canvasProps.width / moduleHoriLength + 1; i++) {
|
||||||
const verticalLine = new fabric.Line(
|
const verticalLine = new fabric.Line(
|
||||||
[
|
[i * moduleHoriLength - moduleHoriLength / 2, 0, i * moduleHoriLength - moduleHoriLength / 2, canvasProps.height],
|
||||||
i * moduleLength.current.value - moduleLength.current.value / 2,
|
|
||||||
0,
|
|
||||||
i * moduleLength.current.value - moduleLength.current.value / 2,
|
|
||||||
canvasProps.height,
|
|
||||||
],
|
|
||||||
{
|
{
|
||||||
stroke: 'gray',
|
stroke: 'gray',
|
||||||
strokeWidth: 1,
|
strokeWidth: 1,
|
||||||
@ -105,60 +81,12 @@ export default function SettingsModal(props) {
|
|||||||
|
|
||||||
const snapDistance = 10
|
const snapDistance = 10
|
||||||
|
|
||||||
canvasProps.on('mouse:move', function (e) {
|
|
||||||
let mouseObj = e
|
|
||||||
const mouseCursorX = e.pointer.x
|
|
||||||
const mouseCursorY = e.pointer.y
|
|
||||||
|
|
||||||
horizontalLineArray.forEach((line) => {
|
|
||||||
if (Math.abs(mouseCursorY - line.y1) < snapDistance) {
|
|
||||||
mouseObj.x = mouseCursorX
|
|
||||||
mouseObj.y = line.y1
|
|
||||||
}
|
|
||||||
// if (Math.abs(mouseCursorX - line.x2) < snapDistance) {
|
|
||||||
// line.set({ x2: mouseCursorX })
|
|
||||||
// }
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// canvasProps.on('mouse:move', (e) => {
|
|
||||||
// console.log('event', e.pointer)
|
|
||||||
|
|
||||||
// const obj = e
|
|
||||||
|
|
||||||
// if (!obj) return
|
|
||||||
|
|
||||||
// canvasProps.forEachObject(function (target) {
|
|
||||||
// if (target.name === 'guideLine' || target.name === 'guideDot') {
|
|
||||||
// console.log('line : ', target)
|
|
||||||
|
|
||||||
// const lineStartX = target.x1,
|
|
||||||
// lineStartY = target.y1
|
|
||||||
// const lineEndX = target.x2,
|
|
||||||
// lineEndY = target.y2
|
|
||||||
|
|
||||||
// console.log('target.x1', target.x1)
|
|
||||||
// console.log('target.y1', target.y1)
|
|
||||||
// console.log(`obj.pointer.y : ${obj.pointer.y}, obj.pointer.x : ${obj.pointer.x}`)
|
|
||||||
|
|
||||||
// // 수평 선에 대한 스냅
|
|
||||||
// if (Math.abs(obj.pointer.y - lineStartY) < snapDistance) {
|
|
||||||
// obj.pointer.y = lineStartY
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 수직 선에 대한 스냅
|
|
||||||
// if (Math.abs(obj.pointer.x - lineStartX) < snapDistance) {
|
|
||||||
// obj.pointer.x = lineStartX
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
|
|
||||||
const recoilObj = {
|
const recoilObj = {
|
||||||
guideMode: 'guideLine',
|
guideMode: 'guideLine',
|
||||||
horizontalLineArray,
|
horizontalLineArray,
|
||||||
verticalLineArray,
|
verticalLineArray,
|
||||||
moduleLength: moduleLength.current.value,
|
moduleVertLength: moduleVertLength,
|
||||||
|
moduleHoriLength: moduleHoriLength,
|
||||||
}
|
}
|
||||||
gridSettingArray.push(recoilObj)
|
gridSettingArray.push(recoilObj)
|
||||||
}
|
}
|
||||||
@ -175,12 +103,11 @@ export default function SettingsModal(props) {
|
|||||||
lockRotation: true,
|
lockRotation: true,
|
||||||
lockScalingX: true,
|
lockScalingX: true,
|
||||||
lockScalingY: true,
|
lockScalingY: true,
|
||||||
name: 'guideDot',
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const patternSourceCanvas = new fabric.StaticCanvas(null, {
|
const patternSourceCanvas = new fabric.StaticCanvas(null, {
|
||||||
width: moduleLength.current.value,
|
width: moduleHoriLength,
|
||||||
height: moduleLength.current.value,
|
height: moduleVertLength,
|
||||||
})
|
})
|
||||||
|
|
||||||
patternSourceCanvas.add(circle)
|
patternSourceCanvas.add(circle)
|
||||||
@ -207,6 +134,7 @@ export default function SettingsModal(props) {
|
|||||||
{
|
{
|
||||||
fill: pattern,
|
fill: pattern,
|
||||||
selectable: false,
|
selectable: false,
|
||||||
|
name: 'guideDot',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
canvasProps.add(backgroundPolygon)
|
canvasProps.add(backgroundPolygon)
|
||||||
@ -214,7 +142,8 @@ export default function SettingsModal(props) {
|
|||||||
|
|
||||||
const recoilObj = {
|
const recoilObj = {
|
||||||
guideMode: 'guideDot',
|
guideMode: 'guideDot',
|
||||||
moduleLength: moduleLength.current.value,
|
moduleVertLength: moduleVertLength,
|
||||||
|
moduleHoriLength: moduleHoriLength,
|
||||||
}
|
}
|
||||||
|
|
||||||
gridSettingArray.push(recoilObj)
|
gridSettingArray.push(recoilObj)
|
||||||
@ -223,27 +152,39 @@ export default function SettingsModal(props) {
|
|||||||
setGuideLine(gridSettingArray)
|
setGuideLine(gridSettingArray)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const removeGuideLines = () => {
|
||||||
|
if (!(Object.keys(guideLine).length === 0 && guideLine.constructor === Object)) {
|
||||||
|
const guideLines = canvasProps._objects.filter((obj) => obj.name === 'guideLine' || obj.name === 'guideDot')
|
||||||
|
guideLines?.forEach((item) => canvasProps.remove(item))
|
||||||
|
canvasProps.renderAll()
|
||||||
|
setGuideLine([])
|
||||||
|
} else {
|
||||||
|
alert('그리드가 없습니다.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<div className="flex w-full flex-wrap md:flex-nowrap gap-4">
|
<div className="flex w-full flex-wrap md:flex-nowrap gap-4">
|
||||||
<label style={{ display: 'block', marginBottom: '5px' }}>
|
<label style={{ display: 'block', marginBottom: '5px' }}>
|
||||||
<CheckboxGroup label="그리드 설정" value={gridCheckedValue} onValueChange={setGridCheckValue}>
|
<CheckboxGroup label="그리드 설정" value={gridCheckedValue} defaultChecked={gridCheckedValue} onValueChange={setGridCheckValue}>
|
||||||
<Checkbox value="dot">점 그리드</Checkbox>
|
<Checkbox value="dot">점 그리드</Checkbox>
|
||||||
<Checkbox value="line">점선 그리드</Checkbox>
|
<Checkbox value="line">점선 그리드</Checkbox>
|
||||||
</CheckboxGroup>
|
</CheckboxGroup>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full flex-wrap md:flex-nowrap gap-4">
|
<div className="flex w-full flex-wrap md:flex-nowrap gap-4">
|
||||||
<Input type="number" label="모듈" ref={moduleLength} defaultValue="90" />
|
<Input type="number" label="모듈" ref={moduleLength} />
|
||||||
mm
|
mm
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full flex-wrap md:flex-nowrap gap-4">
|
<div className="flex w-full flex-wrap md:flex-nowrap gap-4">
|
||||||
<RadioGroup label="비율 설정" onValueChange={setRatioValue}>
|
<RadioGroup label="비율 설정" value={ratioValue} defaultValue={ratioValue} onValueChange={setRatioValue}>
|
||||||
<Radio value="원치수">원치수</Radio>
|
<Radio value="1">원치수</Radio>
|
||||||
<Radio value="1/2">1/2</Radio>
|
<Radio value="2">1/2</Radio>
|
||||||
<Radio value="1/4">1/4</Radio>
|
<Radio value="4">1/4</Radio>
|
||||||
<Radio value="1/10">1/10</Radio>
|
<Radio value="10">1/10</Radio>
|
||||||
<Radio value="custom">임의간격</Radio>
|
<Radio value="custom">임의간격</Radio>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</div>
|
</div>
|
||||||
@ -253,11 +194,11 @@ export default function SettingsModal(props) {
|
|||||||
</Checkbox>
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full flex-wrap md:flex-nowrap gap-4">
|
<div className="flex w-full flex-wrap md:flex-nowrap gap-4">
|
||||||
<Input type="number" label="가로간격" min={0} isDisabled={isCustomGridSetting} />
|
<Input type="number" label="가로간격" ref={customModuleHoriLength} min={0} isDisabled={isCustomGridSetting} />
|
||||||
mm
|
mm
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full flex-wrap md:flex-nowrap gap-4">
|
<div className="flex w-full flex-wrap md:flex-nowrap gap-4">
|
||||||
<Input type="number" label="세로간격" min={0} isDisabled={isCustomGridSetting} />
|
<Input type="number" label="세로간격" ref={customModuleVertLength} min={0} isDisabled={isCustomGridSetting} />
|
||||||
mm
|
mm
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-4 items-center">
|
<div className="flex gap-4 items-center">
|
||||||
@ -268,6 +209,9 @@ export default function SettingsModal(props) {
|
|||||||
<Button size="sm" onClick={() => setOpen(!open)}>
|
<Button size="sm" onClick={() => setOpen(!open)}>
|
||||||
취소
|
취소
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button size="sm" onClick={() => removeGuideLines()}>
|
||||||
|
그리드 삭제
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
71
src/components/common/context-menu/QLineContextMenu.jsx
Normal file
71
src/components/common/context-menu/QLineContextMenu.jsx
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
'use client'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { useRecoilState, useRecoilValue } from 'recoil'
|
||||||
|
|
||||||
|
export default function QLineContextMenu(props) {
|
||||||
|
const { contextRef, canvasProps } = props
|
||||||
|
|
||||||
|
// const children = useRecoilValue(modalContent)
|
||||||
|
const [contextMenu, setContextMenu] = useState({ visible: false, x: 0, y: 0 })
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!contextRef.current) return
|
||||||
|
|
||||||
|
const handleContextMenu = (e) => {
|
||||||
|
e.preventDefault() //기존 contextmenu 막고
|
||||||
|
setContextMenu({ visible: true, x: e.pageX, y: e.pageY })
|
||||||
|
canvasProps.upperCanvasEl.removeEventListener('contextmenu', handleContextMenu) //한번 노출 후 이벤트 삭제
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClick = (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
setContextMenu({ ...contextMenu, visible: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOutsideClick = (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
if (contextMenu.visible && !ref.current.contains(e.target)) {
|
||||||
|
setContextMenu({ ...contextMenu, visible: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent the default context menu from appearing on the canvas
|
||||||
|
canvasProps.upperCanvasEl.addEventListener('contextmenu', handleContextMenu)
|
||||||
|
document.addEventListener('click', handleClick)
|
||||||
|
document.addEventListener('click', handleOutsideClick)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// canvasProps.upperCanvasEl.removeEventListener('contextmenu', handleContextMenu)
|
||||||
|
document.removeEventListener('click', handleClick)
|
||||||
|
document.removeEventListener('click', handleOutsideClick)
|
||||||
|
}
|
||||||
|
}, [contextRef, contextMenu])
|
||||||
|
|
||||||
|
const handleMenuClick = (option) => {
|
||||||
|
alert(`option ${option} clicked`)
|
||||||
|
setContextMenu({ ...contextMenu, visible: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{contextMenu.visible && (
|
||||||
|
<div style={{ position: 'absolute', top: contextMenu.y, left: contextMenu.x, zIndex: 2000 }}>
|
||||||
|
<ul style={{ listStyle: 'none', margin: 0, padding: '5px' }}>
|
||||||
|
<li style={{ padding: '8px 12px', cursor: 'pointer' }} onClick={() => handleMenuClick(1)}>
|
||||||
|
line
|
||||||
|
</li>
|
||||||
|
<li style={{ padding: '8px 12px', cursor: 'pointer' }} onClick={() => handleMenuClick(1)}>
|
||||||
|
Option 1
|
||||||
|
</li>
|
||||||
|
<li style={{ padding: '8px 12px', cursor: 'pointer' }} onClick={() => handleMenuClick(2)}>
|
||||||
|
Option 2
|
||||||
|
</li>
|
||||||
|
<li style={{ padding: '8px 12px', cursor: 'pointer' }} onClick={() => handleMenuClick(3)}>
|
||||||
|
Option 3
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
71
src/components/common/context-menu/QPolygonContextMenu.jsx
Normal file
71
src/components/common/context-menu/QPolygonContextMenu.jsx
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
'use client'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { useRecoilState, useRecoilValue } from 'recoil'
|
||||||
|
|
||||||
|
export default function QPolygonContextMenu(props) {
|
||||||
|
const { contextRef, canvasProps } = props
|
||||||
|
|
||||||
|
// const children = useRecoilValue(modalContent)
|
||||||
|
const [contextMenu, setContextMenu] = useState({ visible: false, x: 0, y: 0 })
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!contextRef.current) return
|
||||||
|
|
||||||
|
const handleContextMenu = (e) => {
|
||||||
|
e.preventDefault() //기존 contextmenu 막고
|
||||||
|
setContextMenu({ visible: true, x: e.pageX, y: e.pageY })
|
||||||
|
canvasProps.upperCanvasEl.removeEventListener('contextmenu', handleContextMenu) //한번 노출 후 이벤트 삭제
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClick = (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
setContextMenu({ ...contextMenu, visible: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOutsideClick = (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
if (contextMenu.visible && !ref.current.contains(e.target)) {
|
||||||
|
setContextMenu({ ...contextMenu, visible: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent the default context menu from appearing on the canvas
|
||||||
|
canvasProps.upperCanvasEl.addEventListener('contextmenu', handleContextMenu)
|
||||||
|
document.addEventListener('click', handleClick)
|
||||||
|
document.addEventListener('click', handleOutsideClick)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// canvasProps.upperCanvasEl.removeEventListener('contextmenu', handleContextMenu)
|
||||||
|
document.removeEventListener('click', handleClick)
|
||||||
|
document.removeEventListener('click', handleOutsideClick)
|
||||||
|
}
|
||||||
|
}, [contextRef, contextMenu])
|
||||||
|
|
||||||
|
const handleMenuClick = (option) => {
|
||||||
|
alert(`option ${option} clicked`)
|
||||||
|
setContextMenu({ ...contextMenu, visible: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{contextMenu.visible && (
|
||||||
|
<div style={{ position: 'absolute', top: contextMenu.y, left: contextMenu.x, zIndex: 2000 }}>
|
||||||
|
<ul style={{ listStyle: 'none', margin: 0, padding: '5px' }}>
|
||||||
|
<li style={{ padding: '8px 12px', cursor: 'pointer' }} onClick={() => handleMenuClick(1)}>
|
||||||
|
polygon
|
||||||
|
</li>
|
||||||
|
<li style={{ padding: '8px 12px', cursor: 'pointer' }} onClick={() => handleMenuClick(1)}>
|
||||||
|
Option 1
|
||||||
|
</li>
|
||||||
|
<li style={{ padding: '8px 12px', cursor: 'pointer' }} onClick={() => handleMenuClick(2)}>
|
||||||
|
Option 2
|
||||||
|
</li>
|
||||||
|
<li style={{ padding: '8px 12px', cursor: 'pointer' }} onClick={() => handleMenuClick(3)}>
|
||||||
|
Option 3
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -11,6 +11,7 @@ export const QLine = fabric.util.createClass(fabric.Line, {
|
|||||||
direction: null,
|
direction: null,
|
||||||
idx: 0,
|
idx: 0,
|
||||||
area: 0,
|
area: 0,
|
||||||
|
children: [],
|
||||||
initialize: function (points, options, canvas) {
|
initialize: function (points, options, canvas) {
|
||||||
this.callSuper('initialize', points, { ...options, selectable: options.selectable ?? false })
|
this.callSuper('initialize', points, { ...options, selectable: options.selectable ?? false })
|
||||||
if (options.id) {
|
if (options.id) {
|
||||||
@ -122,6 +123,7 @@ export const QLine = fabric.util.createClass(fabric.Line, {
|
|||||||
lockRotation: true,
|
lockRotation: true,
|
||||||
lockScalingX: true,
|
lockScalingX: true,
|
||||||
lockScalingY: true,
|
lockScalingY: true,
|
||||||
|
parent: this,
|
||||||
name: 'lengthText',
|
name: 'lengthText',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
cells: [],
|
cells: [],
|
||||||
parentId: null,
|
parentId: null,
|
||||||
innerLines: [],
|
innerLines: [],
|
||||||
|
children: [],
|
||||||
initialize: function (points, options, canvas) {
|
initialize: function (points, options, canvas) {
|
||||||
// 소수점 전부 제거
|
// 소수점 전부 제거
|
||||||
points.forEach((point) => {
|
points.forEach((point) => {
|
||||||
@ -195,6 +196,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
lockScalingY: true,
|
lockScalingY: true,
|
||||||
idx: i,
|
idx: i,
|
||||||
name: 'lengthText',
|
name: 'lengthText',
|
||||||
|
parent: this,
|
||||||
})
|
})
|
||||||
|
|
||||||
this.texts.push(text)
|
this.texts.push(text)
|
||||||
@ -271,6 +273,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
name: 'cell',
|
name: 'cell',
|
||||||
idx: idx,
|
idx: idx,
|
||||||
parentId: this.id,
|
parentId: this.id,
|
||||||
|
parent: this,
|
||||||
})
|
})
|
||||||
|
|
||||||
idx++
|
idx++
|
||||||
|
|||||||
@ -179,25 +179,6 @@ export function useCanvas(id) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 선택한 도형을 삭제한다.
|
|
||||||
*/
|
|
||||||
const handleDelete = () => {
|
|
||||||
const targets = canvas?.getActiveObjects()
|
|
||||||
if (targets?.length === 0) {
|
|
||||||
alert('삭제할 대상을 선택해주세요.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!confirm('정말로 삭제하시겠습니까?')) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
targets?.forEach((target) => {
|
|
||||||
canvas?.remove(target)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 페이지 내 캔버스 저장
|
* 페이지 내 캔버스 저장
|
||||||
* todo : 현재는 localStorage에 그냥 저장하지만 나중에 변경해야함
|
* todo : 현재는 localStorage에 그냥 저장하지만 나중에 변경해야함
|
||||||
@ -469,7 +450,6 @@ export function useCanvas(id) {
|
|||||||
handleUndo,
|
handleUndo,
|
||||||
handleRedo,
|
handleRedo,
|
||||||
handleCopy,
|
handleCopy,
|
||||||
handleDelete,
|
|
||||||
handleSave,
|
handleSave,
|
||||||
handlePaste,
|
handlePaste,
|
||||||
handleRotate,
|
handleRotate,
|
||||||
|
|||||||
@ -1,162 +1,146 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { fabric } from 'fabric'
|
import { fabric } from 'fabric'
|
||||||
import { useRecoilValue } from 'recoil'
|
import { useRecoilState, useRecoilValue } from 'recoil'
|
||||||
import { canvasSizeState, modeState } from '@/store/canvasAtom'
|
import { canvasSizeState, currentObjectState, modeState } from '@/store/canvasAtom'
|
||||||
|
|
||||||
// 캔버스에 필요한 이벤트
|
// 캔버스에 필요한 이벤트
|
||||||
export function useCanvasEvent() {
|
export function useCanvasEvent() {
|
||||||
const [canvas, setCanvasForEvent] = useState(null)
|
const [canvas, setCanvasForEvent] = useState(null)
|
||||||
|
const [currentObject, setCurrentObject] = useRecoilState(currentObjectState)
|
||||||
const canvasSize = useRecoilValue(canvasSizeState)
|
const canvasSize = useRecoilValue(canvasSizeState)
|
||||||
|
|
||||||
// 기본적인 이벤트 필요시 추가
|
// 기본적인 이벤트 필요시 추가
|
||||||
const attachDefaultEventOnCanvas = () => {
|
const attachDefaultEventOnCanvas = () => {
|
||||||
removeEventOnCanvas()
|
removeEventOnCanvas()
|
||||||
canvas?.on('object:added', onChange)
|
canvas?.on('object:added', objectEvent.onChange)
|
||||||
canvas?.on('object:added', addEventOnObject)
|
canvas?.on('object:added', objectEvent.addEvent)
|
||||||
canvas?.on('object:modified', onChange)
|
canvas?.on('object:modified', objectEvent.onChange)
|
||||||
canvas?.on('object:removed', onChange)
|
canvas?.on('object:removed', objectEvent.onChange)
|
||||||
|
canvas?.on('selection:cleared', selectionEvent.cleared)
|
||||||
|
canvas?.on('selection:created', selectionEvent.created)
|
||||||
|
canvas?.on('selection:updated', selectionEvent.updated)
|
||||||
canvas?.on('object:added', () => {
|
canvas?.on('object:added', () => {
|
||||||
document.addEventListener('keydown', handleKeyDown)
|
document.addEventListener('keydown', handleKeyDown)
|
||||||
})
|
})
|
||||||
|
canvas?.on('object:removed', objectEvent.removed)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onChange = (e) => {
|
const objectEvent = {
|
||||||
const target = e.target
|
onChange: (e) => {
|
||||||
if (target) {
|
const target = e.target
|
||||||
// settleDown(target)
|
if (target) {
|
||||||
}
|
// settleDown(target)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addEvent: (e) => {
|
||||||
|
const target = e.target
|
||||||
|
|
||||||
|
if (target.type === 'QPolygon' || target.type === 'QLine') {
|
||||||
|
const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'lengthText')
|
||||||
|
textObjs.forEach((obj) => {
|
||||||
|
obj.bringToFront()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.name === 'cell') {
|
||||||
|
target.on('mousedown', () => {
|
||||||
|
if (target.get('selected')) {
|
||||||
|
target.set({ selected: false })
|
||||||
|
target.set({ fill: '#BFFD9F' })
|
||||||
|
} else {
|
||||||
|
target.set({ selected: true })
|
||||||
|
target.set({ fill: 'red' })
|
||||||
|
}
|
||||||
|
canvas?.renderAll()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.name === 'trestle') {
|
||||||
|
target.on('mousedown', () => {
|
||||||
|
if (target.defense === 'north') {
|
||||||
|
alert('북쪽은 선택 불가합니다.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (target.get('selected')) {
|
||||||
|
target.set({ strokeWidth: 1 })
|
||||||
|
target.set({ strokeDashArray: [5, 5] })
|
||||||
|
target.set({ selected: false })
|
||||||
|
} else {
|
||||||
|
target.set({ strokeWidth: 5 })
|
||||||
|
target.set({ strokeDashArray: [0, 0] })
|
||||||
|
target.set({ selected: true })
|
||||||
|
}
|
||||||
|
canvas?.renderAll()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.name === 'lengthText') {
|
||||||
|
const x = target.left
|
||||||
|
const y = target.top
|
||||||
|
target.on('selected', (e) => {
|
||||||
|
Object.keys(target.controls).forEach((controlKey) => {
|
||||||
|
target.setControlVisible(controlKey, false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
target.on('moving', (e) => {
|
||||||
|
if (target.parentDirection === 'left' || target.parentDirection === 'right') {
|
||||||
|
const minX = target.minX
|
||||||
|
const maxX = target.maxX
|
||||||
|
|
||||||
|
if (target.left <= minX) {
|
||||||
|
target.set({ left: minX, top: y })
|
||||||
|
} else if (target.left >= maxX) {
|
||||||
|
target.set({ left: maxX, top: y })
|
||||||
|
} else {
|
||||||
|
target.set({ top: y })
|
||||||
|
}
|
||||||
|
} else if (target.parentDirection === 'top' || target.parentDirection === 'bottom') {
|
||||||
|
const minY = target.minY
|
||||||
|
const maxY = target.maxY
|
||||||
|
|
||||||
|
if (target.top <= minY) {
|
||||||
|
target.set({ left: x, top: minY })
|
||||||
|
} else if (target.top >= maxY) {
|
||||||
|
target.set({ left: x, top: maxY })
|
||||||
|
} else {
|
||||||
|
target.set({ left: x })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
canvas?.renderAll()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removed: (e) => {
|
||||||
|
const whiteList = ['mouseLine', 'guideLine']
|
||||||
|
|
||||||
|
if (whiteList.includes(e.target.name)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log('removed', e)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectionEvent = {
|
||||||
|
created: (e) => {
|
||||||
|
const target = e.selected[0]
|
||||||
|
setCurrentObject(target)
|
||||||
|
},
|
||||||
|
cleared: (e) => {
|
||||||
|
setCurrentObject(null)
|
||||||
|
},
|
||||||
|
updated: (e) => {
|
||||||
|
const target = e.selected[0]
|
||||||
|
setCurrentObject(target)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeEventOnCanvas = () => {
|
const removeEventOnCanvas = () => {
|
||||||
canvas?.off('object:added')
|
canvas?.off('object:added')
|
||||||
canvas?.off('object:modified')
|
canvas?.off('object:modified')
|
||||||
canvas?.off('object:removed')
|
canvas?.off('object:removed')
|
||||||
canvas?.off('object:added')
|
canvas?.off('selection:cleared')
|
||||||
canvas?.off('mouse:move')
|
canvas?.off('selection:created')
|
||||||
canvas?.off('mouse:down')
|
canvas?.off('selection:updated')
|
||||||
}
|
|
||||||
|
|
||||||
const addEventOnObject = (e) => {
|
|
||||||
const target = e.target
|
|
||||||
|
|
||||||
if (target.type === 'QPolygon' || target.type === 'QLine') {
|
|
||||||
const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'lengthText')
|
|
||||||
textObjs.forEach((obj) => {
|
|
||||||
obj.bringToFront()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target.name === 'cell') {
|
|
||||||
target.on('mousedown', () => {
|
|
||||||
if (target.get('selected')) {
|
|
||||||
target.set({ selected: false })
|
|
||||||
target.set({ fill: '#BFFD9F' })
|
|
||||||
} else {
|
|
||||||
target.set({ selected: true })
|
|
||||||
target.set({ fill: 'red' })
|
|
||||||
}
|
|
||||||
canvas?.renderAll()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target.name === 'trestle') {
|
|
||||||
target.on('mousedown', () => {
|
|
||||||
if (target.defense === 'north') {
|
|
||||||
alert('북쪽은 선택 불가합니다.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (target.get('selected')) {
|
|
||||||
target.set({ strokeWidth: 1 })
|
|
||||||
target.set({ strokeDashArray: [5, 5] })
|
|
||||||
target.set({ selected: false })
|
|
||||||
} else {
|
|
||||||
target.set({ strokeWidth: 5 })
|
|
||||||
target.set({ strokeDashArray: [0, 0] })
|
|
||||||
target.set({ selected: true })
|
|
||||||
}
|
|
||||||
canvas?.renderAll()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target.name === 'lengthText') {
|
|
||||||
const x = target.left
|
|
||||||
const y = target.top
|
|
||||||
target.on('selected', (e) => {
|
|
||||||
Object.keys(target.controls).forEach((controlKey) => {
|
|
||||||
target.setControlVisible(controlKey, false)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
target.on('moving', (e) => {
|
|
||||||
if (target.parentDirection === 'left' || target.parentDirection === 'right') {
|
|
||||||
const minX = target.minX
|
|
||||||
const maxX = target.maxX
|
|
||||||
|
|
||||||
if (target.left <= minX) {
|
|
||||||
target.set({ left: minX, top: y })
|
|
||||||
} else if (target.left >= maxX) {
|
|
||||||
target.set({ left: maxX, top: y })
|
|
||||||
} else {
|
|
||||||
target.set({ top: y })
|
|
||||||
}
|
|
||||||
} else if (target.parentDirection === 'top' || target.parentDirection === 'bottom') {
|
|
||||||
const minY = target.minY
|
|
||||||
const maxY = target.maxY
|
|
||||||
|
|
||||||
if (target.top <= minY) {
|
|
||||||
target.set({ left: x, top: minY })
|
|
||||||
} else if (target.top >= maxY) {
|
|
||||||
target.set({ left: x, top: maxY })
|
|
||||||
} else {
|
|
||||||
target.set({ left: x })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
canvas?.renderAll()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 마우스 가로, 세로선 그리기
|
|
||||||
const drawMouseLines = (e) => {
|
|
||||||
// 현재 마우스 포인터의 위치를 가져옵니다.
|
|
||||||
const pointer = canvas?.getPointer(e.e)
|
|
||||||
|
|
||||||
// 기존에 그려진 가이드라인을 제거합니다.
|
|
||||||
removeMouseLines()
|
|
||||||
|
|
||||||
if (canvas?.getActiveObject()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 가로선을 그립니다.
|
|
||||||
const horizontalLine = new fabric.Line([0, pointer.y, canvasSize.horizontal, pointer.y], {
|
|
||||||
stroke: 'black',
|
|
||||||
strokeWidth: 1,
|
|
||||||
selectable: false,
|
|
||||||
name: 'mouseLine',
|
|
||||||
})
|
|
||||||
|
|
||||||
// 세로선을 그립니다.
|
|
||||||
const verticalLine = new fabric.Line([pointer.x, 0, pointer.x, canvasSize.vertical], {
|
|
||||||
stroke: 'black',
|
|
||||||
strokeWidth: 1,
|
|
||||||
selectable: false,
|
|
||||||
name: 'mouseLine',
|
|
||||||
})
|
|
||||||
|
|
||||||
// 선들을 캔버스에 추가합니다.
|
|
||||||
canvas?.add(horizontalLine, verticalLine)
|
|
||||||
|
|
||||||
// 캔버스를 다시 그립니다.
|
|
||||||
canvas?.renderAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
//가로, 세로 선 삭제
|
|
||||||
const removeMouseLines = () => {
|
|
||||||
if (canvas?._objects.length > 0) {
|
|
||||||
const mouseLines = canvas?._objects.filter((obj) => obj.name === 'mouseLine')
|
|
||||||
mouseLines.forEach((item) => canvas?.remove(item))
|
|
||||||
}
|
|
||||||
canvas?.renderAll()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -197,6 +181,16 @@ export function useCanvasEvent() {
|
|||||||
case 'Esc': // IE/Edge에서 사용되는 값
|
case 'Esc': // IE/Edge에서 사용되는 값
|
||||||
case 'Escape':
|
case 'Escape':
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case 'z':
|
||||||
|
if (!e.ctrlKey) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('뒤로가기')
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return // 키 이벤트를 처리하지 않는다면 종료합니다.
|
return // 키 이벤트를 처리하지 않는다면 종료합니다.
|
||||||
}
|
}
|
||||||
@ -216,6 +210,7 @@ export function useCanvasEvent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
targetObj.set({ top: top })
|
targetObj.set({ top: top })
|
||||||
|
targetObj.fire('modified')
|
||||||
canvas?.renderAll()
|
canvas?.renderAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,6 +227,7 @@ export function useCanvasEvent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
targetObj.set({ top: top })
|
targetObj.set({ top: top })
|
||||||
|
targetObj.fire('modified')
|
||||||
canvas?.renderAll()
|
canvas?.renderAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,6 +244,7 @@ export function useCanvasEvent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
targetObj.set({ left: left })
|
targetObj.set({ left: left })
|
||||||
|
targetObj.fire('modified')
|
||||||
canvas?.renderAll()
|
canvas?.renderAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,9 +261,29 @@ export function useCanvasEvent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
targetObj.set({ left: left })
|
targetObj.set({ left: left })
|
||||||
|
targetObj.fire('modified')
|
||||||
canvas?.renderAll()
|
canvas?.renderAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 선택한 도형을 삭제한다.
|
||||||
|
*/
|
||||||
|
const handleDelete = () => {
|
||||||
|
const targets = canvas?.getActiveObjects()
|
||||||
|
if (targets?.length === 0) {
|
||||||
|
alert('삭제할 대상을 선택해주세요.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!confirm('정말로 삭제하시겠습니까?')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
targets?.forEach((target) => {
|
||||||
|
canvas?.remove(target)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
setCanvasForEvent,
|
setCanvasForEvent,
|
||||||
attachDefaultEventOnCanvas,
|
attachDefaultEventOnCanvas,
|
||||||
|
|||||||
@ -125,7 +125,7 @@ export function useMode() {
|
|||||||
const drawMouseLines = (e) => {
|
const drawMouseLines = (e) => {
|
||||||
let isGuideLineMode = false,
|
let isGuideLineMode = false,
|
||||||
isGuideDotMode = false
|
isGuideDotMode = false
|
||||||
let guideDotLength, guideLineLength, horizontalLineArray, verticalLineArray
|
let guideDotLength, guideLineLengthHori, guideLineLengthVert, horizontalLineArray, verticalLineArray
|
||||||
|
|
||||||
if (isObjectNotEmpty(guideLineInfo)) {
|
if (isObjectNotEmpty(guideLineInfo)) {
|
||||||
const guideLineState = guideLineInfo.filter((item) => item.guideMode === 'guideLine')
|
const guideLineState = guideLineInfo.filter((item) => item.guideMode === 'guideLine')
|
||||||
@ -136,13 +136,15 @@ export function useMode() {
|
|||||||
isGuideDotMode = guideDotState.length > 0
|
isGuideDotMode = guideDotState.length > 0
|
||||||
|
|
||||||
if (isGuideDotMode) {
|
if (isGuideDotMode) {
|
||||||
guideDotLength = Number(guideDotState[0].moduleLength)
|
guideLineLengthHori = Number(guideDotState[0].moduleHoriLength)
|
||||||
|
guideLineLengthVert = Number(guideDotState[0].moduleVertLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isGuideLineMode) {
|
if (isGuideLineMode) {
|
||||||
horizontalLineArray = [...guideLineState[0].horizontalLineArray]
|
horizontalLineArray = [...guideLineState[0].horizontalLineArray]
|
||||||
verticalLineArray = [...guideLineState[0].verticalLineArray]
|
verticalLineArray = [...guideLineState[0].verticalLineArray]
|
||||||
guideLineLength = Number(guideLineState[0].moduleLength)
|
guideLineLengthHori = Number(guideLineState[0].moduleHoriLength)
|
||||||
|
guideLineLengthVert = Number(guideLineState[0].moduleVertLength)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,17 +163,17 @@ export function useMode() {
|
|||||||
const xDiff = Math.abs(pointer.x - closetVerticalLine.x1)
|
const xDiff = Math.abs(pointer.x - closetVerticalLine.x1)
|
||||||
const yDiff = Math.abs(pointer.y - closestHorizontalLine.y1)
|
const yDiff = Math.abs(pointer.y - closestHorizontalLine.y1)
|
||||||
|
|
||||||
const x = pointer.x - guideLineLength * Math.floor(pointer.x / guideLineLength)
|
const x = pointer.x - guideLineLengthHori * Math.floor(pointer.x / guideLineLengthHori)
|
||||||
const y = pointer.y - guideLineLength * Math.floor(pointer.y / guideLineLength)
|
const y = pointer.y - guideLineLengthVert * Math.floor(pointer.y / guideLineLengthVert)
|
||||||
|
|
||||||
const xRate = x / guideLineLength
|
const xRate = x / guideLineLengthHori
|
||||||
const yRate = y / guideLineLength
|
const yRate = y / guideLineLengthVert
|
||||||
const isAttachX = xRate >= 0.4 && xRate <= 0.7
|
const isAttachX = xRate >= 0.4 && xRate <= 0.7
|
||||||
const isAttachY = yRate >= 0.4 && yRate <= 0.7
|
const isAttachY = yRate >= 0.4 && yRate <= 0.7
|
||||||
|
|
||||||
if (isAttachX && isAttachY) {
|
if (isAttachX && isAttachY) {
|
||||||
newX = Math.floor(pointer.x / guideLineLength) * guideLineLength + guideLineLength / 2
|
newX = Math.floor(pointer.x / guideLineLengthHori) * guideLineLengthHori + guideLineLengthHori / 2
|
||||||
newY = Math.floor(pointer.y / guideLineLength) * guideLineLength + guideLineLength / 2
|
newY = Math.floor(pointer.y / guideLineLengthVert) * guideLineLengthVert + guideLineLengthVert / 2
|
||||||
} else {
|
} else {
|
||||||
if (Math.min(xDiff, yDiff) <= 20) {
|
if (Math.min(xDiff, yDiff) <= 20) {
|
||||||
if (xDiff < yDiff) {
|
if (xDiff < yDiff) {
|
||||||
@ -184,17 +186,17 @@ export function useMode() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (isGuideDotMode && mode === Mode.EDIT) {
|
} else if (isGuideDotMode && mode === Mode.EDIT) {
|
||||||
const x = pointer.x - guideDotLength * Math.floor(pointer.x / guideDotLength)
|
const x = pointer.x - guideLineLengthHori * Math.floor(pointer.x / guideLineLengthHori)
|
||||||
const y = pointer.y - guideDotLength * Math.floor(pointer.y / guideDotLength)
|
const y = pointer.y - guideLineLengthVert * Math.floor(pointer.y / guideLineLengthVert)
|
||||||
|
|
||||||
const xRate = x / guideDotLength
|
const xRate = x / guideLineLengthHori
|
||||||
const yRate = y / guideDotLength
|
const yRate = y / guideLineLengthVert
|
||||||
const isAttachX = xRate >= 0.4 && xRate <= 0.7
|
const isAttachX = xRate >= 0.4 && xRate <= 0.7
|
||||||
const isAttachY = yRate >= 0.4 && yRate <= 0.7
|
const isAttachY = yRate >= 0.4 && yRate <= 0.7
|
||||||
|
|
||||||
if (isAttachX && isAttachY) {
|
if (isAttachX && isAttachY) {
|
||||||
newX = Math.floor(pointer.x / guideDotLength) * guideDotLength + guideDotLength / 2
|
newX = Math.floor(pointer.x / guideLineLengthHori) * guideLineLengthHori + guideLineLengthHori / 2
|
||||||
newY = Math.floor(pointer.y / guideDotLength) * guideDotLength + guideDotLength / 2
|
newY = Math.floor(pointer.y / guideLineLengthVert) * guideLineLengthVert + guideLineLengthVert / 2
|
||||||
}
|
}
|
||||||
} else if (isGuideLineMode && mode === Mode.EDIT) {
|
} else if (isGuideLineMode && mode === Mode.EDIT) {
|
||||||
const closestHorizontalLine = getClosestHorizontalLine(pointer, horizontalLineArray)
|
const closestHorizontalLine = getClosestHorizontalLine(pointer, horizontalLineArray)
|
||||||
@ -202,17 +204,17 @@ export function useMode() {
|
|||||||
const xDiff = Math.abs(pointer.x - closetVerticalLine.x1)
|
const xDiff = Math.abs(pointer.x - closetVerticalLine.x1)
|
||||||
const yDiff = Math.abs(pointer.y - closestHorizontalLine.y1)
|
const yDiff = Math.abs(pointer.y - closestHorizontalLine.y1)
|
||||||
|
|
||||||
const x = pointer.x - guideLineLength * Math.floor(pointer.x / guideLineLength)
|
const x = pointer.x - guideLineLengthHori * Math.floor(pointer.x / guideLineLengthHori)
|
||||||
const y = pointer.y - guideLineLength * Math.floor(pointer.y / guideLineLength)
|
const y = pointer.y - guideLineLengthVert * Math.floor(pointer.y / guideLineLengthVert)
|
||||||
|
|
||||||
const xRate = x / guideLineLength
|
const xRate = x / guideLineLengthHori
|
||||||
const yRate = y / guideLineLength
|
const yRate = y / guideLineLengthVert
|
||||||
const isAttachX = xRate >= 0.4 && xRate <= 0.7
|
const isAttachX = xRate >= 0.4 && xRate <= 0.7
|
||||||
const isAttachY = yRate >= 0.4 && yRate <= 0.7
|
const isAttachY = yRate >= 0.4 && yRate <= 0.7
|
||||||
|
|
||||||
if (isAttachX && isAttachY) {
|
if (isAttachX && isAttachY) {
|
||||||
newX = Math.floor(pointer.x / guideLineLength) * guideLineLength + guideLineLength / 2
|
newX = Math.floor(pointer.x / guideLineLengthHori) * guideLineLengthHori + guideLineLengthHori / 2
|
||||||
newY = Math.floor(pointer.y / guideLineLength) * guideLineLength + guideLineLength / 2
|
newY = Math.floor(pointer.y / guideLineLengthVert) * guideLineLengthVert + guideLineLengthVert / 2
|
||||||
} else {
|
} else {
|
||||||
if (Math.min(xDiff, yDiff) <= 20) {
|
if (Math.min(xDiff, yDiff) <= 20) {
|
||||||
if (xDiff < yDiff) {
|
if (xDiff < yDiff) {
|
||||||
@ -661,9 +663,12 @@ export function useMode() {
|
|||||||
editMode: (options) => {
|
editMode: (options) => {
|
||||||
// const pointer = canvas?.getPointer(options.e)
|
// const pointer = canvas?.getPointer(options.e)
|
||||||
|
|
||||||
|
let pointer = canvas?.getPointer(options.e)
|
||||||
const mouseLines = canvas?._objects.filter((obj) => obj.name === 'mouseLine')
|
const mouseLines = canvas?._objects.filter((obj) => obj.name === 'mouseLine')
|
||||||
|
|
||||||
const pointer = calculateIntersection(mouseLines[0], mouseLines[1])
|
/*if (calculateIntersection(mouseLines[0], mouseLines[1])) {
|
||||||
|
pointer = calculateIntersection(mouseLines[0], mouseLines[1])
|
||||||
|
}*/
|
||||||
|
|
||||||
const circle = new fabric.Circle({
|
const circle = new fabric.Circle({
|
||||||
radius: 5,
|
radius: 5,
|
||||||
@ -949,6 +954,8 @@ export function useMode() {
|
|||||||
points.current = []
|
points.current = []
|
||||||
historyPoints.current = []
|
historyPoints.current = []
|
||||||
historyLines.current = []
|
historyLines.current = []
|
||||||
|
setRoof(null)
|
||||||
|
setWall(null)
|
||||||
|
|
||||||
setSelectedCellRoofArray([]) //셀 그린거 삭제
|
setSelectedCellRoofArray([]) //셀 그린거 삭제
|
||||||
}
|
}
|
||||||
@ -1487,6 +1494,11 @@ export function useMode() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const applyTemplateA = () => {
|
const applyTemplateA = () => {
|
||||||
|
if (historyPoints.current.length === 0) {
|
||||||
|
changeMode(canvas, Mode.EDIT)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
changeMode(canvas, Mode.EDIT)
|
changeMode(canvas, Mode.EDIT)
|
||||||
const polygon = drawWallPolygon(false)
|
const polygon = drawWallPolygon(false)
|
||||||
// handleClear()
|
// handleClear()
|
||||||
@ -2933,7 +2945,11 @@ export function useMode() {
|
|||||||
* 템플릿 B 적용
|
* 템플릿 B 적용
|
||||||
*/
|
*/
|
||||||
const applyTemplateB = () => {
|
const applyTemplateB = () => {
|
||||||
changeMode(canvas, Mode.EDIT)
|
if (historyPoints.current.length === 0) {
|
||||||
|
changeMode(canvas, Mode.EDIT)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const polygon = drawWallPolygon(false)
|
const polygon = drawWallPolygon(false)
|
||||||
const params = {
|
const params = {
|
||||||
eaves: 50,
|
eaves: 50,
|
||||||
@ -4211,38 +4227,47 @@ export function useMode() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//셀이 생성될 지붕의 흐름방향을 정함
|
if (templateCenterLine.length > 0) {
|
||||||
templateCenterLine.some((centerLine) => {
|
//셀이 생성될 지붕의 흐름방향을 정함
|
||||||
|
templateCenterLine.some((centerLine) => {
|
||||||
|
if (templateType === 2) {
|
||||||
|
trestlePolygon.set({
|
||||||
|
referenceDirection: referenceDirection,
|
||||||
|
startIndex: parallelPoint,
|
||||||
|
}) //기준면 방향
|
||||||
|
trestlePolygon.points.forEach((trestleLine, index) => {
|
||||||
|
if (trestleLine.x === centerLine.x1 - 12) {
|
||||||
|
trestlePolygon.set({ wallDirection: 'left' })
|
||||||
|
return true
|
||||||
|
} else if (trestleLine.x === centerLine.x1 + 12) {
|
||||||
|
trestlePolygon.set({ wallDirection: 'right' })
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (templateType === 3) {
|
||||||
|
trestlePolygon.set({
|
||||||
|
referenceDirection: referenceDirection,
|
||||||
|
startIndex: parallelPoint,
|
||||||
|
}) //기준면 방향
|
||||||
|
trestlePolygon.points.forEach((trestleLine, index) => {
|
||||||
|
if (trestleLine.y === centerLine.y1 - 12) {
|
||||||
|
trestlePolygon.set({ wallDirection: 'top' })
|
||||||
|
return true
|
||||||
|
} else if (trestleLine.y === centerLine.y1 + 12) {
|
||||||
|
trestlePolygon.set({ wallDirection: 'bottom' })
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
if (templateType === 2) {
|
if (templateType === 2) {
|
||||||
trestlePolygon.set({
|
index === 0 ? trestlePolygon.set({ wallDirection: 'left' }) : trestlePolygon.set({ wallDirection: 'right' })
|
||||||
referenceDirection: referenceDirection,
|
|
||||||
startIndex: parallelPoint,
|
|
||||||
}) //기준면 방향
|
|
||||||
trestlePolygon.points.forEach((trestleLine, index) => {
|
|
||||||
if (trestleLine.x === centerLine.x1 - 12) {
|
|
||||||
trestlePolygon.set({ wallDirection: 'left' })
|
|
||||||
return true
|
|
||||||
} else if (trestleLine.x === centerLine.x1 + 12) {
|
|
||||||
trestlePolygon.set({ wallDirection: 'right' })
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else if (templateType === 3) {
|
} else if (templateType === 3) {
|
||||||
trestlePolygon.set({
|
index === 0 ? trestlePolygon.set({ wallDirection: 'top' }) : trestlePolygon.set({ wallDirection: 'bottom' })
|
||||||
referenceDirection: referenceDirection,
|
|
||||||
startIndex: parallelPoint,
|
|
||||||
}) //기준면 방향
|
|
||||||
trestlePolygon.points.forEach((trestleLine, index) => {
|
|
||||||
if (trestleLine.y === centerLine.y1 - 12) {
|
|
||||||
trestlePolygon.set({ wallDirection: 'top' })
|
|
||||||
return true
|
|
||||||
} else if (trestleLine.y === centerLine.y1 + 12) {
|
|
||||||
trestlePolygon.set({ wallDirection: 'bottom' })
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
trestlePolygon.set({ referenceDirection: 'none' })
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 가대 선택 이벤트
|
* 가대 선택 이벤트
|
||||||
|
|||||||
@ -94,3 +94,9 @@ export const guideLineState = atom({
|
|||||||
default: {},
|
default: {},
|
||||||
dangerouslyAllowMutability: true,
|
dangerouslyAllowMutability: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const currentObjectState = atom({
|
||||||
|
key: 'currentObject',
|
||||||
|
default: null,
|
||||||
|
dangerouslyAllowMutability: true,
|
||||||
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user