fix: Language selector keep on the page

This commit is contained in:
Samuel Ortion 2024-02-12 09:29:34 +01:00
parent e4a7479f19
commit 150da0f844
15 changed files with 209 additions and 32 deletions

View File

@ -17,19 +17,18 @@ async function region(req, res) {
})
}
async function quizz(req, res) {
debug('Generating quizz');
async function question(req, res) {
debug('Generating question');
const { region } = req.query;
req.session.region = region;
// debug(`Coordinates: ${lat}, ${lng}`);
const locale = req.i18n.locale;
debugLocale("Locale:", locale);
quizzController.getQuizzCached(region, locale, QUIZZ_SIZE)
quizzController.getQuestionCached(region, locale, QUIZZ_SIZE)
.then(({ species, answer, audio }) => {
req.session.answer = answer;
res.json({ species, audio }).send();
debug("Quizz sent");
quizzController.cacheQuizz(); // Prepare the next question in advance.
quizzController.cacheQuestion(); // Prepare the next question in advance.
debug("New quizz cached");
})
.catch(error => {
@ -38,6 +37,17 @@ async function quizz(req, res) {
});
}
async function quizz(req, res) {
const { region } = req.query;
req.session.region = region;
const { locale } = req.i18n.locale;
quizzController.generateQuizz(region, locale, 10)
.then(({ questions, answers, propositions }) => {
res.json({questions, answers, propositions }).send();
});
}
function check(req, res) {
let answer, correct;
try {
@ -87,8 +97,10 @@ async function birdSpeciesCompletion(req, res) {
res.json(speciesCompletion);
}
const game = {
check,
question,
quizz,
birdSpeciesCompletion
}

View File

@ -7,6 +7,14 @@ function getIndex(req, res) {
});
}
function getQuizz(req, res) {
res.render('quizz', {
title: 'SoundBirder Quizz',
csrf_token: req.csrfToken(),
locale: req.i18n.locale
})
}
function getAbout(req, res) {
res.render('about', {
title: req.i18n.__('About SoundBirder'),
@ -16,5 +24,6 @@ function getAbout(req, res) {
module.exports = {
getIndex,
getQuizz,
getAbout
}

View File

@ -5,35 +5,34 @@ const cache = require('./cache');
const eBird = require('@unclesamulus/ebird-api')(process.env.EBIRD_API_KEY);
const XenoCanto = require('@unclesamulus/xeno-canto-api');
const { choices, choice } = require('../utils/choices');
const { getRegion } = require('./region');
function quizzKey(region, locale, size) {
function questionKey(region, locale, size) {
return `${region}:${locale}#${size}`;
}
async function getQuizzCached(region, locale, size) {
let key = quizzKey(region, locale, size);
async function getQuestionCached(region, locale, size) {
let key = questionKey(region, locale, size);
let quizz = await cache.pop(key);
if (!quizz) {
quizz = generateQuizz(region, locale, size);
quizz = generateQuestion(region, locale, size);
cache.push(key, quizz);
}
return quizz;
}
async function cacheQuizz(region, locale, size) {
let quizz = await generateQuizz(region, locale, size);
let key = quizzKey(region, locale, size);
async function cacheQuestion(region, locale, size) {
let quizz = await generateQuestion(region, locale, size);
let key = questionKey(region, locale, size);
cache.push(key, quizz);
return quizz;
}
async function generateQuizz(region, locale, size) {
async function generateQuestion(region, locale, nb_propositions) {
const quizz = {}
try {
let speciesSelection = [];
do {
speciesSelection = await getSpeciesSelection(region, size);
speciesSelection = await getSpeciesSelection(region, nb_propositions);
} while (speciesSelection == []);
debugResponses(`Species selection: ${speciesSelection}`);
const speciesSelectionLocalized = await getLocalizedNames(speciesSelection, locale);
@ -62,6 +61,25 @@ async function generateQuizz(region, locale, size) {
return quizz;
}
async function generateQuizz(region, locale, nb_questions) {
let questions = [];
let answers = [];
const species_in_region = await getSpeciesList(region);
const species_in_region_localized = await getLocalizedNames(species_in_region, locale);
console.error(species_in_region_localized);
for (let i=0; i < nb_questions; i++) {
try {
let question_species = choice(species_in_region_localize);
let question_audio = await getAudio(question_species.sciName);
questions.push(question_audio);
answers.push(question_species);
} catch (error) {
console.error(error);
}
}
return {questions: questions, answers: answers, propositions: species_in_region_localized};
}
async function getSpeciesSelection(region, number) {
const regionCode = region;
const speciesList = await getSpeciesList(regionCode);
@ -74,6 +92,7 @@ async function getLocalizedNames(speciesCodes, locale) {
const localizedNames = [];
for (const speciesCode of speciesCodes) {
const localized = await getLocalizedName(speciesCode, locale);
if (!localized.sciName.includes(" x "))
localizedNames.push(localized);
}
return localizedNames;
@ -145,8 +164,9 @@ async function getSpeciesCompletion(region, locale, term) {
}
module.exports = {
generateQuizz,
getQuizzCached,
generateQuestion,
getQuestionCached,
getSpeciesCompletion,
cacheQuizz
cacheQuestion,
generateQuizz
}

View File

@ -17,5 +17,6 @@
"Set": "Set",
"Play the quizz": "Play the quizz",
"About this game": "About this game",
"Launch a quizz": "Launch a quizz"
"Launch a quizz": "Launch a quizz",
"The project is made with ♥ by Samuel Ortion.": "The project is made with ♥ by Samuel Ortion."
}

View File

@ -19,5 +19,6 @@
"Play the quizz": "Jouer une partie",
"About this game": "À propos de ce jeu",
"Set language": "Modifier la langue",
"Launch a quizz": "Launch a quizz"
"Launch a quizz": "Launch a quizz",
"Enter species name": "Enter species name"
}

View File

@ -1,6 +1,6 @@
{
"name": "soundbirder",
"version": "0.0.0",
"version": "0.1.0",
"private": true,
"scripts": {
"start": "node ./bin/www",

View File

@ -19,10 +19,15 @@ async function query(endpoint, params) {
return await get(`${API_URL}/${endpoint}`, params);
}
function getQuestion(region) {
return query('game/question', { region })
}
function getQuizz(region) {
return query('game/quizz', { region })
}
function getRegion(lat, lon) {
return query('region', { lat, lon })
}
@ -33,13 +38,14 @@ function checkAnswer(species) {
})
}
function getSpeciesCompletion(term) {
return query('game/species/completion', {
q: term
})
}
// function getSpeciesCompletion(term) {
// return query('game/species/completion', {
// q: term
// })
// }
const client = {
getQuestion,
getQuizz,
getRegion,
checkAnswer

View File

@ -47,7 +47,7 @@ function quizzStep() {
gameQuizz.classList.remove("none");
gameLoading.classList.remove("none");
audio.classList.add("none");
client.getQuizz(region)
client.getQuestion(region)
.then(quizz => {
gameLoading.classList.add("none");
displayQuizz(quizz);

View File

@ -1,9 +1,27 @@
let languageSelectorButton = document.getElementById('language-selector-button');
let languageSelector = document.getElementById('language-selector');
let selecting = false;
function addReplaceLangCode(url, langCode) {
let a = window.location;
let path = a.pathname.split('/');
path.shift();
if(path[0].length == 2) {
path[0] = langCode;
}else{
path.unshift(langCode);
}
return a.protocol + '//' +
a.host + '/' + path.join('/') +
(a.search != '' ? a.search : '') +
(a.hash != '' ? a.hash : '');
}
function update() {
let code = languageSelector.value;
window.location = '/' + code;
let langCode = languageSelector.value;
let url = addReplaceLangCode(window.location.href, langCode);
window.location = url;
}
languageSelector.addEventListener('click', function() {
if (selecting) {

View File

@ -0,0 +1,32 @@
import client from './api-client.js';
let Quizz = {
questions: [],
answers: [],
propositions: [],
region: 'FR', // TODO: update
questionPointer: 0,
init: function() {
let { questions, answers, propositions } = client.getQuizz(region);
this.questions = questions;
this.answers = answers;
this.propositions = propositions;
this.questionPointer = 0;
},
nextQuestion: function() {
this.questionPointer += 1;
this.render();
},
previousQuestion: function() {
if (this.questionPointer >= 1) {
this.questionPointer -= 1;
}
this.render()
},
render: function() {
}
}

View File

@ -579,6 +579,9 @@ h1 {
.flex-col {
flex-direction: column;
}
.justify-between {
justify-content: space-between;
}
.rounded-\[7px\] {
border-radius: 7px;
}
@ -647,217 +650,277 @@ h1 {
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
nav {
background-color: white;
opacity: 50%;
}
.before\:pointer-events-none::before {
content: var(--tw-content);
pointer-events: none;
}
.before\:mr-1::before {
content: var(--tw-content);
margin-right: 0.25rem;
}
.before\:mt-\[6\.5px\]::before {
content: var(--tw-content);
margin-top: 6.5px;
}
.before\:box-border::before {
content: var(--tw-content);
box-sizing: border-box;
}
.before\:block::before {
content: var(--tw-content);
display: block;
}
.before\:h-1::before {
content: var(--tw-content);
height: 0.25rem;
}
.before\:h-1\.5::before {
content: var(--tw-content);
height: 0.375rem;
}
.before\:w-2::before {
content: var(--tw-content);
width: 0.5rem;
}
.before\:w-2\.5::before {
content: var(--tw-content);
width: 0.625rem;
}
.before\:rounded-tl-md::before {
content: var(--tw-content);
border-top-left-radius: 0.375rem;
}
.before\:border-l::before {
content: var(--tw-content);
border-left-width: 1px;
}
.before\:border-t::before {
content: var(--tw-content);
border-top-width: 1px;
}
.before\:transition-all::before {
content: var(--tw-content);
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.after\:pointer-events-none::after {
content: var(--tw-content);
pointer-events: none;
}
.after\:ml-1::after {
content: var(--tw-content);
margin-left: 0.25rem;
}
.after\:mt-\[6\.5px\]::after {
content: var(--tw-content);
margin-top: 6.5px;
}
.after\:box-border::after {
content: var(--tw-content);
box-sizing: border-box;
}
.after\:block::after {
content: var(--tw-content);
display: block;
}
.after\:h-1::after {
content: var(--tw-content);
height: 0.25rem;
}
.after\:h-1\.5::after {
content: var(--tw-content);
height: 0.375rem;
}
.after\:w-2::after {
content: var(--tw-content);
width: 0.5rem;
}
.after\:w-2\.5::after {
content: var(--tw-content);
width: 0.625rem;
}
.after\:flex-grow::after {
content: var(--tw-content);
flex-grow: 1;
}
.after\:rounded-tr-md::after {
content: var(--tw-content);
border-top-right-radius: 0.375rem;
}
.after\:border-r::after {
content: var(--tw-content);
border-right-width: 1px;
}
.after\:border-t::after {
content: var(--tw-content);
border-top-width: 1px;
}
.after\:transition-all::after {
content: var(--tw-content);
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.placeholder-shown\:border:-moz-placeholder-shown {
border-width: 1px;
}
.placeholder-shown\:border:placeholder-shown {
border-width: 1px;
}
.empty\:\!bg-gray-900:empty {
--tw-bg-opacity: 1 !important;
background-color: rgb(17 24 39 / var(--tw-bg-opacity)) !important;
}
.focus\:border-2:focus {
border-width: 2px;
}
.focus\:border-gray-900:focus {
--tw-border-opacity: 1;
border-color: rgb(17 24 39 / var(--tw-border-opacity));
}
.focus\:border-t-transparent:focus {
border-top-color: transparent;
}
.focus\:outline-0:focus {
outline-width: 0px;
}
.disabled\:border-0:disabled {
border-width: 0px;
}
.peer:-moz-placeholder-shown ~ .peer-placeholder-shown\:text-sm {
font-size: 0.875rem;
line-height: 1.25rem;
}
.peer:placeholder-shown ~ .peer-placeholder-shown\:text-sm {
font-size: 0.875rem;
line-height: 1.25rem;
}
.peer:-moz-placeholder-shown ~ .peer-placeholder-shown\:leading-\[3\.75\] {
line-height: 3.75;
}
.peer:placeholder-shown ~ .peer-placeholder-shown\:leading-\[3\.75\] {
line-height: 3.75;
}
.peer:-moz-placeholder-shown ~ .peer-placeholder-shown\:before\:border-transparent::before {
content: var(--tw-content);
border-color: transparent;
}
.peer:placeholder-shown ~ .peer-placeholder-shown\:before\:border-transparent::before {
content: var(--tw-content);
border-color: transparent;
}
.peer:-moz-placeholder-shown ~ .peer-placeholder-shown\:after\:border-transparent::after {
content: var(--tw-content);
border-color: transparent;
}
.peer:placeholder-shown ~ .peer-placeholder-shown\:after\:border-transparent::after {
content: var(--tw-content);
border-color: transparent;
}
.peer:focus ~ .peer-focus\:text-\[11px\] {
font-size: 11px;
}
.peer:focus ~ .peer-focus\:leading-tight {
line-height: 1.25;
}
.peer:focus ~ .peer-focus\:text-gray-900 {
--tw-text-opacity: 1;
color: rgb(17 24 39 / var(--tw-text-opacity));
}
.peer:focus ~ .peer-focus\:before\:border-l-2::before {
content: var(--tw-content);
border-left-width: 2px;
}
.peer:focus ~ .peer-focus\:before\:border-t-2::before {
content: var(--tw-content);
border-top-width: 2px;
}
.peer:focus ~ .peer-focus\:before\:border-gray-900::before {
content: var(--tw-content);
--tw-border-opacity: 1;
border-color: rgb(17 24 39 / var(--tw-border-opacity));
}
.peer:focus ~ .peer-focus\:after\:border-r-2::after {
content: var(--tw-content);
border-right-width: 2px;
}
.peer:focus ~ .peer-focus\:after\:border-t-2::after {
content: var(--tw-content);
border-top-width: 2px;
}
.peer:focus ~ .peer-focus\:after\:border-gray-900::after {
content: var(--tw-content);
--tw-border-opacity: 1;
border-color: rgb(17 24 39 / var(--tw-border-opacity));
}
.peer:disabled ~ .peer-disabled\:text-transparent {
color: transparent;
}
.peer:disabled ~ .peer-disabled\:before\:border-transparent::before {
content: var(--tw-content);
border-color: transparent;
}
.peer:disabled ~ .peer-disabled\:after\:border-transparent::after {
content: var(--tw-content);
border-color: transparent;

View File

@ -3,6 +3,9 @@ const router = express.Router();
const apiController = require('../controllers/api.js');
router.route('/game/question')
.get(apiController.game.question);
router.route('/game/quizz')
.get(apiController.game.quizz);

View File

@ -5,6 +5,9 @@ const indexController = require('../controllers/index');
router.route('/')
.get(indexController.getIndex);
router.route('/quizz')
.get(indexController.getQuizz);
router.route('/about')
.get(indexController.getAbout);

View File

@ -1,5 +1,5 @@
doctype html
html
html(lang=locale)
head
title= title
meta(name="viewport" content="width=device-width, initial-scale=1.0")
@ -10,7 +10,7 @@ html
body
.flex.flex-col.min-h-screen
.flex-1.p-5
.menu
.menu.justify-between
nav(role="navigation").flex.flex-row
- var i18n_prefix = locale ? '/' + locale : ''
span.nav-item

9
views/quizz.pug Normal file
View File

@ -0,0 +1,9 @@
extends layout
block content
.game
.quizz-question
audio(controls)
input(type="text", text="", placeholder=__('Enter species name')).p-1
script(src="/dist/axios/axios.min.js")
script(src="/javascripts/game.js" type="module")