From 2ad093faa61b7d44acb0c4b915b30f635d1d55e9 Mon Sep 17 00:00:00 2001 From: Hubert Walczak Date: Mon, 17 Aug 2020 20:14:19 +0200 Subject: [PATCH] Added tournament --- package-lock.json | 2 +- package.json | 2 +- public/hud.json | 2 +- public/panel.json | 14 +++ src/HUD/Layout/Layout.tsx | 3 + src/HUD/Styles/tournament.css | 137 ++++++++++++++++++++++++++++ src/HUD/Tournament/Ladder.tsx | 145 ++++++++++++++++++++++++++++++ src/HUD/Tournament/Tournament.tsx | 60 +++++++++++++ src/api/api.ts | 8 +- src/api/interfaces.ts | 20 +++++ src/index.css | 5 +- 11 files changed, 392 insertions(+), 6 deletions(-) create mode 100644 src/HUD/Styles/tournament.css create mode 100644 src/HUD/Tournament/Ladder.tsx create mode 100644 src/HUD/Tournament/Tournament.tsx diff --git a/package-lock.json b/package-lock.json index c1f2814..5c5d076 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "lexogrine_hud", - "version": "1.3.1", + "version": "1.4.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 55ce17d..6e860e3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lexogrine_hud", - "version": "1.3.1", + "version": "1.4.0", "homepage": "./", "private": true, "dependencies": { diff --git a/public/hud.json b/public/hud.json index 6a092dc..799791c 100644 --- a/public/hud.json +++ b/public/hud.json @@ -1,6 +1,6 @@ { "name":"Lexogrine HUD", - "version":"1.3.1", + "version":"1.4.0", "author":"Lexogrine", "legacy": false, "radar": true, diff --git a/public/panel.json b/public/panel.json index aebbb05..2fae436 100644 --- a/public/panel.json +++ b/public/panel.json @@ -117,6 +117,20 @@ "type": "checkbox", "name": "match_preview_toggle", "label": "Show upcoming match" + }, + { + "type": "action", + "name": "showTournament", + "values": [ + { + "name": "show", + "label": "Show tournament" + }, + { + "name": "hide", + "label": "Hide tournament" + } + ] } ] diff --git a/src/HUD/Layout/Layout.tsx b/src/HUD/Layout/Layout.tsx index babba2a..f7565e4 100644 --- a/src/HUD/Layout/Layout.tsx +++ b/src/HUD/Layout/Layout.tsx @@ -14,6 +14,7 @@ import UtilityLevel from '../SideBoxes/UtilityLevel'; import Killfeed from "../Killfeed/Killfeed"; import MapSeries from "./../MatchBar/MapSeries"; import Overview from "../Overview/Overview"; +import Tournament from "../Tournament/Tournament"; interface Props { game: CSGO, @@ -72,6 +73,8 @@ export default class Layout extends React.Component { + + diff --git a/src/HUD/Styles/tournament.css b/src/HUD/Styles/tournament.css new file mode 100644 index 0000000..3867255 --- /dev/null +++ b/src/HUD/Styles/tournament.css @@ -0,0 +1,137 @@ +.ladder-container { + position: absolute; + margin-left: 50%; + transform: translateX(-50%); + background-color: rgba(0,0,0,0.85); + top: 200px; + opacity: 0; + transition: opacity 1s; + color: white; +} +.ladder-container.show { + opacity:1; +} + +.bracket { + display: flex; + justify-content: flex-end; + position: relative; +} +.parent-brackets { +display: flex; +flex-direction: column; +justify-content: center; +position: relative; +} +.empty-bracket { +position: relative; +} +.empty-bracket > .bracket { +padding-right: 240px; +} +.empty-bracket > .connector { +width: 240px; +height: 1px; +top: 50%; +} +.loser-parent-indicator { +position: absolute; +width: 15px; +height: 15px; +left: 5px; + +top: calc(50% - 20px); +/*background-image: url('./loser-indicator.png');*/ +background-size:contain; +opacity:0.2; +} +.connector { +position: absolute; +right: 0; +height: 50%; +background-color: #ffd700; +width: 1px; +} +.bracket:first-child > .connector { +top: 50%; +} +.bracket:nth-child(2) > .connector, .empty-bracket:nth-child(2) > .bracket > .connector { +top: 0%; +} +.ladder-container > .bracket > .connector { +display: none; +} +.bracket-details .match-details .team-data:first-child { +border-bottom: 1px solid transparent; +} +.match-connector { +width: 100%; +position: absolute; +height: 1px; +background: #ffd700; +} +.match-connector.last-match { +width: 220px; +left:0; +} +.match-connector.first-match { +width: 220px; +right: 0; +} +.bracket-details { +display: flex; +justify-content: center; +align-items: center; +position: relative; +} +.bracket-details .match-details{ +display: flex; +flex-direction: column; +width: 200px; +margin: 10px 20px; +background: rgba(0,0,0,0.5); +border-radius: 2px; +padding: 5px; +z-index: 1; +cursor: pointer; +} +.bracket-details .match-details .team-data { +display: flex; +} +.bracket-details .match-details .team-data .team-name { +flex:1; +display: flex; +align-items: center; +} +.bracket-details .match-details .team-data .team-score { +width: 10px; +display: flex; +align-items: center; +} +.bracket-details .match-details .team-data .team-logo { +width:30px; +height:24px; +display: flex; +align-items: center; +justify-content: center; +} +.bracket-details .match-details .team-data .team-logo img { +height: 20px; +} +.tournament-data img { + max-height: 64px; +} +.tournament-data { + display: flex; + padding: 10px; + align-items: center; + justify-content: center; +} +.tournament-name { + text-transform: uppercase; + font-size: 45px; + padding-left: 22px; +} +.match-details.current { + border: 1px solid gold; +} \ No newline at end of file diff --git a/src/HUD/Tournament/Ladder.tsx b/src/HUD/Tournament/Ladder.tsx new file mode 100644 index 0000000..e245596 --- /dev/null +++ b/src/HUD/Tournament/Ladder.tsx @@ -0,0 +1,145 @@ +import React from 'react'; +import * as I from './../../api/interfaces'; + +interface MatchData { + left: { name: string; score: string | number; logo: string }; + right: { name: string; score: string | number; logo: string }; +} +interface Props { + tournament: I.Tournament, + matches: I.Match[], + teams: I.Team[] +} + +export default class Ladder extends React.Component { + joinParents = (matchup: I.TournamentMatchup, matchups: I.TournamentMatchup[]) => { + const { tournament } = this.props; + if (!tournament || !matchup) return matchup; + + if (matchup.parents.length) return matchup; + + const parents = matchups.filter(m => m.winner_to === matchup._id || m.loser_to === matchup._id); + if (!parents.length) return matchup; + matchup.parents.push(...parents.map(parent => this.joinParents(parent, matchups))); + + return matchup; + }; + + copyMatchups = (): I.DepthTournamentMatchup[] => { + if (!this.props.tournament) return []; + const matchups = JSON.parse(JSON.stringify(this.props.tournament.matchups)) as I.DepthTournamentMatchup[]; + return matchups; + }; + + setDepth = (matchups: I.DepthTournamentMatchup[], matchup: I.DepthTournamentMatchup, depth: number, force = false) => { + const getParents = (matchup: I.DepthTournamentMatchup) => { + return matchups.filter(parent => parent.loser_to === matchup._id || parent.winner_to === matchup._id); + }; + + if (!matchup.depth || force) { + matchup.depth = depth; + getParents(matchup).forEach(matchup => this.setDepth(matchups, matchup, depth + 1)); + } + if (matchup.depth <= depth - 1) { + this.setDepth(matchups, matchup, depth - 1, true); + } + return matchup; + }; + + getMatch = (matchup: I.TournamentMatchup) => { + const { matches } = this.props; + const matchData: MatchData = { + left: { name: 'TBD', score: '-', logo: '' }, + right: { name: 'TBD', score: '-', logo: '' } + }; + const match = matches.find(match => match.id === matchup.matchId); + if (!match) return matchData; + const teams = [ + this.props.teams.find(team => team._id === match.left.id), + this.props.teams.find(team => team._id === match.right.id) + ]; + if (teams[0]) { + matchData.left.name = teams[0].name; + matchData.left.score = match.left.wins; + matchData.left.logo = teams[0].logo; + } + if (teams[1]) { + matchData.right.name = teams[1].name; + matchData.right.score = match.right.wins; + matchData.right.logo = teams[1].logo; + } + return matchData; + }; + + renderBracket = ( + matchup: I.DepthTournamentMatchup | null | undefined, + depth: number, + fromChildId: string | undefined, + childVisibleParents: number, + isLast = false + ) => { + const { tournament, matches } = this.props; + if (!matchup || !tournament) return null; + const match = this.getMatch(matchup); + + if (fromChildId === matchup.loser_to) return null; + const parentsToRender = matchup.parents.filter(matchupParent => matchupParent.loser_to !== matchup._id); + if (matchup.depth > depth) { + return ( +
+ {this.renderBracket(matchup, depth + 1, fromChildId, parentsToRender.length)} +
+
+ ); + } + const currentMatch = matches.find(mtch => mtch.current); + const isCurrent = currentMatch && currentMatch.id === matchup.matchId; + return ( +
+
+ {this.renderBracket(matchup.parents[0], depth + 1, matchup._id, parentsToRender.length)} + {this.renderBracket(matchup.parents[1], depth + 1, matchup._id, parentsToRender.length)} +
+
+
+ {parentsToRender.length === 1 ?
: null} +
+
+
+ {match.left.logo ? Logo : null} +
+
{match.left.name}
+
{match.left.score}
+
+
+
+ {match.right.logo ? Logo : null} +
+
{match.right.name}
+
{match.right.score}
+
+
+
+ + {childVisibleParents === 2 ? ( +
+ ) : null} +
+ ); + }; + + render() { + const { tournament } = this.props; + if (!tournament) return null; + const matchups = this.copyMatchups(); + const gf = matchups.find(matchup => matchup.winner_to === null); + if (!gf) return null; + const joinedParents = this.joinParents(gf, matchups); + const matchupWithDepth = this.setDepth(matchups, joinedParents as I.DepthTournamentMatchup, 0); + return this.renderBracket(matchupWithDepth, 0, undefined, 2, true); + } +} diff --git a/src/HUD/Tournament/Tournament.tsx b/src/HUD/Tournament/Tournament.tsx new file mode 100644 index 0000000..8307d7d --- /dev/null +++ b/src/HUD/Tournament/Tournament.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import './../Styles/tournament.css'; +import { actions } from '../../App'; +import * as I from './../../api/interfaces'; +import api from '../../api/api'; +import Ladder from './Ladder'; +interface State { + tournament: I.Tournament | null, + teams: I.Team[], + matches: I.Match[], + show: boolean, +} +export default class Tournament extends React.Component<{}, State> { + constructor(props: {}) { + super(props); + this.state = { + tournament: null, + matches: [], + teams: [], + show: false + } + } + componentDidMount() { + Promise.all([api.match.get(), api.teams.get()]).then(([matches, teams]) =>{ + console.log(matches, teams); + this.setState({matches, teams}); + }); + actions.on("showTournament", async (show: string) => { + console.log(show); + if(show !== "show"){ + return this.setState({show: false}); + } + const { tournament } = await api.tournaments.get(); + if(tournament){ + this.setState({tournament}, () => this.setState({show:true})); + } + }); + } + render() { + const { tournament, matches, teams, show } = this.state; + if(!tournament) return null; + return ( +
+
+ { tournament.logo ? {tournament.name} : null } +
+ {tournament.name} +
+
+ + +
+ ); + } + +} diff --git a/src/api/api.ts b/src/api/api.ts index 3e6a5b9..dac738e 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -28,13 +28,17 @@ export async function apiV2(url: string, method = 'GET', body?: any) { export default { match: { - get: async (): Promise => apiV2(`match`) + get: async (): Promise => apiV2(`match`), }, teams: { - getOne: async (id: string): Promise => apiV2(`teams/${id}`) + getOne: async (id: string): Promise => apiV2(`teams/${id}`), + get: (): Promise => apiV2(`teams`), }, players: { get: async (): Promise => apiV2(`players`), getAvatarURLs: async (steamid: string): Promise<{custom: string, steam: string}> => apiV2(`players/avatar/steamid/${steamid}`) + }, + tournaments: { + get: () => apiV2('tournament') } } \ No newline at end of file diff --git a/src/api/interfaces.ts b/src/api/interfaces.ts index 107c700..9d82517 100644 --- a/src/api/interfaces.ts +++ b/src/api/interfaces.ts @@ -29,7 +29,27 @@ export interface Config { steamApiKey: string, token: string, }*/ +export interface TournamentMatchup { + _id: string; + loser_to: string | null; // IDs of Matchups, not Matches + winner_to: string | null; + label: string; + matchId: string | null; + parents: TournamentMatchup[]; +} + +export interface DepthTournamentMatchup extends TournamentMatchup { + depth: number; + parents: DepthTournamentMatchup[]; +} +export interface Tournament { + _id: string; + name: string; + logo: string; + matchups: TournamentMatchup[]; + autoCreate: boolean; +} export interface RoundData { round: number, players: { diff --git a/src/index.css b/src/index.css index 931f0ab..ea977e3 100644 --- a/src/index.css +++ b/src/index.css @@ -50,4 +50,7 @@ code { --color-new-ct: #5ab8f4; --color-bomb: #f22222; --color-defuse: #2222f2; -} \ No newline at end of file +} + + +