"use client"; import { useState } from "react"; import { Scissors } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { useToast } from "@/components/ui/use-toast"; import { updateVersionHandles } from "@/actions/versions"; import { cn } from "@/lib/utils"; interface VersionHandlesButtonProps { versionId: string; handleHead: number | null; handleTail: number | null; pictureStartFrame: number | null; onSaved?: () => void; } const DEFAULT_HANDLES = 8; function toInt(value: string): number | null { if (value.trim() === "") return null; const n = Number(value); return Number.isInteger(n) && n >= 0 ? n : NaN; } /** * Top-bar control showing a version's handles, and a dialog to set them — * for manual uploads, or to correct a render. Review metadata only. */ export function VersionHandlesButton({ versionId, handleHead, handleTail, pictureStartFrame, onSaved, }: VersionHandlesButtonProps) { const marked = (handleHead ?? 0) > 0 || (handleTail ?? 0) > 0; const [open, setOpen] = useState(false); const [includes, setIncludes] = useState(marked); const [head, setHead] = useState(String(handleHead ?? DEFAULT_HANDLES)); const [tail, setTail] = useState(String(handleTail ?? DEFAULT_HANDLES)); const [slate, setSlate] = useState(pictureStartFrame == null ? "" : String(pictureStartFrame)); const [saving, setSaving] = useState(false); const { toast } = useToast(); const openDialog = () => { setIncludes(marked); setHead(String(handleHead ?? DEFAULT_HANDLES)); setTail(String(handleTail ?? DEFAULT_HANDLES)); setSlate(pictureStartFrame == null ? "" : String(pictureStartFrame)); setOpen(true); }; const headN = toInt(head); const tailN = toInt(tail); const slateN = toInt(slate); const invalid = (includes && (headN == null || Number.isNaN(headN) || tailN == null || Number.isNaN(tailN))) || Number.isNaN(slateN); const save = async () => { if (invalid) return; setSaving(true); try { await updateVersionHandles({ versionId, handleHead: includes ? headN! : 0, handleTail: includes ? tailN! : 0, pictureStartFrame: slateN, }); setOpen(false); onSaved?.(); } catch { toast({ title: "Couldn't save handles", variant: "destructive" }); } finally { setSaving(false); } }; const label = marked ? handleHead === handleTail ? `Handles ${handleHead}` : `Handles ${handleHead ?? 0}/${handleTail ?? 0}` : "No handles"; return ( <> Handles Frames rendered past the cut at each end. The player marks them “Handle · Not in edit” so reviewers don't flag them. Nothing is burned in.
setHead(e.target.value)} />
setTail(e.target.value)} />
setSlate(e.target.value)} />

Leave blank to detect it (pipeline previews have a 1-frame slate).

); }