mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 09:16:55 +00:00
feat(studio): export AudioWaveform, ImageThumbnail, useRenderClipContent (#4301)
@hyperframes/studio's public index doesn't export the clip-content thumbnail primitives, forcing a deep import. Desktop's Canva timeline lane needs to draw audio waveforms and image thumbnails from a host app without reaching into the package's internals. Adds a test that mounts each export through the package name the way a host app would (not a relative import), so a future removal or accidental un-export fails a test instead of a downstream deep-import workaround.
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
// @vitest-environment happy-dom
|
||||
// Imports the clip-content thumbnail primitives the way a host app does: by package name.
|
||||
// Desktop's timeline lane draws audio waveforms and image thumbnails without a deep import.
|
||||
import { act } from "react";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { installReactActEnvironment, mountReactHarness } from "./hooks/domSelectionTestHarness";
|
||||
|
||||
const { leaseSpy } = vi.hoisted(() => ({
|
||||
leaseSpy: vi.fn((_request: unknown) => ({ status: "loading" as const })),
|
||||
}));
|
||||
|
||||
vi.mock("./hooks/useThumbnailLease", () => ({
|
||||
useThumbnailLease: leaseSpy,
|
||||
}));
|
||||
|
||||
import { AudioWaveform, ImageThumbnail, useRenderClipContent } from "@hyperframes/studio";
|
||||
|
||||
installReactActEnvironment();
|
||||
|
||||
afterEach(() => {
|
||||
leaseSpy.mockClear();
|
||||
document.body.innerHTML = "";
|
||||
});
|
||||
|
||||
describe("clip content package exports", () => {
|
||||
it("mounts AudioWaveform and leases waveform decoding", async () => {
|
||||
const root = mountReactHarness(
|
||||
<AudioWaveform
|
||||
audioUrl="/media/voice.wav"
|
||||
label=""
|
||||
labelColor="#fff"
|
||||
projectId="project-a"
|
||||
sessionEpoch={9}
|
||||
priority="interaction"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(leaseSpy).toHaveBeenCalledWith(expect.objectContaining({ kind: "waveform" }));
|
||||
await act(async () => root.unmount());
|
||||
});
|
||||
|
||||
it("mounts ImageThumbnail and leases the still frame", async () => {
|
||||
const root = mountReactHarness(
|
||||
<ImageThumbnail imageSrc="/media/frame.png" label="" labelColor="#fff" />,
|
||||
);
|
||||
|
||||
expect(leaseSpy).toHaveBeenCalledWith(expect.objectContaining({ kind: "image" }));
|
||||
await act(async () => root.unmount());
|
||||
});
|
||||
|
||||
it("exposes useRenderClipContent as a hook a host can call", () => {
|
||||
expect(typeof useRenderClipContent).toBe("function");
|
||||
});
|
||||
});
|
||||
@@ -97,7 +97,7 @@ function renderAudioClip(
|
||||
});
|
||||
}
|
||||
|
||||
interface UseRenderClipContentOptions {
|
||||
export interface UseRenderClipContentOptions {
|
||||
projectIdRef: { current: string | null };
|
||||
compIdToSrc: Map<string, string>;
|
||||
activePreviewUrl: string | null;
|
||||
|
||||
@@ -77,6 +77,16 @@ export {
|
||||
export { TimelineProvider, useTimelineContext } from "./player/components/TimelineProvider";
|
||||
export type { TimelineTheme } from "./player/components/timelineTheme";
|
||||
|
||||
// Clip content thumbnails: used by a host rendering its own timeline lane.
|
||||
export { AudioWaveform } from "./player/components/AudioWaveform";
|
||||
export type { AudioWaveformProps } from "./player/components/AudioWaveform";
|
||||
export { ImageThumbnail } from "./player/components/ImageThumbnail";
|
||||
export type { ImageThumbnailProps } from "./player/components/ImageThumbnail";
|
||||
export { useRenderClipContent } from "./hooks/useRenderClipContent";
|
||||
export type { UseRenderClipContentOptions } from "./hooks/useRenderClipContent";
|
||||
export type { ThumbnailPriority } from "./player/lib/thumbnailScheduler";
|
||||
export type { TimelineClipRenderContext } from "./player/components/TimelineTypes";
|
||||
|
||||
// Host overlays: draw over the preview in composition coordinates (see EditorShellProps.gestureOverlay)
|
||||
export { usePreviewCompositionRect } from "./components/editor/usePreviewCompositionRect";
|
||||
export type { PreviewCompositionRect } from "./components/editor/usePreviewCompositionRect";
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useThumbnailLease } from "../../hooks/useThumbnailLease";
|
||||
import { createThumbnailKey, type ThumbnailPriority } from "../lib/thumbnailScheduler";
|
||||
import { decimatePeaks, loudnessToOpacity } from "./audioWaveformPeaks";
|
||||
|
||||
interface AudioWaveformProps {
|
||||
export interface AudioWaveformProps {
|
||||
audioUrl: string;
|
||||
waveformUrl?: string;
|
||||
label: string;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createThumbnailKey, type ThumbnailPriority } from "../lib/thumbnailSche
|
||||
import { TIMELINE_VIEWPORT_BUDGETS } from "../lib/timelineViewportBudgets";
|
||||
import { computeThumbnailStrip, probeImageAspect } from "./thumbnailUtils";
|
||||
|
||||
interface ImageThumbnailProps {
|
||||
export interface ImageThumbnailProps {
|
||||
imageSrc: string;
|
||||
label: string;
|
||||
labelColor: string;
|
||||
|
||||
Reference in New Issue
Block a user