1052 lines
40 KiB
JavaScript
1052 lines
40 KiB
JavaScript
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
|
|
import { canvasSettingState, canvasState, currentMenuState, currentObjectState } from '@/store/canvasAtom'
|
|
import { useEffect, useState } from 'react'
|
|
import AuxiliarySize from '@/components/floor-plan/modal/auxiliary/AuxiliarySize'
|
|
import { usePopup } from '@/hooks/usePopup'
|
|
import { v4 as uuidv4 } from 'uuid'
|
|
import GridMove from '@/components/floor-plan/modal/grid/GridMove'
|
|
import GridCopy from '@/components/floor-plan/modal/grid/GridCopy'
|
|
import ColorPickerModal from '@/components/common/color-picker/ColorPickerModal'
|
|
import { gridColorState } from '@/store/gridAtom'
|
|
import { contextPopupPositionState, contextPopupState } from '@/store/popupAtom'
|
|
import AuxiliaryEdit from '@/components/floor-plan/modal/auxiliary/AuxiliaryEdit'
|
|
import SizeSetting from '@/components/floor-plan/modal/object/SizeSetting'
|
|
import DormerOffset from '@/components/floor-plan/modal/object/DormerOffset'
|
|
import FontSetting from '@/components/common/font/FontSetting'
|
|
import RoofAllocationSetting from '@/components/floor-plan/modal/roofAllocation/RoofAllocationSetting'
|
|
import FlowDirectionSetting from '@/components/floor-plan/modal/flowDirection/FlowDirectionSetting'
|
|
|
|
import { useCommonUtils } from './common/useCommonUtils'
|
|
import { useMessage } from '@/hooks/useMessage'
|
|
import { useCanvasEvent } from '@/hooks/useCanvasEvent'
|
|
import { contextMenuListState, contextMenuState } from '@/store/contextMenu'
|
|
import PanelEdit, { PANEL_EDIT_TYPE } from '@/components/floor-plan/modal/module/PanelEdit'
|
|
import DimensionLineSetting from '@/components/floor-plan/modal/dimensionLine/DimensionLineSetting'
|
|
import ColumnRemove from '@/components/floor-plan/modal/module/column/ColumnRemove'
|
|
import ColumnInsert from '@/components/floor-plan/modal/module/column/ColumnInsert'
|
|
import RowRemove from '@/components/floor-plan/modal/module/row/RowRemove'
|
|
import RowInsert from '@/components/floor-plan/modal/module/row/RowInsert'
|
|
import { useObjectBatch } from '@/hooks/object/useObjectBatch'
|
|
import { useSurfaceShapeBatch } from '@/hooks/surface/useSurfaceShapeBatch'
|
|
import { fontSelector, globalFontAtom } from '@/store/fontAtom'
|
|
import { useLine } from '@/hooks/useLine'
|
|
import { useSwal } from '@/hooks/useSwal'
|
|
import ContextRoofAllocationSetting from '@/components/floor-plan/modal/roofAllocation/ContextRoofAllocationSetting'
|
|
import { useCanvasSetting } from './option/useCanvasSetting'
|
|
import { useGrid } from './common/useGrid'
|
|
import { useAdsorptionPoint } from './useAdsorptionPoint'
|
|
import { useRoofFn } from '@/hooks/common/useRoofFn'
|
|
import { MODULE_ALIGN_TYPE, useModule } from './module/useModule'
|
|
import PlacementSurfaceLineProperty from '@/components/floor-plan/modal/placementShape/PlacementSurfaceLineProperty'
|
|
import { selectedMenuState } from '@/store/menuAtom'
|
|
import { useTrestle } from './module/useTrestle'
|
|
import { useCircuitTrestle } from './useCirCuitTrestle'
|
|
import { usePolygon } from '@/hooks/usePolygon'
|
|
import { useText } from '@/hooks/useText'
|
|
import { useUndoRedo } from '@/hooks/useUndoRedo'
|
|
import { POLYGON_TYPE } from '@/common/common'
|
|
import { debugCapture } from '@/util/debugCapture'
|
|
|
|
export function useContextMenu() {
|
|
const canvas = useRecoilValue(canvasState)
|
|
const canvasSetting = useRecoilValue(canvasSettingState)
|
|
const currentMenu = useRecoilValue(currentMenuState) // 현재 메뉴
|
|
const setContextPopupPosition = useSetRecoilState(contextPopupPositionState) // 현재 메뉴
|
|
const [contextMenu, setContextMenu] = useRecoilState(contextMenuListState) // 메뉴.object 별 context menu
|
|
const [currentContextMenu, setCurrentContextMenu] = useRecoilState(contextPopupState) // 선택한 contextMenu
|
|
const currentObject = useRecoilValue(currentObjectState)
|
|
const { getMessage } = useMessage()
|
|
const { addPopup } = usePopup()
|
|
const [popupId, setPopupId] = useState(uuidv4())
|
|
const [gridColor, setGridColor] = useRecoilState(gridColorState)
|
|
const { deleteObject, moveObject, copyObject, editText, changeDimensionExtendLine, deleteOuterLineObject } = useCommonUtils()
|
|
const [qContextMenu, setQContextMenu] = useRecoilState(contextMenuState)
|
|
const [cell, setCell] = useState(null)
|
|
const [column, setColumn] = useState(null)
|
|
const { handleZoomClear } = useCanvasEvent()
|
|
const { moveObjectBatch, copyObjectBatch } = useObjectBatch({})
|
|
const { moveSurfaceShapeBatch, rotateSurfaceShapeBatch } = useSurfaceShapeBatch({})
|
|
const [globalFont, setGlobalFont] = useRecoilState(globalFontAtom)
|
|
const { addLine, removeLine } = useLine()
|
|
const { removeGrid } = useGrid()
|
|
const { removeAdsorptionPoint } = useAdsorptionPoint()
|
|
const commonTextFont = useRecoilValue(fontSelector('commonText'))
|
|
const { settingsData, setSettingsDataSave } = useCanvasSetting(false)
|
|
const { swalFire } = useSwal()
|
|
const { alignModule, modulesRemove, moduleRoofRemove } = useModule()
|
|
const { removeRoofMaterial, removeAllRoofMaterial, moveRoofMaterial, removeOuterLines, setSurfaceShapePattern } = useRoofFn()
|
|
const selectedMenu = useRecoilValue(selectedMenuState)
|
|
const { isAllComplete, clear: resetModule } = useTrestle()
|
|
const { isExistCircuit } = useCircuitTrestle()
|
|
const { changeCorridorDimensionText } = useText()
|
|
const { setPolygonLinesActualSize, drawDirectionArrow } = usePolygon()
|
|
const { saveSnapshot } = useUndoRedo()
|
|
const currentMenuSetting = () => {
|
|
switch (selectedMenu) {
|
|
case 'outline':
|
|
break
|
|
// default:
|
|
// setContextMenu([])
|
|
// break
|
|
}
|
|
}
|
|
|
|
const handleClick = (e, menu) => {
|
|
if (menu?.fn) {
|
|
menu.fn(qContextMenu.currentMousePos)
|
|
}
|
|
|
|
setContextPopupPosition({
|
|
x: window.innerWidth / 2,
|
|
y: 180,
|
|
})
|
|
setCurrentContextMenu(menu)
|
|
currentMenuSetting()
|
|
setQContextMenu({ ...qContextMenu, visible: false })
|
|
}
|
|
|
|
const handleKeyup = (e) => {
|
|
let menu = null
|
|
for (let i = 0; i < contextMenu.length; i++) {
|
|
const temp = contextMenu[i].filter((menu) => {
|
|
return menu.shortcut?.includes(e.key)
|
|
})
|
|
|
|
if (temp.length > 0) menu = temp[0]
|
|
}
|
|
|
|
if (menu) handleClick(null, menu)
|
|
}
|
|
|
|
// [TMP-DEL 2026-05-07] Del 키용: 현재 contextMenu 에서 삭제 메뉴를 찾아 실행
|
|
// 매칭 규칙: id === 'remove' 또는 id 가 'Remove' 로 끝남 (단, 'wallLineRemove' 제외, 'RemoveAll' 제외)
|
|
const triggerDelete = () => {
|
|
let target = null
|
|
for (const group of contextMenu) {
|
|
for (const item of group) {
|
|
const id = item?.id || ''
|
|
if (id === 'wallLineRemove') continue
|
|
if (id === 'remove' || id.endsWith('Remove')) {
|
|
target = item
|
|
break
|
|
}
|
|
}
|
|
if (target) break
|
|
}
|
|
if (target?.fn) handleClick(null, target)
|
|
}
|
|
|
|
useEffect(() => {
|
|
currentMenuSetting()
|
|
}, [gridColor, currentMenu])
|
|
useEffect(() => {
|
|
if (currentContextMenu?.component) addPopup(popupId, 1, currentContextMenu?.component)
|
|
}, [currentContextMenu])
|
|
|
|
useEffect(() => {
|
|
//console.log('currentObject', currentObject)
|
|
if (currentObject?.name) {
|
|
switch (currentObject.name) {
|
|
case 'triangleDormer':
|
|
case 'pentagonDormer':
|
|
if (selectedMenu === 'surface') {
|
|
setContextMenu([
|
|
[
|
|
{
|
|
id: 'dormerRemove',
|
|
name: getMessage('contextmenu.remove'),
|
|
fn: () => deleteObject(),
|
|
},
|
|
{
|
|
id: 'dormerMove',
|
|
shortcut: ['m', 'M'],
|
|
name: `${getMessage('contextmenu.move')}(M)`,
|
|
fn: () => moveObjectBatch(),
|
|
},
|
|
{
|
|
id: 'dormerCopy',
|
|
shortcut: ['c', 'C'],
|
|
name: `${getMessage('contextmenu.copy')}(C)`,
|
|
fn: () => copyObject(),
|
|
},
|
|
{
|
|
id: 'dormerOffset',
|
|
name: getMessage('contextmenu.dormer.offset'),
|
|
component: <DormerOffset id={popupId} title={getMessage('contextmenu.dormer.offset')} />,
|
|
},
|
|
],
|
|
])
|
|
}
|
|
break
|
|
|
|
case 'outerLine': {
|
|
setContextMenu([
|
|
[
|
|
{
|
|
id: 'roofMaterialPlacement',
|
|
name: getMessage('contextmenu.roof.material.placement'),
|
|
component: <RoofAllocationSetting id={popupId} />,
|
|
},
|
|
|
|
{
|
|
id: 'roofMaterialRemoveAll',
|
|
name: getMessage('contextmenu.roof.material.remove.all'),
|
|
fn: () => {
|
|
saveSnapshot()
|
|
removeAllRoofMaterial()
|
|
},
|
|
},
|
|
{
|
|
id: 'selectMove',
|
|
name: getMessage('contextmenu.select.move'),
|
|
fn: (currentMousePos) => {
|
|
moveRoofMaterial(currentMousePos)
|
|
},
|
|
},
|
|
{
|
|
id: 'wallLineRemove',
|
|
name: getMessage('contextmenu.wallline.remove'),
|
|
fn: (currentMousePos) => {
|
|
saveSnapshot()
|
|
removeOuterLines(currentMousePos)
|
|
},
|
|
},
|
|
],
|
|
])
|
|
break
|
|
}
|
|
case 'roof':
|
|
if (selectedMenu === 'surface') {
|
|
setContextMenu([
|
|
[
|
|
{
|
|
id: 'sizeEdit',
|
|
name: getMessage('contextmenu.size.edit'),
|
|
component: <SizeSetting id={popupId} target={currentObject} />,
|
|
},
|
|
{
|
|
id: 'rotate',
|
|
name: `${getMessage('contextmenu.rotate')}`,
|
|
fn: () => rotateSurfaceShapeBatch(),
|
|
},
|
|
{
|
|
id: 'roofMaterialRemove',
|
|
name: getMessage('contextmenu.remove'),
|
|
fn: () => deleteObject(),
|
|
},
|
|
{
|
|
id: 'roofMaterialMove',
|
|
shortcut: ['m', 'M'],
|
|
name: `${getMessage('contextmenu.move')}(M)`,
|
|
fn: () => moveSurfaceShapeBatch(),
|
|
},
|
|
{
|
|
id: 'roofMaterialCopy',
|
|
shortcut: ['c', 'C'],
|
|
name: `${getMessage('contextmenu.copy')}(C)`,
|
|
fn: () => copyObject(),
|
|
},
|
|
],
|
|
[
|
|
{
|
|
id: 'roofMaterialEdit',
|
|
name: getMessage('contextmenu.roof.material.edit'),
|
|
component: <ContextRoofAllocationSetting id={popupId} />,
|
|
},
|
|
{
|
|
id: 'linePropertyEdit',
|
|
name: getMessage('contextmenu.line.property.edit'),
|
|
fn: () => {
|
|
if (+canvasSetting.roofSizeSet === 3) {
|
|
swalFire({ text: getMessage('contextmenu.line.property.edit.roof.size.3') })
|
|
} else {
|
|
addPopup(popupId, 1, <PlacementSurfaceLineProperty id={popupId} roof={currentObject} />)
|
|
}
|
|
},
|
|
// component: <LinePropertySetting id={popupId} target={currentObject} />,
|
|
},
|
|
{
|
|
id: 'flowDirectionEdit',
|
|
name: getMessage('contextmenu.flow.direction.edit'),
|
|
component: <FlowDirectionSetting id={popupId} target={currentObject} />,
|
|
},
|
|
],
|
|
])
|
|
} else if (selectedMenu === 'outline') {
|
|
setContextMenu([
|
|
[
|
|
{
|
|
id: 'roofMaterialPlacement',
|
|
name: getMessage('contextmenu.roof.material.placement'),
|
|
component: <RoofAllocationSetting id={popupId} />,
|
|
},
|
|
|
|
{
|
|
id: 'roofMaterialRemoveAll',
|
|
name: getMessage('contextmenu.roof.material.remove.all'),
|
|
fn: () => {
|
|
saveSnapshot()
|
|
removeAllRoofMaterial()
|
|
},
|
|
},
|
|
{
|
|
id: 'selectMove',
|
|
name: getMessage('contextmenu.select.move'),
|
|
fn: (currentMousePos) => {
|
|
moveRoofMaterial(currentMousePos)
|
|
},
|
|
},
|
|
{
|
|
id: 'wallLineRemove',
|
|
name: getMessage('contextmenu.wallline.remove'),
|
|
fn: (currentMousePos) => {
|
|
removeOuterLines(currentMousePos)
|
|
},
|
|
},
|
|
],
|
|
])
|
|
}
|
|
|
|
break
|
|
case 'auxiliaryLine':
|
|
case 'hip':
|
|
case 'ridge':
|
|
case 'eaveHelpLine':
|
|
if (selectedMenu === 'surface') {
|
|
setContextMenu([
|
|
[
|
|
{
|
|
id: 'sizeEdit',
|
|
name: getMessage('contextmenu.size.edit'),
|
|
component: <SizeSetting id={popupId} target={currentObject} />,
|
|
},
|
|
{
|
|
id: 'rotate',
|
|
name: `${getMessage('contextmenu.rotate')}`,
|
|
fn: () => rotateSurfaceShapeBatch(),
|
|
},
|
|
{
|
|
id: 'roofMaterialRemove',
|
|
name: getMessage('contextmenu.remove'),
|
|
fn: () => deleteObject(),
|
|
},
|
|
{
|
|
id: 'roofMaterialMove',
|
|
shortcut: ['m', 'M'],
|
|
name: `${getMessage('contextmenu.move')}(M)`,
|
|
fn: () => moveSurfaceShapeBatch(),
|
|
},
|
|
{
|
|
id: 'roofMaterialCopy',
|
|
shortcut: ['c', 'C'],
|
|
name: `${getMessage('contextmenu.copy')}(C)`,
|
|
fn: () => copyObject(),
|
|
},
|
|
],
|
|
[
|
|
{
|
|
id: 'roofMaterialEdit',
|
|
name: getMessage('contextmenu.roof.material.edit'),
|
|
component: <ContextRoofAllocationSetting id={popupId} />,
|
|
},
|
|
{
|
|
id: 'linePropertyEdit',
|
|
name: getMessage('contextmenu.line.property.edit'),
|
|
fn: () => {
|
|
if (+canvasSetting.roofSizeSet === 3) {
|
|
swalFire({ text: getMessage('contextmenu.line.property.edit.roof.size.3') })
|
|
} else {
|
|
addPopup(popupId, 1, <PlacementSurfaceLineProperty id={popupId} roof={currentObject} />)
|
|
}
|
|
},
|
|
// component: <LinePropertySetting id={popupId} target={currentObject} />,
|
|
},
|
|
{
|
|
id: 'flowDirectionEdit',
|
|
name: getMessage('contextmenu.flow.direction.edit'),
|
|
component: <FlowDirectionSetting id={popupId} target={currentObject} />,
|
|
},
|
|
],
|
|
])
|
|
} else if (selectedMenu === 'outline') {
|
|
setContextMenu([
|
|
[
|
|
{
|
|
id: 'sizeEdit',
|
|
name: getMessage('contextmenu.size.edit'),
|
|
component: <AuxiliarySize id={popupId} />,
|
|
},
|
|
{
|
|
id: 'auxiliaryMove',
|
|
name: `${getMessage('contextmenu.auxiliary.move')}(M)`,
|
|
shortcut: ['m', 'M'],
|
|
component: <AuxiliaryEdit id={popupId} type={'move'} />,
|
|
},
|
|
{
|
|
id: 'auxiliaryCopy',
|
|
name: `${getMessage('contextmenu.auxiliary.copy')}(C)`,
|
|
shortcut: ['c', 'C'],
|
|
component: <AuxiliaryEdit id={popupId} type={'copy'} />,
|
|
},
|
|
{
|
|
id: 'auxiliaryRemove',
|
|
name: getMessage('contextmenu.auxiliary.remove'),
|
|
fn: () => {
|
|
if (!currentObject) return
|
|
|
|
saveSnapshot()
|
|
const co = currentObject
|
|
// [처마선 삭제 면분할 반영 2026-06-15] 기존 '보조선 삭제'는 roof.innerLines/canvas 에서만 지우고
|
|
// roof.lines(외곽 처마선)는 건드리지 않았다. 그래서 외곽 처마선(eaveHelpLine)을 이 메뉴로 지워도
|
|
// 면분할 입력(splitPolygonWithLines 의 polygon.lines)엔 그대로 남아, 그 변이 닫는 면(예: 위 사다리꼴)이
|
|
// 계속 생성됐다(GETSPLIT-IO 진단). 삭제 대상과 같은 좌표의 변을 roof.lines 에서도 제거해 삭제를 면분할까지 반영한다.
|
|
const pEq = (ax, ay, bx, by) => Math.abs(ax - bx) <= 2 && Math.abs(ay - by) <= 2
|
|
const sameSeg = (l) =>
|
|
l.id === co.id ||
|
|
(pEq(l.x1, l.y1, co.x1, co.y1) && pEq(l.x2, l.y2, co.x2, co.y2)) ||
|
|
(pEq(l.x1, l.y1, co.x2, co.y2) && pEq(l.x2, l.y2, co.x1, co.y1))
|
|
// roofId 로 roof 찾기 — eaveHelpLine 은 attributes.roofId 가 없을 수 있어 roofId 도 본다.
|
|
const roofId = co.attributes?.roofId ?? co.roofId
|
|
let roof = canvas.getObjects().find((obj) => obj.name === POLYGON_TYPE.ROOF && obj.id === roofId)
|
|
// id 로 못 찾으면 같은 좌표의 변을 가진 roof 를 탐색
|
|
if (!roof) {
|
|
roof = canvas.getObjects().find((obj) => obj.name === POLYGON_TYPE.ROOF && Array.isArray(obj.lines) && obj.lines.some(sameSeg))
|
|
}
|
|
const beforeLines = roof?.lines?.length ?? 0
|
|
const beforeInner = roof?.innerLines?.length ?? 0
|
|
if (roof) {
|
|
if (Array.isArray(roof.innerLines)) roof.innerLines = roof.innerLines.filter((l) => !sameSeg(l))
|
|
if (Array.isArray(roof.lines)) roof.lines = roof.lines.filter((l) => !sameSeg(l))
|
|
}
|
|
debugCapture.log('AUX-REMOVE-DIAG', {
|
|
co: {
|
|
name: co.name,
|
|
lineName: co.lineName,
|
|
id: co.id?.slice?.(0, 8),
|
|
roofId: co.roofId?.slice?.(0, 8),
|
|
attrRoofId: co.attributes?.roofId?.slice?.(0, 8),
|
|
x1: Math.round(co.x1 * 10) / 10,
|
|
y1: Math.round(co.y1 * 10) / 10,
|
|
x2: Math.round(co.x2 * 10) / 10,
|
|
y2: Math.round(co.y2 * 10) / 10,
|
|
},
|
|
roofFound: !!roof,
|
|
roofId: roof?.id?.slice?.(0, 8),
|
|
lines: `${beforeLines}→${roof?.lines?.length ?? 0}`,
|
|
innerLines: `${beforeInner}→${roof?.innerLines?.length ?? 0}`,
|
|
})
|
|
canvas.remove(co)
|
|
canvas.discardActiveObject()
|
|
},
|
|
},
|
|
{
|
|
id: 'auxiliaryVerticalBisector',
|
|
name: getMessage('contextmenu.auxiliary.vertical.bisector'),
|
|
fn: () => {
|
|
if (!currentObject) return
|
|
saveSnapshot()
|
|
const slope = (currentObject.y2 - currentObject.y1) / (currentObject.x2 - currentObject.x1)
|
|
const length = currentObject.length
|
|
|
|
let startX, startY, endX, endY
|
|
if (slope === 0) {
|
|
startX = endX = (currentObject.x1 + currentObject.x2) / 2
|
|
startY = currentObject.y2 - length / 2
|
|
endY = currentObject.y2 + length / 2
|
|
} else if (slope === Infinity) {
|
|
startX = currentObject.x1 - length / 2
|
|
startY = endY = (currentObject.y1 + currentObject.y2) / 2
|
|
endX = currentObject.x1 + length / 2
|
|
} else {
|
|
const bisectorSlope = -1 / slope
|
|
const dx = length / 2 / Math.sqrt(1 + bisectorSlope * bisectorSlope)
|
|
const dy = bisectorSlope * dx
|
|
|
|
startX = (currentObject.x1 + currentObject.x2) / 2 + dx
|
|
startY = (currentObject.y1 + currentObject.y2) / 2 + dy
|
|
endX = (currentObject.x1 + currentObject.x2) / 2 - dx
|
|
endY = (currentObject.y1 + currentObject.y2) / 2 - dy
|
|
}
|
|
|
|
const line = addLine([startX, startY, endX, endY], {
|
|
stroke: 'red',
|
|
strokeWidth: 1,
|
|
selectable: true,
|
|
name: 'auxiliaryLine',
|
|
attributes: { ...currentObject.attributes },
|
|
})
|
|
|
|
if (!currentObject.attributes.roofId) {
|
|
return
|
|
}
|
|
|
|
canvas
|
|
.getObjects()
|
|
.filter((obj) => obj.id === currentObject.attributes.roofId)[0]
|
|
.innerLines.push(line)
|
|
},
|
|
},
|
|
{
|
|
id: 'auxiliaryRemoveAll',
|
|
name: getMessage('contextmenu.auxiliary.remove.all'),
|
|
fn: () => {
|
|
if (!currentObject) {
|
|
swalFire({ text: getMessage('roof.is.not.selected') })
|
|
return
|
|
}
|
|
saveSnapshot()
|
|
const innerLines = canvas.getObjects().filter((obj) => obj.id === currentObject.attributes.roofId)[0]?.innerLines
|
|
if (innerLines) {
|
|
innerLines.forEach((line) => {
|
|
canvas.remove(line)
|
|
})
|
|
innerLines.length = 0
|
|
}
|
|
|
|
// [전체삭제 누락 fix] 기존에는 미확정 보조선(!isAuxiliaryFixed)만 삭제해,
|
|
// 복사본/미확정 보조선을 우클릭하면 currentObject.roofId 가 위 innerLines 와 어긋나
|
|
// 확정된 보조선이 누락되었다. 확정 여부와 무관하게 모든 보조선을 삭제한다.
|
|
const auxiliaryLines = canvas.getObjects().filter((obj) => obj.name === 'auxiliaryLine')
|
|
auxiliaryLines.forEach((line) => {
|
|
canvas.remove(line)
|
|
})
|
|
|
|
// 지붕 innerLines 에 남은 보조선/eaveHelpLine 참조 정리 (stale 부활 방지, 용마루 hip·ridge 는 보존)
|
|
canvas.getObjects().forEach((obj) => {
|
|
if (Array.isArray(obj.innerLines) && obj.innerLines.length > 0) {
|
|
obj.innerLines = obj.innerLines.filter(
|
|
(line) => line.name !== 'auxiliaryLine' && line.name !== 'eaveHelpLine' && line.lineName !== 'eaveHelpLine',
|
|
)
|
|
}
|
|
})
|
|
|
|
//동이동현이동 - 모든 eaveHelpLine 삭제 (roofId 무관, name/lineName 두 식별자 모두 검사)
|
|
const eaveHelpLines = canvas.getObjects().filter((obj) => obj.name === 'eaveHelpLine' || obj.lineName === 'eaveHelpLine')
|
|
eaveHelpLines.forEach((line) => {
|
|
canvas.remove(line)
|
|
})
|
|
|
|
canvas.renderAll()
|
|
},
|
|
},
|
|
],
|
|
])
|
|
}
|
|
|
|
break
|
|
case 'opening':
|
|
setContextMenu([
|
|
[
|
|
{
|
|
id: 'sizeEdit',
|
|
name: getMessage('contextmenu.size.edit'),
|
|
component: <SizeSetting id={popupId} target={currentObject} />,
|
|
},
|
|
{
|
|
id: 'openingRemove',
|
|
name: getMessage('contextmenu.remove'),
|
|
fn: () => deleteObject(),
|
|
},
|
|
{
|
|
id: 'openingMove',
|
|
shortcut: ['m', 'M'],
|
|
name: `${getMessage('contextmenu.move')}(M)`,
|
|
fn: () => moveObjectBatch(),
|
|
},
|
|
{
|
|
id: 'openingCopy',
|
|
shortcut: ['c', 'C'],
|
|
name: `${getMessage('contextmenu.copy')}(C)`,
|
|
fn: () => copyObjectBatch(),
|
|
},
|
|
{
|
|
id: 'openingOffset',
|
|
name: getMessage('contextmenu.opening.offset'),
|
|
component: <DormerOffset id={popupId} title={getMessage('contextmenu.opening.offset')} />,
|
|
},
|
|
],
|
|
])
|
|
break
|
|
case 'lengthText':
|
|
setContextMenu([
|
|
[
|
|
{
|
|
id: 'lengthTextRemove',
|
|
name: getMessage('contextmenu.remove'),
|
|
},
|
|
{
|
|
id: 'lengthTextMove',
|
|
name: getMessage('contextmenu.move'),
|
|
},
|
|
{
|
|
id: 'lengthTextAuxiliaryLineEdit',
|
|
name: getMessage('contextmenu.dimension.auxiliary.line.edit'),
|
|
},
|
|
{
|
|
id: 'displayEdit',
|
|
name: getMessage('contextmenu.display.edit'),
|
|
},
|
|
],
|
|
])
|
|
break
|
|
case 'commonText':
|
|
setContextMenu([
|
|
[
|
|
{
|
|
id: 'commonTextRemove',
|
|
name: getMessage('contextmenu.remove'),
|
|
fn: () => deleteObject(),
|
|
},
|
|
{
|
|
id: 'commonTextMove',
|
|
name: getMessage('contextmenu.move'),
|
|
fn: () => moveObject(),
|
|
},
|
|
{
|
|
id: 'commonTextCopy',
|
|
name: getMessage('contextmenu.copy'),
|
|
fn: () => copyObject(),
|
|
},
|
|
{
|
|
id: 'commonTextFontSetting',
|
|
name: getMessage('contextmenu.font.setting'),
|
|
component: (
|
|
<FontSetting
|
|
id={popupId}
|
|
type={'commonText'}
|
|
font={commonTextFont}
|
|
onSave={(font) => {
|
|
setGlobalFont((prev) => {
|
|
return {
|
|
...prev,
|
|
commonText: {
|
|
fontFamily: font.fontFamily,
|
|
fontWeight: font.fontWeight,
|
|
fontSize: font.fontSize,
|
|
fontColor: font.fontColor,
|
|
},
|
|
}
|
|
})
|
|
}}
|
|
/>
|
|
),
|
|
},
|
|
{
|
|
id: 'commonTextEdit',
|
|
name: getMessage('contextmenu.edit'),
|
|
fn: () => editText(),
|
|
},
|
|
],
|
|
])
|
|
break
|
|
case 'lineGrid':
|
|
case 'dotGrid':
|
|
case 'tempGrid':
|
|
setContextMenu([
|
|
[
|
|
{
|
|
id: 'gridMove',
|
|
name: getMessage('modal.grid.move'),
|
|
component: <GridMove id={popupId} />,
|
|
},
|
|
{
|
|
id: 'gridCopy',
|
|
name: getMessage('modal.grid.copy'),
|
|
component: <GridCopy id={popupId} />,
|
|
},
|
|
{
|
|
id: 'gridColorEdit',
|
|
name: getMessage('contextmenu.grid.color.edit'),
|
|
component: (
|
|
<ColorPickerModal
|
|
id={popupId}
|
|
color={gridColor}
|
|
setColor={setGridColor}
|
|
settingsData={settingsData}
|
|
setSettingsDataSave={setSettingsDataSave}
|
|
/>
|
|
),
|
|
},
|
|
{
|
|
id: 'remove',
|
|
name: getMessage('contextmenu.remove'),
|
|
fn: () => {
|
|
canvas.remove(currentObject)
|
|
canvas.discardActiveObject()
|
|
},
|
|
},
|
|
{
|
|
id: 'removeAll',
|
|
name: getMessage('contextmenu.remove.all'),
|
|
fn: () => {
|
|
removeGrid()
|
|
removeAdsorptionPoint()
|
|
canvas.discardActiveObject()
|
|
},
|
|
},
|
|
],
|
|
])
|
|
break
|
|
case 'adsorptionPoint':
|
|
setContextMenu([
|
|
[
|
|
{
|
|
id: 'remove',
|
|
name: getMessage('contextmenu.remove'),
|
|
fn: () => {
|
|
canvas.remove(currentObject)
|
|
canvas.discardActiveObject()
|
|
},
|
|
},
|
|
{
|
|
id: 'removeAll',
|
|
name: getMessage('contextmenu.remove.all'),
|
|
fn: () => {
|
|
removeGrid()
|
|
removeAdsorptionPoint()
|
|
canvas.discardActiveObject()
|
|
},
|
|
},
|
|
],
|
|
])
|
|
break
|
|
case 'dimensionGroup':
|
|
setContextMenu([
|
|
[
|
|
{
|
|
id: 'dimensionLineRemove',
|
|
name: getMessage('contextmenu.remove'),
|
|
fn: () => deleteObject(),
|
|
},
|
|
{
|
|
id: 'dimensionLineMove',
|
|
name: getMessage('contextmenu.move'),
|
|
fn: () => moveObject(),
|
|
},
|
|
{
|
|
id: 'dimensionAuxiliaryLineEdit',
|
|
name: getMessage('contextmenu.dimension.auxiliary.line.edit'),
|
|
fn: () => changeDimensionExtendLine(),
|
|
},
|
|
{
|
|
id: 'dimensionLineDisplayEdit',
|
|
name: getMessage('contextmenu.display.edit'),
|
|
component: <DimensionLineSetting id={popupId} isConfig={false} />,
|
|
},
|
|
],
|
|
])
|
|
break
|
|
case 'shadow':
|
|
setContextMenu([
|
|
[
|
|
{
|
|
id: 'sizeEdit',
|
|
name: getMessage('contextmenu.size.edit'),
|
|
component: <SizeSetting id={popupId} target={currentObject} />,
|
|
},
|
|
{
|
|
id: 'remove',
|
|
name: getMessage('contextmenu.remove'),
|
|
fn: () => deleteObject(),
|
|
},
|
|
{
|
|
id: 'move',
|
|
shortcut: ['m', 'M'],
|
|
name: `${getMessage('contextmenu.move')}(M)`,
|
|
fn: () => moveObjectBatch(),
|
|
},
|
|
{
|
|
id: 'copy',
|
|
shortcut: ['c', 'C'],
|
|
name: `${getMessage('contextmenu.copy')}(C)`,
|
|
fn: () => copyObject(),
|
|
},
|
|
],
|
|
])
|
|
break
|
|
case 'module':
|
|
setContextMenu([
|
|
[
|
|
{
|
|
id: 'remove',
|
|
name: getMessage('contextmenu.remove'),
|
|
fn: () => {
|
|
modulesRemove()
|
|
},
|
|
},
|
|
{
|
|
id: 'move',
|
|
name: getMessage('contextmenu.move'),
|
|
fn: () => {
|
|
if (currentObject.circuit) {
|
|
swalFire({
|
|
title: getMessage('can.not.move.module'),
|
|
icon: 'error',
|
|
type: 'alert',
|
|
})
|
|
return
|
|
}
|
|
|
|
addPopup(popupId, 1, <PanelEdit id={popupId} type={PANEL_EDIT_TYPE.MOVE} />)
|
|
},
|
|
// component: <PanelEdit id={popupId} type={PANEL_EDIT_TYPE.MOVE} />,
|
|
},
|
|
{
|
|
id: 'copy',
|
|
name: getMessage('contextmenu.copy'),
|
|
fn: () => {
|
|
if (currentObject.circuit) {
|
|
swalFire({
|
|
title: getMessage('can.not.copy.module'),
|
|
icon: 'error',
|
|
type: 'alert',
|
|
})
|
|
return
|
|
}
|
|
|
|
addPopup(popupId, 1, <PanelEdit id={popupId} type={PANEL_EDIT_TYPE.COPY} />)
|
|
},
|
|
},
|
|
],
|
|
[
|
|
{
|
|
id: 'columnMove',
|
|
name: getMessage('contextmenu.column.move'),
|
|
fn: () => {
|
|
if (isAllComplete()) {
|
|
swalFire({
|
|
title: getMessage('can.not.move.module'),
|
|
icon: 'error',
|
|
type: 'alert',
|
|
})
|
|
return
|
|
}
|
|
resetModule()
|
|
addPopup(popupId, 1, <PanelEdit id={popupId} type={PANEL_EDIT_TYPE.COLUMN_MOVE} />)
|
|
},
|
|
},
|
|
{
|
|
id: 'columnCopy',
|
|
name: getMessage('contextmenu.column.copy'),
|
|
fn: () => {
|
|
if (isAllComplete()) {
|
|
swalFire({
|
|
title: getMessage('can.not.copy.module'),
|
|
icon: 'error',
|
|
type: 'alert',
|
|
})
|
|
return
|
|
}
|
|
resetModule()
|
|
addPopup(popupId, 1, <PanelEdit id={popupId} type={PANEL_EDIT_TYPE.COLUMN_COPY} />)
|
|
},
|
|
// component: <PanelEdit id={popupId} type={PANEL_EDIT_TYPE.COLUMN_COPY} />,
|
|
},
|
|
{
|
|
id: 'columnRemove',
|
|
name: getMessage('contextmenu.column.remove'),
|
|
fn: () => {
|
|
if (isExistCircuit()) {
|
|
resetModule()
|
|
}
|
|
|
|
addPopup(popupId, 1, <ColumnRemove id={popupId} />)
|
|
},
|
|
// component: <ColumnRemove id={popupId} />,
|
|
},
|
|
{
|
|
id: 'columnInsert',
|
|
name: getMessage('contextmenu.column.insert'),
|
|
fn: () => {
|
|
if (isExistCircuit()) {
|
|
resetModule()
|
|
}
|
|
|
|
addPopup(popupId, 1, <ColumnInsert id={popupId} />)
|
|
},
|
|
// component: <ColumnInsert id={popupId} />,
|
|
},
|
|
],
|
|
[
|
|
{
|
|
id: 'rowMove',
|
|
name: getMessage('contextmenu.row.move'),
|
|
fn: () => {
|
|
if (isAllComplete()) {
|
|
swalFire({
|
|
title: getMessage('can.not.move.module'),
|
|
icon: 'error',
|
|
type: 'alert',
|
|
})
|
|
return
|
|
}
|
|
resetModule()
|
|
addPopup(popupId, 1, <PanelEdit id={popupId} type={PANEL_EDIT_TYPE.ROW_MOVE} />)
|
|
},
|
|
// component: <PanelEdit id={popupId} type={PANEL_EDIT_TYPE.ROW_MOVE} />,
|
|
},
|
|
{
|
|
id: 'rowCopy',
|
|
name: getMessage('contextmenu.row.copy'),
|
|
fn: () => {
|
|
if (isAllComplete()) {
|
|
swalFire({
|
|
title: getMessage('can.not.copy.module'),
|
|
icon: 'error',
|
|
type: 'alert',
|
|
})
|
|
return
|
|
}
|
|
resetModule()
|
|
addPopup(popupId, 1, <PanelEdit id={popupId} type={PANEL_EDIT_TYPE.ROW_COPY} />)
|
|
},
|
|
// component: <PanelEdit id={popupId} type={PANEL_EDIT_TYPE.ROW_COPY} />,
|
|
},
|
|
{
|
|
id: 'rowRemove',
|
|
name: getMessage('contextmenu.row.remove'),
|
|
fn: () => {
|
|
if (isExistCircuit()) {
|
|
resetModule()
|
|
}
|
|
|
|
addPopup(popupId, 1, <RowRemove id={popupId} />)
|
|
},
|
|
// component: <RowRemove id={popupId} />,
|
|
},
|
|
{
|
|
id: 'rowInsert',
|
|
name: getMessage('contextmenu.row.insert'),
|
|
fn: () => {
|
|
if (isExistCircuit()) {
|
|
resetModule()
|
|
}
|
|
|
|
addPopup(popupId, 1, <RowInsert id={popupId} />)
|
|
},
|
|
// component: <RowInsert id={popupId} />,
|
|
},
|
|
],
|
|
])
|
|
break
|
|
case 'moduleSetupSurface':
|
|
setContextMenu([
|
|
[
|
|
{
|
|
id: 'moduleVerticalCenterAlign',
|
|
name: getMessage('contextmenu.module.vertical.align'),
|
|
fn: () => {
|
|
if (isAllComplete()) {
|
|
swalFire({
|
|
title: getMessage('can.not.move.module'),
|
|
icon: 'error',
|
|
type: 'alert',
|
|
})
|
|
return
|
|
}
|
|
|
|
saveSnapshot()
|
|
resetModule()
|
|
alignModule(MODULE_ALIGN_TYPE.VERTICAL, currentObject.arrayData ?? [currentObject])
|
|
},
|
|
},
|
|
{
|
|
id: 'moduleHorizonCenterAlign',
|
|
name: getMessage('contextmenu.module.horizon.align'),
|
|
fn: () => {
|
|
if (isAllComplete()) {
|
|
swalFire({
|
|
title: getMessage('can.not.move.module'),
|
|
icon: 'error',
|
|
type: 'alert',
|
|
})
|
|
return
|
|
}
|
|
|
|
saveSnapshot()
|
|
resetModule()
|
|
alignModule(MODULE_ALIGN_TYPE.HORIZONTAL, currentObject.arrayData ?? [currentObject])
|
|
},
|
|
},
|
|
{
|
|
id: 'moduleRemove',
|
|
name: getMessage('contextmenu.module.remove'),
|
|
fn: () => {
|
|
saveSnapshot()
|
|
moduleRoofRemove(currentObject.arrayData ?? [currentObject])
|
|
|
|
// const moduleSetupSurface = canvas.getObjects().filter((obj) => canvas.getActiveObjects()[0].id === obj.id)[0]
|
|
// const modules = canvas.getObjects().filter((obj) => obj.surfaceId === moduleSetupSurface.id && obj.name === POLYGON_TYPE.MODULE)
|
|
// canvas.remove(...modules)
|
|
// canvas.renderAll()
|
|
},
|
|
},
|
|
{
|
|
id: 'moduleMove',
|
|
name: getMessage('contextmenu.module.move'),
|
|
fn: () => {
|
|
if (isAllComplete()) {
|
|
swalFire({
|
|
title: getMessage('can.not.move.module'),
|
|
icon: 'error',
|
|
type: 'alert',
|
|
})
|
|
return
|
|
}
|
|
resetModule()
|
|
addPopup(
|
|
popupId,
|
|
1,
|
|
<PanelEdit id={popupId} type={PANEL_EDIT_TYPE.MOVE_ALL} arrayData={currentObject.arrayData ?? [currentObject]} />,
|
|
)
|
|
},
|
|
|
|
// component: <PanelEdit id={popupId} type={PANEL_EDIT_TYPE.MOVE_ALL} arrayData={currentObject.arrayData ?? [currentObject]} />,
|
|
},
|
|
{
|
|
id: 'moduleCopy',
|
|
name: getMessage('contextmenu.module.copy'),
|
|
fn: () => {
|
|
if (isAllComplete()) {
|
|
swalFire({
|
|
title: getMessage('can.not.move.module'),
|
|
icon: 'error',
|
|
type: 'alert',
|
|
})
|
|
return
|
|
}
|
|
resetModule()
|
|
addPopup(
|
|
popupId,
|
|
1,
|
|
<PanelEdit id={popupId} type={PANEL_EDIT_TYPE.COPY_ALL} arrayData={currentObject.arrayData ?? [currentObject]} />,
|
|
)
|
|
},
|
|
// component: <PanelEdit id={popupId} type={PANEL_EDIT_TYPE.COPY_ALL} arrayData={currentObject.arrayData ?? [currentObject]} />,
|
|
},
|
|
// {
|
|
// id: 'moduleCircuitNumberEdit',
|
|
// name: getMessage('contextmenu.module.circuit.number.edit'),
|
|
// component: <CircuitNumberEdit id={popupId} />,
|
|
// },
|
|
],
|
|
])
|
|
break
|
|
default:
|
|
currentMenuSetting()
|
|
}
|
|
} else {
|
|
currentMenuSetting()
|
|
}
|
|
}, [currentObject])
|
|
|
|
return {
|
|
contextMenu,
|
|
currentContextMenu,
|
|
setCurrentContextMenu,
|
|
handleClick,
|
|
handleKeyup,
|
|
triggerDelete, // [TMP-DEL 2026-05-07]
|
|
}
|
|
}
|