@hyperframes/studio 0.7.57 → 0.7.59
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--Z69cEkE.js → hyperframes-player-Bm07FLkl.js} +1 -1
- package/dist/assets/{index-KsfE1bUu.js → index-B995FG46.js} +1 -1
- package/dist/assets/index-CrkAdJkb.js +426 -0
- package/dist/assets/index-Dj5p8U_A.css +1 -0
- package/dist/assets/{index-Bf1x1y8H.js → index-FvzmPhfG.js} +1 -1
- package/dist/{chunk-5QSIMBEJ.js → chunk-OBAG3GWK.js} +33 -21
- package/dist/chunk-OBAG3GWK.js.map +1 -0
- package/dist/{domEditingLayers-6LQGKPOI.js → domEditingLayers-2CKWGEHS.js} +2 -2
- package/dist/index.d.ts +27 -7
- package/dist/index.html +2 -2
- package/dist/index.js +18029 -11563
- package/dist/index.js.map +1 -1
- package/dist/styles/tailwind-preset.d.ts +5 -0
- package/dist/styles/tailwind-preset.js +8 -1
- package/dist/styles/tailwind-preset.js.map +1 -1
- package/package.json +7 -7
- package/src/App.tsx +6 -6
- package/src/components/DesignPanelPromoteProvider.tsx +15 -2
- package/src/components/StudioFeedbackBar.tsx +2 -2
- package/src/components/StudioRightPanel.tsx +29 -51
- package/src/components/editor/AnimationCard.test.tsx +134 -0
- package/src/components/editor/AnimationCard.tsx +19 -4
- package/src/components/editor/ArcPathControls.tsx +1 -0
- package/src/components/editor/CanvasContextMenu.test.tsx +27 -14
- package/src/components/editor/CanvasContextMenu.tsx +71 -6
- package/src/components/editor/DomEditOverlay.tsx +5 -2
- package/src/components/editor/GestureRecordControl.tsx +7 -1
- package/src/components/editor/GsapAnimationSection.tsx +109 -14
- package/src/components/editor/InspectorHeaderActions.tsx +42 -4
- package/src/components/editor/LayersPanel.tsx +110 -99
- package/src/components/editor/PropertyPanel.test.tsx +948 -0
- package/src/components/editor/PropertyPanel.tsx +144 -148
- package/src/components/editor/PropertyPanelEmptyState.test.tsx +74 -0
- package/src/components/editor/PropertyPanelEmptyState.tsx +179 -1
- package/src/components/editor/PropertyPanelFlat.tsx +580 -0
- package/src/components/editor/PropertyPanelFlatFooter.test.tsx +83 -0
- package/src/components/editor/PropertyPanelFlatFooter.tsx +73 -0
- package/src/components/editor/PropertyPanelFlatHeader.test.tsx +79 -0
- package/src/components/editor/PropertyPanelFlatHeader.tsx +112 -0
- package/src/components/editor/canvasContextMenuZOrder.test.ts +57 -0
- package/src/components/editor/canvasContextMenuZOrder.ts +40 -1
- package/src/components/editor/domEditingDom.ts +5 -6
- package/src/components/editor/domEditingElement.ts +15 -32
- package/src/components/editor/gsapAnimationCallbacks.ts +27 -0
- package/src/components/editor/gsapLivePreview.ts +34 -0
- package/src/components/editor/layersPanelSort.ts +80 -0
- package/src/components/editor/manualEditingAvailability.test.ts +10 -0
- package/src/components/editor/manualEditingAvailability.ts +9 -0
- package/src/components/editor/propertyPanel3dTransform.tsx +5 -0
- package/src/components/editor/propertyPanelColor.test.tsx +28 -0
- package/src/components/editor/propertyPanelColor.tsx +31 -1
- package/src/components/editor/propertyPanelColorGradingControls.tsx +11 -2
- package/src/components/editor/propertyPanelColorGradingSection.tsx +45 -365
- package/src/components/editor/propertyPanelColorGradingSlider.tsx +19 -5
- package/src/components/editor/propertyPanelFill.tsx +22 -5
- package/src/components/editor/propertyPanelFlatColorGradingSection.test.tsx +622 -0
- package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +570 -0
- package/src/components/editor/propertyPanelFlatLayoutSection.test.tsx +293 -0
- package/src/components/editor/propertyPanelFlatLayoutSection.tsx +383 -0
- package/src/components/editor/propertyPanelFlatMaskInsetRows.tsx +102 -0
- package/src/components/editor/propertyPanelFlatMediaSection.test.tsx +436 -0
- package/src/components/editor/propertyPanelFlatMediaSection.tsx +286 -0
- package/src/components/editor/propertyPanelFlatMotionSection.test.tsx +272 -0
- package/src/components/editor/propertyPanelFlatMotionSection.tsx +315 -0
- package/src/components/editor/propertyPanelFlatPrimitives.test.tsx +1174 -0
- package/src/components/editor/propertyPanelFlatPrimitives.tsx +563 -0
- package/src/components/editor/propertyPanelFlatSelectRow.tsx +101 -0
- package/src/components/editor/propertyPanelFlatStyleHelpers.test.ts +23 -0
- package/src/components/editor/propertyPanelFlatStyleHelpers.ts +27 -0
- package/src/components/editor/propertyPanelFlatStyleSections.test.tsx +598 -0
- package/src/components/editor/propertyPanelFlatStyleSections.tsx +516 -0
- package/src/components/editor/propertyPanelFlatTextSection.test.tsx +465 -0
- package/src/components/editor/propertyPanelFlatTextSection.tsx +407 -0
- package/src/components/editor/propertyPanelFlatTimingDerivation.test.ts +71 -0
- package/src/components/editor/propertyPanelFlatTimingDerivation.ts +59 -0
- package/src/components/editor/propertyPanelFlatToggle.test.tsx +63 -0
- package/src/components/editor/propertyPanelFlatToggle.tsx +53 -0
- package/src/components/editor/propertyPanelFont.test.tsx +30 -0
- package/src/components/editor/propertyPanelFont.tsx +136 -95
- package/src/components/editor/propertyPanelHelpers.ts +73 -0
- package/src/components/editor/propertyPanelInputCoverage.test.tsx +587 -0
- package/src/components/editor/propertyPanelMediaSection.tsx +10 -0
- package/src/components/editor/propertyPanelPrimitives.tsx +60 -29
- package/src/components/editor/propertyPanelSections.test.tsx +161 -0
- package/src/components/editor/propertyPanelSections.tsx +154 -90
- package/src/components/editor/propertyPanelStyleSections.tsx +12 -1
- package/src/components/editor/propertyPanelTimingSection.tsx +1 -1
- package/src/components/editor/propertyPanelTypes.ts +14 -1
- package/src/components/editor/propertyPanelValueTier.test.ts +32 -0
- package/src/components/editor/propertyPanelValueTier.ts +28 -0
- package/src/components/editor/useColorGradingController.test.ts +418 -0
- package/src/components/editor/useColorGradingController.ts +596 -0
- package/src/components/editor/useLayerRevealOverride.test.ts +169 -0
- package/src/components/editor/useLayerRevealOverride.ts +389 -0
- package/src/components/nle/PreviewOverlays.tsx +27 -2
- package/src/components/nle/TimelinePane.test.ts +3 -0
- package/src/components/nle/TimelinePane.tsx +11 -1
- package/src/components/nle/useCanvasZOrderTimelineMirror.test.tsx +440 -0
- package/src/components/nle/useCanvasZOrderTimelineMirror.ts +191 -0
- package/src/components/nle/useTimelineEditCallbacks.ts +1 -0
- package/src/components/nle/zLaneGesture.test.ts +176 -0
- package/src/components/nle/zLaneGesture.ts +77 -0
- package/src/components/panels/VariablesPanel.tsx +4 -0
- package/src/components/sidebar/CompositionsTab.test.ts +28 -2
- package/src/components/sidebar/CompositionsTab.tsx +3 -1
- package/src/components/storyboard/FramePoster.tsx +24 -4
- package/src/components/storyboard/StoryboardFrameFocus.tsx +4 -0
- package/src/components/storyboard/StoryboardFrameTile.tsx +30 -1
- package/src/components/storyboard/StoryboardGrid.tsx +23 -1
- package/src/components/storyboard/StoryboardLoaded.tsx +58 -2
- package/src/components/storyboard/StoryboardView.tsx +9 -2
- package/src/components/storyboard/frameComments.test.ts +95 -0
- package/src/components/storyboard/frameComments.ts +115 -0
- package/src/components/storyboard/useFrameComments.ts +82 -0
- package/src/contexts/DesignPanelInputContext.test.tsx +81 -0
- package/src/contexts/DesignPanelInputContext.tsx +48 -0
- package/src/contexts/DomEditContext.tsx +4 -0
- package/src/contexts/VariablePromoteContext.test.tsx +67 -0
- package/src/contexts/VariablePromoteContext.tsx +10 -5
- package/src/hooks/domEditCommitRunner.ts +11 -0
- package/src/hooks/domEditCommitTypes.ts +17 -1
- package/src/hooks/elementLifecycleOpsTestUtils.ts +25 -0
- package/src/hooks/fetchStubTestUtils.ts +16 -0
- package/src/hooks/gsapScriptCommitTypes.ts +6 -4
- package/src/hooks/timelineEditingHelpers.test.ts +92 -1
- package/src/hooks/timelineEditingHelpers.ts +32 -15
- package/src/hooks/timelineMoveAdapter.ts +2 -1
- package/src/hooks/timelineTimingSync.test.ts +703 -33
- package/src/hooks/timelineTimingSync.ts +282 -97
- package/src/hooks/timelineTrackVisibility.test.ts +55 -1
- package/src/hooks/timelineTrackVisibility.ts +31 -13
- package/src/hooks/useAppHotkeys.ts +9 -0
- package/src/hooks/useClipboard.ts +19 -17
- package/src/hooks/useDomEditAttributeCommits.ts +123 -1
- package/src/hooks/useDomEditCommits.test.tsx +316 -38
- package/src/hooks/useDomEditCommits.ts +81 -162
- package/src/hooks/useDomEditCommitsHelpers.ts +158 -0
- package/src/hooks/useDomEditSession.test.tsx +3 -1
- package/src/hooks/useDomEditSession.ts +18 -6
- package/src/hooks/useDomEditTextCommits.ts +14 -9
- package/src/hooks/useDomEditWiring.ts +10 -9
- package/src/hooks/useElementLifecycleOps.test.tsx +426 -60
- package/src/hooks/useElementLifecycleOps.ts +182 -84
- package/src/hooks/useFileManager.projectOwnership.test.tsx +98 -0
- package/src/hooks/useFileManager.ts +82 -60
- package/src/hooks/useGsapAnimationOps.ts +7 -6
- package/src/hooks/useGsapKeyframeOps.ts +6 -5
- package/src/hooks/useGsapPropertyDebounce.ts +43 -28
- package/src/hooks/useGsapPropertyDebounceFlush.test.ts +35 -0
- package/src/hooks/useGsapScriptCommits.test.tsx +48 -2
- package/src/hooks/useGsapScriptCommits.ts +150 -50
- package/src/hooks/useGsapSelectionHandlers.test.tsx +116 -0
- package/src/hooks/useGsapSelectionHandlers.ts +94 -31
- package/src/hooks/useInspectorSplitResize.ts +51 -0
- package/src/hooks/usePersistentEditHistory.projectOwnership.test.tsx +60 -0
- package/src/hooks/usePersistentEditHistory.test.ts +49 -1
- package/src/hooks/usePersistentEditHistory.ts +71 -31
- package/src/hooks/usePreviewPersistence.ts +2 -2
- package/src/hooks/useProjectCompositionVariables.ts +10 -10
- package/src/hooks/useProjectSignaturePoll.test.tsx +135 -0
- package/src/hooks/useProjectSignaturePoll.ts +68 -0
- package/src/hooks/useSdkSession.lifecycle.test.tsx +137 -0
- package/src/hooks/useSdkSession.ts +155 -14
- package/src/hooks/useSlideshowPersist.ts +7 -1
- package/src/hooks/useStoryboard.ts +23 -4
- package/src/hooks/useTimelineEditing.test.tsx +306 -155
- package/src/hooks/useTimelineEditing.ts +111 -101
- package/src/hooks/useTimelineEditingTypes.ts +4 -1
- package/src/hooks/useTimelineGroupEditing.ts +51 -5
- package/src/hooks/useVariablesPersist.ts +9 -7
- package/src/icons/SystemIcons.tsx +2 -0
- package/src/player/components/Timeline.test.ts +31 -21
- package/src/player/components/Timeline.tsx +62 -91
- package/src/player/components/TimelineCanvas.tsx +36 -5
- package/src/player/components/TimelineLanes.tsx +32 -10
- package/src/player/components/TimelineOverlays.tsx +36 -0
- package/src/player/components/TimelineRuler.tsx +18 -5
- package/src/player/components/TrackGapContextMenu.tsx +116 -0
- package/src/player/components/timelineCallbacks.ts +4 -1
- package/src/player/components/timelineClipDragCommit.test.ts +279 -3
- package/src/player/components/timelineClipDragCommit.ts +173 -71
- package/src/player/components/timelineGapCommit.test.ts +163 -0
- package/src/player/components/timelineGapCommit.ts +103 -0
- package/src/player/components/timelineGaps.test.ts +221 -0
- package/src/player/components/timelineGaps.ts +182 -0
- package/src/player/components/timelineLayout.test.ts +2 -1
- package/src/player/components/timelineLayout.ts +22 -6
- package/src/player/components/timelineMarquee.test.ts +20 -10
- package/src/player/components/timelineMarquee.ts +9 -2
- package/src/player/components/timelineStackingSync.test.ts +42 -1
- package/src/player/components/timelineStackingSync.ts +31 -16
- package/src/player/components/timelineZMirror.test.ts +667 -0
- package/src/player/components/timelineZMirror.ts +393 -0
- package/src/player/components/timelineZoom.test.ts +2 -2
- package/src/player/components/timelineZoom.ts +7 -3
- package/src/player/components/useTimelineClipDrag.ts +1 -1
- package/src/player/components/useTimelineGapHighlights.test.ts +119 -0
- package/src/player/components/useTimelineGapHighlights.ts +110 -0
- package/src/player/components/useTimelinePlayhead.ts +4 -3
- package/src/player/components/useTimelineRangeSelection.ts +6 -4
- package/src/player/components/useTimelineScrollViewport.ts +73 -0
- package/src/player/components/useTimelineStackingSync.test.tsx +0 -1
- package/src/player/components/useTimelineStackingSync.ts +5 -3
- package/src/player/components/useTimelineTrackDerivations.ts +38 -0
- package/src/player/components/useTrackGapMenu.ts +155 -0
- package/src/player/hooks/useExpandedTimelineElements.test.ts +67 -4
- package/src/player/hooks/useExpandedTimelineElements.ts +12 -1
- package/src/player/hooks/useTimelineSyncCallbacks.ts +2 -0
- package/src/player/lib/layerOrdering.test.ts +1 -15
- package/src/player/lib/layerOrdering.ts +5 -28
- package/src/player/lib/timelineDOM.test.ts +72 -0
- package/src/player/lib/timelineDOM.ts +3 -0
- package/src/player/lib/timelineElementHelpers.ts +35 -11
- package/src/player/store/playerStore.ts +12 -1
- package/src/styles/studio.css +22 -0
- package/src/styles/tailwind-preset.shared.js +7 -0
- package/src/telemetry/events.test.ts +10 -0
- package/src/telemetry/events.ts +1 -0
- package/src/utils/designInputTracking.test.ts +97 -0
- package/src/utils/designInputTracking.ts +80 -0
- package/src/utils/domEditSaveQueue.ts +4 -3
- package/src/utils/editHistory.test.ts +82 -0
- package/src/utils/editHistory.ts +14 -2
- package/src/utils/gsapSoftReload.test.ts +54 -115
- package/src/utils/gsapSoftReload.ts +42 -152
- package/src/utils/gsapUndoRestore.test.ts +269 -0
- package/src/utils/gsapUndoRestore.ts +269 -0
- package/src/utils/sdkCutover.gate.test.ts +7 -7
- package/src/utils/sdkCutover.test.ts +490 -57
- package/src/utils/sdkCutover.ts +181 -219
- package/src/utils/sdkEditTransaction.ts +292 -0
- package/src/utils/sdkResolverAttempts.ts +94 -0
- package/src/utils/sdkResolverShadow.test.ts +148 -0
- package/src/utils/sdkResolverShadow.ts +139 -106
- package/src/utils/setSlideshowManifest.test.ts +0 -9
- package/src/utils/setSlideshowManifest.ts +20 -27
- package/src/utils/sourceScopedSelectorIndex.ts +29 -0
- package/src/utils/studioFileHistory.test.ts +41 -0
- package/src/utils/studioFileHistory.ts +30 -27
- package/src/utils/studioFileMutationCoordinator.ts +45 -0
- package/src/utils/studioHelpers.test.ts +45 -0
- package/src/utils/studioHelpers.ts +45 -14
- package/src/utils/studioSaveDiagnostics.ts +17 -0
- package/dist/assets/index-DfmYkU44.js +0 -423
- package/dist/assets/index-_pqzyxB1.css +0 -1
- package/dist/chunk-5QSIMBEJ.js.map +0 -1
- /package/dist/{domEditingLayers-6LQGKPOI.js.map → domEditingLayers-2CKWGEHS.js.map} +0 -0
|
@@ -5,6 +5,7 @@ import { MetricField } from "./propertyPanelPrimitives";
|
|
|
5
5
|
import { KeyframeNavigation } from "./KeyframeNavigation";
|
|
6
6
|
import { formatPxMetricValue, parsePxMetricValue, RESPONSIVE_GRID } from "./propertyPanelHelpers";
|
|
7
7
|
import { Transform3DCube, type CubePose } from "./Transform3DCube";
|
|
8
|
+
import { useTrackDesignInput } from "../../contexts/DesignPanelInputContext";
|
|
8
9
|
|
|
9
10
|
// translateZ only foreshortens under a perspective lens. Rather than hardcode one
|
|
10
11
|
// (an arbitrary px value reads wrong at different canvas sizes), derive it from the
|
|
@@ -71,6 +72,7 @@ function Cube3dControl({
|
|
|
71
72
|
onKeyframe?: () => void;
|
|
72
73
|
keyframed?: boolean;
|
|
73
74
|
}) {
|
|
75
|
+
const track = useTrackDesignInput();
|
|
74
76
|
const pose: CubePose = {
|
|
75
77
|
rotationX: gsapRuntimeValues.rotationX ?? 0,
|
|
76
78
|
rotationY: gsapRuntimeValues.rotationY ?? 0,
|
|
@@ -96,6 +98,7 @@ function Cube3dControl({
|
|
|
96
98
|
}
|
|
97
99
|
const axes = Object.keys(changedProps);
|
|
98
100
|
if (axes.length === 0) return;
|
|
101
|
+
track("slider", "3D rotation pose");
|
|
99
102
|
// ONE keyframe for the whole pose change — avoids per-axis commits racing into
|
|
100
103
|
// adjacent duplicate keyframes.
|
|
101
104
|
void onCommitAnimatedProperties(element, changedProps);
|
|
@@ -111,6 +114,7 @@ function Cube3dControl({
|
|
|
111
114
|
scale: 1,
|
|
112
115
|
transformPerspective: 0,
|
|
113
116
|
};
|
|
117
|
+
track("button", "Reset 3D transform");
|
|
114
118
|
void onCommitAnimatedProperties(element, identity);
|
|
115
119
|
};
|
|
116
120
|
// Immediate element feedback while dragging — set the live transform without a
|
|
@@ -168,6 +172,7 @@ function Cube3dControl({
|
|
|
168
172
|
}
|
|
169
173
|
// One commit for all props so the writes can't race read-modify-write on
|
|
170
174
|
// the same script (which dropped a prop and reverted after a seek).
|
|
175
|
+
track("slider", "3D depth");
|
|
171
176
|
void onCommitAnimatedProperties(element, props);
|
|
172
177
|
}}
|
|
173
178
|
onRecenter={recenter}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
|
|
3
|
+
import React, { act } from "react";
|
|
4
|
+
import { createRoot } from "react-dom/client";
|
|
5
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { ColorField } from "./propertyPanelColor";
|
|
7
|
+
|
|
8
|
+
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
9
|
+
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
document.body.innerHTML = "";
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe("ColorField flat trigger", () => {
|
|
15
|
+
it("renders label and value inline with a small swatch, no boxed border", () => {
|
|
16
|
+
const host = document.createElement("div");
|
|
17
|
+
document.body.append(host);
|
|
18
|
+
const root = createRoot(host);
|
|
19
|
+
act(() => {
|
|
20
|
+
root.render(<ColorField flat label="Color" value="rgb(255, 176, 32)" onCommit={vi.fn()} />);
|
|
21
|
+
});
|
|
22
|
+
const trigger = host.querySelector<HTMLButtonElement>('[data-flat-color-trigger="true"]');
|
|
23
|
+
expect(trigger).not.toBeNull();
|
|
24
|
+
expect(trigger?.className).not.toContain("border-neutral-800");
|
|
25
|
+
expect(host.textContent).toContain("Color");
|
|
26
|
+
act(() => root.unmount());
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from "./colorValue";
|
|
12
12
|
import { resolveFloatingPanelPosition, type FloatingPosition } from "./floatingPanel";
|
|
13
13
|
import { colorFromCss, FIELD, LABEL } from "./propertyPanelHelpers";
|
|
14
|
+
import { useTrackDesignInput } from "../../contexts/DesignPanelInputContext";
|
|
14
15
|
|
|
15
16
|
const COLOR_PICKER_SIZE = { width: 292, height: 386 };
|
|
16
17
|
|
|
@@ -121,13 +122,16 @@ export function ColorField({
|
|
|
121
122
|
label,
|
|
122
123
|
value,
|
|
123
124
|
disabled,
|
|
125
|
+
flat,
|
|
124
126
|
onCommit,
|
|
125
127
|
}: {
|
|
126
128
|
label: string;
|
|
127
129
|
value: string;
|
|
128
130
|
disabled?: boolean;
|
|
131
|
+
flat?: boolean;
|
|
129
132
|
onCommit: (nextValue: string) => void;
|
|
130
133
|
}) {
|
|
134
|
+
const track = useTrackDesignInput();
|
|
131
135
|
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
|
132
136
|
const panelRef = useRef<HTMLDivElement | null>(null);
|
|
133
137
|
const [open, setOpen] = useState(false);
|
|
@@ -201,7 +205,9 @@ export function ColorField({
|
|
|
201
205
|
const commitColor = (nextColor: ParsedColor) => {
|
|
202
206
|
setDraftColor(nextColor);
|
|
203
207
|
setHexDraft(toHexColor(nextColor).toUpperCase());
|
|
204
|
-
|
|
208
|
+
const nextValue = formatCssColor(nextColor);
|
|
209
|
+
if (nextValue !== value) track("color", label);
|
|
210
|
+
onCommit(nextValue);
|
|
205
211
|
};
|
|
206
212
|
|
|
207
213
|
const commitHsv = (nextHsv: { hue?: number; saturation?: number; value?: number }) => {
|
|
@@ -349,6 +355,30 @@ export function ColorField({
|
|
|
349
355
|
}
|
|
350
356
|
};
|
|
351
357
|
|
|
358
|
+
if (flat) {
|
|
359
|
+
return (
|
|
360
|
+
<div className="flex min-h-[30px] items-center justify-between">
|
|
361
|
+
<span className="text-[11px] text-panel-text-2">{label}</span>
|
|
362
|
+
<button
|
|
363
|
+
type="button"
|
|
364
|
+
data-flat-color-trigger="true"
|
|
365
|
+
disabled={disabled}
|
|
366
|
+
aria-label={`Pick ${label.toLowerCase()} color`}
|
|
367
|
+
ref={buttonRef}
|
|
368
|
+
onClick={openPicker}
|
|
369
|
+
className="flex items-center gap-2 disabled:cursor-not-allowed"
|
|
370
|
+
>
|
|
371
|
+
<span
|
|
372
|
+
className="h-4 w-4 flex-shrink-0 rounded-[4px]"
|
|
373
|
+
style={{ backgroundColor: value || "transparent" }}
|
|
374
|
+
/>
|
|
375
|
+
<span className="font-mono text-[11px] text-panel-text-0">{value}</span>
|
|
376
|
+
</button>
|
|
377
|
+
{picker}
|
|
378
|
+
</div>
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
352
382
|
return (
|
|
353
383
|
<div className="grid min-w-0 gap-1.5">
|
|
354
384
|
<span className={LABEL}>{label}</span>
|
|
@@ -11,6 +11,7 @@ import { ChevronDown, ChevronRight, Plus, X } from "../../icons/SystemIcons";
|
|
|
11
11
|
import { LUT_EXT } from "../../utils/mediaTypes";
|
|
12
12
|
import { LABEL } from "./propertyPanelHelpers";
|
|
13
13
|
import { ColorGradingSliderControl } from "./propertyPanelColorGradingSlider";
|
|
14
|
+
import { useTrackDesignInput } from "../../contexts/DesignPanelInputContext";
|
|
14
15
|
|
|
15
16
|
const LUT_UPLOAD_DIR = "assets/luts";
|
|
16
17
|
|
|
@@ -175,6 +176,7 @@ export function ColorGradingControls({
|
|
|
175
176
|
onImportAssets?: (files: FileList, dir?: string) => Promise<string[]>;
|
|
176
177
|
onCommitColorGrading: (nextGrading: NormalizedHfColorGrading) => void;
|
|
177
178
|
}) {
|
|
179
|
+
const track = useTrackDesignInput();
|
|
178
180
|
const lutInputRef = useRef<HTMLInputElement>(null);
|
|
179
181
|
const [lutOpen, setLutOpen] = useState(false);
|
|
180
182
|
const [detailSettings, setDetailSettings] = useState<"vignette" | "grain" | null>(null);
|
|
@@ -195,7 +197,10 @@ export function ColorGradingControls({
|
|
|
195
197
|
|
|
196
198
|
const applyPreset = (preset: string) => {
|
|
197
199
|
const next = normalizeHfColorGrading({ preset, intensity: 1, lut: grading.lut });
|
|
198
|
-
if (next)
|
|
200
|
+
if (next) {
|
|
201
|
+
track("select", "Preset");
|
|
202
|
+
onCommitColorGrading(next);
|
|
203
|
+
}
|
|
199
204
|
};
|
|
200
205
|
const updateFilterIntensity = (value: number) => {
|
|
201
206
|
onCommitColorGrading({
|
|
@@ -218,7 +223,10 @@ export function ColorGradingControls({
|
|
|
218
223
|
if (!files?.length || !onImportAssets) return;
|
|
219
224
|
const uploaded = await onImportAssets(files, LUT_UPLOAD_DIR);
|
|
220
225
|
const firstLut = uploaded.find((asset) => LUT_EXT.test(asset));
|
|
221
|
-
if (firstLut)
|
|
226
|
+
if (firstLut) {
|
|
227
|
+
track("button", "Import LUT");
|
|
228
|
+
applyLut(firstLut, 1);
|
|
229
|
+
}
|
|
222
230
|
};
|
|
223
231
|
const commitDetailSlider = (slider: DetailSlider, next: number) => {
|
|
224
232
|
onCommitColorGrading({
|
|
@@ -313,6 +321,7 @@ export function ColorGradingControls({
|
|
|
313
321
|
value={selectedLut}
|
|
314
322
|
onChange={(event) => {
|
|
315
323
|
const nextSrc = event.target.value;
|
|
324
|
+
track("select", "Custom LUT");
|
|
316
325
|
applyLut(
|
|
317
326
|
nextSrc || null,
|
|
318
327
|
nextSrc && grading.lut?.src === nextSrc ? grading.lut.intensity : 1,
|
|
@@ -1,159 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
useEffect,
|
|
4
|
-
useMemo,
|
|
5
|
-
useRef,
|
|
6
|
-
useState,
|
|
7
|
-
type PointerEvent as ReactPointerEvent,
|
|
8
|
-
type RefObject,
|
|
9
|
-
} from "react";
|
|
10
|
-
import {
|
|
11
|
-
HF_COLOR_GRADING_ATTR,
|
|
12
|
-
isHfColorGradingActive,
|
|
13
|
-
normalizeHfColorGrading,
|
|
14
|
-
serializeHfColorGrading,
|
|
15
|
-
type HfColorGradingTarget,
|
|
16
|
-
type NormalizedHfColorGrading,
|
|
17
|
-
} from "@hyperframes/core/color-grading";
|
|
1
|
+
import { type PointerEvent as ReactPointerEvent, type RefObject } from "react";
|
|
2
|
+
import { isHfColorGradingActive } from "@hyperframes/core/color-grading";
|
|
18
3
|
import { Compare, Palette, RotateCcw } from "../../icons/SystemIcons";
|
|
19
|
-
import {
|
|
20
|
-
addStudioPendingEditFlushListener,
|
|
21
|
-
trackStudioPendingEdit,
|
|
22
|
-
} from "../../utils/studioPendingEdits";
|
|
23
4
|
import type { DomEditSelection } from "./domEditing";
|
|
24
5
|
import { ColorGradingControls } from "./propertyPanelColorGradingControls";
|
|
25
|
-
import { stripQueryAndHash } from "./propertyPanelHelpers";
|
|
26
6
|
import { Section } from "./propertyPanelPrimitives";
|
|
27
7
|
import {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const RUNTIME_STATUS_REFRESH_DELAYS = [50, 250, 1000, 2500] as const;
|
|
34
|
-
const MEDIA_METADATA_CACHE = new Map<string, MediaMetadata | null>();
|
|
35
|
-
|
|
36
|
-
interface RuntimeColorGradingStatus {
|
|
37
|
-
state: "missing" | "inactive" | "pending" | "active" | "unavailable";
|
|
38
|
-
message: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
interface MediaMetadata {
|
|
42
|
-
kind: "video" | "image" | "audio" | "unknown";
|
|
43
|
-
color: {
|
|
44
|
-
dynamicRange: "hdr" | "sdr" | "unknown";
|
|
45
|
-
hdrTransfer: "pq" | "hlg" | "unknown" | null;
|
|
46
|
-
label: string;
|
|
47
|
-
isHdr: boolean;
|
|
48
|
-
codecName?: string;
|
|
49
|
-
profile?: string;
|
|
50
|
-
pixelFormat?: string;
|
|
51
|
-
colorSpace?: string;
|
|
52
|
-
colorTransfer?: string;
|
|
53
|
-
colorPrimaries?: string;
|
|
54
|
-
};
|
|
55
|
-
probeError?: string;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
interface MediaMetadataResponse {
|
|
59
|
-
path: string;
|
|
60
|
-
metadata: MediaMetadata;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function stripPreviewAssetPath(src: string, projectId: string): string | null {
|
|
64
|
-
let pathname = src;
|
|
65
|
-
try {
|
|
66
|
-
pathname = new URL(src, window.location.href).pathname;
|
|
67
|
-
} catch {
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
const projectMarker = `/api/projects/${encodeURIComponent(projectId)}/preview/`;
|
|
71
|
-
const genericMarker = "/preview/";
|
|
72
|
-
const marker = pathname.includes(projectMarker) ? projectMarker : genericMarker;
|
|
73
|
-
const index = pathname.indexOf(marker);
|
|
74
|
-
if (index < 0) return null;
|
|
75
|
-
const assetPath = decodeURIComponent(pathname.slice(index + marker.length)).replace(/^\/+/, "");
|
|
76
|
-
if (!assetPath || assetPath.startsWith("comp/")) return null;
|
|
77
|
-
return assetPath;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// fallow-ignore-next-line complexity
|
|
81
|
-
function resolveProjectAssetPath(
|
|
82
|
-
sourceFile: string,
|
|
83
|
-
src: string,
|
|
84
|
-
projectId: string,
|
|
85
|
-
): string | null {
|
|
86
|
-
const trimmed = stripQueryAndHash(src.trim());
|
|
87
|
-
if (!trimmed || /^(?:data:|blob:)/i.test(trimmed)) return null;
|
|
88
|
-
if (/^https?:\/\//i.test(trimmed)) return stripPreviewAssetPath(trimmed, projectId);
|
|
89
|
-
if (trimmed.startsWith("/")) {
|
|
90
|
-
return stripPreviewAssetPath(trimmed, projectId);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const sourceDir = sourceFile.includes("/")
|
|
94
|
-
? sourceFile.slice(0, sourceFile.lastIndexOf("/"))
|
|
95
|
-
: "";
|
|
96
|
-
const parts = `${sourceDir}/${trimmed}`.split("/");
|
|
97
|
-
const normalized: string[] = [];
|
|
98
|
-
for (const part of parts) {
|
|
99
|
-
if (!part || part === ".") continue;
|
|
100
|
-
if (part === "..") {
|
|
101
|
-
normalized.pop();
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
normalized.push(part);
|
|
105
|
-
}
|
|
106
|
-
return normalized.join("/") || null;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function selectedMediaAssetPath(element: DomEditSelection, projectId: string): string | null {
|
|
110
|
-
if (element.tagName !== "video" && element.tagName !== "img") return null;
|
|
111
|
-
const media = element.element as HTMLImageElement | HTMLVideoElement;
|
|
112
|
-
const src = media.getAttribute("src") || media.currentSrc || "";
|
|
113
|
-
return resolveProjectAssetPath(element.sourceFile || "index.html", src, projectId);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function defaultColorGrading(): NormalizedHfColorGrading {
|
|
117
|
-
const grading = normalizeHfColorGrading("neutral");
|
|
118
|
-
if (!grading) throw new Error("Missing neutral color grading preset");
|
|
119
|
-
return grading;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function readColorGradingFromElement(element: DomEditSelection): NormalizedHfColorGrading {
|
|
123
|
-
return (
|
|
124
|
-
normalizeHfColorGrading(element.dataAttributes[COLOR_GRADING_DATA_KEY]) ?? defaultColorGrading()
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function toBridgeColorGrading(grading: NormalizedHfColorGrading): unknown {
|
|
129
|
-
if (!isHfColorGradingActive(grading)) return null;
|
|
130
|
-
const { enabled: _enabled, ...bridgeGrading } = grading;
|
|
131
|
-
return bridgeGrading;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function readRuntimeColorGradingStatus(
|
|
135
|
-
iframe: HTMLIFrameElement | null | undefined,
|
|
136
|
-
target: HfColorGradingTarget,
|
|
137
|
-
): RuntimeColorGradingStatus {
|
|
138
|
-
try {
|
|
139
|
-
const win = iframe?.contentWindow as
|
|
140
|
-
| (Window & {
|
|
141
|
-
__hf?: {
|
|
142
|
-
colorGrading?: {
|
|
143
|
-
getStatus?: (
|
|
144
|
-
target: HfColorGradingTarget | string | null | undefined,
|
|
145
|
-
) => RuntimeColorGradingStatus;
|
|
146
|
-
};
|
|
147
|
-
};
|
|
148
|
-
})
|
|
149
|
-
| null
|
|
150
|
-
| undefined;
|
|
151
|
-
const status = win?.__hf?.colorGrading?.getStatus?.(target);
|
|
152
|
-
return status ?? { state: "pending", message: "Waiting for runtime" };
|
|
153
|
-
} catch {
|
|
154
|
-
return { state: "unavailable", message: "Preview unavailable" };
|
|
155
|
-
}
|
|
156
|
-
}
|
|
8
|
+
useColorGradingController,
|
|
9
|
+
type MediaMetadata,
|
|
10
|
+
type RuntimeColorGradingStatus,
|
|
11
|
+
} from "./useColorGradingController";
|
|
12
|
+
import { useTrackDesignInput } from "../../contexts/DesignPanelInputContext";
|
|
157
13
|
|
|
158
14
|
function StatusPill({ status }: { status: RuntimeColorGradingStatus }) {
|
|
159
15
|
const dotClass =
|
|
@@ -213,10 +69,12 @@ function HoldBeforeButton({
|
|
|
213
69
|
disabled: boolean;
|
|
214
70
|
onHoldChange: (holding: boolean) => void;
|
|
215
71
|
}) {
|
|
72
|
+
const track = useTrackDesignInput();
|
|
216
73
|
const startHold = (event: ReactPointerEvent<HTMLButtonElement>) => {
|
|
217
74
|
if (disabled) return;
|
|
218
75
|
event.preventDefault();
|
|
219
76
|
event.stopPropagation();
|
|
77
|
+
track("toggle", "Compare original");
|
|
220
78
|
onHoldChange(true);
|
|
221
79
|
const release = () => {
|
|
222
80
|
onHoldChange(false);
|
|
@@ -250,7 +108,10 @@ function HoldBeforeButton({
|
|
|
250
108
|
onKeyDown={(event) => {
|
|
251
109
|
if (disabled || (event.key !== " " && event.key !== "Enter")) return;
|
|
252
110
|
event.preventDefault();
|
|
253
|
-
if (!active)
|
|
111
|
+
if (!active) {
|
|
112
|
+
track("toggle", "Compare original");
|
|
113
|
+
onHoldChange(true);
|
|
114
|
+
}
|
|
254
115
|
}}
|
|
255
116
|
onKeyUp={(event) => {
|
|
256
117
|
if (disabled || (event.key !== " " && event.key !== "Enter")) return;
|
|
@@ -283,222 +144,36 @@ export function ColorGradingSection({
|
|
|
283
144
|
assets: string[];
|
|
284
145
|
previewIframeRef?: RefObject<HTMLIFrameElement | null>;
|
|
285
146
|
onImportAssets?: (files: FileList, dir?: string) => Promise<string[]>;
|
|
286
|
-
onSetAttributeLive: (
|
|
147
|
+
onSetAttributeLive: (
|
|
148
|
+
attr: string,
|
|
149
|
+
value: string | null,
|
|
150
|
+
onSettled?: (ok: boolean) => void,
|
|
151
|
+
) => void | Promise<void>;
|
|
287
152
|
onApplyScope?: (
|
|
288
153
|
scope: "source-file" | "project",
|
|
289
154
|
value: string | null,
|
|
290
155
|
) => Promise<{ changedFiles: number; changedElements: number }>;
|
|
291
156
|
}) {
|
|
292
|
-
const
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
latestGradingRef.current = grading;
|
|
313
|
-
compareEnabledRef.current = compareEnabled;
|
|
314
|
-
const target = useMemo(
|
|
315
|
-
(): HfColorGradingTarget => ({
|
|
316
|
-
id: element.id ?? null,
|
|
317
|
-
hfId: element.hfId ?? null,
|
|
318
|
-
selector: element.selector ?? null,
|
|
319
|
-
selectorIndex: element.selectorIndex ?? null,
|
|
320
|
-
}),
|
|
321
|
-
[element.hfId, element.id, element.selector, element.selectorIndex],
|
|
322
|
-
);
|
|
323
|
-
|
|
324
|
-
const refreshRuntimeStatus = useCallback(() => {
|
|
325
|
-
setRuntimeStatus(readRuntimeColorGradingStatus(previewIframeRef?.current, target));
|
|
326
|
-
}, [previewIframeRef, target]);
|
|
327
|
-
|
|
328
|
-
useEffect(() => {
|
|
329
|
-
setMediaMetadata(null);
|
|
330
|
-
if (!selectedAssetPath) return;
|
|
331
|
-
const cacheKey = `${projectId}:${selectedAssetPath}`;
|
|
332
|
-
if (MEDIA_METADATA_CACHE.has(cacheKey)) {
|
|
333
|
-
setMediaMetadata(MEDIA_METADATA_CACHE.get(cacheKey) ?? null);
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
const controller = new AbortController();
|
|
337
|
-
fetch(
|
|
338
|
-
`/api/projects/${encodeURIComponent(projectId)}/media/metadata?path=${encodeURIComponent(
|
|
339
|
-
selectedAssetPath,
|
|
340
|
-
)}`,
|
|
341
|
-
{ signal: controller.signal },
|
|
342
|
-
)
|
|
343
|
-
.then((response) => (response.ok ? response.json() : null))
|
|
344
|
-
.then((data: MediaMetadataResponse | null) => {
|
|
345
|
-
if (controller.signal.aborted) return;
|
|
346
|
-
const metadata = data?.metadata ?? null;
|
|
347
|
-
MEDIA_METADATA_CACHE.set(cacheKey, metadata);
|
|
348
|
-
setMediaMetadata(metadata);
|
|
349
|
-
})
|
|
350
|
-
.catch(() => {
|
|
351
|
-
if (!controller.signal.aborted) MEDIA_METADATA_CACHE.set(cacheKey, null);
|
|
352
|
-
});
|
|
353
|
-
return () => controller.abort();
|
|
354
|
-
}, [projectId, selectedAssetPath]);
|
|
355
|
-
|
|
356
|
-
const clearStatusTimers = useCallback(() => {
|
|
357
|
-
for (const timer of statusTimersRef.current) clearTimeout(timer);
|
|
358
|
-
statusTimersRef.current = [];
|
|
359
|
-
}, []);
|
|
360
|
-
|
|
361
|
-
const scheduleRuntimeStatusRefresh = useCallback(() => {
|
|
362
|
-
clearStatusTimers();
|
|
363
|
-
statusTimersRef.current = RUNTIME_STATUS_REFRESH_DELAYS.map((delay) =>
|
|
364
|
-
window.setTimeout(refreshRuntimeStatus, delay),
|
|
365
|
-
);
|
|
366
|
-
}, [clearStatusTimers, refreshRuntimeStatus]);
|
|
367
|
-
|
|
368
|
-
useEffect(() => {
|
|
369
|
-
refreshRuntimeStatus();
|
|
370
|
-
}, [refreshRuntimeStatus]);
|
|
371
|
-
|
|
372
|
-
const persistColorGradingValue = useCallback((value: string | null) => {
|
|
373
|
-
return trackStudioPendingEdit(
|
|
374
|
-
onSetAttributeLiveRef.current(COLOR_GRADING_DATA_KEY, value ?? null),
|
|
375
|
-
);
|
|
376
|
-
}, []);
|
|
377
|
-
|
|
378
|
-
const flushPendingPersist = useCallback(() => {
|
|
379
|
-
if (persistTimerRef.current) {
|
|
380
|
-
clearTimeout(persistTimerRef.current);
|
|
381
|
-
persistTimerRef.current = null;
|
|
382
|
-
}
|
|
383
|
-
if (pendingPersistValueRef.current === undefined) return undefined;
|
|
384
|
-
const value = pendingPersistValueRef.current;
|
|
385
|
-
pendingPersistValueRef.current = undefined;
|
|
386
|
-
return persistColorGradingValue(value);
|
|
387
|
-
}, [persistColorGradingValue]);
|
|
388
|
-
|
|
389
|
-
useEffect(() => addStudioPendingEditFlushListener(flushPendingPersist), [flushPendingPersist]);
|
|
390
|
-
|
|
391
|
-
useEffect(() => {
|
|
392
|
-
return () => {
|
|
393
|
-
clearStatusTimers();
|
|
394
|
-
void flushPendingPersist();
|
|
395
|
-
};
|
|
396
|
-
}, [clearStatusTimers, flushPendingPersist]);
|
|
397
|
-
|
|
398
|
-
const postColorGrading = useCallback(
|
|
399
|
-
(nextGrading: NormalizedHfColorGrading) => {
|
|
400
|
-
postRuntimeControlMessage(previewIframeRef?.current?.contentWindow, "set-color-grading", {
|
|
401
|
-
target,
|
|
402
|
-
grading: toBridgeColorGrading(nextGrading),
|
|
403
|
-
});
|
|
404
|
-
},
|
|
405
|
-
[previewIframeRef, target],
|
|
406
|
-
);
|
|
407
|
-
|
|
408
|
-
const postCompare = useCallback(
|
|
409
|
-
(enabled: boolean) => {
|
|
410
|
-
postRuntimeControlMessage(
|
|
411
|
-
previewIframeRef?.current?.contentWindow,
|
|
412
|
-
"set-color-grading-compare",
|
|
413
|
-
{
|
|
414
|
-
target,
|
|
415
|
-
compare: { enabled, position: 1, lineWidth: 0 },
|
|
416
|
-
},
|
|
417
|
-
);
|
|
418
|
-
},
|
|
419
|
-
[previewIframeRef, target],
|
|
420
|
-
);
|
|
421
|
-
|
|
422
|
-
useEffect(() => {
|
|
423
|
-
const iframe = previewIframeRef?.current;
|
|
424
|
-
if (!iframe) return;
|
|
425
|
-
const refreshAndReplay = () => {
|
|
426
|
-
const nextGrading = latestGradingRef.current;
|
|
427
|
-
const active = isHfColorGradingActive(nextGrading);
|
|
428
|
-
if (active) postColorGrading(nextGrading);
|
|
429
|
-
postCompare(compareEnabledRef.current && active);
|
|
430
|
-
scheduleRuntimeStatusRefresh();
|
|
431
|
-
};
|
|
432
|
-
const onMessage = (event: MessageEvent) => {
|
|
433
|
-
if (event.source !== iframe.contentWindow) return;
|
|
434
|
-
const data = event.data as { source?: unknown; type?: unknown } | null;
|
|
435
|
-
if (data?.source !== "hf-preview" || data.type !== "ready") return;
|
|
436
|
-
if (!acceptStudioRuntimeMessage(data)) return;
|
|
437
|
-
refreshAndReplay();
|
|
438
|
-
};
|
|
439
|
-
iframe.addEventListener("load", refreshAndReplay);
|
|
440
|
-
window.addEventListener("message", onMessage);
|
|
441
|
-
const timer = window.setTimeout(refreshAndReplay, 80);
|
|
442
|
-
return () => {
|
|
443
|
-
iframe.removeEventListener("load", refreshAndReplay);
|
|
444
|
-
window.removeEventListener("message", onMessage);
|
|
445
|
-
window.clearTimeout(timer);
|
|
446
|
-
};
|
|
447
|
-
}, [postColorGrading, postCompare, previewIframeRef, scheduleRuntimeStatusRefresh]);
|
|
448
|
-
|
|
449
|
-
useEffect(
|
|
450
|
-
() => () => {
|
|
451
|
-
postCompare(false);
|
|
452
|
-
},
|
|
453
|
-
[postCompare],
|
|
454
|
-
);
|
|
455
|
-
|
|
456
|
-
const commitColorGrading = useCallback(
|
|
457
|
-
(nextGrading: NormalizedHfColorGrading) => {
|
|
458
|
-
setGrading(nextGrading);
|
|
459
|
-
setRuntimeStatus({ state: "pending", message: "Updating shader" });
|
|
460
|
-
postColorGrading(nextGrading);
|
|
461
|
-
const active = isHfColorGradingActive(nextGrading);
|
|
462
|
-
if (compareEnabledRef.current) {
|
|
463
|
-
postCompare(active);
|
|
464
|
-
if (!active) setCompareEnabled(false);
|
|
465
|
-
}
|
|
466
|
-
scheduleRuntimeStatusRefresh();
|
|
467
|
-
if (persistTimerRef.current) clearTimeout(persistTimerRef.current);
|
|
468
|
-
pendingPersistValueRef.current = isHfColorGradingActive(nextGrading)
|
|
469
|
-
? serializeHfColorGrading(nextGrading)
|
|
470
|
-
: null;
|
|
471
|
-
persistTimerRef.current = setTimeout(() => {
|
|
472
|
-
const value = pendingPersistValueRef.current;
|
|
473
|
-
pendingPersistValueRef.current = undefined;
|
|
474
|
-
persistTimerRef.current = null;
|
|
475
|
-
void persistColorGradingValue(value ?? null);
|
|
476
|
-
}, 350);
|
|
477
|
-
},
|
|
478
|
-
[persistColorGradingValue, postColorGrading, postCompare, scheduleRuntimeStatusRefresh],
|
|
479
|
-
);
|
|
480
|
-
|
|
481
|
-
const commitCompare = useCallback(
|
|
482
|
-
(enabled: boolean) => {
|
|
483
|
-
const nextEnabled = enabled && isHfColorGradingActive(grading);
|
|
484
|
-
setCompareEnabled(nextEnabled);
|
|
485
|
-
if (nextEnabled) postColorGrading(grading);
|
|
486
|
-
postCompare(nextEnabled);
|
|
487
|
-
scheduleRuntimeStatusRefresh();
|
|
488
|
-
},
|
|
489
|
-
[grading, postColorGrading, postCompare, scheduleRuntimeStatusRefresh],
|
|
490
|
-
);
|
|
491
|
-
|
|
492
|
-
const applyToScope = useCallback(async () => {
|
|
493
|
-
if (!onApplyScope || applyBusy) return;
|
|
494
|
-
setApplyBusy(true);
|
|
495
|
-
try {
|
|
496
|
-
const value = isHfColorGradingActive(grading) ? serializeHfColorGrading(grading) : null;
|
|
497
|
-
await onApplyScope(applyScope, value);
|
|
498
|
-
} finally {
|
|
499
|
-
setApplyBusy(false);
|
|
500
|
-
}
|
|
501
|
-
}, [applyBusy, applyScope, grading, onApplyScope]);
|
|
157
|
+
const track = useTrackDesignInput();
|
|
158
|
+
const {
|
|
159
|
+
grading,
|
|
160
|
+
compareEnabled,
|
|
161
|
+
applyScope,
|
|
162
|
+
applyBusy,
|
|
163
|
+
runtimeStatus,
|
|
164
|
+
mediaMetadata,
|
|
165
|
+
commitColorGrading,
|
|
166
|
+
commitCompare,
|
|
167
|
+
setApplyScope,
|
|
168
|
+
applyToScope,
|
|
169
|
+
resetGrading,
|
|
170
|
+
} = useColorGradingController({
|
|
171
|
+
projectId,
|
|
172
|
+
element,
|
|
173
|
+
previewIframeRef,
|
|
174
|
+
onSetAttributeLive,
|
|
175
|
+
onApplyScope,
|
|
176
|
+
});
|
|
502
177
|
|
|
503
178
|
return (
|
|
504
179
|
<Section
|
|
@@ -516,7 +191,8 @@ export function ColorGradingSection({
|
|
|
516
191
|
type="button"
|
|
517
192
|
onClick={(event) => {
|
|
518
193
|
event.stopPropagation();
|
|
519
|
-
|
|
194
|
+
track("button", "Reset color grading");
|
|
195
|
+
resetGrading();
|
|
520
196
|
}}
|
|
521
197
|
className="flex h-6 w-6 flex-shrink-0 items-center justify-center rounded text-panel-text-4 transition-colors hover:bg-panel-hover hover:text-panel-text-1"
|
|
522
198
|
title="Reset color grading"
|
|
@@ -537,7 +213,10 @@ export function ColorGradingSection({
|
|
|
537
213
|
<div className="mt-4 grid min-w-0 grid-cols-[minmax(0,1fr)_auto] gap-2">
|
|
538
214
|
<select
|
|
539
215
|
value={applyScope}
|
|
540
|
-
onChange={(event) =>
|
|
216
|
+
onChange={(event) => {
|
|
217
|
+
track("select", "Apply scope");
|
|
218
|
+
setApplyScope(event.currentTarget.value as typeof applyScope);
|
|
219
|
+
}}
|
|
541
220
|
disabled={applyBusy}
|
|
542
221
|
className="w-full min-w-0 rounded-md bg-panel-input px-3 py-2 text-[11px] font-medium text-panel-text-1 outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
|
543
222
|
title="Choose where to copy these color grading settings"
|
|
@@ -550,6 +229,7 @@ export function ColorGradingSection({
|
|
|
550
229
|
disabled={applyBusy}
|
|
551
230
|
onClick={(event) => {
|
|
552
231
|
event.stopPropagation();
|
|
232
|
+
track("button", "Apply color grading scope");
|
|
553
233
|
void applyToScope();
|
|
554
234
|
}}
|
|
555
235
|
className="h-8 rounded-md bg-panel-input px-3 text-[11px] font-medium text-panel-text-2 transition-colors hover:bg-panel-hover hover:text-panel-text-1 disabled:cursor-not-allowed disabled:opacity-50"
|