import React, { useState, useEffect } from 'react'; import { TaskSegment, Interaction, InteractionType } from '../types'; import { MousePointerClick, Type, Pencil } from './Icons'; type HighlightPoint = { x: number; y: number; isEditing: boolean } | null; type CoordinatePickerCallback = ((coords: { x: number; y: number }) => void) | null; const interactionIcons: Record = { click: , type: , }; interface TaskSegmentCardProps { task: TaskSegment; videoDuration: number; totalFrames: number; onSeekToTime?: (time: number) => void; onUpdateInteraction: (taskId: number, interactionIndex: number, updatedInteraction: Interaction) => void; onHighlightPoint: (point: HighlightPoint) => void; onSetCoordinatePicker: (callback: CoordinatePickerCallback) => void; } const formatTime = (seconds: number) => { if (isNaN(seconds) || seconds < 0) return '0.0s'; return `${seconds.toFixed(1)}s`; }; const formatCoords = (x?: number, y?: number) => { if (x === undefined || y === undefined) return ''; return `(x: ${x.toFixed(2)}, y: ${y.toFixed(2)})`; } export const TaskSegmentCard: React.FC = ({ task, videoDuration, totalFrames, onSeekToTime, onUpdateInteraction, onHighlightPoint, onSetCoordinatePicker }) => { const [editingIndex, setEditingIndex] = useState(null); const [editingInteractionType, setEditingInteractionType] = useState(null); const [editDetails, setEditDetails] = useState(''); const [editTime, setEditTime] = useState(''); const [editX, setEditX] = useState(''); const [editY, setEditY] = useState(''); // Live seek video when editing timestamp useEffect(() => { if (editingIndex !== null && onSeekToTime) { const timeInSeconds = parseFloat(editTime); if (!isNaN(timeInSeconds) && timeInSeconds >= 0 && timeInSeconds <= videoDuration) { onSeekToTime(timeInSeconds); } } }, [editTime, editingIndex, onSeekToTime, videoDuration]); // Live update highlight marker when editing coordinates useEffect(() => { if (editingInteractionType === 'click' && editingIndex !== null) { const x = parseFloat(editX); const y = parseFloat(editY); if (!isNaN(x) && !isNaN(y)) { onHighlightPoint({ x, y, isEditing: true }); } } }, [editX, editY, editingInteractionType, editingIndex, onHighlightPoint]); const calculateTime = (frameIndex: number): number | null => { if (!videoDuration || !totalFrames || videoDuration === 0 || totalFrames === 0) return null; return (frameIndex / totalFrames) * videoDuration; }; const handleInteractionClick = (interaction: Interaction) => { const time = calculateTime(interaction.frameIndex); if (time !== null && onSeekToTime) { onSeekToTime(time); } }; const handleEditClick = (interaction: Interaction, index: number) => { setEditingIndex(index); setEditingInteractionType(interaction.type); setEditDetails(interaction.details); const time = calculateTime(interaction.frameIndex); setEditTime(time !== null ? time.toFixed(1) : ''); if (interaction.type === 'click') { setEditX(interaction.x?.toFixed(4) || '0.5'); setEditY(interaction.y?.toFixed(4) || '0.5'); onSetCoordinatePicker((coords) => { setEditX(coords.x.toFixed(4)); setEditY(coords.y.toFixed(4)); }); } }; const handleCancelEdit = () => { setEditingIndex(null); setEditingInteractionType(null); setEditDetails(''); setEditTime(''); setEditX(''); setEditY(''); onHighlightPoint(null); onSetCoordinatePicker(null); }; const handleSaveEdit = () => { if (editingIndex === null) return; const timeInSeconds = parseFloat(editTime); if (isNaN(timeInSeconds) || !videoDuration || !totalFrames) { console.error("Cannot save edit due to invalid time or video data."); return; } const newFrameIndex = Math.round((timeInSeconds / videoDuration) * totalFrames); const originalInteraction = task.interactions[editingIndex]; const updatedInteraction: Interaction = { ...originalInteraction, details: editDetails, frameIndex: newFrameIndex, }; if (updatedInteraction.type === 'click') { updatedInteraction.x = parseFloat(editX) || 0; updatedInteraction.y = parseFloat(editY) || 0; } onUpdateInteraction(task.id, editingIndex, updatedInteraction); handleCancelEdit(); }; const handleInteractionMouseEnter = (interaction: Interaction) => { if (interaction.type === 'click' && interaction.x !== undefined && interaction.y !== undefined) { onHighlightPoint({ x: interaction.x, y: interaction.y, isEditing: false }); } }; const handleInteractionMouseLeave = () => { onHighlightPoint(null); }; const segmentStartTime = formatTime(calculateTime(task.startFrame) ?? 0); const segmentEndTime = formatTime(calculateTime(task.endFrame) ?? 0); return (
{task.id}

{task.description}

{(calculateTime(task.startFrame) !== null) && ( {segmentStartTime} - {segmentEndTime} )}
{task.interactions && task.interactions.length > 0 && (
{task.interactions.map((interaction, index) => { if (editingIndex === index) { // EDITING VIEW return (
{interactionIcons[interaction.type] ||
}
setEditDetails(e.target.value)} className="w-full bg-slate-900 border border-slate-600 rounded-md px-2 py-1 text-sm text-slate-200 focus:ring-1 focus:ring-indigo-400 focus:border-indigo-400" />
setEditTime(e.target.value)} className="w-full bg-slate-900 border border-slate-600 rounded-md px-2 py-1 text-sm text-slate-200 focus:ring-1 focus:ring-indigo-400 focus:border-indigo-400" />
{interaction.type === 'click' && ( <>
setEditX(e.target.value)} className="w-full bg-slate-900 border border-slate-600 rounded-md px-2 py-1 text-sm text-slate-200 focus:ring-1 focus:ring-indigo-400 focus:border-indigo-400" />
setEditY(e.target.value)} className="w-full bg-slate-900 border border-slate-600 rounded-md px-2 py-1 text-sm text-slate-200 focus:ring-1 focus:ring-indigo-400 focus:border-indigo-400" />
)}
{interaction.type === 'click' && (

Click on the video player to update coordinates.

)}
); } // DISPLAY VIEW const interactionTime = calculateTime(interaction.frameIndex); const interactionCoords = formatCoords(interaction.x, interaction.y); return (
handleInteractionMouseEnter(interaction)} onMouseLeave={handleInteractionMouseLeave} className="group flex items-start gap-3 p-1.5 -mx-1.5 rounded-md transition-colors hover:bg-slate-800/60" >
handleInteractionClick(interaction)} role="button" tabIndex={0} onKeyPress={(e) => (e.key === 'Enter' || e.key === ' ') && handleInteractionClick(interaction)} > {interactionIcons[interaction.type] ||
}
handleInteractionClick(interaction)} role="button" > {interaction.details} {interaction.type === 'click' && interactionCoords && ( {interactionCoords} )}
{interactionTime !== null && ( (~{formatTime(interactionTime)}) )}
); })}
)}
); };