From bd7e9aea06d3319002741120232e3ffe23c725f9 Mon Sep 17 00:00:00 2001 From: "micael.bergeron" Date: Thu, 27 Oct 2016 14:14:09 -0400 Subject: [PATCH 01/21] add ward log draft --- package.json | 2 + .../Match/MatchHeader/MatchHeader.jsx | 2 +- src/components/Match/WardLog.jsx | 125 ++++++++++++++ src/components/Match/matchColumns.jsx | 6 +- src/components/Match/matchPages.jsx | 3 + src/components/Match/renderMatch.js | 156 +++++++++++------- src/components/TabBar/TabBar.jsx | 4 +- src/components/Table/Table.jsx | 56 ++++--- .../Visualizations/Table/HeroImage.jsx | 14 +- 9 files changed, 273 insertions(+), 95 deletions(-) create mode 100644 src/components/Match/WardLog.jsx diff --git a/package.json b/package.json index 13c560a502..ce91cc800d 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,9 @@ "flexboxgrid": "6.3.1", "heatmap.js": "2.0.5", "isomorphic-fetch": "2.2.1", + "lodash": "^4.16.4", "material-ui": "0.16.1", + "moment-duration-format": "^1.3.0", "node-uuid": "1.4.7", "postcss-browser-reporter": "0.5.0", "postcss-color-function": "2.0.1", diff --git a/src/components/Match/MatchHeader/MatchHeader.jsx b/src/components/Match/MatchHeader/MatchHeader.jsx index 1e66d4059a..6a09dbe24f 100644 --- a/src/components/Match/MatchHeader/MatchHeader.jsx +++ b/src/components/Match/MatchHeader/MatchHeader.jsx @@ -80,7 +80,7 @@ export default ({ match, user, loading }) => {
  • {strings.match_avg_mmr} - {(mmrPlayers.reduce(sum) / mmrPlayers.length).toFixed(0)} + {(mmrPlayers.reduce(sum, 0) / mmrPlayers.length).toFixed(0)}
  • diff --git a/src/components/Match/WardLog.jsx b/src/components/Match/WardLog.jsx new file mode 100644 index 0000000000..ec3a74d0b6 --- /dev/null +++ b/src/components/Match/WardLog.jsx @@ -0,0 +1,125 @@ +import React from 'react'; +import Checkbox from 'material-ui/Checkbox'; +import Visibility from 'material-ui/svg-icons/action/visibility'; +import VisibilityOff from 'material-ui/svg-icons/action/visibility-off'; +import { + isRadiant, + // transformations, +} from 'utility'; +import Table from 'components/Table'; +import { + Row, + Col, +} from 'react-flexbox-grid'; +// import heroes from 'dotaconstants/json/heroes.json'; +import strings from 'lang'; +import { + heroTd, +} from './matchColumns'; +import { API_HOST } from 'config'; +import _ from 'lodash'; + +const obsWard = (style, stroke, iconSize) => ( + + Observer + + + + + + + +); + +const senWard = (style, stroke, iconSize) => ( + + Sentry + + + + + + + +); + +// a simple functor that will call the correct function depending on value +const threshold = _.curry((start, limits, values, value) => { + if (limits.length != values.length) throw "Limits must be the same as functions."; + var limits = limits.slice(0); + limits.unshift(start); + return _.findLast(values, (v, i) => _.inRange(value, limits[i], limits[i+1])); +}); + +const durationTdColor = threshold(0, [121, 241, 361], ['red', 'yellow', 'green']); + +// TODO Hero icon on ward circles? +class WardLog extends React.Component { + componentWillMount() { + this.setState({ + enabledIndex: {}, + from: 0, + to: -1 + }); + } + render() { + const match = this.props.match; + const width = this.props.width; + const enabledIndex = this.state.enabledIndex; + const iconSize = width / 12; + const style = ward => ({ + position: 'absolute', + top: ((width / 127) * ward.y) - (iconSize / 2), + left: ((width / 127) * ward.x) - (iconSize / 2), + }); + const obsIcons = []; + const senIcons = []; + return ( + + + }, { + displayName: "Player", + displayFn: (row) => heroTd(match.players[row.player]), + },{ + displayName: strings.th_ward_observer, + field: 'entered', + displayFn: (row, col, field) => field.time, + }, { + displayName: "Left", + field: 'left', + displayFn: (row, col, field) => (field && field.time) || "-", + }, { + displayName: strings.th_ward_log_duration || "Duration (s)", + displayFn: (row) => { + if (!row.left) return "-"; + const duration = row.left.time - row.entered.time; + const style = { color: durationTdColor(duration) }; + return {duration} + }, + }, { + displayName: strings.th_ward_log_killed_by || "Killed by", + displayFn: (row) => { + if (!row.left || !row.left.player1) return "-"; + return heroTd(match.players[row.left.player1]); + } + } + ]} + /> + + ); + } +} + +// TODO use defaultprops and export directly +export default function ({ + match, + width = 600, +}) { + return ; +} diff --git a/src/components/Match/matchColumns.jsx b/src/components/Match/matchColumns.jsx index 2e22d761ee..d3cfbd0b93 100644 --- a/src/components/Match/matchColumns.jsx +++ b/src/components/Match/matchColumns.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import heroes from 'dotaconstants/json/heroes.json'; import runes from 'dotaconstants/json/runes.json'; import items from 'dotaconstants/json/items.json'; import orderTypes from 'dotaconstants/json/order_types.json'; @@ -28,13 +27,14 @@ import styles from './Match.css'; // {row.last_login && row.last_login && } export const heroTd = (row, col, field, index, hideName) => ( ); @@ -604,4 +604,4 @@ export const teamfightColumns = [ return
    ; }) : ''), }, -]; +]; \ No newline at end of file diff --git a/src/components/Match/matchPages.jsx b/src/components/Match/matchPages.jsx index 3e943820ef..7c79a0a0b1 100644 --- a/src/components/Match/matchPages.jsx +++ b/src/components/Match/matchPages.jsx @@ -13,6 +13,7 @@ import Table from 'components/Table'; import { Row, Col } from 'react-flexbox-grid'; import { IconRadiant, IconDire } from 'components/Icons'; import VisionMap from './VisionMap'; +import WardLog from './WardLog'; import CastTable from './CastTable'; import CrossTable from './CrossTable'; import MatchGraph from './MatchGraph'; @@ -198,6 +199,8 @@ const matchPages = [{ content: match => (
    + +
    ), }, { name: strings.tab_actions, diff --git a/src/components/Match/renderMatch.js b/src/components/Match/renderMatch.js index c469589bec..0a5dce0551 100644 --- a/src/components/Match/renderMatch.js +++ b/src/components/Match/renderMatch.js @@ -8,6 +8,9 @@ import specific from 'dotaconstants/json/specific.json'; import laneRole from 'dotaconstants/json/lane_role.json'; import analysis from './analysis'; +import { flow, map, flatten, sortBy } from 'lodash/fp'; +import _ from 'lodash/fp' + const expanded = {}; Object.keys(specific).forEach((key) => { for (let i = 1; i < 5; i += 1) { @@ -119,66 +122,98 @@ function generateTeamfights(match) { return (match.teamfights || []).map(tf => computeTfData(tf)); } +// create a detailed history of each wards +function generateWardLog(match) { + const computeWardData = (player, i) => { + const sameWard = _.curry((w1, w2) => w1.ehandle === w2.ehandle); + + // let's zip the *_log and the *_left log in a 2-tuples + const extractWardLog = (type, entered_log, left_log) => { + return entered_log.map((e) => { + let wards = [e, left_log.find(sameWard(e))]; + return { + player: i, + type: type, + entered: wards[0], + left: wards[1] + }; + }); + }; + + var observers = extractWardLog("observer", player.obs_log, player.obs_left_log); + var sentries = extractWardLog("sentry", player.sen_log, player.sen_left_log); + return _.concat(observers, sentries); + }; + + + const wardLog = flow( + map.convert({ cap: false })(computeWardData), + flatten, + sortBy(xs => xs['entered']['time']), + ); + return wardLog(match.players); // cap: false to keep the index +} + function renderMatch(m) { /* - // Not using for MVP - // originally implemented by @coreymaher - m.hero_combat = { - damage: { - radiant: 0, - dire: 0, - }, - kills: { - radiant: 0, - dire: 0, - }, - }; - m.players.forEach(pm => { - // Compute combat k/d and damage tables - pm.hero_combat = { - damage: { - total: 0, - }, - taken: { - total: 0, - }, - kills: { - total: 0, - }, - deaths: { - total: 0, - }, - }; - m.players.forEach((other_pm) => { - const team = (pm.isRadiant) ? 'radiant' : 'dire'; - const other_hero = heroes[other_pm.hero_id]; - let damage = 0; - let taken = 0; - let kills = 0; - let deaths = 0; - // Only care about enemy hero combat - if (pm.isRadiant !== other_pm.isRadiant && pm.damage) { - damage = (pm.damage[other_hero.name]) ? pm.damage[other_hero.name] : 0; - taken = (pm.damage_taken[other_hero.name]) ? pm.damage_taken[other_hero.name] : 0; - } - if (pm.isRadiant !== other_pm.isRadiant && pm.killed) { - kills = (pm.killed[other_hero.name]) ? pm.killed[other_hero.name] : 0; - deaths = (pm.killed_by[other_hero.name]) ? pm.killed_by[other_hero.name] : 0; - } - pm.hero_combat.damage[other_hero.name] = damage; - pm.hero_combat.taken[other_hero.name] = taken; - pm.hero_combat.damage.total += damage; - pm.hero_combat.taken.total += taken; - pm.hero_combat.kills[other_hero.name] = kills; - pm.hero_combat.deaths[other_hero.name] = deaths; - pm.hero_combat.kills.total += kills; - pm.hero_combat.deaths.total += deaths; - m.hero_combat.damage[team] += damage; - m.hero_combat.kills[team] += kills; - }); - }); - }); - */ + // Not using for MVP + // originally implemented by @coreymaher + m.hero_combat = { + damage: { + radiant: 0, + dire: 0, + }, + kills: { + radiant: 0, + dire: 0, + }, + }; + m.players.forEach(pm => { + // Compute combat k/d and damage tables + pm.hero_combat = { + damage: { + total: 0, + }, + taken: { + total: 0, + }, + kills: { + total: 0, + }, + deaths: { + total: 0, + }, + }; + m.players.forEach((other_pm) => { + const team = (pm.isRadiant) ? 'radiant' : 'dire'; + const other_hero = heroes[other_pm.hero_id]; + let damage = 0; + let taken = 0; + let kills = 0; + let deaths = 0; + // Only care about enemy hero combat + if (pm.isRadiant !== other_pm.isRadiant && pm.damage) { + damage = (pm.damage[other_hero.name]) ? pm.damage[other_hero.name] : 0; + taken = (pm.damage_taken[other_hero.name]) ? pm.damage_taken[other_hero.name] : 0; + } + if (pm.isRadiant !== other_pm.isRadiant && pm.killed) { + kills = (pm.killed[other_hero.name]) ? pm.killed[other_hero.name] : 0; + deaths = (pm.killed_by[other_hero.name]) ? pm.killed_by[other_hero.name] : 0; + } + pm.hero_combat.damage[other_hero.name] = damage; + pm.hero_combat.taken[other_hero.name] = taken; + pm.hero_combat.damage.total += damage; + pm.hero_combat.taken.total += taken; + pm.hero_combat.kills[other_hero.name] = kills; + pm.hero_combat.deaths[other_hero.name] = deaths; + pm.hero_combat.kills.total += kills; + pm.hero_combat.deaths.total += deaths; + m.hero_combat.damage[team] += damage; + m.hero_combat.kills[team] += kills; + }); + }); + }); + */ const newPlayers = m.players.map((player) => { const newPlayer = { ...player, @@ -217,8 +252,8 @@ function renderMatch(m) { identifier = 'fort'; } newPlayer.objective_damage[identifier] = newPlayer.objective_damage[identifier] ? - newPlayer.objective_damage[identifier] + player.damage[key] : - player.damage[key]; + newPlayer.objective_damage[identifier] + player.damage[key] : + player.damage[key]; }); } if (player.killed) { @@ -249,6 +284,7 @@ function renderMatch(m) { graphData: generateGraphData(m), teamfights: generateTeamfights(m), players: newPlayers, + wards_log: generateWardLog(m), }; } diff --git a/src/components/TabBar/TabBar.jsx b/src/components/TabBar/TabBar.jsx index 0e6c7f144b..7185f8b8a2 100644 --- a/src/components/TabBar/TabBar.jsx +++ b/src/components/TabBar/TabBar.jsx @@ -30,10 +30,10 @@ const TabBar = ({ router, tabs, info }) => (
    ); -const { string, shape, arrayOf } = React.PropTypes; +const { string, shape, array } = React.PropTypes; TabBar.propTypes = { router: shape({}), - tabs: arrayOf(), + tabs: array, info: string, }; diff --git a/src/components/Table/Table.jsx b/src/components/Table/Table.jsx index ca8cadb91e..79f10b3e1c 100644 --- a/src/components/Table/Table.jsx +++ b/src/components/Table/Table.jsx @@ -25,34 +25,42 @@ const getTable = (data, columns, sortState, sortField, sortClick) => ( {data.map((row, index) => ( - - {columns.map((column, colIndex) => { - const MaterialTableRowColumnStyle = { - // width: `${getWidthStyle(column.width, totalWidth)}%`, - overflow: `${column.field === 'kills' ? 'visible' : null}`, - color: column.color, - }; - return ( - - {row && column.displayFn && column.displayFn(row, column, row[column.field], index)} - {row && !column.displayFn && row[column.field]} - - ); - })} - - ))} + + {columns.map((column, colIndex) => { + const MaterialTableRowColumnStyle = { + // width: `${getWidthStyle(column.width, totalWidth)}%`, + overflow: `${column.field === 'kills' ? 'visible' : null}`, + color: column.color, + }; + const displayColumn = (column, row) => { + if (row && column.displayFn) { + if (column.field && column.field in row) + return column.displayFn(row, column, row[column.field], index); + return column.displayFn(row, column); + } else { + return row[column.field]; + } + } + return ( + + {displayColumn(column, row)} + + ); + })} + + ))} diff --git a/src/components/Visualizations/Table/HeroImage.jsx b/src/components/Visualizations/Table/HeroImage.jsx index cdaf3a1e8f..dc10caf453 100644 --- a/src/components/Visualizations/Table/HeroImage.jsx +++ b/src/components/Visualizations/Table/HeroImage.jsx @@ -6,16 +6,19 @@ import strings from 'lang'; import { TableLink } from 'components/Table'; import { playerColors } from 'utility'; import styles from './HeroImage.css'; +import heroes from 'dotaconstants/json/heroes.json'; +import { API_HOST } from 'config'; const TableHeroImage = ({ parsed, - image, + heroId, registered, title, subtitle, accountId, playerSlot, hideText, + hideImage, }) => { const tooltipId = uuid.v4(); @@ -33,10 +36,10 @@ const TableHeroImage = ({ } - {image && + {!hideImage &&
    @@ -49,7 +52,7 @@ const TableHeroImage = ({
    } {!hideText && -
    +
    {registered &&
    @@ -78,7 +81,7 @@ const { number, string, object, oneOfType, bool } = React.PropTypes; TableHeroImage.propTypes = { parsed: number, - image: string, + heroId: number, title: string, subtitle: oneOfType([ string, @@ -88,6 +91,7 @@ TableHeroImage.propTypes = { accountId: number, playerSlot: number, hideText: bool, + hideImage: bool, }; export default TableHeroImage; From 9d1f65f90b421b076bc833dd61531a3b946b86bb Mon Sep 17 00:00:00 2001 From: "micael.bergeron" Date: Thu, 3 Nov 2016 19:03:25 -0400 Subject: [PATCH 02/21] fix a performance issue for the Vision page remove the usage of redux's Connect from the vision page by using the PlainTable instead of the default Table (TableWithOptions). there are still performance issues, buut it is usable. --- package.json | 2 + src/components/Match/VisionMap.jsx | 5 +- src/components/Match/VisionPage.jsx | 73 ++++++++++++++++ src/components/Match/WardLog.jsx | 37 ++++---- src/components/Match/matchPages.jsx | 11 +-- src/components/Match/renderMatch.js | 9 +- src/components/Table/Table.jsx | 128 ++++++++++++++-------------- src/utility/components.jsx | 16 ++++ 8 files changed, 187 insertions(+), 94 deletions(-) create mode 100644 src/components/Match/VisionPage.jsx create mode 100644 src/utility/components.jsx diff --git a/package.json b/package.json index ce91cc800d..fe9ca837a8 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,8 @@ "eslint-plugin-react": "6.4.1", "file-loader": "0.9.0", "json-loader": "0.5.4", + "react-addons-perf": "^15.3.2", + "react-perf-tool": "^0.1.7", "style-loader": "0.13.1", "url-loader": "0.5.7" }, diff --git a/src/components/Match/VisionMap.jsx b/src/components/Match/VisionMap.jsx index 6bc0fc6e1a..fc7b656cc9 100644 --- a/src/components/Match/VisionMap.jsx +++ b/src/components/Match/VisionMap.jsx @@ -6,7 +6,7 @@ import { isRadiant, // transformations, } from 'utility'; -import Table from 'components/Table'; +import { PlainTable as Table } from 'components/Table'; import { Row, Col, @@ -16,6 +16,7 @@ import strings from 'lang'; import { heroTdColumn, } from './matchColumns'; +import { Fixed } from 'utility/components'; const obsWard = (style, stroke, iconSize) => ( @@ -41,6 +42,8 @@ const senWard = (style, stroke, iconSize) => ( ); +const FixedTable = Fixed(Table); + // TODO Hero icon on ward circles? class VisionMap extends React.Component { componentWillMount() { diff --git a/src/components/Match/VisionPage.jsx b/src/components/Match/VisionPage.jsx new file mode 100644 index 0000000000..3097698e5b --- /dev/null +++ b/src/components/Match/VisionPage.jsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { + Row, + Col, +} from 'react-flexbox-grid'; + +import Slider from 'material-ui/Slider'; +import Heading from 'components/Heading'; +import VisionMap from './VisionMap' ; +import WardLog from './WardLog'; +import _ from 'lodash/fp'; +import Immutable from 'immutable'; +import Perf from 'react-addons-perf'; +import strings from 'lang'; + +window.Perf = Perf; + +class VisionPage extends React.Component { + componentWillMount() { + Perf.start(); + } + + constructor(props) { + super(props); + const min = _.first(props.match.wards_log).entered.time; + const max = _.last(props.match.wards_log).entered.time + 360; + this.state = { + from: 0, + to: this.props.match.wards_log.length, + min: min, + max: max, + wardsLog: Immutable.List(props.match.wards_log).map((e, i) => Object.assign(e, {key: i})) + } + + this.findPivot = (value) => this.state.wardsLog + .toSeq() + .map(x => x.entered.time) + .findKey((e) => e > value); + } + + handleViewportChange(e, value) { + const log = this.state.wardsLog; + const p = this.state.from; + + this.setState({ from: this.findPivot(value) }); + } + + shouldComponentUpdate(newProps, newState) { + return this.state.from != newState.from; + } + + visibleData() { + return this.state.wardsLog.skip(this.state.from); + } + + render() { + const visibleWards = this.visibleData(); + return ( +
    + + + this.handleViewportChange(e, value)} /> + +
    + ); + } +} + +export default VisionPage; diff --git a/src/components/Match/WardLog.jsx b/src/components/Match/WardLog.jsx index ec3a74d0b6..93c7183d87 100644 --- a/src/components/Match/WardLog.jsx +++ b/src/components/Match/WardLog.jsx @@ -6,7 +6,7 @@ import { isRadiant, // transformations, } from 'utility'; -import Table from 'components/Table'; +import { PlainTable as Table } from 'components/Table'; import { Row, Col, @@ -17,7 +17,17 @@ import { heroTd, } from './matchColumns'; import { API_HOST } from 'config'; -import _ from 'lodash'; +import _ from 'lodash/fp'; + +// remove this when done +import { + Table as MaterialTable, + TableBody as MaterialTableBody, + TableHeader as MaterialTableHeader, + TableRow as MaterialTableRow, + TableRowColumn as MaterialTableRowColumn, +} from 'material-ui/Table'; + const obsWard = (style, stroke, iconSize) => ( @@ -51,21 +61,13 @@ const threshold = _.curry((start, limits, values, value) => { return _.findLast(values, (v, i) => _.inRange(value, limits[i], limits[i+1])); }); -const durationTdColor = threshold(0, [121, 241, 361], ['red', 'yellow', 'green']); +// const durationSentryColor = threshold(0, [121, 120, 500], ['red', 'yellow', 'green']); +const durationObserverColor = threshold(0, [121, 241, 500], ['red', 'yellow', 'green']); // TODO Hero icon on ward circles? class WardLog extends React.Component { - componentWillMount() { - this.setState({ - enabledIndex: {}, - from: 0, - to: -1 - }); - } render() { - const match = this.props.match; const width = this.props.width; - const enabledIndex = this.state.enabledIndex; const iconSize = width / 12; const style = ward => ({ position: 'absolute', @@ -77,7 +79,7 @@ class WardLog extends React.Component { return (
    }, { displayName: "Player", - displayFn: (row) => heroTd(match.players[row.player]), + displayFn: (row) => heroTd(this.props.match.players[row.player]), },{ displayName: strings.th_ward_observer, field: 'entered', @@ -99,14 +101,14 @@ class WardLog extends React.Component { displayFn: (row) => { if (!row.left) return "-"; const duration = row.left.time - row.entered.time; - const style = { color: durationTdColor(duration) }; + const style = { color: row.type == "sentry" ? 'white' : durationObserverColor(duration) }; return {duration} }, }, { displayName: strings.th_ward_log_killed_by || "Killed by", displayFn: (row) => { if (!row.left || !row.left.player1) return "-"; - return heroTd(match.players[row.left.player1]); + return heroTd(this.props.match.players[row.left.player1]); } } ]} @@ -119,7 +121,8 @@ class WardLog extends React.Component { // TODO use defaultprops and export directly export default function ({ match, + wardsLog, width = 600, }) { - return ; + return ; } diff --git a/src/components/Match/matchPages.jsx b/src/components/Match/matchPages.jsx index 7c79a0a0b1..9edde436df 100644 --- a/src/components/Match/matchPages.jsx +++ b/src/components/Match/matchPages.jsx @@ -12,8 +12,7 @@ import Heading from 'components/Heading'; import Table from 'components/Table'; import { Row, Col } from 'react-flexbox-grid'; import { IconRadiant, IconDire } from 'components/Icons'; -import VisionMap from './VisionMap'; -import WardLog from './WardLog'; +import VisionPage from './VisionPage'; import CastTable from './CastTable'; import CrossTable from './CrossTable'; import MatchGraph from './MatchGraph'; @@ -197,11 +196,9 @@ const matchPages = [{ name: strings.tab_vision, parsed: true, content: match => (
    - - - - -
    ), + + + ), }, { name: strings.tab_actions, parsed: true, diff --git a/src/components/Match/renderMatch.js b/src/components/Match/renderMatch.js index 0a5dce0551..3ffdaeccf0 100644 --- a/src/components/Match/renderMatch.js +++ b/src/components/Match/renderMatch.js @@ -133,6 +133,7 @@ function generateWardLog(match) { let wards = [e, left_log.find(sameWard(e))]; return { player: i, + key: wards[0].ehandle, type: type, entered: wards[0], left: wards[1] @@ -146,10 +147,10 @@ function generateWardLog(match) { }; - const wardLog = flow( - map.convert({ cap: false })(computeWardData), - flatten, - sortBy(xs => xs['entered']['time']), + const wardLog = _.flow( + _.map.convert({ cap: false })(computeWardData), + _.flatten, + _.sortBy(xs => xs['entered']['time']), ); return wardLog(match.players); // cap: false to keep the index } diff --git a/src/components/Table/Table.jsx b/src/components/Table/Table.jsx index 79f10b3e1c..30581a94a0 100644 --- a/src/components/Table/Table.jsx +++ b/src/components/Table/Table.jsx @@ -17,71 +17,70 @@ import { // getTotalWidth, // getWidthStyle, } from './tableHelpers'; +import { Fixed } from 'utility/components'; -const getTable = (data, columns, sortState, sortField, sortClick) => ( - // Not currently using totalWidth (default auto width) - // const totalWidth = getTotalWidth(columns); -
    - - - - - - {data.map((row, index) => ( - - {columns.map((column, colIndex) => { - const MaterialTableRowColumnStyle = { - // width: `${getWidthStyle(column.width, totalWidth)}%`, - overflow: `${column.field === 'kills' ? 'visible' : null}`, - color: column.color, - }; - const displayColumn = (column, row) => { - if (row && column.displayFn) { - if (column.field && column.field in row) - return column.displayFn(row, column, row[column.field], index); - return column.displayFn(row, column); - } else { - return row[column.field]; - } - } - return ( - - {displayColumn(column, row)} - - ); - })} - - ))} - - -
    -); +const PureRow = Fixed(MaterialTableRow); -const Table = ({ - data, - columns, - loading, - error, - sortState, - sortField, - sortClick, - numRows, -}) => ( -
    - {loading && } - {!loading && error && } - {!loading && !error && data && getTable(data, columns, sortState, sortField, sortClick, numRows)} -
    -); +class Table extends React.PureComponent { + render() { + let { + data, + columns, + sortState, + sortField, + sortClick + } = this.props; + + return ( +
    +
    + + + + + + {data.map((row, index) => ( + + {columns.map((column, colIndex) => { + const displayColumn = (column, row) => { + if (row && column.displayFn) { + if (column.field && column.field in row) + return column.displayFn(row, column, row[column.field], index); + return column.displayFn(row, column); + } else { + return row[column.field]; + } + } + return ( + + {displayColumn(column, row)} + + ); + })} + + ))} + + +
    +
    + ); + } +} + +const TableLoading = (props) => { + let { loading, error } = props; + if (loading) return ; + if (error) return ; + return
    ; +}; const { arrayOf, @@ -99,7 +98,6 @@ Table.propTypes = { sortState: string, sortField: string, sortClick: func, - numRows: number, }; -export default Table; +export default TableLoading; diff --git a/src/utility/components.jsx b/src/utility/components.jsx new file mode 100644 index 0000000000..8e652486d1 --- /dev/null +++ b/src/utility/components.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +export const Fixed = (Component) => class extends React.Component { + shouldComponentUpdate() { + return false; + } + render() { + return + } +}; + +export const Pure = (Component) => class extends React.PureComponent { + render() { + return + } +}; From c23f12d546d28cf214bdf2cb6121935ed5f58fb3 Mon Sep 17 00:00:00 2001 From: "micael.bergeron" Date: Sat, 5 Nov 2016 19:25:18 -0400 Subject: [PATCH 03/21] improve the ward log table i've decided to not use a
    anymore to host this data as it seems the material-ui table causes some issues with transitions and animating table row is hacky at best a simple + + + + {heroTd(match.players[row.player])} + + {formatSeconds(row.entered.time)} + {formatSeconds(row.left && row.left.time) || "-"} + + {duration} + + {wardKiller} + + + ); +} +const PureRowItem = Fixed(RowItem); + class WardLog extends React.Component { + getDefaultStyles() { + return this.props.wardsLog.map((row, i) => ( + { + data: row, + key: String(row.key || i), + style: {height: 0} + })); + } + + getStyles() { + return this.props.wardsLog.map((row, i) => ( + { + data: row, + key: String(row.key || i), + style: { + height: spring(50, presets.stiff), + } + } + )); + } + + willEnter() { + return { + height: 0, + }; + } + + willLeave() { + return { + height: spring(0), + }; + } + render() { const width = this.props.width; const iconSize = width / 12; @@ -77,43 +104,18 @@ class WardLog extends React.Component { const obsIcons = []; const senIcons = []; return ( - -
    - }, { - displayName: "Player", - displayFn: (row) => heroTd(this.props.match.players[row.player]), - },{ - displayName: strings.th_ward_observer, - field: 'entered', - displayFn: (row, col, field) => field.time, - }, { - displayName: "Left", - field: 'left', - displayFn: (row, col, field) => (field && field.time) || "-", - }, { - displayName: strings.th_ward_log_duration || "Duration (s)", - displayFn: (row) => { - if (!row.left) return "-"; - const duration = row.left.time - row.entered.time; - const style = { color: row.type == "sentry" ? 'white' : durationObserverColor(duration) }; - return {duration} - }, - }, { - displayName: strings.th_ward_log_killed_by || "Killed by", - displayFn: (row) => { - if (!row.left || !row.left.player1) return "-"; - return heroTd(this.props.match.players[row.left.player1]); - } - } - ]} - /> - + + + {this.props.wardsLog.map((log, index) => + + )} + + ); } } diff --git a/src/components/palette.css b/src/components/palette.css index 77b2a7ad6c..1d6f2f0a04 100644 --- a/src/components/palette.css +++ b/src/components/palette.css @@ -38,7 +38,7 @@ --fontSizeSmall: 12px; /* Font -- */ - --dividerColor: color(var(--textSecondaryColor) lightness(20%)); + --dividerColor: color(var(--textColorSecondary) lightness(20%)); --navDrawerWidth: 256px; @@ -65,6 +65,7 @@ primaryTextColor: var(--textColorPrimary); textColorSecondary: var(--textColorSecondary); secondaryTextColor: var(--textColorSecondary); + dividerColor: var(--dividerColor); /* Colors -- */ /* -- Font */ diff --git a/src/utility/components.jsx b/src/utility/components.jsx index 8e652486d1..0014f2cc4f 100644 --- a/src/utility/components.jsx +++ b/src/utility/components.jsx @@ -1,8 +1,8 @@ import React from 'react'; export const Fixed = (Component) => class extends React.Component { - shouldComponentUpdate() { - return false; + shouldComponentUpdate(newProps, newState) { + return this.props.style != newProps.style; } render() { return From e38c4632c9c1ce6318ea2fc0fc11df92c211ed99 Mon Sep 17 00:00:00 2001 From: "micael.bergeron" Date: Sat, 5 Nov 2016 23:07:37 -0400 Subject: [PATCH 04/21] improve the ward log visual --- src/components/Match/Match.css | 35 +++++++++++-------- src/components/Match/VisionPage.jsx | 24 ++++++------- src/components/Match/WardLog.jsx | 54 ++++++++--------------------- src/components/Table/Table.css | 6 ++-- src/components/palette.css | 4 +++ src/lang/en.json | 7 ++++ 6 files changed, 60 insertions(+), 70 deletions(-) diff --git a/src/components/Match/Match.css b/src/components/Match/Match.css index 0d2db02ba6..9090e742a6 100644 --- a/src/components/Match/Match.css +++ b/src/components/Match/Match.css @@ -160,25 +160,38 @@ .ward-log { width: 100%; - padding: 0; -} -.ward-log-item { - height: var(--ward-log-height); - background: rgba(0, 0, 0, 0.19); - border-bottom: 1px solid var(--dividerColor); + & .ward-log-header { + background-color: var(--tableHeaderSurfaceColor); + text-transform: uppercase; + font-size: 0.8em; + height: var(--ward-log-height); + } + + & .ward-log-list { + padding: 0; + margin: 0; + } & .timespan { text-align: right; } } +.ward-log-item { + height: var(--ward-log-height); + background: rgb(31, 33, 45); + border-bottom: 1px solid var(--dividerColor); +} + .trans-table-row-enter { height: 0; + opacity: 0; &.trans-table-row-enter-active { height: var(--ward-log-height); - transition: height 300ms ease; + opacity: 1; + transition: height 300ms ease, opacity 300ms ease; } } @@ -211,6 +224,7 @@ border-color: inherit; border-style: solid; border-left-width: 1px; + cursor: pointer; transition: color 150ms ease, border-color 150ms ease; @@ -218,13 +232,6 @@ border-color: var(--slider-ticks-color-active); color: var(--slider-ticks-color-active); } - - &:last-child { - border-left-width: 0px; - border-right-width: 1px; - text-align: right; - right: 0px; - } } } diff --git a/src/components/Match/VisionPage.jsx b/src/components/Match/VisionPage.jsx index 9556d33f24..3a10bb3a9d 100644 --- a/src/components/Match/VisionPage.jsx +++ b/src/components/Match/VisionPage.jsx @@ -19,12 +19,12 @@ window.Perf = Perf; const SliderTicks = (props) => (
    {props.ticks.map((tick) => { - const percent = 100*(tick-props.min)/props.max; + const percent = 100*(tick-props.min)/(props.max-props.min); const cls = [styles['slider-tick']]; if (tick <= props.value) cls.push(styles['active']); - return {tick} + return props.onTickClick(tick)} className={cls.join(' ')} style={{left: percent + '%'}}>{formatSeconds(tick)} })}
    ); @@ -37,11 +37,11 @@ class VisionPage extends React.Component { constructor(props) { super(props); this.state = { - currentTick: 0, - from: 0, - to: this.props.match.wards_log.length, + currentTick: -90, min: -90, max: props.match.duration, + from: 0, + to: this.props.match.wards_log.length, wardsLog: props.match.wards_log.map((e, i) => Object.assign(e, {key: i})) } @@ -51,15 +51,11 @@ class VisionPage extends React.Component { } computeTick() { - const minute = 60; - // we place the 0/end of the match - // then every 10 minutes interval - const { duration } = this.props.match; - let ticks = _.rangeStep(10 * minute, 0, duration); - return [...ticks, duration]; + const interval = 10 * 60; // every 10 minutes interval + return _.rangeStep(interval, 0, this.props.match.duration); } - handleViewportChange(e, value) { + handleViewportChange(value) { const log = this.state.wardsLog; const p = this.state.from; @@ -81,11 +77,13 @@ class VisionPage extends React.Component { min={this.state.min} max={this.state.max} className={styles['slider-ticks']} + onTickClick={(tick) => this.handleViewportChange(tick)} ticks={this.ticks} /> this.handleViewportChange(e, value)} /> + onChange={(e, value) => this.handleViewportChange(value)} /> diff --git a/src/components/Match/WardLog.jsx b/src/components/Match/WardLog.jsx index 9a08eaccb5..c870f48bcf 100644 --- a/src/components/Match/WardLog.jsx +++ b/src/components/Match/WardLog.jsx @@ -7,10 +7,10 @@ import { Row, Col } from 'react-flexbox-grid'; // import heroes from 'dotaconstants/json/heroes.json'; import { heroTd } from './matchColumns'; import { API_HOST } from 'config'; -import { TransitionMotion, spring, presets } from 'react-motion'; import { Fixed, Pure } from 'utility/components'; import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; import styles from './Match.css'; +import tableStyle from '../Table/Table.css'; import strings from 'lang'; import _ from 'lodash/fp'; @@ -40,7 +40,7 @@ const RowItem = ({ style, row, match, index }) => { const duration = row.left ? formatSeconds(row.left.time - row.entered.time) : "-"; return (
  • - +
  • @@ -60,39 +60,6 @@ const RowItem = ({ style, row, match, index }) => { const PureRowItem = Fixed(RowItem); class WardLog extends React.Component { - getDefaultStyles() { - return this.props.wardsLog.map((row, i) => ( - { - data: row, - key: String(row.key || i), - style: {height: 0} - })); - } - - getStyles() { - return this.props.wardsLog.map((row, i) => ( - { - data: row, - key: String(row.key || i), - style: { - height: spring(50, presets.stiff), - } - } - )); - } - - willEnter() { - return { - height: 0, - }; - } - - willLeave() { - return { - height: spring(0), - }; - } - render() { const width = this.props.width; const iconSize = width / 12; @@ -103,14 +70,21 @@ class WardLog extends React.Component { }); const obsIcons = []; const senIcons = []; + const columns = ['type', 'owner', 'entered_at', 'left_at', 'duration', 'killed_by'].map(h => 'ward_log_' + h); return ( - - + + {strings[columns[0]]} + {strings[columns[1]]} + {strings[columns[2]]} + {strings[columns[3]]} + {strings[columns[4]]} + {strings[columns[5]]} + + + transitionLeaveTimeout={300}> {this.props.wardsLog.map((log, index) => )} diff --git a/src/components/Table/Table.css b/src/components/Table/Table.css index 34e6794598..28304baf68 100644 --- a/src/components/Table/Table.css +++ b/src/components/Table/Table.css @@ -27,7 +27,7 @@ } .headerRow th { - background-color: rgba(0, 0, 0, .3); + background-color: var(--tableHeaderSurfaceColor); } .headerCellNoSort { @@ -53,10 +53,10 @@ } tbody tr:nth-child(odd) { - background-color: rgba(255, 255, 255, .019); + background-color: var(--tableRowOddSurfaceColor); } tbody tr:nth-child(even) { - background-color: rgba(0, 0, 0, .019); + background-color: var(--tableRowEvenSurfaceColor); } table { diff --git a/src/components/palette.css b/src/components/palette.css index 1d6f2f0a04..d21b9405b4 100644 --- a/src/components/palette.css +++ b/src/components/palette.css @@ -18,6 +18,10 @@ --primaryTextColor: #F5F5F5; --textColorSecondary: #212020; --secondaryTextColor: #212020; + + --tableHeaderSurfaceColor: rgba(0, 0, 0, .3); + --tableRowOddSurfaceColor: rgba(255, 255, 255, .019); + --tableRowEvenSurfaceColor: rgba(0, 0, 0, .019); /* Colors -- */ /* -- Font */ diff --git a/src/lang/en.json b/src/lang/en.json index 34461299de..2196108296 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -333,6 +333,13 @@ "th_hits": "Hits", "th_damage": "Damage", + "ward_log_type": "Type", + "ward_log_owner": "Owner", + "ward_log_entered_at": "Placed", + "ward_log_left_at": "Left", + "ward_log_duration": "Lifespan", + "ward_log_killed_by": "Killed by", + "time_just_now": "just now", "time_second": "second", "time_minute": "minute", From 4910708531c337b2c3d1802019aa3c425ea80a4f Mon Sep 17 00:00:00 2001 From: "micael.bergeron" Date: Tue, 8 Nov 2016 07:58:17 -0500 Subject: [PATCH 05/21] polish the wardlog visual --- src/components/Match/Match.css | 10 ++++++++-- src/components/Match/VisionPage.jsx | 2 +- src/components/Match/WardLog.jsx | 20 +++++++++++++------- src/components/palette.css | 1 + 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/components/Match/Match.css b/src/components/Match/Match.css index 9090e742a6..81835c5ecf 100644 --- a/src/components/Match/Match.css +++ b/src/components/Match/Match.css @@ -6,6 +6,11 @@ --slider-ticks-color-active: #337AB7; } +:export { + wardLogRowOddSurfaceColor: var(--tableRowOddSurfaceColor); + wardLogRowEvenSurfaceColor: var(--tableRowEvenSurfaceColor); +} + .Header { text-align: center; display: block; @@ -160,12 +165,13 @@ .ward-log { width: 100%; + font-size: .8em; & .ward-log-header { background-color: var(--tableHeaderSurfaceColor); + border-bottom: 1px solid var(--dividerLightColor); text-transform: uppercase; - font-size: 0.8em; - height: var(--ward-log-height); + height: 58px; } & .ward-log-list { diff --git a/src/components/Match/VisionPage.jsx b/src/components/Match/VisionPage.jsx index 3a10bb3a9d..8589ef6552 100644 --- a/src/components/Match/VisionPage.jsx +++ b/src/components/Match/VisionPage.jsx @@ -71,7 +71,7 @@ class VisionPage extends React.Component { const visibleWards = this.visibleData(); return (
    - + ({ enter: styles[name + '-enter'], @@ -29,15 +32,20 @@ const threshold = _.curry((start, limits, values, value) => { if (limits.length != values.length) throw "Limits must be the same as functions."; var limits = limits.slice(0); limits.unshift(start); - return _.findLast(values, (v, i) => _.inRange(value, limits[i], limits[i+1])); + return findLast(values, (v, i) => _.inRange(limits[i], limits[i+1], value)); }); -// const durationSentryColor = threshold(0, [121, 120, 500], ['red', 'yellow', 'green']); -const durationObserverColor = threshold(0, [121, 241, 500], ['red', 'yellow', 'green']); +const durationObserverColor = threshold(0, [121, 241, 500], [styles.red, styles.yelor, styles.green]); const RowItem = ({ style, row, match, index }) => { const wardKiller = (row.left && row.left.player1) ? heroTd(match.players[row.left.player1]) : "-"; - const duration = row.left ? formatSeconds(row.left.time - row.entered.time) : "-"; + const duration = row.left ? row.left.time - row.entered.time : "-"; + const durationColor = row.type == "observer" ? durationObserverColor(duration) : "inherit"; + style = {...style, + backgroundColor: row.key % 2 == 0 + ? styles.wardLogRowEvenSurfaceColor + : styles.wardLogRowOddSurfaceColor + }; return (
  • @@ -49,9 +57,7 @@ const RowItem = ({ style, row, match, index }) => {
  • {formatSeconds(row.entered.time)}{formatSeconds(row.left && row.left.time) || "-"} - - {duration} - + {formatSeconds(duration)}{wardKiller} diff --git a/src/components/palette.css b/src/components/palette.css index d21b9405b4..1a6bf2f1dd 100644 --- a/src/components/palette.css +++ b/src/components/palette.css @@ -43,6 +43,7 @@ /* Font -- */ --dividerColor: color(var(--textColorSecondary) lightness(20%)); + --dividerLightColor: #C3C3C3; --navDrawerWidth: 256px; From 4db566ab11e1da5df9e389377098ddde79b0958b Mon Sep 17 00:00:00 2001 From: "micael.bergeron" Date: Wed, 9 Nov 2016 07:13:44 -0500 Subject: [PATCH 06/21] add the player filter prototypes --- src/components/Match/Match.css | 28 +++++ src/components/Match/VisionMap.jsx | 175 +++++++++++----------------- src/components/Match/VisionPage.jsx | 122 +++++++++++++++---- src/components/Match/WardLog.jsx | 18 +-- src/utility/index.jsx | 15 +++ webpack.config.js | 3 +- 6 files changed, 216 insertions(+), 145 deletions(-) diff --git a/src/components/Match/Match.css b/src/components/Match/Match.css index 81835c5ecf..cf71743c01 100644 --- a/src/components/Match/Match.css +++ b/src/components/Match/Match.css @@ -212,6 +212,24 @@ } } +.ward-pin-enter { + opacity: 0; + + &.ward-pin-enter-active { + opacity: 1; + transition: opacity 150ms ease; + } +} + +.ward-pin-leave { + opacity: 1; + + &.ward-pin-leave-active { + opacity: 0; + transition: opacity 150ms ease; + } +} + .slider-ticks { position: relative; height: 30px; @@ -241,3 +259,13 @@ } } +.ward-log-player-filter { + padding: 0.33em; + font-size: 0.8em; + background-color: white; + + & .filter-row { + margin: .5em 0; + border-bottom: 1px solid var(--dividerColor); + } +} diff --git a/src/components/Match/VisionMap.jsx b/src/components/Match/VisionMap.jsx index fc7b656cc9..02f3856173 100644 --- a/src/components/Match/VisionMap.jsx +++ b/src/components/Match/VisionMap.jsx @@ -1,10 +1,13 @@ import React from 'react'; +import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; import Checkbox from 'material-ui/Checkbox'; import Visibility from 'material-ui/svg-icons/action/visibility'; import VisibilityOff from 'material-ui/svg-icons/action/visibility-off'; import { isRadiant, // transformations, + gameCoordToUV, + extractTransitionClasses, } from 'utility'; import { PlainTable as Table } from 'components/Table'; import { @@ -13,127 +16,83 @@ import { } from 'react-flexbox-grid'; // import heroes from 'dotaconstants/json/heroes.json'; import strings from 'lang'; -import { - heroTdColumn, -} from './matchColumns'; +import styles from './Match.css'; import { Fixed } from 'utility/components'; -const obsWard = (style, stroke, iconSize) => ( - - Observer - - - - - - - -); +const style = (width, iconSize, ward) => { + const gamePos = gameCoordToUV(ward.x, ward.y); + return { + position: 'absolute', + top: ((width / 127) * gamePos.y) - iconSize/2, + left: ((width / 127) * gamePos.x) - iconSize/2, + } +}; + +const WardLogPin = ({ width, iconSize, log }) => { + const stroke = log.entered.player_slot < 5 ? styles.green : styles.red; + const fill = log.type == "observer" ? styles.yelo : styles.blue; + return ( + + + Observer + + + + + + + + + ) +}; + -const senWard = (style, stroke, iconSize) => ( - - Sentry - - - - - - - -); +// sen = #0000ff; const FixedTable = Fixed(Table); // TODO Hero icon on ward circles? class VisionMap extends React.Component { - componentWillMount() { - this.setState({ - enabledIndex: {}, - }); - } - super() { - this.updateMap = this.updateMap.bind(this); - } - updateMap(event, checked, index) { - const newEnabledIndex = Object.assign({}, this.state.enabledIndex, { - [index]: checked, - }); - this.setState(Object.assign({}, this.state, { - enabledIndex: newEnabledIndex, - })); + shouldComponentUpdate(newProps) { + if (newProps.wardsLog.length == this.props.wardsLog.length) return false; + return true; } + render() { - const match = this.props.match; const width = this.props.width; - const enabledIndex = this.state.enabledIndex; const iconSize = width / 12; - const style = ward => ({ - position: 'absolute', - top: ((width / 127) * ward.y) - (iconSize / 2), - left: ((width / 127) * ward.x) - (iconSize / 2), - }); - const obsIcons = []; - const senIcons = []; - Object.keys(enabledIndex).forEach((index) => { - if (enabledIndex[index]) { - if (match && match.players && match.players[index]) { - const obs = (match.players[index].posData && match.players[index].posData.obs) || []; - const sen = (match.players[index].posData && match.players[index].posData.sen) || []; - const stroke = isRadiant(match.players[index].player_slot) ? 'green' : 'red'; - obs.forEach(ward => obsIcons.push(obsWard(style(ward), stroke, iconSize))); - sen.forEach(ward => senIcons.push(senWard(style(ward), stroke, iconSize))); - } - } - }); - return ( - -
    - - {obsIcons} - {senIcons} -
    - - -
    (} - uncheckedIcon={} - checked={this.state.enabledIndex[this.props.match.players.findIndex(player => player.player_slot === row.player_slot)]} - onCheck={(event, checked) => - this.updateMap(event, checked, this.props.match.players.findIndex(player => player.player_slot === row.player_slot))} - label="" - />) }, - heroTdColumn, - { - displayName: strings.th_ward_observer, - field: 'obs_log', - displayFn: (row, col, field) => (field && field.length), - }, - { - displayName: strings.th_ward_sentry, - field: 'sen_log', - displayFn: (row, col, field) => (field && field.length), - }, - ]} - /> - - ); + const obsIcons = this.props.wardsLog.map(w => ); + //const senIcons = this.props.wardsLog.map(w => senWard(width)(stroke, iconSize)(w.entered)) + const transition = extractTransitionClasses(styles); + return ( + + {obsIcons} + + ); } } -// TODO use defaultprops and export directly -export default function ({ - match, - width = 600, -}) { - return ; +VisionMap.defaultProps = { + width: 400, } + +export default VisionMap; diff --git a/src/components/Match/VisionPage.jsx b/src/components/Match/VisionPage.jsx index 8589ef6552..aa208a0441 100644 --- a/src/components/Match/VisionPage.jsx +++ b/src/components/Match/VisionPage.jsx @@ -4,8 +4,13 @@ import { Col, } from 'react-flexbox-grid'; import { formatSeconds } from 'utility'; +import { Fixed } from 'utility/components'; +import Paper from 'material-ui/Paper'; +import Button from 'material-ui/RaisedButton'; import Slider from 'material-ui/Slider'; +import Avatar from 'material-ui/Avatar'; + import Heading from 'components/Heading'; import VisionMap from './VisionMap' ; import WardLog from './WardLog'; @@ -13,22 +18,60 @@ import _ from 'lodash/fp'; import Perf from 'react-addons-perf'; import strings from 'lang'; import styles from './Match.css'; +import { heroTd } from './matchColumns'; window.Perf = Perf; const SliderTicks = (props) => (
    {props.ticks.map((tick) => { - const percent = 100*(tick-props.min)/(props.max-props.min); - const cls = [styles['slider-tick']]; - if (tick <= props.value) - cls.push(styles['active']); + const percent = 100*(tick-props.min)/(props.max-props.min); + const cls = [styles['slider-tick']]; + if (tick <= props.value) + cls.push(styles['active']); - return props.onTickClick(tick)} className={cls.join(' ')} style={{left: percent + '%'}}>{formatSeconds(tick)} - })} + return props.onTickClick(tick)} className={cls.join(' ')} style={{left: percent + '%'}}>{formatSeconds(tick)} + })}
    ); +const generateFilterKey = ({ player_slot }, type) => `${player_slot}-${type}`; + +const PlayersFilter = ({ activatedFilters, players, onFilterClick }) => ( + + {players.map((p,i) => ( + +
    + {heroTd(p)} + + + + + + Radiant + {} + Dire + {} + + + + + this.handleViewportChange(tick)} + ticks={this.ticks} /> + this.handleViewportChange(value)} /> + + diff --git a/src/components/Match/WardLog.jsx b/src/components/Match/WardLog.jsx index ba19ecd280..7fb6cdf36c 100644 --- a/src/components/Match/WardLog.jsx +++ b/src/components/Match/WardLog.jsx @@ -2,7 +2,7 @@ import React from 'react'; import Checkbox from 'material-ui/Checkbox'; import Visibility from 'material-ui/svg-icons/action/visibility'; import VisibilityOff from 'material-ui/svg-icons/action/visibility-off'; -import { isRadiant, formatSeconds } from 'utility'; +import { isRadiant, formatSeconds, extractTransitionClasses } from 'utility'; import { Row, Col } from 'react-flexbox-grid'; // import heroes from 'dotaconstants/json/heroes.json'; import { heroTd } from './matchColumns'; @@ -16,16 +16,6 @@ import strings from 'lang'; import _ from 'lodash/fp'; import { findLast } from 'lodash'; -console.log(styles); - -const extractTransitionClasses = (name, styles) => ({ - enter: styles[name + '-enter'], - enterActive: styles[name + '-enter-active'], - leave: styles[name + '-leave'], - leaveActive: styles[name + '-leave-active'], - appear: styles[name + '-appear'], - appearActive: styles[name + '-appear-active'] -}) // a simple functor that will call the correct function depending on value const threshold = _.curry((start, limits, values, value) => { @@ -44,8 +34,7 @@ const RowItem = ({ style, row, match, index }) => { style = {...style, backgroundColor: row.key % 2 == 0 ? styles.wardLogRowEvenSurfaceColor - : styles.wardLogRowOddSurfaceColor - }; + : styles.wardLogRowOddSurfaceColor }; return (
  • @@ -77,6 +66,7 @@ class WardLog extends React.Component { const obsIcons = []; const senIcons = []; const columns = ['type', 'owner', 'entered_at', 'left_at', 'duration', 'killed_by'].map(h => 'ward_log_' + h); + const transition = extractTransitionClasses(styles); return (
  • @@ -88,7 +78,7 @@ class WardLog extends React.Component { {strings[columns[5]]} {this.props.wardsLog.map((log, index) => diff --git a/src/utility/index.jsx b/src/utility/index.jsx index 5ed2776267..7a01a9d650 100644 --- a/src/utility/index.jsx +++ b/src/utility/index.jsx @@ -321,3 +321,18 @@ export const playerColors = { 131: '#00771F', 132: '#956000', }; + +export const extractTransitionClasses = styles => name => ({ + enter: styles[name + '-enter'], + enterActive: styles[name + '-enter-active'], + leave: styles[name + '-leave'], + leaveActive: styles[name + '-leave-active'], + appear: styles[name + '-appear'], + appearActive: styles[name + '-appear-active'] +}); + +export const gameCoordToUV = (x, y) => ({ + x: Number(x) - 64, + y: 127 - (Number(y) - 64) +}); + diff --git a/webpack.config.js b/webpack.config.js index 0cba1de760..7aebad8f58 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -18,7 +18,6 @@ const config = { path: 'build/', publicPath: 'build/', }, - devTool: '#cheap-eval-module-source-map', resolve: { extensions: ['', '.jsx', '.js', '.css', '.json'], modules: [ @@ -27,7 +26,7 @@ const config = { path.resolve('./node_modules'), ], }, - devtool: 'eval-source-map', + devtool: 'source-map', module: { // We need to load flexboxgrid with css-modules, but others need to be loaded // with regular css loader. From 9e346df311b0d96926383f2461f9f3ca34e0bc78 Mon Sep 17 00:00:00 2001 From: "micael.bergeron" Date: Thu, 10 Nov 2016 20:41:52 -0500 Subject: [PATCH 07/21] fix the responsive bugs in the vision page --- package.json | 2 + src/components/App/App.jsx | 2 + src/components/Match/Match.css | 10 ++- src/components/Match/VisionMap.jsx | 48 +++++++----- src/components/Match/VisionPage.jsx | 113 +++++++++++++++++++++------- src/components/Match/WardLog.jsx | 24 +++--- src/components/palette.css | 11 ++- 7 files changed, 147 insertions(+), 63 deletions(-) diff --git a/package.json b/package.json index fe9ca837a8..a033ce6910 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "react": "15.3.2", "react-dom": "15.3.2", "react-flexbox-grid": "0.10.2", + "react-measure": "^1.3.1", "react-redux": "4.4.5", "react-router": "2.8.1", "react-router-redux": "4.0.6", @@ -47,6 +48,7 @@ "redux-responsive": "^3.1.4", "redux-thunk": "2.1.0", "reselect": "2.5.4", + "seamless-immutable": "^6.3.0", "webpack": "2.1.0-beta.22", "webpack-dashboard": "0.2.0", "webpack-dev-server": "1.16.2", diff --git a/src/components/App/App.jsx b/src/components/App/App.jsx index ab73c4ebbc..d42b730359 100644 --- a/src/components/App/App.jsx +++ b/src/components/App/App.jsx @@ -20,6 +20,8 @@ const muiTheme = { palette: { textColor: palette.textColorPrimary, primary1Color: palette.blue, + canvasColor: palette.primarySurfaceColor, + borderColor: palette.dividerColor, }, button: { height: 38 }, }; diff --git a/src/components/Match/Match.css b/src/components/Match/Match.css index cf71743c01..1656121326 100644 --- a/src/components/Match/Match.css +++ b/src/components/Match/Match.css @@ -260,12 +260,14 @@ } .ward-log-player-filter { - padding: 0.33em; - font-size: 0.8em; + padding: 0 .5em; + font-size: .8em; background-color: white; & .filter-row { - margin: .5em 0; - border-bottom: 1px solid var(--dividerColor); + padding: .5em; + border-bottom: 1px solid var(--dividerLightColor); + + &:last-child { border: none; } } } diff --git a/src/components/Match/VisionMap.jsx b/src/components/Match/VisionMap.jsx index 02f3856173..2994609571 100644 --- a/src/components/Match/VisionMap.jsx +++ b/src/components/Match/VisionMap.jsx @@ -18,7 +18,9 @@ import { import strings from 'lang'; import styles from './Match.css'; import { Fixed } from 'utility/components'; +import Measure from 'react-measure'; +// with the actual game size, the width parameters is optional const style = (width, iconSize, ward) => { const gamePos = gameCoordToUV(ward.x, ward.y); return { @@ -66,27 +68,39 @@ class VisionMap extends React.Component { if (newProps.wardsLog.length == this.props.wardsLog.length) return false; return true; } + + componentDidMount() { + window.addEventListener("resize", () => this.forceUpdate()); + } + + componentWillUnmount() { + window.removeEventListener("resize", () => this.forceUpdate()); + } + + renderWardPins(width) { + const iconSize = width / 12; + return this.props.wardsLog.map(w => ); + } render() { - const width = this.props.width; - const iconSize = width / 12; - const obsIcons = this.props.wardsLog.map(w => ); - //const senIcons = this.props.wardsLog.map(w => senWard(width)(stroke, iconSize)(w.entered)) const transition = extractTransitionClasses(styles); return ( - - {obsIcons} - + + {dimension => ( + + {this.renderWardPins(dimension.width)} + + )} + ); } } diff --git a/src/components/Match/VisionPage.jsx b/src/components/Match/VisionPage.jsx index aa208a0441..5eecadb93d 100644 --- a/src/components/Match/VisionPage.jsx +++ b/src/components/Match/VisionPage.jsx @@ -19,6 +19,11 @@ import Perf from 'react-addons-perf'; import strings from 'lang'; import styles from './Match.css'; import { heroTd } from './matchColumns'; +import getMuiTheme from 'material-ui/styles/getMuiTheme'; +import { + grey800 as filterOff, + blueGrey700 as filterOn +} from 'material-ui/styles/colors'; window.Perf = Perf; @@ -35,30 +40,76 @@ const SliderTicks = (props) => ( ); -const generateFilterKey = ({ player_slot }, type) => `${player_slot}-${type}`; -const PlayersFilter = ({ activatedFilters, players, onFilterClick }) => ( +class PlayerFilter extends React.PureComponent { + constructor(props) { + super(props); + this.getObserverCount = () => this.props.player.obs_log.length; + this.getSentryCount = () => this.props.player.sen_log.length; + } + + generateFilterKey(type) { + return `${this.props.player.player_slot}-${type}`; + } + + getMuiThemeProps() { + return { + fullWidth: true, + backgroundColor: _.sample([filterOn, filterOff]), + disabledBackgroundColor: filterOff, + }; + } + + render() { + const { + player, + onFilterClick, + } = this.props; + const obs_count = this.getObserverCount(); + const sen_count = this.getSentryCount(); + const [opacityOn, opacityOff] = [1, .4]; + return ( + + + + + {heroTd(player)} + + + + + + + - {heroTd(p)} - - + - - Radiant - {} - Dire - {} + + + + Radiant + {} + + + Dire + {} + + diff --git a/src/components/Match/WardLog.jsx b/src/components/Match/WardLog.jsx index 7fb6cdf36c..29843b578f 100644 --- a/src/components/Match/WardLog.jsx +++ b/src/components/Match/WardLog.jsx @@ -38,16 +38,16 @@ const RowItem = ({ style, row, match, index }) => { return (
  • -
  • + - + {heroTd(match.players[row.player])} - {formatSeconds(row.entered.time)} - {formatSeconds(row.left && row.left.time) || "-"} - {formatSeconds(duration)} - {wardKiller} + {formatSeconds(row.entered.time)} + {formatSeconds(row.left && row.left.time) || "-"} + {formatSeconds(duration)} + {wardKiller} ); @@ -70,12 +70,12 @@ class WardLog extends React.Component { return ( - {strings[columns[0]]} - {strings[columns[1]]} - {strings[columns[2]]} - {strings[columns[3]]} - {strings[columns[4]]} - {strings[columns[5]]} + {strings[columns[0]]} + {strings[columns[1]]} + {strings[columns[2]]} + {strings[columns[3]]} + {strings[columns[4]]} + {strings[columns[5]]} Date: Fri, 11 Nov 2016 11:10:41 -0500 Subject: [PATCH 08/21] fixes the errors caused by merging upstream --- index.html | 1 - src/components/Match/Match.css | 12 +++--- src/components/Match/VisionPage.jsx | 60 +++++++++++++++-------------- src/components/Match/WardLog.jsx | 1 - src/components/Match/renderMatch.js | 3 +- webpack.config.js | 29 ++++++++------ 6 files changed, 57 insertions(+), 49 deletions(-) diff --git a/index.html b/index.html index d2660d51a1..03990ba2b1 100644 --- a/index.html +++ b/index.html @@ -43,5 +43,4 @@ - diff --git a/src/components/Match/Match.css b/src/components/Match/Match.css index 6c4589da40..0f78aced9e 100644 --- a/src/components/Match/Match.css +++ b/src/components/Match/Match.css @@ -256,15 +256,17 @@ } .ward-log-player-filter { - padding: 0 .5em; + padding: 0 .5rem; font-size: .8em; - background-color: white; - & .filter-row { - padding: .5em; + & .filter-header { + padding: .5rem; border-bottom: 1px solid var(--dividerLightColor); + } - &:last-child { border: none; } + & .filter-row { + padding: .5em; + margin: 0 0; } } diff --git a/src/components/Match/VisionPage.jsx b/src/components/Match/VisionPage.jsx index 5eecadb93d..9cb60960ff 100644 --- a/src/components/Match/VisionPage.jsx +++ b/src/components/Match/VisionPage.jsx @@ -24,8 +24,7 @@ import { grey800 as filterOff, blueGrey700 as filterOn } from 'material-ui/styles/colors'; - -window.Perf = Perf; +import Immutable from 'seamless-immutable' const SliderTicks = (props) => (
    @@ -74,32 +73,31 @@ class PlayerFilter extends React.PureComponent { between="xs">
    - {heroTd(player)} - - + - - Radiant + + + {strings.general_radiant} + {} - - Dire + + + {strings.general_dire} + {} diff --git a/src/components/Match/WardLog.jsx b/src/components/Match/WardLog.jsx index 29843b578f..09876c2855 100644 --- a/src/components/Match/WardLog.jsx +++ b/src/components/Match/WardLog.jsx @@ -6,7 +6,6 @@ import { isRadiant, formatSeconds, extractTransitionClasses } from 'utility'; import { Row, Col } from 'react-flexbox-grid'; // import heroes from 'dotaconstants/json/heroes.json'; import { heroTd } from './matchColumns'; -import { API_HOST } from 'config'; import { Fixed, Pure } from 'utility/components'; import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; import styles from './Match.css'; diff --git a/src/components/Match/renderMatch.js b/src/components/Match/renderMatch.js index 3ffdaeccf0..f55f38e979 100644 --- a/src/components/Match/renderMatch.js +++ b/src/components/Match/renderMatch.js @@ -8,6 +8,7 @@ import specific from 'dotaconstants/json/specific.json'; import laneRole from 'dotaconstants/json/lane_role.json'; import analysis from './analysis'; +import Immutable from 'seamless-immutable' import { flow, map, flatten, sortBy } from 'lodash/fp'; import _ from 'lodash/fp' @@ -285,7 +286,7 @@ function renderMatch(m) { graphData: generateGraphData(m), teamfights: generateTeamfights(m), players: newPlayers, - wards_log: generateWardLog(m), + wards_log: generateWardLog(Immutable(m)), }; } diff --git a/webpack.config.js b/webpack.config.js index c361364a98..476c488dbf 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -102,17 +102,22 @@ if (!isProd) { config.devtool = 'eval-source-map'; } if (isProd) { - config.plugins.push(new webpack.LoaderOptionsPlugin({ - minimize: true, - debug: false, - }), new webpack.optimize.UglifyJsPlugin({ - compress: { - warnings: false, - }, - output: { - comments: false, - }, - sourceMap: false, - }), new HashBundlePlugin(), new webpack.optimize.DedupePlugin()); + config.plugins.push( + new webpack.LoaderOptionsPlugin({ + minimize: true, + debug: false, + }), + new webpack.optimize.UglifyJsPlugin({ + compress: { + warnings: false, + }, + output: { + comments: false, + }, + sourceMap: false, + }), + new HashBundlePlugin(), + new webpack.optimize.DedupePlugin() + ); } module.exports = config; From 3ddc16b2af6a352681dd7327bd18120324cd5a9b Mon Sep 17 00:00:00 2001 From: "micael.bergeron" Date: Fri, 11 Nov 2016 11:50:12 -0500 Subject: [PATCH 09/21] apply fixes following code review --- src/components/Match/VisionMap.jsx | 3 +- src/components/Match/VisionPage.jsx | 53 +++++++++++++---------------- src/components/Match/WardLog.jsx | 11 +----- src/utility/index.jsx | 10 ++++++ 4 files changed, 35 insertions(+), 42 deletions(-) diff --git a/src/components/Match/VisionMap.jsx b/src/components/Match/VisionMap.jsx index 5cdb82cabc..15163bbae5 100644 --- a/src/components/Match/VisionMap.jsx +++ b/src/components/Match/VisionMap.jsx @@ -58,8 +58,7 @@ const WardLogPin = ({ width, iconSize, log }) => { // TODO Hero icon on ward circles? class VisionMap extends React.Component { shouldComponentUpdate(newProps) { - if (newProps.wardsLog.length == this.props.wardsLog.length) return false; - return true; + return newProps.wardsLog.length !== this.props.wardsLog.length; } componentDidMount() { diff --git a/src/components/Match/VisionPage.jsx b/src/components/Match/VisionPage.jsx index 9cb60960ff..8a87704850 100644 --- a/src/components/Match/VisionPage.jsx +++ b/src/components/Match/VisionPage.jsx @@ -24,7 +24,6 @@ import { grey800 as filterOff, blueGrey700 as filterOn } from 'material-ui/styles/colors'; -import Immutable from 'seamless-immutable' const SliderTicks = (props) => (
    @@ -39,7 +38,6 @@ const SliderTicks = (props) => (
    ); - class PlayerFilter extends React.PureComponent { constructor(props) { super(props); @@ -79,25 +77,25 @@ class PlayerFilter extends React.PureComponent {
    - - + + + {heroTd(player)} + + + + + + + - - {heroTd(player)} - - - - - - - + - + {strings.general_radiant} - {} + { + } {strings.general_dire} - {} + { + } - this.handleViewportChange(tick)} - ticks={this.ticks} /> - this.handleViewportChange(value)} /> + this.handleViewportChange(tick)} + ticks={this.ticks} + /> + this.handleViewportChange(value)} + /> - + ); } diff --git a/src/components/Match/WardLog.jsx b/src/components/Match/WardLog.jsx index a334952565..532ea8d0cc 100644 --- a/src/components/Match/WardLog.jsx +++ b/src/components/Match/WardLog.jsx @@ -1,84 +1,74 @@ import React from 'react'; -import Checkbox from 'material-ui/Checkbox'; -import Visibility from 'material-ui/svg-icons/action/visibility'; -import VisibilityOff from 'material-ui/svg-icons/action/visibility-off'; -import { isRadiant, formatSeconds, extractTransitionClasses } from 'utility'; +import { threshold, formatSeconds, extractTransitionClasses } from 'utility'; import { Row, Col } from 'react-flexbox-grid'; // import heroes from 'dotaconstants/json/heroes.json'; -import { heroTd } from './matchColumns'; -import { threshold } from 'utility'; -import { Fixed, Pure } from 'utility/components'; +import { Fixed } from 'utility/components'; import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; +import strings from 'lang'; + +import { heroTd } from './matchColumns'; import styles from './Match.css'; -import tableStyle from '../Table/Table.css'; -import strings from 'lang'; -import _ from 'lodash/fp'; const durationObserverColor = threshold(0, [121, 241, 500], [styles.red, styles.yelor, styles.green]); -const RowItem = ({ style, row, match, index }) => { - const wardKiller = (row.left && row.left.player1) ? heroTd(match.players[row.left.player1]) : "-"; - const duration = row.left ? row.left.time - row.entered.time : "-"; - const durationColor = row.type == "observer" ? durationObserverColor(duration) : "inherit"; - style = {...style, - backgroundColor: row.key % 2 == 0 - ? styles.wardLogRowEvenSurfaceColor - : styles.wardLogRowOddSurfaceColor }; +const RowItem = ({ style, row, match }) => { + const wardKiller = (row.left && row.left.player1) ? heroTd(match.players[row.left.player1]) : '-'; + const duration = row.left ? row.left.time - row.entered.time : '-'; + const durationColor = row.type === 'observer' ? durationObserverColor(duration) : 'inherit'; + const rowStyle = { ...style, + backgroundColor: row.key % 2 === 0 + ? styles.wardLogRowEvenSurfaceColor + : styles.wardLogRowOddSurfaceColor, + }; return (
  • - -
  • + + - + {heroTd(match.players[row.player])} - {formatSeconds(row.entered.time)} - {formatSeconds(row.left && row.left.time) || "-"} - {formatSeconds(duration)} - {wardKiller} + {formatSeconds(row.entered.time)} + {formatSeconds(row.left && row.left.time) || '-'} + {formatSeconds(duration)} + {wardKiller} ); -} +}; const PureRowItem = Fixed(RowItem); -class WardLog extends React.Component { - render() { - const width = this.props.width; - const iconSize = width / 12; - const style = ward => ({ - position: 'absolute', - top: ((width / 127) * ward.y) - (iconSize / 2), - left: ((width / 127) * ward.x) - (iconSize / 2), - }); - const obsIcons = []; - const senIcons = []; - const columns = ['type', 'owner', 'entered_at', 'left_at', 'duration', 'killed_by'].map(h => 'ward_log_' + h); - const transition = extractTransitionClasses(styles); - return ( - - - {strings[columns[0]]} - {strings[columns[1]]} - {strings[columns[2]]} - {strings[columns[3]]} - {strings[columns[4]]} - {strings[columns[5]]} - - - {this.props.wardsLog.map((log, index) => - - )} - - - ); - } -} +const WardLog = (props) => { + const columns = ['type', 'owner', 'entered_at', 'left_at', 'duration', 'killed_by'].map(h => `ward_log_${h}`); + const transition = extractTransitionClasses(styles); + return ( + + + {strings[columns[0]]} + {strings[columns[1]]} + {strings[columns[2]]} + {strings[columns[3]]} + {strings[columns[4]]} + {strings[columns[5]]} + + + {props.wardsLog.map((log, index) => + , + )} + + + ); +}; + // TODO use defaultprops and export directly export default function ({ diff --git a/src/components/Match/matchColumns.jsx b/src/components/Match/matchColumns.jsx index 340b2e02dd..9fafa20670 100644 --- a/src/components/Match/matchColumns.jsx +++ b/src/components/Match/matchColumns.jsx @@ -678,4 +678,4 @@ export const teamfightColumns = [ return
    ; }) : ''), }, -]; \ No newline at end of file +]; diff --git a/src/components/Match/matchPages.jsx b/src/components/Match/matchPages.jsx index bd8123162a..85b6a7ab6e 100644 --- a/src/components/Match/matchPages.jsx +++ b/src/components/Match/matchPages.jsx @@ -208,9 +208,9 @@ const matchPages = [{ key: 'vision', parsed: true, content: match => (
    - + -
    ), +
    ), }, { name: strings.tab_actions, key: 'actions', diff --git a/src/components/Match/renderMatch.js b/src/components/Match/renderMatch.js index b486fd62a1..b1b08fb1e9 100644 --- a/src/components/Match/renderMatch.js +++ b/src/components/Match/renderMatch.js @@ -7,11 +7,11 @@ import { import heroes from 'dotaconstants/json/heroes.json'; import specific from 'dotaconstants/json/specific.json'; import laneRole from 'dotaconstants/json/lane_role.json'; +import Immutable from 'seamless-immutable'; +import _ from 'lodash/fp'; + import analysis from './analysis'; -import Immutable from 'seamless-immutable' -import { flow, map, flatten, sortBy } from 'lodash/fp'; -import _ from 'lodash/fp' const expanded = {}; Object.keys(specific).forEach((key) => { @@ -21,7 +21,7 @@ Object.keys(specific).forEach((key) => { }); const getMaxKeyOfObject = field => - (field ? Object.keys(field).sort((a, b) => Number(b) - Number(a))[0] : ''); + (field ? Object.keys(field).sort((a, b) => Number(b) - Number(a))[0] : ''); /** * Generates data for c3 charts in a match @@ -102,29 +102,29 @@ function generateWardLog(match) { const sameWard = _.curry((w1, w2) => w1.ehandle === w2.ehandle); // let's zip the *_log and the *_left log in a 2-tuples - const extractWardLog = (type, entered_log, left_log) => { - return entered_log.map((e) => { - let wards = [e, left_log.find(sameWard(e))]; - return { - player: i, - key: wards[0].ehandle, - type: type, - entered: wards[0], - left: wards[1] - }; - }); - }; + const extractWardLog = (type, enteredLog, leftLog) => + enteredLog.map((e) => { + const wards = [e, leftLog.find(sameWard(e))]; + return { + player: i, + key: wards[0].ehandle, + type, + entered: wards[0], + left: wards[1], + }; + }) + ; - var observers = extractWardLog("observer", player.obs_log, player.obs_left_log); - var sentries = extractWardLog("sentry", player.sen_log, player.sen_left_log); + const observers = extractWardLog('observer', player.obs_log, player.obs_left_log); + const sentries = extractWardLog('sentry', player.sen_log, player.sen_left_log); return _.concat(observers, sentries); }; - + const wardLog = _.flow( _.map.convert({ cap: false })(computeWardData), _.flatten, - _.sortBy(xs => xs['entered']['time']), + _.sortBy(xs => xs.entered.time), ); return wardLog(match.players); // cap: false to keep the index } diff --git a/src/utility/components.jsx b/src/utility/components.jsx index 0014f2cc4f..ca4fc4a4a5 100644 --- a/src/utility/components.jsx +++ b/src/utility/components.jsx @@ -1,16 +1,12 @@ +/*eslint-disable*/ + import React from 'react'; -export const Fixed = (Component) => class extends React.Component { - shouldComponentUpdate(newProps, newState) { - return this.props.style != newProps.style; - } - render() { - return +export const Fixed = Component => class extends React.Component { + shouldComponentUpdate(newProps) { + return this.props.style !== newProps.style; } -}; - -export const Pure = (Component) => class extends React.PureComponent { render() { - return + return ; } }; diff --git a/src/utility/index.jsx b/src/utility/index.jsx index c2b350cffb..ab7ed9e2de 100644 --- a/src/utility/index.jsx +++ b/src/utility/index.jsx @@ -338,17 +338,17 @@ export const playerColors = { }; export const extractTransitionClasses = styles => name => ({ - enter: styles[name + '-enter'], - enterActive: styles[name + '-enter-active'], - leave: styles[name + '-leave'], - leaveActive: styles[name + '-leave-active'], - appear: styles[name + '-appear'], - appearActive: styles[name + '-appear-active'] + enter: styles[`${name}-enter`], + enterActive: styles[`${name}-enter-active`], + leave: styles[`${name}-leave`], + leaveActive: styles[`${name}-leave-active`], + appear: styles[`${name}-appear`], + appearActive: styles[`${name}-appear-active`], }); export const gameCoordToUV = (x, y) => ({ x: Number(x) - 64, - y: 127 - (Number(y) - 64) + y: 127 - (Number(y) - 64), }); // TODO: refactor this to use gameCoordToUV @@ -375,9 +375,10 @@ export function unpackPositionData(input) { } export const threshold = _.curry((start, limits, values, value) => { - if (limits.length !== values.length) throw "Limits must be the same as functions."; - var limits = limits.slice(0); - limits.unshift(start); - return findLast(values, (v, i) => _.inRange(limits[i], limits[i+1], value)); + if (limits.length !== values.length) throw new Error('Limits must be the same as functions.'); + + const limitsWithStart = limits.slice(0); + limitsWithStart.unshift(start); + return findLast(values, (v, i) => _.inRange(limitsWithStart[i], limitsWithStart[i + 1], value)); }); diff --git a/webpack.config.js b/webpack.config.js index 476c488dbf..c361364a98 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -102,22 +102,17 @@ if (!isProd) { config.devtool = 'eval-source-map'; } if (isProd) { - config.plugins.push( - new webpack.LoaderOptionsPlugin({ - minimize: true, - debug: false, - }), - new webpack.optimize.UglifyJsPlugin({ - compress: { - warnings: false, - }, - output: { - comments: false, - }, - sourceMap: false, - }), - new HashBundlePlugin(), - new webpack.optimize.DedupePlugin() - ); + config.plugins.push(new webpack.LoaderOptionsPlugin({ + minimize: true, + debug: false, + }), new webpack.optimize.UglifyJsPlugin({ + compress: { + warnings: false, + }, + output: { + comments: false, + }, + sourceMap: false, + }), new HashBundlePlugin(), new webpack.optimize.DedupePlugin()); } module.exports = config; From b4db5a363b55b7ec7cb7096cf39c1a3f56730df6 Mon Sep 17 00:00:00 2001 From: "micael.bergeron" Date: Fri, 11 Nov 2016 22:32:19 -0500 Subject: [PATCH 11/21] remove a lint bypass --- src/utility/components.jsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/utility/components.jsx b/src/utility/components.jsx index ca4fc4a4a5..c279c4c221 100644 --- a/src/utility/components.jsx +++ b/src/utility/components.jsx @@ -1,12 +1,12 @@ -/*eslint-disable*/ - import React from 'react'; -export const Fixed = Component => class extends React.Component { - shouldComponentUpdate(newProps) { - return this.props.style !== newProps.style; - } - render() { - return ; - } +export default { + Fixed: Component => class extends React.Component { + shouldComponentUpdate(newProps) { + return this.props.style !== newProps.style; + } + render() { + return ; + } + }, }; From 98a2831ee5e38d0f9880d37a2992d7aca5b48145 Mon Sep 17 00:00:00 2001 From: "micael.bergeron" Date: Fri, 11 Nov 2016 22:32:19 -0500 Subject: [PATCH 12/21] remove a lint bypass --- src/utility/components.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/utility/components.jsx b/src/utility/components.jsx index c279c4c221..8a4cbec8c1 100644 --- a/src/utility/components.jsx +++ b/src/utility/components.jsx @@ -10,3 +10,5 @@ export default { } }, }; + +export default Fixed; From 890e51ff013c0b605be12998f536928bb263b845 Mon Sep 17 00:00:00 2001 From: "micael.bergeron" Date: Fri, 11 Nov 2016 22:47:19 -0500 Subject: [PATCH 13/21] refactored Fixed to its own file --- src/components/Match/WardLog.jsx | 2 +- src/utility/components.jsx | 14 -------------- src/utility/components/Fixed.jsx | 10 ++++++++++ 3 files changed, 11 insertions(+), 15 deletions(-) delete mode 100644 src/utility/components.jsx create mode 100644 src/utility/components/Fixed.jsx diff --git a/src/components/Match/WardLog.jsx b/src/components/Match/WardLog.jsx index 532ea8d0cc..c24e111742 100644 --- a/src/components/Match/WardLog.jsx +++ b/src/components/Match/WardLog.jsx @@ -2,7 +2,7 @@ import React from 'react'; import { threshold, formatSeconds, extractTransitionClasses } from 'utility'; import { Row, Col } from 'react-flexbox-grid'; // import heroes from 'dotaconstants/json/heroes.json'; -import { Fixed } from 'utility/components'; +import Fixed from 'utility/components/Fixed'; import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; import strings from 'lang'; diff --git a/src/utility/components.jsx b/src/utility/components.jsx deleted file mode 100644 index 8a4cbec8c1..0000000000 --- a/src/utility/components.jsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; - -export default { - Fixed: Component => class extends React.Component { - shouldComponentUpdate(newProps) { - return this.props.style !== newProps.style; - } - render() { - return ; - } - }, -}; - -export default Fixed; diff --git a/src/utility/components/Fixed.jsx b/src/utility/components/Fixed.jsx new file mode 100644 index 0000000000..e863c0ab12 --- /dev/null +++ b/src/utility/components/Fixed.jsx @@ -0,0 +1,10 @@ +import React from 'react'; + +export default Component => class extends React.Component { + shouldComponentUpdate(newProps) { + return this.props.style !== newProps.style; + } + render() { + return ; + } +}; From fdf69000f02bf35afc38679e7698dfc58f9c19f8 Mon Sep 17 00:00:00 2001 From: "micael.bergeron" Date: Sat, 12 Nov 2016 08:01:22 -0500 Subject: [PATCH 14/21] fix issues related to the CR --- package.json | 1 - src/components/Match/Match.css | 16 +++++++--------- src/components/palette.css | 4 ++++ 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 6c5c756f8b..f29dd3b8bb 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,6 @@ "isomorphic-fetch": "2.2.1", "lodash": "^4.16.4", "material-ui": "0.16.1", - "moment-duration-format": "^1.3.0", "node-uuid": "1.4.7", "postcss-browser-reporter": "0.5.0", "postcss-color-function": "2.0.1", diff --git a/src/components/Match/Match.css b/src/components/Match/Match.css index 0f78aced9e..714221040b 100644 --- a/src/components/Match/Match.css +++ b/src/components/Match/Match.css @@ -2,8 +2,6 @@ :root { --ward-log-height: 50px; - --slider-ticks-color: #757575; - --slider-ticks-color-active: #337AB7; } :export { @@ -161,7 +159,7 @@ .ward-log { width: 100%; - font-size: .8em; + font-size: var(--fontSizeSmall); & .ward-log-header { background-color: var(--tableHeaderSurfaceColor); @@ -231,9 +229,9 @@ height: 30px; margin-top: 33px; margin-bottom: -33px; - font-size: 0.66em; - border-color: var(--slider-ticks-color); - color: var(--slider-ticks-color); + font-size: var(--fontSizeTiny); + border-color: var(--sliderTicksColor); + color: var(--sliderTicksColor); & .slider-tick { position: absolute; @@ -249,15 +247,15 @@ transition: color 150ms ease, border-color 150ms ease; &.active { - border-color: var(--slider-ticks-color-active); - color: var(--slider-ticks-color-active); + border-color: var(--sliderTicksColorActive); + color: var(--sliderTicksColorActive); } } } .ward-log-player-filter { padding: 0 .5rem; - font-size: .8em; + font-size: var(--fontSizeSmall); & .filter-header { padding: .5rem; diff --git a/src/components/palette.css b/src/components/palette.css index 595bda8497..f294f0a188 100644 --- a/src/components/palette.css +++ b/src/components/palette.css @@ -26,6 +26,9 @@ --tableHeaderSurfaceColor: rgba(0, 0, 0, .3); --tableRowOddSurfaceColor: rgba(255, 255, 255, .019); --tableRowEvenSurfaceColor: rgba(0, 0, 0, .019); + + --sliderTicksColor: #757575; + --sliderTicksColorActive: #337AB7; /* Colors -- */ /* -- Font */ @@ -44,6 +47,7 @@ --fontSizeCommon: 16px; --fontSizeMedium: 14px; --fontSizeSmall: 12px; + --fontSizeTiny: 10px; /* Font -- */ --dividerColor: color(var(--textColorSecondary) lightness(20%)); From 12d6828fb0b93216fb9d2d35882af3c576a5d3f9 Mon Sep 17 00:00:00 2001 From: "micael.bergeron" Date: Sat, 12 Nov 2016 08:26:04 -0500 Subject: [PATCH 15/21] eslint: allow same precedence operators to be mix --- .eslintrc.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.eslintrc.json b/.eslintrc.json index 9217f5c709..021448670a 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -23,6 +23,9 @@ "import/named": 2, "import/no-dynamic-require": 0, "jsx-a11y/no-static-element-interactions": [0], - "react/forbid-prop-types": [2, { "forbid": ["any"] }] + "react/forbid-prop-types": [2, { "forbid": ["any"] }], + "no-mixed-operators": ["error", { + "allowSamePrecedence": true + }] } } From 6eb4a48688fa1c2379a753bc67d7b12d4d82b2cf Mon Sep 17 00:00:00 2001 From: "micael.bergeron" Date: Sat, 12 Nov 2016 10:09:12 -0500 Subject: [PATCH 16/21] fixed a problem with a mutation of immutable data --- src/components/Match/VisionPage.jsx | 2 +- src/components/Match/renderMatch.js | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/Match/VisionPage.jsx b/src/components/Match/VisionPage.jsx index d29e1f831e..39313892a3 100644 --- a/src/components/Match/VisionPage.jsx +++ b/src/components/Match/VisionPage.jsx @@ -59,7 +59,7 @@ class VisionPage extends React.Component { max: props.match.duration, from: 0, to: this.props.match.wards_log.length, - wardsLog: props.match.wards_log.map((e, i) => Object.assign(e, { key: i })), + wardsLog: props.match.wards_log, filters: {}, }; diff --git a/src/components/Match/renderMatch.js b/src/components/Match/renderMatch.js index b1b08fb1e9..aaaa7b8b5b 100644 --- a/src/components/Match/renderMatch.js +++ b/src/components/Match/renderMatch.js @@ -120,13 +120,14 @@ function generateWardLog(match) { return _.concat(observers, sentries); }; - + const imap = _.map.convert({ cap: false}); // cap: false to keep the index const wardLog = _.flow( - _.map.convert({ cap: false })(computeWardData), + imap(computeWardData), _.flatten, _.sortBy(xs => xs.entered.time), + imap((x, i) => ({...x, key: i})), ); - return wardLog(match.players); // cap: false to keep the index + return wardLog(match.players); } function renderMatch(m) { From 60f06bf9994e3d7f73c1dfcf856d0a9beb2daa87 Mon Sep 17 00:00:00 2001 From: "micael.bergeron" Date: Sat, 12 Nov 2016 10:10:20 -0500 Subject: [PATCH 17/21] Revert "eslint: allow same precedence operators to be mix" This reverts commit 12d6828fb0b93216fb9d2d35882af3c576a5d3f9. --- .eslintrc.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 021448670a..9217f5c709 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -23,9 +23,6 @@ "import/named": 2, "import/no-dynamic-require": 0, "jsx-a11y/no-static-element-interactions": [0], - "react/forbid-prop-types": [2, { "forbid": ["any"] }], - "no-mixed-operators": ["error", { - "allowSamePrecedence": true - }] + "react/forbid-prop-types": [2, { "forbid": ["any"] }] } } From e827ba7656ec41d2fb16ccce1201992270e30036 Mon Sep 17 00:00:00 2001 From: "micael.bergeron" Date: Sat, 12 Nov 2016 10:29:03 -0500 Subject: [PATCH 18/21] fix linting issues --- src/components/Match/renderMatch.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/Match/renderMatch.js b/src/components/Match/renderMatch.js index aaaa7b8b5b..d97fab2782 100644 --- a/src/components/Match/renderMatch.js +++ b/src/components/Match/renderMatch.js @@ -120,14 +120,14 @@ function generateWardLog(match) { return _.concat(observers, sentries); }; - const imap = _.map.convert({ cap: false}); // cap: false to keep the index + const imap = _.map.convert({ cap: false }); // cap: false to keep the index const wardLog = _.flow( imap(computeWardData), _.flatten, _.sortBy(xs => xs.entered.time), - imap((x, i) => ({...x, key: i})), + imap((x, i) => ({ ...x, key: i })), ); - return wardLog(match.players); + return wardLog(match.players); } function renderMatch(m) { From cb0106a4d6e1a90e777b928c44b89e2877324f71 Mon Sep 17 00:00:00 2001 From: "micael.bergeron" Date: Sun, 13 Nov 2016 08:38:16 -0500 Subject: [PATCH 19/21] fix some issues after review --- src/components/Match/Match.css | 18 +++++++++--------- src/components/Match/Vision/PlayerFilter.jsx | 2 +- src/components/Match/VisionPage.jsx | 20 ++++++++++---------- src/components/Match/WardLog.jsx | 15 ++++++++------- 4 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/components/Match/Match.css b/src/components/Match/Match.css index 714221040b..7210c1ba3e 100644 --- a/src/components/Match/Match.css +++ b/src/components/Match/Match.css @@ -157,18 +157,18 @@ vertical-align: middle; } -.ward-log { +.wardLog { width: 100%; font-size: var(--fontSizeSmall); - & .ward-log-header { + & .wardLogHeader { background-color: var(--tableHeaderSurfaceColor); border-bottom: 1px solid var(--dividerLightColor); text-transform: uppercase; height: 58px; } - & .ward-log-list { + & .wardLogList { padding: 0; margin: 0; } @@ -178,7 +178,7 @@ } } -.ward-log-item { +.wardLogItem { height: var(--ward-log-height); background: rgb(31, 33, 45); border-bottom: 1px solid var(--dividerColor); @@ -224,7 +224,7 @@ } } -.slider-ticks { +.sliderTicks { position: relative; height: 30px; margin-top: 33px; @@ -233,7 +233,7 @@ border-color: var(--sliderTicksColor); color: var(--sliderTicksColor); - & .slider-tick { + & .sliderTick { position: absolute; display: inline-block; height: 100%; @@ -253,16 +253,16 @@ } } -.ward-log-player-filter { +.wardLogPlayerFilter { padding: 0 .5rem; font-size: var(--fontSizeSmall); - & .filter-header { + & .filterHeader { padding: .5rem; border-bottom: 1px solid var(--dividerLightColor); } - & .filter-row { + & .filterRow { padding: .5em; margin: 0 0; } diff --git a/src/components/Match/Vision/PlayerFilter.jsx b/src/components/Match/Vision/PlayerFilter.jsx index 70315d7009..16a4521bf2 100644 --- a/src/components/Match/Vision/PlayerFilter.jsx +++ b/src/components/Match/Vision/PlayerFilter.jsx @@ -39,7 +39,7 @@ export default class PlayerFilter extends React.PureComponent { const [opacityOn, opacityOff] = [1, 0.4]; return ( diff --git a/src/components/Match/VisionPage.jsx b/src/components/Match/VisionPage.jsx index 39313892a3..5d0b47ea77 100644 --- a/src/components/Match/VisionPage.jsx +++ b/src/components/Match/VisionPage.jsx @@ -19,7 +19,7 @@ const SliderTicks = props => ( {props.ticks.map((tick) => { const [t, min, max] = [tick, props.min, props.max]; const percent = 100 * ((t - min) / (max - min)); - const cls = [styles['slider-tick']]; + const cls = [styles.sliderTick]; if (tick <= props.value) { cls.push(styles.active); } return ( @@ -38,8 +38,8 @@ const PlayersFilter = ({ activeFilters, players, onFilterClick }) => ( ); -const PipelineFilter = (filters, data, iter = Array.prototype.filter) => { - const filtered = filters.map(f => iter.call(data, f)) +const PipelineFilter = (filters, data) => { + const filtered = filters.map(f => data.filter(f)) .reduce((o, v) => o.concat(v), []); return _.differenceWith((x, y) => x === y, data, filtered); }; @@ -106,25 +106,25 @@ class VisionPage extends React.Component { - - + + {strings.general_radiant} { } - - + + {strings.general_dire} { } @@ -137,7 +137,7 @@ class VisionPage extends React.Component { value={this.state.currentTick} min={this.state.min} max={this.state.max} - className={styles['slider-ticks']} + className={styles.sliderTicks} onTickClick={tick => this.handleViewportChange(tick)} ticks={this.ticks} /> diff --git a/src/components/Match/WardLog.jsx b/src/components/Match/WardLog.jsx index c24e111742..5df6051fc0 100644 --- a/src/components/Match/WardLog.jsx +++ b/src/components/Match/WardLog.jsx @@ -23,29 +23,30 @@ const RowItem = ({ style, row, match }) => { }; return (
  • - +
  • - + {heroTd(match.players[row.player])} {formatSeconds(row.entered.time)}{formatSeconds(row.left && row.left.time) || '-'}{formatSeconds(duration)} - {wardKiller} + {wardKiller} ); }; const PureRowItem = Fixed(RowItem); +// i18n column names +const columns = ['type', 'owner', 'entered_at', 'left_at', 'duration', 'killed_by'].map(h => `ward_log_${h}`); const WardLog = (props) => { - const columns = ['type', 'owner', 'entered_at', 'left_at', 'duration', 'killed_by'].map(h => `ward_log_${h}`); const transition = extractTransitionClasses(styles); return ( - - + + {strings[columns[0]]}{strings[columns[1]]}{strings[columns[2]]} @@ -54,7 +55,7 @@ const WardLog = (props) => { {strings[columns[5]]} Date: Sun, 13 Nov 2016 21:50:05 -0500 Subject: [PATCH 20/21] fixed a crash when opening an unparsed match --- src/components/Match/renderMatch.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/Match/renderMatch.js b/src/components/Match/renderMatch.js index d97fab2782..f301afdda8 100644 --- a/src/components/Match/renderMatch.js +++ b/src/components/Match/renderMatch.js @@ -98,6 +98,9 @@ function generateTeamfights(match) { // create a detailed history of each wards function generateWardLog(match) { + // this should be handled by the Match page component + if (!(match.players && match.players[0].obs_log)) return []; + const computeWardData = (player, i) => { const sameWard = _.curry((w1, w2) => w1.ehandle === w2.ehandle); From 8b2de47921b4013819cf7830d61d642dcdbb36dd Mon Sep 17 00:00:00 2001 From: "micael.bergeron" Date: Sun, 13 Nov 2016 22:03:28 -0500 Subject: [PATCH 21/21] coerce the player instead of using a guard --- src/components/Match/renderMatch.js | 38 +++++++++++++++++------------ 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/src/components/Match/renderMatch.js b/src/components/Match/renderMatch.js index f301afdda8..b1eebaab0d 100644 --- a/src/components/Match/renderMatch.js +++ b/src/components/Match/renderMatch.js @@ -98,28 +98,34 @@ function generateTeamfights(match) { // create a detailed history of each wards function generateWardLog(match) { - // this should be handled by the Match page component - if (!(match.players && match.players[0].obs_log)) return []; - const computeWardData = (player, i) => { const sameWard = _.curry((w1, w2) => w1.ehandle === w2.ehandle); + // let's coerce some value to be sure the structure is what we expect. + const safePlayer = { + ...player, + obs_log: player.obs_log || [], + sen_log: player.sen_log || [], + obs_left_log: player.obs_left_log || [], + sen_left_log: player.sen_left_log || [], + }; + // let's zip the *_log and the *_left log in a 2-tuples const extractWardLog = (type, enteredLog, leftLog) => - enteredLog.map((e) => { - const wards = [e, leftLog.find(sameWard(e))]; - return { - player: i, - key: wards[0].ehandle, - type, - entered: wards[0], - left: wards[1], - }; - }) + enteredLog.map((e) => { + const wards = [e, leftLog.find(sameWard(e))]; + return { + player: i, + key: wards[0].ehandle, + type, + entered: wards[0], + left: wards[1], + }; + }) ; - const observers = extractWardLog('observer', player.obs_log, player.obs_left_log); - const sentries = extractWardLog('sentry', player.sen_log, player.sen_left_log); + const observers = extractWardLog('observer', safePlayer.obs_log, safePlayer.obs_left_log); + const sentries = extractWardLog('sentry', safePlayer.sen_log, safePlayer.sen_left_log); return _.concat(observers, sentries); }; @@ -130,7 +136,7 @@ function generateWardLog(match) { _.sortBy(xs => xs.entered.time), imap((x, i) => ({ ...x, key: i })), ); - return wardLog(match.players); + return wardLog(match.players || []); } function renderMatch(m) {