import { useRef } from "react"; import { useEnableKeyframes, type EnableKeyframesSession } from "../hooks/useEnableKeyframes"; import { getNextTimelineZoomPercent, getTimelineZoomPercent, } from "../player/components/timelineZoom"; import { useTimelineZoom } from "../player/components/useTimelineZoom"; import { getTimelineToggleTitle } from "../utils/timelineDiscovery"; import { usePlayerStore, type TimelineElement } from "../player"; import { STUDIO_KEYFRAMES_ENABLED, STUDIO_RAZOR_TOOL_ENABLED, } from "./editor/manualEditingAvailability"; import { Tooltip } from "./ui"; import { Scissors } from "../icons/SystemIcons"; import type { GsapAnimation } from "@hyperframes/core/gsap-parser"; import type { DomEditSelection } from "./editor/domEditingTypes"; import { canSplitElement } from "../utils/timelineElementSplit"; import { canAddBeatAt, addBeatAtCompositionTime } from "../utils/beatEditActions"; interface DomEditSessionSlice extends EnableKeyframesSession { domEditSelection: DomEditSelection | null; selectedGsapAnimations: GsapAnimation[]; } interface TimelineToolbarProps { toggleTimelineVisibility: () => void; domEditSession?: DomEditSessionSlice; onSplitElement?: (element: TimelineElement, splitTime: number) => void; } function useKeyframeToggle(session?: DomEditSessionSlice) { const currentTime = usePlayerStore((s) => s.currentTime); const sessionRef = useRef(session); sessionRef.current = session; const onToggle = useEnableKeyframes( sessionRef as React.RefObject, ); if (!session) return { state: "none" as const, onToggle: undefined }; const sel = session.domEditSelection; const anims = session.selectedGsapAnimations; const kfAnim = anims.find((a) => a.keyframes); const computePct = (time: number) => { const elStart = Number.parseFloat(sel?.dataAttributes?.start ?? "0") || 0; const elDuration = Number.parseFloat(sel?.dataAttributes?.duration ?? "1") || 1; return elDuration > 0 ? Math.max(0, Math.min(100, Math.round(((time - elStart) / elDuration) * 1000) / 10)) : 0; }; let state: "active" | "inactive" | "none" = "none"; if (kfAnim?.keyframes && sel) { const pct = computePct(currentTime); state = kfAnim.keyframes.keyframes.some((k) => Math.abs(k.percentage - pct) <= 1) ? "active" : "inactive"; } return { state, onToggle: sel ? onToggle : undefined }; } // fallow-ignore-next-line complexity export function TimelineToolbar({ toggleTimelineVisibility, domEditSession, onSplitElement, }: TimelineToolbarProps) { const activeTool = usePlayerStore((s) => s.activeTool); const setActiveTool = usePlayerStore((s) => s.setActiveTool); // Subscribe so the add-beat button reacts to playhead movement and analysis load. const currentTime = usePlayerStore((s) => s.currentTime); const beatAnalysisReady = usePlayerStore((s) => s.beatAnalysis !== null); const { zoomMode, manualZoomPercent, setZoomMode, setManualZoomPercent } = useTimelineZoom(); const displayedTimelineZoomPercent = getTimelineZoomPercent(zoomMode, manualZoomPercent); const { state: keyframeState, onToggle: onToggleKeyframe } = useKeyframeToggle(domEditSession); return (
Timeline
{STUDIO_RAZOR_TOOL_ENABLED && (
)} {STUDIO_KEYFRAMES_ENABLED && onToggleKeyframe && ( <> )} {onSplitElement && (() => { const { selectedElementId, elements, currentTime } = usePlayerStore.getState(); const el = selectedElementId ? elements.find((e) => (e.key ?? e.id) === selectedElementId) : null; if (!el || !canSplitElement(el)) return null; const canSplit = currentTime > el.start && currentTime < el.start + el.duration; return ( ); })()} {beatAnalysisReady && canAddBeatAt(currentTime) && (() => ( ))()}
{`${displayedTimelineZoomPercent}%`}
); }