Files
vfxreview/hooks/use-annotations.ts
twotalesanimation 0fbe856dce Initial commit
2026-05-19 22:20:29 +02:00

105 lines
2.9 KiB
TypeScript

"use client";
import { useState, useCallback, useRef } from "react";
import { saveAnnotation, getAnnotationsForVersion } from "@/actions/annotations";
import type { AnnotationShape, AnnotationDrawingData } from "@/types";
import { useReviewStore } from "@/hooks/use-review-player";
import { useToast } from "@/components/ui/use-toast";
import { v4 as uuidv4 } from "uuid";
interface UseAnnotationsOptions {
versionId: string;
fps: number;
}
export function useAnnotations({ versionId, fps }: UseAnnotationsOptions) {
const [savedAnnotations, setSavedAnnotations] = useState<
{ id: string; frameNumber: number; drawingData: AnnotationDrawingData }[]
>([]);
const [sessionShapes, setSessionShapes] = useState<AnnotationShape[]>([]);
const [currentShape, setCurrentShape] = useState<AnnotationShape | null>(null);
const [isDrawing, setIsDrawing] = useState(false);
const { selectedTool, selectedColor, strokeWidth } = useReviewStore();
const { toast } = useToast();
const startShape = useCallback(
(frameNumber: number, point: { x: number; y: number }) => {
const shape: AnnotationShape = {
id: uuidv4(),
tool: selectedTool,
points: [point],
color: selectedColor,
strokeWidth,
frameNumber,
};
setCurrentShape(shape);
setIsDrawing(true);
},
[selectedTool, selectedColor, strokeWidth]
);
const addPoint = useCallback((point: { x: number; y: number }) => {
setCurrentShape((prev) =>
prev ? { ...prev, points: [...prev.points, point] } : null
);
}, []);
const finishShape = useCallback(
async (canvasWidth: number, canvasHeight: number) => {
if (!currentShape) return;
const finished = currentShape;
setSessionShapes((prev) => [...prev, finished]);
setCurrentShape(null);
setIsDrawing(false);
const drawingData: AnnotationDrawingData = {
shapes: [finished],
canvasWidth,
canvasHeight,
version: "1.0",
};
try {
await saveAnnotation({
versionId,
frameNumber: finished.frameNumber,
drawingData,
color: selectedColor,
});
} catch {
toast({ title: "Failed to save annotation", variant: "destructive" });
}
},
[currentShape, versionId, selectedColor, toast]
);
const clearSessionShapes = useCallback((frameNumber?: number) => {
if (frameNumber !== undefined) {
setSessionShapes((prev) => prev.filter((s) => s.frameNumber !== frameNumber));
} else {
setSessionShapes([]);
}
}, []);
const loadAnnotations = useCallback(async () => {
try {
const data = await getAnnotationsForVersion(versionId);
setSavedAnnotations(data as any);
} catch {
// non-fatal
}
}, [versionId]);
return {
savedAnnotations,
sessionShapes,
currentShape,
isDrawing,
startShape,
addPoint,
finishShape,
clearSessionShapes,
loadAnnotations,
};
}