summaryrefslogtreecommitdiff
path: root/apps/web/src/components/editor
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/components/editor')
-rw-r--r--apps/web/src/components/editor/preview-panel.tsx34
-rw-r--r--apps/web/src/components/editor/timeline-track.tsx101
2 files changed, 91 insertions, 44 deletions
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;
}