refactor: extract timeline model types

Move project entity shapes into the model layer so sibling domains no longer depend on timeline for core data types.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Maze Winther
2026-05-03 03:49:52 +02:00
co-authored by Cursor
parent 79cc7d2159
commit 6ac2116daa
177 changed files with 661 additions and 676 deletions
+3 -3
View File
@@ -1,6 +1,6 @@
import { getKeyframeById } from "@/animation"; import { getKeyframeById } from "@/animation";
import type { SelectedKeyframeRef } from "@/animation/types"; import type { SelectedKeyframeRef } from "@/animation/types";
import type { TimelineElement } from "@/timeline"; import { elementId, type ElementRef, type TimelineElement } from "@/model";
import { PasteKeyframesCommand } from "@/commands/timeline"; import { PasteKeyframesCommand } from "@/commands/timeline";
import type { import type {
ClipboardHandler, ClipboardHandler,
@@ -12,7 +12,7 @@ function resolveSingleSourceElement({
selectedKeyframes, selectedKeyframes,
}: { }: {
selectedKeyframes: SelectedKeyframeRef[]; selectedKeyframes: SelectedKeyframeRef[];
}) { }): ElementRef | null {
const firstKeyframe = selectedKeyframes[0]; const firstKeyframe = selectedKeyframes[0];
if (!firstKeyframe) { if (!firstKeyframe) {
return null; return null;
@@ -20,7 +20,7 @@ function resolveSingleSourceElement({
const sourceElement = { const sourceElement = {
trackId: firstKeyframe.trackId, trackId: firstKeyframe.trackId,
elementId: firstKeyframe.elementId, elementId: elementId(firstKeyframe.elementId),
}; };
const isSingleSource = selectedKeyframes.every( const isSingleSource = selectedKeyframes.every(
(keyframe) => (keyframe) =>
+1 -5
View File
@@ -7,11 +7,7 @@ import type {
SelectedKeyframeRef, SelectedKeyframeRef,
} from "@/animation/types"; } from "@/animation/types";
import type { Command } from "@/commands/base-command"; import type { Command } from "@/commands/base-command";
import type { import type { CreateTimelineElement, ElementRef, TrackType } from "@/model";
CreateTimelineElement,
ElementRef,
TrackType,
} from "@/timeline";
export interface ElementClipboardItem { export interface ElementClipboardItem {
trackId: string; trackId: string;
+1 -1
View File
@@ -1,5 +1,5 @@
import type { EditorSelectionPatch } from "@/selection/editor-selection"; import type { EditorSelectionPatch } from "@/selection/editor-selection";
import type { ElementRef } from "@/timeline/types"; import type { ElementRef } from "@/model";
export interface CommandResult { export interface CommandResult {
selection?: EditorSelectionPatch; selection?: EditorSelectionPatch;
@@ -6,7 +6,7 @@ import { storageService } from "@/services/storage/service";
import { videoCache } from "@/services/video-cache/service"; import { videoCache } from "@/services/video-cache/service";
import { waveformCache } from "@/services/waveform-cache/service"; import { waveformCache } from "@/services/waveform-cache/service";
import { hasMediaId } from "@/timeline/element-utils"; import { hasMediaId } from "@/timeline/element-utils";
import type { SceneTracks } from "@/timeline"; import type { SceneTracks } from "@/model";
export class RemoveMediaAssetCommand extends Command { export class RemoveMediaAssetCommand extends Command {
private savedAssets: MediaAsset[] | null = null; private savedAssets: MediaAsset[] | null = null;
+1 -1
View File
@@ -1,6 +1,6 @@
import { Command, type CommandResult } from "@/commands/base-command"; import { Command, type CommandResult } from "@/commands/base-command";
import { EditorCore } from "@/core"; import { EditorCore } from "@/core";
import type { TScene } from "@/timeline"; import type { TScene } from "@/model";
import { buildDefaultScene } from "@/timeline/scenes"; import { buildDefaultScene } from "@/timeline/scenes";
export class CreateSceneCommand extends Command { export class CreateSceneCommand extends Command {
+1 -1
View File
@@ -1,6 +1,6 @@
import { Command, type CommandResult } from "@/commands/base-command"; import { Command, type CommandResult } from "@/commands/base-command";
import { EditorCore } from "@/core"; import { EditorCore } from "@/core";
import type { TScene } from "@/timeline"; import type { TScene } from "@/model";
import { canDeleteScene, getFallbackSceneAfterDelete } from "@/timeline/scenes"; import { canDeleteScene, getFallbackSceneAfterDelete } from "@/timeline/scenes";
export class DeleteSceneCommand extends Command { export class DeleteSceneCommand extends Command {
+1 -1
View File
@@ -1,6 +1,6 @@
import { Command, type CommandResult } from "@/commands/base-command"; import { Command, type CommandResult } from "@/commands/base-command";
import { EditorCore } from "@/core"; import { EditorCore } from "@/core";
import type { TScene } from "@/timeline"; import type { TScene } from "@/model";
import { updateSceneInArray } from "@/timeline/scenes"; import { updateSceneInArray } from "@/timeline/scenes";
import { getFrameTime, moveBookmarkInArray } from "@/timeline/bookmarks/index"; import { getFrameTime, moveBookmarkInArray } from "@/timeline/bookmarks/index";
@@ -1,6 +1,6 @@
import { Command, type CommandResult } from "@/commands/base-command"; import { Command, type CommandResult } from "@/commands/base-command";
import { EditorCore } from "@/core"; import { EditorCore } from "@/core";
import type { TScene } from "@/timeline"; import type { TScene } from "@/model";
import { updateSceneInArray } from "@/timeline/scenes"; import { updateSceneInArray } from "@/timeline/scenes";
import { import {
getFrameTime, getFrameTime,
+1 -1
View File
@@ -1,6 +1,6 @@
import { Command, type CommandResult } from "@/commands/base-command"; import { Command, type CommandResult } from "@/commands/base-command";
import { EditorCore } from "@/core"; import { EditorCore } from "@/core";
import type { TScene } from "@/timeline"; import type { TScene } from "@/model";
import { updateSceneInArray } from "@/timeline/scenes"; import { updateSceneInArray } from "@/timeline/scenes";
export class RenameSceneCommand extends Command { export class RenameSceneCommand extends Command {
@@ -1,6 +1,6 @@
import { Command, type CommandResult } from "@/commands/base-command"; import { Command, type CommandResult } from "@/commands/base-command";
import { EditorCore } from "@/core"; import { EditorCore } from "@/core";
import type { TScene } from "@/timeline"; import type { TScene } from "@/model";
import { updateSceneInArray } from "@/timeline/scenes"; import { updateSceneInArray } from "@/timeline/scenes";
import { import {
getFrameTime, getFrameTime,
@@ -1,6 +1,6 @@
import { Command, type CommandResult } from "@/commands/base-command"; import { Command, type CommandResult } from "@/commands/base-command";
import { EditorCore } from "@/core"; import { EditorCore } from "@/core";
import type { Bookmark, TScene } from "@/timeline"; import type { Bookmark, TScene } from "@/model";
import { updateSceneInArray } from "@/timeline/scenes"; import { updateSceneInArray } from "@/timeline/scenes";
import { import {
getFrameTime, getFrameTime,
@@ -6,7 +6,7 @@ import {
} from "@/animation"; } from "@/animation";
import { Command, type CommandResult } from "@/commands/base-command"; import { Command, type CommandResult } from "@/commands/base-command";
import type { KeyframeClipboardItem } from "@/clipboard"; import type { KeyframeClipboardItem } from "@/clipboard";
import type { SceneTracks, TimelineElement } from "@/timeline"; import type { SceneTracks, TimelineElement } from "@/model";
import { updateElementInSceneTracks } from "@/timeline"; import { updateElementInSceneTracks } from "@/timeline";
import { resolveAnimationTarget } from "@/timeline/animation-targets"; import { resolveAnimationTarget } from "@/timeline/animation-targets";
import { generateUUID } from "@/utils/id"; import { generateUUID } from "@/utils/id";
@@ -4,7 +4,7 @@ import {
type CommandResult, type CommandResult,
} from "@/commands/base-command"; } from "@/commands/base-command";
import { EditorCore } from "@/core"; import { EditorCore } from "@/core";
import type { SceneTracks, TimelineElement } from "@/timeline"; import type { ElementRef, SceneTracks, TimelineElement } from "@/model";
import type { ElementClipboardItem } from "@/clipboard"; import type { ElementClipboardItem } from "@/clipboard";
import { generateUUID } from "@/utils/id"; import { generateUUID } from "@/utils/id";
import { import {
@@ -16,7 +16,7 @@ import { cloneAnimations } from "@/animation";
export class PasteCommand extends Command { export class PasteCommand extends Command {
private savedState: SceneTracks | null = null; private savedState: SceneTracks | null = null;
private pastedElements: { trackId: string; elementId: string }[] = []; private pastedElements: ElementRef[] = [];
private readonly time: number; private readonly time: number;
private readonly clipboardItems: ElementClipboardItem[]; private readonly clipboardItems: ElementClipboardItem[];
@@ -142,7 +142,7 @@ export class PasteCommand extends Command {
} }
} }
getPastedElements(): { trackId: string; elementId: string }[] { getPastedElements(): ElementRef[] {
return this.pastedElements; return this.pastedElements;
} }
} }
@@ -1,7 +1,7 @@
import { Command, type CommandResult } from "@/commands/base-command"; import { Command, type CommandResult } from "@/commands/base-command";
import type { SceneTracks } from "@/timeline"; import type { SceneTracks } from "@/model";
import { EditorCore } from "@/core"; import { EditorCore } from "@/core";
import type { TimelineTrack } from "@/timeline"; import type { TimelineTrack } from "@/model";
function removeTrackElements<TTrack extends TimelineTrack>({ function removeTrackElements<TTrack extends TimelineTrack>({
track, track,
@@ -3,7 +3,13 @@ import {
createElementSelectionResult, createElementSelectionResult,
type CommandResult, type CommandResult,
} from "@/commands/base-command"; } from "@/commands/base-command";
import type { SceneTracks, TimelineElement } from "@/timeline"; import {
elementId,
type ElementId,
type ElementRef,
type SceneTracks,
type TimelineElement,
} from "@/model";
import { generateUUID } from "@/utils/id"; import { generateUUID } from "@/utils/id";
import { EditorCore } from "@/core"; import { EditorCore } from "@/core";
import { applyPlacement, resolveTrackPlacement } from "@/timeline/placement"; import { applyPlacement, resolveTrackPlacement } from "@/timeline/placement";
@@ -14,7 +20,7 @@ interface DuplicateElementsParams {
} }
export class DuplicateElementsCommand extends Command { export class DuplicateElementsCommand extends Command {
private duplicatedElements: { trackId: string; elementId: string }[] = []; private duplicatedElements: ElementRef[] = [];
private savedState: SceneTracks | null = null; private savedState: SceneTracks | null = null;
private elements: DuplicateElementsParams["elements"]; private elements: DuplicateElementsParams["elements"];
@@ -53,7 +59,7 @@ export class DuplicateElementsCommand extends Command {
continue; continue;
} }
const newId = generateUUID(); const newId = elementId(generateUUID());
newTrackElements.push( newTrackElements.push(
buildDuplicateElement({ buildDuplicateElement({
element, element,
@@ -107,7 +113,7 @@ export class DuplicateElementsCommand extends Command {
} }
} }
getDuplicatedElements(): { trackId: string; elementId: string }[] { getDuplicatedElements(): ElementRef[] {
return this.duplicatedElements; return this.duplicatedElements;
} }
} }
@@ -118,7 +124,7 @@ function buildDuplicateElement({
startTime, startTime,
}: { }: {
element: TimelineElement; element: TimelineElement;
id: string; id: ElementId;
startTime: number; startTime: number;
}): TimelineElement { }): TimelineElement {
return { return {
@@ -1,7 +1,7 @@
import { Command, type CommandResult } from "@/commands/base-command"; import { Command, type CommandResult } from "@/commands/base-command";
import { EditorCore } from "@/core"; import { EditorCore } from "@/core";
import { isVisualElement, updateElementInSceneTracks } from "@/timeline"; import { isVisualElement, updateElementInSceneTracks } from "@/timeline";
import type { SceneTracks, VisualElement } from "@/timeline"; import type { SceneTracks, VisualElement } from "@/model";
import { buildDefaultEffectInstance } from "@/effects"; import { buildDefaultEffectInstance } from "@/effects";
function addEffectToElement({ function addEffectToElement({
@@ -1,7 +1,7 @@
import { Command, type CommandResult } from "@/commands/base-command"; import { Command, type CommandResult } from "@/commands/base-command";
import { EditorCore } from "@/core"; import { EditorCore } from "@/core";
import { isVisualElement, updateElementInSceneTracks } from "@/timeline"; import { isVisualElement, updateElementInSceneTracks } from "@/timeline";
import type { SceneTracks, VisualElement } from "@/timeline"; import type { SceneTracks, VisualElement } from "@/model";
function removeEffectFromElement({ function removeEffectFromElement({
element, element,
@@ -1,7 +1,7 @@
import { Command, type CommandResult } from "@/commands/base-command"; import { Command, type CommandResult } from "@/commands/base-command";
import { EditorCore } from "@/core"; import { EditorCore } from "@/core";
import { isVisualElement, updateElementInSceneTracks } from "@/timeline"; import { isVisualElement, updateElementInSceneTracks } from "@/timeline";
import type { SceneTracks, VisualElement } from "@/timeline"; import type { SceneTracks, VisualElement } from "@/model";
function reorderEffectsOnElement({ function reorderEffectsOnElement({
element, element,
@@ -1,7 +1,7 @@
import { Command, type CommandResult } from "@/commands/base-command"; import { Command, type CommandResult } from "@/commands/base-command";
import { EditorCore } from "@/core"; import { EditorCore } from "@/core";
import { isVisualElement, updateElementInSceneTracks } from "@/timeline"; import { isVisualElement, updateElementInSceneTracks } from "@/timeline";
import type { SceneTracks, VisualElement } from "@/timeline"; import type { SceneTracks, VisualElement } from "@/model";
export function toggleEffectOnElement({ export function toggleEffectOnElement({
element, element,
@@ -2,7 +2,7 @@ import { Command, type CommandResult } from "@/commands/base-command";
import { EditorCore } from "@/core"; import { EditorCore } from "@/core";
import { isVisualElement, updateElementInSceneTracks } from "@/timeline"; import { isVisualElement, updateElementInSceneTracks } from "@/timeline";
import type { ParamValues } from "@/params"; import type { ParamValues } from "@/params";
import type { SceneTracks, VisualElement } from "@/timeline"; import type { SceneTracks, VisualElement } from "@/model";
function updateEffectParamsOnElement({ function updateEffectParamsOnElement({
element, element,
@@ -1,11 +1,13 @@
import { Command, type CommandResult } from "@/commands/base-command"; import { Command, type CommandResult } from "@/commands/base-command";
import { EditorCore } from "@/core"; import { EditorCore } from "@/core";
import type { import {
CreateTimelineElement, elementId,
SceneTracks, type CreateTimelineElement,
TimelineElement, type ElementId,
TrackType, type SceneTracks,
} from "@/timeline"; type TimelineElement,
type TrackType,
} from "@/model";
import { generateUUID } from "@/utils/id"; import { generateUUID } from "@/utils/id";
import { requiresMediaId } from "@/timeline/element-utils"; import { requiresMediaId } from "@/timeline/element-utils";
import type { MediaAsset } from "@/media/types"; import type { MediaAsset } from "@/media/types";
@@ -29,13 +31,13 @@ export interface InsertElementParams {
} }
export class InsertElementCommand extends Command { export class InsertElementCommand extends Command {
private elementId: string; private elementId: ElementId;
private savedState: SceneTracks | null = null; private savedState: SceneTracks | null = null;
private targetTrackId: string | null = null; private targetTrackId: string | null = null;
constructor({ element, placement }: InsertElementParams) { constructor({ element, placement }: InsertElementParams) {
super(); super();
this.elementId = generateUUID(); this.elementId = elementId(generateUUID());
this.element = element; this.element = element;
this.placement = placement; this.placement = placement;
} }
@@ -3,7 +3,7 @@ import { Command, type CommandResult } from "@/commands/base-command";
import { removeEffectParamKeyframe } from "@/animation/effect-param-channel"; import { removeEffectParamKeyframe } from "@/animation/effect-param-channel";
import { updateElementInSceneTracks } from "@/timeline"; import { updateElementInSceneTracks } from "@/timeline";
import { isVisualElement } from "@/timeline/element-utils"; import { isVisualElement } from "@/timeline/element-utils";
import type { SceneTracks } from "@/timeline"; import type { SceneTracks } from "@/model";
export class RemoveEffectParamKeyframeCommand extends Command { export class RemoveEffectParamKeyframeCommand extends Command {
private savedState: SceneTracks | null = null; private savedState: SceneTracks | null = null;
@@ -6,7 +6,7 @@ import {
import { Command, type CommandResult } from "@/commands/base-command"; import { Command, type CommandResult } from "@/commands/base-command";
import { updateElementInSceneTracks } from "@/timeline"; import { updateElementInSceneTracks } from "@/timeline";
import type { AnimationPath, AnimationValue } from "@/animation/types"; import type { AnimationPath, AnimationValue } from "@/animation/types";
import type { SceneTracks, TimelineElement } from "@/timeline"; import type { SceneTracks, TimelineElement } from "@/model";
import { resolveAnimationTarget } from "@/timeline/animation-targets"; import { resolveAnimationTarget } from "@/timeline/animation-targets";
function removeKeyframeAndPersist({ function removeKeyframeAndPersist({
@@ -3,7 +3,7 @@ import { retimeElementKeyframe } from "@/animation";
import { Command, type CommandResult } from "@/commands/base-command"; import { Command, type CommandResult } from "@/commands/base-command";
import { updateElementInSceneTracks } from "@/timeline"; import { updateElementInSceneTracks } from "@/timeline";
import type { AnimationPath } from "@/animation/types"; import type { AnimationPath } from "@/animation/types";
import type { SceneTracks } from "@/timeline"; import type { SceneTracks } from "@/model";
import { resolveAnimationTarget } from "@/timeline/animation-targets"; import { resolveAnimationTarget } from "@/timeline/animation-targets";
export class RetimeKeyframeCommand extends Command { export class RetimeKeyframeCommand extends Command {
@@ -9,7 +9,7 @@ import type {
AnimationPath, AnimationPath,
ScalarCurveKeyframePatch, ScalarCurveKeyframePatch,
} from "@/animation/types"; } from "@/animation/types";
import type { SceneTracks } from "@/timeline"; import type { SceneTracks } from "@/model";
export class UpdateScalarKeyframeCurveCommand extends Command { export class UpdateScalarKeyframeCurveCommand extends Command {
private savedState: SceneTracks | null = null; private savedState: SceneTracks | null = null;
@@ -8,7 +8,7 @@ import { updateElementInSceneTracks } from "@/timeline";
import { isVisualElement } from "@/timeline/element-utils"; import { isVisualElement } from "@/timeline/element-utils";
import { resolveAnimationTarget } from "@/timeline/animation-targets"; import { resolveAnimationTarget } from "@/timeline/animation-targets";
import type { AnimationInterpolation } from "@/animation/types"; import type { AnimationInterpolation } from "@/animation/types";
import type { SceneTracks } from "@/timeline"; import type { SceneTracks } from "@/model";
export class UpsertEffectParamKeyframeCommand extends Command { export class UpsertEffectParamKeyframeCommand extends Command {
private savedState: SceneTracks | null = null; private savedState: SceneTracks | null = null;
@@ -2,7 +2,7 @@ import { EditorCore } from "@/core";
import { Command, type CommandResult } from "@/commands/base-command"; import { Command, type CommandResult } from "@/commands/base-command";
import { upsertPathKeyframe } from "@/animation"; import { upsertPathKeyframe } from "@/animation";
import { updateElementInSceneTracks } from "@/timeline"; import { updateElementInSceneTracks } from "@/timeline";
import type { SceneTracks } from "@/timeline"; import type { SceneTracks } from "@/model";
import { resolveAnimationTarget } from "@/timeline/animation-targets"; import { resolveAnimationTarget } from "@/timeline/animation-targets";
import type { import type {
AnimationPath, AnimationPath,
@@ -6,7 +6,7 @@ import {
} from "@/masks/custom-path"; } from "@/masks/custom-path";
import type { CustomMask } from "@/masks/types"; import type { CustomMask } from "@/masks/types";
import { isMaskableElement, updateElementInSceneTracks } from "@/timeline"; import { isMaskableElement, updateElementInSceneTracks } from "@/timeline";
import type { MaskableElement, SceneTracks } from "@/timeline"; import type { MaskableElement, SceneTracks } from "@/model";
function deletePointsFromCustomMask({ function deletePointsFromCustomMask({
mask, mask,
@@ -4,7 +4,7 @@ import { insertPointOnCustomMaskSegment } from "@/masks/definitions/custom";
import type { ElementBounds } from "@/preview/element-bounds"; import type { ElementBounds } from "@/preview/element-bounds";
import type { CustomMask } from "@/masks/types"; import type { CustomMask } from "@/masks/types";
import { isMaskableElement, updateElementInSceneTracks } from "@/timeline"; import { isMaskableElement, updateElementInSceneTracks } from "@/timeline";
import type { MaskableElement, SceneTracks } from "@/timeline"; import type { MaskableElement, SceneTracks } from "@/model";
function insertPointIntoCustomMask({ function insertPointIntoCustomMask({
mask, mask,
@@ -1,7 +1,7 @@
import { EditorCore } from "@/core"; import { EditorCore } from "@/core";
import { Command, type CommandResult } from "@/commands/base-command"; import { Command, type CommandResult } from "@/commands/base-command";
import { isMaskableElement, updateElementInSceneTracks } from "@/timeline"; import { isMaskableElement, updateElementInSceneTracks } from "@/timeline";
import type { SceneTracks, MaskableElement } from "@/timeline"; import type { MaskableElement, SceneTracks } from "@/model";
function removeMaskFromElement({ function removeMaskFromElement({
element, element,
@@ -2,7 +2,7 @@ import { EditorCore } from "@/core";
import { Command, type CommandResult } from "@/commands/base-command"; import { Command, type CommandResult } from "@/commands/base-command";
import { isMaskableElement, updateElementInSceneTracks } from "@/timeline"; import { isMaskableElement, updateElementInSceneTracks } from "@/timeline";
import type { Mask } from "@/masks/types"; import type { Mask } from "@/masks/types";
import type { SceneTracks, MaskableElement } from "@/timeline"; import type { MaskableElement, SceneTracks } from "@/model";
export function toggleMaskInvertedOnElement({ export function toggleMaskInvertedOnElement({
element, element,
@@ -4,7 +4,12 @@ import {
type CommandResult, type CommandResult,
} from "@/commands/base-command"; } from "@/commands/base-command";
import { EditorCore } from "@/core"; import { EditorCore } from "@/core";
import type { SceneTracks, TimelineElement, TimelineTrack } from "@/timeline"; import {
elementId as toElementId,
type SceneTracks,
type TimelineElement,
type TimelineTrack,
} from "@/model";
import { import {
buildEmptyTrack, buildEmptyTrack,
validateElementTrackCompatibility, validateElementTrackCompatibility,
@@ -117,7 +122,7 @@ export class MoveElementCommand extends Command {
return createElementSelectionResult( return createElementSelectionResult(
this.moves.map(({ elementId, targetTrackId }) => ({ this.moves.map(({ elementId, targetTrackId }) => ({
trackId: targetTrackId, trackId: targetTrackId,
elementId, elementId: toElementId(elementId),
})), })),
); );
} }
@@ -3,7 +3,12 @@ import {
createElementSelectionResult, createElementSelectionResult,
type CommandResult, type CommandResult,
} from "@/commands/base-command"; } from "@/commands/base-command";
import type { SceneTracks, TimelineElement } from "@/timeline"; import {
elementId,
type ElementRef,
type SceneTracks,
type TimelineElement,
} from "@/model";
import { generateUUID } from "@/utils/id"; import { generateUUID } from "@/utils/id";
import { EditorCore } from "@/core"; import { EditorCore } from "@/core";
import { isRetimableElement } from "@/timeline"; import { isRetimableElement } from "@/timeline";
@@ -12,7 +17,7 @@ import { getSourceSpanAtClipTime } from "@/retime";
export class SplitElementsCommand extends Command { export class SplitElementsCommand extends Command {
private savedState: SceneTracks | null = null; private savedState: SceneTracks | null = null;
private rightSideElements: { trackId: string; elementId: string }[] = []; private rightSideElements: ElementRef[] = [];
private readonly elements: { trackId: string; elementId: string }[]; private readonly elements: { trackId: string; elementId: string }[];
private readonly splitTime: number; private readonly splitTime: number;
private readonly retainSide: "both" | "left" | "right"; private readonly retainSide: "both" | "left" | "right";
@@ -32,7 +37,7 @@ export class SplitElementsCommand extends Command {
this.retainSide = retainSide; this.retainSide = retainSide;
} }
getRightSideElements(): { trackId: string; elementId: string }[] { getRightSideElements(): ElementRef[] {
return this.rightSideElements; return this.rightSideElements;
} }
@@ -107,7 +112,7 @@ export class SplitElementsCommand extends Command {
}, },
]; ];
} else if (this.retainSide === "right") { } else if (this.retainSide === "right") {
const newId = generateUUID(); const newId = elementId(generateUUID());
this.rightSideElements.push({ this.rightSideElements.push({
trackId: track.id, trackId: track.id,
elementId: newId, elementId: newId,
@@ -126,7 +131,7 @@ export class SplitElementsCommand extends Command {
]; ];
} else { } else {
// "both" - split into two pieces // "both" - split into two pieces
const secondElementId = generateUUID(); const secondElementId = elementId(generateUUID());
this.rightSideElements.push({ this.rightSideElements.push({
trackId: track.id, trackId: track.id,
elementId: secondElementId, elementId: secondElementId,
@@ -10,11 +10,12 @@ import {
resolveTrackPlacement, resolveTrackPlacement,
} from "@/timeline/placement"; } from "@/timeline/placement";
import { updateElementInSceneTracks } from "@/timeline/track-element-update"; import { updateElementInSceneTracks } from "@/timeline/track-element-update";
import type { import {
SceneTracks, elementId,
TimelineElement, type SceneTracks,
VideoElement, type TimelineElement,
} from "@/timeline/types"; type VideoElement,
} from "@/model";
import { generateUUID } from "@/utils/id"; import { generateUUID } from "@/utils/id";
export class ToggleSourceAudioSeparationCommand extends Command { export class ToggleSourceAudioSeparationCommand extends Command {
@@ -75,7 +76,7 @@ export class ToggleSourceAudioSeparationCommand extends Command {
...buildSeparatedAudioElement({ ...buildSeparatedAudioElement({
sourceElement: videoElement, sourceElement: videoElement,
}), }),
id: generateUUID(), id: elementId(generateUUID()),
}; };
const placementResult = resolveTrackPlacement({ const placementResult = resolveTrackPlacement({
tracks: this.savedState, tracks: this.savedState,
@@ -1,6 +1,6 @@
import { EditorCore } from "@/core"; import { EditorCore } from "@/core";
import { Command, type CommandResult } from "@/commands/base-command"; import { Command, type CommandResult } from "@/commands/base-command";
import type { SceneTracks, TimelineElement } from "@/timeline"; import type { SceneTracks, TimelineElement } from "@/model";
import { import {
findTrackInSceneTracks, findTrackInSceneTracks,
updateElementInSceneTracks, updateElementInSceneTracks,
@@ -1,5 +1,5 @@
import { Command, type CommandResult } from "@/commands/base-command"; import { Command, type CommandResult } from "@/commands/base-command";
import type { SceneTracks, TrackType } from "@/timeline"; import type { SceneTracks, TrackType } from "@/model";
import { generateUUID } from "@/utils/id"; import { generateUUID } from "@/utils/id";
import { EditorCore } from "@/core"; import { EditorCore } from "@/core";
import { import {
@@ -1,6 +1,6 @@
import { Command, type CommandResult } from "@/commands/base-command"; import { Command, type CommandResult } from "@/commands/base-command";
import { EditorCore } from "@/core"; import { EditorCore } from "@/core";
import type { SceneTracks } from "@/timeline"; import type { SceneTracks } from "@/model";
export class RemoveTrackCommand extends Command { export class RemoveTrackCommand extends Command {
private savedState: SceneTracks | null = null; private savedState: SceneTracks | null = null;
@@ -1,5 +1,5 @@
import { Command, type CommandResult } from "@/commands/base-command"; import { Command, type CommandResult } from "@/commands/base-command";
import type { SceneTracks } from "@/timeline"; import type { SceneTracks } from "@/model";
import { EditorCore } from "@/core"; import { EditorCore } from "@/core";
import { canTrackHaveAudio, findTrackInSceneTracks, updateTrackInSceneTracks } from "@/timeline"; import { canTrackHaveAudio, findTrackInSceneTracks, updateTrackInSceneTracks } from "@/timeline";
@@ -1,5 +1,5 @@
import { Command, type CommandResult } from "@/commands/base-command"; import { Command, type CommandResult } from "@/commands/base-command";
import type { SceneTracks } from "@/timeline"; import type { SceneTracks } from "@/model";
import { EditorCore } from "@/core"; import { EditorCore } from "@/core";
import { canTrackBeHidden, findTrackInSceneTracks, updateTrackInSceneTracks } from "@/timeline"; import { canTrackBeHidden, findTrackInSceneTracks, updateTrackInSceneTracks } from "@/timeline";
@@ -1,5 +1,5 @@
import { Command, type CommandResult } from "@/commands/base-command"; import { Command, type CommandResult } from "@/commands/base-command";
import type { SceneTracks } from "@/timeline"; import type { SceneTracks } from "@/model";
import { EditorCore } from "@/core"; import { EditorCore } from "@/core";
export class TracksSnapshotCommand extends Command { export class TracksSnapshotCommand extends Command {
@@ -45,7 +45,7 @@ import {
type MediaViewMode, type MediaViewMode,
useAssetsPanelStore, useAssetsPanelStore,
} from "@/components/editor/panels/assets/assets-panel-store"; } from "@/components/editor/panels/assets/assets-panel-store";
import { MASKABLE_ELEMENT_TYPES } from "@/timeline"; import { MASKABLE_ELEMENT_TYPES } from "@/model";
import type { MediaAsset } from "@/media/types"; import type { MediaAsset } from "@/media/types";
import { cn } from "@/utils/ui"; import { cn } from "@/utils/ui";
import { import {
@@ -4,7 +4,7 @@ import {
hasKeyframesForPath, hasKeyframesForPath,
} from "@/animation"; } from "@/animation";
import type { AnimationPropertyPath, ElementAnimations } from "@/animation/types"; import type { AnimationPropertyPath, ElementAnimations } from "@/animation/types";
import type { TimelineElement } from "@/timeline"; import type { TimelineElement } from "@/model";
import { upsertElementKeyframe } from "@/timeline/animation-properties"; import { upsertElementKeyframe } from "@/timeline/animation-properties";
export function useKeyframedColorProperty({ export function useKeyframedColorProperty({
@@ -4,7 +4,7 @@ import {
hasKeyframesForPath, hasKeyframesForPath,
} from "@/animation"; } from "@/animation";
import type { AnimationPropertyPath, ElementAnimations } from "@/animation/types"; import type { AnimationPropertyPath, ElementAnimations } from "@/animation/types";
import type { TimelineElement } from "@/timeline"; import type { TimelineElement } from "@/model";
import { upsertElementKeyframe } from "@/timeline/animation-properties"; import { upsertElementKeyframe } from "@/timeline/animation-properties";
import { snapToStep } from "@/utils/math"; import { snapToStep } from "@/utils/math";
import { usePropertyDraft } from "./use-property-draft"; import { usePropertyDraft } from "./use-property-draft";
@@ -16,7 +16,7 @@ import {
getAnimationParamValueKind, getAnimationParamValueKind,
} from "@/animation/animated-params"; } from "@/animation/animated-params";
import type { ParamDefinition } from "@/params"; import type { ParamDefinition } from "@/params";
import type { TimelineElement } from "@/timeline"; import type { TimelineElement } from "@/model";
export interface KeyframedParamPropertyResult { export interface KeyframedParamPropertyResult {
hasAnimatedKeyframes: boolean; hasAnimatedKeyframes: boolean;
@@ -1,17 +1,5 @@
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import type { import type { AudioElement, EffectElement, GraphicElement, ImageElement, MaskableElement, RetimableElement, StickerElement, TextElement, TimelineElement, VideoElement, VisualElement } from "@/model";
EffectElement,
GraphicElement,
ImageElement,
MaskableElement,
RetimableElement,
StickerElement,
TextElement,
VisualElement,
VideoElement,
AudioElement,
TimelineElement,
} from "@/timeline";
import type { MediaAsset } from "@/media/types"; import type { MediaAsset } from "@/media/types";
import { HugeiconsIcon } from "@hugeicons/react"; import { HugeiconsIcon } from "@hugeicons/react";
import { import {
+1 -1
View File
@@ -2,7 +2,7 @@ import type { EditorCore } from "@/core";
import type { Command, CommandResult } from "@/commands"; import type { Command, CommandResult } from "@/commands";
import type { EditorSelectionSnapshot } from "@/selection/editor-selection"; import type { EditorSelectionSnapshot } from "@/selection/editor-selection";
import { applyRippleAdjustments, computeRippleAdjustments } from "@/ripple"; import { applyRippleAdjustments, computeRippleAdjustments } from "@/ripple";
import type { SceneTracks } from "@/timeline/types"; import type { SceneTracks } from "@/model";
interface CommandHistoryEntry { interface CommandHistoryEntry {
command: Command; command: Command;
+1 -1
View File
@@ -1,5 +1,5 @@
import type { EditorCore } from "@/core"; import type { EditorCore } from "@/core";
import type { SceneTracks, TScene } from "@/timeline"; import type { SceneTracks, TScene } from "@/model";
import { storageService } from "@/services/storage/service"; import { storageService } from "@/services/storage/service";
import { import {
getMainScene, getMainScene,
@@ -6,7 +6,7 @@ import type {
EditorSelectionSnapshot, EditorSelectionSnapshot,
SelectedMaskPointSelection, SelectedMaskPointSelection,
} from "@/selection/editor-selection"; } from "@/selection/editor-selection";
import type { ElementRef } from "@/timeline/types"; import type { ElementRef } from "@/model";
export class SelectionManager { export class SelectionManager {
private selectedElements: ElementRef[] = []; private selectedElements: ElementRef[] = [];
@@ -1,13 +1,7 @@
import type { EditorCore } from "@/core"; import type { EditorCore } from "@/core";
import type { ElementBounds } from "@/preview/element-bounds"; import type { ElementBounds } from "@/preview/element-bounds";
import type { ParamValues } from "@/params"; import type { ParamValues } from "@/params";
import type { import type { RetimeConfig, SceneTracks, TimelineElement, TimelineTrack, TrackType } from "@/model";
SceneTracks,
TrackType,
TimelineTrack,
TimelineElement,
RetimeConfig,
} from "@/timeline";
import { calculateTotalDuration } from "@/timeline"; import { calculateTotalDuration } from "@/timeline";
import { TimelineDragSource } from "@/timeline/drag-source"; import { TimelineDragSource } from "@/timeline/drag-source";
import { findTrackInSceneTracks } from "@/timeline/track-element-update"; import { findTrackInSceneTracks } from "@/timeline/track-element-update";
@@ -3,7 +3,7 @@
import { useState } from "react"; import { useState } from "react";
import type { ParamValues } from "@/params"; import type { ParamValues } from "@/params";
import type { Effect } from "@/effects/types"; import type { Effect } from "@/effects/types";
import type { EffectElement, VisualElement } from "@/timeline"; import type { EffectElement, VisualElement } from "@/model";
import { effectsRegistry } from "@/effects"; import { effectsRegistry } from "@/effects";
import { useEditor } from "@/editor/use-editor"; import { useEditor } from "@/editor/use-editor";
import { useElementPreview } from "@/timeline/hooks/use-element-preview"; import { useElementPreview } from "@/timeline/hooks/use-element-preview";
+1 -1
View File
@@ -3,7 +3,7 @@ import { buildDefaultParamValues } from "@/params/registry";
import { effectsRegistry } from "./registry"; import { effectsRegistry } from "./registry";
import type { ParamValues } from "@/params"; import type { ParamValues } from "@/params";
import type { Effect, EffectDefinition, EffectPass } from "@/effects/types"; import type { Effect, EffectDefinition, EffectPass } from "@/effects/types";
import { VISUAL_ELEMENT_TYPES } from "@/timeline"; import { VISUAL_ELEMENT_TYPES } from "@/model";
export { effectsRegistry } from "./registry"; export { effectsRegistry } from "./registry";
export { registerDefaultEffects } from "./definitions"; export { registerDefaultEffects } from "./definitions";
@@ -7,7 +7,7 @@ import {
type KeyframedParamPropertyResult, type KeyframedParamPropertyResult,
} from "@/components/editor/panels/properties/hooks/use-keyframed-param-property"; } from "@/components/editor/panels/properties/hooks/use-keyframed-param-property";
import type { ParamDefinition, ParamValues } from "@/params"; import type { ParamDefinition, ParamValues } from "@/params";
import type { GraphicElement } from "@/timeline"; import type { GraphicElement } from "@/model";
import { import {
graphicsRegistry, graphicsRegistry,
registerDefaultGraphics, registerDefaultGraphics,
+1 -1
View File
@@ -1,6 +1,6 @@
"use client"; "use client";
import type { MaskableElement } from "@/timeline"; import type { MaskableElement } from "@/model";
import type { Mask, MaskType, TextMask } from "@/masks/types"; import type { Mask, MaskType, TextMask } from "@/masks/types";
import type { NumberParamDefinition, SelectParamDefinition } from "@/params"; import type { NumberParamDefinition, SelectParamDefinition } from "@/params";
import { masksRegistry, buildDefaultMaskInstance } from "@/masks"; import { masksRegistry, buildDefaultMaskInstance } from "@/masks";
+3 -3
View File
@@ -1,5 +1,5 @@
import type { ElementBounds } from "@/preview/element-bounds"; import type { ElementBounds } from "@/preview/element-bounds";
import type { SnapLine } from "@/preview/preview-snap"; import type { PreviewSnapLine } from "@/preview/preview-snap";
import { MIN_MASK_DIMENSION } from "@/masks/dimensions"; import { MIN_MASK_DIMENSION } from "@/masks/dimensions";
import type { RectangleMaskParams } from "@/masks/types"; import type { RectangleMaskParams } from "@/masks/types";
@@ -100,10 +100,10 @@ export function toGlobalMaskSnapLines({
bounds, bounds,
canvasSize, canvasSize,
}: { }: {
lines: SnapLine[]; lines: PreviewSnapLine[];
bounds: ElementBounds; bounds: ElementBounds;
canvasSize: { width: number; height: number }; canvasSize: { width: number; height: number };
}): SnapLine[] { }): PreviewSnapLine[] {
const centerX = bounds.cx - canvasSize.width / 2; const centerX = bounds.cx - canvasSize.width / 2;
const centerY = bounds.cy - canvasSize.height / 2; const centerY = bounds.cy - canvasSize.height / 2;
+2 -2
View File
@@ -6,7 +6,7 @@ import {
snapScale, snapScale,
snapScaleAxes, snapScaleAxes,
type ScaleEdgePreference, type ScaleEdgePreference,
type SnapLine, type PreviewSnapLine,
} from "@/preview/preview-snap"; } from "@/preview/preview-snap";
import type { RectangleMaskParams, SplitMaskParams } from "@/masks/types"; import type { RectangleMaskParams, SplitMaskParams } from "@/masks/types";
import { import {
@@ -20,7 +20,7 @@ type SharedMaskParams = SplitMaskParams | RectangleMaskParams;
type MaskSnapResult<TParams extends SharedMaskParams> = { type MaskSnapResult<TParams extends SharedMaskParams> = {
params: TParams; params: TParams;
activeLines: SnapLine[]; activeLines: PreviewSnapLine[];
}; };
const CORNER_SIZE_HANDLES = new Set([ const CORNER_SIZE_HANDLES = new Set([
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ElementBounds } from "@/preview/element-bounds"; import type { ElementBounds } from "@/preview/element-bounds";
import type { SnapLine } from "@/preview/preview-snap"; import type { PreviewSnapLine } from "@/preview/preview-snap";
import type { ParamDefinition } from "@/params"; import type { ParamDefinition } from "@/params";
import type { CustomMaskPathPoint } from "@/masks/custom-path"; import type { CustomMaskPathPoint } from "@/masks/custom-path";
import type { import type {
@@ -261,7 +261,7 @@ export interface MaskSnapResult<
TParams extends BaseMaskParams = BaseMaskParams, TParams extends BaseMaskParams = BaseMaskParams,
> { > {
params: TParams; params: TParams;
activeLines: SnapLine[]; activeLines: PreviewSnapLine[];
} }
export interface MaskInteractionResult { export interface MaskInteractionResult {
+5 -5
View File
@@ -14,11 +14,11 @@ import {
} from "@/preview/element-bounds"; } from "@/preview/element-bounds";
import { import {
SNAP_THRESHOLD_SCREEN_PIXELS, SNAP_THRESHOLD_SCREEN_PIXELS,
type SnapLine, type PreviewSnapLine,
} from "@/preview/preview-snap"; } from "@/preview/preview-snap";
import type { SelectedMaskPointSelection } from "@/selection/editor-selection"; import type { SelectedMaskPointSelection } from "@/selection/editor-selection";
import type { Mask, MaskInteractionResult } from "@/masks/types"; import type { Mask, MaskInteractionResult } from "@/masks/types";
import type { MaskableElement } from "@/timeline"; import type { MaskableElement } from "@/model";
import { registerCanceller } from "@/editor/cancel-interaction"; import { registerCanceller } from "@/editor/cancel-interaction";
interface DragState { interface DragState {
@@ -94,7 +94,7 @@ function withUpdatedMaskParams<TMask extends Mask>({
export function useMaskHandles({ export function useMaskHandles({
onSnapLinesChange, onSnapLinesChange,
}: { }: {
onSnapLinesChange?: (lines: SnapLine[]) => void; onSnapLinesChange?: (lines: PreviewSnapLine[]) => void;
}) { }) {
const editor = useEditor(); const editor = useEditor();
const isShiftHeldRef = useShiftKey(); const isShiftHeldRef = useShiftKey();
@@ -552,7 +552,7 @@ export function useMaskHandles({
screenPixels: SNAP_THRESHOLD_SCREEN_PIXELS, screenPixels: SNAP_THRESHOLD_SCREEN_PIXELS,
}); });
const { params: nextParams, activeLines } = isShiftHeldRef.current const { params: nextParams, activeLines } = isShiftHeldRef.current
? { params: proposedParams, activeLines: [] as SnapLine[] } ? { params: proposedParams, activeLines: [] as PreviewSnapLine[] }
: (def.interaction.snap?.({ : (def.interaction.snap?.({
handleId: drag.handleId, handleId: drag.handleId,
startParams: drag.startParams, startParams: drag.startParams,
@@ -560,7 +560,7 @@ export function useMaskHandles({
bounds: selectedWithMask.bounds, bounds: selectedWithMask.bounds,
canvasSize, canvasSize,
snapThreshold, snapThreshold,
}) ?? { params: proposedParams, activeLines: [] as SnapLine[] }); }) ?? { params: proposedParams, activeLines: [] as PreviewSnapLine[] });
onSnapLinesChange?.(activeLines); onSnapLinesChange?.(activeLines);
+1 -7
View File
@@ -1,10 +1,4 @@
import type { import type { AudioElement, LibraryAudioElement, RetimeConfig, SceneTracks, VideoElement } from "@/model";
AudioElement,
VideoElement,
LibraryAudioElement,
RetimeConfig,
SceneTracks,
} from "@/timeline";
import { shouldMaintainPitch } from "@/retime/rate"; import { shouldMaintainPitch } from "@/retime/rate";
import type { MediaAsset } from "@/media/types"; import type { MediaAsset } from "@/media/types";
import { applyAudioMasteringToBuffer } from "@/media/audio-mastering"; import { applyAudioMasteringToBuffer } from "@/media/audio-mastering";
+1 -1
View File
@@ -6,7 +6,7 @@ import {
type VideoCodec, type VideoCodec,
} from "mediabunny"; } from "mediabunny";
import { createTimelineAudioBuffer } from "@/media/audio"; import { createTimelineAudioBuffer } from "@/media/audio";
import type { SceneTracks } from "@/timeline"; import type { SceneTracks } from "@/model";
import type { MediaAsset } from "@/media/types"; import type { MediaAsset } from "@/media/types";
import { TICKS_PER_SECOND } from "@/wasm"; import { TICKS_PER_SECOND } from "@/wasm";
import { renderThumbnailDataUrl } from "./thumbnail"; import { renderThumbnailDataUrl } from "./thumbnail";
+1 -1
View File
@@ -1,7 +1,7 @@
"use client"; "use client";
import { getSourceTimeAtClipTime } from "@/retime"; import { getSourceTimeAtClipTime } from "@/retime";
import type { RetimeConfig } from "@/timeline"; import type { RetimeConfig } from "@/model";
const RMS_ANALYSIS_WINDOW_SECONDS = 0.02; const RMS_ANALYSIS_WINDOW_SECONDS = 0.02;
const DEFAULT_SOURCE_WAVEFORM_BUCKET_SIZE = 128; const DEFAULT_SOURCE_WAVEFORM_BUCKET_SIZE = 128;
+6
View File
@@ -0,0 +1,6 @@
export interface Bookmark {
time: number;
note?: string;
color?: string;
duration?: number;
}
+6
View File
@@ -0,0 +1,6 @@
import type { ElementId } from "./ids";
export type ElementRef = {
trackId: string;
elementId: ElementId;
};
+21
View File
@@ -0,0 +1,21 @@
import type { BaseTimelineElement, RetimeConfig } from "./base";
export interface BaseAudioElement extends BaseTimelineElement {
type: "audio";
volume: number;
muted?: boolean;
buffer?: AudioBuffer;
retime?: RetimeConfig;
}
export interface UploadAudioElement extends BaseAudioElement {
sourceType: "upload";
mediaId: string;
}
export interface LibraryAudioElement extends BaseAudioElement {
sourceType: "library";
sourceUrl: string;
}
export type AudioElement = UploadAudioElement | LibraryAudioElement;
+18
View File
@@ -0,0 +1,18 @@
import type { ElementAnimations } from "@/animation/types";
import type { ElementId } from "../ids";
export interface BaseTimelineElement {
id: ElementId;
name: string;
duration: number;
startTime: number;
trimStart: number;
trimEnd: number;
sourceDuration?: number;
animations?: ElementAnimations;
}
export interface RetimeConfig {
rate: number;
maintainPitch?: boolean;
}
+8
View File
@@ -0,0 +1,8 @@
import type { ParamValues } from "@/params";
import type { BaseTimelineElement } from "./base";
export interface EffectElement extends BaseTimelineElement {
type: "effect";
effectType: string;
params: ParamValues;
}
+17
View File
@@ -0,0 +1,17 @@
import type { Effect } from "@/effects/types";
import type { Mask } from "@/masks/types";
import type { ParamValues } from "@/params";
import type { BlendMode, Transform } from "@/rendering";
import type { BaseTimelineElement } from "./base";
export interface GraphicElement extends BaseTimelineElement {
type: "graphic";
definitionId: string;
params: ParamValues;
hidden?: boolean;
transform: Transform;
opacity: number;
blendMode?: BlendMode;
effects?: Effect[];
masks?: Mask[];
}
+15
View File
@@ -0,0 +1,15 @@
import type { Effect } from "@/effects/types";
import type { Mask } from "@/masks/types";
import type { BlendMode, Transform } from "@/rendering";
import type { BaseTimelineElement } from "./base";
export interface ImageElement extends BaseTimelineElement {
type: "image";
mediaId: string;
hidden?: boolean;
transform: Transform;
opacity: number;
blendMode?: BlendMode;
effects?: Effect[];
masks?: Mask[];
}
+84
View File
@@ -0,0 +1,84 @@
import type { AudioElement, LibraryAudioElement, UploadAudioElement } from "./audio";
import type { EffectElement } from "./effect";
import type { GraphicElement } from "./graphic";
import type { ImageElement } from "./image";
import type { StickerElement } from "./sticker";
import type { TextElement } from "./text";
import type { VideoElement } from "./video";
import type { Transform } from "@/rendering";
export * from "./audio";
export * from "./base";
export * from "./effect";
export * from "./graphic";
export * from "./image";
export * from "./sticker";
export * from "./text";
export * from "./video";
export type ElementUpdatePatch =
| { transform: Transform }
| { opacity: number }
| { volume: number };
export type TimelineElement =
| AudioElement
| VideoElement
| ImageElement
| TextElement
| StickerElement
| GraphicElement
| EffectElement;
export type ElementType = TimelineElement["type"];
function elementTypes<T extends ElementType[]>(...types: T): T {
return types;
}
export const MASKABLE_ELEMENT_TYPES = elementTypes("video", "image", "graphic");
export type MaskableElement = Extract<
TimelineElement,
{ type: (typeof MASKABLE_ELEMENT_TYPES)[number] }
>;
export const RETIMABLE_ELEMENT_TYPES = elementTypes("video", "audio");
export type RetimableElement = Extract<
TimelineElement,
{ type: (typeof RETIMABLE_ELEMENT_TYPES)[number] }
>;
export const VISUAL_ELEMENT_TYPES = elementTypes(
"video",
"image",
"text",
"sticker",
"graphic",
);
export type VisualElement = Extract<
TimelineElement,
{ type: (typeof VISUAL_ELEMENT_TYPES)[number] }
>;
export type CreateUploadAudioElement = Omit<UploadAudioElement, "id">;
export type CreateLibraryAudioElement = Omit<LibraryAudioElement, "id">;
export type CreateAudioElement =
| CreateUploadAudioElement
| CreateLibraryAudioElement;
export type CreateVideoElement = Omit<VideoElement, "id">;
export type CreateImageElement = Omit<ImageElement, "id">;
export type CreateTextElement = Omit<TextElement, "id">;
export type CreateStickerElement = Omit<StickerElement, "id">;
export type CreateGraphicElement = Omit<GraphicElement, "id">;
export type CreateEffectElement = Omit<EffectElement, "id">;
export type CreateTimelineElement =
| CreateAudioElement
| CreateVideoElement
| CreateImageElement
| CreateTextElement
| CreateStickerElement
| CreateGraphicElement
| CreateEffectElement;
+16
View File
@@ -0,0 +1,16 @@
import type { Effect } from "@/effects/types";
import type { BlendMode, Transform } from "@/rendering";
import type { BaseTimelineElement } from "./base";
export interface StickerElement extends BaseTimelineElement {
type: "sticker";
stickerId: string;
/** Natural dimensions of the sticker asset, stored at insert time. Used by renderer and preview bounds to avoid split-brain geometry. */
intrinsicWidth?: number;
intrinsicHeight?: number;
hidden?: boolean;
transform: Transform;
opacity: number;
blendMode?: BlendMode;
effects?: Effect[];
}
+33
View File
@@ -0,0 +1,33 @@
import type { Effect } from "@/effects/types";
import type { BlendMode, Transform } from "@/rendering";
import type { BaseTimelineElement } from "./base";
export interface TextBackground {
enabled: boolean;
color: string;
cornerRadius?: number;
paddingX?: number;
paddingY?: number;
offsetX?: number;
offsetY?: number;
}
export interface TextElement extends BaseTimelineElement {
type: "text";
content: string;
fontSize: number;
fontFamily: string;
color: string;
background: TextBackground;
textAlign: "left" | "center" | "right";
fontWeight: "normal" | "bold";
fontStyle: "normal" | "italic";
textDecoration: "none" | "underline" | "line-through";
letterSpacing?: number;
lineHeight?: number;
hidden?: boolean;
transform: Transform;
opacity: number;
blendMode?: BlendMode;
effects?: Effect[];
}
+19
View File
@@ -0,0 +1,19 @@
import type { Effect } from "@/effects/types";
import type { Mask } from "@/masks/types";
import type { BlendMode, Transform } from "@/rendering";
import type { BaseTimelineElement, RetimeConfig } from "./base";
export interface VideoElement extends BaseTimelineElement {
type: "video";
mediaId: string;
volume?: number;
muted?: boolean;
isSourceAudioEnabled?: boolean;
hidden?: boolean;
retime?: RetimeConfig;
transform: Transform;
opacity: number;
blendMode?: BlendMode;
effects?: Effect[];
masks?: Mask[];
}
+15
View File
@@ -0,0 +1,15 @@
export type ElementId = string & { readonly __brand: "ElementId" };
export type TrackId = string & { readonly __brand: "TrackId" };
export type SceneId = string & { readonly __brand: "SceneId" };
export function elementId(value: string): ElementId {
return value as ElementId;
}
export function trackId(value: string): TrackId {
return value as TrackId;
}
export function sceneId(value: string): SceneId {
return value as SceneId;
}
+7
View File
@@ -0,0 +1,7 @@
export * from "./bookmark";
export * from "./element-ref";
export * from "./elements";
export * from "./ids";
export * from "./scene";
export * from "./time";
export * from "./track";
+12
View File
@@ -0,0 +1,12 @@
import type { Bookmark } from "./bookmark";
import type { SceneTracks } from "./track";
export interface TScene {
id: string;
name: string;
isMain: boolean;
tracks: SceneTracks;
bookmarks: Bookmark[];
createdAt: Date;
updatedAt: Date;
}
+5
View File
@@ -0,0 +1,5 @@
export type MediaTime = number & { readonly __brand: "MediaTime" };
export function mediaTime(value: number): MediaTime {
return value as MediaTime;
}
+62
View File
@@ -0,0 +1,62 @@
import type {
AudioElement,
EffectElement,
GraphicElement,
ImageElement,
StickerElement,
TextElement,
VideoElement,
} from "./elements";
export type TrackType = "video" | "text" | "audio" | "graphic" | "effect";
export interface BaseTrack {
id: string;
name: string;
}
export interface VideoTrack extends BaseTrack {
type: "video";
elements: (VideoElement | ImageElement)[];
muted: boolean;
hidden: boolean;
}
export interface TextTrack extends BaseTrack {
type: "text";
elements: TextElement[];
hidden: boolean;
}
export interface AudioTrack extends BaseTrack {
type: "audio";
elements: AudioElement[];
muted: boolean;
}
export interface GraphicTrack extends BaseTrack {
type: "graphic";
elements: (StickerElement | GraphicElement)[];
hidden: boolean;
}
export interface EffectTrack extends BaseTrack {
type: "effect";
elements: EffectElement[];
hidden: boolean;
}
export type TimelineTrack =
| VideoTrack
| TextTrack
| AudioTrack
| GraphicTrack
| EffectTrack;
export type OverlayTrack = VideoTrack | TextTrack | GraphicTrack | EffectTrack;
export interface SceneTracks {
overlay: OverlayTrack[];
main: VideoTrack;
audio: AudioTrack[];
}
@@ -3,7 +3,7 @@
import { PEN_CURSOR } from "@/preview/components/cursors"; import { PEN_CURSOR } from "@/preview/components/cursors";
import { usePreviewViewport } from "@/preview/components/preview-viewport"; import { usePreviewViewport } from "@/preview/components/preview-viewport";
import { useMaskHandles } from "@/masks/use-mask-handles"; import { useMaskHandles } from "@/masks/use-mask-handles";
import type { SnapLine } from "@/preview/preview-snap"; import type { PreviewSnapLine } from "@/preview/preview-snap";
import { import {
CornerHandle, CornerHandle,
CircleHandle, CircleHandle,
@@ -22,7 +22,7 @@ import { Rotate01Icon, FeatherIcon } from "@hugeicons/core-free-icons";
export function MaskHandles({ export function MaskHandles({
onSnapLinesChange, onSnapLinesChange,
}: { }: {
onSnapLinesChange?: (lines: SnapLine[]) => void; onSnapLinesChange?: (lines: PreviewSnapLine[]) => void;
}) { }) {
const viewport = usePreviewViewport(); const viewport = usePreviewViewport();
const { const {
@@ -1,7 +1,7 @@
import { useState } from "react"; import { useState } from "react";
import { usePreviewViewport } from "@/preview/components/preview-viewport"; import { usePreviewViewport } from "@/preview/components/preview-viewport";
import { usePreviewInteraction } from "@/preview/hooks/use-preview-interaction"; import { usePreviewInteraction } from "@/preview/hooks/use-preview-interaction";
import type { SnapLine } from "@/preview/preview-snap"; import type { PreviewSnapLine } from "@/preview/preview-snap";
import { TransformHandles } from "./transform-handles"; import { TransformHandles } from "./transform-handles";
import { MaskHandles } from "./mask-handles"; import { MaskHandles } from "./mask-handles";
import { SnapGuides } from "./snap-guides"; import { SnapGuides } from "./snap-guides";
@@ -10,7 +10,7 @@ import { usePropertiesStore } from "@/components/editor/panels/properties/stores
import { useEditor } from "@/editor/use-editor"; import { useEditor } from "@/editor/use-editor";
export function PreviewInteractionOverlay() { export function PreviewInteractionOverlay() {
const [snapLines, setSnapLines] = useState<SnapLine[]>([]); const [snapLines, setSnapLines] = useState<PreviewSnapLine[]>([]);
const editor = useEditor(); const editor = useEditor();
const viewport = usePreviewViewport(); const viewport = usePreviewViewport();
const selectedElements = useEditor((e) => e.selection.getSelectedElements()); const selectedElements = useEditor((e) => e.selection.getSelectedElements());
@@ -1,9 +1,9 @@
"use client"; "use client";
import { usePreviewViewport } from "@/preview/components/preview-viewport"; import { usePreviewViewport } from "@/preview/components/preview-viewport";
import type { SnapLine } from "@/preview/preview-snap"; import type { PreviewSnapLine } from "@/preview/preview-snap";
export function SnapGuides({ lines }: { lines: SnapLine[] }) { export function SnapGuides({ lines }: { lines: PreviewSnapLine[] }) {
const viewport = usePreviewViewport(); const viewport = usePreviewViewport();
if (lines.length === 0) { if (lines.length === 0) {
@@ -3,7 +3,7 @@
import { useCallback, useEffect, useRef } from "react"; import { useCallback, useEffect, useRef } from "react";
import { usePreviewViewport } from "@/preview/components/preview-viewport"; import { usePreviewViewport } from "@/preview/components/preview-viewport";
import { useEditor } from "@/editor/use-editor"; import { useEditor } from "@/editor/use-editor";
import type { TextElement } from "@/timeline"; import type { TextElement } from "@/model";
import { DEFAULTS } from "@/timeline/defaults"; import { DEFAULTS } from "@/timeline/defaults";
import { import {
getElementLocalTime, getElementLocalTime,
@@ -15,19 +15,12 @@ import {
import { import {
SNAP_THRESHOLD_SCREEN_PIXELS, SNAP_THRESHOLD_SCREEN_PIXELS,
snapPosition, snapPosition,
type SnapLine, type PreviewSnapLine,
} from "@/preview/preview-snap"; } from "@/preview/preview-snap";
import type { TCanvasSize } from "@/project/types"; import type { TCanvasSize } from "@/project/types";
import type { Transform } from "@/rendering"; import type { Transform } from "@/rendering";
import { isVisualElement } from "@/timeline/element-utils"; import { isVisualElement } from "@/timeline/element-utils";
import type { import type { ElementRef, SceneTracks, TextElement, TimelineElement, TimelineTrack, VisualElement } from "@/model";
ElementRef,
SceneTracks,
TextElement,
TimelineElement,
TimelineTrack,
VisualElement,
} from "@/timeline";
const MIN_DRAG_DISTANCE = 0.5; const MIN_DRAG_DISTANCE = 0.5;
const PRIMARY_POINTER_BUTTON = 0; const PRIMARY_POINTER_BUTTON = 0;
@@ -133,7 +126,7 @@ export interface PlaybackApi {
export interface PreviewOptions { export interface PreviewOptions {
isMaskMode: () => boolean; isMaskMode: () => boolean;
onSnapLinesChange?: (lines: SnapLine[]) => void; onSnapLinesChange?: (lines: PreviewSnapLine[]) => void;
} }
export interface PreviewInteractionDeps { export interface PreviewInteractionDeps {
@@ -554,7 +547,7 @@ export class PreviewInteractionController {
}) })
: { : {
snappedPosition: proposedPosition, snappedPosition: proposedPosition,
activeLines: [] as SnapLine[], activeLines: [] as PreviewSnapLine[],
}; };
this.deps.preview.onSnapLinesChange?.(activeLines); this.deps.preview.onSnapLinesChange?.(activeLines);
@@ -14,7 +14,7 @@ import {
snapScale, snapScale,
snapScaleAxes, snapScaleAxes,
type ScaleEdgePreference, type ScaleEdgePreference,
type SnapLine, type PreviewSnapLine,
} from "@/preview/preview-snap"; } from "@/preview/preview-snap";
import { isVisualElement } from "@/timeline/element-utils"; import { isVisualElement } from "@/timeline/element-utils";
import { import {
@@ -25,12 +25,7 @@ import {
import type { ElementAnimations } from "@/animation/types"; import type { ElementAnimations } from "@/animation/types";
import type { Transform } from "@/rendering"; import type { Transform } from "@/rendering";
import { resolveTransformAtTime } from "@/rendering/animation-values"; import { resolveTransformAtTime } from "@/rendering/animation-values";
import type { import type { ElementRef, SceneTracks, TimelineElement, VisualElement } from "@/model";
ElementRef,
SceneTracks,
TimelineElement,
VisualElement,
} from "@/timeline";
type Point = { readonly x: number; readonly y: number }; type Point = { readonly x: number; readonly y: number };
type CanvasSize = { readonly width: number; readonly height: number }; type CanvasSize = { readonly width: number; readonly height: number };
@@ -137,7 +132,7 @@ export interface TimelineOps {
} }
export interface PreviewOptions { export interface PreviewOptions {
onSnapLinesChange?: (lines: SnapLine[]) => void; onSnapLinesChange?: (lines: PreviewSnapLine[]) => void;
} }
export interface TransformHandleDeps { export interface TransformHandleDeps {
@@ -590,7 +585,7 @@ export class TransformHandleController {
screenPixels: SNAP_THRESHOLD_SCREEN_PIXELS, screenPixels: SNAP_THRESHOLD_SCREEN_PIXELS,
}); });
const { snappedScale, activeLines } = this.deps.input.isShiftHeld() const { snappedScale, activeLines } = this.deps.input.isShiftHeld()
? { snappedScale: scaleFactor, activeLines: [] as SnapLine[] } ? { snappedScale: scaleFactor, activeLines: [] as PreviewSnapLine[] }
: snapScale({ : snapScale({
proposedScale: scaleFactor, proposedScale: scaleFactor,
position: session.initialTransform.position, position: session.initialTransform.position,
@@ -670,12 +665,12 @@ export class TransformHandleController {
x: { x: {
snappedScale: proposedScaleX, snappedScale: proposedScaleX,
snapDistance: Infinity, snapDistance: Infinity,
activeLines: [] as SnapLine[], activeLines: [] as PreviewSnapLine[],
}, },
y: { y: {
snappedScale: proposedScaleY, snappedScale: proposedScaleY,
snapDistance: Infinity, snapDistance: Infinity,
activeLines: [] as SnapLine[], activeLines: [] as PreviewSnapLine[],
}, },
} }
: snapScaleAxes({ : snapScaleAxes({
+2 -2
View File
@@ -1,4 +1,4 @@
import type { SceneTracks, TimelineElement } from "@/timeline"; import type { ElementId, SceneTracks, TimelineElement } from "@/model";
import type { MediaAsset } from "@/media/types"; import type { MediaAsset } from "@/media/types";
import { STICKER_INTRINSIC_SIZE_FALLBACK } from "@/stickers/intrinsic-size"; import { STICKER_INTRINSIC_SIZE_FALLBACK } from "@/stickers/intrinsic-size";
import { DEFAULT_GRAPHIC_SOURCE_SIZE } from "@/graphics"; import { DEFAULT_GRAPHIC_SOURCE_SIZE } from "@/graphics";
@@ -18,7 +18,7 @@ export interface ElementBounds {
export interface ElementWithBounds { export interface ElementWithBounds {
trackId: string; trackId: string;
elementId: string; elementId: ElementId;
element: TimelineElement; element: TimelineElement;
bounds: ElementBounds; bounds: ElementBounds;
} }
+1 -1
View File
@@ -1,5 +1,5 @@
import type { ElementWithBounds } from "./element-bounds"; import type { ElementWithBounds } from "./element-bounds";
import type { ElementRef } from "@/timeline/types"; import type { ElementRef } from "@/model";
function pointInRotatedRect({ function pointInRotatedRect({
px, px,
@@ -2,7 +2,7 @@ import { useEffect, useReducer, useRef } from "react";
import { useEditor } from "@/editor/use-editor"; import { useEditor } from "@/editor/use-editor";
import { useShiftKey } from "@/hooks/use-shift-key"; import { useShiftKey } from "@/hooks/use-shift-key";
import { usePreviewViewport } from "@/preview/components/preview-viewport"; import { usePreviewViewport } from "@/preview/components/preview-viewport";
import type { SnapLine } from "@/preview/preview-snap"; import type { PreviewSnapLine } from "@/preview/preview-snap";
import { registerCanceller } from "@/editor/cancel-interaction"; import { registerCanceller } from "@/editor/cancel-interaction";
import { import {
PreviewInteractionController, PreviewInteractionController,
@@ -10,7 +10,7 @@ import {
type PreviewInteractionDepsRef, type PreviewInteractionDepsRef,
} from "@/preview/controllers/preview-interaction-controller"; } from "@/preview/controllers/preview-interaction-controller";
export type OnSnapLinesChange = (lines: SnapLine[]) => void; export type OnSnapLinesChange = (lines: PreviewSnapLine[]) => void;
export function usePreviewInteraction({ export function usePreviewInteraction({
onSnapLinesChange, onSnapLinesChange,
+16 -16
View File
@@ -1,4 +1,4 @@
export interface SnapLine { export interface PreviewSnapLine {
type: "horizontal" | "vertical"; type: "horizontal" | "vertical";
position: number; position: number;
} }
@@ -8,9 +8,9 @@ const ROTATION_SNAP_THRESHOLD_DEGREES = 5;
export const MIN_SCALE = 0.01; export const MIN_SCALE = 0.01;
export const SNAP_THRESHOLD_SCREEN_PIXELS = 8; export const SNAP_THRESHOLD_SCREEN_PIXELS = 8;
export interface SnapResult { export interface PreviewSnapResult {
snappedPosition: { x: number; y: number }; snappedPosition: { x: number; y: number };
activeLines: SnapLine[]; activeLines: PreviewSnapLine[];
} }
type ScaleEdge = "left" | "right" | "top" | "bottom"; type ScaleEdge = "left" | "right" | "top" | "bottom";
@@ -78,7 +78,7 @@ export function snapPosition({
elementSize: { width: number; height: number }; elementSize: { width: number; height: number };
rotation?: number; rotation?: number;
snapThreshold: { x: number; y: number }; snapThreshold: { x: number; y: number };
}): SnapResult { }): PreviewSnapResult {
const centerX = 0; const centerX = 0;
const centerY = 0; const centerY = 0;
const left = -canvasSize.width / 2; const left = -canvasSize.width / 2;
@@ -91,11 +91,11 @@ export function snapPosition({
const sinR = Math.abs(Math.sin(rotRad)); const sinR = Math.abs(Math.sin(rotRad));
const halfWidth = (elementSize.width * cosR + elementSize.height * sinR) / 2; const halfWidth = (elementSize.width * cosR + elementSize.height * sinR) / 2;
const halfHeight = (elementSize.width * sinR + elementSize.height * cosR) / 2; const halfHeight = (elementSize.width * sinR + elementSize.height * cosR) / 2;
const activeLines: SnapLine[] = []; const activeLines: PreviewSnapLine[] = [];
type AxisSnapCandidate = { type AxisSnapCandidate = {
snappedPosition: number; snappedPosition: number;
line: SnapLine; line: PreviewSnapLine;
distance: number; distance: number;
}; };
@@ -183,7 +183,7 @@ export function snapPosition({
export interface ScaleSnapResult { export interface ScaleSnapResult {
snappedScale: number; snappedScale: number;
activeLines: SnapLine[]; activeLines: PreviewSnapLine[];
} }
export function snapScale({ export function snapScale({
@@ -226,7 +226,7 @@ export function snapScale({
interface SnapCandidate { interface SnapCandidate {
scale: number; scale: number;
distance: number; distance: number;
lines: SnapLine[]; lines: PreviewSnapLine[];
edge: ScaleEdge; edge: ScaleEdge;
} }
@@ -320,10 +320,10 @@ export function snapScale({
const snappedTop = position.y - aabbBaseHalfH * best.scale; const snappedTop = position.y - aabbBaseHalfH * best.scale;
const snappedBottom = position.y + aabbBaseHalfH * best.scale; const snappedBottom = position.y + aabbBaseHalfH * best.scale;
const activeLines: SnapLine[] = []; const activeLines: PreviewSnapLine[] = [];
const seenKeys = new Set<string>(); const seenKeys = new Set<string>();
function addLine({ line }: { line: SnapLine }) { function addLine({ line }: { line: PreviewSnapLine }) {
const key = `${line.type}-${line.position}`; const key = `${line.type}-${line.position}`;
if (!seenKeys.has(key)) { if (!seenKeys.has(key)) {
seenKeys.add(key); seenKeys.add(key);
@@ -368,7 +368,7 @@ export interface AxisSnapResult {
snappedScale: number; snappedScale: number;
/** Infinity when no snap candidate was within threshold */ /** Infinity when no snap candidate was within threshold */
snapDistance: number; snapDistance: number;
activeLines: SnapLine[]; activeLines: PreviewSnapLine[];
} }
export function snapScaleAxes({ export function snapScaleAxes({
@@ -413,7 +413,7 @@ export function snapScaleAxes({
interface Candidate { interface Candidate {
scale: number; scale: number;
distance: number; distance: number;
line: SnapLine; line: PreviewSnapLine;
edge: ScaleEdge; edge: ScaleEdge;
} }
@@ -442,7 +442,7 @@ export function snapScaleAxes({
if (cosR > EPSILON) { if (cosR > EPSILON) {
for (const T of [canvasLeft, 0, canvasRight]) { for (const T of [canvasLeft, 0, canvasRight]) {
const line: SnapLine = { type: "vertical", position: T }; const line: PreviewSnapLine = { type: "vertical", position: T };
const distLeft = Math.abs(currentLeftEdge - T); const distLeft = Math.abs(currentLeftEdge - T);
if (distLeft <= snapThreshold.x) { if (distLeft <= snapThreshold.x) {
const scale = (2 * (position.x - T) - yContribW) / (baseWidth * cosR); const scale = (2 * (position.x - T) - yContribW) / (baseWidth * cosR);
@@ -458,7 +458,7 @@ export function snapScaleAxes({
if (sinR > EPSILON) { if (sinR > EPSILON) {
for (const T of [canvasTop, 0, canvasBottom]) { for (const T of [canvasTop, 0, canvasBottom]) {
const line: SnapLine = { type: "horizontal", position: T }; const line: PreviewSnapLine = { type: "horizontal", position: T };
const distTop = Math.abs(currentTopEdge - T); const distTop = Math.abs(currentTopEdge - T);
if (distTop <= snapThreshold.y) { if (distTop <= snapThreshold.y) {
const scale = (2 * (position.y - T) - yContribH) / (baseWidth * sinR); const scale = (2 * (position.y - T) - yContribH) / (baseWidth * sinR);
@@ -480,7 +480,7 @@ export function snapScaleAxes({
if (sinR > EPSILON) { if (sinR > EPSILON) {
for (const T of [canvasLeft, 0, canvasRight]) { for (const T of [canvasLeft, 0, canvasRight]) {
const line: SnapLine = { type: "vertical", position: T }; const line: PreviewSnapLine = { type: "vertical", position: T };
const distLeft = Math.abs(currentLeftEdge - T); const distLeft = Math.abs(currentLeftEdge - T);
if (distLeft <= snapThreshold.x) { if (distLeft <= snapThreshold.x) {
const scale = (2 * (position.x - T) - xContribW) / (baseHeight * sinR); const scale = (2 * (position.x - T) - xContribW) / (baseHeight * sinR);
@@ -496,7 +496,7 @@ export function snapScaleAxes({
if (cosR > EPSILON) { if (cosR > EPSILON) {
for (const T of [canvasTop, 0, canvasBottom]) { for (const T of [canvasTop, 0, canvasBottom]) {
const line: SnapLine = { type: "horizontal", position: T }; const line: PreviewSnapLine = { type: "horizontal", position: T };
const distTop = Math.abs(currentTopEdge - T); const distTop = Math.abs(currentTopEdge - T);
if (distTop <= snapThreshold.y) { if (distTop <= snapThreshold.y) {
const scale = (2 * (position.y - T) - xContribH) / (baseHeight * cosR); const scale = (2 * (position.y - T) - xContribH) / (baseHeight * cosR);
+1 -1
View File
@@ -1,5 +1,5 @@
import type { FrameRate } from "opencut-wasm"; import type { FrameRate } from "opencut-wasm";
import type { TScene } from "@/timeline/types"; import type { TScene } from "@/model";
export type TBackground = export type TBackground =
| { | {
@@ -20,7 +20,7 @@ import {
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import type { BlendMode } from "@/rendering"; import type { BlendMode } from "@/rendering";
import type { ElementType } from "@/timeline"; import type { ElementType } from "@/model";
import type { ElementAnimations } from "@/animation/types"; import type { ElementAnimations } from "@/animation/types";
import { HugeiconsIcon } from "@hugeicons/react"; import { HugeiconsIcon } from "@hugeicons/react";
import { RainDropIcon } from "@hugeicons/core-free-icons"; import { RainDropIcon } from "@hugeicons/core-free-icons";
@@ -1,7 +1,7 @@
import { NumberField } from "@/components/ui/number-field"; import { NumberField } from "@/components/ui/number-field";
import { useEditor } from "@/editor/use-editor"; import { useEditor } from "@/editor/use-editor";
import { clamp, isNearlyEqual } from "@/utils/math"; import { clamp, isNearlyEqual } from "@/utils/math";
import type { VisualElement } from "@/timeline"; import type { VisualElement } from "@/model";
import { import {
Section, Section,
SectionContent, SectionContent,
@@ -5,7 +5,7 @@ import {
getSourceTimeAtClipTime, getSourceTimeAtClipTime,
getTimelineDurationForSourceSpan, getTimelineDurationForSourceSpan,
} from "@/retime"; } from "@/retime";
import type { RetimeConfig } from "@/timeline"; import type { RetimeConfig } from "@/model";
const twoX: RetimeConfig = { rate: 2 }; const twoX: RetimeConfig = { rate: 2 };
const halfX: RetimeConfig = { rate: 0.5 }; const halfX: RetimeConfig = { rate: 0.5 };
+1 -1
View File
@@ -3,7 +3,7 @@ import {
getSourceSpanAtClipTime, getSourceSpanAtClipTime,
splitRetimeAtClipTime, splitRetimeAtClipTime,
} from "@/retime"; } from "@/retime";
import type { RetimeConfig } from "@/timeline"; import type { RetimeConfig } from "@/model";
describe("retime split", () => { describe("retime split", () => {
test("measures source span at a clip time", () => { test("measures source span at a clip time", () => {
+1 -1
View File
@@ -1,6 +1,6 @@
import { PitchShifter } from "soundtouchjs"; import { PitchShifter } from "soundtouchjs";
import { clampRetimeRate, shouldMaintainPitch } from "@/retime/rate"; import { clampRetimeRate, shouldMaintainPitch } from "@/retime/rate";
import type { RetimeConfig } from "@/timeline"; import type { RetimeConfig } from "@/model";
import { getSourceTimeAtClipTime } from "./resolve"; import { getSourceTimeAtClipTime } from "./resolve";
const RATE_EPSILON = 1e-6; const RATE_EPSILON = 1e-6;
+1 -1
View File
@@ -1,4 +1,4 @@
import type { RetimeConfig } from "@/timeline"; import type { RetimeConfig } from "@/model";
import { clampRetimeRate } from "@/retime/rate"; import { clampRetimeRate } from "@/retime/rate";
export function buildConstantRetime({ export function buildConstantRetime({
+1 -1
View File
@@ -1,4 +1,4 @@
import type { RetimeConfig } from "@/timeline"; import type { RetimeConfig } from "@/model";
import { clampRetimeRate } from "@/retime/rate"; import { clampRetimeRate } from "@/retime/rate";
function getSafeRate({ rate }: { rate: number }): number { function getSafeRate({ rate }: { rate: number }): number {
+1 -1
View File
@@ -1,4 +1,4 @@
import type { RetimeConfig } from "@/timeline"; import type { RetimeConfig } from "@/model";
import { getSourceTimeAtClipTime } from "./resolve"; import { getSourceTimeAtClipTime } from "./resolve";
export function getSourceSpanAtClipTime({ export function getSourceSpanAtClipTime({
+1 -1
View File
@@ -1,4 +1,4 @@
import type { SceneTracks, TimelineTrack } from "@/timeline/types"; import type { SceneTracks, TimelineTrack } from "@/model";
import { rippleShiftElements } from "./shift"; import { rippleShiftElements } from "./shift";
export interface RippleAdjustment { export interface RippleAdjustment {
+1 -1
View File
@@ -1,4 +1,4 @@
import type { SceneTracks, TimelineElement, TimelineTrack } from "@/timeline/types"; import type { SceneTracks, TimelineElement, TimelineTrack } from "@/model";
import type { RippleAdjustment } from "./apply"; import type { RippleAdjustment } from "./apply";
interface Interval { interface Interval {
+1 -1
View File
@@ -1,4 +1,4 @@
import type { TimelineElement } from "@/timeline/types"; import type { TimelineElement } from "@/model";
export function rippleShiftElements<TElement extends TimelineElement>({ export function rippleShiftElements<TElement extends TimelineElement>({
elements, elements,
+1 -1
View File
@@ -1,5 +1,5 @@
import type { SelectedKeyframeRef } from "@/animation/types"; import type { SelectedKeyframeRef } from "@/animation/types";
import type { ElementRef } from "@/timeline/types"; import type { ElementRef } from "@/model";
export interface SelectedMaskPointSelection { export interface SelectedMaskPointSelection {
trackId: string; trackId: string;
@@ -1,5 +1,5 @@
import type { EffectPass } from "@/effects/types"; import type { EffectPass } from "@/effects/types";
import type { RetimeConfig } from "@/timeline"; import type { RetimeConfig } from "@/model";
import { BaseNode } from "./base-node"; import { BaseNode } from "./base-node";
export type BlurBackgroundNodeParams = { export type BlurBackgroundNodeParams = {

Some files were not shown because too many files have changed in this diff Show More