From 150da0f844e76736c0c51880ed7a14005185d213 Mon Sep 17 00:00:00 2001 From: Samuel Ortion Date: Mon, 12 Feb 2024 09:29:34 +0100 Subject: [PATCH] fix: Language selector keep on the page --- controllers/api.js | 22 ++++++++--- controllers/index.js | 9 +++++ controllers/quizz.js | 48 +++++++++++++++++------- locales/en.json | 3 +- locales/fr.json | 3 +- package.json | 2 +- public/javascripts/api-client.js | 16 +++++--- public/javascripts/game.js | 2 +- public/javascripts/lang.js | 22 ++++++++++- public/javascripts/quizz.js | 32 ++++++++++++++++ public/stylesheets/style.css | 63 ++++++++++++++++++++++++++++++++ routes/api.js | 3 ++ routes/index.js | 3 ++ views/layout.pug | 4 +- views/quizz.pug | 9 +++++ 15 files changed, 209 insertions(+), 32 deletions(-) create mode 100644 public/javascripts/quizz.js create mode 100644 views/quizz.pug diff --git a/controllers/api.js b/controllers/api.js index b8553b8..74fb131 100755 --- a/controllers/api.js +++ b/controllers/api.js @@ -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 } diff --git a/controllers/index.js b/controllers/index.js index bc0730d..9b84d1d 100755 --- a/controllers/index.js +++ b/controllers/index.js @@ -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 } \ No newline at end of file diff --git a/controllers/quizz.js b/controllers/quizz.js index 799d59f..f968280 100755 --- a/controllers/quizz.js +++ b/controllers/quizz.js @@ -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 } \ No newline at end of file diff --git a/locales/en.json b/locales/en.json index b606c8d..6642266 100644 --- a/locales/en.json +++ b/locales/en.json @@ -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." } \ No newline at end of file diff --git a/locales/fr.json b/locales/fr.json index 4b6b4fa..27767c6 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -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" } \ No newline at end of file diff --git a/package.json b/package.json index d7f4317..f1798a3 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "soundbirder", - "version": "0.0.0", + "version": "0.1.0", "private": true, "scripts": { "start": "node ./bin/www", diff --git a/public/javascripts/api-client.js b/public/javascripts/api-client.js index 017abc0..8bb2396 100755 --- a/public/javascripts/api-client.js +++ b/public/javascripts/api-client.js @@ -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 diff --git a/public/javascripts/game.js b/public/javascripts/game.js index e2c9fe3..a457f0a 100755 --- a/public/javascripts/game.js +++ b/public/javascripts/game.js @@ -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); diff --git a/public/javascripts/lang.js b/public/javascripts/lang.js index a5b2050..ed0b3cd 100755 --- a/public/javascripts/lang.js +++ b/public/javascripts/lang.js @@ -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) { diff --git a/public/javascripts/quizz.js b/public/javascripts/quizz.js new file mode 100644 index 0000000..5a70c95 --- /dev/null +++ b/public/javascripts/quizz.js @@ -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() { + + } +} \ No newline at end of file diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 1a6a3a6..bbb440c 100755 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -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; diff --git a/routes/api.js b/routes/api.js index 393c201..da43cf5 100755 --- a/routes/api.js +++ b/routes/api.js @@ -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); diff --git a/routes/index.js b/routes/index.js index 57f443c..1ab8992 100755 --- a/routes/index.js +++ b/routes/index.js @@ -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); diff --git a/views/layout.pug b/views/layout.pug index 4a1f651..b2f0a24 100755 --- a/views/layout.pug +++ b/views/layout.pug @@ -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 diff --git a/views/quizz.pug b/views/quizz.pug new file mode 100644 index 0000000..e8d4376 --- /dev/null +++ b/views/quizz.pug @@ -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") \ No newline at end of file