summaryrefslogtreecommitdiff
path: root/apps/web/src/hooks/timeline/element/use-element-resize.ts
diff options
context:
space:
mode:
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.ts314
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;