import { useCallback, useEffect, useRef, useState } from 'react'; import type { Object3D } from 'three'; import { MachineAnimationPlayer } from './MachineAnimationPlayer'; import type { MachineAnimationDirection, MachineAnimationModule, MachineAnimationPlayerOptions, MachineAnimationSnapshot, } from './types'; export interface UseMachineAnimationResult { player: MachineAnimationPlayer; snapshot: MachineAnimationSnapshot; registerPart: (partId: string, object: Object3D | null) => void; play: () => void; pause: () => void; resume: () => void; restart: (autoPlay?: boolean) => void; stop: () => void; step: (steps?: number) => void; seekSeconds: (seconds: number) => void; seekCycleProgress: (progress: number) => void; setRpm: (rpm: number) => void; setTimeScale: (timeScale: number) => void; setLoop: (loop: boolean) => void; setStepMode: (enabled: boolean) => void; setReducedMotion: (enabled: boolean) => void; setDirection: (direction: MachineAnimationDirection) => void; } export function useMachineAnimation( module: MachineAnimationModule | null | undefined, options: Omit = {}, ): UseMachineAnimationResult { const playerRef = useRef(null); if (!playerRef.current) { playerRef.current = new MachineAnimationPlayer({ ...options, module }); } const player = playerRef.current; const [snapshot, setSnapshot] = useState(() => player.getSnapshot()); useEffect(() => player.subscribe(setSnapshot), [player]); useEffect(() => { player.setModule(module ?? null); }, [module, player]); useEffect(() => { if (typeof options.rpm === 'number') player.setRpm(options.rpm); }, [options.rpm, player]); useEffect(() => { if (typeof options.timeScale === 'number') player.setTimeScale(options.timeScale); }, [options.timeScale, player]); useEffect(() => { if (typeof options.loop === 'boolean') player.setLoop(options.loop); }, [options.loop, player]); useEffect(() => { if (typeof options.stepMode === 'boolean') player.setStepMode(options.stepMode); }, [options.stepMode, player]); useEffect(() => { if (typeof options.direction === 'number') player.setDirection(options.direction); }, [options.direction, player]); useEffect(() => { if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') { if (typeof options.reducedMotion === 'boolean') player.setReducedMotion(options.reducedMotion); return undefined; } const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)'); const syncReducedMotion = () => { player.setReducedMotion(options.reducedMotion ?? mediaQuery.matches); }; syncReducedMotion(); if (typeof mediaQuery.addEventListener === 'function') { mediaQuery.addEventListener('change', syncReducedMotion); return () => mediaQuery.removeEventListener('change', syncReducedMotion); } mediaQuery.addListener(syncReducedMotion); return () => mediaQuery.removeListener(syncReducedMotion); }, [options.reducedMotion, player]); useEffect(() => { if (options.autoPlay) player.play(); }, [options.autoPlay, player]); useEffect( () => () => { player.dispose(); }, [player], ); return { player, snapshot, registerPart: useCallback((partId: string, object: Object3D | null) => { player.registerPart(partId, object); }, [player]), play: useCallback(() => player.play(), [player]), pause: useCallback(() => player.pause(), [player]), resume: useCallback(() => player.resume(), [player]), restart: useCallback((autoPlay = false) => player.restart(autoPlay), [player]), stop: useCallback(() => player.stop(), [player]), step: useCallback((steps = 1) => player.step(steps), [player]), seekSeconds: useCallback((seconds: number) => player.seekSeconds(seconds), [player]), seekCycleProgress: useCallback((progress: number) => player.seekCycleProgress(progress), [player]), setRpm: useCallback((rpm: number) => player.setRpm(rpm), [player]), setTimeScale: useCallback((timeScale: number) => player.setTimeScale(timeScale), [player]), setLoop: useCallback((loop: boolean) => player.setLoop(loop), [player]), setStepMode: useCallback((enabled: boolean) => player.setStepMode(enabled), [player]), setReducedMotion: useCallback((enabled: boolean) => player.setReducedMotion(enabled), [player]), setDirection: useCallback((direction: MachineAnimationDirection) => player.setDirection(direction), [player]), }; }