@hyperframes/studio 0.6.107 → 0.6.108
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-DgsMQSvV.js → hyperframes-player-67pq7USK.js} +2 -2
- package/dist/assets/index-BVqybwMG.css +1 -0
- package/dist/assets/index-BgbVhDYd.js +296 -0
- package/dist/assets/{index-zIK7PWvk.js → index-Btg-jqxS.js} +1 -1
- package/dist/assets/{index-P52mbTRb.js → index-tn9M43-U.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +5 -5
- package/src/App.tsx +1 -0
- package/src/components/StudioRightPanel.tsx +161 -75
- package/src/components/editor/PropertyPanel.tsx +26 -1
- package/src/components/editor/domEditOverlayGeometry.ts +2 -11
- package/src/components/editor/domEditingDom.ts +21 -0
- package/src/components/editor/domEditingElement.ts +2 -11
- package/src/components/editor/manualEditingAvailability.test.ts +12 -0
- package/src/components/editor/manualEditingAvailability.ts +6 -0
- package/src/components/editor/propertyPanelColorGradingControls.tsx +475 -0
- package/src/components/editor/propertyPanelColorGradingSection.tsx +355 -0
- package/src/components/editor/propertyPanelHelpers.ts +2 -1
- package/src/components/editor/propertyPanelPrimitives.tsx +32 -37
- package/src/contexts/DomEditContext.tsx +4 -0
- package/src/contexts/PanelLayoutContext.tsx +6 -0
- package/src/hooks/useDomEditCommits.ts +7 -0
- package/src/hooks/useDomEditSession.ts +2 -2
- package/src/hooks/useDomEditTextCommits.ts +82 -26
- package/src/hooks/useDomEditWiring.ts +1 -0
- package/src/hooks/useDomSelection.ts +2 -10
- package/src/hooks/usePanelLayout.ts +26 -1
- package/src/hooks/useStudioContextValue.ts +8 -3
- package/src/icons/SystemIcons.tsx +4 -0
- package/src/player/lib/timelineDOM.test.ts +36 -0
- package/src/player/lib/timelineDOM.ts +2 -0
- package/src/player/lib/timelineElementHelpers.ts +18 -1
- package/src/styles/studio.css +81 -0
- package/src/utils/mediaTypes.ts +1 -0
- package/src/utils/studioHelpers.ts +6 -0
- package/dist/assets/index-BrhJl2JY.css +0 -1
- package/dist/assets/index-CVHV-S-B.js +0 -296
|
@@ -1 +1 @@
|
|
|
1
|
-
import{g as P}from"./index-
|
|
1
|
+
import{g as P}from"./index-BgbVhDYd.js";function j(c,d){for(var s=0;s<d.length;s++){const a=d[s];if(typeof a!="string"&&!Array.isArray(a)){for(const i in a)if(i!=="default"&&!(i in c)){const l=Object.getOwnPropertyDescriptor(a,i);l&&Object.defineProperty(c,i,l.get?l:{enumerable:!0,get:()=>a[i]})}}}return Object.freeze(Object.defineProperty(c,Symbol.toStringTag,{value:"Module"}))}var v={},w;function k(){if(w)return v;w=1,Object.defineProperty(v,"__esModule",{value:!0}),v.default=d;var c=window.OfflineAudioContext||window.webkitOfflineAudioContext;function d(e){var r=a(e);return r.start(0),[i,y,O(e.sampleRate),s].reduce(function(t,o){return o(t)},r.buffer.getChannelData(0))}function s(e){return e.sort(function(r,t){return t.count-r.count}).splice(0,5)[0].tempo}function a(e){var r=e.length,t=e.numberOfChannels,o=e.sampleRate,n=new c(t,r,o),u=n.createBufferSource();u.buffer=e;var f=n.createBiquadFilter();return f.type="lowpass",u.connect(f),f.connect(n.destination),u}function i(e){for(var r=[],t=.9,o=.3,n=15;r.length<n&&t>=o;)r=l(e,t),t-=.05;if(r.length<n)throw new Error("Could not find enough samples for a reliable detection.");return r}function l(e,r){for(var t=[],o=0,n=e.length;o<n;o+=1)e[o]>r&&(t.push(o),o+=1e4);return t}function y(e){var r=[];return e.forEach(function(t,o){for(var n=function(x){var g=e[o+x]-t,_=r.some(function(h){if(h.interval===g)return h.count+=1});_||r.push({interval:g,count:1})},u=0;u<10;u+=1)n(u)}),r}function O(e){return function(r){var t=[];return r.forEach(function(o){if(o.interval!==0){for(var n=60/(o.interval/e);n<90;)n*=2;for(;n>180;)n/=2;n=Math.round(n);var u=t.some(function(f){if(f.tempo===n)return f.count+=o.count});u||t.push({tempo:n,count:o.count})}}),t}}return v}var p,b;function q(){return b||(b=1,p=k().default),p}var m=q();const A=P(m),D=j({__proto__:null,default:A},[m]);export{D as i};
|
package/dist/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
|
6
6
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
7
7
|
<title>HyperFrames Studio</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-BgbVhDYd.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BVqybwMG.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/studio",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.108",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -33,9 +33,9 @@
|
|
|
33
33
|
"@phosphor-icons/react": "^2.1.10",
|
|
34
34
|
"bpm-detective": "^2.0.5",
|
|
35
35
|
"mediabunny": "^1.45.3",
|
|
36
|
-
"@hyperframes/
|
|
37
|
-
"@hyperframes/
|
|
38
|
-
"@hyperframes/sdk": "0.6.
|
|
36
|
+
"@hyperframes/player": "0.6.108",
|
|
37
|
+
"@hyperframes/core": "0.6.108",
|
|
38
|
+
"@hyperframes/sdk": "0.6.108"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/react": "19",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"vite": "^6.4.2",
|
|
50
50
|
"vitest": "^3.2.4",
|
|
51
51
|
"zustand": "^5.0.0",
|
|
52
|
-
"@hyperframes/producer": "0.6.
|
|
52
|
+
"@hyperframes/producer": "0.6.108"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"react": "19",
|
package/src/App.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useCallback, useRef, useState, type PointerEvent as ReactPointerEvent } from "react";
|
|
1
2
|
import { Tooltip } from "./ui";
|
|
2
3
|
import { PropertyPanel } from "./editor/PropertyPanel";
|
|
3
4
|
import { LayersPanel } from "./editor/LayersPanel";
|
|
@@ -14,6 +15,9 @@ import { useFileManagerContext } from "../contexts/FileManagerContext";
|
|
|
14
15
|
import { useDomEditContext } from "../contexts/DomEditContext";
|
|
15
16
|
import { usePlayerStore } from "../player";
|
|
16
17
|
|
|
18
|
+
const MIN_INSPECTOR_SPLIT_PERCENT = 20;
|
|
19
|
+
const MAX_INSPECTOR_SPLIT_PERCENT = 75;
|
|
20
|
+
|
|
17
21
|
export interface StudioRightPanelProps {
|
|
18
22
|
designPanelActive: boolean;
|
|
19
23
|
activeBlockParams?: {
|
|
@@ -41,6 +45,8 @@ export function StudioRightPanel({
|
|
|
41
45
|
rightWidth,
|
|
42
46
|
rightPanelTab,
|
|
43
47
|
setRightPanelTab,
|
|
48
|
+
rightInspectorPanes,
|
|
49
|
+
toggleRightInspectorPane,
|
|
44
50
|
handlePanelResizeStart,
|
|
45
51
|
handlePanelResizeMove,
|
|
46
52
|
handlePanelResizeEnd,
|
|
@@ -63,6 +69,7 @@ export function StudioRightPanel({
|
|
|
63
69
|
clearDomSelection,
|
|
64
70
|
handleDomStyleCommit,
|
|
65
71
|
handleDomAttributeCommit,
|
|
72
|
+
handleDomAttributeLiveCommit,
|
|
66
73
|
handleDomHtmlAttributeCommit,
|
|
67
74
|
handleDomPathOffsetCommit,
|
|
68
75
|
handleDomBoxSizeCommit,
|
|
@@ -96,7 +103,130 @@ export function StudioRightPanel({
|
|
|
96
103
|
const { assets, fontAssets, projectDir, handleImportFiles, handleImportFonts } =
|
|
97
104
|
useFileManagerContext();
|
|
98
105
|
|
|
106
|
+
const [layersPanePercent, setLayersPanePercent] = useState(40);
|
|
107
|
+
const splitContainerRef = useRef<HTMLDivElement>(null);
|
|
108
|
+
const splitDragRef = useRef<{
|
|
109
|
+
startY: number;
|
|
110
|
+
startPercent: number;
|
|
111
|
+
height: number;
|
|
112
|
+
} | null>(null);
|
|
113
|
+
|
|
99
114
|
const renderJobs = renderQueue.jobs as RenderJob[];
|
|
115
|
+
const inspectorTabActive = rightPanelTab === "design" || rightPanelTab === "layers";
|
|
116
|
+
const designPaneOpen = inspectorTabActive && rightInspectorPanes.design && designPanelActive;
|
|
117
|
+
const layersPaneOpen =
|
|
118
|
+
inspectorTabActive && rightInspectorPanes.layers && STUDIO_INSPECTOR_PANELS_ENABLED;
|
|
119
|
+
|
|
120
|
+
const handleInspectorPaneButtonClick = (pane: "design" | "layers") => {
|
|
121
|
+
if (!inspectorTabActive) {
|
|
122
|
+
setRightPanelTab(pane);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
toggleRightInspectorPane(pane);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const handleInspectorSplitResizeStart = useCallback(
|
|
129
|
+
(event: ReactPointerEvent<HTMLDivElement>) => {
|
|
130
|
+
event.preventDefault();
|
|
131
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
132
|
+
const height = splitContainerRef.current?.getBoundingClientRect().height ?? 0;
|
|
133
|
+
splitDragRef.current = {
|
|
134
|
+
startY: event.clientY,
|
|
135
|
+
startPercent: layersPanePercent,
|
|
136
|
+
height,
|
|
137
|
+
};
|
|
138
|
+
},
|
|
139
|
+
[layersPanePercent],
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const handleInspectorSplitResizeMove = useCallback((event: ReactPointerEvent<HTMLDivElement>) => {
|
|
143
|
+
const drag = splitDragRef.current;
|
|
144
|
+
if (!drag || drag.height <= 0) return;
|
|
145
|
+
const deltaPercent = ((event.clientY - drag.startY) / drag.height) * 100;
|
|
146
|
+
const next = Math.min(
|
|
147
|
+
MAX_INSPECTOR_SPLIT_PERCENT,
|
|
148
|
+
Math.max(MIN_INSPECTOR_SPLIT_PERCENT, drag.startPercent + deltaPercent),
|
|
149
|
+
);
|
|
150
|
+
setLayersPanePercent(next);
|
|
151
|
+
}, []);
|
|
152
|
+
|
|
153
|
+
const handleInspectorSplitResizeEnd = useCallback(() => {
|
|
154
|
+
splitDragRef.current = null;
|
|
155
|
+
}, []);
|
|
156
|
+
|
|
157
|
+
const propertyPanel = (
|
|
158
|
+
<PropertyPanel
|
|
159
|
+
projectId={projectId}
|
|
160
|
+
projectDir={projectDir}
|
|
161
|
+
assets={assets}
|
|
162
|
+
element={domEditGroupSelections.length > 1 ? null : domEditSelection}
|
|
163
|
+
multiSelectCount={domEditGroupSelections.length}
|
|
164
|
+
copiedAgentPrompt={copiedAgentPrompt}
|
|
165
|
+
onClearSelection={clearDomSelection}
|
|
166
|
+
onSetStyle={handleDomStyleCommit}
|
|
167
|
+
onSetAttribute={handleDomAttributeCommit}
|
|
168
|
+
onSetAttributeLive={handleDomAttributeLiveCommit}
|
|
169
|
+
onSetHtmlAttribute={handleDomHtmlAttributeCommit}
|
|
170
|
+
onSetManualOffset={handleDomPathOffsetCommit}
|
|
171
|
+
onSetManualSize={handleDomBoxSizeCommit}
|
|
172
|
+
onSetManualRotation={handleDomRotationCommit}
|
|
173
|
+
onSetText={handleDomTextCommit}
|
|
174
|
+
onSetTextFieldStyle={handleDomTextFieldStyleCommit}
|
|
175
|
+
onAddTextField={handleDomAddTextField}
|
|
176
|
+
onRemoveTextField={handleDomRemoveTextField}
|
|
177
|
+
onAskAgent={handleAskAgent}
|
|
178
|
+
onImportAssets={handleImportFiles}
|
|
179
|
+
fontAssets={fontAssets}
|
|
180
|
+
onImportFonts={handleImportFonts}
|
|
181
|
+
previewIframeRef={previewIframeRef}
|
|
182
|
+
gsapAnimations={selectedGsapAnimations}
|
|
183
|
+
gsapMultipleTimelines={gsapMultipleTimelines}
|
|
184
|
+
gsapUnsupportedTimelinePattern={gsapUnsupportedTimelinePattern}
|
|
185
|
+
onUpdateGsapProperty={handleGsapUpdateProperty}
|
|
186
|
+
onUpdateGsapMeta={handleGsapUpdateMeta}
|
|
187
|
+
onDeleteGsapAnimation={handleGsapDeleteAnimation}
|
|
188
|
+
onAddGsapProperty={handleGsapAddProperty}
|
|
189
|
+
onRemoveGsapProperty={handleGsapRemoveProperty}
|
|
190
|
+
onUpdateGsapFromProperty={handleGsapUpdateFromProperty}
|
|
191
|
+
onAddGsapFromProperty={handleGsapAddFromProperty}
|
|
192
|
+
onRemoveGsapFromProperty={handleGsapRemoveFromProperty}
|
|
193
|
+
onAddGsapAnimation={handleGsapAddAnimation}
|
|
194
|
+
onCommitAnimatedProperty={commitAnimatedProperty}
|
|
195
|
+
onAddKeyframe={handleGsapAddKeyframe}
|
|
196
|
+
onRemoveKeyframe={handleGsapRemoveKeyframe}
|
|
197
|
+
onConvertToKeyframes={handleGsapConvertToKeyframes}
|
|
198
|
+
onSeekToTime={(t) => usePlayerStore.getState().requestSeek(t)}
|
|
199
|
+
onSetArcPath={handleSetArcPath}
|
|
200
|
+
onUpdateArcSegment={handleUpdateArcSegment}
|
|
201
|
+
onUnroll={handleUnroll}
|
|
202
|
+
recordingState={recordingState}
|
|
203
|
+
recordingDuration={recordingDuration}
|
|
204
|
+
onToggleRecording={onToggleRecording}
|
|
205
|
+
/>
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
const renderQueuePanel = (
|
|
209
|
+
<RenderQueue
|
|
210
|
+
jobs={renderJobs}
|
|
211
|
+
projectId={projectId}
|
|
212
|
+
onDelete={renderQueue.deleteRender}
|
|
213
|
+
onClearCompleted={renderQueue.clearCompleted}
|
|
214
|
+
onStartRender={async (format, quality, resolution, fps) => {
|
|
215
|
+
await waitForPendingDomEditSaves();
|
|
216
|
+
const composition =
|
|
217
|
+
activeCompPath && activeCompPath !== "index.html" ? activeCompPath : undefined;
|
|
218
|
+
await renderQueue.startRender({
|
|
219
|
+
fps,
|
|
220
|
+
quality,
|
|
221
|
+
format,
|
|
222
|
+
resolution,
|
|
223
|
+
composition,
|
|
224
|
+
});
|
|
225
|
+
}}
|
|
226
|
+
compositionDimensions={compositionDimensions}
|
|
227
|
+
isRendering={renderQueue.isRendering}
|
|
228
|
+
/>
|
|
229
|
+
);
|
|
100
230
|
|
|
101
231
|
return (
|
|
102
232
|
<>
|
|
@@ -123,9 +253,9 @@ export function StudioRightPanel({
|
|
|
123
253
|
<Tooltip label="Element styles and properties" side="bottom">
|
|
124
254
|
<button
|
|
125
255
|
type="button"
|
|
126
|
-
onClick={() =>
|
|
256
|
+
onClick={() => handleInspectorPaneButtonClick("design")}
|
|
127
257
|
className={`h-8 rounded-xl px-3 text-[11px] font-medium transition-colors ${
|
|
128
|
-
|
|
258
|
+
designPaneOpen
|
|
129
259
|
? "bg-neutral-800 text-white"
|
|
130
260
|
: "text-neutral-500 hover:bg-neutral-800/70 hover:text-neutral-200"
|
|
131
261
|
}`}
|
|
@@ -136,9 +266,9 @@ export function StudioRightPanel({
|
|
|
136
266
|
<Tooltip label="Composition layer stack" side="bottom">
|
|
137
267
|
<button
|
|
138
268
|
type="button"
|
|
139
|
-
onClick={() =>
|
|
269
|
+
onClick={() => handleInspectorPaneButtonClick("layers")}
|
|
140
270
|
className={`h-8 rounded-xl px-3 text-[11px] font-medium transition-colors ${
|
|
141
|
-
|
|
271
|
+
layersPaneOpen
|
|
142
272
|
? "bg-neutral-800 text-white"
|
|
143
273
|
: "text-neutral-500 hover:bg-neutral-800/70 hover:text-neutral-200"
|
|
144
274
|
}`}
|
|
@@ -171,79 +301,35 @@ export function StudioRightPanel({
|
|
|
171
301
|
compositionPath={activeBlockParams.compositionPath}
|
|
172
302
|
onClose={onCloseBlockParams ?? (() => {})}
|
|
173
303
|
/>
|
|
174
|
-
) :
|
|
304
|
+
) : layersPaneOpen && designPaneOpen ? (
|
|
305
|
+
<div ref={splitContainerRef} className="flex h-full min-h-0 flex-col">
|
|
306
|
+
<div
|
|
307
|
+
className="min-h-[120px] overflow-hidden"
|
|
308
|
+
style={{ flexBasis: `${layersPanePercent}%`, flexShrink: 0 }}
|
|
309
|
+
>
|
|
310
|
+
<LayersPanel />
|
|
311
|
+
</div>
|
|
312
|
+
<div
|
|
313
|
+
role="separator"
|
|
314
|
+
aria-label="Resize Layers and Design panes"
|
|
315
|
+
aria-orientation="horizontal"
|
|
316
|
+
className="group flex h-2 flex-shrink-0 cursor-row-resize items-center justify-center border-y border-neutral-800 bg-neutral-900"
|
|
317
|
+
style={{ touchAction: "none" }}
|
|
318
|
+
onPointerDown={handleInspectorSplitResizeStart}
|
|
319
|
+
onPointerMove={handleInspectorSplitResizeMove}
|
|
320
|
+
onPointerUp={handleInspectorSplitResizeEnd}
|
|
321
|
+
onPointerCancel={handleInspectorSplitResizeEnd}
|
|
322
|
+
>
|
|
323
|
+
<div className="h-px w-10 rounded-full bg-white/12 transition-colors group-hover:bg-white/24 group-active:bg-studio-accent/70" />
|
|
324
|
+
</div>
|
|
325
|
+
<div className="min-h-0 flex-1 overflow-hidden">{propertyPanel}</div>
|
|
326
|
+
</div>
|
|
327
|
+
) : layersPaneOpen ? (
|
|
175
328
|
<LayersPanel />
|
|
176
|
-
) :
|
|
177
|
-
|
|
178
|
-
projectId={projectId}
|
|
179
|
-
projectDir={projectDir}
|
|
180
|
-
assets={assets}
|
|
181
|
-
element={domEditGroupSelections.length > 1 ? null : domEditSelection}
|
|
182
|
-
multiSelectCount={domEditGroupSelections.length}
|
|
183
|
-
copiedAgentPrompt={copiedAgentPrompt}
|
|
184
|
-
onClearSelection={clearDomSelection}
|
|
185
|
-
onSetStyle={handleDomStyleCommit}
|
|
186
|
-
onSetAttribute={handleDomAttributeCommit}
|
|
187
|
-
onSetHtmlAttribute={handleDomHtmlAttributeCommit}
|
|
188
|
-
onSetManualOffset={handleDomPathOffsetCommit}
|
|
189
|
-
onSetManualSize={handleDomBoxSizeCommit}
|
|
190
|
-
onSetManualRotation={handleDomRotationCommit}
|
|
191
|
-
onSetText={handleDomTextCommit}
|
|
192
|
-
onSetTextFieldStyle={handleDomTextFieldStyleCommit}
|
|
193
|
-
onAddTextField={handleDomAddTextField}
|
|
194
|
-
onRemoveTextField={handleDomRemoveTextField}
|
|
195
|
-
onAskAgent={handleAskAgent}
|
|
196
|
-
onImportAssets={handleImportFiles}
|
|
197
|
-
fontAssets={fontAssets}
|
|
198
|
-
onImportFonts={handleImportFonts}
|
|
199
|
-
previewIframeRef={previewIframeRef}
|
|
200
|
-
gsapAnimations={selectedGsapAnimations}
|
|
201
|
-
gsapMultipleTimelines={gsapMultipleTimelines}
|
|
202
|
-
gsapUnsupportedTimelinePattern={gsapUnsupportedTimelinePattern}
|
|
203
|
-
onUpdateGsapProperty={handleGsapUpdateProperty}
|
|
204
|
-
onUpdateGsapMeta={handleGsapUpdateMeta}
|
|
205
|
-
onDeleteGsapAnimation={handleGsapDeleteAnimation}
|
|
206
|
-
onAddGsapProperty={handleGsapAddProperty}
|
|
207
|
-
onRemoveGsapProperty={handleGsapRemoveProperty}
|
|
208
|
-
onUpdateGsapFromProperty={handleGsapUpdateFromProperty}
|
|
209
|
-
onAddGsapFromProperty={handleGsapAddFromProperty}
|
|
210
|
-
onRemoveGsapFromProperty={handleGsapRemoveFromProperty}
|
|
211
|
-
onAddGsapAnimation={handleGsapAddAnimation}
|
|
212
|
-
onCommitAnimatedProperty={commitAnimatedProperty}
|
|
213
|
-
onAddKeyframe={handleGsapAddKeyframe}
|
|
214
|
-
onRemoveKeyframe={handleGsapRemoveKeyframe}
|
|
215
|
-
onConvertToKeyframes={handleGsapConvertToKeyframes}
|
|
216
|
-
onSeekToTime={(t) => usePlayerStore.getState().requestSeek(t)}
|
|
217
|
-
onSetArcPath={handleSetArcPath}
|
|
218
|
-
onUpdateArcSegment={handleUpdateArcSegment}
|
|
219
|
-
onUnroll={handleUnroll}
|
|
220
|
-
recordingState={recordingState}
|
|
221
|
-
recordingDuration={recordingDuration}
|
|
222
|
-
onToggleRecording={onToggleRecording}
|
|
223
|
-
/>
|
|
329
|
+
) : designPaneOpen ? (
|
|
330
|
+
propertyPanel
|
|
224
331
|
) : (
|
|
225
|
-
|
|
226
|
-
jobs={renderJobs}
|
|
227
|
-
projectId={projectId}
|
|
228
|
-
onDelete={renderQueue.deleteRender}
|
|
229
|
-
onClearCompleted={renderQueue.clearCompleted}
|
|
230
|
-
onStartRender={async (format, quality, resolution, fps) => {
|
|
231
|
-
await waitForPendingDomEditSaves();
|
|
232
|
-
const composition =
|
|
233
|
-
activeCompPath && activeCompPath !== "index.html"
|
|
234
|
-
? activeCompPath
|
|
235
|
-
: undefined;
|
|
236
|
-
await renderQueue.startRender({
|
|
237
|
-
fps,
|
|
238
|
-
quality,
|
|
239
|
-
format,
|
|
240
|
-
resolution,
|
|
241
|
-
composition,
|
|
242
|
-
});
|
|
243
|
-
}}
|
|
244
|
-
compositionDimensions={compositionDimensions}
|
|
245
|
-
isRendering={renderQueue.isRendering}
|
|
246
|
-
/>
|
|
332
|
+
renderQueuePanel
|
|
247
333
|
)}
|
|
248
334
|
</div>
|
|
249
335
|
</>
|
|
@@ -14,11 +14,19 @@ import { MetricField, Section } from "./propertyPanelPrimitives";
|
|
|
14
14
|
import { createTransformCommitHandlers } from "./propertyPanelTransformCommit";
|
|
15
15
|
import { classifyPropertyGroup } from "@hyperframes/core/gsap-parser";
|
|
16
16
|
import { isMediaElement, MediaSection } from "./propertyPanelMediaSection";
|
|
17
|
+
import {
|
|
18
|
+
ColorGradingSection,
|
|
19
|
+
isColorGradingCapableElement,
|
|
20
|
+
} from "./propertyPanelColorGradingSection";
|
|
17
21
|
import { TextSection, StyleSections } from "./propertyPanelSections";
|
|
18
22
|
import { GsapAnimationSection } from "./GsapAnimationSection";
|
|
19
23
|
import { PropertyPanel3dTransform } from "./propertyPanel3dTransform";
|
|
20
24
|
import { KeyframeNavigation } from "./KeyframeNavigation";
|
|
21
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
STUDIO_COLOR_GRADING_ENABLED,
|
|
27
|
+
STUDIO_GSAP_PANEL_ENABLED,
|
|
28
|
+
STUDIO_KEYFRAMES_ENABLED,
|
|
29
|
+
} from "./manualEditingAvailability";
|
|
22
30
|
import { usePlayerStore, liveTime } from "../../player";
|
|
23
31
|
import { TimingSection } from "./propertyPanelTimingSection";
|
|
24
32
|
import { type PropertyPanelProps } from "./propertyPanelHelpers";
|
|
@@ -47,6 +55,7 @@ export const PropertyPanel = memo(function PropertyPanel({
|
|
|
47
55
|
onClearSelection,
|
|
48
56
|
onSetStyle,
|
|
49
57
|
onSetAttribute,
|
|
58
|
+
onSetAttributeLive,
|
|
50
59
|
onSetHtmlAttribute,
|
|
51
60
|
onSetManualOffset,
|
|
52
61
|
onSetManualSize,
|
|
@@ -355,6 +364,22 @@ export const PropertyPanel = memo(function PropertyPanel({
|
|
|
355
364
|
/>
|
|
356
365
|
)}
|
|
357
366
|
|
|
367
|
+
{STUDIO_COLOR_GRADING_ENABLED && isColorGradingCapableElement(element) && (
|
|
368
|
+
<ColorGradingSection
|
|
369
|
+
key={[
|
|
370
|
+
element.id ?? "",
|
|
371
|
+
element.hfId ?? "",
|
|
372
|
+
element.selector ?? "",
|
|
373
|
+
String(element.selectorIndex ?? ""),
|
|
374
|
+
].join("|")}
|
|
375
|
+
element={element}
|
|
376
|
+
assets={assets}
|
|
377
|
+
previewIframeRef={previewIframeRef}
|
|
378
|
+
onImportAssets={onImportAssets}
|
|
379
|
+
onSetAttributeLive={onSetAttributeLive}
|
|
380
|
+
/>
|
|
381
|
+
)}
|
|
382
|
+
|
|
358
383
|
<Section title="Layout" icon={<Move size={15} />}>
|
|
359
384
|
<div className={RESPONSIVE_GRID}>
|
|
360
385
|
<div className="flex items-center gap-1">
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type DomEditSelection, findElementForSelection } from "./domEditing";
|
|
2
|
+
import { isElementVisibleThroughAncestors } from "./domEditingDom";
|
|
2
3
|
|
|
3
4
|
export interface OverlayRect {
|
|
4
5
|
left: number;
|
|
@@ -21,17 +22,7 @@ export type ResolvedElementRef = {
|
|
|
21
22
|
};
|
|
22
23
|
|
|
23
24
|
export function isElementVisibleForOverlay(el: HTMLElement): boolean {
|
|
24
|
-
|
|
25
|
-
if (!win) return true;
|
|
26
|
-
let current: HTMLElement | null = el;
|
|
27
|
-
while (current) {
|
|
28
|
-
const computed = win.getComputedStyle(current);
|
|
29
|
-
if (computed.display === "none" || computed.visibility === "hidden") return false;
|
|
30
|
-
const opacity = Number.parseFloat(computed.opacity);
|
|
31
|
-
if (Number.isFinite(opacity) && opacity <= 0.01) return false;
|
|
32
|
-
current = current.parentElement;
|
|
33
|
-
}
|
|
34
|
-
return true;
|
|
25
|
+
return isElementVisibleThroughAncestors(el);
|
|
35
26
|
}
|
|
36
27
|
|
|
37
28
|
function readPositiveDimension(value: string | null): number | null {
|
|
@@ -57,6 +57,27 @@ export function isTextBearingTag(tagName: string): boolean {
|
|
|
57
57
|
return ["div", "span", "p", "strong", "h1", "h2", "h3", "h4", "h5", "h6"].includes(tagName);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
const COLOR_GRADING_SOURCE_HIDDEN_ATTR = "data-hf-color-grading-source-hidden";
|
|
61
|
+
|
|
62
|
+
export function isElementVisibleThroughAncestors(el: HTMLElement): boolean {
|
|
63
|
+
const win = el.ownerDocument.defaultView;
|
|
64
|
+
if (!win) return true;
|
|
65
|
+
let current: HTMLElement | null = el;
|
|
66
|
+
while (current) {
|
|
67
|
+
const computed = win.getComputedStyle(current);
|
|
68
|
+
if (computed.display === "none" || computed.visibility === "hidden") return false;
|
|
69
|
+
const opacity = Number.parseFloat(computed.opacity);
|
|
70
|
+
if (
|
|
71
|
+
Number.isFinite(opacity) &&
|
|
72
|
+
opacity <= 0.01 &&
|
|
73
|
+
!current.hasAttribute(COLOR_GRADING_SOURCE_HIDDEN_ATTR)
|
|
74
|
+
)
|
|
75
|
+
return false;
|
|
76
|
+
current = current.parentElement;
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
60
81
|
// ─── Style accessors ──────────────────────────────────────────────────────────
|
|
61
82
|
|
|
62
83
|
export function getCuratedComputedStyles(el: HTMLElement): Record<string, string> {
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
getSelectorIndex,
|
|
16
16
|
getSourceFileForElement,
|
|
17
17
|
isHtmlElement,
|
|
18
|
+
isElementVisibleThroughAncestors,
|
|
18
19
|
normalizeTimelineCompositionSource,
|
|
19
20
|
querySelectorAllSafely,
|
|
20
21
|
} from "./domEditingDom";
|
|
@@ -22,17 +23,7 @@ import {
|
|
|
22
23
|
// ─── Visibility ──────────────────────────────────────────────────────────────
|
|
23
24
|
|
|
24
25
|
export function isElementComputedVisible(el: HTMLElement): boolean {
|
|
25
|
-
|
|
26
|
-
if (!win) return true;
|
|
27
|
-
let current: HTMLElement | null = el;
|
|
28
|
-
while (current) {
|
|
29
|
-
const computed = win.getComputedStyle(current);
|
|
30
|
-
if (computed.display === "none" || computed.visibility === "hidden") return false;
|
|
31
|
-
const opacity = Number.parseFloat(computed.opacity);
|
|
32
|
-
if (Number.isFinite(opacity) && opacity <= 0.01) return false;
|
|
33
|
-
current = current.parentElement;
|
|
34
|
-
}
|
|
35
|
-
return true;
|
|
26
|
+
return isElementVisibleThroughAncestors(el);
|
|
36
27
|
}
|
|
37
28
|
|
|
38
29
|
const VISUAL_LEAF_TAGS = new Set(["img", "video", "canvas", "svg", "audio"]);
|
|
@@ -29,6 +29,18 @@ describe("manual editing availability", () => {
|
|
|
29
29
|
expect(availability.STUDIO_GSAP_DRAG_INTERCEPT_ENABLED).toBe(true);
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
+
it("keeps color grading off by default", async () => {
|
|
33
|
+
const availability = await loadAvailabilityWithEnv({});
|
|
34
|
+
expect(availability.STUDIO_COLOR_GRADING_ENABLED).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("enables color grading with an explicit env flag", async () => {
|
|
38
|
+
const availability = await loadAvailabilityWithEnv({
|
|
39
|
+
VITE_STUDIO_ENABLE_COLOR_GRADING: "1",
|
|
40
|
+
});
|
|
41
|
+
expect(availability.STUDIO_COLOR_GRADING_ENABLED).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
32
44
|
it("disables GSAP drag intercept when env var is false", async () => {
|
|
33
45
|
const availability = await loadAvailabilityWithEnv({
|
|
34
46
|
VITE_STUDIO_ENABLE_GSAP_DRAG_INTERCEPT: "false",
|
|
@@ -64,6 +64,12 @@ export const STUDIO_GSAP_PANEL_ENABLED = resolveStudioBooleanEnvFlag(
|
|
|
64
64
|
true,
|
|
65
65
|
);
|
|
66
66
|
|
|
67
|
+
export const STUDIO_COLOR_GRADING_ENABLED = resolveStudioBooleanEnvFlag(
|
|
68
|
+
env,
|
|
69
|
+
["VITE_STUDIO_ENABLE_COLOR_GRADING", "VITE_STUDIO_COLOR_GRADING_ENABLED"],
|
|
70
|
+
false,
|
|
71
|
+
);
|
|
72
|
+
|
|
67
73
|
export const STUDIO_KEYFRAMES_ENABLED = resolveStudioBooleanEnvFlag(
|
|
68
74
|
env,
|
|
69
75
|
["VITE_STUDIO_ENABLE_KEYFRAMES", "VITE_STUDIO_KEYFRAMES_ENABLED"],
|