"use client"; import { ArrowRightDoubleIcon, MagicWand05Icon, } from "@hugeicons/core-free-icons"; import { HugeiconsIcon } from "@hugeicons/react"; import { PanelView } from "./base-panel"; import { Button } from "@/components/ui/button"; import { useEditor } from "@/editor/use-editor"; import { useElementSelection } from "@/timeline/hooks/element/use-element-selection"; import { isVisualElement } from "@/timeline/element-utils"; import type { TimelineElement, TimelineTrack } from "@/timeline"; import { type TransitionKind, withVisualTransitionParams, } from "@/rendering/transition"; const transitions: Array<{ kind: TransitionKind; name: string; description: string; inDuration: number; outDuration: number; }> = [ { kind: "fade", name: "Soft fade", description: "Clean dissolve for slides and stills.", inDuration: 0.35, outDuration: 0, }, { kind: "push-left", name: "Slide left", description: "Deck-like horizontal slide motion.", inDuration: 0.45, outDuration: 0, }, { kind: "push-right", name: "Slide right", description: "Reverse horizontal slide motion.", inDuration: 0.45, outDuration: 0, }, { kind: "zoom", name: "Zoom in", description: "Subtle punch-in with a fade edge.", inDuration: 0.4, outDuration: 0, }, { kind: "zoom-out", name: "Zoom out", description: "Settle inward from a wider opening.", inDuration: 0.4, outDuration: 0, }, { kind: "spin", name: "Spin dissolve", description: "A restrained turn with a soft fade.", inDuration: 0.45, outDuration: 0, }, ]; export function TransitionsView() { const editor = useEditor(); const { selectedElements } = useElementSelection(); const scene = useEditor((currentEditor) => currentEditor.scenes.getActiveSceneOrNull(), ); const currentTime = useEditor((currentEditor) => currentEditor.playback.getCurrentTime(), ); const selectedVisualElements = editor.timeline .getElementsWithTracks({ elements: selectedElements }) .filter(({ element }) => isVisualElement(element)); const nearestTarget = scene ? findNearestTransitionTarget({ tracks: [scene.tracks.main, ...scene.tracks.overlay], currentTime, }) : null; const targets = selectedVisualElements.length > 0 ? selectedVisualElements : nearestTarget ? [nearestTarget] : []; const selectedCount = selectedVisualElements.length; const applyTransition = ({ kind, inDuration, outDuration, }: { kind: TransitionKind; inDuration: number; outDuration: number; }) => { if (targets.length === 0) { return; } editor.timeline.updateElements({ updates: targets.map(({ track, element }) => ({ trackId: track.id, elementId: element.id, patch: { params: withVisualTransitionParams({ params: element.params, transitionKind: kind, inDuration, outDuration, }), }, })), }); }; return (
Apply at the cut

{selectedCount > 0 ? `${selectedCount} selected clip${selectedCount === 1 ? "" : "s"}.` : nearestTarget ? `Ready for the cut into ${nearestTarget.element.name}.` : "Add at least two image or video clips to one track."}

{transitions.map((transition) => ( ))}
); } function findNearestTransitionTarget({ tracks, currentTime, }: { tracks: TimelineTrack[]; currentTime: number; }): { track: TimelineTrack; element: TimelineElement } | null { let nearest: { track: TimelineTrack; element: TimelineElement } | null = null; let nearestDistance = Number.POSITIVE_INFINITY; for (const track of tracks) { const elements = track.elements .filter((element) => element.type === "video" || element.type === "image") .slice() .sort((left, right) => left.startTime - right.startTime); for (let index = 1; index < elements.length; index += 1) { const element = elements[index]; const distance = Math.abs(element.startTime - currentTime); if (distance < nearestDistance) { nearest = { track, element }; nearestDistance = distance; } } } return nearest; } function TransitionCard({ transition, disabled, onApply, }: { transition: (typeof transitions)[number]; disabled: boolean; onApply: (transition: (typeof transitions)[number]) => void; }) { const editor = useEditor(); return ( ); } function TransitionPreview({ kind }: { kind: TransitionKind }) { return (
{kind === "fade" && (
)} {kind === "push-left" && ( )} {kind === "push-right" && ( )} {kind === "zoom" && (
)} {kind === "zoom-out" && (
)} {kind === "spin" && (
)}
); }