redo undo 작업중
This commit is contained in:
parent
d4766e30a3
commit
77e3f688c6
@ -9,6 +9,7 @@ import { useAuxiliaryDrawing } from '@/hooks/roofcover/useAuxiliaryDrawing'
|
||||
import { useSwal } from '@/hooks/useSwal'
|
||||
import { normalizeDigits } from '@/util/input-utils'
|
||||
import { CalculatorInput } from '@/components/common/input/CalcInput'
|
||||
import { useUndoRedo } from '@/hooks/useUndoRedo'
|
||||
|
||||
export default function AuxiliaryEdit(props) {
|
||||
const contextPopupPosition = useRecoilValue(contextPopupPositionState)
|
||||
@ -22,6 +23,7 @@ export default function AuxiliaryEdit(props) {
|
||||
const [arrow2, setArrow2] = useState(null)
|
||||
const currentObject = useRecoilValue(currentObjectState)
|
||||
const { swalFire } = useSwal()
|
||||
const { saveSnapshot } = useUndoRedo()
|
||||
const handleSave = () => {
|
||||
if ((!arrow1 && !arrow2) || (+verticalSize === 0 && +horizonSize === 0)) {
|
||||
swalFire({ title: getMessage('length.direction.is.required'), type: 'alert' })
|
||||
@ -38,6 +40,7 @@ export default function AuxiliaryEdit(props) {
|
||||
return
|
||||
}
|
||||
|
||||
saveSnapshot()
|
||||
if (type === 'copy') {
|
||||
if (currentObject) {
|
||||
copy(
|
||||
@ -87,14 +90,12 @@ export default function AuxiliaryEdit(props) {
|
||||
className={`direction up ${arrow1 === '↑' ? 'act' : ''}`}
|
||||
onClick={() => {
|
||||
setArrow1('↑')
|
||||
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp' }))
|
||||
}}
|
||||
></button>
|
||||
<button
|
||||
className={`direction down ${arrow1 === '↓' ? 'act' : ''}`}
|
||||
onClick={() => {
|
||||
setArrow1('↓')
|
||||
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }))
|
||||
}}
|
||||
></button>
|
||||
</div>
|
||||
@ -121,14 +122,12 @@ export default function AuxiliaryEdit(props) {
|
||||
className={`direction left ${arrow2 === '←' ? 'act' : ''}`}
|
||||
onClick={() => {
|
||||
setArrow2('←')
|
||||
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowLeft' }))
|
||||
}}
|
||||
></button>
|
||||
<button
|
||||
className={`direction right ${arrow2 === '→' ? 'act' : ''}`}
|
||||
onClick={() => {
|
||||
setArrow2('→')
|
||||
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight' }))
|
||||
}}
|
||||
></button>
|
||||
</div>
|
||||
|
||||
@ -8,6 +8,7 @@ import { useEffect, useState } from 'react'
|
||||
import Big from 'big.js'
|
||||
import { calcLineActualSize, calcLinePlaneSize } from '@/util/qpolygon-utils'
|
||||
import { normalizeDigits } from '@/util/input-utils'
|
||||
import { useUndoRedo } from '@/hooks/useUndoRedo'
|
||||
import { CalculatorInput } from '@/components/common/input/CalcInput'
|
||||
|
||||
export default function AuxiliarySize(props) {
|
||||
@ -53,7 +54,9 @@ export default function AuxiliarySize(props) {
|
||||
}
|
||||
}
|
||||
|
||||
const { saveSnapshot } = useUndoRedo()
|
||||
const handleSave = () => {
|
||||
saveSnapshot()
|
||||
const { x1, y1, x2, y2 } = currentObject
|
||||
|
||||
// 선택한 선분의 planeSize와 actualSize의 사잇각을 구한다.
|
||||
|
||||
@ -8,6 +8,7 @@ import { canvasState } from '@/store/canvasAtom'
|
||||
import { POLYGON_TYPE } from '@/common/common'
|
||||
import { useModule } from '@/hooks/module/useModule'
|
||||
import { useSwal } from '@/hooks/useSwal'
|
||||
import { useUndoRedo } from '@/hooks/useUndoRedo'
|
||||
|
||||
export const PANEL_EDIT_TYPE = {
|
||||
MOVE: 'move',
|
||||
@ -30,6 +31,7 @@ export default function PanelEdit(props) {
|
||||
const canvas = useRecoilValue(canvasState)
|
||||
const { swalFire } = useSwal()
|
||||
const { moduleMove, moduleCopy, moduleMultiMove, moduleMultiCopy, moduleMoveAll, moduleCopyAll } = useModule()
|
||||
const { saveSnapshot } = useUndoRedo()
|
||||
useEffect(() => {
|
||||
if (!canvas) {
|
||||
const isSetupModules = canvas.getObjects().filter((obj) => obj.name === 'module') // selectedObj에 없는 객체만 필터링
|
||||
@ -52,6 +54,7 @@ export default function PanelEdit(props) {
|
||||
|
||||
//모듈 이동 적용
|
||||
const handleApply = () => {
|
||||
saveSnapshot()
|
||||
if (length <= 0) {
|
||||
swalFire({
|
||||
title: getMessage('common.message.please.input.over', [1]),
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useUndoRedo } from '@/hooks/useUndoRedo'
|
||||
import WithDraggable from '@/components/common/draggable/WithDraggable'
|
||||
import { useRecoilValue } from 'recoil'
|
||||
import { contextPopupPositionState } from '@/store/popupAtom'
|
||||
@ -14,7 +15,9 @@ export default function ColumnInsert(props) {
|
||||
const [selectedType, setSelectedType] = useState(MODULE_INSERT_TYPE.LEFT)
|
||||
const { getMessage } = useMessage()
|
||||
const { moduleColumnInsert } = useModule()
|
||||
const { saveSnapshot } = useUndoRedo()
|
||||
const handleApply = () => {
|
||||
saveSnapshot()
|
||||
moduleColumnInsert(selectedType)
|
||||
closePopup(id)
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useUndoRedo } from '@/hooks/useUndoRedo'
|
||||
import WithDraggable from '@/components/common/draggable/WithDraggable'
|
||||
import { useRecoilValue } from 'recoil'
|
||||
import { contextPopupPositionState } from '@/store/popupAtom'
|
||||
@ -14,6 +15,7 @@ export default function ColumnRemove(props) {
|
||||
const [selectedType, setSelectedType] = useState(MODULE_REMOVE_TYPE.LEFT)
|
||||
const { getMessage } = useMessage()
|
||||
const { moduleColumnRemove } = useModule()
|
||||
const { saveSnapshot } = useUndoRedo()
|
||||
const types = [
|
||||
{ name: getMessage('modal.panel.column.remove.type.left'), value: MODULE_REMOVE_TYPE.LEFT },
|
||||
{ name: getMessage('modal.panel.column.remove.type.right'), value: MODULE_REMOVE_TYPE.RIGHT },
|
||||
@ -21,6 +23,7 @@ export default function ColumnRemove(props) {
|
||||
{ name: getMessage('modal.panel.column.remove.type.none'), value: MODULE_REMOVE_TYPE.NONE },
|
||||
]
|
||||
const handleApply = () => {
|
||||
saveSnapshot()
|
||||
moduleColumnRemove(selectedType)
|
||||
closePopup(id)
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useUndoRedo } from '@/hooks/useUndoRedo'
|
||||
import WithDraggable from '@/components/common/draggable/WithDraggable'
|
||||
import Image from 'next/image'
|
||||
import { useState } from 'react'
|
||||
@ -14,7 +15,9 @@ export default function RowInsert(props) {
|
||||
const [selectedType, setSelectedType] = useState(MODULE_INSERT_TYPE.TOP)
|
||||
const { getMessage } = useMessage()
|
||||
const { muduleRowInsert } = useModule()
|
||||
const { saveSnapshot } = useUndoRedo()
|
||||
const handleApply = () => {
|
||||
saveSnapshot()
|
||||
muduleRowInsert(selectedType)
|
||||
closePopup(id)
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useUndoRedo } from '@/hooks/useUndoRedo'
|
||||
import WithDraggable from '@/components/common/draggable/WithDraggable'
|
||||
import Image from 'next/image'
|
||||
import { useState } from 'react'
|
||||
@ -14,6 +15,7 @@ export default function RowRemove(props) {
|
||||
const [selectedType, setSelectedType] = useState(MODULE_REMOVE_TYPE.TOP)
|
||||
const { getMessage } = useMessage()
|
||||
const { moduleRowRemove } = useModule()
|
||||
const { saveSnapshot } = useUndoRedo()
|
||||
const types = [
|
||||
{ name: getMessage('modal.row.remove.type.up'), value: MODULE_REMOVE_TYPE.TOP },
|
||||
{ name: getMessage('modal.row.remove.type.down'), value: MODULE_REMOVE_TYPE.BOTTOM },
|
||||
@ -21,6 +23,7 @@ export default function RowRemove(props) {
|
||||
{ name: getMessage('modal.row.remove.type.none'), value: MODULE_REMOVE_TYPE.NONE },
|
||||
]
|
||||
const handleApply = () => {
|
||||
saveSnapshot()
|
||||
// if (apply) apply()
|
||||
moduleRowRemove(selectedType)
|
||||
closePopup(id)
|
||||
|
||||
@ -21,6 +21,7 @@ import { MENU } from '@/common/common'
|
||||
import { useTrestle } from '@/hooks/module/useTrestle'
|
||||
import { useOrientation } from '@/hooks/module/useOrientation'
|
||||
import { corridorDimensionSelector } from '@/store/settingAtom'
|
||||
import { useUndoRedo } from '@/hooks/useUndoRedo'
|
||||
|
||||
/**
|
||||
* 메뉴 처리 훅
|
||||
@ -36,6 +37,7 @@ export default function useMenu() {
|
||||
const { clear: trestleClear, setAllModuleSurfaceIsComplete } = useTrestle()
|
||||
const { nextStep } = useOrientation()
|
||||
const [corridorDimension, setCorridorDimension] = useRecoilState(corridorDimensionSelector)
|
||||
const { saveSnapshot } = useUndoRedo()
|
||||
const handleMenu = (type) => {
|
||||
closeAll()
|
||||
if (type === 'outline') {
|
||||
|
||||
@ -8,6 +8,7 @@ import { useLine } from '@/hooks/useLine'
|
||||
import { outerLinePointsState } from '@/store/outerLineAtom'
|
||||
import { usePolygon } from '@/hooks/usePolygon'
|
||||
import { useText } from '@/hooks/useText'
|
||||
import { useUndoRedo } from '@/hooks/useUndoRedo'
|
||||
|
||||
const ROOF_COLOR = {
|
||||
0: 'rgb(199,240,213)',
|
||||
@ -26,6 +27,7 @@ export function useRoofFn() {
|
||||
const { setPolygonLinesActualSize } = usePolygon()
|
||||
const { changeCorridorDimensionText } = useText()
|
||||
const [outerLinePoints, setOuterLinePoints] = useRecoilState(outerLinePointsState)
|
||||
const { saveSnapshot } = useUndoRedo()
|
||||
|
||||
//면형상 선택 클릭시 지붕 패턴 입히기
|
||||
function setSurfaceShapePattern(polygon, mode = 'onlyBorder', trestleMode = false, roofMaterial, isForceChange = false, isDisplay = false) {
|
||||
@ -236,6 +238,9 @@ export function useRoofFn() {
|
||||
})
|
||||
|
||||
canvas.on('mouse:down', (event) => {
|
||||
// checkPolygon 을 캔버스에서 제거한 뒤 스냅샷을 찍어야 빨간 사각형이 포함되지 않음
|
||||
canvas.remove(checkPolygon)
|
||||
saveSnapshot()
|
||||
let mousePos = canvas.getPointer(event.e)
|
||||
mousePos = { x: Math.round(mousePos.x), y: Math.round(mousePos.y) }
|
||||
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { ANGLE_TYPE, canvasState, currentAngleTypeSelector, currentObjectState, pitchTextSelector } from '@/store/canvasAtom'
|
||||
import { useRecoilValue } from 'recoil'
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil'
|
||||
import { currentMenuState } from '@/store/canvasAtom'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useLine } from '@/hooks/useLine'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
import { useEvent } from '@/hooks/useEvent'
|
||||
import { LINE_TYPE, POLYGON_TYPE } from '@/common/common'
|
||||
import { LINE_TYPE, MENU, POLYGON_TYPE } from '@/common/common'
|
||||
import { useMode } from '@/hooks/useMode'
|
||||
import { usePolygon } from '@/hooks/usePolygon'
|
||||
import { useSwal } from '@/hooks/useSwal'
|
||||
@ -20,6 +21,7 @@ export function useRoofShapePassivitySetting(id) {
|
||||
SHED: 'shed',
|
||||
}
|
||||
const canvas = useRecoilValue(canvasState)
|
||||
const setCurrentMenu = useSetRecoilState(currentMenuState)
|
||||
const currentAngleType = useRecoilValue(currentAngleTypeSelector)
|
||||
const pitchText = useRecoilValue(pitchTextSelector)
|
||||
const { getMessage } = useMessage()
|
||||
@ -194,10 +196,11 @@ export function useRoofShapePassivitySetting(id) {
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
saveSnapshot()
|
||||
saveSnapshot(null, { force: true })
|
||||
isFix.current = true
|
||||
handleLineToPolygon()
|
||||
|
||||
// 수동설정 완료 → 지붕면 할당 메뉴로 전환 (undo 가드 해제)
|
||||
setCurrentMenu(MENU.ROOF_COVERING.ROOF_SHAPE_ALLOC)
|
||||
closePopup(id)
|
||||
}
|
||||
|
||||
|
||||
@ -7,7 +7,9 @@ import { useLine } from '@/hooks/useLine'
|
||||
import { useSwal } from '@/hooks/useSwal'
|
||||
import { usePopup } from '@/hooks/usePopup'
|
||||
import Big from 'big.js'
|
||||
import { calcLinePlaneSize } from '@/util/qpolygon-utils'
|
||||
import { outerLineFixState } from '@/store/outerLineAtom'
|
||||
import { useUndoRedo } from '@/hooks/useUndoRedo'
|
||||
|
||||
// 외벽선 편집 및 오프셋
|
||||
export function useWallLineOffsetSetting(id) {
|
||||
@ -17,7 +19,7 @@ export function useWallLineOffsetSetting(id) {
|
||||
const { closePopup } = usePopup()
|
||||
const { swalFire } = useSwal()
|
||||
const { addCanvasMouseEventListener, initEvent } = useEvent()
|
||||
// const { addCanvasMouseEventListener, initEvent } = useContext(EventContext)
|
||||
const { saveSnapshot } = useUndoRedo()
|
||||
const wallLineEditRef = useRef(null)
|
||||
const length1Ref = useRef(null)
|
||||
const length2Ref = useRef(null)
|
||||
@ -46,7 +48,13 @@ export function useWallLineOffsetSetting(id) {
|
||||
direction: direction,
|
||||
})
|
||||
|
||||
line.attributes = { ...currentWallLineRef.current.attributes }
|
||||
// 분할된 라인의 실제 길이로 planeSize/actualSize 재계산
|
||||
const newPlaneSize = calcLinePlaneSize({ x1: point1.x, y1: point1.y, x2: point2.x, y2: point2.y })
|
||||
line.attributes = {
|
||||
...currentWallLineRef.current.attributes,
|
||||
planeSize: newPlaneSize,
|
||||
actualSize: newPlaneSize,
|
||||
}
|
||||
}
|
||||
|
||||
const TYPES = {
|
||||
@ -197,9 +205,9 @@ export function useWallLineOffsetSetting(id) {
|
||||
|
||||
const handleSave = () => {
|
||||
if (currentWallLineRef.current === null) {
|
||||
// alert('보조선을 먼저 선택하세요')
|
||||
return
|
||||
}
|
||||
saveSnapshot()
|
||||
switch (type) {
|
||||
case TYPES.WALL_LINE_EDIT:
|
||||
handleWallLineEditSave()
|
||||
|
||||
@ -27,6 +27,7 @@ import { useSurfaceShapeBatch } from './useSurfaceShapeBatch'
|
||||
import { roofDisplaySelector } from '@/store/settingAtom'
|
||||
import { useRoofFn } from '@/hooks/common/useRoofFn'
|
||||
import { useObject } from '@/hooks/useObject'
|
||||
import { useUndoRedo } from '@/hooks/useUndoRedo'
|
||||
|
||||
// 배치면 그리기
|
||||
export function usePlacementShapeDrawing(id) {
|
||||
@ -48,6 +49,7 @@ export function usePlacementShapeDrawing(id) {
|
||||
const { setSurfaceShapePattern } = useRoofFn()
|
||||
const { changeSurfaceLineType } = useSurfaceShapeBatch({})
|
||||
const { handleSelectableObjects } = useObject()
|
||||
const { saveSnapshot, popLastSnapshot } = useUndoRedo()
|
||||
|
||||
const verticalHorizontalMode = useRecoilValue(verticalHorizontalModeState)
|
||||
const adsorptionRange = useRecoilValue(adsorptionRangeState)
|
||||
@ -92,13 +94,19 @@ export function usePlacementShapeDrawing(id) {
|
||||
}, [verticalHorizontalMode, points, adsorptionRange, adsorptionPointMode])
|
||||
|
||||
useEffect(() => {
|
||||
// 배치면 그리기 시작 시 스냅샷 저장 (undo 하면 그리기 전 상태로 복원)
|
||||
saveSnapshot()
|
||||
setPoints([])
|
||||
setType(OUTER_LINE_TYPE.OUTER_LINE)
|
||||
|
||||
return () => {
|
||||
const placementShapeDrawingStartPoint = canvas.getObjects().find((obj) => obj.name === 'placementShapeDrawingStartPoint')
|
||||
|
||||
canvas.remove(placementShapeDrawingStartPoint)
|
||||
|
||||
// 확정하지 않고 닫은 경우 스냅샷 제거
|
||||
if (!isFix.current) {
|
||||
popLastSnapshot()
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ import { usePopup } from '../usePopup'
|
||||
import useSWR from 'swr'
|
||||
import { useSwal } from '../useSwal'
|
||||
import { useMessage } from '../useMessage'
|
||||
import { useUndoRedo } from '../useUndoRedo'
|
||||
|
||||
const LINE_COLOR = {
|
||||
EAVES: '#45CD7D',
|
||||
@ -23,6 +24,7 @@ export function useRoofLinePropertySetting(props) {
|
||||
const { closePopup } = usePopup()
|
||||
const { swalFire } = useSwal()
|
||||
const { getMessage } = useMessage()
|
||||
const { saveSnapshot, popLastSnapshot } = useUndoRedo()
|
||||
|
||||
useEffect(() => {
|
||||
if (currentObject && currentObject.name === 'cloneRoofLine') {
|
||||
@ -47,10 +49,20 @@ export function useRoofLinePropertySetting(props) {
|
||||
}
|
||||
}, [currentObject])
|
||||
|
||||
const isFixed = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
// 팝업 열릴 때 스냅샷 저장
|
||||
saveSnapshot()
|
||||
|
||||
return () => {
|
||||
canvas.remove(...canvas.getObjects().filter((obj) => obj.name === 'cloneRoofLine'))
|
||||
canvas.renderAll()
|
||||
|
||||
// 설정 완료하지 않고 닫은 경우 스냅샷 제거
|
||||
if (!isFixed.current) {
|
||||
popLastSnapshot()
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
@ -149,8 +161,6 @@ export function useRoofLinePropertySetting(props) {
|
||||
}
|
||||
|
||||
const handleFix = () => {
|
||||
// const roof = canvas.getObjects().find((obj) => currentObject.parentId === obj.id)
|
||||
// const notSettingLines = roof.lines.filter(
|
||||
const notSettingLines = canvas
|
||||
.getObjects()
|
||||
.filter((obj) => obj.name === 'cloneRoofLine')
|
||||
@ -187,6 +197,7 @@ export function useRoofLinePropertySetting(props) {
|
||||
// })
|
||||
// })
|
||||
|
||||
isFixed.current = true
|
||||
canvas.renderAll()
|
||||
closePopup(id)
|
||||
if (setIsHidden) setIsHidden(false)
|
||||
|
||||
@ -52,6 +52,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
|
||||
const { saveSnapshot } = useUndoRedo()
|
||||
|
||||
const applySurfaceShape = (surfaceRefs, selectedType, id) => {
|
||||
saveSnapshot()
|
||||
let length1, length2, length3, length4, length5
|
||||
const surfaceId = selectedType?.id
|
||||
const azimuth = surfaceRefs.azimuth.current
|
||||
@ -844,6 +845,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
|
||||
text: getMessage('batch.canvas.delete.all'),
|
||||
type: 'confirm',
|
||||
confirmFn: () => {
|
||||
saveSnapshot()
|
||||
canvas.clear()
|
||||
delete canvas.outerLineFix
|
||||
|
||||
|
||||
@ -155,6 +155,7 @@ export function useCanvas(id) {
|
||||
|
||||
// 해당 오브젝트 타입의 경우 저장한 값 그대로 불러와야함
|
||||
OBJECT_PROTOTYPE.forEach((type) => {
|
||||
const originalToObject = type.toObject
|
||||
type.toObject = function (propertiesToInclude) {
|
||||
let source = {}
|
||||
|
||||
@ -175,7 +176,9 @@ export function useCanvas(id) {
|
||||
}
|
||||
}
|
||||
|
||||
return fabric.util.object.extend(this.callSuper('toObject', propertiesToInclude), source)
|
||||
// 원본 toObject 를 호출하여 Group 의 objects 등 고유 직렬화 로직을 유지
|
||||
const base = originalToObject ? originalToObject.call(this, propertiesToInclude) : this.callSuper('toObject', propertiesToInclude)
|
||||
return fabric.util.object.extend(base, source)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ export function useCanvasEvent() {
|
||||
const currentMenu = useRecoilValue(currentMenuState)
|
||||
const { changeCorridorDimensionText } = useText()
|
||||
const { setPolygonLinesActualSize } = usePolygon()
|
||||
const { undo, redo } = useUndoRedo()
|
||||
const { undo, redo, saveSnapshot } = useUndoRedo()
|
||||
|
||||
useEffect(() => {
|
||||
canvas?.setZoom(canvasZoom / 100)
|
||||
@ -291,22 +291,18 @@ export function useCanvasEvent() {
|
||||
break
|
||||
case 'Down': // IE/Edge에서 사용되는 값
|
||||
case 'ArrowDown':
|
||||
// "아래 화살표" 키가 눌렸을 때의 동작입니다.
|
||||
moveDown()
|
||||
break
|
||||
case 'Up': // IE/Edge에서 사용되는 값
|
||||
case 'ArrowUp':
|
||||
// "위 화살표" 키가 눌렸을 때의 동작입니다.
|
||||
moveUp()
|
||||
break
|
||||
case 'Left': // IE/Edge에서 사용되는 값
|
||||
case 'ArrowLeft':
|
||||
// "왼쪽 화살표" 키가 눌렸을 때의 동작입니다.
|
||||
moveLeft()
|
||||
break
|
||||
case 'Right': // IE/Edge에서 사용되는 값
|
||||
case 'ArrowRight':
|
||||
// "오른쪽 화살표" 키가 눌렸을 때의 동작입니다.
|
||||
moveRight()
|
||||
break
|
||||
case 'Enter':
|
||||
|
||||
@ -43,6 +43,7 @@ import { useTrestle } from './module/useTrestle'
|
||||
import { useCircuitTrestle } from './useCirCuitTrestle'
|
||||
import { usePolygon } from '@/hooks/usePolygon'
|
||||
import { useText } from '@/hooks/useText'
|
||||
import { useUndoRedo } from '@/hooks/useUndoRedo'
|
||||
|
||||
export function useContextMenu() {
|
||||
const canvas = useRecoilValue(canvasState)
|
||||
@ -77,6 +78,7 @@ export function useContextMenu() {
|
||||
const { isExistCircuit } = useCircuitTrestle()
|
||||
const { changeCorridorDimensionText } = useText()
|
||||
const { setPolygonLinesActualSize, drawDirectionArrow } = usePolygon()
|
||||
const { saveSnapshot } = useUndoRedo()
|
||||
const currentMenuSetting = () => {
|
||||
switch (selectedMenu) {
|
||||
case 'outline':
|
||||
@ -170,7 +172,10 @@ export function useContextMenu() {
|
||||
{
|
||||
id: 'roofMaterialRemoveAll',
|
||||
name: getMessage('contextmenu.roof.material.remove.all'),
|
||||
fn: () => removeAllRoofMaterial(),
|
||||
fn: () => {
|
||||
saveSnapshot()
|
||||
removeAllRoofMaterial()
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'selectMove',
|
||||
@ -183,6 +188,7 @@ export function useContextMenu() {
|
||||
id: 'wallLineRemove',
|
||||
name: getMessage('contextmenu.wallline.remove'),
|
||||
fn: (currentMousePos) => {
|
||||
saveSnapshot()
|
||||
removeOuterLines(currentMousePos)
|
||||
},
|
||||
},
|
||||
@ -260,7 +266,10 @@ export function useContextMenu() {
|
||||
{
|
||||
id: 'roofMaterialRemoveAll',
|
||||
name: getMessage('contextmenu.roof.material.remove.all'),
|
||||
fn: () => removeAllRoofMaterial(),
|
||||
fn: () => {
|
||||
saveSnapshot()
|
||||
removeAllRoofMaterial()
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'selectMove',
|
||||
@ -368,6 +377,7 @@ export function useContextMenu() {
|
||||
name: `${getMessage('contextmenu.auxiliary.remove')}(D)`,
|
||||
fn: () => {
|
||||
if (!currentObject) return
|
||||
saveSnapshot()
|
||||
const roof = canvas.getObjects().filter((obj) => obj.id === currentObject.attributes.roofId)[0]
|
||||
if (!roof) {
|
||||
// 아직 innerLines로 세팅이 안되어있는 line인 경우 제거
|
||||
@ -386,6 +396,7 @@ export function useContextMenu() {
|
||||
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
|
||||
|
||||
@ -435,6 +446,7 @@ export function useContextMenu() {
|
||||
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) => {
|
||||
@ -702,7 +714,10 @@ export function useContextMenu() {
|
||||
{
|
||||
id: 'remove',
|
||||
name: getMessage('contextmenu.remove'),
|
||||
fn: () => modulesRemove(),
|
||||
fn: () => {
|
||||
saveSnapshot()
|
||||
modulesRemove()
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'move',
|
||||
@ -900,6 +915,7 @@ export function useContextMenu() {
|
||||
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]
|
||||
|
||||
@ -16,7 +16,7 @@ import {
|
||||
import { outerLinePointsState } from '@/store/outerLineAtom'
|
||||
import { selectedMenuState } from '@/store/menuAtom'
|
||||
import { pendingPopupState, popupState } from '@/store/popupAtom'
|
||||
import { POLYGON_TYPE, SAVE_KEY } from '@/common/common'
|
||||
import { MENU, POLYGON_TYPE, SAVE_KEY } from '@/common/common'
|
||||
|
||||
const MAX_HISTORY = 3
|
||||
|
||||
@ -182,6 +182,20 @@ export function useUndoRedo() {
|
||||
surface.modules = surfaceModules
|
||||
})
|
||||
|
||||
// fabric.Group 내부의 QPolygon/QLine 자식에 canvas 참조 설정
|
||||
// loadFromJSON 후 Group 자식은 canvas 를 받지 못해 init/addLengthText 등이 실패할 수 있음
|
||||
objects
|
||||
.filter((obj) => obj.type === 'group')
|
||||
.forEach((group) => {
|
||||
if (group._objects) {
|
||||
group._objects.forEach((child) => {
|
||||
if (!child.canvas) {
|
||||
child.canvas = canvas
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 부모가 없는 고아 lengthText, arrow, flowText 제거
|
||||
const parentIds = new Set(objects.filter((obj) => obj.id).map((obj) => obj.id))
|
||||
const orphans = objects.filter(
|
||||
@ -258,6 +272,26 @@ export function useUndoRedo() {
|
||||
|
||||
const cells = roofs.filter((obj) => obj.cells && obj.cells.length > 0)
|
||||
setDrewRoofCells(cells)
|
||||
|
||||
// loadFromJSON 후 QPolygon/QLine 의 lengthText 가 orphan 제거되었을 수 있으므로 재생성
|
||||
// (initLines 로 새 QLine 이 만들어지면 기존 lengthText 의 parentId 가 안 맞음)
|
||||
const qPolygons = objects.filter((obj) => obj.type === 'QPolygon' && obj.name !== 'arrow' && obj.name !== POLYGON_TYPE.MODULE && obj.name !== POLYGON_TYPE.MODULE_SETUP_SURFACE && obj.name !== POLYGON_TYPE.OBJECT_SURFACE)
|
||||
qPolygons.forEach((polygon) => {
|
||||
try {
|
||||
polygon.initLines()
|
||||
polygon.addLengthText()
|
||||
} catch (e) { /* skip */ }
|
||||
})
|
||||
|
||||
canvas.getObjects()
|
||||
.filter((obj) => obj.type === 'QLine')
|
||||
.forEach((line) => {
|
||||
try {
|
||||
if (typeof line.addLengthText === 'function') {
|
||||
line.addLengthText()
|
||||
}
|
||||
} catch (e) { /* skip */ }
|
||||
})
|
||||
}, [canvas, rebuildObjectGraph])
|
||||
|
||||
const restoreAtomSnapshot = useCallback((atomSnapshot) => {
|
||||
@ -277,7 +311,6 @@ export function useUndoRedo() {
|
||||
if (!canvas) return null
|
||||
try {
|
||||
const rawJSON = canvas.toJSON(SAVE_KEY)
|
||||
// 순환 참조 제거 후 다시 파싱하여 순수 데이터 객체로 변환
|
||||
return JSON.parse(safeStringify(rawJSON))
|
||||
} catch (e) {
|
||||
console.warn('Canvas snapshot failed:', e)
|
||||
@ -285,9 +318,12 @@ export function useUndoRedo() {
|
||||
}
|
||||
}, [canvas])
|
||||
|
||||
const saveSnapshot = useCallback((atomOverrides = null) => {
|
||||
const saveSnapshot = useCallback((atomOverrides = null, { force = false } = {}) => {
|
||||
if (!canvas || _isRestoring) return
|
||||
|
||||
// 지붕형상 수동설정 메뉴에서는 스냅샷을 쌓지 않는다 (force 시 예외)
|
||||
if (!force && currentMenu === MENU.ROOF_COVERING.ROOF_SHAPE_PASSIVITY_SETTINGS) return
|
||||
|
||||
// snapshot 전에 mouseLine 제거
|
||||
const mouseLines = canvas.getObjects().filter((obj) => obj.name === 'mouseLine')
|
||||
mouseLines.forEach((obj) => canvas.remove(obj))
|
||||
@ -315,21 +351,28 @@ export function useUndoRedo() {
|
||||
return newStack
|
||||
})
|
||||
setRedoStack([])
|
||||
}, [canvas, getCanvasSnapshot, getAtomSnapshot])
|
||||
}, [canvas, currentMenu, getCanvasSnapshot, getAtomSnapshot])
|
||||
|
||||
const closeAllPopups = useCallback(() => {
|
||||
setPopup({ config: [], other: [] })
|
||||
setPopup((prev) => ({
|
||||
// 처마 케라바 변경 팝업(EavesGableEdit)은 undo/redo 시 닫지 않는다
|
||||
config: prev.config.filter((p) => p.component?.type?.name === 'EavesGableEdit'),
|
||||
other: prev.other.filter((p) => p.component?.type?.name === 'EavesGableEdit'),
|
||||
}))
|
||||
}, [setPopup])
|
||||
|
||||
const undo = useCallback(() => {
|
||||
// 지붕형상 수동설정 중에는 undo 차단
|
||||
if (currentMenu === MENU.ROOF_COVERING.ROOF_SHAPE_PASSIVITY_SETTINGS) return
|
||||
|
||||
const currentUndoStack = undoStackRef.current
|
||||
console.log('currentUndoStack', currentUndoStack)
|
||||
if (!canvas || currentUndoStack.length === 0 || _isRestoring) return
|
||||
|
||||
_isRestoring = true
|
||||
|
||||
const currentCanvasJSON = getCanvasSnapshot()
|
||||
const currentAtomSnapshot = getAtomSnapshot()
|
||||
|
||||
const currentSnapshot = {
|
||||
canvasJSON: currentCanvasJSON,
|
||||
atomSnapshot: currentAtomSnapshot,
|
||||
@ -348,6 +391,9 @@ export function useUndoRedo() {
|
||||
}, [canvas, currentMenu, getCanvasSnapshot, getAtomSnapshot, restoreAtomSnapshot, rebuildCanvasAtoms, closeAllPopups])
|
||||
|
||||
const redo = useCallback(() => {
|
||||
// 지붕형상 수동설정 중에는 redo 차단
|
||||
if (currentMenu === MENU.ROOF_COVERING.ROOF_SHAPE_PASSIVITY_SETTINGS) return
|
||||
|
||||
const currentRedoStack = redoStackRef.current
|
||||
if (!canvas || currentRedoStack.length === 0 || _isRestoring) return
|
||||
|
||||
@ -377,11 +423,17 @@ export function useUndoRedo() {
|
||||
setRedoStack([])
|
||||
}, [])
|
||||
|
||||
/** undo 스택에서 마지막 스냅샷 1개만 제거 (취소 시 사용) */
|
||||
const popLastSnapshot = useCallback(() => {
|
||||
setUndoStack((prev) => (prev.length > 0 ? prev.slice(0, -1) : prev))
|
||||
}, [])
|
||||
|
||||
return {
|
||||
saveSnapshot,
|
||||
undo,
|
||||
redo,
|
||||
clearStacks,
|
||||
popLastSnapshot,
|
||||
canUndo: undoStack.length > 0,
|
||||
canRedo: redoStack.length > 0,
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user