122 lines
3.6 KiB
TypeScript
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,
|
|
};
|
|
}
|