diff --git a/src/actions/index.js b/src/actions/index.js index 35f83dca50..3efb820f31 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -27,3 +27,4 @@ export * from './proMatchesActions'; export * from './localizationActions'; export * from './pvgnaActions'; export * from './heroStatsActions'; +export * from './publicMatchesActions'; diff --git a/src/actions/publicMatchesActions.js b/src/actions/publicMatchesActions.js new file mode 100644 index 0000000000..07161f7d73 --- /dev/null +++ b/src/actions/publicMatchesActions.js @@ -0,0 +1,35 @@ +/* global API_HOST */ +import fetch from 'isomorphic-fetch'; +import querystring from 'querystring'; + +const REQUEST = 'publicMatches/REQUEST'; +const OK = 'publicMatches/OK'; +const ERROR = 'publicMatches/ERROR'; + +export const publicMatchesActions = { + REQUEST, + OK, + ERROR, +}; + +export const getPublicMatchesRequest = () => ({ + type: REQUEST, +}); + +export const getPublicMatchesOk = payload => ({ + type: OK, + payload, +}); + +export const getPublicMatchesError = payload => ({ + type: ERROR, + payload, +}); + +export const getPublicMatches = options => (dispatch) => { + dispatch(getPublicMatchesRequest()); + return fetch(`${API_HOST}/api/publicMatches?${querystring.stringify(options)}`) + .then(response => response.json()) + .then(json => dispatch(getPublicMatchesOk(json))) + .catch(error => dispatch(getPublicMatchesError(error))); +}; diff --git a/src/components/Hero/index.jsx b/src/components/Hero/index.jsx index 0c9ec008f1..179e5be513 100644 --- a/src/components/Hero/index.jsx +++ b/src/components/Hero/index.jsx @@ -11,17 +11,19 @@ import styles from './Hero.css'; const getSingleHero = heroId => ({ ...heroes[heroId], img: API_HOST + heroes[heroId].img }); -const Hero = ({ props }) => (
+const Hero = ({ props }) => (
- - +
+ + +
- +
diff --git a/src/components/Heroes/index.jsx b/src/components/Heroes/index.jsx index f46a72e3b1..5f34ffd36a 100644 --- a/src/components/Heroes/index.jsx +++ b/src/components/Heroes/index.jsx @@ -94,7 +94,7 @@ class RequestLayer extends React.Component { route: '/heroes/public', }]; - const tab = heroTabs.find(tab => tab.key.toLowerCase() === route); + const tab = heroTabs.find(tab => tab.key === route); const loading = this.props.loading; return (
@@ -104,7 +104,7 @@ class RequestLayer extends React.Component { info={route} tabs={heroTabs} /> - {heroTabs && tab.content(processedData, columns[route])} + {tab && tab.content(processedData, columns[route])}
}
); } diff --git a/src/components/Matches/Matches.css b/src/components/Matches/Matches.css index e6a13b442d..325c3ac5c5 100644 --- a/src/components/Matches/Matches.css +++ b/src/components/Matches/Matches.css @@ -1 +1,19 @@ @import "../palette.css"; + +.badge { + display: inline-block; + + & svg { + width: 10px !important; + height: 10px !important; + margin-right: 5px; + } +} + +.confirmed { + composes: badge; + + & svg { + fill: var(--colorGolden); + } +} diff --git a/src/components/Matches/index.jsx b/src/components/Matches/index.jsx index 588d7702a4..41e72429cd 100644 --- a/src/components/Matches/index.jsx +++ b/src/components/Matches/index.jsx @@ -1,15 +1,19 @@ +/* global API_HOST */ import React from 'react'; import { connect } from 'react-redux'; import Helmet from 'react-helmet'; -import { getProMatches } from 'actions'; +import { getProMatches, getPublicMatches } from 'actions'; import strings from 'lang'; import Table, { TableLink } from 'components/Table'; // import Heading from 'components/Heading'; import { transformations } from 'utility'; import subTextStyle from 'components/Visualizations/Table/subText.css'; -import { IconRadiant, IconDire } from 'components/Icons'; +import { IconRadiant, IconDire, IconTrophy } from 'components/Icons'; import matchStyles from 'components/Match/Match.css'; -import Container from 'components/Container'; +import Match from 'components/Match'; +import TabBar from 'components/TabBar'; +import heroes from 'dotaconstants/build/heroes.json'; +import styles from './Matches.css'; const matchesColumns = [{ displayName: strings.th_match_id, @@ -31,33 +35,114 @@ const matchesColumns = [{ displayName: {strings.general_radiant}, field: 'radiant_name', color: matchStyles.green, + displayFn: (row, col, field) =>
{row.radiant_win && }{field}
, }, { displayName: {strings.general_dire}, field: 'dire_name', color: matchStyles.red, + displayFn: (row, col, field) =>
{!row.radiant_win && }{field}
, }]; +const publicMatchesColumns = [ + { + displayName: strings.th_match_id, + field: 'match_id', + sortFn: true, + displayFn: (row, col, field) =>
+ {field} + + {row.avg_mmr} {strings.th_mmr} + +
, + }, { + displayName: strings.th_duration, + tooltip: strings.tooltip_duration, + field: 'duration', + sortFn: true, + displayFn: transformations.duration, + }, + { + displayName: {strings.general_radiant}, + field: 'radiant_team', + displayFn: (row, col, field) => (field || '').split(',').map(heroId => + ), + }, + { + displayName: {strings.general_dire}, + field: 'dire_team', + displayFn: (row, col, field) => (field || '').split(',').map(heroId => + ), + }, +]; + +const matchTabs = [{ + name: strings.hero_pro_tab, + key: 'pro', + content: props => (
+ + ), + route: '/matches/pro', +}, { + name: strings.matches_highest_mmr, + key: 'highMmr', + content: props => (
+
+ ), + route: '/matches/highMmr', +}, { + name: strings.matches_lowest_mmr, + key: 'lowMmr', + content: props => (
+
+ ), + route: '/matches/lowMmr', +}]; + +const getData = (props) => { + props.dispatchProMatches(); + props.dispatchPublicMatches({ mmr_ascending: props.routeParams.matchId === 'lowMmr' ? '1' : '' }); +}; + class RequestLayer extends React.Component { componentDidMount() { - this.props.dispatchProMatches(); + getData(this.props); + } + + componentWillUpdate(nextProps) { + if (this.props.routeParams.matchId !== nextProps.routeParams.matchId) { + getData(nextProps); + } } render() { + const route = this.props.routeParams.matchId || 'pro'; + + if (Number.isInteger(Number(route))) { + return ; + } + + const tab = matchTabs.find(tab => tab.key === route); return (
- -
- +
+ + {tab && tab.content(this.props)} +
); } } const mapStateToProps = state => ({ - data: state.app.proMatches.list, + proData: state.app.proMatches.list, + publicData: state.app.publicMatches.list, loading: state.app.proMatches.loading, }); const mapDispatchToProps = dispatch => ({ dispatchProMatches: () => dispatch(getProMatches()), + dispatchPublicMatches: options => dispatch(getPublicMatches(options)), }); export default connect(mapStateToProps, mapDispatchToProps)(RequestLayer); diff --git a/src/components/Player/Pages/Rankings/Rankings.jsx b/src/components/Player/Pages/Rankings/Rankings.jsx index 2a8c5bf34e..a5d1fa6c07 100644 --- a/src/components/Player/Pages/Rankings/Rankings.jsx +++ b/src/components/Player/Pages/Rankings/Rankings.jsx @@ -11,7 +11,7 @@ import playerRankingsColumns from './playerRankingsColumns'; const Rankings = ({ data, error, loading }) => (
- +
diff --git a/src/components/Router/index.jsx b/src/components/Router/index.jsx index 3bae6c3857..bf9ffc659d 100644 --- a/src/components/Router/index.jsx +++ b/src/components/Router/index.jsx @@ -8,7 +8,6 @@ import { } from 'react-router'; import { syncHistoryWithStore } from 'react-router-redux'; import App from 'components/App'; -import Match from 'components/Match'; import Player from 'components/Player'; import Home from 'components/Home'; import Search from 'components/Search'; @@ -33,8 +32,7 @@ export default () => ( - - + diff --git a/src/lang/en-US.json b/src/lang/en-US.json index ad818f1928..9bd2ada684 100644 --- a/src/lang/en-US.json +++ b/src/lang/en-US.json @@ -330,6 +330,9 @@ "match_first_barracks": "First barracks", "match_pick": "Pick", "match_ban": "Ban", + + "matches_highest_mmr": "High MMR", + "matches_lowest_mmr": "Low MMR", "npc_dota_beastmaster_boar_#": "Boar", "npc_dota_lesser_eidolon": "Eidolon", @@ -416,7 +419,7 @@ "tab_wardmap": "Wardmap", "tab_wordcloud": "Wordcloud", "tab_mmr": "MMR", - "tab_rankings": "Seasonal Rankings", + "tab_rankings": "Rankings", "tab_benchmarks": "Benchmarks", "tab_performances": "Performances", "tab_damage": "Damage", @@ -551,6 +554,7 @@ "th_party_mmr": "Party MMR", "th_estimated_mmr": "Estimated MMR", "th_permanent_buffs": "Buffs", + "th_winner": "Winner", "ward_log_type": "Type", "ward_log_owner": "Owner", @@ -584,7 +588,7 @@ "title_default": "OpenDota - Dota 2 Statistics", "title_template": "%s - OpenDota - Dota 2 Statistics", - "title_matches": "Professional Matches", + "title_matches": "Matches", "title_request": "Request a Parse", "title_search": "Search", "title_status": "Status", @@ -698,11 +702,7 @@ "xp_reasons_2": "Hero", "xp_reasons_3": "Roshan", - "subheading_ranking": "Based on wins and average visible MMR in matches played", - "teamfight_participation": "Radiant participation/Dire participation", - "teamfight_score": "Radiant kills/Dire kills", - "teamfight_radiant_gold_adv": "Net gold advantage for Radiant", - "teamfight_radiant_xp_adv": "Net XP advantage for Radiant", + "rankings_description": "Based on visible MMR in matches won. Resets each quarter.", "vision_expired": "Expired after", "vision_destroyed": "Destroyed after", diff --git a/src/reducers/index.js b/src/reducers/index.js index b70acc5ac4..0c325965b6 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -6,6 +6,7 @@ import heroRanking, { getHeroRanking } from 'reducers/heroRanking'; import heroBenchmark, { getHeroBenchmark } from 'reducers/heroBenchmark'; import search from 'reducers/search'; import proPlayers, { getProPlayers } from 'reducers/proPlayers'; +import publicMatches, { getPublicMatches } from 'reducers/publicMatches'; import proMatches, { getProMatches } from 'reducers/proMatches'; import gotPlayer, { player, @@ -55,6 +56,7 @@ export { getLocalization as localization, pvgnaGuides, getHeroStats as heroStats, + getPublicMatches as publicMatches, }; export default combineReducers({ @@ -73,4 +75,5 @@ export default combineReducers({ localization, pvgnaGuides, heroStats, + publicMatches, }); diff --git a/src/reducers/publicMatches.js b/src/reducers/publicMatches.js new file mode 100644 index 0000000000..8c0db58893 --- /dev/null +++ b/src/reducers/publicMatches.js @@ -0,0 +1,15 @@ +import { publicMatchesActions } from 'actions'; +import { listData, selectors } from './reducerFactory'; + +const initialState = { + loaded: false, + error: false, + loading: false, + list: [], +}; + +export default listData(initialState, publicMatchesActions); + +export const getPublicMatches = { + ...selectors(state => state.app.publicMatches), +};