From dc35619017ed7fbaa5e83151ba99c143da8cf1f1 Mon Sep 17 00:00:00 2001 From: Hyteq Date: Tue, 24 Jun 2025 08:10:11 +0300 Subject: [PATCH] clean up and simplifying more logic --- apps/web/src/app/editor/page.tsx | 104 +++-- apps/web/src/components/editor-provider.tsx | 14 +- .../src/components/editor/preview-panel.tsx | 407 +++--------------- apps/web/src/components/ui/video-player.tsx | 113 ++--- apps/web/src/stores/panel-store.ts | 62 +-- 5 files changed, 156 insertions(+), 544 deletions(-) diff --git a/apps/web/src/app/editor/page.tsx b/apps/web/src/app/editor/page.tsx index 64186a3..19df364 100644 --- a/apps/web/src/app/editor/page.tsx +++ b/apps/web/src/app/editor/page.tsx @@ -42,59 +42,71 @@ export default function Editor() { return ( -
+
- - - {/* Main content area */} - - {/* Tools Panel */} - - - +
+ + + {/* Main content area */} + + {/* Tools Panel */} + + + - + - {/* Preview Area */} - - - + {/* Preview Area */} + + + - + - {/* Properties Panel */} - {/* - - */} - - + {/* Properties Panel - Hidden for now but ready */} + {/* + + */} + + - + - {/* Timeline */} - - - - + {/* Timeline */} + + + + +
); diff --git a/apps/web/src/components/editor-provider.tsx b/apps/web/src/components/editor-provider.tsx index ab55c7d..3825595 100644 --- a/apps/web/src/components/editor-provider.tsx +++ b/apps/web/src/components/editor-provider.tsx @@ -3,7 +3,6 @@ import { useEffect } from "react"; import { Loader2 } from "lucide-react"; import { useEditorStore } from "@/stores/editor-store"; -import { usePanelStore } from "@/stores/panel-store"; interface EditorProviderProps { children: React.ReactNode; @@ -11,19 +10,10 @@ interface EditorProviderProps { export function EditorProvider({ children }: EditorProviderProps) { const { isInitializing, isPanelsReady, initializeApp } = useEditorStore(); - const { setInitialized } = usePanelStore(); useEffect(() => { - const initialize = async () => { - // Initialize the app - await initializeApp(); - - // Initialize panel store for future resize events - setInitialized(); - }; - - initialize(); - }, [initializeApp, setInitialized]); + initializeApp(); + }, [initializeApp]); // Show loading screen while initializing if (isInitializing || !isPanelsReady) { diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx index 5d03e94..101f1ff 100644 --- a/apps/web/src/components/editor/preview-panel.tsx +++ b/apps/web/src/components/editor/preview-panel.tsx @@ -5,53 +5,25 @@ 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, Move, RotateCw, Crop, ZoomIn, ZoomOut } from "lucide-react"; -import { useState, useRef, useEffect, useCallback } from "react"; - -interface ClipTransform { - x: number; - y: number; - scale: number; - rotation: number; - opacity: number; - width: number; - height: number; - blendMode: string; - cropTop: number; - cropBottom: number; - cropLeft: number; - cropRight: number; -} - -interface DragState { - isDragging: boolean; - dragType: 'move' | 'resize-nw' | 'resize-ne' | 'resize-sw' | 'resize-se' | 'rotate' | 'scale' | 'crop-n' | 'crop-s' | 'crop-e' | 'crop-w'; - startMouseX: number; - startMouseY: number; - startTransform: ClipTransform; - clipId: string; -} +import { Play, Pause } from "lucide-react"; +import { useState, useRef } from "react"; export function PreviewPanel() { const { tracks } = useTimelineStore(); const { mediaItems } = useMediaStore(); const { isPlaying, toggle, currentTime } = usePlaybackStore(); - - const [clipTransforms, setClipTransforms] = useState>({}); - const [canvasSize, setCanvasSize] = useState({ width: 1920, height: 1080 }); // Default 16:9 - const [dragState, setDragState] = useState(null); + const [canvasSize, setCanvasSize] = useState({ width: 1920, height: 1080 }); const previewRef = useRef(null); - // Get all active clips at current time (for overlaying) + // Get active clips at current time const getActiveClips = () => { const activeClips: Array<{ clip: any; track: any; mediaItem: any; - layer: number; }> = []; - tracks.forEach((track, trackIndex) => { + tracks.forEach((track) => { track.clips.forEach((clip) => { const clipStart = clip.startTime; const clipEnd = clip.startTime + (clip.duration - clip.trimStart - clip.trimEnd); @@ -62,338 +34,78 @@ export function PreviewPanel() { : mediaItems.find((item) => item.id === clip.mediaId); if (mediaItem || clip.mediaId === "test") { - activeClips.push({ - clip, - track, - mediaItem, - layer: trackIndex, // Track index determines layer order - }); + activeClips.push({ clip, track, mediaItem }); } } }); }); - // Sort by layer (track order) - higher index = on top - return activeClips.sort((a, b) => a.layer - b.layer); + return activeClips; }; const activeClips = getActiveClips(); const aspectRatio = canvasSize.width / canvasSize.height; - // Get or create transform for a clip - const getClipTransform = (clipId: string): ClipTransform => { - return clipTransforms[clipId] || { - x: 0, - y: 0, - scale: 1, - rotation: 0, - opacity: 1, - width: 100, // Percentage of canvas - height: 100, - blendMode: 'normal', - cropTop: 0, - cropBottom: 0, - cropLeft: 0, - cropRight: 0, - }; - }; - - // Update clip transform - const updateClipTransform = useCallback((clipId: string, updates: Partial) => { - setClipTransforms(prev => { - const currentTransform = prev[clipId] || { - x: 0, - y: 0, - scale: 1, - rotation: 0, - opacity: 1, - width: 100, - height: 100, - blendMode: 'normal', - cropTop: 0, - cropBottom: 0, - cropLeft: 0, - cropRight: 0, - }; - - return { - ...prev, - [clipId]: { ...currentTransform, ...updates } - }; - }); - }, []); - - // Mouse event handlers - const handleMouseDown = (e: React.MouseEvent, clipId: string, dragType: DragState['dragType']) => { - e.preventDefault(); - e.stopPropagation(); - - setDragState({ - isDragging: true, - dragType, - startMouseX: e.clientX, - startMouseY: e.clientY, - startTransform: getClipTransform(clipId), - clipId - }); - }; - - const handleMouseMove = useCallback((e: MouseEvent) => { - if (!dragState || !dragState.isDragging) return; - - const deltaX = e.clientX - dragState.startMouseX; - const deltaY = e.clientY - dragState.startMouseY; - const { startTransform, clipId, dragType } = dragState; - - switch (dragType) { - case 'move': - updateClipTransform(clipId, { - x: Math.max(-100, Math.min(100, startTransform.x + deltaX * 0.3)), - y: Math.max(-100, Math.min(100, startTransform.y + deltaY * 0.3)) - }); - break; - - case 'resize-nw': - updateClipTransform(clipId, { - width: Math.max(20, startTransform.width - deltaX * 0.5), - height: Math.max(20, startTransform.height - deltaY * 0.5) - }); - break; - - case 'resize-ne': - updateClipTransform(clipId, { - width: Math.max(20, startTransform.width + deltaX * 0.5), - height: Math.max(20, startTransform.height - deltaY * 0.5) - }); - break; - - case 'resize-sw': - updateClipTransform(clipId, { - width: Math.max(20, startTransform.width - deltaX * 0.5), - height: Math.max(20, startTransform.height + deltaY * 0.5) - }); - break; - - case 'resize-se': - updateClipTransform(clipId, { - width: Math.max(20, startTransform.width + deltaX * 0.5), - height: Math.max(20, startTransform.height + deltaY * 0.5) - }); - break; - - case 'rotate': - updateClipTransform(clipId, { - rotation: (startTransform.rotation + deltaX * 2) % 360 - }); - break; - - case 'scale': - updateClipTransform(clipId, { - scale: Math.max(0.1, Math.min(3, startTransform.scale + deltaX * 0.01)) - }); - break; - - case 'crop-n': - updateClipTransform(clipId, { - cropTop: Math.max(0, Math.min(40, startTransform.cropTop + deltaY * 0.2)) - }); - break; - - case 'crop-s': - updateClipTransform(clipId, { - cropBottom: Math.max(0, Math.min(40, startTransform.cropBottom - deltaY * 0.2)) - }); - break; - - case 'crop-e': - updateClipTransform(clipId, { - cropRight: Math.max(0, Math.min(40, startTransform.cropRight - deltaX * 0.2)) - }); - break; - - case 'crop-w': - updateClipTransform(clipId, { - cropLeft: Math.max(0, Math.min(40, startTransform.cropLeft + deltaX * 0.2)) - }); - break; - } - }, [dragState, updateClipTransform]); - - const handleMouseUp = useCallback(() => { - setDragState(null); - }, []); - - // Add global mouse event listeners - useEffect(() => { - if (dragState?.isDragging) { - document.addEventListener('mousemove', handleMouseMove); - document.addEventListener('mouseup', handleMouseUp); - return () => { - document.removeEventListener('mousemove', handleMouseMove); - document.removeEventListener('mouseup', handleMouseUp); - }; - } - }, [dragState, handleMouseMove, handleMouseUp]); - - // Initialize transforms for new clips - useEffect(() => { - const activeClips = getActiveClips(); - const newTransforms: Record = {}; - let hasNewClips = false; - - activeClips.forEach(({ clip }) => { - if (!clipTransforms[clip.id]) { - hasNewClips = true; - newTransforms[clip.id] = { - x: 0, - y: 0, - scale: 1, - rotation: 0, - opacity: 1, - width: 100, - height: 100, - blendMode: 'normal', - cropTop: 0, - cropBottom: 0, - cropLeft: 0, - cropRight: 0, - }; - } - }); - - if (hasNewClips) { - setClipTransforms(prev => ({ ...prev, ...newTransforms })); - } - }, [tracks, currentTime]); // Re-run when tracks or time changes - - - - // Render a single clip layer - const renderClipLayer = (clipData: any, index: number) => { + // Render a clip + const renderClip = (clipData: any, index: number) => { const { clip, mediaItem } = clipData; - const transform = getClipTransform(clip.id); - const layerStyle = { - position: 'absolute' as const, - left: '50%', - top: '50%', - width: `${transform.width}%`, - height: `${transform.height}%`, - transform: `translate(-50%, -50%) translate(${transform.x}%, ${transform.y}%) scale(${transform.scale}) rotate(${transform.rotation}deg)`, - opacity: transform.opacity, - mixBlendMode: transform.blendMode as any, - clipPath: `inset(${transform.cropTop}% ${transform.cropRight}% ${transform.cropBottom}% ${transform.cropLeft}%)`, - zIndex: index + 10, - cursor: dragState?.isDragging && dragState.clipId === clip.id ? 'grabbing' : 'grab', - userSelect: 'none' as const, - }; - - const handleClipMouseDown = (e: React.MouseEvent) => { - e.stopPropagation(); - handleMouseDown(e, clip.id, 'move'); - }; - - // Handle test clips + // Test clips if (!mediaItem || clip.mediaId === "test") { return (
-
-
🎬
+
+
🎬

{clip.name}

- - {/* Hover resize corners */} -
{ e.stopPropagation(); handleMouseDown(e, clip.id, 'resize-nw'); }}>
-
{ e.stopPropagation(); handleMouseDown(e, clip.id, 'resize-ne'); }}>
-
{ e.stopPropagation(); handleMouseDown(e, clip.id, 'resize-sw'); }}>
-
{ e.stopPropagation(); handleMouseDown(e, clip.id, 'resize-se'); }}>
); } - // Render video + // Video clips if (mediaItem.type === "video") { return ( -
+
- - {/* Hover resize corners */} -
{ e.stopPropagation(); handleMouseDown(e, clip.id, 'resize-nw'); }}>
-
{ e.stopPropagation(); handleMouseDown(e, clip.id, 'resize-ne'); }}>
-
{ e.stopPropagation(); handleMouseDown(e, clip.id, 'resize-sw'); }}>
-
{ e.stopPropagation(); handleMouseDown(e, clip.id, 'resize-se'); }}>
); } - // Render image + // Image clips if (mediaItem.type === "image") { return ( -
+
{mediaItem.name} - - {/* Hover resize corners */} -
{ e.stopPropagation(); handleMouseDown(e, clip.id, 'resize-nw'); }}>
-
{ e.stopPropagation(); handleMouseDown(e, clip.id, 'resize-ne'); }}>
-
{ e.stopPropagation(); handleMouseDown(e, clip.id, 'resize-sw'); }}>
-
{ e.stopPropagation(); handleMouseDown(e, clip.id, 'resize-se'); }}>
); } - // Render audio (visual representation) + // Audio clips (visual representation) if (mediaItem.type === "audio") { return (
-
-
🎵
+
+
🎵

{mediaItem.name}

- - {/* Hover resize corners */} -
{ e.stopPropagation(); handleMouseDown(e, clip.id, 'resize-nw'); }}>
-
{ e.stopPropagation(); handleMouseDown(e, clip.id, 'resize-ne'); }}>
-
{ e.stopPropagation(); handleMouseDown(e, clip.id, 'resize-sw'); }}>
-
{ e.stopPropagation(); handleMouseDown(e, clip.id, 'resize-se'); }}>
); } @@ -401,7 +113,7 @@ export function PreviewPanel() { return null; }; - // Canvas size presets + // Canvas presets const canvasPresets = [ { name: "16:9 HD", width: 1920, height: 1080 }, { name: "16:9 4K", width: 3840, height: 2160 }, @@ -411,9 +123,9 @@ export function PreviewPanel() { ]; return ( -
- {/* Canvas Controls */} -
+
+ {/* Controls */} +
Canvas: -
- {/* Preview Area - Full Width */} -
+ {/* Preview Area */} +
1 ? "100%" : "auto", - height: aspectRatio <= 1 ? "100%" : "auto", - maxWidth: "100%", - maxHeight: "100%", - background: '#000000', - border: '1px solid #374151' + width: "100%", + height: "100%", }} - > - - - {/* Render all active clips as layers */} {activeClips.length === 0 ? (
{tracks.length === 0 ? "Drop media to start editing" : "No clips at current time"}
) : ( - activeClips.map((clipData, index) => renderClipLayer(clipData, index)) + activeClips.map((clipData, index) => renderClip(clipData, index)) )} - -
- {/* Bottom Info Panel */} -
- {/* Layer List */} -
-
Active Layers ({activeClips.length})
-
- {activeClips.map((clipData, index) => ( -
- - {index + 1} - - {clipData.clip.name} - {clipData.track.name} -
- ))} -
+ {/* Info Panel */} +
+
Active Clips ({activeClips.length})
+
+ {activeClips.map((clipData, index) => ( +
+ + {index + 1} + + {clipData.clip.name} +
+ ))}
- -
); diff --git a/apps/web/src/components/ui/video-player.tsx b/apps/web/src/components/ui/video-player.tsx index 8731e1a..f489f70 100644 --- a/apps/web/src/components/ui/video-player.tsx +++ b/apps/web/src/components/ui/video-player.tsx @@ -1,8 +1,6 @@ "use client"; import { useRef, useEffect } from "react"; -import { Button } from "./button"; -import { Play, Pause, Volume2 } from "lucide-react"; import { usePlaybackStore } from "@/stores/playback-store"; interface VideoPlayerProps { @@ -25,128 +23,87 @@ export function VideoPlayer({ clipDuration }: VideoPlayerProps) { const videoRef = useRef(null); - const { isPlaying, currentTime, volume, speed, play, pause, setVolume } = usePlaybackStore(); + const { isPlaying, currentTime, volume, speed } = usePlaybackStore(); // Calculate if we're within this clip's timeline range const clipEndTime = clipStartTime + (clipDuration - trimStart - trimEnd); const isInClipRange = currentTime >= clipStartTime && currentTime < clipEndTime; - // Calculate the video's internal time based on timeline position - const videoTime = Math.max(trimStart, Math.min( - clipDuration - trimEnd, - currentTime - clipStartTime + trimStart - )); - + // Sync playback events useEffect(() => { const video = videoRef.current; - if (!video) return; + if (!video || !isInClipRange) return; - const handleSeekEvent = (e: CustomEvent) => { - if (!isInClipRange) return; + const handleSeek = (e: CustomEvent) => { const timelineTime = e.detail.time; - const newVideoTime = Math.max(trimStart, Math.min( + const videoTime = Math.max(trimStart, Math.min( clipDuration - trimEnd, timelineTime - clipStartTime + trimStart )); - video.currentTime = newVideoTime; + video.currentTime = videoTime; }; - const handleUpdateEvent = (e: CustomEvent) => { - if (!isInClipRange) return; + const handleUpdate = (e: CustomEvent) => { const timelineTime = e.detail.time; - const targetVideoTime = Math.max(trimStart, Math.min( + const targetTime = Math.max(trimStart, Math.min( clipDuration - trimEnd, timelineTime - clipStartTime + trimStart )); - // Only sync if there's a significant difference to avoid micro-adjustments - if (Math.abs(video.currentTime - targetVideoTime) > 0.5) { - video.currentTime = targetVideoTime; + if (Math.abs(video.currentTime - targetTime) > 0.5) { + video.currentTime = targetTime; } }; - const handleSpeedEvent = (e: CustomEvent) => { - if (!isInClipRange) return; - // Set playbackRate directly without any additional checks + const handleSpeed = (e: CustomEvent) => { video.playbackRate = e.detail.speed; }; - window.addEventListener("playback-seek", handleSeekEvent as EventListener); - window.addEventListener("playback-update", handleUpdateEvent as EventListener); - window.addEventListener("playback-speed", handleSpeedEvent as EventListener); + window.addEventListener("playback-seek", handleSeek as EventListener); + window.addEventListener("playback-update", handleUpdate as EventListener); + window.addEventListener("playback-speed", handleSpeed as EventListener); return () => { - window.removeEventListener("playback-seek", handleSeekEvent as EventListener); - window.removeEventListener("playback-update", handleUpdateEvent as EventListener); - window.removeEventListener("playback-speed", handleSpeedEvent as EventListener); + window.removeEventListener("playback-seek", handleSeek as EventListener); + window.removeEventListener("playback-update", handleUpdate as EventListener); + window.removeEventListener("playback-speed", handleSpeed as EventListener); }; }, [clipStartTime, trimStart, trimEnd, clipDuration, isInClipRange]); - // Sync video playback state - only play if in clip range + // Sync playback state useEffect(() => { const video = videoRef.current; if (!video) return; if (isPlaying && isInClipRange) { - video.play().catch(console.error); + video.play().catch(() => { }); } else { video.pause(); } }, [isPlaying, isInClipRange]); - // Sync volume + // Sync volume and speed useEffect(() => { const video = videoRef.current; if (!video) return; - video.volume = volume; - }, [volume]); - // Sync speed immediately when it changes - useEffect(() => { - const video = videoRef.current; - if (!video) return; + video.volume = volume; video.playbackRate = speed; - }, [speed]); + }, [volume, speed]); return ( -
-