summaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-04-22 14:36:51 +0200
committerMaze Winther <mazewinther@gmail.com>2026-04-22 14:36:51 +0200
commitf1b45b4328cff6a016720ce7b38b87ac2992dc2d (patch)
tree16b816caa0ecf9f7ae680d7f440667ecd57b748d /apps
parent7428dc75cfef15481ad747806e81309a5a99e3a9 (diff)
downloadopencut-f1b45b4328cff6a016720ce7b38b87ac2992dc2d.tar.gz
chore: remove stale files
Diffstat (limited to 'apps')
-rw-r--r--apps/web/src/lib/commands/timeline/clipboard/paste.ts193
-rw-r--r--apps/web/src/lib/commands/timeline/element/duplicate-elements.ts137
-rw-r--r--apps/web/src/lib/commands/timeline/element/move-elements.ts187
-rw-r--r--apps/web/src/lib/commands/timeline/element/split-elements.ts182
4 files changed, 0 insertions, 699 deletions
diff --git a/apps/web/src/lib/commands/timeline/clipboard/paste.ts b/apps/web/src/lib/commands/timeline/clipboard/paste.ts
deleted file mode 100644
index 58199a94..00000000
--- a/apps/web/src/lib/commands/timeline/clipboard/paste.ts
+++ /dev/null
@@ -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;
-}
diff --git a/apps/web/src/lib/commands/timeline/element/duplicate-elements.ts b/apps/web/src/lib/commands/timeline/element/duplicate-elements.ts
deleted file mode 100644
index c044a4e3..00000000
--- a/apps/web/src/lib/commands/timeline/element/duplicate-elements.ts
+++ /dev/null
@@ -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,
- }),
- };
-}
diff --git a/apps/web/src/lib/commands/timeline/element/move-elements.ts b/apps/web/src/lib/commands/timeline/element/move-elements.ts
deleted file mode 100644
index 782374d5..00000000
--- a/apps/web/src/lib/commands/timeline/element/move-elements.ts
+++ /dev/null
@@ -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),
- ],
- };
-}
diff --git a/apps/web/src/lib/commands/timeline/element/split-elements.ts b/apps/web/src/lib/commands/timeline/element/split-elements.ts
deleted file mode 100644
index f172d9c4..00000000
--- a/apps/web/src/lib/commands/timeline/element/split-elements.ts
+++ /dev/null
@@ -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);
- }
- }
-}