From 5cf29ec019074c99cb177024a224ff7e530dd108 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Wed, 28 Aug 2024 13:35:51 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useCanvas.js | 2 + src/hooks/useCanvasEvent.js | 4 +- src/hooks/useMode.js | 532 +++++++++++++++++++----------------- 3 files changed, 292 insertions(+), 246 deletions(-) diff --git a/src/hooks/useCanvas.js b/src/hooks/useCanvas.js index bcc8b7d7..0dc724f3 100644 --- a/src/hooks/useCanvas.js +++ b/src/hooks/useCanvas.js @@ -425,6 +425,8 @@ export function useCanvas(id) { 'maxY', 'minX', 'minY', + 'x', + 'y', ]) const str = JSON.stringify(objs) diff --git a/src/hooks/useCanvasEvent.js b/src/hooks/useCanvasEvent.js index 89a15b3a..ee2f2fdd 100644 --- a/src/hooks/useCanvasEvent.js +++ b/src/hooks/useCanvasEvent.js @@ -19,9 +19,9 @@ export function useCanvasEvent() { 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) - }) + })*/ canvas?.on('object:removed', objectEvent.removed) } diff --git a/src/hooks/useMode.js b/src/hooks/useMode.js index ae970461..a481fec9 100644 --- a/src/hooks/useMode.js +++ b/src/hooks/useMode.js @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import { calculateIntersection, distanceBetweenPoints, @@ -64,23 +64,22 @@ export function useMode() { const compass = useRecoilValue(compassState) const [isCellCenter, setIsCellCenter] = useState(false) - const guideLineInfo = useRecoilValue(guideLineState) + const [guideLineInfo, setGuideLineInfo] = useRecoilState(guideLineState) const [guideLineMode, setGuideLineMode] = useState(false) const [guideDotMode, setGuideDotMode] = useState(false) + const [horiGuideLines, setHoriGuideLines] = useState([]) + const [vertGuideLines, setVertGuideLines] = useState([]) + useEffect(() => { - // 이벤트 리스너 추가 // if (!canvas) { // canvas?.setZoom(0.8) // return // } - document.addEventListener('keydown', handleKeyDown) + if (!canvas) return + setCanvas(canvas) canvas?.on('mouse:move', drawMouseLines) - // 컴포넌트가 언마운트될 때 이벤트 리스너 제거 - return () => { - document.removeEventListener('keydown', handleKeyDown) - } }, [canvas]) // 빈 배열을 전달하여 컴포넌트가 마운트될 때만 실행되도록 함 useEffect(() => { @@ -103,11 +102,6 @@ export function useMode() { canvas?.off('mouse:move') canvas?.on('mouse:move', drawMouseLines) changeMode(canvas, mode) - /* - if (mode === Mode.EDIT) { - canvas?.off('mouse:down') - canvas?.on('mouse:down', mouseEvent.editMode) - }*/ }, [mode]) useEffect(() => { @@ -117,6 +111,9 @@ export function useMode() { const guideLineState = guideLineInfo.filter((item) => item.guideMode === 'guideLine') const guideDotState = guideLineInfo.filter((item) => item.guideMode === 'guideDot') + setHoriGuideLines(guideLineState[0].horizontalLineArray) + setVertGuideLines(guideLineState[0].verticalLineArray) + setGuideLineMode(guideLineState.length > 0) setGuideDotMode(guideDotState.length > 0) } @@ -142,8 +139,8 @@ export function useMode() { } if (isGuideLineMode) { - horizontalLineArray = [...guideLineState[0].horizontalLineArray] - verticalLineArray = [...guideLineState[0].verticalLineArray] + horizontalLineArray = [...horiGuideLines] + verticalLineArray = [...vertGuideLines] guideLineLengthHori = Number(guideLineState[0].moduleHoriLength) guideLineLengthVert = Number(guideLineState[0].moduleVertLength) } @@ -163,7 +160,6 @@ export function useMode() { if (mode === Mode.EDIT || mode === Mode.ADSORPTION_POINT) { let adsorptionPoint = adsorptionPointList.length > 0 ? findClosestPoint(pointer, adsorptionPointList) : null - if (isGuideLineMode && isGuideDotMode) { const closestHorizontalLine = getClosestHorizontalLine(pointer, horizontalLineArray) const closetVerticalLine = getClosestVerticalLine(pointer, verticalLineArray) @@ -362,12 +358,12 @@ export function useMode() { // 모드에 따른 마우스 이벤트 변경 const changeMouseEvent = (mode) => { - canvas?.off('mouse:down') + document.removeEventListener('contextmenu', mouseEvent.drawLineModeRightClick) + switch (mode) { case 'drawLine': canvas?.on('mouse:down', mouseEvent.drawLineModeLeftClick) - window.document.removeEventListener('contextmenu', mouseEvent.drawLineModeRightClick) - window.document.addEventListener('contextmenu', mouseEvent.drawLineModeRightClick) + document.addEventListener('contextmenu', mouseEvent.drawLineModeRightClick) break case 'edit': canvas?.on('mouse:down', mouseEvent.editMode) @@ -394,9 +390,6 @@ export function useMode() { } } - // 모드에 따른 키보드 이벤트 변경 - const changeKeyboardEvent = (mode) => {} - const keyValid = () => { if (points.current.length === 0) { alert('시작점을 선택해주세요') @@ -552,70 +545,85 @@ export function useMode() { } } - const handleKeyDown = (e) => { - switch (e.key) { - case 'ArrowDown': { - if (!keyValid()) { - return - } - const verticalLength = Number(prompt('길이를 입력하세요:')) - const horizontalLength = 0 + const mouseAndkeyboardEventClear = () => { + Object.keys(mouseEvent).forEach((key) => { + canvas?.off('mouse:down', mouseEvent[key]) + }) + Object.keys(keyboardEvent).forEach((key) => { + window.removeEventListener('keydown', keyboardEvent[key]) + }) + } - drawCircleAndLine(verticalLength, horizontalLength) + const keyboardEvent = { + // rerendering을 막기 위해 useCallback 사용 + editMode: useCallback( + (e) => { + switch (e.key) { + case 'ArrowDown': { + if (!keyValid()) { + return + } + const verticalLength = Number(prompt('길이를 입력하세요:')) + const horizontalLength = 0 - break - } - case 'ArrowUp': { - if (!keyValid()) { - return - } - const verticalLength = -Number(prompt('길이를 입력하세요:')) - const horizontalLength = 0 + drawCircleAndLine(verticalLength, horizontalLength) - drawCircleAndLine(verticalLength, horizontalLength) - - break - } - case 'ArrowLeft': { - if (!keyValid()) { - return - } - const verticalLength = 0 - const horizontalLength = -Number(prompt('길이를 입력하세요:')) - - drawCircleAndLine(verticalLength, horizontalLength) - - break - } - case 'ArrowRight': { - if (!keyValid()) { - return - } - - const verticalLength = 0 - const horizontalLength = Number(prompt('길이를 입력하세요:')) - - drawCircleAndLine(verticalLength, horizontalLength) - - break - } - - case 'Enter': { - const result = prompt('입력하세요 (a(A패턴),b(B패턴),t(지붕))') - - switch (result) { - case 'a': - applyTemplateA() break - case 'b': - applyTemplateB() + } + case 'ArrowUp': { + if (!keyValid()) { + return + } + const verticalLength = -Number(prompt('길이를 입력하세요:')) + const horizontalLength = 0 + + drawCircleAndLine(verticalLength, horizontalLength) + break - case 't': - templateMode() + } + case 'ArrowLeft': { + if (!keyValid()) { + return + } + const verticalLength = 0 + const horizontalLength = -Number(prompt('길이를 입력하세요:')) + + drawCircleAndLine(verticalLength, horizontalLength) + break + } + case 'ArrowRight': { + if (!keyValid()) { + return + } + + const verticalLength = 0 + const horizontalLength = Number(prompt('길이를 입력하세요:')) + + drawCircleAndLine(verticalLength, horizontalLength) + + break + } + + case 'Enter': { + const result = prompt('입력하세요 (a(A패턴),b(B패턴),t(지붕))') + + switch (result) { + case 'a': + applyTemplateA() + break + case 'b': + applyTemplateB() + break + case 't': + templateMode() + break + } + } } - } - } + }, + [canvas], + ), } const changeMode = (canvas, mode) => { @@ -623,6 +631,7 @@ export function useMode() { setCanvas(canvas) // mode별 이벤트 변경 + mouseAndkeyboardEventClear() changeMouseEvent(mode) changeKeyboardEvent(mode) @@ -657,151 +666,184 @@ export function useMode() { } } + const changeKeyboardEvent = (mode) => { + if (mode === Mode.EDIT) { + switch (mode) { + case 'edit': + window.addEventListener('keydown', keyboardEvent.editMode) + break + } + } + } + const mouseEvent = { - drawLineModeLeftClick: (options) => { - const pointer = canvas?.getPointer(options.e) + drawLineModeLeftClick: useCallback( + (options) => { + const pointer = canvas?.getPointer(options.e) - const line = new QLine( - [pointer.x, 0, pointer.x, canvas.height], // y축에 1자 선을 그립니다. - { - stroke: 'black', - strokeWidth: 2, - viewLengthText: true, + const line = new QLine( + [pointer.x, 0, pointer.x, canvas.height], // y축에 1자 선을 그립니다. + { + stroke: 'gray', + strokeWidth: 1, + selectable: true, + lockMovementX: true, + lockMovementY: true, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + name: 'guideLine', + }, + ) + + canvas?.add(line) + canvas?.renderAll() + + const newVerticalLineArray = [...vertGuideLines, line] + setVertGuideLines(newVerticalLineArray) + }, + [canvas, vertGuideLines], + ), + drawLineModeRightClick: useCallback( + (options) => { + const line = new fabric.Line( + [0, options.offsetY, canvasSize.horizontal, options.offsetY], // y축에 1자 선을 그립니다. + { + stroke: 'gray', + strokeWidth: 1, + selectable: true, + lockMovementX: true, + lockMovementY: true, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + name: 'guideLine', + }, + ) + + canvas?.add(line) + canvas?.renderAll() + + const newHorizontalLineArray = [...horiGuideLines, line] + setHoriGuideLines(newHorizontalLineArray) + }, + [canvas, horiGuideLines], + ), + editMode: useCallback( + (options) => { + let pointer = canvas?.getPointer(options.e) + + if (getInterSectPointByMouseLine()) { + pointer = getInterSectPointByMouseLine() + } + + const circle = new fabric.Circle({ + radius: 5, + fill: 'transparent', // 원 안을 비웁니다. + stroke: 'red', // 원 테두리 색상을 검은색으로 설정합니다. + left: pointer.x, + top: pointer.y, + x: pointer.x, + y: pointer.y, + originX: 'center', + originY: 'center', selectable: false, - fontSize: fontSize, - }, - ) - - canvas?.add(line) - canvas?.renderAll() - }, - drawLineModeRightClick: (options) => { - const line = new fabric.Line( - [0, options.offsetY, canvas.width, options.offsetY], // y축에 1자 선을 그립니다. - { - stroke: 'black', - strokeWidth: 2, - viewLengthText: true, - selectable: false, - fontSize: fontSize, - }, - ) - - canvas?.add(line) - canvas?.renderAll() - }, - editMode: (options) => { - let pointer = canvas?.getPointer(options.e) - - if (getInterSectPointByMouseLine()) { - pointer = getInterSectPointByMouseLine() - } - - const circle = new fabric.Circle({ - radius: 5, - fill: 'transparent', // 원 안을 비웁니다. - stroke: 'red', // 원 테두리 색상을 검은색으로 설정합니다. - left: pointer.x, - top: pointer.y, - x: pointer.x, - y: pointer.y, - originX: 'center', - originY: 'center', - selectable: false, - }) - if (!startPoint.current) { - startPoint.current = circle - pointCount.current = pointCount.current + 1 - } - let prevEndPoint - - setEndPoint((prev) => { - prevEndPoint = prev - return circle - }) - - historyPoints.current.push(circle) - points.current.push(circle) - canvas?.add(circle) - - if (points.current.length === 2) { - if (guideLineMode || guideDotMode) { - const vector = { - x: points.current[1].left - points.current[0].left, - y: points.current[1].top - points.current[0].top, - } - const slope = Math.abs(vector.y / vector.x) // 기울기 계산 - - let scaledVector - - if (slope >= 1) { - // 기울기가 1 이상이면 x축 방향으로 그림 - scaledVector = { - x: 0, - y: pointer.y - prevEndPoint?.top, - } - } else { - // 기울기가 1 미만이면 y축 방향으로 그림 - scaledVector = { - x: pointer.x - prevEndPoint?.left, - y: 0, - } - } - - const verticalLength = scaledVector.y - const horizontalLength = scaledVector.x - - drawCircleAndLine(verticalLength, horizontalLength) - canvas?.renderAll() - return + }) + if (!startPoint.current) { + startPoint.current = circle + pointCount.current = pointCount.current + 1 } - const length = Number(prompt('길이를 입력하세요:')) + let prevEndPoint - // length 값이 숫자가 아닌 경우 - if (isNaN(length) || length === 0) { - //마지막 추가 된 points 제거합니다. + setEndPoint((prev) => { + prevEndPoint = prev + return circle + }) - const lastPoint = historyPoints.current[historyPoints.current.length - 1] + historyPoints.current.push(circle) + points.current.push(circle) + canvas?.add(circle) - canvas?.remove(lastPoint) - setEndPoint(prevEndPoint) - historyPoints.current.pop() - points.current.pop() - return + if (points.current.length === 2) { + if (guideLineMode || guideDotMode) { + const vector = { + x: points.current[1].left - points.current[0].left, + y: points.current[1].top - points.current[0].top, + } + const slope = Math.abs(vector.y / vector.x) // 기울기 계산 + + let scaledVector + + if (slope >= 1) { + // 기울기가 1 이상이면 x축 방향으로 그림 + scaledVector = { + x: 0, + y: pointer.y - prevEndPoint?.top, + } + } else { + // 기울기가 1 미만이면 y축 방향으로 그림 + scaledVector = { + x: pointer.x - prevEndPoint?.left, + y: 0, + } + } + + const verticalLength = scaledVector.y + const horizontalLength = scaledVector.x + + drawCircleAndLine(verticalLength, horizontalLength) + canvas?.renderAll() + return + } + const length = Number(prompt('길이를 입력하세요:')) + + // length 값이 숫자가 아닌 경우 + if (isNaN(length) || length === 0) { + //마지막 추가 된 points 제거합니다. + + const lastPoint = historyPoints.current[historyPoints.current.length - 1] + + canvas?.remove(lastPoint) + setEndPoint(prevEndPoint) + historyPoints.current.pop() + points.current.pop() + return + } + + if (length) { + const vector = { + x: points.current[1].left - points.current[0].left, + y: points.current[1].top - points.current[0].top, + } + const slope = Math.abs(vector.y / vector.x) // 기울기 계산 + + let scaledVector + if (slope >= 1) { + // 기울기가 1 이상이면 x축 방향으로 그림 + scaledVector = { + x: 0, + y: vector.y >= 0 ? Number(length) : -Number(length), + } + } else { + // 기울기가 1 미만이면 y축 방향으로 그림 + scaledVector = { + x: vector.x >= 0 ? Number(length) : -Number(length), + y: 0, + } + } + + const verticalLength = scaledVector.y + const horizontalLength = scaledVector.x + + drawCircleAndLine(verticalLength, horizontalLength) + } } - if (length) { - const vector = { - x: points.current[1].left - points.current[0].left, - y: points.current[1].top - points.current[0].top, - } - const slope = Math.abs(vector.y / vector.x) // 기울기 계산 - - let scaledVector - if (slope >= 1) { - // 기울기가 1 이상이면 x축 방향으로 그림 - scaledVector = { - x: 0, - y: vector.y >= 0 ? Number(length) : -Number(length), - } - } else { - // 기울기가 1 미만이면 y축 방향으로 그림 - scaledVector = { - x: vector.x >= 0 ? Number(length) : -Number(length), - y: 0, - } - } - - const verticalLength = scaledVector.y - const horizontalLength = scaledVector.x - - drawCircleAndLine(verticalLength, horizontalLength) - } - } - - canvas?.renderAll() - }, - textboxMode: (options) => { + canvas?.renderAll() + }, + [canvas], + ), + textboxMode: useCallback((options) => { if (canvas?.getActiveObject()?.type === 'textbox') return const pointer = canvas?.getPointer(options.e) @@ -819,8 +861,8 @@ export function useMode() { textbox?.on('editing:exited', function () { changeMode(canvas, Mode.EDIT) }) - }, - drawRectMode: (o) => { + }, []), + drawRectMode: useCallback((o) => { let rect, isDown, origX, origY isDown = true const pointer = canvas.getPointer(o.e) @@ -860,36 +902,39 @@ export function useMode() { canvas.off('mouse:up') setMode(Mode.DEFAULT) }) - }, + }, []), // 흡착점 추가 - adsorptionPoint(o) { - const pointer = canvas.getPointer(o.e) - let newX = pointer.x - let newY = pointer.y + adsorptionPoint: useCallback( + (o) => { + const pointer = canvas.getPointer(o.e) + let newX = pointer.x + let newY = pointer.y - if (getInterSectPointByMouseLine()) { - const interSectPoint = getInterSectPointByMouseLine() - newX = interSectPoint.x - newY = interSectPoint.y - } + if (getInterSectPointByMouseLine()) { + const interSectPoint = getInterSectPointByMouseLine() + newX = interSectPoint.x + newY = interSectPoint.y + } - const circle = new fabric.Circle({ - radius: 5, - fill: 'transparent', // 원 안을 비웁니다. - stroke: 'black', // 원 테두리 색상을 검은색으로 설정합니다. - left: newX, - top: newY, - originX: 'center', - originY: 'center', - x: newX - 5, - y: newY - 5, - selectable: false, - name: 'adsorptionPoint', - }) + const circle = new fabric.Circle({ + radius: 5, + fill: 'transparent', // 원 안을 비웁니다. + stroke: 'black', // 원 테두리 색상을 검은색으로 설정합니다. + left: newX, + top: newY, + originX: 'center', + originY: 'center', + x: newX - 5, + y: newY - 5, + selectable: false, + name: 'adsorptionPoint', + }) - canvas.add(circle) - canvas.renderAll() - }, + canvas.add(circle) + canvas.renderAll() + }, + [canvas], + ), } const getInterSectPointByMouseLine = () => { @@ -4484,7 +4529,6 @@ export function useMode() { canvas?.off('mouse:move') canvas?.off('mouse:out') - document.removeEventListener('keydown', handleKeyDown) const roofs = canvas?.getObjects().filter((obj) => obj.name === 'roof') roofs.forEach((roof, index) => { const offsetPolygonPoint = offsetPolygon(roof.points, -20)