"use client"; import Image from "next/image"; import { useCallback, useEffect, useRef, useState } from "react"; import { useTranslations } from "next-intl"; import { Copy, Trash2 } from "lucide-react"; import { clampSceneDuration, formatTimelineTime, SCENE_THUMB_GRADIENTS, STRIP_PX_PER_SECOND, } from "@/lib/studio-timeline"; import type { Scene } from "@/lib/studio-types"; import { cn } from "@/lib/utils"; interface SceneThumbnailBlockProps { scene: Scene; colorIndex: number; isActive: boolean; canDelete: boolean; pxPerSecond?: number; onSelect: () => void; onRename: (name: string) => void; onDurationChange: (duration: number) => void; onDuplicate: () => void; onDelete: () => void; } export function SceneThumbnailBlock({ scene, colorIndex, isActive, canDelete, pxPerSecond = STRIP_PX_PER_SECOND, onSelect, onRename, onDurationChange, onDuplicate, onDelete, }: SceneThumbnailBlockProps) { const t = useTranslations("auto.componentsStudioTimelineSceneThumbnailBlock"); const [resizeDuration, setResizeDuration] = useState(null); const [isEditing, setIsEditing] = useState(false); const [editName, setEditName] = useState(scene.name); const resizeRef = useRef({ startX: 0, startDuration: scene.duration }); const inputRef = useRef(null); const duration = resizeDuration ?? scene.duration; const width = Math.max(80, duration * pxPerSecond); const gradient = SCENE_THUMB_GRADIENTS[colorIndex % SCENE_THUMB_GRADIENTS.length]; // Format duration: show seconds for short clips, MM:SS for long const durationLabel = duration >= 60 ? formatTimelineTime(duration) : `${duration}s`; useEffect(() => { setEditName(scene.name); }, [scene.name]); useEffect(() => { if (isEditing) { inputRef.current?.focus(); inputRef.current?.select(); } }, [isEditing]); const commitRename = () => { const trimmed = editName.trim(); if (trimmed && trimmed !== scene.name) { onRename(trimmed); } else { setEditName(scene.name); } setIsEditing(false); }; const handleResizeStart = useCallback( (clientX: number) => { resizeRef.current = { startX: clientX, startDuration: scene.duration }; setResizeDuration(scene.duration); const handleMove = (event: globalThis.MouseEvent) => { const deltaSeconds = (event.clientX - resizeRef.current.startX) / pxPerSecond; setResizeDuration( clampSceneDuration(resizeRef.current.startDuration + deltaSeconds) ); }; const handleUp = (event: globalThis.MouseEvent) => { const deltaSeconds = (event.clientX - resizeRef.current.startX) / pxPerSecond; onDurationChange( clampSceneDuration(resizeRef.current.startDuration + deltaSeconds) ); setResizeDuration(null); document.removeEventListener("mousemove", handleMove); document.removeEventListener("mouseup", handleUp); }; document.addEventListener("mousemove", handleMove); document.addEventListener("mouseup", handleUp); }, [scene.duration, pxPerSecond, onDurationChange] ); return (
{/* ── Thumbnail block ── */}
{ if (event.key === "Enter" || event.key === " ") { event.preventDefault(); onSelect(); } }} className={cn( "group relative h-20 w-full cursor-pointer overflow-hidden rounded-lg", isActive ? "ring-2 ring-blue-500 ring-offset-1 ring-offset-gray-50" : "hover:ring-1 hover:ring-gray-300" )} > {/* Background: real thumbnail or gradient placeholder */} {scene.thumbnailUrl ? ( ) : (
)} {/* Dark overlay so text labels stay readable */}
{/* Duration badge — top-left */} {durationLabel} {/* Action buttons — top-right, revealed on hover */}
{canDelete ? ( ) : null}
{/* Duration bar — thin colored bar across the bottom */}
{/* Right-edge drag handle to resize duration */}
{ event.stopPropagation(); event.preventDefault(); handleResizeStart(event.clientX); }} className="absolute right-0 top-0 z-20 h-full w-2 cursor-ew-resize hover:bg-white/20" />
{/* ── Scene name below the block ── */} {isEditing ? ( event.stopPropagation()} onChange={(event) => setEditName(event.target.value)} onBlur={commitRename} onKeyDown={(event) => { if (event.key === "Enter") commitRename(); if (event.key === "Escape") { setEditName(scene.name); setIsEditing(false); } }} className="mt-1 w-full rounded border border-gray-300 bg-white px-1 py-0.5 text-[10px] text-gray-800 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-blue-500" aria-label={t("sceneNameLabel")} /> ) : (

{ event.preventDefault(); event.stopPropagation(); setIsEditing(true); }} title={t("doubleClickToRename")} > {scene.name}

)}
); }