mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
feat(studio): add the 10 Adjust sliders to FlatColorGradingSection
This commit is contained in:
@@ -183,3 +183,98 @@ describe("FlatColorGradingSection — Preset + LUT", () => {
|
||||
act(() => root.unmount());
|
||||
});
|
||||
});
|
||||
|
||||
describe("FlatColorGradingSection — Adjust sliders", () => {
|
||||
it("renders all 10 adjust rows with a center tick, formatting exposure distinctly from percentage sliders", () => {
|
||||
const { host, root } = renderInto(<FlatColorGradingSection {...neutralPropsBase()} />);
|
||||
const adjustRows = host.querySelectorAll('[data-flat-grade-adjust="true"]');
|
||||
expect(adjustRows).toHaveLength(10);
|
||||
for (const row of Array.from(adjustRows)) {
|
||||
expect(row.querySelector('[data-flat-slider-center-tick="true"]')).not.toBeNull();
|
||||
}
|
||||
expect(host.textContent).toContain("+0.00");
|
||||
act(() => root.unmount());
|
||||
});
|
||||
|
||||
it("commits an adjust change scaled correctly and shows a reset when non-neutral", () => {
|
||||
const onCommitColorGrading = vi.fn();
|
||||
const grading = { ...neutralGrading(), adjust: { ...neutralGrading().adjust, contrast: 0.12 } };
|
||||
const { host, root } = renderInto(
|
||||
<FlatColorGradingSection
|
||||
{...neutralPropsBase()}
|
||||
grading={grading}
|
||||
onCommitColorGrading={onCommitColorGrading}
|
||||
/>,
|
||||
);
|
||||
const contrastRow = Array.from(host.querySelectorAll('[data-flat-grade-adjust="true"]')).find(
|
||||
(row) => row.textContent?.includes("Contrast"),
|
||||
);
|
||||
if (!contrastRow) throw new Error("expected a Contrast row");
|
||||
const resetButton = contrastRow.querySelector<HTMLButtonElement>(
|
||||
'[data-flat-slider-reset="true"]',
|
||||
);
|
||||
expect(resetButton).not.toBeNull();
|
||||
act(() => resetButton?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
||||
expect(onCommitColorGrading).toHaveBeenCalledTimes(1);
|
||||
expect(onCommitColorGrading.mock.calls[0][0].adjust.contrast).toBe(0);
|
||||
act(() => root.unmount());
|
||||
});
|
||||
|
||||
it("commits a dragged contrast value on slider track pointerdown, scaled from percent back to the internal -1..1 range", () => {
|
||||
const onCommitColorGrading = vi.fn();
|
||||
const { host, root } = renderInto(
|
||||
<FlatColorGradingSection
|
||||
{...neutralPropsBase()}
|
||||
onCommitColorGrading={onCommitColorGrading}
|
||||
/>,
|
||||
);
|
||||
const contrastRow = Array.from(host.querySelectorAll('[data-flat-grade-adjust="true"]')).find(
|
||||
(row) => row.textContent?.includes("Contrast"),
|
||||
);
|
||||
if (!contrastRow) throw new Error("expected a Contrast row");
|
||||
const track = contrastRow.querySelector<HTMLElement>('[data-flat-slider-track="true"]');
|
||||
if (!track) throw new Error("expected a slider track");
|
||||
Object.defineProperty(track, "getBoundingClientRect", {
|
||||
value: () => ({ left: 0, width: 100, top: 0, height: 2, right: 100, bottom: 2 }),
|
||||
});
|
||||
act(() => {
|
||||
// min=-100, max=100, step=1, ratio=0.75 -> raw=50 -> commit(50) -> adjust.contrast = 50/100 = 0.5
|
||||
track.dispatchEvent(new MouseEvent("pointerdown", { bubbles: true, clientX: 75 }));
|
||||
});
|
||||
expect(onCommitColorGrading).toHaveBeenCalledTimes(1);
|
||||
expect(onCommitColorGrading.mock.calls[0][0].adjust.contrast).toBe(0.5);
|
||||
act(() => root.unmount());
|
||||
});
|
||||
|
||||
it("commits a dragged exposure value scaled into stops, keeping other adjust keys untouched", () => {
|
||||
const onCommitColorGrading = vi.fn();
|
||||
const grading = {
|
||||
...neutralGrading(),
|
||||
adjust: { ...neutralGrading().adjust, saturation: 0.2 },
|
||||
};
|
||||
const { host, root } = renderInto(
|
||||
<FlatColorGradingSection
|
||||
{...neutralPropsBase()}
|
||||
grading={grading}
|
||||
onCommitColorGrading={onCommitColorGrading}
|
||||
/>,
|
||||
);
|
||||
const exposureRow = Array.from(host.querySelectorAll('[data-flat-grade-adjust="true"]')).find(
|
||||
(row) => row.textContent?.includes("Exposure"),
|
||||
);
|
||||
if (!exposureRow) throw new Error("expected an Exposure row");
|
||||
const track = exposureRow.querySelector<HTMLElement>('[data-flat-slider-track="true"]');
|
||||
if (!track) throw new Error("expected a slider track");
|
||||
Object.defineProperty(track, "getBoundingClientRect", {
|
||||
value: () => ({ left: 0, width: 200, top: 0, height: 2, right: 200, bottom: 2 }),
|
||||
});
|
||||
act(() => {
|
||||
// min=-200, max=200, step=5, ratio=1.0 -> raw=200 -> commit(200) -> adjust.exposure = 200/100 = 2
|
||||
track.dispatchEvent(new MouseEvent("pointerdown", { bubbles: true, clientX: 200 }));
|
||||
});
|
||||
expect(onCommitColorGrading).toHaveBeenCalledTimes(1);
|
||||
expect(onCommitColorGrading.mock.calls[0][0].adjust.exposure).toBe(2);
|
||||
expect(onCommitColorGrading.mock.calls[0][0].adjust.saturation).toBe(0.2);
|
||||
act(() => root.unmount());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
HF_COLOR_GRADING_PRESETS,
|
||||
isHfColorGradingActive,
|
||||
normalizeHfColorGrading,
|
||||
type HfColorGradingAdjustKey,
|
||||
type NormalizedHfColorGrading,
|
||||
} from "@hyperframes/core/color-grading";
|
||||
import { Compare, Plus, RotateCcw } from "../../icons/SystemIcons";
|
||||
@@ -78,6 +79,33 @@ export function FlatColorGradingAccessory({
|
||||
|
||||
const PRESET_OPTIONS = HF_COLOR_GRADING_PRESETS.map((p) => ({ value: p.id, label: p.label }));
|
||||
|
||||
const ADJUST_SLIDERS: Array<{
|
||||
key: HfColorGradingAdjustKey;
|
||||
label: string;
|
||||
min: number;
|
||||
max: number;
|
||||
step: number;
|
||||
}> = [
|
||||
{ key: "exposure", label: "Exposure", min: -200, max: 200, step: 5 },
|
||||
{ key: "contrast", label: "Contrast", min: -100, max: 100, step: 1 },
|
||||
{ key: "highlights", label: "Highlights", min: -100, max: 100, step: 1 },
|
||||
{ key: "shadows", label: "Shadows", min: -100, max: 100, step: 1 },
|
||||
{ key: "whites", label: "White Point", min: -100, max: 100, step: 1 },
|
||||
{ key: "blacks", label: "Black Point", min: -100, max: 100, step: 1 },
|
||||
{ key: "temperature", label: "Warmth", min: -100, max: 100, step: 1 },
|
||||
{ key: "tint", label: "Tint", min: -100, max: 100, step: 1 },
|
||||
{ key: "vibrance", label: "Vibrance", min: -100, max: 100, step: 1 },
|
||||
{ key: "saturation", label: "Saturation", min: -100, max: 100, step: 1 },
|
||||
];
|
||||
|
||||
function formatAdjustValue(key: HfColorGradingAdjustKey, rawPercent: number): string {
|
||||
if (key === "exposure") {
|
||||
const stops = rawPercent / 100;
|
||||
return `${stops >= 0 ? "+" : ""}${stops.toFixed(2)}`;
|
||||
}
|
||||
return `${Math.round(rawPercent)}%`;
|
||||
}
|
||||
|
||||
export function FlatColorGradingSection({
|
||||
grading,
|
||||
assets,
|
||||
@@ -228,6 +256,43 @@ export function FlatColorGradingSection({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-0.5 border-t border-panel-hairline pt-1.5">
|
||||
<div className="mb-1 text-[9px] font-semibold uppercase tracking-[0.12em] text-panel-text-5">
|
||||
Adjust
|
||||
</div>
|
||||
{ADJUST_SLIDERS.map((slider) => {
|
||||
const scale = slider.key === "exposure" ? 100 : 100;
|
||||
const rawPercent = grading.adjust[slider.key] * scale;
|
||||
const isSet = Math.abs(grading.adjust[slider.key]) > 1e-6;
|
||||
return (
|
||||
<div key={slider.key} data-flat-grade-adjust="true">
|
||||
<FlatSlider
|
||||
label={slider.label}
|
||||
value={rawPercent}
|
||||
min={slider.min}
|
||||
max={slider.max}
|
||||
step={slider.step}
|
||||
tier={isSet ? "explicitCustom" : "default"}
|
||||
displayValue={formatAdjustValue(slider.key, rawPercent)}
|
||||
centerTick
|
||||
onCommit={(next) =>
|
||||
onCommitColorGrading({
|
||||
...grading,
|
||||
adjust: { ...grading.adjust, [slider.key]: next / scale },
|
||||
})
|
||||
}
|
||||
onReset={() =>
|
||||
onCommitColorGrading({
|
||||
...grading,
|
||||
adjust: { ...grading.adjust, [slider.key]: 0 },
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user