흡착점 추가

This commit is contained in:
hyojun.choi 2024-09-25 13:29:25 +09:00
parent c6801960b5
commit 1c2d3b7968
8 changed files with 184 additions and 18 deletions

View File

@ -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

View File

@ -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') //

View File

@ -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 (
<>
<div className="modal-check-btn-wrap">

View File

@ -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() {
<button className="arr-btn">
<span>{getMessage('modal.canvas.setting.font.plan.absorption.plan.size.setting')}</span>
</button>
<button className="adsorption-point act">
<button
className="adsorption-point act"
onClick={(e) => {
setAdsorptionPointMode(!adsorptionPointMode)
}}
>
<span>{getMessage('modal.canvas.setting.font.plan.absorption.point')}</span>
<i>ON</i>
<i>{adsorptionPointMode ? 'ON' : 'OFF'}</i>
</button>
</div>
</div>

View File

@ -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,

View File

@ -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,
}
}

View File

@ -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,
})

View File

@ -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,