import { useState, useCallback, useMemo } from "react" import { Modal, message } from "antd" import type { ClipData, ClipType, TransitionConfig, SpeedConfig, TtsConfig, TrimConfig, } from "../types" import type { AssetItem } from "@/api/assets" interface UseClipOperationsParams { clips: ClipData[] setClips: (updater: (prev: ClipData[]) => ClipData[]) => void } /** * 片段操作 Hook * 片段增删改查、排序、裁剪、分割、转场、调速、TTS、配音选择 */ export const useClipOperations = ({ clips, setClips }: UseClipOperationsParams) => { const [selectedClipId, setSelectedClipId] = useState(null) const selectedClip = useMemo( () => clips.find((c) => c.id === selectedClipId) || null, [clips, selectedClipId], ) /* ── 选中 / 重排 / 删除 ── */ const handleClipSelect = useCallback((clipId: string) => { setSelectedClipId(clipId) }, []) const handleClipReorder = useCallback( (fromIdx: number, toIdx: number) => { setClips((prev) => { const updated = [...prev] const [moved] = updated.splice(fromIdx, 1) updated.splice(toIdx, 0, moved) return updated.map((c, i) => ({ ...c, order: i })) }) }, [setClips], ) const handleClipRemove = useCallback( (clipId: string) => { Modal.confirm({ title: "删除片段", content: "确定要删除这个片段吗?此操作可通过撤销恢复。", okText: "删除", okType: "danger", cancelText: "取消", onOk: () => { setClips((prev) => prev.filter((c) => c.id !== clipId)) if (selectedClipId === clipId) setSelectedClipId(null) }, }) }, [setClips, selectedClipId], ) const handleClipUpdate = useCallback( (clipId: string, data: Partial) => { setClips((prev) => prev.map((c) => (c.id === clipId ? { ...c, ...data } : c))) }, [setClips], ) /** * 添加片段(不绑定任何素材) * 片段 = 时间规划 + 类型标记 */ const handleAddClip = useCallback( (type: ClipType, duration: number) => { const newClip: ClipData = { id: `clip-${Date.now()}`, type, duration, startOffset: 0, order: clips.length, } setClips((prev) => [...prev, newClip]) }, [clips.length, setClips], ) /* ── 裁剪 / 分割 / 重置 ── */ const handleClipTrim = useCallback( (clipId: string, trimConfig: TrimConfig, newDuration: number) => { setClips((prev) => prev.map((c) => c.id === clipId ? { ...c, trim_config: trimConfig, duration: newDuration } : c, ), ) }, [setClips], ) const handleClipSplit = useCallback( (clipId: string, splitRatio: number) => { setClips((prev) => { const idx = prev.findIndex((c) => c.id === clipId) if (idx === -1) return prev const clip = prev[idx] const splitPoint = Math.round(clip.duration * splitRatio * 10) / 10 if (splitPoint < 0.5 || splitPoint >= clip.duration - 0.5) return prev // 前半段 const firstHalf: ClipData = { ...clip, duration: splitPoint, trim_config: clip.trim_config ? { ...clip.trim_config, end_time: clip.trim_config.start_time + splitPoint, } : undefined, } // 后半段 const secondHalf: ClipData = { ...clip, id: `clip-${Date.now()}`, duration: clip.duration - splitPoint, startOffset: clip.startOffset + splitPoint, trim_config: clip.trim_config ? { ...clip.trim_config, start_time: clip.trim_config.start_time + splitPoint, } : undefined, order: (clip.order ?? idx) + 1, } const updated = [...prev] updated[idx] = firstHalf updated.splice(idx + 1, 0, secondHalf) return updated.map((c, i) => ({ ...c, order: i })) }) }, [setClips], ) const handleClipResetTrim = useCallback( (clipId: string) => { setClips((prev) => prev.map((c) => { if (c.id !== clipId || !c.trim_config) return c const originalDuration = c.trim_config.original_duration ?? c.duration return { ...c, duration: originalDuration, trim_config: undefined, } }), ) }, [setClips], ) /* ── 转场 / 调速 / TTS ── */ const handleTransitionChange = useCallback( (targetClipId: string | null, config: TransitionConfig) => { if (targetClipId) { handleClipUpdate(targetClipId, { transition: config }) } // 同时更新全局默认转场(供新片段使用)—— 暂未实现全局默认 }, [handleClipUpdate], ) const handleSpeedChange = useCallback( (targetClipId: string | null, config: SpeedConfig) => { if (targetClipId) { handleClipUpdate(targetClipId, { speed: config }) } }, [handleClipUpdate], ) const handleApplySpeedAll = useCallback( (config: SpeedConfig) => { setClips((prev) => prev.map((c) => ({ ...c, speed: { ...config } }))) message.success("已应用到所有片段") }, [setClips], ) const handleTtsChange = useCallback( (targetClipId: string | null, ttsConfig: TtsConfig) => { if (!targetClipId) return handleClipUpdate(targetClipId, { tts_config: ttsConfig }) }, [handleClipUpdate], ) /** 为片段选择配音素材 */ const handleClipVoiceSelect = useCallback( (clipId: string, asset: AssetItem | null) => { setClips((prev) => prev.map((c) => c.id === clipId ? { ...c, voice_asset_id: asset?.id ?? undefined, voice_file_url: asset?.file_url ?? undefined, } : c, ), ) }, [setClips], ) return { selectedClipId, setSelectedClipId, selectedClip, handleClipSelect, handleClipReorder, handleClipRemove, handleClipUpdate, handleAddClip, handleClipTrim, handleClipSplit, handleClipResetTrim, handleTransitionChange, handleSpeedChange, handleApplySpeedAll, handleTtsChange, handleClipVoiceSelect, } }