chore: remove stale files
This commit is contained in:
parent
7428dc75cf
commit
f1b45b4328
|
|
@ -1,193 +0,0 @@
|
||||||
import {
|
|
||||||
Command,
|
|
||||||
createElementSelectionResult,
|
|
||||||
type CommandResult,
|
|
||||||
} from "@/lib/commands/base-command";
|
|
||||||
import { EditorCore } from "@/core";
|
|
||||||
import type { SceneTracks, TimelineElement } from "@/lib/timeline";
|
|
||||||
import type { ElementClipboardItem } from "@/lib/clipboard";
|
|
||||||
import { generateUUID } from "@/utils/id";
|
|
||||||
import {
|
|
||||||
applyPlacement,
|
|
||||||
resolveTrackPlacement,
|
|
||||||
enforceMainTrackStart,
|
|
||||||
} from "@/lib/timeline/placement";
|
|
||||||
import { cloneAnimations } from "@/lib/animation";
|
|
||||||
|
|
||||||
export class PasteCommand extends Command {
|
|
||||||
private savedState: SceneTracks | null = null;
|
|
||||||
private pastedElements: { trackId: string; elementId: string }[] = [];
|
|
||||||
private readonly time: number;
|
|
||||||
private readonly clipboardItems: ElementClipboardItem[];
|
|
||||||
|
|
||||||
constructor({
|
|
||||||
time,
|
|
||||||
clipboardItems,
|
|
||||||
}: {
|
|
||||||
time: number;
|
|
||||||
clipboardItems: ElementClipboardItem[];
|
|
||||||
}) {
|
|
||||||
super();
|
|
||||||
this.time = time;
|
|
||||||
this.clipboardItems = clipboardItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
execute(): CommandResult | undefined {
|
|
||||||
if (this.clipboardItems.length === 0) return undefined;
|
|
||||||
|
|
||||||
const editor = EditorCore.getInstance();
|
|
||||||
this.savedState = editor.scenes.getActiveScene().tracks;
|
|
||||||
this.pastedElements = [];
|
|
||||||
|
|
||||||
const minStart = Math.min(
|
|
||||||
...this.clipboardItems.map((item) => item.element.startTime),
|
|
||||||
);
|
|
||||||
|
|
||||||
let updatedTracks = this.savedState;
|
|
||||||
const itemsByTrackId = groupClipboardItemsByTrackId({
|
|
||||||
clipboardItems: this.clipboardItems,
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const [trackId, items] of itemsByTrackId) {
|
|
||||||
const elementsToAdd = buildPastedElements({
|
|
||||||
items,
|
|
||||||
minStart,
|
|
||||||
time: this.time,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (elementsToAdd.length === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const trackType = items[0].trackType;
|
|
||||||
const sourceTrackIndex = [
|
|
||||||
...updatedTracks.overlay,
|
|
||||||
updatedTracks.main,
|
|
||||||
...updatedTracks.audio,
|
|
||||||
].findIndex((track) => track.id === trackId);
|
|
||||||
const placementResult = resolveTrackPlacement({
|
|
||||||
tracks: updatedTracks,
|
|
||||||
trackType,
|
|
||||||
timeSpans: elementsToAdd.map((element) => ({
|
|
||||||
startTime: element.startTime,
|
|
||||||
duration: element.duration,
|
|
||||||
})),
|
|
||||||
strategy: { type: "aboveSource", sourceTrackIndex },
|
|
||||||
});
|
|
||||||
if (!placementResult) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let elementsForPlacement = elementsToAdd;
|
|
||||||
if (placementResult.kind === "existingTrack") {
|
|
||||||
const targetTrack =
|
|
||||||
placementResult.trackIndex < updatedTracks.overlay.length
|
|
||||||
? updatedTracks.overlay[placementResult.trackIndex]
|
|
||||||
: placementResult.trackIndex === updatedTracks.overlay.length
|
|
||||||
? updatedTracks.main
|
|
||||||
: updatedTracks.audio[
|
|
||||||
placementResult.trackIndex - updatedTracks.overlay.length - 1
|
|
||||||
];
|
|
||||||
if (targetTrack?.id === updatedTracks.main.id) {
|
|
||||||
const earliestElement = elementsToAdd.reduce((earliest, element) =>
|
|
||||||
element.startTime < earliest.startTime ? element : earliest,
|
|
||||||
);
|
|
||||||
const adjustedEarliestStartTime = enforceMainTrackStart({
|
|
||||||
tracks: updatedTracks,
|
|
||||||
targetTrackId: targetTrack.id,
|
|
||||||
requestedStartTime: earliestElement.startTime,
|
|
||||||
});
|
|
||||||
const delta = adjustedEarliestStartTime - earliestElement.startTime;
|
|
||||||
|
|
||||||
if (delta !== 0) {
|
|
||||||
elementsForPlacement = elementsToAdd.map((element) => ({
|
|
||||||
...element,
|
|
||||||
startTime: Math.max(0, element.startTime + delta),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const applied = applyPlacement({
|
|
||||||
tracks: updatedTracks,
|
|
||||||
placementResult,
|
|
||||||
elements: elementsForPlacement,
|
|
||||||
});
|
|
||||||
if (!applied) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
updatedTracks = applied.updatedTracks;
|
|
||||||
|
|
||||||
for (const element of elementsForPlacement) {
|
|
||||||
this.pastedElements.push({
|
|
||||||
trackId: applied.targetTrackId,
|
|
||||||
elementId: element.id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
editor.timeline.updateTracks(updatedTracks);
|
|
||||||
|
|
||||||
if (this.pastedElements.length > 0) {
|
|
||||||
return createElementSelectionResult(this.pastedElements);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
undo(): void {
|
|
||||||
if (this.savedState) {
|
|
||||||
const editor = EditorCore.getInstance();
|
|
||||||
editor.timeline.updateTracks(this.savedState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getPastedElements(): { trackId: string; elementId: string }[] {
|
|
||||||
return this.pastedElements;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function groupClipboardItemsByTrackId({
|
|
||||||
clipboardItems,
|
|
||||||
}: {
|
|
||||||
clipboardItems: ElementClipboardItem[];
|
|
||||||
}): Map<string, ElementClipboardItem[]> {
|
|
||||||
const groupedItems = new Map<string, ElementClipboardItem[]>();
|
|
||||||
|
|
||||||
for (const item of clipboardItems) {
|
|
||||||
const existingItems = groupedItems.get(item.trackId) ?? [];
|
|
||||||
groupedItems.set(item.trackId, [...existingItems, item]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return groupedItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildPastedElements({
|
|
||||||
items,
|
|
||||||
minStart,
|
|
||||||
time,
|
|
||||||
}: {
|
|
||||||
items: ElementClipboardItem[];
|
|
||||||
minStart: number;
|
|
||||||
time: number;
|
|
||||||
}): TimelineElement[] {
|
|
||||||
const elementsToAdd: TimelineElement[] = [];
|
|
||||||
|
|
||||||
for (const item of items) {
|
|
||||||
const relativeOffset = item.element.startTime - minStart;
|
|
||||||
const startTime = Math.max(0, time + relativeOffset);
|
|
||||||
const newElementId = generateUUID();
|
|
||||||
|
|
||||||
elementsToAdd.push({
|
|
||||||
...item.element,
|
|
||||||
id: newElementId,
|
|
||||||
startTime,
|
|
||||||
animations: cloneAnimations({
|
|
||||||
animations: item.element.animations,
|
|
||||||
shouldRegenerateKeyframeIds: true,
|
|
||||||
}),
|
|
||||||
} as TimelineElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
return elementsToAdd;
|
|
||||||
}
|
|
||||||
|
|
@ -1,137 +0,0 @@
|
||||||
import {
|
|
||||||
Command,
|
|
||||||
createElementSelectionResult,
|
|
||||||
type CommandResult,
|
|
||||||
} from "@/lib/commands/base-command";
|
|
||||||
import type { SceneTracks, TimelineElement } from "@/lib/timeline";
|
|
||||||
import { generateUUID } from "@/utils/id";
|
|
||||||
import { EditorCore } from "@/core";
|
|
||||||
import {
|
|
||||||
applyPlacement,
|
|
||||||
resolveTrackPlacement,
|
|
||||||
} from "@/lib/timeline/placement";
|
|
||||||
import { cloneAnimations } from "@/lib/animation";
|
|
||||||
|
|
||||||
interface DuplicateElementsParams {
|
|
||||||
elements: { trackId: string; elementId: string }[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DuplicateElementsCommand extends Command {
|
|
||||||
private duplicatedElements: { trackId: string; elementId: string }[] = [];
|
|
||||||
private savedState: SceneTracks | null = null;
|
|
||||||
private elements: DuplicateElementsParams["elements"];
|
|
||||||
|
|
||||||
constructor({ elements }: DuplicateElementsParams) {
|
|
||||||
super();
|
|
||||||
this.elements = elements;
|
|
||||||
}
|
|
||||||
|
|
||||||
execute(): CommandResult | undefined {
|
|
||||||
const editor = EditorCore.getInstance();
|
|
||||||
this.savedState = editor.scenes.getActiveScene().tracks;
|
|
||||||
this.duplicatedElements = [];
|
|
||||||
|
|
||||||
let updatedTracks = this.savedState;
|
|
||||||
|
|
||||||
for (const track of [
|
|
||||||
...this.savedState.overlay,
|
|
||||||
this.savedState.main,
|
|
||||||
...this.savedState.audio,
|
|
||||||
]) {
|
|
||||||
const elementsToDuplicate = this.elements.filter(
|
|
||||||
(elementEntry) => elementEntry.trackId === track.id,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (elementsToDuplicate.length === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const elementIdsToDuplicate = new Set(
|
|
||||||
elementsToDuplicate.map((element) => element.elementId),
|
|
||||||
);
|
|
||||||
const newTrackElements: TimelineElement[] = [];
|
|
||||||
|
|
||||||
for (const element of track.elements) {
|
|
||||||
if (!elementIdsToDuplicate.has(element.id)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newId = generateUUID();
|
|
||||||
newTrackElements.push(
|
|
||||||
buildDuplicateElement({
|
|
||||||
element,
|
|
||||||
id: newId,
|
|
||||||
startTime: element.startTime,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const placementResult = resolveTrackPlacement({
|
|
||||||
tracks: updatedTracks,
|
|
||||||
trackType: track.type,
|
|
||||||
timeSpans: [],
|
|
||||||
strategy: { type: "alwaysNew", position: "highest" },
|
|
||||||
});
|
|
||||||
if (!placementResult || placementResult.kind !== "newTrack") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const applied = applyPlacement({
|
|
||||||
tracks: updatedTracks,
|
|
||||||
placementResult,
|
|
||||||
elements: newTrackElements,
|
|
||||||
});
|
|
||||||
if (!applied) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
updatedTracks = applied.updatedTracks;
|
|
||||||
|
|
||||||
for (const element of newTrackElements) {
|
|
||||||
this.duplicatedElements.push({
|
|
||||||
trackId: applied.targetTrackId,
|
|
||||||
elementId: element.id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
editor.timeline.updateTracks(updatedTracks);
|
|
||||||
|
|
||||||
if (this.duplicatedElements.length > 0) {
|
|
||||||
return createElementSelectionResult(this.duplicatedElements);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
undo(): void {
|
|
||||||
if (this.savedState) {
|
|
||||||
const editor = EditorCore.getInstance();
|
|
||||||
editor.timeline.updateTracks(this.savedState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getDuplicatedElements(): { trackId: string; elementId: string }[] {
|
|
||||||
return this.duplicatedElements;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildDuplicateElement({
|
|
||||||
element,
|
|
||||||
id,
|
|
||||||
startTime,
|
|
||||||
}: {
|
|
||||||
element: TimelineElement;
|
|
||||||
id: string;
|
|
||||||
startTime: number;
|
|
||||||
}): TimelineElement {
|
|
||||||
return {
|
|
||||||
...element,
|
|
||||||
id,
|
|
||||||
name: `${element.name} (copy)`,
|
|
||||||
startTime,
|
|
||||||
animations: cloneAnimations({
|
|
||||||
animations: element.animations,
|
|
||||||
shouldRegenerateKeyframeIds: true,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,187 +0,0 @@
|
||||||
import {
|
|
||||||
Command,
|
|
||||||
createElementSelectionResult,
|
|
||||||
type CommandResult,
|
|
||||||
} from "@/lib/commands/base-command";
|
|
||||||
import { EditorCore } from "@/core";
|
|
||||||
import type {
|
|
||||||
SceneTracks,
|
|
||||||
TimelineElement,
|
|
||||||
TimelineTrack,
|
|
||||||
} from "@/lib/timeline";
|
|
||||||
import {
|
|
||||||
buildEmptyTrack,
|
|
||||||
validateElementTrackCompatibility,
|
|
||||||
} from "@/lib/timeline/placement";
|
|
||||||
import type {
|
|
||||||
PlannedElementMove,
|
|
||||||
PlannedTrackCreation,
|
|
||||||
} from "@/lib/timeline/group-move";
|
|
||||||
import { findTrackInSceneTracks } from "@/lib/timeline/track-element-update";
|
|
||||||
|
|
||||||
export class MoveElementCommand extends Command {
|
|
||||||
private savedState: SceneTracks | null = null;
|
|
||||||
|
|
||||||
constructor({
|
|
||||||
moves,
|
|
||||||
createTracks = [],
|
|
||||||
}: {
|
|
||||||
moves: PlannedElementMove[];
|
|
||||||
createTracks?: PlannedTrackCreation[];
|
|
||||||
}) {
|
|
||||||
super();
|
|
||||||
this.moves = moves;
|
|
||||||
this.createTracks = createTracks;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly moves: PlannedElementMove[];
|
|
||||||
private readonly createTracks: PlannedTrackCreation[];
|
|
||||||
|
|
||||||
execute(): CommandResult | undefined {
|
|
||||||
const editor = EditorCore.getInstance();
|
|
||||||
this.savedState = editor.scenes.getActiveScene().tracks;
|
|
||||||
|
|
||||||
let tracksToUpdate = this.savedState;
|
|
||||||
for (const createTrack of [...this.createTracks].sort(
|
|
||||||
(firstTrack, secondTrack) => firstTrack.index - secondTrack.index,
|
|
||||||
)) {
|
|
||||||
tracksToUpdate = insertTrackAtDisplayIndex({
|
|
||||||
tracks: tracksToUpdate,
|
|
||||||
track: buildEmptyTrack({
|
|
||||||
id: createTrack.id,
|
|
||||||
type: createTrack.type,
|
|
||||||
}),
|
|
||||||
insertIndex: createTrack.index,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const movedElementsById = new Map<string, TimelineElement>();
|
|
||||||
for (const move of this.moves) {
|
|
||||||
const sourceTrack = findTrackInSceneTracks({
|
|
||||||
tracks: this.savedState,
|
|
||||||
trackId: move.sourceTrackId,
|
|
||||||
});
|
|
||||||
const sourceElement = sourceTrack?.elements.find(
|
|
||||||
(trackElement) => trackElement.id === move.elementId,
|
|
||||||
);
|
|
||||||
if (!sourceTrack || !sourceElement) {
|
|
||||||
throw new Error("Source track or element not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
const targetTrack = findTrackInSceneTracks({
|
|
||||||
tracks: tracksToUpdate,
|
|
||||||
trackId: move.targetTrackId,
|
|
||||||
});
|
|
||||||
if (!targetTrack) {
|
|
||||||
throw new Error("Target track not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
const validation = validateElementTrackCompatibility({
|
|
||||||
element: sourceElement,
|
|
||||||
track: targetTrack,
|
|
||||||
});
|
|
||||||
if (!validation.isValid) {
|
|
||||||
throw new Error(validation.errorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
movedElementsById.set(move.elementId, {
|
|
||||||
...sourceElement,
|
|
||||||
startTime: move.newStartTime,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const movedElementIds = new Set(this.moves.map((move) => move.elementId));
|
|
||||||
const movedElementsByTargetTrackId = new Map<string, TimelineElement[]>();
|
|
||||||
for (const move of this.moves) {
|
|
||||||
const movedElement = movedElementsById.get(move.elementId);
|
|
||||||
if (!movedElement) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextTargetElements =
|
|
||||||
movedElementsByTargetTrackId.get(move.targetTrackId) ?? [];
|
|
||||||
nextTargetElements.push(movedElement);
|
|
||||||
movedElementsByTargetTrackId.set(move.targetTrackId, nextTargetElements);
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedTracks = mapSceneTracks({
|
|
||||||
tracks: tracksToUpdate,
|
|
||||||
update: (track) => ({
|
|
||||||
...track,
|
|
||||||
elements: [
|
|
||||||
...track.elements.filter(
|
|
||||||
(element) => !movedElementIds.has(element.id),
|
|
||||||
),
|
|
||||||
...(movedElementsByTargetTrackId.get(track.id) ?? []),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
editor.timeline.updateTracks(updatedTracks);
|
|
||||||
return createElementSelectionResult(
|
|
||||||
this.moves.map(({ elementId, targetTrackId }) => ({
|
|
||||||
trackId: targetTrackId,
|
|
||||||
elementId,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
undo(): void {
|
|
||||||
if (this.savedState) {
|
|
||||||
const editor = EditorCore.getInstance();
|
|
||||||
editor.timeline.updateTracks(this.savedState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapSceneTracks({
|
|
||||||
tracks,
|
|
||||||
update,
|
|
||||||
}: {
|
|
||||||
tracks: SceneTracks;
|
|
||||||
update: <TTrack extends TimelineTrack>(track: TTrack) => TTrack;
|
|
||||||
}): SceneTracks {
|
|
||||||
return {
|
|
||||||
overlay: tracks.overlay.map((track) => update(track)),
|
|
||||||
main: update(tracks.main),
|
|
||||||
audio: tracks.audio.map((track) => update(track)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function insertTrackAtDisplayIndex({
|
|
||||||
tracks,
|
|
||||||
track,
|
|
||||||
insertIndex,
|
|
||||||
}: {
|
|
||||||
tracks: SceneTracks;
|
|
||||||
track: TimelineTrack;
|
|
||||||
insertIndex: number;
|
|
||||||
}): SceneTracks {
|
|
||||||
if (track.type === "audio") {
|
|
||||||
const audioInsertIndex = Math.max(
|
|
||||||
0,
|
|
||||||
Math.min(insertIndex - tracks.overlay.length - 1, tracks.audio.length),
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
...tracks,
|
|
||||||
audio: [
|
|
||||||
...tracks.audio.slice(0, audioInsertIndex),
|
|
||||||
track,
|
|
||||||
...tracks.audio.slice(audioInsertIndex),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const overlayInsertIndex = Math.max(
|
|
||||||
0,
|
|
||||||
Math.min(insertIndex, tracks.overlay.length),
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
...tracks,
|
|
||||||
overlay: [
|
|
||||||
...tracks.overlay.slice(0, overlayInsertIndex),
|
|
||||||
track,
|
|
||||||
...tracks.overlay.slice(overlayInsertIndex),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,182 +0,0 @@
|
||||||
import {
|
|
||||||
Command,
|
|
||||||
createElementSelectionResult,
|
|
||||||
type CommandResult,
|
|
||||||
} from "@/lib/commands/base-command";
|
|
||||||
import type { SceneTracks, TimelineElement } from "@/lib/timeline";
|
|
||||||
import { generateUUID } from "@/utils/id";
|
|
||||||
import { EditorCore } from "@/core";
|
|
||||||
import { isRetimableElement } from "@/lib/timeline";
|
|
||||||
import { splitAnimationsAtTime } from "@/lib/animation";
|
|
||||||
import { getSourceSpanAtClipTime } from "@/lib/retime";
|
|
||||||
|
|
||||||
export class SplitElementsCommand extends Command {
|
|
||||||
private savedState: SceneTracks | null = null;
|
|
||||||
private rightSideElements: { trackId: string; elementId: string }[] = [];
|
|
||||||
private readonly elements: { trackId: string; elementId: string }[];
|
|
||||||
private readonly splitTime: number;
|
|
||||||
private readonly retainSide: "both" | "left" | "right";
|
|
||||||
|
|
||||||
constructor({
|
|
||||||
elements,
|
|
||||||
splitTime,
|
|
||||||
retainSide = "both",
|
|
||||||
}: {
|
|
||||||
elements: { trackId: string; elementId: string }[];
|
|
||||||
splitTime: number;
|
|
||||||
retainSide?: "both" | "left" | "right";
|
|
||||||
}) {
|
|
||||||
super();
|
|
||||||
this.elements = elements;
|
|
||||||
this.splitTime = splitTime;
|
|
||||||
this.retainSide = retainSide;
|
|
||||||
}
|
|
||||||
|
|
||||||
getRightSideElements(): { trackId: string; elementId: string }[] {
|
|
||||||
return this.rightSideElements;
|
|
||||||
}
|
|
||||||
|
|
||||||
execute(): CommandResult | undefined {
|
|
||||||
const editor = EditorCore.getInstance();
|
|
||||||
this.savedState = editor.scenes.getActiveScene().tracks;
|
|
||||||
this.rightSideElements = [];
|
|
||||||
|
|
||||||
const splitTrack = <
|
|
||||||
TTrack extends { id: string; elements: TimelineElement[] },
|
|
||||||
>(
|
|
||||||
track: TTrack,
|
|
||||||
): TTrack => {
|
|
||||||
const elementsToSplit = this.elements.filter(
|
|
||||||
(target) => target.trackId === track.id,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (elementsToSplit.length === 0) {
|
|
||||||
return track;
|
|
||||||
}
|
|
||||||
|
|
||||||
const elements = track.elements.flatMap((element) => {
|
|
||||||
const shouldSplit = elementsToSplit.some(
|
|
||||||
(target) => target.elementId === element.id,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!shouldSplit) {
|
|
||||||
return [element];
|
|
||||||
}
|
|
||||||
|
|
||||||
const effectiveStart = element.startTime;
|
|
||||||
const effectiveEnd = element.startTime + element.duration;
|
|
||||||
|
|
||||||
if (
|
|
||||||
this.splitTime <= effectiveStart ||
|
|
||||||
this.splitTime >= effectiveEnd
|
|
||||||
) {
|
|
||||||
return [element];
|
|
||||||
}
|
|
||||||
|
|
||||||
const relativeTime = this.splitTime - element.startTime;
|
|
||||||
const leftVisibleDuration = relativeTime;
|
|
||||||
const rightVisibleDuration = element.duration - relativeTime;
|
|
||||||
const retimeRef = isRetimableElement(element)
|
|
||||||
? element.retime
|
|
||||||
: undefined;
|
|
||||||
const leftSourceSpan = getSourceSpanAtClipTime({
|
|
||||||
clipTime: leftVisibleDuration,
|
|
||||||
retime: retimeRef,
|
|
||||||
});
|
|
||||||
const totalSourceSpan = getSourceSpanAtClipTime({
|
|
||||||
clipTime: element.duration,
|
|
||||||
retime: retimeRef,
|
|
||||||
});
|
|
||||||
const rightSourceSpan = totalSourceSpan - leftSourceSpan;
|
|
||||||
const { leftAnimations, rightAnimations } = splitAnimationsAtTime({
|
|
||||||
animations: element.animations,
|
|
||||||
splitTime: relativeTime,
|
|
||||||
shouldIncludeSplitBoundary: true,
|
|
||||||
});
|
|
||||||
let splitResult: TimelineElement[];
|
|
||||||
|
|
||||||
if (this.retainSide === "left") {
|
|
||||||
splitResult = [
|
|
||||||
{
|
|
||||||
...element,
|
|
||||||
duration: leftVisibleDuration,
|
|
||||||
trimEnd: element.trimEnd + rightSourceSpan,
|
|
||||||
name: `${element.name} (left)`,
|
|
||||||
animations: leftAnimations,
|
|
||||||
...(retimeRef !== undefined ? { retime: retimeRef } : {}),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
} else if (this.retainSide === "right") {
|
|
||||||
const newId = generateUUID();
|
|
||||||
this.rightSideElements.push({
|
|
||||||
trackId: track.id,
|
|
||||||
elementId: newId,
|
|
||||||
});
|
|
||||||
splitResult = [
|
|
||||||
{
|
|
||||||
...element,
|
|
||||||
id: newId,
|
|
||||||
startTime: this.splitTime,
|
|
||||||
duration: rightVisibleDuration,
|
|
||||||
trimStart: element.trimStart + leftSourceSpan,
|
|
||||||
name: `${element.name} (right)`,
|
|
||||||
animations: rightAnimations,
|
|
||||||
...(retimeRef !== undefined ? { retime: retimeRef } : {}),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
// "both" - split into two pieces
|
|
||||||
const secondElementId = generateUUID();
|
|
||||||
this.rightSideElements.push({
|
|
||||||
trackId: track.id,
|
|
||||||
elementId: secondElementId,
|
|
||||||
});
|
|
||||||
splitResult = [
|
|
||||||
{
|
|
||||||
...element,
|
|
||||||
duration: leftVisibleDuration,
|
|
||||||
trimEnd: element.trimEnd + rightSourceSpan,
|
|
||||||
name: `${element.name} (left)`,
|
|
||||||
animations: leftAnimations,
|
|
||||||
...(retimeRef !== undefined ? { retime: retimeRef } : {}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
...element,
|
|
||||||
id: secondElementId,
|
|
||||||
startTime: this.splitTime,
|
|
||||||
duration: rightVisibleDuration,
|
|
||||||
trimStart: element.trimStart + leftSourceSpan,
|
|
||||||
name: `${element.name} (right)`,
|
|
||||||
animations: rightAnimations,
|
|
||||||
...(retimeRef !== undefined ? { retime: retimeRef } : {}),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return splitResult;
|
|
||||||
});
|
|
||||||
|
|
||||||
return { ...track, elements } as TTrack;
|
|
||||||
};
|
|
||||||
|
|
||||||
const updatedTracks: SceneTracks = {
|
|
||||||
overlay: this.savedState.overlay.map((track) => splitTrack(track)),
|
|
||||||
main: splitTrack(this.savedState.main),
|
|
||||||
audio: this.savedState.audio.map((track) => splitTrack(track)),
|
|
||||||
};
|
|
||||||
|
|
||||||
editor.timeline.updateTracks(updatedTracks);
|
|
||||||
|
|
||||||
if (this.rightSideElements.length > 0) {
|
|
||||||
return createElementSelectionResult(this.rightSideElements);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
undo(): void {
|
|
||||||
if (this.savedState) {
|
|
||||||
const editor = EditorCore.getInstance();
|
|
||||||
editor.timeline.updateTracks(this.savedState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue