From 421bf9e2e106724d8c206efd1d8a8b3324a9f0a6 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 30 Jul 2026 07:14:09 +0800 Subject: [PATCH] =?UTF-8?q?refactor(TimelinePanel):=20=E4=B8=BB=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E7=B2=BE=E7=AE=80=EF=BC=8C=E6=8A=BDHeader/ClipTrack/P?= =?UTF-8?q?layheadDrag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/TimelinePanel.tsx | 225 ++++++------------ 1 file changed, 67 insertions(+), 158 deletions(-) diff --git a/apps/web/src/pages/editing-planner/components/TimelinePanel.tsx b/apps/web/src/pages/editing-planner/components/TimelinePanel.tsx index f9e1f35ec..b086a459c 100644 --- a/apps/web/src/pages/editing-planner/components/TimelinePanel.tsx +++ b/apps/web/src/pages/editing-planner/components/TimelinePanel.tsx @@ -1,22 +1,32 @@ /** * 水平轨道时间线 — 支持裁剪手柄、分割、右键菜单 - * 时间标尺(20px) + 水平片段卡片轨道(100x100) + HTML5拖拽排序 - * "+" 卡片 → 类型+时长选择器 + * 时间标尺 + 片段轨道 + HTML5拖拽排序 * - * 裁剪交互: - * - 鼠标悬停片段两端显示拖拽手柄,拖动调整入点/出点 - * - 拖动时实时显示裁剪预览(入点/出点/时长) - * - 右键片段弹出菜单:分割 / 恢复原始长度 / 删除 + * 子组件(timeline/): + * - TimeRuler - 时间标尺 + * - ClipCard - 片段卡片 + * - ClipTrack - 片段轨道(播放头+片段列表+添加卡片) + * - TimelineHeader - 时间线头部(标题+缩放+操作按钮) + * - AddClipPicker - 添加片段选择器 + * - TrimPreview - 裁剪预览 tooltip + * - ContextMenu - 右键菜单 + * + * Hooks: + * - useClipDrag - 片段拖拽排序 + * - useTrimDrag - 裁剪拖拽 + * - useTimelineMenus - 菜单 & 面板 + * - usePlayheadDrag - 播放头拖拽 */ -import React, { useState, useRef, useCallback, useEffect } from "react" +import React from "react" import type { ClipData, ClipType, TrimConfig } from "../types" import { DEFAULT_PIXELS_PER_SECOND } from "../constants/timeline" -import { formatTime } from "../utils/timeline" import { useClipDrag } from "../hooks/useClipDrag" import { useTrimDrag } from "../hooks/useTrimDrag" import { useTimelineMenus } from "../hooks/useTimelineMenus" -import { ClipCard } from "./timeline/ClipCard" +import { usePlayheadDrag } from "./timeline/hooks/usePlayheadDrag" import { TimeRuler } from "./timeline/TimeRuler" +import { ClipTrack } from "./timeline/ClipTrack" +import { TimelineHeader } from "./timeline/TimelineHeader" import { AddClipPicker } from "./timeline/AddClipPicker" import { TrimPreview } from "./timeline/TrimPreview" import { ContextMenu } from "./timeline/ContextMenu" @@ -105,106 +115,32 @@ const TimelinePanel: React.FC = ({ setHoveredClipId, } = useTimelineMenus(clips, currentMode, onAddClip, onClipSplit, onClipResetTrim, onClipRemove) - /* ── 播放头拖拽状态 ── */ - const [playheadDragging, setPlayheadDragging] = useState(false) - const trackRef = useRef(null) + /* ── 播放头拖拽 ── */ + const { trackRef, handlePlayheadMouseDown, handleRulerClick } = usePlayheadDrag({ + currentTime, + pps, + totalDuration, + onSeek, + }) - /* ── 播放头拖拽全局 mousemove/mouseup ── */ - useEffect(() => { - if (!playheadDragging) return - - const handleMouseMove = (e: MouseEvent) => { - const trackEl = trackRef.current - if (!trackEl) return - const rect = trackEl.getBoundingClientRect() - const x = e.clientX - rect.left + trackEl.scrollLeft - const time = Math.max(0, Math.min(x / pps, totalDuration)) - onSeek?.(Math.round(time * 10) / 10) + /* ── 撤销最后一个片段 ── */ + const handleUndoClip = () => { + if (clips.length > 1) { + const last = clips[clips.length - 1] + onClipRemove(last.id) } - - const handleMouseUp = () => { - setPlayheadDragging(false) - } - - document.addEventListener("mousemove", handleMouseMove) - document.addEventListener("mouseup", handleMouseUp) - return () => { - document.removeEventListener("mousemove", handleMouseMove) - document.removeEventListener("mouseup", handleMouseUp) - } - }, [playheadDragging, pps, totalDuration, onSeek]) - - /* ── 标尺点击跳转播放头 ── */ - const handleRulerClick = useCallback( - (e: React.MouseEvent) => { - const rect = e.currentTarget.getBoundingClientRect() - const x = e.clientX - rect.left - const time = Math.max(0, Math.min(x / pps, totalDuration)) - onSeek?.(Math.round(time * 10) / 10) - }, - [pps, totalDuration, onSeek], - ) - - /* ── 播放头拖拽开始 ── */ - const handlePlayheadMouseDown = useCallback((e: React.MouseEvent) => { - e.preventDefault() - e.stopPropagation() - setPlayheadDragging(true) - }, []) + } return (
{/* 时间线头部 */} -
-
- 🎬 时间线 - 总时长: {formatTime(totalDuration)} -
-
- {/* 缩放控件 */} -
- - onZoomChange?.(Number(e.target.value))} - /> - - {pps}px/s -
- - -
-
+ {/* 一镜到底提示 */} {currentMode === "one_take" &&
🎥 一镜到底模式无片段
} @@ -216,61 +152,33 @@ const TimelinePanel: React.FC = ({ {/* 水平片段轨道 */} {currentMode !== "one_take" && ( -
- {/* 播放头 */} - {currentMode !== "one_take" && totalDuration > 0 && ( -
-
-
- )} - {clips.length === 0 ? ( -
-
🎬
-
点击右侧 + 添加片段
-
- ) : ( - clips.map((clip, idx) => ( - setHoveredClipId(clip.id)} - onMouseLeave={() => setHoveredClipId(null)} - onTrimHandleMouseDown={handleTrimHandleMouseDown} - onRemove={onClipRemove} - /> - )) - )} - - {/* ── 轨道末尾 "+" 添加卡片 ── */} -
-
- + -
-
-
+ setHoveredClipId(null)} + onTrimHandleMouseDown={handleTrimHandleMouseDown} + onClipRemove={onClipRemove} + onTogglePicker={handleTogglePicker} + /> )} {/* 裁剪预览 tooltip */} @@ -315,3 +223,4 @@ const TimelinePanel: React.FC = ({ } export default TimelinePanel +