"use client";
import { useState } from "react";
import type { ParamValues } from "@/params";
import type { Effect } from "@/effects/types";
import type { EffectElement, VisualElement } from "@/model";
import { effectsRegistry } from "@/effects";
import { useEditor } from "@/editor/use-editor";
import { useElementPreview } from "@/timeline/hooks/use-element-preview";
import {
Section,
SectionContent,
SectionHeader,
SectionTitle,
SectionFields,
} from "@/components/section";
import { PropertyParamField } from "@/components/editor/panels/properties/components/property-param-field";
import { Button } from "@/components/ui/button";
import { HugeiconsIcon } from "@hugeicons/react";
import {
Delete02Icon,
ViewIcon,
ViewOffSlashIcon,
MagicWand05Icon,
} from "@hugeicons/core-free-icons";
import { cn } from "@/utils/ui";
import { Separator } from "@/components/ui/separator";
import { useAssetsPanelStore } from "@/components/editor/panels/assets/assets-panel-store";
export function StandaloneEffectTab({
element,
trackId,
}: {
element: EffectElement;
trackId: string;
}) {
const { renderElement, previewUpdates, commit } = useElementPreview({
trackId,
elementId: element.id,
fallback: element,
});
const effect: Effect = {
id: element.id,
type: element.effectType,
params: element.params,
enabled: true,
};
const previewParam = (key: string) => (value: number | string | boolean) => {
previewUpdates({
params: { ...(renderElement as EffectElement).params, [key]: value },
});
};
return (
);
}
export function ClipEffectsTab({
element,
trackId,
}: {
element: VisualElement;
trackId: string;
}) {
const [dragIndex, setDragIndex] = useState(null);
const [dropIndex, setDropIndex] = useState(null);
const editor = useEditor();
const { renderElement, previewUpdates, commit } = useElementPreview({
trackId,
elementId: element.id,
fallback: element,
});
const effects: Effect[] = element.effects ?? [];
const getRenderParams = ({ effectId }: { effectId: string }): ParamValues => {
return (
(renderElement as VisualElement).effects?.find((ef) => ef.id === effectId)
?.params ??
effects.find((ef) => ef.id === effectId)?.params ??
{}
);
};
const buildPreviewParam =
(effectId: string) =>
(key: string) =>
(value: number | string | boolean) => {
const updatedEffects = (
(renderElement as VisualElement).effects ?? []
).map((existing) =>
existing.id !== effectId
? existing
: { ...existing, params: { ...existing.params, [key]: value } },
);
previewUpdates({ effects: updatedEffects });
};
const handleDragStart = ({ index }: { index: number }) => setDragIndex(index);
const handleDragOver = ({
event,
index,
}: {
event: React.DragEvent;
index: number;
}) => {
event.preventDefault();
if (index !== dropIndex) setDropIndex(index);
};
const handleDrop = ({ toIndex }: { toIndex: number }) => {
if (dragIndex !== null && dragIndex !== toIndex) {
editor.timeline.reorderClipEffects({
trackId,
elementId: element.id,
fromIndex: dragIndex,
toIndex,
});
}
setDragIndex(null);
setDropIndex(null);
};
const handleDragEnd = () => {
setDragIndex(null);
setDropIndex(null);
};
return (
Effects
{effects.length === 0 ? (
) : (
{effects.map((effect, index) => {
const resolvedDragIndex = dragIndex ?? -1;
const isDragging = dragIndex === index;
const isDropTarget =
dropIndex === index && dragIndex !== null && dragIndex !== index;
const showTopDropIndicator =
isDropTarget && index < resolvedDragIndex;
const showBottomDropIndicator =
isDropTarget && index > resolvedDragIndex;
return (
- handleDragStart({ index })}
onDragOver={(event) => handleDragOver({ event, index })}
onDrop={() => handleDrop({ toIndex: index })}
onDragEnd={handleDragEnd}
className={cn(
"group list-none",
isDragging && "opacity-40",
showTopDropIndicator && "border-t-2 border-primary",
showBottomDropIndicator && "border-b-2 border-primary",
)}
>
editor.timeline.toggleClipEffect({
trackId,
elementId: element.id,
effectId: effect.id,
})
}
onRemove={() =>
editor.timeline.removeClipEffect({
trackId,
elementId: element.id,
effectId: effect.id,
})
}
/>
);
})}
)}
);
}
function EmptyView() {
const setActiveTab = useAssetsPanelStore((s) => s.setActiveTab);
return (
No effects
Add effects to this layer from the Assets panel.
);
}
function EffectSection({
effect,
renderParams,
previewParam,
onCommit,
onToggle,
onRemove,
}: {
effect: Effect;
renderParams: ParamValues;
previewParam: (key: string) => (value: number | string | boolean) => void;
onCommit: () => void;
onToggle?: () => void;
onRemove?: () => void;
}) {
const definition = effectsRegistry.get(effect.type);
return (
)
}
>
{definition.name}
{definition.params.map((param) => (
))}
);
}