dev #931

Merged
ysCha merged 95 commits from dev into prd-deploy 2026-06-23 17:47:25 +09:00
5 changed files with 80 additions and 10 deletions
Showing only changes of commit 80720fef2a - Show all commits

View File

@ -9,6 +9,7 @@ import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil'
import { subMenusState } from '@/store/menuAtom'
import { useCanvasMenu } from '@/hooks/common/useCanvasMenu'
import { commonUtilsState } from '@/store/commonUtilsAtom'
import { isUndoRedoRestoring } from '@/hooks/useUndoRedo'
export default function MenuDepth01() {
const canvas = useRecoilValue(canvasState)
@ -29,6 +30,10 @@ export default function MenuDepth01() {
}
useEffect(() => {
// undo/redo .
// setDrawing(true)
// redo .
if (isUndoRedoRestoring()) return
handleMenu(selectedMenu)
canvas?.discardActiveObject()
}, [currentMenu])

View File

@ -63,7 +63,7 @@ export function useOuterLineWall(id, propertiesId) {
const { tempGridMode } = useTempGrid()
const { addPolygonByLines } = usePolygon()
const { handleSelectableObjects } = useObject()
const { saveSnapshot } = useUndoRedo()
const { saveSnapshot, setDrawing } = useUndoRedo()
const verticalHorizontalMode = useRecoilValue(verticalHorizontalModeState)
const adsorptionPointAddMode = useRecoilValue(adsorptionPointAddModeState)
@ -121,6 +121,15 @@ export function useOuterLineWall(id, propertiesId) {
}
}, [verticalHorizontalMode, points, adsorptionPointAddMode, adsorptionPointMode, adsorptionRange, interval, tempGridMode])
// 외벽선 그리기 진행 중에는 undo/redo/스냅샷을 차단한다.
// 그리기 전 스냅샷은 첫 클릭(mouseDown) 에서 force 로 저장한다 (기존 동작 유지).
useEffect(() => {
setDrawing(true)
return () => {
setDrawing(false)
}
}, [])
useEffect(() => {
arrow1Ref.current = arrow1
}, [arrow1])
@ -158,7 +167,9 @@ export function useOuterLineWall(id, propertiesId) {
pointer = { x: Big(pointer.x).toNumber(), y: Big(pointer.y).toNumber() }
if (points.length === 0) {
saveSnapshot()
// 그리기 진행 중에는 스냅샷이 차단되므로 force 로 저장한다.
// (처음 한 번만 저장되고 이후 추가 클릭은 points.length > 0 분기로 빠지므로 중복 저장 안 됨)
saveSnapshot(null, { force: true })
setPoints((prev) => [...prev, pointer])
} else {
const lastPoint = points[points.length - 1]

View File

@ -51,7 +51,7 @@ export function usePlacementShapeDrawing(id) {
const { setSurfaceShapePattern } = useRoofFn()
const { changeSurfaceLineType } = useSurfaceShapeBatch({})
const { handleSelectableObjects } = useObject()
const { saveSnapshot, popLastSnapshot } = useUndoRedo()
const { saveSnapshot, popLastSnapshot, setDrawing } = useUndoRedo()
const { swalFire } = useSwal()
const { getMessage } = useMessage()
@ -98,12 +98,15 @@ export function usePlacementShapeDrawing(id) {
}, [verticalHorizontalMode, points, adsorptionRange, adsorptionPointMode])
useEffect(() => {
// 배치면 그리기 시작 시 스냅샷 저장 (undo 하면 그리기 전 상태로 복원)
saveSnapshot()
// 배치면 그리기 시작 시 스냅샷 저장 (undo 하면 그리기 전 상태로 복원).
// 그리기 진행 중에는 undo/redo/snapshot 을 차단하므로 force 로 저장한다.
setDrawing(true)
saveSnapshot(null, { force: true })
setPoints([])
setType(OUTER_LINE_TYPE.OUTER_LINE)
return () => {
setDrawing(false)
const placementShapeDrawingStartPoint = canvas.getObjects().find((obj) => obj.name === 'placementShapeDrawingStartPoint')
canvas.remove(placementShapeDrawingStartPoint)

View File

@ -11,6 +11,7 @@ import {
moduleSetupSurfaceState,
redoStackState,
roofPolygonArrayState,
undoRedoDrawingState,
undoStackState,
} from '@/store/canvasAtom'
import { outerLinePointsState } from '@/store/outerLineAtom'
@ -25,6 +26,10 @@ const MAX_HISTORY = 3
let _isRestoring = false
let _restoreTimer = null
// 외벽선/배치면 등 그리기 진행 중에는 undo/redo/snapshot 을 차단한다.
// (확정 시점부터 history 가 동작해야 하므로)
let _isDrawing = false
/**
* 외부 /모듈에서 현재 undo/redo 복원 중인지 확인할 사용
*/
@ -32,6 +37,21 @@ export function isUndoRedoRestoring() {
return _isRestoring
}
/**
* 외벽선/배치면 그리기 진행 상태를 외부에서 설정한다.
* 그리기가 시작될 true, 확정 또는 취소 false 설정한다.
*/
export function setUndoRedoDrawing(value) {
_isDrawing = !!value
}
/**
* 외부 /모듈에서 현재 그리기 진행 중인지 확인할 사용
*/
export function isUndoRedoDrawing() {
return _isDrawing
}
// loadFromJSON 공통 처리 - 이벤트 일시 중단 후 복원
function _restoreFromJSON(canvas, snapshot, restoreAtomSnapshot, rebuildCanvasAtoms, setCanvasZoom) {
// 진행 중인 복원 타이머가 있으면 취소
@ -98,6 +118,7 @@ export function useUndoRedo() {
const canvas = useRecoilValue(canvasState)
const [undoStack, setUndoStack] = useRecoilState(undoStackState)
const [redoStack, setRedoStack] = useRecoilState(redoStackState)
const [isDrawing, setIsDrawing] = useRecoilState(undoRedoDrawingState)
const [, setModuleSetupSurface] = useRecoilState(moduleSetupSurfaceState)
const [moduleIsSetup, setModuleIsSetup] = useRecoilState(moduleIsSetupState)
@ -375,6 +396,9 @@ export function useUndoRedo() {
// 지붕형상 수동설정 메뉴에서는 스냅샷을 쌓지 않는다 (force 시 예외)
if (!force && currentMenu === MENU.ROOF_COVERING.ROOF_SHAPE_PASSIVITY_SETTINGS) return
// 외벽선/배치면 등 그리기 진행 중에는 스냅샷을 쌓지 않는다 (force 시 예외)
if (!force && (_isDrawing || isDrawing)) return
// snapshot 전에 mouseLine 제거
const mouseLines = canvas.getObjects().filter((obj) => obj.name === 'mouseLine')
mouseLines.forEach((obj) => canvas.remove(obj))
@ -402,7 +426,7 @@ export function useUndoRedo() {
return newStack
})
setRedoStack([])
}, [canvas, currentMenu, getCanvasSnapshot, getAtomSnapshot])
}, [canvas, currentMenu, isDrawing, getCanvasSnapshot, getAtomSnapshot])
const closeAllPopups = useCallback(() => {
setPopup((prev) => ({
@ -416,6 +440,9 @@ export function useUndoRedo() {
// 지붕형상 수동설정 중에는 undo 차단
if (currentMenu === MENU.ROOF_COVERING.ROOF_SHAPE_PASSIVITY_SETTINGS) return
// 외벽선/배치면 등 그리기 진행 중에는 undo 차단
if (_isDrawing || isDrawing) return
const currentUndoStack = undoStackRef.current
if (!canvas || currentUndoStack.length === 0 || _isRestoring) return
@ -439,12 +466,15 @@ export function useUndoRedo() {
setRedoStack((prev) => [...prev, currentSnapshot])
_restoreFromJSON(canvas, snapshot, restoreAtomSnapshot, rebuildCanvasAtoms, setCanvasZoom)
}, [canvas, currentMenu, getCanvasSnapshot, getAtomSnapshot, restoreAtomSnapshot, rebuildCanvasAtoms, closeAllPopups])
}, [canvas, currentMenu, isDrawing, getCanvasSnapshot, getAtomSnapshot, restoreAtomSnapshot, rebuildCanvasAtoms, closeAllPopups])
const redo = useCallback(() => {
// 지붕형상 수동설정 중에는 redo 차단
if (currentMenu === MENU.ROOF_COVERING.ROOF_SHAPE_PASSIVITY_SETTINGS) return
// 외벽선/배치면 등 그리기 진행 중에는 redo 차단
if (_isDrawing || isDrawing) return
const currentRedoStack = redoStackRef.current
if (!canvas || currentRedoStack.length === 0 || _isRestoring) return
@ -467,7 +497,7 @@ export function useUndoRedo() {
setUndoStack((prev) => [...prev, currentSnapshot])
_restoreFromJSON(canvas, snapshot, restoreAtomSnapshot, rebuildCanvasAtoms, setCanvasZoom)
}, [canvas, currentMenu, getCanvasSnapshot, getAtomSnapshot, restoreAtomSnapshot, rebuildCanvasAtoms, closeAllPopups])
}, [canvas, currentMenu, isDrawing, getCanvasSnapshot, getAtomSnapshot, restoreAtomSnapshot, rebuildCanvasAtoms, closeAllPopups])
const clearStacks = useCallback(() => {
setUndoStack([])
@ -479,13 +509,27 @@ export function useUndoRedo() {
setUndoStack((prev) => (prev.length > 0 ? prev.slice(0, -1) : prev))
}, [])
/**
* 외벽선/배치면 그리기 진행 중에는 undo/redo 차단한다.
* 마운트 true, 언마운트 false 설정한다.
*/
const setDrawing = useCallback(
(value) => {
const v = !!value
setIsDrawing(v)
setUndoRedoDrawing(v)
},
[setIsDrawing],
)
return {
saveSnapshot,
undo,
redo,
clearStacks,
popLastSnapshot,
canUndo: undoStack.length > 0,
canRedo: redoStack.length > 0,
setDrawing,
canUndo: undoStack.length > 0 && !isDrawing,
canRedo: redoStack.length > 0 && !isDrawing,
}
}

View File

@ -421,3 +421,10 @@ export const redoStackState = atom({
default: [],
dangerouslyAllowMutability: true,
})
// 외벽선/배치면 등 그리기 진행 중 여부.
// true 동안에는 undo/redo 가 비활성화되고 새 스냅샷이 쌓이지 않는다.
export const undoRedoDrawingState = atom({
key: 'undoRedoDrawingState',
default: false,
})