Fix bracket graphics

This commit is contained in:
Felix Albrigtsen 2022-03-25 02:43:30 +01:00
parent 2205d07585
commit 522277b911
7 changed files with 286 additions and 227 deletions

View File

@ -1 +1,2 @@
REACT_APP_BACKEND_URL="http://demiurgen.pvv.ntnu.no:3000" REACT_APP_BACKEND_URL=http://localhost:3001
BROWSER=none

View File

@ -1,6 +1,6 @@
import * as React from "react"; import * as React from "react";
import { BrowserRouter as Router, Link, Route, Routes } from "react-router-dom"; 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 Menu from '@mui/icons-material/Menu'
import HomeImage from "./homeimage"; import HomeImage from "./homeimage";

View File

@ -1,86 +1,141 @@
/* https://codepen.io/semibran/pen/VjmPJd */ /* https://codepen.io/semibran/pen/VjmPJd */
html { html {
font-size: 1rem; } font-size: 1rem;
}
.bracket { .bracket {
display: inline-block; display: inline-block;
position: absolute; position: absolute;
left: 50%; left: 50%;
top: 50%; top: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
white-space: nowrap; white-space: nowrap;
background: #f0f2f2 !important; }
}
.bracket .round { .bracket .round {
display: inline-block; display: inline-block;
vertical-align: middle; } vertical-align: middle;
.bracket .round .winners > div { }
display: inline-block;
vertical-align: middle; } .bracket .round .winners>div {
.bracket .round .winners > div.matchups .matchup:last-child { display: inline-block;
margin-bottom: 0 !important; } vertical-align: middle;
.bracket .round .winners > div.matchups .matchup .participants { }
border-radius: 0.25rem;
overflow: hidden; } .bracket .round .winners>div.matchups .matchup:last-child {
.bracket .round .winners > div.matchups .matchup .participants .participant { margin-bottom: 0 !important;
box-sizing: border-box; }
color: #858585;
border-left: 0.25rem solid #858585; .bracket .round .winners>div.matchups .matchup .participants {
background: white; border-radius: 0.25rem;
width: 14rem; overflow: hidden;
height: 3rem; }
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.12); }
.bracket .round .winners > div.matchups .matchup .participants .participant.winner { .bracket .round .winners>div.matchups .matchup .participants .participant {
color: #60c645; box-sizing: border-box;
border-color: #60c645; } color: #858585;
.bracket .round .winners > div.matchups .matchup .participants .participant.loser { border-left: 0.25rem solid #858585;
color: #dc563f; background: white;
border-color: #dc563f; } width: 14rem;
.bracket .round .winners > div.matchups .matchup .participants .participant:not(:last-child) { height: 3rem;
border-bottom: thin solid #f0f2f2; } box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.12);
.bracket .round .winners > div.matchups .matchup .participants .participant span { }
margin: 0 1.25rem;
line-height: 3; .bracket .round .winners>div.matchups .matchup .participants .participant.winner {
font-size: 1rem; color: #60c645;
font-family: "Roboto Slab"; } border-color: #60c645;
.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 { .bracket .round .winners>div.matchups .matchup .participants .participant.loser {
box-sizing: border-box; color: #dc563f;
width: 2rem; border-color: #dc563f;
display: inline-block; }
vertical-align: top; }
.bracket .round .winners > div.connector .line { .bracket .round .winners>div.matchups .matchup .participants .participant:not(:last-child) {
border-bottom: thin solid #c0c0c8; border-bottom: thin solid #f0f2f2;
height: 4rem; } }
.bracket .round .winners > div.connector .merger {
position: relative; .bracket .round .winners>div.matchups .matchup .participants .participant span {
height: 8rem; } margin: 0 1.25rem;
.bracket .round .winners > div.connector .merger:before, .bracket .round .winners > div.connector .merger:after { line-height: 3;
content: ""; font-size: 1rem;
display: block; font-family: "Roboto Slab";
box-sizing: border-box; overflow: ellipsis;
width: 100%; }
height: 50%;
border: 0 solid; .bracket .round .winners>div.connector.filled .line,
border-color: #c0c0c8; } .bracket .round .winners>div.connector.filled.bottom .merger:after,
.bracket .round .winners > div.connector .merger:before { .bracket .round .winners>div.connector.filled.top .merger:before {
border-right-width: thin; border-color: #60c645;
border-top-width: thin; } }
.bracket .round .winners > div.connector .merger:after {
border-right-width: thin; .bracket .round .winners>div.connector .line,
border-bottom-width: thin; } .bracket .round .winners>div.connector .merger {
.bracket .round.quarterfinals .winners:not(:last-child) { box-sizing: border-box;
margin-bottom: 2rem; } width: 2rem;
.bracket .round.quarterfinals .winners .matchups .matchup:not(:last-child) { display: inline-block;
margin-bottom: 2rem; } vertical-align: top;
.bracket .round.semifinals .winners .matchups .matchup:not(:last-child) { }
margin-bottom: 10rem; }
.bracket .round.semifinals .winners .connector .merger { .bracket .round .winners>div.connector .line {
height: 16rem; } border-bottom: thin solid #c0c0c8;
.bracket .round.semifinals .winners .connector .line { height: 4rem;
height: 8rem; } }
.bracket .round.finals .winners .connector .merger {
height: 3rem; } .bracket .round .winners>div.connector .merger {
.bracket .round.finals .winners .connector .line { position: relative;
height: 1.5rem; } 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;
}

View File

@ -1,84 +0,0 @@
import './tournamentBracket.css';
//
export default function TournamentBracket(props) {
return <>
<section class="round quarterfinals">
<div class="winners">
<div class="matchups">
<div class="matchup">
<div class="participants">
<div class="participant winner"><span>Uno</span></div>
<div class="participant"><span>Ocho</span></div>
</div>
</div>
<div class="matchup">
<div class="participants">
<div class="participant"><span>Dos</span></div>
<div class="participant winner"><span>Siete</span></div>
</div>
</div>
</div>
<div class="connector">
<div class="merger"></div>
<div class="line"></div>
</div>
</div>
<div class="winners">
<div class="matchups">
<div class="matchup">
<div class="participants">
<div class="participant"><span>Treis</span></div>
<div class="participant winner"><span>Seis</span></div>
</div>
</div>
<div class="matchup">
<div class="participants">
<div class="participant"><span>Cuatro</span></div>
<div class="participant winner"><span>Cinco</span></div>
</div>
</div>
</div>
<div class="connector">
<div class="merger"></div>
<div class="line"></div>
</div>
</div>
</section>
<section class="round semifinals">
<div class="winners">
<div class="matchups">
<div class="matchup">
<div class="participants">
<div class="participant winner"><span>Uno</span></div>
<div class="participant"><span>Dos</span></div>
</div>
</div>
<div class="matchup">
<div class="participants">
<div class="participant winner"><span>Seis</span></div>
<div class="participant"><span>Cinco</span></div>
</div>
</div>
</div>
<div class="connector">
<div class="merger"></div>
<div class="line"></div>
</div>
</div>
</section>
<section class="round finals">
<div class="winners">
<div class="matchups">
<div class="matchup">
<div class="participants">
<div class="participant winner"><span>Uno</span></div>
<div class="participant"><span>Seis</span></div>
</div>
</div>
</div>
</div>
</section>
</>
}

View File

@ -7,8 +7,8 @@ import TournamentAnnouncement from "./tournamentannouncement";
import TournamentMatches from "./tournamentmatches"; import TournamentMatches from "./tournamentmatches";
import TeamEditor from "./teameditor"; import TeamEditor from "./teameditor";
import Appbar from './components/appbar'; import Appbar from './components/appbar';
import { Button, Container, Typography, Grid, Box } from "@mui/material"; import { Button, Container, Typography, Box } from "@mui/material";
import { Card, CardActions,CardACtionsArea, CardContent, CardHeader, CardMedia, Collapse, Paper } from "@mui/material"; import { Card, CardContent, CardMedia, Paper } from "@mui/material";
import AddCircleIcon from '@mui/icons-material/AddCircle'; import AddCircleIcon from '@mui/icons-material/AddCircle';
function CreateButton(props) { function CreateButton(props) {
@ -73,16 +73,13 @@ function TournamentListItem(props) {
} }
function TournamentList() { function TournamentList() {
let [data, setData] = React.useState(null);
let [tournamentList, setTournamentList] = React.useState([]); let [tournamentList, setTournamentList] = React.useState([]);
React.useEffect(() => { React.useEffect(() => {
fetch(process.env.REACT_APP_BACKEND_URL + "/api/tournament/getTournaments") fetch(process.env.REACT_APP_BACKEND_URL + "/api/tournament/getTournaments")
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
if (data.status !== "OK") {
if (data.status != "OK") {
// Do your error thing
console.error(data); console.error(data);
return; return;
} }
@ -99,7 +96,6 @@ function TournamentList() {
}, []); }, []);
return <> return <>
{/* {tournamentList && tournamentList.map((tournamentObject, i) => <TournamentListItem key={tournamentObject.id.toString()} name={tournamentObject.name} description={tournamentObject.description} startDate={tournamentObject.startTime} endDate={tournamentObject.endTime} teamLimit={tournamentObject.teamLimit} />)} */}
{tournamentList && tournamentList.map((tournamentObject) => <TournamentListItem key={tournamentObject.id.toString()} tournament={tournamentObject} />)} {tournamentList && tournamentList.map((tournamentObject) => <TournamentListItem key={tournamentObject.id.toString()} tournament={tournamentObject} />)}
</>; </>;

View File

@ -16,7 +16,7 @@ function ManageTournament(props) {
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
if (data.status != "OK") { if (data.status !== "OK") {
// Do your error thing // Do your error thing
console.error(data.data); console.error(data.data);
return; return;

View File

@ -1,65 +1,120 @@
import * as React from "react"; 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 Appbar from './components/appbar';
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
import { Button } from "@mui/material"; import { Button } from "@mui/material";
import TournamentBracket from "./components/tournamentBracket"; import AddCircleIcon from '@mui/icons-material/AddCircle';
import "./components/tournamentBracket.css";
function TournamentTier(props) { function MatchPair(props) {
let roundTypes = ["finals", "semifinals", "quarterfinals", "eighthfinals"]; let match1 = <Match teams={props.teams} match={props.matches[0]} key={0} />;
let connector; let match2 = <Match teams={props.teams} match={props.matches[1]} key={1} />;
if (props.tier != 0) {
connector = <div className="connector"> return <div className="winners">
<div className="matchups">
{match1}
{match2}
</div>
<div className="connector">
<div className="merger"></div> <div className="merger"></div>
<div className="line"></div> <div className="line"></div>
</div>; </div>
} </div>
}
return <section className={`round ${roundTypes[props.tier]}`}><div className="winners"> 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 (
<section className="round finals"><div className="winners">
<div className="matchups"> <div className="matchups">
{props.matches.map((match, i) => { <Match teams={props.teams} match={props.matches[0]} key={0} />
return <Match teams={props.teams} match={match} key={i} />
})}
</div> </div>
{connector}
</div> </div>
</section> </section>
);
} 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(<MatchPair teams={props.teams} matches={props.matches.slice(i * 2, i * 2 + 2)} key={i} />);
}
return (
<section className={`round ${roundTypes[props.tier]}`}>
{matchPairs}
</section>
);
}
} }
function Match(props) { function Match(props) {
let team1; // A single match object, as used by MatchPair and TournamentTier
let team2; let team1Name = "TBA";
if (props.match.team1Id != null) { let team2Name = "TBA";
team1 = <div className='participant'><span>{props.match.team1Id}</span></div>; if (props.match.team1Id !== null) {
} else { team1Name = props.teams.find(team => team.id === props.match.team1Id).name;
team1 = <div className='participant'><span>TBA</span></div>; }
if (props.match.team2Id !== null) {
team2Name = props.teams.find(team => team.id === props.match.team2Id).name;
} }
if (props.match.team2Id != null) { let setWinner = curryTeamId => event => {
team2 = <div className='participant'><span>{props.match.team2Id}</span></div>; let teamId = curryTeamId;
} else { console.log(teamId);
team2 = <div className='participant'><span>TBA</span></div>; 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 <div className="matchup"> return (
<div className="participants"> <div className="matchup">
{/* <div class="participant winner"><span>{if (props.match.team1Id) { props.match.team1Id} else { "TBA" }}</span></div> <div className="participants">
<div class="participant"><span>{props.match.team2Id}</span></div> */} {/* Team 1 (Winner-status?) (Team name) */}
{team1} <div onClick={setWinner(props.match.team1Id)} className={`participant ${props.match.winnerId && (props.match.team1Id === props.match.winnerId) ? "winner" : ""}`}>
{team2} <span>{team1Name}</span>
</div>
{/* Team 2 (Winner-status?) (Team name) */}
<div onClick={setWinner(props.match.team2Id)} className={`participant ${props.match.winnerId && (props.match.team2Id === props.match.winnerId) ? "winner" : ""}`}>
<span>{team2Name}</span>
</div>
</div>
</div> </div>
</div>; );
} }
function BracketViewer(props) { function BracketViewer(props) {
const [tournament, setTournament] = React.useState(null); const [tournament, setTournament] = React.useState(null);
const [matches, setMatches] = React.useState([]); const [matches, setMatches] = React.useState(null);
const [teams, setTeams] = React.useState([]); const [teams, setTeams] = React.useState(null);
// One fetch statement for each of the three state variables
React.useEffect(() => { React.useEffect(() => {
fetch(process.env.REACT_APP_BACKEND_URL + `/api/tournament/${props.tournamentId}`) fetch(process.env.REACT_APP_BACKEND_URL + `/api/tournament/${props.tournamentId}`)
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
if (data.status != "OK") { if (data.status !== "OK") {
// Do your error thing // Do your error thing
console.error(data); console.error(data);
return; return;
@ -73,14 +128,15 @@ function BracketViewer(props) {
fetch(process.env.REACT_APP_BACKEND_URL + `/api/tournament/${props.tournamentId}/getMatches`) fetch(process.env.REACT_APP_BACKEND_URL + `/api/tournament/${props.tournamentId}/getMatches`)
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
if (data.status != "OK") { if (data.status !== "OK") {
// Do your error thing // Do your error thing
console.error(data); console.error(data);
return; return;
} }
let matches = data.data; let matches = data.data;
// Group all matches by their round/tier
let tiers = matches.reduce((tiers, match) => { let tiers = matches.reduce((tiers, match) => {
if (tiers[match.tier] == undefined) { if (!tiers[match.tier]) {
tiers[match.tier] = []; tiers[match.tier] = [];
} }
tiers[match.tier].push(match); tiers[match.tier].push(match);
@ -97,24 +153,61 @@ function BracketViewer(props) {
fetch(process.env.REACT_APP_BACKEND_URL + `/api/tournament/${props.tournamentId}/getTeams`) fetch(process.env.REACT_APP_BACKEND_URL + `/api/tournament/${props.tournamentId}/getTeams`)
.then(res => res.json()) .then(res => res.json())
.then(data=>{ .then(data=>{
if(data.status != "OK"){ if(data.status !== "OK"){
console.error(data) console.error(data)
return; return;
} }
console.log(data);
let teams = data.data; let teams = data.data;
setTeams(teams); setTeams(teams);
}) })
.catch((err) => console.log(err.message)); .catch((err) => console.log(err.message));
}, []); }, []);
return <div className="bracket"> return (
{matches.map(tier => { (matches && teams) ?
let tierNum = tier[0].tier; <div className="bracket">
return <TournamentTier key={tierNum} tier={tierNum} matches={tier} teams={teams} /> {matches.map(tier => {
let tierNum = tier[0].tier;
return <TournamentTier key={tierNum} tier={tierNum} matches={tier} teams={teams} />
})}
</div>
: <div className="loader"><h2>Loading...</h2></div>
);
}
})} // // api.post("/match/:matchId/setWinner"
</div>; // 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 (
// <Button className="selectWinnerButton" variant="contained" color="success" onClick={setWinner} disabled={props.disableButton} >
// +
// </Button>
// );
// }
function showError(error) {
alert("Something went wrong. \n" + error);
console.error(error);
} }
export default function TournamentOverview(props) { export default function TournamentOverview(props) {
@ -128,9 +221,7 @@ export default function TournamentOverview(props) {
<Button className="ManageButton" variant="contained" color="rackley">Manage Tournament</Button> <Button className="ManageButton" variant="contained" color="rackley">Manage Tournament</Button>
</Link> </Link>
<Link to={`/tournament/${tournamentId}/teams`}> <Link to={`/tournament/${tournamentId}/teams`}>
<Button className="OverviewButton" variant="contained" color="grape"> <Button className="OverviewButton" variant="contained" color="grape">Manage Teams</Button>
Manage Teams
</Button>
</Link> </Link>
<BracketViewer tournamentId={tournamentId} className="bracketViewer" /> <BracketViewer tournamentId={tournamentId} className="bracketViewer" />