summaryrefslogtreecommitdiff
path: root/apps/web/src/lib
diff options
context:
space:
mode:
authorahmedfahim21 <ahmed.fahim0207@gmail.com>2025-07-13 00:59:54 +0530
committerahmedfahim21 <ahmed.fahim0207@gmail.com>2025-07-13 00:59:54 +0530
commit03b3c952c75b7642b537fa2499d081edd1a0a1e4 (patch)
tree2df0eb90fcd2c36c6e2f1c9ce6b5638b73c5a86c /apps/web/src/lib
parent22db0b8b893621a8f714e4ae2fff6cebf1df1394 (diff)
downloadopencut-03b3c952c75b7642b537fa2499d081edd1a0a1e4.tar.gz
feat: click to add to timeline
Diffstat (limited to 'apps/web/src/lib')
-rw-r--r--apps/web/src/lib/timeline-utils.ts117
1 files changed, 117 insertions, 0 deletions
diff --git a/apps/web/src/lib/timeline-utils.ts b/apps/web/src/lib/timeline-utils.ts
new file mode 100644
index 00000000..994cb314
--- /dev/null
+++ b/apps/web/src/lib/timeline-utils.ts
@@ -0,0 +1,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);
+};