diff --git a/tournaments/models/match.py b/tournaments/models/match.py index 329fab6..0414ae1 100644 --- a/tournaments/models/match.py +++ b/tournaments/models/match.py @@ -412,7 +412,7 @@ class Match(models.Model): ended = self.end_date is not None live_format = "Format " + FederalMatchCategory(self.format).format_label_short - livematch = LiveMatch(title, date, time_indication, court, self.started(), ended, group_stage_name, live_format, self.start_date, self.court_index, self.disabled) + livematch = LiveMatch(self.index, title, date, time_indication, court, self.started(), ended, group_stage_name, live_format, self.start_date, self.court_index, self.disabled) for team in self.live_teams(): livematch.add_team(team) @@ -473,7 +473,8 @@ class Team: } class LiveMatch: - def __init__(self, title, date, time_indication, court, started, ended, group_stage_name, format, start_date, court_index, disabled): + def __init__(self, index, title, date, time_indication, court, started, ended, group_stage_name, format, start_date, court_index, disabled): + self.index = index self.title = title self.date = date self.teams = [] @@ -495,6 +496,7 @@ class LiveMatch: def to_dict(self): return { + "index": self.index, "title": self.title, "date": self.date, "teams": [team.to_dict() for team in self.teams], diff --git a/tournaments/models/round.py b/tournaments/models/round.py index 0115a33..1e23535 100644 --- a/tournaments/models/round.py +++ b/tournaments/models/round.py @@ -118,7 +118,9 @@ class Round(models.Model): # Only filter out the pair if both matches are disabled if current_match.disabled and pair_match and pair_match.disabled: # Skip one of the matches in the pair - filtered_matches.append(current_match) + if next_round_matches.filter(index=current_match.index // 2).exists(): + filtered_matches.append(current_match) + filtered_matches.append(pair_match) pass else: # Keep the current match diff --git a/tournaments/static/tournaments/css/tournament_bracket.css b/tournaments/static/tournaments/css/tournament_bracket.css index 6632ca8..f78e603 100644 --- a/tournaments/static/tournaments/css/tournament_bracket.css +++ b/tournaments/static/tournaments/css/tournament_bracket.css @@ -23,6 +23,7 @@ } .incoming-line.disabled, +.outgoing-line.disabled, .butterfly-match:has(.match-content.disabled)::after, .butterfly-match:has(.match-content.disabled)::before { visibility: hidden; @@ -104,90 +105,89 @@ } /* Horizontal line after match */ -.butterfly-match::after { +.outgoing-line { content: ""; position: absolute; left: 100%; /* Start from end of match cell */ top: 50%; width: 20px; height: 2px; - background: #707070; + background: orange; } -.semi-final::after { +.butterfly-match::before { content: ""; position: absolute; - left: calc(100% + 20px); /* After horizontal line */ + left: calc(0% - 20px); width: 2px; - height: calc((var(--next-match-distance)) / 2); - background: #707070; + top: calc(50% - (var(--next-match-distance) / 2)); + height: calc(var(--next-match-distance) + 2px); + background: pink; } /* Vertical line connecting pair of matches */ -.butterfly-match:nth-child(2n)::before { +.butterfly-match.reverse-bracket::before { content: ""; position: absolute; - left: calc(100% + 20px); /* After horizontal line */ - top: 50%; + left: calc(100% + 20px); width: 2px; - height: calc((var(--next-match-distance)) / 2); - background: #707070; + top: calc(50% - (var(--next-match-distance) / 2)); + height: calc(var(--next-match-distance) + 2px); + background: red; } -.butterfly-match:nth-child(2n + 1)::before { - content: ""; +/* Horizontal line to next round match */ +.incoming-line { position: absolute; - left: calc(100% + 20px); - bottom: calc(50% - 2px); /* Account for half of horizontal line height */ - width: 2px; - height: calc( - ((var(--next-match-distance)) / 2) - ); /* Add half of horizontal line height */ - background: #707070; + left: -20px; + top: 50%; + width: 20px; + height: 2px; + background: blue; } -/* Vertical line connecting pair of matches */ -.butterfly-match.reverse-bracket:nth-child(2n)::before { - content: ""; +/* Horizontal line to next round match */ +.butterfly-match .outgoing-line-downward { position: absolute; - left: calc(0% - 20px); /* After horizontal line */ - top: 50%; + right: -20px; + top: 50%; /* Start from middle of match */ width: 2px; - height: calc((var(--next-match-distance)) / 2); - background: #707070; + height: 120px; + background: black; } -.butterfly-match.reverse-bracket:nth-child(2n + 1)::before { - content: ""; +/* Horizontal line to next round match */ +.butterfly-match .outgoing-line-upward { position: absolute; - left: calc(0% - 20px); - bottom: 50%; /* Account for half of horizontal line height */ + right: -20px; + bottom: 50%; width: 2px; - height: calc( - (var(--next-match-distance)) / 2 - ); /* Add half of horizontal line height */ - background: #707070; + height: 120px; + background: black; } /* Horizontal line to next round match */ -.butterfly-match .incoming-line { +.butterfly-match.reverse-bracket .outgoing-line-downward { position: absolute; left: -20px; - top: 50%; - width: 20px; - height: 2px; - background: #707070; -} - -.inward .incoming-line { - display: none; + top: 50%; /* Start from middle of match */ + width: 2px; + height: 120px; + background: black; } -.butterfly-match.outward::after { - display: none; +/* Horizontal line to next round match */ +.butterfly-match.reverse-bracket .outgoing-line-upward { + position: absolute; + left: -20px; + bottom: 50%; + width: 2px; + height: 120px; + background: black; } -.butterfly-round:last-child .butterfly-match::after, +.butterfly-round:last-child .butterfly-match.reverse-bracket::before, +.butterfly-round:last-child .outgoing-line, .butterfly-round:first-child .incoming-line { display: none; } @@ -205,16 +205,25 @@ border-radius: 24px; } /* Broadcast mode styling for all lines */ -.broadcast-mode .butterfly-match::after, -.broadcast-mode .butterfly-match:nth-child(2n)::before, -.broadcast-mode .butterfly-match:nth-child(2n + 1)::before, -.broadcast-mode .butterfly-match.reverse-bracket:nth-child(2n)::before, -.broadcast-mode .butterfly-match.reverse-bracket:nth-child(2n + 1)::before, -.broadcast-mode .butterfly-match .incoming-line, -.broadcast-mode .semi-final::after { +.broadcast-mode .butterfly-match::before, +.broadcast-mode .butterfly-match.reverse-bracket::before, +.broadcast-mode .incoming-line, +.broadcast-mode .outgoing-line, +.broadcast-mode .outgoing-line-upward, +.broadcast-mode .outgoing-line-downward { background-color: black; /* Bright yellow - change to your preferred color */ } +/* Broadcast mode styling for all lines */ +.butterfly-match::before, +.butterfly-match.reverse-bracket::before, +.incoming-line, +.outgoing-line, +.outgoing-line-upward, +.outgoing-line-downward { + background-color: #707070 !important; /* Bright yellow - change to your preferred color */ +} + .bubble.match-running { position: relative; } diff --git a/tournaments/static/tournaments/js/tournament_bracket.js b/tournaments/static/tournaments/js/tournament_bracket.js index 159c70b..0c6a172 100644 --- a/tournaments/static/tournaments/js/tournament_bracket.js +++ b/tournaments/static/tournaments/js/tournament_bracket.js @@ -42,13 +42,20 @@ function renderBracket(options) { } const padding = 50 * roundTotalCount; // Account for some padding/margin const availableWidth = screenWidth - padding; - let responsiveMatchWidth = Math.min( + responsiveMatchWidth = Math.min( 365, - Math.floor(availableWidth / roundTotalCount), + Math.max(365, Math.floor(availableWidth / roundTotalCount)), ); + if (isBroadcast) { + responsiveMatchWidth = Math.min( + 365, + Math.floor(availableWidth / roundTotalCount), + ); + } + rounds.forEach((roundMatches, roundIndex) => { - if (rounds[0].length <= 2) { + if (rounds[0].length <= 2 && doubleButterflyMode) { minimumMatchDistance = 2; nextMatchDistance = baseDistance * 2; } @@ -105,29 +112,39 @@ function renderBracket(options) { } } roundDiv.appendChild(matchesContainer); - - matchPositions[roundIndex] = []; + if (matchPositions[roundIndex] == undefined) { + matchPositions[roundIndex] = {}; + } matchDisabled[roundIndex] = []; // Initialize array for this round roundMatches.forEach((matchTemplate, matchIndex) => { const matchTitle = matchTemplate.dataset.matchTitle; + const matchRealIndex = matchTemplate.dataset.matchRealIndex; const matchDiv = document.createElement("div"); matchDiv.className = "butterfly-match"; matchDiv.style.position = "absolute"; const isDisabled = matchTemplate.dataset.disabled === "true"; - matchDisabled[roundIndex][matchIndex] = isDisabled; + matchDisabled[roundIndex][matchRealIndex] = isDisabled; let isIncomingLineIsDisabled = isDisabled; - + let isOutgoingLineIsDisabled = isDisabled; let top; - let left; - let right; const currentMatchesCount = roundMatches.length; if (roundIndex > finalRoundIndex) { matchDiv.classList.add("reverse-bracket"); - top = matchPositions[roundCount - roundIndex - 1][matchIndex]; + + if (roundIndex <= finalRoundIndex + 2) { + const values = Object.values( + matchPositions[roundCount - roundIndex - 1], + ); + top = values[matchIndex]; + } else { + top = matchPositions[roundIndex][matchRealIndex]; + console.log(matchTitle, top); + } } if (roundIndex === 0) { + nextMatchDistance = 0; if (roundCount > 1) { const nextMatchesCount = rounds[roundIndex + 1].length; @@ -144,33 +161,34 @@ function renderBracket(options) { top = top + (matchHeight + matchSpacing) / 2; } } else if (roundIndex === roundCount - 1 && doubleButterflyMode == true) { - const nextMatchesCount = rounds[roundIndex - 1].length; - if (currentMatchesCount == nextMatchesCount) { - nextMatchDistance = 0; - } + nextMatchDistance = 0; } else if (roundIndex == finalRoundIndex) { + const values = Object.values(matchPositions[roundIndex - 1]); + const parentPos1 = values[0]; + const parentPos2 = values[1]; + if (doubleButterflyMode == true) { let lgth = matchPositions[0].length / 2; let index = lgth + matchIndex - 1; // If index goes negative, use 0 instead if (displayLoserFinal == true) { if (matchIndex == 0) { - top = matchPositions[roundIndex - 1][0] - baseDistance / 2; + top = parentPos1 - baseDistance / 2; } else { - top = matchPositions[roundIndex - 1][0] + baseDistance / 2; + top = parentPos1 + baseDistance / 2; } - nextMatchDistance = baseDistance; + nextMatchDistance = 0; } else { - top = matchPositions[roundIndex - 1][0]; + top = parentPos1; nextMatchDistance = 0; } } else { - const parentIndex1 = matchIndex * 2; - const parentIndex2 = parentIndex1 + 1; - const parentPos1 = matchPositions[roundIndex - 1][parentIndex1]; - const parentPos2 = matchPositions[roundIndex - 1][parentIndex2]; top = (parentPos1 + parentPos2) / 2; - nextMatchDistance = 0; + if (matchIndex == 0) { + nextMatchDistance = parentPos2 - parentPos1; + } else { + nextMatchDistance = 0; + } if (displayLoserFinal == true) { if (matchIndex == 1) { @@ -180,82 +198,115 @@ function renderBracket(options) { } } } else if (roundIndex < finalRoundIndex) { - const previousMatchesCount = rounds[roundIndex - 1].length; - const nextMatchesCount = rounds[roundIndex + 1].length; - + const parentIndex1 = matchRealIndex * 2 + 1; + const parentIndex2 = matchRealIndex * 2 + 2; + const parentPos1 = matchPositions[roundIndex - 1][parentIndex1]; + const parentPos2 = matchPositions[roundIndex - 1][parentIndex2]; + const parent1Disable = matchDisabled[roundIndex - 1][parentIndex1]; + const parent2Disable = matchDisabled[roundIndex - 1][parentIndex2]; if ( - currentMatchesCount == nextMatchesCount && - displayLoserFinal == false + (parent1Disable == undefined || parent1Disable == true) && + (parent2Disable == undefined || parent2Disable == true) ) { - nextMatchDistance = 0; - } else if (matchPositions.length > roundIndex - 1) { - nextMatchDistance = - matchPositions[roundIndex - 1][1] - - matchPositions[roundIndex - 1][0]; - nextMatchDistance = - nextMatchDistance * (previousMatchesCount / currentMatchesCount); + isIncomingLineIsDisabled = true; } - - if (currentMatchesCount == previousMatchesCount) { - if (matchDisabled[roundIndex - 1][matchIndex] == true) { - isIncomingLineIsDisabled = true; + if ( + matchPositions[roundIndex - 1][parentIndex1] != undefined && + matchPositions[roundIndex - 1][parentIndex2] != undefined + ) { + top = (parentPos1 + parentPos2) / 2; + if (parent1Disable && parent2Disable) { + nextMatchDistance = 0; + } else { + nextMatchDistance = parentPos2 - parentPos1; } - top = matchPositions[roundIndex - 1][matchIndex]; + } else if (matchPositions[roundIndex - 1][parentIndex1] != undefined) { + nextMatchDistance = 0; + top = matchPositions[roundIndex - 1][parentIndex1]; + } else if (matchPositions[roundIndex - 1][parentIndex2] != undefined) { + nextMatchDistance = 0; + top = matchPositions[roundIndex - 1][parentIndex2]; } else { - const parentIndex1 = matchIndex * 2; - const parentIndex2 = parentIndex1 + 1; - const parentPos1 = matchPositions[roundIndex - 1][parentIndex1]; - const parentPos2 = matchPositions[roundIndex - 1][parentIndex2]; - top = (parentPos1 + parentPos2) / 2; + nextMatchDistance = 0; + top = 0; } } else if (roundIndex < roundCount) { - const nextMatchesCount = rounds[roundIndex - 1].length; - const previousMatchesCount = rounds[roundIndex + 1].length; + const parentIndex1 = matchRealIndex * 2 + 1; + const parentIndex2 = matchRealIndex * 2 + 2; + const parentMatch1 = rounds[roundIndex + 1].find( + (match) => parseInt(match.dataset.matchRealIndex) === parentIndex1, + ); + const parentMatch2 = rounds[roundIndex + 1].find( + (match) => parseInt(match.dataset.matchRealIndex) === parentIndex2, + ); - if (currentMatchesCount == nextMatchesCount) { + if (matchPositions[roundIndex + 1] == undefined) { + matchPositions[roundIndex + 1] = {}; + } + if ( + parentMatch1 != undefined && + parentMatch2 != undefined && + parentMatch1.dataset.disabled == "false" && + parentMatch2.dataset.disabled == "false" + ) { + console.log( + roundIndex, + matchTitle, + parentMatch1.dataset.matchTitle, + parentMatch2.dataset.matchTitle, + parentMatch1.dataset.disabled, + parentMatch2.dataset.disabled, + top, + ); + nextMatchDistance = baseDistance; + matchPositions[roundIndex + 1][parentIndex1] = top - baseDistance / 2; + matchPositions[roundIndex + 1][parentIndex2] = top + baseDistance / 2; + console.log(matchPositions[roundIndex + 1]); + // } else if (parentMatch1 != undefined) { + // matchPositions[roundIndex + 1][parentIndex1] = top; + // nextMatchDistance = 0; + // } else if (parentMatch2 != undefined) { + // matchPositions[roundIndex + 1][parentIndex1] = top; + // nextMatchDistance = 0; + } else if ( + parentMatch2 != undefined && + parentMatch2.dataset.disabled == "false" + ) { nextMatchDistance = 0; - } else if (matchPositions.length > roundCount - roundIndex - 1 - 1) { - nextMatchDistance = - matchPositions[roundCount - roundIndex - 1 - 1][1] - - matchPositions[roundCount - roundIndex - 1 - 1][0]; - nextMatchDistance = - nextMatchDistance * (previousMatchesCount / currentMatchesCount); + matchPositions[roundIndex + 1][parentIndex2] = top; + } else if ( + parentMatch1 != undefined && + parentMatch1.dataset.disabled == "false" + ) { + nextMatchDistance = 0; + matchPositions[roundIndex + 1][parentIndex1] = top; + } else { + isOutgoingLineIsDisabled = true; } } if (roundCount > 5 && doubleButterflyMode == true) { if (roundIndex >= finalRoundIndex - 2) { if (roundIndex == finalRoundIndex - 1) { - matchDiv.classList.add("inward"); + matchDiv.classList.add("reverse-bracket"); + isIncomingLineIsDisabled = true; + nextMatchDistance = nextMatchDistance / 2; } if (roundIndex == finalRoundIndex + 1) { - matchDiv.classList.add("outward"); - } - if ( - roundIndex === finalRoundIndex - 2 || - roundIndex === finalRoundIndex + 2 - ) { - nextMatchDistance = nextMatchDistance - baseDistance; + matchDiv.classList.remove("reverse-bracket"); + isOutgoingLineIsDisabled = true; + nextMatchDistance = nextMatchDistance; } } } - if (displayLoserFinal == true) { - if ( - doubleButterflyMode == true && - (roundIndex == finalRoundIndex - 1 || - roundIndex == finalRoundIndex + 1) - ) { - nextMatchDistance = baseDistance; - } - } - matchDiv.style.setProperty( "--next-match-distance", `${nextMatchDistance}px`, ); matchDiv.style.top = `${top}px`; - matchPositions[roundIndex][matchIndex] = top; + matchPositions[roundIndex][matchRealIndex] = top; + if (matchIndex === 0) { // // Add logo for final round // if (roundIndex == finalRoundIndex) { @@ -270,7 +321,14 @@ function renderBracket(options) { // } // Position title above the first match - titleDiv.style.top = `${top - 80}px`; // Adjust the 60px offset as needed + if ( + roundIndex < finalRoundIndex - 1 || + roundIndex > finalRoundIndex + 1 + ) { + titleDiv.style.top = `${-80}px`; // Adjust the 60px offset as needed + } else { + titleDiv.style.top = `${top - 80}px`; // Adjust the 60px offset as needed + } titleDiv.style.position = "absolute"; if (roundCount >= 5 && doubleButterflyMode == true) { if (roundIndex == finalRoundIndex - 1) { @@ -285,6 +343,7 @@ function renderBracket(options) { if ( roundIndex == finalRoundIndex && matchIndex === 1 && + matchDisabled[roundIndex][matchRealIndex] == false && displayLoserFinal == true && doubleButterflyMode == false ) { @@ -306,25 +365,53 @@ function renderBracket(options) { matchDiv.innerHTML = `