From 522277b911b56d7a8f5d211e5380c1d684c6b81e Mon Sep 17 00:00:00 2001 From: Felix Albrigtsen Date: Fri, 25 Mar 2022 02:43:30 +0100 Subject: [PATCH] Fix bracket graphics --- src/client/.env | 3 +- src/client/src/components/appbar.js | 2 +- .../src/components/tournamentBracket.css | 223 +++++++++++------- .../src/components/tournamentBracket.js | 84 ------- src/client/src/frontpage.js | 10 +- src/client/src/managetournament.js | 2 +- src/client/src/tournamentoverview.js | 189 +++++++++++---- 7 files changed, 286 insertions(+), 227 deletions(-) delete mode 100644 src/client/src/components/tournamentBracket.js diff --git a/src/client/.env b/src/client/.env index bae2b61..c7b1e4b 100644 --- a/src/client/.env +++ b/src/client/.env @@ -1 +1,2 @@ -REACT_APP_BACKEND_URL="http://demiurgen.pvv.ntnu.no:3000" \ No newline at end of file +REACT_APP_BACKEND_URL=http://localhost:3001 +BROWSER=none \ No newline at end of file diff --git a/src/client/src/components/appbar.js b/src/client/src/components/appbar.js index 5ca4187..7b32e02 100644 --- a/src/client/src/components/appbar.js +++ b/src/client/src/components/appbar.js @@ -1,6 +1,6 @@ import * as React from "react"; import { BrowserRouter as Router, Link, Route, Routes } from "react-router-dom"; -import { AppBar, Typography, Toolbar, CssBaseline, Button, Box, IconButton } from "@mui/material" +import { AppBar, Typography, Toolbar, CssBaseline, Box, IconButton } from "@mui/material" import Menu from '@mui/icons-material/Menu' import HomeImage from "./homeimage"; diff --git a/src/client/src/components/tournamentBracket.css b/src/client/src/components/tournamentBracket.css index 65db4d0..97ea29a 100644 --- a/src/client/src/components/tournamentBracket.css +++ b/src/client/src/components/tournamentBracket.css @@ -1,86 +1,141 @@ /* https://codepen.io/semibran/pen/VjmPJd */ html { - font-size: 1rem; } - - .bracket { - display: inline-block; - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); - white-space: nowrap; - background: #f0f2f2 !important; - } - .bracket .round { - display: inline-block; - vertical-align: middle; } - .bracket .round .winners > div { - display: inline-block; - vertical-align: middle; } - .bracket .round .winners > div.matchups .matchup:last-child { - margin-bottom: 0 !important; } - .bracket .round .winners > div.matchups .matchup .participants { - border-radius: 0.25rem; - overflow: hidden; } - .bracket .round .winners > div.matchups .matchup .participants .participant { - box-sizing: border-box; - color: #858585; - border-left: 0.25rem solid #858585; - background: white; - width: 14rem; - height: 3rem; - box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.12); } - .bracket .round .winners > div.matchups .matchup .participants .participant.winner { - color: #60c645; - border-color: #60c645; } - .bracket .round .winners > div.matchups .matchup .participants .participant.loser { - color: #dc563f; - border-color: #dc563f; } - .bracket .round .winners > div.matchups .matchup .participants .participant:not(:last-child) { - border-bottom: thin solid #f0f2f2; } - .bracket .round .winners > div.matchups .matchup .participants .participant span { - margin: 0 1.25rem; - line-height: 3; - font-size: 1rem; - font-family: "Roboto Slab"; } - .bracket .round .winners > div.connector.filled .line, .bracket .round .winners > div.connector.filled.bottom .merger:after, .bracket .round .winners > div.connector.filled.top .merger:before { - border-color: #60c645; } - .bracket .round .winners > div.connector .line, .bracket .round .winners > div.connector .merger { - box-sizing: border-box; - width: 2rem; - display: inline-block; - vertical-align: top; } - .bracket .round .winners > div.connector .line { - border-bottom: thin solid #c0c0c8; - height: 4rem; } - .bracket .round .winners > div.connector .merger { - position: relative; - height: 8rem; } - .bracket .round .winners > div.connector .merger:before, .bracket .round .winners > div.connector .merger:after { - content: ""; - display: block; - box-sizing: border-box; - width: 100%; - height: 50%; - border: 0 solid; - border-color: #c0c0c8; } - .bracket .round .winners > div.connector .merger:before { - border-right-width: thin; - border-top-width: thin; } - .bracket .round .winners > div.connector .merger:after { - border-right-width: thin; - border-bottom-width: thin; } - .bracket .round.quarterfinals .winners:not(:last-child) { - margin-bottom: 2rem; } - .bracket .round.quarterfinals .winners .matchups .matchup:not(:last-child) { - margin-bottom: 2rem; } - .bracket .round.semifinals .winners .matchups .matchup:not(:last-child) { - margin-bottom: 10rem; } - .bracket .round.semifinals .winners .connector .merger { - height: 16rem; } - .bracket .round.semifinals .winners .connector .line { - height: 8rem; } - .bracket .round.finals .winners .connector .merger { - height: 3rem; } - .bracket .round.finals .winners .connector .line { - height: 1.5rem; } \ No newline at end of file + font-size: 1rem; +} + +.bracket { + display: inline-block; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + white-space: nowrap; +} + +.bracket .round { + display: inline-block; + vertical-align: middle; +} + +.bracket .round .winners>div { + display: inline-block; + vertical-align: middle; +} + +.bracket .round .winners>div.matchups .matchup:last-child { + margin-bottom: 0 !important; +} + +.bracket .round .winners>div.matchups .matchup .participants { + border-radius: 0.25rem; + overflow: hidden; +} + +.bracket .round .winners>div.matchups .matchup .participants .participant { + box-sizing: border-box; + color: #858585; + border-left: 0.25rem solid #858585; + background: white; + width: 14rem; + height: 3rem; + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.12); +} + +.bracket .round .winners>div.matchups .matchup .participants .participant.winner { + color: #60c645; + border-color: #60c645; +} + +.bracket .round .winners>div.matchups .matchup .participants .participant.loser { + color: #dc563f; + border-color: #dc563f; +} + +.bracket .round .winners>div.matchups .matchup .participants .participant:not(:last-child) { + border-bottom: thin solid #f0f2f2; +} + +.bracket .round .winners>div.matchups .matchup .participants .participant span { + margin: 0 1.25rem; + line-height: 3; + font-size: 1rem; + font-family: "Roboto Slab"; + overflow: ellipsis; +} + +.bracket .round .winners>div.connector.filled .line, +.bracket .round .winners>div.connector.filled.bottom .merger:after, +.bracket .round .winners>div.connector.filled.top .merger:before { + border-color: #60c645; +} + +.bracket .round .winners>div.connector .line, +.bracket .round .winners>div.connector .merger { + box-sizing: border-box; + width: 2rem; + display: inline-block; + vertical-align: top; +} + +.bracket .round .winners>div.connector .line { + border-bottom: thin solid #c0c0c8; + height: 4rem; +} + +.bracket .round .winners>div.connector .merger { + position: relative; + height: 8rem; +} + +.bracket .round .winners>div.connector .merger:before, +.bracket .round .winners>div.connector .merger:after { + content: ""; + display: block; + box-sizing: border-box; + width: 100%; + height: 50%; + border: 0 solid; + border-color: #c0c0c8; +} + +.bracket .round .winners>div.connector .merger:before { + border-right-width: thin; + border-top-width: thin; +} + +.bracket .round .winners>div.connector .merger:after { + border-right-width: thin; + border-bottom-width: thin; +} + +.bracket .round.quarterfinals .winners:not(:last-child) { + margin-bottom: 2rem; +} + +.bracket .round.quarterfinals .winners .matchups .matchup:not(:last-child) { + margin-bottom: 2rem; +} + +.bracket .round.semifinals .winners .matchups .matchup:not(:last-child) { + margin-bottom: 10rem; +} + +.bracket .round.semifinals .winners .connector .merger { + height: 16rem; +} + +.bracket .round.semifinals .winners .connector .line { + height: 8rem; +} + +.bracket .round.finals .winners .connector .merger { + height: 3rem; +} + +.bracket .round.finals .winners .connector .line { + height: 1.5rem; +} + +.participant:hover { + background: lightgreen!important; +} \ No newline at end of file diff --git a/src/client/src/components/tournamentBracket.js b/src/client/src/components/tournamentBracket.js deleted file mode 100644 index 92d0711..0000000 --- a/src/client/src/components/tournamentBracket.js +++ /dev/null @@ -1,84 +0,0 @@ -import './tournamentBracket.css'; - - -// -export default function TournamentBracket(props) { - return <> -
-
-
-
-
-
Uno
-
Ocho
-
-
-
-
-
Dos
-
Siete
-
-
-
-
-
-
-
-
-
-
-
-
-
Treis
-
Seis
-
-
-
-
-
Cuatro
-
Cinco
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Uno
-
Dos
-
-
-
-
-
Seis
-
Cinco
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Uno
-
Seis
-
-
-
-
-
- -} \ No newline at end of file diff --git a/src/client/src/frontpage.js b/src/client/src/frontpage.js index cd78294..302c5c6 100644 --- a/src/client/src/frontpage.js +++ b/src/client/src/frontpage.js @@ -7,8 +7,8 @@ import TournamentAnnouncement from "./tournamentannouncement"; import TournamentMatches from "./tournamentmatches"; import TeamEditor from "./teameditor"; import Appbar from './components/appbar'; -import { Button, Container, Typography, Grid, Box } from "@mui/material"; -import { Card, CardActions,CardACtionsArea, CardContent, CardHeader, CardMedia, Collapse, Paper } from "@mui/material"; +import { Button, Container, Typography, Box } from "@mui/material"; +import { Card, CardContent, CardMedia, Paper } from "@mui/material"; import AddCircleIcon from '@mui/icons-material/AddCircle'; function CreateButton(props) { @@ -73,16 +73,13 @@ function TournamentListItem(props) { } function TournamentList() { - let [data, setData] = React.useState(null); let [tournamentList, setTournamentList] = React.useState([]); React.useEffect(() => { fetch(process.env.REACT_APP_BACKEND_URL + "/api/tournament/getTournaments") .then(res => res.json()) .then(data => { - - if (data.status != "OK") { - // Do your error thing + if (data.status !== "OK") { console.error(data); return; } @@ -99,7 +96,6 @@ function TournamentList() { }, []); return <> - {/* {tournamentList && tournamentList.map((tournamentObject, i) => )} */} {tournamentList && tournamentList.map((tournamentObject) => )} ; diff --git a/src/client/src/managetournament.js b/src/client/src/managetournament.js index bab9d7c..bec0d07 100644 --- a/src/client/src/managetournament.js +++ b/src/client/src/managetournament.js @@ -16,7 +16,7 @@ function ManageTournament(props) { .then(res => res.json()) .then(data => { - if (data.status != "OK") { + if (data.status !== "OK") { // Do your error thing console.error(data.data); return; diff --git a/src/client/src/tournamentoverview.js b/src/client/src/tournamentoverview.js index e575f48..0a405d1 100644 --- a/src/client/src/tournamentoverview.js +++ b/src/client/src/tournamentoverview.js @@ -1,65 +1,120 @@ import * as React from "react"; -import { BrowserRouter as Router, Link, Route, Routes } from "react-router-dom"; +import { Link } from "react-router-dom"; import Appbar from './components/appbar'; import { useParams } from 'react-router-dom' import { Button } from "@mui/material"; -import TournamentBracket from "./components/tournamentBracket"; +import AddCircleIcon from '@mui/icons-material/AddCircle'; +import "./components/tournamentBracket.css"; -function TournamentTier(props) { - let roundTypes = ["finals", "semifinals", "quarterfinals", "eighthfinals"]; - let connector; - if (props.tier != 0) { - connector =
+function MatchPair(props) { + let match1 = ; + let match2 = ; + + return
+
+ {match1} + {match2} +
+
-
; - } +
+
+} - return
+function TournamentTier(props) { + // One round/tier of the tournament, as used by BracketViewer + let roundTypes = ["finals", "semifinals", "quarterfinals", "eighthfinals", "sixteenthfinals", "thirtysecondfinals"]; + + if (props.tier === 0) { + // The final, just a single match without the bracket lines + return ( +
- {props.matches.map((match, i) => { - return - })} +
- {connector}
+ ); + } else { + // The rest of the rounds/tiers, divide into pairs of two matches + let matchPairCount = props.matches.length / 2; + let matchPairs = []; + for (let i = 0; i < matchPairCount; i++) { + matchPairs.push(); + } + return ( +
+ {matchPairs} +
+ ); + } } function Match(props) { - let team1; - let team2; - if (props.match.team1Id != null) { - team1 =
{props.match.team1Id}
; - } else { - team1 =
TBA
; + // A single match object, as used by MatchPair and TournamentTier + let team1Name = "TBA"; + let team2Name = "TBA"; + if (props.match.team1Id !== null) { + team1Name = props.teams.find(team => team.id === props.match.team1Id).name; } - - if (props.match.team2Id != null) { - team2 =
{props.match.team2Id}
; - } else { - team2 =
TBA
; + if (props.match.team2Id !== null) { + team2Name = props.teams.find(team => team.id === props.match.team2Id).name; } - return
-
- {/*
{if (props.match.team1Id) { props.match.team1Id} else { "TBA" }}
-
{props.match.team2Id}
*/} - {team1} - {team2} + let setWinner = curryTeamId => event => { + let teamId = curryTeamId; + console.log(teamId); + if (!teamId || teamId == null) { + console.log("oops"); + return; + } + let formData = new FormData(); + formData.append("winnerId",teamId); + let body = new URLSearchParams(formData); + fetch(process.env.REACT_APP_BACKEND_URL + `/api/match/${props.match.id}/setWinner`, { + method: "POST", + body: body + }) + .then(response => response.json()) + .then(data => { + if (data.status === "OK") { + //Refresh when winner is set successfully + window.location.reload(); + } else { + showError(data.data) + } + }) + .catch(error => showError(error)); + } + + return ( +
+
+ {/* Team 1 (Winner-status?) (Team name) */} +
+ {team1Name} +
+ {/* Team 2 (Winner-status?) (Team name) */} +
+ {team2Name} +
+
-
; + ); } function BracketViewer(props) { const [tournament, setTournament] = React.useState(null); - const [matches, setMatches] = React.useState([]); - const [teams, setTeams] = React.useState([]); + const [matches, setMatches] = React.useState(null); + const [teams, setTeams] = React.useState(null); + + // One fetch statement for each of the three state variables React.useEffect(() => { fetch(process.env.REACT_APP_BACKEND_URL + `/api/tournament/${props.tournamentId}`) .then(res => res.json()) .then(data => { - if (data.status != "OK") { + if (data.status !== "OK") { // Do your error thing console.error(data); return; @@ -73,14 +128,15 @@ function BracketViewer(props) { fetch(process.env.REACT_APP_BACKEND_URL + `/api/tournament/${props.tournamentId}/getMatches`) .then(res => res.json()) .then(data => { - if (data.status != "OK") { + if (data.status !== "OK") { // Do your error thing console.error(data); return; } let matches = data.data; + // Group all matches by their round/tier let tiers = matches.reduce((tiers, match) => { - if (tiers[match.tier] == undefined) { + if (!tiers[match.tier]) { tiers[match.tier] = []; } tiers[match.tier].push(match); @@ -97,24 +153,61 @@ function BracketViewer(props) { fetch(process.env.REACT_APP_BACKEND_URL + `/api/tournament/${props.tournamentId}/getTeams`) .then(res => res.json()) .then(data=>{ - if(data.status != "OK"){ + if(data.status !== "OK"){ console.error(data) return; } + console.log(data); let teams = data.data; setTeams(teams); }) .catch((err) => console.log(err.message)); - }, []); - return
- {matches.map(tier => { - let tierNum = tier[0].tier; - return + return ( + (matches && teams) ? +
+ {matches.map(tier => { + let tierNum = tier[0].tier; + return + })} +
+ :

Loading...

+ ); +} - })} -
; +// // api.post("/match/:matchId/setWinner" +// function SelectWinnerButton(props) { +// const setWinner = function() { +// let formData = new FormData(); +// formData.append("winner", props.teamId); +// let body = new URLSearchParams(formData); + +// fetch(process.env.REACT_APP_BACKEND_URL + `/api/match/${props.matchId}`, { +// method: "POST", +// body: body +// }) +// .then(response => response.json()) +// .then(data => { +// if (data.status === "OK") { +// alert("Tournament created successfully"); +// window.location.href = "/"; +// } else { +// showError(data.data) +// } +// }) +// .catch(error => showError(error)); +// } +// return ( +// +// ); +// } + +function showError(error) { + alert("Something went wrong. \n" + error); + console.error(error); } export default function TournamentOverview(props) { @@ -128,11 +221,9 @@ export default function TournamentOverview(props) { - + - + );