Change UI ranking
This commit is contained in:
parent
d38342ad43
commit
9b1724cdad
16 changed files with 291 additions and 631 deletions
10
dmoj/urls.py
10
dmoj/urls.py
|
@ -310,7 +310,6 @@ urlpatterns = [
|
||||||
ticket.NewProblemTicketView.as_view(),
|
ticket.NewProblemTicketView.as_view(),
|
||||||
name="new_problem_ticket",
|
name="new_problem_ticket",
|
||||||
),
|
),
|
||||||
url(r"^/vote$", problem.Vote.as_view(), name="vote"),
|
|
||||||
url(
|
url(
|
||||||
r"^/manage/submission",
|
r"^/manage/submission",
|
||||||
include(
|
include(
|
||||||
|
@ -529,11 +528,18 @@ urlpatterns = [
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
url(
|
url(
|
||||||
r"^/submissions/(?P<user>\w+)/(?P<problem>\w+)/",
|
r"^/submissions/(?P<user>\w+)/(?P<problem>\w+)",
|
||||||
paged_list_view(
|
paged_list_view(
|
||||||
submission.UserContestSubmissions, "contest_user_submissions"
|
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(
|
url(
|
||||||
r"^/participations$",
|
r"^/participations$",
|
||||||
contests.ContestParticipationList.as_view(),
|
contests.ContestParticipationList.as_view(),
|
||||||
|
|
|
@ -55,7 +55,7 @@ class DefaultContestFormat(BaseContestFormat):
|
||||||
format_data = (participation.format_data or {}).get(str(contest_problem.id))
|
format_data = (participation.format_data or {}).get(str(contest_problem.id))
|
||||||
if format_data:
|
if format_data:
|
||||||
return format_html(
|
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=(
|
state=(
|
||||||
(
|
(
|
||||||
"pretest-"
|
"pretest-"
|
||||||
|
@ -68,7 +68,7 @@ class DefaultContestFormat(BaseContestFormat):
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
url=reverse(
|
url=reverse(
|
||||||
"contest_user_submissions",
|
"contest_user_submissions_ajax",
|
||||||
args=[
|
args=[
|
||||||
self.contest.key,
|
self.contest.key,
|
||||||
participation.user.user.username,
|
participation.user.user.username,
|
||||||
|
|
|
@ -171,7 +171,8 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
"Content-Type: text/plain; charset=utf-8\\n"
|
"Content-Type: text/plain; charset=utf-8\\n"
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
if self.verbosity > 1:
|
if self.verbosity > 1:
|
||||||
self.stdout.write("processing navigation bar")
|
self.stdout.write("processing navigation bar")
|
||||||
for label in NavigationBar.objects.values_list("label", flat=True):
|
for label in NavigationBar.objects.values_list("label", flat=True):
|
||||||
|
|
|
@ -561,29 +561,6 @@ class Problem(models.Model):
|
||||||
|
|
||||||
save.alters_data = True
|
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:
|
class Meta:
|
||||||
permissions = (
|
permissions = (
|
||||||
("see_private_problem", "See hidden problems"),
|
("see_private_problem", "See hidden problems"),
|
||||||
|
|
|
@ -644,6 +644,7 @@ class KickUserWidgetView(
|
||||||
LoginRequiredMixin, AdminOrganizationMixin, SingleObjectMixin, View
|
LoginRequiredMixin, AdminOrganizationMixin, SingleObjectMixin, View
|
||||||
):
|
):
|
||||||
model = Organization
|
model = Organization
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
organization = self.get_object()
|
organization = self.get_object()
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -306,78 +306,9 @@ class ProblemDetail(ProblemMixin, SolvedProblemMixin, CommentedDetailView):
|
||||||
context["meta_description"] = self.object.summary or metadata[0]
|
context["meta_description"] = self.object.summary or metadata[0]
|
||||||
context["og_image"] = self.object.og_image or metadata[1]
|
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
|
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):
|
class LatexError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ from django.http import HttpResponseRedirect
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
from django.template.defaultfilters import floatformat
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.functional import cached_property
|
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.raw_sql import join_sql_subquery, use_straight_join
|
||||||
from judge.utils.views import DiggPaginatorMixin
|
from judge.utils.views import DiggPaginatorMixin
|
||||||
from judge.utils.views import TitleMixin
|
from judge.utils.views import TitleMixin
|
||||||
|
from judge.utils.timedelta import nice_repr
|
||||||
|
|
||||||
|
|
||||||
def submission_related(queryset):
|
def submission_related(queryset):
|
||||||
|
@ -358,7 +360,8 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView):
|
||||||
)
|
)
|
||||||
if self.selected_statuses:
|
if self.selected_statuses:
|
||||||
queryset = queryset.filter(
|
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
|
return queryset
|
||||||
|
@ -392,9 +395,7 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView):
|
||||||
hidden_codes = ["SC", "D", "G"]
|
hidden_codes = ["SC", "D", "G"]
|
||||||
if not self.request.user.is_superuser and not self.request.user.is_staff:
|
if not self.request.user.is_superuser and not self.request.user.is_staff:
|
||||||
hidden_codes += ["IE"]
|
hidden_codes += ["IE"]
|
||||||
return [
|
return [(key, value) for key, value in all_statuses if key not in hidden_codes]
|
||||||
(key, value) for key, value in all_statuses if key not in hidden_codes
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(SubmissionsListBase, self).get_context_data(**kwargs)
|
context = super(SubmissionsListBase, self).get_context_data(**kwargs)
|
||||||
|
@ -782,3 +783,30 @@ class UserContestSubmissions(ForceContestMixin, UserProblemSubmissions):
|
||||||
self.contest.name,
|
self.contest.name,
|
||||||
reverse("contest_view", args=[self.contest.key]),
|
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
|
||||||
|
|
|
@ -362,3 +362,9 @@ label[for="language"], label[for="status"] {
|
||||||
color: gray;
|
color: gray;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lightbox-submissions {
|
||||||
|
td {
|
||||||
|
padding-right: 0.2em;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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">
|
<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 () {
|
$(function () {
|
||||||
$('.leaving-forever').click(function () {
|
$('.leaving-forever').click(function () {
|
||||||
return confirm('{{ _('Are you sure you want to leave?') }}\n' +
|
return confirm('{{ _('Are you sure you want to leave?') }}\n' +
|
||||||
|
@ -9,5 +147,68 @@
|
||||||
return confirm('{{ _('Are you sure you want to join?') }}\n' +
|
return confirm('{{ _('Are you sure you want to join?') }}\n' +
|
||||||
'{{ _('Joining a contest starts your timer, after which it becomes unstoppable.') }}');
|
'{{ _('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>
|
</script>
|
|
@ -58,7 +58,7 @@
|
||||||
|
|
||||||
{% block before_point_head %}
|
{% block before_point_head %}
|
||||||
{% for problem in problems %}
|
{% 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) }}
|
{{- contest.get_label_for_problem(loop.index0) }}
|
||||||
<div class="point-denominator">{{ problem.points }}</div>
|
<div class="point-denominator">{{ problem.points }}</div>
|
||||||
</a></th>
|
</a></th>
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block users_media %}
|
{% block users_media %}
|
||||||
|
<link href="//cdn.jsdelivr.net/npm/featherlight@1.7.14/release/featherlight.min.css" type="text/css" rel="stylesheet" />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
#content-left {
|
#content-left {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
@ -130,6 +132,12 @@
|
||||||
color: gray !important;
|
color: gray !important;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.featherlight-content {
|
||||||
|
border-radius: 10px;
|
||||||
|
height: 80%;
|
||||||
|
width: 60%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
{% if has_rating %}
|
{% if has_rating %}
|
||||||
|
@ -227,215 +235,6 @@
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endif %}
|
{% 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" %}
|
{% include "contest/media-js.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -346,9 +346,6 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block description %}
|
{% 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 %}
|
{% if contest_problem and contest_problem.contest.use_clarifications and has_clarifications %}
|
||||||
<div id="clarification_header_container">
|
<div id="clarification_header_container">
|
||||||
<i class="fa fa-question-circle"></i>
|
<i class="fa fa-question-circle"></i>
|
||||||
|
@ -374,9 +371,6 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block post_description_end %}
|
{% 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 %}
|
{% if request.user.is_authenticated and not request.profile.mute %}
|
||||||
<a href="{{ url('new_problem_ticket', problem.code) }}" class="clarify">
|
<a href="{{ url('new_problem_ticket', problem.code) }}" class="clarify">
|
||||||
<i class="fa fa-flag" style="margin-right:0.5em"></i>
|
<i class="fa fa-flag" style="margin-right:0.5em"></i>
|
||||||
|
|
|
@ -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 %}
|
|
|
@ -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>
|
|
|
@ -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>
|
|
30
templates/submission/user-ajax.html
Normal file
30
templates/submission/user-ajax.html
Normal 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>
|
Loading…
Reference in a new issue