(async () => { // check notification permission // This is useful to alert people that they should do something await Notification.requestPermission() })() const TFJM = JSON.parse(document.getElementById('TFJM_settings').textContent) const RECOMMENDED_SOLUTIONS_COUNT = TFJM.RECOMMENDED_SOLUTIONS_COUNT const problems_count = JSON.parse(document.getElementById('problems_count').textContent) const tournaments = JSON.parse(document.getElementById('tournaments_list').textContent) let socket = null const messages = document.getElementById('messages') /** * Request to abort the draw of the given tournament. * Only volunteers are allowed to do this. * @param tid The tournament id */ function abortDraw(tid) { socket.send(JSON.stringify({'tid': tid, 'type': 'abort'})) } /** * Request to cancel the last step. * Only volunteers are allowed to do this. * @param tid The tournament id */ function cancelLastStep(tid) { socket.send(JSON.stringify({'tid': tid, 'type': 'cancel'})) } /** * Request to launch a dice between 1 and 100, for the two first steps. * The parameter `trigram` can be specified (by volunteers) to launch a dice for a specific team. * @param tid The tournament id * @param trigram The trigram of the team that a volunteer wants to force the dice launch (default: null) * @param result The forced value. Null if unused (for regular people) */ function drawDice(tid, trigram = null, result = null) { socket.send(JSON.stringify({'tid': tid, 'type': 'dice', 'trigram': trigram, 'result': result})) } /** * Fetch the requested dice from the buttons and request to draw it. * Only available for debug purposes and for admins. * @param tid The tournament id */ function drawDebugDice(tid) { let dice_10 = parseInt(document.querySelector(`input[name="debug-dice-${tid}-10"]:checked`).value) let dice_1 = parseInt(document.querySelector(`input[name="debug-dice-${tid}-1"]:checked`).value) let result = (dice_10 + dice_1) || 100 let team_div = document.querySelector(`div[id="dices-${tid}"] > div > div[class*="text-bg-warning"]`) let team = team_div.getAttribute("data-team") drawDice(tid, team, result) } /** * Request to draw a new problem. * @param tid The tournament id * @param problem The forced problem. Null if unused (for regular people) */ function drawProblem(tid, problem = null) { socket.send(JSON.stringify({'tid': tid, 'type': 'draw_problem', 'problem': problem})) } /** * Accept the current proposed problem. * @param tid The tournament id */ function acceptProblem(tid) { socket.send(JSON.stringify({'tid': tid, 'type': 'accept'})) } /** * Reject the current proposed problem. * @param tid The tournament id */ function rejectProblem(tid) { socket.send(JSON.stringify({'tid': tid, 'type': 'reject'})) } /** * Volunteers can export the draw to make it available for notation. * @param tid The tournament id */ function exportDraw(tid) { socket.send(JSON.stringify({'tid': tid, 'type': 'export'})) } /** * Volunteers can make the draw continue for the second round of the final. * @param tid The tournament id */ function continueFinal(tid) { socket.send(JSON.stringify({'tid': tid, 'type': 'continue_final'})) } /** * Display a new notification with the given title and the given body. * @param title The title of the notification * @param body The body of the notification * @param timeout The time (in milliseconds) after that the notification automatically closes. 0 to make indefinite. Default to 5000 ms. * @return Notification */ function showNotification(title, body, timeout = 5000) { let notif = new Notification(title, {'body': body, 'icon': "/static/tfjm.svg"}) if (timeout) setTimeout(() => notif.close(), timeout) return notif } document.addEventListener('DOMContentLoaded', () => { if (document.location.hash) { // Open the tab of the tournament that is present in the hash document.querySelectorAll('button[data-bs-toggle="tab"]').forEach(elem => { if ('#' + elem.innerText.toLowerCase() === document.location.hash.toLowerCase()) { elem.click() } }) } // When a tab is opened, add the tournament name in the hash document.querySelectorAll('button[data-bs-toggle="tab"]').forEach( elem => elem.addEventListener( 'click', () => document.location.hash = '#' + elem.innerText.toLowerCase())) /** * Add alert message on the top on the interface. * @param message The content of the alert. * @param type The alert type, which is a bootstrap color (success, info, warning, danger,…). * @param timeout The time (in milliseconds) before the alert is auto-closing. 0 to infinitely, default to 5000 ms. */ function addMessage(message, type, timeout = 5000) { const wrapper = document.createElement('div') wrapper.innerHTML = [ `<div class="alert alert-${type} alert-dismissible" role="alert">`, `<div>${message}</div>`, '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>', ].join('\n') messages.append(wrapper) if (timeout) setTimeout(() => wrapper.remove(), timeout) } /** * Update the information banner. * @param tid The tournament id * @param info The content to updated */ function setInfo(tid, info) { document.getElementById(`messages-${tid}`).innerHTML = info } /** * Open the draw interface, given the list of teams. * @param tid The tournament id * @param teams The list of teams (represented by their trigrams) that are present on this draw. */ function drawStart(tid, teams) { // Hide the not-started-banner document.getElementById(`banner-not-started-${tid}`).classList.add('d-none') // Display the full draw interface document.getElementById(`draw-content-${tid}`).classList.remove('d-none') let dicesDiv = document.getElementById(`dices-${tid}`) 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) let diceDiv = document.createElement('div') diceDiv.id = `dice-${tid}-${team}` diceDiv.classList.add('badge', 'rounded-pill', 'text-bg-warning') if (document.getElementById(`abort-${tid}`) !== null) { // Check if this is a volunteer, who can launch a die for a specific team diceDiv.onclick = (_) => drawDice(tid, team) } diceDiv.textContent = `${team} 🎲 ??` col.append(diceDiv) } } /** * Abort the current draw, and make all invisible, except the not-started-banner. * @param tid The tournament id */ function drawAbort(tid) { document.getElementById(`banner-not-started-${tid}`).classList.remove('d-none') document.getElementById(`draw-content-${tid}`).classList.add('d-none') document.getElementById(`dices-${tid}`).innerHTML = "" document.getElementById(`recap-${tid}-round-list`).innerHTML = "" document.getElementById(`tables-${tid}`).innerHTML = "" updateDiceVisibility(tid, false) updateBoxVisibility(tid, false) updateButtonsVisibility(tid, false) updateExportVisibility(tid, false) updateContinueVisibility(tid, 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 tid The tournament id * @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(tid, trigram, result) { let elem = document.getElementById(`dice-${tid}-${trigram}`) if (result === null) { elem.classList.remove('text-bg-success') elem.classList.add('text-bg-warning') elem.innerText = `${trigram} 🎲 ??` } else { elem.classList.remove('text-bg-warning') elem.classList.add('text-bg-success') elem.innerText = `${trigram} 🎲 ${result}` } let nextTeam = document.querySelector(` div[id="dices-${tid}"] > div > div[class*="text-bg-warning"]`).getAttribute("data-team") if (nextTeam) { // If there is one team that does not have launched its dice, then we update the debug section let debugSpan = document.getElementById(`debug-dice-${tid}-team`) if (debugSpan) debugSpan.innerText = nextTeam } } /** * Display or hide the dice button. * @param tid The tournament id * @param visible The visibility status */ function updateDiceVisibility(tid, visible) { let div = document.getElementById(`launch-dice-${tid}`) let div_debug = document.getElementById(`debug-dice-form-${tid}`) if (visible) { div.classList.remove('d-none') div_debug.classList.remove('d-none') } else { div.classList.add('d-none') div_debug.classList.add('d-none') } } /** * Display or hide the box button. * @param tid The tournament id * @param visible The visibility status */ function updateBoxVisibility(tid, visible) { let div = document.getElementById(`draw-problem-${tid}`) let div_debug = document.getElementById(`debug-problem-form-${tid}`) if (visible) { div.classList.remove('d-none') div_debug.classList.remove('d-none') } else { div.classList.add('d-none') div_debug.classList.add('d-none') } } /** * Display or hide the accept and reject buttons. * @param tid The tournament id * @param visible The visibility status */ function updateButtonsVisibility(tid, visible) { let div = document.getElementById(`buttons-${tid}`) if (visible) div.classList.remove('d-none') else div.classList.add('d-none') } /** * Display or hide the export button. * @param tid The tournament id * @param visible The visibility status */ function updateExportVisibility(tid, visible) { let div = document.getElementById(`export-${tid}`) if (visible) div.classList.remove('d-none') else div.classList.add('d-none') } /** * Display or hide the continuation button. * @param tid The tournament id * @param visible The visibility status */ function updateContinueVisibility(tid, visible) { let div = document.getElementById(`continue-${tid}`) if (div !== null) { // Only present during the final if (visible) div.classList.remove('d-none') else div.classList.add('d-none') } } /** * Set the different pools for the given round, and update the interface. * @param tid The tournament id * @param round The round number, as integer (1 or 2, or 3 for ETEAM) * @param poules The list of poules, which are represented with their letters and trigrams, * [{'letter': 'A', 'teams': ['ABC', 'DEF', 'GHI']}] */ function updatePoules(tid, round, poules) { let roundList = document.getElementById(`recap-${tid}-round-list`) let poolListId = `recap-${tid}-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-${tid}-round-${round}` div.classList.add('col-md-6', 'px-3', 'py-3') div.setAttribute('data-tournament', tid) let title = document.createElement('strong') title.textContent = 'Tour ' + round poolList = document.createElement('ul') poolList.id = poolListId poolList.classList.add('list-group', 'list-group-flush') div.append(title, poolList) roundList.append(div) } let c = 1 for (let poule of poules) { let teamListId = `recap-${tid}-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-${tid}-round-${round}-pool-${poule.letter}` li.classList.add('list-group-item', 'px-3', 'py-3') li.setAttribute('data-tournament', tid) let title = document.createElement('strong') title.textContent = 'Poule ' + poule.letter + round teamList = document.createElement('ul') teamList.id = teamListId teamList.classList.add('list-group', 'list-group-flush') li.append(title, teamList) poolList.append(li) } teamList.innerHTML = "" for (let team of poule.teams) { // Reorder dices let diceDiv = document.getElementById(`dice-${tid}-${team}`) diceDiv.parentElement.style.order = c.toString() c += 1 let teamLiId = `recap-${tid}-round-${round}-team-${team}` // Add a line for the team in the recap let teamLi = document.createElement('li') teamLi.id = teamLiId teamLi.classList.add('list-group-item') teamLi.setAttribute('data-tournament', tid) teamList.append(teamLi) // Add the accepted problem div (empty for now) let acceptedDivId = `recap-${tid}-round-${round}-team-${team}-accepted` let acceptedDiv = document.getElementById(acceptedDivId) if (acceptedDiv === null) { acceptedDiv = document.createElement('div') acceptedDiv.id = acceptedDivId acceptedDiv.classList.add('badge', 'rounded-pill', 'text-bg-warning') acceptedDiv.textContent = `${team} 📃 ?` teamLi.append(acceptedDiv) } // Add the rejected problems div (empty for now) let rejectedDivId = `recap-${tid}-round-${round}-team-${team}-rejected` let rejectedDiv = document.getElementById(rejectedDivId) if (rejectedDiv === null) { rejectedDiv = document.createElement('div') rejectedDiv.id = rejectedDivId rejectedDiv.classList.add('badge', 'rounded-pill', 'text-bg-danger') rejectedDiv.textContent = '🗑️' teamLi.append(rejectedDiv) } } // Draw tables let tablesDiv = document.getElementById(`tables-${tid}`) let tablesRoundDiv = document.getElementById(`tables-${tid}-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) let cardHeader = document.createElement('div') cardHeader.classList.add('card-header') cardHeader.innerHTML = `<h2>Tour ${round}</h2>` card.append(cardHeader) tablesRoundDiv = document.createElement('div') tablesRoundDiv.id = `tables-${tid}-round-${round}` tablesRoundDiv.classList.add('card-body', 'd-flex', 'flex-wrap') card.append(tablesRoundDiv) } for (let poule of poules) { if (poule.teams.length === 0) continue // Display the table for the pool updatePouleTable(tid, round, poule) } } } /** * Update the table for the given round and the given pool, where there will be the chosen problems. * @param tid The tournament id * @param round The round number, as integer (1 or 2, or 3 for ETEAM) * @param poule The current pool, which id represented with its letter and trigrams, * {'letter': 'A', 'teams': ['ABC', 'DEF', 'GHI']} */ function updatePouleTable(tid, round, poule) { let tablesRoundDiv = document.getElementById(`tables-${tid}-round-${round}`) let pouleTable = document.getElementById(`table-${tid}-${round}-${poule.letter}`) if (pouleTable === null) { // Create table let card = document.createElement('div') card.classList.add('card', 'w-100', 'my-3', `order-${poule.letter.charCodeAt(0) - 64}`) tablesRoundDiv.append(card) let cardHeader = document.createElement('div') cardHeader.classList.add('card-header') cardHeader.innerHTML = `<h2>Poule ${poule.letter}${round}</h2>` card.append(cardHeader) let cardBody = document.createElement('div') cardBody.classList.add('card-body') card.append(cardBody) pouleTable = document.createElement('table') pouleTable.id = `table-${tid}-${round}-${poule.letter}` pouleTable.classList.add('table', 'table-stripped') cardBody.append(pouleTable) let thead = document.createElement('thead') pouleTable.append(thead) let phaseTr = document.createElement('tr') thead.append(phaseTr) let teamTh = document.createElement('th') teamTh.classList.add('text-center') teamTh.rowSpan = poule.teams.length === 5 ? 3 : 2 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') if (poule.teams.length === 5 && i < 3) phaseTh.colSpan = 2 phaseTh.textContent = `Phase ${i}` phaseTr.append(phaseTh) } if (poule.teams.length === 5) { let roomTr = document.createElement('tr') thead.append(roomTr) for (let i = 0; i < 5; ++i) { let roomTh = document.createElement('th') roomTh.classList.add('text-center') roomTh.textContent = `Salle ${1 + (i % 2)}` roomTr.append(roomTh) } } let problemTr = document.createElement('tr') thead.append(problemTr) for (let team of poule.teams) { let problemTh = document.createElement('th') problemTh.classList.add('text-center') // Problem is unknown for now problemTh.innerHTML = `Pb. <span id="table-${tid}-round-${round}-problem-${team}">?</span>` problemTr.append(problemTh) } // Add body let tbody = document.createElement('tbody') pouleTable.append(tbody) for (let i = 0; i < poule.teams.length; ++i) { let team = poule.teams[i] 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 teamTr.append(teamTd) let reporterTd = document.createElement('td') reporterTd.classList.add('text-center') reporterTd.innerText = 'Déf' let opponentTd = document.createElement('td') opponentTd.classList.add('text-center') opponentTd.innerText = 'Opp' let reviewerTd = document.createElement('td') reviewerTd.classList.add('text-center') reviewerTd.innerText = 'Rap' // 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: teamTr.append(reporterTd, reviewerTd, opponentTd) break case 1: teamTr.append(opponentTd, reporterTd, reviewerTd) break case 2: teamTr.append(reviewerTd, opponentTd, reporterTd) break } } else if (poule.teams.length === 4) { let emptyTd = document.createElement('td') switch (i) { case 0: teamTr.append(reporterTd, emptyTd, reviewerTd, opponentTd) break case 1: teamTr.append(opponentTd, reporterTd, emptyTd, reviewerTd) break case 2: teamTr.append(reviewerTd, opponentTd, reporterTd, emptyTd) break case 3: teamTr.append(emptyTd, reviewerTd, opponentTd, reporterTd) break } } else if (poule.teams.length === 5) { let emptyTd = document.createElement('td') let emptyTd2 = document.createElement('td') switch (i) { case 0: teamTr.append(reporterTd, emptyTd, opponentTd, reviewerTd, emptyTd2) break case 1: teamTr.append(emptyTd, reporterTd, reviewerTd, emptyTd2, opponentTd) break case 2: teamTr.append(opponentTd, emptyTd, reporterTd, emptyTd2, reviewerTd) break case 3: teamTr.append(reviewerTd, opponentTd, emptyTd, reporterTd, emptyTd2) break case 4: teamTr.append(emptyTd, reviewerTd, emptyTd2, opponentTd, reporterTd) break } } } } } /** * Highlight the team that is currently choosing its problem. * @param tid The tournament id * @param round The current round number, as integer (1 or 2, or 3 for ETEAM) * @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(tid, round, pool, team) { // Remove the previous highlights document.querySelectorAll(`div.text-bg-secondary[data-tournament="${tid}"]`) .forEach(elem => elem.classList.remove('text-bg-secondary')) document.querySelectorAll(`li.list-group-item-success[data-tournament="${tid}"]`) .forEach(elem => elem.classList.remove('list-group-item-success')) document.querySelectorAll(`li.list-group-item-info[data-tournament="${tid}"]`) .forEach(elem => elem.classList.remove('list-group-item-info')) // Highlight current round, if existing let roundDiv = document.getElementById(`recap-${tid}-round-${round}`) if (roundDiv !== null) roundDiv.classList.add('text-bg-secondary') // Highlight current pool, if existing let poolLi = document.getElementById(`recap-${tid}-round-${round}-pool-${pool}`) if (poolLi !== null) poolLi.classList.add('list-group-item-success') // Highlight current team, if existing let teamLi = document.getElementById(`recap-${tid}-round-${round}-team-${team}`) if (teamLi !== null) teamLi.classList.add('list-group-item-info') let debugSpan = document.getElementById(`debug-problem-${tid}-team`) if (debugSpan && team) { debugSpan.innerText = team } } /** * Update the recap and the table when a team accepts a problem. * @param tid The tournament id * @param round The current round, as integer (1 or 2, or 3 for ETEAM) * @param team The current team trigram * @param problem The accepted problem, as integer */ function setProblemAccepted(tid, round, team, problem) { // Update recap let recapDiv = document.getElementById(`recap-${tid}-round-${round}-team-${team}-accepted`) if (problem !== null) { recapDiv.classList.remove('text-bg-warning') recapDiv.classList.add('text-bg-success') } else { recapDiv.classList.add('text-bg-warning') recapDiv.classList.remove('text-bg-success') } recapDiv.textContent = `${team} 📃 ${problem ? problem : '?'}` // Update table let tableSpan = document.getElementById(`table-${tid}-round-${round}-problem-${team}`) tableSpan.textContent = problem ? problem : '?' } /** * Update the recap when a team rejects a problem. * @param tid The tournament id * @param round The current round, as integer (1 or 2, or 3 for ETEAM) * @param team The current team trigram * @param rejected The full list of rejected problems */ function setProblemRejected(tid, round, team, rejected) { // Update recap let recapDiv = document.getElementById(`recap-${tid}-round-${round}-team-${team}-rejected`) recapDiv.textContent = `🗑️ ${rejected.join(', ')}` let penaltyDiv = document.getElementById(`recap-${tid}-round-${round}-team-${team}-penalty`) if (rejected.length > problems_count - RECOMMENDED_SOLUTIONS_COUNT) { // If more than P - 5 problems were rejected, add a penalty of 25% of the coefficient of the oral reporter // This is P - 6 for the ETEAM if (penaltyDiv === null) { penaltyDiv = document.createElement('div') penaltyDiv.id = `recap-${tid}-round-${round}-team-${team}-penalty` penaltyDiv.classList.add('badge', 'rounded-pill', 'text-bg-info') recapDiv.parentNode.append(penaltyDiv) } penaltyDiv.textContent = `❌ ${25 * (rejected.length - (problems_count - RECOMMENDED_SOLUTIONS_COUNT))} %` } else { // Eventually remove this div if (penaltyDiv !== null) penaltyDiv.remove() } } /** * 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 tid The tournament id * @param round The current round, as integer (1 or 2, or 3 for ETEAM) * @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(tid, round, poule, teams, problems) { // Redraw the pool table let table = document.getElementById(`table-${tid}-${round}-${poule}`) table.parentElement.parentElement.remove() updatePouleTable(tid, 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] setProblemAccepted(tid, round, team, problem) let recapTeam = document.getElementById(`recap-${tid}-round-${round}-team-${team}`) recapTeam.style.order = i.toString() } } /** * Process the received data from the server. * @param tid The tournament id * @param data The received message */ function processMessage(tid, 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(tid, data.information) break case 'draw_start': // Start the draw and update the interface drawStart(tid, data.trigrams) break case 'abort': // Abort the current draw drawAbort(tid) break case 'dice': // Update the interface after a dice launch updateDiceInfo(tid, data.team, data.result) break case 'dice_visibility': // Update the dice button visibility updateDiceVisibility(tid, data.visible) break case 'box_visibility': // Update the box button visibility updateBoxVisibility(tid, data.visible) break case 'buttons_visibility': // Update the accept/reject buttons visibility updateButtonsVisibility(tid, data.visible) break case 'export_visibility': // Update the export button visibility updateExportVisibility(tid, data.visible) break case 'continue_visibility': // Update the continue button visibility for the final tournament updateContinueVisibility(tid, data.visible) break case 'set_poules': // Set teams order and pools and update the interface updatePoules(tid, data.round, data.poules) break case 'set_active': // Highlight the team that is selecting a problem updateActiveRecap(tid, data.round, data.poule, data.team) break case 'set_problem': // Mark a problem as accepted and update the interface setProblemAccepted(tid, data.round, data.team, data.problem) break case 'reject_problem': // Mark a problem as rejected and update the interface setProblemRejected(tid, data.round, data.team, data.rejected) break case 'reorder_poule': // Reorder a pool and redraw the associated table reorderPoule(tid, data.round, data.poule, data.teams, data.problems) break } } function setupSocket(nextDelay = 1000) { // Open a global websocket socket = new WebSocket( (document.location.protocol === 'https:' ? 'wss' : 'ws') + '://' + window.location.host + '/ws/draw/' ) // Listen on websockets and process messages from the server socket.addEventListener('message', e => { // Parse received data as JSON const data = JSON.parse(e.data) processMessage(data['tid'], data) }) // Manage errors socket.addEventListener('close', e => { console.error('Chat socket closed unexpectedly, restarting…') setTimeout(() => setupSocket(2 * nextDelay), nextDelay) }) // 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({ 'tid': tournaments[0].id, 'type': 'set_language', 'language': document.getElementsByName('language')[0].value, })) }) for (let tournament of tournaments) { // Manage the start form let format_form = document.getElementById('format-form-' + tournament.id) if (format_form !== null) { format_form.addEventListener('submit', function (e) { e.preventDefault() socket.send(JSON.stringify({ 'tid': tournament.id, 'type': 'start_draw', 'fmt': document.getElementById('format-' + tournament.id).value })) }) } } } setupSocket() if (document.querySelector('a[href="/admin/"]')) { // Administrators can fake the draw // This is useful for debug purposes, or document.getElementsByTagName('body')[0].addEventListener('keyup', event => { if (event.key === 'f') { let activeTab = document.querySelector('#tournaments-tab button.active') let tid = activeTab.id.substring(4) let dice = document.getElementById(`launch-dice-${tid}`) let box = document.getElementById(`draw-problem-${tid}`) let value = NaN if (!dice.classList.contains('d-none')) { value = parseInt(prompt("Entrez la valeur du dé (laissez vide pour annuler) :")) if (!isNaN(value) && 1 <= value && value <= 100) drawDice(tid, null, value) } else if (!box.classList.contains('d-none')) { value = parseInt(prompt("Entrez le numéro du problème à choisir (laissez vide pour annuler) :")) if (!isNaN(value) && 1 <= value && value <= 8) drawProblem(tid, value) } } }) } })