Change UI ranking

This commit is contained in:
cuom1999 2022-06-01 00:28:56 -05:00
parent d38342ad43
commit 9b1724cdad
16 changed files with 291 additions and 631 deletions

View file

@ -310,7 +310,6 @@ urlpatterns = [
ticket.NewProblemTicketView.as_view(),
name="new_problem_ticket",
),
url(r"^/vote$", problem.Vote.as_view(), name="vote"),
url(
r"^/manage/submission",
include(
@ -529,11 +528,18 @@ urlpatterns = [
),
),
url(
r"^/submissions/(?P<user>\w+)/(?P<problem>\w+)/",
r"^/submissions/(?P<user>\w+)/(?P<problem>\w+)",
paged_list_view(
submission.UserContestSubmissions, "contest_user_submissions"
),
),
url(
r"^/submissions/(?P<user>\w+)/(?P<problem>\w+)/ajax",
paged_list_view(
submission.UserContestSubmissionsAjax,
"contest_user_submissions_ajax",
),
),
url(
r"^/participations$",
contests.ContestParticipationList.as_view(),

View file

@ -55,7 +55,7 @@ class DefaultContestFormat(BaseContestFormat):
format_data = (participation.format_data or {}).get(str(contest_problem.id))
if format_data:
return format_html(
'<td class="{state} problem-score-col"><a href="{url}">{points}<div class="solving-time">{time}</div></a></td>',
'<td class="{state} problem-score-col"><a data-featherlight="{url}" href="#">{points}<div class="solving-time">{time}</div></a></td>',
state=(
(
"pretest-"
@ -68,7 +68,7 @@ class DefaultContestFormat(BaseContestFormat):
)
),
url=reverse(
"contest_user_submissions",
"contest_user_submissions_ajax",
args=[
self.contest.key,
participation.user.user.username,

View file

@ -171,7 +171,8 @@ msgid ""
msgstr ""
"Content-Type: text/plain; charset=utf-8\\n"
""")
"""
)
if self.verbosity > 1:
self.stdout.write("processing navigation bar")
for label in NavigationBar.objects.values_list("label", flat=True):

View file

@ -561,29 +561,6 @@ class Problem(models.Model):
save.alters_data = True
def can_vote(self, request):
return False
user = request.user
if not user.is_authenticated:
return False
# If the user is in contest, nothing should be shown.
if request.in_contest_mode:
return False
# If the user is not allowed to vote
if user.profile.is_unlisted or user.profile.is_banned_problem_voting:
return False
# If the user is banned from submitting to the problem.
if self.banned_users.filter(pk=user.pk).exists():
return False
# If the user has a full AC submission to the problem (solved the problem).
return self.submission_set.filter(
user=user.profile, result="AC", points=F("problem__points")
).exists()
class Meta:
permissions = (
("see_private_problem", "See hidden problems"),

View file

@ -644,6 +644,7 @@ class KickUserWidgetView(
LoginRequiredMixin, AdminOrganizationMixin, SingleObjectMixin, View
):
model = Organization
def post(self, request, *args, **kwargs):
organization = self.get_object()
try:

View file

@ -306,78 +306,9 @@ class ProblemDetail(ProblemMixin, SolvedProblemMixin, CommentedDetailView):
context["meta_description"] = self.object.summary or metadata[0]
context["og_image"] = self.object.og_image or metadata[1]
context["can_vote"] = self.object.can_vote(self.request)
if context["can_vote"]:
try:
context["vote"] = ProblemPointsVote.objects.get(
voter=user.profile, problem=self.object
)
except ObjectDoesNotExist:
context["vote"] = None
else:
context["vote"] = None
context["has_votes"] = False
if user.is_superuser:
all_votes = list(
self.object.problem_points_votes.order_by("points").values_list(
"points", flat=True
)
)
context["all_votes"] = all_votes
context["has_votes"] = len(all_votes) > 0
context["max_possible_vote"] = 600
context["min_possible_vote"] = 100
return context
class DeleteVote(ProblemMixin, SingleObjectMixin, View):
def get(self, request, *args, **kwargs):
return HttpResponseForbidden(status=405, content_type="text/plain")
def post(self, request, *args, **kwargs):
self.object = self.get_object()
if not request.user.is_authenticated:
return HttpResponseForbidden("Not signed in.", content_type="text/plain")
elif self.object.can_vote(request.user):
ProblemPointsVote.objects.filter(
voter=request.profile, problem=self.object
).delete()
return HttpResponse("success", content_type="text/plain")
else:
return HttpResponseForbidden(
"Not allowed to delete votes on this problem.",
content_type="text/plain",
)
class Vote(ProblemMixin, SingleObjectMixin, View):
def get(self, request, *args, **kwargs):
return HttpResponseForbidden(status=405, content_type="text/plain")
def post(self, request, *args, **kwargs):
self.object = self.get_object()
if not self.object.can_vote(request): # Not allowed to vote for some reason.
return HttpResponseForbidden(
"Not allowed to vote on this problem.", content_type="text/plain"
)
form = ProblemPointsVoteForm(request.POST)
if form.is_valid():
with transaction.atomic():
# Delete any pre existing votes.
ProblemPointsVote.objects.filter(
voter=request.profile, problem=self.object
).delete()
vote = form.save(commit=False)
vote.voter = request.profile
vote.problem = self.object
vote.save()
return JsonResponse({"points": vote.points})
else:
return JsonResponse(form.errors, status=400)
class LatexError(Exception):
pass

View file

@ -18,6 +18,7 @@ from django.http import HttpResponseRedirect
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from django.shortcuts import render
from django.template.defaultfilters import floatformat
from django.urls import reverse
from django.utils import timezone
from django.utils.functional import cached_property
@ -47,6 +48,7 @@ from judge.utils.problem_data import get_problem_case
from judge.utils.raw_sql import join_sql_subquery, use_straight_join
from judge.utils.views import DiggPaginatorMixin
from judge.utils.views import TitleMixin
from judge.utils.timedelta import nice_repr
def submission_related(queryset):
@ -358,7 +360,8 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView):
)
if self.selected_statuses:
queryset = queryset.filter(
Q(result__in=self.selected_statuses) | Q(status__in=self.selected_statuses)
Q(result__in=self.selected_statuses)
| Q(status__in=self.selected_statuses)
)
return queryset
@ -392,9 +395,7 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView):
hidden_codes = ["SC", "D", "G"]
if not self.request.user.is_superuser and not self.request.user.is_staff:
hidden_codes += ["IE"]
return [
(key, value) for key, value in all_statuses if key not in hidden_codes
]
return [(key, value) for key, value in all_statuses if key not in hidden_codes]
def get_context_data(self, **kwargs):
context = super(SubmissionsListBase, self).get_context_data(**kwargs)
@ -782,3 +783,30 @@ class UserContestSubmissions(ForceContestMixin, UserProblemSubmissions):
self.contest.name,
reverse("contest_view", args=[self.contest.key]),
)
class UserContestSubmissionsAjax(UserContestSubmissions):
template_name = "submission/user-ajax.html"
def contest_time(self, s):
if s.contest.participation.live:
return s.date - s.contest.participation.real_start
return None
def get_context_data(self, **kwargs):
context = super(UserContestSubmissionsAjax, self).get_context_data(**kwargs)
context["contest"] = self.contest
context["problem"] = self.problem
context["profile"] = self.profile
contest_problem = self.contest.contest_problems.get(problem=self.problem)
for s in context["submissions"]:
contest_time = self.contest_time(s)
if contest_time:
s.contest_time = nice_repr(contest_time, "noday")
else:
s.contest_time = None
points = floatformat(s.contest.points, -self.contest.points_precision)
total = floatformat(contest_problem.points, -self.contest.points_precision)
s.display_point = f"{points} / {total}"
return context

View file

@ -362,3 +362,9 @@ label[for="language"], label[for="status"] {
color: gray;
}
}
.lightbox-submissions {
td {
padding-right: 0.2em;
}
}

View file

@ -1,4 +1,142 @@
<script src="//cdn.jsdelivr.net/npm/featherlight@1.7.14/release/featherlight.min.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
function isFaster(time1, time2) {
let arr1 = time1.split(':');
let arr2 = time2.split(':');
for (let i in arr1) {
let val1 = parseInt(arr1[i]);
let val2 = parseInt(arr2[i]);
if (val1 < val2) return true;
if (val1 > val2) return false;
}
return false;
}
function scoretimeComparison(sub1, sub2) {
if (!sub2) return true;
return sub1['score'] > sub2['score'] || (sub1['score'] === sub2['score'] && isFaster(sub1['time'], sub2['time']));
}
function highlightFirstSolve() {
// bucket to store submissions by problems
let bestSubmissions = {};
// get information
$('td a').each(function() {
let td = $(this)[0]
let link = td['attributes']['href']['value']
if (link.includes('submissions')) {
let scoreAndTime = (td.innerText.split('\n'))
let linkElements = link.split('/')
// get information
let problem = linkElements[linkElements.length - 2];
let score = parseFloat(scoreAndTime[0]);
let time = scoreAndTime[1];
if (time) {
let curSubmission = {
'td': $(this).parent(),
'score': score,
'time': time
}
// update best submissions
let curBest = bestSubmissions[problem]
if (scoretimeComparison(curSubmission, curBest) && score) {
bestSubmissions[problem] = curSubmission;
}
}
}
})
for (let problem in bestSubmissions) {
bestSubmissions[problem]['td'].addClass('first-solve')
}
}
function renew_filter() {
var checkboxes = [
'#show-organizations-checkbox',
'#show-fullnames-checkbox',
'#show-total-score-checkbox',
];
var checkboxes2 = [
'#show-friends-checkbox',
'#show-virtual-checkbox'
]
for (var i of checkboxes) {
var $box = $(i);
if ($box.is(':checked')) {
$box.prop('checked', false);
$box.click();
$box.prop('checked', true);
}
}
var to_update = false;
for (var i of checkboxes2) {
var $box = $(i);
if ($box.is(':checked')) {
to_update = true;
}
}
if (to_update) {
update_ranking();
}
}
function get_initial_rank() {
var ranks = $('.rank-td').map(function() {return this.innerHTML}).get();
var usernames = $('.user-name .rating a').map(function() {return this.text}).get();
window.user_rank = new Map();
for (var i = 0; i < ranks.length; i++) {
window.user_rank[usernames[i]] = ranks[i];
}
}
function add_initial_friend_rank() {
var usernames = $('.user-name .rating a').map(function() {return this.text}).get();
var is_virtual = [];
$('.user-name').each(function() {
if($(this).children('sup').length) {
is_virtual.push(1);
}
else is_virtual.push(0);
});
$('.rank-td').each(function(i) {
if (!is_virtual[i]) this.innerHTML += ' (' + window.user_rank[usernames[i]] + ')';
});
}
function update_ranking() {
var friend = $('#show-friends-checkbox').is(':checked');
var virtual = $('#show-virtual-checkbox').is(':checked');
$('#loading-gif').show();
$.get({
url: `{{url('contest_ranking_ajax', contest.key)}}?friend=${friend}&virtual=${virtual}`,
success: function(HTML) {
$('#users-table').html(HTML);
highlightFirstSolve();
$('#loading-gif').hide();
if (!virtual && !friend) {
get_initial_rank();
}
if (friend) {
add_initial_friend_rank();
}
},
fail: function() {
console.log('Fail to update ranking');
}
});
}
$(function () {
$('.leaving-forever').click(function () {
return confirm('{{ _('Are you sure you want to leave?') }}\n' +
@ -9,5 +147,68 @@
return confirm('{{ _('Are you sure you want to join?') }}\n' +
'{{ _('Joining a contest starts your timer, after which it becomes unstoppable.') }}');
});
var url = '{{ url('contest_participation', contest.key, '__username__') }}';
var placeholder = $('#search-contest').replaceWith($('<select>').attr({
id: 'search-contest'
})).attr('placeholder');
$('#search-contest').select2({
placeholder: placeholder,
ajax: {
url: '{{ url('contest_user_search_select2_ajax', contest.key) }}'
},
minimumInputLength: 1,
escapeMarkup: function (markup) {
return markup;
},
templateResult: function (data, container) {
return ('<img class="user-search-image" src="' + data.gravatar_url + '" width="24" height="24">' +
'<span class="' + data.display_rank + ' user-search-name">' + data.text + '</span>');
}
}).on('change', function () {
window.location.href = url.replace('__username__', $(this).val());
});
$('#show-organizations-checkbox').click(function () {
$('.organization-column').toggle();
});
$('#show-fullnames-checkbox').click(function () {
$('.fullname-column').toggle();
});
{% if request.user.is_authenticated %}
$('#show-friends-checkbox').click(function() {
update_ranking();
})
{% endif %}
$('#show-virtual-checkbox').click(function() {
update_ranking();
})
$('#show-total-score-checkbox').click(function() {
$('.problem-score-col').toggle();
})
highlightFirstSolve();
renew_filter();
get_initial_rank();
{% if participation_tab %}
$('#show-virtual-checkbox').hide();
$('#show-virtual-label').hide();
{% else %}
{% if request.in_contest %}
setInterval(update_ranking, 60 * 1000);
{% endif %}
{% endif %}
// $(".problem-score-a").on('click', function(e) {
// var href = $(this).attr('href');
// if (href !== '#') return;
// e.preventDefault();
// })
});
</script>

View file

@ -58,7 +58,7 @@
{% block before_point_head %}
{% for problem in problems %}
<th class="points header problem-score-col"><a href="{{ url('contest_ranked_submissions', contest.key, problem.problem.code) }}">
<th class="points header problem-score-col" title="{{ problem.problem.name }}"><a href="{{ url('problem_detail', problem.problem.code) }}">
{{- contest.get_label_for_problem(loop.index0) }}
<div class="point-denominator">{{ problem.points }}</div>
</a></th>

View file

@ -8,6 +8,8 @@
{% endblock %}
{% block users_media %}
<link href="//cdn.jsdelivr.net/npm/featherlight@1.7.14/release/featherlight.min.css" type="text/css" rel="stylesheet" />
<style>
#content-left {
overflow-x: auto;
@ -130,6 +132,12 @@
color: gray !important;
font-weight: 600;
}
.featherlight-content {
border-radius: 10px;
height: 80%;
width: 60%;
}
</style>
{% if has_rating %}
@ -227,215 +235,6 @@
});
</script>
{% endif %}
<script type="text/javascript" src="{{ static('event.js') }}"></script>
<script type="text/javascript">
function isFaster(time1, time2) {
let arr1 = time1.split(':');
let arr2 = time2.split(':');
for (let i in arr1) {
let val1 = parseInt(arr1[i]);
let val2 = parseInt(arr2[i]);
if (val1 < val2) return true;
if (val1 > val2) return false;
}
return false;
}
function scoretimeComparison(sub1, sub2) {
if (!sub2) return true;
return sub1['score'] > sub2['score'] || (sub1['score'] === sub2['score'] && isFaster(sub1['time'], sub2['time']));
}
function highlightFirstSolve() {
// bucket to store submissions by problems
let bestSubmissions = {};
// get information
$('td a').each(function() {
let td = $(this)[0]
let link = td['attributes']['href']['value']
if (link.includes('submissions')) {
let scoreAndTime = (td.innerText.split('\n'))
let linkElements = link.split('/')
// get information
let problem = linkElements[linkElements.length - 2];
let score = parseFloat(scoreAndTime[0]);
let time = scoreAndTime[1];
if (time) {
let curSubmission = {
'td': $(this).parent(),
'score': score,
'time': time
}
// update best submissions
let curBest = bestSubmissions[problem]
if (scoretimeComparison(curSubmission, curBest) && score) {
bestSubmissions[problem] = curSubmission;
}
}
}
})
for (let problem in bestSubmissions) {
bestSubmissions[problem]['td'].addClass('first-solve')
}
}
function renew_filter() {
var checkboxes = [
'#show-organizations-checkbox',
'#show-fullnames-checkbox',
'#show-total-score-checkbox',
];
var checkboxes2 = [
'#show-friends-checkbox',
'#show-virtual-checkbox'
]
for (var i of checkboxes) {
var $box = $(i);
if ($box.is(':checked')) {
$box.prop('checked', false);
$box.click();
$box.prop('checked', true);
}
}
var to_update = false;
for (var i of checkboxes2) {
var $box = $(i);
if ($box.is(':checked')) {
to_update = true;
}
}
if (to_update) {
update_ranking();
}
}
function get_initial_rank() {
var ranks = $('.rank-td').map(function() {return this.innerHTML}).get();
var usernames = $('.user-name .rating a').map(function() {return this.text}).get();
window.user_rank = new Map();
for (var i = 0; i < ranks.length; i++) {
window.user_rank[usernames[i]] = ranks[i];
}
}
function add_initial_friend_rank() {
var usernames = $('.user-name .rating a').map(function() {return this.text}).get();
var is_virtual = [];
$('.user-name').each(function() {
if($(this).children('sup').length) {
is_virtual.push(1);
}
else is_virtual.push(0);
});
$('.rank-td').each(function(i) {
if (!is_virtual[i]) this.innerHTML += ' (' + window.user_rank[usernames[i]] + ')';
});
}
function update_ranking() {
var friend = $('#show-friends-checkbox').is(':checked');
var virtual = $('#show-virtual-checkbox').is(':checked');
$('#loading-gif').show();
$.get({
url: `/contest/{{contest.key}}/ranking/ajax?friend=${friend}&virtual=${virtual}`,
success: function(HTML) {
$('#users-table').html(HTML);
highlightFirstSolve();
$('#loading-gif').hide();
if (!virtual && !friend) {
get_initial_rank();
}
if (friend) {
add_initial_friend_rank();
}
},
fail: function() {
console.log('Fail to update ranking');
}
});
}
// window.load_dynamic_update = function (last_msg) {
// return new EventReceiver(
// "{{ EVENT_DAEMON_LOCATION }}", "{{ EVENT_DAEMON_POLL_LOCATION }}",
// ['contest_{{contest.id}}'], last_msg, function (message) {
// switch (message.type) {
// case 'update':
// update_ranking();
// break;
// }
// }
// );
// }
$(function () {
var url = '{{ url('contest_participation', contest.key, '__username__') }}';
var placeholder = $('#search-contest').replaceWith($('<select>').attr({
id: 'search-contest'
})).attr('placeholder');
$('#search-contest').select2({
placeholder: placeholder,
ajax: {
url: '{{ url('contest_user_search_select2_ajax', contest.key) }}'
},
minimumInputLength: 1,
escapeMarkup: function (markup) {
return markup;
},
templateResult: function (data, container) {
return ('<img class="user-search-image" src="' + data.gravatar_url + '" width="24" height="24">' +
'<span class="' + data.display_rank + ' user-search-name">' + data.text + '</span>');
}
}).on('change', function () {
window.location.href = url.replace('__username__', $(this).val());
});
$('#show-organizations-checkbox').click(function () {
$('.organization-column').toggle();
});
$('#show-fullnames-checkbox').click(function () {
$('.fullname-column').toggle();
});
{% if request.user.is_authenticated %}
$('#show-friends-checkbox').click(function() {
update_ranking();
})
{% endif %}
$('#show-virtual-checkbox').click(function() {
update_ranking();
})
$('#show-total-score-checkbox').click(function() {
$('.problem-score-col').toggle();
})
highlightFirstSolve();
renew_filter();
get_initial_rank();
{% if participation_tab %}
$('#show-virtual-checkbox').hide();
$('#show-virtual-label').hide();
{% else %}
{% if request.in_contest %}
setInterval(update_ranking, 60 * 1000);
{% endif %}
{% endif %}
});
</script>
{% include "contest/media-js.html" %}
{% endblock %}

View file

@ -346,9 +346,6 @@
{% endblock %}
{% block description %}
{% if can_vote and not vote %}
{% include 'problem/voting-form.html' %}
{% endif %}
{% if contest_problem and contest_problem.contest.use_clarifications and has_clarifications %}
<div id="clarification_header_container">
<i class="fa fa-question-circle"></i>
@ -374,9 +371,6 @@
{% endblock %}
{% block post_description_end %}
{% if can_vote or request.user.is_superuser %}
{% include 'problem/voting-controls.html' %}
{% endif %}
{% if request.user.is_authenticated and not request.profile.mute %}
<a href="{{ url('new_problem_ticket', problem.code) }}" class="clarify">
<i class="fa fa-flag" style="margin-right:0.5em"></i>

View file

@ -1,75 +0,0 @@
<style>
.featherlight .featherlight-inner{
display: block !important;
}
.featherlight-content {
overflow: inherit !important;
}
.stars-container {
display: inline-block;
vertical-align: top;
}
.stars-container .star {
display: inline-block;
padding: 2px;
color: orange;
cursor: pointer;
font-size: 30px;
}
.stars-container .star:before {
content:"\f006";
}
.filled-star:before, .star:hover:before {
content:"\f005" !important;
}
</style>
{% if can_vote or request.user.is_superuser %}
<div class="vote-form" id="id_vote_form_box" style="display: none">
{% include 'problem/voting-form.html' %}
</div>
<span>
{% if can_vote %}
<a href="#" class="form-button" id="id_vote_button" data-featherlight="#id_vote_form_box"></a>
{% endif %}
{% if request.user.is_superuser %}
- {% include 'problem/voting-stats.html' %}
{% endif %}
</span>
<script src="{{ static('libs/featherlight/featherlight.min.js') }}" type="text/javascript"></script>
<script>
let voted_points = null;
{% if vote is not none %}
let has_voted = true;
voted_points = {{ vote.points }};
{% else %}
let has_voted = false;
{% endif %}
function voteUpdate(){
$('#id_has_voted_prompt').show();
$('#id_current_vote_value').prop('innerText', voted_points);
$('#id_has_not_voted_prompt').hide();
$('#id_vote_button').prop('innerText', `{{ _('Edit difficulty') }} (` + voted_points + ')');
$('#id_points_error_box').hide();
has_voted = true;
}
function deleteVoteUpdate(){
$('#id_has_voted_prompt').hide();
$('#id_has_not_voted_prompt').show();
$('#id_vote_button').prop('innerText', `{{ _('Vote difficulty') }}`);
$('#id_points_error_box').hide();
has_voted = false;
}
if (has_voted) voteUpdate();
else deleteVoteUpdate();
$('#id_vote_form').on('submit', function (e) {
e.preventDefault();
});
</script>
{% endif %}

View file

@ -1,93 +0,0 @@
<style>
.vote_form {
border: 3px solid blue;
border-radius: 5px;
width: fit-content;
margin-left: auto;
margin-right: auto;
padding: 0.2em 1em;
background: #ebf0ff;
color: #2e69ff;
}
</style>
<form id="id_vote_form" class="vote_form">
{% csrf_token %}
<input id="id_points" class="vote-form-value" type="hidden" step="1"
min="{{ min_possible_vote }}" max="{{ max_possible_vote }}" name="points">
<table>
<tbody>
<tr>
<td style="padding-top: 1em; padding-right: 3em">
<span><b>{{_('How difficult is this problem?')}}</b></span>
</td>
<td>
<div class="vote-rating">
<span class="stars-container">
{% for i in range(1, (max_possible_vote - min_possible_vote) // 100 + 2) %}
<span id="star{{i}}" star-score="{{i * 100}}" class="fa star"></span>
{% endfor %}
</span>
</div>
</td>
</tr>
<tr>
<td>
<span>{{_('This helps us improve the site')}}</span>
</td>
<td>
<span style="padding-left: 0.2em"><b>{{min_possible_vote}}</b></span>
<span style="float: right; padding-right: 0.2em"><b>{{max_possible_vote}}</b></span>
</td>
</tr>
</tbody>
</table>
<div id="id_points_error_box">
<span id="id_points_error" class="voting-form-error"></span>
</div>
<div>
<input type="hidden" id="id_vote_form_submit_button">
</div>
</form>
<script>
$('.star').hover(
(e) => $(e.target).prevAll().addClass('filled-star'),
(e) => $(e.target).prevAll().removeClass('filled-star')
);
$('.star').on('click', function() {
$("#id_points").val($(this).attr('star-score'));
$('#id_vote_form_submit_button').click();
$('#id_vote_form').fadeOut(500);
})
$('#id_vote_form_submit_button').on('click', function (e) {
e.preventDefault();
$.ajax({
url: '{{ url('vote', object.code) }}',
type: 'POST',
data: $('#id_vote_form').serialize(),
success: function (data) {
{% if request.user.is_superuser %}
updateUserVote(voted_points, data.points);
{% endif %}
voted_points = data.points;
voteUpdate();
// Forms are auto disabled to prevent resubmission, but we need to allow resubmission here.
$('#id_vote_form_submit_button').removeAttr('disabled');
var current = $.featherlight.current();
if (current) current.close();
},
error: function (data) {
let errors = data.responseJSON;
if(errors === undefined) {
alert('Unable to delete vote: ' + data.responsetext);
}
if('points' in errors){
$('#id_points_error_box').show();
$('#id_points_error').prop('textContent', errors.points[0]);
} else {
$('#id_points_error_box').hide();
}
}
});
});
</script>

View file

@ -1,146 +0,0 @@
<style>
.vote-stats-background {
background-color: rgb(255,255,255);
padding: 20px;
border-radius: 25px;
justify-content: center;
align-items: center;
margin: auto;
text-align: center;
}
.canvas {
border: 1px;
border: solid;
border: #000000;
}
.vote-stats-value {
margin-right: 1em;
}
.vote-stats-info {
font-weight: 500;
}
</style>
<a href="#" class="form-button" id="id_vote_stats_button">{{ _('Statistics') }}</a>
<div class="vote-stats-background" id="id_vote_stats" style="display: none;">
<script src={{ static('libs/chart.js/Chart.js') }}></script>
<span class="vote-stats-info">{{ _('Voting Statistics') }}</span>
<br/>
<canvas class="canvas" id="id_vote_chart"></canvas>
<span id="id_no_votes_error" class="vote-stats-info no_votes_error">{{ _('No Votes Available!') }}</span>
<br/>
<div id="id_has_votes_footer" class="has_votes_footer">
<span class="vote-stats-info">{{ _('Median:') }}</span>
<span class="vote-stats-value median_vote" id="id_median_vote"></span>
<span class="vote-stats-info">{{ _('Mean:') }}</span>
<span class="vote-stats-value mean_vote" id="id_mean_vote"></span>
<span class="vote-stats-info">{{ _('Total:') }}</span>
<span class="vote-stats-value total_vote" id="id_num_of_votes"></span>
</div>
</div>
<script>
let voteChart = null;
let allVotes = {{ all_votes }};
function reload_vote_graph() {
if (voteChart !== null) voteChart.destroy();
if (allVotes.length === 0) {
$('.canvas').hide();
$('.has_votes_footer').hide();
$('.no_votes_error').show();
} else {
$('.canvas').show();
$('.has_votes_footer').show();
$('.no_votes_error').hide();
allVotes.sort(function(a, b){return a - b});
// Give the graph some padding on both sides.
let min_points = {{ min_possible_vote }};
let max_points = {{ max_possible_vote }};
let xlabels = [];
let voteFreq = [];
for (let i = min_points; i <= max_points; i += 100) {
xlabels.push(i);
voteFreq.push(0);
}
let max_number_of_votes = 0;
let total_votes = 0;
let mean = 0;
for (let i = 0; i < allVotes.length; i++) {
// Assume the allVotes is valid.
voteFreq[(allVotes[i] - min_points) / 100]++;
max_number_of_votes = Math.max(max_number_of_votes, voteFreq[(allVotes[i] - min_points) / 100]);
mean += allVotes[i];
total_votes++;
}
mean = mean / total_votes;
let half = Math.floor(total_votes / 2);
let median = allVotes[half];
if (total_votes % 2 === 0) {
median = (median + allVotes[half - 1]) / 2;
}
$('.mean_vote').prop('innerText', mean.toFixed(2));
$('.median_vote').prop('innerText', median.toFixed(2));
$('.total_vote').prop('innerText', total_votes);
const voteData = {
labels: xlabels,
datasets: [{
data: voteFreq,
backgroundColor: 'pink',
}]
};
const voteDataConfig = {
type: 'bar',
data: voteData,
options: {
legend: {
display: false
},
responsive: true,
scales: {
yAxes: [{
ticks: {
precision: 0,
suggestedMax: Math.ceil(max_number_of_votes * 1.2),
beginAtZero: true,
}
}],
xAxes: [{
ticks: {
beginAtZero: false,
}
}]
}
}
};
voteChart = new Chart($('.featherlight-inner .canvas'), voteDataConfig);
}
}
function updateUserVote(prev_voted_points, voted_points) {
let index = allVotes.indexOf(prev_voted_points);
if (index > -1) {
allVotes.splice(index, 1);
}
allVotes.push(voted_points);
}
function deleteUserVote(prev_voted_points) {
allVotes.splice(allVotes.indexOf(prev_voted_points), 1);
}
$(function() {
$('#id_vote_stats_button').featherlight('#id_vote_stats', {
afterOpen: reload_vote_graph,
})
});
</script>

View file

@ -0,0 +1,30 @@
<h4>
{{_('Contest submissions of')}} {{link_user(profile)}} <a href="{{url('contest_user_submissions', contest.key, profile.user.username, problem.code)}}">#</a>
</h4>
<hr>
<table class="lightbox-submissions"><tbody>
{% for submission in submissions %}
<tr>
{% set can_view = submission_layout(submission, profile_id, request.user, editable_problem_ids, completed_problem_ids) %}
<td>
{% if submission.contest_time %}
{{submission.contest_time}}
{% else %}
{% trans time=submission.date|date(_("N j, Y, g:i a")) %}
{{ time }}
{% endtrans %}
{% endif %}
</td>
<td class="case-{{submission.result}}" style="margin-left: 1em">{{submission.display_point}}</td>
<td class="case-{{submission.result}}" style="margin-left: 1em">({{submission.short_status}})</td>
<td>
[{{_('pretests') if submission.contest.is_pretest else _('main tests')}}]
</td>
{% if can_view %}
<td>
<a href="{{url('submission_status', submission.id)}}">{{submission.id}}</a>
</td>
{% endif %}
</tr>
{% endfor %}
</tbody></table>