61 lines
2.3 KiB
TypeScript
61 lines
2.3 KiB
TypeScript
|
|
"use client";
|
||
|
|
|
||
|
|
import { useCallback, useRef, useState } from "react";
|
||
|
|
|
||
|
|
// Undo/Redo (PLAN-FEAT.md §3 F2, D10) — history ฝั่ง client ต่อ session ต่อผู้ใช้
|
||
|
|
// ไม่เก็บใน DB; push เฉพาะ action ของตัวเองที่สำเร็จเท่านั้น (event ของคนอื่นไม่เกี่ยว)
|
||
|
|
// ตัว entry เก็บ closure ที่เรียก Server Action + sync state เอง — id ของ annotation
|
||
|
|
// ที่ "สร้างใหม่" หลัง undo ต้องส่งต่อให้ redo ได้ จึงให้ entry เป็น object ที่แก้ field ได้
|
||
|
|
|
||
|
|
export type HistoryEntry = {
|
||
|
|
undo: () => Promise<void>;
|
||
|
|
redo: () => Promise<void>;
|
||
|
|
};
|
||
|
|
|
||
|
|
export function useHistory() {
|
||
|
|
// source of truth เป็น ref — state ที่เปลี่ยนแค่ไว้ trigger re-render ปุ่ม disabled
|
||
|
|
const undoRef = useRef<HistoryEntry[]>([]);
|
||
|
|
const redoRef = useRef<HistoryEntry[]>([]);
|
||
|
|
const [, bump] = useState(0);
|
||
|
|
|
||
|
|
/** บันทึก action ที่สำเร็จ — action ใหม่เคลียร์ redo stack เสมอ */
|
||
|
|
const push = useCallback((entry: HistoryEntry) => {
|
||
|
|
undoRef.current.push(entry);
|
||
|
|
redoRef.current = [];
|
||
|
|
bump((n) => n + 1);
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
const undo = useCallback(async () => {
|
||
|
|
const entry = undoRef.current.pop();
|
||
|
|
if (!entry) return;
|
||
|
|
try {
|
||
|
|
await entry.undo();
|
||
|
|
redoRef.current.push(entry);
|
||
|
|
} catch (err) {
|
||
|
|
// เช่น undo ของ create แต่ row ถูกลบไปแล้วโดยแท็บอื่น → ข้าม ไม่ crash (§5)
|
||
|
|
console.error("undo ไม่สำเร็จ — ข้ามรายการนี้", err);
|
||
|
|
}
|
||
|
|
bump((n) => n + 1);
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
const redo = useCallback(async () => {
|
||
|
|
const entry = redoRef.current.pop();
|
||
|
|
if (!entry) return;
|
||
|
|
try {
|
||
|
|
await entry.redo();
|
||
|
|
undoRef.current.push(entry);
|
||
|
|
} catch (err) {
|
||
|
|
console.error("redo ไม่สำเร็จ — ข้ามรายการนี้", err);
|
||
|
|
}
|
||
|
|
bump((n) => n + 1);
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
return {
|
||
|
|
push,
|
||
|
|
undo,
|
||
|
|
redo,
|
||
|
|
canUndo: undoRef.current.length > 0,
|
||
|
|
canRedo: redoRef.current.length > 0,
|
||
|
|
};
|
||
|
|
}
|