Skip to content

Commit 40e18d3

Browse files
authored
Merge pull request #125 from Arquisoft/accessibility
Accessibility
2 parents 045b984 + 96bf3a8 commit 40e18d3

File tree

9 files changed

+164
-91
lines changed

9 files changed

+164
-91
lines changed

Diff for: webapp/src/components/GameConfigurator/GameConfigurator.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ function GameConfigurator(){
3030
<h1>{t("gameConfigurator.game_config")}</h1>
3131
<h2>{t("gameConfigurator.custo_game")}</h2>
3232
<ButtonRandomizeCustom t={t} handleClick={handleClickRandomize} />
33-
<label for="select">{t("gameConfigurator.type_quest")}</label>
33+
<label htmlFor="select">{t("gameConfigurator.type_quest")}</label>
3434
<select id="select" className="select-style" value={tipoPregunta} onChange={(e) => setTipoPregunta(e.target.value)}>
3535
<option value="ALL">{t("gameConfigurator.option_all")}</option>
3636
<option value="POPULATION">{t("gameConfigurator.option_population")}</option>
@@ -52,7 +52,7 @@ function GameConfigurator(){
5252
<br></br>
5353
<ButtonCustomized t={t} handleClick={handleClick}/>
5454
<br></br>
55-
<hr class="hr-style"></hr>
55+
<hr className="hr-style"></hr>
5656
<br></br>
5757
<h2>{t("gameConfigurator.competi_game")}</h2>
5858
<p>{t("gameConfigurator.rules_competi")}</p>

Diff for: webapp/src/components/Instructions.js

+17-1
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,30 @@ function Instructions() {
5050
{t("instructions.time_limit_p1")}
5151
</li></ul>
5252
</article>
53+
<article>
54+
<ul className='ins_ul'><p>{t("instructions.voice")}</p>
55+
<li>
56+
{t("instructions.voice_p1")}
57+
</li>
58+
<li>
59+
{t("instructions.voice_p2")}
60+
</li>
61+
<li>
62+
{t("instructions.voice_p3")}
63+
</li>
64+
<li>
65+
{t("instructions.voice_p4")}
66+
</li>
67+
</ul>
68+
</article>
5369
<article>
5470
<ul className='ins_ul'><p>{t("instructions.have_fun")}</p>
5571
<li>
5672
{t("instructions.have_fun_p1")}
5773
</li>
5874
</ul>
5975
</article>
60-
76+
6177
</section>
6278

6379
</div>

Diff for: webapp/src/components/fragments/NavBar.js

+21-16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useState } from 'react';
2-
import { Link } from 'react-router-dom';
2+
import { Link, useLocation } from 'react-router-dom';
33
import MenuItem from '@mui/material/MenuItem';
44
import Menu from '@mui/material/Menu';
55
import "../../custom.css";
@@ -10,6 +10,7 @@ import Cookies from 'js-cookie';
1010

1111
function Navbar() {
1212
const navigate = useNavigate();
13+
const location = useLocation();
1314
const [t, i18n] = useTranslation("global");
1415
const [anchorLanguage, setAnchorLanguage] = useState(null);
1516
const [anchorUser, setAnchorUser] = useState(null);
@@ -41,16 +42,21 @@ function Navbar() {
4142
handleLanguageMenuClose();
4243
};
4344

45+
const isQuestionsPage = location.pathname === '/questions';
46+
4447
return (
4548
<div className="navbar-container">
4649
<div className='left-nav'>
47-
<Profile />
48-
<Link to="/home" className="home-button">
49-
<h1 className='navbar-text'>{t("navBar.title")}</h1>
50-
</Link>
50+
<Profile />
51+
<Link to="/home" className="home-button">
52+
<h1 className='navbar-text'>{t("navBar.title")}</h1>
53+
</Link>
5154
</div>
5255
<div className='right-nav'>
53-
<button className="language-button" onClick={handleLanguageMenuOpen}>{t("navBar.language")}</button>
56+
{/* If /questions, disable button*/}
57+
{!isQuestionsPage && (
58+
<button className="language-button" onClick={handleLanguageMenuOpen}>{t("navBar.language")}</button>
59+
)}
5460
<Menu
5561
anchorEl={anchorLanguage}
5662
open={Boolean(anchorLanguage)}
@@ -65,18 +71,17 @@ function Navbar() {
6571

6672
{Cookies.get('user') ? (
6773
<>
68-
<button className="user-button" onClick={handleUserMenuOpen}>{ JSON.parse(Cookies.get('user')).username}</button>
69-
<Menu
70-
anchorEl={anchorUser}
71-
open={Boolean(anchorUser)}
72-
onClose={handleUserMenuClose}
73-
disableAutoFocusItem
74-
>
75-
<MenuItem id="logout" onClick={() => removeCookie()}> {t("navBar.logout")}</MenuItem>
76-
</Menu>
74+
<button className="user-button" onClick={handleUserMenuOpen}>{ JSON.parse(Cookies.get('user')).username}</button>
75+
<Menu
76+
anchorEl={anchorUser}
77+
open={Boolean(anchorUser)}
78+
onClose={handleUserMenuClose}
79+
disableAutoFocusItem
80+
>
81+
<MenuItem id="logout" onClick={() => removeCookie()}> {t("navBar.logout")}</MenuItem>
82+
</Menu>
7783
</>
7884
) : null}
79-
8085
</div>
8186
</div>
8287
);

Diff for: webapp/src/components/questionView/QuestionGenerator.js

+24-24
Original file line numberDiff line numberDiff line change
@@ -9,33 +9,33 @@ class QuestionGenerator{
99
}
1010

1111
async generateQuestions(lang, type, amount, token) {
12-
/*
13-
try {
14-
//const response = await fetch(this.apiUrl);
15-
//const receivedQuestions = await response.json();
12+
13+
// try {
14+
// //const response = await fetch(this.apiUrl);
15+
// //const receivedQuestions = await response.json();
1616

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

26-
let i = 0;
27-
var questions = [];
28-
for (const key in receivedQuestions) {
29-
questions[i] = new Question(receivedQuestions[key]);
30-
i += 1;
31-
}
32-
console.log(questions);
33-
return questions;
34-
} catch (error) {
35-
throw new Error(error);
36-
}
26+
// let i = 0;
27+
// var questions = [];
28+
// for (const key in receivedQuestions) {
29+
// questions[i] = new Question(receivedQuestions[key]);
30+
// i += 1;
31+
// }
32+
// console.log(questions);
33+
// return questions;
34+
// } catch (error) {
35+
// throw new Error(error);
36+
// }
37+
3738

38-
*/
3939

4040
try {
4141
let response;

Diff for: webapp/src/components/questionView/QuestionView.js

+71-36
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import QuestionGenerator from './QuestionGenerator';
22
import CreationHistoricalRecord from './CreationHistoricalRecord';
3-
import { useState } from 'react';
43
import "../../custom.css";
5-
import React from "react";
4+
import React, { useState, useEffect, useCallback } from "react";
65
import Countdown from 'react-countdown';
76
import {useTranslation} from "react-i18next";
87
import $ from 'jquery';
@@ -21,8 +20,7 @@ function QuestionView({type= "COMPETITIVE", amount=5}){
2120
const [questions, setQuestions] = useState(null);
2221
const[t, i18n] = useTranslation("global");
2322
const cookie = JSON.parse(Cookies.get('user')??JSON.stringify({username : playAsGuestUsername, token : playAsGuestToken}))
24-
const [audio] = useState(new Audio('/tictac.mp3'));
25-
23+
2624

2725
const generateQuestions = async (numQuestion) => {
2826
if (numQuestion < 0) {
@@ -56,11 +54,9 @@ function QuestionView({type= "COMPETITIVE", amount=5}){
5654
});
5755
}
5856
if(answerGiven===correctAnswer){
59-
audio.pause();
6057
audioCorrect.play(); // Reproduce el sonido de respuesta incorrecta
6158
}
6259
else{
63-
audio.pause();
6460
audioIncorrect.play(); // Reproduce el sonido de respuesta correcta
6561
}
6662
$(this).css('pointer-events', 'none');
@@ -80,15 +76,17 @@ function QuestionView({type= "COMPETITIVE", amount=5}){
8076
function computePointsForQuestion(correctAnswer, answerGiven){
8177
if(answerGiven===correctAnswer){
8278
points+=100;
83-
audio.pause();
8479
}else if(points-50>=0){
8580
points-=50;
86-
audio.pause();
8781
}else{
8882
points = 0;
8983
}
9084
}
9185
function handleClick(text){
86+
// Detener el síntesis de voz
87+
if(window.speechSynthesis.speaking)
88+
window.speechSynthesis.cancel();
89+
9290
//create the record to record the response
9391
creationHistoricalRecord.addQuestion(questions[numQuestion].getQuestion(),
9492
questions[numQuestion].getAnswers(),
@@ -108,7 +106,6 @@ function QuestionView({type= "COMPETITIVE", amount=5}){
108106

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

120117
}
121-
118+
122119
if(questions === null)
123120
generateQuestions(numQuestion)
124121

125122

126123
return (
127124
<div className="question-view-container">
128125
{numQuestion >= 0 ?
129-
<QuestionComponent t={t} questions={questions} numQuestion={numQuestion} handleClick={handleClick} points={points} audio = {audio} language={i18n.language}/> :
126+
<QuestionComponent t={t} questions={questions} numQuestion={numQuestion} handleClick={handleClick} points={points} language={i18n.language}/> :
130127
<h1>{t("questionView.no_questions_message")}</h1> }
131128
</div>);
132129
}
133130

134-
function QuestionComponent({questions, numQuestion, handleClick, t, points, audio, language}){
131+
function QuestionComponent({questions, numQuestion, handleClick, t, points, language}){
135132

136133

137-
const speakQuestion = () => {
138-
const speech = new SpeechSynthesisUtterance();
139-
speech.lang = language;
140-
console.log(language);
141-
getVoicesForLanguage(language)
142-
.then(voices => {
143-
// const voice = voices.find(voice => voice.lang === language);
144-
// speech.voice = voice || voices[0]; // If there is no voice for the lang, choose the first one
145-
window.speechSynthesis.speak(speech);
146-
})
147-
.catch(error => {
148-
console.error("Error al obtener las voces para el idioma:", error);
149-
});
150-
};
151-
152-
// Función para obtener las voces disponibles para un idioma
153-
const getVoicesForLanguage = (language) => {
134+
// To obtain available voices for language
135+
const getVoicesForLanguage = useCallback((language) => {
154136
return new Promise((resolve, reject) => {
155137
const speech = new SpeechSynthesisUtterance();
138+
139+
//speaks the question
156140
speech.text = questions[numQuestion].getQuestion();
157141
speech.lang = language;
158142

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

167151
window.speechSynthesis.speak(speech);
168152
});
169-
};
153+
},[questions, numQuestion]);
154+
155+
const speakAnswers = useCallback((answers) => {
156+
const speech = new SpeechSynthesisUtterance();
157+
speech.lang = language;
158+
let concatenatedAnswers = Array.isArray(answers) ? answers.map((answer, index) => `${index + 1}. ${answer}`).join(". ") : '';
159+
160+
getVoicesForLanguage(language)
161+
.then(voices => {
162+
// const voice = voices.find(voice => voice.lang === language);
163+
// speech.voice = voice || voices[0]; // If there is no voice for the lang, choose the first one
164+
speech.text = concatenatedAnswers;
165+
window.speechSynthesis.speak(speech);
166+
})
167+
.catch(error => {
168+
console.error("Error al obtener las voces para el idioma:", error);
169+
});
170+
}, [getVoicesForLanguage, language]);
171+
172+
const speak = useCallback(() => {
173+
speakAnswers(questions[numQuestion].getAnswers());
174+
}, [numQuestion, questions, speakAnswers]);
175+
176+
useEffect(() => {
177+
const handleKeyPress = (event) => {
178+
if (event.key === 's') {
179+
speak();
180+
} else {
181+
const answerIndex = parseInt(event.key) - 1;
182+
if (!isNaN(answerIndex) && answerIndex >= 0 && answerIndex < questions[numQuestion].getAnswers().length) {
183+
184+
handleClick(questions[numQuestion].getAnswers()[answerIndex]);
185+
}
186+
}
187+
};
188+
189+
window.addEventListener("keypress", handleKeyPress);
190+
191+
return () => {
192+
window.removeEventListener("keypress", handleKeyPress);
193+
};
194+
}, [speak, numQuestion, questions, handleClick]);
195+
196+
//To stop the voice when changing of page
197+
useEffect(() => {
198+
const handleBeforeUnload = () => {
199+
if(window.speechSynthesis.speaking)
200+
window.speechSynthesis.cancel();
201+
};
202+
203+
window.addEventListener("beforeunload", handleBeforeUnload);
204+
205+
return () => {
206+
window.removeEventListener("beforeunload", handleBeforeUnload);
207+
};
208+
}, []);
170209

171210

172211

173212
const renderer = ({seconds, completed }) => {
174213
if (completed) {
175-
audio.pause();
176214
return <span>{t("questionView.end_countdown")}</span>; // Rendered when countdown completes
177215
} else {
178-
if (audio.paused) {
179-
audio.loop = true; // Loop of tiktak
180-
audio.play();
181-
}
216+
182217
return <span>{seconds} {t("questionView.seconds")}</span>; // Render countdown
183218
}
184219
};
@@ -189,7 +224,7 @@ function QuestionComponent({questions, numQuestion, handleClick, t, points, audi
189224
<div className='questionContainer'>
190225

191226
<div className='topPanel'>
192-
<h2>{questions[numQuestion].getQuestion()} <button className="altavoz" onClick={speakQuestion}>🔊</button></h2>
227+
<h2>{questions[numQuestion].getQuestion()} <button className="altavoz" onClick={speak}>🔊</button></h2>
193228
<div className="countdown">
194229
<Countdown key={numQuestion} date={Date.now()+10000} renderer={renderer} onComplete={handleClick.bind(this,"no-answer")} />
195230
</div>

Diff for: webapp/src/components/questionView/QuestionView.test.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ describe('Question View component', () => {
112112
expect(correctAnswerButton).toHaveStyle('background-color: #6EF26E');
113113
}, { timeout: 1000 }); // Esperar 1 segundo
114114
});
115+
115116
it('shows colors to reveal false answer and it sounds', async () => {
116117
setupAudioMock()
117118
await act(async () =>{
@@ -130,14 +131,14 @@ describe('Question View component', () => {
130131
}, { timeout: 1000 }); // Esperar 1 segundo
131132
});
132133

133-
it('shows timer and tiktak sound', async () => {
134+
it('shows timer', async () => {
134135
setupAudioMock()
135136
await act(async () =>{
136137
await render(<MemoryRouter><QuestionView /></MemoryRouter>);
137138

138139
})
139140
await waitFor(() => expect(screen.getByText('What is the population of Oviedo?')).toBeInTheDocument());
140-
expect(global.Audio).toHaveBeenCalledWith('/tictac.mp3');
141+
// expect(global.Audio).toHaveBeenCalledWith('/tictac.mp3');
141142

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

0 commit comments

Comments
 (0)