diff options
| author | Maze Winther <mazewinther@gmail.com> | 2025-07-06 22:51:11 +0200 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2025-07-06 22:51:11 +0200 |
| commit | 6edd5b36cf7f09b8b146f57a880eb919c5fdad7f (patch) | |
| tree | f41f166b71715010684b7e9769e557f6902aed02 /apps/web/src/components | |
| parent | 40c7fbb4f86130c83fbd9029e83c5e7550e3219d (diff) | |
| download | opencut-6edd5b36cf7f09b8b146f57a880eb919c5fdad7f.tar.gz | |
feat: main track and ordering tracks
Diffstat (limited to 'apps/web/src/components')
| -rw-r--r-- | apps/web/src/components/development-debug.tsx | 3 | ||||
| -rw-r--r-- | apps/web/src/components/editor/preview-panel.tsx | 34 | ||||
| -rw-r--r-- | apps/web/src/components/editor/timeline-track.tsx | 101 |
3 files changed, 93 insertions, 45 deletions
diff --git a/apps/web/src/components/development-debug.tsx b/apps/web/src/components/development-debug.tsx index f407e2e0..3b891e38 100644 --- a/apps/web/src/components/development-debug.tsx +++ b/apps/web/src/components/development-debug.tsx @@ -1,7 +1,8 @@ "use client";
-import { useTimelineStore, type TimelineTrack } from "@/stores/timeline-store";
+import { useTimelineStore } from "@/stores/timeline-store";
import { useMediaStore, type MediaItem } from "@/stores/media-store";
+import { TimelineTrack } from "@/types/timeline";
import { usePlaybackStore } from "@/stores/playback-store";
import { Button } from "@/components/ui/button";
import { useState } from "react";
diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx index 9a02560b..bbd112c1 100644 --- a/apps/web/src/components/editor/preview-panel.tsx +++ b/apps/web/src/components/editor/preview-panel.tsx @@ -1,10 +1,7 @@ "use client"; -import { - useTimelineStore, - type TimelineTrack, -} from "@/stores/timeline-store"; -import { TimelineElement } from "@/types/timeline"; +import { useTimelineStore } from "@/stores/timeline-store"; +import { TimelineElement, TimelineTrack } from "@/types/timeline"; import { useMediaStore, type MediaItem, @@ -112,16 +109,19 @@ export function PreviewPanel() { track.elements.forEach((element) => { const elementStart = element.startTime; const elementEnd = - element.startTime + (element.duration - element.trimStart - element.trimEnd); + element.startTime + + (element.duration - element.trimStart - element.trimEnd); if (currentTime >= elementStart && currentTime < elementEnd) { let mediaItem = null; - + // Only get media item for media elements if (element.type === "media") { - mediaItem = element.mediaId === "test" - ? null // Test elements don't have a real media item - : mediaItems.find((item) => item.id === element.mediaId) || null; + mediaItem = + element.mediaId === "test" + ? null // Test elements don't have a real media item + : mediaItems.find((item) => item.id === element.mediaId) || + null; } activeElements.push({ element, track, mediaItem }); @@ -165,9 +165,9 @@ export function PreviewPanel() { fontWeight: element.fontWeight, fontStyle: element.fontStyle, textDecoration: element.textDecoration, - padding: '4px 8px', - borderRadius: '2px', - whiteSpace: 'pre-wrap', + padding: "4px 8px", + borderRadius: "2px", + whiteSpace: "pre-wrap", }} > {element.content} @@ -262,7 +262,9 @@ export function PreviewPanel() { No elements at current time </div> ) : ( - activeElements.map((elementData, index) => renderElement(elementData, index)) + activeElements.map((elementData, index) => + renderElement(elementData, index) + ) )} </div> ) : ( @@ -305,7 +307,9 @@ function PreviewToolbar({ hasAnyElements }: { hasAnyElements: boolean }) { for (const track of tracks) { for (const element of track.elements) { if (element.type === "media") { - const mediaItem = mediaItems.find((item) => item.id === element.mediaId); + const mediaItem = mediaItems.find( + (item) => item.id === element.mediaId + ); if ( mediaItem && (mediaItem.type === "video" || mediaItem.type === "image") diff --git a/apps/web/src/components/editor/timeline-track.tsx b/apps/web/src/components/editor/timeline-track.tsx index dd4219f9..eb58e1ee 100644 --- a/apps/web/src/components/editor/timeline-track.tsx +++ b/apps/web/src/components/editor/timeline-track.tsx @@ -13,7 +13,12 @@ import { ContextMenuSeparator, ContextMenuTrigger, } from "../ui/context-menu"; -import { TimelineTrack } from "@/stores/timeline-store"; +import { + TimelineTrack, + sortTracksByOrder, + ensureMainTrack, + getMainTrack +} from "@/types/timeline"; import { usePlaybackStore } from "@/stores/playback-store"; import type { TimelineElement as TimelineElementType, @@ -555,15 +560,23 @@ export function TimelineTrackContent({ // Handle position-aware track creation for text if (track.type !== "text" || dropPosition !== "on") { - // Determine where to insert the new text track + // Text tracks should go above the main track + const mainTrack = getMainTrack(tracks); let insertIndex: number; + if (dropPosition === "above") { insertIndex = currentTrackIndex; } else if (dropPosition === "below") { insertIndex = currentTrackIndex + 1; } else { // dropPosition === "on" but track is not text type - insertIndex = currentTrackIndex + 1; + // Insert above main track if main track exists, otherwise at top + if (mainTrack) { + const mainTrackIndex = tracks.findIndex(t => t.id === mainTrack.id); + insertIndex = mainTrackIndex; + } else { + insertIndex = 0; // Top of timeline + } } targetTrackId = insertTrackAt("text", insertIndex); @@ -646,42 +659,72 @@ export function TimelineTrackContent({ const needsNewTrack = !isCompatible || dropPosition !== "on"; if (needsNewTrack) { - // Determine where to insert the new track - let insertIndex: number; - if (dropPosition === "above") { - insertIndex = currentTrackIndex; - } else if (dropPosition === "below") { - insertIndex = currentTrackIndex + 1; - } else { - // dropPosition === "on" but track is incompatible - insertIndex = currentTrackIndex + 1; - } - if (isVideoOrImage) { - // For video/image, check if main media track is empty and at the right position - const mainMediaTrack = tracks.find((t) => t.type === "media"); - if ( - mainMediaTrack && - mainMediaTrack.elements.length === 0 && - dropPosition === "on" - ) { - targetTrackId = mainMediaTrack.id; - targetTrack = mainMediaTrack; + // For video/image, check if we need a main track or additional media track + const mainTrack = getMainTrack(tracks); + + if (!mainTrack) { + // No main track exists, create it + const updatedTracks = ensureMainTrack(tracks); + const newMainTrack = getMainTrack(updatedTracks); + if (newMainTrack && newMainTrack.elements.length === 0) { + targetTrackId = newMainTrack.id; + targetTrack = newMainTrack; + } else { + // Main track was created but somehow has elements, create new media track + const mainTrackIndex = updatedTracks.findIndex(t => t.id === newMainTrack?.id); + targetTrackId = insertTrackAt("media", mainTrackIndex); + const updatedTracksAfterInsert = useTimelineStore.getState().tracks; + const newTargetTrack = updatedTracksAfterInsert.find(t => t.id === targetTrackId); + if (!newTargetTrack) return; + targetTrack = newTargetTrack; + } + } else if (mainTrack.elements.length === 0 && dropPosition === "on") { + // Main track exists and is empty, use it + targetTrackId = mainTrack.id; + targetTrack = mainTrack; } else { + // Create new media track above main track + const mainTrackIndex = tracks.findIndex(t => t.id === mainTrack.id); + let insertIndex: number; + + if (dropPosition === "above") { + insertIndex = currentTrackIndex; + } else if (dropPosition === "below") { + insertIndex = currentTrackIndex + 1; + } else { + // Insert above main track + insertIndex = mainTrackIndex; + } + targetTrackId = insertTrackAt("media", insertIndex); const updatedTracks = useTimelineStore.getState().tracks; - const newTargetTrack = updatedTracks.find( - (t) => t.id === targetTrackId - ); + const newTargetTrack = updatedTracks.find(t => t.id === targetTrackId); if (!newTargetTrack) return; targetTrack = newTargetTrack; } } else if (isAudio) { + // Audio tracks go at the bottom + const mainTrack = getMainTrack(tracks); + let insertIndex: number; + + if (dropPosition === "above") { + insertIndex = currentTrackIndex; + } else if (dropPosition === "below") { + insertIndex = currentTrackIndex + 1; + } else { + // Insert after main track (bottom area) + if (mainTrack) { + const mainTrackIndex = tracks.findIndex(t => t.id === mainTrack.id); + insertIndex = mainTrackIndex + 1; + } else { + insertIndex = tracks.length; // Bottom of timeline + } + } + targetTrackId = insertTrackAt("audio", insertIndex); const updatedTracks = useTimelineStore.getState().tracks; - const newTargetTrack = updatedTracks.find( - (t) => t.id === targetTrackId - ); + const newTargetTrack = updatedTracks.find(t => t.id === targetTrackId); if (!newTargetTrack) return; targetTrack = newTargetTrack; } |
