1
0
mirror of https://github.com/lexogrine/dota2-react-hud.git synced 2026-05-04 04:23:10 +02:00

HUD Overhaul

This commit is contained in:
Hubert Walczak
2021-09-27 18:37:25 +02:00
parent 01729977e7
commit e759e43425
316 changed files with 3828 additions and 196 deletions
+31 -9
View File
@@ -6,11 +6,28 @@ import { Dota2, DOTA2GSI, PlayerExtension } from 'dotagsi';
import { io } from "socket.io-client";
import { loadAvatarURL } from './api/avatars';
import { Match } from './api/interfaces';
import Statistics from './HUD/GameHUD/ObservedStatistics';
import TopSideBar from './HUD/GameHUD/TopSideBar';
import "./HUD/GameHUD/gamehud.scss";
import { exampleData } from './example';
const DOTA2 = new DOTA2GSI();
const socket = io(isDev ? `localhost:${port}` : '/');
const isTest = false;
if (isTest) {
setTimeout(() => {
DOTA2.digest(exampleData);
}, 100);
setTimeout(() => {
DOTA2.digest(exampleData)
}, 2000)
}
let i = 0;
socket.on('update', (data: any) => {
if (!i) console.log(data);
i = 1;
DOTA2.digest(data);
});
@@ -115,6 +132,7 @@ class App extends React.Component<any, { game: Dota2 | null, steamids: string[],
});
DOTA2.on('data', data => {
if (!this.state.game || this.state.steamids.length) this.verifyPlayers(data);
this.setState({ game: data });
})
socket.on('match', () => {
@@ -133,20 +151,24 @@ class App extends React.Component<any, { game: Dota2 | null, steamids: string[],
return;
}
this.setState({ match });
let isReversed = false;
if (DOTA2.last) {
const mapName = DOTA2.last.map.name.substring(DOTA2.last.map.name.lastIndexOf('/') + 1);
const current = match.vetos.filter(veto => veto.mapName === mapName)[0];
if (current && current.reverseSide) {
isReversed = true;
let current = match.vetos.find(veto => !veto.mapEnd);
console.log(DOTA2.last && DOTA2.last.map.win_team)
if(DOTA2.last && DOTA2.last.map.win_team !== 'none'){
const finished = match.vetos.filter(veto => veto.mapEnd);
const newCurrent = finished[finished.length - 1];
if(newCurrent){
current = newCurrent;
}
this.setState({ checked: true });
}
if (current && current.reverseSide) {
isReversed = true;
}
this.setState({ checked: true });
if (match.left.id) {
api.teams.getOne(match.left.id).then(left => {
const gsiTeamData = { id: left._id, name: left.name, country: left.country, logo: left.logo, map_score: match.left.wins, extra: left.extra };
if (!isReversed) {
DOTA2.teams.radiant = gsiTeamData;
}
@@ -171,7 +193,7 @@ class App extends React.Component<any, { game: Dota2 | null, steamids: string[],
}
}
render() {
if (!this.state.game) return '';
if (!this.state.game) return null;
return (
<Layout game={this.state.game} match={this.state.match} />
);
+40
View File
@@ -0,0 +1,40 @@
import React from 'react';
import { Draft, Team, Faction, Player, TeamDraft } from 'dotagsi';
import { apiUrl } from '../../api/api';
const ObservedPlayer = ({ players, player, team, show}: { show: boolean, player: Player | null, players: Player[], team: Team | null }) => {
const getPlayerById = (id: number) => {
return players.find(player => player.id === id);
}
return <>
{ player ? <div className={`player_container ${player.team_name} ${!show ? 'hide':''}`}>
<div className="player_info">
<div className="team_box">
<div className={`${player.team_name} team_bar`}></div>
<div className="team_logo">
{team && team.id ? <img src={`${apiUrl}api/teams/logo/${team.id}`} /> : null}
</div>
</div>
<div className="username shadowed-text">{player.name} { player.kills ? <div className="player_kills">
{
player.kill_list.map(killEntry => {
const victim = getPlayerById(killEntry.victimid);
if(!victim || !victim.hero || !victim.hero.name) return null;
return (
<div className="player_kill" style={{ backgroundImage: `url('./heroes/icons/${victim.hero.name.replace('npc_dota_hero_', '')}.png')` }}>X{killEntry.amount}</div>
);
})
}</div> : null}
</div>
</div>
<div className="player_picture">
{player.avatar ? <img src={player.avatar} /> : null}
</div>
</div> : null}
</>
}
export default ObservedPlayer;
+26
View File
@@ -0,0 +1,26 @@
import React from 'react';
import { Draft, Team, Faction, Player, TeamDraft } from 'dotagsi';
import TopSideBar from './TopSideBar';
const Statistics = ({ player, type, teamId, show}: { player: Player | null, type: Faction, teamId: string, show: boolean}) => {
if (!player) {
return <TopSideBar type={type} teamId={teamId} show={show}>
</TopSideBar>
}
const { gpm, xpm, kills, deaths, assists } = player;
return <TopSideBar type={type} teamId={teamId} show={show}>
<div className="gpm_xpm_container">
GPM <strong className="shadowed-text">{gpm}</strong> / XPM <strong className="shadowed-text">{xpm}</strong>
</div>
<div className="attack">
<strong className="shadowed-text">{player.last_hits}</strong>&nbsp;/&nbsp;<strong className="shadowed-text">{player.denies}</strong>
</div>
<div className="kda">
KDA <strong className="shadowed-text">{kills}</strong> / <strong className="shadowed-text">{deaths}</strong> / <strong className="shadowed-text">{assists}</strong>
</div>
</TopSideBar>
}
export default Statistics;
+27
View File
@@ -0,0 +1,27 @@
import React from 'react';
import { Draft, Team, Faction, Player, TeamDraft } from 'dotagsi';
import { apiUrl } from '../../api/api';
const TopSideBar = ({ children, type, teamId, show }: { children: any, type: Faction, teamId: string, show: boolean }) => {
return <div className={`top_side_bar ${type === 'radiant' ? 'left':'right'} ${!show ? 'hide':''}`}>
{type === 'radiant' ? (
<div className="logo_and_type">
<div className={`type_color ${type}`}>
</div>
<img src={`${apiUrl}api/teams/logo/${teamId}`}/>
</div>
) : null}
{children}
{type === 'dire' ? (
<div className="logo_and_type">
<div className={`type_color ${type}`}>
</div>
<img src={`${apiUrl}api/teams/logo/${teamId}`}/>
</div>
) : null}
</div>
}
export default TopSideBar;
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

+211
View File
@@ -0,0 +1,211 @@
.top_side_bar {
width: 455px;
height: 55px;
background: rgba(19, 0, 23, 0.9);
display: flex;
justify-content: space-evenly;
align-items: center;
color: white;
position: absolute;
font-size: 16px;
top:0;
transition: opacity 1s;
opacity:1;
&.hide {
opacity: 0;
}
.attack {
background-repeat: no-repeat;
background-position: 0 2px;
background-image: url('./../Scoreboard/sword.png');
padding-left:23px;
}
&.right {
right: 0;
.logo_and_type {
right: unset;
left: -92px;
img {
left: unset;
right: 6px;
}
.type_color {
left:0;
right:unset;
}
}
}
.logo_and_type {
position: absolute;
right:-92px;
width: 92px;
height: 55px;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
background: rgba(19, 0, 23, 0.9);
img {
position: relative;
left: 6px;
max-width: 75px;
}
.type_color{
width: 24px;
height: 100%;
position: absolute;
z-index:999;
top:0;
right:0;
&.radiant {
background-color: #16349E;
}
&.dire {
background-color: #BD1313;
}
}
}
}
.commercial {
width: 379px;
height: 215px;
position: fixed;
bottom: 0;
right: 172px;
transition: opacity 1s;
opacity:1;
display: flex;
align-items: flex-end;
justify-content: center;
.commercial-container {
width: 100%;
height: 153px;
display: flex;
align-items: center;
justify-content: center;
}
img {
position: relative;
left: 10px;
max-width:70%;
max-height: 120px;
transition: opacity 1s;
opacity:1;
&.hide {
opacity: 0;
}
}
&.hide {
opacity: 0;
}
}
#commercial {
position: fixed;
bottom: 0;
right: 172px;
transition: opacity 1s;
opacity:1;
&.hide {
opacity: 0;
}
}
body {
}
.player_container {
display: flex;
flex-direction: column;
position:fixed;
align-items: center;
bottom:0;
left:232px;
transition: opacity 1s;
opacity:1;
&.hide {
opacity: 0;
}
.player_picture {
width: 260px;
height: 170px;
display: flex;
justify-content: center;
background-color: rgba(1, 3, 20, 1);
align-items: flex-end;
img {
max-height: 100%;
max-width: 100%;
}
}
.player_info {
height:37px;
width: 100%;
display:flex;
align-items: flex-end;
background-color: rgba(19, 0, 23, 0.9);
.team_bar {
width: 15px;
height: 100%;
&.radiant {
background-color: #16349E;
}
&.dire {
background-color: #BD1313;
}
}
.team_box{
width: 60px;
height: 100%;
z-index:2;
display: flex;
}
.team_logo {
display:flex;
align-items: center;
justify-content: center;
img {
max-width:30px;
}
}
.username {
color: white;
font-size: 16px;
font-weight: 700;
flex:1;
height: 100%;
display:flex;
padding-left:20px;
text-transform: uppercase;
align-items: center;
position: relative;
left: -8px;
.player_kills {
margin-left: auto;
margin-right: 20px;
font-weight: 500;
display: flex;
height: 100%;
font-size: 14px;
.player_kill {
background-repeat: no-repeat;
background-position: top 2px center;
width: 20px;
background-size: 18px;
display: flex;
justify-content: center;
align-items: flex-end;
}
}
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 827 B

View File
+127 -11
View File
@@ -3,35 +3,151 @@ import React from "react";
import { Match } from "../../api/interfaces";
import MatchBar from "../MatchBar/MatchBar";
import SeriesBox from "../MatchBar/SeriesBox";
import TeamPicker from "../Picker/Picker";
import Observed from "./../Players/Observed";
import "./../Picker/player.scss";
import TeamInfo from "../Picker/TeamInfo";
import Statistics from "../GameHUD/ObservedStatistics";
import TopSideBar from "../GameHUD/TopSideBar";
import Scoreboard, { stringToClock } from "../Scoreboard/Scoreboard";
import TwitchIcon from "./../Scoreboard/twitch.png";
import FacebookIcon from './../Scoreboard/facebook.png';
import TwitterIcon from "./../Scoreboard/twitter.png";
import InstragramIcon from './../Scoreboard/instagram.png';
import ObservedPlayer from "../GameHUD/Game";
import { actions, configs } from "../../App";
const icons = {
twitch: TwitchIcon,
facebook: FacebookIcon,
instagram: InstragramIcon,
twitter: TwitterIcon
} as any;
interface Props {
game: Dota2,
match: Match | null
}
export default class Layout extends React.Component<Props> {
componentDidMount() {
interface State {
view: 'draft' | 'game' | 'scoreboard' | null;
text: string;
}
export default class Layout extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
view: null,
text: '',
}
}
componentDidMount() {
actions.on('viewType', (data: any) => {
this.setState({ view: data });
})
const handleViewType = (type: string) => {
actions.on(type, () => {
this.setState({ view: type === this.state.view ? null : type as any });
})
}
handleViewType('scoreboard');
handleViewType('game');
handleViewType('draft');
const configHandler = (data: any) => {
if (!data || !data.view) return;
this.setState({
text: data.view.info_box,
});
}
if (configs.data) {
configHandler(configs.data);
}
configs.onChange(configHandler)
}
render() {
const { game, match } = this.props;
const state = game.map.game_state;
let view = this.state.view;
if (!view) {
switch (state) {
case "DOTA_GAMERULES_STATE_HERO_SELECTION":
case "DOTA_GAMERULES_STATE_STRATEGY_TIME":
case "DOTA_GAMERULES_STATE_TEAM_SHOWCASE":
case "DOTA_GAMERULES_STATE_PRE_GAME":
view = 'draft';
break;
case "DOTA_GAMERULES_STATE_GAME_IN_PROGRESS":
view = "game";
break;
case "DOTA_GAMERULES_STATE_POST_GAME":
view = "scoreboard";
break;
default:
break;
}
}
//(this.state.sponsor)
/// DOTA_GAMERULES_STATE_HERO_SELECTION
let activeTeamBonusTime = 0;
if(game.draft.activeteam !== undefined){
if(game.draft.activeteam === 2 && game.draft.radiant){
activeTeamBonusTime = game.draft.radiant.bonus_time ;
} else if (game.draft.activeteam === 3 && game.draft.dire){
activeTeamBonusTime = game.draft.dire.bonus_time;
}
}
return (
<div className="layout">
<MatchBar map={game.map} match={match} time={game.map.clock_time} players={game.players} />
<SeriesBox map={game.map} match={match} />
<Observed player={game.player}/>
<>
<div className="layout">
<div className={`draft-screen-container ${view === 'draft' ? '' : 'hide'}`}>
<div className="draft-container">
<TeamPicker draft={game.draft.radiant} type={'radiant'} active={game.draft.activeteam === 2} />
<div className="tournament_info">
<div className="bo shadowed-text">
{(match && match.matchType) || 'BO2'}
</div>
<div className="picker_and_logo">
<div className={`side_pick left ${game.draft.activeteam === 2 ? 'active' : ''}`}></div>
<div className={`side_pick right ${game.draft.activeteam === 3 ? 'active' : ''}`}></div>
</div>
</div>
<TeamPicker draft={game.draft.dire} type={'dire'} active={game.draft.activeteam === 3} />
</div>
<div className="team_info_container s shadowed-text">
<TeamInfo draft={game.draft} team={game.map.radiant} type={'radiant'} players={game.players.filter(player => player.team_name === 'radiant')} />
</div>
<div className="timer">
<div className="label shadowed-text">{ game.draft.activeteam_time_remaining ? 'Pick time' : 'Bonus time'}</div>
<div className="timer-time shadowed-text">{stringToClock(game.draft.activeteam_time_remaining || activeTeamBonusTime)}</div>
</div>
<TeamInfo draft={game.draft} team={game.map.dire} type={'dire'} players={game.players.filter(player => player.team_name === 'dire')} />
</div>
</div>
<Scoreboard players={game.players} map={game.map} match={match} show={view === 'scoreboard'} />
</div>
<Statistics
player={game.player}
type="radiant"
teamId={game.map.radiant && game.map.radiant.id || ''}
show={view === 'game'}
/>
<TopSideBar type="dire" teamId={game.map.dire && game.map.dire.id || ''} show={view === 'game'}>
<span>{this.state.text}</span>
</TopSideBar>
<ObservedPlayer players={game.players} show={view === 'game'} player={game.player} team={game.player ? (game.player.team_name === "radiant" ? game.map.radiant : game.map.dire) : null} />
</>
);
}
}
+2 -2
View File
@@ -32,8 +32,8 @@ export default class TeamBox extends React.Component<IProps> {
const right = map.dire;
const bo = (match && Number(match.matchType.substr(-1))) || 0;
const leftScore = players.filter(player => player.team_name === 'radiant').map(player => player.kills).reduce((a, b) => a + b, 0);
const rightScore = players.filter(player => player.team_name === 'dire').map(player => player.kills).reduce((a, b) => a + b, 0);
const leftScore = players.filter(player => player.team_name === 'dire').map(player => player.deaths).reduce((a, b) => a + b, 0);
const rightScore = players.filter(player => player.team_name === 'radiant').map(player => player.deaths).reduce((a, b) => a + b, 0);
return (
<>
+58
View File
@@ -0,0 +1,58 @@
import React from 'react';
import { Draft, DraftEntry, Faction, TeamDraft } from 'dotagsi';
import Snow from "./snowflake.png";
const PlayerPick = ({ entry, type, active }: { entry: DraftEntry, type: Faction, active: boolean }) => {
const order = entry.order + 1;
let lastPart = 'th';
if (order === 1) {
lastPart = 'st';
} else if (order === 2) {
lastPart = 'nd';
} else if (order === 3) {
lastPart = 'rd';
}
let text = `${order}${lastPart} PICK`;
if (!entry.class) {
text = '';
}
if (active) {
text = 'PICKING...';
}
return <div className={`player_draft ${type} ${active ? 'active' : ''}`}>
<div className="player_preview">
<div className="background-imgs">
</div>
{entry.class ? <video muted={true} autoPlay={true} loop={true} width="123">
<source src={`./heroes/animated/npc_dota_hero_${entry.class}.webm`}
type="video/webm" />
Sorry, your browser doesn't support embedded videos.
</video> : null}
</div>
<div className={`draft_status ${!text ? 'hidden' : ''}`}>
{text}
</div>
</div>
}
export const PlayerBan = ({ entry, type, active }: { entry: DraftEntry, type: Faction, active: boolean }) => {
const order = entry.order + 1;
if (!entry.class) {
return <div className={`player_ban ${type} ${active ? 'active' : ''}`}>
<div className="player_preview" style={{backgroundColor:'#0E0018'}}>
</div>
</div>
}
return <div className={`player_ban ${entry.order} ${type} ${active ? 'active' : ''}`}>
<div className="player_preview">
<img src={`./heroes/${entry.class}.png`} />
</div>
</div>
}
export default PlayerPick;
+49
View File
@@ -0,0 +1,49 @@
import React from 'react';
import { Draft, Faction, TeamDraft } from 'dotagsi';
import PlayerPick, { PlayerBan } from './Pick';
const TeamPicker = ({ draft, type, active }: { draft?: TeamDraft, type: Faction, active: boolean }) => {
if (!draft) {
return <div className="team_draft">
<div className={`players_draft ${type}`}>
</div>
<div className="team_draft_info">
</div>
</div>
}
const picks = draft.picks.filter(pick => pick.type === 'pick').sort((a, b) => a.order - b.order);
const smallestNotPickedOrder = Math.min(...picks.filter(entry => !entry.class).map(entry => entry.order));
return <div className="team_draft">
<div className={`players_draft ${type}`}>
{
picks.map(pick => <PlayerPick entry={pick} type={type} active={active && smallestNotPickedOrder === pick.order} />)
}
</div>
<div className="team_draft_info">
</div>
</div>
}
export const TeamBaner = ({ draft, type, active }: { draft?: TeamDraft, type: Faction, active: boolean }) => {
if (!draft) {
return <div className={`team_draft_ban ${type}`}>
<div className={`players_draft_ban ${type}`}>
</div>
</div>
}
const picks = draft.picks.filter(pick => pick.type === 'ban').sort((a, b) => a.order - b.order);
const smallestNotPickedOrder = Math.min(...picks.filter(entry => !entry.class).map(entry => entry.order));
return <div className={`team_draft_ban ${type}`}>
<div className={`players_draft_ban ${type}`}>
{
picks.map(pick => <PlayerBan entry={pick} type={type} active={active && smallestNotPickedOrder === pick.order} />)
}
</div>
</div>
}
export default TeamPicker;
+24
View File
@@ -0,0 +1,24 @@
import React from 'react';
import { Draft, Team, Faction, Player, TeamDraft } from 'dotagsi';
import { TeamBaner } from './Picker';
const TeamInfo = ({ draft, type, players, team }: { draft: Draft, type: Faction, players: Player[], team: Team, }) => {
return <div className={`team_info ${type}`}>
<div className="team_info_score">
{team.map_score}
</div>
<div className="team_squad">
<div className="top_bar">
<div className="name">
{team.name}
</div>
<TeamBaner draft={draft[type]} type={type} active={false}/>
</div>
<div className="bottom_bar">
{players.map(player => player.name).join(' / ')}
</div>
</div>
</div>
}
export default TeamInfo;
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

+233
View File
@@ -0,0 +1,233 @@
.background {
position: absolute;
background-color: rgba(27, 30, 69, 0.9);
width: 100%;
height: 100%;
transition: opacity 1s;
opacity:1;
&.hide {
opacity: 0;
}
}
.draft-screen-container {
transition: opacity 1s;
opacity:1;
&.hide {
opacity: 0;
}
}
.draft-container {
display: flex;
position: fixed;
margin-left:50%;
transform: translateX(-50%);
bottom: 104px;
}
.tournament_info {
width: 139px;
height: 145px;
display: flex;
flex-direction: column;
justify-content: space-between;
.bo {
height: 33px;
width: 100%;
background-color: rgba(19, 0, 23, 0.9);
color: white;
display: flex;
align-items: center;
font-weight: 700;
justify-content: center;
text-transform: uppercase;
}
.picker_and_logo {
display: flex;
flex: 1;
background-color: rgba(19, 0, 23, 0.9);
background-repeat: no-repeat;
background-position: center;
justify-content: space-between;
background-size: cover;
.side_pick{
display: flex;
flex: 1;
align-items: center;
justify-content: center;
background-repeat: no-repeat;
background-position: center;
max-width: 45px;
&.left {
background-image: url('./leftPick.png');
&.active {
background-image: url('./leftPickActive.png');
}
}
&.right {
background-image: url('./rightPick.png');
&.active {
background-image: url('./rightPickActive.png');
}
}
}
}
}
.players_draft {
display: flex;
&.radiant {
flex-direction: row-reverse;
}
}
.players_draft_ban {
display: flex;
&.radiant {
flex-direction: row-reverse;
}
}
.team_draft_ban {
position: absolute;
&.radiant {
right: 0;
}
&.dire {
left: 0;
}
top: 4px;
}
.player_ban {
margin: 0 2px;
.player_preview {
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
width: 36px;
height: 36px;
img {
height: 36px;
}
}
}
.player_draft {
width:123px;
height:145px;
display:flex;
flex-direction: column;
margin:0 1.5px;
&.dire {
.draft_status {
background-color: #BD1313;
}
}
&.radiant {
.draft_status {
background-color: #16349E;
}
}
.player_preview {
flex: 1;
background-color:rgba(19, 0, 23, 0.9);
display: flex;
background-position: center;
background-repeat: no-repeat;
.background-imgs {
flex:1;
opacity:0.3;
background-position: center;
background-repeat: no-repeat;
}
}
.draft_status {
width:100%;
height:21px;
color: white;
text-align: center;
font-weight: 700;
font-size: 15px;
&.active {
}
&.hidden {
height: 7px;
}
}
}
.team_info_container {
display: flex;
height: 76px;
width: 1390px;
background-color: rgba(19, 0, 23, 0.9);
color: white;
position: fixed;
margin-left:50%;
transform: translateX(-50%);
bottom: 24px;
.timer {
width: 139px;
height: 100%;
background-color: #0E0018;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.label {
font-size: 15px;
text-transform: uppercase;
opacity: 0.7;
font-weight: 700;
letter-spacing: 0.1em;
}
.timer-time {
font-weight: 700;
font-size: 22px;
}
}
.team_info {
flex:1;
display: flex;
position: relative;
.team_info_score {
width: 60px;
display:flex;
align-items: center;
justify-content: center;
font-size: 38px;
font-weight: 700;
}
.team_squad {
display: flex;
flex-direction: column;
.top_bar {
font-size: 24px;
}
.bottom_bar {
font-size: 15px;
}
> div {
flex: 1;
display: flex;
align-items: center;
}
}
&.radiant {
.team_info_score {
background-color: #16349E;
margin-right: 8px;
}
}
&.dire {
flex-direction: row-reverse;
.team_info_score {
background-color: #BD1313;
margin-left: 8px;
}
.team_squad {
align-items: flex-end;
}
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1007 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

+110
View File
@@ -0,0 +1,110 @@
import React from 'react';
import { Draft, Team, Faction, Player, TeamDraft, Map } from 'dotagsi';
import RadiantBorder from './radiantBorder.png';
import DireBorder from './direBorder.png';
import "./scoreboard.scss";
import { Match } from '../../api/interfaces';
import { apiUrl } from '../../api/api';
export 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}`;
}
const ScoreboardPlayer = ({ player }: { player: Player }) => {
const neutralItem = player.items.find(item => item.type === 'neutral');
return <div className={`player_scoreboard`}>
<div className="main_panel">
<div className="player_name shadowed-text">
<strong>{player.name}</strong>
</div>
<div className="player_picture">
{ player.hero && player.hero.name ? <video muted={true} autoPlay={true} loop={true} width="145">
<source src={`./heroes/animated/${player.hero.name}.webm`}
type="video/webm" />
Sorry, your browser doesn't support embedded videos.
</video> : null}
<div className="level_container">
<div className="level_value shadowed-text">{player.hero && player.hero.level || 0}</div>
</div>
</div>
<strong className="shadowed-text kda">{player.kills} / {player.deaths} / {player.assists}</strong>
</div>
<div className="panel">
<strong className="shadowed-text">{player.net_worth} NET WORTH</strong>
</div>
<div className="panel">
<strong className="shadowed-text ">{player.hero_damage}</strong>
</div>
<div className="panel">
<strong className="shadowed-text">{player.gpm}</strong>&nbsp;GPM
</div>
<div className="panel">
<strong className="shadowed-text">{player.xpm}</strong>&nbsp;XPM
</div>
<div className="skills">
{
player.items.filter(item => item.type === "slot" && item.id < 6).map(item => <div className="item-slot">{item.name !== "empty" ? <img src={`./items/${item.name.replace('item_', '')}.png`} height={57} /> : null}</div>)
}
</div>
{ neutralItem ? <div className="neutral-item">
{neutralItem.name !== "empty" ? <img src={`./items/${neutralItem.name.replace('item_', '')}.png`} height={57} /> : null}
</div> : null}
</div>
}
export { ScoreboardPlayer };
const Scoreboard = ({ map, players, match, show }: { players: Player[], match: Match | null, map: Map, show: boolean }) => {
const leftScore = players.filter(player => player.team_name === 'dire').map(player => player.deaths).reduce((a, b) => a + b, 0);
const rightScore = players.filter(player => player.team_name === 'radiant').map(player => player.deaths).reduce((a, b) => a + b, 0);
return <>
<div className={`top_board ${!show ? 'hide' : ''}`}>
<div className="team_logo">
{map.radiant.id ? <img src={`${apiUrl}api/teams/logo/${map.radiant.id}`} /> : null}
</div>
{map.radiant.name}
<div className="score_info">
<div className="score_container radiant shadowed-text">
{map.radiant.map_score || 0}
</div>
<div className="match_info">
<div className="match_time">
{stringToClock(map.clock_time)}
</div>
<div className="match_score">
{leftScore} - {rightScore} | {match && match.matchType}
</div>
</div>
<div className="score_container dire shadowed-text">
{map.dire.map_score || 0}
</div>
</div>
{map.dire.name}
<div className="team_logo">
{map.dire.id ? <img src={`${apiUrl}api/teams/logo/${map.dire.id}`} /> : null}
</div>
</div>
<div className={`players_scoreboard ${!show ? 'hide' : ''}`}>
<div>
{players.filter(player => player.team_name === 'radiant').map(player => <ScoreboardPlayer player={player} />)}
</div>
<div>
{players.filter(player => player.team_name === 'dire').map(player => <ScoreboardPlayer player={player} />)}
</div>
</div>
</>
}
export default Scoreboard;
Binary file not shown.

After

Width:  |  Height:  |  Size: 188 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

+222
View File
@@ -0,0 +1,222 @@
.player_scoreboard{
width:165px;
display: flex;
flex-direction: column;
color: white;
margin: 0 7.5px;
.player_name {
font-size: 22px;
display: flex;
align-items: center;
justify-content: center;
height: 38px;
text-transform: uppercase;
}
.player_picture {
height: 145px;
width: 145px;
position: relative;
display: flex;
justify-content: center;
background:red;
.level_container {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
bottom: -66px;
img {
opacity: 0;
}
.level_value {
position: absolute;
font-size: 20px;
top: -90px;
height: 50px;
width: 50px;
display: flex;
align-items: center;
justify-content: center;
border-radius:50px;
font-weight: 700;
background-color: rgba(19, 0, 23, 0.9);
}
}
}
.main_panel {
width: 100%;
height: 250px;
margin-bottom: 5px;
background-color: rgba(19, 0, 23, 0.9);
display: flex;
flex-direction: column;
align-items: center;
.kda {
position: relative;
top: 35px;
}
}
.panel {
width: 100%;
height: 38px;
background-color: rgba(19, 0, 23, 0.9);
margin-bottom:5px;
display: flex;
align-items: center;
justify-content: center;
background-repeat: no-repeat;
}
.skills {
width: 100%;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.item-slot {
display: flex;
align-items: center;
justify-content: center;
background: rgba(19, 0, 23, 0.9);
width: 80px;
height: 57px;
box-shadow: 0px 4px 20px #070C36;
margin-bottom: 5px;
}
}
}
.neutral-item {
width: 78px;
clip-path: polygon(50% 0, calc(50% + 28.5px) 50%, 50% 100%, calc(50% - 28.5px) 50%);
height: 57px;
margin: auto;
position: relative;
top: -33px;
}
.players_scoreboard {
display: flex;
position: fixed;
top: 243px;
width: 1870px;
justify-content: space-between;
position: fixed;
margin-left: 50%;
transform: translateX(-50%);
transition: opacity 1s;
opacity:1;
&.hide {
opacity: 0;
}
> div {
display: flex;
}
}
strong.ad {
text-transform: uppercase;
width: 200px;
img {
max-width: 34px;;
}
transition: opacity 1s;
opacity:1;
&.hide {
opacity: 0;
}
}
.top_board {
position: fixed;
align-items: center;
margin-left: 50%;
transform: translateX(-50%);
top: 46px;
width: 916px;
height: 67px;
background-color: #130017;
color: white;
display: flex;
justify-content: center;
font-size: 31px;
transition: opacity 1s;
opacity: 1;
text-transform: uppercase;
&.hide {
opacity: 0;
}
.score_info {
display: flex;
color: white;
.score_container {
height: 84px;
margin-bottom: 6px;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 35px;
position: relative;
width: 71px;
margin: 0 10px;
&.dire {
background-color: #BD1313;
}
&.radiant {
background-color: #16349E;
}
}
.match_info {
height: 67px;
align-items: center;
justify-content: center;
flex-direction: column;
font-weight: 700;
text-shadow: 0px 0px 7px rgba(1, 3, 20, 0.2);
.match_score {
font-size: 15px;
letter-spacing: 1px;
width: 156px;
background-color: rgba(19, 0, 23, 0.9);
height: 28px;
display: flex;
align-items: center;
justify-content: center;
text-transform: uppercase;
position: absolute;
bottom: -48px;
}
.match_time {
font-size: 40px;
background: #130017;
width: 156px;
display: flex;
align-items: center;
justify-content: center;
height:84px;
position: relative;
letter-spacing: 2px;
align-self: center;
}
}
}
.team_logo {
&:first-child {
margin-right: auto;
}
&:last-child {
margin-left:auto;
}
height: 100%;
width: 156px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
img {
max-width: 100px;
max-height: 100px;
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 682 B

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 680 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

+6 -11
View File
@@ -69,17 +69,12 @@ export interface PlayerRoundData {
}
export interface Veto {
teamId: string;
mapName: string;
side: "CT" | "T" | "NO";
type: "ban" | "pick" | "decider";
reverseSide?: boolean;
rounds?: RoundData[],
score?: {
[key: string]: number;
};
winner?: string;
mapEnd: boolean;
mapEnd: boolean;
winner?: string;
score?: {
[key: string]: number;
};
reverseSide?: boolean;
}
export interface Match {
+2592
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+40
View File
@@ -0,0 +1,40 @@
@font-face {
font-family: 'Rajdhani';
src: url('Rajdhani-Bold.woff') format('woff');
font-weight: bold;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Rajdhani';
src: url('Rajdhani-Medium.woff') format('woff');
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Rajdhani';
src: url('Rajdhani-Light.woff') format('woff');
font-weight: 300;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Rajdhani';
src: url('Rajdhani-Regular.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Rajdhani';
src: url('Rajdhani-SemiBold.woff') format('woff');
font-weight: 600;
font-style: normal;
font-display: swap;
}
+3 -2
View File
@@ -9,10 +9,11 @@ body,
body {
margin: 0;
font-family: 'Stratum2', 'Montserrat', 'Roboto', sans-serif;
font-family: 'Rajdhani','Stratum2', 'Montserrat', 'Roboto', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/*background-image: url('./assets/bg.png');/**/
/*background-image: url('./HUD/image.png');/**/
background-position: bottom;
}
@font-face {
+1 -2
View File
@@ -1,8 +1,7 @@
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import "./fonts/Louis George Cafe.ttf";
import "./fonts/Rounded_Elegance.ttf";
import "./fonts/rajdhani.css";
import App from "./App";
ReactDOM.render(<App />, document.getElementById("root"));