HUD Overhaul
@@ -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;
|
||||
@@ -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> / <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;
|
||||
@@ -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;
|
||||
|
After Width: | Height: | Size: 37 KiB |
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 827 B |
@@ -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} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 341 B |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 338 B |
|
After Width: | Height: | Size: 1007 B |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 16 KiB |
@@ -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> GPM
|
||||
</div>
|
||||
<div className="panel">
|
||||
<strong className="shadowed-text">{player.xpm}</strong> 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;
|
||||
|
After Width: | Height: | Size: 188 B |
|
After Width: | Height: | Size: 185 B |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 682 B |
|
After Width: | Height: | Size: 680 B |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 3.4 MiB |