summaryrefslogtreecommitdiff
path: root/apps/web/src/lib/timeline/track-utils.ts
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-02-08 01:51:25 +0100
committerMaze Winther <mazewinther@gmail.com>2026-02-08 01:51:25 +0100
commit9b5fd6eb077a3fb2380f42414b65405030a01fcd (patch)
tree91d56e1c71e9a876afb1ae39fe4644ed8d5accee /apps/web/src/lib/timeline/track-utils.ts
parent00cf7f6ab7b35616290cfeab06256a14f6d4beba (diff)
downloadopencut-9b5fd6eb077a3fb2380f42414b65405030a01fcd.tar.gz
feat: ensure elements on main track starts at 0
Diffstat (limited to 'apps/web/src/lib/timeline/track-utils.ts')
-rw-r--r--apps/web/src/lib/timeline/track-utils.ts60
1 files changed, 60 insertions, 0 deletions
diff --git a/apps/web/src/lib/timeline/track-utils.ts b/apps/web/src/lib/timeline/track-utils.ts
index b6261569..415d0ace 100644
--- a/apps/web/src/lib/timeline/track-utils.ts
+++ b/apps/web/src/lib/timeline/track-utils.ts
@@ -6,6 +6,7 @@ import type {
AudioTrack,
StickerTrack,
TextTrack,
+ TimelineElement,
} from "@/types/timeline";
import {
TRACK_COLORS,
@@ -242,3 +243,62 @@ export function validateElementTrackCompatibility({
return { isValid: true };
}
+
+export function getEarliestMainTrackElement({
+ tracks,
+ excludeElementId,
+}: {
+ tracks: TimelineTrack[];
+ excludeElementId?: string;
+}): TimelineElement | null {
+ const mainTrack = getMainTrack({ tracks });
+ if (!mainTrack) {
+ return null;
+ }
+
+ const elements = mainTrack.elements.filter(
+ (element) => !excludeElementId || element.id !== excludeElementId,
+ );
+
+ if (elements.length === 0) {
+ return null;
+ }
+
+ return elements.reduce((earliest, element) =>
+ element.startTime < earliest.startTime ? element : earliest,
+ );
+}
+
+export function enforceMainTrackStart({
+ tracks,
+ targetTrackId,
+ requestedStartTime,
+ excludeElementId,
+}: {
+ tracks: TimelineTrack[];
+ targetTrackId: string;
+ requestedStartTime: number;
+ excludeElementId?: string;
+}): number {
+ const mainTrack = getMainTrack({ tracks });
+ if (!mainTrack || mainTrack.id !== targetTrackId) {
+ return requestedStartTime;
+ }
+
+ const earliestElement = getEarliestMainTrackElement({
+ tracks,
+ excludeElementId,
+ });
+
+ if (!earliestElement) {
+ return 0;
+ }
+
+ // main track must always start at time 0; if this element would
+ // become the earliest, pin it to the start
+ if (requestedStartTime <= earliestElement.startTime) {
+ return 0;
+ }
+
+ return requestedStartTime;
+}