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());
|
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,
|
HF_COLOR_GRADING_PRESETS,
|
||||||
isHfColorGradingActive,
|
isHfColorGradingActive,
|
||||||
normalizeHfColorGrading,
|
normalizeHfColorGrading,
|
||||||
|
type HfColorGradingAdjustKey,
|
||||||
type NormalizedHfColorGrading,
|
type NormalizedHfColorGrading,
|
||||||
} from "@hyperframes/core/color-grading";
|
} from "@hyperframes/core/color-grading";
|
||||||
import { Compare, Plus, RotateCcw } from "../../icons/SystemIcons";
|
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 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({
|
export function FlatColorGradingSection({
|
||||||
grading,
|
grading,
|
||||||
assets,
|
assets,
|
||||||
@@ -228,6 +256,43 @@ export function FlatColorGradingSection({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user