mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
fix(studio): keep the host row when drilling into a sub-composition
The sub-composition drill-in replaced the host row with its children. Since expansion is also driven by the playhead alone (paused auto-expand), an ordinary seek into a sub-composition made the host row disappear, taking its keyframe lane with it: diamonds render per row from keyframeCache.get( elementKey), so no row means no diamonds. Reproduced live with no drag at all, seek 0 gave 3 diamonds, seek 7.68 gave 0, seek 0.2 gave 3. Make the expansion additive instead. The host row stays and its children are appended directly below it. The synthetic fractional lanes already used for children sit strictly between the host's lane and the next integer, so the host keeps its own row without colliding with anything. The time-keyed auto-expand itself is unchanged.
This commit is contained in:
@@ -240,8 +240,73 @@ describe("buildExpandedElements", () => {
|
|||||||
expect(pills[0]!.duration).toBe(6);
|
expect(pills[0]!.duration).toBe(6);
|
||||||
expect(pills[0]!.sourceFile).toBe("scene.html");
|
expect(pills[0]!.sourceFile).toBe("scene.html");
|
||||||
expect(pills.map((pill) => pill.stackingContextId)).toEqual(["css:0.0", "css:0.1", "css:0.1"]);
|
expect(pills.map((pill) => pill.stackingContextId)).toEqual(["css:0.0", "css:0.1", "css:0.1"]);
|
||||||
// The host row is replaced by its children.
|
// The host row survives the expansion; its children are added under it.
|
||||||
expect(out.some((e) => e.domId === "scene-host")).toBe(false);
|
expect(out.some((e) => e.id === "scene-host")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// fallow-ignore-next-line code-duplication
|
||||||
|
it("keeps the host row and appends its children directly below it", () => {
|
||||||
|
const elements = [
|
||||||
|
el({
|
||||||
|
id: "s3",
|
||||||
|
domId: "s3",
|
||||||
|
key: "index.html#s3",
|
||||||
|
start: 16,
|
||||||
|
duration: 7,
|
||||||
|
compositionSrc: "stats.html",
|
||||||
|
}),
|
||||||
|
el({ id: "outro", start: 23, duration: 3, track: 1 }),
|
||||||
|
];
|
||||||
|
const manifest = [
|
||||||
|
clip({ id: "s3", start: 16, duration: 7, compositionSrc: "stats.html" }),
|
||||||
|
clip({ id: "stat-1", start: 16.5, duration: 5 }),
|
||||||
|
clip({ id: "stat-2", start: 16.9, duration: 5 }),
|
||||||
|
];
|
||||||
|
const parentMap = new Map([
|
||||||
|
["stat-1", "s3"],
|
||||||
|
["stat-2", "s3"],
|
||||||
|
]);
|
||||||
|
|
||||||
|
const out = buildExpandedElements(elements, manifest, parentMap, "s3", "s3");
|
||||||
|
|
||||||
|
const hostIndex = out.findIndex((e) => e.id === "s3");
|
||||||
|
expect(hostIndex).toBeGreaterThanOrEqual(0);
|
||||||
|
// Host row untouched (same key → same keyframe lane), children nested under it.
|
||||||
|
expect(out[hostIndex]!.key).toBe("index.html#s3");
|
||||||
|
expect(out[hostIndex]!.track).toBe(0);
|
||||||
|
expect(out[hostIndex + 1]!.domId).toBe("stat-1");
|
||||||
|
expect(out[hostIndex + 2]!.domId).toBe("stat-2");
|
||||||
|
// Exactly one more row than the old substitution behaviour (host + 2 children + outro).
|
||||||
|
expect(out).toHaveLength(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps the host row present at every playhead position (keyframe lane repro)", () => {
|
||||||
|
// Live repro with no drag at all: seek 0 gave 3 diamonds, seek 7.68 gave 0,
|
||||||
|
// seek 0.2 gave 3. Diamonds render per row from keyframeCache.get(elementKey),
|
||||||
|
// so the whole lane went with the host row whenever the paused drill-in
|
||||||
|
// substituted it for its children.
|
||||||
|
const elements = [
|
||||||
|
el({ id: "scene", domId: "scene", key: "index.html#scene", start: 0, duration: 12 }),
|
||||||
|
];
|
||||||
|
const manifest = [
|
||||||
|
clip({ id: "scene", start: 0, duration: 12, compositionSrc: "scene.html" }),
|
||||||
|
clip({ id: "headline", start: 0, duration: 12 }),
|
||||||
|
];
|
||||||
|
const parentMap = new Map([["headline", "scene"]]);
|
||||||
|
|
||||||
|
for (const currentTime of [0, 7.68, 0.2]) {
|
||||||
|
const rawId = resolveTimelineExpansionRawId({
|
||||||
|
selectedElementId: null,
|
||||||
|
isPlaying: false,
|
||||||
|
currentTime,
|
||||||
|
manifest,
|
||||||
|
parentMap,
|
||||||
|
});
|
||||||
|
const rows = rawId
|
||||||
|
? buildExpandedElements(elements, manifest, parentMap, rawId, rawId)
|
||||||
|
: elements;
|
||||||
|
expect(rows.map((row) => row.key)).toContain("index.html#scene");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Regression: DOM-only children were synthesized against the TOP-LEVEL element
|
// Regression: DOM-only children were synthesized against the TOP-LEVEL element
|
||||||
|
|||||||
@@ -284,9 +284,16 @@ export function buildExpandedElements(
|
|||||||
);
|
);
|
||||||
if (expanded.length === 0) return filterToTopLevel(elements, parentMap);
|
if (expanded.length === 0) return filterToTopLevel(elements, parentMap);
|
||||||
|
|
||||||
|
// ADDITIVE drill-in: the host row stays and its children are appended under
|
||||||
|
// it. Expansion is also triggered by the playhead alone (paused auto-expand),
|
||||||
|
// so substituting the host row made it vanish on an ordinary seek — and with
|
||||||
|
// it the host's keyframe lane, since diamonds render per row from
|
||||||
|
// `keyframeCache.get(elementKey)`. The synthetic fractional lanes above sit
|
||||||
|
// strictly between the host's lane and the next integer, so the children have
|
||||||
|
// their own rows without the host having to give up its own.
|
||||||
return elements
|
return elements
|
||||||
.filter((el) => (el.key ?? el.id) === parentKey || !parentMap.has(el.domId ?? el.id))
|
.filter((el) => (el.key ?? el.id) === parentKey || !parentMap.has(el.domId ?? el.id))
|
||||||
.flatMap((el) => ((el.key ?? el.id) === parentKey ? expanded : [el]));
|
.flatMap((el) => ((el.key ?? el.id) === parentKey ? [el, ...expanded] : [el]));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useExpandedTimelineElements(): TimelineElement[] {
|
export function useExpandedTimelineElements(): TimelineElement[] {
|
||||||
|
|||||||
Reference in New Issue
Block a user