fix: ensure timeline clips can be extended to longer than 5s

This commit is contained in:
Maze Winther
2025-07-08 00:42:46 +02:00
parent c0cc4c009e
commit ea59cc3950
3 changed files with 92 additions and 6 deletions

View File

@ -45,6 +45,7 @@ export function TimelineElement({
const { mediaItems } = useMediaStore(); const { mediaItems } = useMediaStore();
const { const {
updateElementTrim, updateElementTrim,
updateElementDuration,
removeElementFromTrack, removeElementFromTrack,
dragState, dragState,
splitElement, splitElement,
@ -67,6 +68,7 @@ export function TimelineElement({
track, track,
zoomLevel, zoomLevel,
onUpdateTrim: updateElementTrim, onUpdateTrim: updateElementTrim,
onUpdateDuration: updateElementDuration,
}); });
const effectiveDuration = const effectiveDuration =

View File

@ -1,5 +1,6 @@
import { useState, useEffect } from "react"; 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";
interface UseTimelineElementResizeProps { interface UseTimelineElementResizeProps {
element: TimelineElement; element: TimelineElement;
@ -11,6 +12,11 @@ interface UseTimelineElementResizeProps {
trimStart: number, trimStart: number,
trimEnd: number trimEnd: number
) => void; ) => void;
onUpdateDuration: (
trackId: string,
elementId: string,
duration: number
) => void;
} }
export function useTimelineElementResize({ export function useTimelineElementResize({
@ -18,8 +24,10 @@ export function useTimelineElementResize({
track, track,
zoomLevel, zoomLevel,
onUpdateTrim, onUpdateTrim,
onUpdateDuration,
}: UseTimelineElementResizeProps) { }: UseTimelineElementResizeProps) {
const [resizing, setResizing] = useState<ResizeState | null>(null); const [resizing, setResizing] = useState<ResizeState | null>(null);
const { mediaItems } = useMediaStore();
// Set up document-level mouse listeners during resize (like proper drag behavior) // Set up document-level mouse listeners during resize (like proper drag behavior)
useEffect(() => { useEffect(() => {
@ -60,6 +68,30 @@ export function useTimelineElementResize({
}); });
}; };
const canExtendElementDuration = () => {
// Text elements can always be extended
if (element.type === "text") {
return true;
}
// Media elements - check the media type
if (element.type === "media") {
const mediaItem = mediaItems.find((item) => item.id === element.mediaId);
if (!mediaItem) return false;
// Images can be extended (static content)
if (mediaItem.type === "image") {
return true;
}
// Videos and audio cannot be extended beyond their natural duration
// (no additional content exists)
return false;
}
return false;
};
const updateTrimFromMouseMove = (e: { clientX: number }) => { const updateTrimFromMouseMove = (e: { clientX: number }) => {
if (!resizing) return; if (!resizing) return;
@ -68,19 +100,48 @@ export function useTimelineElementResize({
const deltaTime = deltaX / (50 * zoomLevel); const deltaTime = deltaX / (50 * zoomLevel);
if (resizing.side === "left") { if (resizing.side === "left") {
// Left resize - only trim within original duration
const maxAllowed = element.duration - resizing.initialTrimEnd - 0.1; const maxAllowed = element.duration - resizing.initialTrimEnd - 0.1;
const calculated = resizing.initialTrimStart + deltaTime; const calculated = resizing.initialTrimStart + deltaTime;
const newTrimStart = Math.max(0, Math.min(maxAllowed, calculated)); const newTrimStart = Math.max(0, Math.min(maxAllowed, calculated));
onUpdateTrim(track.id, element.id, newTrimStart, resizing.initialTrimEnd); onUpdateTrim(track.id, element.id, newTrimStart, resizing.initialTrimEnd);
} else { } else {
// For right resize (expanding element), allow trimEnd to go to 0 but cap at element duration // Right resize - can extend duration for supported element types
const calculated = resizing.initialTrimEnd - deltaTime; 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); if (calculated < 0) {
// We're trying to extend beyond original duration
if (canExtendElementDuration()) {
// Extend the duration instead of reducing trimEnd further
const extensionNeeded = Math.abs(calculated);
const newDuration = element.duration + extensionNeeded;
const newTrimEnd = 0; // Reset trimEnd to 0 since we're extending
// Update duration first, then trim
onUpdateDuration(track.id, element.id, newDuration);
onUpdateTrim(
track.id,
element.id,
resizing.initialTrimStart,
newTrimEnd
);
} else {
// Can't extend - just set trimEnd to 0 (maximum possible extension)
onUpdateTrim(track.id, element.id, resizing.initialTrimStart, 0);
}
} else {
// Normal trimming within original 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
);
}
} }
}; };

View File

@ -87,6 +87,11 @@ interface TimelineStore {
trimStart: number, trimStart: number,
trimEnd: number trimEnd: number
) => void; ) => void;
updateElementDuration: (
trackId: string,
elementId: string,
duration: number
) => void;
updateElementStartTime: ( updateElementStartTime: (
trackId: string, trackId: string,
elementId: string, elementId: string,
@ -284,7 +289,9 @@ export const useTimelineStore = create<TimelineStore>((set, get) => {
removeTrack: (trackId) => { removeTrack: (trackId) => {
get().pushHistory(); get().pushHistory();
updateTracksAndSave(get()._tracks.filter((track) => track.id !== trackId)); updateTracksAndSave(
get()._tracks.filter((track) => track.id !== trackId)
);
}, },
addElementToTrack: (trackId, elementData) => { addElementToTrack: (trackId, elementData) => {
@ -439,6 +446,22 @@ export const useTimelineStore = create<TimelineStore>((set, get) => {
); );
}, },
updateElementDuration: (trackId, elementId, duration) => {
get().pushHistory();
updateTracksAndSave(
get()._tracks.map((track) =>
track.id === trackId
? {
...track,
elements: track.elements.map((element) =>
element.id === elementId ? { ...element, duration } : element
),
}
: track
)
);
},
updateElementStartTime: (trackId, elementId, startTime) => { updateElementStartTime: (trackId, elementId, startTime) => {
get().pushHistory(); get().pushHistory();
updateTracksAndSave( updateTracksAndSave(