diff --git a/public/assets.json b/public/assets.json deleted file mode 100644 index e69de29..0000000 diff --git a/public/index.html b/public/index.html deleted file mode 100644 index b9c2474..0000000 --- a/public/index.html +++ /dev/null @@ -1,203 +0,0 @@ - - - - - Save the reef - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/public/presentation.html b/public/presentation.html index ee422c3..c9b1f4a 100644 --- a/public/presentation.html +++ b/public/presentation.html @@ -1,35 +1,43 @@ - Sauve le corail + Les récifs coraliens
-
- -
-
- +
+
+
+
+

La Galerie

+
+
+
+
+
+
+

Le Recif

-
+
diff --git a/public/questions/vote-config.yaml b/public/questions/vote-config.yaml index 027e05e..190751c 100644 --- a/public/questions/vote-config.yaml +++ b/public/questions/vote-config.yaml @@ -29,4 +29,4 @@ questions: option-2: "Long long long answer that's just very very long just fine fine fine just fine fine fine and then some more" option-3: "Long long long answer that's just very very long just fine fine fine just fine fine fine and then some more" option-4: "Long long long answer that's just very very long just fine fine fine just fine fine fine and then some more" - correct-option: 3 \ No newline at end of file + correct-option: 2 \ No newline at end of file diff --git a/src/aframe-scene.js b/src/aframe-scene.js index 16ba41b..abe3551 100644 --- a/src/aframe-scene.js +++ b/src/aframe-scene.js @@ -6,9 +6,10 @@ import './components/bubble'; // import './components/floating-camera'; // import './components/custom-controls'; import './components/gradient'; -// import './components/simple-navmesh'; -// import './components/surface-placer'; import './components/ocean-shader'; +import 'js-yaml'; +import 'loglevel'; +import 'lit-html'; import 'aframe-orbit-controls'; import 'aframe-extras'; diff --git a/src/reveal-presentation.js b/src/reveal-presentation.js index c32b856..61fc5e6 100644 --- a/src/reveal-presentation.js +++ b/src/reveal-presentation.js @@ -9,20 +9,20 @@ Reveal.initialize({ progress: false, controls: true }).then(() => { - $(document).ready( - function(){ - $('body').on('submit','form',function() { - var sessionCode = $('#session_code')[0].value; - $("section[data-microsquad]").each( - function(){ - var iframeUrl = new URL( $(this).data("background-iframe") ); - iframeUrl.searchParams.set("sc", sessionCode); - $(this).data("background-iframe",iframeUrl.href); - console.log("Now "+$(this).data("background-iframe")); - } - ); - return false; - }); - } - ); + // $(document).ready( + // function(){ + // // $('body').on('submit','form',function() { + // // var sessionCode = $('#session_code')[0].value; + // // $("section[data-microsquad]").each( + // // function(){ + // // var iframeUrl = new URL( $(this).data("background-iframe") ); + // // iframeUrl.searchParams.set("sc", sessionCode); + // // $(this).data("background-iframe",iframeUrl.href); + // // console.log("Now "+$(this).data("background-iframe")); + // // } + // // ); + // // return false; + // // }); + // } + // ); }); diff --git a/src/scene-quizz/QuizzSceneController.js b/src/scene-quizz/QuizzSceneController.js index bfb4c86..6a2995a 100644 --- a/src/scene-quizz/QuizzSceneController.js +++ b/src/scene-quizz/QuizzSceneController.js @@ -40,6 +40,9 @@ class QuizzSceneController { this.questionParentElement.appendChild(this.questionDisplayElement); this.emoji = '🎁'; + + this.revealButton = null; + this.playerAnswers = new Map(); } setupLogging() { @@ -67,13 +70,12 @@ class QuizzSceneController { } } - - rule ProcessTerminalVoteUpdate { + rule ProcessPlayerVote { when { - update: PropertyUpdate update.deviceId.startsWith('terminal-') && update.nodeId === 'vote' && update.propertyId === 'option' + update: PropertyUpdate update.deviceId.startsWith('terminal-') && update.nodeId === 'vote' && update.propertyId === 'option' && update.value.trim().length != 0 } then { - handleTerminalVoteUpdate(update); + handlePlayerVote(update); } } `, { @@ -84,7 +86,7 @@ class QuizzSceneController { scope: { handlePropertyUpdate: this.handlePropertyUpdate.bind(this), handleVoteUpdate: this.handleVoteUpdate.bind(this), - handleTerminalVoteUpdate: this.handleTerminalVoteUpdate.bind(this), + handlePlayerVote: this.handlePlayerVote.bind(this), logger: console }, name: "quizz" @@ -149,24 +151,153 @@ class QuizzSceneController { this.currentVote.properties[update.propertyId] = update.value; + if (update.propertyId === 'correct-option') { + this.currentVote.correctOption = update.value; + } + + if (update.propertyId === 'question-statement' || update.propertyId === 'option-1' || update.propertyId === 'option-2' || update.propertyId === 'option-3' || update.propertyId === 'option-4') { this.updateQuestionDisplay(); + this.createRevealButton(); } log.debug(`Updating vote property: ${update.deviceId}/${update.propertyId} = ${update.value}`); } + handlePlayerVote(update) { + const terminalId = update.deviceId.split('-')[1]; + const playerNodeId = this.terminalToPlayerMap.get(terminalId); + if (playerNodeId) { + this.playerAnswers.set(playerNodeId, update.value); + } + this.handleTerminalVoteUpdate(update); + } + + createRevealButton() { + if (!this.revealButton) { + this.revealButton = document.createElement('button'); + this.revealButton.id = 'mode-toggle'; + this.revealButton.style.position = 'absolute'; + this.revealButton.style.top = '10px'; + this.revealButton.style.left = '10px'; + this.revealButton.style.zIndex = '1000'; + this.revealButton.textContent = 'Reveal Results'; + this.revealButton.addEventListener('click', () => this.revealResults()); + document.body.appendChild(this.revealButton); + } + } + + createNextButton() { + if (!this.nextButton) { + this.nextButton = document.createElement('button'); + this.nextButton.id = 'next-button'; + this.nextButton.style.position = 'absolute'; + this.nextButton.style.top = '10px'; + this.nextButton.style.left = '120px'; + this.nextButton.style.zIndex = '1000'; + this.nextButton.textContent = 'Next Question'; + this.nextButton.style.display = 'none'; + this.nextButton.addEventListener('click', () => this.prepareNextQuestion()); + document.body.appendChild(this.nextButton); + } + } + + + revealResults() { + if (this.currentVote && this.currentVote.correctOption) { + this.highlightCorrectAnswer(); + this.updatePlayerFeedback(); + this.showNextButton(); + } + } + + showNextButton() { + if (this.nextButton) { + this.nextButton.style.display = 'block'; + } else { + this.createNextButton(); + this.nextButton.style.display = 'block'; + } + } + + prepareNextQuestion() { + this.resetPlayerAnimations(); + this.clearPlayerVotes(); + this.clearAllEmojis(); + this.hideNextButton(); + this.clearQuestionDisplay(); + } + + clearAllEmojis() { + this.playerSayEntities.forEach((sayEntity, playerNodeId) => { + if (sayEntity) { + sayEntity.setAttribute('visible', 'false'); + render(html``, sayEntity); // Clear the content of the say entity + } + }); + log.debug('Cleared all player emojis'); + } + + resetPlayerAnimations() { + this.players.forEach((player, nodeId) => { + this.updatePlayerAnimationLocally(nodeId, "clip: Idle; loop: repeat"); + }); + } + + clearPlayerVotes() { + this.terminalToPlayerMap.forEach((playerNodeId, terminalId) => { + this.clearTerminalVote(terminalId); + }); + this.playerAnswers.clear(); + } + + clearTerminalVote(terminalId) { + const deviceId = `terminal-${terminalId}`; + const nodeId = 'vote'; + const propertyId = 'option'; + const value = undefined; + + // Publish MQTT message to clear the vote + this.homieObserver.publish(`${deviceId}/${nodeId}/${propertyId}`, value); + } + + hideNextButton() { + if (this.nextButton) { + this.nextButton.style.display = 'none'; + } + } + + clearQuestionDisplay() { + render(html``, this.questionDisplayElement); + this.currentVote = null; + } + + highlightCorrectAnswer() { + const correctOptionIndex = parseInt(this.currentVote.correctOption) - 1; + const updatedTemplate = questionDisplayTemplate( + this.currentVote.properties['question-statement'], + [ + this.currentVote.properties['option-1'], + this.currentVote.properties['option-2'], + this.currentVote.properties['option-3'], + this.currentVote.properties['option-4'] + ], + correctOptionIndex + ); + render(updatedTemplate, this.questionDisplayElement); + } + handleTerminalVoteUpdate(update) { const terminalId = update.deviceId.split('-')[1]; // remove the "terminal-" prefix log.debug(`Received vote from terminal: ${terminalId}, option: ${update.value}`); this.displayEmoji(terminalId); } -displayEmoji(terminalId) { + displayEmoji(terminalId) { const playerNodeId = this.terminalToPlayerMap.get(terminalId); if (playerNodeId) { @@ -177,6 +308,7 @@ displayEmoji(terminalId) { } } + renderEmojiEntity(playerEntity, playerNodeId) { const template = this.createEmojiTemplate(playerNodeId); @@ -214,6 +346,67 @@ displayEmoji(terminalId) { `; } + updatePlayerFeedback() { + this.playerAnswers.forEach((answer, playerNodeId) => { + const isCorrect = ((parseInt(answer)+1) === parseInt(this.currentVote.correctOption)); + const emoji = isCorrect ? '🟩' : '❌'; + this.updatePlayerEmoji(playerNodeId, emoji); + + if (!isCorrect) { + this.updatePlayerAnimationLocally(playerNodeId, "clip: Death; loop: once; clampWhenFinished: true"); + setTimeout(() => { + this.updatePlayerAnimationLocally(playerNodeId, "clip: Idle; loop: repeat"); + }, 5000); // Reset to Idle after 5 seconds + } + }); + } + + + updatePlayerEmoji(playerNodeId, emoji) { + const playerEntity = this.teamParentElement.querySelector(`#${playerNodeId}`); + if (playerEntity) { + const template = html` + +
+ ${emoji} +
+
+ `; + let sayEntity = this.playerSayEntities.get(playerNodeId); + if (!sayEntity) { + sayEntity = document.createElement('a-entity'); + sayEntity.setAttribute('id', `${playerNodeId}-say`); + playerEntity.appendChild(sayEntity); + this.playerSayEntities.set(playerNodeId, sayEntity); + } + render(template, sayEntity); + sayEntity.setAttribute('visible', 'true'); + } + } + + updatePlayerAnimationLocally(playerNodeId, animationValue) { + const playerEntity = this.teamParentElement.querySelector(`#${playerNodeId}`); + if (playerEntity) { + playerEntity.setAttribute('animation-mixer', animationValue); + log.debug(`Updated animation for player ${playerNodeId}: ${animationValue}`); + } + } + + updatePlayerAnimation(player) { + const playerEntity = this.teamParentElement.querySelector(`#${player.nodeId}`); + if (playerEntity) { + playerEntity.setAttribute('animation-mixer', player.properties['animation-mixer']); + log.debug(`Updated animation for player ${player.nodeId}: ${player.properties['animation-mixer']}`); + } + } + + + updateQuestionDisplay() { if (this.currentVote && this.currentVote.properties['question-statement'] && @@ -237,14 +430,6 @@ displayEmoji(terminalId) { } } - updatePlayerAnimation(player) { - const playerEntity = this.parentElement.querySelector(`#${player.nodeId}`); - if (playerEntity) { - playerEntity.setAttribute('animation-mixer', player.properties['animation-mixer']); - log.debug(`Updated animation for player ${player.nodeId}: ${player.properties['animation-mixer']}`); - } - } - updateTerminalToPlayerMap(player, terminalId) { for (let [key, value] of this.terminalToPlayerMap) { if (value === player.nodeId) { diff --git a/src/scene-quizz/questionDisplayTemplate.js b/src/scene-quizz/questionDisplayTemplate.js index 9ce068e..c44789f 100644 --- a/src/scene-quizz/questionDisplayTemplate.js +++ b/src/scene-quizz/questionDisplayTemplate.js @@ -1,22 +1,27 @@ import { html } from 'lit-html'; -export const questionDisplayTemplate = (question, options) => html` +export const questionDisplayTemplate = (question, options, correctOptionIndex = null) => html` - ${options.map((option, index) => html` - - - - + ${options.map((option, index) => { + const isCorrect = correctOptionIndex !== null && index === correctOptionIndex; + const planeColor = isCorrect ? "#00FF00" : "#333333"; + const textColor = isCorrect ? "#000000" : "#FFFFFF"; + return html` + + + + - - - - `)} + + + + `; + })} `; \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index da1256d..b7f1eb3 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -7,6 +7,7 @@ module.exports = { aframe: './src/aframe-scene.js', tutorial: './src/tutorial-scene.js', quizz: './src/quizz-scene.js', + gallery: './src/gallery-scene.js', presentation: './src/reveal-presentation.js', voteAggregator: './src/vote-aggregator.js' },