fix: Language selector keep on the page
This commit is contained in:
parent
e4a7479f19
commit
150da0f844
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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,7 +92,8 @@ async function getLocalizedNames(speciesCodes, locale) {
|
||||
const localizedNames = [];
|
||||
for (const speciesCode of speciesCodes) {
|
||||
const localized = await getLocalizedName(speciesCode, locale);
|
||||
localizedNames.push(localized);
|
||||
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
|
||||
}
|
@ -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."
|
||||
}
|
@ -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"
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "soundbirder",
|
||||
"version": "0.0.0",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node ./bin/www",
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
32
public/javascripts/quizz.js
Normal file
32
public/javascripts/quizz.js
Normal 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() {
|
||||
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
9
views/quizz.pug
Normal 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")
|
Loading…
Reference in New Issue
Block a user