undo redo 시 메뉴 변경

This commit is contained in:
hyojun.choi 2026-04-13 11:10:38 +09:00
parent 53226d00fd
commit d4766e30a3
4 changed files with 47 additions and 11 deletions

View File

@ -6,9 +6,11 @@ import {
adsorptionPointModeState,
adsorptionRangeState,
canvasState,
currentMenuState,
dotLineIntervalSelector,
verticalHorizontalModeState,
} from '@/store/canvasAtom'
import { MENU } from '@/common/common'
import { useEvent } from '@/hooks/useEvent'
import { useMouse } from '@/hooks/useMouse'
import { useLine } from '@/hooks/useLine'
@ -88,6 +90,7 @@ export function useOuterLineWall(id, propertiesId) {
const { addPopup, closePopup } = usePopup()
const setOuterLineFix = useSetRecoilState(outerLineFixState)
const setCurrentMenu = useSetRecoilState(currentMenuState)
const outerLineDiagonalLengthRef = useRef(null)
@ -257,6 +260,8 @@ export function useOuterLineWall(id, propertiesId) {
canvas.outerLineFix = true
closePopup(id)
ccwCheck()
// 외벽선 완료 → 지붕형상 설정 메뉴로 전환
setCurrentMenu(MENU.ROOF_COVERING.ROOF_SHAPE_SETTINGS)
addPopup(propertiesId, 1, <RoofShapeSetting id={propertiesId} pos={{ x: 50, y: 230 }} />)
}

View File

@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react'
import { useMessage } from '@/hooks/useMessage'
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
import { ANGLE_TYPE, canvasState, currentAngleTypeSelector, currentMenuState, currentObjectState, pitchTextSelector } from '@/store/canvasAtom'
import { LINE_TYPE, POLYGON_TYPE } from '@/common/common'
import { LINE_TYPE, MENU, POLYGON_TYPE } from '@/common/common'
import { usePolygon } from '@/hooks/usePolygon'
import { useMode } from '@/hooks/useMode'
import { useLine } from '@/hooks/useLine'
@ -21,6 +21,8 @@ import {
import { useAxios } from '@/hooks/useAxios'
import { globalLocaleStore } from '@/store/localeAtom'
import { useUndoRedo } from '@/hooks/useUndoRedo'
import { pendingPopupState } from '@/store/popupAtom'
import RoofShapeSetting from '@/components/floor-plan/modal/roofShape/RoofShapeSetting'
// 지붕형상 설정
export function useRoofShapeSetting(id) {
@ -71,6 +73,7 @@ export function useRoofShapeSetting(id) {
const { addLine, removeLine } = useLine()
const { saveSnapshot } = useUndoRedo()
const [pendingPopup, setPendingPopup] = useRecoilState(pendingPopupState)
useEffect(() => {
pitchRef.current = currentAngleType === ANGLE_TYPE.SLOPE ? pitch : getChonByDegree(pitch)
@ -82,6 +85,18 @@ export function useRoofShapeSetting(id) {
jerkinHeadPitchRef.current = currentAngleType === ANGLE_TYPE.SLOPE ? jerkinHeadPitch : getChonByDegree(jerkinHeadPitch)
}, [jerkinHeadPitch])
// undo/redo 로 pendingPopup 이 복원되면 해당 팝업을 다시 연다
useEffect(() => {
if (!pendingPopup) return
if (pendingPopup.type === 'RoofShapeSetting') {
addPopup(pendingPopup.id, 1, <RoofShapeSetting id={pendingPopup.id} pos={pendingPopup.pos} />)
setPendingPopup(null)
} else if (pendingPopup.type === 'RoofAllocation') {
addPopup(pendingPopup.id, 1, <RoofAllocationSetting id={pendingPopup.id} pos={pendingPopup.pos} />)
setPendingPopup(null)
}
}, [pendingPopup])
useEffect(() => {
const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
if (!canvas.outerLineFix || outerLines.length === 0) {
@ -184,7 +199,8 @@ export function useRoofShapeSetting(id) {
]
const handleSave = () => {
saveSnapshot()
// undo 시 지붕형상 설정 팝업이 다시 열리도록 pendingPopup 을 atomOverrides 로 직접 전달
saveSnapshot({ pendingPopup: { type: 'RoofShapeSetting', id, pos: { x: 50, y: 230 } } })
let outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
let direction
@ -485,6 +501,8 @@ export function useRoofShapeSetting(id) {
canvas?.renderAll()
roof.drawHelpLine(settingModalFirstOptions)
isFixRef.current = true
// 지붕형상 설정 완료 → 지붕면 할당 메뉴로 전환
setCurrentMenu(MENU.ROOF_COVERING.ROOF_SHAPE_ALLOC)
addPopup(id, 1, <RoofAllocationSetting id={id} pos={{ x: 50, y: 230 }} />)
}

View File

@ -15,7 +15,7 @@ import {
} from '@/store/canvasAtom'
import { outerLinePointsState } from '@/store/outerLineAtom'
import { selectedMenuState } from '@/store/menuAtom'
import { popupState } from '@/store/popupAtom'
import { pendingPopupState, popupState } from '@/store/popupAtom'
import { POLYGON_TYPE, SAVE_KEY } from '@/common/common'
const MAX_HISTORY = 3
@ -103,6 +103,7 @@ export function useUndoRedo() {
const [currentMenu, setCurrentMenu] = useRecoilState(currentMenuState)
const [, setCanvasZoom] = useRecoilState(canvasZoomState)
const setPopup = useSetRecoilState(popupState)
const [pendingPopup, setPendingPopup] = useRecoilState(pendingPopupState)
// 최신 스택 값을 ref로 유지하여 클로저 stale 문제 방지
const undoStackRef = useRef(undoStack)
@ -118,8 +119,9 @@ export function useUndoRedo() {
outerLinePoints: safeClone(outerLinePoints || []),
selectedMenu,
currentMenu,
pendingPopup: pendingPopup ? safeClone(pendingPopup) : null,
}
}, [moduleIsSetup, checkedModule, moduleRowColArray, outerLinePoints, selectedMenu, currentMenu])
}, [moduleIsSetup, checkedModule, moduleRowColArray, outerLinePoints, selectedMenu, currentMenu, pendingPopup])
// loadFromJSON 후 오브젝트 간 라이브 참조 관계를 재구축
const rebuildObjectGraph = useCallback(() => {
@ -266,6 +268,8 @@ export function useUndoRedo() {
setOuterLinePoints(atomSnapshot.outerLinePoints || [])
if (atomSnapshot.selectedMenu !== undefined) setSelectedMenu(atomSnapshot.selectedMenu)
if (atomSnapshot.currentMenu !== undefined) setCurrentMenu(atomSnapshot.currentMenu)
// undo/redo 시 복원해야 할 팝업 정보
setPendingPopup(atomSnapshot.pendingPopup ?? null)
}, [])
// canvas.toJSON(SAVE_KEY)의 결과를 순환 참조 없이 안전하게 직렬화
@ -281,7 +285,7 @@ export function useUndoRedo() {
}
}, [canvas])
const saveSnapshot = useCallback(() => {
const saveSnapshot = useCallback((atomOverrides = null) => {
if (!canvas || _isRestoring) return
// snapshot 전에 mouseLine 제거
@ -293,6 +297,11 @@ export function useUndoRedo() {
const atomSnapshot = getAtomSnapshot()
// atomOverrides 로 비동기 state 반영이 안 된 값을 즉시 덮어쓸 수 있다
if (atomOverrides) {
Object.assign(atomSnapshot, atomOverrides)
}
const snapshot = {
canvasJSON,
atomSnapshot,
@ -329,9 +338,8 @@ export function useUndoRedo() {
const newUndoStack = [...currentUndoStack]
const snapshot = newUndoStack.pop()
if (snapshot.atomSnapshot?.currentMenu !== currentMenu) {
closeAllPopups()
}
// undo/redo 시 항상 팝업을 닫고 복원한다
closeAllPopups()
setUndoStack(newUndoStack)
setRedoStack((prev) => [...prev, currentSnapshot])
@ -355,9 +363,8 @@ export function useUndoRedo() {
const newRedoStack = [...currentRedoStack]
const snapshot = newRedoStack.pop()
if (snapshot.atomSnapshot?.currentMenu !== currentMenu) {
closeAllPopups()
}
// undo/redo 시 항상 팝업을 닫고 복원한다
closeAllPopups()
setRedoStack(newRedoStack)
setUndoStack((prev) => [...prev, currentSnapshot])

View File

@ -39,3 +39,9 @@ export const promisePopupState = atom({
key: 'promisePopupStore',
default: false,
})
/** undo/redo 시 복원해야 할 팝업 정보 (JSX 직렬화 불가 → 타입+파라미터만 저장) */
export const pendingPopupState = atom({
key: 'pendingPopupState',
default: null, // { type: 'RoofAllocation', id, pos } 등
})