mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
fix(studio): accept razor splits at the canvas clamp boundary (#1340)
This commit is contained in:
@@ -7,7 +7,7 @@ import {
|
||||
canSplitElement,
|
||||
buildPatchTarget,
|
||||
readFileContent,
|
||||
SPLIT_BOUNDARY_EPSILON_S,
|
||||
isSplitTimeWithinBounds,
|
||||
} from "../utils/timelineElementSplit";
|
||||
import type { RecordEditInput } from "./useTimelineEditing";
|
||||
|
||||
@@ -171,12 +171,7 @@ export function useRazorSplit({
|
||||
const pid = projectIdRef.current;
|
||||
if (!pid || !canSplitElement(element)) return;
|
||||
|
||||
const clipStart = element.start;
|
||||
const clipEnd = element.start + element.duration;
|
||||
if (
|
||||
splitTime <= clipStart + SPLIT_BOUNDARY_EPSILON_S ||
|
||||
splitTime >= clipEnd - SPLIT_BOUNDARY_EPSILON_S
|
||||
) {
|
||||
if (!isSplitTimeWithinBounds(splitTime, element.start, element.duration)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { SPLIT_BOUNDARY_EPSILON_S, isSplitTimeWithinBounds } from "./timelineElementSplit";
|
||||
|
||||
describe("isSplitTimeWithinBounds", () => {
|
||||
const start = 1;
|
||||
const duration = 4;
|
||||
const end = start + duration;
|
||||
|
||||
it("accepts the exact lower clamp boundary", () => {
|
||||
// The timeline canvas clamps an edge click to exactly
|
||||
// start + SPLIT_BOUNDARY_EPSILON_S, so that value must be splittable.
|
||||
expect(isSplitTimeWithinBounds(start + SPLIT_BOUNDARY_EPSILON_S, start, duration)).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts the exact upper clamp boundary", () => {
|
||||
expect(
|
||||
isSplitTimeWithinBounds(start + duration - SPLIT_BOUNDARY_EPSILON_S, start, duration),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts an interior split time", () => {
|
||||
expect(isSplitTimeWithinBounds(3, start, duration)).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects times at or outside the clip edges", () => {
|
||||
expect(isSplitTimeWithinBounds(start, start, duration)).toBe(false);
|
||||
expect(isSplitTimeWithinBounds(end, start, duration)).toBe(false);
|
||||
expect(isSplitTimeWithinBounds(start - 1, start, duration)).toBe(false);
|
||||
expect(isSplitTimeWithinBounds(end + 1, start, duration)).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects times inside the epsilon margins", () => {
|
||||
expect(isSplitTimeWithinBounds(start + SPLIT_BOUNDARY_EPSILON_S / 2, start, duration)).toBe(
|
||||
false,
|
||||
);
|
||||
expect(isSplitTimeWithinBounds(end - SPLIT_BOUNDARY_EPSILON_S / 2, start, duration)).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects every time on a clip shorter than two epsilons", () => {
|
||||
// Math.max(min, Math.min(max, t)) collapses to min when the clip is too
|
||||
// short for the clamp range; that collapsed value must still be rejected.
|
||||
const shortDuration = SPLIT_BOUNDARY_EPSILON_S;
|
||||
expect(isSplitTimeWithinBounds(start + SPLIT_BOUNDARY_EPSILON_S, start, shortDuration)).toBe(
|
||||
false,
|
||||
);
|
||||
expect(isSplitTimeWithinBounds(start + shortDuration / 2, start, shortDuration)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -5,6 +5,22 @@ export { buildPatchTarget, readFileContent } from "../hooks/timelineEditingHelpe
|
||||
/** 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 &&
|
||||
|
||||
Reference in New Issue
Block a user