import React, { createContext, useCallback, useContext, useMemo, useState } from "react"; import { useNavigate } from "react-router-dom"; import { ModuleId, moduleHref } from "./registry"; interface LaunchState { /** Monotonic counter — increments on every explicit launch so the animation re-seeds. */ seq: number; /** Module currently being exited (reverse-assembly playing). */ exiting: ModuleId | null; } interface LaunchContextType extends LaunchState { /** Play exit choreography (if inside a module), then navigate to the target module/hub. */ startLaunch: (target: ModuleId | "hub") => void; /** Called by the exiting shell once its reverse-assembly finished. */ finishExit: () => void; } const LaunchContext = createContext({ seq: 0, exiting: null, startLaunch: () => {}, finishExit: () => {}, }); const EXIT_DURATION_MS = 850; export const LaunchProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { const [state, setState] = useState({ seq: 0, exiting: null }); const navigate = useNavigate(); const startLaunch = useCallback( (target: ModuleId | "hub") => { setState((prev) => { const navigateAfterExit = () => { navigate(target === "hub" ? "/" : moduleHref(target)); }; // Bumping seq always re-seeds the next enter animation. const next = { seq: prev.seq + 1, exiting: prev.exiting }; // Controlled exit: if a module shell is currently mounted, let it play // its reverse-assembly first, then navigate. const onHub = window.location.pathname === "/" || !window.location.pathname.startsWith("/modules/"); if (!onHub) { next.exiting = (window.location.pathname.startsWith("/modules/s") ? "s" : "e") as ModuleId; window.setTimeout(navigateAfterExit, EXIT_DURATION_MS); } else { // On the hub nothing needs to disassemble — navigate immediately. window.setTimeout(navigateAfterExit, 0); } return next; }); }, [navigate] ); const finishExit = useCallback(() => { setState((prev) => ({ ...prev, exiting: null })); }, []); const value = useMemo(() => ({ ...state, startLaunch, finishExit }), [state, startLaunch, finishExit]); return {children}; }; export const useLaunch = () => useContext(LaunchContext);