diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-02-23 07:09:26 +0100 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-02-23 07:09:26 +0100 |
| commit | 744419469da403be57d30b5b19f6b43246caa57d (patch) | |
| tree | d86b253652496157223261d54f51d6f7b6fd047c /apps/web/src/lib/commands/timeline/element/split-elements.ts | |
| parent | 1762dc077dd512f6e8afff855b1ca6696338e0be (diff) | |
| download | opencut-744419469da403be57d30b5b19f6b43246caa57d.tar.gz | |
feat: basic ripple editing
Diffstat (limited to 'apps/web/src/lib/commands/timeline/element/split-elements.ts')
| -rw-r--r-- | apps/web/src/lib/commands/timeline/element/split-elements.ts | 134 |
1 files changed, 75 insertions, 59 deletions
diff --git a/apps/web/src/lib/commands/timeline/element/split-elements.ts b/apps/web/src/lib/commands/timeline/element/split-elements.ts index abe133c9..05a23fff 100644 --- a/apps/web/src/lib/commands/timeline/element/split-elements.ts +++ b/apps/web/src/lib/commands/timeline/element/split-elements.ts @@ -2,6 +2,7 @@ import { Command } from "@/lib/commands/base-command"; import type { TimelineTrack } from "@/types/timeline"; import { generateUUID } from "@/utils/id"; import { EditorCore } from "@/core"; +import { rippleShiftElements } from "@/lib/timeline"; export class SplitElementsCommand extends Command { private savedState: TimelineTrack[] | null = null; @@ -12,6 +13,7 @@ export class SplitElementsCommand extends Command { private elements: { trackId: string; elementId: string }[], private splitTime: number, private retainSide: "both" | "left" | "right" = "both", + private rippleEnabled = false, ) { super(); } @@ -28,74 +30,39 @@ export class SplitElementsCommand extends Command { const updatedTracks = this.savedState.map((track) => { const elementsToSplit = this.elements.filter( - (el) => el.trackId === track.id, + (target) => target.trackId === track.id, ); if (elementsToSplit.length === 0) { return track; } - return { - ...track, - elements: track.elements.flatMap((element) => { - const shouldSplit = elementsToSplit.some( - (el) => el.elementId === element.id, - ); + let leftVisibleDurationForRipple: number | null = null; - if (!shouldSplit) { - return [element]; - } - - const effectiveStart = element.startTime; - const effectiveEnd = element.startTime + element.duration; + let elements = track.elements.flatMap((element) => { + const shouldSplit = elementsToSplit.some( + (target) => target.elementId === element.id, + ); - if ( - this.splitTime <= effectiveStart || - this.splitTime >= effectiveEnd - ) { - return [element]; - } + if (!shouldSplit) { + return [element]; + } - const relativeTime = this.splitTime - element.startTime; - const leftVisibleDuration = relativeTime; - const rightVisibleDuration = element.duration - relativeTime; + const effectiveStart = element.startTime; + const effectiveEnd = element.startTime + element.duration; - if (this.retainSide === "left") { - return [ - { - ...element, - duration: leftVisibleDuration, - trimEnd: element.trimEnd + rightVisibleDuration, - name: `${element.name} (left)`, - }, - ]; - } + if ( + this.splitTime <= effectiveStart || + this.splitTime >= effectiveEnd + ) { + return [element]; + } - if (this.retainSide === "right") { - const newId = generateUUID(); - this.rightSideElements.push({ - trackId: track.id, - elementId: newId, - }); - return [ - { - ...element, - id: newId, - startTime: this.splitTime, - duration: rightVisibleDuration, - trimStart: element.trimStart + leftVisibleDuration, - name: `${element.name} (right)`, - }, - ]; - } - - // "both" - split into two pieces - const secondElementId = generateUUID(); - this.rightSideElements.push({ - trackId: track.id, - elementId: secondElementId, - }); + const relativeTime = this.splitTime - element.startTime; + const leftVisibleDuration = relativeTime; + const rightVisibleDuration = element.duration - relativeTime; + if (this.retainSide === "left") { return [ { ...element, @@ -103,17 +70,66 @@ export class SplitElementsCommand extends Command { trimEnd: element.trimEnd + rightVisibleDuration, name: `${element.name} (left)`, }, + ]; + } + + if (this.retainSide === "right") { + if (this.rippleEnabled && elementsToSplit.length === 1) { + leftVisibleDurationForRipple = leftVisibleDuration; + } + const newId = generateUUID(); + this.rightSideElements.push({ + trackId: track.id, + elementId: newId, + }); + return [ { ...element, - id: secondElementId, + id: newId, startTime: this.splitTime, duration: rightVisibleDuration, trimStart: element.trimStart + leftVisibleDuration, name: `${element.name} (right)`, }, ]; - }), - } as typeof track; + } + + const secondElementId = generateUUID(); + this.rightSideElements.push({ + trackId: track.id, + elementId: secondElementId, + }); + + return [ + { + ...element, + duration: leftVisibleDuration, + trimEnd: element.trimEnd + rightVisibleDuration, + name: `${element.name} (left)`, + }, + { + ...element, + id: secondElementId, + startTime: this.splitTime, + duration: rightVisibleDuration, + trimStart: element.trimStart + leftVisibleDuration, + name: `${element.name} (right)`, + }, + ]; + }); + + if ( + this.rippleEnabled && + leftVisibleDurationForRipple !== null + ) { + elements = rippleShiftElements({ + elements, + afterTime: this.splitTime, + shiftAmount: leftVisibleDurationForRipple, + }); + } + + return { ...track, elements } as typeof track; }); editor.timeline.updateTracks(updatedTracks); |
