feat: add ripple editing functionality to timeline store

This commit is contained in:
Anwarul Islam 2025-07-15 05:16:22 +06:00
parent d040ca53cb
commit 8b1c4de01b
1 changed files with 150 additions and 1 deletions

View File

@ -51,10 +51,13 @@ interface TimelineStore {
// Snapping settings
snappingEnabled: boolean;
// Snapping actions
toggleSnapping: () => void;
// Ripple editing mode
rippleEditingEnabled: boolean;
setRippleEditingEnabled: (enabled: boolean) => void;
// Multi-selection
selectedElements: { trackId: string; elementId: string }[];
selectElement: (trackId: string, elementId: string, multi?: boolean) => void;
@ -139,6 +142,17 @@ interface TimelineStore {
newFile: File
) => Promise<boolean>;
// Ripple editing functions
updateElementStartTimeWithRipple: (
trackId: string,
elementId: string,
newStartTime: number
) => void;
removeElementFromTrackWithRipple: (
trackId: string,
elementId: string
) => void;
// Computed values
getTotalDuration: () => number;
@ -226,6 +240,7 @@ export const useTimelineStore = create<TimelineStore>((set, get) => {
history: [],
redoStack: [],
selectedElements: [],
rippleEditingEnabled: false,
// Snapping settings defaults
snappingEnabled: true,
@ -293,6 +308,9 @@ export const useTimelineStore = create<TimelineStore>((set, get) => {
setSelectedElements: (elements) => set({ selectedElements: elements }),
setRippleEditingEnabled: (enabled) =>
set({ rippleEditingEnabled: enabled }),
addTrack: (type) => {
get().pushHistory();
@ -1125,5 +1143,136 @@ export const useTimelineStore = create<TimelineStore>((set, get) => {
});
return true;
},
// Ripple editing functions
updateElementStartTimeWithRipple: (trackId, elementId, newStartTime) => {
const { _tracks, rippleEditingEnabled } = get();
if (!rippleEditingEnabled) {
// If ripple editing is disabled, use regular update
get().updateElementStartTime(trackId, elementId, newStartTime);
return;
}
const track = _tracks.find((t) => t.id === trackId);
const element = track?.elements.find((e) => e.id === elementId);
if (!element || !track) return;
get().pushHistory();
const oldStartTime = element.startTime;
const oldEndTime =
element.startTime +
(element.duration - element.trimStart - element.trimEnd);
const newEndTime =
newStartTime + (element.duration - element.trimStart - element.trimEnd);
const timeDelta = newStartTime - oldStartTime;
// Update all tracks, not just the current one
const updatedTracks = _tracks.map((currentTrack) => {
const updatedElements = currentTrack.elements.map((currentElement) => {
if (currentElement.id === elementId && currentTrack.id === trackId) {
// Update the moved element
return { ...currentElement, startTime: newStartTime };
}
// For ripple editing, we need to move elements that come after the moved element
// across all tracks to maintain sync
const currentElementStart = currentElement.startTime;
const currentElementEnd =
currentElement.startTime +
(currentElement.duration -
currentElement.trimStart -
currentElement.trimEnd);
// If moving element to the right (positive delta)
if (timeDelta > 0) {
// Move elements that start after the original position of the moved element
if (currentElementStart >= oldEndTime) {
return {
...currentElement,
startTime: currentElementStart + timeDelta,
};
}
}
// If moving element to the left (negative delta)
else if (timeDelta < 0) {
// Move elements that start after the new position of the moved element
if (
currentElementStart >= newEndTime &&
currentElementStart >= oldStartTime
) {
return {
...currentElement,
startTime: Math.max(0, currentElementStart + timeDelta),
};
}
}
return currentElement;
});
return { ...currentTrack, elements: updatedElements };
});
updateTracksAndSave(updatedTracks);
},
removeElementFromTrackWithRipple: (trackId, elementId) => {
const { _tracks, rippleEditingEnabled } = get();
if (!rippleEditingEnabled) {
// If ripple editing is disabled, use regular removal
get().removeElementFromTrack(trackId, elementId);
return;
}
const track = _tracks.find((t) => t.id === trackId);
const element = track?.elements.find((e) => e.id === elementId);
if (!element || !track) return;
get().pushHistory();
const elementStartTime = element.startTime;
const elementDuration =
element.duration - element.trimStart - element.trimEnd;
const elementEndTime = elementStartTime + elementDuration;
// Remove the element and shift all elements that come after it
const updatedTracks = _tracks
.map((currentTrack) => {
const updatedElements = currentTrack.elements
.filter((currentElement) => {
// Remove the target element
if (
currentElement.id === elementId &&
currentTrack.id === trackId
) {
return false;
}
return true;
})
.map((currentElement) => {
// Shift elements that start after the removed element
if (currentElement.startTime >= elementEndTime) {
return {
...currentElement,
startTime: Math.max(
0,
currentElement.startTime - elementDuration
),
};
}
return currentElement;
});
return { ...currentTrack, elements: updatedElements };
})
.filter((track) => track.elements.length > 0 || track.isMain);
updateTracksAndSave(updatedTracks);
},
};
});