[IMP] survey: animate leaderboard in survey sessions
Purpose ======= This commit intends to improve the animations on the leaderboard during survey sessions. We now show both the ranking of each attendee before and after taking into account the current question scores. It allows attendees to see their position in the leaderboard shifting up / down depending on how well they answered, and adds a bit more interactivity during the session. Specs ===== The code changes to make this happen implied having multiple layers of animations running one after the other. The use of async/await in the JS allowed for a much smoother and less indented code, simplifying the reading. We now have 2 different "bars" to handle: The bar that shows the previous leaderboard position. The bar that shows the current question score. This means we handle both jQuery items and modify their size & transition properties depending on our animation. Unfortunately, all these animations required a lot of testing to find fitting timings, which means using quite a few setTimeout with "magic" delays. These were just the most good looking ones and it feels like naming them with a constant would not really help. We also slightly improved the graphic font size / styling to make it easier to read for potentially far away attendees. Task 2235815 closes odoo/odoo#50991 Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
committed by
Thibault Delavallée
parent
20b34ae867
commit
4abe3ea9c7
@@ -147,7 +147,7 @@ class UserInputSession(http.Controller):
|
||||
return ''
|
||||
|
||||
return request.env.ref('survey.user_input_session_leaderboard')._render({
|
||||
'animate_width': True,
|
||||
'animate': True,
|
||||
'leaderboard': survey._prepare_leaderboard_values()
|
||||
})
|
||||
|
||||
|
||||
@@ -744,13 +744,58 @@ class Survey(models.Model):
|
||||
return fake_user_input
|
||||
|
||||
def _prepare_leaderboard_values(self):
|
||||
"""" The leaderboard is descending and takes the total of the attendee points up to the current question. """
|
||||
"""" The leaderboard is descending and takes the total of the attendee points minus the
|
||||
current question score.
|
||||
We need both the total and the current question points to be able to show the attendees
|
||||
leaderboard and shift their position based on the score they have on the current question.
|
||||
This prepares a structure containing all the necessary data for the animations done on
|
||||
the frontend side.
|
||||
The leaderboard is sorted based on attendees score *before* the current question.
|
||||
The frontend will shift positions around accordingly. """
|
||||
|
||||
self.ensure_one()
|
||||
|
||||
return self.env['survey.user_input'].search([
|
||||
leaderboard = self.env['survey.user_input'].search_read([
|
||||
('survey_id', '=', self.id),
|
||||
('create_date', '>=', self.session_start_time)
|
||||
], limit=25, order="scoring_total desc")
|
||||
], [
|
||||
'id',
|
||||
'nickname',
|
||||
'scoring_total',
|
||||
], limit=15, order="scoring_total desc")
|
||||
|
||||
if leaderboard and self.session_state == 'in_progress' and \
|
||||
any(answer.answer_score for answer in self.session_question_id.suggested_answer_ids):
|
||||
question_scores = {}
|
||||
input_lines = self.env['survey.user_input.line'].search_read(
|
||||
[('user_input_id', 'in', [score['id'] for score in leaderboard]),
|
||||
('question_id', '=', self.session_question_id.id)],
|
||||
['user_input_id', 'answer_score'])
|
||||
for input_line in input_lines:
|
||||
question_scores[input_line['user_input_id'][0]] = \
|
||||
question_scores.get(input_line['user_input_id'][0], 0) + input_line['answer_score']
|
||||
|
||||
score_position = 0
|
||||
for leaderboard_item in leaderboard:
|
||||
question_score = question_scores.get(leaderboard_item['id'], 0)
|
||||
leaderboard_item.update({
|
||||
'updated_score': leaderboard_item['scoring_total'],
|
||||
'scoring_total': leaderboard_item['scoring_total'] - question_score,
|
||||
'leaderboard_position': score_position,
|
||||
'max_question_score': sum(
|
||||
score for score in self.session_question_id.suggested_answer_ids.mapped('answer_score')
|
||||
if score > 0
|
||||
) or 1,
|
||||
'question_score': question_score
|
||||
})
|
||||
score_position += 1
|
||||
leaderboard = sorted(
|
||||
leaderboard,
|
||||
key=lambda score: score['scoring_total'],
|
||||
reverse=True)
|
||||
|
||||
return leaderboard
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# ACTIONS
|
||||
|
||||
@@ -11,7 +11,7 @@ publicWidget.registry.SurveySessionChart = publicWidget.Widget.extend({
|
||||
this.questionType = options.questionType;
|
||||
this.answersValidity = options.answersValidity;
|
||||
this.hasCorrectAnswers = options.hasCorrectAnswers;
|
||||
this.attendeesCount = options.attendeesCount;
|
||||
this.attendeesCount = options.attendeesCount || 0;
|
||||
this.questionStatistics = this._processQuestionStatistics(options.questionStatistics);
|
||||
this.showInputs = options.showInputs;
|
||||
this.showAnswers = false;
|
||||
@@ -121,7 +121,7 @@ publicWidget.registry.SurveySessionChart = publicWidget.Widget.extend({
|
||||
datalabels: {
|
||||
color: this._getLabelColor.bind(this),
|
||||
font: {
|
||||
size: '25',
|
||||
size: '50',
|
||||
weight: 'bold',
|
||||
},
|
||||
anchor: 'end',
|
||||
@@ -145,12 +145,13 @@ publicWidget.registry.SurveySessionChart = publicWidget.Widget.extend({
|
||||
xAxes: [{
|
||||
ticks: {
|
||||
maxRotation: 0,
|
||||
fontSize: '25',
|
||||
fontSize: '35',
|
||||
fontStyle: 'bold',
|
||||
fontColor: '#212529'
|
||||
},
|
||||
gridLines: {
|
||||
display: false
|
||||
drawOnChartArea: false,
|
||||
color: 'rgba(0, 0, 0, 0.2)'
|
||||
}
|
||||
}]
|
||||
},
|
||||
@@ -200,7 +201,7 @@ publicWidget.registry.SurveySessionChart = publicWidget.Widget.extend({
|
||||
var opacity = '0.8';
|
||||
if (this.showAnswers && this.hasCorrectAnswers) {
|
||||
if (!this._isValidAnswer(metaData.dataIndex)){
|
||||
opacity = '0.3';
|
||||
opacity = '0.2';
|
||||
}
|
||||
}
|
||||
var rgb = SESSION_CHART_COLORS[metaData.dataIndex];
|
||||
|
||||
@@ -3,19 +3,19 @@ odoo.define('survey.session_colors', function (require) {
|
||||
|
||||
/**
|
||||
* Small tool that returns common colors for survey session widgets.
|
||||
* Source: https://www.materialui.co/colors
|
||||
* Source: https://www.materialui.co/colors (500)
|
||||
*/
|
||||
return [
|
||||
'25,118,210',
|
||||
'48,63,159',
|
||||
'175,180,43',
|
||||
'0,121,107',
|
||||
'56,142,60',
|
||||
'93,64,55',
|
||||
'97,97,97',
|
||||
'123,31,162',
|
||||
'69,90,100',
|
||||
'211,47,47',
|
||||
'33,150,243',
|
||||
'63,81,181',
|
||||
'205,220,57',
|
||||
'0,150,136',
|
||||
'76,175,80',
|
||||
'121,85,72',
|
||||
'158,158,158',
|
||||
'156,39,176',
|
||||
'96,125,139',
|
||||
'244,67,54',
|
||||
];
|
||||
|
||||
});
|
||||
|
||||
@@ -10,6 +10,10 @@ publicWidget.registry.SurveySessionLeaderboard = publicWidget.Widget.extend({
|
||||
|
||||
this.surveyAccessToken = options.surveyAccessToken;
|
||||
this.$sessionResults = options.sessionResults;
|
||||
|
||||
this.BAR_MIN_WIDTH = '3rem';
|
||||
this.BAR_WIDTH = '24rem';
|
||||
this.BAR_HEIGHT = '3.8rem';
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -24,7 +28,7 @@ publicWidget.registry.SurveySessionLeaderboard = publicWidget.Widget.extend({
|
||||
*
|
||||
* The width of the progress bars is set after the rendering to enable a width css animation.
|
||||
*/
|
||||
showLeaderboard: function (fadeOut) {
|
||||
showLeaderboard: function (fadeOut, isScoredQuestion) {
|
||||
var self = this;
|
||||
|
||||
var resolveFadeOut;
|
||||
@@ -46,17 +50,26 @@ publicWidget.registry.SurveySessionLeaderboard = publicWidget.Widget.extend({
|
||||
|
||||
Promise.all([fadeOutPromise, leaderboardPromise]).then(function (results) {
|
||||
var leaderboardResults = results[1];
|
||||
var $renderedTemplate = $(leaderboardResults).addClass('py-3');
|
||||
var $renderedTemplate = $(leaderboardResults);
|
||||
self.$('.o_survey_session_leaderboard_container').append($renderedTemplate);
|
||||
|
||||
self.$('.o_survey_session_leaderboard_bar').each(function (index) {
|
||||
self.$('.o_survey_session_leaderboard_item').each(function (index) {
|
||||
var rgb = SESSION_CHART_COLORS[index % 10];
|
||||
$(this).css('background-color', `rgba(${rgb},${0.8})`);
|
||||
$(this)
|
||||
.find('.o_survey_session_leaderboard_bar')
|
||||
.css('background-color', `rgba(${rgb},1)`);
|
||||
$(this)
|
||||
.find('.o_survey_session_leaderboard_bar_question')
|
||||
.css('background-color', `rgba(${rgb},${0.4})`);
|
||||
});
|
||||
self.$el.fadeIn(400, function () {
|
||||
self.$('.o_survey_session_leaderboard_bar').each(function () {
|
||||
$(this).css('width', `calc(calc(100% - 18rem) * ${$(this).data('widthRatio')})`);
|
||||
});
|
||||
|
||||
self.$el.fadeIn(400, async function () {
|
||||
if (isScoredQuestion) {
|
||||
await self._prepareScores();
|
||||
await self._showQuestionScores();
|
||||
await self._sumScores();
|
||||
await self._reorderScores();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
@@ -71,6 +84,250 @@ publicWidget.registry.SurveySessionLeaderboard = publicWidget.Widget.extend({
|
||||
self.$sessionResults.fadeIn(400);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* This method animates the passed jQuery element from 0 points to {totalScore} points.
|
||||
* It will create a nice "animated" effect of a counter increasing by {increment} until it
|
||||
* reaches the actual score.
|
||||
*
|
||||
* @param {$.Element} $scoreEl the element to animate
|
||||
* @param {Integer} currentScore the currently displayed score
|
||||
* @param {Integer} totalScore to total score to animate to
|
||||
* @param {Integer} increment the base increment of each animation iteration
|
||||
* @param {Boolean} plusSign wether or not we add a "+" before the score
|
||||
* @private
|
||||
*/
|
||||
_animateScoreCounter: function ($scoreEl, currentScore, totalScore, increment, plusSign) {
|
||||
var self = this;
|
||||
setTimeout(function () {
|
||||
var nextScore = currentScore + increment;
|
||||
if (nextScore > totalScore) {
|
||||
nextScore = totalScore;
|
||||
}
|
||||
$scoreEl.text(`${plusSign ? '+ ' : ''}${Math.round(nextScore)} p`);
|
||||
|
||||
if (nextScore < totalScore) {
|
||||
self._animateScoreCounter($scoreEl, nextScore, totalScore, increment, plusSign);
|
||||
}
|
||||
}, 25);
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper to move a score bar from its current position in the leaderboard
|
||||
* to a new position.
|
||||
*
|
||||
* @param {$.Element} $score the score bar to move
|
||||
* @param {Integer} position the new position in the leaderboard
|
||||
* @param {Integer} offset an offset in 'rem'
|
||||
* @param {Integer} timeout time to wait while moving before resolving the promise
|
||||
*/
|
||||
_animateMoveTo: function ($score, position, offset, timeout) {
|
||||
var animationDone;
|
||||
var animationPromise = new Promise(function (resolve) {
|
||||
animationDone = resolve;
|
||||
});
|
||||
$score.css('top', `calc(calc(${this.BAR_HEIGHT} * ${position}) + ${offset}rem)`);
|
||||
setTimeout(animationDone, timeout);
|
||||
return animationPromise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Takes the leaderboard prior to the current question results
|
||||
* and reduce all scores bars to a small width (3rem).
|
||||
* We keep the small score bars on screen for 1s.
|
||||
*
|
||||
* This visually prepares the display of points for the current question.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_prepareScores: function () {
|
||||
var self = this;
|
||||
var animationDone;
|
||||
var animationPromise = new Promise(function (resolve) {
|
||||
animationDone = resolve;
|
||||
});
|
||||
setTimeout(function () {
|
||||
this.$('.o_survey_session_leaderboard_bar').each(function () {
|
||||
var currentScore = parseInt($(this)
|
||||
.closest('.o_survey_session_leaderboard_item')
|
||||
.data('currentScore'))
|
||||
if (currentScore && currentScore !== 0) {
|
||||
$(this).css('transition', `width 1s cubic-bezier(.4,0,.4,1)`);
|
||||
$(this).css('width', self.BAR_MIN_WIDTH);
|
||||
}
|
||||
});
|
||||
setTimeout(animationDone, 1000);
|
||||
}, 300);
|
||||
|
||||
return animationPromise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Now that we have summed the score for the current question to the total score
|
||||
* of the user and re-weighted the bars accordingly, we need to re-order everything
|
||||
* to match the new ranking.
|
||||
*
|
||||
* In addition to moving the bars to their new position, we create a "bounce" effect
|
||||
* by moving the bar a little bit more to the top or bottom (depending on if it's moving up
|
||||
* the ranking or down), the moving it the other way around, then moving it to its final
|
||||
* position.
|
||||
*
|
||||
* (Feels complicated when explained but it's fairly simple once you see what it does).
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_reorderScores: function () {
|
||||
var self = this;
|
||||
var animationDone;
|
||||
var animationPromise = new Promise(function (resolve) {
|
||||
animationDone = resolve;
|
||||
});
|
||||
setTimeout(function () {
|
||||
self.$('.o_survey_session_leaderboard_item').each(async function () {
|
||||
var $score = $(this);
|
||||
var currentPosition = parseInt($(this).data('currentPosition'));
|
||||
var newPosition = parseInt($(this).data('newPosition'));
|
||||
if (currentPosition !== newPosition) {
|
||||
var offset = newPosition > currentPosition ? 2 : -2;
|
||||
await self._animateMoveTo($score, newPosition, offset, 300);
|
||||
$score.css('transition', 'top ease-in-out .1s');
|
||||
await self._animateMoveTo($score, newPosition, offset * -0.3, 100);
|
||||
await self._animateMoveTo($score, newPosition, 0, 0);
|
||||
animationDone();
|
||||
}
|
||||
});
|
||||
}, 1800);
|
||||
|
||||
return animationPromise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Will display the score for the current question.
|
||||
* We simultaneously:
|
||||
* - increase the width of "question bar"
|
||||
* (faded out bar right next to the global score one)
|
||||
* - animate the score for the question (ex: from + 0 p to + 40 p)
|
||||
*
|
||||
* (We keep a minimum width of 3rem to be able to display '+30 p' within the bar).
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_showQuestionScores: function () {
|
||||
var self = this;
|
||||
var animationDone;
|
||||
var animationPromise = new Promise(function (resolve) {
|
||||
animationDone = resolve;
|
||||
});
|
||||
setTimeout(function () {
|
||||
this.$('.o_survey_session_leaderboard_bar_question').each(function () {
|
||||
var $barEl = $(this);
|
||||
var width = `calc(calc(100% - ${self.BAR_WIDTH}) * ${$barEl.data('widthRatio')} + ${self.BAR_MIN_WIDTH})`;
|
||||
$barEl.css('transition', 'width 1s ease-out');
|
||||
$barEl.css('width', width);
|
||||
|
||||
var $scoreEl = $barEl
|
||||
.find('.o_survey_session_leaderboard_bar_question_score')
|
||||
.text('0 p');
|
||||
var questionScore = parseInt($barEl.data('questionScore'));
|
||||
if (questionScore && questionScore > 0) {
|
||||
var increment = parseInt($barEl.data('maxQuestionScore') / 40);
|
||||
if (!increment || increment === 0){
|
||||
increment = 1;
|
||||
}
|
||||
$scoreEl.text('+ 0 p');
|
||||
console.log($barEl.data('maxQuestionScore'));
|
||||
setTimeout(function () {
|
||||
self._animateScoreCounter(
|
||||
$scoreEl,
|
||||
0,
|
||||
questionScore,
|
||||
increment,
|
||||
true);
|
||||
}, 400);
|
||||
}
|
||||
setTimeout(animationDone, 1400);
|
||||
});
|
||||
}, 300);
|
||||
|
||||
return animationPromise;
|
||||
},
|
||||
|
||||
/**
|
||||
* After displaying the score for the current question, we sum the total score
|
||||
* of the user so far with the score of the current question.
|
||||
*
|
||||
* Ex:
|
||||
* We have ('#' for total score before question and '=' for current question score):
|
||||
* 210 p ####=================================== +30 p John
|
||||
* We want:
|
||||
* 240 p ###################################==== +30 p John
|
||||
*
|
||||
* Of course, we also have to weight the bars based on the maximum score.
|
||||
* So if John here has 50% of the points of the leader user, both the question score bar
|
||||
* and the total score bar need to have their width divided by 2:
|
||||
* 240 p ##################== +30 p John
|
||||
*
|
||||
* The width of both bars move at the same time to reach their new position,
|
||||
* with an animation on the width property.
|
||||
* The new width of the "question bar" should represent the ratio of won points
|
||||
* when compared to the total points.
|
||||
* (We keep a minimum width of 3rem to be able to display '+30 p' within the bar).
|
||||
*
|
||||
* The updated total score is animated towards the new value.
|
||||
* we keep this on screen for 500ms before reordering the bars.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_sumScores: function () {
|
||||
var self = this;
|
||||
var animationDone;
|
||||
var animationPromise = new Promise(function (resolve) {
|
||||
animationDone = resolve;
|
||||
});
|
||||
// values that felt the best after a lot of testing
|
||||
var growthAnimation = 'cubic-bezier(.5,0,.66,1.11)';
|
||||
setTimeout(function () {
|
||||
this.$('.o_survey_session_leaderboard_item').each(function () {
|
||||
var currentScore = parseInt($(this).data('currentScore'));
|
||||
var updatedScore = parseInt($(this).data('updatedScore'));
|
||||
var increment = parseInt($(this).data('maxQuestionScore') / 40);
|
||||
if (!increment || increment === 0){
|
||||
increment = 1;
|
||||
}
|
||||
self._animateScoreCounter(
|
||||
$(this).find('.o_survey_session_leaderboard_score'),
|
||||
currentScore,
|
||||
updatedScore,
|
||||
increment,
|
||||
false);
|
||||
|
||||
var maxUpdatedScore = parseInt($(this).data('maxUpdatedScore'));
|
||||
var baseRatio = updatedScore / maxUpdatedScore;
|
||||
var questionScore = parseInt($(this).data('questionScore'));
|
||||
var questionRatio = questionScore /
|
||||
(updatedScore && updatedScore !== 0 ? updatedScore : 1);
|
||||
// we keep a min fixed with of 3rem to be able to display "+ 5 p"
|
||||
// even if the user already has 1.000.000 points
|
||||
var questionWith = `calc(calc(calc(100% - ${self.BAR_WIDTH}) * ${questionRatio * baseRatio}) + ${self.BAR_MIN_WIDTH})`;
|
||||
$(this)
|
||||
.find('.o_survey_session_leaderboard_bar_question')
|
||||
.css('transition', `width ease .5s ${growthAnimation}`)
|
||||
.css('width', questionWith);
|
||||
|
||||
var updatedScoreRatio = 1 - questionRatio;
|
||||
var updatedScoreWidth = `calc(calc(100% - ${self.BAR_WIDTH}) * ${updatedScoreRatio * baseRatio})`;
|
||||
$(this)
|
||||
.find('.o_survey_session_leaderboard_bar')
|
||||
.css('min-width', '0px')
|
||||
.css('transition', `width ease .5s ${growthAnimation}`)
|
||||
.css('width', updatedScoreWidth);
|
||||
|
||||
setTimeout(animationDone, 500);
|
||||
});
|
||||
}, 1400);
|
||||
|
||||
return animationPromise;
|
||||
}
|
||||
});
|
||||
|
||||
return publicWidget.registry.SurveySessionLeaderboard;
|
||||
|
||||
@@ -161,7 +161,7 @@ publicWidget.registry.SurveySessionManage = publicWidget.Widget.extend({
|
||||
if (this.isLastQuestion) {
|
||||
this.$('.o_survey_session_navigation_next').addClass('d-none');
|
||||
}
|
||||
this.leaderBoard.showLeaderboard(true);
|
||||
this.leaderBoard.showLeaderboard(true, this.isScoredQuestion);
|
||||
} else {
|
||||
if (!this.isLastQuestion) {
|
||||
this._nextQuestion();
|
||||
@@ -334,7 +334,7 @@ publicWidget.registry.SurveySessionManage = publicWidget.Widget.extend({
|
||||
self.$('.o_survey_session_leaderboard_title').text(_('Final Leaderboard'));
|
||||
self.$('.o_survey_session_navigation_next').addClass('d-none');
|
||||
self.$('.o_survey_leaderboard_buttons').removeClass('d-none');
|
||||
self.leaderBoard.showLeaderboard(false);
|
||||
self.leaderBoard.showLeaderboard(false, false);
|
||||
});
|
||||
} else {
|
||||
self.$('.o_survey_session_close').click();
|
||||
|
||||
@@ -343,19 +343,36 @@ nav#oe_main_menu_navbar {
|
||||
.o_survey_session_leaderboard {
|
||||
font-size: 1.4rem;
|
||||
|
||||
.o_survey_session_leaderboard_item {
|
||||
line-height: 1.8rem;
|
||||
.o_survey_session_leaderboard_container {
|
||||
height: calc(2.8rem * 15);
|
||||
}
|
||||
|
||||
.o_survey_session_leaderboard_index {
|
||||
width: 9.5rem;
|
||||
.o_survey_session_leaderboard_item {
|
||||
line-height: 2.4rem;
|
||||
width: 100%;
|
||||
transition: top ease-in-out .3s;
|
||||
|
||||
.o_survey_session_leaderboard_score {
|
||||
width: 6.5rem;
|
||||
padding-top: .2rem;
|
||||
height: 2.8rem;
|
||||
}
|
||||
|
||||
.o_survey_session_leaderboard_bar, .o_survey_session_leaderboard_bar_question {
|
||||
height: 2.8rem;
|
||||
}
|
||||
|
||||
.o_survey_session_leaderboard_bar {
|
||||
min-width: 90px;
|
||||
height: 2.2rem;
|
||||
min-width: 3rem;
|
||||
background-color: #007A77;
|
||||
transition: width ease 1.5s;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.o_survey_session_leaderboard_bar_question_score {
|
||||
top: .2rem;
|
||||
right: .5rem;
|
||||
width: 20rem;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.o_survey_session_leaderboard_name {
|
||||
|
||||
@@ -66,10 +66,10 @@ var isCorrectAnswer = function (answer) {
|
||||
};
|
||||
|
||||
/**
|
||||
* The check is based on the specific opacity (0.3) and color of incorrect answers.
|
||||
* The check is based on the specific opacity (0.2) and color of incorrect answers.
|
||||
*/
|
||||
var isIncorrectAnswer = function (answer) {
|
||||
return answer.backgroundColor.includes('0.3') &&
|
||||
return answer.backgroundColor.includes('0.2') &&
|
||||
answer.labelColor === WRONG_ANSWER_COLOR;
|
||||
};
|
||||
|
||||
@@ -414,23 +414,14 @@ tour.register('test_survey_session_manage_tour', {
|
||||
}
|
||||
}
|
||||
|
||||
// Now we go forward to the "Final Leaderboard" again (4 times)
|
||||
for (i = 0; i < 4; i++) {
|
||||
// Now we go forward to the "Final Leaderboard" again (3 times)
|
||||
for (i = 0; i < 3; i++) {
|
||||
nextScreen();
|
||||
}
|
||||
}
|
||||
}, {
|
||||
trigger: 'h1:contains("Final Leaderboard")',
|
||||
run: function () {} // Final Leaderboard is displayed
|
||||
}, {
|
||||
trigger: '.o_survey_session_leaderboard_item:contains("azerty"):contains("1."):contains("40")',
|
||||
run: function () {} // 'azerty' is first with 40 points
|
||||
}, {
|
||||
trigger: '.o_survey_session_leaderboard_item:contains("xxxTheB"):contains("2."):contains("20")',
|
||||
run: function () {} // 'xxxTheBestxxx' is second with 20 points
|
||||
}, {
|
||||
trigger: '.o_survey_session_leaderboard_item:contains("nicktalope"):contains("3."):contains("10")',
|
||||
run: function () {} // 'nicktalope' is third with 10 points
|
||||
}, {
|
||||
trigger: '.o_survey_session_close:has("i.fa-close")'
|
||||
}, {
|
||||
|
||||
@@ -144,30 +144,46 @@
|
||||
<a href="#" role="button" class="o_survey_session_close btn btn-primary" t-att-data-show-results="True"><i class="fa fa-bar-chart"/> Results</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="justify-content-center d-flex flex-column flex-grow-1 mb-5 o_survey_session_leaderboard_container" />
|
||||
<div class="justify-content-center d-flex flex-column flex-grow-1 mt-5 mb-5 pb-5 o_survey_session_leaderboard_container"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="user_input_session_leaderboard" name="Survey User Input Leaderboard">
|
||||
<div t-if="leaderboard">
|
||||
<t t-set="max_score" t-value="leaderboard[0].scoring_total or 1" />
|
||||
<t t-foreach="leaderboard" t-as="leaderboard_item">
|
||||
<div class="o_survey_session_leaderboard_item mb-1">
|
||||
<div class="o_survey_session_leaderboard_index d-inline-block font-weight-bold align-top">
|
||||
<div class="d-inline-block" t-esc="'%d.' % (leaderboard_item_index + 1)" />
|
||||
<div class="d-inline-block float-right mr-2" t-esc="'%.0f p' % leaderboard_item.scoring_total" />
|
||||
<div t-if="leaderboard" class="position-relative mb-5" t-attf-style="height: calc(3.8rem * #{len(leaderboard)});">
|
||||
<t t-set="max_score" t-value="max(score.get('scoring_total', 1) for score in leaderboard) or 1" />
|
||||
<t t-set="max_updated_score" t-value="max(score.get('updated_score', 1) for score in leaderboard)" />
|
||||
<t t-foreach="leaderboard" t-as="score">
|
||||
<div class="o_survey_session_leaderboard_item ml-2 d-flex position-absolute"
|
||||
t-attf-style="top: calc(#{score_index} * 3.8rem);"
|
||||
t-att-data-current-position="str(score_index)"
|
||||
t-att-data-new-position="str(score.get('leaderboard_position', score_index))"
|
||||
t-att-data-question-score="str(round(score.get('question_score', 0)))"
|
||||
t-att-data-current-score="str(round(score.get('scoring_total', 0)))"
|
||||
t-att-data-updated-score="str(round(score.get('updated_score', 0)))"
|
||||
t-att-data-max-question-score="round(score.get('max_question_score', 1))"
|
||||
t-att-data-max-updated-score="round(max_updated_score)">
|
||||
<div class="d-inline-block font-weight-bold align-top">
|
||||
<div class="d-inline-block mr-2 o_survey_session_leaderboard_score" t-esc="'%.0f p' % score['scoring_total']" />
|
||||
</div>
|
||||
<!-- We keep "10rem" of space to display the points.
|
||||
<!-- We keep "18rem" of space to display the points / nickname.
|
||||
Then, the length of the bar is a percentage of the attendee's score compared to the max_score. -->
|
||||
<t t-set="width_ratio" t-value="round(leaderboard_item.scoring_total / max_score, 3)"/>
|
||||
<div class="o_survey_session_leaderboard_bar mr-1 pr-2 align-top d-inline-block text-right font-weight-bold"
|
||||
t-att-style="'width: %s' % ('0%' if animate_width else ('calc(calc(%s - 18rem) * %s' % ('100%', width_ratio)))"
|
||||
<t t-set="width_ratio" t-value="round(round(score['scoring_total']) / round(max_score), 3)"/>
|
||||
<t t-set="width_ratio_question" t-value="str(round(round(score.get('question_score', 0)) / round(score.get('max_question_score', 1)), 3))"/>
|
||||
<div class="o_survey_session_leaderboard_bar ml-2 align-top d-inline-block text-right font-weight-bold"
|
||||
t-att-style="'width: calc(calc(%s - 18rem) * %s)' % ('100%', width_ratio)"
|
||||
t-att-data-width-ratio="width_ratio">
|
||||
</div>
|
||||
<div class="o_survey_session_leaderboard_name d-inline-block font-weight-bold">
|
||||
<span t-if="leaderboard_item.nickname" t-esc="leaderboard_item.nickname" />
|
||||
<div class="o_survey_session_leaderboard_bar_question mr-2 align-top d-inline-block text-right font-weight-bold position-relative"
|
||||
style="width: 0px;"
|
||||
t-att-data-width-ratio="width_ratio_question"
|
||||
t-att-data-max-question-score="str(score.get('max_question_score', 1))"
|
||||
t-att-data-question-score="str(score.get('question_score', 0))">
|
||||
<div class="o_survey_session_leaderboard_bar_question_score position-absolute"></div>
|
||||
</div>
|
||||
<div class="o_survey_session_leaderboard_name d-inline-block">
|
||||
<span t-if="score.get('nickname')" t-esc="score['nickname']" />
|
||||
<span t-else="">Anonymous</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user