Files
Genarrative/src/components/rpg-runtime-shell/useRpgRuntimeOverlayState.ts
2026-04-25 22:19:04 +08:00

122 lines
3.6 KiB
TypeScript

import { useCallback, useEffect, useState } from 'react';
import {
pushAppHistoryPath,
resolvePathForSelectionStage,
resolveSelectionStageFromPath,
} from '../../routing/appPageRoutes';
import type { GameState } from '../../types';
import type { GameCanvasEntitySelection } from '../GameCanvas';
import type { SelectionStage } from '../platform-entry';
type OverlayPanel = 'character' | 'inventory' | null;
function useLazyModalMount(active: boolean) {
const [shouldMount, setShouldMount] = useState(active);
useEffect(() => {
if (active) {
setShouldMount(true);
}
}, [active]);
return shouldMount;
}
/**
* RPG 运行态 overlay 与独立面板状态。
* 负责保留现有弹窗装配顺序,同时把壳层 UI 状态从旧 GameShell 命名迁出。
*/
export function useRpgRuntimeOverlayState(params: {
gameState: GameState;
isMapOpen: boolean;
characterChatModalOpen: boolean;
hasNpcModalOpen: boolean;
}) {
const {
gameState,
isMapOpen,
characterChatModalOpen,
hasNpcModalOpen,
} = params;
const [selectionStage, setRawSelectionStage] = useState<SelectionStage>(() =>
resolveSelectionStageFromPath(window.location.pathname),
);
const [overlayPanel, setOverlayPanel] = useState<OverlayPanel>(null);
const [selectedSceneEntity, setSelectedSceneEntity] =
useState<GameCanvasEntitySelection | null>(null);
const [showTeamModal, setShowTeamModal] = useState(false);
const setSelectionStage = useCallback((stage: SelectionStage) => {
setRawSelectionStage(stage);
pushAppHistoryPath(resolvePathForSelectionStage(stage));
}, []);
useEffect(() => {
const syncStageFromHistory = () => {
setRawSelectionStage(
resolveSelectionStageFromPath(window.location.pathname),
);
};
window.addEventListener('popstate', syncStageFromHistory);
return () => window.removeEventListener('popstate', syncStageFromHistory);
}, []);
useEffect(() => {
setSelectedSceneEntity(null);
}, [gameState.currentScenePreset?.id, gameState.playerCharacter?.id]);
const shouldMountAdventureEntityModal = useLazyModalMount(
Boolean(selectedSceneEntity),
);
const shouldMountCampModal = useLazyModalMount(showTeamModal);
const shouldMountMapModal = useLazyModalMount(isMapOpen);
const shouldMountCharacterChatModal = useLazyModalMount(
characterChatModalOpen,
);
const shouldMountNpcModals = useLazyModalMount(hasNpcModalOpen);
const openOverlayPanel = (panel: Exclude<OverlayPanel, null>) => {
setSelectedSceneEntity(null);
setOverlayPanel(panel);
};
const closeOverlayPanel = () => setOverlayPanel(null);
const openPartyMemberDetails = (selection: GameCanvasEntitySelection) =>
setSelectedSceneEntity(selection);
const closeAdventureEntityModal = () => setSelectedSceneEntity(null);
const openCampModal = () => setShowTeamModal(true);
const closeCampModal = () => setShowTeamModal(false);
const resetSelectionFlow = () => setSelectionStage('platform');
const resetForSaveAndExit = () => {
setSelectedSceneEntity(null);
setOverlayPanel(null);
setShowTeamModal(false);
setSelectionStage('platform');
};
return {
selectionStage,
setSelectionStage,
resetSelectionFlow,
overlayPanel,
openOverlayPanel,
closeOverlayPanel,
selectedSceneEntity,
setSelectedSceneEntity,
openPartyMemberDetails,
closeAdventureEntityModal,
showTeamModal,
openCampModal,
closeCampModal,
resetForSaveAndExit,
shouldMountAdventureEntityModal,
shouldMountCampModal,
shouldMountMapModal,
shouldMountCharacterChatModal,
shouldMountNpcModals,
};
}