Skip to content

Commit

Permalink
Merge pull request #125 from Arquisoft/accessibility
Browse files Browse the repository at this point in the history
Accessibility
  • Loading branch information
Mister-Mario authored Apr 27, 2024
2 parents 045b984 + 96bf3a8 commit 40e18d3
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 91 deletions.
4 changes: 2 additions & 2 deletions webapp/src/components/GameConfigurator/GameConfigurator.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ function GameConfigurator(){
<h1>{t("gameConfigurator.game_config")}</h1>
<h2>{t("gameConfigurator.custo_game")}</h2>
<ButtonRandomizeCustom t={t} handleClick={handleClickRandomize} />
<label for="select">{t("gameConfigurator.type_quest")}</label>
<label htmlFor="select">{t("gameConfigurator.type_quest")}</label>
<select id="select" className="select-style" value={tipoPregunta} onChange={(e) => setTipoPregunta(e.target.value)}>
<option value="ALL">{t("gameConfigurator.option_all")}</option>
<option value="POPULATION">{t("gameConfigurator.option_population")}</option>
Expand All @@ -52,7 +52,7 @@ function GameConfigurator(){
<br></br>
<ButtonCustomized t={t} handleClick={handleClick}/>
<br></br>
<hr class="hr-style"></hr>
<hr className="hr-style"></hr>
<br></br>
<h2>{t("gameConfigurator.competi_game")}</h2>
<p>{t("gameConfigurator.rules_competi")}</p>
Expand Down
18 changes: 17 additions & 1 deletion webapp/src/components/Instructions.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,30 @@ function Instructions() {
{t("instructions.time_limit_p1")}
</li></ul>
</article>
<article>
<ul className='ins_ul'><p>{t("instructions.voice")}</p>
<li>
{t("instructions.voice_p1")}
</li>
<li>
{t("instructions.voice_p2")}
</li>
<li>
{t("instructions.voice_p3")}
</li>
<li>
{t("instructions.voice_p4")}
</li>
</ul>
</article>
<article>
<ul className='ins_ul'><p>{t("instructions.have_fun")}</p>
<li>
{t("instructions.have_fun_p1")}
</li>
</ul>
</article>

</section>

</div>
Expand Down
37 changes: 21 additions & 16 deletions webapp/src/components/fragments/NavBar.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import { Link, useLocation } from 'react-router-dom';
import MenuItem from '@mui/material/MenuItem';
import Menu from '@mui/material/Menu';
import "../../custom.css";
Expand All @@ -10,6 +10,7 @@ import Cookies from 'js-cookie';

function Navbar() {
const navigate = useNavigate();
const location = useLocation();
const [t, i18n] = useTranslation("global");
const [anchorLanguage, setAnchorLanguage] = useState(null);
const [anchorUser, setAnchorUser] = useState(null);
Expand Down Expand Up @@ -41,16 +42,21 @@ function Navbar() {
handleLanguageMenuClose();
};

const isQuestionsPage = location.pathname === '/questions';

return (
<div className="navbar-container">
<div className='left-nav'>
<Profile />
<Link to="/home" className="home-button">
<h1 className='navbar-text'>{t("navBar.title")}</h1>
</Link>
<Profile />
<Link to="/home" className="home-button">
<h1 className='navbar-text'>{t("navBar.title")}</h1>
</Link>
</div>
<div className='right-nav'>
<button className="language-button" onClick={handleLanguageMenuOpen}>{t("navBar.language")}</button>
{/* If /questions, disable button*/}
{!isQuestionsPage && (
<button className="language-button" onClick={handleLanguageMenuOpen}>{t("navBar.language")}</button>
)}
<Menu
anchorEl={anchorLanguage}
open={Boolean(anchorLanguage)}
Expand All @@ -65,18 +71,17 @@ function Navbar() {

{Cookies.get('user') ? (
<>
<button className="user-button" onClick={handleUserMenuOpen}>{ JSON.parse(Cookies.get('user')).username}</button>
<Menu
anchorEl={anchorUser}
open={Boolean(anchorUser)}
onClose={handleUserMenuClose}
disableAutoFocusItem
>
<MenuItem id="logout" onClick={() => removeCookie()}> {t("navBar.logout")}</MenuItem>
</Menu>
<button className="user-button" onClick={handleUserMenuOpen}>{ JSON.parse(Cookies.get('user')).username}</button>
<Menu
anchorEl={anchorUser}
open={Boolean(anchorUser)}
onClose={handleUserMenuClose}
disableAutoFocusItem
>
<MenuItem id="logout" onClick={() => removeCookie()}> {t("navBar.logout")}</MenuItem>
</Menu>
</>
) : null}

</div>
</div>
);
Expand Down
48 changes: 24 additions & 24 deletions webapp/src/components/questionView/QuestionGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,33 @@ class QuestionGenerator{
}

async generateQuestions(lang, type, amount, token) {
/*
try {
//const response = await fetch(this.apiUrl);
//const receivedQuestions = await response.json();

// try {
// //const response = await fetch(this.apiUrl);
// //const receivedQuestions = await response.json();

//Mockup
console.log("type: "+type+" amount: "+amount)
const receivedQuestions = JSON.parse('{"0":{"question":"¿Cuál es la población de Oviedo?","answers":["225089","191325","220587","121548"]},'+
'"1":{"question":"¿Cuál es la población de Gijón?","answers":["275274","159658","233982","305554"]},'+
'"2":{"question":"¿Cuál es la población de Avilés?","answers":["82568","115595","41284","122200"]},'+
'"3":{"question":"¿Cuál es la capital de Asturias?","answers":["Ciudad de Oviedo","a","b","c"]},'+
'"4":{"question":"¿Cuál es la capital de España?","answers":["Madrid","a","b","c"]},'+
'"5":{"question":"¿Cuál es la capital de Turquía?","answers":["Ankara","a","b","c"]}}')
// //Mockup
// console.log("type: "+type+" amount: "+amount)
// const receivedQuestions = JSON.parse('{"0":{"question":"What is the population of Oviedo?","answers":["225089","191325","220587","121548"]},'+
// '"1":{"question":"¿Cuál es la población de Gijón?","answers":["275274","159658","233982","305554"]},'+
// '"2":{"question":"¿Cuál es la población de Avilés?","answers":["82568","115595","41284","122200"]},'+
// '"3":{"question":"¿Cuál es la capital de Asturias?","answers":["Ciudad de Oviedo","a","b","c"]},'+
// '"4":{"question":"¿Cuál es la capital de España?","answers":["Madrid","a","b","c"]},'+
// '"5":{"question":"¿Cuál es la capital de Turquía?","answers":["Ankara","a","b","c"]}}')

let i = 0;
var questions = [];
for (const key in receivedQuestions) {
questions[i] = new Question(receivedQuestions[key]);
i += 1;
}
console.log(questions);
return questions;
} catch (error) {
throw new Error(error);
}
// let i = 0;
// var questions = [];
// for (const key in receivedQuestions) {
// questions[i] = new Question(receivedQuestions[key]);
// i += 1;
// }
// console.log(questions);
// return questions;
// } catch (error) {
// throw new Error(error);
// }


*/

try {
let response;
Expand Down
107 changes: 71 additions & 36 deletions webapp/src/components/questionView/QuestionView.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import QuestionGenerator from './QuestionGenerator';
import CreationHistoricalRecord from './CreationHistoricalRecord';
import { useState } from 'react';
import "../../custom.css";
import React from "react";
import React, { useState, useEffect, useCallback } from "react";
import Countdown from 'react-countdown';
import {useTranslation} from "react-i18next";
import $ from 'jquery';
Expand All @@ -21,8 +20,7 @@ function QuestionView({type= "COMPETITIVE", amount=5}){
const [questions, setQuestions] = useState(null);
const[t, i18n] = useTranslation("global");
const cookie = JSON.parse(Cookies.get('user')??JSON.stringify({username : playAsGuestUsername, token : playAsGuestToken}))
const [audio] = useState(new Audio('/tictac.mp3'));



const generateQuestions = async (numQuestion) => {
if (numQuestion < 0) {
Expand Down Expand Up @@ -56,11 +54,9 @@ function QuestionView({type= "COMPETITIVE", amount=5}){
});
}
if(answerGiven===correctAnswer){
audio.pause();
audioCorrect.play(); // Reproduce el sonido de respuesta incorrecta
}
else{
audio.pause();
audioIncorrect.play(); // Reproduce el sonido de respuesta correcta
}
$(this).css('pointer-events', 'none');
Expand All @@ -80,15 +76,17 @@ function QuestionView({type= "COMPETITIVE", amount=5}){
function computePointsForQuestion(correctAnswer, answerGiven){
if(answerGiven===correctAnswer){
points+=100;
audio.pause();
}else if(points-50>=0){
points-=50;
audio.pause();
}else{
points = 0;
}
}
function handleClick(text){
// Detener el síntesis de voz
if(window.speechSynthesis.speaking)
window.speechSynthesis.cancel();

//create the record to record the response
creationHistoricalRecord.addQuestion(questions[numQuestion].getQuestion(),
questions[numQuestion].getAnswers(),
Expand All @@ -108,7 +106,6 @@ function QuestionView({type= "COMPETITIVE", amount=5}){

//Last question sends the record
if(!(numQuestion < questions.length - 1)){
audio.pause();
creationHistoricalRecord.setCompetitive(type === 'COMPETITIVE');
creationHistoricalRecord.setDate(Date.now());
creationHistoricalRecord.setPoints(points);
Expand All @@ -118,41 +115,28 @@ function QuestionView({type= "COMPETITIVE", amount=5}){
}, 1000);

}

if(questions === null)
generateQuestions(numQuestion)


return (
<div className="question-view-container">
{numQuestion >= 0 ?
<QuestionComponent t={t} questions={questions} numQuestion={numQuestion} handleClick={handleClick} points={points} audio = {audio} language={i18n.language}/> :
<QuestionComponent t={t} questions={questions} numQuestion={numQuestion} handleClick={handleClick} points={points} language={i18n.language}/> :
<h1>{t("questionView.no_questions_message")}</h1> }
</div>);
}

function QuestionComponent({questions, numQuestion, handleClick, t, points, audio, language}){
function QuestionComponent({questions, numQuestion, handleClick, t, points, language}){


const speakQuestion = () => {
const speech = new SpeechSynthesisUtterance();
speech.lang = language;
console.log(language);
getVoicesForLanguage(language)
.then(voices => {
// const voice = voices.find(voice => voice.lang === language);
// speech.voice = voice || voices[0]; // If there is no voice for the lang, choose the first one
window.speechSynthesis.speak(speech);
})
.catch(error => {
console.error("Error al obtener las voces para el idioma:", error);
});
};

// Función para obtener las voces disponibles para un idioma
const getVoicesForLanguage = (language) => {
// To obtain available voices for language
const getVoicesForLanguage = useCallback((language) => {
return new Promise((resolve, reject) => {
const speech = new SpeechSynthesisUtterance();

//speaks the question
speech.text = questions[numQuestion].getQuestion();
speech.lang = language;

Expand All @@ -166,19 +150,70 @@ function QuestionComponent({questions, numQuestion, handleClick, t, points, audi

window.speechSynthesis.speak(speech);
});
};
},[questions, numQuestion]);

const speakAnswers = useCallback((answers) => {
const speech = new SpeechSynthesisUtterance();
speech.lang = language;
let concatenatedAnswers = Array.isArray(answers) ? answers.map((answer, index) => `${index + 1}. ${answer}`).join(". ") : '';

getVoicesForLanguage(language)
.then(voices => {
// const voice = voices.find(voice => voice.lang === language);
// speech.voice = voice || voices[0]; // If there is no voice for the lang, choose the first one
speech.text = concatenatedAnswers;
window.speechSynthesis.speak(speech);
})
.catch(error => {
console.error("Error al obtener las voces para el idioma:", error);
});
}, [getVoicesForLanguage, language]);

const speak = useCallback(() => {
speakAnswers(questions[numQuestion].getAnswers());
}, [numQuestion, questions, speakAnswers]);

useEffect(() => {
const handleKeyPress = (event) => {
if (event.key === 's') {
speak();
} else {
const answerIndex = parseInt(event.key) - 1;
if (!isNaN(answerIndex) && answerIndex >= 0 && answerIndex < questions[numQuestion].getAnswers().length) {

handleClick(questions[numQuestion].getAnswers()[answerIndex]);
}
}
};

window.addEventListener("keypress", handleKeyPress);

return () => {
window.removeEventListener("keypress", handleKeyPress);
};
}, [speak, numQuestion, questions, handleClick]);

//To stop the voice when changing of page
useEffect(() => {
const handleBeforeUnload = () => {
if(window.speechSynthesis.speaking)
window.speechSynthesis.cancel();
};

window.addEventListener("beforeunload", handleBeforeUnload);

return () => {
window.removeEventListener("beforeunload", handleBeforeUnload);
};
}, []);



const renderer = ({seconds, completed }) => {
if (completed) {
audio.pause();
return <span>{t("questionView.end_countdown")}</span>; // Rendered when countdown completes
} else {
if (audio.paused) {
audio.loop = true; // Loop of tiktak
audio.play();
}

return <span>{seconds} {t("questionView.seconds")}</span>; // Render countdown
}
};
Expand All @@ -189,7 +224,7 @@ function QuestionComponent({questions, numQuestion, handleClick, t, points, audi
<div className='questionContainer'>

<div className='topPanel'>
<h2>{questions[numQuestion].getQuestion()} <button className="altavoz" onClick={speakQuestion}>🔊</button></h2>
<h2>{questions[numQuestion].getQuestion()} <button className="altavoz" onClick={speak}>🔊</button></h2>
<div className="countdown">
<Countdown key={numQuestion} date={Date.now()+10000} renderer={renderer} onComplete={handleClick.bind(this,"no-answer")} />
</div>
Expand Down
5 changes: 3 additions & 2 deletions webapp/src/components/questionView/QuestionView.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ describe('Question View component', () => {
expect(correctAnswerButton).toHaveStyle('background-color: #6EF26E');
}, { timeout: 1000 }); // Esperar 1 segundo
});

it('shows colors to reveal false answer and it sounds', async () => {
setupAudioMock()
await act(async () =>{
Expand All @@ -130,14 +131,14 @@ describe('Question View component', () => {
}, { timeout: 1000 }); // Esperar 1 segundo
});

it('shows timer and tiktak sound', async () => {
it('shows timer', async () => {
setupAudioMock()
await act(async () =>{
await render(<MemoryRouter><QuestionView /></MemoryRouter>);

})
await waitFor(() => expect(screen.getByText('What is the population of Oviedo?')).toBeInTheDocument());
expect(global.Audio).toHaveBeenCalledWith('/tictac.mp3');
// expect(global.Audio).toHaveBeenCalledWith('/tictac.mp3');

const timerElement = screen.getByText(new RegExp(`(\\d+) ${i18en.t('questionView.seconds')}`));
expect(timerElement).toBeInTheDocument(); // Verificar que el temporizador esté presente en el DOM
Expand Down
Loading

0 comments on commit 40e18d3

Please sign in to comment.