import React, { useCallback, useRef, useState } from "react" import { cn } from "@/lib/utils" import { useSubtitleStyles } from "../../hooks/use-subtitle-styles" import { useTimeline } from "../../hooks/use-timeline" import { useTimelineSelection } from "../../hooks/use-timeline-selection" import { SubtitleClip as SubtitleClipType } from "../../types/timeline" interface SubtitleClipProps { clip: SubtitleClipType trackHeight: number isSelected?: boolean isDragging?: boolean onMouseDown?: (e: React.MouseEvent) => void onDoubleClick?: (e: React.MouseEvent) => void } export function SubtitleClip({ clip, trackHeight, isSelected = false, isDragging = false, onMouseDown, onDoubleClick, }: SubtitleClipProps) { const { uiState, updateClip } = useTimeline() const { selectClip } = useTimelineSelection() const { getComputedStyle, getStyleById } = useSubtitleStyles() // Получаем pixelsPerSecond из uiState.timeScale const pixelsPerSecond = uiState.timeScale // Расчет позиции и размера const left = clip.startTime * pixelsPerSecond const width = clip.duration * pixelsPerSecond // Обработка клика const handleClick = (e: React.MouseEvent) => { e.stopPropagation() selectClip(clip.id) } // Получение стиля субтитров const style = getComputedStyle(clip.subtitleStyleId, clip.formatting) const subtitleStyle = clip.subtitleStyleId ? getStyleById(clip.subtitleStyleId) : undefined // Определение цвета клипа на timeline в зависимости от стиля const getClipBackgroundColor = () => { if (subtitleStyle) { // Генерируем цвет на основе имени стиля для визуального различия const hash = subtitleStyle.name.split("").reduce((a, b) => { a = (a << 5) - a + b.charCodeAt(0) return a & a }, 0) const hue = Math.abs(hash) % 360 return `hsl(${hue} 70% 50%)` } return "hsl(25 95% 50%)" // Оранжевый для субтитров без стиля } // Состояние для отслеживания изменения размера const [isResizing, setIsResizing] = useState(false) const resizeType = useRef<"start" | "end" | null>(null) const initialMouseX = useRef(0) const initialStartTime = useRef(0) const initialDuration = useRef(0) // Обработчики изменения размера const handleResizeStart = useCallback( (e: MouseEvent) => { if (!resizeType.current) return e.preventDefault() const deltaX = e.clientX - initialMouseX.current const deltaTime = deltaX / pixelsPerSecond let newStartTime = initialStartTime.current let newDuration = initialDuration.current if (resizeType.current === "start") { // Изменение начала клипа newStartTime = Math.max(0, initialStartTime.current + deltaTime) newDuration = initialDuration.current - (newStartTime - initialStartTime.current) // Минимальная длительность 0.1 секунды if (newDuration < 0.1) { newDuration = 0.1 newStartTime = initialStartTime.current + initialDuration.current - 0.1 } } else if (resizeType.current === "end") { // Изменение конца клипа newDuration = Math.max(0.1, initialDuration.current + deltaTime) } // Обновляем клип void updateClip(clip.id, { startTime: newStartTime, duration: newDuration, mediaEndTime: clip.mediaStartTime + newDuration, }) }, [clip.id, clip.mediaStartTime, pixelsPerSecond, updateClip], ) const handleResizeEnd = useCallback(() => { setIsResizing(false) resizeType.current = null document.removeEventListener("mousemove", handleResizeStart) document.removeEventListener("mouseup", handleResizeEnd) }, [handleResizeStart]) const startResize = useCallback( (type: "start" | "end", e: React.MouseEvent) => { e.stopPropagation() setIsResizing(true) resizeType.current = type initialMouseX.current = e.clientX initialStartTime.current = clip.startTime initialDuration.current = clip.duration document.addEventListener("mousemove", handleResizeStart) document.addEventListener("mouseup", handleResizeEnd) }, [clip.startTime, clip.duration, handleResizeStart, handleResizeEnd], ) // Обрезка текста если он слишком длинный const truncateText = (text: string, maxLength: number) => { if (text.length <= maxLength) return text return `${text.substring(0, maxLength - 3)}...` } return (
{truncateText(clip.text, 50)}
{subtitleStyle && width > 120 && ({subtitleStyle.name}
)}