- Add timestampSeconds and frameThumbnailUrl fields to Annotation model - New VideoAnnotationLayer component: auto-pause on draw tool activation, SVG annotation overlay on paused video, time-filtered visibility, All/Timed toggle, timecode display in toolbar - New VideoTimelineMarkers: orange=unresolved, green=resolved, clustered markers on scrub bar with click-to-seek and hover scale - Thread timestampSeconds through validator, service, and API layers - Feedback item cards show timestamp badges for video annotations - VideoPlayer gains renderOverlay, timelineMarkers, pause/seek in state - Fix "Processing" overlay shown when MP4 is available (FFmpeg fallback) - Add revision polling when video status is "processing" - Configure proxyClientMaxBodySize: 500mb for large video uploads - Fix pre-existing Prisma JSON type error in upload-service.ts - Update ROADMAP with lawn reference learnings and A7.3 progress Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
52 lines
1.5 KiB
TypeScript
52 lines
1.5 KiB
TypeScript
import { z } from "zod/v4";
|
|
|
|
const annotationTypeEnum = z.enum([
|
|
"RECTANGLE",
|
|
"ELLIPSE",
|
|
"ARROW",
|
|
"FREEHAND",
|
|
"TEXT",
|
|
"PIN",
|
|
"SCREENSHOT",
|
|
]);
|
|
|
|
export type AnnotationTypeValue = z.infer<typeof annotationTypeEnum>;
|
|
|
|
/**
|
|
* Shape data varies by annotation type. We use a loose Json schema
|
|
* here and validate more specifically in the service layer if needed.
|
|
*/
|
|
const annotationDataSchema = z.object({
|
|
x: z.number().optional(),
|
|
y: z.number().optional(),
|
|
width: z.number().optional(),
|
|
height: z.number().optional(),
|
|
endX: z.number().optional(),
|
|
endY: z.number().optional(),
|
|
points: z.array(z.object({ x: z.number(), y: z.number() })).optional(),
|
|
text: z.string().optional(),
|
|
color: z.string().optional(),
|
|
strokeWidth: z.number().optional(),
|
|
imageUrl: z.string().optional(),
|
|
});
|
|
|
|
export const createAnnotationSchema = z.object({
|
|
type: annotationTypeEnum,
|
|
data: annotationDataSchema,
|
|
imageX: z.number(),
|
|
imageY: z.number(),
|
|
timestampSeconds: z.number().nullable().optional(), // null/undefined for image, number for video
|
|
commentContent: z.string().min(1, "Comment text is required"),
|
|
stageId: z.string().min(1, "Stage ID is required"),
|
|
});
|
|
|
|
export type CreateAnnotationInput = z.infer<typeof createAnnotationSchema>;
|
|
|
|
export const updateAnnotationSchema = z.object({
|
|
data: annotationDataSchema.optional(),
|
|
imageX: z.number().optional(),
|
|
imageY: z.number().optional(),
|
|
timestampSeconds: z.number().nullable().optional(),
|
|
});
|
|
|
|
export type UpdateAnnotationInput = z.infer<typeof updateAnnotationSchema>;
|