summaryrefslogtreecommitdiff
path: root/apps/web/src/timeline/placement/apply.ts
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/timeline/placement/apply.ts')
-rw-r--r--apps/web/src/timeline/placement/apply.ts158
1 files changed, 158 insertions, 0 deletions
diff --git a/apps/web/src/timeline/placement/apply.ts b/apps/web/src/timeline/placement/apply.ts
new file mode 100644
index 00000000..214b0611
--- /dev/null
+++ b/apps/web/src/timeline/placement/apply.ts
@@ -0,0 +1,158 @@
+import type {
+ AudioTrack,
+ EffectTrack,
+ GraphicTrack,
+ OverlayTrack,
+ SceneTracks,
+ TextTrack,
+ TimelineElement,
+ TimelineTrack,
+ VideoTrack,
+} from "@/timeline";
+import { generateUUID } from "@/utils/id";
+import { buildEmptyTrack } from "./track-factory";
+import type { PlacementResult } from "./types";
+import { updateTrackInSceneTracks } from "@/timeline/track-element-update";
+
+export function applyPlacement({
+ tracks,
+ placementResult,
+ elements,
+ newTrackInsertIndexOverride,
+}: {
+ tracks: SceneTracks;
+ placementResult: PlacementResult;
+ elements: TimelineElement[];
+ newTrackInsertIndexOverride?: number;
+}): { updatedTracks: SceneTracks; targetTrackId: string } | null {
+ const orderedTracks = [...tracks.overlay, tracks.main, ...tracks.audio];
+ if (placementResult.kind === "existingTrack") {
+ const targetTrack = orderedTracks[placementResult.trackIndex];
+ if (!targetTrack) {
+ return null;
+ }
+
+ const updatedTracks = updateTrackInSceneTracks({
+ tracks,
+ trackId: targetTrack.id,
+ update: (track) => ({
+ ...track,
+ elements: [...track.elements, ...elements],
+ }),
+ });
+
+ return { updatedTracks, targetTrackId: targetTrack.id };
+ }
+
+ const newTrackId = generateUUID();
+ const insertIndex =
+ newTrackInsertIndexOverride ?? placementResult.insertIndex;
+ const updatedTracks =
+ placementResult.trackType === "audio"
+ ? {
+ ...tracks,
+ audio: insertIntoAudioTracks({
+ tracks,
+ insertIndex,
+ track: buildPlacedAudioTrack({
+ id: newTrackId,
+ elements,
+ }),
+ }),
+ }
+ : {
+ ...tracks,
+ overlay: insertIntoOverlayTracks({
+ tracks,
+ insertIndex,
+ track: buildPlacedOverlayTrack({
+ id: newTrackId,
+ type: placementResult.trackType,
+ elements,
+ }),
+ }),
+ };
+ return { updatedTracks, targetTrackId: newTrackId };
+}
+
+function insertIntoOverlayTracks({
+ tracks,
+ insertIndex,
+ track,
+}: {
+ tracks: SceneTracks;
+ insertIndex: number;
+ track: OverlayTrack;
+}): OverlayTrack[] {
+ const normalizedInsertIndex = Math.max(
+ 0,
+ Math.min(insertIndex, tracks.overlay.length),
+ );
+ const nextTracks = [...tracks.overlay];
+ nextTracks.splice(normalizedInsertIndex, 0, track);
+ return nextTracks;
+}
+
+function insertIntoAudioTracks({
+ tracks,
+ insertIndex,
+ track,
+}: {
+ tracks: SceneTracks;
+ insertIndex: number;
+ track: AudioTrack;
+}): AudioTrack[] {
+ const audioInsertIndex = Math.max(
+ 0,
+ Math.min(insertIndex - tracks.overlay.length - 1, tracks.audio.length),
+ );
+ const nextTracks = [...tracks.audio];
+ nextTracks.splice(audioInsertIndex, 0, track);
+ return nextTracks;
+}
+
+function buildPlacedAudioTrack({
+ id,
+ elements,
+}: {
+ id: string;
+ elements: TimelineElement[];
+}): AudioTrack {
+ return {
+ ...buildEmptyTrack({ id, type: "audio" }),
+ elements: elements as AudioTrack["elements"],
+ };
+}
+
+function buildPlacedOverlayTrack({
+ id,
+ type,
+ elements,
+}: {
+ id: string;
+ type: Exclude<OverlayTrack["type"], "audio">;
+ elements: TimelineElement[];
+}): OverlayTrack {
+ switch (type) {
+ case "video":
+ return {
+ ...buildEmptyTrack({ id, type: "video" }),
+ elements: elements as VideoTrack["elements"],
+ };
+ case "text":
+ return {
+ ...buildEmptyTrack({ id, type: "text" }),
+ elements: elements as TextTrack["elements"],
+ };
+ case "graphic":
+ return {
+ ...buildEmptyTrack({ id, type: "graphic" }),
+ elements: elements as GraphicTrack["elements"],
+ };
+ case "effect":
+ return {
+ ...buildEmptyTrack({ id, type: "effect" }),
+ elements: elements as EffectTrack["elements"],
+ };
+ }
+}