mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
feat(studio): add HDR warning banner and Apply-to-scope row, completing FlatColorGradingSection
Also adds a not-busy click assertion for the Apply button so onApplyToScope is exercised end-to-end, not just its disabled state. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
80dc7c49f6
commit
cc07c84a31
@@ -370,3 +370,59 @@ describe("FlatColorGradingSection — Effects", () => {
|
|||||||
act(() => root.unmount());
|
act(() => root.unmount());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("FlatColorGradingSection — HDR banner and Apply scope", () => {
|
||||||
|
it("shows the HDR banner only when mediaMetadata reports an HDR source", () => {
|
||||||
|
const { host, root } = renderInto(
|
||||||
|
<FlatColorGradingSection
|
||||||
|
{...neutralPropsBase()}
|
||||||
|
mediaMetadata={{
|
||||||
|
kind: "video",
|
||||||
|
color: { dynamicRange: "hdr", hdrTransfer: "pq", label: "HDR10", isHdr: true },
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
expect(host.textContent).toContain("SDR preview");
|
||||||
|
act(() => root.unmount());
|
||||||
|
});
|
||||||
|
|
||||||
|
it("omits the HDR banner for SDR media", () => {
|
||||||
|
const { host, root } = renderInto(
|
||||||
|
<FlatColorGradingSection
|
||||||
|
{...neutralPropsBase()}
|
||||||
|
mediaMetadata={{
|
||||||
|
kind: "video",
|
||||||
|
color: { dynamicRange: "sdr", hdrTransfer: null, label: "SDR", isHdr: false },
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
expect(host.textContent).not.toContain("SDR preview");
|
||||||
|
act(() => root.unmount());
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fires onApplyToScope from the Apply button, respecting applyBusy", () => {
|
||||||
|
const onApplyToScope = vi.fn();
|
||||||
|
const { host, root } = renderInto(
|
||||||
|
<FlatColorGradingSection {...neutralPropsBase()} onApplyToScope={onApplyToScope} applyBusy />,
|
||||||
|
);
|
||||||
|
const applyButton = host.querySelector<HTMLButtonElement>('[data-flat-grade-apply="true"]');
|
||||||
|
expect(applyButton?.disabled).toBe(true);
|
||||||
|
act(() => root.unmount());
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fires onApplyToScope exactly once when the Apply button is clicked while not busy", () => {
|
||||||
|
const onApplyToScope = vi.fn();
|
||||||
|
const { host, root } = renderInto(
|
||||||
|
<FlatColorGradingSection
|
||||||
|
{...neutralPropsBase()}
|
||||||
|
onApplyToScope={onApplyToScope}
|
||||||
|
applyBusy={false}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
const applyButton = host.querySelector<HTMLButtonElement>('[data-flat-grade-apply="true"]');
|
||||||
|
expect(applyButton?.disabled).toBe(false);
|
||||||
|
act(() => applyButton?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
||||||
|
expect(onApplyToScope).toHaveBeenCalledTimes(1);
|
||||||
|
act(() => root.unmount());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -138,18 +138,39 @@ const EFFECT_SLIDERS: Array<{ key: HfColorGradingEffectKey; label: string }> = [
|
|||||||
{ key: "pixelate", label: "Pixelate" },
|
{ key: "pixelate", label: "Pixelate" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
function HdrBanner({ metadata }: { metadata: MediaMetadata | null }) {
|
||||||
|
if (metadata?.color.dynamicRange !== "hdr") return null;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-flat-grade-hdr-banner="true"
|
||||||
|
className="rounded-md border border-amber-500/30 bg-amber-500/10 px-2.5 py-1.5 text-[10px] leading-4 text-amber-100"
|
||||||
|
>
|
||||||
|
<div className="mb-0.5 flex items-center justify-between gap-2">
|
||||||
|
<span className="font-semibold">{metadata.color.label} source</span>
|
||||||
|
<span className="rounded bg-amber-400/20 px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wide text-amber-100">
|
||||||
|
SDR preview
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-amber-100/80">
|
||||||
|
These controls use the current SDR shader preview path. Render may stay HDR-tagged, but this
|
||||||
|
is not true HDR color grading yet.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// fallow-ignore-next-line complexity
|
// fallow-ignore-next-line complexity
|
||||||
export function FlatColorGradingSection({
|
export function FlatColorGradingSection({
|
||||||
grading,
|
grading,
|
||||||
assets,
|
assets,
|
||||||
onImportAssets,
|
onImportAssets,
|
||||||
onCommitColorGrading,
|
onCommitColorGrading,
|
||||||
applyScope: _applyScope,
|
applyScope,
|
||||||
applyBusy: _applyBusy,
|
applyBusy,
|
||||||
onSetApplyScope: _onSetApplyScope,
|
onSetApplyScope,
|
||||||
onApplyToScope: _onApplyToScope,
|
onApplyToScope,
|
||||||
onApplyScopeAvailable: _onApplyScopeAvailable,
|
onApplyScopeAvailable,
|
||||||
mediaMetadata: _mediaMetadata,
|
mediaMetadata,
|
||||||
}: {
|
}: {
|
||||||
grading: NormalizedHfColorGrading;
|
grading: NormalizedHfColorGrading;
|
||||||
assets: string[];
|
assets: string[];
|
||||||
@@ -218,6 +239,7 @@ export function FlatColorGradingSection({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
|
<HdrBanner metadata={mediaMetadata} />
|
||||||
<div data-flat-grade-preset="true" className="flex min-h-[30px] items-center justify-between">
|
<div data-flat-grade-preset="true" className="flex min-h-[30px] items-center justify-between">
|
||||||
<span className="text-[11px] text-panel-text-2">Preset</span>
|
<span className="text-[11px] text-panel-text-2">Preset</span>
|
||||||
<FlatSelectRow
|
<FlatSelectRow
|
||||||
@@ -424,6 +446,32 @@ export function FlatColorGradingSection({
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{onApplyScopeAvailable && (
|
||||||
|
<div className="flex items-center justify-between gap-2 border-t border-panel-hairline pt-1.5">
|
||||||
|
<span className="flex items-center gap-1.5 text-[11px] text-panel-text-2">
|
||||||
|
Copy grade to
|
||||||
|
<select
|
||||||
|
value={applyScope}
|
||||||
|
onChange={(e) => onSetApplyScope(e.target.value as "source-file" | "project")}
|
||||||
|
disabled={applyBusy}
|
||||||
|
className="bg-transparent font-mono text-[11px] text-panel-text-0 outline-none disabled:opacity-50"
|
||||||
|
>
|
||||||
|
<option value="source-file">Current file media</option>
|
||||||
|
<option value="project">All project media</option>
|
||||||
|
</select>
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-flat-grade-apply="true"
|
||||||
|
disabled={applyBusy}
|
||||||
|
onClick={onApplyToScope}
|
||||||
|
className="text-[11px] font-medium text-panel-accent hover:text-panel-accent/80 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{applyBusy ? "Applying" : "Apply"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user