diff --git a/apps/web/package.json b/apps/web/package.json index eb41cb3..bc12e24 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -21,7 +21,6 @@ "@hookform/resolvers": "^3.9.1", "@opencut/auth": "workspace:*", "@opencut/db": "workspace:*", - "@types/pg": "^8.15.4", "@upstash/ratelimit": "^2.0.5", "@upstash/redis": "^1.35.0", "@vercel/analytics": "^1.4.1", @@ -57,6 +56,7 @@ "zustand": "^5.0.2" }, "devDependencies": { + "@types/pg": "^8.15.4", "@types/bun": "latest", "@types/react": "^18.2.48", "@types/react-dom": "^18.2.18", diff --git a/apps/web/src/components/editor/audio-waveform.tsx b/apps/web/src/components/editor/audio-waveform.tsx new file mode 100644 index 0000000..75a5ae0 --- /dev/null +++ b/apps/web/src/components/editor/audio-waveform.tsx @@ -0,0 +1,115 @@ +import React, { useEffect, useRef, useState } from 'react'; +import WaveSurfer from 'wavesurfer.js'; + +interface AudioWaveformProps { + audioUrl: string; + height?: number; + className?: string; +} + +const AudioWaveform: React.FC = ({ + audioUrl, + height = 32, + className = '' +}) => { + const waveformRef = useRef(null); + const wavesurfer = useRef(null); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(false); + + useEffect(() => { + let mounted = true; + + const initWaveSurfer = async () => { + if (!waveformRef.current || !audioUrl) return; + + try { + // Clean up any existing instance + if (wavesurfer.current) { + try { + wavesurfer.current.destroy(); + } catch (e) { + // Silently ignore destroy errors + } + wavesurfer.current = null; + } + + wavesurfer.current = WaveSurfer.create({ + container: waveformRef.current, + waveColor: 'rgba(255, 255, 255, 0.6)', + progressColor: 'rgba(255, 255, 255, 0.9)', + cursorColor: 'transparent', + barWidth: 2, + barGap: 1, + height: height, + normalize: true, + interact: false, + }); + + // Event listeners + wavesurfer.current.on('ready', () => { + if (mounted) { + setIsLoading(false); + setError(false); + } + }); + + wavesurfer.current.on('error', (err) => { + console.error('WaveSurfer error:', err); + if (mounted) { + setError(true); + setIsLoading(false); + } + }); + + await wavesurfer.current.load(audioUrl); + + } catch (err) { + console.error('Failed to initialize WaveSurfer:', err); + if (mounted) { + setError(true); + setIsLoading(false); + } + } + }; + + initWaveSurfer(); + + return () => { + mounted = false; + if (wavesurfer.current) { + try { + wavesurfer.current.destroy(); + } catch (e) { + // Silently ignore destroy errors + } + wavesurfer.current = null; + } + }; + }, [audioUrl, height]); + + if (error) { + return ( +
+ Audio unavailable +
+ ); + } + + return ( +
+ {isLoading && ( +
+ Loading... +
+ )} +
+
+ ); +}; + +export default AudioWaveform; \ No newline at end of file diff --git a/apps/web/src/components/editor/media-panel.tsx b/apps/web/src/components/editor/media-panel.tsx index 95cfa0a..f2b3f65 100644 --- a/apps/web/src/components/editor/media-panel.tsx +++ b/apps/web/src/components/editor/media-panel.tsx @@ -84,17 +84,20 @@ export function MediaPanel() { useEffect(() => { const filtered = mediaItems.filter((item) => { - if (mediaFilter && mediaFilter !== 'all' && item.type !== mediaFilter) { + if (mediaFilter && mediaFilter !== "all" && item.type !== mediaFilter) { return false; } - - if (searchQuery && !item.name.toLowerCase().includes(searchQuery.toLowerCase())) { + + if ( + searchQuery && + !item.name.toLowerCase().includes(searchQuery.toLowerCase()) + ) { return false; } - + return true; }); - + setFilteredMediaItems(filtered); }, [mediaItems, mediaFilter, searchQuery]); @@ -209,23 +212,23 @@ export function MediaPanel() { {/* Button to add/upload media */}
{/* Search and filter controls */} - - setSearchQuery(e.target.value)} - /> + + setSearchQuery(e.target.value)} + /> {/* Add media button */} -
+
@@ -276,7 +284,15 @@ export function MediaPanel() { {renderPreview(item)} - {item.name} + + {item.name.length > 8 + ? `${item.name.slice(0, 4)}...${item.name.slice(-3)}` + : item.name} + {/* Show remove button on hover */} diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx index 624a549..33bfe68 100644 --- a/apps/web/src/components/editor/preview-panel.tsx +++ b/apps/web/src/components/editor/preview-panel.tsx @@ -5,16 +5,17 @@ import { useMediaStore } from "@/stores/media-store"; import { usePlaybackStore } from "@/stores/playback-store"; import { VideoPlayer } from "@/components/ui/video-player"; import { Button } from "@/components/ui/button"; -import { Play, Pause } from "lucide-react"; +import { Play, Pause, Volume2, VolumeX } from "lucide-react"; import { useState, useRef } from "react"; // Debug flag - set to false to hide active clips info -const SHOW_DEBUG_INFO = process.env.NODE_ENV === 'development'; +const SHOW_DEBUG_INFO = process.env.NODE_ENV === "development"; export function PreviewPanel() { const { tracks } = useTimelineStore(); const { mediaItems } = useMediaStore(); - const { isPlaying, toggle, currentTime } = usePlaybackStore(); + const { isPlaying, toggle, currentTime, muted, toggleMute, volume } = + usePlaybackStore(); const [canvasSize, setCanvasSize] = useState({ width: 1920, height: 1080 }); const [showDebug, setShowDebug] = useState(SHOW_DEBUG_INFO); const previewRef = useRef(null); @@ -30,12 +31,14 @@ export function PreviewPanel() { tracks.forEach((track) => { track.clips.forEach((clip) => { const clipStart = clip.startTime; - const clipEnd = clip.startTime + (clip.duration - clip.trimStart - clip.trimEnd); + const clipEnd = + clip.startTime + (clip.duration - clip.trimStart - clip.trimEnd); if (currentTime >= clipStart && currentTime < clipEnd) { - const mediaItem = clip.mediaId === "test" - ? { type: "test", name: clip.name, url: "", thumbnailUrl: "" } - : mediaItems.find((item) => item.id === clip.mediaId); + const mediaItem = + clip.mediaId === "test" + ? { type: "test", name: clip.name, url: "", thumbnailUrl: "" } + : mediaItems.find((item) => item.id === clip.mediaId); if (mediaItem || clip.mediaId === "test") { activeClips.push({ clip, track, mediaItem }); @@ -134,13 +137,19 @@ export function PreviewPanel() {