@hyperframes/studio 0.7.70 → 0.7.72
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-DpKeYLEo.js → hyperframes-player-Bjf2HPzR.js} +1 -1
- package/dist/assets/index-BgZMle9I.css +1 -0
- package/dist/assets/{index-DXjAllII.js → index-Ch1hbJ3e.js} +1 -1
- package/dist/assets/{index-CbX-xoud.js → index-Ct_pxETK.js} +1 -1
- package/dist/assets/index-DCyWLHnx.js +428 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.html +2 -2
- package/dist/index.js +5383 -3804
- 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 +74 -88
- 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/propertyPanelFlatColorGradingSection.test.tsx +73 -40
- package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +85 -66
- 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.test.tsx +5 -5
- package/src/components/editor/propertyPanelFlatPrimitives.tsx +2 -0
- package/src/components/editor/propertyPanelPresetPreview.ts +36 -0
- package/src/components/editor/propertyPanelTypes.ts +13 -0
- package/src/components/editor/useColorGradingController.test.ts +235 -30
- package/src/components/editor/useColorGradingController.ts +46 -88
- package/src/components/editor/useColorGradingPreviews.ts +307 -0
- 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/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-CAeU317U.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
|
@@ -215,39 +215,98 @@ function neutralPropsBase() {
|
|
|
215
215
|
grading: neutralGrading(),
|
|
216
216
|
assets: [] as string[],
|
|
217
217
|
onCommitColorGrading: vi.fn(),
|
|
218
|
+
onPreviewColorGrading: vi.fn(),
|
|
218
219
|
applyScope: "source-file" as const,
|
|
219
220
|
applyBusy: false,
|
|
220
221
|
onSetApplyScope: vi.fn(),
|
|
221
222
|
onApplyToScope: vi.fn(),
|
|
222
223
|
onApplyScopeAvailable: true,
|
|
223
224
|
mediaMetadata: null,
|
|
225
|
+
presetPreviews: {
|
|
226
|
+
status: "ready" as const,
|
|
227
|
+
images: { "bright-pop": "data:image/png;base64,bright" },
|
|
228
|
+
width: 160,
|
|
229
|
+
height: 90,
|
|
230
|
+
},
|
|
231
|
+
onRequestPresetPreviews: vi.fn(),
|
|
224
232
|
};
|
|
225
233
|
}
|
|
226
234
|
|
|
227
235
|
describe("FlatColorGradingSection — Preset + LUT", () => {
|
|
228
|
-
it("
|
|
236
|
+
it("previews looks without committing, restores on leave, and commits only on click", () => {
|
|
229
237
|
const onCommitColorGrading = vi.fn();
|
|
238
|
+
const onPreviewColorGrading = vi.fn();
|
|
239
|
+
const base = neutralGrading();
|
|
240
|
+
const grading = {
|
|
241
|
+
...base,
|
|
242
|
+
intensity: 0.4,
|
|
243
|
+
adjust: { ...base.adjust, contrast: 0.3 },
|
|
244
|
+
details: { ...base.details, vignette: 0.2 },
|
|
245
|
+
effects: { ...base.effects, pixelate: 0.5 },
|
|
246
|
+
palette: ["#112233", "#ffffff"],
|
|
247
|
+
lut: { src: "assets/luts/custom.cube", intensity: 0.6 },
|
|
248
|
+
};
|
|
230
249
|
const { host, root } = renderInto(
|
|
231
250
|
<FlatColorGradingSection
|
|
232
251
|
{...neutralPropsBase()}
|
|
252
|
+
grading={grading}
|
|
233
253
|
onCommitColorGrading={onCommitColorGrading}
|
|
254
|
+
onPreviewColorGrading={onPreviewColorGrading}
|
|
234
255
|
/>,
|
|
235
256
|
);
|
|
236
|
-
const
|
|
237
|
-
'[data-flat-grade-preset="
|
|
257
|
+
const brightPop = host.querySelector<HTMLButtonElement>(
|
|
258
|
+
'[data-flat-grade-preset="bright-pop"]',
|
|
238
259
|
);
|
|
239
|
-
if (!
|
|
240
|
-
expect(presetSelect.value).toBe("neutral");
|
|
241
|
-
// The visible "Preset" label is a sibling span outside FlatSelectRow
|
|
242
|
-
// (label="" there, to avoid rendering it twice) — the select still
|
|
243
|
-
// needs its own accessible name via the dedicated ariaLabel prop.
|
|
244
|
-
expect(presetSelect.getAttribute("aria-label")).toBe("Preset");
|
|
260
|
+
if (!brightPop) throw new Error("expected a Bright Pop look button");
|
|
245
261
|
act(() => {
|
|
246
|
-
|
|
247
|
-
presetSelect.dispatchEvent(new Event("change", { bubbles: true }));
|
|
262
|
+
brightPop.dispatchEvent(new MouseEvent("pointerover", { bubbles: true }));
|
|
248
263
|
});
|
|
264
|
+
expect(onPreviewColorGrading.mock.calls.at(-1)?.[0].preset).toBe("bright-pop");
|
|
265
|
+
expect(onPreviewColorGrading.mock.calls.at(-1)?.[1]).toEqual({
|
|
266
|
+
animatedPreview: { kind: "presets", id: "bright-pop" },
|
|
267
|
+
});
|
|
268
|
+
expect(onCommitColorGrading).not.toHaveBeenCalled();
|
|
269
|
+
act(() => brightPop.dispatchEvent(new MouseEvent("pointerout", { bubbles: true })));
|
|
270
|
+
expect(onPreviewColorGrading).toHaveBeenLastCalledWith(null);
|
|
271
|
+
act(() => brightPop.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
|
249
272
|
expect(onCommitColorGrading).toHaveBeenCalledTimes(1);
|
|
250
|
-
expect(onCommitColorGrading.mock.calls[0][0]
|
|
273
|
+
expect(onCommitColorGrading.mock.calls[0][0]).toMatchObject({
|
|
274
|
+
preset: "bright-pop",
|
|
275
|
+
intensity: 1,
|
|
276
|
+
effects: { pixelate: 0.5 },
|
|
277
|
+
palette: ["#112233", "#ffffff"],
|
|
278
|
+
lut: { src: "assets/luts/custom.cube", intensity: 0.6 },
|
|
279
|
+
});
|
|
280
|
+
expect(onCommitColorGrading.mock.calls[0][0].adjust.contrast).not.toBe(0.3);
|
|
281
|
+
expect(onCommitColorGrading.mock.calls[0][0].details.vignette).not.toBe(0.2);
|
|
282
|
+
act(() => root.unmount());
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("keeps effect-bearing saved styles out of Grade", () => {
|
|
286
|
+
const { host, root } = renderInto(<FlatColorGradingSection {...neutralPropsBase()} />);
|
|
287
|
+
const presets = host.querySelector('[data-flat-grade-preset-group="presets"]');
|
|
288
|
+
expect(presets?.querySelector('[data-flat-grade-preset="bright-pop"]')).not.toBeNull();
|
|
289
|
+
expect(presets?.querySelector('[data-flat-grade-preset="vhs-playback"]')).toBeNull();
|
|
290
|
+
expect(presets?.querySelector('[data-flat-grade-preset="editorial-halftone"]')).toBeNull();
|
|
291
|
+
expect(host.querySelector('[data-flat-grade-preset="neutral"]')?.textContent).toContain(
|
|
292
|
+
"Neutral",
|
|
293
|
+
);
|
|
294
|
+
act(() => root.unmount());
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it("shows exact selected-media preview images and requests a fresh batch on mount", () => {
|
|
298
|
+
const onRequestPresetPreviews = vi.fn();
|
|
299
|
+
const { host, root } = renderInto(
|
|
300
|
+
<FlatColorGradingSection
|
|
301
|
+
{...neutralPropsBase()}
|
|
302
|
+
onRequestPresetPreviews={onRequestPresetPreviews}
|
|
303
|
+
/>,
|
|
304
|
+
);
|
|
305
|
+
expect(onRequestPresetPreviews).toHaveBeenCalledTimes(1);
|
|
306
|
+
expect(
|
|
307
|
+
host.querySelector<HTMLImageElement>('[data-flat-grade-preview="bright-pop"]')?.src,
|
|
308
|
+
).toContain("data:image/png;base64,bright");
|
|
309
|
+
expect(host.textContent).not.toContain("VHS Playback");
|
|
251
310
|
act(() => root.unmount());
|
|
252
311
|
});
|
|
253
312
|
|
|
@@ -410,7 +469,7 @@ describe("FlatColorGradingSection — Adjust sliders", () => {
|
|
|
410
469
|
act(() => root.unmount());
|
|
411
470
|
});
|
|
412
471
|
|
|
413
|
-
it("does NOT force intensity to revive when the
|
|
472
|
+
it("does NOT force intensity to revive when the Amount slider itself is dragged — it writes the value directly", () => {
|
|
414
473
|
const onCommitColorGrading = vi.fn();
|
|
415
474
|
const grading = { ...neutralGrading(), intensity: 0 };
|
|
416
475
|
const { host, root } = renderInto(
|
|
@@ -420,7 +479,7 @@ describe("FlatColorGradingSection — Adjust sliders", () => {
|
|
|
420
479
|
onCommitColorGrading={onCommitColorGrading}
|
|
421
480
|
/>,
|
|
422
481
|
);
|
|
423
|
-
const strengthRow = findRowByText(host, "div", "
|
|
482
|
+
const strengthRow = findRowByText(host, "div", "Amount", "startsWith");
|
|
424
483
|
// min=0, max=100, step=1, ratio=0.4 -> raw=40 -> commit(40) -> intensity = 40/100 = 0.4
|
|
425
484
|
dragSliderTrack(strengthRow, 40, 100);
|
|
426
485
|
expect(onCommitColorGrading).toHaveBeenCalledTimes(1);
|
|
@@ -492,32 +551,6 @@ describe("FlatColorGradingSection — Vignette and Grain", () => {
|
|
|
492
551
|
});
|
|
493
552
|
});
|
|
494
553
|
|
|
495
|
-
describe("FlatColorGradingSection — Effects", () => {
|
|
496
|
-
it("renders Blur and Pixelate sliders under an Effects micro-label", () => {
|
|
497
|
-
const { host, root } = renderInto(<FlatColorGradingSection {...neutralPropsBase()} />);
|
|
498
|
-
expect(host.textContent).toContain("Effects");
|
|
499
|
-
const rows = host.querySelectorAll('[data-flat-grade-effect="true"]');
|
|
500
|
-
expect(rows).toHaveLength(2);
|
|
501
|
-
act(() => root.unmount());
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
it("commits a dragged Pixelate value on slider track pointerdown, scaled from percent to the 0..1 effect range", () => {
|
|
505
|
-
const onCommitColorGrading = vi.fn();
|
|
506
|
-
const { host, root } = renderInto(
|
|
507
|
-
<FlatColorGradingSection
|
|
508
|
-
{...neutralPropsBase()}
|
|
509
|
-
onCommitColorGrading={onCommitColorGrading}
|
|
510
|
-
/>,
|
|
511
|
-
);
|
|
512
|
-
const pixelateRow = findRowByText(host, '[data-flat-grade-effect="true"]', "Pixelate");
|
|
513
|
-
// min=0, max=100, step=1, ratio=0.75 -> raw=75 -> commit(75) -> effects.pixelate = 75/100 = 0.75
|
|
514
|
-
dragSliderTrack(pixelateRow, 75, 100);
|
|
515
|
-
expect(onCommitColorGrading).toHaveBeenCalledTimes(1);
|
|
516
|
-
expect(onCommitColorGrading.mock.calls[0][0].effects.pixelate).toBe(0.75);
|
|
517
|
-
act(() => root.unmount());
|
|
518
|
-
});
|
|
519
|
-
});
|
|
520
|
-
|
|
521
554
|
describe("FlatColorGradingSection — HDR banner and Apply scope", () => {
|
|
522
555
|
it("shows the HDR banner only when mediaMetadata reports an HDR source", () => {
|
|
523
556
|
const { host, root } = renderInto(
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
HF_COLOR_GRADING_GRADE_PRESETS,
|
|
4
4
|
isHfColorGradingActive,
|
|
5
5
|
normalizeHfColorGrading,
|
|
6
6
|
type HfColorGradingAdjustKey,
|
|
7
7
|
type HfColorGradingDetailKey,
|
|
8
|
-
type HfColorGradingEffectKey,
|
|
9
8
|
type NormalizedHfColorGrading,
|
|
10
9
|
} from "@hyperframes/core/color-grading";
|
|
11
10
|
import { Compare, Plus, RotateCcw, Settings } from "../../icons/SystemIcons";
|
|
12
11
|
import { useTrackDesignInput } from "../../contexts/DesignPanelInputContext";
|
|
13
12
|
import { LUT_EXT } from "../../utils/mediaTypes";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
|
|
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";
|
|
17
21
|
|
|
18
22
|
const STATUS_DOT_CLASS: Record<ColorGradingControllerState["runtimeStatus"]["state"], string> = {
|
|
19
23
|
active: "bg-emerald-400",
|
|
@@ -123,8 +127,6 @@ export function FlatColorGradingAccessory({
|
|
|
123
127
|
);
|
|
124
128
|
}
|
|
125
129
|
|
|
126
|
-
const PRESET_OPTIONS = HF_COLOR_GRADING_PRESETS.map((p) => ({ value: p.id, label: p.label }));
|
|
127
|
-
|
|
128
130
|
const ADJUST_SLIDERS: Array<{
|
|
129
131
|
key: HfColorGradingAdjustKey;
|
|
130
132
|
label: string;
|
|
@@ -182,11 +184,6 @@ const VIGNETTE_TUNE_KEYS: HfColorGradingDetailKey[] = [
|
|
|
182
184
|
];
|
|
183
185
|
const GRAIN_TUNE_KEYS: HfColorGradingDetailKey[] = ["grainSize", "grainRoughness"];
|
|
184
186
|
|
|
185
|
-
const EFFECT_SLIDERS: Array<{ key: HfColorGradingEffectKey; label: string }> = [
|
|
186
|
-
{ key: "blur", label: "Blur" },
|
|
187
|
-
{ key: "pixelate", label: "Pixelate" },
|
|
188
|
-
];
|
|
189
|
-
|
|
190
187
|
function HdrBanner({ metadata }: { metadata: MediaMetadata | null }) {
|
|
191
188
|
if (metadata?.color.dynamicRange !== "hdr") return null;
|
|
192
189
|
const details = [
|
|
@@ -231,23 +228,32 @@ export function FlatColorGradingSection({
|
|
|
231
228
|
assets,
|
|
232
229
|
onImportAssets,
|
|
233
230
|
onCommitColorGrading,
|
|
231
|
+
onPreviewColorGrading,
|
|
234
232
|
applyScope,
|
|
235
233
|
applyBusy,
|
|
236
234
|
onSetApplyScope,
|
|
237
235
|
onApplyToScope,
|
|
238
236
|
onApplyScopeAvailable,
|
|
239
237
|
mediaMetadata,
|
|
238
|
+
presetPreviews,
|
|
239
|
+
onRequestPresetPreviews,
|
|
240
240
|
}: {
|
|
241
241
|
grading: NormalizedHfColorGrading;
|
|
242
242
|
assets: string[];
|
|
243
243
|
onImportAssets?: (files: FileList, dir?: string) => Promise<string[]>;
|
|
244
244
|
onCommitColorGrading: (next: NormalizedHfColorGrading) => void;
|
|
245
|
+
onPreviewColorGrading: (
|
|
246
|
+
next: NormalizedHfColorGrading | null,
|
|
247
|
+
options?: ColorGradingPreviewOptions,
|
|
248
|
+
) => void;
|
|
245
249
|
applyScope: "source-file" | "project";
|
|
246
250
|
applyBusy: boolean;
|
|
247
251
|
onSetApplyScope: (scope: "source-file" | "project") => void;
|
|
248
252
|
onApplyToScope: () => void;
|
|
249
253
|
onApplyScopeAvailable: boolean;
|
|
250
254
|
mediaMetadata: MediaMetadata | null;
|
|
255
|
+
presetPreviews: ColorGradingPresetPreviews;
|
|
256
|
+
onRequestPresetPreviews: () => void;
|
|
251
257
|
}) {
|
|
252
258
|
const track = useTrackDesignInput();
|
|
253
259
|
const lutInputRef = useRef<HTMLInputElement>(null);
|
|
@@ -260,10 +266,21 @@ export function FlatColorGradingSection({
|
|
|
260
266
|
const lut = grading.lut;
|
|
261
267
|
const selectedLutName = lut?.src ? (lut.src.split("/").pop() ?? lut.src) : null;
|
|
262
268
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
269
|
+
useEffect(() => {
|
|
270
|
+
if (
|
|
271
|
+
presetPreviews.status !== "loading" &&
|
|
272
|
+
HF_COLOR_GRADING_GRADE_PRESETS.some((preset) => !presetPreviews.images[preset.id])
|
|
273
|
+
) {
|
|
274
|
+
onRequestPresetPreviews();
|
|
275
|
+
}
|
|
276
|
+
}, [onRequestPresetPreviews, presetPreviews.images, presetPreviews.status]);
|
|
277
|
+
|
|
278
|
+
const resolvePreset = (presetId: string) => {
|
|
279
|
+
const resolved = normalizeHfColorGrading({ preset: presetId, lut: grading.lut });
|
|
280
|
+
return resolved ? { ...resolved, effects: grading.effects, palette: grading.palette } : grading;
|
|
266
281
|
};
|
|
282
|
+
|
|
283
|
+
useEffect(() => () => onPreviewColorGrading(null), [onPreviewColorGrading]);
|
|
267
284
|
const updateIntensity = (value: number) => {
|
|
268
285
|
onCommitColorGrading({ ...grading, intensity: value / 100 });
|
|
269
286
|
};
|
|
@@ -319,22 +336,60 @@ export function FlatColorGradingSection({
|
|
|
319
336
|
return (
|
|
320
337
|
<div className="space-y-1.5">
|
|
321
338
|
<HdrBanner metadata={mediaMetadata} />
|
|
322
|
-
<div data-flat-grade-
|
|
323
|
-
<
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
339
|
+
<div data-flat-grade-presets="true" className="space-y-1.5">
|
|
340
|
+
<div data-flat-grade-preset-group="presets" className={FLAT_PREVIEW_GRID}>
|
|
341
|
+
{HF_COLOR_GRADING_GRADE_PRESETS.map((preset) => {
|
|
342
|
+
const label = preset.label;
|
|
343
|
+
const selected = grading.preset === preset.id;
|
|
344
|
+
const preview = presetPreviews.images[preset.id];
|
|
345
|
+
return (
|
|
346
|
+
<button
|
|
347
|
+
key={preset.id}
|
|
348
|
+
type="button"
|
|
349
|
+
data-flat-grade-preset={preset.id}
|
|
350
|
+
aria-pressed={selected}
|
|
351
|
+
{...presetPreviewHandlers({
|
|
352
|
+
id: preset.id,
|
|
353
|
+
label,
|
|
354
|
+
resolve: () => resolvePreset(preset.id),
|
|
355
|
+
onPreview: onPreviewColorGrading,
|
|
356
|
+
onCommit: onCommitColorGrading,
|
|
357
|
+
onTrack: (name) => track("button", `Apply ${name}`),
|
|
358
|
+
})}
|
|
359
|
+
className={`min-w-0 overflow-hidden border text-left text-[10px] transition-colors ${
|
|
360
|
+
selected
|
|
361
|
+
? "border-panel-accent bg-panel-accent/10 text-panel-text-0"
|
|
362
|
+
: "border-panel-hairline bg-panel-bg-soft text-panel-text-3 hover:border-panel-border-input hover:text-panel-text-1"
|
|
363
|
+
}`}
|
|
364
|
+
>
|
|
365
|
+
<span
|
|
366
|
+
data-flat-grade-preview-frame={preset.id}
|
|
367
|
+
className="flex w-full items-center justify-center overflow-hidden bg-black/20"
|
|
368
|
+
style={{ aspectRatio: `${presetPreviews.width} / ${presetPreviews.height}` }}
|
|
369
|
+
>
|
|
370
|
+
{preview ? (
|
|
371
|
+
<img
|
|
372
|
+
data-flat-grade-preview={preset.id}
|
|
373
|
+
src={preview}
|
|
374
|
+
alt=""
|
|
375
|
+
draggable={false}
|
|
376
|
+
className="block h-full w-full object-contain"
|
|
377
|
+
/>
|
|
378
|
+
) : (
|
|
379
|
+
<span
|
|
380
|
+
data-flat-grade-preview-placeholder={presetPreviews.status}
|
|
381
|
+
className="h-full w-full bg-panel-bg-soft"
|
|
382
|
+
/>
|
|
383
|
+
)}
|
|
384
|
+
</span>
|
|
385
|
+
<span className="block truncate px-2 py-1.5">{label}</span>
|
|
386
|
+
</button>
|
|
387
|
+
);
|
|
388
|
+
})}
|
|
389
|
+
</div>
|
|
335
390
|
</div>
|
|
336
391
|
<FlatSlider
|
|
337
|
-
label="
|
|
392
|
+
label="Amount"
|
|
338
393
|
value={Math.round(grading.intensity * 100)}
|
|
339
394
|
min={0}
|
|
340
395
|
max={100}
|
|
@@ -462,7 +517,7 @@ export function FlatColorGradingSection({
|
|
|
462
517
|
|
|
463
518
|
<div className="space-y-1.5 border-t border-panel-hairline pt-1.5">
|
|
464
519
|
<div className="mb-1 text-[9px] font-semibold uppercase tracking-[0.12em] text-panel-text-5">
|
|
465
|
-
|
|
520
|
+
Finish
|
|
466
521
|
</div>
|
|
467
522
|
<div className="flex items-center gap-1.5">
|
|
468
523
|
<div className="flex-1">{renderDetailSlider("vignette")}</div>
|
|
@@ -497,42 +552,6 @@ export function FlatColorGradingSection({
|
|
|
497
552
|
)}
|
|
498
553
|
</div>
|
|
499
554
|
|
|
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
555
|
{onApplyScopeAvailable && (
|
|
537
556
|
<div className="flex items-center justify-between gap-2 border-t border-panel-hairline pt-1.5">
|
|
538
557
|
<span className="flex items-center gap-1.5 text-[11px] text-panel-text-2">
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HF_COLOR_GRADING_EFFECT_APPLY_DEFAULTS,
|
|
3
|
+
type HfColorGradingEffectKey,
|
|
4
|
+
type NormalizedHfColorGrading,
|
|
5
|
+
} from "@hyperframes/core/color-grading";
|
|
6
|
+
import { FlatSelectRow } from "./propertyPanelFlatSelectRow";
|
|
7
|
+
import { FlatSlider } from "./propertyPanelFlatPrimitives";
|
|
8
|
+
import { FlatToggle } from "./propertyPanelFlatToggle";
|
|
9
|
+
import {
|
|
10
|
+
DEFAULT_EFFECTS,
|
|
11
|
+
type EffectControl,
|
|
12
|
+
type EffectSpec,
|
|
13
|
+
} from "./propertyPanelFlatEffectSpecs";
|
|
14
|
+
|
|
15
|
+
type CommitEffect = (key: HfColorGradingEffectKey, value: number) => void;
|
|
16
|
+
|
|
17
|
+
function defaultValueFor(control: EffectControl, effect: EffectSpec): number {
|
|
18
|
+
return (
|
|
19
|
+
HF_COLOR_GRADING_EFFECT_APPLY_DEFAULTS[effect.key][control.key] ?? DEFAULT_EFFECTS[control.key]
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function EffectSliderControl({
|
|
24
|
+
control,
|
|
25
|
+
value,
|
|
26
|
+
defaultValue,
|
|
27
|
+
onCommit,
|
|
28
|
+
}: {
|
|
29
|
+
control: Extract<EffectControl, { kind: "slider" }>;
|
|
30
|
+
value: number;
|
|
31
|
+
defaultValue: number;
|
|
32
|
+
onCommit: CommitEffect;
|
|
33
|
+
}) {
|
|
34
|
+
const scale = control.scale ?? 100;
|
|
35
|
+
const sliderValue = value * scale;
|
|
36
|
+
const displayValue = control.format
|
|
37
|
+
? control.format(value)
|
|
38
|
+
: `${Number.isInteger(sliderValue) ? sliderValue : sliderValue.toFixed(1)}${control.unit ?? "%"}`;
|
|
39
|
+
return (
|
|
40
|
+
<FlatSlider
|
|
41
|
+
label={control.label}
|
|
42
|
+
value={sliderValue}
|
|
43
|
+
min={control.min ?? 0}
|
|
44
|
+
max={control.max ?? 100}
|
|
45
|
+
step={control.step ?? 1}
|
|
46
|
+
tier={Math.abs(value - defaultValue) > 0.0001 ? "explicitCustom" : "default"}
|
|
47
|
+
displayValue={displayValue}
|
|
48
|
+
onCommit={(next) => onCommit(control.key, next / scale)}
|
|
49
|
+
onReset={() => onCommit(control.key, defaultValue)}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function FlatEffectControl({
|
|
55
|
+
control,
|
|
56
|
+
effect,
|
|
57
|
+
effects,
|
|
58
|
+
onCommit,
|
|
59
|
+
}: {
|
|
60
|
+
control: EffectControl;
|
|
61
|
+
effect: EffectSpec;
|
|
62
|
+
effects: NormalizedHfColorGrading["effects"];
|
|
63
|
+
onCommit: CommitEffect;
|
|
64
|
+
}) {
|
|
65
|
+
const value = effects[control.key];
|
|
66
|
+
const defaultValue = defaultValueFor(control, effect);
|
|
67
|
+
if (control.kind === "toggle") {
|
|
68
|
+
return (
|
|
69
|
+
<FlatToggle
|
|
70
|
+
label={control.label}
|
|
71
|
+
checked={value >= 0.5}
|
|
72
|
+
onChange={(next) => onCommit(control.key, next ? 1 : 0)}
|
|
73
|
+
/>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
if (control.kind === "select") {
|
|
77
|
+
return (
|
|
78
|
+
<FlatSelectRow
|
|
79
|
+
label={control.label}
|
|
80
|
+
value={String(Math.round(value))}
|
|
81
|
+
options={control.options}
|
|
82
|
+
tier={value === defaultValue ? "default" : "explicitCustom"}
|
|
83
|
+
onChange={(next) => onCommit(control.key, Number.parseInt(next, 10))}
|
|
84
|
+
onReset={() => onCommit(control.key, defaultValue)}
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return (
|
|
89
|
+
<EffectSliderControl
|
|
90
|
+
control={control}
|
|
91
|
+
value={value}
|
|
92
|
+
defaultValue={defaultValue}
|
|
93
|
+
onCommit={onCommit}
|
|
94
|
+
/>
|
|
95
|
+
);
|
|
96
|
+
}
|