diff options
Diffstat (limited to 'apps/web/src/hooks/timeline/element/use-element-resize.ts')
| -rw-r--r-- | apps/web/src/hooks/timeline/element/use-element-resize.ts | 314 |
1 files changed, 170 insertions, 144 deletions
diff --git a/apps/web/src/hooks/timeline/element/use-element-resize.ts b/apps/web/src/hooks/timeline/element/use-element-resize.ts index 803d9788..14693fff 100644 --- a/apps/web/src/hooks/timeline/element/use-element-resize.ts +++ b/apps/web/src/hooks/timeline/element/use-element-resize.ts @@ -1,7 +1,9 @@ import { useState, useEffect, useRef, useCallback } from "react"; -import type { TimelineElement, TimelineTrack } from "@/types/timeline"; +import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants"; import { snapTimeToFrame } from "@/lib/time"; -import { EditorCore } from "@/core"; +import type { TimelineElement, TimelineTrack } from "@/types/timeline"; +import { useEditor } from "@/hooks/use-editor"; +import { useShiftKey } from "@/hooks/use-shift-key"; import { useTimelineSnapping, type SnapPoint, @@ -33,8 +35,9 @@ export function useTimelineElementResize({ onSnapPointChange, onResizeStateChange, }: UseTimelineElementResizeProps) { - const editor = EditorCore.getInstance(); + const editor = useEditor(); const activeProject = editor.project.getActive(); + const isShiftHeldRef = useShiftKey(); const snappingEnabled = useTimelineStore((state) => state.snappingEnabled); const { findSnapPoints, snapToNearestPoint } = useTimelineSnapping(); @@ -49,21 +52,21 @@ export function useTimelineElementResize({ const currentDurationRef = useRef(element.duration); const handleResizeStart = ({ - e, + event, elementId, side, }: { - e: React.MouseEvent; + event: React.MouseEvent; elementId: string; side: "left" | "right"; }) => { - e.stopPropagation(); - e.preventDefault(); + event.stopPropagation(); + event.preventDefault(); setResizing({ elementId, side, - startX: e.clientX, + startX: event.clientX, initialTrimStart: element.trimStart, initialTrimEnd: element.trimEnd, initialStartTime: element.startTime, @@ -89,104 +92,69 @@ export function useTimelineElementResize({ return false; }, [element.type]); - const updateTrimFromMouseMove = useCallback(({ clientX }: { clientX: number }) => { - if (!resizing) return; + const updateTrimFromMouseMove = useCallback( + ({ clientX }: { clientX: number }) => { + if (!resizing) return; - const deltaX = clientX - resizing.startX; - let deltaTime = deltaX / (50 * zoomLevel); - let resizeSnapPoint: SnapPoint | null = null; + const deltaX = clientX - resizing.startX; + let deltaTime = + deltaX / (TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel); + let resizeSnapPoint: SnapPoint | null = null; - const projectFps = activeProject.settings.fps; - const minDurationSeconds = 1 / projectFps; - const canSnap = snappingEnabled; - if (canSnap) { - const tracks = editor.timeline.getTracks(); - const playheadTime = editor.playback.getCurrentTime(); - const snapPoints = findSnapPoints({ - tracks, - playheadTime, - excludeElementId: element.id, - }); - if (resizing.side === "left") { - const targetStartTime = resizing.initialStartTime + deltaTime; - const snapResult = snapToNearestPoint({ - targetTime: targetStartTime, - snapPoints, - zoomLevel, - }); - resizeSnapPoint = snapResult.snapPoint; - if (snapResult.snapPoint) { - deltaTime = snapResult.snappedTime - resizing.initialStartTime; - } - } else { - const baseEndTime = - resizing.initialStartTime + resizing.initialDuration; - const targetEndTime = baseEndTime + deltaTime; - const snapResult = snapToNearestPoint({ - targetTime: targetEndTime, - snapPoints, - zoomLevel, + const projectFps = activeProject.settings.fps; + const minDurationSeconds = 1 / projectFps; + const shouldSnap = snappingEnabled && !isShiftHeldRef.current; + if (shouldSnap) { + const tracks = editor.timeline.getTracks(); + const playheadTime = editor.playback.getCurrentTime(); + const snapPoints = findSnapPoints({ + tracks, + playheadTime, + excludeElementId: element.id, }); - resizeSnapPoint = snapResult.snapPoint; - if (snapResult.snapPoint) { - deltaTime = snapResult.snappedTime - baseEndTime; + if (resizing.side === "left") { + const targetStartTime = resizing.initialStartTime + deltaTime; + const snapResult = snapToNearestPoint({ + targetTime: targetStartTime, + snapPoints, + zoomLevel, + }); + resizeSnapPoint = snapResult.snapPoint; + if (snapResult.snapPoint) { + deltaTime = snapResult.snappedTime - resizing.initialStartTime; + } + } else { + const baseEndTime = + resizing.initialStartTime + resizing.initialDuration; + const targetEndTime = baseEndTime + deltaTime; + const snapResult = snapToNearestPoint({ + targetTime: targetEndTime, + snapPoints, + zoomLevel, + }); + resizeSnapPoint = snapResult.snapPoint; + if (snapResult.snapPoint) { + deltaTime = snapResult.snappedTime - baseEndTime; + } } } - } - onSnapPointChange?.(resizeSnapPoint); + onSnapPointChange?.(resizeSnapPoint); - if (resizing.side === "left") { - const sourceDuration = - resizing.initialTrimStart + - resizing.initialDuration + - resizing.initialTrimEnd; - const maxAllowed = - sourceDuration - resizing.initialTrimEnd - minDurationSeconds; - const calculated = resizing.initialTrimStart + deltaTime; - - if (calculated >= 0 && calculated <= maxAllowed) { - const newTrimStart = snapTimeToFrame({ - time: Math.min(maxAllowed, calculated), - fps: projectFps, - }); - const trimDelta = newTrimStart - resizing.initialTrimStart; - const newStartTime = snapTimeToFrame({ - time: resizing.initialStartTime + trimDelta, - fps: projectFps, - }); - const newDuration = snapTimeToFrame({ - time: resizing.initialDuration - trimDelta, - fps: projectFps, - }); + if (resizing.side === "left") { + const sourceDuration = + resizing.initialTrimStart + + resizing.initialDuration + + resizing.initialTrimEnd; + const maxAllowed = + sourceDuration - resizing.initialTrimEnd - minDurationSeconds; + const calculated = resizing.initialTrimStart + deltaTime; - setCurrentTrimStart(newTrimStart); - setCurrentStartTime(newStartTime); - setCurrentDuration(newDuration); - currentTrimStartRef.current = newTrimStart; - currentStartTimeRef.current = newStartTime; - currentDurationRef.current = newDuration; - } else if (calculated < 0) { - if (canExtendElementDuration()) { - const extensionAmount = Math.abs(calculated); - const maxExtension = resizing.initialStartTime; - const actualExtension = Math.min(extensionAmount, maxExtension); - const newStartTime = snapTimeToFrame({ - time: resizing.initialStartTime - actualExtension, + if (calculated >= 0 && calculated <= maxAllowed) { + const newTrimStart = snapTimeToFrame({ + time: Math.min(maxAllowed, calculated), fps: projectFps, }); - const newDuration = snapTimeToFrame({ - time: resizing.initialDuration + actualExtension, - fps: projectFps, - }); - - setCurrentTrimStart(0); - setCurrentStartTime(newStartTime); - setCurrentDuration(newDuration); - currentTrimStartRef.current = 0; - currentStartTimeRef.current = newStartTime; - currentDurationRef.current = newDuration; - } else { - const trimDelta = 0 - resizing.initialTrimStart; + const trimDelta = newTrimStart - resizing.initialTrimStart; const newStartTime = snapTimeToFrame({ time: resizing.initialStartTime + trimDelta, fps: projectFps, @@ -196,68 +164,119 @@ export function useTimelineElementResize({ fps: projectFps, }); - setCurrentTrimStart(0); + setCurrentTrimStart(newTrimStart); setCurrentStartTime(newStartTime); setCurrentDuration(newDuration); - currentTrimStartRef.current = 0; + currentTrimStartRef.current = newTrimStart; currentStartTimeRef.current = newStartTime; currentDurationRef.current = newDuration; + } else if (calculated < 0) { + if (canExtendElementDuration()) { + const extensionAmount = Math.abs(calculated); + const maxExtension = resizing.initialStartTime; + const actualExtension = Math.min(extensionAmount, maxExtension); + const newStartTime = snapTimeToFrame({ + time: resizing.initialStartTime - actualExtension, + fps: projectFps, + }); + const newDuration = snapTimeToFrame({ + time: resizing.initialDuration + actualExtension, + fps: projectFps, + }); + + setCurrentTrimStart(0); + setCurrentStartTime(newStartTime); + setCurrentDuration(newDuration); + currentTrimStartRef.current = 0; + currentStartTimeRef.current = newStartTime; + currentDurationRef.current = newDuration; + } else { + const trimDelta = 0 - resizing.initialTrimStart; + const newStartTime = snapTimeToFrame({ + time: resizing.initialStartTime + trimDelta, + fps: projectFps, + }); + const newDuration = snapTimeToFrame({ + time: resizing.initialDuration - trimDelta, + fps: projectFps, + }); + + setCurrentTrimStart(0); + setCurrentStartTime(newStartTime); + setCurrentDuration(newDuration); + currentTrimStartRef.current = 0; + currentStartTimeRef.current = newStartTime; + currentDurationRef.current = newDuration; + } } - } - } else { - const sourceDuration = - resizing.initialTrimStart + - resizing.initialDuration + - resizing.initialTrimEnd; - const newTrimEnd = resizing.initialTrimEnd - deltaTime; + } else { + const sourceDuration = + resizing.initialTrimStart + + resizing.initialDuration + + resizing.initialTrimEnd; + const newTrimEnd = resizing.initialTrimEnd - deltaTime; - if (newTrimEnd < 0) { - if (canExtendElementDuration()) { - const extensionNeeded = Math.abs(newTrimEnd); - const baseDuration = - resizing.initialDuration + resizing.initialTrimEnd; - const newDuration = snapTimeToFrame({ - time: baseDuration + extensionNeeded, - fps: projectFps, - }); + if (newTrimEnd < 0) { + if (canExtendElementDuration()) { + const extensionNeeded = Math.abs(newTrimEnd); + const baseDuration = + resizing.initialDuration + resizing.initialTrimEnd; + const newDuration = snapTimeToFrame({ + time: baseDuration + extensionNeeded, + fps: projectFps, + }); - setCurrentDuration(newDuration); - setCurrentTrimEnd(0); - currentDurationRef.current = newDuration; - currentTrimEndRef.current = 0; + setCurrentDuration(newDuration); + setCurrentTrimEnd(0); + currentDurationRef.current = newDuration; + currentTrimEndRef.current = 0; + } else { + const extensionToLimit = resizing.initialTrimEnd; + const newDuration = snapTimeToFrame({ + time: resizing.initialDuration + extensionToLimit, + fps: projectFps, + }); + + setCurrentDuration(newDuration); + setCurrentTrimEnd(0); + currentDurationRef.current = newDuration; + currentTrimEndRef.current = 0; + } } else { - const extensionToLimit = resizing.initialTrimEnd; + const maxTrimEnd = + sourceDuration - resizing.initialTrimStart - minDurationSeconds; + const clampedTrimEnd = Math.min(maxTrimEnd, Math.max(0, newTrimEnd)); + const finalTrimEnd = snapTimeToFrame({ + time: clampedTrimEnd, + fps: projectFps, + }); + const trimDelta = finalTrimEnd - resizing.initialTrimEnd; const newDuration = snapTimeToFrame({ - time: resizing.initialDuration + extensionToLimit, + time: resizing.initialDuration - trimDelta, fps: projectFps, }); + setCurrentTrimEnd(finalTrimEnd); setCurrentDuration(newDuration); - setCurrentTrimEnd(0); + currentTrimEndRef.current = finalTrimEnd; currentDurationRef.current = newDuration; - currentTrimEndRef.current = 0; } - } else { - const maxTrimEnd = - sourceDuration - resizing.initialTrimStart - minDurationSeconds; - const clampedTrimEnd = Math.min(maxTrimEnd, Math.max(0, newTrimEnd)); - const finalTrimEnd = snapTimeToFrame({ - time: clampedTrimEnd, - fps: projectFps, - }); - const trimDelta = finalTrimEnd - resizing.initialTrimEnd; - const newDuration = snapTimeToFrame({ - time: resizing.initialDuration - trimDelta, - fps: projectFps, - }); - - setCurrentTrimEnd(finalTrimEnd); - setCurrentDuration(newDuration); - currentTrimEndRef.current = finalTrimEnd; - currentDurationRef.current = newDuration; } - } - }, [resizing, zoomLevel, activeProject.settings.fps, snappingEnabled, editor, findSnapPoints, snapToNearestPoint, element.id, onSnapPointChange, canExtendElementDuration]); + }, + [ + resizing, + zoomLevel, + activeProject.settings.fps, + snappingEnabled, + editor, + findSnapPoints, + snapToNearestPoint, + element.id, + onSnapPointChange, + canExtendElementDuration, + isShiftHeldRef, + ], + ); const handleResizeEnd = useCallback(() => { if (!resizing) return; @@ -297,7 +316,14 @@ export function useTimelineElementResize({ setResizing(null); onResizeStateChange?.({ isResizing: false }); onSnapPointChange?.(null); - }, [resizing, editor.timeline, element.id, track.id, onResizeStateChange, onSnapPointChange]); + }, [ + resizing, + editor.timeline, + element.id, + track.id, + onResizeStateChange, + onSnapPointChange, + ]); useEffect(() => { if (!resizing) return; |
