Skip to content

Commit 2d75a85

Browse files
authored
Merge pull request #712 from odota/public_matches
add highest/lowest mmr matches
2 parents c5b57b0 + 297c595 commit 2d75a85

File tree

11 files changed

+186
-29
lines changed

11 files changed

+186
-29
lines changed

src/actions/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@ export * from './proMatchesActions';
2727
export * from './localizationActions';
2828
export * from './pvgnaActions';
2929
export * from './heroStatsActions';
30+
export * from './publicMatchesActions';
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/* global API_HOST */
2+
import fetch from 'isomorphic-fetch';
3+
import querystring from 'querystring';
4+
5+
const REQUEST = 'publicMatches/REQUEST';
6+
const OK = 'publicMatches/OK';
7+
const ERROR = 'publicMatches/ERROR';
8+
9+
export const publicMatchesActions = {
10+
REQUEST,
11+
OK,
12+
ERROR,
13+
};
14+
15+
export const getPublicMatchesRequest = () => ({
16+
type: REQUEST,
17+
});
18+
19+
export const getPublicMatchesOk = payload => ({
20+
type: OK,
21+
payload,
22+
});
23+
24+
export const getPublicMatchesError = payload => ({
25+
type: ERROR,
26+
payload,
27+
});
28+
29+
export const getPublicMatches = options => (dispatch) => {
30+
dispatch(getPublicMatchesRequest());
31+
return fetch(`${API_HOST}/api/publicMatches?${querystring.stringify(options)}`)
32+
.then(response => response.json())
33+
.then(json => dispatch(getPublicMatchesOk(json)))
34+
.catch(error => dispatch(getPublicMatchesError(error)));
35+
};

src/components/Hero/index.jsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,19 @@ import styles from './Hero.css';
1111

1212
const getSingleHero = heroId => ({ ...heroes[heroId], img: API_HOST + heroes[heroId].img });
1313

14-
const Hero = ({ props }) => (<div className={styles.Header}>
14+
const Hero = ({ props }) => (<div>
1515
<Helmet title={getSingleHero(props.routeParams.heroId).localized_name} />
16-
<Heading
17-
title={getSingleHero(props.routeParams.heroId).localized_name}
18-
className={styles.Heading}
19-
icon=""
20-
/>
21-
<img role="presentation" src={getSingleHero(props.routeParams.heroId).img} className={styles.image} />
16+
<div className={styles.Header}>
17+
<Heading
18+
title={getSingleHero(props.routeParams.heroId).localized_name}
19+
className={styles.Heading}
20+
icon=""
21+
/>
22+
<img role="presentation" src={getSingleHero(props.routeParams.heroId).img} className={styles.image} />
23+
</div>
2224
<div style={{ display: 'flex' }}>
2325
<div style={{ width: '50%', padding: '15px' }}>
24-
<Heading title={strings.tab_rankings} />
26+
<Heading title={strings.tab_rankings} subtitle={strings.rankings_description} />
2527
<Ranking {...props} />
2628
</div>
2729
<div style={{ width: '50%', padding: '15px' }}>

src/components/Heroes/index.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ class RequestLayer extends React.Component {
9494
route: '/heroes/public',
9595
}];
9696

97-
const tab = heroTabs.find(tab => tab.key.toLowerCase() === route);
97+
const tab = heroTabs.find(tab => tab.key === route);
9898
const loading = this.props.loading;
9999

100100
return (<div>
@@ -104,7 +104,7 @@ class RequestLayer extends React.Component {
104104
info={route}
105105
tabs={heroTabs}
106106
/>
107-
{heroTabs && tab.content(processedData, columns[route])}
107+
{tab && tab.content(processedData, columns[route])}
108108
</div>}
109109
</div>);
110110
}

src/components/Matches/Matches.css

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,19 @@
11
@import "../palette.css";
2+
3+
.badge {
4+
display: inline-block;
5+
6+
& svg {
7+
width: 10px !important;
8+
height: 10px !important;
9+
margin-right: 5px;
10+
}
11+
}
12+
13+
.confirmed {
14+
composes: badge;
15+
16+
& svg {
17+
fill: var(--colorGolden);
18+
}
19+
}

src/components/Matches/index.jsx

Lines changed: 93 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
1+
/* global API_HOST */
12
import React from 'react';
23
import { connect } from 'react-redux';
34
import Helmet from 'react-helmet';
4-
import { getProMatches } from 'actions';
5+
import { getProMatches, getPublicMatches } from 'actions';
56
import strings from 'lang';
67
import Table, { TableLink } from 'components/Table';
78
// import Heading from 'components/Heading';
89
import { transformations } from 'utility';
910
import subTextStyle from 'components/Visualizations/Table/subText.css';
10-
import { IconRadiant, IconDire } from 'components/Icons';
11+
import { IconRadiant, IconDire, IconTrophy } from 'components/Icons';
1112
import matchStyles from 'components/Match/Match.css';
12-
import Container from 'components/Container';
13+
import Match from 'components/Match';
14+
import TabBar from 'components/TabBar';
15+
import heroes from 'dotaconstants/build/heroes.json';
16+
import styles from './Matches.css';
1317

1418
const matchesColumns = [{
1519
displayName: strings.th_match_id,
@@ -31,33 +35,114 @@ const matchesColumns = [{
3135
displayName: <span className={matchStyles.teamIconContainer} ><IconRadiant className={matchStyles.iconRadiant} />{strings.general_radiant}</span>,
3236
field: 'radiant_name',
3337
color: matchStyles.green,
38+
displayFn: (row, col, field) => <div>{row.radiant_win && <span className={styles.confirmed}><IconTrophy /></span>}{field}</div>,
3439
}, {
3540
displayName: <span className={matchStyles.teamIconContainer} ><IconDire className={matchStyles.iconDire} />{strings.general_dire}</span>,
3641
field: 'dire_name',
3742
color: matchStyles.red,
43+
displayFn: (row, col, field) => <div>{!row.radiant_win && <span className={styles.confirmed}><IconTrophy /></span>}{field}</div>,
3844
}];
3945

46+
const publicMatchesColumns = [
47+
{
48+
displayName: strings.th_match_id,
49+
field: 'match_id',
50+
sortFn: true,
51+
displayFn: (row, col, field) => <div>
52+
<TableLink to={`/matches/${field}`}>{field}</TableLink>
53+
<span className={subTextStyle.subText} style={{ display: 'block', marginTop: 1 }}>
54+
{row.avg_mmr} {strings.th_mmr}
55+
</span>
56+
</div>,
57+
}, {
58+
displayName: strings.th_duration,
59+
tooltip: strings.tooltip_duration,
60+
field: 'duration',
61+
sortFn: true,
62+
displayFn: transformations.duration,
63+
},
64+
{
65+
displayName: <span className={matchStyles.teamIconContainer} ><IconRadiant className={matchStyles.iconRadiant} />{strings.general_radiant}</span>,
66+
field: 'radiant_team',
67+
displayFn: (row, col, field) => (field || '').split(',').map(heroId =>
68+
<img key={heroId} style={{ width: '50px' }} src={`${API_HOST}${heroes[heroId].img}`} role="presentation" />),
69+
},
70+
{
71+
displayName: <span className={matchStyles.teamIconContainer} ><IconDire className={matchStyles.iconDire} />{strings.general_dire}</span>,
72+
field: 'dire_team',
73+
displayFn: (row, col, field) => (field || '').split(',').map(heroId =>
74+
<img key={heroId} style={{ width: '50px' }} src={`${API_HOST}${heroes[heroId].img}`} role="presentation" />),
75+
},
76+
];
77+
78+
const matchTabs = [{
79+
name: strings.hero_pro_tab,
80+
key: 'pro',
81+
content: props => (<div>
82+
<Table data={props.proData} columns={matchesColumns} />
83+
</div>),
84+
route: '/matches/pro',
85+
}, {
86+
name: strings.matches_highest_mmr,
87+
key: 'highMmr',
88+
content: props => (<div>
89+
<Table data={props.publicData} columns={publicMatchesColumns} />
90+
</div>),
91+
route: '/matches/highMmr',
92+
}, {
93+
name: strings.matches_lowest_mmr,
94+
key: 'lowMmr',
95+
content: props => (<div>
96+
<Table data={props.publicData} columns={publicMatchesColumns} />
97+
</div>),
98+
route: '/matches/lowMmr',
99+
}];
100+
101+
const getData = (props) => {
102+
props.dispatchProMatches();
103+
props.dispatchPublicMatches({ mmr_ascending: props.routeParams.matchId === 'lowMmr' ? '1' : '' });
104+
};
105+
40106
class RequestLayer extends React.Component {
41107
componentDidMount() {
42-
this.props.dispatchProMatches();
108+
getData(this.props);
109+
}
110+
111+
componentWillUpdate(nextProps) {
112+
if (this.props.routeParams.matchId !== nextProps.routeParams.matchId) {
113+
getData(nextProps);
114+
}
43115
}
44116
render() {
117+
const route = this.props.routeParams.matchId || 'pro';
118+
119+
if (Number.isInteger(Number(route))) {
120+
return <Match {...this.props} />;
121+
}
122+
123+
const tab = matchTabs.find(tab => tab.key === route);
45124
return (<div>
46125
<Helmet title={strings.title_matches} />
47-
<Container>
48-
<Table data={this.props.data} columns={matchesColumns} />
49-
</Container>
126+
<div>
127+
<TabBar
128+
info={route}
129+
tabs={matchTabs}
130+
/>
131+
{tab && tab.content(this.props)}
132+
</div>
50133
</div>);
51134
}
52135
}
53136

54137
const mapStateToProps = state => ({
55-
data: state.app.proMatches.list,
138+
proData: state.app.proMatches.list,
139+
publicData: state.app.publicMatches.list,
56140
loading: state.app.proMatches.loading,
57141
});
58142

59143
const mapDispatchToProps = dispatch => ({
60144
dispatchProMatches: () => dispatch(getProMatches()),
145+
dispatchPublicMatches: options => dispatch(getPublicMatches(options)),
61146
});
62147

63148
export default connect(mapStateToProps, mapDispatchToProps)(RequestLayer);

src/components/Player/Pages/Rankings/Rankings.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import playerRankingsColumns from './playerRankingsColumns';
1111

1212
const Rankings = ({ data, error, loading }) => (
1313
<div>
14-
<Container title={strings.heading_rankings} error={error} loading={loading}>
14+
<Container title={strings.heading_rankings} subtitle={strings.rankings_description} error={error} loading={loading}>
1515
<Table paginated columns={playerRankingsColumns} data={data} />
1616
</Container>
1717
</div>

src/components/Router/index.jsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
} from 'react-router';
99
import { syncHistoryWithStore } from 'react-router-redux';
1010
import App from 'components/App';
11-
import Match from 'components/Match';
1211
import Player from 'components/Player';
1312
import Home from 'components/Home';
1413
import Search from 'components/Search';
@@ -33,8 +32,7 @@ export default () => (
3332
<Router history={history}>
3433
<Route path="/" component={App}>
3534
<IndexRoute component={Home} />
36-
<Route path="matches" component={Matches} />
37-
<Route path="matches/:matchId(/:info)" component={Match} />
35+
<Route path="matches(/:matchId)(/:info)" component={Matches} />
3836
<Route path="players/:playerId(/:info)(/:subInfo)" component={Player} />
3937
<Route path="heroes(/:heroId)(/:info)" component={Heroes} />
4038
<Route path="distributions(/:info)" component={Distributions} />

src/lang/en-US.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,9 @@
330330
"match_first_barracks": "First barracks",
331331
"match_pick": "Pick",
332332
"match_ban": "Ban",
333+
334+
"matches_highest_mmr": "High MMR",
335+
"matches_lowest_mmr": "Low MMR",
333336

334337
"npc_dota_beastmaster_boar_#": "Boar",
335338
"npc_dota_lesser_eidolon": "Eidolon",
@@ -416,7 +419,7 @@
416419
"tab_wardmap": "Wardmap",
417420
"tab_wordcloud": "Wordcloud",
418421
"tab_mmr": "MMR",
419-
"tab_rankings": "Seasonal Rankings",
422+
"tab_rankings": "Rankings",
420423
"tab_benchmarks": "Benchmarks",
421424
"tab_performances": "Performances",
422425
"tab_damage": "Damage",
@@ -551,6 +554,7 @@
551554
"th_party_mmr": "Party MMR",
552555
"th_estimated_mmr": "Estimated MMR",
553556
"th_permanent_buffs": "Buffs",
557+
"th_winner": "Winner",
554558

555559
"ward_log_type": "Type",
556560
"ward_log_owner": "Owner",
@@ -584,7 +588,7 @@
584588

585589
"title_default": "OpenDota - Dota 2 Statistics",
586590
"title_template": "%s - OpenDota - Dota 2 Statistics",
587-
"title_matches": "Professional Matches",
591+
"title_matches": "Matches",
588592
"title_request": "Request a Parse",
589593
"title_search": "Search",
590594
"title_status": "Status",
@@ -698,11 +702,7 @@
698702
"xp_reasons_2": "Hero",
699703
"xp_reasons_3": "Roshan",
700704

701-
"subheading_ranking": "Based on wins and average visible MMR in matches played",
702-
"teamfight_participation": "Radiant participation/Dire participation",
703-
"teamfight_score": "Radiant kills/Dire kills",
704-
"teamfight_radiant_gold_adv": "Net gold advantage for Radiant",
705-
"teamfight_radiant_xp_adv": "Net XP advantage for Radiant",
705+
"rankings_description": "Based on visible MMR in matches won. Resets each quarter.",
706706

707707
"vision_expired": "Expired after",
708708
"vision_destroyed": "Destroyed after",

src/reducers/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import heroRanking, { getHeroRanking } from 'reducers/heroRanking';
66
import heroBenchmark, { getHeroBenchmark } from 'reducers/heroBenchmark';
77
import search from 'reducers/search';
88
import proPlayers, { getProPlayers } from 'reducers/proPlayers';
9+
import publicMatches, { getPublicMatches } from 'reducers/publicMatches';
910
import proMatches, { getProMatches } from 'reducers/proMatches';
1011
import gotPlayer, {
1112
player,
@@ -55,6 +56,7 @@ export {
5556
getLocalization as localization,
5657
pvgnaGuides,
5758
getHeroStats as heroStats,
59+
getPublicMatches as publicMatches,
5860
};
5961

6062
export default combineReducers({
@@ -73,4 +75,5 @@ export default combineReducers({
7375
localization,
7476
pvgnaGuides,
7577
heroStats,
78+
publicMatches,
7679
});

0 commit comments

Comments
 (0)