"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 (
Effect
); } 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) => (
))}
); }