mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
refactor: new parameters modulerefactor: unify element params
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
"use client";
|
||||
|
||||
import { resolveAnimationPathValueAtTime } from "@/animation";
|
||||
import { Section, SectionContent, SectionFields } from "@/components/section";
|
||||
import { useElementPlayhead } from "@/components/editor/panels/properties/hooks/use-element-playhead";
|
||||
import { useKeyframedParamProperty } from "@/components/editor/panels/properties/hooks/use-keyframed-param-property";
|
||||
import { PropertyParamField } from "@/components/editor/panels/properties/components/property-param-field";
|
||||
import type { ParamValue, ParamValues } from "@/params";
|
||||
import {
|
||||
getElementParams,
|
||||
readElementParamValue,
|
||||
writeElementParamValue,
|
||||
type ElementParamDefinition,
|
||||
} from "@/params/registry";
|
||||
import type { TimelineElement } from "@/timeline";
|
||||
import type { MediaTime } from "@/wasm";
|
||||
|
||||
export function ElementParamsTab({
|
||||
element,
|
||||
trackId,
|
||||
paramKeys,
|
||||
sectionKey,
|
||||
}: {
|
||||
element: TimelineElement;
|
||||
trackId: string;
|
||||
paramKeys?: readonly string[];
|
||||
sectionKey: string;
|
||||
}) {
|
||||
const { localTime, isPlayheadWithinElementRange } = useElementPlayhead({
|
||||
startTime: element.startTime,
|
||||
duration: element.duration,
|
||||
});
|
||||
const params = getElementParams({ element }).filter(
|
||||
(param) => !paramKeys || paramKeys.includes(param.key),
|
||||
);
|
||||
const baseValues = buildValues({ element, params });
|
||||
|
||||
return (
|
||||
<Section sectionKey={`${element.id}:${sectionKey}`}>
|
||||
<SectionContent className="pt-4">
|
||||
<SectionFields>
|
||||
{params
|
||||
.filter((param) => isVisible({ param, values: baseValues }))
|
||||
.map((param) => (
|
||||
<ElementParamField
|
||||
key={param.key}
|
||||
element={element}
|
||||
trackId={trackId}
|
||||
param={param}
|
||||
baseValue={baseValues[param.key] ?? param.default}
|
||||
localTime={localTime}
|
||||
isPlayheadWithinElementRange={isPlayheadWithinElementRange}
|
||||
/>
|
||||
))}
|
||||
</SectionFields>
|
||||
</SectionContent>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
function ElementParamField({
|
||||
element,
|
||||
trackId,
|
||||
param,
|
||||
baseValue,
|
||||
localTime,
|
||||
isPlayheadWithinElementRange,
|
||||
}: {
|
||||
element: TimelineElement;
|
||||
trackId: string;
|
||||
param: ElementParamDefinition;
|
||||
baseValue: ParamValue;
|
||||
localTime: MediaTime;
|
||||
isPlayheadWithinElementRange: boolean;
|
||||
}) {
|
||||
const resolvedValue = resolveAnimationPathValueAtTime({
|
||||
animations: element.animations,
|
||||
propertyPath: param.key,
|
||||
localTime,
|
||||
fallbackValue: baseValue,
|
||||
});
|
||||
const animatedParam = useKeyframedParamProperty({
|
||||
param,
|
||||
trackId,
|
||||
elementId: element.id,
|
||||
animations: element.animations,
|
||||
propertyPath: param.key,
|
||||
localTime,
|
||||
isPlayheadWithinElementRange,
|
||||
resolvedValue,
|
||||
buildBaseUpdates: ({ value }) =>
|
||||
writeElementParamValue({ element, param, value }),
|
||||
});
|
||||
|
||||
return (
|
||||
<PropertyParamField
|
||||
param={param}
|
||||
value={resolvedValue}
|
||||
onPreview={animatedParam.onPreview}
|
||||
onCommit={animatedParam.onCommit}
|
||||
keyframe={
|
||||
param.keyframable === false
|
||||
? undefined
|
||||
: {
|
||||
isActive: animatedParam.isKeyframedAtTime,
|
||||
isDisabled: !isPlayheadWithinElementRange,
|
||||
onToggle: animatedParam.toggleKeyframe,
|
||||
}
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function buildValues({
|
||||
element,
|
||||
params,
|
||||
}: {
|
||||
element: TimelineElement;
|
||||
params: readonly ElementParamDefinition[];
|
||||
}): ParamValues {
|
||||
const values: ParamValues = {};
|
||||
for (const param of params) {
|
||||
const value = readElementParamValue({ element, param });
|
||||
if (value !== null) {
|
||||
values[param.key] = value;
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
function isVisible({
|
||||
param,
|
||||
values,
|
||||
}: {
|
||||
param: ElementParamDefinition;
|
||||
values: ParamValues;
|
||||
}): boolean {
|
||||
return (param.dependencies ?? []).every((dependency) =>
|
||||
areParamValuesEqual({
|
||||
left: values[dependency.param],
|
||||
right: dependency.equals,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function areParamValuesEqual({
|
||||
left,
|
||||
right,
|
||||
}: {
|
||||
left: ParamValue | undefined;
|
||||
right: ParamValue;
|
||||
}): boolean {
|
||||
return left === right;
|
||||
}
|
||||
+32
-6
@@ -1,6 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import type { ParamDefinition, NumberParamDefinition } from "@/params";
|
||||
import type {
|
||||
ParamDefinition,
|
||||
NumberParamDefinition,
|
||||
ParamValue,
|
||||
} from "@/params";
|
||||
import {
|
||||
formatNumberForDisplay,
|
||||
getFractionDigitsForStep,
|
||||
@@ -19,6 +23,7 @@ import {
|
||||
} from "@/components/ui/select";
|
||||
import { usePropertyDraft } from "../hooks/use-property-draft";
|
||||
import { KeyframeToggle } from "./keyframe-toggle";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
|
||||
export function PropertyParamField({
|
||||
param,
|
||||
@@ -28,8 +33,8 @@ export function PropertyParamField({
|
||||
keyframe,
|
||||
}: {
|
||||
param: ParamDefinition;
|
||||
value: number | string | boolean;
|
||||
onPreview: (value: number | string | boolean) => void;
|
||||
value: ParamValue;
|
||||
onPreview: (value: ParamValue) => void;
|
||||
onCommit: () => void;
|
||||
keyframe?: {
|
||||
isActive: boolean;
|
||||
@@ -41,7 +46,7 @@ export function PropertyParamField({
|
||||
<SectionField
|
||||
label={param.label}
|
||||
beforeLabel={
|
||||
keyframe ? (
|
||||
keyframe && param.keyframable !== false ? (
|
||||
<KeyframeToggle
|
||||
isActive={keyframe.isActive}
|
||||
isDisabled={keyframe.isDisabled}
|
||||
@@ -68,8 +73,8 @@ function ParamInput({
|
||||
onCommit,
|
||||
}: {
|
||||
param: ParamDefinition;
|
||||
value: number | string | boolean;
|
||||
onPreview: (value: number | string | boolean) => void;
|
||||
value: ParamValue;
|
||||
onPreview: (value: ParamValue) => void;
|
||||
onCommit: () => void;
|
||||
}) {
|
||||
if (param.type === "number") {
|
||||
@@ -131,6 +136,27 @@ function ParamInput({
|
||||
);
|
||||
}
|
||||
|
||||
if (param.type === "text") {
|
||||
return (
|
||||
<Textarea
|
||||
value={String(value)}
|
||||
onChange={(event) => onPreview(event.currentTarget.value)}
|
||||
onBlur={onCommit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (param.type === "font") {
|
||||
return (
|
||||
<input
|
||||
className="border-input bg-accent h-9 w-full rounded-md border px-3 text-sm outline-none"
|
||||
value={String(value)}
|
||||
onChange={(event) => onPreview(event.currentTarget.value)}
|
||||
onBlur={onCommit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
-96
@@ -1,96 +0,0 @@
|
||||
import { useEditor } from "@/editor/use-editor";
|
||||
import {
|
||||
getKeyframeAtTime,
|
||||
hasKeyframesForPath,
|
||||
upsertElementKeyframe,
|
||||
} from "@/animation";
|
||||
import type { AnimationPropertyPath, ElementAnimations } from "@/animation/types";
|
||||
import type { TimelineElement } from "@/timeline";
|
||||
import type { MediaTime } from "@/wasm";
|
||||
|
||||
export function useKeyframedColorProperty({
|
||||
trackId,
|
||||
elementId,
|
||||
animations,
|
||||
propertyPath,
|
||||
localTime,
|
||||
isPlayheadWithinElementRange,
|
||||
resolvedColor,
|
||||
buildBaseUpdates,
|
||||
}: {
|
||||
trackId: string;
|
||||
elementId: string;
|
||||
animations: ElementAnimations | undefined;
|
||||
propertyPath: AnimationPropertyPath;
|
||||
localTime: MediaTime;
|
||||
isPlayheadWithinElementRange: boolean;
|
||||
resolvedColor: string;
|
||||
buildBaseUpdates: ({ value }: { value: string }) => Partial<TimelineElement>;
|
||||
}) {
|
||||
const editor = useEditor();
|
||||
|
||||
const hasAnimatedKeyframes = hasKeyframesForPath({ animations, propertyPath });
|
||||
const keyframeAtTime = isPlayheadWithinElementRange
|
||||
? getKeyframeAtTime({ animations, propertyPath, time: localTime })
|
||||
: null;
|
||||
const keyframeIdAtTime = keyframeAtTime?.id ?? null;
|
||||
const isKeyframedAtTime = keyframeAtTime !== null;
|
||||
const shouldUseAnimatedChannel =
|
||||
hasAnimatedKeyframes && isPlayheadWithinElementRange;
|
||||
|
||||
const onChange = ({ color }: { color: string }) => {
|
||||
if (shouldUseAnimatedChannel) {
|
||||
editor.timeline.previewElements({
|
||||
updates: [
|
||||
{
|
||||
trackId,
|
||||
elementId,
|
||||
updates: {
|
||||
animations: upsertElementKeyframe({
|
||||
animations,
|
||||
propertyPath,
|
||||
time: localTime,
|
||||
value: color,
|
||||
}),
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
editor.timeline.previewElements({
|
||||
updates: [{ trackId, elementId, updates: buildBaseUpdates({ value: color }) }],
|
||||
});
|
||||
};
|
||||
|
||||
const onChangeEnd = () => editor.timeline.commitPreview();
|
||||
|
||||
const toggleKeyframe = () => {
|
||||
if (!isPlayheadWithinElementRange) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (keyframeIdAtTime) {
|
||||
editor.timeline.removeKeyframes({
|
||||
keyframes: [{ trackId, elementId, propertyPath, keyframeId: keyframeIdAtTime }],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
editor.timeline.upsertKeyframes({
|
||||
keyframes: [
|
||||
{ trackId, elementId, propertyPath, time: localTime, value: resolvedColor },
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
isKeyframedAtTime,
|
||||
hasAnimatedKeyframes,
|
||||
keyframeIdAtTime,
|
||||
onChange,
|
||||
onChangeEnd,
|
||||
toggleKeyframe,
|
||||
};
|
||||
}
|
||||
-175
@@ -1,175 +0,0 @@
|
||||
import { useEditor } from "@/editor/use-editor";
|
||||
import {
|
||||
getKeyframeAtTime,
|
||||
hasKeyframesForPath,
|
||||
upsertElementKeyframe,
|
||||
} from "@/animation";
|
||||
import type { AnimationPropertyPath, ElementAnimations } from "@/animation/types";
|
||||
import type { TimelineElement } from "@/timeline";
|
||||
import { snapToStep } from "@/utils/math";
|
||||
import { usePropertyDraft } from "./use-property-draft";
|
||||
import type { MediaTime } from "@/wasm";
|
||||
|
||||
export function useKeyframedNumberProperty({
|
||||
trackId,
|
||||
elementId,
|
||||
animations,
|
||||
propertyPath,
|
||||
localTime,
|
||||
isPlayheadWithinElementRange,
|
||||
displayValue,
|
||||
parse,
|
||||
valueAtPlayhead,
|
||||
step,
|
||||
buildBaseUpdates,
|
||||
buildAdditionalKeyframes,
|
||||
}: {
|
||||
trackId: string;
|
||||
elementId: string;
|
||||
animations: ElementAnimations | undefined;
|
||||
propertyPath: AnimationPropertyPath;
|
||||
localTime: MediaTime;
|
||||
isPlayheadWithinElementRange: boolean;
|
||||
displayValue: string;
|
||||
parse: (input: string) => number | null;
|
||||
valueAtPlayhead: number;
|
||||
step?: number;
|
||||
buildBaseUpdates: ({ value }: { value: number }) => Partial<TimelineElement>;
|
||||
buildAdditionalKeyframes?: ({
|
||||
value,
|
||||
}: { value: number }) => Array<{ propertyPath: AnimationPropertyPath; value: number }>;
|
||||
}) {
|
||||
const editor = useEditor();
|
||||
const snapValue = (value: number) =>
|
||||
step != null ? snapToStep({ value, step }) : value;
|
||||
|
||||
const hasAnimatedKeyframes = hasKeyframesForPath({ animations, propertyPath });
|
||||
const keyframeAtTime = isPlayheadWithinElementRange
|
||||
? getKeyframeAtTime({ animations, propertyPath, time: localTime })
|
||||
: null;
|
||||
const keyframeIdAtTime = keyframeAtTime?.id ?? null;
|
||||
const isKeyframedAtTime = keyframeAtTime !== null;
|
||||
const shouldUseAnimatedChannel =
|
||||
hasAnimatedKeyframes && isPlayheadWithinElementRange;
|
||||
|
||||
const previewValue = ({ value }: { value: number }) => {
|
||||
const nextValue = snapValue(value);
|
||||
if (shouldUseAnimatedChannel) {
|
||||
const additionalKeyframes = buildAdditionalKeyframes?.({ value: nextValue }) ?? [];
|
||||
const updatedAnimations = [
|
||||
{ propertyPath, value: nextValue },
|
||||
...additionalKeyframes,
|
||||
].reduce(
|
||||
(currentAnimations, keyframe) =>
|
||||
upsertElementKeyframe({
|
||||
animations: currentAnimations,
|
||||
propertyPath: keyframe.propertyPath,
|
||||
time: localTime,
|
||||
value: keyframe.value,
|
||||
}),
|
||||
animations,
|
||||
);
|
||||
editor.timeline.previewElements({
|
||||
updates: [
|
||||
{
|
||||
trackId,
|
||||
elementId,
|
||||
updates: { animations: updatedAnimations },
|
||||
},
|
||||
],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
editor.timeline.previewElements({
|
||||
updates: [
|
||||
{
|
||||
trackId,
|
||||
elementId,
|
||||
updates: buildBaseUpdates({ value: nextValue }),
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
const propertyDraft = usePropertyDraft({
|
||||
displayValue,
|
||||
parse: (input) => {
|
||||
const parsedValue = parse(input);
|
||||
return parsedValue === null ? null : snapValue(parsedValue);
|
||||
},
|
||||
onPreview: (value) => previewValue({ value }),
|
||||
onCommit: () => editor.timeline.commitPreview(),
|
||||
});
|
||||
|
||||
const toggleKeyframe = () => {
|
||||
if (!isPlayheadWithinElementRange) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (keyframeIdAtTime) {
|
||||
editor.timeline.removeKeyframes({
|
||||
keyframes: [
|
||||
{
|
||||
trackId,
|
||||
elementId,
|
||||
propertyPath,
|
||||
keyframeId: keyframeIdAtTime,
|
||||
},
|
||||
],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
editor.timeline.upsertKeyframes({
|
||||
keyframes: [
|
||||
{
|
||||
trackId,
|
||||
elementId,
|
||||
propertyPath,
|
||||
time: localTime,
|
||||
value: snapValue(valueAtPlayhead),
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
const commitValue = ({ value }: { value: number }) => {
|
||||
const nextValue = snapValue(value);
|
||||
if (shouldUseAnimatedChannel) {
|
||||
const additionalKeyframes = buildAdditionalKeyframes?.({ value: nextValue }) ?? [];
|
||||
editor.timeline.upsertKeyframes({
|
||||
keyframes: [
|
||||
{ trackId, elementId, propertyPath, time: localTime, value: nextValue },
|
||||
...additionalKeyframes.map((keyframe) => ({
|
||||
trackId,
|
||||
elementId,
|
||||
propertyPath: keyframe.propertyPath,
|
||||
time: localTime,
|
||||
value: keyframe.value,
|
||||
})),
|
||||
],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
editor.timeline.updateElements({
|
||||
updates: [
|
||||
{
|
||||
trackId,
|
||||
elementId,
|
||||
patch: buildBaseUpdates({ value: nextValue }),
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
...propertyDraft,
|
||||
hasAnimatedKeyframes,
|
||||
isKeyframedAtTime,
|
||||
keyframeIdAtTime,
|
||||
toggleKeyframe,
|
||||
commitValue,
|
||||
};
|
||||
}
|
||||
+18
-14
@@ -8,14 +8,15 @@ import {
|
||||
upsertPathKeyframe,
|
||||
} from "@/animation";
|
||||
import type {
|
||||
AnimationPath,
|
||||
ElementAnimations,
|
||||
} from "@/animation/types";
|
||||
import {
|
||||
coerceAnimationParamValue,
|
||||
getAnimationParamDefaultInterpolation,
|
||||
getAnimationParamValueKind,
|
||||
} from "@/animation/animated-params";
|
||||
import type { ParamDefinition } from "@/params";
|
||||
coerceParamValue,
|
||||
getParamDefaultInterpolation,
|
||||
getParamValueKind,
|
||||
type ParamDefinition,
|
||||
} from "@/params";
|
||||
import type { TimelineElement } from "@/timeline";
|
||||
import type { MediaTime } from "@/wasm";
|
||||
|
||||
@@ -33,6 +34,7 @@ export function useKeyframedParamProperty({
|
||||
trackId,
|
||||
elementId,
|
||||
animations,
|
||||
propertyPath,
|
||||
localTime,
|
||||
isPlayheadWithinElementRange,
|
||||
resolvedValue,
|
||||
@@ -42,6 +44,7 @@ export function useKeyframedParamProperty({
|
||||
trackId: string;
|
||||
elementId: string;
|
||||
animations: ElementAnimations | undefined;
|
||||
propertyPath?: AnimationPath;
|
||||
localTime: MediaTime;
|
||||
isPlayheadWithinElementRange: boolean;
|
||||
resolvedValue: number | string | boolean;
|
||||
@@ -52,15 +55,16 @@ export function useKeyframedParamProperty({
|
||||
}) => Partial<TimelineElement>;
|
||||
}): KeyframedParamPropertyResult {
|
||||
const editor = useEditor();
|
||||
const propertyPath = buildGraphicParamPath({ paramKey: param.key });
|
||||
const resolvedPropertyPath =
|
||||
propertyPath ?? buildGraphicParamPath({ paramKey: param.key });
|
||||
const hasAnimatedKeyframes = hasKeyframesForPath({
|
||||
animations,
|
||||
propertyPath,
|
||||
propertyPath: resolvedPropertyPath,
|
||||
});
|
||||
const keyframeAtTime = isPlayheadWithinElementRange
|
||||
? getKeyframeAtTime({
|
||||
animations,
|
||||
propertyPath,
|
||||
propertyPath: resolvedPropertyPath,
|
||||
time: localTime,
|
||||
})
|
||||
: null;
|
||||
@@ -79,15 +83,15 @@ export function useKeyframedParamProperty({
|
||||
updates: {
|
||||
animations: upsertPathKeyframe({
|
||||
animations,
|
||||
propertyPath,
|
||||
propertyPath: resolvedPropertyPath,
|
||||
time: localTime,
|
||||
value,
|
||||
kind: getAnimationParamValueKind({ param }),
|
||||
defaultInterpolation: getAnimationParamDefaultInterpolation({
|
||||
kind: getParamValueKind({ param }),
|
||||
defaultInterpolation: getParamDefaultInterpolation({
|
||||
param,
|
||||
}),
|
||||
coerceValue: ({ value: nextValue }) =>
|
||||
coerceAnimationParamValue({
|
||||
coerceParamValue({
|
||||
param,
|
||||
value: nextValue,
|
||||
}),
|
||||
@@ -121,7 +125,7 @@ export function useKeyframedParamProperty({
|
||||
{
|
||||
trackId,
|
||||
elementId,
|
||||
propertyPath,
|
||||
propertyPath: resolvedPropertyPath,
|
||||
keyframeId: keyframeIdAtTime,
|
||||
},
|
||||
],
|
||||
@@ -134,7 +138,7 @@ export function useKeyframedParamProperty({
|
||||
{
|
||||
trackId,
|
||||
elementId,
|
||||
propertyPath,
|
||||
propertyPath: resolvedPropertyPath,
|
||||
time: localTime,
|
||||
value: resolvedValue,
|
||||
},
|
||||
|
||||
@@ -22,16 +22,43 @@ import {
|
||||
MagicWand05Icon,
|
||||
DashboardSpeed02Icon,
|
||||
} from "@hugeicons/core-free-icons";
|
||||
import { TransformTab } from "@/rendering/components/transform-tab";
|
||||
import { BlendingTab } from "@/rendering/components/blending-tab";
|
||||
import { AudioTab } from "@/timeline/components/audio-tab";
|
||||
import { TextTab } from "@/text/components/text-tab";
|
||||
import { ElementParamsTab } from "./components/element-params-tab";
|
||||
import { ClipEffectsTab, StandaloneEffectTab } from "@/effects/components/effects-tab";
|
||||
import { MasksTab } from "@/masks/components/masks-tab";
|
||||
import { SpeedTab } from "@/speed/components/speed-tab";
|
||||
import { GraphicTab } from "@/graphics/components/graphic-tab";
|
||||
import { OcShapesIcon } from "@/components/icons";
|
||||
|
||||
const TRANSFORM_PARAM_KEYS = [
|
||||
"transform.positionX",
|
||||
"transform.positionY",
|
||||
"transform.scaleX",
|
||||
"transform.scaleY",
|
||||
"transform.rotate",
|
||||
] as const;
|
||||
|
||||
const BLENDING_PARAM_KEYS = ["opacity", "blendMode"] as const;
|
||||
const AUDIO_PARAM_KEYS = ["volume", "muted"] as const;
|
||||
const TEXT_PARAM_KEYS = [
|
||||
"content",
|
||||
"fontFamily",
|
||||
"fontSize",
|
||||
"color",
|
||||
"textAlign",
|
||||
"fontWeight",
|
||||
"fontStyle",
|
||||
"textDecoration",
|
||||
"letterSpacing",
|
||||
"lineHeight",
|
||||
"background.enabled",
|
||||
"background.color",
|
||||
"background.cornerRadius",
|
||||
"background.paddingX",
|
||||
"background.paddingY",
|
||||
"background.offsetX",
|
||||
"background.offsetY",
|
||||
] as const;
|
||||
|
||||
export type TabContentProps = {
|
||||
trackId: string;
|
||||
};
|
||||
@@ -58,7 +85,12 @@ function buildTransformTab({
|
||||
label: "Transform",
|
||||
icon: <HugeiconsIcon icon={ArrowExpandIcon} size={16} />,
|
||||
content: ({ trackId }) => (
|
||||
<TransformTab element={element} trackId={trackId} />
|
||||
<ElementParamsTab
|
||||
element={element}
|
||||
trackId={trackId}
|
||||
paramKeys={TRANSFORM_PARAM_KEYS}
|
||||
sectionKey="transform"
|
||||
/>
|
||||
),
|
||||
};
|
||||
}
|
||||
@@ -73,7 +105,12 @@ function buildBlendingTab({
|
||||
label: "Blending",
|
||||
icon: <HugeiconsIcon icon={RainDropIcon} size={16} />,
|
||||
content: ({ trackId }) => (
|
||||
<BlendingTab element={element} trackId={trackId} />
|
||||
<ElementParamsTab
|
||||
element={element}
|
||||
trackId={trackId}
|
||||
paramKeys={BLENDING_PARAM_KEYS}
|
||||
sectionKey="blending"
|
||||
/>
|
||||
),
|
||||
};
|
||||
}
|
||||
@@ -87,7 +124,14 @@ function buildAudioTab({
|
||||
id: "audio",
|
||||
label: "Audio",
|
||||
icon: <HugeiconsIcon icon={MusicNote03Icon} size={16} />,
|
||||
content: ({ trackId }) => <AudioTab element={element} trackId={trackId} />,
|
||||
content: ({ trackId }) => (
|
||||
<ElementParamsTab
|
||||
element={element}
|
||||
trackId={trackId}
|
||||
paramKeys={AUDIO_PARAM_KEYS}
|
||||
sectionKey="audio"
|
||||
/>
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -137,7 +181,14 @@ function buildTextTab({ element }: { element: TextElement }): PropertiesTabDef {
|
||||
id: "text",
|
||||
label: "Text",
|
||||
icon: <HugeiconsIcon icon={TextFontIcon} size={16} />,
|
||||
content: ({ trackId }) => <TextTab element={element} trackId={trackId} />,
|
||||
content: ({ trackId }) => (
|
||||
<ElementParamsTab
|
||||
element={element}
|
||||
trackId={trackId}
|
||||
paramKeys={TEXT_PARAM_KEYS}
|
||||
sectionKey="text"
|
||||
/>
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user