diff --git a/common/helper.js b/common/helper.js index d31f1ae..6a127a1 100644 --- a/common/helper.js +++ b/common/helper.js @@ -132,6 +132,7 @@ async function prepareLeaderboard (challengeId, finalists, groupId, groupChallen if (member) { member.scoreLevel = l.scoreLevel + member.finalDetails = l.finalDetails if (member.scoreLevel !== 'queued') { member.points = Math.round(l.aggregateScore * 10000) / 10000 diff --git a/components/FinalistTable.js b/components/FinalistTable.js index ab571c7..d297c5e 100644 --- a/components/FinalistTable.js +++ b/components/FinalistTable.js @@ -65,7 +65,7 @@ const table = (props) => { { !algorithmLeaderboard && !f2fLeaderboard &&
-
+
@@ -73,7 +73,7 @@ const table = (props) => {
{profile.handle}
-
+
{ profile.scoreLevel && } { profile.points >= 0 &&
@@ -86,7 +86,7 @@ const table = (props) => {
{ - !isQa && profile.totalTestCases > 0 &&
+ !isQa && profile.totalTestCases > 0 &&
{ !isDev && @@ -115,7 +115,7 @@ const table = (props) => { } { - !profile.hasOwnProperty('points') &&
+ !profile.hasOwnProperty('points') &&
{profile.status}
} @@ -216,6 +216,7 @@ const table = (props) => { display: flex; flex-direction: column; position: relative; + min-width: 705px; } .row { @@ -534,6 +535,29 @@ const table = (props) => { padding-left: 0; } + // The MIT License (MIT) + + // Copyright (c) 2019 Daniel Eden + + // Permission is hereby granted, free of charge, to any person obtaining a copy + // of this software and associated documentation files (the "Software"), to deal + // in the Software without restriction, including without limitation the rights + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + // copies of the Software, and to permit persons to whom the Software is + // furnished to do so, subject to the following conditions: + + // The above copyright notice and this permission notice shall be included in all + // copies or substantial portions of the Software. + + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + // SOFTWARE. + + // <---- animate.css BEGIN ---> @keyframes fadeUp { from { opacity: 0; @@ -568,6 +592,45 @@ const table = (props) => { } } + @keyframes flipInX { + from { + transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + animation-timing-function: ease-in; + opacity: 0; + } + + 40% { + transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + animation-timing-function: ease-in; + } + + 60% { + transform: perspective(400px) rotate3d(1, 0, 0, 10deg); + opacity: 1; + } + + 80% { + transform: perspective(400px) rotate3d(1, 0, 0, -5deg); + } + + to { + transform: perspective(400px); + } + } + + @keyframes flash { + from, + 50%, + to { + opacity: 1; + } + + 25%, + 75% { + opacity: 0; + } + } + .animate { animation-duration: 3s; animation-fill-mode: both; @@ -589,12 +652,27 @@ const table = (props) => { animation-name: fadeIn; } + .flipInX { + backface-visibility: visible !important; + animation-name: flipInX; + } + + .flash { + animation-name: flash; + } + + .hidden { + visibility: hidden; + } + + // <---- animate.css END ---> + @media only screen and (min-width:1800px){ .competitor { width: 250px; } } - + @media only screen and (min-width:1920px){ .competitor { width: 320px; diff --git a/next.config.js b/next.config.js index 46a3519..7d5656b 100644 --- a/next.config.js +++ b/next.config.js @@ -2,6 +2,7 @@ module.exports = { publicRuntimeConfig: { host: process.env.HOST, pollTimeInterval: process.env.LEADERBOARD_POLL_TIME_INTERVAL || 1000, - leaderboardRevealDelay: process.env.LEADERBOARD_REVEAL_DELAY || 500 + leaderboardRevealDelay: process.env.LEADERBOARD_REVEAL_DELAY || 500, + processDevRevealDelay: process.env.DEV_LEADERBOARD_REVEAL_DELAY || 7000 } } diff --git a/pages/problem-statement.js b/pages/problem-statement.js index 103c95b..51c9764 100644 --- a/pages/problem-statement.js +++ b/pages/problem-statement.js @@ -9,15 +9,29 @@ import Sponsors from '../components/Sponsors' import FinalistTable from '../components/FinalistTable' import { prepareLeaderboard, checkForMainSponsor } from '../common/helper' +function getMemberFinalScore (member) { + const finalScores = JSON.parse(JSON.stringify(member.finalDetails || {})) + + const newMember = Object.assign({ + animationClass: 'animate flipInX' + }, member, finalScores) + + newMember.points = Math.round(newMember.aggregateScore * 10000) / 10000 + + return newMember +} + class ProblemStatement extends React.Component { constructor (props) { super(props) this.polling = null + this.finalLeaderboard = null this.state = { leaderboard: [] } this.setupLeaderboard = this.setupLeaderboard.bind(this) + this.displayFinalScores = this.displayFinalScores.bind(this) } static async getInitialProps ({ query }) { @@ -73,9 +87,40 @@ class ProblemStatement extends React.Component { prepareLeaderboard(this.props.challengeId, this.props.members, this.props.groupId, this.props.challengeIds, this.props.isF2f) .then((leaderboard) => { - this.setState({ leaderboard }) - // Poll after configured second - this.polling = setTimeout(this.setupLeaderboard, publicRuntimeConfig.pollTimeInterval) + const finalResultsAvailable = leaderboard.every(l => { + let hasScore = false + + if (l.finalDetails) { + if (l.finalDetails.aggregateScore > -1) { + hasScore = true + } + } + + return hasScore + }) + + if (!finalResultsAvailable) { + this.setState({ leaderboard }) + // Poll after configured second + this.polling = setTimeout(this.setupLeaderboard, publicRuntimeConfig.pollTimeInterval) + } else { + this.finalLeaderboard = JSON.parse(JSON.stringify(leaderboard)) + + leaderboard = leaderboard.map(l => { + const member = { + handle: l.handle, + profilePic: l.profilePic, + countryFlag: l.countryFlag, + status: 'Processing final scores...', + statusAnimationClass: 'animate flash infinite' + } + return member + }) + + this.setState({ leaderboard }) + + this.polling = setTimeout(this.displayFinalScores, publicRuntimeConfig.processDevRevealDelay) + } }) .catch((err) => { console.log('Failed to fetch leaderboard. Error details follow') @@ -83,6 +128,43 @@ class ProblemStatement extends React.Component { }) } + displayFinalScores () { + const { publicRuntimeConfig } = getConfig() + let leaderboard = [] + let noMoreToReveal = true + + for (let i = this.finalLeaderboard.length - 1; i >= 0; i--) { + if (this.finalLeaderboard[i].revealed === true) { + leaderboard.push(getMemberFinalScore(this.finalLeaderboard[i])) + continue + } else { + this.finalLeaderboard[i].revealed = true + leaderboard.push(getMemberFinalScore(this.finalLeaderboard[i])) + break + } + } + + const leaderboardLength = leaderboard.length + + for (let i = 0; i < this.finalLeaderboard.length - leaderboardLength; i++) { + noMoreToReveal = false + leaderboard.push({ + handle: i, + status: 'Processing final scores...', + statusAnimationClass: 'animate flash infinite', + animationClass: 'hidden' + }) + } + + leaderboard.reverse() + + this.setState({ leaderboard }) + + if (!noMoreToReveal) { + this.polling = setTimeout(this.displayFinalScores, publicRuntimeConfig.leaderboardRevealDelay) + } + } + componentWillUnmount () { clearTimeout(this.polling) }