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