mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
The single-clip razor path guards splits with isSplitTimeWithinBounds, which keeps a SPLIT_BOUNDARY_EPSILON_S margin from each clip edge so a cut never produces a degenerate near-zero slice. The split-all path filtered with raw `splitTime > start && splitTime < end` instead, so it accepted cuts inside that margin (and on clips shorter than two epsilons that the single path always rejects), producing the very degenerate slice the epsilon exists to prevent. Extract the shared predicate canSplitElementAt and a selectSplittableElements helper, and route both razor paths through them so the two stay consistent. Adds unit coverage for the new helpers, including the regression where a sub-epsilon clip with an interior split time must not be selected. Co-authored-by: Carlos Alcaraz <193642530+calcarazgre646@users.noreply.github.com>
51 lines
1.7 KiB
TypeScript
51 lines
1.7 KiB
TypeScript
import type { TimelineElement } from "../player/store/playerStore";
|
|
|
|
export { buildPatchTarget, readFileContent } from "../hooks/timelineEditingHelpers";
|
|
|
|
/** Minimum distance (seconds) from clip boundaries to allow a split. */
|
|
export const SPLIT_BOUNDARY_EPSILON_S = 0.03;
|
|
|
|
/**
|
|
* True when splitTime leaves at least SPLIT_BOUNDARY_EPSILON_S on both sides
|
|
* of the cut. Inclusive at the epsilon offsets: the timeline canvas clamps
|
|
* edge clicks to exactly start/end ± epsilon, so the clamped value must pass.
|
|
*/
|
|
export function isSplitTimeWithinBounds(
|
|
splitTime: number,
|
|
clipStart: number,
|
|
clipDuration: number,
|
|
): boolean {
|
|
return (
|
|
splitTime >= clipStart + SPLIT_BOUNDARY_EPSILON_S &&
|
|
splitTime <= clipStart + clipDuration - SPLIT_BOUNDARY_EPSILON_S
|
|
);
|
|
}
|
|
|
|
export function canSplitElement(el: TimelineElement): boolean {
|
|
return (
|
|
!el.timelineLocked &&
|
|
el.timingSource !== "implicit" &&
|
|
!el.compositionSrc &&
|
|
!!el.duration &&
|
|
Number.isFinite(el.duration)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* True when `el` can be split AND `splitTime` lies within its boundary epsilon.
|
|
* Shared by the single-clip and split-all razor paths so both honor the same
|
|
* minimum-distance rule (split-all previously used raw `>`/`<`, letting cuts
|
|
* land inside the epsilon margin and produce a degenerate slice).
|
|
*/
|
|
export function canSplitElementAt(el: TimelineElement, splitTime: number): boolean {
|
|
return canSplitElement(el) && isSplitTimeWithinBounds(splitTime, el.start, el.duration);
|
|
}
|
|
|
|
/** Elements that the split-all razor action can cut at `splitTime`. */
|
|
export function selectSplittableElements(
|
|
elements: TimelineElement[],
|
|
splitTime: number,
|
|
): TimelineElement[] {
|
|
return elements.filter((el) => canSplitElementAt(el, splitTime));
|
|
}
|