Files
decoda/app/src/components/game/GameBase.jsx
2026-06-05 00:11:45 -03:00

214 lines
5.7 KiB
JavaScript

/**
* @fileoverview React component for GameBase.jsx
*
* @module components.game.GameBase
*/
import React from "react";
import PropTypes from "prop-types";
import { Panel, PanelGroup } from "react-resizable-panels";
import GameNavBar from "./GameNavBar";
import GameFaseInfo from "./GameFaseInfo";
import GameArea from "./GameArea";
import GameFooter from "./GameFooter";
import SeletorDeFases from "./SeletorDeFases";
import SucessoModal from "./SucessoModal";
import FalhaModal from "./FalhaModal";
import ResizeHandle from "./ResizeHandle";
import { useIsMobile } from "../../hooks/useIsMobile";
import { useGameState } from "../../contexts/GameStateContext";
import { EditorProvider } from "../../contexts/EditorContext";
import { usePhaser } from "../../hooks/usePhaser";
import { useGameModals } from "../../hooks/useGameModals";
function GameBaseContent({
gameFactory,
gameConfig,
children,
onHelpClick,
customFailureHandler,
}) {
const isMobile = useIsMobile();
const {
currentPhase,
setCurrentPhase,
resetProgress,
executionState,
generatedCode,
failureMessage,
restart,
setOnWorkspaceChange,
} = useGameState();
const phaseConfig = gameConfig.fases[currentPhase - 1];
const usaModalFalha = !!customFailureHandler;
const { gameContainerRef } = usePhaser({
gameFactory,
phaseConfig,
currentPhase,
customFailureHandler,
gameConfig,
});
const {
modalFasesAberto,
setModalFasesAberto,
modalSucessoAberto,
modalFalhaAberto,
blocksRemainingCount,
handleProximaFase,
handleFecharModalSucesso,
handleFecharModalFalha,
handleTentarNovamente,
} = useGameModals({
executionState,
currentPhase,
phaseConfig,
gameConfig,
setCurrentPhase,
restart,
setOnWorkspaceChange,
usaModalFalha,
});
const handleResetProgresso = () => {
resetProgress();
window.dispatchEvent(
new CustomEvent("resetBlocklyWorkspace", {
detail: { gameId: gameConfig.gameId },
}),
);
};
const codeToShow = React.useMemo(() => {
if (!generatedCode) return "Nenhum código gerado";
let codigo = "";
if (typeof generatedCode === "string") {
codigo = generatedCode;
} else if (typeof generatedCode === "object" && generatedCode.codigo) {
codigo = generatedCode.codigo;
} else {
return "Código não disponível";
}
const codigoLimpo = codigo
.split("\n")
.filter((linha) => !linha.trim().startsWith("highlightBlock("))
.join("\n")
.trim();
return codigoLimpo || codigo;
}, [generatedCode]);
return (
<div className="game-base-page flex flex-col h-screen w-screen">
<GameNavBar
title={`${gameConfig.gameName}`}
type={`${gameConfig.type}`}
thumbnail={gameConfig.thumbnail}
icon={gameConfig.icon}
/>
<GameFaseInfo phaseData={phaseConfig} phaseNumber={currentPhase} />
<div className="flex-1 min-h-0 flex flex-col">
<PanelGroup
direction={isMobile ? "vertical" : "horizontal"}
className="h-full w-full"
>
<Panel defaultSize={isMobile ? 48 : 48} minSize={isMobile ? 10 : 10}>
<EditorProvider gameConfig={gameConfig} currentPhase={currentPhase}>
{children}
</EditorProvider>
</Panel>
<ResizeHandle direction={isMobile ? "vertical" : "horizontal"} />
<Panel defaultSize={isMobile ? 49 : 49} minSize={isMobile ? 10 : 10}>
<GameArea blocksRemaining={blocksRemainingCount} phaseId={currentPhase}>
<div ref={gameContainerRef} className="w-full h-full" />
</GameArea>
</Panel>
</PanelGroup>
</div>
<GameFooter
gameConfig={gameConfig}
currentPhase={currentPhase}
onOpenPhaseSelector={() => setModalFasesAberto(true)}
onHelpClick={onHelpClick}
/>
<SeletorDeFases
isVisible={modalFasesAberto}
onClose={() => setModalFasesAberto(false)}
currentPhase={currentPhase}
gameConfig={gameConfig}
onChangePhase={(fase) => {
setCurrentPhase(fase, "manual_selector");
setModalFasesAberto(false);
}}
onResetProgress={handleResetProgresso}
/>
<SucessoModal
isOpen={modalSucessoAberto}
onClose={handleFecharModalSucesso}
onNextPhase={handleProximaFase}
generatedCode={codeToShow}
currentPhase={currentPhase}
totalPhases={gameConfig.fases.length}
canGoNext={currentPhase < gameConfig.fases.length}
/>
<FalhaModal
isOpen={modalFalhaAberto}
onClose={handleFecharModalFalha}
onRetry={handleTentarNovamente}
customMessage={failureMessage}
currentPhase={currentPhase}
generatedCode={codeToShow}
/>
</div>
);
}
export default function GameBase({
gameFactory,
gameConfig,
children,
onHelpClick,
customFailureHandler,
}) {
return (
<GameBaseContent
gameFactory={gameFactory}
gameConfig={gameConfig}
onHelpClick={onHelpClick}
customFailureHandler={customFailureHandler}
>
{children}
</GameBaseContent>
);
}
GameBase.propTypes = {
gameFactory: PropTypes.func.isRequired,
gameConfig: PropTypes.shape({
gameId: PropTypes.string.isRequired,
gameName: PropTypes.string.isRequired,
fases: PropTypes.array.isRequired,
type: PropTypes.string,
thumbnail: PropTypes.string,
icon: PropTypes.string,
}).isRequired,
children: PropTypes.node.isRequired,
onHelpClick: PropTypes.func,
customFailureHandler: PropTypes.func,
helpHandler: PropTypes.func,
failureHandler: PropTypes.func,
onHelp: PropTypes.func,
title: PropTypes.string,
};