@hyperframes/studio 0.7.36 → 0.7.38
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-BBrKzTGd.js +459 -0
- package/dist/assets/index-BLRTwY5l.js +396 -0
- package/dist/assets/{index-BltxqwG6.js → index-ClUipc8i.js} +1 -1
- package/dist/assets/index-DJaiR8T2.css +1 -0
- package/dist/assets/{index-_bjyggFK.js → index-Ykq7ihge.js} +1 -1
- package/dist/index.d.ts +35 -10
- package/dist/index.html +2 -2
- package/dist/index.js +5147 -3322
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/App.tsx +19 -18
- package/src/components/AskAgentModal.tsx +23 -6
- package/src/components/LintModal.tsx +40 -10
- package/src/components/MediaPreview.tsx +36 -1
- package/src/components/SaveQueuePausedBanner.tsx +7 -6
- package/src/components/StudioErrorBoundary.tsx +19 -31
- package/src/components/StudioFeedbackBar.tsx +11 -2
- package/src/components/StudioGlobalDragOverlay.tsx +1 -1
- package/src/components/StudioHeader.tsx +184 -106
- package/src/components/StudioLeftSidebar.tsx +24 -3
- package/src/components/StudioOverlays.tsx +34 -8
- package/src/components/StudioRightPanel.tsx +154 -9
- package/src/components/StudioSplash.tsx +6 -3
- package/src/components/StudioToast.tsx +12 -11
- package/src/components/TimelineToolbar.test.tsx +9 -3
- package/src/components/TimelineToolbar.tsx +63 -29
- package/src/components/editor/PropertyPanel.tsx +18 -17
- package/src/components/editor/colorGradingScopePatch.test.ts +74 -0
- package/src/components/editor/colorGradingScopePatch.ts +57 -0
- package/src/components/editor/manualEditingAvailability.test.ts +0 -12
- package/src/components/editor/manualEditingAvailability.ts +0 -6
- package/src/components/editor/propertyPanelColorGradingControls.tsx +371 -329
- package/src/components/editor/propertyPanelColorGradingSection.tsx +274 -55
- package/src/components/editor/propertyPanelColorGradingSlider.tsx +275 -0
- package/src/components/editor/propertyPanelHelpers.ts +22 -91
- package/src/components/editor/propertyPanelMediaSection.tsx +253 -99
- package/src/components/editor/propertyPanelTypes.ts +111 -0
- package/src/components/nle/CompositionBreadcrumb.tsx +3 -2
- package/src/components/nle/NLELayout.tsx +54 -51
- package/src/components/nle/NLEPreview.tsx +41 -5
- package/src/components/nle/TimelineResizeDivider.tsx +97 -0
- package/src/components/nle/usePreviewBlockDrop.ts +31 -7
- package/src/components/renders/RenderQueue.tsx +125 -24
- package/src/components/renders/RenderQueueItem.tsx +132 -64
- package/src/components/renders/useRenderQueue.ts +136 -24
- package/src/components/storyboard/StoryboardFrameFocus.tsx +39 -16
- package/src/components/storyboard/StoryboardLoaded.tsx +19 -2
- package/src/components/storyboard/StoryboardView.tsx +6 -0
- package/src/components/studioColorGradingScope.ts +124 -0
- package/src/components/studioMediaJobs.ts +123 -0
- package/src/components/ui/Button.tsx +20 -11
- package/src/components/ui/HyperframesLoader.tsx +9 -2
- package/src/components/ui/SearchInput.tsx +53 -0
- package/src/components/ui/Tooltip.tsx +52 -6
- package/src/components/ui/VideoFrameThumbnail.tsx +26 -3
- package/src/components/ui/useDialogBehavior.ts +83 -0
- package/src/contexts/PanelLayoutContext.tsx +3 -0
- package/src/contexts/StudioContext.tsx +7 -0
- package/src/hooks/useCompositionContentLoader.ts +40 -0
- package/src/hooks/useEditorSave.ts +14 -0
- package/src/hooks/useFileManager.ts +39 -25
- package/src/hooks/useFrameCapture.ts +12 -1
- package/src/hooks/usePanelLayout.ts +1 -0
- package/src/hooks/usePreviewPersistence.ts +24 -15
- package/src/hooks/useStudioContextValue.ts +5 -0
- package/src/hooks/useToast.ts +66 -13
- package/src/styles/studio.css +61 -0
- package/src/utils/sdkResolverShadow.test.ts +70 -3
- package/src/utils/sdkResolverShadow.ts +47 -4
- package/src/utils/studioPendingEdits.test.ts +43 -0
- package/src/utils/studioPendingEdits.ts +45 -0
- package/src/utils/studioUiPreferences.ts +4 -0
- package/dist/assets/hyperframes-player-BGuumSiM.js +0 -459
- package/dist/assets/index-DfeE1_Rl.js +0 -396
- package/dist/assets/index-DmkOvZns.css +0 -1
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
|
-
import { Check, ClipboardList, Film, Music } from "../../icons/SystemIcons";
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { Check, ClipboardList, Film, Music, Scissors } from "../../icons/SystemIcons";
|
|
3
3
|
import type { DomEditSelection } from "./domEditing";
|
|
4
4
|
import {
|
|
5
|
+
type BackgroundRemovalProgress,
|
|
6
|
+
type BackgroundRemovalResult,
|
|
5
7
|
formatNumericValue,
|
|
6
8
|
formatTimingValue,
|
|
7
9
|
LABEL,
|
|
8
10
|
parseNumericValue,
|
|
9
11
|
RESPONSIVE_GRID,
|
|
12
|
+
stripQueryAndHash,
|
|
10
13
|
} from "./propertyPanelHelpers";
|
|
11
14
|
import { Section, SegmentedControl, SelectField, SliderControl } from "./propertyPanelPrimitives";
|
|
12
15
|
|
|
16
|
+
// fallow-ignore-next-line complexity
|
|
13
17
|
export function MediaSection({
|
|
14
18
|
projectDir,
|
|
15
19
|
element,
|
|
@@ -17,6 +21,7 @@ export function MediaSection({
|
|
|
17
21
|
onSetStyle,
|
|
18
22
|
onSetAttribute,
|
|
19
23
|
onSetHtmlAttribute,
|
|
24
|
+
onRemoveBackground,
|
|
20
25
|
}: {
|
|
21
26
|
projectDir: string | null;
|
|
22
27
|
element: DomEditSelection;
|
|
@@ -24,8 +29,19 @@ export function MediaSection({
|
|
|
24
29
|
onSetStyle: (prop: string, value: string) => void | Promise<void>;
|
|
25
30
|
onSetAttribute: (attr: string, value: string) => void | Promise<void>;
|
|
26
31
|
onSetHtmlAttribute: (attr: string, value: string | null) => void | Promise<void>;
|
|
32
|
+
onRemoveBackground?: (
|
|
33
|
+
inputPath: string,
|
|
34
|
+
options: {
|
|
35
|
+
createBackgroundPlate?: boolean;
|
|
36
|
+
quality?: "fast" | "balanced" | "best";
|
|
37
|
+
onProgress?: (progress: BackgroundRemovalProgress) => void;
|
|
38
|
+
},
|
|
39
|
+
) => Promise<BackgroundRemovalResult>;
|
|
27
40
|
}) {
|
|
28
41
|
const isVideo = element.tagName === "video";
|
|
42
|
+
const isAudio = element.tagName === "audio";
|
|
43
|
+
const isImage = element.tagName === "img";
|
|
44
|
+
const isVisualMedia = isVideo || isImage;
|
|
29
45
|
const el = element.element;
|
|
30
46
|
|
|
31
47
|
const volume = parseNumericValue(element.dataAttributes.volume ?? "") ?? 1;
|
|
@@ -53,15 +69,64 @@ export function MediaSection({
|
|
|
53
69
|
|
|
54
70
|
const srcAttr = el.getAttribute("src") ?? "";
|
|
55
71
|
const [copied, setCopied] = useState(false);
|
|
72
|
+
const [removeBusy, setRemoveBusy] = useState(false);
|
|
73
|
+
const [removeProgress, setRemoveProgress] = useState<BackgroundRemovalProgress | null>(null);
|
|
74
|
+
const [createPlate, setCreatePlate] = useState(false);
|
|
75
|
+
const [quality, setQuality] = useState<"fast" | "balanced" | "best">("balanced");
|
|
56
76
|
|
|
57
77
|
const absoluteSrc =
|
|
58
78
|
projectDir && srcAttr && !srcAttr.startsWith("http") ? `${projectDir}/${srcAttr}` : srcAttr;
|
|
79
|
+
const projectSrc =
|
|
80
|
+
srcAttr && !/^(?:https?:|data:|blob:)/i.test(srcAttr)
|
|
81
|
+
? stripQueryAndHash(srcAttr.startsWith("./") ? srcAttr.slice(2) : srcAttr)
|
|
82
|
+
: "";
|
|
83
|
+
const canRemoveBackground = Boolean(onRemoveBackground && isVisualMedia && projectSrc);
|
|
84
|
+
const panelTitle = isImage ? "Image" : isVideo ? "Video" : "Audio";
|
|
85
|
+
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
setRemoveProgress(null);
|
|
88
|
+
setCreatePlate(false);
|
|
89
|
+
}, [srcAttr]);
|
|
90
|
+
|
|
91
|
+
const applyCutoutResult = async (result: BackgroundRemovalResult) => {
|
|
92
|
+
await onSetHtmlAttribute("src", result.outputPath);
|
|
93
|
+
if (isVideo) {
|
|
94
|
+
await onSetAttribute("has-audio", "");
|
|
95
|
+
await onSetHtmlAttribute("muted", "true");
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const runBackgroundRemoval = async () => {
|
|
100
|
+
if (!onRemoveBackground || !projectSrc || removeBusy) return;
|
|
101
|
+
setRemoveBusy(true);
|
|
102
|
+
setRemoveProgress({ status: "processing", progress: 0, stage: "Preparing" });
|
|
103
|
+
try {
|
|
104
|
+
const result = await onRemoveBackground(projectSrc, {
|
|
105
|
+
createBackgroundPlate: isVideo && createPlate,
|
|
106
|
+
quality,
|
|
107
|
+
onProgress: setRemoveProgress,
|
|
108
|
+
});
|
|
109
|
+
await applyCutoutResult(result);
|
|
110
|
+
setRemoveProgress({
|
|
111
|
+
status: "complete",
|
|
112
|
+
progress: 100,
|
|
113
|
+
stage: "Applied cutout",
|
|
114
|
+
...result,
|
|
115
|
+
});
|
|
116
|
+
} catch (error) {
|
|
117
|
+
setRemoveProgress({
|
|
118
|
+
status: "failed",
|
|
119
|
+
progress: 0,
|
|
120
|
+
stage: "Failed",
|
|
121
|
+
error: error instanceof Error ? error.message : String(error),
|
|
122
|
+
});
|
|
123
|
+
} finally {
|
|
124
|
+
setRemoveBusy(false);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
59
127
|
|
|
60
128
|
return (
|
|
61
|
-
<Section
|
|
62
|
-
title={isVideo ? "Video" : "Audio"}
|
|
63
|
-
icon={isVideo ? <Film size={15} /> : <Music size={15} />}
|
|
64
|
-
>
|
|
129
|
+
<Section title={panelTitle} icon={isAudio ? <Music size={15} /> : <Film size={15} />}>
|
|
65
130
|
<div className="space-y-4">
|
|
66
131
|
{srcAttr && (
|
|
67
132
|
<div className="min-w-0">
|
|
@@ -90,103 +155,192 @@ export function MediaSection({
|
|
|
90
155
|
</div>
|
|
91
156
|
)}
|
|
92
157
|
|
|
93
|
-
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
step={5}
|
|
115
|
-
displayValue={`${formatNumericValue(playbackRate)}x`}
|
|
116
|
-
formatDisplayValue={(next) => `${formatNumericValue(next / 100)}x`}
|
|
117
|
-
onCommit={(next) => {
|
|
118
|
-
void onSetAttribute("playback-rate", formatNumericValue(next / 100));
|
|
119
|
-
}}
|
|
120
|
-
/>
|
|
121
|
-
</div>
|
|
122
|
-
|
|
123
|
-
<div className="grid min-w-0 gap-1.5">
|
|
124
|
-
<span className={LABEL}>Media start</span>
|
|
125
|
-
<SliderControl
|
|
126
|
-
value={Math.round(mediaStart * 100)}
|
|
127
|
-
min={0}
|
|
128
|
-
max={mediaStartMax * 100}
|
|
129
|
-
step={10}
|
|
130
|
-
displayValue={formatTimingValue(mediaStart)}
|
|
131
|
-
formatDisplayValue={(next) => formatTimingValue(next / 100)}
|
|
132
|
-
onCommit={(next) => {
|
|
133
|
-
void onSetAttribute("media-start", (next / 100).toFixed(2));
|
|
134
|
-
}}
|
|
135
|
-
/>
|
|
136
|
-
</div>
|
|
137
|
-
|
|
138
|
-
<div className={RESPONSIVE_GRID}>
|
|
139
|
-
<div className="grid min-w-0 gap-1.5">
|
|
140
|
-
<span className={LABEL}>Loop</span>
|
|
141
|
-
<SegmentedControl
|
|
142
|
-
value={hasLoop ? "on" : "off"}
|
|
143
|
-
onChange={(next) => {
|
|
144
|
-
void onSetHtmlAttribute("loop", next === "on" ? "true" : null);
|
|
145
|
-
}}
|
|
146
|
-
options={[
|
|
147
|
-
{ label: "On", value: "on" },
|
|
148
|
-
{ label: "Off", value: "off" },
|
|
149
|
-
]}
|
|
150
|
-
/>
|
|
151
|
-
</div>
|
|
152
|
-
<div className="grid min-w-0 gap-1.5">
|
|
153
|
-
<span className={LABEL}>Muted</span>
|
|
154
|
-
<SegmentedControl
|
|
155
|
-
value={hasMuted ? "on" : "off"}
|
|
156
|
-
onChange={(next) => {
|
|
157
|
-
void onSetHtmlAttribute("muted", next === "on" ? "true" : null);
|
|
158
|
-
}}
|
|
159
|
-
options={[
|
|
160
|
-
{ label: "On", value: "on" },
|
|
161
|
-
{ label: "Off", value: "off" },
|
|
162
|
-
]}
|
|
163
|
-
/>
|
|
164
|
-
</div>
|
|
165
|
-
</div>
|
|
166
|
-
|
|
167
|
-
{isVideo && (
|
|
168
|
-
<div className="grid min-w-0 gap-1.5">
|
|
169
|
-
<span className={LABEL}>Has audio track</span>
|
|
170
|
-
<SegmentedControl
|
|
171
|
-
value={hasAudio ? "yes" : "no"}
|
|
172
|
-
onChange={(next) => {
|
|
173
|
-
if (next === "yes") {
|
|
174
|
-
void onSetAttribute("has-audio", "true");
|
|
175
|
-
void onSetHtmlAttribute("muted", null);
|
|
176
|
-
} else {
|
|
177
|
-
void onSetAttribute("has-audio", "");
|
|
178
|
-
void onSetHtmlAttribute("muted", "true");
|
|
158
|
+
{isVisualMedia && (
|
|
159
|
+
<div className="grid min-w-0 max-w-full gap-2 overflow-hidden rounded-md bg-panel-input/30 p-2">
|
|
160
|
+
<div className="flex min-w-0 items-center justify-between gap-2">
|
|
161
|
+
<div className="min-w-0">
|
|
162
|
+
<div className={LABEL}>Cutout</div>
|
|
163
|
+
<div className="mt-0.5 truncate text-[10px] text-panel-text-4">
|
|
164
|
+
Create transparent {isVideo ? "WebM video" : "PNG image"}
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
<button
|
|
168
|
+
type="button"
|
|
169
|
+
disabled={!canRemoveBackground || removeBusy}
|
|
170
|
+
onClick={(event) => {
|
|
171
|
+
event.stopPropagation();
|
|
172
|
+
void runBackgroundRemoval();
|
|
173
|
+
}}
|
|
174
|
+
className="flex h-8 flex-shrink-0 items-center gap-1.5 rounded-md bg-panel-input px-2.5 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"
|
|
175
|
+
title={
|
|
176
|
+
canRemoveBackground
|
|
177
|
+
? "Remove background and save a transparent asset"
|
|
178
|
+
: "Select a project-local image or video asset"
|
|
179
179
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
{
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
180
|
+
>
|
|
181
|
+
<Scissors size={13} />
|
|
182
|
+
<span>{removeBusy ? "Working" : "Remove BG"}</span>
|
|
183
|
+
</button>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<div className="grid grid-cols-2 gap-2">
|
|
187
|
+
<SelectField
|
|
188
|
+
label="Quality"
|
|
189
|
+
value={quality}
|
|
190
|
+
onChange={(next) => setQuality(next as typeof quality)}
|
|
191
|
+
options={["fast", "balanced", "best"]}
|
|
192
|
+
/>
|
|
193
|
+
{isVideo ? (
|
|
194
|
+
<div className="grid min-w-0 gap-1.5">
|
|
195
|
+
<span className={LABEL}>BG plate</span>
|
|
196
|
+
<SegmentedControl
|
|
197
|
+
value={createPlate ? "on" : "off"}
|
|
198
|
+
onChange={(next) => setCreatePlate(next === "on")}
|
|
199
|
+
options={[
|
|
200
|
+
{ label: "On", value: "on" },
|
|
201
|
+
{ label: "Off", value: "off" },
|
|
202
|
+
]}
|
|
203
|
+
/>
|
|
204
|
+
<span className="text-[10px] leading-tight text-panel-text-4">
|
|
205
|
+
Optional hole-cut background copy.
|
|
206
|
+
</span>
|
|
207
|
+
</div>
|
|
208
|
+
) : (
|
|
209
|
+
<div />
|
|
210
|
+
)}
|
|
211
|
+
</div>
|
|
212
|
+
|
|
213
|
+
{removeProgress && (
|
|
214
|
+
<div className="space-y-1">
|
|
215
|
+
<div className="flex min-w-0 items-center justify-between gap-2 text-[10px] text-panel-text-4">
|
|
216
|
+
<span className="min-w-0 flex-1 truncate">
|
|
217
|
+
{removeProgress.error ?? removeProgress.stage ?? "Processing"}
|
|
218
|
+
</span>
|
|
219
|
+
<span>{Math.round(removeProgress.progress)}%</span>
|
|
220
|
+
</div>
|
|
221
|
+
<div className="h-1 overflow-hidden rounded-full bg-panel-border">
|
|
222
|
+
<div
|
|
223
|
+
className={`h-full rounded-full ${
|
|
224
|
+
removeProgress.status === "failed" ? "bg-red-400" : "bg-studio-accent"
|
|
225
|
+
}`}
|
|
226
|
+
style={{ width: `${Math.max(0, Math.min(100, removeProgress.progress))}%` }}
|
|
227
|
+
/>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
)}
|
|
231
|
+
|
|
232
|
+
{removeProgress?.status === "complete" && removeProgress.outputPath && (
|
|
233
|
+
<div
|
|
234
|
+
className="truncate text-[10px] font-medium text-panel-text-3"
|
|
235
|
+
title={removeProgress.outputPath}
|
|
236
|
+
>
|
|
237
|
+
Applied {removeProgress.outputPath}
|
|
238
|
+
</div>
|
|
239
|
+
)}
|
|
186
240
|
</div>
|
|
187
241
|
)}
|
|
188
242
|
|
|
189
|
-
{isVideo && (
|
|
243
|
+
{(isVideo || isAudio) && (
|
|
244
|
+
<>
|
|
245
|
+
<div className="grid min-w-0 gap-1.5">
|
|
246
|
+
<span className={LABEL}>Volume</span>
|
|
247
|
+
<SliderControl
|
|
248
|
+
value={volumePercent}
|
|
249
|
+
min={0}
|
|
250
|
+
max={100}
|
|
251
|
+
step={1}
|
|
252
|
+
displayValue={`${volumePercent}%`}
|
|
253
|
+
formatDisplayValue={(next) => `${Math.round(next)}%`}
|
|
254
|
+
onCommit={(next) => {
|
|
255
|
+
void onSetAttribute("volume", formatNumericValue(next / 100));
|
|
256
|
+
}}
|
|
257
|
+
/>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
<div className="grid min-w-0 gap-1.5">
|
|
261
|
+
<span className={LABEL}>Playback rate</span>
|
|
262
|
+
<SliderControl
|
|
263
|
+
value={playbackRate * 100}
|
|
264
|
+
min={25}
|
|
265
|
+
max={300}
|
|
266
|
+
step={5}
|
|
267
|
+
displayValue={`${formatNumericValue(playbackRate)}x`}
|
|
268
|
+
formatDisplayValue={(next) => `${formatNumericValue(next / 100)}x`}
|
|
269
|
+
onCommit={(next) => {
|
|
270
|
+
void onSetAttribute("playback-rate", formatNumericValue(next / 100));
|
|
271
|
+
}}
|
|
272
|
+
/>
|
|
273
|
+
</div>
|
|
274
|
+
|
|
275
|
+
<div className="grid min-w-0 gap-1.5">
|
|
276
|
+
<span className={LABEL}>Media start</span>
|
|
277
|
+
<SliderControl
|
|
278
|
+
value={Math.round(mediaStart * 100)}
|
|
279
|
+
min={0}
|
|
280
|
+
max={mediaStartMax * 100}
|
|
281
|
+
step={10}
|
|
282
|
+
displayValue={formatTimingValue(mediaStart)}
|
|
283
|
+
formatDisplayValue={(next) => formatTimingValue(next / 100)}
|
|
284
|
+
onCommit={(next) => {
|
|
285
|
+
void onSetAttribute("media-start", (next / 100).toFixed(2));
|
|
286
|
+
}}
|
|
287
|
+
/>
|
|
288
|
+
</div>
|
|
289
|
+
|
|
290
|
+
<div className={RESPONSIVE_GRID}>
|
|
291
|
+
<div className="grid min-w-0 gap-1.5">
|
|
292
|
+
<span className={LABEL}>Loop</span>
|
|
293
|
+
<SegmentedControl
|
|
294
|
+
value={hasLoop ? "on" : "off"}
|
|
295
|
+
onChange={(next) => {
|
|
296
|
+
void onSetHtmlAttribute("loop", next === "on" ? "true" : null);
|
|
297
|
+
}}
|
|
298
|
+
options={[
|
|
299
|
+
{ label: "On", value: "on" },
|
|
300
|
+
{ label: "Off", value: "off" },
|
|
301
|
+
]}
|
|
302
|
+
/>
|
|
303
|
+
</div>
|
|
304
|
+
<div className="grid min-w-0 gap-1.5">
|
|
305
|
+
<span className={LABEL}>Muted</span>
|
|
306
|
+
<SegmentedControl
|
|
307
|
+
value={hasMuted ? "on" : "off"}
|
|
308
|
+
onChange={(next) => {
|
|
309
|
+
void onSetHtmlAttribute("muted", next === "on" ? "true" : null);
|
|
310
|
+
}}
|
|
311
|
+
options={[
|
|
312
|
+
{ label: "On", value: "on" },
|
|
313
|
+
{ label: "Off", value: "off" },
|
|
314
|
+
]}
|
|
315
|
+
/>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
|
|
319
|
+
{isVideo && (
|
|
320
|
+
<div className="grid min-w-0 gap-1.5">
|
|
321
|
+
<span className={LABEL}>Has audio track</span>
|
|
322
|
+
<SegmentedControl
|
|
323
|
+
value={hasAudio ? "yes" : "no"}
|
|
324
|
+
onChange={(next) => {
|
|
325
|
+
if (next === "yes") {
|
|
326
|
+
void onSetAttribute("has-audio", "true");
|
|
327
|
+
void onSetHtmlAttribute("muted", null);
|
|
328
|
+
} else {
|
|
329
|
+
void onSetAttribute("has-audio", "");
|
|
330
|
+
void onSetHtmlAttribute("muted", "true");
|
|
331
|
+
}
|
|
332
|
+
}}
|
|
333
|
+
options={[
|
|
334
|
+
{ label: "Yes", value: "yes" },
|
|
335
|
+
{ label: "No", value: "no" },
|
|
336
|
+
]}
|
|
337
|
+
/>
|
|
338
|
+
</div>
|
|
339
|
+
)}
|
|
340
|
+
</>
|
|
341
|
+
)}
|
|
342
|
+
|
|
343
|
+
{isVisualMedia && (
|
|
190
344
|
<>
|
|
191
345
|
<div className={RESPONSIVE_GRID}>
|
|
192
346
|
<SelectField
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { RefObject } from "react";
|
|
2
|
+
import type { ArcPathSegment, GsapAnimation } from "@hyperframes/parsers/gsap-parser";
|
|
3
|
+
import type { DomEditSelection } from "./domEditing";
|
|
4
|
+
import type { ImportedFontAsset } from "./fontAssets";
|
|
5
|
+
|
|
6
|
+
export interface BackgroundRemovalProgress {
|
|
7
|
+
status: "processing" | "complete" | "failed";
|
|
8
|
+
progress: number;
|
|
9
|
+
stage?: string;
|
|
10
|
+
outputPath?: string;
|
|
11
|
+
backgroundOutputPath?: string;
|
|
12
|
+
error?: string;
|
|
13
|
+
provider?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface BackgroundRemovalResult {
|
|
17
|
+
outputPath: string;
|
|
18
|
+
backgroundOutputPath?: string;
|
|
19
|
+
provider?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface PropertyPanelProps {
|
|
23
|
+
projectId: string;
|
|
24
|
+
projectDir: string | null;
|
|
25
|
+
assets: string[];
|
|
26
|
+
element: DomEditSelection | null;
|
|
27
|
+
multiSelectCount?: number;
|
|
28
|
+
copiedAgentPrompt: boolean;
|
|
29
|
+
onClearSelection: () => void;
|
|
30
|
+
onUngroup?: () => void;
|
|
31
|
+
onSetStyle: (prop: string, value: string) => void | Promise<void>;
|
|
32
|
+
onSetAttribute: (attr: string, value: string) => void | Promise<void>;
|
|
33
|
+
onSetAttributeLive: (attr: string, value: string | null) => void | Promise<void>;
|
|
34
|
+
onApplyColorGradingScope?: (
|
|
35
|
+
scope: "source-file" | "project",
|
|
36
|
+
value: string | null,
|
|
37
|
+
) => Promise<{ changedFiles: number; changedElements: number }>;
|
|
38
|
+
onSetHtmlAttribute: (attr: string, value: string | null) => void | Promise<void>;
|
|
39
|
+
onRemoveBackground?: (
|
|
40
|
+
inputPath: string,
|
|
41
|
+
options: {
|
|
42
|
+
createBackgroundPlate?: boolean;
|
|
43
|
+
quality?: "fast" | "balanced" | "best";
|
|
44
|
+
onProgress?: (progress: BackgroundRemovalProgress) => void;
|
|
45
|
+
},
|
|
46
|
+
) => Promise<BackgroundRemovalResult>;
|
|
47
|
+
onSetManualOffset: (element: DomEditSelection, next: { x: number; y: number }) => void;
|
|
48
|
+
onSetManualSize: (element: DomEditSelection, next: { width: number; height: number }) => void;
|
|
49
|
+
onSetManualRotation: (element: DomEditSelection, next: { angle: number }) => void;
|
|
50
|
+
onSetText: (value: string, fieldKey?: string) => void;
|
|
51
|
+
onSetTextFieldStyle: (fieldKey: string, property: string, value: string) => void;
|
|
52
|
+
onAddTextField: (afterFieldKey?: string) => string | Promise<string | null> | null;
|
|
53
|
+
onRemoveTextField: (fieldKey: string) => void;
|
|
54
|
+
onAskAgent: () => void;
|
|
55
|
+
onImportAssets?: (files: FileList, dir?: string) => Promise<string[]>;
|
|
56
|
+
fontAssets?: ImportedFontAsset[];
|
|
57
|
+
onImportFonts?: (files: FileList | File[]) => Promise<ImportedFontAsset[]>;
|
|
58
|
+
previewIframeRef?: RefObject<HTMLIFrameElement | null>;
|
|
59
|
+
gsapAnimations?: GsapAnimation[];
|
|
60
|
+
gsapMultipleTimelines?: boolean;
|
|
61
|
+
gsapUnsupportedTimelinePattern?: boolean;
|
|
62
|
+
onUpdateGsapProperty?: (animId: string, prop: string, value: number | string) => void;
|
|
63
|
+
onUpdateGsapMeta?: (
|
|
64
|
+
animId: string,
|
|
65
|
+
updates: { duration?: number; ease?: string; position?: number },
|
|
66
|
+
) => void;
|
|
67
|
+
onDeleteGsapAnimation?: (animId: string) => void;
|
|
68
|
+
onAddGsapProperty?: (animId: string, prop: string) => void;
|
|
69
|
+
onRemoveGsapProperty?: (animId: string, prop: string) => void;
|
|
70
|
+
onUpdateGsapFromProperty?: (animId: string, prop: string, value: number | string) => void;
|
|
71
|
+
onAddGsapFromProperty?: (animId: string, prop: string) => void;
|
|
72
|
+
onRemoveGsapFromProperty?: (animId: string, prop: string) => void;
|
|
73
|
+
onAddGsapAnimation?: (method: "to" | "from" | "set" | "fromTo") => void;
|
|
74
|
+
onSetArcPath?: (
|
|
75
|
+
animId: string,
|
|
76
|
+
config: {
|
|
77
|
+
enabled: boolean;
|
|
78
|
+
autoRotate?: boolean | number;
|
|
79
|
+
segments?: ArcPathSegment[];
|
|
80
|
+
},
|
|
81
|
+
) => void;
|
|
82
|
+
onUpdateArcSegment?: (
|
|
83
|
+
animId: string,
|
|
84
|
+
segmentIndex: number,
|
|
85
|
+
update: Partial<ArcPathSegment>,
|
|
86
|
+
) => void;
|
|
87
|
+
onUnroll?: (animationId: string) => void;
|
|
88
|
+
onAddKeyframe?: (
|
|
89
|
+
animationId: string,
|
|
90
|
+
percentage: number,
|
|
91
|
+
property: string,
|
|
92
|
+
value: number | string,
|
|
93
|
+
) => void;
|
|
94
|
+
onRemoveKeyframe?: (animationId: string, percentage: number) => void;
|
|
95
|
+
onUpdateKeyframeEase?: (animationId: string, percentage: number, ease: string) => void;
|
|
96
|
+
onSetAllKeyframeEases?: (animationId: string, ease: string) => void;
|
|
97
|
+
onConvertToKeyframes?: (animationId: string, duration?: number) => void;
|
|
98
|
+
onCommitAnimatedProperty?: (
|
|
99
|
+
selection: DomEditSelection,
|
|
100
|
+
property: string,
|
|
101
|
+
value: number | string,
|
|
102
|
+
) => Promise<void>;
|
|
103
|
+
onCommitAnimatedProperties?: (
|
|
104
|
+
selection: DomEditSelection,
|
|
105
|
+
props: Record<string, number | string>,
|
|
106
|
+
) => Promise<void>;
|
|
107
|
+
onSeekToTime?: (time: number) => void;
|
|
108
|
+
recordingState?: "idle" | "recording" | "preview";
|
|
109
|
+
recordingDuration?: number;
|
|
110
|
+
onToggleRecording?: () => void;
|
|
111
|
+
}
|
|
@@ -33,8 +33,9 @@ export function CompositionBreadcrumb({ stack, onNavigate }: CompositionBreadcru
|
|
|
33
33
|
});
|
|
34
34
|
onNavigate(stack.length - 2);
|
|
35
35
|
}}
|
|
36
|
-
className="flex items-center gap-1 px-1.5 py-0.5 rounded text-xs text-neutral-400 hover:text-white hover:bg-neutral-800 transition-colors"
|
|
37
|
-
title="Back (Esc)"
|
|
36
|
+
className="flex items-center gap-1 px-1.5 py-0.5 rounded text-xs text-neutral-400 hover:text-white hover:bg-neutral-800 active:scale-[0.98] transition-colors"
|
|
37
|
+
title="Back (Esc, or double-click empty timeline)"
|
|
38
|
+
aria-label="Back to parent composition"
|
|
38
39
|
>
|
|
39
40
|
<ArrowLeft size={12} weight="bold" />
|
|
40
41
|
</button>
|