@hyperframes/studio 0.7.71 → 0.7.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{hyperframes-player-C1X90psg.js → hyperframes-player-DGlzhf_s.js} +1 -1
- package/dist/assets/index-B37rWXo4.css +1 -0
- package/dist/assets/{index-DzxDHPR1.js → index-CbVTOJ-E.js} +1 -1
- package/dist/assets/{index-nY4lLBqM.js → index-CcA5WVJv.js} +1 -1
- package/dist/assets/index-DXcLI2wu.js +428 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.html +2 -2
- package/dist/index.js +7762 -4261
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/App.tsx +4 -4
- package/src/components/StudioLeftSidebar.tsx +2 -3
- package/src/components/StudioRightPanel.tsx +10 -4
- package/src/components/editor/AnimationCard.test.tsx +100 -0
- package/src/components/editor/AnimationCard.tsx +5 -2
- package/src/components/editor/EaseCurveSection.test.tsx +348 -0
- package/src/components/editor/EaseCurveSection.tsx +444 -237
- package/src/components/editor/EaseParamFields.test.tsx +188 -0
- package/src/components/editor/EaseParamFields.tsx +246 -0
- package/src/components/editor/PropertyPanel.test.tsx +9 -7
- package/src/components/editor/PropertyPanelFlat.tsx +75 -88
- package/src/components/editor/colorGradingFrameAnalysis.test.ts +101 -0
- package/src/components/editor/colorGradingFrameAnalysis.ts +203 -0
- package/src/components/editor/colorGradingScopePatch.test.ts +3 -3
- package/src/components/editor/easeCurveSvg.tsx +65 -0
- package/src/components/editor/easePresetLibrary.test.ts +114 -0
- package/src/components/editor/easePresetLibrary.ts +49 -0
- package/src/components/editor/gsapAnimationConstants.ts +15 -13
- package/src/components/editor/propertyPanelColorCurveGraph.tsx +549 -0
- package/src/components/editor/propertyPanelColorCurves.test.tsx +158 -0
- package/src/components/editor/propertyPanelColorCurves.tsx +185 -0
- package/src/components/editor/propertyPanelColorGradingControls.tsx +115 -77
- package/src/components/editor/propertyPanelColorScopes.tsx +258 -0
- package/src/components/editor/propertyPanelColorSecondary.test.tsx +181 -0
- package/src/components/editor/propertyPanelColorSecondary.tsx +448 -0
- package/src/components/editor/propertyPanelColorWheels.test.tsx +119 -0
- package/src/components/editor/propertyPanelColorWheels.tsx +334 -0
- package/src/components/editor/propertyPanelFlatColorGradingAccessory.tsx +109 -0
- package/src/components/editor/propertyPanelFlatColorGradingSection.test.tsx +239 -41
- package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +260 -300
- package/src/components/editor/propertyPanelFlatEffectControl.tsx +96 -0
- package/src/components/editor/propertyPanelFlatEffectSpecs.ts +305 -0
- package/src/components/editor/propertyPanelFlatEffectsSection.test.tsx +331 -0
- package/src/components/editor/propertyPanelFlatEffectsSection.tsx +503 -0
- package/src/components/editor/propertyPanelFlatOverlaysSection.test.tsx +113 -0
- package/src/components/editor/propertyPanelFlatOverlaysSection.tsx +108 -0
- package/src/components/editor/propertyPanelFlatPrimitives.tsx +2 -0
- package/src/components/editor/propertyPanelGradingNumberField.test.tsx +79 -0
- package/src/components/editor/propertyPanelGradingNumberField.tsx +116 -0
- package/src/components/editor/propertyPanelPresetPreview.ts +36 -0
- package/src/components/editor/propertyPanelTypes.ts +13 -0
- package/src/components/editor/useColorGradingController.test.ts +331 -10
- package/src/components/editor/useColorGradingController.ts +57 -90
- package/src/components/editor/useColorGradingPreviews.ts +350 -0
- package/src/components/editor/useColorGradingScopes.test.tsx +143 -0
- package/src/components/editor/useColorGradingScopes.ts +62 -0
- package/src/components/editor/useInspectorGestureTransaction.ts +30 -1
- package/src/components/nle/NLEContext.tsx +1 -1
- package/src/contexts/PanelLayoutContext.tsx +3 -6
- package/src/hooks/useBlockCatalog.ts +31 -13
- package/src/hooks/useBlockHandlers.ts +22 -0
- package/src/hooks/useGsapScriptCommits.test.tsx +15 -0
- package/src/hooks/useGsapScriptCommits.ts +14 -1
- package/src/hooks/usePanelLayout.test.ts +68 -1
- package/src/hooks/usePanelLayout.ts +72 -34
- package/src/icons/SystemIcons.tsx +4 -0
- package/src/player/components/PlayerControls.tsx +68 -229
- package/src/player/components/ShortcutsPanel.tsx +2 -4
- package/src/player/components/SpeedMenu.tsx +1 -2
- package/src/telemetry/events.test.ts +9 -0
- package/src/telemetry/events.ts +5 -0
- package/src/utils/blockInstaller.test.ts +67 -0
- package/src/utils/blockInstaller.ts +162 -109
- package/src/utils/studioUiPreferences.test.ts +5 -0
- package/src/utils/studioUiPreferences.ts +8 -0
- package/dist/assets/index-CSCh2Vrp.js +0 -428
- package/dist/assets/index-Ceyz8Qt2.css +0 -1
- package/src/player/components/PlayerControls.test.ts +0 -20
- package/src/player/components/useSeekBarDrag.ts +0 -169
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import type { HfColorCurvePoint, HfHueCurvePoint } from "@hyperframes/core/color-grading";
|
|
3
|
+
import { RotateCcw } from "../../icons/SystemIcons";
|
|
4
|
+
import {
|
|
5
|
+
CurveGraph,
|
|
6
|
+
formatPointValue,
|
|
7
|
+
movePoint,
|
|
8
|
+
pointsFor,
|
|
9
|
+
RGB_IDENTITY,
|
|
10
|
+
TABS,
|
|
11
|
+
type ColorCurveValues,
|
|
12
|
+
type CurveTab,
|
|
13
|
+
withPoints,
|
|
14
|
+
} from "./propertyPanelColorCurveGraph";
|
|
15
|
+
import { GradingNumberField } from "./propertyPanelGradingNumberField";
|
|
16
|
+
import { useInspectorGestureDraft } from "./useInspectorGestureTransaction";
|
|
17
|
+
|
|
18
|
+
export type { ColorCurveValues } from "./propertyPanelColorCurveGraph";
|
|
19
|
+
|
|
20
|
+
export function ColorCurves({
|
|
21
|
+
value,
|
|
22
|
+
disabled,
|
|
23
|
+
onPreview,
|
|
24
|
+
onCommit,
|
|
25
|
+
}: {
|
|
26
|
+
value: ColorCurveValues;
|
|
27
|
+
disabled?: boolean;
|
|
28
|
+
onPreview: (value: ColorCurveValues) => void;
|
|
29
|
+
onCommit: (value: ColorCurveValues) => void;
|
|
30
|
+
}) {
|
|
31
|
+
const [activeKey, setActiveKey] = useState<CurveTab["key"]>("master");
|
|
32
|
+
const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
|
|
33
|
+
const { draft, setDraft, transaction } = useInspectorGestureDraft({
|
|
34
|
+
sourceValue: value,
|
|
35
|
+
onPreview,
|
|
36
|
+
onCommit,
|
|
37
|
+
});
|
|
38
|
+
const tab = TABS.find((candidate) => candidate.key === activeKey) ?? TABS[0];
|
|
39
|
+
if (!tab) throw new Error("Color curve tabs are unavailable");
|
|
40
|
+
const points = pointsFor(draft, tab);
|
|
41
|
+
|
|
42
|
+
const previewPoints = (
|
|
43
|
+
nextPoints: readonly (HfColorCurvePoint | HfHueCurvePoint)[],
|
|
44
|
+
nextSelectedIndex: number,
|
|
45
|
+
) => {
|
|
46
|
+
setSelectedIndex(nextSelectedIndex);
|
|
47
|
+
transaction.preview(withPoints(draft, tab, nextPoints));
|
|
48
|
+
};
|
|
49
|
+
const resetActive = () => {
|
|
50
|
+
transaction.cancel();
|
|
51
|
+
const next = withPoints(draft, tab, tab.kind === "rgb" ? RGB_IDENTITY : []);
|
|
52
|
+
setDraft(next);
|
|
53
|
+
setSelectedIndex(null);
|
|
54
|
+
onCommit(next);
|
|
55
|
+
};
|
|
56
|
+
const deleteSelected = () => {
|
|
57
|
+
if (selectedIndex === null) return;
|
|
58
|
+
if (tab.kind === "rgb" && (selectedIndex === 0 || selectedIndex === points.length - 1)) return;
|
|
59
|
+
transaction.cancel();
|
|
60
|
+
const nextPoints =
|
|
61
|
+
tab.kind === "hue" && points.length <= 3
|
|
62
|
+
? []
|
|
63
|
+
: points.filter((_, index) => index !== selectedIndex);
|
|
64
|
+
const next = withPoints(draft, tab, nextPoints);
|
|
65
|
+
setDraft(next);
|
|
66
|
+
setSelectedIndex(null);
|
|
67
|
+
onCommit(next);
|
|
68
|
+
};
|
|
69
|
+
const updateSelected = (axis: "input" | "output", rawValue: number) => {
|
|
70
|
+
if (selectedIndex === null || !Number.isFinite(rawValue)) return;
|
|
71
|
+
const point = points[selectedIndex];
|
|
72
|
+
if (!point) return;
|
|
73
|
+
const moved = movePoint(
|
|
74
|
+
points,
|
|
75
|
+
selectedIndex,
|
|
76
|
+
axis === "input" ? rawValue : point[0],
|
|
77
|
+
axis === "output" ? rawValue : point[1],
|
|
78
|
+
tab,
|
|
79
|
+
);
|
|
80
|
+
previewPoints(moved.points, moved.selected);
|
|
81
|
+
};
|
|
82
|
+
const selectedPoint = selectedIndex === null ? null : points[selectedIndex];
|
|
83
|
+
const endpointSelected =
|
|
84
|
+
tab.kind === "rgb" &&
|
|
85
|
+
selectedIndex !== null &&
|
|
86
|
+
(selectedIndex === 0 || selectedIndex === points.length - 1);
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<div data-color-curves="true" className="space-y-2">
|
|
90
|
+
<div className="flex flex-wrap items-center gap-x-2 gap-y-1 border-b border-panel-hairline">
|
|
91
|
+
{TABS.map((candidate) => (
|
|
92
|
+
<button
|
|
93
|
+
key={candidate.key}
|
|
94
|
+
type="button"
|
|
95
|
+
data-color-curve-tab={candidate.key}
|
|
96
|
+
aria-pressed={candidate.key === tab.key}
|
|
97
|
+
disabled={disabled}
|
|
98
|
+
onClick={() => {
|
|
99
|
+
transaction.cancel();
|
|
100
|
+
setActiveKey(candidate.key);
|
|
101
|
+
setSelectedIndex(null);
|
|
102
|
+
}}
|
|
103
|
+
className={`border-b-2 px-0.5 py-1 text-[9px] ${
|
|
104
|
+
candidate.key === tab.key
|
|
105
|
+
? "border-panel-accent text-panel-text-1"
|
|
106
|
+
: "border-transparent text-panel-text-4 hover:text-panel-text-2"
|
|
107
|
+
}`}
|
|
108
|
+
>
|
|
109
|
+
{candidate.label}
|
|
110
|
+
</button>
|
|
111
|
+
))}
|
|
112
|
+
</div>
|
|
113
|
+
<CurveGraph
|
|
114
|
+
tab={tab}
|
|
115
|
+
points={points}
|
|
116
|
+
selectedIndex={selectedIndex}
|
|
117
|
+
disabled={disabled}
|
|
118
|
+
onBegin={transaction.begin}
|
|
119
|
+
onPreview={previewPoints}
|
|
120
|
+
onSelect={setSelectedIndex}
|
|
121
|
+
onDelete={deleteSelected}
|
|
122
|
+
onSettle={transaction.settle}
|
|
123
|
+
onCancel={transaction.cancel}
|
|
124
|
+
/>
|
|
125
|
+
<div className="flex min-h-7 items-end gap-2">
|
|
126
|
+
{selectedPoint ? (
|
|
127
|
+
<>
|
|
128
|
+
<GradingNumberField
|
|
129
|
+
label={tab.kind === "rgb" ? "Input" : "Hue"}
|
|
130
|
+
ariaLabel={`Curve point ${tab.kind === "rgb" ? "input" : "hue"}`}
|
|
131
|
+
value={formatPointValue(selectedPoint[0], tab, "input")}
|
|
132
|
+
min={0}
|
|
133
|
+
max={tab.kind === "rgb" ? 1 : 359.999}
|
|
134
|
+
disabled={disabled || endpointSelected}
|
|
135
|
+
labelClassName="min-w-0 flex-1"
|
|
136
|
+
labelTextClassName="block text-[8px] uppercase text-panel-text-5"
|
|
137
|
+
inputClassName="block w-full"
|
|
138
|
+
onBegin={transaction.begin}
|
|
139
|
+
onPreview={(next) => updateSelected("input", next)}
|
|
140
|
+
onSettle={transaction.settle}
|
|
141
|
+
onCancel={transaction.cancel}
|
|
142
|
+
/>
|
|
143
|
+
<GradingNumberField
|
|
144
|
+
label="Output"
|
|
145
|
+
ariaLabel="Curve point output"
|
|
146
|
+
value={formatPointValue(selectedPoint[1], tab, "output")}
|
|
147
|
+
min={tab.min}
|
|
148
|
+
max={tab.max}
|
|
149
|
+
disabled={disabled}
|
|
150
|
+
labelClassName="min-w-0 flex-1"
|
|
151
|
+
labelTextClassName="block text-[8px] uppercase text-panel-text-5"
|
|
152
|
+
inputClassName="block w-full"
|
|
153
|
+
onBegin={transaction.begin}
|
|
154
|
+
onPreview={(next) => updateSelected("output", next)}
|
|
155
|
+
onSettle={transaction.settle}
|
|
156
|
+
onCancel={transaction.cancel}
|
|
157
|
+
/>
|
|
158
|
+
<button
|
|
159
|
+
type="button"
|
|
160
|
+
disabled={disabled || endpointSelected}
|
|
161
|
+
onClick={deleteSelected}
|
|
162
|
+
className="pb-0.5 text-[9px] text-panel-text-4 hover:text-panel-text-1 disabled:opacity-30"
|
|
163
|
+
>
|
|
164
|
+
Delete
|
|
165
|
+
</button>
|
|
166
|
+
</>
|
|
167
|
+
) : (
|
|
168
|
+
<span className="flex-1 text-[9px] text-panel-text-5">
|
|
169
|
+
Click the graph or press Enter to add a point
|
|
170
|
+
</span>
|
|
171
|
+
)}
|
|
172
|
+
<button
|
|
173
|
+
type="button"
|
|
174
|
+
aria-label={`Reset ${tab.label} curve`}
|
|
175
|
+
title={`Reset ${tab.label} curve`}
|
|
176
|
+
disabled={disabled}
|
|
177
|
+
onClick={resetActive}
|
|
178
|
+
className="pb-0.5 text-panel-text-4 hover:text-panel-text-1 disabled:opacity-40"
|
|
179
|
+
>
|
|
180
|
+
<RotateCcw size={11} />
|
|
181
|
+
</button>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
@@ -15,7 +15,7 @@ import { useTrackDesignInput } from "../../contexts/DesignPanelInputContext";
|
|
|
15
15
|
|
|
16
16
|
const LUT_UPLOAD_DIR = "assets/luts";
|
|
17
17
|
|
|
18
|
-
const
|
|
18
|
+
export const COLOR_GRADING_ADJUST_SLIDERS: Array<{
|
|
19
19
|
key: HfColorGradingAdjustKey;
|
|
20
20
|
label: string;
|
|
21
21
|
min: number;
|
|
@@ -60,7 +60,7 @@ const ADJUST_SLIDERS: Array<{
|
|
|
60
60
|
{ key: "saturation", label: "Saturation", min: -100, max: 100, step: 1, scale: 100, suffix: "%" },
|
|
61
61
|
];
|
|
62
62
|
|
|
63
|
-
const
|
|
63
|
+
export const COLOR_GRADING_DETAIL_SLIDERS: Array<{
|
|
64
64
|
key: HfColorGradingDetailKey;
|
|
65
65
|
label: string;
|
|
66
66
|
min: number;
|
|
@@ -123,7 +123,7 @@ const DETAIL_SLIDERS: Array<{
|
|
|
123
123
|
},
|
|
124
124
|
];
|
|
125
125
|
|
|
126
|
-
type DetailSlider = (typeof
|
|
126
|
+
type DetailSlider = (typeof COLOR_GRADING_DETAIL_SLIDERS)[number];
|
|
127
127
|
type SliderSettings = {
|
|
128
128
|
active?: boolean;
|
|
129
129
|
label: string;
|
|
@@ -143,28 +143,111 @@ const EFFECT_SLIDERS: Array<{
|
|
|
143
143
|
{ key: "pixelate", label: "Pixelate", min: 0, max: 100, step: 1, scale: 100, suffix: "%" },
|
|
144
144
|
];
|
|
145
145
|
|
|
146
|
-
const AMOUNT_DETAIL_SLIDERS =
|
|
146
|
+
const AMOUNT_DETAIL_SLIDERS = COLOR_GRADING_DETAIL_SLIDERS.filter(
|
|
147
147
|
(slider) => slider.key === "vignette" || slider.key === "grain",
|
|
148
148
|
);
|
|
149
|
-
const VIGNETTE_TUNE_SLIDERS =
|
|
149
|
+
export const VIGNETTE_TUNE_SLIDERS = COLOR_GRADING_DETAIL_SLIDERS.filter(
|
|
150
150
|
(slider) =>
|
|
151
151
|
slider.key === "vignetteMidpoint" ||
|
|
152
152
|
slider.key === "vignetteRoundness" ||
|
|
153
153
|
slider.key === "vignetteFeather",
|
|
154
154
|
);
|
|
155
|
-
const GRAIN_TUNE_SLIDERS =
|
|
155
|
+
export const GRAIN_TUNE_SLIDERS = COLOR_GRADING_DETAIL_SLIDERS.filter(
|
|
156
156
|
(slider) => slider.key === "grainSize" || slider.key === "grainRoughness",
|
|
157
157
|
);
|
|
158
158
|
|
|
159
|
-
function
|
|
159
|
+
export function normalizedColorGradingDefault(slider: {
|
|
160
|
+
defaultValue?: number;
|
|
161
|
+
scale: number;
|
|
162
|
+
}): number {
|
|
160
163
|
return (slider.defaultValue ?? 0) / slider.scale;
|
|
161
164
|
}
|
|
162
165
|
|
|
163
|
-
function
|
|
166
|
+
export function visibleColorGradingIntensity(grading: NormalizedHfColorGrading): number {
|
|
164
167
|
// Earlier drafts could persist 0% strength; the next manual edit should revive visible grading.
|
|
165
168
|
return grading.intensity === 0 ? 1 : grading.intensity;
|
|
166
169
|
}
|
|
167
170
|
|
|
171
|
+
function colorGradingWithLut(
|
|
172
|
+
grading: NormalizedHfColorGrading,
|
|
173
|
+
src: string | null,
|
|
174
|
+
intensity = 1,
|
|
175
|
+
): NormalizedHfColorGrading {
|
|
176
|
+
return {
|
|
177
|
+
...grading,
|
|
178
|
+
intensity: visibleColorGradingIntensity(grading),
|
|
179
|
+
lut: src ? { src, intensity } : null,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function colorGradingWithDetail(
|
|
184
|
+
grading: NormalizedHfColorGrading,
|
|
185
|
+
key: HfColorGradingDetailKey,
|
|
186
|
+
value: number,
|
|
187
|
+
): NormalizedHfColorGrading {
|
|
188
|
+
return {
|
|
189
|
+
...grading,
|
|
190
|
+
intensity: visibleColorGradingIntensity(grading),
|
|
191
|
+
details: { ...grading.details, [key]: value },
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function colorGradingWithIntensity(
|
|
196
|
+
grading: NormalizedHfColorGrading,
|
|
197
|
+
intensity: number,
|
|
198
|
+
): NormalizedHfColorGrading {
|
|
199
|
+
return { ...grading, intensity };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function colorGradingWithAdjust(
|
|
203
|
+
grading: NormalizedHfColorGrading,
|
|
204
|
+
key: HfColorGradingAdjustKey,
|
|
205
|
+
value: number,
|
|
206
|
+
): NormalizedHfColorGrading {
|
|
207
|
+
return {
|
|
208
|
+
...grading,
|
|
209
|
+
intensity: visibleColorGradingIntensity(grading),
|
|
210
|
+
adjust: { ...grading.adjust, [key]: value },
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function importFirstLut(
|
|
215
|
+
files: FileList | null,
|
|
216
|
+
onImportAssets?: (files: FileList, dir?: string) => Promise<string[]>,
|
|
217
|
+
): Promise<string | null> {
|
|
218
|
+
if (!files?.length || !onImportAssets) return null;
|
|
219
|
+
const uploaded = await onImportAssets(files, LUT_UPLOAD_DIR);
|
|
220
|
+
return uploaded.find((asset) => LUT_EXT.test(asset)) ?? null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function createColorGradingActions(
|
|
224
|
+
grading: NormalizedHfColorGrading,
|
|
225
|
+
onCommit: (grading: NormalizedHfColorGrading) => void,
|
|
226
|
+
) {
|
|
227
|
+
const applyLut = (src: string | null, intensity = 1) => {
|
|
228
|
+
onCommit(colorGradingWithLut(grading, src, intensity));
|
|
229
|
+
};
|
|
230
|
+
return {
|
|
231
|
+
setIntensityPercent(value: number) {
|
|
232
|
+
onCommit(colorGradingWithIntensity(grading, value / 100));
|
|
233
|
+
},
|
|
234
|
+
applyLut,
|
|
235
|
+
setLutIntensityPercent(value: number) {
|
|
236
|
+
if (grading.lut) applyLut(grading.lut.src, value / 100);
|
|
237
|
+
},
|
|
238
|
+
async importLut(
|
|
239
|
+
files: FileList | null,
|
|
240
|
+
onImportAssets: ((files: FileList, dir?: string) => Promise<string[]>) | undefined,
|
|
241
|
+
onImported: () => void,
|
|
242
|
+
) {
|
|
243
|
+
const src = await importFirstLut(files, onImportAssets);
|
|
244
|
+
if (!src) return;
|
|
245
|
+
onImported();
|
|
246
|
+
applyLut(src);
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
168
251
|
export function ColorGradingControls({
|
|
169
252
|
grading,
|
|
170
253
|
assets,
|
|
@@ -189,11 +272,14 @@ export function ColorGradingControls({
|
|
|
189
272
|
const detailSettingsSliders =
|
|
190
273
|
detailSettings === "vignette" ? VIGNETTE_TUNE_SLIDERS : GRAIN_TUNE_SLIDERS;
|
|
191
274
|
const vignetteSettingsActive = VIGNETTE_TUNE_SLIDERS.some(
|
|
192
|
-
(slider) =>
|
|
275
|
+
(slider) =>
|
|
276
|
+
Math.abs(grading.details[slider.key] - normalizedColorGradingDefault(slider)) > 0.0001,
|
|
193
277
|
);
|
|
194
278
|
const grainSettingsActive = GRAIN_TUNE_SLIDERS.some(
|
|
195
|
-
(slider) =>
|
|
279
|
+
(slider) =>
|
|
280
|
+
Math.abs(grading.details[slider.key] - normalizedColorGradingDefault(slider)) > 0.0001,
|
|
196
281
|
);
|
|
282
|
+
const actions = createColorGradingActions(grading, onCommitColorGrading);
|
|
197
283
|
|
|
198
284
|
const applyPreset = (preset: string) => {
|
|
199
285
|
const next = normalizeHfColorGrading({ preset, intensity: 1, lut: grading.lut });
|
|
@@ -202,51 +288,13 @@ export function ColorGradingControls({
|
|
|
202
288
|
onCommitColorGrading(next);
|
|
203
289
|
}
|
|
204
290
|
};
|
|
205
|
-
const updateFilterIntensity = (value: number) => {
|
|
206
|
-
onCommitColorGrading({
|
|
207
|
-
...grading,
|
|
208
|
-
intensity: value / 100,
|
|
209
|
-
});
|
|
210
|
-
};
|
|
211
|
-
const applyLut = (src: string | null, intensity = 1) => {
|
|
212
|
-
onCommitColorGrading({
|
|
213
|
-
...grading,
|
|
214
|
-
intensity: visibleIntensity(grading),
|
|
215
|
-
lut: src ? { src, intensity } : null,
|
|
216
|
-
});
|
|
217
|
-
};
|
|
218
|
-
const updateLutIntensity = (value: number) => {
|
|
219
|
-
if (!grading.lut) return;
|
|
220
|
-
applyLut(grading.lut.src, value / 100);
|
|
221
|
-
};
|
|
222
|
-
const importLuts = async (files: FileList | null) => {
|
|
223
|
-
if (!files?.length || !onImportAssets) return;
|
|
224
|
-
const uploaded = await onImportAssets(files, LUT_UPLOAD_DIR);
|
|
225
|
-
const firstLut = uploaded.find((asset) => LUT_EXT.test(asset));
|
|
226
|
-
if (firstLut) {
|
|
227
|
-
track("button", "Import LUT");
|
|
228
|
-
applyLut(firstLut, 1);
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
291
|
const commitDetailSlider = (slider: DetailSlider, next: number) => {
|
|
232
|
-
onCommitColorGrading(
|
|
233
|
-
...grading,
|
|
234
|
-
intensity: visibleIntensity(grading),
|
|
235
|
-
details: {
|
|
236
|
-
...grading.details,
|
|
237
|
-
[slider.key]: next / slider.scale,
|
|
238
|
-
},
|
|
239
|
-
});
|
|
292
|
+
onCommitColorGrading(colorGradingWithDetail(grading, slider.key, next / slider.scale));
|
|
240
293
|
};
|
|
241
294
|
const resetDetailSlider = (slider: DetailSlider) => {
|
|
242
|
-
onCommitColorGrading(
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
details: {
|
|
246
|
-
...grading.details,
|
|
247
|
-
[slider.key]: normalizedDefaultValue(slider),
|
|
248
|
-
},
|
|
249
|
-
});
|
|
295
|
+
onCommitColorGrading(
|
|
296
|
+
colorGradingWithDetail(grading, slider.key, normalizedColorGradingDefault(slider)),
|
|
297
|
+
);
|
|
250
298
|
};
|
|
251
299
|
const renderDetailSlider = (slider: DetailSlider, settings?: SliderSettings) => {
|
|
252
300
|
const value = Math.round(grading.details[slider.key] * slider.scale);
|
|
@@ -293,8 +341,8 @@ export function ColorGradingControls({
|
|
|
293
341
|
neutral={0}
|
|
294
342
|
suffix="%"
|
|
295
343
|
displayValue={`${Math.round(grading.intensity * 100)}%`}
|
|
296
|
-
onCommit={
|
|
297
|
-
onReset={() =>
|
|
344
|
+
onCommit={actions.setIntensityPercent}
|
|
345
|
+
onReset={() => actions.setIntensityPercent(100)}
|
|
298
346
|
/>
|
|
299
347
|
|
|
300
348
|
<div className="min-w-0 rounded-md border border-panel-border/70 bg-panel-input/15">
|
|
@@ -322,7 +370,7 @@ export function ColorGradingControls({
|
|
|
322
370
|
onChange={(event) => {
|
|
323
371
|
const nextSrc = event.target.value;
|
|
324
372
|
track("select", "Custom LUT");
|
|
325
|
-
applyLut(
|
|
373
|
+
actions.applyLut(
|
|
326
374
|
nextSrc || null,
|
|
327
375
|
nextSrc && grading.lut?.src === nextSrc ? grading.lut.intensity : 1,
|
|
328
376
|
);
|
|
@@ -361,7 +409,9 @@ export function ColorGradingControls({
|
|
|
361
409
|
multiple
|
|
362
410
|
className="hidden"
|
|
363
411
|
onChange={(event) => {
|
|
364
|
-
void
|
|
412
|
+
void actions.importLut(event.currentTarget.files, onImportAssets, () =>
|
|
413
|
+
track("button", "Import LUT"),
|
|
414
|
+
);
|
|
365
415
|
event.currentTarget.value = "";
|
|
366
416
|
}}
|
|
367
417
|
/>
|
|
@@ -386,8 +436,8 @@ export function ColorGradingControls({
|
|
|
386
436
|
neutral={0}
|
|
387
437
|
suffix="%"
|
|
388
438
|
displayValue={`${Math.round((grading.lut.intensity ?? 1) * 100)}%`}
|
|
389
|
-
onCommit={
|
|
390
|
-
onReset={() =>
|
|
439
|
+
onCommit={actions.setLutIntensityPercent}
|
|
440
|
+
onReset={() => actions.setLutIntensityPercent(100)}
|
|
391
441
|
/>
|
|
392
442
|
</div>
|
|
393
443
|
)}
|
|
@@ -398,7 +448,7 @@ export function ColorGradingControls({
|
|
|
398
448
|
<div className="grid min-w-0 gap-1.5">
|
|
399
449
|
<span className={LABEL}>Adjust</span>
|
|
400
450
|
<div className="grid min-w-0 grid-cols-2 gap-1.5">
|
|
401
|
-
{
|
|
451
|
+
{COLOR_GRADING_ADJUST_SLIDERS.map((slider) => {
|
|
402
452
|
const value = grading.adjust[slider.key] * slider.scale;
|
|
403
453
|
const isExposure = slider.key === "exposure";
|
|
404
454
|
return (
|
|
@@ -418,24 +468,12 @@ export function ColorGradingControls({
|
|
|
418
468
|
: `${Math.round(value)}%`
|
|
419
469
|
}
|
|
420
470
|
onCommit={(next) => {
|
|
421
|
-
onCommitColorGrading(
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
adjust: {
|
|
425
|
-
...grading.adjust,
|
|
426
|
-
[slider.key]: next / slider.scale,
|
|
427
|
-
},
|
|
428
|
-
});
|
|
471
|
+
onCommitColorGrading(
|
|
472
|
+
colorGradingWithAdjust(grading, slider.key, next / slider.scale),
|
|
473
|
+
);
|
|
429
474
|
}}
|
|
430
475
|
onReset={() => {
|
|
431
|
-
onCommitColorGrading(
|
|
432
|
-
...grading,
|
|
433
|
-
intensity: visibleIntensity(grading),
|
|
434
|
-
adjust: {
|
|
435
|
-
...grading.adjust,
|
|
436
|
-
[slider.key]: 0,
|
|
437
|
-
},
|
|
438
|
-
});
|
|
476
|
+
onCommitColorGrading(colorGradingWithAdjust(grading, slider.key, 0));
|
|
439
477
|
}}
|
|
440
478
|
/>
|
|
441
479
|
);
|
|
@@ -499,7 +537,7 @@ export function ColorGradingControls({
|
|
|
499
537
|
onCommit={(next) => {
|
|
500
538
|
onCommitColorGrading({
|
|
501
539
|
...grading,
|
|
502
|
-
intensity:
|
|
540
|
+
intensity: visibleColorGradingIntensity(grading),
|
|
503
541
|
effects: {
|
|
504
542
|
...grading.effects,
|
|
505
543
|
[slider.key]: next / slider.scale,
|
|
@@ -509,7 +547,7 @@ export function ColorGradingControls({
|
|
|
509
547
|
onReset={() => {
|
|
510
548
|
onCommitColorGrading({
|
|
511
549
|
...grading,
|
|
512
|
-
intensity:
|
|
550
|
+
intensity: visibleColorGradingIntensity(grading),
|
|
513
551
|
effects: {
|
|
514
552
|
...grading.effects,
|
|
515
553
|
[slider.key]: 0,
|