mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-05 17:30:50 +00:00
* fix(studio): preserve composition playback continuity * feat(studio): drag compositions into the timeline * fix(studio): collapse expanded composition move aliases * fix(studio): make timeline cuts atomic * fix(studio): group inspector gesture history * test(studio): cover masked text selection * fix(studio): harden composition timeline reliability * fix(studio): satisfy CI source gates * fix(studio): harden composition mutation requests
55 lines
1.9 KiB
TypeScript
55 lines
1.9 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 {
|
|
const hasStableIdentity = Boolean(el.hfId || el.domId || el.selector);
|
|
const hasValidRate =
|
|
el.playbackRate == null || (Number.isFinite(el.playbackRate) && el.playbackRate > 0);
|
|
return (
|
|
!el.timelineLocked &&
|
|
el.timingSource !== "implicit" &&
|
|
hasStableIdentity &&
|
|
hasValidRate &&
|
|
!!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));
|
|
}
|