diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-04-20 11:31:17 +0200 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-04-20 11:31:17 +0200 |
| commit | 3e89d2998588f8e35c524e523ba2ee853f0f6475 (patch) | |
| tree | 57221f96c1259b3b07e3ddf8c1adefc502b9d8eb /apps/web/src/components/editor/panels/timeline/drop-target.ts | |
| parent | 729d10592f4e802f91c555c309ff3ba4b119f843 (diff) | |
| download | opencut-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.ts | 262 |
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; -} |
