chore: remove stale files

This commit is contained in:
Maze Winther 2026-04-22 14:36:51 +02:00
parent 7428dc75cf
commit f1b45b4328
4 changed files with 0 additions and 699 deletions

View File

@ -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;
}

View File

@ -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,
}),
};
}

View File

@ -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),
],
};
}

View File

@ -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);
}
}
}