105 lines
2.9 KiB
TypeScript
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,
|
|
};
|
|
}
|