1
0
mirror of https://github.com/lexogrine/dota2-react-hud.git synced 2026-05-04 20:43:10 +02:00
This commit is contained in:
Hubert Walczak
2021-07-10 20:25:28 +02:00
commit 52e2cd949b
1417 changed files with 35119 additions and 0 deletions
+203
View File
@@ -0,0 +1,203 @@
import React from "react";
import * as I from "csgogsi-socket";
import "./matchbar.scss";
import TeamScore from "./TeamScore";
import Bomb from "./../Timers/BombTimer";
import Countdown from "./../Timers/Countdown";
import { GSI } from "../../App";
import { Match } from "../../api/interfaces";
function stringToClock(time: string | number, pad = true) {
if (typeof time === "string") {
time = parseFloat(time);
}
const countdown = Math.abs(Math.ceil(time));
const minutes = Math.floor(countdown / 60);
const seconds = countdown - minutes * 60;
if (pad && seconds < 10) {
return `${minutes}:0${seconds}`;
}
return `${minutes}:${seconds}`;
}
interface IProps {
match: Match | null;
map: I.Map;
phase: I.PhaseRaw,
bomb: I.Bomb | null,
}
export interface Timer {
width: number;
active: boolean;
countdown: number;
side: "left"|"right";
type: "defusing" | "planting";
player: I.Player | null;
}
interface IState {
defusing: Timer,
planting: Timer,
winState: {
side: "left"|"right",
show: boolean
}
}
export default class TeamBox extends React.Component<IProps, IState> {
constructor(props: IProps){
super(props);
this.state = {
defusing: {
width: 0,
active: false,
countdown: 10,
side: "left",
type: "defusing",
player: null
},
planting: {
width: 0,
active: false,
countdown: 10, // Fake
side: "right",
type: "planting",
player: null
},
winState: {
side: 'left',
show: false
}
}
}
plantStop = () => this.setState(state => {
state.planting.active = false;
return state;
});
setWidth = (type: 'defusing' | 'planting', width: number) => {
this.setState(state => {
state[type].width = width;
return state;
})
}
initPlantTimer = () => {
const bomb = new Countdown(time => {
let width = time * 100;
this.setWidth("planting", width/3);
});
GSI.on("bombPlantStart", player => {
if(!player || !player.team) return;
this.setState(state => {
state.planting.active = true;
state.planting.side = player.team.orientation;
state.planting.player = player;
})
})
GSI.on("data", data => {
if(!data.bomb || !data.bomb.countdown || data.bomb.state !== "planting") return this.plantStop();
this.setState(state => {
state.planting.active = true;
})
return bomb.go(data.bomb.countdown);
});
}
defuseStop = () => this.setState(state => {
state.defusing.active = false;
state.defusing.countdown = 10;
return state;
});
initDefuseTimer = () => {
const bomb = new Countdown(time => {
let width = time > this.state.defusing.countdown ? this.state.defusing.countdown*100 : time * 100;
this.setWidth("defusing", width/this.state.defusing.countdown);
});
GSI.on("defuseStart", player => {
if(!player || !player.team) return;
this.setState(state => {
state.defusing.active = true;
state.defusing.countdown = !Boolean(player.state.defusekit) ? 10 : 5;
state.defusing.side = player.team.orientation;
state.defusing.player = player;
return state;
})
})
GSI.on("data", data => {
if(!data.bomb || !data.bomb.countdown || data.bomb.state !== "defusing") return this.defuseStop();
this.setState(state => {
state.defusing.active = true;
return state;
})
return bomb.go(data.bomb.countdown);
});
}
resetWin = () => {
setTimeout(() => {
this.setState(state => {
state.winState.show = false;
return state;
})
}, 6000);
}
componentDidMount(){
this.initDefuseTimer();
this.initPlantTimer();
GSI.on("roundEnd", score => {
this.setState(state => {
state.winState.show = true;
state.winState.side = score.winner.orientation;
return state;
}, this.resetWin);
});
}
getRoundLabel = () => {
const { map } = this.props;
const round = map.round + 1;
if (round <= 30) {
return `Round ${round}/30`;
}
const additionalRounds = round - 30;
const OT = Math.ceil(additionalRounds/6);
return `OT ${OT} (${additionalRounds - (OT - 1)*6}/6)`;
}
render() {
const { defusing, planting, winState } = this.state;
const { bomb, match, map, phase } = this.props;
const time = stringToClock(phase.phase_ends_in);
const left = map.team_ct.orientation === "left" ? map.team_ct : map.team_t;
const right = map.team_ct.orientation === "left" ? map.team_t : map.team_ct;
const isPlanted = bomb && (bomb.state === "defusing" || bomb.state === "planted");
const bo = (match && Number(match.matchType.substr(-1))) || 0;
let leftTimer: Timer | null = null, rightTimer: Timer | null = null;
if(defusing.active || planting.active){
if(defusing.active){
if(defusing.side === "left") leftTimer = defusing;
else rightTimer = defusing;
} else {
if(planting.side === "left") leftTimer = planting;
else rightTimer = planting;
}
}
return (
<>
<div id={`matchbar`}>
<TeamScore team={left} orientation={"left"} timer={leftTimer} showWin={winState.show && winState.side === "left"} />
<div className={`score left ${left.side}`}>{left.score}</div>
<div id="timer" className={bo === 0 ? 'no-bo' : ''}>
<div id={`round_timer_text`} className={isPlanted ? "hide":""}>{time}</div>
<div id="round_now" className={isPlanted ? "hide":""}>{this.getRoundLabel()}</div>
<Bomb />
</div>
<div className={`score right ${right.side}`}>{right.score}</div>
<TeamScore team={right} orientation={"right"} timer={rightTimer} showWin={winState.show && winState.side === "right"} />
</div>
</>
);
}
}
+44
View File
@@ -0,0 +1,44 @@
import React from "react";
import * as I from "csgogsi-socket";
import { Match } from "../../api/interfaces";
interface Props {
map: I.Map;
phase: I.PhaseRaw;
match: Match | null;
}
export default class SeriesBox extends React.Component<Props> {
render() {
const { match, map } = this.props;
const amountOfMaps = (match && Math.floor(Number(match.matchType.substr(-1)) / 2) + 1) || 0;
const bo = (match && Number(match.matchType.substr(-1))) || 0;
const left = map.team_ct.orientation === "left" ? map.team_ct : map.team_t;
const right = map.team_ct.orientation === "left" ? map.team_t : map.team_ct;
return (
<div id="encapsulator">
<div className="container left">
<div className={`series_wins left `}>
<div className={`wins_box_container`}>
{new Array(amountOfMaps).fill(0).map((_, i) => (
<div key={i} className={`wins_box ${left.matches_won_this_series > i ? "win" : ""} ${left.side}`} />
))}
</div>
</div>
</div>
<div id="series_container">
<div id="series_text">{ bo ? `BEST OF ${bo}` : '' }</div>
</div>
<div className="container right">
<div className={`series_wins right `}>
<div className={`wins_box_container`}>
{new Array(amountOfMaps).fill(0).map((_, i) => (
<div key={i} className={`wins_box ${right.matches_won_this_series > i ? "win" : ""} ${right.side}`} />
))}
</div>
</div>
</div>
</div>
);
}
}
+24
View File
@@ -0,0 +1,24 @@
import React from 'react';
import { Team } from 'csgogsi-socket';
import * as I from '../../api/interfaces';
import { apiUrl } from './../../api/api';
export default class TeamLogo extends React.Component<{ team?: Team | I.Team | null, height?: number, width?: number}> {
render(){
const { team } = this.props;
if(!team) return null;
let id = '';
const { logo } = team;
if('_id' in team){
id = team._id;
} else if('id' in team && team.id){
id = team.id;
}
return (
<div className={`logo`}>
{ logo && id ? <img src={`${apiUrl}api/teams/logo/${id}`} width={this.props.width} height={this.props.height} alt={'Team logo'} /> : ''}
</div>
);
}
}
+30
View File
@@ -0,0 +1,30 @@
import React from "react";
import * as I from "csgogsi-socket";
import WinIndicator from "./WinIndicator";
import { Timer } from "./MatchBar";
import TeamLogo from './TeamLogo';
import PlantDefuse from "../Timers/PlantDefuse"
interface IProps {
team: I.Team;
orientation: "left" | "right";
timer: Timer | null;
showWin: boolean;
}
export default class TeamScore extends React.Component<IProps> {
render() {
const { orientation, timer, team, showWin } = this.props;
return (
<>
<div className={`team ${orientation} ${team.side}`}>
<div className="team-name">{team.name}</div>
<TeamLogo team={team} />
<div className="round-thingy"><div className="inner"></div></div>
</div>
<PlantDefuse timer={timer} side={orientation} />
<WinIndicator team={team} show={showWin}/>
</>
);
}
}
+12
View File
@@ -0,0 +1,12 @@
import React from 'react';
import { Team } from 'csgogsi-socket';
export default class WinAnnouncement extends React.Component<{ team: Team | null, show: boolean }> {
render() {
const { team, show } = this.props;
if(!team) return null;
return <div className={`win_text ${show ? 'show' : ''} ${team.orientation} ${team.side}`}>
WINS THE ROUND!
</div>
}
}
+391
View File
@@ -0,0 +1,391 @@
@keyframes ShowWinCycle {
0% {
opacity: 0;
height: 0;
}
5% {
opacity: 1;
height: 50px;
}
95% {
opacity: 1;
height: 50px;
}
100% {
opacity: 0;
height: 0;
}
}
#matchbar {
display: flex;
flex-direction: row;
position: fixed;
justify-content: center;
width: 1148px;
height: 70px;
top: 10px;
left: 50%;
transform: translateX(-50%);
.CT {
color: var(--color-new-ct);
.round-thingy {
.inner {
background-color: #28abff;
}
background-color: #28abff80;
}
}
.T {
color: var(--color-new-t);
.round-thingy {
.inner {
background-color: #ffc600;
}
background-color: #ffc60080;
}
}
#timer {
display: flex;
flex-direction: column;
position: relative;
width: 126px;
height: 115px;
margin-left: 8px;
margin-right: 8px;
background-color: var(--sub-panel-color);
top: -10px;
&.no-bo {
height: 87px;
}
}
#bomb_container {
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: center;
position: absolute;
width: 100%;
height: 70px;
z-index: 0;
.bomb_timer {
width: 100%;
top: 0;
height: 0;
background-color: var(--color-bomb);
&.hide {
display: none;
}
}
.bomb_icon {
position: absolute;
width: 100%;
height: 100%;
svg {
position: absolute;
left: 50%;
transform: translateX(-50%);
top: 6px;
max-height: 80%;
max-width: 80%;
}
&.hide {
display: none;
}
}
}
#round_timer_text {
display: flex;
width: 100%;
height: 55px;
justify-content: center;
font-size: 34px;
font-weight: bold;
z-index: 1;
color: var(--white-full);
align-items: flex-end;
&.hide {
display: none;
}
}
#round_now {
display: flex;
flex-direction: column;
width: 100%;
height: 27px;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: bold;
z-index: 1;
color: var(--white-full);
&.hide {
display: none;
}
}
.team {
width: 426px;
display: flex;
align-items: center;
.logo {
display: flex;
flex-direction: row;
width: 105px;
height: 70px;
align-items: center;
overflow: hidden;
background-color: var(--sub-panel-color);
img {
max-width: 90%;
max-height: 65%;
}
}
&.left {
justify-content: center;
flex-direction: row-reverse;
.round-thingy {
left: -30px;
}
.logo {
justify-content: flex-end;
}
}
&.right {
justify-content: center;
flex-direction: row;
.round-thingy {
right: -30px;
}
.logo {
justify-content: flex-start;
}
}
}
.team-name {
display: flex;
width: 360px;
height: 70px;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 30px;
background-color: var(--sub-panel-color);
}
.round-thingy {
width: 60px;
height: 60px;
position: absolute;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
.inner {
width: 35px;
height: 35px;
border-radius: 50%;
}
}
.score {
display: flex;
flex-direction: row;
width: 77px;
height: 70px;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 36px;
background-color: var(--sub-panel-color);
}
.bar {
display: flex;
flex-direction: row;
width: 10px;
height: 70px;
&.CT {
background-color: var(--color-new-ct);
}
&.T {
background-color: var(--color-new-t);
}
}
}
.win_text {
position: fixed;
display: none;
opacity: 1;
width: 503px;
height: 50px;
top: 70px;
align-items: center;
color: black;
justify-content: center;
background-color: white;
font-size: 20px;
font-family: Montserrat;
font-weight: 600;
&.show {
display: flex;
animation: ShowWinCycle 5s linear 1;
animation-fill-mode: forwards;
}
&.right {
left: calc(50% + 71px);
}
&.left {
right: calc(50% + 71px);
}
}
.defuse_plant_container {
position: fixed;
display: flex;
opacity: 1;
width: 503px;
height: 49px;
top: 70px;
align-items: center;
color: white;
justify-content: center;
background-color: rgba(0,0,0,0.65);
.defuse_plant_bar {
height: 49px;
background-color: #3c3c3c;
position: absolute;
width: 0%;
}
.defuse_plant_caption {
z-index: 1;
display: flex;
text-transform: uppercase;
align-items: flex-end;
svg {
margin-right: 13px;
}
}
&.right {
left: calc(50% + 71px);
.defuse_plant_bar {
left: 0;
}
}
&.left {
right: calc(50% + 71px);
.defuse_plant_bar {
right: 0;
}
}
&.hide {
opacity: 0;
}
}
#encapsulator {
overflow: hidden;
display: flex;
flex-direction: row;
position: fixed;
justify-content: center;
top: 80px;
width: 1148px;
height: 50px;
left: 50%;
transform: translateX(-50%);
.CT {
color: var(--color-new-ct);
}
.T {
color: var(--color-new-t);
}
.wins_bar {
display: flex;
flex-direction: row;
width: 10px;
height: 30px;
}
.wins_bar.CT {
background-color: var(--color-new-ct);
}
.wins_bar.T {
background-color: var(--color-new-t);
}
}
.alert_bar.CT {
background-color: var(--color-new-ct);
}
.alert_bar.T {
background-color: var(--color-new-t);
}
#series_container {
display: flex;
flex-direction: row;
width: 126px;
height: 30px;
}
#series_text {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: bold;
color: var(--white-full);
}
.container {
display: flex;
flex-direction: row;
width: 511px;
height: 100%;
}
.container.left {
justify-content: flex-end;
}
.container.right {
justify-content: flex-start;
}
.series_wins {
display: flex;
flex-direction: row;
width: 400px;
height: 30px;
z-index: 1;
padding-left: 6px;
padding-right: 6px;
top: -30px;
transition: top 0.5s;
}
.series_wins.show {
top: 0px;
}
.wins_box_container {
display: flex;
flex-direction: row;
width: 100%;
height: 100%;
align-items: flex-start;
justify-content: flex-start;
}
.series_wins.left {
.wins_box_container {
flex-direction: row-reverse;
}
}
.wins_box {
width: 77px;
height: 7px;
margin-left: 2px;
margin-right: 2px;
box-sizing: border-box;
}
.wins_box.CT {
background-color: rgba(0,0,0,0.6);
}
.wins_box.CT.win {
background-color: var(--color-new-ct);
}
.wins_box.T {
background-color: rgba(0,0,0,0.6);
}
.wins_box.T.win {
background-color: var(--color-new-t);
}