@hyperframes/studio 0.7.71 → 0.7.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{hyperframes-player-C1X90psg.js → hyperframes-player-DGlzhf_s.js} +1 -1
- package/dist/assets/index-B37rWXo4.css +1 -0
- package/dist/assets/{index-DzxDHPR1.js → index-CbVTOJ-E.js} +1 -1
- package/dist/assets/{index-nY4lLBqM.js → index-CcA5WVJv.js} +1 -1
- package/dist/assets/index-DXcLI2wu.js +428 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.html +2 -2
- package/dist/index.js +7762 -4261
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/App.tsx +4 -4
- package/src/components/StudioLeftSidebar.tsx +2 -3
- package/src/components/StudioRightPanel.tsx +10 -4
- package/src/components/editor/AnimationCard.test.tsx +100 -0
- package/src/components/editor/AnimationCard.tsx +5 -2
- package/src/components/editor/EaseCurveSection.test.tsx +348 -0
- package/src/components/editor/EaseCurveSection.tsx +444 -237
- package/src/components/editor/EaseParamFields.test.tsx +188 -0
- package/src/components/editor/EaseParamFields.tsx +246 -0
- package/src/components/editor/PropertyPanel.test.tsx +9 -7
- package/src/components/editor/PropertyPanelFlat.tsx +75 -88
- package/src/components/editor/colorGradingFrameAnalysis.test.ts +101 -0
- package/src/components/editor/colorGradingFrameAnalysis.ts +203 -0
- package/src/components/editor/colorGradingScopePatch.test.ts +3 -3
- package/src/components/editor/easeCurveSvg.tsx +65 -0
- package/src/components/editor/easePresetLibrary.test.ts +114 -0
- package/src/components/editor/easePresetLibrary.ts +49 -0
- package/src/components/editor/gsapAnimationConstants.ts +15 -13
- package/src/components/editor/propertyPanelColorCurveGraph.tsx +549 -0
- package/src/components/editor/propertyPanelColorCurves.test.tsx +158 -0
- package/src/components/editor/propertyPanelColorCurves.tsx +185 -0
- package/src/components/editor/propertyPanelColorGradingControls.tsx +115 -77
- package/src/components/editor/propertyPanelColorScopes.tsx +258 -0
- package/src/components/editor/propertyPanelColorSecondary.test.tsx +181 -0
- package/src/components/editor/propertyPanelColorSecondary.tsx +448 -0
- package/src/components/editor/propertyPanelColorWheels.test.tsx +119 -0
- package/src/components/editor/propertyPanelColorWheels.tsx +334 -0
- package/src/components/editor/propertyPanelFlatColorGradingAccessory.tsx +109 -0
- package/src/components/editor/propertyPanelFlatColorGradingSection.test.tsx +239 -41
- package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +260 -300
- package/src/components/editor/propertyPanelFlatEffectControl.tsx +96 -0
- package/src/components/editor/propertyPanelFlatEffectSpecs.ts +305 -0
- package/src/components/editor/propertyPanelFlatEffectsSection.test.tsx +331 -0
- package/src/components/editor/propertyPanelFlatEffectsSection.tsx +503 -0
- package/src/components/editor/propertyPanelFlatOverlaysSection.test.tsx +113 -0
- package/src/components/editor/propertyPanelFlatOverlaysSection.tsx +108 -0
- package/src/components/editor/propertyPanelFlatPrimitives.tsx +2 -0
- package/src/components/editor/propertyPanelGradingNumberField.test.tsx +79 -0
- package/src/components/editor/propertyPanelGradingNumberField.tsx +116 -0
- package/src/components/editor/propertyPanelPresetPreview.ts +36 -0
- package/src/components/editor/propertyPanelTypes.ts +13 -0
- package/src/components/editor/useColorGradingController.test.ts +331 -10
- package/src/components/editor/useColorGradingController.ts +57 -90
- package/src/components/editor/useColorGradingPreviews.ts +350 -0
- package/src/components/editor/useColorGradingScopes.test.tsx +143 -0
- package/src/components/editor/useColorGradingScopes.ts +62 -0
- package/src/components/editor/useInspectorGestureTransaction.ts +30 -1
- package/src/components/nle/NLEContext.tsx +1 -1
- package/src/contexts/PanelLayoutContext.tsx +3 -6
- package/src/hooks/useBlockCatalog.ts +31 -13
- package/src/hooks/useBlockHandlers.ts +22 -0
- package/src/hooks/useGsapScriptCommits.test.tsx +15 -0
- package/src/hooks/useGsapScriptCommits.ts +14 -1
- package/src/hooks/usePanelLayout.test.ts +68 -1
- package/src/hooks/usePanelLayout.ts +72 -34
- package/src/icons/SystemIcons.tsx +4 -0
- package/src/player/components/PlayerControls.tsx +68 -229
- package/src/player/components/ShortcutsPanel.tsx +2 -4
- package/src/player/components/SpeedMenu.tsx +1 -2
- package/src/telemetry/events.test.ts +9 -0
- package/src/telemetry/events.ts +5 -0
- package/src/utils/blockInstaller.test.ts +67 -0
- package/src/utils/blockInstaller.ts +162 -109
- package/src/utils/studioUiPreferences.test.ts +5 -0
- package/src/utils/studioUiPreferences.ts +8 -0
- package/dist/assets/index-CSCh2Vrp.js +0 -428
- package/dist/assets/index-Ceyz8Qt2.css +0 -1
- package/src/player/components/PlayerControls.test.ts +0 -20
- package/src/player/components/useSeekBarDrag.ts +0 -169
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import type { RegistryItem } from "@hyperframes/core/registry";
|
|
3
|
+
import { useBlockCatalog } from "../../hooks/useBlockCatalog";
|
|
4
|
+
import { Film, Plus } from "../../icons/SystemIcons";
|
|
5
|
+
import { useTrackDesignInput } from "../../contexts/DesignPanelInputContext";
|
|
6
|
+
import type { DomEditSelection } from "./domEditing";
|
|
7
|
+
import { FLAT_PREVIEW_GRID } from "./propertyPanelFlatPrimitives";
|
|
8
|
+
import type { ElementTiming } from "./propertyPanelFlatTimingDerivation";
|
|
9
|
+
|
|
10
|
+
export const MEDIA_TREATMENT_OVERLAY_TAG = "media-treatment-overlay";
|
|
11
|
+
|
|
12
|
+
export function filterMediaTreatmentOverlays(items: readonly RegistryItem[]): RegistryItem[] {
|
|
13
|
+
return items.filter(
|
|
14
|
+
(item) =>
|
|
15
|
+
item.type === "hyperframes:block" &&
|
|
16
|
+
item.tags?.includes(MEDIA_TREATMENT_OVERLAY_TAG) === true,
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function deriveMediaOverlayPlacement(
|
|
21
|
+
element: Pick<DomEditSelection, "dataAttributes" | "sourceFile">,
|
|
22
|
+
timing: Pick<ElementTiming, "start" | "duration">,
|
|
23
|
+
) {
|
|
24
|
+
const authoredTrack = Number.parseInt(element.dataAttributes["track-index"] ?? "", 10);
|
|
25
|
+
return {
|
|
26
|
+
start: timing.start,
|
|
27
|
+
...(timing.duration > 0 ? { duration: timing.duration } : {}),
|
|
28
|
+
...(Number.isFinite(authoredTrack) ? { track: authoredTrack + 1 } : {}),
|
|
29
|
+
compositionPath: element.sourceFile,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function FlatOverlaysSection({
|
|
34
|
+
onAddOverlay,
|
|
35
|
+
}: {
|
|
36
|
+
onAddOverlay: (name: string) => Promise<void>;
|
|
37
|
+
}) {
|
|
38
|
+
const track = useTrackDesignInput();
|
|
39
|
+
const { blocks, loading, error } = useBlockCatalog();
|
|
40
|
+
const overlays = filterMediaTreatmentOverlays(blocks);
|
|
41
|
+
const [adding, setAdding] = useState<string | null>(null);
|
|
42
|
+
const [previewing, setPreviewing] = useState<string | null>(null);
|
|
43
|
+
|
|
44
|
+
if (loading) {
|
|
45
|
+
return <div className="py-4 text-center text-[10px] text-panel-text-4">Loading overlays…</div>;
|
|
46
|
+
}
|
|
47
|
+
if (error) {
|
|
48
|
+
return <div className="py-4 text-center text-[10px] text-red-300">{error}</div>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const busy = adding !== null;
|
|
52
|
+
return (
|
|
53
|
+
<div data-flat-overlays="true" className={FLAT_PREVIEW_GRID}>
|
|
54
|
+
{overlays.map((overlay) => (
|
|
55
|
+
<button
|
|
56
|
+
key={overlay.name}
|
|
57
|
+
type="button"
|
|
58
|
+
data-flat-overlay={overlay.name}
|
|
59
|
+
aria-label={`Add ${overlay.title}`}
|
|
60
|
+
disabled={busy}
|
|
61
|
+
title={overlay.description}
|
|
62
|
+
onPointerEnter={() => setPreviewing(overlay.name)}
|
|
63
|
+
onPointerLeave={() => setPreviewing(null)}
|
|
64
|
+
onFocus={() => setPreviewing(overlay.name)}
|
|
65
|
+
onBlur={() => setPreviewing(null)}
|
|
66
|
+
onClick={() => {
|
|
67
|
+
track("button", `Add ${overlay.title}`);
|
|
68
|
+
setAdding(overlay.name);
|
|
69
|
+
void onAddOverlay(overlay.name).finally(() => setAdding(null));
|
|
70
|
+
}}
|
|
71
|
+
className="group min-w-0 overflow-hidden border border-panel-hairline bg-panel-bg-soft text-left transition-colors hover:border-panel-border-input hover:bg-panel-bg disabled:cursor-wait disabled:opacity-50"
|
|
72
|
+
>
|
|
73
|
+
<span className="relative flex aspect-video w-full items-center justify-center overflow-hidden bg-black/20">
|
|
74
|
+
{previewing === overlay.name && overlay.preview?.video ? (
|
|
75
|
+
<video
|
|
76
|
+
src={overlay.preview.video}
|
|
77
|
+
poster={overlay.preview.poster}
|
|
78
|
+
autoPlay
|
|
79
|
+
muted
|
|
80
|
+
loop
|
|
81
|
+
playsInline
|
|
82
|
+
className="block h-full w-full object-cover"
|
|
83
|
+
/>
|
|
84
|
+
) : overlay.preview?.poster ? (
|
|
85
|
+
<img
|
|
86
|
+
data-flat-overlay-preview={overlay.name}
|
|
87
|
+
src={overlay.preview.poster}
|
|
88
|
+
alt=""
|
|
89
|
+
draggable={false}
|
|
90
|
+
loading="lazy"
|
|
91
|
+
className="block h-full w-full object-cover"
|
|
92
|
+
/>
|
|
93
|
+
) : (
|
|
94
|
+
<Film size={15} className="text-panel-text-4" />
|
|
95
|
+
)}
|
|
96
|
+
<Plus
|
|
97
|
+
size={12}
|
|
98
|
+
className="absolute right-1.5 top-1.5 text-white opacity-70 drop-shadow group-hover:text-panel-accent group-hover:opacity-100"
|
|
99
|
+
/>
|
|
100
|
+
</span>
|
|
101
|
+
<span className="block truncate px-2 py-1.5 text-[10px] text-panel-text-2">
|
|
102
|
+
{adding === overlay.name ? "Adding…" : overlay.title}
|
|
103
|
+
</span>
|
|
104
|
+
</button>
|
|
105
|
+
))}
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
type PropertyValueTier,
|
|
9
9
|
} from "./propertyPanelValueTier";
|
|
10
10
|
|
|
11
|
+
export const FLAT_PREVIEW_GRID = "grid grid-cols-[repeat(auto-fill,minmax(120px,1fr))] gap-1";
|
|
12
|
+
|
|
11
13
|
/* ------------------------------------------------------------------ */
|
|
12
14
|
/* FlatRow — single-column label/value property row */
|
|
13
15
|
/* ------------------------------------------------------------------ */
|
|
@@ -0,0 +1,79 @@
|
|
|
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 { GradingNumberField } from "./propertyPanelGradingNumberField";
|
|
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
|
+
function changeInput(input: HTMLInputElement, value: string) {
|
|
15
|
+
const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set;
|
|
16
|
+
if (!setter) throw new Error("Expected the native input setter");
|
|
17
|
+
setter.call(input, value);
|
|
18
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function renderField() {
|
|
22
|
+
const callbacks = {
|
|
23
|
+
onBegin: vi.fn(),
|
|
24
|
+
onPreview: vi.fn(),
|
|
25
|
+
onSettle: vi.fn(),
|
|
26
|
+
onCancel: vi.fn(),
|
|
27
|
+
};
|
|
28
|
+
const host = document.body.appendChild(document.createElement("div"));
|
|
29
|
+
const root = createRoot(host);
|
|
30
|
+
act(() =>
|
|
31
|
+
root.render(
|
|
32
|
+
<GradingNumberField label="Level" value={10} min={-100} max={100} {...callbacks} />,
|
|
33
|
+
),
|
|
34
|
+
);
|
|
35
|
+
const input = host.querySelector("input");
|
|
36
|
+
if (!input) throw new Error("Expected a grading field");
|
|
37
|
+
return { root, input, callbacks };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
describe("GradingNumberField", () => {
|
|
41
|
+
it("does not begin or settle an untouched focus and blur", () => {
|
|
42
|
+
const { root, input, callbacks } = renderField();
|
|
43
|
+
act(() => input.focus());
|
|
44
|
+
act(() => input.blur());
|
|
45
|
+
|
|
46
|
+
expect(callbacks.onBegin).not.toHaveBeenCalled();
|
|
47
|
+
expect(callbacks.onPreview).not.toHaveBeenCalled();
|
|
48
|
+
expect(callbacks.onSettle).not.toHaveBeenCalled();
|
|
49
|
+
expect(callbacks.onCancel).not.toHaveBeenCalled();
|
|
50
|
+
act(() => root.unmount());
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("cancels a real edit that returns to its baseline", () => {
|
|
54
|
+
const { root, input, callbacks } = renderField();
|
|
55
|
+
act(() => input.focus());
|
|
56
|
+
act(() => changeInput(input, "25"));
|
|
57
|
+
act(() => changeInput(input, "10"));
|
|
58
|
+
act(() => input.blur());
|
|
59
|
+
|
|
60
|
+
expect(callbacks.onBegin).toHaveBeenCalledOnce();
|
|
61
|
+
expect(callbacks.onSettle).not.toHaveBeenCalled();
|
|
62
|
+
expect(callbacks.onCancel).toHaveBeenCalledOnce();
|
|
63
|
+
act(() => root.unmount());
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("previews valid text and settles once on Enter", () => {
|
|
67
|
+
const { root, input, callbacks } = renderField();
|
|
68
|
+
act(() => input.focus());
|
|
69
|
+
act(() => changeInput(input, "-25"));
|
|
70
|
+
act(() => {
|
|
71
|
+
input.dispatchEvent(new KeyboardEvent("keydown", { bubbles: true, key: "Enter" }));
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expect(callbacks.onBegin).toHaveBeenCalledOnce();
|
|
75
|
+
expect(callbacks.onPreview).toHaveBeenLastCalledWith(-25);
|
|
76
|
+
expect(callbacks.onSettle).toHaveBeenCalledOnce();
|
|
77
|
+
act(() => root.unmount());
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { clampNumber } from "../../utils/studioHelpers";
|
|
3
|
+
|
|
4
|
+
export function GradingNumberField({
|
|
5
|
+
label,
|
|
6
|
+
ariaLabel = label,
|
|
7
|
+
value,
|
|
8
|
+
min,
|
|
9
|
+
max,
|
|
10
|
+
disabled,
|
|
11
|
+
formatValue = String,
|
|
12
|
+
labelClassName = "min-w-0",
|
|
13
|
+
labelTextClassName = "block",
|
|
14
|
+
inputClassName = "w-full",
|
|
15
|
+
onBegin,
|
|
16
|
+
onPreview,
|
|
17
|
+
onSettle,
|
|
18
|
+
onCancel,
|
|
19
|
+
}: {
|
|
20
|
+
label: string;
|
|
21
|
+
ariaLabel?: string;
|
|
22
|
+
value: number;
|
|
23
|
+
min: number;
|
|
24
|
+
max: number;
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
formatValue?: (value: number) => string;
|
|
27
|
+
labelClassName?: string;
|
|
28
|
+
labelTextClassName?: string;
|
|
29
|
+
inputClassName?: string;
|
|
30
|
+
onBegin: () => void;
|
|
31
|
+
onPreview: (value: number) => void;
|
|
32
|
+
onSettle: () => void;
|
|
33
|
+
onCancel: () => void;
|
|
34
|
+
}) {
|
|
35
|
+
const [draft, setDraft] = useState(() => formatValue(value));
|
|
36
|
+
const focusedRef = useRef(false);
|
|
37
|
+
const cancelBlurRef = useRef(false);
|
|
38
|
+
const baselineRef = useRef(value);
|
|
39
|
+
const dirtyRef = useRef(false);
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (!focusedRef.current) setDraft(formatValue(value));
|
|
43
|
+
}, [formatValue, value]);
|
|
44
|
+
|
|
45
|
+
const settle = () => {
|
|
46
|
+
focusedRef.current = false;
|
|
47
|
+
if (cancelBlurRef.current) {
|
|
48
|
+
cancelBlurRef.current = false;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const parsed = Number(draft);
|
|
52
|
+
if (draft.trim() === "" || !Number.isFinite(parsed)) {
|
|
53
|
+
setDraft(formatValue(value));
|
|
54
|
+
if (dirtyRef.current) onCancel();
|
|
55
|
+
dirtyRef.current = false;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const next = clampNumber(parsed, min, max);
|
|
59
|
+
setDraft(formatValue(next));
|
|
60
|
+
if (!dirtyRef.current || Object.is(next, baselineRef.current)) {
|
|
61
|
+
if (dirtyRef.current) onCancel();
|
|
62
|
+
dirtyRef.current = false;
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
onPreview(next);
|
|
66
|
+
onSettle();
|
|
67
|
+
dirtyRef.current = false;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<label className={labelClassName}>
|
|
72
|
+
<span className={labelTextClassName}>{label}</span>
|
|
73
|
+
<input
|
|
74
|
+
type="text"
|
|
75
|
+
inputMode="decimal"
|
|
76
|
+
aria-label={ariaLabel}
|
|
77
|
+
value={draft}
|
|
78
|
+
disabled={disabled}
|
|
79
|
+
onFocus={() => {
|
|
80
|
+
focusedRef.current = true;
|
|
81
|
+
baselineRef.current = value;
|
|
82
|
+
dirtyRef.current = false;
|
|
83
|
+
setDraft(formatValue(value));
|
|
84
|
+
}}
|
|
85
|
+
onChange={(event) => {
|
|
86
|
+
const next = event.target.value;
|
|
87
|
+
setDraft(next);
|
|
88
|
+
if (next.trim() === "") return;
|
|
89
|
+
const parsed = Number(next);
|
|
90
|
+
if (!Number.isFinite(parsed)) return;
|
|
91
|
+
const clamped = clampNumber(parsed, min, max);
|
|
92
|
+
if (!Object.is(clamped, baselineRef.current) && !dirtyRef.current) {
|
|
93
|
+
dirtyRef.current = true;
|
|
94
|
+
onBegin();
|
|
95
|
+
}
|
|
96
|
+
if (dirtyRef.current) onPreview(clamped);
|
|
97
|
+
}}
|
|
98
|
+
onBlur={settle}
|
|
99
|
+
onKeyDown={(event) => {
|
|
100
|
+
if (event.key === "Escape") {
|
|
101
|
+
event.preventDefault();
|
|
102
|
+
cancelBlurRef.current = true;
|
|
103
|
+
focusedRef.current = false;
|
|
104
|
+
setDraft(formatValue(value));
|
|
105
|
+
if (dirtyRef.current) onCancel();
|
|
106
|
+
dirtyRef.current = false;
|
|
107
|
+
event.currentTarget.blur();
|
|
108
|
+
} else if (event.key === "Enter") {
|
|
109
|
+
event.currentTarget.blur();
|
|
110
|
+
}
|
|
111
|
+
}}
|
|
112
|
+
className={`border-b border-panel-border-input/50 bg-transparent py-0.5 text-right font-mono text-[9px] text-panel-text-2 outline-none focus:border-panel-accent disabled:opacity-40 ${inputClassName}`}
|
|
113
|
+
/>
|
|
114
|
+
</label>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { NormalizedHfColorGrading } from "@hyperframes/core/color-grading";
|
|
2
|
+
import type { ColorGradingPreviewOptions } from "./useColorGradingController";
|
|
3
|
+
|
|
4
|
+
export function presetPreviewHandlers({
|
|
5
|
+
id,
|
|
6
|
+
label,
|
|
7
|
+
resolve,
|
|
8
|
+
onPreview,
|
|
9
|
+
onCommit,
|
|
10
|
+
onTrack,
|
|
11
|
+
}: {
|
|
12
|
+
id: string;
|
|
13
|
+
label: string;
|
|
14
|
+
resolve: () => NormalizedHfColorGrading;
|
|
15
|
+
onPreview: (
|
|
16
|
+
grading: NormalizedHfColorGrading | null,
|
|
17
|
+
options?: ColorGradingPreviewOptions,
|
|
18
|
+
) => void;
|
|
19
|
+
onCommit: (grading: NormalizedHfColorGrading) => void;
|
|
20
|
+
onTrack: (label: string) => void;
|
|
21
|
+
}) {
|
|
22
|
+
return {
|
|
23
|
+
title: `Preview ${label}`,
|
|
24
|
+
onPointerEnter: () =>
|
|
25
|
+
onPreview(resolve(), {
|
|
26
|
+
animatedPreview: { kind: "presets" as const, id },
|
|
27
|
+
}),
|
|
28
|
+
onPointerLeave: () => onPreview(null),
|
|
29
|
+
onFocus: () => onPreview(resolve()),
|
|
30
|
+
onBlur: () => onPreview(null),
|
|
31
|
+
onClick: () => {
|
|
32
|
+
onTrack(label);
|
|
33
|
+
onCommit(resolve());
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -19,6 +19,18 @@ export interface BackgroundRemovalResult {
|
|
|
19
19
|
provider?: string;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
export interface MediaOverlayPlacement {
|
|
23
|
+
start: number;
|
|
24
|
+
duration?: number;
|
|
25
|
+
track?: number;
|
|
26
|
+
compositionPath?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type AddMediaOverlayHandler = (
|
|
30
|
+
blockName: string,
|
|
31
|
+
placement: MediaOverlayPlacement,
|
|
32
|
+
) => Promise<void>;
|
|
33
|
+
|
|
22
34
|
export interface PropertyPanelProps {
|
|
23
35
|
projectId: string;
|
|
24
36
|
projectDir: string | null;
|
|
@@ -69,6 +81,7 @@ export interface PropertyPanelProps {
|
|
|
69
81
|
onAskAgent: () => void;
|
|
70
82
|
onToggleElementHidden?: (elementKey: string, hidden: boolean) => void | Promise<void>;
|
|
71
83
|
onImportAssets?: (files: FileList, dir?: string) => Promise<string[]>;
|
|
84
|
+
onAddMediaOverlay?: AddMediaOverlayHandler;
|
|
72
85
|
fontAssets?: ImportedFontAsset[];
|
|
73
86
|
onImportFonts?: (files: FileList | File[]) => Promise<ImportedFontAsset[]>;
|
|
74
87
|
previewIframeRef?: RefObject<HTMLIFrameElement | null>;
|