Files
OpenCut/apps/web/src/lib/timeline/track-utils.ts
T

141 lines
3.2 KiB
TypeScript
Raw Normal View History

2026-01-16 18:07:28 +01:00
import type {
TrackType,
TimelineTrack,
ElementType,
VideoTrack,
AudioTrack,
StickerTrack,
TextTrack,
} from "@/types/timeline";
2026-01-05 01:35:10 +01:00
import {
TRACK_COLORS,
TRACK_HEIGHTS,
TRACK_GAP,
} from "@/constants/timeline-constants";
2025-11-26 08:47:03 +01:00
import { generateUUID } from "@/lib/utils";
2026-01-16 18:07:28 +01:00
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 | StickerTrack {
return track.type !== "audio";
}
2026-01-05 01:35:10 +01:00
export function getTrackColor({ type }: { type: TrackType }) {
2025-11-26 08:47:03 +01:00
return TRACK_COLORS[type];
}
2026-01-15 11:05:17 +01:00
export function getTrackClasses({ type }: { type: TrackType }) {
const colors = TRACK_COLORS[type];
return `${colors.solid} ${colors.background} ${colors.border}`.trim();
}
2025-11-26 08:47:03 +01:00
export function getTrackHeight({ type }: { type: TrackType }): number {
return TRACK_HEIGHTS[type];
}
export function getCumulativeHeightBefore({
tracks,
trackIndex,
}: {
tracks: Array<{ type: TrackType }>;
trackIndex: number;
}): number {
return tracks
.slice(0, trackIndex)
.reduce(
2026-01-05 01:35:10 +01:00
(sum, track) => sum + getTrackHeight({ type: track.type }) + TRACK_GAP,
2025-11-26 08:47:03 +01:00
0,
);
}
export function getTotalTracksHeight({
tracks,
}: {
tracks: Array<{ type: TrackType }>;
}): number {
const tracksHeight = tracks.reduce(
(sum, track) => sum + getTrackHeight({ type: track.type }),
0,
);
2026-01-05 01:35:10 +01:00
const gapsHeight = Math.max(0, tracks.length - 1) * TRACK_GAP;
2025-11-26 08:47:03 +01:00
return tracksHeight + gapsHeight;
}
2026-01-16 18:07:28 +01:00
export function isMainTrack(track: TimelineTrack): track is VideoTrack {
return track.type === "video" && track.isMain === true;
}
2025-11-26 08:47:03 +01:00
export function getMainTrack({
tracks,
}: {
tracks: TimelineTrack[];
}): TimelineTrack | null {
2026-01-16 18:07:28 +01:00
return tracks.find((track) => isMainTrack(track)) ?? null;
2025-11-26 08:47:03 +01:00
}
export function ensureMainTrack({
tracks,
}: {
tracks: TimelineTrack[];
}): TimelineTrack[] {
2026-01-16 18:07:28 +01:00
const hasMainTrack = tracks.some((track) => isMainTrack(track));
2025-11-26 08:47:03 +01:00
if (!hasMainTrack) {
const mainTrack: TimelineTrack = {
id: generateUUID(),
name: "Main Track",
2026-01-15 11:05:17 +01:00
type: "video",
2025-11-26 08:47:03 +01:00
elements: [],
muted: false,
isMain: true,
};
return [mainTrack, ...tracks];
}
return tracks;
}
export function canElementGoOnTrack({
elementType,
trackType,
}: {
2026-01-15 11:05:17 +01:00
elementType: ElementType;
2025-11-26 08:47:03 +01:00
trackType: TrackType;
}): boolean {
2026-01-05 01:35:10 +01:00
if (elementType === "text") return trackType === "text";
if (elementType === "audio") return trackType === "audio";
2026-01-15 11:05:17 +01:00
if (elementType === "sticker") return trackType === "sticker";
2026-01-05 01:35:10 +01:00
if (elementType === "video" || elementType === "image") {
2026-01-15 11:05:17 +01:00
return trackType === "video";
2025-11-26 08:47:03 +01:00
}
return false;
}
export function validateElementTrackCompatibility({
element,
track,
}: {
2026-01-15 11:05:17 +01:00
element: { type: ElementType };
2025-11-26 08:47:03 +01:00
track: { type: TrackType };
}): { isValid: boolean; errorMessage?: string } {
const isValid = canElementGoOnTrack({
elementType: element.type,
trackType: track.type,
});
if (!isValid) {
2026-01-05 01:35:10 +01:00
return {
isValid: false,
errorMessage: `${element.type} elements cannot be placed on ${track.type} tracks`,
};
2025-11-26 08:47:03 +01:00
}
return { isValid: true };
}