@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
|
@@ -1,153 +1,40 @@
|
|
|
1
1
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
isHfColorGradingActive,
|
|
3
|
+
HF_COLOR_GRADING_GRADE_PRESETS,
|
|
5
4
|
normalizeHfColorGrading,
|
|
5
|
+
serializeHfColorGrading,
|
|
6
6
|
type HfColorGradingAdjustKey,
|
|
7
7
|
type HfColorGradingDetailKey,
|
|
8
|
-
type HfColorGradingEffectKey,
|
|
9
8
|
type NormalizedHfColorGrading,
|
|
10
9
|
} from "@hyperframes/core/color-grading";
|
|
11
|
-
import {
|
|
10
|
+
import { Plus, Settings } from "../../icons/SystemIcons";
|
|
12
11
|
import { useTrackDesignInput } from "../../contexts/DesignPanelInputContext";
|
|
13
12
|
import { LUT_EXT } from "../../utils/mediaTypes";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
// Tracks the active hold's cleanup so it can be torn down on unmount too —
|
|
38
|
-
// without this, switching selection away mid-hold (unmounting this
|
|
39
|
-
// accessory) leaves the pointerup/pointercancel/blur listeners registered
|
|
40
|
-
// on `window` forever, each holding a closure over the old commitCompare.
|
|
41
|
-
const releaseRef = useRef<(() => void) | null>(null);
|
|
42
|
-
useEffect(
|
|
43
|
-
() => () => {
|
|
44
|
-
releaseRef.current?.();
|
|
45
|
-
releaseRef.current = null;
|
|
46
|
-
},
|
|
47
|
-
[],
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
<span className="flex items-center gap-2.5">
|
|
52
|
-
<button
|
|
53
|
-
type="button"
|
|
54
|
-
aria-pressed={compareEnabled}
|
|
55
|
-
aria-label="Hold to show original"
|
|
56
|
-
disabled={!gradingActive}
|
|
57
|
-
onPointerDown={(e) => {
|
|
58
|
-
if (!gradingActive) return;
|
|
59
|
-
e.preventDefault();
|
|
60
|
-
e.stopPropagation();
|
|
61
|
-
track("button", "Compare original");
|
|
62
|
-
commitCompare(true);
|
|
63
|
-
const release = () => {
|
|
64
|
-
commitCompare(false);
|
|
65
|
-
window.removeEventListener("pointerup", release);
|
|
66
|
-
window.removeEventListener("pointercancel", release);
|
|
67
|
-
window.removeEventListener("blur", release);
|
|
68
|
-
releaseRef.current = null;
|
|
69
|
-
};
|
|
70
|
-
releaseRef.current = release;
|
|
71
|
-
window.addEventListener("pointerup", release);
|
|
72
|
-
window.addEventListener("pointercancel", release);
|
|
73
|
-
window.addEventListener("blur", release);
|
|
74
|
-
}}
|
|
75
|
-
onBlur={() => {
|
|
76
|
-
if (compareEnabled) commitCompare(false);
|
|
77
|
-
}}
|
|
78
|
-
onKeyDown={(e) => {
|
|
79
|
-
if (!gradingActive || (e.key !== " " && e.key !== "Enter")) return;
|
|
80
|
-
e.preventDefault();
|
|
81
|
-
if (!compareEnabled) {
|
|
82
|
-
track("button", "Compare original");
|
|
83
|
-
commitCompare(true);
|
|
84
|
-
}
|
|
85
|
-
}}
|
|
86
|
-
onKeyUp={(e) => {
|
|
87
|
-
if (!gradingActive || (e.key !== " " && e.key !== "Enter")) return;
|
|
88
|
-
e.preventDefault();
|
|
89
|
-
commitCompare(false);
|
|
90
|
-
}}
|
|
91
|
-
title="Hold to show original"
|
|
92
|
-
className="flex-shrink-0 text-panel-text-3 hover:text-panel-text-1 disabled:cursor-not-allowed disabled:opacity-40"
|
|
93
|
-
>
|
|
94
|
-
<Compare size={12} />
|
|
95
|
-
</button>
|
|
96
|
-
<span className="flex min-w-0 items-center gap-1" title={runtimeStatus.message}>
|
|
97
|
-
<span
|
|
98
|
-
data-flat-grade-status-dot="true"
|
|
99
|
-
title={runtimeStatus.message}
|
|
100
|
-
className={`h-[5px] w-[5px] flex-shrink-0 rounded-full ${STATUS_DOT_CLASS[runtimeStatus.state]}`}
|
|
101
|
-
/>
|
|
102
|
-
<span
|
|
103
|
-
data-flat-grade-status-message="true"
|
|
104
|
-
className="max-w-[84px] truncate text-[9px] text-panel-text-4"
|
|
105
|
-
>
|
|
106
|
-
{runtimeStatus.message}
|
|
107
|
-
</span>
|
|
108
|
-
</span>
|
|
109
|
-
<button
|
|
110
|
-
type="button"
|
|
111
|
-
data-flat-grade-reset="true"
|
|
112
|
-
title="Reset color grading"
|
|
113
|
-
onClick={(e) => {
|
|
114
|
-
e.stopPropagation();
|
|
115
|
-
track("button", "Reset color grading");
|
|
116
|
-
resetGrading();
|
|
117
|
-
}}
|
|
118
|
-
className="flex-shrink-0 text-panel-text-3 hover:text-panel-text-1"
|
|
119
|
-
>
|
|
120
|
-
<RotateCcw size={12} />
|
|
121
|
-
</button>
|
|
122
|
-
</span>
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const PRESET_OPTIONS = HF_COLOR_GRADING_PRESETS.map((p) => ({ value: p.id, label: p.label }));
|
|
127
|
-
|
|
128
|
-
const ADJUST_SLIDERS: Array<{
|
|
129
|
-
key: HfColorGradingAdjustKey;
|
|
130
|
-
label: string;
|
|
131
|
-
min: number;
|
|
132
|
-
max: number;
|
|
133
|
-
step: number;
|
|
134
|
-
}> = [
|
|
135
|
-
{ key: "exposure", label: "Exposure", min: -200, max: 200, step: 5 },
|
|
136
|
-
{ key: "contrast", label: "Contrast", min: -100, max: 100, step: 1 },
|
|
137
|
-
{ key: "highlights", label: "Highlights", min: -100, max: 100, step: 1 },
|
|
138
|
-
{ key: "shadows", label: "Shadows", min: -100, max: 100, step: 1 },
|
|
139
|
-
{ key: "whites", label: "White Point", min: -100, max: 100, step: 1 },
|
|
140
|
-
{ key: "blacks", label: "Black Point", min: -100, max: 100, step: 1 },
|
|
141
|
-
{ key: "temperature", label: "Warmth", min: -100, max: 100, step: 1 },
|
|
142
|
-
{ key: "tint", label: "Tint", min: -100, max: 100, step: 1 },
|
|
143
|
-
{ key: "vibrance", label: "Vibrance", min: -100, max: 100, step: 1 },
|
|
144
|
-
{ key: "saturation", label: "Saturation", min: -100, max: 100, step: 1 },
|
|
145
|
-
];
|
|
13
|
+
import { FLAT_PREVIEW_GRID, FlatSlider } from "./propertyPanelFlatPrimitives";
|
|
14
|
+
import type {
|
|
15
|
+
ColorGradingControllerState,
|
|
16
|
+
ColorGradingPresetPreviews,
|
|
17
|
+
ColorGradingPreviewOptions,
|
|
18
|
+
MediaMetadata,
|
|
19
|
+
} from "./useColorGradingController";
|
|
20
|
+
import { presetPreviewHandlers } from "./propertyPanelPresetPreview";
|
|
21
|
+
import { ColorCurves } from "./propertyPanelColorCurves";
|
|
22
|
+
import {
|
|
23
|
+
COLOR_GRADING_ADJUST_SLIDERS,
|
|
24
|
+
COLOR_GRADING_DETAIL_SLIDERS,
|
|
25
|
+
colorGradingWithAdjust,
|
|
26
|
+
colorGradingWithDetail,
|
|
27
|
+
createColorGradingActions,
|
|
28
|
+
GRAIN_TUNE_SLIDERS,
|
|
29
|
+
normalizedColorGradingDefault,
|
|
30
|
+
VIGNETTE_TUNE_SLIDERS,
|
|
31
|
+
visibleColorGradingIntensity,
|
|
32
|
+
} from "./propertyPanelColorGradingControls";
|
|
33
|
+
import { PropertyPanelColorScopes } from "./propertyPanelColorScopes";
|
|
34
|
+
import { PropertyPanelColorSecondary } from "./propertyPanelColorSecondary";
|
|
35
|
+
import { ColorWheels } from "./propertyPanelColorWheels";
|
|
146
36
|
|
|
147
|
-
|
|
148
|
-
// Earlier drafts could persist 0% strength; the next manual edit should revive visible grading.
|
|
149
|
-
return grading.intensity === 0 ? 1 : grading.intensity;
|
|
150
|
-
}
|
|
37
|
+
export { FlatColorGradingAccessory } from "./propertyPanelFlatColorGradingAccessory";
|
|
151
38
|
|
|
152
39
|
function formatAdjustValue(key: HfColorGradingAdjustKey, rawPercent: number): string {
|
|
153
40
|
if (key === "exposure") {
|
|
@@ -157,35 +44,17 @@ function formatAdjustValue(key: HfColorGradingAdjustKey, rawPercent: number): st
|
|
|
157
44
|
return `${Math.round(rawPercent)}%`;
|
|
158
45
|
}
|
|
159
46
|
|
|
160
|
-
const DETAIL_SLIDERS: Array<{
|
|
161
|
-
key: HfColorGradingDetailKey;
|
|
162
|
-
label: string;
|
|
163
|
-
defaultValue: number;
|
|
164
|
-
}> = [
|
|
165
|
-
{ key: "vignette", label: "Vignette", defaultValue: 0 },
|
|
166
|
-
{ key: "vignetteMidpoint", label: "Midpoint", defaultValue: 0.5 },
|
|
167
|
-
{ key: "vignetteRoundness", label: "Roundness", defaultValue: 0 },
|
|
168
|
-
{ key: "vignetteFeather", label: "Feather", defaultValue: 0.65 },
|
|
169
|
-
{ key: "grain", label: "Grain", defaultValue: 0 },
|
|
170
|
-
{ key: "grainSize", label: "Grain Size", defaultValue: 0.25 },
|
|
171
|
-
{ key: "grainRoughness", label: "Roughness", defaultValue: 0.5 },
|
|
172
|
-
];
|
|
173
47
|
const detailByKey = (key: HfColorGradingDetailKey) => {
|
|
174
|
-
const spec =
|
|
48
|
+
const spec = COLOR_GRADING_DETAIL_SLIDERS.find((candidate) => candidate.key === key);
|
|
175
49
|
if (!spec) throw new Error(`Unknown color grading detail key: ${key}`);
|
|
176
50
|
return spec;
|
|
177
51
|
};
|
|
178
|
-
const VIGNETTE_TUNE_KEYS: HfColorGradingDetailKey[] = [
|
|
179
|
-
"vignetteMidpoint",
|
|
180
|
-
"vignetteRoundness",
|
|
181
|
-
"vignetteFeather",
|
|
182
|
-
];
|
|
183
|
-
const GRAIN_TUNE_KEYS: HfColorGradingDetailKey[] = ["grainSize", "grainRoughness"];
|
|
184
52
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
53
|
+
function resolveColorGrading(grading: Parameters<typeof normalizeHfColorGrading>[0]) {
|
|
54
|
+
const resolved = normalizeHfColorGrading(grading);
|
|
55
|
+
if (!resolved) throw new Error("Missing resolved color grading");
|
|
56
|
+
return resolved;
|
|
57
|
+
}
|
|
189
58
|
|
|
190
59
|
function HdrBanner({ metadata }: { metadata: MediaMetadata | null }) {
|
|
191
60
|
if (metadata?.color.dynamicRange !== "hdr") return null;
|
|
@@ -231,23 +100,34 @@ export function FlatColorGradingSection({
|
|
|
231
100
|
assets,
|
|
232
101
|
onImportAssets,
|
|
233
102
|
onCommitColorGrading,
|
|
103
|
+
onPreviewColorGrading,
|
|
234
104
|
applyScope,
|
|
235
105
|
applyBusy,
|
|
236
106
|
onSetApplyScope,
|
|
237
107
|
onApplyToScope,
|
|
238
108
|
onApplyScopeAvailable,
|
|
239
109
|
mediaMetadata,
|
|
110
|
+
presetPreviews,
|
|
111
|
+
onRequestPresetPreviews,
|
|
112
|
+
captureGradedFrame,
|
|
240
113
|
}: {
|
|
241
114
|
grading: NormalizedHfColorGrading;
|
|
242
115
|
assets: string[];
|
|
243
116
|
onImportAssets?: (files: FileList, dir?: string) => Promise<string[]>;
|
|
244
117
|
onCommitColorGrading: (next: NormalizedHfColorGrading) => void;
|
|
118
|
+
onPreviewColorGrading: (
|
|
119
|
+
next: NormalizedHfColorGrading | null,
|
|
120
|
+
options?: ColorGradingPreviewOptions,
|
|
121
|
+
) => void;
|
|
245
122
|
applyScope: "source-file" | "project";
|
|
246
123
|
applyBusy: boolean;
|
|
247
124
|
onSetApplyScope: (scope: "source-file" | "project") => void;
|
|
248
125
|
onApplyToScope: () => void;
|
|
249
126
|
onApplyScopeAvailable: boolean;
|
|
250
127
|
mediaMetadata: MediaMetadata | null;
|
|
128
|
+
presetPreviews: ColorGradingPresetPreviews;
|
|
129
|
+
onRequestPresetPreviews: () => void;
|
|
130
|
+
captureGradedFrame: ColorGradingControllerState["captureGradedFrame"];
|
|
251
131
|
}) {
|
|
252
132
|
const track = useTrackDesignInput();
|
|
253
133
|
const lutInputRef = useRef<HTMLInputElement>(null);
|
|
@@ -259,59 +139,65 @@ export function FlatColorGradingSection({
|
|
|
259
139
|
);
|
|
260
140
|
const lut = grading.lut;
|
|
261
141
|
const selectedLutName = lut?.src ? (lut.src.split("/").pop() ?? lut.src) : null;
|
|
142
|
+
const resolvedGrading = useMemo(() => resolveColorGrading(grading), [grading]);
|
|
143
|
+
const secondaryInputGrading = useMemo(
|
|
144
|
+
() =>
|
|
145
|
+
resolveColorGrading({
|
|
146
|
+
intensity: 1,
|
|
147
|
+
adjust: resolvedGrading.adjust,
|
|
148
|
+
wheels: resolvedGrading.wheels,
|
|
149
|
+
curves: resolvedGrading.curves,
|
|
150
|
+
hueCurves: resolvedGrading.hueCurves,
|
|
151
|
+
colorSpace: resolvedGrading.colorSpace,
|
|
152
|
+
}),
|
|
153
|
+
[
|
|
154
|
+
resolvedGrading.adjust,
|
|
155
|
+
resolvedGrading.colorSpace,
|
|
156
|
+
resolvedGrading.curves,
|
|
157
|
+
resolvedGrading.hueCurves,
|
|
158
|
+
resolvedGrading.wheels,
|
|
159
|
+
],
|
|
160
|
+
);
|
|
161
|
+
const actions = createColorGradingActions(grading, onCommitColorGrading);
|
|
162
|
+
const scopesRefreshKey = useMemo(() => serializeHfColorGrading(grading), [grading]);
|
|
262
163
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
};
|
|
277
|
-
const importLuts = async (files: FileList | null) => {
|
|
278
|
-
if (!files?.length || !onImportAssets) return;
|
|
279
|
-
const uploaded = await onImportAssets(files, "assets/luts");
|
|
280
|
-
const firstLut = uploaded.find((asset) => LUT_EXT.test(asset));
|
|
281
|
-
if (firstLut) {
|
|
282
|
-
track("button", "Import LUT");
|
|
283
|
-
applyLut(firstLut, 1);
|
|
284
|
-
}
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
if (presetPreviews.status === "idle") onRequestPresetPreviews();
|
|
166
|
+
}, [onRequestPresetPreviews, presetPreviews.status]);
|
|
167
|
+
|
|
168
|
+
const resolvePreset = (presetId: string) => {
|
|
169
|
+
const resolved = normalizeHfColorGrading({ preset: presetId, lut: grading.lut });
|
|
170
|
+
return resolved
|
|
171
|
+
? {
|
|
172
|
+
...resolved,
|
|
173
|
+
effects: grading.effects,
|
|
174
|
+
palette: grading.palette,
|
|
175
|
+
}
|
|
176
|
+
: grading;
|
|
285
177
|
};
|
|
286
178
|
|
|
179
|
+
useEffect(() => () => onPreviewColorGrading(null), [onPreviewColorGrading]);
|
|
180
|
+
|
|
287
181
|
const renderDetailSlider = (key: HfColorGradingDetailKey) => {
|
|
288
182
|
const spec = detailByKey(key);
|
|
289
183
|
const value = grading.details[key];
|
|
290
|
-
const
|
|
184
|
+
const defaultValue = normalizedColorGradingDefault(spec);
|
|
185
|
+
const isSet = Math.abs(value - defaultValue) > 1e-4;
|
|
291
186
|
return (
|
|
292
187
|
<FlatSlider
|
|
293
188
|
key={key}
|
|
294
189
|
label={spec.label}
|
|
295
|
-
value={Math.round(value *
|
|
296
|
-
min={
|
|
297
|
-
max={
|
|
190
|
+
value={Math.round(value * spec.scale)}
|
|
191
|
+
min={spec.min}
|
|
192
|
+
max={spec.max}
|
|
193
|
+
step={spec.step}
|
|
298
194
|
tier={isSet ? "explicitCustom" : "default"}
|
|
299
|
-
displayValue={`${Math.round(value *
|
|
195
|
+
displayValue={`${Math.round(value * spec.scale)}${spec.suffix}`}
|
|
300
196
|
centerTick={key === "vignetteRoundness"}
|
|
301
197
|
onCommit={(next) =>
|
|
302
|
-
onCommitColorGrading(
|
|
303
|
-
...grading,
|
|
304
|
-
intensity: visibleIntensity(grading),
|
|
305
|
-
details: { ...grading.details, [key]: next / 100 },
|
|
306
|
-
})
|
|
307
|
-
}
|
|
308
|
-
onReset={() =>
|
|
309
|
-
onCommitColorGrading({
|
|
310
|
-
...grading,
|
|
311
|
-
intensity: visibleIntensity(grading),
|
|
312
|
-
details: { ...grading.details, [key]: spec.defaultValue },
|
|
313
|
-
})
|
|
198
|
+
onCommitColorGrading(colorGradingWithDetail(grading, key, next / spec.scale))
|
|
314
199
|
}
|
|
200
|
+
onReset={() => onCommitColorGrading(colorGradingWithDetail(grading, key, defaultValue))}
|
|
315
201
|
/>
|
|
316
202
|
);
|
|
317
203
|
};
|
|
@@ -319,31 +205,177 @@ export function FlatColorGradingSection({
|
|
|
319
205
|
return (
|
|
320
206
|
<div className="space-y-1.5">
|
|
321
207
|
<HdrBanner metadata={mediaMetadata} />
|
|
322
|
-
<
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
"
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
208
|
+
<PropertyPanelColorScopes
|
|
209
|
+
captureFrame={() => captureGradedFrame()}
|
|
210
|
+
refreshKey={scopesRefreshKey}
|
|
211
|
+
/>
|
|
212
|
+
<div data-flat-grade-presets="true" className="space-y-1.5">
|
|
213
|
+
{presetPreviews.status === "unavailable" && (
|
|
214
|
+
<button
|
|
215
|
+
type="button"
|
|
216
|
+
onClick={onRequestPresetPreviews}
|
|
217
|
+
className="text-[10px] font-medium text-panel-accent hover:text-panel-accent/80"
|
|
218
|
+
>
|
|
219
|
+
Retry look previews
|
|
220
|
+
</button>
|
|
221
|
+
)}
|
|
222
|
+
<div data-flat-grade-preset-group="presets" className={FLAT_PREVIEW_GRID}>
|
|
223
|
+
{HF_COLOR_GRADING_GRADE_PRESETS.map((preset) => {
|
|
224
|
+
const label = preset.label;
|
|
225
|
+
const selected = grading.preset === preset.id;
|
|
226
|
+
const preview = presetPreviews.images[preset.id];
|
|
227
|
+
return (
|
|
228
|
+
<button
|
|
229
|
+
key={preset.id}
|
|
230
|
+
type="button"
|
|
231
|
+
data-flat-grade-preset={preset.id}
|
|
232
|
+
aria-pressed={selected}
|
|
233
|
+
{...presetPreviewHandlers({
|
|
234
|
+
id: preset.id,
|
|
235
|
+
label,
|
|
236
|
+
resolve: () => resolvePreset(preset.id),
|
|
237
|
+
onPreview: onPreviewColorGrading,
|
|
238
|
+
onCommit: onCommitColorGrading,
|
|
239
|
+
onTrack: (name) => track("button", `Apply ${name}`),
|
|
240
|
+
})}
|
|
241
|
+
className={`min-w-0 overflow-hidden border text-left text-[10px] transition-colors ${
|
|
242
|
+
selected
|
|
243
|
+
? "border-panel-accent bg-panel-accent/10 text-panel-text-0"
|
|
244
|
+
: "border-panel-hairline bg-panel-bg-soft text-panel-text-3 hover:border-panel-border-input hover:text-panel-text-1"
|
|
245
|
+
}`}
|
|
246
|
+
>
|
|
247
|
+
<span
|
|
248
|
+
data-flat-grade-preview-frame={preset.id}
|
|
249
|
+
className="flex w-full items-center justify-center overflow-hidden bg-black/20"
|
|
250
|
+
style={{ aspectRatio: `${presetPreviews.width} / ${presetPreviews.height}` }}
|
|
251
|
+
>
|
|
252
|
+
{preview ? (
|
|
253
|
+
<img
|
|
254
|
+
data-flat-grade-preview={preset.id}
|
|
255
|
+
src={preview}
|
|
256
|
+
alt=""
|
|
257
|
+
draggable={false}
|
|
258
|
+
className="block h-full w-full object-contain"
|
|
259
|
+
/>
|
|
260
|
+
) : (
|
|
261
|
+
<span
|
|
262
|
+
data-flat-grade-preview-placeholder={presetPreviews.status}
|
|
263
|
+
className="h-full w-full bg-panel-bg-soft"
|
|
264
|
+
/>
|
|
265
|
+
)}
|
|
266
|
+
</span>
|
|
267
|
+
<span className="block truncate px-2 py-1.5">{label}</span>
|
|
268
|
+
</button>
|
|
269
|
+
);
|
|
270
|
+
})}
|
|
271
|
+
</div>
|
|
335
272
|
</div>
|
|
336
273
|
<FlatSlider
|
|
337
|
-
label="
|
|
274
|
+
label="Amount"
|
|
338
275
|
value={Math.round(grading.intensity * 100)}
|
|
339
276
|
min={0}
|
|
340
277
|
max={100}
|
|
341
278
|
tier={grading.intensity === 1 ? "default" : "explicitCustom"}
|
|
342
279
|
displayValue={`${Math.round(grading.intensity * 100)}%`}
|
|
343
|
-
onCommit={
|
|
344
|
-
onReset={() =>
|
|
280
|
+
onCommit={actions.setIntensityPercent}
|
|
281
|
+
onReset={() => actions.setIntensityPercent(100)}
|
|
345
282
|
/>
|
|
346
283
|
|
|
284
|
+
<div className="space-y-0.5 border-t border-panel-hairline pt-1.5">
|
|
285
|
+
<div className="mb-1 text-[9px] font-semibold uppercase tracking-[0.12em] text-panel-text-5">
|
|
286
|
+
Primary
|
|
287
|
+
</div>
|
|
288
|
+
{COLOR_GRADING_ADJUST_SLIDERS.map((slider) => {
|
|
289
|
+
const rawPercent = grading.adjust[slider.key] * slider.scale;
|
|
290
|
+
const isSet = Math.abs(grading.adjust[slider.key]) > 1e-6;
|
|
291
|
+
return (
|
|
292
|
+
<div key={slider.key} data-flat-grade-adjust="true">
|
|
293
|
+
<FlatSlider
|
|
294
|
+
label={slider.label}
|
|
295
|
+
value={rawPercent}
|
|
296
|
+
min={slider.min}
|
|
297
|
+
max={slider.max}
|
|
298
|
+
step={slider.step}
|
|
299
|
+
tier={isSet ? "explicitCustom" : "default"}
|
|
300
|
+
displayValue={formatAdjustValue(slider.key, rawPercent)}
|
|
301
|
+
centerTick
|
|
302
|
+
onCommit={(next) =>
|
|
303
|
+
onCommitColorGrading(
|
|
304
|
+
colorGradingWithAdjust(grading, slider.key, next / slider.scale),
|
|
305
|
+
)
|
|
306
|
+
}
|
|
307
|
+
onReset={() => onCommitColorGrading(colorGradingWithAdjust(grading, slider.key, 0))}
|
|
308
|
+
/>
|
|
309
|
+
</div>
|
|
310
|
+
);
|
|
311
|
+
})}
|
|
312
|
+
</div>
|
|
313
|
+
|
|
314
|
+
<div className="space-y-1.5 border-t border-panel-hairline pt-1.5">
|
|
315
|
+
<div className="mb-1 text-[9px] font-semibold uppercase tracking-[0.12em] text-panel-text-5">
|
|
316
|
+
Color wheels
|
|
317
|
+
</div>
|
|
318
|
+
<ColorWheels
|
|
319
|
+
value={resolvedGrading.wheels}
|
|
320
|
+
onPreview={(wheels) =>
|
|
321
|
+
onPreviewColorGrading({
|
|
322
|
+
...grading,
|
|
323
|
+
intensity: visibleColorGradingIntensity(grading),
|
|
324
|
+
wheels,
|
|
325
|
+
})
|
|
326
|
+
}
|
|
327
|
+
onCommit={(wheels) =>
|
|
328
|
+
onCommitColorGrading({
|
|
329
|
+
...grading,
|
|
330
|
+
intensity: visibleColorGradingIntensity(grading),
|
|
331
|
+
wheels,
|
|
332
|
+
})
|
|
333
|
+
}
|
|
334
|
+
/>
|
|
335
|
+
</div>
|
|
336
|
+
|
|
337
|
+
<div className="space-y-1.5 border-t border-panel-hairline pt-1.5">
|
|
338
|
+
<div className="mb-1 text-[9px] font-semibold uppercase tracking-[0.12em] text-panel-text-5">
|
|
339
|
+
Curves
|
|
340
|
+
</div>
|
|
341
|
+
<ColorCurves
|
|
342
|
+
value={{ curves: resolvedGrading.curves, hueCurves: resolvedGrading.hueCurves }}
|
|
343
|
+
onPreview={({ curves, hueCurves }) =>
|
|
344
|
+
onPreviewColorGrading({
|
|
345
|
+
...grading,
|
|
346
|
+
intensity: visibleColorGradingIntensity(grading),
|
|
347
|
+
curves,
|
|
348
|
+
hueCurves,
|
|
349
|
+
})
|
|
350
|
+
}
|
|
351
|
+
onCommit={({ curves, hueCurves }) =>
|
|
352
|
+
onCommitColorGrading({
|
|
353
|
+
...grading,
|
|
354
|
+
intensity: visibleColorGradingIntensity(grading),
|
|
355
|
+
curves,
|
|
356
|
+
hueCurves,
|
|
357
|
+
})
|
|
358
|
+
}
|
|
359
|
+
/>
|
|
360
|
+
</div>
|
|
361
|
+
|
|
362
|
+
<div className="space-y-1.5 border-t border-panel-hairline pt-1.5">
|
|
363
|
+
<div className="mb-1 text-[9px] font-semibold uppercase tracking-[0.12em] text-panel-text-5">
|
|
364
|
+
Secondary color
|
|
365
|
+
</div>
|
|
366
|
+
<PropertyPanelColorSecondary
|
|
367
|
+
secondaries={resolvedGrading.secondaries}
|
|
368
|
+
captureFrame={() => captureGradedFrame({ grading: secondaryInputGrading })}
|
|
369
|
+
onCommit={(secondaries) =>
|
|
370
|
+
onCommitColorGrading({
|
|
371
|
+
...grading,
|
|
372
|
+
intensity: visibleColorGradingIntensity(grading),
|
|
373
|
+
secondaries,
|
|
374
|
+
})
|
|
375
|
+
}
|
|
376
|
+
/>
|
|
377
|
+
</div>
|
|
378
|
+
|
|
347
379
|
<div className="border-t border-panel-hairline pt-1.5">
|
|
348
380
|
<button
|
|
349
381
|
type="button"
|
|
@@ -375,7 +407,7 @@ export function FlatColorGradingSection({
|
|
|
375
407
|
onChange={(e) => {
|
|
376
408
|
const src = e.target.value;
|
|
377
409
|
track("select", "Custom LUT");
|
|
378
|
-
applyLut(src || null, src && lut?.src === src ? lut.intensity : 1);
|
|
410
|
+
actions.applyLut(src || null, src && lut?.src === src ? lut.intensity : 1);
|
|
379
411
|
}}
|
|
380
412
|
className="border-b border-panel-border-input/50 bg-transparent font-mono text-[10px] text-panel-text-3 outline-none hover:border-panel-border-input"
|
|
381
413
|
>
|
|
@@ -401,7 +433,9 @@ export function FlatColorGradingSection({
|
|
|
401
433
|
accept=".cube"
|
|
402
434
|
className="hidden"
|
|
403
435
|
onChange={(e) => {
|
|
404
|
-
void
|
|
436
|
+
void actions.importLut(e.currentTarget.files, onImportAssets, () =>
|
|
437
|
+
track("button", "Import LUT"),
|
|
438
|
+
);
|
|
405
439
|
e.currentTarget.value = "";
|
|
406
440
|
}}
|
|
407
441
|
/>
|
|
@@ -414,55 +448,17 @@ export function FlatColorGradingSection({
|
|
|
414
448
|
max={100}
|
|
415
449
|
tier={lut.intensity === 1 ? "default" : "explicitCustom"}
|
|
416
450
|
displayValue={`${Math.round((lut.intensity ?? 1) * 100)}%`}
|
|
417
|
-
onCommit={(v) => applyLut(lut.src, v / 100)}
|
|
418
|
-
onReset={() => applyLut(lut.src, 1)}
|
|
451
|
+
onCommit={(v) => actions.applyLut(lut.src, v / 100)}
|
|
452
|
+
onReset={() => actions.applyLut(lut.src, 1)}
|
|
419
453
|
/>
|
|
420
454
|
)}
|
|
421
455
|
</div>
|
|
422
456
|
)}
|
|
423
457
|
</div>
|
|
424
458
|
|
|
425
|
-
<div className="space-y-0.5 border-t border-panel-hairline pt-1.5">
|
|
426
|
-
<div className="mb-1 text-[9px] font-semibold uppercase tracking-[0.12em] text-panel-text-5">
|
|
427
|
-
Adjust
|
|
428
|
-
</div>
|
|
429
|
-
{ADJUST_SLIDERS.map((slider) => {
|
|
430
|
-
const rawPercent = grading.adjust[slider.key] * 100;
|
|
431
|
-
const isSet = Math.abs(grading.adjust[slider.key]) > 1e-6;
|
|
432
|
-
return (
|
|
433
|
-
<div key={slider.key} data-flat-grade-adjust="true">
|
|
434
|
-
<FlatSlider
|
|
435
|
-
label={slider.label}
|
|
436
|
-
value={rawPercent}
|
|
437
|
-
min={slider.min}
|
|
438
|
-
max={slider.max}
|
|
439
|
-
step={slider.step}
|
|
440
|
-
tier={isSet ? "explicitCustom" : "default"}
|
|
441
|
-
displayValue={formatAdjustValue(slider.key, rawPercent)}
|
|
442
|
-
centerTick
|
|
443
|
-
onCommit={(next) =>
|
|
444
|
-
onCommitColorGrading({
|
|
445
|
-
...grading,
|
|
446
|
-
intensity: visibleIntensity(grading),
|
|
447
|
-
adjust: { ...grading.adjust, [slider.key]: next / 100 },
|
|
448
|
-
})
|
|
449
|
-
}
|
|
450
|
-
onReset={() =>
|
|
451
|
-
onCommitColorGrading({
|
|
452
|
-
...grading,
|
|
453
|
-
intensity: visibleIntensity(grading),
|
|
454
|
-
adjust: { ...grading.adjust, [slider.key]: 0 },
|
|
455
|
-
})
|
|
456
|
-
}
|
|
457
|
-
/>
|
|
458
|
-
</div>
|
|
459
|
-
);
|
|
460
|
-
})}
|
|
461
|
-
</div>
|
|
462
|
-
|
|
463
459
|
<div className="space-y-1.5 border-t border-panel-hairline pt-1.5">
|
|
464
460
|
<div className="mb-1 text-[9px] font-semibold uppercase tracking-[0.12em] text-panel-text-5">
|
|
465
|
-
|
|
461
|
+
Finish
|
|
466
462
|
</div>
|
|
467
463
|
<div className="flex items-center gap-1.5">
|
|
468
464
|
<div className="flex-1">{renderDetailSlider("vignette")}</div>
|
|
@@ -490,49 +486,13 @@ export function FlatColorGradingSection({
|
|
|
490
486
|
</div>
|
|
491
487
|
{detailSettingsOpen && (
|
|
492
488
|
<div className="space-y-0.5 border-l-2 border-panel-border-input pl-2.5">
|
|
493
|
-
{(detailSettingsOpen === "vignette" ?
|
|
494
|
-
renderDetailSlider,
|
|
489
|
+
{(detailSettingsOpen === "vignette" ? VIGNETTE_TUNE_SLIDERS : GRAIN_TUNE_SLIDERS).map(
|
|
490
|
+
(slider) => renderDetailSlider(slider.key),
|
|
495
491
|
)}
|
|
496
492
|
</div>
|
|
497
493
|
)}
|
|
498
494
|
</div>
|
|
499
495
|
|
|
500
|
-
<div className="space-y-0.5 border-t border-panel-hairline pt-1.5">
|
|
501
|
-
<div className="mb-1 text-[9px] font-semibold uppercase tracking-[0.12em] text-panel-text-5">
|
|
502
|
-
Effects
|
|
503
|
-
</div>
|
|
504
|
-
{EFFECT_SLIDERS.map((slider) => {
|
|
505
|
-
const value = grading.effects[slider.key];
|
|
506
|
-
const isSet = value > 1e-6;
|
|
507
|
-
return (
|
|
508
|
-
<div key={slider.key} data-flat-grade-effect="true">
|
|
509
|
-
<FlatSlider
|
|
510
|
-
label={slider.label}
|
|
511
|
-
value={Math.round(value * 100)}
|
|
512
|
-
min={0}
|
|
513
|
-
max={100}
|
|
514
|
-
tier={isSet ? "explicitCustom" : "default"}
|
|
515
|
-
displayValue={`${Math.round(value * 100)}%`}
|
|
516
|
-
onCommit={(next) =>
|
|
517
|
-
onCommitColorGrading({
|
|
518
|
-
...grading,
|
|
519
|
-
intensity: visibleIntensity(grading),
|
|
520
|
-
effects: { ...grading.effects, [slider.key]: next / 100 },
|
|
521
|
-
})
|
|
522
|
-
}
|
|
523
|
-
onReset={() =>
|
|
524
|
-
onCommitColorGrading({
|
|
525
|
-
...grading,
|
|
526
|
-
intensity: visibleIntensity(grading),
|
|
527
|
-
effects: { ...grading.effects, [slider.key]: 0 },
|
|
528
|
-
})
|
|
529
|
-
}
|
|
530
|
-
/>
|
|
531
|
-
</div>
|
|
532
|
-
);
|
|
533
|
-
})}
|
|
534
|
-
</div>
|
|
535
|
-
|
|
536
496
|
{onApplyScopeAvailable && (
|
|
537
497
|
<div className="flex items-center justify-between gap-2 border-t border-panel-hairline pt-1.5">
|
|
538
498
|
<span className="flex items-center gap-1.5 text-[11px] text-panel-text-2">
|