diff --git a/src/components/floor-plan/modal/outerlinesetting/OuterLineWall.jsx b/src/components/floor-plan/modal/outerlinesetting/OuterLineWall.jsx
index 74f492b1..386e98cd 100644
--- a/src/components/floor-plan/modal/outerlinesetting/OuterLineWall.jsx
+++ b/src/components/floor-plan/modal/outerlinesetting/OuterLineWall.jsx
@@ -5,7 +5,13 @@ import WithDraggable from '@/components/common/draggable/withDraggable'
import { useRecoilState, useRecoilValue } from 'recoil'
import { useMessage } from '@/hooks/useMessage'
import { useEvent } from '@/hooks/useEvent'
-import { canvasState, verticalHorizontalModeState } from '@/store/canvasAtom'
+import {
+ adsorptionPointAddModeState,
+ adsorptionPointModeState,
+ canvasHistoryState,
+ canvasState,
+ verticalHorizontalModeState,
+} from '@/store/canvasAtom'
import {
OUTER_LINE_TYPE,
outerLineAngle1State,
@@ -25,11 +31,18 @@ import { onlyNumberInputChange, onlyNumberWithDotInputChange } from '@/util/inpu
export default function OuterLineWall(props) {
const { setShowOutlineModal } = props
const { getMessage } = useMessage()
- const { addCanvasMouseEventListener, addDocumentEventListener, removeAllMouseEventListeners, removeAllDocumentEventListeners, removeMouseEvent } =
- useEvent()
+ const {
+ addCanvasMouseEventListener,
+ addDocumentEventListener,
+ removeAllMouseEventListeners,
+ removeAllDocumentEventListeners,
+ removeMouseEvent,
+ getIntersectMousePoint,
+ } = useEvent()
const { addLine, removeLine } = useLine()
const { addPolygonByLines } = usePolygon()
const verticalHorizontalMode = useRecoilValue(verticalHorizontalModeState)
+ const adsorptionPointAddMode = useRecoilValue(adsorptionPointAddModeState)
const length1Ref = useRef(null)
const length2Ref = useRef(null)
@@ -50,13 +63,16 @@ export default function OuterLineWall(props) {
const canvas = useRecoilValue(canvasState)
useEffect(() => {
+ if (adsorptionPointAddMode) {
+ return
+ }
removeMouseEvent('mouse:down', mouseDown)
addCanvasMouseEventListener('mouse:down', mouseDown)
clear()
return () => {
removeAllMouseEventListeners()
}
- }, [verticalHorizontalMode, points])
+ }, [verticalHorizontalMode, points, adsorptionPointAddMode])
useEffect(() => {
arrow1Ref.current = arrow1
@@ -83,7 +99,8 @@ export default function OuterLineWall(props) {
}
const mouseDown = (e) => {
- const pointer = canvas.getPointer(e.e)
+ let pointer = getIntersectMousePoint(e)
+
if (points.length === 0) {
setPoints((prev) => [...prev, pointer])
} else {
@@ -328,6 +345,12 @@ export default function OuterLineWall(props) {
if (points.length === 0) {
return
}
+ // 포커스가 length1에 있지 않으면 length1에 포커스를 줌
+ const activeElem = document.activeElement
+ if (activeElem !== length1Ref.current) {
+ length1Ref.current.focus()
+ }
+
const key = e.key
if (!length1Ref.current) {
@@ -389,6 +412,9 @@ export default function OuterLineWall(props) {
const key = e.key
const activeElem = document.activeElement
+ if (activeElem !== length1Ref.current && activeElem !== length2Ref.current) {
+ length1Ref.current.focus()
+ }
switch (key) {
case 'Down': // IE/Edge에서 사용되는 값
diff --git a/src/components/floor-plan/modal/setting01/FirstOption.jsx b/src/components/floor-plan/modal/setting01/FirstOption.jsx
index 3be1eb46..1c2cf20a 100644
--- a/src/components/floor-plan/modal/setting01/FirstOption.jsx
+++ b/src/components/floor-plan/modal/setting01/FirstOption.jsx
@@ -4,6 +4,7 @@ import { useMessage } from '@/hooks/useMessage'
import React, { useEffect, useState } from 'react'
import { useAxios } from '@/hooks/useAxios'
import { toastUp } from '@/hooks/useToast'
+import { adsorptionPointAddModeState } from '@/store/canvasAtom'
export default function FirstOption() {
const [objectNo, setObjectNo] = useState('test123240912001') // 이후 삭제 필요
diff --git a/src/components/floor-plan/modal/setting01/GridOption.jsx b/src/components/floor-plan/modal/setting01/GridOption.jsx
index 6147f610..5e2f31db 100644
--- a/src/components/floor-plan/modal/setting01/GridOption.jsx
+++ b/src/components/floor-plan/modal/setting01/GridOption.jsx
@@ -2,10 +2,12 @@ import React from 'react'
import { useRecoilState } from 'recoil'
import { settingModalGridOptionsState } from '@/store/settingAtom'
import { useMessage } from '@/hooks/useMessage'
+import { adsorptionPointAddModeState } from '@/store/canvasAtom'
export default function GridOption(props) {
const { setShowDotLineGridModal } = props
const [gridOptions, setGridOptions] = useRecoilState(settingModalGridOptionsState)
+ const [adsorptionPointAddMode, setAdsorptionPointAddMode] = useRecoilState(adsorptionPointAddModeState)
const { getMessage } = useMessage()
const onClickOption = (option) => {
@@ -16,7 +18,12 @@ export default function GridOption(props) {
// 점.선 그리드
setShowDotLineGridModal(true)
}
+
+ if (option.name === 'modal.canvas.setting.grid.absorption.add') {
+ setAdsorptionPointAddMode(!adsorptionPointAddMode)
+ }
}
+
return (
<>
diff --git a/src/components/floor-plan/modal/setting01/SecondOption.jsx b/src/components/floor-plan/modal/setting01/SecondOption.jsx
index 4904678d..e870cd73 100644
--- a/src/components/floor-plan/modal/setting01/SecondOption.jsx
+++ b/src/components/floor-plan/modal/setting01/SecondOption.jsx
@@ -1,14 +1,18 @@
-import { useRecoilState } from 'recoil'
+import { useRecoilState, useSetRecoilState } from 'recoil'
import { settingModalFirstOptionsState, settingModalSecondOptionsState } from '@/store/settingAtom'
import { useMessage } from '@/hooks/useMessage'
import React, { useEffect, useState } from 'react'
import { useAxios } from '@/hooks/useAxios'
import { toastUp } from '@/hooks/useToast'
+import { adsorptionPointModeState, adsorptionRangeState } from '@/store/canvasAtom'
export default function SecondOption() {
const [objectNo, setObjectNo] = useState('test123240912001') // 이후 삭제 필요
const [settingModalFirstOptions, setSettingModalFirstOptions] = useRecoilState(settingModalFirstOptionsState)
const [settingModalSecondOptions, setSettingModalSecondOptions] = useRecoilState(settingModalSecondOptionsState)
+ const [adsorptionPointMode, setAdsorptionPointMode] = useRecoilState(adsorptionPointModeState)
+ const setAdsorptionRange = useSetRecoilState(adsorptionRangeState)
+
const { option1, option2 } = settingModalFirstOptions
const { option3, option4 } = settingModalSecondOptions
const { getMessage } = useMessage()
@@ -106,6 +110,7 @@ export default function SecondOption() {
// HTTP POST 요청 보내기
await post({ url: `/api/canvas-management/canvas-settings`, data: patternData }).then((res) => {
toastUp({ message: getMessage(res.returnMessage), type: 'success' })
+ setAdsorptionRange(option.range)
})
} catch (error) {
toastUp({ message: getMessage(res.returnMessage), type: 'error' })
@@ -142,9 +147,14 @@ export default function SecondOption() {
-
diff --git a/src/hooks/useCanvas.js b/src/hooks/useCanvas.js
index c0e74faf..052605b6 100644
--- a/src/hooks/useCanvas.js
+++ b/src/hooks/useCanvas.js
@@ -490,6 +490,34 @@ export function useCanvas(id) {
canvas.clear()
}
+ const getCurrentCanvas = () => {
+ return canvas.toJSON([
+ 'selectable',
+ 'name',
+ 'parentId',
+ 'id',
+ 'length',
+ 'idx',
+ 'direction',
+ 'lines',
+ 'points',
+ 'lockMovementX',
+ 'lockMovementY',
+ 'lockRotation',
+ 'lockScalingX',
+ 'lockScalingY',
+ 'opacity',
+ 'cells',
+ 'maxX',
+ 'maxY',
+ 'minX',
+ 'minY',
+ 'x',
+ 'y',
+ 'stickeyPoint',
+ ])
+ }
+
return {
canvas,
addShape,
diff --git a/src/hooks/useEvent.js b/src/hooks/useEvent.js
index c41b189f..34af6ca9 100644
--- a/src/hooks/useEvent.js
+++ b/src/hooks/useEvent.js
@@ -1,7 +1,15 @@
import { useEffect, useRef } from 'react'
import { useRecoilState, useRecoilValue } from 'recoil'
-import { canvasState, canvasZoomState, currentMenuState } from '@/store/canvasAtom'
+import {
+ adsorptionPointAddModeState,
+ adsorptionPointModeState,
+ adsorptionRangeState,
+ canvasState,
+ canvasZoomState,
+ currentMenuState,
+} from '@/store/canvasAtom'
import { fabric } from 'fabric'
+import { calculateIntersection, distanceBetweenPoints } from '@/util/canvas-util'
export function useEvent() {
const canvas = useRecoilValue(canvasState)
@@ -9,6 +17,9 @@ export function useEvent() {
const keyboardEventListeners = useRef([])
const mouseEventListeners = useRef([])
const [canvasZoom, setCanvasZoom] = useRecoilState(canvasZoomState)
+ const adsorptionPointAddMode = useRecoilValue(adsorptionPointAddModeState)
+ const adsorptionPointMode = useRecoilValue(adsorptionPointModeState)
+ const adsorptionRange = useRecoilValue(adsorptionRangeState)
useEffect(() => {
if (!canvas) {
@@ -24,14 +35,42 @@ export function useEvent() {
canvas?.on('mouse:wheel', wheelEvent)
addDefaultEvent()
- }, [currentMenu, canvas])
+ }, [currentMenu, canvas, adsorptionPointAddMode, adsorptionPointMode, adsorptionRange])
const addDefaultEvent = () => {
//default Event 추가
addCanvasMouseEventListener('mouse:move', defaultMouseMoveEvent)
addCanvasMouseEventListener('mouse:out', defaultMouseOutEvent)
-
+ if (adsorptionPointAddMode) {
+ addCanvasMouseEventListener('mouse:down', adsorptionPointAddModeStateEvent)
+ }
addDocumentEventListener('keydown', document, defaultKeyboardEvent)
+ addDocumentEventListener('contextmenu', document, defaultContextMenuEvent)
+ }
+
+ const defaultContextMenuEvent = (e) => {
+ e.preventDefault()
+ e.stopPropagation()
+ }
+
+ const adsorptionPointAddModeStateEvent = (opt) => {
+ //흡착점 모드일 경우
+ let pointer = getIntersectMousePoint(opt)
+
+ const adsorptionPoint = new fabric.Circle({
+ radius: 3,
+ fill: 'red',
+ left: pointer.x - 3,
+ top: pointer.y - 3,
+ x: pointer.x,
+ y: pointer.y,
+ selectable: false,
+ name: 'adsorptionPoint',
+ })
+
+ canvas.add(adsorptionPoint)
+
+ canvas.renderAll()
}
const wheelEvent = (opt) => {
@@ -62,7 +101,28 @@ export function useEvent() {
removeMouseLine()
// 가로선
const pointer = canvas.getPointer(e.e)
- const horizontalLine = new fabric.Line([-1 * canvas.width, pointer.y, 2 * canvas.width, pointer.y], {
+
+ const adsorptionPoints = getAdsorptionPoints()
+
+ let arrivalPoint = { x: pointer.x, y: pointer.y }
+
+ if (adsorptionPointMode) {
+ // pointer와 adsorptionPoints의 거리를 계산하여 가장 가까운 점을 찾는다.
+ let minDistance = adsorptionRange
+ let adsorptionPoint = null
+ adsorptionPoints.forEach((point) => {
+ const distance = distanceBetweenPoints(pointer, point)
+ if (distance < minDistance) {
+ minDistance = distance
+ adsorptionPoint = point
+ }
+ })
+ if (adsorptionPoint) {
+ arrivalPoint = { ...adsorptionPoint }
+ }
+ }
+
+ const horizontalLine = new fabric.Line([-1 * canvas.width, arrivalPoint.y, 2 * canvas.width, arrivalPoint.y], {
stroke: 'red',
strokeWidth: 1,
selectable: false,
@@ -70,7 +130,7 @@ export function useEvent() {
})
// 세로선
- const verticalLine = new fabric.Line([pointer.x, -1 * canvas.height, pointer.x, 2 * canvas.height], {
+ const verticalLine = new fabric.Line([arrivalPoint.x, -1 * canvas.height, arrivalPoint.x, 2 * canvas.height], {
stroke: 'red',
strokeWidth: 1,
selectable: false,
@@ -143,11 +203,28 @@ export function useEvent() {
})
}
+ const getAdsorptionPoints = () => {
+ return canvas.getObjects().filter((obj) => obj.visible && obj.name === 'adsorptionPoint')
+ }
+
+ //가로선, 세로선의 교차점을 return
+ const getIntersectMousePoint = (e) => {
+ let pointer = canvas.getPointer(e.e)
+ const mouseLines = canvas.getObjects().filter((obj) => obj.name === 'mouseLine')
+
+ if (mouseLines.length < 2) {
+ return pointer
+ }
+
+ return calculateIntersection(mouseLines[0], mouseLines[1]) ?? pointer
+ }
+
return {
addDocumentEventListener,
addCanvasMouseEventListener,
removeAllMouseEventListeners,
removeAllDocumentEventListeners,
removeMouseEvent,
+ getIntersectMousePoint,
}
}
diff --git a/src/store/canvasAtom.js b/src/store/canvasAtom.js
index 2d9ef2dd..7f942f59 100644
--- a/src/store/canvasAtom.js
+++ b/src/store/canvasAtom.js
@@ -1,4 +1,4 @@
-import { atom } from 'recoil'
+import { atom, selector } from 'recoil'
import { MENU } from '@/common/common'
export const canvasState = atom({
@@ -201,3 +201,20 @@ export const verticalHorizontalModeState = atom({
key: 'verticalHorizontalMode',
default: true,
})
+
+// 흡착점 모드
+export const adsorptionPointModeState = atom({
+ key: 'adsorptionPointModeState',
+ default: false,
+})
+// 흡착점 추가모드
+export const adsorptionPointAddModeState = atom({
+ key: 'adsorptionPointAddModeState',
+ default: false,
+})
+
+// 흡착점 범위
+export const adsorptionRangeState = atom({
+ key: 'adsorptionRangeState',
+ default: 50,
+})
diff --git a/src/store/settingAtom.js b/src/store/settingAtom.js
index c00cc667..93cec544 100644
--- a/src/store/settingAtom.js
+++ b/src/store/settingAtom.js
@@ -40,10 +40,10 @@ export const settingModalSecondOptionsState = atom({
{ id: 4, name: 'modal.canvas.setting.font.plan.edit.circuit.num' },
],
option4: [
- { id: 1, column: 'adsorpRangeSmall', name: 'modal.canvas.setting.font.plan.absorption.small', selected: true },
- { id: 2, column: 'adsorpRangeSmallSemi', name: 'modal.canvas.setting.font.plan.absorption.small.semi', selected: false },
- { id: 3, column: 'adsorpRangeMedium', name: 'modal.canvas.setting.font.plan.absorption.medium', selected: false },
- { id: 4, column: 'adsorpRangeLarge', name: 'modal.canvas.setting.font.plan.absorption.large', selected: false },
+ { id: 1, column: 'adsorpRangeSmall', name: 'modal.canvas.setting.font.plan.absorption.small', selected: true, range: 10 },
+ { id: 2, column: 'adsorpRangeSmallSemi', name: 'modal.canvas.setting.font.plan.absorption.small.semi', selected: false, range: 30 },
+ { id: 3, column: 'adsorpRangeMedium', name: 'modal.canvas.setting.font.plan.absorption.medium', selected: false, range: 50 },
+ { id: 4, column: 'adsorpRangeLarge', name: 'modal.canvas.setting.font.plan.absorption.large', selected: false, range: 80 },
],
},
dangerouslyAllowMutability: true,