summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2025-07-07 23:03:35 +0200
committerMaze Winther <mazewinther@gmail.com>2025-07-07 23:03:35 +0200
commitacda7064bd0f328b954dcd91c852ad8ba5a3d5af (patch)
tree8675e1f89716077317c144e889b17c13de992e88
parentf3763b846543debeb3ebfdbbbee16f614931cb06 (diff)
downloadopencut-acda7064bd0f328b954dcd91c852ad8ba5a3d5af.tar.gz
fix: unable to resize elements to be bigger
-rw-r--r--apps/web/src/hooks/use-timeline-element-resize.ts186
1 files changed, 103 insertions, 83 deletions
diff --git a/apps/web/src/hooks/use-timeline-element-resize.ts b/apps/web/src/hooks/use-timeline-element-resize.ts
index b12087ea..3882a8f8 100644
--- a/apps/web/src/hooks/use-timeline-element-resize.ts
+++ b/apps/web/src/hooks/use-timeline-element-resize.ts
@@ -1,83 +1,103 @@
-import { useState } from "react";
-import { ResizeState, TimelineElement, TimelineTrack } from "@/types/timeline";
-
-interface UseTimelineElementResizeProps {
- element: TimelineElement;
- track: TimelineTrack;
- zoomLevel: number;
- onUpdateTrim: (
- trackId: string,
- elementId: string,
- trimStart: number,
- trimEnd: number
- ) => void;
-}
-
-export function useTimelineElementResize({
- element,
- track,
- zoomLevel,
- onUpdateTrim,
-}: UseTimelineElementResizeProps) {
- const [resizing, setResizing] = useState<ResizeState | null>(null);
-
- const handleResizeStart = (
- e: React.MouseEvent,
- elementId: string,
- side: "left" | "right"
- ) => {
- e.stopPropagation();
- e.preventDefault();
-
- setResizing({
- elementId,
- side,
- startX: e.clientX,
- initialTrimStart: element.trimStart,
- initialTrimEnd: element.trimEnd,
- });
- };
-
- const updateTrimFromMouseMove = (e: { clientX: number }) => {
- if (!resizing) return;
-
- const deltaX = e.clientX - resizing.startX;
- const deltaTime = deltaX / (50 * zoomLevel);
-
- if (resizing.side === "left") {
- const newTrimStart = Math.max(
- 0,
- Math.min(
- element.duration - element.trimEnd - 0.1,
- resizing.initialTrimStart + deltaTime
- )
- );
- onUpdateTrim(track.id, element.id, newTrimStart, element.trimEnd);
- } else {
- const newTrimEnd = Math.max(
- 0,
- Math.min(
- element.duration - element.trimStart - 0.1,
- resizing.initialTrimEnd - deltaTime
- )
- );
- onUpdateTrim(track.id, element.id, element.trimStart, newTrimEnd);
- }
- };
-
- const handleResizeMove = (e: React.MouseEvent) => {
- updateTrimFromMouseMove(e);
- };
-
- const handleResizeEnd = () => {
- setResizing(null);
- };
-
- return {
- resizing,
- isResizing: resizing !== null,
- handleResizeStart,
- handleResizeMove,
- handleResizeEnd,
- };
-}
+import { useState, useEffect } from "react";
+import { ResizeState, TimelineElement, TimelineTrack } from "@/types/timeline";
+
+interface UseTimelineElementResizeProps {
+ element: TimelineElement;
+ track: TimelineTrack;
+ zoomLevel: number;
+ onUpdateTrim: (
+ trackId: string,
+ elementId: string,
+ trimStart: number,
+ trimEnd: number
+ ) => void;
+}
+
+export function useTimelineElementResize({
+ element,
+ track,
+ zoomLevel,
+ onUpdateTrim,
+}: UseTimelineElementResizeProps) {
+ const [resizing, setResizing] = useState<ResizeState | null>(null);
+
+ // Set up document-level mouse listeners during resize (like proper drag behavior)
+ useEffect(() => {
+ if (!resizing) return;
+
+ const handleDocumentMouseMove = (e: MouseEvent) => {
+ updateTrimFromMouseMove({ clientX: e.clientX });
+ };
+
+ const handleDocumentMouseUp = () => {
+ handleResizeEnd();
+ };
+
+ // Add document-level listeners for proper drag behavior
+ document.addEventListener("mousemove", handleDocumentMouseMove);
+ document.addEventListener("mouseup", handleDocumentMouseUp);
+
+ return () => {
+ document.removeEventListener("mousemove", handleDocumentMouseMove);
+ document.removeEventListener("mouseup", handleDocumentMouseUp);
+ };
+ }, [resizing]); // Re-run when resizing state changes
+
+ const handleResizeStart = (
+ e: React.MouseEvent,
+ elementId: string,
+ side: "left" | "right"
+ ) => {
+ e.stopPropagation();
+ e.preventDefault();
+
+ setResizing({
+ elementId,
+ side,
+ startX: e.clientX,
+ initialTrimStart: element.trimStart,
+ initialTrimEnd: element.trimEnd,
+ });
+ };
+
+ const updateTrimFromMouseMove = (e: { clientX: number }) => {
+ if (!resizing) return;
+
+ const deltaX = e.clientX - resizing.startX;
+ // Reasonable sensitivity for resize operations - similar to timeline scale
+ const deltaTime = deltaX / (50 * zoomLevel);
+
+ if (resizing.side === "left") {
+ const maxAllowed = element.duration - resizing.initialTrimEnd - 0.1;
+ const calculated = resizing.initialTrimStart + deltaTime;
+ const newTrimStart = Math.max(0, Math.min(maxAllowed, calculated));
+
+ onUpdateTrim(track.id, element.id, newTrimStart, resizing.initialTrimEnd);
+ } else {
+ // For right resize (expanding element), allow trimEnd to go to 0 but cap at element duration
+ const calculated = resizing.initialTrimEnd - deltaTime;
+ // Prevent negative trim AND prevent trimEnd from exceeding element duration
+ const maxTrimEnd = element.duration - resizing.initialTrimStart - 0.1; // Leave at least 0.1s visible
+ const newTrimEnd = Math.max(0, Math.min(maxTrimEnd, calculated));
+
+ onUpdateTrim(track.id, element.id, resizing.initialTrimStart, newTrimEnd);
+ }
+ };
+
+ const handleResizeMove = (e: React.MouseEvent) => {
+ updateTrimFromMouseMove(e);
+ };
+
+ const handleResizeEnd = () => {
+ setResizing(null);
+ };
+
+ return {
+ resizing,
+ isResizing: resizing !== null,
+ handleResizeStart,
+ // Return empty handlers since we use document listeners now
+ handleResizeMove: () => {}, // Not used anymore
+ handleResizeEnd: () => {}, // Not used anymore
+ };
+}