diff --git a/src/components/Roof2.jsx b/src/components/Roof2.jsx
index 3fce8640..e3d24216 100644
--- a/src/components/Roof2.jsx
+++ b/src/components/Roof2.jsx
@@ -11,6 +11,7 @@ import { useRecoilState, useRecoilValue } from 'recoil'
import {
canvasSizeState,
compassState,
+ currentObjectState,
fontSizeState,
roofMaterialState,
roofState,
@@ -27,6 +28,8 @@ import QContextMenu from './common/context-menu/QContextMenu'
import { modalContent, modalState } from '@/store/modalAtom'
import SettingsModal from './SettingsModal'
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) {
const { name, userId, email, isLoggedIn } = props
@@ -67,6 +70,7 @@ export default function Roof2(props) {
const [contents, setContent] = useRecoilState(modalContent)
const [scale, setScale] = useState(1)
+ const currentObject = useRecoilValue(currentObjectState)
//canvas 썸네일
const [thumbnails, setThumbnails] = useState([])
@@ -125,6 +129,7 @@ export default function Roof2(props) {
if (canvas) {
const line = new QLine([50, 50, 200, 50], {
stroke: 'black',
+ selectable: true,
strokeWidth: 2,
fontSize: fontSize,
})
@@ -757,7 +762,13 @@ export default function Roof2(props) {
- {canvas !== undefined && }
+ {!canvas ? null : currentObject?.type === 'QPolygon' ? (
+
+ ) : currentObject?.type === 'QLine' ? (
+
+ ) : (
+
+ )}
>
)
diff --git a/src/components/common/context-menu/QLineContextMenu.jsx b/src/components/common/context-menu/QLineContextMenu.jsx
new file mode 100644
index 00000000..058c3b95
--- /dev/null
+++ b/src/components/common/context-menu/QLineContextMenu.jsx
@@ -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 && (
+
+
+ - handleMenuClick(1)}>
+ line
+
+ - handleMenuClick(1)}>
+ Option 1
+
+ - handleMenuClick(2)}>
+ Option 2
+
+ - handleMenuClick(3)}>
+ Option 3
+
+
+
+ )}
+ >
+ )
+}
diff --git a/src/components/common/context-menu/QPolygonContextMenu.jsx b/src/components/common/context-menu/QPolygonContextMenu.jsx
new file mode 100644
index 00000000..b3246165
--- /dev/null
+++ b/src/components/common/context-menu/QPolygonContextMenu.jsx
@@ -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 && (
+
+
+ - handleMenuClick(1)}>
+ polygon
+
+ - handleMenuClick(1)}>
+ Option 1
+
+ - handleMenuClick(2)}>
+ Option 2
+
+ - handleMenuClick(3)}>
+ Option 3
+
+
+
+ )}
+ >
+ )
+}
diff --git a/src/components/fabric/QLine.js b/src/components/fabric/QLine.js
index 9604ec7c..55765279 100644
--- a/src/components/fabric/QLine.js
+++ b/src/components/fabric/QLine.js
@@ -11,6 +11,7 @@ export const QLine = fabric.util.createClass(fabric.Line, {
direction: null,
idx: 0,
area: 0,
+ children: [],
initialize: function (points, options, canvas) {
this.callSuper('initialize', points, { ...options, selectable: options.selectable ?? false })
if (options.id) {
@@ -122,6 +123,7 @@ export const QLine = fabric.util.createClass(fabric.Line, {
lockRotation: true,
lockScalingX: true,
lockScalingY: true,
+ parent: this,
name: 'lengthText',
})
diff --git a/src/components/fabric/QPolygon.js b/src/components/fabric/QPolygon.js
index 28a7d84a..c5093f91 100644
--- a/src/components/fabric/QPolygon.js
+++ b/src/components/fabric/QPolygon.js
@@ -17,6 +17,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
cells: [],
parentId: null,
innerLines: [],
+ children: [],
initialize: function (points, options, canvas) {
// 소수점 전부 제거
points.forEach((point) => {
@@ -195,6 +196,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
lockScalingY: true,
idx: i,
name: 'lengthText',
+ parent: this,
})
this.texts.push(text)
@@ -271,6 +273,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
name: 'cell',
idx: idx,
parentId: this.id,
+ parent: this,
})
idx++
diff --git a/src/hooks/useCanvas.js b/src/hooks/useCanvas.js
index abcb22cc..42fe035d 100644
--- a/src/hooks/useCanvas.js
+++ b/src/hooks/useCanvas.js
@@ -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에 그냥 저장하지만 나중에 변경해야함
@@ -469,7 +450,6 @@ export function useCanvas(id) {
handleUndo,
handleRedo,
handleCopy,
- handleDelete,
handleSave,
handlePaste,
handleRotate,
diff --git a/src/hooks/useCanvasEvent.js b/src/hooks/useCanvasEvent.js
index 1947708f..89a15b3a 100644
--- a/src/hooks/useCanvasEvent.js
+++ b/src/hooks/useCanvasEvent.js
@@ -1,162 +1,146 @@
import { useEffect, useState } from 'react'
import { fabric } from 'fabric'
-import { useRecoilValue } from 'recoil'
-import { canvasSizeState, modeState } from '@/store/canvasAtom'
+import { useRecoilState, useRecoilValue } from 'recoil'
+import { canvasSizeState, currentObjectState, modeState } from '@/store/canvasAtom'
// 캔버스에 필요한 이벤트
export function useCanvasEvent() {
const [canvas, setCanvasForEvent] = useState(null)
+ const [currentObject, setCurrentObject] = useRecoilState(currentObjectState)
const canvasSize = useRecoilValue(canvasSizeState)
// 기본적인 이벤트 필요시 추가
const attachDefaultEventOnCanvas = () => {
removeEventOnCanvas()
- canvas?.on('object:added', onChange)
- canvas?.on('object:added', addEventOnObject)
- canvas?.on('object:modified', onChange)
- canvas?.on('object:removed', onChange)
+ canvas?.on('object:added', objectEvent.onChange)
+ canvas?.on('object:added', objectEvent.addEvent)
+ canvas?.on('object:modified', objectEvent.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', () => {
document.addEventListener('keydown', handleKeyDown)
})
+ canvas?.on('object:removed', objectEvent.removed)
}
- const onChange = (e) => {
- const target = e.target
- if (target) {
- // settleDown(target)
- }
+ const objectEvent = {
+ onChange: (e) => {
+ const target = e.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 = () => {
canvas?.off('object:added')
canvas?.off('object:modified')
canvas?.off('object:removed')
- canvas?.off('object:added')
- canvas?.off('mouse:move')
- canvas?.off('mouse:down')
- }
-
- 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()
+ canvas?.off('selection:cleared')
+ canvas?.off('selection:created')
+ canvas?.off('selection:updated')
}
/**
@@ -197,6 +181,16 @@ export function useCanvasEvent() {
case 'Esc': // IE/Edge에서 사용되는 값
case 'Escape':
break
+
+ case 'z':
+ if (!e.ctrlKey) {
+ return
+ }
+
+ console.log('뒤로가기')
+
+ break
+
default:
return // 키 이벤트를 처리하지 않는다면 종료합니다.
}
@@ -216,6 +210,7 @@ export function useCanvasEvent() {
}
targetObj.set({ top: top })
+ targetObj.fire('modified')
canvas?.renderAll()
}
@@ -232,6 +227,7 @@ export function useCanvasEvent() {
}
targetObj.set({ top: top })
+ targetObj.fire('modified')
canvas?.renderAll()
}
@@ -248,6 +244,7 @@ export function useCanvasEvent() {
}
targetObj.set({ left: left })
+ targetObj.fire('modified')
canvas?.renderAll()
}
@@ -264,9 +261,29 @@ export function useCanvasEvent() {
}
targetObj.set({ left: left })
+ targetObj.fire('modified')
canvas?.renderAll()
}
+ /**
+ * 선택한 도형을 삭제한다.
+ */
+ const handleDelete = () => {
+ const targets = canvas?.getActiveObjects()
+ if (targets?.length === 0) {
+ alert('삭제할 대상을 선택해주세요.')
+ return
+ }
+
+ if (!confirm('정말로 삭제하시겠습니까?')) {
+ return
+ }
+
+ targets?.forEach((target) => {
+ canvas?.remove(target)
+ })
+ }
+
return {
setCanvasForEvent,
attachDefaultEventOnCanvas,
diff --git a/src/store/canvasAtom.js b/src/store/canvasAtom.js
index 062698c0..fafe26a1 100644
--- a/src/store/canvasAtom.js
+++ b/src/store/canvasAtom.js
@@ -94,3 +94,9 @@ export const guideLineState = atom({
default: {},
dangerouslyAllowMutability: true,
})
+
+export const currentObjectState = atom({
+ key: 'currentObject',
+ default: null,
+ dangerouslyAllowMutability: true,
+})