fix: make timeline element resizing frame based instead of pixel based

This commit is contained in:
Maze Winther
2025-07-31 23:47:10 +02:00
parent 60d4b0c3d6
commit b00d6d5fb6
@@ -2,6 +2,8 @@ import { useState, useEffect } from "react";
import { ResizeState, TimelineElement, TimelineTrack } from "@/types/timeline"; import { ResizeState, TimelineElement, TimelineTrack } from "@/types/timeline";
import { useMediaStore } from "@/stores/media-store"; import { useMediaStore } from "@/stores/media-store";
import { useTimelineStore } from "@/stores/timeline-store"; import { useTimelineStore } from "@/stores/timeline-store";
import { useProjectStore } from "@/stores/project-store";
import { snapTimeToFrame } from "@/constants/timeline-constants";
interface UseTimelineElementResizeProps { interface UseTimelineElementResizeProps {
element: TimelineElement; element: TimelineElement;
@@ -109,6 +111,10 @@ export function useTimelineElementResize({
// Reasonable sensitivity for resize operations - similar to timeline scale // Reasonable sensitivity for resize operations - similar to timeline scale
const deltaTime = deltaX / (50 * zoomLevel); const deltaTime = deltaX / (50 * zoomLevel);
// Get project FPS for frame snapping
const projectStore = useProjectStore.getState();
const projectFps = projectStore.activeProject?.fps || 30;
if (resizing.side === "left") { if (resizing.side === "left") {
// Left resize - different behavior for media vs text/image elements // Left resize - different behavior for media vs text/image elements
const maxAllowed = element.duration - resizing.initialTrimEnd - 0.1; const maxAllowed = element.duration - resizing.initialTrimEnd - 0.1;
@@ -116,9 +122,15 @@ export function useTimelineElementResize({
if (calculated >= 0) { if (calculated >= 0) {
// Normal trimming within available content // Normal trimming within available content
const newTrimStart = Math.min(maxAllowed, calculated); const newTrimStart = snapTimeToFrame(
Math.min(maxAllowed, calculated),
projectFps
);
const trimDelta = newTrimStart - resizing.initialTrimStart; const trimDelta = newTrimStart - resizing.initialTrimStart;
const newStartTime = element.startTime + trimDelta; const newStartTime = snapTimeToFrame(
element.startTime + trimDelta,
projectFps
);
updateElementTrim( updateElementTrim(
track.id, track.id,
@@ -135,8 +147,14 @@ export function useTimelineElementResize({
const extensionAmount = Math.abs(calculated); const extensionAmount = Math.abs(calculated);
const maxExtension = element.startTime; const maxExtension = element.startTime;
const actualExtension = Math.min(extensionAmount, maxExtension); const actualExtension = Math.min(extensionAmount, maxExtension);
const newStartTime = element.startTime - actualExtension; const newStartTime = snapTimeToFrame(
const newDuration = element.duration + actualExtension; element.startTime - actualExtension,
projectFps
);
const newDuration = snapTimeToFrame(
element.duration + actualExtension,
projectFps
);
// Keep trimStart at 0 and extend the element // Keep trimStart at 0 and extend the element
updateElementTrim( updateElementTrim(
@@ -152,7 +170,10 @@ export function useTimelineElementResize({
// Video/Audio: can't extend beyond original content - limit to trimStart = 0 // Video/Audio: can't extend beyond original content - limit to trimStart = 0
const newTrimStart = 0; const newTrimStart = 0;
const trimDelta = newTrimStart - resizing.initialTrimStart; const trimDelta = newTrimStart - resizing.initialTrimStart;
const newStartTime = element.startTime + trimDelta; const newStartTime = snapTimeToFrame(
element.startTime + trimDelta,
projectFps
);
updateElementTrim( updateElementTrim(
track.id, track.id,
@@ -173,7 +194,10 @@ export function useTimelineElementResize({
if (canExtendElementDuration()) { if (canExtendElementDuration()) {
// Extend the duration instead of reducing trimEnd further // Extend the duration instead of reducing trimEnd further
const extensionNeeded = Math.abs(calculated); const extensionNeeded = Math.abs(calculated);
const newDuration = element.duration + extensionNeeded; const newDuration = snapTimeToFrame(
element.duration + extensionNeeded,
projectFps
);
const newTrimEnd = 0; // Reset trimEnd to 0 since we're extending const newTrimEnd = 0; // Reset trimEnd to 0 since we're extending
// Update duration first, then trim // Update duration first, then trim
@@ -198,7 +222,10 @@ export function useTimelineElementResize({
} else { } else {
// Normal trimming within original duration // Normal trimming within original duration
const maxTrimEnd = element.duration - resizing.initialTrimStart - 0.1; // Leave at least 0.1s visible const maxTrimEnd = element.duration - resizing.initialTrimStart - 0.1; // Leave at least 0.1s visible
const newTrimEnd = Math.max(0, Math.min(maxTrimEnd, calculated)); const newTrimEnd = snapTimeToFrame(
Math.max(0, Math.min(maxTrimEnd, calculated)),
projectFps
);
updateElementTrim( updateElementTrim(
track.id, track.id,