`,
@@ -75,16 +126,27 @@ document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => wrapper.remove(), timeout)
}
+ /**
+ * Update the information banner.
+ * @param info The content to updated
+ */
function setInfo(info) {
document.getElementById(`messages-${tournament.id}`).innerHTML = info
}
+ /**
+ * Open the draw interface, given the list of teams.
+ * @param teams The list of teams (represented by their trigrams) that are present on this draw.
+ */
function drawStart(teams) {
+ // Hide the not-started-banner
document.getElementById(`banner-not-started-${tournament.id}`).classList.add('d-none')
+ // Display the full draw interface
document.getElementById(`draw-content-${tournament.id}`).classList.remove('d-none')
let dicesDiv = document.getElementById(`dices-${tournament.id}`)
for (let team of teams) {
+ // Add empty dice score badge for each team
let col = document.createElement('div')
col.classList.add('col-md-1')
dicesDiv.append(col)
@@ -93,7 +155,7 @@ document.addEventListener('DOMContentLoaded', () => {
diceDiv.id = `dice-${tournament.id}-${team}`
diceDiv.classList.add('badge', 'rounded-pill', 'text-bg-warning')
if (document.getElementById(`abort-${tournament.id}`) !== null) {
- // Check if this is a volunteer
+ // Check if this is a volunteer, who can launch a dice for a specific team
diceDiv.onclick = (e) => drawDice(tournament.id, team)
}
diceDiv.textContent = `${team} 🎲 ??`
@@ -101,6 +163,9 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
+ /**
+ * Abort the current draw, and make all invisible, except the not-started-banner.
+ */
function drawAbort() {
document.getElementById(`banner-not-started-${tournament.id}`).classList.remove('d-none')
document.getElementById(`draw-content-${tournament.id}`).classList.add('d-none')
@@ -114,6 +179,12 @@ document.addEventListener('DOMContentLoaded', () => {
updateContinueVisibility(false)
}
+ /**
+ * This function is triggered after a new dice result. We update the score of the team.
+ * Can be resetted to empty values if the result is null.
+ * @param trigram The trigram of the team that launched its dice
+ * @param result The result of the dice. null if it is a reset.
+ */
function updateDiceInfo(trigram, result) {
let elem = document.getElementById(`dice-${tournament.id}-${trigram}`)
if (result === null) {
@@ -128,6 +199,10 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
+ /**
+ * Display or hide the dice button.
+ * @param visible The visibility status
+ */
function updateDiceVisibility(visible) {
let div = document.getElementById(`launch-dice-${tournament.id}`)
if (visible)
@@ -136,6 +211,10 @@ document.addEventListener('DOMContentLoaded', () => {
div.classList.add('d-none')
}
+ /**
+ * Display or hide the box button.
+ * @param visible The visibility status
+ */
function updateBoxVisibility(visible) {
let div = document.getElementById(`draw-problem-${tournament.id}`)
if (visible)
@@ -144,6 +223,10 @@ document.addEventListener('DOMContentLoaded', () => {
div.classList.add('d-none')
}
+ /**
+ * Display or hide the accept and reject buttons.
+ * @param visible The visibility status
+ */
function updateButtonsVisibility(visible) {
let div = document.getElementById(`buttons-${tournament.id}`)
if (visible)
@@ -152,6 +235,10 @@ document.addEventListener('DOMContentLoaded', () => {
div.classList.add('d-none')
}
+ /**
+ * Display or hide the export button.
+ * @param visible The visibility status
+ */
function updateExportVisibility(visible) {
let div = document.getElementById(`export-${tournament.id}`)
if (visible)
@@ -160,6 +247,10 @@ document.addEventListener('DOMContentLoaded', () => {
div.classList.add('d-none')
}
+ /**
+ * Display or hide the continuation button.
+ * @param visible The visibility status
+ */
function updateContinueVisibility(visible) {
let div = document.getElementById(`continue-${tournament.id}`)
if (visible)
@@ -168,11 +259,18 @@ document.addEventListener('DOMContentLoaded', () => {
div.classList.add('d-none')
}
+ /**
+ * Set the different pools for the given round, and update the interface.
+ * @param round The round number, as integer (1 or 2)
+ * @param poules The list of poules, which are represented with their letters and trigrams,
+ * [{'letter': 'A', 'teams': ['ABC', 'DEF', 'GHI']}]
+ */
function updatePoules(round, poules) {
let roundList = document.getElementById(`recap-${tournament.id}-round-list`)
let poolListId = `recap-${tournament.id}-round-${round}-pool-list`
let poolList = document.getElementById(poolListId)
if (poolList === null) {
+ // Add a div for the round in the recap div
let div = document.createElement('div')
div.id = `recap-${tournament.id}-round-${round}`
div.classList.add('col-md-6', 'px-3', 'py-3')
@@ -195,6 +293,7 @@ document.addEventListener('DOMContentLoaded', () => {
let teamListId = `recap-${tournament.id}-round-${round}-pool-${poule.letter}-team-list`
let teamList = document.getElementById(teamListId)
if (teamList === null) {
+ // Add a div for the pool in the recap div
let li = document.createElement('li')
li.id = `recap-${tournament.id}-round-${round}-pool-${poule.letter}`
li.classList.add('list-group-item', 'px-3', 'py-3')
@@ -212,6 +311,7 @@ document.addEventListener('DOMContentLoaded', () => {
}
if (poule.teams.length > 0) {
+ // The pool is initialized
for (let team of poule.teams) {
// Reorder dices
let diceDiv = document.getElementById(`dice-${tournament.id}-${team}`)
@@ -222,6 +322,7 @@ document.addEventListener('DOMContentLoaded', () => {
let teamLi = document.getElementById(teamLiId)
if (teamLi === null) {
+ // Add a line for the team in the recap
teamLi = document.createElement('li')
teamLi.id = teamLiId
teamLi.classList.add('list-group-item')
@@ -230,6 +331,7 @@ document.addEventListener('DOMContentLoaded', () => {
teamList.append(teamLi)
}
+ // Add the accepted problem div (empty for now)
let acceptedDivId = `recap-${tournament.id}-round-${round}-team-${team}-accepted`
let acceptedDiv = document.getElementById(acceptedDivId)
if (acceptedDiv === null) {
@@ -240,6 +342,7 @@ document.addEventListener('DOMContentLoaded', () => {
teamLi.append(acceptedDiv)
}
+ // Add the rejected problems div (empty for now)
let rejectedDivId = `recap-${tournament.id}-round-${round}-team-${team}-rejected`
let rejectedDiv = document.getElementById(rejectedDivId)
if (rejectedDiv === null) {
@@ -256,6 +359,7 @@ document.addEventListener('DOMContentLoaded', () => {
let tablesDiv = document.getElementById(`tables-${tournament.id}`)
let tablesRoundDiv = document.getElementById(`tables-${tournament.id}-round-${round}`)
if (tablesRoundDiv === null) {
+ // Add the tables div for the current round if necessary
let card = document.createElement('div')
card.classList.add('card', 'col-md-6')
tablesDiv.append(card)
@@ -275,11 +379,18 @@ document.addEventListener('DOMContentLoaded', () => {
if (poule.teams.length === 0)
continue
+ // Display the table for the pool
updatePouleTable(round, poule)
}
}
}
+ /**
+ * Update the table for the given round and the given pool, where there will be the chosen problems.
+ * @param round The round number, as integer (1 or 2)
+ * @param poule The current pool, which id represented with its letter and trigrams,
+ * {'letter': 'A', 'teams': ['ABC', 'DEF', 'GHI']}
+ */
function updatePouleTable(round, poule) {
let tablesRoundDiv = document.getElementById(`tables-${tournament.id}-round-${round}`)
let pouleTable = document.getElementById(`table-${tournament.id}-${round}-${poule.letter}`)
@@ -315,6 +426,7 @@ document.addEventListener('DOMContentLoaded', () => {
teamTh.textContent = "Équipe"
phaseTr.append(teamTh)
+ // Add columns
for (let i = 1; i <= (poule.teams.length === 4 ? 4 : 3); ++i) {
let phaseTh = document.createElement('th')
phaseTh.classList.add('text-center')
@@ -342,10 +454,12 @@ document.addEventListener('DOMContentLoaded', () => {
for (let team of poule.teams) {
let problemTh = document.createElement('th')
problemTh.classList.add('text-center')
+ // Problem is unknown for now
problemTh.innerHTML = `Pb.
?`
problemTr.append(problemTh)
}
+ // Add body
let tbody = document.createElement('tbody')
pouleTable.append(tbody)
@@ -355,6 +469,7 @@ document.addEventListener('DOMContentLoaded', () => {
let teamTr = document.createElement('tr')
tbody.append(teamTr)
+ // First create cells, then we will add them in the table
let teamTd = document.createElement('td')
teamTd.classList.add('text-center')
teamTd.innerText = team
@@ -372,10 +487,7 @@ document.addEventListener('DOMContentLoaded', () => {
reporterTd.classList.add('text-center')
reporterTd.innerText = 'Rap'
- let emptyTd = document.createElement('td')
- let emptyTd2 = document.createElement('td')
-
-
+ // Put the cells in their right places, according to the pool size and the row number.
if (poule.teams.length === 3) {
switch (i) {
case 0:
@@ -390,6 +502,7 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
else if (poule.teams.length === 4) {
+ let emptyTd = document.createElement('td')
switch (i) {
case 0:
teamTr.append(defenderTd, emptyTd, reporterTd, opponentTd)
@@ -406,6 +519,8 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
else if (poule.teams.length === 5) {
+ let emptyTd = document.createElement('td')
+ let emptyTd2 = document.createElement('td')
switch (i) {
case 0:
teamTr.append(defenderTd, emptyTd, opponentTd, reporterTd, emptyTd2)
@@ -428,7 +543,14 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
+ /**
+ * Highligh the team that is currently choosing its problem.
+ * @param round The current round number, as integer (1 or 2)
+ * @param pool The current pool letter (A, B, C or D) (null if non-relevant)
+ * @param team The current team trigram (null if non-relevant)
+ */
function updateActiveRecap(round, pool, team) {
+ // Remove the previous highlights
document.querySelectorAll(`div.text-bg-secondary[data-tournament="${tournament.id}"]`)
.forEach(elem => elem.classList.remove('text-bg-secondary'))
document.querySelectorAll(`li.list-group-item-success[data-tournament="${tournament.id}"]`)
@@ -436,35 +558,53 @@ document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll(`li.list-group-item-info[data-tournament="${tournament.id}"]`)
.forEach(elem => elem.classList.remove('list-group-item-info'))
+ // Highlight current round, if existing
let roundDiv = document.getElementById(`recap-${tournament.id}-round-${round}`)
if (roundDiv !== null)
roundDiv.classList.add('text-bg-secondary')
+ // Highlight current pool, if existing
let poolLi = document.getElementById(`recap-${tournament.id}-round-${round}-pool-${pool}`)
if (poolLi !== null)
poolLi.classList.add('list-group-item-success')
+ // Highlight current team, if existing
let teamLi = document.getElementById(`recap-${tournament.id}-round-${round}-team-${team}`)
if (teamLi !== null)
teamLi.classList.add('list-group-item-info')
}
+ /**
+ * Update the recap and the table when a team accepts a problem.
+ * @param round The current round, as integer (1 or 2)
+ * @param team The current team trigram
+ * @param problem The accepted problem, as integer
+ */
function setProblemAccepted(round, team, problem) {
+ // Update recap
let recapDiv = document.getElementById(`recap-${tournament.id}-round-${round}-team-${team}-accepted`)
recapDiv.classList.remove('text-bg-warning')
recapDiv.classList.add('text-bg-success')
recapDiv.textContent = `${team} 📃 ${problem}`
+ // Update table
let tableSpan = document.getElementById(`table-${tournament.id}-round-${round}-problem-${team}`)
tableSpan.textContent = problem
}
+ /**
+ * Update the recap when a team rejects a problem.
+ * @param round The current round, as integer (1 or 2)
+ * @param team The current team trigram
+ * @param rejected The full list of rejected problems
+ */
function setProblemRejected(round, team, rejected) {
+ // Update recap
let recapDiv = document.getElementById(`recap-${tournament.id}-round-${round}-team-${team}-rejected`)
recapDiv.textContent = `🗑️ ${rejected.join(', ')}`
- if (rejected.length >= 4) {
- // TODO Fix this static value
+ if (rejected.length > problems_count - 5) {
+ // If more than P - 5 problems were rejected, add a penalty of 0.5 of the coefficient of the oral defender
let penaltyDiv = document.getElementById(`recap-${tournament.id}-round-${round}-team-${team}-penalty`)
if (penaltyDiv === null) {
penaltyDiv = document.createElement('div')
@@ -472,16 +612,26 @@ document.addEventListener('DOMContentLoaded', () => {
penaltyDiv.classList.add('badge', 'rounded-pill', 'text-bg-info')
recapDiv.parentNode.append(penaltyDiv)
}
- penaltyDiv.textContent = `❌ ${0.5 * (rejected.length - 3)}`
+ penaltyDiv.textContent = `❌ ${0.5 * (rejected.length - (problems_count - 5))}`
}
}
+ /**
+ * For a 5-teams pool, we may reorder the pool if two teams select the same problem.
+ * Then, we redraw the table and set the accepted problems.
+ * @param round The current round, as integer (1 or 2)
+ * @param poule The pool represented by its letter
+ * @param teams The teams list represented by their trigrams, ["ABC", "DEF", "GHI", "JKL", "MNO"]
+ * @param problems The accepted problems in the same order than the teams, [1, 1, 2, 2, 3]
+ */
function reorderPoule(round, poule, teams, problems) {
+ // Redraw the pool table
let table = document.getElementById(`table-${tournament.id}-${round}-${poule}`)
table.parentElement.parentElement.remove()
updatePouleTable(round, {'letter': poule, 'teams': teams})
+ // Put the problems in the table
for (let i = 0; i < teams.length; ++i) {
let team = teams[i]
let problem = problems[i]
@@ -490,66 +640,85 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
+ // Listen on websockets and process messages from the server
socket.addEventListener('message', e => {
+ // Parse received data as JSON
const data = JSON.parse(e.data)
- console.log(data)
switch (data.type) {
case 'alert':
+ // Add alert message
addMessage(data.message, data.alert_type)
break
case 'notification':
+ // Add notification
showNotification(data.title, data.body)
break
case 'set_info':
+ // Update information banner
setInfo(data.information)
break
case 'draw_start':
+ // Start the draw and update the interface
drawStart(data.trigrams)
break
case 'abort':
+ // Abort the current draw
drawAbort()
break
case 'dice':
+ // Update the interface after a dice launch
updateDiceInfo(data.team, data.result)
break
case 'dice_visibility':
+ // Update the dice button visibility
updateDiceVisibility(data.visible)
break
case 'box_visibility':
+ // Update the box button visibility
updateBoxVisibility(data.visible)
break
case 'buttons_visibility':
+ // Update the accept/reject buttons visibility
updateButtonsVisibility(data.visible)
break
case 'export_visibility':
+ // Update the export button visibility
updateExportVisibility(data.visible)
break
case 'continue_visibility':
+ // Update the continue button visibility for the final tournament
updateContinueVisibility(data.visible)
break
case 'set_poules':
+ // Set teams order and pools and update the interface
updatePoules(data.round, data.poules)
break
case 'set_active':
+ // Highlight the team that is selecting a problem
updateActiveRecap(data.round, data.poule, data.team)
break
case 'set_problem':
+ // Mark a problem as accepted and update the interface
setProblemAccepted(data.round, data.team, data.problem)
break
case 'reject_problem':
+ // Mark a problem as rejected and update the interface
setProblemRejected(data.round, data.team, data.rejected)
break
case 'reorder_poule':
+ // Reorder a pool and redraw the associated table
reorderPoule(data.round, data.poule, data.teams, data.problems)
break
}
})
+ // Manage errors
socket.addEventListener('close', e => {
console.error('Chat socket closed unexpectedly')
})
+ // When the socket is opened, set the language in order to receive alerts in the good language
socket.addEventListener('open', e => {
socket.send(JSON.stringify({
'type': 'set_language',
@@ -557,6 +726,7 @@ document.addEventListener('DOMContentLoaded', () => {
}))
})
+ // Manage the start form
let format_form = document.getElementById('format-form-' + tournament.id)
if (format_form !== null) {
format_form.addEventListener('submit', function (e) {
diff --git a/draw/templates/draw/index.html b/draw/templates/draw/index.html
index 1b31e24..840bbed 100644
--- a/draw/templates/draw/index.html
+++ b/draw/templates/draw/index.html
@@ -3,6 +3,7 @@
{% load static %}
{% block content %}
+ {# The navbar to select the tournament #}
{% for tournament in tournaments %}
-
@@ -17,6 +18,7 @@
+ {# For each tournament, we draw a div #}
{% for tournament in tournaments %}
{% endblock %}
diff --git a/draw/templates/draw/tournament_content.html b/draw/templates/draw/tournament_content.html
index 4d7f3cb..0763366 100644
--- a/draw/templates/draw/tournament_content.html
+++ b/draw/templates/draw/tournament_content.html
@@ -1,15 +1,18 @@
{% load i18n %}
+ {# This div is visible iff the draw is not started. #}
{% trans "The draw has not started yet." %}
{% if user.registration.is_volunteer %}
+ {# Volunteers have a form to start the draw #}