"use client"; import * as React from "react"; import { DiamondIcon } from "@phosphor-icons/react"; import { Button, ControlFieldLabelActionProvider, Tooltip, TooltipContent, TooltipTrigger, } from "@repo/ui"; import { getToolcraftControlKeyframeCapability } from "../../../schema/keyframe-capability"; import type { ToolcraftControlSchema } from "../../../schema/types"; import type { ToolcraftCommand, ToolcraftTimelineKeyframeGroup, } from "../../../state/types"; import { getControlName, type ControlEntry, } from "../layout/controls-panel-layout"; function cn(...classNames: Array): string { return classNames.filter(Boolean).join(" "); } function canCreateControlKeyframe(control: ToolcraftControlSchema): boolean { return getToolcraftControlKeyframeCapability(control).capable; } export function getControlsPanelSectionHeaderKeyframeEntry( entries: readonly ControlEntry[], title: React.ReactNode, ): ControlEntry | null { if (typeof title !== "string") { return null; } return ( entries.find(([id, control]) => { if (control.type === "channelMixer" || control.type === "curves") { return false; } return ( getControlName(id, control.label) === title && canCreateControlKeyframe(control) ); }) ?? null ); } function ControlKeyframeButton({ active, name, onClick, }: { active: boolean; name: string; onClick: () => void; }): React.JSX.Element { const label = active ? `Disable ${name} keyframes` : `Add ${name} keyframe`; return ( { event.stopPropagation(); onClick(); if (typeof event.currentTarget.blur === "function") { event.currentTarget.blur(); } }} size="icon-sm" style={active ? { color: "var(--link)" } : undefined} type="button" variant="ghost-static" /> } > {label} ); } export function createControlsPanelKeyframeActions({ dispatchCommand, formatValueLabel, getControlName, getControlValue, keyframeControlsEnabled, keyframedControlIds, keyframeGroups, selectedKeyframeId, }: { dispatchCommand: (command: ToolcraftCommand) => void; formatValueLabel: (control: ToolcraftControlSchema, value: unknown) => string; getControlName: ( id: string, label: boolean | string | undefined, ) => string; getControlValue: (control: ToolcraftControlSchema) => unknown; keyframeControlsEnabled: boolean; keyframedControlIds: ReadonlySet; keyframeGroups: readonly ToolcraftTimelineKeyframeGroup[]; selectedKeyframeId: string | null; }): { getKeyframeLabelAction: ( control: ToolcraftControlSchema, name: string, value: unknown, ) => React.ReactNode; getSectionHeaderKeyframeAction: (entry: ControlEntry) => React.ReactNode; getSectionHeaderKeyframeEntry: ( entries: readonly ControlEntry[], title: React.ReactNode, ) => ControlEntry | null; maybeUpsertControlKeyframe: ( control: ToolcraftControlSchema, name: string, value: unknown, ) => void; withKeyframeLabelAction: (args: { children: React.ReactNode; control: ToolcraftControlSchema; disableAction?: boolean; labelActionName?: string; name: string; providerKey: string; value: unknown; }) => React.ReactNode; } { function getSelectedControlKeyframeTime(controlId: string): number | undefined { if (!selectedKeyframeId) { return undefined; } const selectedKeyframe = keyframeGroups .find((group) => group.controlId === controlId) ?.keyframes.find((keyframe) => keyframe.id === selectedKeyframeId); return selectedKeyframe?.timeSeconds; } function maybeUpsertControlKeyframe( control: ToolcraftControlSchema, name: string, value: unknown, ): void { if ( !keyframeControlsEnabled || !keyframedControlIds.has(control.target) || !canCreateControlKeyframe(control) ) { return; } dispatchCommand({ controlId: control.target, controlLabel: name, timeSeconds: getSelectedControlKeyframeTime(control.target), type: "timeline.upsertControlKeyframe", value, valueLabel: formatValueLabel(control, value), }); } function getKeyframeLabelAction( control: ToolcraftControlSchema, name: string, value: unknown, ): React.ReactNode { if (!keyframeControlsEnabled || !canCreateControlKeyframe(control)) { return null; } return ( { dispatchCommand({ controlId: control.target, controlLabel: name, type: "timeline.toggleControlKeyframes", value, valueLabel: formatValueLabel(control, value), }); }} /> ); } function withKeyframeLabelAction({ children, control, disableAction = false, labelActionName, name, providerKey, value, }: { children: React.ReactNode; control: ToolcraftControlSchema; disableAction?: boolean; labelActionName?: string; name: string; providerKey: string; value: unknown; }): React.ReactNode { if (disableAction) { return children; } const actionName = labelActionName ?? name; const action = getKeyframeLabelAction(control, actionName, value); if (!action) { return children; } return ( {children} ); } function getSectionHeaderKeyframeEntry( entries: readonly ControlEntry[], title: React.ReactNode, ): ControlEntry | null { return getControlsPanelSectionHeaderKeyframeEntry(entries, title); } function getSectionHeaderKeyframeAction(entry: ControlEntry): React.ReactNode { const [id, control] = entry; const name = getControlName(id, control.label); return getKeyframeLabelAction(control, name, getControlValue(control)); } return { getKeyframeLabelAction, getSectionHeaderKeyframeAction, getSectionHeaderKeyframeEntry, maybeUpsertControlKeyframe, withKeyframeLabelAction, }; } export type ControlsPanelKeyframeActions = ReturnType< typeof createControlsPanelKeyframeActions >;