playball 2.2.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/.eslintrc.json +2 -2
  2. package/README.md +18 -4
  3. package/demo.cast +95 -0
  4. package/demo.gif +0 -0
  5. package/dist/components/AllPlays.js +94 -93
  6. package/dist/components/App.js +45 -117
  7. package/dist/components/AtBat.js +44 -43
  8. package/dist/components/Bases.js +17 -26
  9. package/dist/components/Count.js +15 -26
  10. package/dist/components/FinishedGame.js +51 -66
  11. package/dist/components/Game.js +51 -114
  12. package/dist/components/GameList.js +161 -135
  13. package/dist/components/Grid.js +115 -0
  14. package/dist/components/HelpBar.js +13 -6
  15. package/dist/components/InningDisplay.js +26 -0
  16. package/dist/components/LineScore.js +32 -90
  17. package/dist/components/LiveGame.js +24 -19
  18. package/dist/components/LoadingSpinner.js +34 -125
  19. package/dist/components/Matchup.js +38 -59
  20. package/dist/components/PreviewGame.js +34 -55
  21. package/dist/components/Standings.js +91 -0
  22. package/dist/features/games.js +135 -0
  23. package/dist/features/keys.js +63 -0
  24. package/dist/features/schedule.js +58 -0
  25. package/dist/features/standings.js +57 -0
  26. package/dist/hooks/useKey.js +23 -0
  27. package/dist/logger.js +8 -10
  28. package/dist/main.js +13 -23
  29. package/dist/screen.js +22 -0
  30. package/dist/store/index.js +21 -7
  31. package/dist/style/index.js +3 -3
  32. package/package.json +44 -26
  33. package/src/components/AllPlays.jsx +95 -63
  34. package/src/components/App.jsx +38 -66
  35. package/src/components/AtBat.jsx +34 -36
  36. package/src/components/Bases.jsx +8 -13
  37. package/src/components/Count.jsx +10 -15
  38. package/src/components/FinishedGame.jsx +29 -40
  39. package/src/components/Game.jsx +48 -65
  40. package/src/components/GameList.jsx +125 -81
  41. package/src/components/Grid.jsx +91 -0
  42. package/src/components/HelpBar.jsx +14 -9
  43. package/src/components/InningDisplay.jsx +19 -0
  44. package/src/components/LineScore.jsx +27 -33
  45. package/src/components/LiveGame.jsx +7 -3
  46. package/src/components/LoadingSpinner.jsx +26 -60
  47. package/src/components/Matchup.jsx +26 -39
  48. package/src/components/PreviewGame.jsx +22 -37
  49. package/src/components/Standings.jsx +78 -0
  50. package/src/features/games.js +165 -0
  51. package/src/features/keys.js +38 -0
  52. package/src/features/schedule.js +59 -0
  53. package/src/features/standings.js +60 -0
  54. package/src/hooks/useKey.js +13 -0
  55. package/src/main.js +7 -14
  56. package/src/screen.js +14 -0
  57. package/src/store/index.js +16 -7
  58. package/src/style/index.js +1 -1
  59. package/dist/actions/game.js +0 -36
  60. package/dist/actions/schedule.js +0 -33
  61. package/dist/actions/types.js +0 -16
  62. package/dist/reducers/game.js +0 -70
  63. package/dist/reducers/index.js +0 -21
  64. package/dist/reducers/schedule.js +0 -35
  65. package/dist/selectors/game.js +0 -82
  66. package/dist/selectors/schedule.js +0 -25
  67. package/src/actions/game.js +0 -25
  68. package/src/actions/schedule.js +0 -21
  69. package/src/actions/types.js +0 -5
  70. package/src/reducers/game.js +0 -56
  71. package/src/reducers/index.js +0 -9
  72. package/src/reducers/schedule.js +0 -28
  73. package/src/selectors/game.js +0 -93
  74. package/src/selectors/schedule.js +0 -18
@@ -3,16 +3,30 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports["default"] = void 0;
6
+ exports.default = void 0;
7
7
 
8
- var _redux = require("redux");
8
+ var _toolkit = require("@reduxjs/toolkit");
9
9
 
10
- var _reduxThunk = _interopRequireDefault(require("redux-thunk"));
10
+ var _schedule = _interopRequireDefault(require("../features/schedule"));
11
11
 
12
- var _reducers = _interopRequireDefault(require("../reducers"));
12
+ var _games = _interopRequireDefault(require("../features/games"));
13
13
 
14
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
14
+ var _keys = _interopRequireDefault(require("../features/keys"));
15
15
 
16
- var _default = (0, _redux.createStore)(_reducers["default"], (0, _redux.applyMiddleware)(_reduxThunk["default"]));
16
+ var _standings = _interopRequireDefault(require("../features/standings"));
17
17
 
18
- exports["default"] = _default;
18
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
19
+
20
+ var _default = (0, _toolkit.configureStore)({
21
+ reducer: {
22
+ schedule: _schedule.default,
23
+ games: _games.default,
24
+ keys: _keys.default,
25
+ standings: _standings.default
26
+ },
27
+ middleware: getDefaultMiddleware => getDefaultMiddleware({
28
+ serializableCheck: false
29
+ })
30
+ });
31
+
32
+ exports.default = _default;
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports["default"] = void 0;
6
+ exports.default = void 0;
7
7
  var _default = {
8
8
  list: {
9
9
  selected: {
@@ -14,9 +14,9 @@ var _default = {
14
14
  scrollbar: {
15
15
  ch: ' ',
16
16
  style: {
17
- fg: 'yellow',
17
+ fg: 'white',
18
18
  inverse: true
19
19
  }
20
20
  }
21
21
  };
22
- exports["default"] = _default;
22
+ exports.default = _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playball",
3
- "version": "2.2.2",
3
+ "version": "3.0.0",
4
4
  "description": "Watch MLB games from the comfort of your terminal",
5
5
  "keywords": [
6
6
  "MLB",
@@ -13,9 +13,10 @@
13
13
  "build": "npm run lint && npm run clean && npm run compile",
14
14
  "clean": "rimraf ./dist",
15
15
  "compile": "babel src --out-dir dist",
16
- "lint": "eslint src",
16
+ "lint": "eslint --ext .jsx,.js src",
17
17
  "start": "babel-node ./src/main.js",
18
- "prepublishOnly": "npm run build"
18
+ "prepublishOnly": "npm run build",
19
+ "react-devtools": "react-devtools"
19
20
  },
20
21
  "main": "dist/main.js",
21
22
  "bin": {
@@ -33,37 +34,54 @@
33
34
  "preferGlobal": true,
34
35
  "license": "MIT",
35
36
  "dependencies": {
36
- "axios": "^0.19.2",
37
+ "@reduxjs/toolkit": "^1.8.0",
38
+ "axios": "^0.26.1",
37
39
  "blessed": "^0.1.81",
38
- "immutable": "^3.8.2",
40
+ "date-fns": "^2.28.0",
39
41
  "json-patch": "^0.7.0",
40
- "moment": "^2.24.0",
41
- "prop-types": "^15.6.2",
42
- "react": "^16.8.6",
43
- "react-blessed": "^0.5.0",
44
- "react-devtools-core": "^3.6.1",
45
- "react-redux": "^5.0.7",
46
- "redux": "^4.0.0",
47
- "redux-actions": "^2.6.1",
48
- "redux-thunk": "^2.3.0",
49
- "reselect": "^3.0.1",
50
- "winston": "^3.3.3",
51
- "ws": "^6.2.1"
42
+ "prop-types": "^15.8.1",
43
+ "raf": "^3.4.1",
44
+ "react": "^17.0.2",
45
+ "react-blessed": "^0.7.2",
46
+ "react-devtools-core": "^4.24.7",
47
+ "react-redux": "^7.2.6",
48
+ "redux": "^4.1.2",
49
+ "winston": "^3.7.2"
52
50
  },
53
51
  "devDependencies": {
54
- "@babel/cli": "^7.10.5",
55
- "@babel/core": "^7.11.1",
56
- "@babel/node": "^7.10.5",
57
- "@babel/preset-env": "^7.11.0",
58
- "@babel/preset-react": "^7.10.4",
59
- "eslint": "^7.6.0",
60
- "eslint-plugin-react": "^7.20.5",
61
- "rimraf": "^2.6.3"
52
+ "@babel/cli": "^7.17.10",
53
+ "@babel/core": "^7.18.2",
54
+ "@babel/node": "^7.17.10",
55
+ "@babel/preset-env": "^7.18.2",
56
+ "@babel/preset-react": "^7.17.12",
57
+ "babel-plugin-module-resolver": "^4.1.0",
58
+ "eslint": "^8.12.0",
59
+ "eslint-plugin-react": "^7.29.4",
60
+ "react-devtools": "^4.24.7",
61
+ "rimraf": "^2.6.3",
62
+ "ws": "^8.5.0"
62
63
  },
63
64
  "babel": {
64
65
  "presets": [
65
- "@babel/preset-env",
66
+ [
67
+ "@babel/preset-env",
68
+ {
69
+ "targets": {
70
+ "node": "10"
71
+ }
72
+ }
73
+ ],
66
74
  "@babel/preset-react"
75
+ ],
76
+ "plugins": [
77
+ [
78
+ "module-resolver",
79
+ {
80
+ "alias": {
81
+ "react-redux": "react-redux/lib/alternate-renderers"
82
+ }
83
+ }
84
+ ]
67
85
  ]
68
86
  }
69
87
  }
@@ -1,75 +1,107 @@
1
1
  import React from 'react';
2
- import PropTypes from 'prop-types';
3
- import { connect } from 'react-redux';
4
- import { selectAllPlays, selectTeams } from '../selectors/game';
2
+ import { useSelector } from 'react-redux';
3
+ import { selectAllPlays, selectTeams } from '../features/games';
5
4
 
6
5
  import style from '../style';
7
6
 
8
- class AllPlays extends React.Component {
9
- render() {
10
- const { plays, teams } = this.props;
11
- let inning = '';
12
- const lines = [];
13
- plays && plays.reverse().forEach(play => {
14
- const playInning = play.getIn(['about', 'halfInning']) + ' ' + play.getIn(['about', 'inning']);
15
- if (playInning !== inning) {
16
- inning = playInning;
17
- lines.push(`{yellow-fg}[${inning.toUpperCase()}]{/yellow-fg}`);
7
+
8
+ function getPlayResultColor(play) {
9
+ const lastPlay = play.playEvents[play.playEvents.length - 1]?.details;
10
+ if (!lastPlay) {
11
+ return 'white';
12
+ } else if (lastPlay.isBall) {
13
+ return 'green';
14
+ } else if (lastPlay.isStrike) {
15
+ return 'red';
16
+ } else if (lastPlay.isInPlay && !play.about.hasOut) {
17
+ return 'blue';
18
+ } else {
19
+ return 'white';
20
+ }
21
+ }
22
+
23
+ function formatOut(out) {
24
+ return ` {bold}${out} out{/bold}`;
25
+ }
26
+
27
+
28
+ function AllPlays() {
29
+ const plays = useSelector(selectAllPlays);
30
+ const teams = useSelector(selectTeams);
31
+
32
+ const formatScoreDetail = (scoreObj) => (
33
+ ' {bold}{white-bg}{black-fg} ' +
34
+ `${teams.away.abbreviation} ${scoreObj.awayScore} - ` +
35
+ `${teams.home.abbreviation} ${scoreObj.homeScore}` +
36
+ ' {/black-fg}{/white-bg}{/bold}'
37
+ );
38
+
39
+ let inning = '';
40
+ const lines = [];
41
+ plays && plays.slice().reverse().forEach((play, playIdx, plays) => {
42
+ let lastPlay;
43
+ if (playIdx < plays.length - 1) {
44
+ lastPlay = plays[playIdx + 1];
45
+ }
46
+ const playInning = play.about.halfInning + ' ' + play.about.inning;
47
+ if (playInning !== inning) {
48
+ inning = playInning;
49
+ if (lines.length > 0) {
50
+ lines.push('');
51
+ }
52
+ lines.push(`{bold}[${inning.toUpperCase()}]{/bold}`);
53
+ }
54
+
55
+ if (play.about.isComplete) {
56
+ const color = getPlayResultColor(play);
57
+ let line = `{${color}-fg}[${play.result.event}]{/${color}-fg} ${play.result.description}`;
58
+ if (play.about.hasOut) {
59
+ const lastOut = play.playEvents[play.playEvents.length - 1].count.outs;
60
+ if (lastOut !== play.count.outs) {
61
+ line += formatOut(play.count.outs);
62
+ }
63
+ }
64
+ if (play.about.isScoringPlay) {
65
+ line += formatScoreDetail(play.result);
18
66
  }
19
- if (play.getIn(['about', 'isComplete'])) {
20
- let line = `[${play.getIn(['result', 'event'])}] ${play.getIn(['result', 'description'])}`;
21
- if (play.getIn(['about', 'hasOut'])) {
22
- line += `{bold}${play.getIn(['count', 'outs'])} out{/bold}`;
67
+ lines.push(line);
68
+ }
69
+
70
+ play.playEvents && play.playEvents.slice().reverse().forEach((event, eventIdx, events) => {
71
+ if (event.type === 'action') {
72
+ let line = '';
73
+ if (event.details.event) {
74
+ line += `[${event.details.event}] `;
75
+ }
76
+ line += event.details.description;
77
+ if (event.isScoringPlay || event.details.isScoringPlay) {
78
+ line += formatScoreDetail(event.details);
23
79
  }
24
- if (play.getIn(['about', 'isScoringPlay'])) {
25
- line += '{bold}{white-bg}{black-fg} ' +
26
- `${teams.getIn(['away', 'abbreviation'])} ${play.getIn(['result', 'awayScore'])} - ` +
27
- `${teams.getIn(['home', 'abbreviation'])} ${play.getIn(['result', 'homeScore'])}` +
28
- ' {/black-fg}{/white-bg}{/bold}';
80
+ const currentOut = event.count?.outs;
81
+ let prevOut = lastPlay ? lastPlay.count.outs : 0;
82
+ if (eventIdx < events.length - 1) {
83
+ prevOut = events[eventIdx + 1].count?.outs;
84
+ }
85
+ if (currentOut > prevOut) {
86
+ line += formatOut(currentOut);
29
87
  }
30
88
  lines.push(line);
31
89
  }
32
- play.get('playEvents') && play.get('playEvents').reverse().forEach(event => {
33
- if (event.get('type') === 'action') {
34
- let line = '';
35
- if (event.getIn(['details', 'event'])) {
36
- line += `[${event.getIn(['details', 'event'])}] `;
37
- }
38
- line += event.getIn(['details', 'description']);
39
- if (event.get('isScoringPlay')) {
40
- line += '{white-bg}{black-fg}{bold}' +
41
- `${teams.getIn(['away', 'abbreviation'])} ${event.getIn(['details', 'awayScore'])} - ` +
42
- `${teams.getIn(['home', 'abbreviation'])} ${event.getIn(['details', 'homeScore'])}` +
43
- '{/bold}{/black-fg}{/white-bg}';
44
- }
45
- lines.push(line);
46
- }
47
- });
48
90
  });
49
- return (
50
- <box
51
- content={lines.join('\n')}
52
- focused
53
- mouse
54
- keys
55
- vi
56
- scrollable
57
- scrollbar={style.scrollbar}
58
- alwaysScroll
59
- tags
60
- />
61
- );
62
- }
91
+ });
92
+ return (
93
+ <box
94
+ content={lines.join('\n')}
95
+ focused
96
+ mouse
97
+ keys
98
+ vi
99
+ scrollable
100
+ scrollbar={style.scrollbar}
101
+ alwaysScroll
102
+ tags
103
+ />
104
+ );
63
105
  }
64
106
 
65
- AllPlays.propTypes = {
66
- plays: PropTypes.object,
67
- teams: PropTypes.object,
68
- };
69
-
70
- const mapStateToProps = state => ({
71
- plays: selectAllPlays(state),
72
- teams: selectTeams(state),
73
- });
74
-
75
- export default connect(mapStateToProps)(AllPlays);
107
+ export default AllPlays;
@@ -1,71 +1,43 @@
1
- import React from 'react';
2
- import { connect } from 'react-redux';
3
- import PropTypes from 'prop-types';
1
+ import React, { useState } from 'react';
2
+ import { useDispatch } from 'react-redux';
4
3
  import GameList from './GameList';
5
- import Game from './Game';
6
4
  import HelpBar from './HelpBar';
7
- import fs from 'fs';
8
-
9
- import { setSelectedGame } from '../actions/game';
10
- import { selectGame, selectSelectedId } from '../selectors/game';
11
-
12
- import winston from 'winston';
13
-
14
- class App extends React.Component {
15
- componentDidMount() {
16
- const { onKeyPress, setSelectedGame } = this.props;
17
- onKeyPress(['l'], () => setSelectedGame(null));
18
- onKeyPress(['C-d'], () => {
19
- const { selectedGame, game } = this.props;
20
- fs.writeFileSync(
21
- selectedGame + '_' + Date.now() + '.json',
22
- JSON.stringify(game, null, 2)
23
- );
24
- });
25
- }
26
-
27
- componentDidCatch(error, info) {
28
- winston.error(error);
29
- winston.error(JSON.stringify(info, null, 2));
30
- }
5
+ import { setSelectedId } from '../features/games';
6
+ import Game from './Game';
7
+ import useKey from '../hooks/useKey';
8
+ import Standings from './Standings';
9
+
10
+ const SCHEDULE = 'schedule';
11
+ const STANDINGS = 'standings';
12
+ const GAME = 'game';
13
+
14
+ function App() {
15
+ const [view, setView] = useState(SCHEDULE);
16
+ const dispatch = useDispatch();
17
+
18
+ useKey('c', () => {
19
+ setView(SCHEDULE);
20
+ dispatch(setSelectedId(null));
21
+ }, { key: 'C', label: 'Schedule' });
22
+ useKey('s', () => setView(STANDINGS), { key: 'S', label: 'Standings'});
23
+
24
+ const handleGameSelect = (game) => {
25
+ dispatch(setSelectedId(game.gamePk));
26
+ setView(GAME);
27
+ };
31
28
 
32
- render() {
33
- const { selectedGame, setSelectedGame } = this.props;
34
- try {
35
- return (
36
- <element>
37
- <element top={0} left={0} height='100%-1'>
38
- {selectedGame ?
39
- <Game /> :
40
- <GameList onGameSelect={game => setSelectedGame(game.gamePk)} />
41
- }
42
- </element>
43
- <element top='100%-1' left={0} height={1}>
44
- <HelpBar />
45
- </element>
46
- </element>
47
- );
48
- } catch (error) {
49
- this.props.debug(error);
50
- }
51
- }
29
+ return (
30
+ <element>
31
+ <element top={0} left={0} height='100%-1'>
32
+ {view === STANDINGS && <Standings />}
33
+ {view === SCHEDULE && <GameList onGameSelect={handleGameSelect} />}
34
+ {view === GAME && <Game />}
35
+ </element>
36
+ <element top='100%-1' left={0} height={1}>
37
+ <HelpBar />
38
+ </element>
39
+ </element>
40
+ );
52
41
  }
53
42
 
54
- App.propTypes = {
55
- debug: PropTypes.func,
56
- onKeyPress: PropTypes.func,
57
- selectedGame: PropTypes.number,
58
- setSelectedGame: PropTypes.func,
59
- game: PropTypes.object,
60
- };
61
-
62
- const mapStateToProps = state => ({
63
- selectedGame: selectSelectedId(state),
64
- game: selectGame(state),
65
- });
66
-
67
- const mapDispatchToProps = {
68
- setSelectedGame
69
- };
70
-
71
- export default connect(mapStateToProps, mapDispatchToProps)(App);
43
+ export default App;
@@ -1,43 +1,41 @@
1
1
  import React from 'react';
2
- import { connect } from 'react-redux';
3
- import PropTypes from 'prop-types';
4
- import { selectCurrentPlay } from '../selectors/game';
2
+ import { useSelector } from 'react-redux';
3
+ import { selectCurrentPlay } from '../features/games';
5
4
 
6
- const AtBat = ({currentPlay}) => {
7
- const playEvents = currentPlay.get('playEvents');
8
- const playResult = currentPlay.getIn(['about', 'isComplete']) ?
9
- currentPlay.getIn(['result', 'description']) : '';
10
- const content = playEvents && playEvents.map(event => {
11
- let line = '';
12
- if (event.get('isPitch')) {
13
- line = `[${event.getIn(['details', 'description'])}] `;
14
- if (event.hasIn(['pitchData', 'startSpeed'])) {
15
- line += `${event.getIn(['pitchData', 'startSpeed'])} MPH `;
5
+ function AtBat() {
6
+ const currentPlay = useSelector(selectCurrentPlay);
7
+ const playEvents = currentPlay.playEvents;
8
+ const playResult = currentPlay.about.isComplete ? currentPlay.result.description : '';
9
+ let content = '';
10
+ if (playResult) {
11
+ content += `${playResult}\n\n`;
12
+ }
13
+ if (playEvents && playEvents.length) {
14
+ content += playEvents.slice().reverse().map(event => {
15
+ let line = '';
16
+ if (event.isPitch) {
17
+ line = `[${event.details.description}] `;
18
+ if (event.pitchData?.startSpeed) {
19
+ line += `${event.pitchData.startSpeed} MPH `;
20
+ }
21
+ if (event.details?.type?.description) {
22
+ line += event.details.type.description;
23
+ }
24
+ if (!event.details?.isInPlay) {
25
+ line += `{|} ${event.count.balls}-${event.count.strikes}`;
26
+ }
27
+ } else {
28
+ if (event.details?.event) {
29
+ line += `[${event.details.event}] `;
30
+ }
31
+ line += event.details.description;
16
32
  }
17
- line += event.getIn(['details', 'type', 'description']);
18
- if (!event.getIn(['details', 'isInPlay'])) {
19
- line += `{|} ${event.getIn(['count', 'balls'])}-${event.getIn(['count', 'strikes'])}`;
20
- }
21
- } else {
22
- if (event.getIn(['details', 'event'])) {
23
- line += `[${event.getIn(['details', 'event'])}] `;
24
- }
25
- line += event.getIn(['details', 'description']);
26
- }
27
- return line;
28
- }).join('\n') + `\n\n${playResult}`;
33
+ return line;
34
+ }).join('\n');
35
+ }
29
36
  return (
30
37
  <box content={content} tags />
31
38
  );
32
- };
33
-
34
- AtBat.propTypes = {
35
- boxscore: PropTypes.object,
36
- currentPlay: PropTypes.object,
37
- };
38
-
39
- const mapStateToProps = state => ({
40
- currentPlay: selectCurrentPlay(state),
41
- });
39
+ }
42
40
 
43
- export default connect(mapStateToProps)(AtBat);
41
+ export default AtBat;
@@ -1,27 +1,22 @@
1
1
  import React from 'react';
2
- import { connect } from 'react-redux';
2
+ import { useSelector } from 'react-redux';
3
3
  import PropTypes from 'prop-types';
4
- import { selectGame } from '../selectors/game';
4
+ import { selectLineScore } from '../features/games';
5
5
 
6
- const formatBase = (offense, base) => offense.has(base) ? '{yellow-fg}◆{/yellow-fg}' : '◇';
6
+ const formatBase = (offense, base) => (base in offense) ? '{yellow-fg}◆{/yellow-fg}' : '◇';
7
7
 
8
- const Bases = ({align, game}) => {
9
- const offense = game.getIn(['liveData', 'linescore', 'offense']);
8
+ function Bases({align}) {
9
+ const { offense } = useSelector(selectLineScore);
10
10
  const content =
11
11
  ` ${formatBase(offense, 'second')}\n` +
12
12
  `${formatBase(offense, 'third')} ${formatBase(offense, 'first')}`;
13
13
  return (
14
14
  <box align={align} content={content} tags />
15
15
  );
16
- };
16
+ }
17
17
 
18
18
  Bases.propTypes = {
19
- align: PropTypes.string,
20
- game: PropTypes.object,
19
+ align: PropTypes.oneOf(['left', 'center', 'right']),
21
20
  };
22
21
 
23
- const mapStateToProps = state => ({
24
- game: selectGame(state),
25
- });
26
-
27
- export default connect(mapStateToProps)(Bases);
22
+ export default Bases;
@@ -1,29 +1,24 @@
1
1
  import React from 'react';
2
- import { connect } from 'react-redux';
2
+ import { useSelector } from 'react-redux';
3
3
  import PropTypes from 'prop-types';
4
4
 
5
- import { selectGame } from '../selectors/game';
5
+ import { selectLineScore } from '../features/games';
6
6
 
7
7
  const formatCount = (count, total) => '● '.repeat(count) + '○ '.repeat(total - count);
8
8
 
9
- const Count = ({align, game}) => {
10
- const linescore = game.getIn(['liveData', 'linescore']);
9
+ function Count({align}) {
10
+ const linescore = useSelector(selectLineScore);
11
11
  const content =
12
- `B: {green-fg}${formatCount(linescore.get('balls'), 4)}{/green-fg}\n` +
13
- `S: {red-fg}${formatCount(linescore.get('strikes'), 3)}{/red-fg}\n` +
14
- `O: {red-fg}${formatCount(linescore.get('outs'), 3)}{/red-fg}`;
12
+ `B: {green-fg}${formatCount(linescore.balls, 4)}{/green-fg}\n` +
13
+ `S: {red-fg}${formatCount(linescore.strikes, 3)}{/red-fg}\n` +
14
+ `O: {red-fg}${formatCount(linescore.outs, 3)}{/red-fg}`;
15
15
  return (
16
16
  <box align={align} content={content} tags />
17
17
  );
18
- };
18
+ }
19
19
 
20
20
  Count.propTypes = {
21
- align: PropTypes.string,
22
- game: PropTypes.object,
21
+ align: PropTypes.oneOf(['left', 'center', 'right']),
23
22
  };
24
23
 
25
- const mapStateToProps = state => ({
26
- game: selectGame(state),
27
- });
28
-
29
- export default connect(mapStateToProps)(Count);
24
+ export default Count;