mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-04 07:19:52 +00:00
fix(studio): resolve recent timeline regressions
This commit is contained in:
@@ -228,6 +228,9 @@ function dispatchPlainKey(event: KeyboardEvent, key: string, cb: HotkeyCallbacks
|
||||
}
|
||||
|
||||
if (event.key === "s" && !event.altKey) {
|
||||
// Reserve bare `s` for Split even when the current selection cannot split,
|
||||
// so secondary listeners do not reinterpret the same key as Snap toggle.
|
||||
event.preventDefault();
|
||||
const { selectedElementId, elements, currentTime } = usePlayerStore.getState();
|
||||
if (selectedElementId) {
|
||||
const el = elements.find((e) => (e.key ?? e.id) === selectedElementId);
|
||||
@@ -237,7 +240,6 @@ function dispatchPlainKey(event: KeyboardEvent, key: string, cb: HotkeyCallbacks
|
||||
currentTime > el.start &&
|
||||
currentTime < el.start + el.duration
|
||||
) {
|
||||
event.preventDefault();
|
||||
void cb.handleTimelineElementSplit(el, currentTime);
|
||||
return;
|
||||
}
|
||||
@@ -245,7 +247,6 @@ function dispatchPlainKey(event: KeyboardEvent, key: string, cb: HotkeyCallbacks
|
||||
// that isn't in the raw `elements` list, so the s-key can't resolve them.
|
||||
// Nudge toward the razor tool instead of failing silently.
|
||||
if (!el && selectedElementId.includes("#")) {
|
||||
event.preventDefault();
|
||||
cb.showToast("Use the razor tool (B) to split clips inside a sub-composition", "info");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import React, { act, isValidElement, type ReactNode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { AudioWaveform } from "../player/components/AudioWaveform";
|
||||
import type { TimelineElement } from "../player/store/playerStore";
|
||||
import { normalizeCompositionSrc } from "./useRenderClipContent";
|
||||
import { useRenderClipContent } from "./useRenderClipContent";
|
||||
|
||||
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
|
||||
afterEach(() => {
|
||||
document.body.innerHTML = "";
|
||||
});
|
||||
|
||||
describe("normalizeCompositionSrc", () => {
|
||||
const origin = "http://localhost:5190";
|
||||
@@ -48,3 +61,43 @@ describe("normalizeCompositionSrc", () => {
|
||||
expect(result).toBe("compositions/scenes/hero.html");
|
||||
});
|
||||
});
|
||||
|
||||
describe("useRenderClipContent", () => {
|
||||
function renderClipContent(el: TimelineElement): ReactNode {
|
||||
const host = document.createElement("div");
|
||||
document.body.append(host);
|
||||
const root = createRoot(host);
|
||||
let content: ReactNode = null;
|
||||
|
||||
function Harness() {
|
||||
const render = useRenderClipContent({
|
||||
projectIdRef: { current: "my-project" },
|
||||
compIdToSrc: new Map(),
|
||||
activePreviewUrl: "/api/projects/my-project/preview",
|
||||
effectiveTimelineDuration: 12,
|
||||
});
|
||||
content = render(el, { clip: "#222", label: "#fff" });
|
||||
return null;
|
||||
}
|
||||
|
||||
act(() => {
|
||||
root.render(React.createElement(Harness));
|
||||
});
|
||||
act(() => root.unmount());
|
||||
return content;
|
||||
}
|
||||
|
||||
it("renders audio clips as waveforms even when a composition preview URL is active", () => {
|
||||
const content = renderClipContent({
|
||||
id: "voiceover",
|
||||
tag: "audio",
|
||||
start: 1,
|
||||
duration: 4,
|
||||
track: 1,
|
||||
src: "assets/voiceover.mp3",
|
||||
});
|
||||
|
||||
expect(isValidElement(content)).toBe(true);
|
||||
if (isValidElement(content)) expect(content.type).toBe(AudioWaveform);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -110,6 +110,13 @@ export function useRenderClipContent({
|
||||
});
|
||||
}
|
||||
|
||||
// Audio clips — waveform visualization. Resolve these before the generic
|
||||
// activePreviewUrl thumbnail branch; audio rows need waveform data, not a
|
||||
// captured frame from the currently drilled composition preview.
|
||||
if (el.tag === "audio") {
|
||||
return renderAudioClip(el, pid, style.label);
|
||||
}
|
||||
|
||||
// When drilled into a composition, render all inner elements via
|
||||
// CompositionThumbnail at their start time — most accurate visual.
|
||||
if (activePreviewUrl && el.duration > 0) {
|
||||
@@ -131,11 +138,6 @@ export function useRenderClipContent({
|
||||
el.duration < effectiveTimelineDuration * 0.92 &&
|
||||
!/(backdrop|background|overlay|scrim|mask)/i.test(el.id);
|
||||
|
||||
// Audio clips — waveform visualization
|
||||
if (el.tag === "audio") {
|
||||
return renderAudioClip(el, pid, style.label);
|
||||
}
|
||||
|
||||
if ((el.tag === "video" || el.tag === "img") && el.src) {
|
||||
const mediaSrc = el.src.startsWith("http")
|
||||
? el.src
|
||||
|
||||
Reference in New Issue
Block a user