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' 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 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) } 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', shortcut: ['d', 'D'], name: `${getMessage('contextmenu.remove')}(D)`, 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: , }, ], ]) } break case 'roof': case 'auxiliaryLine': case 'hip': case 'ridge': case 'eaveHelpLine': if (selectedMenu === 'surface') { setContextMenu([ [ { id: 'sizeEdit', name: getMessage('contextmenu.size.edit'), component: , }, { id: 'rotate', name: `${getMessage('contextmenu.rotate')}`, fn: () => rotateSurfaceShapeBatch(), }, { id: 'roofMaterialRemove', shortcut: ['d', 'D'], name: `${getMessage('contextmenu.remove')}(D)`, 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: , }, { 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, ) } }, // component: , }, { id: 'flowDirectionEdit', name: getMessage('contextmenu.flow.direction.edit'), component: , }, ], ]) } else if (selectedMenu === 'outline') { setContextMenu([ [ { id: 'roofMaterialPlacement', name: getMessage('contextmenu.roof.material.placement'), component: , }, { id: 'roofMaterialRemoveAll', name: getMessage('contextmenu.roof.material.remove.all'), fn: () => removeAllRoofMaterial(), }, { id: 'selectMove', name: getMessage('contextmenu.select.move'), fn: (currentMousePos) => { moveRoofMaterial(currentMousePos) }, }, { id: 'wallLineRemove', name: getMessage('contextmenu.wallline.remove'), fn: (currentMousePos) => { removeOuterLines(currentMousePos) }, }, ], [ { id: 'sizeEdit', name: getMessage('contextmenu.size.edit'), component: , }, { id: 'auxiliaryMove', name: `${getMessage('contextmenu.auxiliary.move')}(M)`, shortcut: ['m', 'M'], component: , }, { id: 'auxiliaryCopy', name: `${getMessage('contextmenu.auxiliary.copy')}(C)`, shortcut: ['c', 'C'], component: , }, { id: 'auxiliaryRemove', shortcut: ['d', 'D'], name: `${getMessage('contextmenu.auxiliary.remove')}(D)`, fn: () => { if (!currentObject) return const roof = canvas.getObjects().filter((obj) => obj.id === currentObject.attributes.roofId)[0] if (!roof) { // 아직 innerLines로 세팅이 안되어있는 line인 경우 제거 canvas.remove(currentObject) canvas.discardActiveObject() return } const innerLines = roof.innerLines?.filter((line) => currentObject.id !== line.id) roof.innerLines = [...innerLines] canvas.remove(currentObject) canvas.discardActiveObject() }, }, { id: 'auxiliaryVerticalBisector', name: getMessage('contextmenu.auxiliary.vertical.bisector'), fn: () => { if (!currentObject) return 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 } const innerLines = canvas.getObjects().filter((obj) => obj.id === currentObject.attributes.roofId)[0]?.innerLines if (innerLines) { innerLines.forEach((line) => { canvas.remove(line) }) innerLines.length = 0 } // 확정되지 않은 보조선 const notFixedAuxiliaryLines = canvas.getObjects().filter((obj) => obj.name === 'auxiliaryLine' && !obj.isAuxiliaryFixed) notFixedAuxiliaryLines.forEach((line) => { canvas.remove(line) }) canvas.renderAll() }, }, ], ]) } break case 'opening': setContextMenu([ [ { id: 'sizeEdit', name: getMessage('contextmenu.size.edit'), component: , }, { id: 'openingRemove', shortcut: ['d', 'D'], name: `${getMessage('contextmenu.remove')}(D)`, 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: , }, ], ]) 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: ( { 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: , }, { id: 'gridCopy', name: getMessage('modal.grid.copy'), component: , }, { id: 'gridColorEdit', name: getMessage('contextmenu.grid.color.edit'), component: ( ), }, { 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: , }, ], ]) break case 'shadow': setContextMenu([ [ { id: 'sizeEdit', name: getMessage('contextmenu.size.edit'), component: , }, { id: 'remove', shortcut: ['d', 'D'], name: `${getMessage('contextmenu.remove')}(D)`, 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, ) }, // component: , }, { 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, ) }, }, ], [ { 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, ) }, }, { 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, ) }, // component: , }, { id: 'columnRemove', name: getMessage('contextmenu.column.remove'), fn: () => { if (isExistCircuit()) { resetModule() } addPopup(popupId, 1, ) }, // component: , }, { id: 'columnInsert', name: getMessage('contextmenu.column.insert'), fn: () => { if (isExistCircuit()) { resetModule() } addPopup(popupId, 1, ) }, // component: , }, ], [ { 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, ) }, // component: , }, { 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, ) }, // component: , }, { id: 'rowRemove', name: getMessage('contextmenu.row.remove'), fn: () => { if (isExistCircuit()) { resetModule() } addPopup(popupId, 1, ) }, // component: , }, { id: 'rowInsert', name: getMessage('contextmenu.row.insert'), fn: () => { if (isExistCircuit()) { resetModule() } addPopup(popupId, 1, ) }, // component: , }, ], ]) 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 } 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 } resetModule() alignModule(MODULE_ALIGN_TYPE.HORIZONTAL, currentObject.arrayData ?? [currentObject]) }, }, { id: 'moduleRemove', name: getMessage('contextmenu.module.remove'), fn: () => { 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, , ) }, // component: , }, { 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, , ) }, // component: , }, // { // id: 'moduleCircuitNumberEdit', // name: getMessage('contextmenu.module.circuit.number.edit'), // component: , // }, ], ]) break default: currentMenuSetting() } } else { currentMenuSetting() } }, [currentObject]) return { contextMenu, currentContextMenu, setCurrentContextMenu, handleClick, handleKeyup, } }