import { animated, useSpring as useThreeSpring } from "@react-spring/three"; import { useDisableRaycast } from "../hooks/useDisableRaycast"; import { View, viewToHash } from "@/components/scene/consts"; import Stack from "@/util/stack"; import { type Dispatch, type SetStateAction, useCallback, useEffect, useState, } from "react"; import { type MeshStandardMaterial, type BufferGeometry, BackSide, } from "three"; import { Billboard, Text } from "@react-three/drei"; interface ButtonsProps { menuTraversal: Stack; setMenuTraversal: Dispatch>>; pendingView: View | null; setPendingView: Dispatch>; goPreviousView(): void; currentView: View; } const AnimatedText = animated(Text); export const Buttons = ({ menuTraversal, setMenuTraversal, pendingView, setPendingView, goPreviousView, currentView, }: ButtonsProps) => { const [hovered, setHovered] = useState(null); const desktopRef = useDisableRaycast(currentView !== View.MainView); const printerRef = useDisableRaycast(currentView !== View.MainView); const sideRef = useDisableRaycast(currentView !== View.MainView); const cellphoneRef = useDisableRaycast(currentView !== View.DesktopView); const pcRef = useDisableRaycast(currentView !== View.DesktopView); const goToView = useCallback( (view: View) => { setMenuTraversal((prev) => { const next = prev.clone(); next.push(view); return next; }); setPendingView(view); }, [setMenuTraversal, setPendingView] ); useEffect(() => { const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") goPreviousView(); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [goPreviousView]); useEffect(() => { const onPop = () => { goPreviousView(); }; window.addEventListener("popstate", onPop); return () => window.removeEventListener("popstate", onPop); }, [goPreviousView]); useEffect(() => { if (pendingView === null) return; window.history.pushState( { depth: menuTraversal.size() }, "", viewToHash[pendingView] ? `#${viewToHash[pendingView]}` : "/" ); setPendingView(null); }, [menuTraversal, pendingView, setPendingView]); useEffect(() => { if (hovered) { document.body.style.cursor = "pointer"; } else { document.body.style.cursor = "auto"; } }, [hovered]); const mainMenuSpring = useThreeSpring({ opacity: currentView === View.MainView ? 1 : 0, }); const desktopMenuSpring = useThreeSpring({ opacity: currentView === View.DesktopView ? 1 : 0, }); const creditsButtonSpring = useThreeSpring({ opacity: currentView === View.DesktopView ? hovered === View.CreditsView ? 1 : 0.25 : 0, }); const desktopViewSpring = useThreeSpring({ scale: hovered === View.DesktopView ? 1.25 : 1, }); const resumeViewSpring = useThreeSpring({ scale: hovered === View.ResumeView ? 1.25 : 1, }); const cellphoneViewSpring = useThreeSpring({ scale: hovered === View.CellphoneView ? 1.25 : 1, }); const pcViewSpring = useThreeSpring({ scale: hovered === View.PCView ? 1.25 : 1, }); const creditsViewSpring = useThreeSpring({ scale: hovered === View.CreditsView ? 1.25 : 1, }); const printerViewSpring = useThreeSpring({ scale: hovered === View.PrinterView ? 1.25 : 1, }); return ( <> {/* Main Menu */} goToView(View.DesktopView) : undefined } onPointerOver={ currentView === View.MainView ? () => setHovered(View.DesktopView) : undefined } onPointerOut={() => setHovered(null)} scale={desktopViewSpring.scale} > Projects & Socials goToView(View.PrinterView) : undefined } onPointerOver={ currentView === View.MainView ? () => setHovered(View.PrinterView) : undefined } onPointerOut={() => setHovered(null)} scale={printerViewSpring.scale} > Hobby Corner goToView(View.ResumeView) : undefined } onPointerOver={ currentView === View.MainView ? () => setHovered(View.ResumeView) : undefined } onPointerOut={() => setHovered(null)} scale={resumeViewSpring.scale} > Resume {/* Desktop Menu */} goToView(View.CellphoneView) : undefined } onPointerOver={ currentView === View.DesktopView ? () => setHovered(View.CellphoneView) : undefined } onPointerOut={() => setHovered(null)} scale={cellphoneViewSpring.scale} > Socials goToView(View.PCView) : undefined } onPointerOver={ currentView === View.DesktopView ? () => setHovered(View.PCView) : undefined } onPointerOut={() => setHovered(null)} scale={pcViewSpring.scale} > Projects goToView(View.CreditsView) : undefined } onPointerOver={ currentView === View.DesktopView ? () => setHovered(View.CreditsView) : undefined } onPointerOut={() => setHovered(null)} scale={creditsViewSpring.scale} > ); };