dev #931

Merged
ysCha merged 95 commits from dev into prd-deploy 2026-06-23 17:47:25 +09:00
Showing only changes of commit b775af1911 - Show all commits

View File

@ -17,7 +17,7 @@ import {
import { outerLinePointsState } from '@/store/outerLineAtom'
import { selectedMenuState } from '@/store/menuAtom'
import { pendingPopupState, popupState } from '@/store/popupAtom'
import { MENU, POLYGON_TYPE, SAVE_KEY } from '@/common/common'
import { POLYGON_TYPE, SAVE_KEY } from '@/common/common'
const MAX_HISTORY = 3
@ -70,13 +70,12 @@ function _restoreFromJSON(canvas, snapshot, restoreAtomSnapshot, rebuildCanvasAt
canvas.loadFromJSON(snapshot.canvasJSON, () => {
rebuildCanvasAtoms()
// undo/redo 후 모듈 설치영역이 active 인 채로 남으면
// 사용자가 다시 클릭하지 않아도 선택된 것처럼 보여 자동 배치 등 부작용 발생.
// 활성 객체가 MODULE_SETUP_SURFACE 이면 선택 해제한다.
const active = canvas.getActiveObject()
if (active && active.name === POLYGON_TYPE.MODULE_SETUP_SURFACE) {
canvas.discardActiveObject()
}
canvas.discardActiveObject()
canvas.getObjects().forEach((obj) => {
if (obj.type === 'QPolygon' && obj.name === POLYGON_TYPE.MODULE) {
obj.set({ stroke: 'black', strokeWidth: 0.3 })
}
})
canvas.renderAll()
restoreAtomSnapshot(snapshot.atomSnapshot)
@ -92,6 +91,7 @@ function _restoreFromJSON(canvas, snapshot, restoreAtomSnapshot, rebuildCanvasAt
}, 50)
})
}
// 순환 참조만 안전하게 건너뛰는 JSON 직렬화
// WeakSet 방식은 공유 참조(같은 배열이 여러 곳에서 참조)까지 제거하므로 사용 불가
// 대신 조상 경로(ancestors) 스택으로 진짜 순환만 감지
@ -364,7 +364,9 @@ export function useUndoRedo() {
if (polygon.id) {
polygon.texts = lengthTextsByParent.get(polygon.id) || []
}
} catch (e) { /* skip */ }
} catch (e) {
/* skip */
}
})
// Group 내부 QPolygon(도머 등)도 동일하게 내부 라인 인스턴스만 재구축한다.
@ -379,7 +381,9 @@ export function useUndoRedo() {
if (child.id) {
child.texts = lengthTextsByParent.get(child.id) || []
}
} catch (e) { /* skip */ }
} catch (e) {
/* skip */
}
}
})
})
@ -409,40 +413,43 @@ export function useUndoRedo() {
}
}, [canvas])
const saveSnapshot = useCallback((atomOverrides = null, { force = false } = {}) => {
if (!canvas || _isRestoring) return
const saveSnapshot = useCallback(
(atomOverrides = null, { force = false } = {}) => {
if (!canvas || _isRestoring) return
// 외벽선/배치면/지붕형상 수동설정 등 그리기 진행 중에는 스냅샷을 쌓지 않는다 (force 시 예외)
if (!force && (_isDrawing || isDrawing)) return
// 외벽선/배치면/지붕형상 수동설정 등 그리기 진행 중에는 스냅샷을 쌓지 않는다 (force 시 예외)
if (!force && (_isDrawing || isDrawing)) return
// snapshot 전에 mouseLine 제거
const mouseLines = canvas.getObjects().filter((obj) => obj.name === 'mouseLine')
mouseLines.forEach((obj) => canvas.remove(obj))
// snapshot 전에 mouseLine 제거
const mouseLines = canvas.getObjects().filter((obj) => obj.name === 'mouseLine')
mouseLines.forEach((obj) => canvas.remove(obj))
const canvasJSON = getCanvasSnapshot()
if (!canvasJSON) return
const canvasJSON = getCanvasSnapshot()
if (!canvasJSON) return
const atomSnapshot = getAtomSnapshot()
const atomSnapshot = getAtomSnapshot()
// atomOverrides 로 비동기 state 반영이 안 된 값을 즉시 덮어쓸 수 있다
if (atomOverrides) {
Object.assign(atomSnapshot, atomOverrides)
}
const snapshot = {
canvasJSON,
atomSnapshot,
}
setUndoStack((prev) => {
const newStack = [...prev, snapshot]
if (newStack.length > MAX_HISTORY) {
return newStack.slice(newStack.length - MAX_HISTORY)
// atomOverrides 로 비동기 state 반영이 안 된 값을 즉시 덮어쓸 수 있다
if (atomOverrides) {
Object.assign(atomSnapshot, atomOverrides)
}
return newStack
})
setRedoStack([])
}, [canvas, currentMenu, isDrawing, getCanvasSnapshot, getAtomSnapshot])
const snapshot = {
canvasJSON,
atomSnapshot,
}
setUndoStack((prev) => {
const newStack = [...prev, snapshot]
if (newStack.length > MAX_HISTORY) {
return newStack.slice(newStack.length - MAX_HISTORY)
}
return newStack
})
setRedoStack([])
},
[canvas, currentMenu, isDrawing, getCanvasSnapshot, getAtomSnapshot],
)
const closeAllPopups = useCallback(() => {
setPopup((prev) => ({