Compare commits
1 Commits
83a0c96d95
...
feature/so
| Author | SHA1 | Date | |
|---|---|---|---|
| f7108d50bb |
@@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
import { getCategoryIcon } from "./editors/toolboxIcons"
|
||||||
|
import { useVolume } from "../../hooks/useVolume";
|
||||||
|
|
||||||
export default function GameFooter({
|
export default function GameFooter({
|
||||||
gameConfig,
|
gameConfig,
|
||||||
@@ -17,6 +19,8 @@ export default function GameFooter({
|
|||||||
const totalPhases = gameConfig.fases.length;
|
const totalPhases = gameConfig.fases.length;
|
||||||
const displayPhase = currentPhase ?? currentPhase;
|
const displayPhase = currentPhase ?? currentPhase;
|
||||||
|
|
||||||
|
const { isMuted, toggleVolume } = useVolume();
|
||||||
|
|
||||||
const ajuda = () => {
|
const ajuda = () => {
|
||||||
if (onHelpClick) {
|
if (onHelpClick) {
|
||||||
onHelpClick();
|
onHelpClick();
|
||||||
@@ -25,11 +29,12 @@ export default function GameFooter({
|
|||||||
alert("Recurso de ajuda será implementado em breve!");
|
alert("Recurso de ajuda será implementado em breve!");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-gray-900">
|
<div className="bg-gray-900">
|
||||||
<div className="flex items-center justify-between px-6 py-3">
|
<div className="flex items-center justify-between px-6 py-3">
|
||||||
{/* Lado esquerdo - Botão de Ajuda (desativado temporariamente, tour será reimplementado) */}
|
{/* Lado esquerdo - Botão de Ajuda (desativado temporariamente, tour será reimplementado) */}
|
||||||
<div className="flex items-center invisible">
|
{/*<div className="flex items-center invisible">
|
||||||
<button
|
<button
|
||||||
onClick={ajuda}
|
onClick={ajuda}
|
||||||
data-tour="help-button"
|
data-tour="help-button"
|
||||||
@@ -38,7 +43,19 @@ export default function GameFooter({
|
|||||||
>
|
>
|
||||||
Ajuda
|
Ajuda
|
||||||
</button>
|
</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>
|
</div>
|
||||||
|
|
||||||
{/* Centro - Indicador de Fase Atual/Total */}
|
{/* Centro - Indicador de Fase Atual/Total */}
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<div className="phase-indicator">
|
<div className="phase-indicator">
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ export function getCategoryIcon(name) {
|
|||||||
return "fa fa-database";
|
return "fa fa-database";
|
||||||
case "Caneta":
|
case "Caneta":
|
||||||
return "fa fa-pencil-alt";
|
return "fa fa-pencil-alt";
|
||||||
|
case "Volume Desligado":
|
||||||
|
return "fa-solid fa-volume-xmark";
|
||||||
|
case "Volume Ligado":
|
||||||
|
return "fa-solid fa-volume-high";
|
||||||
default:
|
default:
|
||||||
return "fa fa-cube";
|
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("executeCode", executeCodeHandler);
|
||||||
gameEventBus.addEventListener("resetGame", resetGameHandler);
|
gameEventBus.addEventListener("resetGame", resetGameHandler);
|
||||||
gameEventBus.addEventListener("stopExecution", stopExecutionHandler);
|
gameEventBus.addEventListener("stopExecution", stopExecutionHandler);
|
||||||
|
gameEventBus.addEventListener("setVolumeMuted", setVolumeMutedHandler);
|
||||||
|
|
||||||
|
if (scene.game?.sound) {
|
||||||
|
scene.game.sound.mute = localStorage.getItem("decoda-volume-muted") === "true";
|
||||||
|
}
|
||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
gameEventBus.removeEventListener("executeCode", executeCodeHandler);
|
gameEventBus.removeEventListener("executeCode", executeCodeHandler);
|
||||||
gameEventBus.removeEventListener("resetGame", resetGameHandler);
|
gameEventBus.removeEventListener("resetGame", resetGameHandler);
|
||||||
gameEventBus.removeEventListener("stopExecution", stopExecutionHandler);
|
gameEventBus.removeEventListener("stopExecution", stopExecutionHandler);
|
||||||
|
gameEventBus.removeEventListener("setVolumeMuted", setVolumeMutedHandler);
|
||||||
};
|
};
|
||||||
|
|
||||||
scene._cleanupController = cleanup;
|
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", () => {
|
it("is an EventTarget", () => {
|
||||||
expect(gameEventBus).toBeInstanceOf(EventTarget);
|
expect(gameEventBus).toBeInstanceOf(EventTarget);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -103,6 +103,18 @@ class GameEventBus extends EventTarget {
|
|||||||
stopExecution() {
|
stopExecution() {
|
||||||
this.dispatchEvent(new CustomEvent("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