This commit is contained in:
root 2025-07-10 23:35:20 +08:00
parent b64c0e7452
commit b7887caf9d
2 changed files with 0 additions and 232 deletions

View File

@ -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

View File

@ -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 */}