From 6ac2116daa16520554034846328e141cecded99b Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Sun, 3 May 2026 03:49:52 +0200 Subject: [PATCH] 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 --- apps/web/src/clipboard/handlers/keyframes.ts | 6 +- apps/web/src/clipboard/types.ts | 6 +- apps/web/src/commands/base-command.ts | 2 +- .../src/commands/media/remove-media-asset.ts | 2 +- apps/web/src/commands/scene/create-scene.ts | 2 +- apps/web/src/commands/scene/delete-scene.ts | 2 +- apps/web/src/commands/scene/move-bookmark.ts | 2 +- .../web/src/commands/scene/remove-bookmark.ts | 2 +- apps/web/src/commands/scene/rename-scene.ts | 2 +- .../web/src/commands/scene/toggle-bookmark.ts | 2 +- .../web/src/commands/scene/update-bookmark.ts | 2 +- .../timeline/clipboard/paste-keyframes.ts | 2 +- .../src/commands/timeline/clipboard/paste.ts | 6 +- .../timeline/element/delete-elements.ts | 4 +- .../timeline/element/duplicate-elements.ts | 16 +- .../timeline/element/effects/add-effect.ts | 2 +- .../timeline/element/effects/remove-effect.ts | 2 +- .../element/effects/reorder-effect.ts | 2 +- .../timeline/element/effects/toggle-effect.ts | 2 +- .../element/effects/update-effect-params.ts | 2 +- .../timeline/element/insert-element.ts | 18 +- .../keyframes/remove-effect-param-keyframe.ts | 2 +- .../element/keyframes/remove-keyframe.ts | 2 +- .../element/keyframes/retime-keyframe.ts | 2 +- .../keyframes/update-scalar-keyframe-curve.ts | 2 +- .../keyframes/upsert-effect-param-keyframe.ts | 2 +- .../element/keyframes/upsert-keyframe.ts | 2 +- .../masks/delete-custom-mask-points.ts | 2 +- .../element/masks/insert-custom-mask-point.ts | 2 +- .../timeline/element/masks/remove-mask.ts | 2 +- .../element/masks/toggle-mask-inverted.ts | 2 +- .../timeline/element/move-elements.ts | 9 +- .../timeline/element/split-elements.ts | 15 +- .../element/toggle-source-audio-separation.ts | 13 +- .../timeline/element/update-elements.ts | 2 +- .../src/commands/timeline/track/add-track.ts | 2 +- .../commands/timeline/track/remove-track.ts | 2 +- .../timeline/track/toggle-track-mute.ts | 2 +- .../timeline/track/toggle-track-visibility.ts | 2 +- .../src/commands/timeline/tracks-snapshot.ts | 2 +- .../editor/panels/assets/views/assets.tsx | 2 +- .../hooks/use-keyframed-color-property.ts | 2 +- .../hooks/use-keyframed-number-property.ts | 2 +- .../hooks/use-keyframed-param-property.ts | 2 +- .../editor/panels/properties/registry.tsx | 14 +- apps/web/src/core/managers/commands.ts | 2 +- apps/web/src/core/managers/scenes-manager.ts | 2 +- .../src/core/managers/selection-manager.ts | 2 +- .../web/src/core/managers/timeline-manager.ts | 8 +- .../src/effects/components/effects-tab.tsx | 2 +- apps/web/src/effects/index.ts | 2 +- .../src/graphics/components/graphic-tab.tsx | 2 +- apps/web/src/masks/components/masks-tab.tsx | 2 +- apps/web/src/masks/geometry.ts | 6 +- apps/web/src/masks/snap.ts | 4 +- apps/web/src/masks/types.ts | 4 +- apps/web/src/masks/use-mask-handles.ts | 10 +- apps/web/src/media/audio.ts | 8 +- apps/web/src/media/mediabunny.ts | 2 +- apps/web/src/media/waveform-summary.ts | 2 +- apps/web/src/model/bookmark.ts | 6 + apps/web/src/model/element-ref.ts | 6 + apps/web/src/model/elements/audio.ts | 21 ++ apps/web/src/model/elements/base.ts | 18 ++ apps/web/src/model/elements/effect.ts | 8 + apps/web/src/model/elements/graphic.ts | 17 ++ apps/web/src/model/elements/image.ts | 15 + apps/web/src/model/elements/index.ts | 84 ++++++ apps/web/src/model/elements/sticker.ts | 16 + apps/web/src/model/elements/text.ts | 33 +++ apps/web/src/model/elements/video.ts | 19 ++ apps/web/src/model/ids.ts | 15 + apps/web/src/model/index.ts | 7 + apps/web/src/model/scene.ts | 12 + apps/web/src/model/time.ts | 5 + apps/web/src/model/track.ts | 62 ++++ .../src/preview/components/mask-handles.tsx | 4 +- .../preview-interaction-overlay.tsx | 4 +- .../src/preview/components/snap-guides.tsx | 4 +- .../preview/components/text-edit-overlay.tsx | 2 +- .../preview-interaction-controller.ts | 15 +- .../transform-handle-controller.ts | 17 +- apps/web/src/preview/element-bounds.ts | 4 +- apps/web/src/preview/hit-test.ts | 2 +- .../preview/hooks/use-preview-interaction.ts | 4 +- apps/web/src/preview/preview-snap.ts | 32 +- apps/web/src/project/types.ts | 2 +- .../src/rendering/components/blending-tab.tsx | 2 +- .../rendering/components/transform-tab.tsx | 2 +- apps/web/src/retime/__tests__/resolve.test.ts | 2 +- apps/web/src/retime/__tests__/split.test.ts | 2 +- apps/web/src/retime/audio-stretch.ts | 2 +- apps/web/src/retime/presets.ts | 2 +- apps/web/src/retime/resolve.ts | 2 +- apps/web/src/retime/split.ts | 2 +- apps/web/src/ripple/apply.ts | 2 +- apps/web/src/ripple/diff.ts | 2 +- apps/web/src/ripple/shift.ts | 2 +- apps/web/src/selection/editor-selection.ts | 2 +- .../renderer/nodes/blur-background-node.ts | 2 +- .../src/services/renderer/nodes/text-node.ts | 2 +- .../services/renderer/nodes/visual-node.ts | 2 +- .../src/services/renderer/scene-builder.ts | 2 +- apps/web/src/services/storage/service.ts | 2 +- apps/web/src/services/storage/types.ts | 2 +- apps/web/src/speed/components/speed-tab.tsx | 2 +- .../subtitles/build-subtitle-text-element.ts | 2 +- apps/web/src/subtitles/types.ts | 2 +- apps/web/src/text/components/text-tab.tsx | 2 +- apps/web/src/text/layout.ts | 2 +- apps/web/src/text/measure-element.ts | 2 +- apps/web/src/timeline/animation-properties.ts | 2 +- .../web/src/timeline/animation-snap-points.ts | 8 +- apps/web/src/timeline/animation-targets.ts | 2 +- .../src/timeline/audio-separation/index.ts | 7 +- apps/web/src/timeline/audio-state.ts | 2 +- .../bookmarks/components/bookmarks.tsx | 2 +- .../bookmarks/hooks/use-bookmark-drag.ts | 8 +- .../bookmarks/preview-overlay-source.tsx | 2 +- .../web/src/timeline/bookmarks/snap-source.ts | 8 +- apps/web/src/timeline/bookmarks/utils.ts | 2 +- .../web/src/timeline/components/audio-tab.tsx | 2 +- .../timeline/components/audio-volume-line.tsx | 2 +- .../timeline/components/audio-waveform.tsx | 2 +- .../web/src/timeline/components/drag-line.tsx | 3 +- .../src/timeline/components/drop-target.ts | 2 +- .../timeline/components/expanded-layout.ts | 2 +- .../components/graph-editor/session.ts | 2 +- apps/web/src/timeline/components/index.tsx | 8 +- apps/web/src/timeline/components/layout.ts | 2 +- .../components/selection-hit-testing.ts | 8 +- .../timeline/components/snap-indicator.tsx | 4 +- apps/web/src/timeline/components/theme.ts | 2 +- .../timeline/components/timeline-element.tsx | 10 +- .../timeline/components/timeline-track.tsx | 4 +- .../src/timeline/components/track-layout.ts | 2 +- .../controllers/drag-drop-controller.ts | 10 +- .../element-interaction-controller.ts | 14 +- .../controllers/keyframe-drag-controller.ts | 2 +- .../controllers/playhead-controller.ts | 2 +- .../timeline/controllers/resize-controller.ts | 16 +- apps/web/src/timeline/defaults.ts | 2 +- apps/web/src/timeline/drag.ts | 2 +- apps/web/src/timeline/element-snap-source.ts | 8 +- apps/web/src/timeline/element-utils.ts | 26 +- .../src/timeline/group-move/build-group.ts | 2 +- .../src/timeline/group-move/resolve-move.ts | 2 +- apps/web/src/timeline/group-move/snap.ts | 8 +- .../timeline/group-move/track-placement.ts | 2 +- apps/web/src/timeline/group-move/types.ts | 4 +- apps/web/src/timeline/group-resize/types.ts | 2 +- .../hooks/element/use-element-interaction.ts | 4 +- .../hooks/element/use-element-selection.ts | 2 +- .../hooks/element/use-keyframe-drag.ts | 2 +- .../src/timeline/hooks/use-element-preview.ts | 3 +- .../src/timeline/hooks/use-timeline-resize.ts | 4 +- apps/web/src/timeline/index.ts | 2 +- .../placement/__tests__/resolve.test.ts | 36 +-- apps/web/src/timeline/placement/apply.ts | 12 +- .../src/timeline/placement/compatibility.ts | 2 +- .../src/timeline/placement/insert-index.ts | 2 +- apps/web/src/timeline/placement/main-track.ts | 2 +- apps/web/src/timeline/placement/overlap.ts | 2 +- apps/web/src/timeline/placement/resolve.ts | 2 +- .../src/timeline/placement/track-factory.ts | 10 +- apps/web/src/timeline/placement/types.ts | 2 +- apps/web/src/timeline/playhead-snap-source.ts | 4 +- apps/web/src/timeline/scenes.ts | 2 +- apps/web/src/timeline/snapping/build.ts | 6 +- apps/web/src/timeline/snapping/index.ts | 6 +- apps/web/src/timeline/snapping/resolve.ts | 8 +- apps/web/src/timeline/snapping/types.ts | 12 +- apps/web/src/timeline/track-capabilities.ts | 9 +- apps/web/src/timeline/track-element-update.ts | 2 +- apps/web/src/timeline/tracks.ts | 2 +- apps/web/src/timeline/types.ts | 280 +----------------- apps/web/src/timeline/update-pipeline.ts | 2 +- 177 files changed, 661 insertions(+), 676 deletions(-) create mode 100644 apps/web/src/model/bookmark.ts create mode 100644 apps/web/src/model/element-ref.ts create mode 100644 apps/web/src/model/elements/audio.ts create mode 100644 apps/web/src/model/elements/base.ts create mode 100644 apps/web/src/model/elements/effect.ts create mode 100644 apps/web/src/model/elements/graphic.ts create mode 100644 apps/web/src/model/elements/image.ts create mode 100644 apps/web/src/model/elements/index.ts create mode 100644 apps/web/src/model/elements/sticker.ts create mode 100644 apps/web/src/model/elements/text.ts create mode 100644 apps/web/src/model/elements/video.ts create mode 100644 apps/web/src/model/ids.ts create mode 100644 apps/web/src/model/index.ts create mode 100644 apps/web/src/model/scene.ts create mode 100644 apps/web/src/model/time.ts create mode 100644 apps/web/src/model/track.ts diff --git a/apps/web/src/clipboard/handlers/keyframes.ts b/apps/web/src/clipboard/handlers/keyframes.ts index ad156e12..57e813e3 100644 --- a/apps/web/src/clipboard/handlers/keyframes.ts +++ b/apps/web/src/clipboard/handlers/keyframes.ts @@ -1,6 +1,6 @@ import { getKeyframeById } from "@/animation"; 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 type { ClipboardHandler, @@ -12,7 +12,7 @@ function resolveSingleSourceElement({ selectedKeyframes, }: { selectedKeyframes: SelectedKeyframeRef[]; -}) { +}): ElementRef | null { const firstKeyframe = selectedKeyframes[0]; if (!firstKeyframe) { return null; @@ -20,7 +20,7 @@ function resolveSingleSourceElement({ const sourceElement = { trackId: firstKeyframe.trackId, - elementId: firstKeyframe.elementId, + elementId: elementId(firstKeyframe.elementId), }; const isSingleSource = selectedKeyframes.every( (keyframe) => diff --git a/apps/web/src/clipboard/types.ts b/apps/web/src/clipboard/types.ts index 0d249c9d..1a7c5048 100644 --- a/apps/web/src/clipboard/types.ts +++ b/apps/web/src/clipboard/types.ts @@ -7,11 +7,7 @@ import type { SelectedKeyframeRef, } from "@/animation/types"; import type { Command } from "@/commands/base-command"; -import type { - CreateTimelineElement, - ElementRef, - TrackType, -} from "@/timeline"; +import type { CreateTimelineElement, ElementRef, TrackType } from "@/model"; export interface ElementClipboardItem { trackId: string; diff --git a/apps/web/src/commands/base-command.ts b/apps/web/src/commands/base-command.ts index 2ab9af09..1df1f4a8 100644 --- a/apps/web/src/commands/base-command.ts +++ b/apps/web/src/commands/base-command.ts @@ -1,5 +1,5 @@ import type { EditorSelectionPatch } from "@/selection/editor-selection"; -import type { ElementRef } from "@/timeline/types"; +import type { ElementRef } from "@/model"; export interface CommandResult { selection?: EditorSelectionPatch; diff --git a/apps/web/src/commands/media/remove-media-asset.ts b/apps/web/src/commands/media/remove-media-asset.ts index 8da62181..f08f0144 100644 --- a/apps/web/src/commands/media/remove-media-asset.ts +++ b/apps/web/src/commands/media/remove-media-asset.ts @@ -6,7 +6,7 @@ import { storageService } from "@/services/storage/service"; import { videoCache } from "@/services/video-cache/service"; import { waveformCache } from "@/services/waveform-cache/service"; import { hasMediaId } from "@/timeline/element-utils"; -import type { SceneTracks } from "@/timeline"; +import type { SceneTracks } from "@/model"; export class RemoveMediaAssetCommand extends Command { private savedAssets: MediaAsset[] | null = null; diff --git a/apps/web/src/commands/scene/create-scene.ts b/apps/web/src/commands/scene/create-scene.ts index b554fe90..1c41612e 100644 --- a/apps/web/src/commands/scene/create-scene.ts +++ b/apps/web/src/commands/scene/create-scene.ts @@ -1,6 +1,6 @@ import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; -import type { TScene } from "@/timeline"; +import type { TScene } from "@/model"; import { buildDefaultScene } from "@/timeline/scenes"; export class CreateSceneCommand extends Command { diff --git a/apps/web/src/commands/scene/delete-scene.ts b/apps/web/src/commands/scene/delete-scene.ts index 92de518f..7a390a2a 100644 --- a/apps/web/src/commands/scene/delete-scene.ts +++ b/apps/web/src/commands/scene/delete-scene.ts @@ -1,6 +1,6 @@ import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; -import type { TScene } from "@/timeline"; +import type { TScene } from "@/model"; import { canDeleteScene, getFallbackSceneAfterDelete } from "@/timeline/scenes"; export class DeleteSceneCommand extends Command { diff --git a/apps/web/src/commands/scene/move-bookmark.ts b/apps/web/src/commands/scene/move-bookmark.ts index a1624eb0..48c63b71 100644 --- a/apps/web/src/commands/scene/move-bookmark.ts +++ b/apps/web/src/commands/scene/move-bookmark.ts @@ -1,6 +1,6 @@ import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; -import type { TScene } from "@/timeline"; +import type { TScene } from "@/model"; import { updateSceneInArray } from "@/timeline/scenes"; import { getFrameTime, moveBookmarkInArray } from "@/timeline/bookmarks/index"; diff --git a/apps/web/src/commands/scene/remove-bookmark.ts b/apps/web/src/commands/scene/remove-bookmark.ts index ea592556..03746b22 100644 --- a/apps/web/src/commands/scene/remove-bookmark.ts +++ b/apps/web/src/commands/scene/remove-bookmark.ts @@ -1,6 +1,6 @@ import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; -import type { TScene } from "@/timeline"; +import type { TScene } from "@/model"; import { updateSceneInArray } from "@/timeline/scenes"; import { getFrameTime, diff --git a/apps/web/src/commands/scene/rename-scene.ts b/apps/web/src/commands/scene/rename-scene.ts index 19d6bfd8..957df71e 100644 --- a/apps/web/src/commands/scene/rename-scene.ts +++ b/apps/web/src/commands/scene/rename-scene.ts @@ -1,6 +1,6 @@ import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; -import type { TScene } from "@/timeline"; +import type { TScene } from "@/model"; import { updateSceneInArray } from "@/timeline/scenes"; export class RenameSceneCommand extends Command { diff --git a/apps/web/src/commands/scene/toggle-bookmark.ts b/apps/web/src/commands/scene/toggle-bookmark.ts index 4193c76e..35e35407 100644 --- a/apps/web/src/commands/scene/toggle-bookmark.ts +++ b/apps/web/src/commands/scene/toggle-bookmark.ts @@ -1,6 +1,6 @@ import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; -import type { TScene } from "@/timeline"; +import type { TScene } from "@/model"; import { updateSceneInArray } from "@/timeline/scenes"; import { getFrameTime, diff --git a/apps/web/src/commands/scene/update-bookmark.ts b/apps/web/src/commands/scene/update-bookmark.ts index 0d6a0193..f1d679e2 100644 --- a/apps/web/src/commands/scene/update-bookmark.ts +++ b/apps/web/src/commands/scene/update-bookmark.ts @@ -1,6 +1,6 @@ import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; -import type { Bookmark, TScene } from "@/timeline"; +import type { Bookmark, TScene } from "@/model"; import { updateSceneInArray } from "@/timeline/scenes"; import { getFrameTime, diff --git a/apps/web/src/commands/timeline/clipboard/paste-keyframes.ts b/apps/web/src/commands/timeline/clipboard/paste-keyframes.ts index c19bd526..13b272f5 100644 --- a/apps/web/src/commands/timeline/clipboard/paste-keyframes.ts +++ b/apps/web/src/commands/timeline/clipboard/paste-keyframes.ts @@ -6,7 +6,7 @@ import { } from "@/animation"; import { Command, type CommandResult } from "@/commands/base-command"; import type { KeyframeClipboardItem } from "@/clipboard"; -import type { SceneTracks, TimelineElement } from "@/timeline"; +import type { SceneTracks, TimelineElement } from "@/model"; import { updateElementInSceneTracks } from "@/timeline"; import { resolveAnimationTarget } from "@/timeline/animation-targets"; import { generateUUID } from "@/utils/id"; diff --git a/apps/web/src/commands/timeline/clipboard/paste.ts b/apps/web/src/commands/timeline/clipboard/paste.ts index 4523566e..f97f0df8 100644 --- a/apps/web/src/commands/timeline/clipboard/paste.ts +++ b/apps/web/src/commands/timeline/clipboard/paste.ts @@ -4,7 +4,7 @@ import { type CommandResult, } from "@/commands/base-command"; import { EditorCore } from "@/core"; -import type { SceneTracks, TimelineElement } from "@/timeline"; +import type { ElementRef, SceneTracks, TimelineElement } from "@/model"; import type { ElementClipboardItem } from "@/clipboard"; import { generateUUID } from "@/utils/id"; import { @@ -16,7 +16,7 @@ import { cloneAnimations } from "@/animation"; export class PasteCommand extends Command { private savedState: SceneTracks | null = null; - private pastedElements: { trackId: string; elementId: string }[] = []; + private pastedElements: ElementRef[] = []; private readonly time: number; private readonly clipboardItems: ElementClipboardItem[]; @@ -142,7 +142,7 @@ export class PasteCommand extends Command { } } - getPastedElements(): { trackId: string; elementId: string }[] { + getPastedElements(): ElementRef[] { return this.pastedElements; } } diff --git a/apps/web/src/commands/timeline/element/delete-elements.ts b/apps/web/src/commands/timeline/element/delete-elements.ts index ad0267b7..5469a6df 100644 --- a/apps/web/src/commands/timeline/element/delete-elements.ts +++ b/apps/web/src/commands/timeline/element/delete-elements.ts @@ -1,7 +1,7 @@ import { Command, type CommandResult } from "@/commands/base-command"; -import type { SceneTracks } from "@/timeline"; +import type { SceneTracks } from "@/model"; import { EditorCore } from "@/core"; -import type { TimelineTrack } from "@/timeline"; +import type { TimelineTrack } from "@/model"; function removeTrackElements({ track, diff --git a/apps/web/src/commands/timeline/element/duplicate-elements.ts b/apps/web/src/commands/timeline/element/duplicate-elements.ts index 8f65f7c7..3081b6e9 100644 --- a/apps/web/src/commands/timeline/element/duplicate-elements.ts +++ b/apps/web/src/commands/timeline/element/duplicate-elements.ts @@ -3,7 +3,13 @@ import { createElementSelectionResult, type CommandResult, } 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 { EditorCore } from "@/core"; import { applyPlacement, resolveTrackPlacement } from "@/timeline/placement"; @@ -14,7 +20,7 @@ interface DuplicateElementsParams { } export class DuplicateElementsCommand extends Command { - private duplicatedElements: { trackId: string; elementId: string }[] = []; + private duplicatedElements: ElementRef[] = []; private savedState: SceneTracks | null = null; private elements: DuplicateElementsParams["elements"]; @@ -53,7 +59,7 @@ export class DuplicateElementsCommand extends Command { continue; } - const newId = generateUUID(); + const newId = elementId(generateUUID()); newTrackElements.push( buildDuplicateElement({ element, @@ -107,7 +113,7 @@ export class DuplicateElementsCommand extends Command { } } - getDuplicatedElements(): { trackId: string; elementId: string }[] { + getDuplicatedElements(): ElementRef[] { return this.duplicatedElements; } } @@ -118,7 +124,7 @@ function buildDuplicateElement({ startTime, }: { element: TimelineElement; - id: string; + id: ElementId; startTime: number; }): TimelineElement { return { diff --git a/apps/web/src/commands/timeline/element/effects/add-effect.ts b/apps/web/src/commands/timeline/element/effects/add-effect.ts index be08b433..7aad229d 100644 --- a/apps/web/src/commands/timeline/element/effects/add-effect.ts +++ b/apps/web/src/commands/timeline/element/effects/add-effect.ts @@ -1,7 +1,7 @@ import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; import { isVisualElement, updateElementInSceneTracks } from "@/timeline"; -import type { SceneTracks, VisualElement } from "@/timeline"; +import type { SceneTracks, VisualElement } from "@/model"; import { buildDefaultEffectInstance } from "@/effects"; function addEffectToElement({ diff --git a/apps/web/src/commands/timeline/element/effects/remove-effect.ts b/apps/web/src/commands/timeline/element/effects/remove-effect.ts index e6ef9934..a9cb80ef 100644 --- a/apps/web/src/commands/timeline/element/effects/remove-effect.ts +++ b/apps/web/src/commands/timeline/element/effects/remove-effect.ts @@ -1,7 +1,7 @@ import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; import { isVisualElement, updateElementInSceneTracks } from "@/timeline"; -import type { SceneTracks, VisualElement } from "@/timeline"; +import type { SceneTracks, VisualElement } from "@/model"; function removeEffectFromElement({ element, diff --git a/apps/web/src/commands/timeline/element/effects/reorder-effect.ts b/apps/web/src/commands/timeline/element/effects/reorder-effect.ts index 554804e2..9a6b21e1 100644 --- a/apps/web/src/commands/timeline/element/effects/reorder-effect.ts +++ b/apps/web/src/commands/timeline/element/effects/reorder-effect.ts @@ -1,7 +1,7 @@ import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; import { isVisualElement, updateElementInSceneTracks } from "@/timeline"; -import type { SceneTracks, VisualElement } from "@/timeline"; +import type { SceneTracks, VisualElement } from "@/model"; function reorderEffectsOnElement({ element, diff --git a/apps/web/src/commands/timeline/element/effects/toggle-effect.ts b/apps/web/src/commands/timeline/element/effects/toggle-effect.ts index 89149a79..a6661a87 100644 --- a/apps/web/src/commands/timeline/element/effects/toggle-effect.ts +++ b/apps/web/src/commands/timeline/element/effects/toggle-effect.ts @@ -1,7 +1,7 @@ import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; import { isVisualElement, updateElementInSceneTracks } from "@/timeline"; -import type { SceneTracks, VisualElement } from "@/timeline"; +import type { SceneTracks, VisualElement } from "@/model"; export function toggleEffectOnElement({ element, diff --git a/apps/web/src/commands/timeline/element/effects/update-effect-params.ts b/apps/web/src/commands/timeline/element/effects/update-effect-params.ts index 62b3c937..480b578f 100644 --- a/apps/web/src/commands/timeline/element/effects/update-effect-params.ts +++ b/apps/web/src/commands/timeline/element/effects/update-effect-params.ts @@ -2,7 +2,7 @@ import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; import { isVisualElement, updateElementInSceneTracks } from "@/timeline"; import type { ParamValues } from "@/params"; -import type { SceneTracks, VisualElement } from "@/timeline"; +import type { SceneTracks, VisualElement } from "@/model"; function updateEffectParamsOnElement({ element, diff --git a/apps/web/src/commands/timeline/element/insert-element.ts b/apps/web/src/commands/timeline/element/insert-element.ts index 49ac6f2d..ee7daea6 100644 --- a/apps/web/src/commands/timeline/element/insert-element.ts +++ b/apps/web/src/commands/timeline/element/insert-element.ts @@ -1,11 +1,13 @@ import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; -import type { - CreateTimelineElement, - SceneTracks, - TimelineElement, - TrackType, -} from "@/timeline"; +import { + elementId, + type CreateTimelineElement, + type ElementId, + type SceneTracks, + type TimelineElement, + type TrackType, +} from "@/model"; import { generateUUID } from "@/utils/id"; import { requiresMediaId } from "@/timeline/element-utils"; import type { MediaAsset } from "@/media/types"; @@ -29,13 +31,13 @@ export interface InsertElementParams { } export class InsertElementCommand extends Command { - private elementId: string; + private elementId: ElementId; private savedState: SceneTracks | null = null; private targetTrackId: string | null = null; constructor({ element, placement }: InsertElementParams) { super(); - this.elementId = generateUUID(); + this.elementId = elementId(generateUUID()); this.element = element; this.placement = placement; } diff --git a/apps/web/src/commands/timeline/element/keyframes/remove-effect-param-keyframe.ts b/apps/web/src/commands/timeline/element/keyframes/remove-effect-param-keyframe.ts index c049d5bb..7aa42ce3 100644 --- a/apps/web/src/commands/timeline/element/keyframes/remove-effect-param-keyframe.ts +++ b/apps/web/src/commands/timeline/element/keyframes/remove-effect-param-keyframe.ts @@ -3,7 +3,7 @@ import { Command, type CommandResult } from "@/commands/base-command"; import { removeEffectParamKeyframe } from "@/animation/effect-param-channel"; import { updateElementInSceneTracks } from "@/timeline"; import { isVisualElement } from "@/timeline/element-utils"; -import type { SceneTracks } from "@/timeline"; +import type { SceneTracks } from "@/model"; export class RemoveEffectParamKeyframeCommand extends Command { private savedState: SceneTracks | null = null; diff --git a/apps/web/src/commands/timeline/element/keyframes/remove-keyframe.ts b/apps/web/src/commands/timeline/element/keyframes/remove-keyframe.ts index 07abe2be..6e83cc29 100644 --- a/apps/web/src/commands/timeline/element/keyframes/remove-keyframe.ts +++ b/apps/web/src/commands/timeline/element/keyframes/remove-keyframe.ts @@ -6,7 +6,7 @@ import { import { Command, type CommandResult } from "@/commands/base-command"; import { updateElementInSceneTracks } from "@/timeline"; 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"; function removeKeyframeAndPersist({ diff --git a/apps/web/src/commands/timeline/element/keyframes/retime-keyframe.ts b/apps/web/src/commands/timeline/element/keyframes/retime-keyframe.ts index aa04e2bc..ea501f39 100644 --- a/apps/web/src/commands/timeline/element/keyframes/retime-keyframe.ts +++ b/apps/web/src/commands/timeline/element/keyframes/retime-keyframe.ts @@ -3,7 +3,7 @@ import { retimeElementKeyframe } from "@/animation"; import { Command, type CommandResult } from "@/commands/base-command"; import { updateElementInSceneTracks } from "@/timeline"; import type { AnimationPath } from "@/animation/types"; -import type { SceneTracks } from "@/timeline"; +import type { SceneTracks } from "@/model"; import { resolveAnimationTarget } from "@/timeline/animation-targets"; export class RetimeKeyframeCommand extends Command { diff --git a/apps/web/src/commands/timeline/element/keyframes/update-scalar-keyframe-curve.ts b/apps/web/src/commands/timeline/element/keyframes/update-scalar-keyframe-curve.ts index 51f10ee9..efeba176 100644 --- a/apps/web/src/commands/timeline/element/keyframes/update-scalar-keyframe-curve.ts +++ b/apps/web/src/commands/timeline/element/keyframes/update-scalar-keyframe-curve.ts @@ -9,7 +9,7 @@ import type { AnimationPath, ScalarCurveKeyframePatch, } from "@/animation/types"; -import type { SceneTracks } from "@/timeline"; +import type { SceneTracks } from "@/model"; export class UpdateScalarKeyframeCurveCommand extends Command { private savedState: SceneTracks | null = null; diff --git a/apps/web/src/commands/timeline/element/keyframes/upsert-effect-param-keyframe.ts b/apps/web/src/commands/timeline/element/keyframes/upsert-effect-param-keyframe.ts index 7bd1b2e5..d0992024 100644 --- a/apps/web/src/commands/timeline/element/keyframes/upsert-effect-param-keyframe.ts +++ b/apps/web/src/commands/timeline/element/keyframes/upsert-effect-param-keyframe.ts @@ -8,7 +8,7 @@ import { updateElementInSceneTracks } from "@/timeline"; import { isVisualElement } from "@/timeline/element-utils"; import { resolveAnimationTarget } from "@/timeline/animation-targets"; import type { AnimationInterpolation } from "@/animation/types"; -import type { SceneTracks } from "@/timeline"; +import type { SceneTracks } from "@/model"; export class UpsertEffectParamKeyframeCommand extends Command { private savedState: SceneTracks | null = null; diff --git a/apps/web/src/commands/timeline/element/keyframes/upsert-keyframe.ts b/apps/web/src/commands/timeline/element/keyframes/upsert-keyframe.ts index 15c18bfc..48403186 100644 --- a/apps/web/src/commands/timeline/element/keyframes/upsert-keyframe.ts +++ b/apps/web/src/commands/timeline/element/keyframes/upsert-keyframe.ts @@ -2,7 +2,7 @@ import { EditorCore } from "@/core"; import { Command, type CommandResult } from "@/commands/base-command"; import { upsertPathKeyframe } from "@/animation"; import { updateElementInSceneTracks } from "@/timeline"; -import type { SceneTracks } from "@/timeline"; +import type { SceneTracks } from "@/model"; import { resolveAnimationTarget } from "@/timeline/animation-targets"; import type { AnimationPath, diff --git a/apps/web/src/commands/timeline/element/masks/delete-custom-mask-points.ts b/apps/web/src/commands/timeline/element/masks/delete-custom-mask-points.ts index 1445b1f8..84f46be4 100644 --- a/apps/web/src/commands/timeline/element/masks/delete-custom-mask-points.ts +++ b/apps/web/src/commands/timeline/element/masks/delete-custom-mask-points.ts @@ -6,7 +6,7 @@ import { } from "@/masks/custom-path"; import type { CustomMask } from "@/masks/types"; import { isMaskableElement, updateElementInSceneTracks } from "@/timeline"; -import type { MaskableElement, SceneTracks } from "@/timeline"; +import type { MaskableElement, SceneTracks } from "@/model"; function deletePointsFromCustomMask({ mask, diff --git a/apps/web/src/commands/timeline/element/masks/insert-custom-mask-point.ts b/apps/web/src/commands/timeline/element/masks/insert-custom-mask-point.ts index 58559af6..42eae44c 100644 --- a/apps/web/src/commands/timeline/element/masks/insert-custom-mask-point.ts +++ b/apps/web/src/commands/timeline/element/masks/insert-custom-mask-point.ts @@ -4,7 +4,7 @@ import { insertPointOnCustomMaskSegment } from "@/masks/definitions/custom"; import type { ElementBounds } from "@/preview/element-bounds"; import type { CustomMask } from "@/masks/types"; import { isMaskableElement, updateElementInSceneTracks } from "@/timeline"; -import type { MaskableElement, SceneTracks } from "@/timeline"; +import type { MaskableElement, SceneTracks } from "@/model"; function insertPointIntoCustomMask({ mask, diff --git a/apps/web/src/commands/timeline/element/masks/remove-mask.ts b/apps/web/src/commands/timeline/element/masks/remove-mask.ts index b543bce1..44d4f4bf 100644 --- a/apps/web/src/commands/timeline/element/masks/remove-mask.ts +++ b/apps/web/src/commands/timeline/element/masks/remove-mask.ts @@ -1,7 +1,7 @@ import { EditorCore } from "@/core"; import { Command, type CommandResult } from "@/commands/base-command"; import { isMaskableElement, updateElementInSceneTracks } from "@/timeline"; -import type { SceneTracks, MaskableElement } from "@/timeline"; +import type { MaskableElement, SceneTracks } from "@/model"; function removeMaskFromElement({ element, diff --git a/apps/web/src/commands/timeline/element/masks/toggle-mask-inverted.ts b/apps/web/src/commands/timeline/element/masks/toggle-mask-inverted.ts index fec9b923..c8990828 100644 --- a/apps/web/src/commands/timeline/element/masks/toggle-mask-inverted.ts +++ b/apps/web/src/commands/timeline/element/masks/toggle-mask-inverted.ts @@ -2,7 +2,7 @@ import { EditorCore } from "@/core"; import { Command, type CommandResult } from "@/commands/base-command"; import { isMaskableElement, updateElementInSceneTracks } from "@/timeline"; import type { Mask } from "@/masks/types"; -import type { SceneTracks, MaskableElement } from "@/timeline"; +import type { MaskableElement, SceneTracks } from "@/model"; export function toggleMaskInvertedOnElement({ element, diff --git a/apps/web/src/commands/timeline/element/move-elements.ts b/apps/web/src/commands/timeline/element/move-elements.ts index 3ed31d42..a2bd46b4 100644 --- a/apps/web/src/commands/timeline/element/move-elements.ts +++ b/apps/web/src/commands/timeline/element/move-elements.ts @@ -4,7 +4,12 @@ import { type CommandResult, } from "@/commands/base-command"; import { EditorCore } from "@/core"; -import type { SceneTracks, TimelineElement, TimelineTrack } from "@/timeline"; +import { + elementId as toElementId, + type SceneTracks, + type TimelineElement, + type TimelineTrack, +} from "@/model"; import { buildEmptyTrack, validateElementTrackCompatibility, @@ -117,7 +122,7 @@ export class MoveElementCommand extends Command { return createElementSelectionResult( this.moves.map(({ elementId, targetTrackId }) => ({ trackId: targetTrackId, - elementId, + elementId: toElementId(elementId), })), ); } diff --git a/apps/web/src/commands/timeline/element/split-elements.ts b/apps/web/src/commands/timeline/element/split-elements.ts index 11c317f4..a2e9ae65 100644 --- a/apps/web/src/commands/timeline/element/split-elements.ts +++ b/apps/web/src/commands/timeline/element/split-elements.ts @@ -3,7 +3,12 @@ import { createElementSelectionResult, type CommandResult, } 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 { EditorCore } from "@/core"; import { isRetimableElement } from "@/timeline"; @@ -12,7 +17,7 @@ import { getSourceSpanAtClipTime } from "@/retime"; export class SplitElementsCommand extends Command { private savedState: SceneTracks | null = null; - private rightSideElements: { trackId: string; elementId: string }[] = []; + private rightSideElements: ElementRef[] = []; private readonly elements: { trackId: string; elementId: string }[]; private readonly splitTime: number; private readonly retainSide: "both" | "left" | "right"; @@ -32,7 +37,7 @@ export class SplitElementsCommand extends Command { this.retainSide = retainSide; } - getRightSideElements(): { trackId: string; elementId: string }[] { + getRightSideElements(): ElementRef[] { return this.rightSideElements; } @@ -107,7 +112,7 @@ export class SplitElementsCommand extends Command { }, ]; } else if (this.retainSide === "right") { - const newId = generateUUID(); + const newId = elementId(generateUUID()); this.rightSideElements.push({ trackId: track.id, elementId: newId, @@ -126,7 +131,7 @@ export class SplitElementsCommand extends Command { ]; } else { // "both" - split into two pieces - const secondElementId = generateUUID(); + const secondElementId = elementId(generateUUID()); this.rightSideElements.push({ trackId: track.id, elementId: secondElementId, diff --git a/apps/web/src/commands/timeline/element/toggle-source-audio-separation.ts b/apps/web/src/commands/timeline/element/toggle-source-audio-separation.ts index 36d4cd87..091a0d4e 100644 --- a/apps/web/src/commands/timeline/element/toggle-source-audio-separation.ts +++ b/apps/web/src/commands/timeline/element/toggle-source-audio-separation.ts @@ -10,11 +10,12 @@ import { resolveTrackPlacement, } from "@/timeline/placement"; import { updateElementInSceneTracks } from "@/timeline/track-element-update"; -import type { - SceneTracks, - TimelineElement, - VideoElement, -} from "@/timeline/types"; +import { + elementId, + type SceneTracks, + type TimelineElement, + type VideoElement, +} from "@/model"; import { generateUUID } from "@/utils/id"; export class ToggleSourceAudioSeparationCommand extends Command { @@ -75,7 +76,7 @@ export class ToggleSourceAudioSeparationCommand extends Command { ...buildSeparatedAudioElement({ sourceElement: videoElement, }), - id: generateUUID(), + id: elementId(generateUUID()), }; const placementResult = resolveTrackPlacement({ tracks: this.savedState, diff --git a/apps/web/src/commands/timeline/element/update-elements.ts b/apps/web/src/commands/timeline/element/update-elements.ts index f03f2498..bf5fab0b 100644 --- a/apps/web/src/commands/timeline/element/update-elements.ts +++ b/apps/web/src/commands/timeline/element/update-elements.ts @@ -1,6 +1,6 @@ import { EditorCore } from "@/core"; import { Command, type CommandResult } from "@/commands/base-command"; -import type { SceneTracks, TimelineElement } from "@/timeline"; +import type { SceneTracks, TimelineElement } from "@/model"; import { findTrackInSceneTracks, updateElementInSceneTracks, diff --git a/apps/web/src/commands/timeline/track/add-track.ts b/apps/web/src/commands/timeline/track/add-track.ts index 6b58b687..8a9c8e42 100644 --- a/apps/web/src/commands/timeline/track/add-track.ts +++ b/apps/web/src/commands/timeline/track/add-track.ts @@ -1,5 +1,5 @@ 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 { EditorCore } from "@/core"; import { diff --git a/apps/web/src/commands/timeline/track/remove-track.ts b/apps/web/src/commands/timeline/track/remove-track.ts index 13a9ff4e..eae8ef41 100644 --- a/apps/web/src/commands/timeline/track/remove-track.ts +++ b/apps/web/src/commands/timeline/track/remove-track.ts @@ -1,6 +1,6 @@ import { Command, type CommandResult } from "@/commands/base-command"; import { EditorCore } from "@/core"; -import type { SceneTracks } from "@/timeline"; +import type { SceneTracks } from "@/model"; export class RemoveTrackCommand extends Command { private savedState: SceneTracks | null = null; diff --git a/apps/web/src/commands/timeline/track/toggle-track-mute.ts b/apps/web/src/commands/timeline/track/toggle-track-mute.ts index 0d1f41cd..424eb06a 100644 --- a/apps/web/src/commands/timeline/track/toggle-track-mute.ts +++ b/apps/web/src/commands/timeline/track/toggle-track-mute.ts @@ -1,5 +1,5 @@ import { Command, type CommandResult } from "@/commands/base-command"; -import type { SceneTracks } from "@/timeline"; +import type { SceneTracks } from "@/model"; import { EditorCore } from "@/core"; import { canTrackHaveAudio, findTrackInSceneTracks, updateTrackInSceneTracks } from "@/timeline"; diff --git a/apps/web/src/commands/timeline/track/toggle-track-visibility.ts b/apps/web/src/commands/timeline/track/toggle-track-visibility.ts index f4160e3f..95de0c55 100644 --- a/apps/web/src/commands/timeline/track/toggle-track-visibility.ts +++ b/apps/web/src/commands/timeline/track/toggle-track-visibility.ts @@ -1,5 +1,5 @@ import { Command, type CommandResult } from "@/commands/base-command"; -import type { SceneTracks } from "@/timeline"; +import type { SceneTracks } from "@/model"; import { EditorCore } from "@/core"; import { canTrackBeHidden, findTrackInSceneTracks, updateTrackInSceneTracks } from "@/timeline"; diff --git a/apps/web/src/commands/timeline/tracks-snapshot.ts b/apps/web/src/commands/timeline/tracks-snapshot.ts index 5b6d871a..f7a65686 100644 --- a/apps/web/src/commands/timeline/tracks-snapshot.ts +++ b/apps/web/src/commands/timeline/tracks-snapshot.ts @@ -1,5 +1,5 @@ import { Command, type CommandResult } from "@/commands/base-command"; -import type { SceneTracks } from "@/timeline"; +import type { SceneTracks } from "@/model"; import { EditorCore } from "@/core"; export class TracksSnapshotCommand extends Command { diff --git a/apps/web/src/components/editor/panels/assets/views/assets.tsx b/apps/web/src/components/editor/panels/assets/views/assets.tsx index 4afd22b7..5b76c1f2 100644 --- a/apps/web/src/components/editor/panels/assets/views/assets.tsx +++ b/apps/web/src/components/editor/panels/assets/views/assets.tsx @@ -45,7 +45,7 @@ import { type MediaViewMode, useAssetsPanelStore, } 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 { cn } from "@/utils/ui"; import { diff --git a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-color-property.ts b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-color-property.ts index 80fc171b..1f134bb4 100644 --- a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-color-property.ts +++ b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-color-property.ts @@ -4,7 +4,7 @@ import { hasKeyframesForPath, } from "@/animation"; import type { AnimationPropertyPath, ElementAnimations } from "@/animation/types"; -import type { TimelineElement } from "@/timeline"; +import type { TimelineElement } from "@/model"; import { upsertElementKeyframe } from "@/timeline/animation-properties"; export function useKeyframedColorProperty({ diff --git a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-number-property.ts b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-number-property.ts index 32222b7a..4ed3f68c 100644 --- a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-number-property.ts +++ b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-number-property.ts @@ -4,7 +4,7 @@ import { hasKeyframesForPath, } from "@/animation"; import type { AnimationPropertyPath, ElementAnimations } from "@/animation/types"; -import type { TimelineElement } from "@/timeline"; +import type { TimelineElement } from "@/model"; import { upsertElementKeyframe } from "@/timeline/animation-properties"; import { snapToStep } from "@/utils/math"; import { usePropertyDraft } from "./use-property-draft"; diff --git a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-param-property.ts b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-param-property.ts index 18e027c7..09dc1400 100644 --- a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-param-property.ts +++ b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-param-property.ts @@ -16,7 +16,7 @@ import { getAnimationParamValueKind, } from "@/animation/animated-params"; import type { ParamDefinition } from "@/params"; -import type { TimelineElement } from "@/timeline"; +import type { TimelineElement } from "@/model"; export interface KeyframedParamPropertyResult { hasAnimatedKeyframes: boolean; diff --git a/apps/web/src/components/editor/panels/properties/registry.tsx b/apps/web/src/components/editor/panels/properties/registry.tsx index f9e40ab3..e63bd9fa 100644 --- a/apps/web/src/components/editor/panels/properties/registry.tsx +++ b/apps/web/src/components/editor/panels/properties/registry.tsx @@ -1,17 +1,5 @@ import type { ReactNode } from "react"; -import type { - EffectElement, - GraphicElement, - ImageElement, - MaskableElement, - RetimableElement, - StickerElement, - TextElement, - VisualElement, - VideoElement, - AudioElement, - TimelineElement, -} from "@/timeline"; +import type { AudioElement, EffectElement, GraphicElement, ImageElement, MaskableElement, RetimableElement, StickerElement, TextElement, TimelineElement, VideoElement, VisualElement } from "@/model"; import type { MediaAsset } from "@/media/types"; import { HugeiconsIcon } from "@hugeicons/react"; import { diff --git a/apps/web/src/core/managers/commands.ts b/apps/web/src/core/managers/commands.ts index 9082db10..806b9fa6 100644 --- a/apps/web/src/core/managers/commands.ts +++ b/apps/web/src/core/managers/commands.ts @@ -2,7 +2,7 @@ import type { EditorCore } from "@/core"; import type { Command, CommandResult } from "@/commands"; import type { EditorSelectionSnapshot } from "@/selection/editor-selection"; import { applyRippleAdjustments, computeRippleAdjustments } from "@/ripple"; -import type { SceneTracks } from "@/timeline/types"; +import type { SceneTracks } from "@/model"; interface CommandHistoryEntry { command: Command; diff --git a/apps/web/src/core/managers/scenes-manager.ts b/apps/web/src/core/managers/scenes-manager.ts index 13f251a1..e556649a 100644 --- a/apps/web/src/core/managers/scenes-manager.ts +++ b/apps/web/src/core/managers/scenes-manager.ts @@ -1,5 +1,5 @@ import type { EditorCore } from "@/core"; -import type { SceneTracks, TScene } from "@/timeline"; +import type { SceneTracks, TScene } from "@/model"; import { storageService } from "@/services/storage/service"; import { getMainScene, diff --git a/apps/web/src/core/managers/selection-manager.ts b/apps/web/src/core/managers/selection-manager.ts index cfc46ccc..cc095246 100644 --- a/apps/web/src/core/managers/selection-manager.ts +++ b/apps/web/src/core/managers/selection-manager.ts @@ -6,7 +6,7 @@ import type { EditorSelectionSnapshot, SelectedMaskPointSelection, } from "@/selection/editor-selection"; -import type { ElementRef } from "@/timeline/types"; +import type { ElementRef } from "@/model"; export class SelectionManager { private selectedElements: ElementRef[] = []; diff --git a/apps/web/src/core/managers/timeline-manager.ts b/apps/web/src/core/managers/timeline-manager.ts index a2bf7451..decaf2c5 100644 --- a/apps/web/src/core/managers/timeline-manager.ts +++ b/apps/web/src/core/managers/timeline-manager.ts @@ -1,13 +1,7 @@ import type { EditorCore } from "@/core"; import type { ElementBounds } from "@/preview/element-bounds"; import type { ParamValues } from "@/params"; -import type { - SceneTracks, - TrackType, - TimelineTrack, - TimelineElement, - RetimeConfig, -} from "@/timeline"; +import type { RetimeConfig, SceneTracks, TimelineElement, TimelineTrack, TrackType } from "@/model"; import { calculateTotalDuration } from "@/timeline"; import { TimelineDragSource } from "@/timeline/drag-source"; import { findTrackInSceneTracks } from "@/timeline/track-element-update"; diff --git a/apps/web/src/effects/components/effects-tab.tsx b/apps/web/src/effects/components/effects-tab.tsx index 102d818a..5802fbce 100644 --- a/apps/web/src/effects/components/effects-tab.tsx +++ b/apps/web/src/effects/components/effects-tab.tsx @@ -3,7 +3,7 @@ import { useState } from "react"; import type { ParamValues } from "@/params"; import type { Effect } from "@/effects/types"; -import type { EffectElement, VisualElement } from "@/timeline"; +import type { EffectElement, VisualElement } from "@/model"; import { effectsRegistry } from "@/effects"; import { useEditor } from "@/editor/use-editor"; import { useElementPreview } from "@/timeline/hooks/use-element-preview"; diff --git a/apps/web/src/effects/index.ts b/apps/web/src/effects/index.ts index eebb8cbc..595bec0e 100644 --- a/apps/web/src/effects/index.ts +++ b/apps/web/src/effects/index.ts @@ -3,7 +3,7 @@ import { buildDefaultParamValues } from "@/params/registry"; import { effectsRegistry } from "./registry"; import type { ParamValues } from "@/params"; 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 { registerDefaultEffects } from "./definitions"; diff --git a/apps/web/src/graphics/components/graphic-tab.tsx b/apps/web/src/graphics/components/graphic-tab.tsx index e0ae0236..aa6e26cf 100644 --- a/apps/web/src/graphics/components/graphic-tab.tsx +++ b/apps/web/src/graphics/components/graphic-tab.tsx @@ -7,7 +7,7 @@ import { type KeyframedParamPropertyResult, } from "@/components/editor/panels/properties/hooks/use-keyframed-param-property"; import type { ParamDefinition, ParamValues } from "@/params"; -import type { GraphicElement } from "@/timeline"; +import type { GraphicElement } from "@/model"; import { graphicsRegistry, registerDefaultGraphics, diff --git a/apps/web/src/masks/components/masks-tab.tsx b/apps/web/src/masks/components/masks-tab.tsx index 97fba3ab..851f2205 100644 --- a/apps/web/src/masks/components/masks-tab.tsx +++ b/apps/web/src/masks/components/masks-tab.tsx @@ -1,6 +1,6 @@ "use client"; -import type { MaskableElement } from "@/timeline"; +import type { MaskableElement } from "@/model"; import type { Mask, MaskType, TextMask } from "@/masks/types"; import type { NumberParamDefinition, SelectParamDefinition } from "@/params"; import { masksRegistry, buildDefaultMaskInstance } from "@/masks"; diff --git a/apps/web/src/masks/geometry.ts b/apps/web/src/masks/geometry.ts index 007dfa49..1bfead0d 100644 --- a/apps/web/src/masks/geometry.ts +++ b/apps/web/src/masks/geometry.ts @@ -1,5 +1,5 @@ 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 type { RectangleMaskParams } from "@/masks/types"; @@ -100,10 +100,10 @@ export function toGlobalMaskSnapLines({ bounds, canvasSize, }: { - lines: SnapLine[]; + lines: PreviewSnapLine[]; bounds: ElementBounds; canvasSize: { width: number; height: number }; -}): SnapLine[] { +}): PreviewSnapLine[] { const centerX = bounds.cx - canvasSize.width / 2; const centerY = bounds.cy - canvasSize.height / 2; diff --git a/apps/web/src/masks/snap.ts b/apps/web/src/masks/snap.ts index cff62ebb..a029353f 100644 --- a/apps/web/src/masks/snap.ts +++ b/apps/web/src/masks/snap.ts @@ -6,7 +6,7 @@ import { snapScale, snapScaleAxes, type ScaleEdgePreference, - type SnapLine, + type PreviewSnapLine, } from "@/preview/preview-snap"; import type { RectangleMaskParams, SplitMaskParams } from "@/masks/types"; import { @@ -20,7 +20,7 @@ type SharedMaskParams = SplitMaskParams | RectangleMaskParams; type MaskSnapResult = { params: TParams; - activeLines: SnapLine[]; + activeLines: PreviewSnapLine[]; }; const CORNER_SIZE_HANDLES = new Set([ diff --git a/apps/web/src/masks/types.ts b/apps/web/src/masks/types.ts index 4f6c90aa..087f2ad9 100644 --- a/apps/web/src/masks/types.ts +++ b/apps/web/src/masks/types.ts @@ -1,5 +1,5 @@ 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 { CustomMaskPathPoint } from "@/masks/custom-path"; import type { @@ -261,7 +261,7 @@ export interface MaskSnapResult< TParams extends BaseMaskParams = BaseMaskParams, > { params: TParams; - activeLines: SnapLine[]; + activeLines: PreviewSnapLine[]; } export interface MaskInteractionResult { diff --git a/apps/web/src/masks/use-mask-handles.ts b/apps/web/src/masks/use-mask-handles.ts index b81de4f4..c5af06cb 100644 --- a/apps/web/src/masks/use-mask-handles.ts +++ b/apps/web/src/masks/use-mask-handles.ts @@ -14,11 +14,11 @@ import { } from "@/preview/element-bounds"; import { SNAP_THRESHOLD_SCREEN_PIXELS, - type SnapLine, + type PreviewSnapLine, } from "@/preview/preview-snap"; import type { SelectedMaskPointSelection } from "@/selection/editor-selection"; import type { Mask, MaskInteractionResult } from "@/masks/types"; -import type { MaskableElement } from "@/timeline"; +import type { MaskableElement } from "@/model"; import { registerCanceller } from "@/editor/cancel-interaction"; interface DragState { @@ -94,7 +94,7 @@ function withUpdatedMaskParams({ export function useMaskHandles({ onSnapLinesChange, }: { - onSnapLinesChange?: (lines: SnapLine[]) => void; + onSnapLinesChange?: (lines: PreviewSnapLine[]) => void; }) { const editor = useEditor(); const isShiftHeldRef = useShiftKey(); @@ -552,7 +552,7 @@ export function useMaskHandles({ screenPixels: SNAP_THRESHOLD_SCREEN_PIXELS, }); const { params: nextParams, activeLines } = isShiftHeldRef.current - ? { params: proposedParams, activeLines: [] as SnapLine[] } + ? { params: proposedParams, activeLines: [] as PreviewSnapLine[] } : (def.interaction.snap?.({ handleId: drag.handleId, startParams: drag.startParams, @@ -560,7 +560,7 @@ export function useMaskHandles({ bounds: selectedWithMask.bounds, canvasSize, snapThreshold, - }) ?? { params: proposedParams, activeLines: [] as SnapLine[] }); + }) ?? { params: proposedParams, activeLines: [] as PreviewSnapLine[] }); onSnapLinesChange?.(activeLines); diff --git a/apps/web/src/media/audio.ts b/apps/web/src/media/audio.ts index 42756d49..1aa98b8d 100644 --- a/apps/web/src/media/audio.ts +++ b/apps/web/src/media/audio.ts @@ -1,10 +1,4 @@ -import type { - AudioElement, - VideoElement, - LibraryAudioElement, - RetimeConfig, - SceneTracks, -} from "@/timeline"; +import type { AudioElement, LibraryAudioElement, RetimeConfig, SceneTracks, VideoElement } from "@/model"; import { shouldMaintainPitch } from "@/retime/rate"; import type { MediaAsset } from "@/media/types"; import { applyAudioMasteringToBuffer } from "@/media/audio-mastering"; diff --git a/apps/web/src/media/mediabunny.ts b/apps/web/src/media/mediabunny.ts index 3c949b35..a193bae4 100644 --- a/apps/web/src/media/mediabunny.ts +++ b/apps/web/src/media/mediabunny.ts @@ -6,7 +6,7 @@ import { type VideoCodec, } from "mediabunny"; import { createTimelineAudioBuffer } from "@/media/audio"; -import type { SceneTracks } from "@/timeline"; +import type { SceneTracks } from "@/model"; import type { MediaAsset } from "@/media/types"; import { TICKS_PER_SECOND } from "@/wasm"; import { renderThumbnailDataUrl } from "./thumbnail"; diff --git a/apps/web/src/media/waveform-summary.ts b/apps/web/src/media/waveform-summary.ts index 03a0059c..615b0267 100644 --- a/apps/web/src/media/waveform-summary.ts +++ b/apps/web/src/media/waveform-summary.ts @@ -1,7 +1,7 @@ "use client"; import { getSourceTimeAtClipTime } from "@/retime"; -import type { RetimeConfig } from "@/timeline"; +import type { RetimeConfig } from "@/model"; const RMS_ANALYSIS_WINDOW_SECONDS = 0.02; const DEFAULT_SOURCE_WAVEFORM_BUCKET_SIZE = 128; diff --git a/apps/web/src/model/bookmark.ts b/apps/web/src/model/bookmark.ts new file mode 100644 index 00000000..4d55b062 --- /dev/null +++ b/apps/web/src/model/bookmark.ts @@ -0,0 +1,6 @@ +export interface Bookmark { + time: number; + note?: string; + color?: string; + duration?: number; +} diff --git a/apps/web/src/model/element-ref.ts b/apps/web/src/model/element-ref.ts new file mode 100644 index 00000000..3cc7f02d --- /dev/null +++ b/apps/web/src/model/element-ref.ts @@ -0,0 +1,6 @@ +import type { ElementId } from "./ids"; + +export type ElementRef = { + trackId: string; + elementId: ElementId; +}; diff --git a/apps/web/src/model/elements/audio.ts b/apps/web/src/model/elements/audio.ts new file mode 100644 index 00000000..7a71cf94 --- /dev/null +++ b/apps/web/src/model/elements/audio.ts @@ -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; diff --git a/apps/web/src/model/elements/base.ts b/apps/web/src/model/elements/base.ts new file mode 100644 index 00000000..49ca7455 --- /dev/null +++ b/apps/web/src/model/elements/base.ts @@ -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; +} diff --git a/apps/web/src/model/elements/effect.ts b/apps/web/src/model/elements/effect.ts new file mode 100644 index 00000000..ec9f86a9 --- /dev/null +++ b/apps/web/src/model/elements/effect.ts @@ -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; +} diff --git a/apps/web/src/model/elements/graphic.ts b/apps/web/src/model/elements/graphic.ts new file mode 100644 index 00000000..955f23fa --- /dev/null +++ b/apps/web/src/model/elements/graphic.ts @@ -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[]; +} diff --git a/apps/web/src/model/elements/image.ts b/apps/web/src/model/elements/image.ts new file mode 100644 index 00000000..032a3a26 --- /dev/null +++ b/apps/web/src/model/elements/image.ts @@ -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[]; +} diff --git a/apps/web/src/model/elements/index.ts b/apps/web/src/model/elements/index.ts new file mode 100644 index 00000000..83cf2abf --- /dev/null +++ b/apps/web/src/model/elements/index.ts @@ -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(...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; +export type CreateLibraryAudioElement = Omit; +export type CreateAudioElement = + | CreateUploadAudioElement + | CreateLibraryAudioElement; +export type CreateVideoElement = Omit; +export type CreateImageElement = Omit; +export type CreateTextElement = Omit; +export type CreateStickerElement = Omit; +export type CreateGraphicElement = Omit; +export type CreateEffectElement = Omit; +export type CreateTimelineElement = + | CreateAudioElement + | CreateVideoElement + | CreateImageElement + | CreateTextElement + | CreateStickerElement + | CreateGraphicElement + | CreateEffectElement; diff --git a/apps/web/src/model/elements/sticker.ts b/apps/web/src/model/elements/sticker.ts new file mode 100644 index 00000000..1db64ea7 --- /dev/null +++ b/apps/web/src/model/elements/sticker.ts @@ -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[]; +} diff --git a/apps/web/src/model/elements/text.ts b/apps/web/src/model/elements/text.ts new file mode 100644 index 00000000..a4afd27d --- /dev/null +++ b/apps/web/src/model/elements/text.ts @@ -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[]; +} diff --git a/apps/web/src/model/elements/video.ts b/apps/web/src/model/elements/video.ts new file mode 100644 index 00000000..3611382b --- /dev/null +++ b/apps/web/src/model/elements/video.ts @@ -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[]; +} diff --git a/apps/web/src/model/ids.ts b/apps/web/src/model/ids.ts new file mode 100644 index 00000000..881ea2be --- /dev/null +++ b/apps/web/src/model/ids.ts @@ -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; +} diff --git a/apps/web/src/model/index.ts b/apps/web/src/model/index.ts new file mode 100644 index 00000000..25afc741 --- /dev/null +++ b/apps/web/src/model/index.ts @@ -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"; diff --git a/apps/web/src/model/scene.ts b/apps/web/src/model/scene.ts new file mode 100644 index 00000000..3f59738a --- /dev/null +++ b/apps/web/src/model/scene.ts @@ -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; +} diff --git a/apps/web/src/model/time.ts b/apps/web/src/model/time.ts new file mode 100644 index 00000000..b22b50fd --- /dev/null +++ b/apps/web/src/model/time.ts @@ -0,0 +1,5 @@ +export type MediaTime = number & { readonly __brand: "MediaTime" }; + +export function mediaTime(value: number): MediaTime { + return value as MediaTime; +} diff --git a/apps/web/src/model/track.ts b/apps/web/src/model/track.ts new file mode 100644 index 00000000..1e433d3e --- /dev/null +++ b/apps/web/src/model/track.ts @@ -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[]; +} diff --git a/apps/web/src/preview/components/mask-handles.tsx b/apps/web/src/preview/components/mask-handles.tsx index 340cab0a..2d51fc3e 100644 --- a/apps/web/src/preview/components/mask-handles.tsx +++ b/apps/web/src/preview/components/mask-handles.tsx @@ -3,7 +3,7 @@ import { PEN_CURSOR } from "@/preview/components/cursors"; import { usePreviewViewport } from "@/preview/components/preview-viewport"; import { useMaskHandles } from "@/masks/use-mask-handles"; -import type { SnapLine } from "@/preview/preview-snap"; +import type { PreviewSnapLine } from "@/preview/preview-snap"; import { CornerHandle, CircleHandle, @@ -22,7 +22,7 @@ import { Rotate01Icon, FeatherIcon } from "@hugeicons/core-free-icons"; export function MaskHandles({ onSnapLinesChange, }: { - onSnapLinesChange?: (lines: SnapLine[]) => void; + onSnapLinesChange?: (lines: PreviewSnapLine[]) => void; }) { const viewport = usePreviewViewport(); const { diff --git a/apps/web/src/preview/components/preview-interaction-overlay.tsx b/apps/web/src/preview/components/preview-interaction-overlay.tsx index e16a31d2..e103d406 100644 --- a/apps/web/src/preview/components/preview-interaction-overlay.tsx +++ b/apps/web/src/preview/components/preview-interaction-overlay.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import { usePreviewViewport } from "@/preview/components/preview-viewport"; 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 { MaskHandles } from "./mask-handles"; import { SnapGuides } from "./snap-guides"; @@ -10,7 +10,7 @@ import { usePropertiesStore } from "@/components/editor/panels/properties/stores import { useEditor } from "@/editor/use-editor"; export function PreviewInteractionOverlay() { - const [snapLines, setSnapLines] = useState([]); + const [snapLines, setSnapLines] = useState([]); const editor = useEditor(); const viewport = usePreviewViewport(); const selectedElements = useEditor((e) => e.selection.getSelectedElements()); diff --git a/apps/web/src/preview/components/snap-guides.tsx b/apps/web/src/preview/components/snap-guides.tsx index 5c2e1b9f..e8a685da 100644 --- a/apps/web/src/preview/components/snap-guides.tsx +++ b/apps/web/src/preview/components/snap-guides.tsx @@ -1,9 +1,9 @@ "use client"; 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(); if (lines.length === 0) { diff --git a/apps/web/src/preview/components/text-edit-overlay.tsx b/apps/web/src/preview/components/text-edit-overlay.tsx index 1d599b3b..7090b704 100644 --- a/apps/web/src/preview/components/text-edit-overlay.tsx +++ b/apps/web/src/preview/components/text-edit-overlay.tsx @@ -3,7 +3,7 @@ import { useCallback, useEffect, useRef } from "react"; import { usePreviewViewport } from "@/preview/components/preview-viewport"; import { useEditor } from "@/editor/use-editor"; -import type { TextElement } from "@/timeline"; +import type { TextElement } from "@/model"; import { DEFAULTS } from "@/timeline/defaults"; import { getElementLocalTime, diff --git a/apps/web/src/preview/controllers/preview-interaction-controller.ts b/apps/web/src/preview/controllers/preview-interaction-controller.ts index 00573662..394acbb1 100644 --- a/apps/web/src/preview/controllers/preview-interaction-controller.ts +++ b/apps/web/src/preview/controllers/preview-interaction-controller.ts @@ -15,19 +15,12 @@ import { import { SNAP_THRESHOLD_SCREEN_PIXELS, snapPosition, - type SnapLine, + type PreviewSnapLine, } from "@/preview/preview-snap"; import type { TCanvasSize } from "@/project/types"; import type { Transform } from "@/rendering"; import { isVisualElement } from "@/timeline/element-utils"; -import type { - ElementRef, - SceneTracks, - TextElement, - TimelineElement, - TimelineTrack, - VisualElement, -} from "@/timeline"; +import type { ElementRef, SceneTracks, TextElement, TimelineElement, TimelineTrack, VisualElement } from "@/model"; const MIN_DRAG_DISTANCE = 0.5; const PRIMARY_POINTER_BUTTON = 0; @@ -133,7 +126,7 @@ export interface PlaybackApi { export interface PreviewOptions { isMaskMode: () => boolean; - onSnapLinesChange?: (lines: SnapLine[]) => void; + onSnapLinesChange?: (lines: PreviewSnapLine[]) => void; } export interface PreviewInteractionDeps { @@ -554,7 +547,7 @@ export class PreviewInteractionController { }) : { snappedPosition: proposedPosition, - activeLines: [] as SnapLine[], + activeLines: [] as PreviewSnapLine[], }; this.deps.preview.onSnapLinesChange?.(activeLines); diff --git a/apps/web/src/preview/controllers/transform-handle-controller.ts b/apps/web/src/preview/controllers/transform-handle-controller.ts index 67f677f6..e4f6c197 100644 --- a/apps/web/src/preview/controllers/transform-handle-controller.ts +++ b/apps/web/src/preview/controllers/transform-handle-controller.ts @@ -14,7 +14,7 @@ import { snapScale, snapScaleAxes, type ScaleEdgePreference, - type SnapLine, + type PreviewSnapLine, } from "@/preview/preview-snap"; import { isVisualElement } from "@/timeline/element-utils"; import { @@ -25,12 +25,7 @@ import { import type { ElementAnimations } from "@/animation/types"; import type { Transform } from "@/rendering"; import { resolveTransformAtTime } from "@/rendering/animation-values"; -import type { - ElementRef, - SceneTracks, - TimelineElement, - VisualElement, -} from "@/timeline"; +import type { ElementRef, SceneTracks, TimelineElement, VisualElement } from "@/model"; type Point = { readonly x: number; readonly y: number }; type CanvasSize = { readonly width: number; readonly height: number }; @@ -137,7 +132,7 @@ export interface TimelineOps { } export interface PreviewOptions { - onSnapLinesChange?: (lines: SnapLine[]) => void; + onSnapLinesChange?: (lines: PreviewSnapLine[]) => void; } export interface TransformHandleDeps { @@ -590,7 +585,7 @@ export class TransformHandleController { screenPixels: SNAP_THRESHOLD_SCREEN_PIXELS, }); const { snappedScale, activeLines } = this.deps.input.isShiftHeld() - ? { snappedScale: scaleFactor, activeLines: [] as SnapLine[] } + ? { snappedScale: scaleFactor, activeLines: [] as PreviewSnapLine[] } : snapScale({ proposedScale: scaleFactor, position: session.initialTransform.position, @@ -670,12 +665,12 @@ export class TransformHandleController { x: { snappedScale: proposedScaleX, snapDistance: Infinity, - activeLines: [] as SnapLine[], + activeLines: [] as PreviewSnapLine[], }, y: { snappedScale: proposedScaleY, snapDistance: Infinity, - activeLines: [] as SnapLine[], + activeLines: [] as PreviewSnapLine[], }, } : snapScaleAxes({ diff --git a/apps/web/src/preview/element-bounds.ts b/apps/web/src/preview/element-bounds.ts index 0afc6e4e..d9289e0f 100644 --- a/apps/web/src/preview/element-bounds.ts +++ b/apps/web/src/preview/element-bounds.ts @@ -1,4 +1,4 @@ -import type { SceneTracks, TimelineElement } from "@/timeline"; +import type { ElementId, SceneTracks, TimelineElement } from "@/model"; import type { MediaAsset } from "@/media/types"; import { STICKER_INTRINSIC_SIZE_FALLBACK } from "@/stickers/intrinsic-size"; import { DEFAULT_GRAPHIC_SOURCE_SIZE } from "@/graphics"; @@ -18,7 +18,7 @@ export interface ElementBounds { export interface ElementWithBounds { trackId: string; - elementId: string; + elementId: ElementId; element: TimelineElement; bounds: ElementBounds; } diff --git a/apps/web/src/preview/hit-test.ts b/apps/web/src/preview/hit-test.ts index 0f5fd45e..62605219 100644 --- a/apps/web/src/preview/hit-test.ts +++ b/apps/web/src/preview/hit-test.ts @@ -1,5 +1,5 @@ import type { ElementWithBounds } from "./element-bounds"; -import type { ElementRef } from "@/timeline/types"; +import type { ElementRef } from "@/model"; function pointInRotatedRect({ px, diff --git a/apps/web/src/preview/hooks/use-preview-interaction.ts b/apps/web/src/preview/hooks/use-preview-interaction.ts index d093dcf2..8448a432 100644 --- a/apps/web/src/preview/hooks/use-preview-interaction.ts +++ b/apps/web/src/preview/hooks/use-preview-interaction.ts @@ -2,7 +2,7 @@ import { useEffect, useReducer, useRef } from "react"; import { useEditor } from "@/editor/use-editor"; import { useShiftKey } from "@/hooks/use-shift-key"; 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 { PreviewInteractionController, @@ -10,7 +10,7 @@ import { type PreviewInteractionDepsRef, } from "@/preview/controllers/preview-interaction-controller"; -export type OnSnapLinesChange = (lines: SnapLine[]) => void; +export type OnSnapLinesChange = (lines: PreviewSnapLine[]) => void; export function usePreviewInteraction({ onSnapLinesChange, diff --git a/apps/web/src/preview/preview-snap.ts b/apps/web/src/preview/preview-snap.ts index 86d35390..c414eee7 100644 --- a/apps/web/src/preview/preview-snap.ts +++ b/apps/web/src/preview/preview-snap.ts @@ -1,4 +1,4 @@ -export interface SnapLine { +export interface PreviewSnapLine { type: "horizontal" | "vertical"; position: number; } @@ -8,9 +8,9 @@ const ROTATION_SNAP_THRESHOLD_DEGREES = 5; export const MIN_SCALE = 0.01; export const SNAP_THRESHOLD_SCREEN_PIXELS = 8; -export interface SnapResult { +export interface PreviewSnapResult { snappedPosition: { x: number; y: number }; - activeLines: SnapLine[]; + activeLines: PreviewSnapLine[]; } type ScaleEdge = "left" | "right" | "top" | "bottom"; @@ -78,7 +78,7 @@ export function snapPosition({ elementSize: { width: number; height: number }; rotation?: number; snapThreshold: { x: number; y: number }; -}): SnapResult { +}): PreviewSnapResult { const centerX = 0; const centerY = 0; const left = -canvasSize.width / 2; @@ -91,11 +91,11 @@ export function snapPosition({ const sinR = Math.abs(Math.sin(rotRad)); const halfWidth = (elementSize.width * cosR + elementSize.height * sinR) / 2; const halfHeight = (elementSize.width * sinR + elementSize.height * cosR) / 2; - const activeLines: SnapLine[] = []; + const activeLines: PreviewSnapLine[] = []; type AxisSnapCandidate = { snappedPosition: number; - line: SnapLine; + line: PreviewSnapLine; distance: number; }; @@ -183,7 +183,7 @@ export function snapPosition({ export interface ScaleSnapResult { snappedScale: number; - activeLines: SnapLine[]; + activeLines: PreviewSnapLine[]; } export function snapScale({ @@ -226,7 +226,7 @@ export function snapScale({ interface SnapCandidate { scale: number; distance: number; - lines: SnapLine[]; + lines: PreviewSnapLine[]; edge: ScaleEdge; } @@ -320,10 +320,10 @@ export function snapScale({ const snappedTop = position.y - aabbBaseHalfH * best.scale; const snappedBottom = position.y + aabbBaseHalfH * best.scale; - const activeLines: SnapLine[] = []; + const activeLines: PreviewSnapLine[] = []; const seenKeys = new Set(); - function addLine({ line }: { line: SnapLine }) { + function addLine({ line }: { line: PreviewSnapLine }) { const key = `${line.type}-${line.position}`; if (!seenKeys.has(key)) { seenKeys.add(key); @@ -368,7 +368,7 @@ export interface AxisSnapResult { snappedScale: number; /** Infinity when no snap candidate was within threshold */ snapDistance: number; - activeLines: SnapLine[]; + activeLines: PreviewSnapLine[]; } export function snapScaleAxes({ @@ -413,7 +413,7 @@ export function snapScaleAxes({ interface Candidate { scale: number; distance: number; - line: SnapLine; + line: PreviewSnapLine; edge: ScaleEdge; } @@ -442,7 +442,7 @@ export function snapScaleAxes({ if (cosR > EPSILON) { 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); if (distLeft <= snapThreshold.x) { const scale = (2 * (position.x - T) - yContribW) / (baseWidth * cosR); @@ -458,7 +458,7 @@ export function snapScaleAxes({ if (sinR > EPSILON) { 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); if (distTop <= snapThreshold.y) { const scale = (2 * (position.y - T) - yContribH) / (baseWidth * sinR); @@ -480,7 +480,7 @@ export function snapScaleAxes({ if (sinR > EPSILON) { 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); if (distLeft <= snapThreshold.x) { const scale = (2 * (position.x - T) - xContribW) / (baseHeight * sinR); @@ -496,7 +496,7 @@ export function snapScaleAxes({ if (cosR > EPSILON) { 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); if (distTop <= snapThreshold.y) { const scale = (2 * (position.y - T) - xContribH) / (baseHeight * cosR); diff --git a/apps/web/src/project/types.ts b/apps/web/src/project/types.ts index 42e844f6..042bb030 100644 --- a/apps/web/src/project/types.ts +++ b/apps/web/src/project/types.ts @@ -1,5 +1,5 @@ import type { FrameRate } from "opencut-wasm"; -import type { TScene } from "@/timeline/types"; +import type { TScene } from "@/model"; export type TBackground = | { diff --git a/apps/web/src/rendering/components/blending-tab.tsx b/apps/web/src/rendering/components/blending-tab.tsx index 48b1d0e5..a03c7050 100644 --- a/apps/web/src/rendering/components/blending-tab.tsx +++ b/apps/web/src/rendering/components/blending-tab.tsx @@ -20,7 +20,7 @@ import { SelectValue, } from "@/components/ui/select"; import type { BlendMode } from "@/rendering"; -import type { ElementType } from "@/timeline"; +import type { ElementType } from "@/model"; import type { ElementAnimations } from "@/animation/types"; import { HugeiconsIcon } from "@hugeicons/react"; import { RainDropIcon } from "@hugeicons/core-free-icons"; diff --git a/apps/web/src/rendering/components/transform-tab.tsx b/apps/web/src/rendering/components/transform-tab.tsx index f4d5a183..5e606fa9 100644 --- a/apps/web/src/rendering/components/transform-tab.tsx +++ b/apps/web/src/rendering/components/transform-tab.tsx @@ -1,7 +1,7 @@ import { NumberField } from "@/components/ui/number-field"; import { useEditor } from "@/editor/use-editor"; import { clamp, isNearlyEqual } from "@/utils/math"; -import type { VisualElement } from "@/timeline"; +import type { VisualElement } from "@/model"; import { Section, SectionContent, diff --git a/apps/web/src/retime/__tests__/resolve.test.ts b/apps/web/src/retime/__tests__/resolve.test.ts index bb7b9a6d..4464a38a 100644 --- a/apps/web/src/retime/__tests__/resolve.test.ts +++ b/apps/web/src/retime/__tests__/resolve.test.ts @@ -5,7 +5,7 @@ import { getSourceTimeAtClipTime, getTimelineDurationForSourceSpan, } from "@/retime"; -import type { RetimeConfig } from "@/timeline"; +import type { RetimeConfig } from "@/model"; const twoX: RetimeConfig = { rate: 2 }; const halfX: RetimeConfig = { rate: 0.5 }; diff --git a/apps/web/src/retime/__tests__/split.test.ts b/apps/web/src/retime/__tests__/split.test.ts index 3c7bef63..9b913ba4 100644 --- a/apps/web/src/retime/__tests__/split.test.ts +++ b/apps/web/src/retime/__tests__/split.test.ts @@ -3,7 +3,7 @@ import { getSourceSpanAtClipTime, splitRetimeAtClipTime, } from "@/retime"; -import type { RetimeConfig } from "@/timeline"; +import type { RetimeConfig } from "@/model"; describe("retime split", () => { test("measures source span at a clip time", () => { diff --git a/apps/web/src/retime/audio-stretch.ts b/apps/web/src/retime/audio-stretch.ts index 7df67f36..d63d16a0 100644 --- a/apps/web/src/retime/audio-stretch.ts +++ b/apps/web/src/retime/audio-stretch.ts @@ -1,6 +1,6 @@ import { PitchShifter } from "soundtouchjs"; import { clampRetimeRate, shouldMaintainPitch } from "@/retime/rate"; -import type { RetimeConfig } from "@/timeline"; +import type { RetimeConfig } from "@/model"; import { getSourceTimeAtClipTime } from "./resolve"; const RATE_EPSILON = 1e-6; diff --git a/apps/web/src/retime/presets.ts b/apps/web/src/retime/presets.ts index 816c8675..060644e1 100644 --- a/apps/web/src/retime/presets.ts +++ b/apps/web/src/retime/presets.ts @@ -1,4 +1,4 @@ -import type { RetimeConfig } from "@/timeline"; +import type { RetimeConfig } from "@/model"; import { clampRetimeRate } from "@/retime/rate"; export function buildConstantRetime({ diff --git a/apps/web/src/retime/resolve.ts b/apps/web/src/retime/resolve.ts index 0dea9dd8..28e380f6 100644 --- a/apps/web/src/retime/resolve.ts +++ b/apps/web/src/retime/resolve.ts @@ -1,4 +1,4 @@ -import type { RetimeConfig } from "@/timeline"; +import type { RetimeConfig } from "@/model"; import { clampRetimeRate } from "@/retime/rate"; function getSafeRate({ rate }: { rate: number }): number { diff --git a/apps/web/src/retime/split.ts b/apps/web/src/retime/split.ts index 1b7360cc..5c34312f 100644 --- a/apps/web/src/retime/split.ts +++ b/apps/web/src/retime/split.ts @@ -1,4 +1,4 @@ -import type { RetimeConfig } from "@/timeline"; +import type { RetimeConfig } from "@/model"; import { getSourceTimeAtClipTime } from "./resolve"; export function getSourceSpanAtClipTime({ diff --git a/apps/web/src/ripple/apply.ts b/apps/web/src/ripple/apply.ts index 4ad83529..d2077c27 100644 --- a/apps/web/src/ripple/apply.ts +++ b/apps/web/src/ripple/apply.ts @@ -1,4 +1,4 @@ -import type { SceneTracks, TimelineTrack } from "@/timeline/types"; +import type { SceneTracks, TimelineTrack } from "@/model"; import { rippleShiftElements } from "./shift"; export interface RippleAdjustment { diff --git a/apps/web/src/ripple/diff.ts b/apps/web/src/ripple/diff.ts index e450d5ae..cebe6608 100644 --- a/apps/web/src/ripple/diff.ts +++ b/apps/web/src/ripple/diff.ts @@ -1,4 +1,4 @@ -import type { SceneTracks, TimelineElement, TimelineTrack } from "@/timeline/types"; +import type { SceneTracks, TimelineElement, TimelineTrack } from "@/model"; import type { RippleAdjustment } from "./apply"; interface Interval { diff --git a/apps/web/src/ripple/shift.ts b/apps/web/src/ripple/shift.ts index a88d2b2d..028396b2 100644 --- a/apps/web/src/ripple/shift.ts +++ b/apps/web/src/ripple/shift.ts @@ -1,4 +1,4 @@ -import type { TimelineElement } from "@/timeline/types"; +import type { TimelineElement } from "@/model"; export function rippleShiftElements({ elements, diff --git a/apps/web/src/selection/editor-selection.ts b/apps/web/src/selection/editor-selection.ts index 21ab6466..7c7e67ce 100644 --- a/apps/web/src/selection/editor-selection.ts +++ b/apps/web/src/selection/editor-selection.ts @@ -1,5 +1,5 @@ import type { SelectedKeyframeRef } from "@/animation/types"; -import type { ElementRef } from "@/timeline/types"; +import type { ElementRef } from "@/model"; export interface SelectedMaskPointSelection { trackId: string; diff --git a/apps/web/src/services/renderer/nodes/blur-background-node.ts b/apps/web/src/services/renderer/nodes/blur-background-node.ts index d83c1cf5..d773229b 100644 --- a/apps/web/src/services/renderer/nodes/blur-background-node.ts +++ b/apps/web/src/services/renderer/nodes/blur-background-node.ts @@ -1,5 +1,5 @@ import type { EffectPass } from "@/effects/types"; -import type { RetimeConfig } from "@/timeline"; +import type { RetimeConfig } from "@/model"; import { BaseNode } from "./base-node"; export type BlurBackgroundNodeParams = { diff --git a/apps/web/src/services/renderer/nodes/text-node.ts b/apps/web/src/services/renderer/nodes/text-node.ts index 7bdf135c..2e12f5d4 100644 --- a/apps/web/src/services/renderer/nodes/text-node.ts +++ b/apps/web/src/services/renderer/nodes/text-node.ts @@ -1,5 +1,5 @@ import { BaseNode } from "./base-node"; -import type { TextElement } from "@/timeline"; +import type { TextElement } from "@/model"; import type { EffectPass } from "@/effects/types"; import type { Transform } from "@/rendering"; import { drawMeasuredTextLayout } from "@/text/primitives"; diff --git a/apps/web/src/services/renderer/nodes/visual-node.ts b/apps/web/src/services/renderer/nodes/visual-node.ts index b9bf1709..8bc3ba61 100644 --- a/apps/web/src/services/renderer/nodes/visual-node.ts +++ b/apps/web/src/services/renderer/nodes/visual-node.ts @@ -2,7 +2,7 @@ import { BaseNode } from "./base-node"; import type { Effect, EffectPass } from "@/effects/types"; import type { Mask } from "@/masks/types"; import type { BlendMode, Transform } from "@/rendering"; -import type { RetimeConfig, VisualElement } from "@/timeline"; +import type { RetimeConfig, VisualElement } from "@/model"; export interface VisualNodeParams { duration: number; diff --git a/apps/web/src/services/renderer/scene-builder.ts b/apps/web/src/services/renderer/scene-builder.ts index e79c62d4..8f65a7a3 100644 --- a/apps/web/src/services/renderer/scene-builder.ts +++ b/apps/web/src/services/renderer/scene-builder.ts @@ -1,4 +1,4 @@ -import type { SceneTracks, TimelineTrack } from "@/timeline"; +import type { SceneTracks, TimelineTrack } from "@/model"; import type { MediaAsset } from "@/media/types"; import { RootNode } from "./nodes/root-node"; import { VideoNode } from "./nodes/video-node"; diff --git a/apps/web/src/services/storage/service.ts b/apps/web/src/services/storage/service.ts index 858198eb..f5e94974 100644 --- a/apps/web/src/services/storage/service.ts +++ b/apps/web/src/services/storage/service.ts @@ -21,7 +21,7 @@ import { migrations, runStorageMigrations, } from "@/services/storage/migrations"; -import type { Bookmark, SceneTracks, TScene } from "@/timeline"; +import type { Bookmark, SceneTracks, TScene } from "@/model"; function normalizeBookmarks({ raw }: { raw: unknown }): Bookmark[] { if (!Array.isArray(raw)) return []; diff --git a/apps/web/src/services/storage/types.ts b/apps/web/src/services/storage/types.ts index 58c14b49..278b5f74 100644 --- a/apps/web/src/services/storage/types.ts +++ b/apps/web/src/services/storage/types.ts @@ -4,7 +4,7 @@ import type { TProjectMetadata, TTimelineViewState, } from "@/project/types"; -import type { TScene } from "@/timeline"; +import type { TScene } from "@/model"; export interface StorageAdapter { get(key: string): Promise; diff --git a/apps/web/src/speed/components/speed-tab.tsx b/apps/web/src/speed/components/speed-tab.tsx index 2f9231d6..2e435253 100644 --- a/apps/web/src/speed/components/speed-tab.tsx +++ b/apps/web/src/speed/components/speed-tab.tsx @@ -12,7 +12,7 @@ import { clampRetimeRate, canMaintainPitch, } from "@/retime/rate"; -import type { AudioElement, VideoElement } from "@/timeline"; +import type { AudioElement, VideoElement } from "@/model"; import { Section, SectionContent, diff --git a/apps/web/src/subtitles/build-subtitle-text-element.ts b/apps/web/src/subtitles/build-subtitle-text-element.ts index c4ac21ac..3857e9bd 100644 --- a/apps/web/src/subtitles/build-subtitle-text-element.ts +++ b/apps/web/src/subtitles/build-subtitle-text-element.ts @@ -6,7 +6,7 @@ import { } from "@/text/layout"; import { DEFAULTS } from "@/timeline/defaults"; import { TICKS_PER_SECOND } from "@/wasm"; -import type { CreateTextElement } from "@/timeline"; +import type { CreateTextElement } from "@/model"; import type { SubtitleCue, SubtitleStyleOverrides } from "./types"; const SUBTITLE_MAX_WIDTH_RATIO = 0.8; diff --git a/apps/web/src/subtitles/types.ts b/apps/web/src/subtitles/types.ts index e4f5db3c..d76273a4 100644 --- a/apps/web/src/subtitles/types.ts +++ b/apps/web/src/subtitles/types.ts @@ -1,4 +1,4 @@ -import type { TextBackground, TextElement } from "@/timeline"; +import type { TextBackground, TextElement } from "@/model"; import type { CaptionChunk } from "@/transcription/types"; export interface SubtitlePlacementStyle { diff --git a/apps/web/src/text/components/text-tab.tsx b/apps/web/src/text/components/text-tab.tsx index 0f0b274a..745b03bb 100644 --- a/apps/web/src/text/components/text-tab.tsx +++ b/apps/web/src/text/components/text-tab.tsx @@ -2,7 +2,7 @@ import { Textarea } from "@/components/ui/textarea"; import { FontPicker } from "@/components/ui/font-picker"; -import type { TextElement } from "@/timeline"; +import type { TextElement } from "@/model"; import { NumberField } from "@/components/ui/number-field"; import { useRef } from "react"; import { diff --git a/apps/web/src/text/layout.ts b/apps/web/src/text/layout.ts index aabf5ca9..ba429575 100644 --- a/apps/web/src/text/layout.ts +++ b/apps/web/src/text/layout.ts @@ -1,4 +1,4 @@ -import type { TextBackground, TextElement } from "@/timeline"; +import type { TextBackground, TextElement } from "@/model"; import { DEFAULTS } from "@/timeline/defaults"; type TextRect = { diff --git a/apps/web/src/text/measure-element.ts b/apps/web/src/text/measure-element.ts index ec14421b..54651776 100644 --- a/apps/web/src/text/measure-element.ts +++ b/apps/web/src/text/measure-element.ts @@ -1,6 +1,6 @@ import { CORNER_RADIUS_MIN } from "@/text/background"; import { DEFAULTS } from "@/timeline/defaults"; -import type { TextBackground, TextElement } from "@/timeline"; +import type { TextBackground, TextElement } from "@/model"; import { resolveNumberAtTime } from "@/animation/values"; import { getTextVisualRect, diff --git a/apps/web/src/timeline/animation-properties.ts b/apps/web/src/timeline/animation-properties.ts index 31479a58..90d4423a 100644 --- a/apps/web/src/timeline/animation-properties.ts +++ b/apps/web/src/timeline/animation-properties.ts @@ -11,7 +11,7 @@ import { parseColorToLinearRgba } from "@/animation/binding-values"; import { isAnimationPropertyPath } from "@/animation/path"; import { MIN_TRANSFORM_SCALE } from "@/animation/transform"; import { snapToStep } from "@/utils/math"; -import type { TimelineElement } from "@/timeline"; +import type { TimelineElement } from "@/model"; import { CORNER_RADIUS_MAX, CORNER_RADIUS_MIN, diff --git a/apps/web/src/timeline/animation-snap-points.ts b/apps/web/src/timeline/animation-snap-points.ts index f7bf549a..349c8702 100644 --- a/apps/web/src/timeline/animation-snap-points.ts +++ b/apps/web/src/timeline/animation-snap-points.ts @@ -1,6 +1,6 @@ import { getElementKeyframes } from "@/animation"; -import type { SceneTracks } from "@/timeline"; -import type { SnapPoint } from "@/timeline/snapping"; +import type { SceneTracks } from "@/model"; +import type { TimelineSnapPoint } from "@/timeline/snapping"; export function getAnimationKeyframeSnapPointsForTimeline({ tracks, @@ -8,8 +8,8 @@ export function getAnimationKeyframeSnapPointsForTimeline({ }: { tracks: SceneTracks; excludeElementIds?: Set; -}): SnapPoint[] { - const snapPoints: SnapPoint[] = []; +}): TimelineSnapPoint[] { + const snapPoints: TimelineSnapPoint[] = []; const orderedTracks = [...tracks.overlay, tracks.main, ...tracks.audio]; for (const track of orderedTracks) { diff --git a/apps/web/src/timeline/animation-targets.ts b/apps/web/src/timeline/animation-targets.ts index f9d53fcc..5c94e331 100644 --- a/apps/web/src/timeline/animation-targets.ts +++ b/apps/web/src/timeline/animation-targets.ts @@ -20,7 +20,7 @@ import { import { effectsRegistry, registerDefaultEffects } from "@/effects"; import { getGraphicDefinition } from "@/graphics"; import type { ParamDefinition } from "@/params"; -import type { TimelineElement } from "@/timeline"; +import type { TimelineElement } from "@/model"; import { isVisualElement } from "@/timeline/element-utils"; import { isAnimationPropertyPath } from "@/animation/path"; import { diff --git a/apps/web/src/timeline/audio-separation/index.ts b/apps/web/src/timeline/audio-separation/index.ts index beb488de..465a418f 100644 --- a/apps/web/src/timeline/audio-separation/index.ts +++ b/apps/web/src/timeline/audio-separation/index.ts @@ -2,12 +2,7 @@ import { cloneAnimations } from "@/animation"; import type { ElementAnimations } from "@/animation/types"; import type { MediaAsset } from "@/media/types"; import { DEFAULTS } from "@/timeline/defaults"; -import type { - CreateUploadAudioElement, - TimelineElement, - AudioElement, - VideoElement, -} from "../types"; +import type { AudioElement, CreateUploadAudioElement, TimelineElement, VideoElement } from "@/model"; type MediaAudioState = Pick; diff --git a/apps/web/src/timeline/audio-state.ts b/apps/web/src/timeline/audio-state.ts index 44df5aaf..33bd55e7 100644 --- a/apps/web/src/timeline/audio-state.ts +++ b/apps/web/src/timeline/audio-state.ts @@ -1,7 +1,7 @@ import { hasKeyframesForPath } from "@/animation/keyframe-query"; import { resolveNumberAtTime } from "@/animation/values"; import { VOLUME_DB_MAX, VOLUME_DB_MIN } from "./audio-constants"; -import type { TimelineElement } from "./types"; +import type { TimelineElement } from "@/model"; const DEFAULT_STEP_SECONDS = 1 / 60; export type AudioCapableElement = Extract< diff --git a/apps/web/src/timeline/bookmarks/components/bookmarks.tsx b/apps/web/src/timeline/bookmarks/components/bookmarks.tsx index 20e22c57..5f6fb199 100644 --- a/apps/web/src/timeline/bookmarks/components/bookmarks.tsx +++ b/apps/web/src/timeline/bookmarks/components/bookmarks.tsx @@ -13,7 +13,7 @@ import { Delete02Icon, } from "@hugeicons/core-free-icons"; import { HugeiconsIcon } from "@hugeicons/react"; -import type { Bookmark } from "@/timeline"; +import type { Bookmark } from "@/model"; import { Popover, PopoverAnchor, diff --git a/apps/web/src/timeline/bookmarks/hooks/use-bookmark-drag.ts b/apps/web/src/timeline/bookmarks/hooks/use-bookmark-drag.ts index 35ee9cca..5ab7d18d 100644 --- a/apps/web/src/timeline/bookmarks/hooks/use-bookmark-drag.ts +++ b/apps/web/src/timeline/bookmarks/hooks/use-bookmark-drag.ts @@ -14,13 +14,13 @@ import { buildTimelineSnapPoints, getTimelineSnapThresholdInTicks, resolveTimelineSnap, - type SnapPoint, + type TimelineSnapPoint, } from "@/timeline/snapping"; import { getBookmarkSnapPoints } from "../snap-source"; import { getElementEdgeSnapPoints } from "@/timeline/element-snap-source"; import { getPlayheadSnapPoints } from "@/timeline/playhead-snap-source"; import { getAnimationKeyframeSnapPointsForTimeline } from "@/timeline/animation-snap-points"; -import type { Bookmark } from "@/timeline"; +import type { Bookmark } from "@/model"; export interface BookmarkDragState { isDragging: boolean; @@ -38,7 +38,7 @@ interface UseBookmarkDragProps { zoomLevel: number; scrollRef: RefObject; snappingEnabled: boolean; - onSnapPointChange?: (snapPoint: SnapPoint | null) => void; + onSnapPointChange?: (snapPoint: TimelineSnapPoint | null) => void; } export function useBookmarkDrag({ @@ -96,7 +96,7 @@ export function useBookmarkDrag({ }: { rawTime: number; excludeBookmarkTime: number; - }): { snappedTime: number; snapPoint: SnapPoint | null } => { + }): { snappedTime: number; snapPoint: TimelineSnapPoint | null } => { const shouldSnap = snappingEnabled && !isShiftHeldRef.current; if (!shouldSnap) { return { snappedTime: rawTime, snapPoint: null }; diff --git a/apps/web/src/timeline/bookmarks/preview-overlay-source.tsx b/apps/web/src/timeline/bookmarks/preview-overlay-source.tsx index 8e2b4bcf..8d8c92ed 100644 --- a/apps/web/src/timeline/bookmarks/preview-overlay-source.tsx +++ b/apps/web/src/timeline/bookmarks/preview-overlay-source.tsx @@ -1,4 +1,4 @@ -import type { Bookmark } from "@/timeline"; +import type { Bookmark } from "@/model"; import { EMPTY_PREVIEW_OVERLAY_SOURCE_RESULT, type PreviewOverlayDefinition, diff --git a/apps/web/src/timeline/bookmarks/snap-source.ts b/apps/web/src/timeline/bookmarks/snap-source.ts index 32a84baa..69e17978 100644 --- a/apps/web/src/timeline/bookmarks/snap-source.ts +++ b/apps/web/src/timeline/bookmarks/snap-source.ts @@ -1,5 +1,5 @@ -import type { Bookmark } from "@/timeline"; -import type { SnapPoint } from "@/timeline/snapping"; +import type { Bookmark } from "@/model"; +import type { TimelineSnapPoint } from "@/timeline/snapping"; export function getBookmarkSnapPoints({ bookmarks, @@ -7,14 +7,14 @@ export function getBookmarkSnapPoints({ }: { bookmarks: Bookmark[]; excludeBookmarkTime?: number; -}): SnapPoint[] { +}): TimelineSnapPoint[] { return bookmarks.flatMap((bookmark) => { if (excludeBookmarkTime != null && bookmark.time === excludeBookmarkTime) { return []; } return [ - { time: bookmark.time, type: "bookmark" satisfies SnapPoint["type"] }, + { time: bookmark.time, type: "bookmark" satisfies TimelineSnapPoint["type"] }, ]; }); } diff --git a/apps/web/src/timeline/bookmarks/utils.ts b/apps/web/src/timeline/bookmarks/utils.ts index d05fe3a9..909cad31 100644 --- a/apps/web/src/timeline/bookmarks/utils.ts +++ b/apps/web/src/timeline/bookmarks/utils.ts @@ -1,4 +1,4 @@ -import type { Bookmark } from "@/timeline"; +import type { Bookmark } from "@/model"; import type { FrameRate } from "opencut-wasm"; import { roundToFrame } from "opencut-wasm"; diff --git a/apps/web/src/timeline/components/audio-tab.tsx b/apps/web/src/timeline/components/audio-tab.tsx index 1e47dcc8..35d6f513 100644 --- a/apps/web/src/timeline/components/audio-tab.tsx +++ b/apps/web/src/timeline/components/audio-tab.tsx @@ -10,7 +10,7 @@ import { isNearlyEqual, snapToStep, } from "@/utils/math"; -import type { AudioElement, VideoElement } from "@/timeline"; +import type { AudioElement, VideoElement } from "@/model"; import { resolveNumberAtTime } from "@/animation/values"; import { useEditor } from "@/editor/use-editor"; import { useElementPlayhead } from "@/components/editor/panels/properties/hooks/use-element-playhead"; diff --git a/apps/web/src/timeline/components/audio-volume-line.tsx b/apps/web/src/timeline/components/audio-volume-line.tsx index 1baac2bd..8c396e22 100644 --- a/apps/web/src/timeline/components/audio-volume-line.tsx +++ b/apps/web/src/timeline/components/audio-volume-line.tsx @@ -10,7 +10,7 @@ import { } from "@/timeline/audio-display"; import { VOLUME_DB_MAX, VOLUME_DB_MIN } from "@/timeline/audio-constants"; import { hasAnimatedVolume } from "@/timeline/audio-state"; -import type { AudioElement } from "@/timeline/types"; +import type { AudioElement } from "@/model"; import { clamp, formatNumberForDisplay, diff --git a/apps/web/src/timeline/components/audio-waveform.tsx b/apps/web/src/timeline/components/audio-waveform.tsx index 7777bc09..e86ea1d2 100644 --- a/apps/web/src/timeline/components/audio-waveform.tsx +++ b/apps/web/src/timeline/components/audio-waveform.tsx @@ -8,7 +8,7 @@ import { sampleSourceWaveformSummary, type SourceWaveformSummary, } from "@/media/waveform-summary"; -import type { RetimeConfig } from "@/timeline"; +import type { RetimeConfig } from "@/model"; import { getBarFractionFromOutputAmplitude } from "@/timeline/audio-display"; import { waveformCache } from "@/services/waveform-cache/service"; import { findScrollParent } from "@/utils/browser"; diff --git a/apps/web/src/timeline/components/drag-line.tsx b/apps/web/src/timeline/components/drag-line.tsx index 49621f5c..ce29d034 100644 --- a/apps/web/src/timeline/components/drag-line.tsx +++ b/apps/web/src/timeline/components/drag-line.tsx @@ -1,5 +1,6 @@ import { getDropLineY } from "./drop-target"; -import type { TimelineTrack, DropTarget } from "@/timeline"; +import type { TimelineTrack } from "@/model"; +import type { DropTarget } from "@/timeline"; import { TIMELINE_LAYERS } from "./layers"; interface DragLineProps { diff --git a/apps/web/src/timeline/components/drop-target.ts b/apps/web/src/timeline/components/drop-target.ts index be2495e4..9ef582b0 100644 --- a/apps/web/src/timeline/components/drop-target.ts +++ b/apps/web/src/timeline/components/drop-target.ts @@ -1,4 +1,4 @@ -import type { TimelineTrack, TimelineElement } from "@/timeline"; +import type { TimelineElement, TimelineTrack } from "@/model"; import type { ComputeDropTargetParams, DropTarget } from "@/timeline"; import { resolveTrackPlacement } from "@/timeline/placement"; import { TIMELINE_TRACK_GAP_PX } from "./layout"; diff --git a/apps/web/src/timeline/components/expanded-layout.ts b/apps/web/src/timeline/components/expanded-layout.ts index fae0e50c..999a0a0d 100644 --- a/apps/web/src/timeline/components/expanded-layout.ts +++ b/apps/web/src/timeline/components/expanded-layout.ts @@ -2,7 +2,7 @@ import type { AnimationPath, ElementAnimations, } from "@/animation/types"; -import type { TimelineTrack } from "@/timeline"; +import type { TimelineTrack } from "@/model"; import { getElementKeyframes } from "@/animation"; import { KEYFRAME_LANE_HEIGHT_PX } from "./layout"; diff --git a/apps/web/src/timeline/components/graph-editor/session.ts b/apps/web/src/timeline/components/graph-editor/session.ts index d7c6b001..74100182 100644 --- a/apps/web/src/timeline/components/graph-editor/session.ts +++ b/apps/web/src/timeline/components/graph-editor/session.ts @@ -14,7 +14,7 @@ import type { ScalarGraphKeyframeContext, SelectedKeyframeRef, } from "@/animation/types"; -import type { SceneTracks, TimelineElement } from "@/timeline"; +import type { SceneTracks, TimelineElement } from "@/model"; const GRAPH_LINEAR_CURVE: NormalizedCubicBezier = [0, 0, 1, 1]; const FLAT_VALUE_EPSILON = 1e-6; diff --git a/apps/web/src/timeline/components/index.tsx b/apps/web/src/timeline/components/index.tsx index 88fb93b7..e53be8c0 100644 --- a/apps/web/src/timeline/components/index.tsx +++ b/apps/web/src/timeline/components/index.tsx @@ -35,8 +35,8 @@ import { TimelinePlayhead } from "./timeline-playhead"; import { SelectionBox } from "@/selection/selection-box"; import { useBoxSelect } from "@/selection/hooks/use-box-select"; import { SnapIndicator } from "./snap-indicator"; -import type { SnapPoint } from "@/timeline/snapping"; -import type { TimelineTrack } from "@/timeline"; +import type { TimelineSnapPoint } from "@/timeline/snapping"; +import type { TimelineTrack } from "@/model"; import { TIMELINE_SCROLLBAR_SIZE_PX, TIMELINE_CONTENT_TOP_PADDING_PX, @@ -144,11 +144,11 @@ export function Timeline() { const playheadRef = useRef(null); const trackLabelsScrollRef = useRef(null); - const [currentSnapPoint, setCurrentSnapPoint] = useState( + const [currentSnapPoint, setCurrentSnapPoint] = useState( null, ); - const handleSnapPointChange = useCallback((snapPoint: SnapPoint | null) => { + const handleSnapPointChange = useCallback((snapPoint: TimelineSnapPoint | null) => { setCurrentSnapPoint(snapPoint); }, []); diff --git a/apps/web/src/timeline/components/layout.ts b/apps/web/src/timeline/components/layout.ts index 57a7f5df..da1d6bde 100644 --- a/apps/web/src/timeline/components/layout.ts +++ b/apps/web/src/timeline/components/layout.ts @@ -1,4 +1,4 @@ -import type { TrackType } from "@/timeline"; +import type { TrackType } from "@/model"; export const TIMELINE_TRACK_HEIGHTS_PX: Record = { video: 65, diff --git a/apps/web/src/timeline/components/selection-hit-testing.ts b/apps/web/src/timeline/components/selection-hit-testing.ts index da5617c6..969e4ca2 100644 --- a/apps/web/src/timeline/components/selection-hit-testing.ts +++ b/apps/web/src/timeline/components/selection-hit-testing.ts @@ -1,12 +1,10 @@ -import type { TimelineTrack } from "@/timeline"; +import type { ElementRef, TimelineTrack } from "@/model"; import { timelineTimeToPixels } from "@/timeline/pixel-utils"; import { TIMELINE_CONTENT_TOP_PADDING_PX, } from "./layout"; import { getCumulativeHeightBefore, getTrackHeight } from "./track-layout"; -type TimelineElementRef = { trackId: string; elementId: string }; - interface SelectionRectangle { left: number; top: number; @@ -89,14 +87,14 @@ export function resolveTimelineElementIntersections({ zoomLevel: number; startPos: { x: number; y: number }; currentPos: { x: number; y: number }; -}): TimelineElementRef[] { +}): ElementRef[] { const selectionRectangle = getSelectionRectangleInContent({ container, scrollContainer, startPos, endPos: currentPos, }); - const selectedElements: TimelineElementRef[] = []; + const selectedElements: ElementRef[] = []; for (const [trackIndex, track] of tracks.entries()) { const trackTop = getCumulativeHeightBefore({ diff --git a/apps/web/src/timeline/components/snap-indicator.tsx b/apps/web/src/timeline/components/snap-indicator.tsx index 6c1dbf1a..a456bf26 100644 --- a/apps/web/src/timeline/components/snap-indicator.tsx +++ b/apps/web/src/timeline/components/snap-indicator.tsx @@ -1,14 +1,14 @@ "use client"; import { useSnapIndicatorPosition } from "@/timeline/hooks/use-snap-indicator-position"; -import type { SnapPoint } from "@/timeline/snapping"; +import type { TimelineSnapPoint } from "@/timeline/snapping"; import { getCenteredLineLeft, TIMELINE_INDICATOR_LINE_WIDTH_PX, } from "@/timeline"; import { TIMELINE_LAYERS } from "./layers"; interface SnapIndicatorProps { - snapPoint: SnapPoint | null; + snapPoint: TimelineSnapPoint | null; zoomLevel: number; isVisible: boolean; timelineRef: React.RefObject; diff --git a/apps/web/src/timeline/components/theme.ts b/apps/web/src/timeline/components/theme.ts index 9f93dc16..9a4bdb17 100644 --- a/apps/web/src/timeline/components/theme.ts +++ b/apps/web/src/timeline/components/theme.ts @@ -1,4 +1,4 @@ -import type { TrackType } from "@/timeline"; +import type { TrackType } from "@/model"; export const TIMELINE_AUDIO_WAVEFORM_COLOR = "rgba(255, 255, 255, 0.7)"; diff --git a/apps/web/src/timeline/components/timeline-element.tsx b/apps/web/src/timeline/components/timeline-element.tsx index 35785332..7b6d9e68 100644 --- a/apps/web/src/timeline/components/timeline-element.tsx +++ b/apps/web/src/timeline/components/timeline-element.tsx @@ -31,14 +31,8 @@ import { ContextMenuSeparator, ContextMenuTrigger, } from "@/components/ui/context-menu"; -import type { - TimelineElement as TimelineElementType, - TimelineTrack, - ElementDragView, - VideoElement, - ImageElement, - AudioElement, -} from "@/timeline"; +import type { AudioElement, ImageElement, TimelineElement as TimelineElementType, TimelineTrack, VideoElement } from "@/model"; +import type { ElementDragView } from "@/timeline"; import type { MediaAsset } from "@/media/types"; import { mediaSupportsAudio } from "@/media/media-utils"; import { diff --git a/apps/web/src/timeline/components/timeline-track.tsx b/apps/web/src/timeline/components/timeline-track.tsx index 7608e6df..abf4069e 100644 --- a/apps/web/src/timeline/components/timeline-track.tsx +++ b/apps/web/src/timeline/components/timeline-track.tsx @@ -2,8 +2,8 @@ import { useElementSelection } from "@/timeline/hooks/element/use-element-selection"; import { TimelineElement } from "./timeline-element"; -import type { TimelineTrack } from "@/timeline"; -import type { TimelineElement as TimelineElementType } from "@/timeline"; +import type { TimelineTrack } from "@/model"; +import type { TimelineElement as TimelineElementType } from "@/model"; import { TIMELINE_LAYERS } from "./layers"; import type { ElementDragView } from "@/timeline"; diff --git a/apps/web/src/timeline/components/track-layout.ts b/apps/web/src/timeline/components/track-layout.ts index 59f4a5f9..b1df50fa 100644 --- a/apps/web/src/timeline/components/track-layout.ts +++ b/apps/web/src/timeline/components/track-layout.ts @@ -1,4 +1,4 @@ -import type { TrackType } from "@/timeline"; +import type { TrackType } from "@/model"; import { KEYFRAME_LANE_HEIGHT_PX, TIMELINE_TRACK_GAP_PX, diff --git a/apps/web/src/timeline/controllers/drag-drop-controller.ts b/apps/web/src/timeline/controllers/drag-drop-controller.ts index cd03300e..e037190e 100644 --- a/apps/web/src/timeline/controllers/drag-drop-controller.ts +++ b/apps/web/src/timeline/controllers/drag-drop-controller.ts @@ -19,14 +19,8 @@ import { BatchCommand } from "@/commands"; import type { Command } from "@/commands/base-command"; import { computeDropTarget } from "@/timeline/components/drop-target"; import type { TimelineDragSource } from "@/timeline/drag-source"; -import type { - TrackType, - DropTarget, - ElementType, - SceneTracks, - TimelineTrack, - CreateTimelineElement, -} from "@/timeline"; +import type { CreateTimelineElement, ElementType, SceneTracks, TimelineTrack, TrackType } from "@/model"; +import type { DropTarget } from "@/timeline"; import type { TimelineDragData } from "@/timeline/drag"; import type { MediaAsset } from "@/media/types"; import type { ProcessedMediaAsset } from "@/media/processing"; diff --git a/apps/web/src/timeline/controllers/element-interaction-controller.ts b/apps/web/src/timeline/controllers/element-interaction-controller.ts index cc19daaa..1c51f1c7 100644 --- a/apps/web/src/timeline/controllers/element-interaction-controller.ts +++ b/apps/web/src/timeline/controllers/element-interaction-controller.ts @@ -13,15 +13,15 @@ import { roundToFrame, type FrameRate } from "opencut-wasm"; import { computeDropTarget } from "@/timeline/components/drop-target"; import { getMouseTimeFromClientX } from "@/timeline/drag-utils"; import { generateUUID } from "@/utils/id"; -import type { SnapPoint } from "@/timeline/snapping"; +import type { TimelineSnapPoint } from "@/timeline/snapping"; import type { - DropTarget, + ElementId, ElementRef, - ElementDragView, SceneTracks, TimelineElement, TimelineTrack, -} from "@/timeline"; +} from "@/model"; +import type { DropTarget, ElementDragView } from "@/timeline"; const MOUSE_BUTTON_RIGHT = 2; @@ -61,7 +61,7 @@ export interface TimelineOps { export interface SnapConfig { isEnabled: () => boolean; - onChange?: (snapPoint: SnapPoint | null) => void; + onChange?: (snapPoint: TimelineSnapPoint | null) => void; } export interface ElementInteractionDeps { @@ -84,7 +84,7 @@ type Point = { readonly x: number; readonly y: number }; interface MousedownSnapshot { readonly origin: Point; - readonly elementId: string; + readonly elementId: ElementId; readonly trackId: string; readonly startElementTime: number; readonly clickOffsetTime: number; @@ -429,7 +429,7 @@ export class ElementInteractionController { private snapResult( frameSnappedTime: number, group: MoveGroup, - ): { snappedTime: number; snapPoint: SnapPoint | null } { + ): { snappedTime: number; snapPoint: TimelineSnapPoint | null } { const { snap, input, scene, viewport, playback } = this.deps; if (!snap.isEnabled() || input.isShiftHeld()) { diff --git a/apps/web/src/timeline/controllers/keyframe-drag-controller.ts b/apps/web/src/timeline/controllers/keyframe-drag-controller.ts index 51c60dee..ae0cb486 100644 --- a/apps/web/src/timeline/controllers/keyframe-drag-controller.ts +++ b/apps/web/src/timeline/controllers/keyframe-drag-controller.ts @@ -8,7 +8,7 @@ import { getKeyframeById } from "@/animation"; import { RetimeKeyframeCommand } from "@/commands/timeline/element/keyframes/retime-keyframe"; import { BatchCommand } from "@/commands"; import type { SelectedKeyframeRef } from "@/animation/types"; -import type { TimelineElement } from "@/timeline"; +import type { TimelineElement } from "@/model"; import type { Command } from "@/commands/base-command"; // --- Session --- diff --git a/apps/web/src/timeline/controllers/playhead-controller.ts b/apps/web/src/timeline/controllers/playhead-controller.ts index d2b7d82f..7bf3ea1b 100644 --- a/apps/web/src/timeline/controllers/playhead-controller.ts +++ b/apps/web/src/timeline/controllers/playhead-controller.ts @@ -15,7 +15,7 @@ import { timelineTimeToSnappedPixels, } from "@/timeline"; import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/timeline/scale"; -import type { Bookmark, SceneTracks } from "@/timeline"; +import type { Bookmark, SceneTracks } from "@/model"; // --- Session --- diff --git a/apps/web/src/timeline/controllers/resize-controller.ts b/apps/web/src/timeline/controllers/resize-controller.ts index cdf502a1..6c4c2803 100644 --- a/apps/web/src/timeline/controllers/resize-controller.ts +++ b/apps/web/src/timeline/controllers/resize-controller.ts @@ -12,18 +12,14 @@ import { buildTimelineSnapPoints, getTimelineSnapThresholdInTicks, resolveTimelineSnap, - type SnapPoint, + type TimelineSnapPoint, } from "@/timeline/snapping"; import { getElementEdgeSnapPoints } from "@/timeline/element-snap-source"; import { getPlayheadSnapPoints } from "@/timeline/playhead-snap-source"; import { getAnimationKeyframeSnapPointsForTimeline } from "@/timeline/animation-snap-points"; -import { - isRetimableElement, - type SceneTracks, - type TimelineElement, - type TimelineTrack, -} from "@/timeline"; -import type { ElementRef } from "@/timeline/types"; +import type { SceneTracks, TimelineElement, TimelineTrack } from "@/model"; +import { isRetimableElement } from "@/timeline"; +import type { ElementRef } from "@/model"; import type { FrameRate } from "opencut-wasm"; // --- Session --- @@ -52,7 +48,7 @@ export interface ResizeConfig { discardPreview: () => void; previewElements: (updates: GroupResizeUpdate[]) => void; commitElements: (updates: GroupResizeUpdate[]) => void; - onSnapPointChange?: (snapPoint: SnapPoint | null) => void; + onSnapPointChange?: (snapPoint: TimelineSnapPoint | null) => void; } export interface ResizeConfigRef { @@ -262,7 +258,7 @@ export class ResizeController { }); const maxSnapDistance = getTimelineSnapThresholdInTicks({ zoomLevel }); - let closestSnapPoint: SnapPoint | null = null; + let closestSnapPoint: TimelineSnapPoint | null = null; let closestSnapDistance = Infinity; let deltaTicks = rawDeltaTicks; diff --git a/apps/web/src/timeline/defaults.ts b/apps/web/src/timeline/defaults.ts index 06639e01..0cac6a4d 100644 --- a/apps/web/src/timeline/defaults.ts +++ b/apps/web/src/timeline/defaults.ts @@ -1,7 +1,7 @@ import { DEFAULT_NEW_ELEMENT_DURATION } from "@/timeline/creation"; import type { TTimelineViewState } from "@/project/types"; import type { BlendMode, Transform } from "@/rendering"; -import type { TextElement } from "./types"; +import type { TextElement } from "@/model"; const defaultTransform: Transform = { scaleX: 1, diff --git a/apps/web/src/timeline/drag.ts b/apps/web/src/timeline/drag.ts index 7bf99a6d..0522af9e 100644 --- a/apps/web/src/timeline/drag.ts +++ b/apps/web/src/timeline/drag.ts @@ -1,4 +1,4 @@ -import type { MaskableElement, VisualElement } from "./types"; +import type { MaskableElement, VisualElement } from "@/model"; import type { ParamValues } from "@/params"; interface BaseDragData { diff --git a/apps/web/src/timeline/element-snap-source.ts b/apps/web/src/timeline/element-snap-source.ts index ec1e5f65..60ffaa90 100644 --- a/apps/web/src/timeline/element-snap-source.ts +++ b/apps/web/src/timeline/element-snap-source.ts @@ -1,5 +1,5 @@ -import type { SceneTracks } from "@/timeline"; -import type { SnapPoint } from "@/timeline/snapping"; +import type { SceneTracks } from "@/model"; +import type { TimelineSnapPoint } from "@/timeline/snapping"; export function getElementEdgeSnapPoints({ tracks, @@ -7,8 +7,8 @@ export function getElementEdgeSnapPoints({ }: { tracks: SceneTracks; excludeElementIds?: Set; -}): SnapPoint[] { - const snapPoints: SnapPoint[] = []; +}): TimelineSnapPoint[] { + const snapPoints: TimelineSnapPoint[] = []; const orderedTracks = [...tracks.overlay, tracks.main, ...tracks.audio]; for (const track of orderedTracks) { diff --git a/apps/web/src/timeline/element-utils.ts b/apps/web/src/timeline/element-utils.ts index 676bc823..4bb2e383 100644 --- a/apps/web/src/timeline/element-utils.ts +++ b/apps/web/src/timeline/element-utils.ts @@ -1,28 +1,6 @@ import { DEFAULT_NEW_ELEMENT_DURATION } from "@/timeline/creation"; -import { - MASKABLE_ELEMENT_TYPES, - RETIMABLE_ELEMENT_TYPES, - VISUAL_ELEMENT_TYPES, - type CreateEffectElement, - type CreateGraphicElement, - type CreateTimelineElement, - type CreateVideoElement, - type CreateImageElement, - type CreateStickerElement, - type CreateUploadAudioElement, - type CreateLibraryAudioElement, - type TextBackground, - type TextElement, - type SceneTracks, - type TimelineElement, - type AudioElement, - type VideoElement, - type ImageElement, - type MaskableElement, - type RetimableElement, - type VisualElement, - type UploadAudioElement, -} from "@/timeline"; +import type { AudioElement, CreateEffectElement, CreateGraphicElement, CreateImageElement, CreateLibraryAudioElement, CreateStickerElement, CreateTimelineElement, CreateUploadAudioElement, CreateVideoElement, ImageElement, MaskableElement, RetimableElement, SceneTracks, TextBackground, TextElement, TimelineElement, UploadAudioElement, VideoElement, VisualElement } from "@/model"; +import { MASKABLE_ELEMENT_TYPES, RETIMABLE_ELEMENT_TYPES, VISUAL_ELEMENT_TYPES } from "@/model"; import { DEFAULTS } from "@/timeline/defaults"; import type { MediaType } from "@/media/types"; import { buildDefaultEffectInstance } from "@/effects"; diff --git a/apps/web/src/timeline/group-move/build-group.ts b/apps/web/src/timeline/group-move/build-group.ts index 00f39d55..d254ebb9 100644 --- a/apps/web/src/timeline/group-move/build-group.ts +++ b/apps/web/src/timeline/group-move/build-group.ts @@ -1,4 +1,4 @@ -import type { ElementRef, SceneTracks } from "@/timeline"; +import type { ElementRef, SceneTracks } from "@/model"; import { findTrackInSceneTracks } from "@/timeline/track-element-update"; import type { GroupMember, MoveGroup } from "./types"; import { getTrackPlacementById } from "./track-placement"; diff --git a/apps/web/src/timeline/group-move/resolve-move.ts b/apps/web/src/timeline/group-move/resolve-move.ts index 60940315..ab700979 100644 --- a/apps/web/src/timeline/group-move/resolve-move.ts +++ b/apps/web/src/timeline/group-move/resolve-move.ts @@ -1,4 +1,4 @@ -import type { SceneTracks } from "@/timeline"; +import type { SceneTracks } from "@/model"; import { getTrackTypeForElementType } from "@/timeline/placement/compatibility"; import { canPlaceTimeSpansOnTrack } from "@/timeline/placement/overlap"; import type { diff --git a/apps/web/src/timeline/group-move/snap.ts b/apps/web/src/timeline/group-move/snap.ts index 7f431e6e..22e829fe 100644 --- a/apps/web/src/timeline/group-move/snap.ts +++ b/apps/web/src/timeline/group-move/snap.ts @@ -1,9 +1,9 @@ -import type { SceneTracks } from "@/timeline"; +import type { SceneTracks } from "@/model"; import { buildTimelineSnapPoints, getTimelineSnapThresholdInTicks, resolveTimelineSnap, - type SnapPoint, + type TimelineSnapPoint, } from "@/timeline/snapping"; import { getElementEdgeSnapPoints } from "@/timeline/element-snap-source"; import { getPlayheadSnapPoints } from "@/timeline/playhead-snap-source"; @@ -24,7 +24,7 @@ export function snapGroupEdges({ zoomLevel: number; }): { snappedAnchorStartTime: number; - snapPoint: SnapPoint | null; + snapPoint: TimelineSnapPoint | null; } { const excludeElementIds = new Set( group.members.map((member) => member.elementId), @@ -44,7 +44,7 @@ export function snapGroupEdges({ let closestSnapDistance = Infinity; let snappedAnchorStartTime = anchorStartTime; - let snapPoint: SnapPoint | null = null; + let snapPoint: TimelineSnapPoint | null = null; for (const member of group.members) { const memberStartTime = anchorStartTime + member.timeOffset; diff --git a/apps/web/src/timeline/group-move/track-placement.ts b/apps/web/src/timeline/group-move/track-placement.ts index c4c5efb5..67a10920 100644 --- a/apps/web/src/timeline/group-move/track-placement.ts +++ b/apps/web/src/timeline/group-move/track-placement.ts @@ -1,4 +1,4 @@ -import type { SceneTracks, TimelineTrack } from "@/timeline"; +import type { SceneTracks, TimelineTrack } from "@/model"; import type { GroupTrackSection } from "./types"; export interface TrackPlacement { diff --git a/apps/web/src/timeline/group-move/types.ts b/apps/web/src/timeline/group-move/types.ts index daf9c6e9..dcd1b793 100644 --- a/apps/web/src/timeline/group-move/types.ts +++ b/apps/web/src/timeline/group-move/types.ts @@ -1,4 +1,4 @@ -import type { ElementRef, ElementType, TrackType } from "@/timeline"; +import type { ElementId, ElementRef, ElementType, TrackType } from "@/model"; export type GroupTrackSection = "overlay" | "main" | "audio"; @@ -25,7 +25,7 @@ export interface PlannedTrackCreation { export interface PlannedElementMove { sourceTrackId: string; targetTrackId: string; - elementId: string; + elementId: ElementId; newStartTime: number; } diff --git a/apps/web/src/timeline/group-resize/types.ts b/apps/web/src/timeline/group-resize/types.ts index 511358ba..3ad0f5ab 100644 --- a/apps/web/src/timeline/group-resize/types.ts +++ b/apps/web/src/timeline/group-resize/types.ts @@ -1,5 +1,5 @@ import type { FrameRate } from "opencut-wasm"; -import type { ElementRef, RetimeConfig } from "@/timeline/types"; +import type { ElementRef, RetimeConfig } from "@/model"; export type ResizeSide = "left" | "right"; diff --git a/apps/web/src/timeline/hooks/element/use-element-interaction.ts b/apps/web/src/timeline/hooks/element/use-element-interaction.ts index 26a21f85..e43f88b6 100644 --- a/apps/web/src/timeline/hooks/element/use-element-interaction.ts +++ b/apps/web/src/timeline/hooks/element/use-element-interaction.ts @@ -8,7 +8,7 @@ import { type ElementInteractionDeps, type ElementInteractionDepsRef, } from "@/timeline/controllers/element-interaction-controller"; -import type { SnapPoint } from "@/timeline/snapping"; +import type { TimelineSnapPoint } from "@/timeline/snapping"; interface UseElementInteractionProps { zoomLevel: number; @@ -16,7 +16,7 @@ interface UseElementInteractionProps { tracksScrollRef: RefObject; headerRef?: RefObject; snappingEnabled: boolean; - onSnapPointChange?: (snapPoint: SnapPoint | null) => void; + onSnapPointChange?: (snapPoint: TimelineSnapPoint | null) => void; } export function useElementInteraction({ diff --git a/apps/web/src/timeline/hooks/element/use-element-selection.ts b/apps/web/src/timeline/hooks/element/use-element-selection.ts index 19037e10..7ec77763 100644 --- a/apps/web/src/timeline/hooks/element/use-element-selection.ts +++ b/apps/web/src/timeline/hooks/element/use-element-selection.ts @@ -1,6 +1,6 @@ import { useCallback } from "react"; import { useEditor } from "@/editor/use-editor"; -import type { ElementRef } from "@/timeline/types"; +import type { ElementRef } from "@/model"; export function useElementSelection() { const editor = useEditor(); diff --git a/apps/web/src/timeline/hooks/element/use-keyframe-drag.ts b/apps/web/src/timeline/hooks/element/use-keyframe-drag.ts index 5a1ff1f0..ebc50b35 100644 --- a/apps/web/src/timeline/hooks/element/use-keyframe-drag.ts +++ b/apps/web/src/timeline/hooks/element/use-keyframe-drag.ts @@ -7,7 +7,7 @@ import { type KeyframeDragConfig, type KeyframeDragState, } from "@/timeline/controllers/keyframe-drag-controller"; -import type { TimelineElement } from "@/timeline"; +import type { TimelineElement } from "@/model"; export type { KeyframeDragState }; diff --git a/apps/web/src/timeline/hooks/use-element-preview.ts b/apps/web/src/timeline/hooks/use-element-preview.ts index 67213094..05e8f951 100644 --- a/apps/web/src/timeline/hooks/use-element-preview.ts +++ b/apps/web/src/timeline/hooks/use-element-preview.ts @@ -1,5 +1,6 @@ import { useEditor } from "@/editor/use-editor"; -import { findTrackInSceneTracks, type TimelineElement } from "@/timeline"; +import type { TimelineElement } from "@/model"; +import { findTrackInSceneTracks } from "@/timeline"; /** * Subscribes to render tracks and returns the live (preview-aware) version of diff --git a/apps/web/src/timeline/hooks/use-timeline-resize.ts b/apps/web/src/timeline/hooks/use-timeline-resize.ts index 46363fb6..4c71ee12 100644 --- a/apps/web/src/timeline/hooks/use-timeline-resize.ts +++ b/apps/web/src/timeline/hooks/use-timeline-resize.ts @@ -9,13 +9,13 @@ import { type ResizeConfig, } from "@/timeline/controllers/resize-controller"; import type { ResizeSide } from "@/timeline/group-resize"; -import type { SnapPoint } from "@/timeline/snapping"; +import type { TimelineSnapPoint } from "@/timeline/snapping"; export type { ResizeSide }; interface UseTimelineResizeProps { zoomLevel: number; - onSnapPointChange?: (snapPoint: SnapPoint | null) => void; + onSnapPointChange?: (snapPoint: TimelineSnapPoint | null) => void; } export function useTimelineResize({ diff --git a/apps/web/src/timeline/index.ts b/apps/web/src/timeline/index.ts index 5ee079b1..07ac7e2e 100644 --- a/apps/web/src/timeline/index.ts +++ b/apps/web/src/timeline/index.ts @@ -1,4 +1,4 @@ -import type { SceneTracks } from "./types"; +import type { SceneTracks } from "@/model"; export * from "./types"; export * from "./drag"; diff --git a/apps/web/src/timeline/placement/__tests__/resolve.test.ts b/apps/web/src/timeline/placement/__tests__/resolve.test.ts index ebbfe578..e83d1d9f 100644 --- a/apps/web/src/timeline/placement/__tests__/resolve.test.ts +++ b/apps/web/src/timeline/placement/__tests__/resolve.test.ts @@ -1,17 +1,18 @@ import { describe, expect, test } from "bun:test"; -import type { - AudioElement, - AudioTrack, - GraphicElement, - GraphicTrack, - OverlayTrack, - SceneTracks, - TextElement, - TextTrack, - TimelineTrack, - VideoElement, - VideoTrack, -} from "@/timeline"; +import { + elementId, + type AudioElement, + type AudioTrack, + type GraphicElement, + type GraphicTrack, + type OverlayTrack, + type SceneTracks, + type TextElement, + type TextTrack, + type TimelineTrack, + type VideoElement, + type VideoTrack, +} from "@/model"; import type { Transform } from "@/rendering"; import { resolveTrackPlacement } from "@/timeline/placement"; @@ -61,10 +62,11 @@ function buildElement({ startTime: number; duration: number; }): TestElement { + const idValue = elementId(id); switch (type) { case "audio": return { - id, + id: idValue, type: "audio", name: id, startTime, @@ -77,7 +79,7 @@ function buildElement({ } satisfies AudioElement; case "graphic": return { - id, + id: idValue, type: "graphic", name: id, startTime, @@ -91,7 +93,7 @@ function buildElement({ } satisfies GraphicElement; case "text": return { - id, + id: idValue, type: "text", name: id, startTime, @@ -115,7 +117,7 @@ function buildElement({ } satisfies TextElement; case "video": return { - id, + id: idValue, type: "video", name: id, startTime, diff --git a/apps/web/src/timeline/placement/apply.ts b/apps/web/src/timeline/placement/apply.ts index 214b0611..0abbab92 100644 --- a/apps/web/src/timeline/placement/apply.ts +++ b/apps/web/src/timeline/placement/apply.ts @@ -1,14 +1,4 @@ -import type { - AudioTrack, - EffectTrack, - GraphicTrack, - OverlayTrack, - SceneTracks, - TextTrack, - TimelineElement, - TimelineTrack, - VideoTrack, -} from "@/timeline"; +import type { AudioTrack, EffectTrack, GraphicTrack, OverlayTrack, SceneTracks, TextTrack, TimelineElement, TimelineTrack, VideoTrack } from "@/model"; import { generateUUID } from "@/utils/id"; import { buildEmptyTrack } from "./track-factory"; import type { PlacementResult } from "./types"; diff --git a/apps/web/src/timeline/placement/compatibility.ts b/apps/web/src/timeline/placement/compatibility.ts index b1af547c..4886bc31 100644 --- a/apps/web/src/timeline/placement/compatibility.ts +++ b/apps/web/src/timeline/placement/compatibility.ts @@ -1,4 +1,4 @@ -import type { ElementType, TrackType } from "@/timeline"; +import type { ElementType, TrackType } from "@/model"; const ELEMENT_TRACK_MAP: Record = { audio: "audio", diff --git a/apps/web/src/timeline/placement/insert-index.ts b/apps/web/src/timeline/placement/insert-index.ts index 5e2786dc..87e0ca21 100644 --- a/apps/web/src/timeline/placement/insert-index.ts +++ b/apps/web/src/timeline/placement/insert-index.ts @@ -1,4 +1,4 @@ -import type { SceneTracks, TrackType } from "@/timeline"; +import type { SceneTracks, TrackType } from "@/model"; export function getDefaultInsertIndexForTrack({ tracks, diff --git a/apps/web/src/timeline/placement/main-track.ts b/apps/web/src/timeline/placement/main-track.ts index ab2ab7bd..244a0805 100644 --- a/apps/web/src/timeline/placement/main-track.ts +++ b/apps/web/src/timeline/placement/main-track.ts @@ -1,4 +1,4 @@ -import type { SceneTracks, TimelineElement, VideoTrack } from "@/timeline"; +import type { SceneTracks, TimelineElement, VideoTrack } from "@/model"; export const MAIN_TRACK_NAME = "Main Track"; diff --git a/apps/web/src/timeline/placement/overlap.ts b/apps/web/src/timeline/placement/overlap.ts index daf3e627..736bfa46 100644 --- a/apps/web/src/timeline/placement/overlap.ts +++ b/apps/web/src/timeline/placement/overlap.ts @@ -1,4 +1,4 @@ -import type { TimelineElement } from "@/timeline"; +import type { TimelineElement } from "@/model"; import type { PlacementTimeSpan } from "./types"; interface TrackWithElements { diff --git a/apps/web/src/timeline/placement/resolve.ts b/apps/web/src/timeline/placement/resolve.ts index 26275392..a63ab809 100644 --- a/apps/web/src/timeline/placement/resolve.ts +++ b/apps/web/src/timeline/placement/resolve.ts @@ -1,4 +1,4 @@ -import type { SceneTracks, TrackType, TimelineTrack } from "@/timeline"; +import type { SceneTracks, TimelineTrack, TrackType } from "@/model"; import { getDefaultInsertIndexForTrack, getHighestInsertIndexForTrack, diff --git a/apps/web/src/timeline/placement/track-factory.ts b/apps/web/src/timeline/placement/track-factory.ts index 9c69ea8c..87afd888 100644 --- a/apps/web/src/timeline/placement/track-factory.ts +++ b/apps/web/src/timeline/placement/track-factory.ts @@ -1,13 +1,5 @@ import { DEFAULT_TRACK_NAMES } from "@/timeline/tracks"; -import type { - AudioTrack, - EffectTrack, - GraphicTrack, - TextTrack, - TrackType, - TimelineTrack, - VideoTrack, -} from "@/timeline"; +import type { AudioTrack, EffectTrack, GraphicTrack, TextTrack, TimelineTrack, TrackType, VideoTrack } from "@/model"; export function buildEmptyTrack({ id, diff --git a/apps/web/src/timeline/placement/types.ts b/apps/web/src/timeline/placement/types.ts index 6847ab36..02a7f44b 100644 --- a/apps/web/src/timeline/placement/types.ts +++ b/apps/web/src/timeline/placement/types.ts @@ -1,4 +1,4 @@ -import type { ElementType, TrackType } from "@/timeline"; +import type { ElementType, TrackType } from "@/model"; export interface PlacementTimeSpan { startTime: number; diff --git a/apps/web/src/timeline/playhead-snap-source.ts b/apps/web/src/timeline/playhead-snap-source.ts index 88e21eba..bc532836 100644 --- a/apps/web/src/timeline/playhead-snap-source.ts +++ b/apps/web/src/timeline/playhead-snap-source.ts @@ -1,9 +1,9 @@ -import type { SnapPoint } from "@/timeline/snapping"; +import type { TimelineSnapPoint } from "@/timeline/snapping"; export function getPlayheadSnapPoints({ playheadTime, }: { playheadTime: number; -}): SnapPoint[] { +}): TimelineSnapPoint[] { return [{ time: playheadTime, type: "playhead" }]; } diff --git a/apps/web/src/timeline/scenes.ts b/apps/web/src/timeline/scenes.ts index d48c943c..6b5781cb 100644 --- a/apps/web/src/timeline/scenes.ts +++ b/apps/web/src/timeline/scenes.ts @@ -1,4 +1,4 @@ -import type { TScene } from "@/timeline"; +import type { TScene } from "@/model"; import { generateUUID } from "@/utils/id"; import { calculateTotalDuration } from "@/timeline"; import { MAIN_TRACK_NAME } from "@/timeline/placement/main-track"; diff --git a/apps/web/src/timeline/snapping/build.ts b/apps/web/src/timeline/snapping/build.ts index 87d3db2e..770f303d 100644 --- a/apps/web/src/timeline/snapping/build.ts +++ b/apps/web/src/timeline/snapping/build.ts @@ -1,11 +1,11 @@ -import type { SnapPoint, TimelineSnapPointSource } from "./types"; +import type { TimelineSnapPoint, TimelineSnapPointSource } from "./types"; export function buildTimelineSnapPoints({ sources, }: { sources: TimelineSnapPointSource[]; -}): SnapPoint[] { - const snapPoints: SnapPoint[] = []; +}): TimelineSnapPoint[] { + const snapPoints: TimelineSnapPoint[] = []; for (const source of sources) { for (const snapPoint of source()) { diff --git a/apps/web/src/timeline/snapping/index.ts b/apps/web/src/timeline/snapping/index.ts index 73f49272..dbf6cb62 100644 --- a/apps/web/src/timeline/snapping/index.ts +++ b/apps/web/src/timeline/snapping/index.ts @@ -2,8 +2,8 @@ export { buildTimelineSnapPoints } from "./build"; export { resolveTimelineSnap } from "./resolve"; export { getTimelineSnapThresholdInTicks } from "./threshold"; export type { - SnapPoint, - SnapPointType, - SnapResult, + TimelineSnapPoint, + TimelineSnapPointType, + TimelineSnapResult, TimelineSnapPointSource, } from "./types"; diff --git a/apps/web/src/timeline/snapping/resolve.ts b/apps/web/src/timeline/snapping/resolve.ts index 2f00d081..72fb8cff 100644 --- a/apps/web/src/timeline/snapping/resolve.ts +++ b/apps/web/src/timeline/snapping/resolve.ts @@ -1,4 +1,4 @@ -import type { SnapPoint, SnapResult } from "./types"; +import type { TimelineSnapPoint, TimelineSnapResult } from "./types"; export function resolveTimelineSnap({ targetTime, @@ -6,10 +6,10 @@ export function resolveTimelineSnap({ maxSnapDistance, }: { targetTime: number; - snapPoints: SnapPoint[]; + snapPoints: TimelineSnapPoint[]; maxSnapDistance: number; -}): SnapResult { - let closestSnapPoint: SnapPoint | null = null; +}): TimelineSnapResult { + let closestSnapPoint: TimelineSnapPoint | null = null; let closestDistance = Infinity; for (const snapPoint of snapPoints) { diff --git a/apps/web/src/timeline/snapping/types.ts b/apps/web/src/timeline/snapping/types.ts index 2a6d0903..e8a37ced 100644 --- a/apps/web/src/timeline/snapping/types.ts +++ b/apps/web/src/timeline/snapping/types.ts @@ -1,21 +1,21 @@ -export type SnapPointType = +export type TimelineSnapPointType = | "element-start" | "element-end" | "playhead" | "bookmark" | "keyframe"; -export interface SnapPoint { +export interface TimelineSnapPoint { time: number; - type: SnapPointType; + type: TimelineSnapPointType; elementId?: string; trackId?: string; } -export interface SnapResult { +export interface TimelineSnapResult { snappedTime: number; - snapPoint: SnapPoint | null; + snapPoint: TimelineSnapPoint | null; snapDistance: number; } -export type TimelineSnapPointSource = () => Iterable; +export type TimelineSnapPointSource = () => Iterable; diff --git a/apps/web/src/timeline/track-capabilities.ts b/apps/web/src/timeline/track-capabilities.ts index e6ff3945..8886eb49 100644 --- a/apps/web/src/timeline/track-capabilities.ts +++ b/apps/web/src/timeline/track-capabilities.ts @@ -1,11 +1,4 @@ -import type { - TimelineTrack, - VideoTrack, - AudioTrack, - GraphicTrack, - TextTrack, - EffectTrack, -} from "@/timeline"; +import type { AudioTrack, EffectTrack, GraphicTrack, TextTrack, TimelineTrack, VideoTrack } from "@/model"; export function canTrackHaveAudio( track: TimelineTrack, diff --git a/apps/web/src/timeline/track-element-update.ts b/apps/web/src/timeline/track-element-update.ts index 7d86a4e8..079682a2 100644 --- a/apps/web/src/timeline/track-element-update.ts +++ b/apps/web/src/timeline/track-element-update.ts @@ -1,4 +1,4 @@ -import type { SceneTracks, TimelineElement, TimelineTrack } from "@/timeline"; +import type { SceneTracks, TimelineElement, TimelineTrack } from "@/model"; export function findTrackInSceneTracks({ tracks, diff --git a/apps/web/src/timeline/tracks.ts b/apps/web/src/timeline/tracks.ts index 9ce5511d..2c87ecb1 100644 --- a/apps/web/src/timeline/tracks.ts +++ b/apps/web/src/timeline/tracks.ts @@ -1,4 +1,4 @@ -import type { TrackType } from "@/timeline"; +import type { TrackType } from "@/model"; export const DEFAULT_TRACK_NAMES: Record = { video: "Video track", diff --git a/apps/web/src/timeline/types.ts b/apps/web/src/timeline/types.ts index 338da9d9..abf19463 100644 --- a/apps/web/src/timeline/types.ts +++ b/apps/web/src/timeline/types.ts @@ -1,273 +1,7 @@ -import type { ElementAnimations } from "@/animation/types"; -import type { Effect } from "@/effects/types"; -import type { Mask } from "@/masks/types"; -import type { ParamValues } from "@/params"; -import type { BlendMode, Transform } from "@/rendering"; - -export type ElementRef = { - trackId: string; - elementId: string; -}; - -export interface Bookmark { - time: number; - note?: string; - color?: string; - duration?: number; -} - -export interface TScene { - id: string; - name: string; - isMain: boolean; - tracks: SceneTracks; - bookmarks: Bookmark[]; - createdAt: Date; - updatedAt: Date; -} - -export type TrackType = "video" | "text" | "audio" | "graphic" | "effect"; - -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[]; -} - -export interface RetimeConfig { - rate: number; - maintainPitch?: boolean; -} - -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; - -interface BaseTimelineElement { - id: string; - name: string; - duration: number; - startTime: number; - trimStart: number; - trimEnd: number; - sourceDuration?: number; - animations?: ElementAnimations; -} - -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[]; -} - -export interface ImageElement extends BaseTimelineElement { - type: "image"; - mediaId: string; - hidden?: boolean; - transform: Transform; - opacity: number; - blendMode?: BlendMode; - effects?: Effect[]; - masks?: Mask[]; -} - -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[]; -} - -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[]; -} - -export interface GraphicElement extends BaseTimelineElement { - type: "graphic"; - definitionId: string; - params: ParamValues; - hidden?: boolean; - transform: Transform; - opacity: number; - blendMode?: BlendMode; - effects?: Effect[]; - masks?: Mask[]; -} - -export interface EffectElement extends BaseTimelineElement { - type: "effect"; - effectType: string; - params: ParamValues; -} - -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(...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; -export type CreateLibraryAudioElement = Omit; -export type CreateAudioElement = - | CreateUploadAudioElement - | CreateLibraryAudioElement; -export type CreateVideoElement = Omit; -export type CreateImageElement = Omit; -export type CreateTextElement = Omit; -export type CreateStickerElement = Omit; -export type CreateGraphicElement = Omit; -export type CreateEffectElement = Omit; -export type CreateTimelineElement = - | CreateAudioElement - | CreateVideoElement - | CreateImageElement - | CreateTextElement - | CreateStickerElement - | CreateGraphicElement - | CreateEffectElement; +import type { + ElementType, + SceneTracks, +} from "@/model"; export type ElementDragView = | { readonly kind: "idle" } @@ -309,9 +43,3 @@ export interface ComputeDropTargetParams { excludeElementId?: string; targetElementTypes?: string[]; } - -export interface ClipboardItem { - trackId: string; - trackType: TrackType; - element: CreateTimelineElement; -} diff --git a/apps/web/src/timeline/update-pipeline.ts b/apps/web/src/timeline/update-pipeline.ts index beacf897..e3e70ac0 100644 --- a/apps/web/src/timeline/update-pipeline.ts +++ b/apps/web/src/timeline/update-pipeline.ts @@ -4,7 +4,7 @@ import { getSourceSpanAtClipTime, getTimelineDurationForSourceSpan, } from "@/retime"; -import type { RetimeConfig, SceneTracks, TimelineElement } from "@/timeline"; +import type { RetimeConfig, SceneTracks, TimelineElement } from "@/model"; import { isRetimableElement } from "@/timeline"; type ElementUpdateField = keyof TimelineElement | string;