fix: ensure timeline clips can be extended to longer than 5s
This commit is contained in:
@ -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 =
|
||||||
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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(
|
||||||
|
Reference in New Issue
Block a user