import { useEffect, useState } from "react"; import { HF_COLOR_GRADING_EFFECT_APPLY_DEFAULTS, HF_COLOR_GRADING_EFFECT_PRESETS, HF_COLOR_GRADING_PALETTES, normalizeHfColorGrading, type HfColorGradingActiveEffectKey, type HfColorGradingEffectKey, type NormalizedHfColorGrading, } from "@hyperframes/core/color-grading"; import { Plus, RotateCcw, X } from "../../icons/SystemIcons"; import { useTrackDesignInput } from "../../contexts/DesignPanelInputContext"; import { FLAT_PREVIEW_GRID, FlatSlider } from "./propertyPanelFlatPrimitives"; import type { ColorGradingPresetPreviews, ColorGradingPreviewOptions, } from "./useColorGradingController"; import { DEFAULT_EFFECTS, EFFECT_GROUPS, EFFECT_SPECS, type EffectControl, type EffectSpec, } from "./propertyPanelFlatEffectSpecs"; import { FlatEffectControl } from "./propertyPanelFlatEffectControl"; import { presetPreviewHandlers } from "./propertyPanelPresetPreview"; export function activeColorGradingEffectCount(grading: NormalizedHfColorGrading): number { return EFFECT_SPECS.filter((effect) => grading.effects[effect.key] > 0.0001).length; } export function FlatEffectsAccessory({ grading, onCommitColorGrading, }: { grading: NormalizedHfColorGrading; onCommitColorGrading: (next: NormalizedHfColorGrading) => void; }) { const track = useTrackDesignInput(); if (!activeColorGradingEffectCount(grading)) return null; return ( ); } export function FlatEffectsSection({ grading, previews, presetPreviews, onCommitColorGrading, onPreviewColorGrading, onRequestEffectPreviews, onRequestPresetPreviews, }: { grading: NormalizedHfColorGrading; previews: ColorGradingPresetPreviews; presetPreviews: ColorGradingPresetPreviews; onCommitColorGrading: (next: NormalizedHfColorGrading) => void; onPreviewColorGrading: ( next: NormalizedHfColorGrading | null, options?: ColorGradingPreviewOptions, ) => void; onRequestEffectPreviews: (effects: readonly HfColorGradingActiveEffectKey[]) => void; onRequestPresetPreviews: () => void; }) { const track = useTrackDesignInput(); const activeEffects = EFFECT_SPECS.filter((effect) => grading.effects[effect.key] > 0.0001); const [catalogOpen, setCatalogOpen] = useState(activeEffects.length === 0); const [catalogGroup, setCatalogGroup] = useState(EFFECT_GROUPS[0].label); const [selectedKey, setSelectedKey] = useState( activeEffects[0]?.key ?? null, ); const selectedEffect = activeEffects.find((effect) => effect.key === selectedKey) ?? activeEffects[0] ?? null; useEffect(() => { if (!catalogOpen) return; const group = EFFECT_GROUPS.find((candidate) => candidate.label === catalogGroup); if (!group) return; const effectKeys = group.effects.map((effect) => effect.key); if (previews.status !== "loading" && effectKeys.some((effect) => !previews.images[effect])) { onRequestEffectPreviews(effectKeys); } if ( group.presets?.some((preset) => !presetPreviews.images[preset]) && presetPreviews.status !== "loading" ) { onRequestPresetPreviews(); } }, [ catalogGroup, catalogOpen, onRequestEffectPreviews, onRequestPresetPreviews, presetPreviews.images, presetPreviews.status, previews.images, previews.status, ]); useEffect(() => () => onPreviewColorGrading(null), [onPreviewColorGrading]); const commitEffects = (effects: NormalizedHfColorGrading["effects"]) => { onCommitColorGrading({ ...grading, effects, }); }; const commitEffect = (key: HfColorGradingEffectKey, value: number) => { commitEffects({ ...grading.effects, [key]: value }); }; const resolveEffect = (effect: EffectSpec): NormalizedHfColorGrading => ({ ...grading, effects: { ...grading.effects, ...HF_COLOR_GRADING_EFFECT_APPLY_DEFAULTS[effect.key], }, }); const applyEffect = (effect: EffectSpec) => { track("button", `Add ${effect.label}`); onCommitColorGrading(resolveEffect(effect)); setSelectedKey(effect.key); setCatalogOpen(false); }; const resolvePreset = (presetId: string) => normalizeHfColorGrading({ preset: presetId, lut: grading.lut }) ?? grading; const removeEffect = (effect: EffectSpec) => { track("button", `Remove ${effect.label}`); commitEffect(effect.key, 0); setSelectedKey(null); }; const renderPalette = (kind: "mono" | "art") => { const fallback = kind === "art" ? ["#1a1a1a", "#f5f5dc"] : ["#000000", "#ffffff"]; const palette = grading.palette; return (
{HF_COLOR_GRADING_PALETTES.map((preset) => { const selected = palette?.length === preset.colors.length && palette.every((color, index) => color === preset.colors[index]); return ( ); })}
{!palette ? ( ) : ( <>
Custom palette
{palette.map((color, index) => ( { const nextPalette = [...palette]; nextPalette[index] = event.target.value; onCommitColorGrading({ ...grading, palette: nextPalette }); }} className="h-6 w-6 cursor-pointer rounded-sm border border-panel-border-input bg-transparent p-0" /> {palette.length > 2 && ( )} ))} {palette.length < 6 && ( )}
)}
); }; return (
{activeEffects.length > 0 && (
{activeEffects.map((effect) => { const selected = selectedEffect?.key === effect.key; return ( ); })}
)} {selectedEffect && (
{selectedEffect.label}
{selectedEffect.showMaster !== false && ( commitEffect(selectedEffect.key, next / 100)} onReset={() => commitEffect( selectedEffect.key, HF_COLOR_GRADING_EFFECT_APPLY_DEFAULTS[selectedEffect.key][selectedEffect.key] ?? 1, ) } /> )} {selectedEffect.settings?.map((control: EffectControl) => ( ))} {selectedEffect.palette && renderPalette(selectedEffect.palette)}
)} {catalogOpen && (
{EFFECT_GROUPS.map((group) => ( ))}
{EFFECT_GROUPS.filter((group) => group.label === catalogGroup).map((group) => (
{group.presets?.map((presetId) => { const preset = HF_COLOR_GRADING_EFFECT_PRESETS.find( (candidate) => candidate.id === presetId, ); if (!preset) return null; const selected = grading.preset === preset.id; const preview = presetPreviews.images[preset.id]; return ( ); })} {group.effects.map((effect) => { const active = grading.effects[effect.key] > 0.0001; const preview = previews.images[effect.key]; return ( ); })}
))}
)}
); }