1
0
mirror of https://github.com/lexogrine/dota2-react-hud.git synced 2026-05-04 12:33:11 +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
+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