fix
This commit is contained in:
parent
b64c0e7452
commit
b7887caf9d
|
|
@ -1,228 +0,0 @@
|
|||
import React, { useRef, useEffect, useState } from 'react'
|
||||
import { Play, Pause, SkipBack, SkipForward, Volume2, Scissors } from 'lucide-react'
|
||||
import { useProjectStore } from '../stores/useProjectStore'
|
||||
import { useMediaStore } from '../stores/useMediaStore'
|
||||
|
||||
interface TimelineProps {
|
||||
className?: string
|
||||
}
|
||||
|
||||
const Timeline: React.FC<TimelineProps> = ({ className = '' }) => {
|
||||
const timelineRef = useRef<HTMLDivElement>(null)
|
||||
const [isDragging, setIsDragging] = useState(false)
|
||||
const [dragStartX, setDragStartX] = useState(0)
|
||||
|
||||
const { currentProject, setTimelinePosition, setTimelineZoom } = useProjectStore()
|
||||
const {
|
||||
isPlaying,
|
||||
currentTime,
|
||||
duration,
|
||||
volume,
|
||||
setPlaying,
|
||||
setCurrentTime,
|
||||
setVolume
|
||||
} = useMediaStore()
|
||||
|
||||
const timeline = currentProject?.timeline || { duration: 0, zoom: 1, position: 0 }
|
||||
const videoTracks = currentProject?.video_tracks || []
|
||||
const audioTracks = currentProject?.audio_tracks || []
|
||||
|
||||
// Handle timeline scrubbing
|
||||
const handleTimelineClick = (e: React.MouseEvent) => {
|
||||
if (!timelineRef.current) return
|
||||
|
||||
const rect = timelineRef.current.getBoundingClientRect()
|
||||
const x = e.clientX - rect.left
|
||||
const percentage = x / rect.width
|
||||
const newTime = percentage * timeline.duration
|
||||
|
||||
setCurrentTime(newTime)
|
||||
setTimelinePosition(newTime)
|
||||
}
|
||||
|
||||
// Handle track dragging
|
||||
const handleTrackMouseDown = (e: React.MouseEvent, trackId: string, trackType: 'video' | 'audio') => {
|
||||
e.preventDefault()
|
||||
setIsDragging(true)
|
||||
setDragStartX(e.clientX)
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
if (!isDragging) return
|
||||
|
||||
const deltaX = e.clientX - dragStartX
|
||||
const timeDelta = (deltaX / (timelineRef.current?.clientWidth || 1)) * timeline.duration
|
||||
|
||||
// Update track position
|
||||
// This would update the track's start_time
|
||||
console.log(`Moving ${trackType} track ${trackId} by ${timeDelta}s`)
|
||||
}
|
||||
|
||||
const handleMouseUp = () => {
|
||||
setIsDragging(false)
|
||||
document.removeEventListener('mousemove', handleMouseMove)
|
||||
document.removeEventListener('mouseup', handleMouseUp)
|
||||
}
|
||||
|
||||
document.addEventListener('mousemove', handleMouseMove)
|
||||
document.addEventListener('mouseup', handleMouseUp)
|
||||
}
|
||||
|
||||
// Format time for display
|
||||
const formatTime = (seconds: number): string => {
|
||||
const mins = Math.floor(seconds / 60)
|
||||
const secs = Math.floor(seconds % 60)
|
||||
return `${mins}:${secs.toString().padStart(2, '0')}`
|
||||
}
|
||||
|
||||
// Calculate track position and width
|
||||
const getTrackStyle = (startTime: number, duration: number) => {
|
||||
const left = (startTime / timeline.duration) * 100
|
||||
const width = (duration / timeline.duration) * 100
|
||||
return {
|
||||
left: `${left}%`,
|
||||
width: `${width}%`
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`bg-secondary-800 border-t border-secondary-700 ${className}`}>
|
||||
{/* Timeline Controls */}
|
||||
<div className="flex items-center justify-between p-4 border-b border-secondary-700">
|
||||
<div className="flex items-center space-x-4">
|
||||
<button
|
||||
onClick={() => setCurrentTime(Math.max(0, currentTime - 10))}
|
||||
className="text-white hover:text-primary-400 transition-colors"
|
||||
>
|
||||
<SkipBack size={20} />
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => setPlaying(!isPlaying)}
|
||||
className="w-12 h-12 bg-primary-600 hover:bg-primary-700 rounded-full flex items-center justify-center text-white transition-colors"
|
||||
>
|
||||
{isPlaying ? <Pause size={20} /> : <Play size={20} />}
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => setCurrentTime(Math.min(duration, currentTime + 10))}
|
||||
className="text-white hover:text-primary-400 transition-colors"
|
||||
>
|
||||
<SkipForward size={20} />
|
||||
</button>
|
||||
|
||||
<div className="flex items-center space-x-2 ml-8">
|
||||
<Volume2 className="text-white" size={16} />
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value={volume}
|
||||
onChange={(e) => setVolume(parseFloat(e.target.value))}
|
||||
className="w-20"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-4">
|
||||
<span className="text-white text-sm">
|
||||
{formatTime(currentTime)} / {formatTime(timeline.duration)}
|
||||
</span>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-white text-sm">缩放:</span>
|
||||
<input
|
||||
type="range"
|
||||
min="0.1"
|
||||
max="5"
|
||||
step="0.1"
|
||||
value={timeline.zoom}
|
||||
onChange={(e) => setTimelineZoom(parseFloat(e.target.value))}
|
||||
className="w-20"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Timeline Ruler */}
|
||||
<div className="relative h-8 bg-secondary-900 border-b border-secondary-700">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
{Array.from({ length: Math.ceil(timeline.duration / 10) + 1 }, (_, i) => {
|
||||
const time = i * 10
|
||||
const left = (time / timeline.duration) * 100
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
className="absolute text-xs text-secondary-400"
|
||||
style={{ left: `${left}%` }}
|
||||
>
|
||||
{formatTime(time)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Playhead */}
|
||||
<div
|
||||
className="absolute top-0 bottom-0 w-0.5 bg-red-500 z-10"
|
||||
style={{ left: `${(currentTime / timeline.duration) * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Timeline Tracks */}
|
||||
<div
|
||||
ref={timelineRef}
|
||||
className="relative min-h-32 bg-secondary-100 overflow-y-auto"
|
||||
onClick={handleTimelineClick}
|
||||
>
|
||||
{/* Video Tracks */}
|
||||
{videoTracks.map((track, index) => (
|
||||
<div key={track.id} className="timeline-track">
|
||||
<div className="absolute left-0 top-0 bottom-0 w-24 bg-secondary-800 border-r border-secondary-700 flex items-center px-2">
|
||||
<span className="text-white text-sm truncate">视频 {index + 1}</span>
|
||||
</div>
|
||||
<div
|
||||
className="timeline-clip bg-primary-500 hover:bg-primary-600 cursor-pointer"
|
||||
style={getTrackStyle(track.start_time, track.duration)}
|
||||
onMouseDown={(e) => handleTrackMouseDown(e, track.id, 'video')}
|
||||
>
|
||||
<div className="p-2 text-white text-xs truncate">
|
||||
{track.name}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Audio Tracks */}
|
||||
{audioTracks.map((track, index) => (
|
||||
<div key={track.id} className="timeline-track">
|
||||
<div className="absolute left-0 top-0 bottom-0 w-24 bg-secondary-800 border-r border-secondary-700 flex items-center px-2">
|
||||
<span className="text-white text-sm truncate">音频 {index + 1}</span>
|
||||
</div>
|
||||
<div
|
||||
className="timeline-clip bg-green-500 hover:bg-green-600 cursor-pointer"
|
||||
style={getTrackStyle(track.start_time, track.duration)}
|
||||
onMouseDown={(e) => handleTrackMouseDown(e, track.id, 'audio')}
|
||||
>
|
||||
<div className="p-2 text-white text-xs truncate">
|
||||
{track.name}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Empty state */}
|
||||
{videoTracks.length === 0 && audioTracks.length === 0 && (
|
||||
<div className="flex items-center justify-center h-32 text-secondary-500">
|
||||
<div className="text-center">
|
||||
<Scissors size={32} className="mx-auto mb-2" />
|
||||
<p>拖拽媒体文件到这里开始编辑</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Timeline
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react'
|
||||
import { Download, Save, Scissors } from 'lucide-react'
|
||||
import VideoPreview from '../components/VideoPreview'
|
||||
import Timeline from '../components/Timeline'
|
||||
import MediaLibrary from '../components/MediaLibrary'
|
||||
import { useProjectStore } from '../stores/useProjectStore'
|
||||
|
||||
|
|
@ -71,9 +70,6 @@ const EditorPage: React.FC = () => {
|
|||
<div className="flex-1 bg-black p-4">
|
||||
<VideoPreview className="w-full h-full" />
|
||||
</div>
|
||||
|
||||
{/* Timeline */}
|
||||
<Timeline className="h-48" />
|
||||
</div>
|
||||
|
||||
{/* Properties Panel */}
|
||||
|
|
|
|||
Loading…
Reference in New Issue