]> git.localhorst.tv Git - alttp.git/blobdiff - resources/js/pages/GuessingGameMonitor.js
add simple guessing game browser source
[alttp.git] / resources / js / pages / GuessingGameMonitor.js
diff --git a/resources/js/pages/GuessingGameMonitor.js b/resources/js/pages/GuessingGameMonitor.js
new file mode 100644 (file)
index 0000000..bf2922b
--- /dev/null
@@ -0,0 +1,194 @@
+import axios from 'axios';
+import moment from 'moment';
+import React from 'react';
+import { Container } from 'react-bootstrap';
+import { Helmet } from 'react-helmet';
+import { useParams } from 'react-router-dom';
+
+import {
+       hasActiveGuessing,
+       isAcceptingGuesses,
+       patchGuess,
+       patchWinner,
+} from '../helpers/Channel';
+import ErrorBoundary from '../components/common/ErrorBoundary';
+import Icon from '../components/common/Icon';
+import Slider from '../components/common/Slider';
+
+export const Component = () => {
+       const [channel, setChannel] = React.useState({});
+       const [guesses, setGuesses] = React.useState([]);
+       const [winnerExpiry, setWinnerExpiry] = React.useState(moment().subtract(15, 'second'));
+       const [winners, setWinners] = React.useState([]);
+
+       const params = useParams();
+       const { key } = params;
+
+       React.useEffect(() => {
+               if (!key) return;
+               axios.get(`/api/guessing-game-monitor/${key}`)
+                       .then(res => {
+                               setChannel(res.data.channel);
+                               res.data.guesses.forEach(g => {
+                                       setGuesses(gs => patchGuess(gs, g));
+                               });
+                               res.data.winners.forEach(w => {
+                                       setWinners(ws => patchGuess(ws, w));
+                               });
+                       });
+               window.Echo.channel(`ChannelKey.${key}`)
+                       .listen('.GuessingGuessCreated', (e) => {
+                               setGuesses(gs => patchGuess(gs, e.model));
+                       })
+                       .listen('.GuessingWinnerCreated', (e) => {
+                               setWinners(ws => patchWinner(ws, e.model));
+                       })
+                       .listen('.ChannelUpdated', (e) => {
+                               setChannel(c => ({ ...c, ...e.model }));
+                       });
+               return () => {
+                       window.Echo.leave(`ChannelKey.${key}`);
+               };
+       }, [key]);
+
+       React.useEffect(() => {
+               if (isAcceptingGuesses(channel)) {
+                       setGuesses(gs => gs.filter(g => g.created_at >= channel.guessing_start));
+                       setWinners([]);
+               }
+       }, [channel]);
+
+       React.useEffect(() => {
+               const interval = setInterval(() => {
+                       setWinnerExpiry(moment().subtract(15, 'second'));
+               }, 1000);
+               return () => {
+                       clearInterval(interval);
+               };
+       }, []);
+
+       const guessingStats = React.useMemo(() => {
+               const stats = {
+                       counts: [],
+                       lastWin: null,
+                       max: 0,
+                       wins: [],
+                       winners: [],
+               };
+               for (let i = 0; i < 22; ++i) {
+                       stats.counts.push(0);
+                       stats.wins.push(false);
+               }
+               const seen = [];
+               guesses.forEach(guess => {
+                       if (seen[guess.uid]) {
+                               --stats.counts[parseInt(seen[guess.uid].guess, 10) - 1];
+                       }
+                       ++stats.counts[parseInt(guess.guess, 10) - 1];
+                       seen[guess.uid] = guess;
+               });
+               winners.forEach(winner => {
+                       if (winner.score) {
+                               stats.wins[parseInt(winner.guess, 10) - 1] = true;
+                               stats.winners.push(winner.uname);
+                       }
+                       if (!stats.lastWin || stats.lastWin < winner.created_at) {
+                               stats.lastWin = winner.created_at;
+                       }
+               });
+               for (let i = 0; i < 22; ++i) {
+                       if (stats.counts[i] > stats.max) {
+                               stats.max = stats.counts[i];
+                       }
+               }
+               return stats;
+       }, [guesses, winners]);
+
+       const getNumberHeight = React.useCallback((number) => {
+               if (!guessingStats || !guessingStats.max) return 3;
+               if (!number) return 3;
+               return Math.max(0.05, number / guessingStats.max) * 100;
+       }, [guessingStats]);
+
+       const getStatClass = React.useCallback((index) => {
+               const names = ['guessing-stat'];
+               if (guessingStats.wins[index]) {
+                       names.push('has-won');
+               }
+               return names.join(' ');
+       }, [guessingStats]);
+
+       const showOpen = React.useMemo(() => {
+               return isAcceptingGuesses(channel);
+       }, [channel]);
+
+       const showClosed = React.useMemo(() => {
+               return hasActiveGuessing(channel) && !isAcceptingGuesses(channel);
+       }, [channel]);
+
+       const showWinners = React.useMemo(() => {
+               return !hasActiveGuessing(channel) && (
+                       guessingStats?.lastWin &&
+                       moment(guessingStats.lastWin).isAfter(winnerExpiry));
+       }, [channel, guessingStats, winnerExpiry]);
+
+       return <ErrorBoundary>
+               <Helmet>
+                       <title>Guessing Game</title>
+               </Helmet>
+               <Container className="guessing-game-monitor" fluid>
+               {showOpen || showClosed || showWinners ?
+                       <div className="message-box">
+                               {showOpen ?
+                                       <div className="message-title accepting-guesses">
+                                               <Icon.WARNING className="message-icon" />
+                                               <div className="message-text">
+                                                       <Slider duration={3500}>
+                                                               <Slider.Slide>GT Big Key Guessing Game</Slider.Slide>
+                                                               <Slider.Slide>Zahlen von 1 bis 22 in den Chat!</Slider.Slide>
+                                                       </Slider>
+                                               </div>
+                                               <Icon.WARNING className="message-icon" />
+                                       </div>
+                               : null}
+                               {showClosed ?
+                                       <div className="message-title guessing-closed">
+                                               <div className="message-text">
+                                                       Anmeldung geschlossen
+                                               </div>
+                                       </div>
+                               : null}
+                               {showWinners ?
+                                       <div className="message-title guessing-winners">
+                                               <div className="message-text">
+                                                       {guessingStats.winners.length ?
+                                                               <Slider duration={2500}>
+                                                                       <Slider.Slide>Herzlichen Glückwunsch!</Slider.Slide>
+                                                                       {guessingStats.winners.map(winner =>
+                                                                               <Slider.Slide key={winner}>{winner}</Slider.Slide>
+                                                                       )}
+                                                               </Slider>
+                                                       :
+                                                               'Leider keiner richtig'
+                                                       }
+                                               </div>
+                                       </div>
+                               : null}
+                               <div className="guessing-stats">
+                                       {guessingStats.counts.map((number, index) =>
+                                               <div className={getStatClass(index)} key={index}>
+                                                       <div className="guessing-box">
+                                                               <div
+                                                                       className="guessing-box-bar"
+                                                                       style={{ height: `${getNumberHeight(number)}%` }}
+                                                               />
+                                                       </div>
+                                                       <div className="guessing-number">{index + 1}</div>
+                                               </div>
+                                       )}
+                               </div>
+                       </div>
+               : null}
+               </Container>
+       </ErrorBoundary>;
+};