summaryrefslogtreecommitdiff
path: root/apps/web/src/components/editor/panels/timeline/drop-target.ts
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-04-20 11:31:17 +0200
committerMaze Winther <mazewinther@gmail.com>2026-04-20 11:31:17 +0200
commit3e89d2998588f8e35c524e523ba2ee853f0f6475 (patch)
tree57221f96c1259b3b07e3ddf8c1adefc502b9d8eb /apps/web/src/components/editor/panels/timeline/drop-target.ts
parent729d10592f4e802f91c555c309ff3ba4b119f843 (diff)
downloadopencut-3e89d2998588f8e35c524e523ba2ee853f0f6475.tar.gz
refactor: restructure files to their domains, new preview overlay system, and dep graph
Diffstat (limited to 'apps/web/src/components/editor/panels/timeline/drop-target.ts')
-rw-r--r--apps/web/src/components/editor/panels/timeline/drop-target.ts262
1 files changed, 0 insertions, 262 deletions
diff --git a/apps/web/src/components/editor/panels/timeline/drop-target.ts b/apps/web/src/components/editor/panels/timeline/drop-target.ts
deleted file mode 100644
index ef6cbd90..00000000
--- a/apps/web/src/components/editor/panels/timeline/drop-target.ts
+++ /dev/null
@@ -1,262 +0,0 @@
-import type { TimelineTrack, TimelineElement } from "@/lib/timeline";
-import type { ComputeDropTargetParams, DropTarget } from "@/lib/timeline";
-import { resolveTrackPlacement } from "@/lib/timeline/placement";
-import { TIMELINE_TRACK_GAP_PX } from "./layout";
-import { getTrackHeight } from "./track-layout";
-import { TICKS_PER_SECOND } from "@/lib/wasm";
-
-function findElementAtPosition({
- mouseX,
- tracks,
- trackIndex,
- targetElementTypes,
- pixelsPerSecond,
- zoomLevel,
-}: {
- mouseX: number;
- tracks: TimelineTrack[];
- trackIndex: number;
- targetElementTypes: string[];
- pixelsPerSecond: number;
- zoomLevel: number;
-}): { elementId: string; trackId: string } | null {
- const time = Math.round(
- (mouseX / (pixelsPerSecond * zoomLevel)) * TICKS_PER_SECOND,
- );
- const track = tracks[trackIndex];
- if (!track || !("elements" in track)) return null;
-
- const hit = track.elements.find(
- (element: TimelineElement) =>
- targetElementTypes.includes(element.type) &&
- element.startTime <= time &&
- time < element.startTime + element.duration,
- );
- if (!hit) return null;
- return { elementId: hit.id, trackId: track.id };
-}
-
-function getTrackAtY({
- mouseY,
- tracks,
- verticalDragDirection,
-}: {
- mouseY: number;
- tracks: TimelineTrack[];
- verticalDragDirection?: "up" | "down" | null;
-}): { trackIndex: number; relativeY: number } | null {
- let cumulativeHeight = 0;
-
- for (let i = 0; i < tracks.length; i++) {
- const trackHeight = getTrackHeight({ type: tracks[i].type });
- const trackTop = cumulativeHeight;
- const trackBottom = trackTop + trackHeight;
-
- if (mouseY >= trackTop && mouseY < trackBottom) {
- return {
- trackIndex: i,
- relativeY: mouseY - trackTop,
- };
- }
-
- if (i < tracks.length - 1 && verticalDragDirection) {
- const gapTop = trackBottom;
- const gapBottom = gapTop + TIMELINE_TRACK_GAP_PX;
- if (mouseY >= gapTop && mouseY < gapBottom) {
- const isDraggingUp = verticalDragDirection === "up";
- return {
- trackIndex: isDraggingUp ? i : i + 1,
- relativeY: isDraggingUp ? trackHeight - 1 : 0,
- };
- }
- }
-
- cumulativeHeight += trackHeight + TIMELINE_TRACK_GAP_PX;
- }
-
- return null;
-}
-
-const EMPTY_TARGET_ELEMENT = null;
-
-function fallbackNewTrackDropTarget({
- xPosition,
-}: {
- xPosition: number;
-}): DropTarget {
- return {
- trackIndex: 0,
- isNewTrack: true,
- insertPosition: null,
- xPosition,
- targetElement: EMPTY_TARGET_ELEMENT,
- };
-}
-
-export function computeDropTarget({
- elementType,
- mouseX,
- mouseY,
- tracks,
- playheadTime,
- isExternalDrop,
- elementDuration,
- pixelsPerSecond,
- zoomLevel,
- verticalDragDirection,
- startTimeOverride,
- excludeElementId,
- targetElementTypes,
-}: ComputeDropTargetParams): DropTarget {
- const orderedTracks = [...tracks.overlay, tracks.main, ...tracks.audio];
- const mainTrackIndex = tracks.overlay.length;
- const xPosition =
- typeof startTimeOverride === "number"
- ? startTimeOverride
- : isExternalDrop
- ? playheadTime
- : Math.round(
- Math.max(0, mouseX / (pixelsPerSecond * zoomLevel)) * TICKS_PER_SECOND,
- );
-
- if (orderedTracks.length === 0) {
- const placementResult = resolveTrackPlacement({
- tracks,
- elementType,
- timeSpans: [{ startTime: xPosition, duration: elementDuration, excludeElementId }],
- strategy: {
- type: "preferIndex",
- trackIndex: 0,
- hoverDirection: "below",
- createNewTrackOnly: true,
- },
- });
- const emptyTimelineResult =
- placementResult?.kind === "newTrack" ? placementResult : null;
- if (!emptyTimelineResult) {
- return fallbackNewTrackDropTarget({ xPosition });
- }
-
- return {
- trackIndex: emptyTimelineResult.insertIndex,
- isNewTrack: true,
- insertPosition: emptyTimelineResult.insertPosition,
- xPosition,
- targetElement: EMPTY_TARGET_ELEMENT,
- };
- }
-
- const trackAtMouse = getTrackAtY({
- mouseY,
- tracks: orderedTracks,
- verticalDragDirection,
- });
-
- if (!trackAtMouse) {
- const isAboveAllTracks = mouseY < 0;
-
- const placementResult = resolveTrackPlacement({
- tracks,
- elementType,
- timeSpans: [{ startTime: xPosition, duration: elementDuration, excludeElementId }],
- strategy: {
- type: "preferIndex",
- trackIndex: isAboveAllTracks ? 0 : orderedTracks.length - 1,
- hoverDirection: isAboveAllTracks ? "above" : "below",
- createNewTrackOnly: true,
- },
- });
- const outOfBoundsResult =
- placementResult?.kind === "newTrack" ? placementResult : null;
- if (!outOfBoundsResult) {
- return fallbackNewTrackDropTarget({ xPosition });
- }
-
- return {
- trackIndex: outOfBoundsResult.insertIndex,
- isNewTrack: true,
- insertPosition: outOfBoundsResult.insertPosition,
- xPosition,
- targetElement: EMPTY_TARGET_ELEMENT,
- };
- }
-
- const { trackIndex, relativeY } = trackAtMouse;
- const track = orderedTracks[trackIndex];
-
- if (targetElementTypes && targetElementTypes.length > 0) {
- const targetElement = findElementAtPosition({
- mouseX,
- tracks: orderedTracks,
- trackIndex,
- targetElementTypes,
- pixelsPerSecond,
- zoomLevel,
- });
- if (targetElement) {
- return {
- trackIndex,
- isNewTrack: false,
- insertPosition: null,
- xPosition,
- targetElement,
- };
- }
- }
-
- const trackHeight = getTrackHeight({ type: track.type });
- const placementResult = resolveTrackPlacement({
- tracks,
- elementType,
- timeSpans: [{ startTime: xPosition, duration: elementDuration, excludeElementId }],
- strategy: {
- type: "preferIndex",
- trackIndex,
- hoverDirection: relativeY < trackHeight / 2 ? "above" : "below",
- verticalDragDirection,
- },
- });
- if (!placementResult) {
- return fallbackNewTrackDropTarget({ xPosition });
- }
-
- if (placementResult.kind === "existingTrack") {
- const adjustedXPosition = placementResult.adjustedStartTime ?? xPosition;
-
- return {
- trackIndex: placementResult.trackIndex,
- isNewTrack: false,
- insertPosition: null,
- xPosition: adjustedXPosition,
- targetElement: EMPTY_TARGET_ELEMENT,
- };
- }
-
- return {
- trackIndex: placementResult.insertIndex,
- isNewTrack: true,
- insertPosition: placementResult.insertPosition,
- xPosition,
- targetElement: EMPTY_TARGET_ELEMENT,
- };
-}
-
-export function getDropLineY({
- dropTarget,
- tracks,
-}: {
- dropTarget: DropTarget;
- tracks: TimelineTrack[];
-}): number {
- const safeTrackIndex = Math.min(
- Math.max(dropTarget.trackIndex, 0),
- tracks.length,
- );
- let y = 0;
-
- for (let i = 0; i < safeTrackIndex; i++) {
- y += getTrackHeight({ type: tracks[i].type }) + TIMELINE_TRACK_GAP_PX;
- }
-
- return y;
-}