Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit e32143a

Browse files
author
Dushyant Bhalgami
authored
Merge pull request #69 from topcoder-platform/leaderboard-reveal
Animate provisional and final score reveal in mini leaderboard
2 parents 574015e + 04f0313 commit e32143a

File tree

4 files changed

+171
-9
lines changed

4 files changed

+171
-9
lines changed

common/helper.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ async function prepareLeaderboard (challengeId, finalists, groupId, groupChallen
132132

133133
if (member) {
134134
member.scoreLevel = l.scoreLevel
135+
member.finalDetails = l.finalDetails
135136

136137
if (member.scoreLevel !== 'queued') {
137138
member.points = Math.round(l.aggregateScore * 10000) / 10000

components/FinalistTable.js

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,15 @@ const table = (props) => {
6565
</div>
6666

6767
{ !algorithmLeaderboard && !f2fLeaderboard && <div style={{ display: 'flex', flexGrow: '1', justifyContent: 'space-between' }}>
68-
<div className='competitor'>
68+
<div className={`competitor ${profile.animationClass}`}>
6969
<div className='avatar'>
7070
<img src={profile.profilePic} />
7171
</div>
7272
<img className='country-flag' src={profile.countryFlag} />
7373
<div className='handle' style={{ color: primaryColor }}>{profile.handle}</div>
7474
</div>
7575

76-
<div className='points'>
76+
<div className={`points ${profile.animationClass}`}>
7777
{ profile.scoreLevel && <img className={`animate fade${profile.scoreLevel} infinite`} src={`/static/img/trend/${profile.scoreLevel}.png`} /> }
7878
{ profile.points >= 0 && <div className={profile.scoreLevel ? '' : 'non-score-lvl-pt'}>
7979
<span className='value'>
@@ -86,7 +86,7 @@ const table = (props) => {
8686
</div>
8787

8888
{
89-
!isQa && profile.totalTestCases > 0 && <div className='tests-passed'>
89+
!isQa && profile.totalTestCases > 0 && <div className={`tests-passed ${profile.animationClass}`}>
9090
<div>
9191
{
9292
!isDev &&
@@ -115,7 +115,7 @@ const table = (props) => {
115115
}
116116

117117
{
118-
!profile.hasOwnProperty('points') && <div className='status'>
118+
!profile.hasOwnProperty('points') && <div className={`status ${profile.statusAnimationClass}`}>
119119
{profile.status}
120120
</div>
121121
}
@@ -216,6 +216,7 @@ const table = (props) => {
216216
display: flex;
217217
flex-direction: column;
218218
position: relative;
219+
min-width: 705px;
219220
}
220221
221222
.row {
@@ -534,6 +535,29 @@ const table = (props) => {
534535
padding-left: 0;
535536
}
536537
538+
// The MIT License (MIT)
539+
540+
// Copyright (c) 2019 Daniel Eden
541+
542+
// Permission is hereby granted, free of charge, to any person obtaining a copy
543+
// of this software and associated documentation files (the "Software"), to deal
544+
// in the Software without restriction, including without limitation the rights
545+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
546+
// copies of the Software, and to permit persons to whom the Software is
547+
// furnished to do so, subject to the following conditions:
548+
549+
// The above copyright notice and this permission notice shall be included in all
550+
// copies or substantial portions of the Software.
551+
552+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
553+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
554+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
555+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
556+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
557+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
558+
// SOFTWARE.
559+
560+
// <---- animate.css BEGIN --->
537561
@keyframes fadeUp {
538562
from {
539563
opacity: 0;
@@ -568,6 +592,45 @@ const table = (props) => {
568592
}
569593
}
570594
595+
@keyframes flipInX {
596+
from {
597+
transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
598+
animation-timing-function: ease-in;
599+
opacity: 0;
600+
}
601+
602+
40% {
603+
transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
604+
animation-timing-function: ease-in;
605+
}
606+
607+
60% {
608+
transform: perspective(400px) rotate3d(1, 0, 0, 10deg);
609+
opacity: 1;
610+
}
611+
612+
80% {
613+
transform: perspective(400px) rotate3d(1, 0, 0, -5deg);
614+
}
615+
616+
to {
617+
transform: perspective(400px);
618+
}
619+
}
620+
621+
@keyframes flash {
622+
from,
623+
50%,
624+
to {
625+
opacity: 1;
626+
}
627+
628+
25%,
629+
75% {
630+
opacity: 0;
631+
}
632+
}
633+
571634
.animate {
572635
animation-duration: 3s;
573636
animation-fill-mode: both;
@@ -589,12 +652,27 @@ const table = (props) => {
589652
animation-name: fadeIn;
590653
}
591654
655+
.flipInX {
656+
backface-visibility: visible !important;
657+
animation-name: flipInX;
658+
}
659+
660+
.flash {
661+
animation-name: flash;
662+
}
663+
664+
.hidden {
665+
visibility: hidden;
666+
}
667+
668+
// <---- animate.css END --->
669+
592670
@media only screen and (min-width:1800px){
593671
.competitor {
594672
width: 250px;
595673
}
596674
}
597-
675+
598676
@media only screen and (min-width:1920px){
599677
.competitor {
600678
width: 320px;

next.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module.exports = {
22
publicRuntimeConfig: {
33
host: process.env.HOST,
44
pollTimeInterval: process.env.LEADERBOARD_POLL_TIME_INTERVAL || 1000,
5-
leaderboardRevealDelay: process.env.LEADERBOARD_REVEAL_DELAY || 500
5+
leaderboardRevealDelay: process.env.LEADERBOARD_REVEAL_DELAY || 500,
6+
processDevRevealDelay: process.env.DEV_LEADERBOARD_REVEAL_DELAY || 7000
67
}
78
}

pages/problem-statement.js

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,29 @@ import Sponsors from '../components/Sponsors'
99
import FinalistTable from '../components/FinalistTable'
1010
import { prepareLeaderboard, checkForMainSponsor } from '../common/helper'
1111

12+
function getMemberFinalScore (member) {
13+
const finalScores = JSON.parse(JSON.stringify(member.finalDetails || {}))
14+
15+
const newMember = Object.assign({
16+
animationClass: 'animate flipInX'
17+
}, member, finalScores)
18+
19+
newMember.points = Math.round(newMember.aggregateScore * 10000) / 10000
20+
21+
return newMember
22+
}
23+
1224
class ProblemStatement extends React.Component {
1325
constructor (props) {
1426
super(props)
1527

1628
this.polling = null
29+
this.finalLeaderboard = null
1730
this.state = {
1831
leaderboard: []
1932
}
2033
this.setupLeaderboard = this.setupLeaderboard.bind(this)
34+
this.displayFinalScores = this.displayFinalScores.bind(this)
2135
}
2236

2337
static async getInitialProps ({ query }) {
@@ -73,16 +87,84 @@ class ProblemStatement extends React.Component {
7387

7488
prepareLeaderboard(this.props.challengeId, this.props.members, this.props.groupId, this.props.challengeIds, this.props.isF2f)
7589
.then((leaderboard) => {
76-
this.setState({ leaderboard })
77-
// Poll after configured second
78-
this.polling = setTimeout(this.setupLeaderboard, publicRuntimeConfig.pollTimeInterval)
90+
const finalResultsAvailable = leaderboard.every(l => {
91+
let hasScore = false
92+
93+
if (l.finalDetails) {
94+
if (l.finalDetails.aggregateScore > -1) {
95+
hasScore = true
96+
}
97+
}
98+
99+
return hasScore
100+
})
101+
102+
if (!finalResultsAvailable) {
103+
this.setState({ leaderboard })
104+
// Poll after configured second
105+
this.polling = setTimeout(this.setupLeaderboard, publicRuntimeConfig.pollTimeInterval)
106+
} else {
107+
this.finalLeaderboard = JSON.parse(JSON.stringify(leaderboard))
108+
109+
leaderboard = leaderboard.map(l => {
110+
const member = {
111+
handle: l.handle,
112+
profilePic: l.profilePic,
113+
countryFlag: l.countryFlag,
114+
status: 'Processing final scores...',
115+
statusAnimationClass: 'animate flash infinite'
116+
}
117+
return member
118+
})
119+
120+
this.setState({ leaderboard })
121+
122+
this.polling = setTimeout(this.displayFinalScores, publicRuntimeConfig.processDevRevealDelay)
123+
}
79124
})
80125
.catch((err) => {
81126
console.log('Failed to fetch leaderboard. Error details follow')
82127
console.log(err)
83128
})
84129
}
85130

131+
displayFinalScores () {
132+
const { publicRuntimeConfig } = getConfig()
133+
let leaderboard = []
134+
let noMoreToReveal = true
135+
136+
for (let i = this.finalLeaderboard.length - 1; i >= 0; i--) {
137+
if (this.finalLeaderboard[i].revealed === true) {
138+
leaderboard.push(getMemberFinalScore(this.finalLeaderboard[i]))
139+
continue
140+
} else {
141+
this.finalLeaderboard[i].revealed = true
142+
leaderboard.push(getMemberFinalScore(this.finalLeaderboard[i]))
143+
break
144+
}
145+
}
146+
147+
const leaderboardLength = leaderboard.length
148+
149+
for (let i = 0; i < this.finalLeaderboard.length - leaderboardLength; i++) {
150+
noMoreToReveal = false
151+
leaderboard.push({
152+
handle: i,
153+
status: 'Processing final scores...',
154+
statusAnimationClass: 'animate flash infinite',
155+
animationClass: 'hidden'
156+
})
157+
}
158+
159+
leaderboard.reverse()
160+
161+
this.setState({ leaderboard })
162+
163+
if (!noMoreToReveal) {
164+
this.polling = setTimeout(this.displayFinalScores, publicRuntimeConfig.leaderboardRevealDelay)
165+
}
166+
}
167+
86168
componentWillUnmount () {
87169
clearTimeout(this.polling)
88170
}

0 commit comments

Comments
 (0)