From 4d127a038bf768eded68aae3cfa5c1b12241bdac Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Fri, 27 Mar 2026 00:55:15 +0100 Subject: chore: normalize line endings --- apps/web/src/lib/timeline/track-utils.ts | 594 +++++++++++++++---------------- 1 file changed, 297 insertions(+), 297 deletions(-) (limited to 'apps/web/src/lib/timeline/track-utils.ts') diff --git a/apps/web/src/lib/timeline/track-utils.ts b/apps/web/src/lib/timeline/track-utils.ts index cde2c814..0034c72e 100644 --- a/apps/web/src/lib/timeline/track-utils.ts +++ b/apps/web/src/lib/timeline/track-utils.ts @@ -1,297 +1,297 @@ -import type { - TrackType, - TimelineTrack, - ElementType, - VideoTrack, - AudioTrack, - GraphicTrack, - TextTrack, - EffectTrack, - TimelineElement, -} from "@/lib/timeline"; -import { - ELEMENT_TRACK_MAP, - TRACK_CONFIG, - ELEMENT_TYPE_CONFIG, - TRACK_GAP, -} from "@/constants/timeline-constants"; -import { generateUUID } from "@/utils/id"; - -export function canTracktHaveAudio( - track: TimelineTrack, -): track is VideoTrack | AudioTrack { - return track.type === "audio" || track.type === "video"; -} - -export function canTrackBeHidden( - track: TimelineTrack, -): track is VideoTrack | TextTrack | GraphicTrack | EffectTrack { - return track.type !== "audio"; -} - -export function getElementClasses({ type }: { type: TrackType }) { - return ELEMENT_TYPE_CONFIG[type].background.trim(); -} - -export function getTrackHeight({ type }: { type: TrackType }): number { - return TRACK_CONFIG[type].height; -} - -export function getCumulativeHeightBefore({ - tracks, - trackIndex, -}: { - tracks: Array<{ type: TrackType }>; - trackIndex: number; -}): number { - return tracks - .slice(0, trackIndex) - .reduce( - (sum, track) => sum + getTrackHeight({ type: track.type }) + TRACK_GAP, - 0, - ); -} - -export function getTotalTracksHeight({ - tracks, -}: { - tracks: Array<{ type: TrackType }>; -}): number { - const tracksHeight = tracks.reduce( - (sum, track) => sum + getTrackHeight({ type: track.type }), - 0, - ); - const gapsHeight = Math.max(0, tracks.length - 1) * TRACK_GAP; - return tracksHeight + gapsHeight; -} - -export function buildEmptyTrack({ - id, - type, - name, -}: { - id: string; - type: TrackType; - name?: string; -}): TimelineTrack { - const trackName = name ?? TRACK_CONFIG[type].defaultName; - - switch (type) { - case "video": - return { - id, - name: trackName, - type: "video", - elements: [], - hidden: false, - muted: false, - isMain: false, - }; - case "text": - return { - id, - name: trackName, - type: "text", - elements: [], - hidden: false, - }; - case "graphic": - return { - id, - name: trackName, - type: "graphic", - elements: [], - hidden: false, - }; - case "audio": - return { - id, - name: trackName, - type: "audio", - elements: [], - muted: false, - }; - case "effect": - return { - id, - name: trackName, - type: "effect", - elements: [], - hidden: false, - }; - default: - throw new Error(`Unsupported track type: ${type}`); - } -} - -export function getDefaultInsertIndexForTrack({ - tracks, - trackType, -}: { - tracks: TimelineTrack[]; - trackType: TrackType; -}): number { - if (trackType === "audio") { - return tracks.length; - } - - if (trackType === "effect") { - return 0; - } - - const mainTrackIndex = tracks.findIndex((track) => isMainTrack(track)); - if (mainTrackIndex >= 0) { - return mainTrackIndex; - } - - const firstAudioTrackIndex = tracks.findIndex( - (track) => track.type === "audio", - ); - if (firstAudioTrackIndex >= 0) { - return firstAudioTrackIndex; - } - - return tracks.length; -} - -export function getHighestInsertIndexForTrack({ - tracks, - trackType, -}: { - tracks: TimelineTrack[]; - trackType: TrackType; -}): number { - const mainTrackIndex = tracks.findIndex((track) => isMainTrack(track)); - - if (trackType === "audio") { - return mainTrackIndex >= 0 ? mainTrackIndex + 1 : tracks.length; - } - - return 0; -} - -export function isMainTrack(track: TimelineTrack): track is VideoTrack { - return track.type === "video" && track.isMain === true; -} - -export function getMainTrack({ - tracks, -}: { - tracks: TimelineTrack[]; -}): TimelineTrack | null { - return tracks.find((track) => isMainTrack(track)) ?? null; -} - -export function ensureMainTrack({ - tracks, -}: { - tracks: TimelineTrack[]; -}): TimelineTrack[] { - const hasMainTrack = tracks.some((track) => isMainTrack(track)); - - if (!hasMainTrack) { - const mainTrack: TimelineTrack = { - id: generateUUID(), - name: "Main Track", - type: "video", - elements: [], - muted: false, - isMain: true, - hidden: false, - }; - return [mainTrack, ...tracks]; - } - - return tracks; -} - -export function canElementGoOnTrack({ - elementType, - trackType, -}: { - elementType: ElementType; - trackType: TrackType; -}): boolean { - return ELEMENT_TRACK_MAP[elementType] === trackType; -} - -export function validateElementTrackCompatibility({ - element, - track, -}: { - element: { type: ElementType }; - track: { type: TrackType }; -}): { isValid: boolean; errorMessage?: string } { - const isValid = canElementGoOnTrack({ - elementType: element.type, - trackType: track.type, - }); - - if (!isValid) { - return { - isValid: false, - errorMessage: `${element.type} elements cannot be placed on ${track.type} tracks`, - }; - } - - 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; -} +import type { + TrackType, + TimelineTrack, + ElementType, + VideoTrack, + AudioTrack, + GraphicTrack, + TextTrack, + EffectTrack, + TimelineElement, +} from "@/lib/timeline"; +import { + ELEMENT_TRACK_MAP, + TRACK_CONFIG, + ELEMENT_TYPE_CONFIG, + TRACK_GAP, +} from "@/constants/timeline-constants"; +import { generateUUID } from "@/utils/id"; + +export function canTracktHaveAudio( + track: TimelineTrack, +): track is VideoTrack | AudioTrack { + return track.type === "audio" || track.type === "video"; +} + +export function canTrackBeHidden( + track: TimelineTrack, +): track is VideoTrack | TextTrack | GraphicTrack | EffectTrack { + return track.type !== "audio"; +} + +export function getElementClasses({ type }: { type: TrackType }) { + return ELEMENT_TYPE_CONFIG[type].background.trim(); +} + +export function getTrackHeight({ type }: { type: TrackType }): number { + return TRACK_CONFIG[type].height; +} + +export function getCumulativeHeightBefore({ + tracks, + trackIndex, +}: { + tracks: Array<{ type: TrackType }>; + trackIndex: number; +}): number { + return tracks + .slice(0, trackIndex) + .reduce( + (sum, track) => sum + getTrackHeight({ type: track.type }) + TRACK_GAP, + 0, + ); +} + +export function getTotalTracksHeight({ + tracks, +}: { + tracks: Array<{ type: TrackType }>; +}): number { + const tracksHeight = tracks.reduce( + (sum, track) => sum + getTrackHeight({ type: track.type }), + 0, + ); + const gapsHeight = Math.max(0, tracks.length - 1) * TRACK_GAP; + return tracksHeight + gapsHeight; +} + +export function buildEmptyTrack({ + id, + type, + name, +}: { + id: string; + type: TrackType; + name?: string; +}): TimelineTrack { + const trackName = name ?? TRACK_CONFIG[type].defaultName; + + switch (type) { + case "video": + return { + id, + name: trackName, + type: "video", + elements: [], + hidden: false, + muted: false, + isMain: false, + }; + case "text": + return { + id, + name: trackName, + type: "text", + elements: [], + hidden: false, + }; + case "graphic": + return { + id, + name: trackName, + type: "graphic", + elements: [], + hidden: false, + }; + case "audio": + return { + id, + name: trackName, + type: "audio", + elements: [], + muted: false, + }; + case "effect": + return { + id, + name: trackName, + type: "effect", + elements: [], + hidden: false, + }; + default: + throw new Error(`Unsupported track type: ${type}`); + } +} + +export function getDefaultInsertIndexForTrack({ + tracks, + trackType, +}: { + tracks: TimelineTrack[]; + trackType: TrackType; +}): number { + if (trackType === "audio") { + return tracks.length; + } + + if (trackType === "effect") { + return 0; + } + + const mainTrackIndex = tracks.findIndex((track) => isMainTrack(track)); + if (mainTrackIndex >= 0) { + return mainTrackIndex; + } + + const firstAudioTrackIndex = tracks.findIndex( + (track) => track.type === "audio", + ); + if (firstAudioTrackIndex >= 0) { + return firstAudioTrackIndex; + } + + return tracks.length; +} + +export function getHighestInsertIndexForTrack({ + tracks, + trackType, +}: { + tracks: TimelineTrack[]; + trackType: TrackType; +}): number { + const mainTrackIndex = tracks.findIndex((track) => isMainTrack(track)); + + if (trackType === "audio") { + return mainTrackIndex >= 0 ? mainTrackIndex + 1 : tracks.length; + } + + return 0; +} + +export function isMainTrack(track: TimelineTrack): track is VideoTrack { + return track.type === "video" && track.isMain === true; +} + +export function getMainTrack({ + tracks, +}: { + tracks: TimelineTrack[]; +}): TimelineTrack | null { + return tracks.find((track) => isMainTrack(track)) ?? null; +} + +export function ensureMainTrack({ + tracks, +}: { + tracks: TimelineTrack[]; +}): TimelineTrack[] { + const hasMainTrack = tracks.some((track) => isMainTrack(track)); + + if (!hasMainTrack) { + const mainTrack: TimelineTrack = { + id: generateUUID(), + name: "Main Track", + type: "video", + elements: [], + muted: false, + isMain: true, + hidden: false, + }; + return [mainTrack, ...tracks]; + } + + return tracks; +} + +export function canElementGoOnTrack({ + elementType, + trackType, +}: { + elementType: ElementType; + trackType: TrackType; +}): boolean { + return ELEMENT_TRACK_MAP[elementType] === trackType; +} + +export function validateElementTrackCompatibility({ + element, + track, +}: { + element: { type: ElementType }; + track: { type: TrackType }; +}): { isValid: boolean; errorMessage?: string } { + const isValid = canElementGoOnTrack({ + elementType: element.type, + trackType: track.type, + }); + + if (!isValid) { + return { + isValid: false, + errorMessage: `${element.type} elements cannot be placed on ${track.type} tracks`, + }; + } + + 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; +} -- cgit v1.3.1