summaryrefslogtreecommitdiff
path: root/apps/web/src/lib/timeline-utils.ts
blob: 994cb3146421e3e479c7f89f44f900d13c1c451b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import { useTimelineStore } from "@/stores/timeline-store";
import { type MediaItem } from "@/stores/media-store";
import { toast } from "sonner";
import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants";
import { DragData, TextElement } from "@/types/timeline";


const findOrCreateTrack = (trackType: "media" | "audio" | "text") => {
  const timelineStore = useTimelineStore.getState();
  
  // Always create new text track to allow multiple text elements
  if (trackType === "text") {
    return timelineStore.addTrack(trackType);
  }
  
  const existingTrack = timelineStore.tracks.find(track => track.type === trackType);
  return existingTrack ? existingTrack.id : timelineStore.addTrack(trackType);
};


const checkOverlap = (trackId: string, startTime: number, duration: number, excludeElementId?: string) => {
  const timelineStore = useTimelineStore.getState();
  const targetTrack = timelineStore.tracks.find(track => track.id === trackId);
  
  if (!targetTrack) {
    return true; 
  }

  const elementEnd = startTime + duration;

  return targetTrack.elements.some((existingElement) => {
    if (excludeElementId && existingElement.id === excludeElementId) {
      return false;
    }
    
    const existingStart = existingElement.startTime;
    const existingEnd = existingElement.startTime + 
      (existingElement.duration - existingElement.trimStart - existingElement.trimEnd);

    return startTime < existingEnd && elementEnd > existingStart;
  });
};

const addMediaElement = (trackId: string, item: MediaItem, startTime: number) => {
  const timelineStore = useTimelineStore.getState();
  
  timelineStore.addElementToTrack(trackId, {
    type: "media",
    mediaId: item.id,
    name: item.name,
    duration: item.duration || TIMELINE_CONSTANTS.DEFAULT_IMAGE_DURATION,
    startTime,
    trimStart: 0,
    trimEnd: 0,
  });
};

const addTextElement = (trackId: string, item: TextElement | DragData, startTime: number) => {
  const timelineStore = useTimelineStore.getState();
  
  timelineStore.addElementToTrack(trackId, {
    type: "text",
    name: item.name,
    content: ("content" in item ? item.content : "Default Text"),
    duration: TIMELINE_CONSTANTS.DEFAULT_TEXT_DURATION,
    startTime,
    trimStart: ("trimStart" in item ? item.trimStart : 0),
    trimEnd: ("trimEnd" in item ? item.trimEnd : 0),
    fontSize: ("fontSize" in item ? item.fontSize : 48),
    fontFamily: ("fontFamily" in item ? item.fontFamily : "Arial"),
    color: ("color" in item ? item.color : "#ffffff"),
    backgroundColor: ("backgroundColor" in item ? item.backgroundColor : "transparent"),
    textAlign: ("textAlign" in item ? item.textAlign : "center"),
    fontWeight: ("fontWeight" in item ? item.fontWeight : "normal"),
    fontStyle: ("fontStyle" in item ? item.fontStyle : "normal"),
    textDecoration: ("textDecoration" in item ? item.textDecoration : "none"),
    x: ("x" in item ? item.x : 0),
    y: ("y" in item ? item.y : 0),
    rotation: ("rotation" in item ? item.rotation : 0),
    opacity: ("opacity" in item && item.opacity !== undefined ? item.opacity : 1),
  });
};


// Adds a media item to the timeline at the specified time
export const addMediaToTimeline = (item: MediaItem, currentTime: number = 0) => {
  const trackType = item.type === "audio" ? "audio" : "media";
  const targetTrackId = findOrCreateTrack(trackType);
  
  const duration = item.duration || TIMELINE_CONSTANTS.DEFAULT_IMAGE_DURATION;
  
  if (checkOverlap(targetTrackId, currentTime, duration)) {
    toast.error("Cannot place element here - it would overlap with existing elements");
    return;
  }

  addMediaElement(targetTrackId, item, currentTime);
};

// Adds a text item to the timeline at the specified time
export const addTextToTimeline = (item: TextElement, currentTime: number = 0) => {
  const targetTrackId = findOrCreateTrack("text");
  addTextElement(targetTrackId, item, currentTime);
};

// Adds a media item to timeline
export const addMediaToNewTrack = (item: MediaItem) => {
  const trackType = item.type === "audio" ? "audio" : "media";
  const targetTrackId = findOrCreateTrack(trackType);
  addMediaElement(targetTrackId, item, 0);
};

// Adds a text item to timeline
export const addTextToNewTrack = (item: TextElement | DragData) => {
  const targetTrackId = findOrCreateTrack("text");
  addTextElement(targetTrackId, item, 0);
};