Add application codebase: PDF annotation for e-Court
Next.js 14 + Drizzle/PostgreSQL + SSE realtime sync — highlight, freehand, comment, bookmark + ฟีเจอร์ต่อยอด F1–F19 (edit/move, undo/redo, shapes, redaction, export PDF/CSV, reply, resolve, search, summary, textbox, stamp, version history) Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
parent
6fd0c3174f
commit
eddcec28c7
60 changed files with 15055 additions and 0 deletions
60
hooks/useHistory.ts
Normal file
60
hooks/useHistory.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
"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,
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue