Compare commits
1 Commits
feature/ma
...
feature/so
| Author | SHA1 | Date | |
|---|---|---|---|
| f7108d50bb |
@@ -7,6 +7,8 @@
|
||||
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { getCategoryIcon } from "./editors/toolboxIcons"
|
||||
import { useVolume } from "../../hooks/useVolume";
|
||||
|
||||
export default function GameFooter({
|
||||
gameConfig,
|
||||
@@ -17,6 +19,8 @@ export default function GameFooter({
|
||||
const totalPhases = gameConfig.fases.length;
|
||||
const displayPhase = currentPhase ?? currentPhase;
|
||||
|
||||
const { isMuted, toggleVolume } = useVolume();
|
||||
|
||||
const ajuda = () => {
|
||||
if (onHelpClick) {
|
||||
onHelpClick();
|
||||
@@ -25,11 +29,12 @@ export default function GameFooter({
|
||||
alert("Recurso de ajuda será implementado em breve!");
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className="bg-gray-900">
|
||||
<div className="flex items-center justify-between px-6 py-3">
|
||||
{/* Lado esquerdo - Botão de Ajuda (desativado temporariamente, tour será reimplementado) */}
|
||||
<div className="flex items-center invisible">
|
||||
{/*<div className="flex items-center invisible">
|
||||
<button
|
||||
onClick={ajuda}
|
||||
data-tour="help-button"
|
||||
@@ -38,7 +43,19 @@ export default function GameFooter({
|
||||
>
|
||||
Ajuda
|
||||
</button>
|
||||
</div>*/}
|
||||
<div className="flex items-center">
|
||||
<button
|
||||
onClick={toggleVolume}
|
||||
data-tour="volume-button"
|
||||
title={isMuted ? "Ligar som" : "Desligar som"}
|
||||
aria-label={isMuted ? "Ligar som" : "Desligar som"}
|
||||
className="bg-green-100 text-black font-medium py-2 px-6 lg:py-3 lg:px-9 text-sm lg:text-base rounded-full transition-all duration-200 hover:bg-green-200 hover:scale-105 hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-green-300"
|
||||
>
|
||||
<i className={isMuted ? getCategoryIcon("Volume Desligado") : getCategoryIcon("Volume Ligado")} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Centro - Indicador de Fase Atual/Total */}
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="phase-indicator">
|
||||
|
||||
@@ -42,6 +42,10 @@ export function getCategoryIcon(name) {
|
||||
return "fa fa-database";
|
||||
case "Caneta":
|
||||
return "fa fa-pencil-alt";
|
||||
case "Volume Desligado":
|
||||
return "fa-solid fa-volume-xmark";
|
||||
case "Volume Ligado":
|
||||
return "fa-solid fa-volume-high";
|
||||
default:
|
||||
return "fa fa-cube";
|
||||
}
|
||||
|
||||
23
app/src/hooks/useVolume.js
Normal file
23
app/src/hooks/useVolume.js
Normal file
@@ -0,0 +1,23 @@
|
||||
// hooks/useVolume.js
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { gameEventBus } from "../utils/gameEvents";
|
||||
|
||||
const STORAGE_KEY = "decoda-volume-muted";
|
||||
|
||||
export function useVolume() {
|
||||
const [isMuted, setIsMuted] = useState(() => localStorage.getItem(STORAGE_KEY) === "true");
|
||||
|
||||
useEffect(() => {
|
||||
gameEventBus.setVolumeMuted(isMuted);
|
||||
}, [isMuted]);
|
||||
|
||||
const toggleVolume = useCallback(() => {
|
||||
setIsMuted((prev) => {
|
||||
const next = !prev;
|
||||
localStorage.setItem(STORAGE_KEY, String(next));
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
return { isMuted, toggleVolume };
|
||||
}
|
||||
@@ -49,14 +49,26 @@ export function setupGameController(
|
||||
}
|
||||
};
|
||||
|
||||
const setVolumeMutedHandler = (event) => {
|
||||
if (scene.game?.sound) {
|
||||
scene.game.sound.mute = event.detail.muted;
|
||||
}
|
||||
};
|
||||
|
||||
gameEventBus.addEventListener("executeCode", executeCodeHandler);
|
||||
gameEventBus.addEventListener("resetGame", resetGameHandler);
|
||||
gameEventBus.addEventListener("stopExecution", stopExecutionHandler);
|
||||
gameEventBus.addEventListener("setVolumeMuted", setVolumeMutedHandler);
|
||||
|
||||
if (scene.game?.sound) {
|
||||
scene.game.sound.mute = localStorage.getItem("decoda-volume-muted") === "true";
|
||||
}
|
||||
|
||||
const cleanup = () => {
|
||||
gameEventBus.removeEventListener("executeCode", executeCodeHandler);
|
||||
gameEventBus.removeEventListener("resetGame", resetGameHandler);
|
||||
gameEventBus.removeEventListener("stopExecution", stopExecutionHandler);
|
||||
gameEventBus.removeEventListener("setVolumeMuted", setVolumeMutedHandler);
|
||||
};
|
||||
|
||||
scene._cleanupController = cleanup;
|
||||
|
||||
@@ -86,6 +86,15 @@ describe("gameEventBus", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("setVolumeMuted", () => {
|
||||
it("dispatches setVolumeMuted with muted flag in detail", async () => {
|
||||
const p = listenOnce("setVolumeMuted");
|
||||
gameEventBus.setVolumeMuted(true);
|
||||
const e = await p;
|
||||
expect(e.detail).toEqual({ muted: true });
|
||||
});
|
||||
});
|
||||
|
||||
it("is an EventTarget", () => {
|
||||
expect(gameEventBus).toBeInstanceOf(EventTarget);
|
||||
});
|
||||
|
||||
@@ -103,6 +103,18 @@ class GameEventBus extends EventTarget {
|
||||
stopExecution() {
|
||||
this.dispatchEvent(new CustomEvent("stopExecution"));
|
||||
}
|
||||
|
||||
/**
|
||||
* React define mute global do áudio no Phaser.
|
||||
*
|
||||
* @function setVolumeMuted
|
||||
* @param {boolean} muted - `true` para silenciar, `false` para restaurar
|
||||
* @returns {void}
|
||||
* @fires setVolumeMuted - CustomEvent com detail: { muted }
|
||||
*/
|
||||
setVolumeMuted(muted) {
|
||||
this.dispatchEvent(new CustomEvent("setVolumeMuted", { detail: { muted } }));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user