BRIDGE_Modul-S_prod / src /modules /LaunchContext.tsx
BRIDGE Platform Agent
feat(platform): BRIDGE Modules hub with /modules/e and /modules/s routed workspaces
1955a0f
Raw History Blame Contribute Delete
2.45 kB
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<LaunchContextType>({
seq: 0,
exiting: null,
startLaunch: () => {},
finishExit: () => {},
});
const EXIT_DURATION_MS = 850;
export const LaunchProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [state, setState] = useState<LaunchState>({ 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 <LaunchContext.Provider value={value}>{children}</LaunchContext.Provider>;
};
export const useLaunch = () => useContext(LaunchContext);