208 lines
5.1 KiB
TypeScript
208 lines
5.1 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useCallback, useRef } from "react";
|
|
|
|
// ========================================
|
|
// 레이아웃 히스토리 훅
|
|
// Undo/Redo 기능 제공
|
|
// ========================================
|
|
|
|
interface HistoryState<T> {
|
|
past: T[];
|
|
present: T;
|
|
future: T[];
|
|
}
|
|
|
|
interface UseLayoutHistoryReturn<T> {
|
|
// 현재 상태
|
|
state: T;
|
|
// 상태 설정 (히스토리에 기록)
|
|
setState: (newState: T | ((prev: T) => T)) => void;
|
|
// Undo
|
|
undo: () => void;
|
|
// Redo
|
|
redo: () => void;
|
|
// Undo 가능 여부
|
|
canUndo: boolean;
|
|
// Redo 가능 여부
|
|
canRedo: boolean;
|
|
// 히스토리 초기화 (새 레이아웃 로드 시)
|
|
reset: (initialState: T) => void;
|
|
// 히스토리 크기
|
|
historySize: number;
|
|
}
|
|
|
|
/**
|
|
* 레이아웃 히스토리 관리 훅
|
|
* @param initialState 초기 상태
|
|
* @param maxHistory 최대 히스토리 개수 (기본 50)
|
|
*/
|
|
export function useLayoutHistory<T>(
|
|
initialState: T,
|
|
maxHistory: number = 50
|
|
): UseLayoutHistoryReturn<T> {
|
|
const [history, setHistory] = useState<HistoryState<T>>({
|
|
past: [],
|
|
present: initialState,
|
|
future: [],
|
|
});
|
|
|
|
// 배치 업데이트를 위한 타이머
|
|
const batchTimerRef = useRef<NodeJS.Timeout | null>(null);
|
|
const pendingStateRef = useRef<T | null>(null);
|
|
|
|
/**
|
|
* 상태 설정 (히스토리에 기록)
|
|
* 연속적인 드래그 등의 작업 시 배치 처리
|
|
*/
|
|
const setState = useCallback(
|
|
(newState: T | ((prev: T) => T)) => {
|
|
setHistory((prev) => {
|
|
const resolvedState =
|
|
typeof newState === "function"
|
|
? (newState as (prev: T) => T)(prev.present)
|
|
: newState;
|
|
|
|
// 같은 상태면 무시
|
|
if (JSON.stringify(resolvedState) === JSON.stringify(prev.present)) {
|
|
console.log("[History] 상태 동일, 무시");
|
|
return prev;
|
|
}
|
|
|
|
// 히스토리에 현재 상태 추가
|
|
const newPast = [...prev.past, prev.present];
|
|
|
|
// 최대 히스토리 개수 제한
|
|
if (newPast.length > maxHistory) {
|
|
newPast.shift();
|
|
}
|
|
|
|
console.log("[History] 상태 저장, past 크기:", newPast.length);
|
|
|
|
return {
|
|
past: newPast,
|
|
present: resolvedState,
|
|
future: [], // Redo 히스토리 초기화
|
|
};
|
|
});
|
|
},
|
|
[maxHistory]
|
|
);
|
|
|
|
/**
|
|
* 배치 상태 설정 (드래그 중 연속 업데이트용)
|
|
* 마지막 상태만 히스토리에 기록
|
|
*/
|
|
const setStateBatched = useCallback(
|
|
(newState: T | ((prev: T) => T), batchDelay: number = 300) => {
|
|
// 현재 상태 업데이트 (히스토리에는 바로 기록하지 않음)
|
|
setHistory((prev) => {
|
|
const resolvedState =
|
|
typeof newState === "function"
|
|
? (newState as (prev: T) => T)(prev.present)
|
|
: newState;
|
|
|
|
pendingStateRef.current = prev.present;
|
|
|
|
return {
|
|
...prev,
|
|
present: resolvedState,
|
|
};
|
|
});
|
|
|
|
// 배치 타이머 리셋
|
|
if (batchTimerRef.current) {
|
|
clearTimeout(batchTimerRef.current);
|
|
}
|
|
|
|
// 일정 시간 후 히스토리에 기록
|
|
batchTimerRef.current = setTimeout(() => {
|
|
if (pendingStateRef.current !== null) {
|
|
setHistory((prev) => {
|
|
const newPast = [...prev.past, pendingStateRef.current as T];
|
|
if (newPast.length > maxHistory) {
|
|
newPast.shift();
|
|
}
|
|
pendingStateRef.current = null;
|
|
return {
|
|
...prev,
|
|
past: newPast,
|
|
future: [],
|
|
};
|
|
});
|
|
}
|
|
}, batchDelay);
|
|
},
|
|
[maxHistory]
|
|
);
|
|
|
|
/**
|
|
* Undo - 이전 상태로 복원
|
|
*/
|
|
const undo = useCallback(() => {
|
|
console.log("[History] Undo 호출");
|
|
setHistory((prev) => {
|
|
console.log("[History] Undo 실행, past 크기:", prev.past.length);
|
|
if (prev.past.length === 0) {
|
|
console.log("[History] Undo 불가 - past 비어있음");
|
|
return prev;
|
|
}
|
|
|
|
const newPast = [...prev.past];
|
|
const previousState = newPast.pop()!;
|
|
|
|
console.log("[History] Undo 성공, 남은 past 크기:", newPast.length);
|
|
|
|
return {
|
|
past: newPast,
|
|
present: previousState,
|
|
future: [prev.present, ...prev.future],
|
|
};
|
|
});
|
|
}, []);
|
|
|
|
/**
|
|
* Redo - 되돌린 상태 다시 적용
|
|
*/
|
|
const redo = useCallback(() => {
|
|
setHistory((prev) => {
|
|
if (prev.future.length === 0) {
|
|
return prev;
|
|
}
|
|
|
|
const newFuture = [...prev.future];
|
|
const nextState = newFuture.shift()!;
|
|
|
|
return {
|
|
past: [...prev.past, prev.present],
|
|
present: nextState,
|
|
future: newFuture,
|
|
};
|
|
});
|
|
}, []);
|
|
|
|
/**
|
|
* 히스토리 초기화 (새 레이아웃 로드 시)
|
|
*/
|
|
const reset = useCallback((initialState: T) => {
|
|
setHistory({
|
|
past: [],
|
|
present: initialState,
|
|
future: [],
|
|
});
|
|
}, []);
|
|
|
|
return {
|
|
state: history.present,
|
|
setState,
|
|
undo,
|
|
redo,
|
|
canUndo: history.past.length > 0,
|
|
canRedo: history.future.length > 0,
|
|
reset,
|
|
historySize: history.past.length,
|
|
};
|
|
}
|
|
|
|
export default useLayoutHistory;
|