@@ -1,200 +0,0 @@
|
||||
/**
|
||||
* 水平轨道时间线 — 支持裁剪手柄、分割、右键菜单
|
||||
* 时间标尺 + 片段轨道 + HTML5拖拽排序
|
||||
*
|
||||
* 子组件(timeline/):
|
||||
* - TimeRuler - 时间标尺
|
||||
* - ClipCard - 片段卡片
|
||||
* - ClipTrack - 片段轨道(播放头+片段列表+添加卡片)
|
||||
* - TimelineHeader - 时间线头部(标题+缩放+操作按钮)
|
||||
* - TrimPreview - 裁剪预览 tooltip
|
||||
* - ContextMenu - 右键菜单
|
||||
*
|
||||
* Hooks:
|
||||
* - useClipDrag - 片段拖拽排序
|
||||
* - useTrimDrag - 裁剪拖拽
|
||||
* - useTimelineMenus - 菜单 & 面板
|
||||
* - usePlayheadDrag - 播放头拖拽
|
||||
*/
|
||||
import React from "react"
|
||||
import type { ClipData, ClipType, TrimConfig } from "../types"
|
||||
import { DEFAULT_PIXELS_PER_SECOND } from "../constants/timeline"
|
||||
import { useClipDrag } from "../hooks/useClipDrag"
|
||||
import { useTrimDrag } from "../hooks/useTrimDrag"
|
||||
import { useTimelineMenus } from "../hooks/useTimelineMenus"
|
||||
import { usePlayheadDrag } from "./timeline/hooks/usePlayheadDrag"
|
||||
import { TimeRuler } from "./timeline/TimeRuler"
|
||||
import { ClipTrack } from "./timeline/ClipTrack"
|
||||
import { TimelineHeader } from "./timeline/TimelineHeader"
|
||||
import { TrimPreview } from "./timeline/TrimPreview"
|
||||
import { ContextMenu } from "./timeline/ContextMenu"
|
||||
|
||||
interface TimelinePanelProps {
|
||||
clips: ClipData[]
|
||||
selectedClipId: string | null
|
||||
currentMode: string
|
||||
onClipSelect: (clipId: string) => void
|
||||
onClipReorder: (fromIdx: number, toIdx: number) => void
|
||||
onClipRemove: (clipId: string) => void
|
||||
onAddClip: (type: ClipType, duration: number) => void
|
||||
/** 裁剪更新:调整片段的 trim_config 和 duration */
|
||||
onClipTrim?: (clipId: string, trimConfig: TrimConfig, newDuration: number) => void
|
||||
/** 在指定位置分割片段 */
|
||||
onClipSplit?: (clipId: string, splitRatio: number) => void
|
||||
/** 恢复片段原始长度 */
|
||||
onClipResetTrim?: (clipId: string) => void
|
||||
/** 当前播放时间(秒) */
|
||||
currentTime?: number
|
||||
/** 缩放:每秒像素数 */
|
||||
pixelsPerSecond?: number
|
||||
/** 缩放变更回调 */
|
||||
onZoomChange?: (pps: number) => void
|
||||
/** 播放头跳转回调 */
|
||||
onSeek?: (time: number) => void
|
||||
/** 总时长(秒),可选(默认由 clips 计算) */
|
||||
totalDuration?: number
|
||||
}
|
||||
|
||||
const TimelinePanel: React.FC<TimelinePanelProps> = ({
|
||||
clips,
|
||||
selectedClipId,
|
||||
currentMode,
|
||||
onClipSelect,
|
||||
onClipReorder,
|
||||
onClipRemove,
|
||||
onAddClip,
|
||||
onClipTrim,
|
||||
onClipSplit,
|
||||
onClipResetTrim,
|
||||
currentTime = 0,
|
||||
pixelsPerSecond = 40,
|
||||
onZoomChange,
|
||||
onSeek,
|
||||
totalDuration: totalDurationProp,
|
||||
}) => {
|
||||
/* ── 缩放 & 时长 ── */
|
||||
const pps = pixelsPerSecond ?? DEFAULT_PIXELS_PER_SECOND
|
||||
const totalDuration = totalDurationProp ?? clips.reduce((s, c) => s + c.duration, 0)
|
||||
|
||||
/* ── 裁剪拖拽 ── */
|
||||
const { trimDrag, trimPreview, handleTrimHandleMouseDown } = useTrimDrag(clips, pps, onClipTrim)
|
||||
|
||||
/* ── 片段拖拽排序 ── */
|
||||
const {
|
||||
dragIdx,
|
||||
dragOverIdx,
|
||||
handleDragStart,
|
||||
handleDragOver,
|
||||
handleDragEnd,
|
||||
handleDrop,
|
||||
handleEmptyDragOver,
|
||||
} = useClipDrag(onClipReorder, !!trimDrag)
|
||||
|
||||
/* ── 菜单 & 面板 ── */
|
||||
const {
|
||||
contextMenu,
|
||||
contextMenuRef,
|
||||
handleContextMenu,
|
||||
handleContextSplit,
|
||||
handleContextResetTrim,
|
||||
handleContextDelete,
|
||||
addCardRef,
|
||||
handleConfirmAdd,
|
||||
hoveredClipId,
|
||||
setHoveredClipId,
|
||||
} = useTimelineMenus(clips, currentMode, onAddClip, onClipSplit, onClipResetTrim, onClipRemove)
|
||||
|
||||
/* ── 播放头拖拽 ── */
|
||||
const { trackRef, handlePlayheadMouseDown, handleRulerClick } = usePlayheadDrag({
|
||||
currentTime,
|
||||
pps,
|
||||
totalDuration,
|
||||
onSeek,
|
||||
})
|
||||
|
||||
/* ── 撤销最后一个片段 ── */
|
||||
const handleUndoClip = () => {
|
||||
if (clips.length > 1) {
|
||||
const last = clips[clips.length - 1]
|
||||
onClipRemove(last.id)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ep-timeline-area">
|
||||
{/* 时间线头部 */}
|
||||
<TimelineHeader
|
||||
totalDuration={totalDuration}
|
||||
pps={pps}
|
||||
onZoomChange={onZoomChange}
|
||||
onUndoClip={handleUndoClip}
|
||||
clipsCount={clips.length}
|
||||
/>
|
||||
|
||||
{/* 一镜到底提示 */}
|
||||
{currentMode === "one_take" && <div className="ep-one-take-hint">🎥 一镜到底模式无片段</div>}
|
||||
|
||||
{/* 时间标尺 */}
|
||||
{currentMode !== "one_take" && (
|
||||
<TimeRuler totalDuration={totalDuration} pps={pps} onClick={handleRulerClick} />
|
||||
)}
|
||||
|
||||
{/* 水平片段轨道 */}
|
||||
{currentMode !== "one_take" && (
|
||||
<ClipTrack
|
||||
clips={clips}
|
||||
selectedClipId={selectedClipId}
|
||||
pps={pps}
|
||||
currentTime={currentTime}
|
||||
totalDuration={totalDuration}
|
||||
dragIdx={dragIdx}
|
||||
dragOverIdx={dragOverIdx}
|
||||
hoveredClipId={hoveredClipId}
|
||||
trimDragActive={!!trimDrag}
|
||||
showTrimHandles={!!onClipTrim}
|
||||
trackRef={trackRef}
|
||||
addCardRef={addCardRef}
|
||||
onPlayheadMouseDown={handlePlayheadMouseDown}
|
||||
onClipSelect={onClipSelect}
|
||||
onDragStart={handleDragStart}
|
||||
onDragOver={handleDragOver}
|
||||
onDragEnd={handleDragEnd}
|
||||
onDrop={handleDrop}
|
||||
onEmptyDragOver={handleEmptyDragOver}
|
||||
onContextMenu={handleContextMenu}
|
||||
onClipMouseEnter={setHoveredClipId}
|
||||
onClipMouseLeave={() => setHoveredClipId(null)}
|
||||
onTrimHandleMouseDown={handleTrimHandleMouseDown}
|
||||
onClipRemove={onClipRemove}
|
||||
onTogglePicker={handleConfirmAdd}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 裁剪预览 tooltip */}
|
||||
{trimPreview && (
|
||||
<TrimPreview
|
||||
startTime={trimPreview.startTime}
|
||||
endTime={trimPreview.endTime}
|
||||
duration={trimPreview.duration}
|
||||
x={trimPreview.x}
|
||||
y={trimPreview.y}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 右键菜单 */}
|
||||
{contextMenu && (
|
||||
<ContextMenu
|
||||
x={contextMenu.x}
|
||||
y={contextMenu.y}
|
||||
menuRef={contextMenuRef}
|
||||
hasTrim={!!clips.find((c) => c.id === contextMenu.clipId)?.trim_config}
|
||||
onSplit={handleContextSplit}
|
||||
onResetTrim={handleContextResetTrim}
|
||||
onDelete={handleContextDelete}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TimelinePanel
|
||||
Reference in New Issue
Block a user