redo undo 작업중

This commit is contained in:
hyojun.choi 2026-04-01 15:54:55 +09:00
parent c7238b2e27
commit d68f92c5d5
8 changed files with 37 additions and 30 deletions

View File

@ -36,7 +36,12 @@ import {
import { sessionStore } from '@/store/commonAtom' import { sessionStore } from '@/store/commonAtom'
import { outerLinePointsState } from '@/store/outerLineAtom' import { outerLinePointsState } from '@/store/outerLineAtom'
import { appMessageStore, globalLocaleStore } from '@/store/localeAtom' import { appMessageStore, globalLocaleStore } from '@/store/localeAtom'
import { addedRoofsState, basicSettingState, selectedRoofMaterialSelector, settingModalFirstOptionsState } from '@/store/settingAtom' import {
addedRoofsState,
basicSettingState,
selectedRoofMaterialSelector,
settingModalFirstOptionsState,
} from '@/store/settingAtom'
import { placementShapeDrawingPointsState } from '@/store/placementShapeDrawingAtom' import { placementShapeDrawingPointsState } from '@/store/placementShapeDrawingAtom'
import { commonUtilsState } from '@/store/commonUtilsAtom' import { commonUtilsState } from '@/store/commonUtilsAtom'
import { menusState } from '@/store/menuAtom' import { menusState } from '@/store/menuAtom'
@ -572,23 +577,13 @@ export default function CanvasMenu(props) {
{!['estimate', 'simulation'].some((num) => num === selectedMenu) && ( {!['estimate', 'simulation'].some((num) => num === selectedMenu) && (
<> <>
<div className="undo-redo-wrap"> <div className="undo-redo-wrap">
<button <button className={`undo-btn ${!canUndo ? 'disabled' : ''}`} onClick={undo} disabled={!canUndo} title="Undo">
className={`undo-btn ${!canUndo ? 'disabled' : ''}`}
onClick={undo}
disabled={!canUndo}
title="Undo (Ctrl+Z)"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M3 10h10c4.42 0 8 3.58 8 8v1" /> <path d="M3 10h10c4.42 0 8 3.58 8 8v1" />
<polyline points="7 14 3 10 7 6" /> <polyline points="7 14 3 10 7 6" />
</svg> </svg>
</button> </button>
<button <button className={`redo-btn ${!canRedo ? 'disabled' : ''}`} onClick={redo} disabled={!canRedo} title="Redo">
className={`redo-btn ${!canRedo ? 'disabled' : ''}`}
onClick={redo}
disabled={!canRedo}
title="Redo (Ctrl+Shift+Z)"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M21 10H11C6.58 10 3 13.58 3 18v1" /> <path d="M21 10H11C6.58 10 3 13.58 3 18v1" />
<polyline points="17 14 21 10 17 6" /> <polyline points="17 14 21 10 17 6" />

View File

@ -11,6 +11,7 @@ import { useModuleSelection } from '@/hooks/module/useModuleSelection'
import { useOrientation } from '@/hooks/module/useOrientation' import { useOrientation } from '@/hooks/module/useOrientation'
import { useMessage } from '@/hooks/useMessage' import { useMessage } from '@/hooks/useMessage'
import { usePopup } from '@/hooks/usePopup' import { usePopup } from '@/hooks/usePopup'
import { useUndoRedo } from '@/hooks/useUndoRedo'
import { import {
canvasState, canvasState,
checkedModuleState, checkedModuleState,
@ -32,6 +33,7 @@ import Trestle from './step/Trestle'
export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) { export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) {
const { getMessage } = useMessage() const { getMessage } = useMessage()
const { closePopup } = usePopup() const { closePopup } = usePopup()
const { clearStacks } = useUndoRedo()
const [tabNum, setTabNum] = useState(1) const [tabNum, setTabNum] = useState(1)
const orientationRef = useRef(null) const orientationRef = useRef(null)
const [isManualModuleSetup, setIsManualModuleSetup] = useRecoilState(isManualModuleSetupState) const [isManualModuleSetup, setIsManualModuleSetup] = useRecoilState(isManualModuleSetupState)
@ -84,6 +86,11 @@ export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) {
useModuleBasicSetting(tabNum) useModuleBasicSetting(tabNum)
const { updateObjectDate } = useMasterController() const { updateObjectDate } = useMasterController()
// / undo/redo
useEffect(() => {
clearStacks()
}, [])
useEffect(() => { useEffect(() => {
const moduleTabNum = basicSetting.roofSizeSet != 3 ? 3 : 2 const moduleTabNum = basicSetting.roofSizeSet != 3 ? 3 : 2

View File

@ -18,6 +18,7 @@ import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedM
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { useEstimate } from '@/hooks/useEstimate' import { useEstimate } from '@/hooks/useEstimate'
import { useUndoRedo } from '@/hooks/useUndoRedo'
import { useCircuitTrestle } from '@/hooks/useCirCuitTrestle' import { useCircuitTrestle } from '@/hooks/useCirCuitTrestle'
import { useImgLoader } from '@/hooks/floorPlan/useImgLoader' import { useImgLoader } from '@/hooks/floorPlan/useImgLoader'
import { QcastContext } from '@/app/QcastProvider' import { QcastContext } from '@/app/QcastProvider'
@ -34,6 +35,7 @@ export default function CircuitTrestleSetting({ id }) {
const { apply, setViewCircuitNumberTexts, getEstimateData, clear: clearTrestle, setAllModuleSurfaceIsComplete } = useTrestle() const { apply, setViewCircuitNumberTexts, getEstimateData, clear: clearTrestle, setAllModuleSurfaceIsComplete } = useTrestle()
const { swalFire } = useSwal() const { swalFire } = useSwal()
const { saveEstimate } = useEstimate() const { saveEstimate } = useEstimate()
const { clearStacks } = useUndoRedo()
const canvas = useRecoilValue(canvasState) const canvas = useRecoilValue(canvasState)
const setCurrentMenu = useSetRecoilState(currentMenuState) const setCurrentMenu = useSetRecoilState(currentMenuState)
const [canvasZoom, setCanvasZoom] = useRecoilState(canvasZoomState) const [canvasZoom, setCanvasZoom] = useRecoilState(canvasZoomState)
@ -711,6 +713,7 @@ export default function CircuitTrestleSetting({ id }) {
setIsGlobalLoading(false) setIsGlobalLoading(false)
} }
clearStacks()
// removeNotAllocationModules() // removeNotAllocationModules()
} }

View File

@ -106,7 +106,6 @@ export function useModuleBasicSetting(tabNum) {
} }
} else { } else {
//육지붕 일경우에는 바로 배치면 설치LL //육지붕 일경우에는 바로 배치면 설치LL
saveSnapshot()
canvas canvas
.getObjects() .getObjects()
.filter((roof) => roof.name === POLYGON_TYPE.ROOF) .filter((roof) => roof.name === POLYGON_TYPE.ROOF)
@ -120,7 +119,6 @@ export function useModuleBasicSetting(tabNum) {
//가대 상세 데이터 들어오면 실행 //가대 상세 데이터 들어오면 실행
useEffect(() => { useEffect(() => {
if (trestleDetailList.length > 0) { if (trestleDetailList.length > 0) {
saveSnapshot()
let rowColArray = [] let rowColArray = []
//지붕을 가져옴 //지붕을 가져옴
canvas canvas

View File

@ -13,7 +13,6 @@ import { usePopup } from '@/hooks/usePopup'
import { calculateAngle, isSamePoint } from '@/util/qpolygon-utils' import { calculateAngle, isSamePoint } from '@/util/qpolygon-utils'
import { POLYGON_TYPE } from '@/common/common' import { POLYGON_TYPE } from '@/common/common'
import { useMessage } from '../useMessage' import { useMessage } from '../useMessage'
import { useUndoRedo } from '@/hooks/useUndoRedo'
import { import {
auxiliaryLineAngle1State, auxiliaryLineAngle1State,
auxiliaryLineAngle2State, auxiliaryLineAngle2State,
@ -37,7 +36,6 @@ export function useAuxiliaryDrawing(id, isUseEffect = true) {
const { getAdsorptionPoints } = useAdsorptionPoint() const { getAdsorptionPoints } = useAdsorptionPoint()
const { closePopup } = usePopup() const { closePopup } = usePopup()
const { getMessage } = useMessage() const { getMessage } = useMessage()
const { saveSnapshot } = useUndoRedo()
const adsorptionRange = useRecoilValue(adsorptionRangeState) const adsorptionRange = useRecoilValue(adsorptionRangeState)
@ -654,8 +652,6 @@ export function useAuxiliaryDrawing(id, isUseEffect = true) {
return return
} }
saveSnapshot()
const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
/*const allLines = [...auxiliaryLines] /*const allLines = [...auxiliaryLines]
@ -917,7 +913,6 @@ export function useAuxiliaryDrawing(id, isUseEffect = true) {
if (!confirm(getMessage('want.to.complete.auxiliary.creation'))) { if (!confirm(getMessage('want.to.complete.auxiliary.creation'))) {
return return
} }
saveSnapshot()
// cutAuxiliary() // cutAuxiliary()

View File

@ -26,7 +26,6 @@ import { useSurfaceShapeBatch } from './useSurfaceShapeBatch'
import { roofDisplaySelector } from '@/store/settingAtom' import { roofDisplaySelector } from '@/store/settingAtom'
import { useRoofFn } from '@/hooks/common/useRoofFn' import { useRoofFn } from '@/hooks/common/useRoofFn'
import { useObject } from '@/hooks/useObject' import { useObject } from '@/hooks/useObject'
import { useUndoRedo } from '@/hooks/useUndoRedo'
// 배치면 그리기 // 배치면 그리기
export function usePlacementShapeDrawing(id) { export function usePlacementShapeDrawing(id) {
@ -75,7 +74,6 @@ export function usePlacementShapeDrawing(id) {
const isFix = useRef(false) const isFix = useRef(false)
const { closePopup, addPopup } = usePopup() const { closePopup, addPopup } = usePopup()
const { saveSnapshot } = useUndoRedo()
const globalPitch = useRecoilValue(globalPitchState) const globalPitch = useRecoilValue(globalPitchState)
@ -132,7 +130,6 @@ export function usePlacementShapeDrawing(id) {
let pointer = getIntersectMousePoint(e) let pointer = getIntersectMousePoint(e)
if (points.length === 0) { if (points.length === 0) {
saveSnapshot()
setPoints((prev) => [...prev, pointer]) setPoints((prev) => [...prev, pointer])
} else { } else {
const lastPoint = points[points.length - 1] const lastPoint = points[points.length - 1]

View File

@ -304,7 +304,7 @@ export function useCanvasEvent() {
case 'Escape': case 'Escape':
break break
case 'z': /*case 'z':
if (!e.ctrlKey && !e.metaKey) { if (!e.ctrlKey && !e.metaKey) {
return return
} }
@ -313,7 +313,7 @@ export function useCanvasEvent() {
} else { } else {
undo() undo()
} }
break break*/
default: default:
return // 키 이벤트를 처리하지 않는다면 종료합니다. return // 키 이벤트를 처리하지 않는다면 종료합니다.

View File

@ -18,7 +18,7 @@ import { selectedMenuState } from '@/store/menuAtom'
import { popupState } from '@/store/popupAtom' import { popupState } from '@/store/popupAtom'
import { POLYGON_TYPE, SAVE_KEY } from '@/common/common' import { POLYGON_TYPE, SAVE_KEY } from '@/common/common'
const MAX_HISTORY = 20 const MAX_HISTORY = 3
// 모든 useUndoRedo 인스턴스가 공유하는 복원 플래그 // 모든 useUndoRedo 인스턴스가 공유하는 복원 플래그
// useRef는 인스턴스마다 독립적이므로, 모듈 레벨 변수로 전환 // useRef는 인스턴스마다 독립적이므로, 모듈 레벨 변수로 전환
@ -318,7 +318,6 @@ export function useUndoRedo() {
if (!canvas || currentUndoStack.length === 0 || _isRestoring) return if (!canvas || currentUndoStack.length === 0 || _isRestoring) return
_isRestoring = true _isRestoring = true
closeAllPopups()
const currentCanvasJSON = getCanvasSnapshot() const currentCanvasJSON = getCanvasSnapshot()
const currentAtomSnapshot = getAtomSnapshot() const currentAtomSnapshot = getAtomSnapshot()
@ -330,18 +329,21 @@ export function useUndoRedo() {
const newUndoStack = [...currentUndoStack] const newUndoStack = [...currentUndoStack]
const snapshot = newUndoStack.pop() const snapshot = newUndoStack.pop()
if (snapshot.atomSnapshot?.currentMenu !== currentMenu) {
closeAllPopups()
}
setUndoStack(newUndoStack) setUndoStack(newUndoStack)
setRedoStack((prev) => [...prev, currentSnapshot]) setRedoStack((prev) => [...prev, currentSnapshot])
_restoreFromJSON(canvas, snapshot, restoreAtomSnapshot, rebuildCanvasAtoms, setCanvasZoom) _restoreFromJSON(canvas, snapshot, restoreAtomSnapshot, rebuildCanvasAtoms, setCanvasZoom)
}, [canvas, getCanvasSnapshot, getAtomSnapshot, restoreAtomSnapshot, rebuildCanvasAtoms, closeAllPopups]) }, [canvas, currentMenu, getCanvasSnapshot, getAtomSnapshot, restoreAtomSnapshot, rebuildCanvasAtoms, closeAllPopups])
const redo = useCallback(() => { const redo = useCallback(() => {
const currentRedoStack = redoStackRef.current const currentRedoStack = redoStackRef.current
if (!canvas || currentRedoStack.length === 0 || _isRestoring) return if (!canvas || currentRedoStack.length === 0 || _isRestoring) return
_isRestoring = true _isRestoring = true
closeAllPopups()
const currentCanvasJSON = getCanvasSnapshot() const currentCanvasJSON = getCanvasSnapshot()
const currentAtomSnapshot = getAtomSnapshot() const currentAtomSnapshot = getAtomSnapshot()
@ -353,16 +355,26 @@ export function useUndoRedo() {
const newRedoStack = [...currentRedoStack] const newRedoStack = [...currentRedoStack]
const snapshot = newRedoStack.pop() const snapshot = newRedoStack.pop()
if (snapshot.atomSnapshot?.currentMenu !== currentMenu) {
closeAllPopups()
}
setRedoStack(newRedoStack) setRedoStack(newRedoStack)
setUndoStack((prev) => [...prev, currentSnapshot]) setUndoStack((prev) => [...prev, currentSnapshot])
_restoreFromJSON(canvas, snapshot, restoreAtomSnapshot, rebuildCanvasAtoms, setCanvasZoom) _restoreFromJSON(canvas, snapshot, restoreAtomSnapshot, rebuildCanvasAtoms, setCanvasZoom)
}, [canvas, getCanvasSnapshot, getAtomSnapshot, restoreAtomSnapshot, rebuildCanvasAtoms, closeAllPopups]) }, [canvas, currentMenu, getCanvasSnapshot, getAtomSnapshot, restoreAtomSnapshot, rebuildCanvasAtoms, closeAllPopups])
const clearStacks = useCallback(() => {
setUndoStack([])
setRedoStack([])
}, [])
return { return {
saveSnapshot, saveSnapshot,
undo, undo,
redo, redo,
clearStacks,
canUndo: undoStack.length > 0, canUndo: undoStack.length > 0,
canRedo: redoStack.length > 0, canRedo: redoStack.length > 0,
} }