From 5cf29ec019074c99cb177024a224ff7e530dd108 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Wed, 28 Aug 2024 13:35:51 +0900 Subject: [PATCH 1/4] =?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) From fab80fc8091b42d0f7fe817190feebb12583e212 Mon Sep 17 00:00:00 2001 From: basssy Date: Wed, 28 Aug 2024 14:47:59 +0900 Subject: [PATCH 2/4] =?UTF-8?q?common-util=20=EC=97=90=20queryStringFormat?= =?UTF-8?q?ter=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/common-utils.js | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/util/common-utils.js b/src/util/common-utils.js index 9f53fede..04f3dccf 100644 --- a/src/util/common-utils.js +++ b/src/util/common-utils.js @@ -9,3 +9,45 @@ export const isObjectNotEmpty = (obj) => { } return Object.keys(obj).length > 0 } + +/** + * ex) const params = {page:10, searchDvsnCd: 20} + * @param {*} params + * @returns page=10&searchDvsnCd=20 + */ +export const queryStringFormatter = (params = {}) => { + const queries = [] + Object.keys(params).forEach((parameterKey) => { + const parameterValue = params[parameterKey] + + if (parameterValue === undefined || parameterValue === null) { + return + } + + // string trim + if (typeof parameterValue === 'string' && !parameterValue.trim()) { + return + } + + // array to query string + if (Array.isArray(parameterValue)) { + // primitive type + if (parameterValue.every((v) => typeof v === 'number' || typeof v === 'string')) { + queries.push(`${encodeURIComponent(parameterKey)}=${parameterValue.map((v) => encodeURIComponent(v)).join(',')}`) + return + } + // reference type + if (parameterValue.every((v) => typeof v === 'object' && v !== null)) { + parameterValue.map((pv, i) => { + return Object.keys(pv).forEach((valueKey) => { + queries.push(`${encodeURIComponent(`${parameterKey}[${i}].${valueKey}`)}=${encodeURIComponent(pv[valueKey])}`) + }) + }) + return + } + } + // 나머지 + queries.push(`${encodeURIComponent(parameterKey)}=${encodeURIComponent(parameterValue)}`) + }) + return queries.join('&') +} From 06180dae94581a4f4115b1b75f623c7568b83f06 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Wed, 28 Aug 2024 16:19:27 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=EC=9E=84=EC=9D=98=20=EA=B7=B8=EB=A6=AC?= =?UTF-8?q?=EB=93=9C=EB=AA=A8=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/SettingsModal.jsx | 18 +- src/hooks/useMode.js | 421 ++++++++++++++++--------------- src/store/canvasAtom.js | 12 + 3 files changed, 241 insertions(+), 210 deletions(-) diff --git a/src/components/SettingsModal.jsx b/src/components/SettingsModal.jsx index 3cf9997b..dc7dc5ba 100644 --- a/src/components/SettingsModal.jsx +++ b/src/components/SettingsModal.jsx @@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react' import { Button, Checkbox, CheckboxGroup, RadioGroup, Radio, Input } from '@nextui-org/react' import { useRecoilState, useRecoilValue } from 'recoil' import { modalContent, modalState } from '@/store/modalAtom' -import { guideLineState } from '@/store/canvasAtom' +import { guideLineState, horiGuideLinesState, vertGuideLinesState } from '@/store/canvasAtom' import { fabric } from 'fabric' export default function SettingsModal(props) { @@ -16,6 +16,8 @@ export default function SettingsModal(props) { const [open, setOpen] = useRecoilState(modalState) const [guideLine, setGuideLine] = useRecoilState(guideLineState) + const [horiGuideLines, setHoriGuideLines] = useRecoilState(horiGuideLinesState) + const [vertGuideLines, setVertGuideLines] = useRecoilState(vertGuideLinesState) const gridSettingArray = [] @@ -63,6 +65,7 @@ export default function SettingsModal(props) { lockScalingX: true, lockScalingY: true, name: 'guideLine', + direction: 'horizontal', }, ) canvasProps.add(horizontalLine) @@ -82,6 +85,7 @@ export default function SettingsModal(props) { lockScalingX: true, lockScalingY: true, name: 'guideLine', + direction: 'vertical', }, ) canvasProps.add(verticalLine) @@ -99,6 +103,16 @@ export default function SettingsModal(props) { moduleHoriLength: moduleHoriLength, } gridSettingArray.push(recoilObj) + const newHoriGuideLines = [...horiGuideLines] + horizontalLineArray.forEach((line) => { + newHoriGuideLines.push(line) + }) + const newVertGuideLines = [...vertGuideLines] + verticalLineArray.forEach((line) => { + newVertGuideLines.push(line) + }) + setHoriGuideLines(newHoriGuideLines) + setVertGuideLines(newVertGuideLines) } if (gridCheckedValue.includes('dot')) { @@ -170,6 +184,8 @@ export default function SettingsModal(props) { guideLines?.forEach((item) => canvasProps.remove(item)) canvasProps.renderAll() setGuideLine([]) + setHoriGuideLines([]) + setVertGuideLines([]) } else { alert('그리드가 없습니다.') return diff --git a/src/hooks/useMode.js b/src/hooks/useMode.js index a481fec9..4d6dc238 100644 --- a/src/hooks/useMode.js +++ b/src/hooks/useMode.js @@ -25,6 +25,8 @@ import { templateTypeState, wallState, guideLineState, + horiGuideLinesState, + vertGuideLinesState, } from '@/store/canvasAtom' import { QLine } from '@/components/fabric/QLine' import { fabric } from 'fabric' @@ -69,8 +71,8 @@ export function useMode() { const [guideLineMode, setGuideLineMode] = useState(false) const [guideDotMode, setGuideDotMode] = useState(false) - const [horiGuideLines, setHoriGuideLines] = useState([]) - const [vertGuideLines, setVertGuideLines] = useState([]) + const [horiGuideLines, setHoriGuideLines] = useRecoilState(horiGuideLinesState) + const [vertGuideLines, setVertGuideLines] = useRecoilState(vertGuideLinesState) useEffect(() => { // if (!canvas) { @@ -99,10 +101,10 @@ export function useMode() { }, [endPoint]) useEffect(() => { + changeMode(canvas, mode) canvas?.off('mouse:move') canvas?.on('mouse:move', drawMouseLines) - changeMode(canvas, mode) - }, [mode]) + }, [mode, horiGuideLines, vertGuideLines]) useEffect(() => { setGuideLineMode(false) @@ -111,9 +113,6 @@ 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) } @@ -160,12 +159,8 @@ 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) - const xDiff = Math.abs(pointer.x - closetVerticalLine.x1) - const yDiff = Math.abs(pointer.y - closestHorizontalLine.y1) - + if ((horiGuideLines.length > 0 || vertGuideLines.length > 0) && guideDotMode) { + } else if (guideDotMode) { const x = pointer.x - guideLineLengthHori * Math.floor(pointer.x / guideLineLengthHori) const y = pointer.y - guideLineLengthVert * Math.floor(pointer.y / guideLineLengthVert) @@ -177,35 +172,28 @@ export function useMode() { if (isAttachX && isAttachY) { newX = Math.floor(pointer.x / guideLineLengthHori) * guideLineLengthHori + guideLineLengthHori / 2 newY = Math.floor(pointer.y / guideLineLengthVert) * guideLineLengthVert + guideLineLengthVert / 2 - } else { - if (Math.min(xDiff, yDiff) <= 20) { - if (xDiff < yDiff) { - newX = closetVerticalLine.x1 - newY = pointer.y - } else { - newX = pointer.x - newY = closestHorizontalLine.y1 - } + } + } else if (horiGuideLines.length > 0 || vertGuideLines.length > 0) { + const closestHorizontalLine = getClosestHorizontalLine(pointer, horiGuideLines) + const closetVerticalLine = getClosestVerticalLine(pointer, vertGuideLines) + let intersection = null + let intersectionDistance = Infinity + + if (closestHorizontalLine && closetVerticalLine) { + intersection = calculateIntersection(closestHorizontalLine, closetVerticalLine) + if (intersection) { + intersectionDistance = distanceBetweenPoints(pointer, intersection) } } - } else if (isGuideDotMode) { - const x = pointer.x - guideLineLengthHori * Math.floor(pointer.x / guideLineLengthHori) - const y = pointer.y - guideLineLengthVert * Math.floor(pointer.y / guideLineLengthVert) - const xRate = x / guideLineLengthHori - const yRate = y / guideLineLengthVert - const isAttachX = xRate >= 0.4 && xRate <= 0.7 - const isAttachY = yRate >= 0.4 && yRate <= 0.7 + let xDiff, yDiff - if (isAttachX && isAttachY) { - newX = Math.floor(pointer.x / guideLineLengthHori) * guideLineLengthHori + guideLineLengthHori / 2 - newY = Math.floor(pointer.y / guideLineLengthVert) * guideLineLengthVert + guideLineLengthVert / 2 + if (closetVerticalLine) { + xDiff = Math.abs(pointer.x - closetVerticalLine.x1) + } + if (closestHorizontalLine) { + yDiff = Math.abs(pointer.y - closestHorizontalLine.y1) } - } else if (isGuideLineMode) { - const closestHorizontalLine = getClosestHorizontalLine(pointer, horizontalLineArray) - const closetVerticalLine = getClosestVerticalLine(pointer, verticalLineArray) - const xDiff = Math.abs(pointer.x - closetVerticalLine.x1) - const yDiff = Math.abs(pointer.y - closestHorizontalLine.y1) const x = pointer.x - guideLineLengthHori * Math.floor(pointer.x / guideLineLengthHori) const y = pointer.y - guideLineLengthVert * Math.floor(pointer.y / guideLineLengthVert) @@ -214,23 +202,26 @@ export function useMode() { const yRate = y / guideLineLengthVert const isAttachX = xRate >= 0.4 && xRate <= 0.7 const isAttachY = yRate >= 0.4 && yRate <= 0.7 - if (isAttachX && isAttachY) { newX = Math.floor(pointer.x / guideLineLengthHori) * guideLineLengthHori + guideLineLengthHori / 2 newY = Math.floor(pointer.y / guideLineLengthVert) * guideLineLengthVert + guideLineLengthVert / 2 } else { - if (Math.min(xDiff, yDiff) <= 20) { - if (xDiff < yDiff) { - newX = closetVerticalLine.x1 - newY = pointer.y - } else { - newX = pointer.x - newY = closestHorizontalLine.y1 + if (intersection && intersectionDistance < 20) { + newX = intersection.x + newY = intersection.y + } else { + if (Math.min(xDiff, yDiff) <= 20) { + if (xDiff < yDiff) { + newX = closetVerticalLine.x1 + newY = pointer.y + } else { + newX = pointer.x + newY = closestHorizontalLine.y1 + } } } } } - if (adsorptionPoint && distanceBetweenPoints(pointer, adsorptionPoint) < 20) { newX = adsorptionPoint.left newY = adsorptionPoint.top @@ -358,8 +349,6 @@ export function useMode() { // 모드에 따른 마우스 이벤트 변경 const changeMouseEvent = (mode) => { - document.removeEventListener('contextmenu', mouseEvent.drawLineModeRightClick) - switch (mode) { case 'drawLine': canvas?.on('mouse:down', mouseEvent.drawLineModeLeftClick) @@ -367,7 +356,6 @@ export function useMode() { break case 'edit': canvas?.on('mouse:down', mouseEvent.editMode) - break case 'textbox': canvas?.on('mouse:down', mouseEvent.textboxMode) @@ -546,11 +534,14 @@ export function useMode() { } const mouseAndkeyboardEventClear = () => { + canvas?.off('mouse:down') Object.keys(mouseEvent).forEach((key) => { canvas?.off('mouse:down', mouseEvent[key]) + document.removeEventListener('contextmenu', mouseEvent[key]) }) + Object.keys(keyboardEvent).forEach((key) => { - window.removeEventListener('keydown', keyboardEvent[key]) + document.removeEventListener('keydown', keyboardEvent[key]) }) } @@ -558,6 +549,7 @@ export function useMode() { // rerendering을 막기 위해 useCallback 사용 editMode: useCallback( (e) => { + e.preventDefault() switch (e.key) { case 'ArrowDown': { if (!keyValid()) { @@ -627,11 +619,12 @@ export function useMode() { } const changeMode = (canvas, mode) => { + mouseAndkeyboardEventClear() setMode(mode) setCanvas(canvas) // mode별 이벤트 변경 - mouseAndkeyboardEventClear() + changeMouseEvent(mode) changeKeyboardEvent(mode) @@ -670,42 +663,49 @@ export function useMode() { if (mode === Mode.EDIT) { switch (mode) { case 'edit': - window.addEventListener('keydown', keyboardEvent.editMode) + document.addEventListener('keydown', keyboardEvent.editMode) break } } } const mouseEvent = { - drawLineModeLeftClick: useCallback( - (options) => { - const pointer = canvas?.getPointer(options.e) + drawLineModeLeftClick: (options) => { + if (mode !== Mode.DRAW_LINE) { + return + } + const pointer = canvas?.getPointer(options.e) - 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', - }, - ) + const line = new QLine( + [pointer.x, 0, pointer.x, canvasSize.vertical], // y축에 1자 선을 그립니다. + { + stroke: 'gray', + strokeWidth: 1, + selectable: true, + lockMovementX: true, + lockMovementY: true, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + name: 'guideLine', + direction: 'vertical', + }, + ) - canvas?.add(line) - canvas?.renderAll() + canvas?.add(line) + canvas?.renderAll() - const newVerticalLineArray = [...vertGuideLines, line] - setVertGuideLines(newVerticalLineArray) - }, - [canvas, vertGuideLines], - ), + const newVerticalLineArray = [...vertGuideLines] + newVerticalLineArray.push(line) + + setVertGuideLines(newVerticalLineArray) + }, drawLineModeRightClick: useCallback( (options) => { + document.removeEventListener('contextmenu', mouseEvent.drawLineModeRightClick) + if (mode !== Mode.DRAW_LINE) { + return + } const line = new fabric.Line( [0, options.offsetY, canvasSize.horizontal, options.offsetY], // y축에 1자 선을 그립니다. { @@ -718,132 +718,136 @@ export function useMode() { lockScalingX: true, lockScalingY: true, name: 'guideLine', + direction: 'horizontal', }, ) canvas?.add(line) canvas?.renderAll() - const newHorizontalLineArray = [...horiGuideLines, line] + const newHorizontalLineArray = [...horiGuideLines] + newHorizontalLineArray.push(line) setHoriGuideLines(newHorizontalLineArray) }, - [canvas, horiGuideLines], + [canvas, mode, horiGuideLines], ), - editMode: useCallback( - (options) => { - let pointer = canvas?.getPointer(options.e) + editMode: (options) => { + if (mode !== Mode.EDIT) { + return + } + let pointer = canvas?.getPointer(options.e) - if (getInterSectPointByMouseLine()) { - pointer = getInterSectPointByMouseLine() - } + 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 + 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 - }) + setEndPoint((prev) => { + prevEndPoint = prev + return circle + }) - historyPoints.current.push(circle) - points.current.push(circle) - canvas?.add(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 (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 length = Number(prompt('길이를 입력하세요:')) + const slope = Math.abs(vector.y / vector.x) // 기울기 계산 - // length 값이 숫자가 아닌 경우 - if (isNaN(length) || length === 0) { - //마지막 추가 된 points 제거합니다. + let scaledVector - const lastPoint = historyPoints.current[historyPoints.current.length - 1] - - canvas?.remove(lastPoint) - setEndPoint(prevEndPoint) - historyPoints.current.pop() - points.current.pop() - return + if (slope >= 1) { + // 기울기가 1 이상이면 x축 방향으로 그림 + scaledVector = { + x: 0, + y: pointer.y - prevEndPoint?.top, + } + } else { + // 기울기가 1 미만이면 y축 방향으로 그림 + scaledVector = { + x: pointer.x - prevEndPoint?.left, + y: 0, + } } - 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) // 기울기 계산 + const verticalLength = scaledVector.y + const horizontalLength = scaledVector.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, - } - } + drawCircleAndLine(verticalLength, horizontalLength) + canvas?.renderAll() + return + } + const length = Number(prompt('길이를 입력하세요:')) - const verticalLength = scaledVector.y - const horizontalLength = scaledVector.x + // length 값이 숫자가 아닌 경우 + if (isNaN(length) || length === 0) { + //마지막 추가 된 points 제거합니다. - drawCircleAndLine(verticalLength, horizontalLength) - } + const lastPoint = historyPoints.current[historyPoints.current.length - 1] + + canvas?.remove(lastPoint) + setEndPoint(prevEndPoint) + historyPoints.current.pop() + points.current.pop() + return } - canvas?.renderAll() - }, - [canvas], - ), - textboxMode: useCallback((options) => { + 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) => { + if (mode !== Mode.TEXTBOX) return if (canvas?.getActiveObject()?.type === 'textbox') return const pointer = canvas?.getPointer(options.e) @@ -861,8 +865,9 @@ export function useMode() { textbox?.on('editing:exited', function () { changeMode(canvas, Mode.EDIT) }) - }, []), - drawRectMode: useCallback((o) => { + }, + drawRectMode: (o) => { + if (mode !== Mode.DRAW_RECT) return let rect, isDown, origX, origY isDown = true const pointer = canvas.getPointer(o.e) @@ -902,39 +907,37 @@ export function useMode() { canvas.off('mouse:up') setMode(Mode.DEFAULT) }) - }, []), + }, // 흡착점 추가 - adsorptionPoint: useCallback( - (o) => { - const pointer = canvas.getPointer(o.e) - let newX = pointer.x - let newY = pointer.y + adsorptionPoint: (o) => { + if (mode !== Mode.ADSORPTION_POINT) return + 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], - ), + canvas.add(circle) + canvas.renderAll() + }, } const getInterSectPointByMouseLine = () => { diff --git a/src/store/canvasAtom.js b/src/store/canvasAtom.js index c2b245d7..8d16b98f 100644 --- a/src/store/canvasAtom.js +++ b/src/store/canvasAtom.js @@ -95,3 +95,15 @@ export const currentObjectState = atom({ default: null, dangerouslyAllowMutability: true, }) + +export const horiGuideLinesState = atom({ + key: 'horiGuideLines', + default: [], + dangerouslyAllowMutability: true, +}) + +export const vertGuideLinesState = atom({ + key: 'vertGuideLines', + default: [], + dangerouslyAllowMutability: true, +}) From 7d87f8983c9af5644b29cdbb3482832fdd7cd823 Mon Sep 17 00:00:00 2001 From: yjnoh Date: Wed, 28 Aug 2024 16:56:35 +0900 Subject: [PATCH 4/4] =?UTF-8?q?dayjs=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + yarn.lock | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/package.json b/package.json index 628550a1..c11ef8dc 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ }, "devDependencies": { "@turf/turf": "^7.0.0", + "dayjs": "^1.11.13", "postcss": "^8", "prettier": "^3.3.3", "prisma": "^5.18.0", diff --git a/yarn.lock b/yarn.lock index d0d3b65e..77b7ed38 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4319,6 +4319,11 @@ date-fns@^3.3.1: resolved "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz" integrity sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww== +dayjs@^1.11.13: + version "1.11.13" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c" + integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg== + debug@4, debug@^4.3.3, debug@^4.3.4: version "4.3.5" resolved "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz"