playball 3.2.0 → 3.3.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.
- package/README.md +22 -12
- package/dist/cli.js +1 -1
- package/dist/components/AllPlays.js +66 -40
- package/dist/components/App.js +16 -5
- package/dist/components/BoxScore.js +69 -0
- package/dist/components/FinishedGame.js +71 -34
- package/dist/components/Game.js +5 -3
- package/dist/components/LiveGame.js +30 -2
- package/dist/components/PreviewGame.js +3 -4
- package/dist/components/Table.js +44 -0
- package/dist/config.js +8 -1
- package/dist/features/games.js +46 -4
- package/dist/main.js +4 -2
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -25,18 +25,27 @@ $ playball
|
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
### Docker
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
$ docker run -it --rm
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
```
|
|
38
|
-
$ docker
|
|
39
|
-
```
|
|
28
|
+
Don't have Node.js installed? You can run via Docker instead.
|
|
29
|
+
```
|
|
30
|
+
$ docker run -it --rm paaatrick0/playball
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
> [!TIP]
|
|
34
|
+
> When running via Docker, times will be shown by default in Eastern Time. To change this, set the `TZ` environment variable to your desired timezone.
|
|
35
|
+
>
|
|
36
|
+
> For Central Time use:
|
|
37
|
+
> ```
|
|
38
|
+
> $ docker run -it --rm -e TZ=America/Chicago paaatrick0/playball
|
|
39
|
+
> ```
|
|
40
|
+
> For Mountain Time use:
|
|
41
|
+
> ```
|
|
42
|
+
> $ docker run -it --rm -e TZ=America/Denver paaatrick0/playball
|
|
43
|
+
> ```
|
|
44
|
+
> For Pacific Time use:
|
|
45
|
+
> ```
|
|
46
|
+
> $ docker run -it --rm -e TZ=America/Los_Angeles paaatrick0/playball
|
|
47
|
+
> ```
|
|
48
|
+
> For other timezones see the list of [TZ database time zones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).
|
|
40
49
|
|
|
41
50
|
### Keys
|
|
42
51
|
#### Global
|
|
@@ -122,6 +131,7 @@ key | description | default | allowed values
|
|
|
122
131
|
`color.walk` | Color of result where play ends on a ball (walk, hit by pitch) in list of plays in game view | green | _See above_
|
|
123
132
|
`favorites` | Teams to highlight in schedule and standings views | | Any one of the following: `ATL`, `AZ`, `BAL`, `BOS`, `CHC`, `CIN`, `CLE`, `COL`, `CWS`, `DET`, `HOU`, `KC`, `LAA`, `LAD`, `MIA`, `MIL`, `MIN`, `NYM`, `NYY`, `OAK`, `PHI`, `PIT`, `SD`, `SEA`, `SF`, `STL`, `TB`, `TEX`, `TOR`, `WSH`. Or a comma-separated list of multiple (e.g. `SEA,MIL`).<br/><br />Note: in some terminals the list must be quoted: `playball config favorites "SEA,MIL"`
|
|
124
133
|
`title` | If enabled, the terminal title will be set to the score of the current game | `false` | `false`, `true`
|
|
134
|
+
`live-delay` | Number of seconds to delay the live game stream. Useful when watching with delayed broadcast streams. | `0` (no delay) | Any positive number
|
|
125
135
|
|
|
126
136
|
### Development
|
|
127
137
|
```
|
package/dist/cli.js
CHANGED
|
@@ -10,7 +10,7 @@ const notifier = updateNotifier({
|
|
|
10
10
|
updateCheckInterval: 1000 * 60 * 60 * 24 * 7 // 1 week
|
|
11
11
|
});
|
|
12
12
|
|
|
13
|
-
program.name(pkg.name).description(pkg.description).version(pkg.version).action(main).hook('postAction', () => notifier.notify({
|
|
13
|
+
program.name(pkg.name).description(pkg.description).version(pkg.version).option('--replay <gameId>', 'Replay a game by game ID').action(main).hook('postAction', () => notifier.notify({
|
|
14
14
|
isGlobal: true
|
|
15
15
|
}));
|
|
16
16
|
program.command('config').description('Set or get configration values').argument('[key]').argument('[value]').option('--unset', 'Unset configuration value').action((key, value, options) => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { useSelector } from "react-redux/lib/alternate-renderers.js";
|
|
3
|
+
import PropTypes from 'prop-types';
|
|
3
4
|
import { selectAllPlays, selectTeams } from "../features/games.js";
|
|
4
5
|
import { get } from "../config.js";
|
|
5
6
|
import style from "../style/index.js";
|
|
@@ -19,28 +20,46 @@ function getPlayResultColor(play) {
|
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
function formatOut(out) {
|
|
22
|
-
return ` {bold}${out} out{/}`;
|
|
23
|
+
return ` {bold}${out} out${out > 1 ? 's' : ''}{/}`;
|
|
23
24
|
}
|
|
24
|
-
function AllPlays(
|
|
25
|
+
function AllPlays({
|
|
26
|
+
reverse,
|
|
27
|
+
scoringOnly
|
|
28
|
+
}) {
|
|
25
29
|
const plays = useSelector(selectAllPlays);
|
|
26
30
|
const teams = useSelector(selectTeams);
|
|
27
31
|
const formatScoreDetail = scoreObj => ` {bold}{${get('color.in-play-runs-bg')}-bg}{${get('color.in-play-runs-fg')}-fg} ` + `${teams.away.abbreviation} ${scoreObj.awayScore} - ` + `${teams.home.abbreviation} ${scoreObj.homeScore}` + ' {/}';
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
let lastPlay;
|
|
32
|
-
if (playIdx < plays.length - 1) {
|
|
33
|
-
lastPlay = plays[playIdx + 1];
|
|
34
|
-
}
|
|
32
|
+
const playsByInning = {};
|
|
33
|
+
plays === null || plays === void 0 ? void 0 : plays.forEach(play => {
|
|
34
|
+
var _play$playEvents2;
|
|
35
35
|
const playInning = play.about.halfInning + ' ' + play.about.inning;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
(_play$playEvents2 = play.playEvents) === null || _play$playEvents2 === void 0 ? void 0 : _play$playEvents2.forEach(event => {
|
|
37
|
+
if (event.type === 'action') {
|
|
38
|
+
if (scoringOnly && !event.details.isScoringPlay) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (event.details.eventType === 'batter_timeout' || event.details.eventType === 'mound_visit') {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
let line = '';
|
|
45
|
+
if (event.details.event) {
|
|
46
|
+
line += `{${get('color.other-event')}-fg}[${event.details.event}]{/} `;
|
|
47
|
+
}
|
|
48
|
+
line += event.details.description;
|
|
49
|
+
if (event.details.isOut) {
|
|
50
|
+
var _event$count;
|
|
51
|
+
line += formatOut((_event$count = event.count) === null || _event$count === void 0 ? void 0 : _event$count.outs);
|
|
52
|
+
}
|
|
53
|
+
if (event.isScoringPlay || event.details.isScoringPlay) {
|
|
54
|
+
line += formatScoreDetail(event.details);
|
|
55
|
+
}
|
|
56
|
+
if (!(playInning in playsByInning)) {
|
|
57
|
+
playsByInning[playInning] = [];
|
|
58
|
+
}
|
|
59
|
+
playsByInning[playInning].push(line);
|
|
40
60
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (play.about.isComplete) {
|
|
61
|
+
});
|
|
62
|
+
if (play.about.isComplete && (!scoringOnly || play.about.isScoringPlay)) {
|
|
44
63
|
const color = getPlayResultColor(play);
|
|
45
64
|
let line = `{${color}-fg}[${play.result.event}]{/} ${play.result.description}`;
|
|
46
65
|
if (play.about.hasOut) {
|
|
@@ -52,32 +71,35 @@ function AllPlays() {
|
|
|
52
71
|
if (play.about.isScoringPlay) {
|
|
53
72
|
line += formatScoreDetail(play.result);
|
|
54
73
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
play.playEvents && play.playEvents.slice().reverse().forEach((event, eventIdx, events) => {
|
|
58
|
-
if (event.type === 'action') {
|
|
59
|
-
var _event$count;
|
|
60
|
-
let line = '';
|
|
61
|
-
if (event.details.event) {
|
|
62
|
-
line += `{${get('color.other-event')}-fg}[${event.details.event}]{/} `;
|
|
63
|
-
}
|
|
64
|
-
line += event.details.description;
|
|
65
|
-
if (event.isScoringPlay || event.details.isScoringPlay) {
|
|
66
|
-
line += formatScoreDetail(event.details);
|
|
67
|
-
}
|
|
68
|
-
const currentOut = (_event$count = event.count) === null || _event$count === void 0 ? void 0 : _event$count.outs;
|
|
69
|
-
let prevOut = lastPlay ? lastPlay.count.outs : 0;
|
|
70
|
-
if (eventIdx < events.length - 1) {
|
|
71
|
-
var _events$count;
|
|
72
|
-
prevOut = (_events$count = events[eventIdx + 1].count) === null || _events$count === void 0 ? void 0 : _events$count.outs;
|
|
73
|
-
}
|
|
74
|
-
if (currentOut > prevOut) {
|
|
75
|
-
line += formatOut(currentOut);
|
|
76
|
-
}
|
|
77
|
-
lines.push(line);
|
|
74
|
+
if (!(playInning in playsByInning)) {
|
|
75
|
+
playsByInning[playInning] = [];
|
|
78
76
|
}
|
|
79
|
-
|
|
77
|
+
playsByInning[playInning].push(line);
|
|
78
|
+
}
|
|
80
79
|
});
|
|
80
|
+
const lines = [];
|
|
81
|
+
const inningKeys = Object.keys(playsByInning);
|
|
82
|
+
if (reverse) {
|
|
83
|
+
inningKeys.reverse();
|
|
84
|
+
}
|
|
85
|
+
inningKeys.forEach(inning => {
|
|
86
|
+
if (lines.length > 0) {
|
|
87
|
+
lines.push('');
|
|
88
|
+
}
|
|
89
|
+
lines.push(`{bold}[${inning.toUpperCase()}]{/}`);
|
|
90
|
+
if (reverse) {
|
|
91
|
+
lines.push(...playsByInning[inning].slice().reverse());
|
|
92
|
+
} else {
|
|
93
|
+
lines.push(...playsByInning[inning]);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
if (lines.length === 0) {
|
|
97
|
+
if (scoringOnly) {
|
|
98
|
+
lines.push('No scoring plays yet');
|
|
99
|
+
} else {
|
|
100
|
+
lines.push('No plays yet');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
81
103
|
return /*#__PURE__*/React.createElement("box", {
|
|
82
104
|
content: lines.join('\n'),
|
|
83
105
|
focused: true,
|
|
@@ -90,4 +112,8 @@ function AllPlays() {
|
|
|
90
112
|
tags: true
|
|
91
113
|
});
|
|
92
114
|
}
|
|
115
|
+
AllPlays.propTypes = {
|
|
116
|
+
reverse: PropTypes.bool,
|
|
117
|
+
scoringOnly: PropTypes.bool
|
|
118
|
+
};
|
|
93
119
|
export default AllPlays;
|
package/dist/components/App.js
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
2
|
import { useDispatch } from "react-redux/lib/alternate-renderers.js";
|
|
3
|
+
import PropTypes from 'prop-types';
|
|
3
4
|
import GameList from "./GameList.js";
|
|
4
5
|
import HelpBar from "./HelpBar.js";
|
|
5
|
-
import {
|
|
6
|
+
import { setReplayGame, setLiveGame } from "../features/games.js";
|
|
6
7
|
import Game from "./Game.js";
|
|
7
8
|
import useKey from "../hooks/useKey.js";
|
|
8
9
|
import Standings from "./Standings.js";
|
|
9
10
|
const SCHEDULE = 'schedule';
|
|
10
11
|
const STANDINGS = 'standings';
|
|
11
12
|
const GAME = 'game';
|
|
12
|
-
function App(
|
|
13
|
+
function App({
|
|
14
|
+
replayId
|
|
15
|
+
}) {
|
|
13
16
|
const [view, setView] = useState(SCHEDULE);
|
|
14
17
|
const dispatch = useDispatch();
|
|
15
18
|
useKey('c', () => {
|
|
16
19
|
setView(SCHEDULE);
|
|
17
|
-
dispatch(
|
|
20
|
+
dispatch(setLiveGame(null));
|
|
18
21
|
}, {
|
|
19
22
|
key: 'C',
|
|
20
23
|
label: 'Schedule'
|
|
@@ -24,9 +27,14 @@ function App() {
|
|
|
24
27
|
label: 'Standings'
|
|
25
28
|
});
|
|
26
29
|
const handleGameSelect = game => {
|
|
27
|
-
dispatch(
|
|
30
|
+
dispatch(setLiveGame(game.gamePk));
|
|
28
31
|
setView(GAME);
|
|
29
32
|
};
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (replayId) {
|
|
35
|
+
dispatch(setReplayGame(replayId)).unwrap().then(() => setView(GAME)).catch(() => setView(SCHEDULE));
|
|
36
|
+
}
|
|
37
|
+
}, [replayId]);
|
|
30
38
|
return /*#__PURE__*/React.createElement("element", null, /*#__PURE__*/React.createElement("element", {
|
|
31
39
|
top: 0,
|
|
32
40
|
left: 0,
|
|
@@ -39,4 +47,7 @@ function App() {
|
|
|
39
47
|
height: 1
|
|
40
48
|
}, /*#__PURE__*/React.createElement(HelpBar, null)));
|
|
41
49
|
}
|
|
50
|
+
App.propTypes = {
|
|
51
|
+
replayId: PropTypes.string
|
|
52
|
+
};
|
|
42
53
|
export default App;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useSelector } from "react-redux/lib/alternate-renderers.js";
|
|
3
|
+
import { selectBoxscore, selectPlayers } from "../features/games.js";
|
|
4
|
+
import Table from "./Table.js";
|
|
5
|
+
function getBatterRows(boxscoreTeam, players) {
|
|
6
|
+
const batters = Object.values(boxscoreTeam.players).filter(player => player.battingOrder !== undefined).sort((a, b) => parseInt(a.battingOrder) - parseInt(b.battingOrder));
|
|
7
|
+
const batterNames = batters.map(batter => {
|
|
8
|
+
var _batter$allPositions, _batter$gameStatus;
|
|
9
|
+
const name = players[`ID${batter.person.id}`].boxscoreName;
|
|
10
|
+
const positions = (_batter$allPositions = batter.allPositions) === null || _batter$allPositions === void 0 ? void 0 : _batter$allPositions.map(pos => pos.abbreviation).join('-');
|
|
11
|
+
const prefix = (_batter$gameStatus = batter.gameStatus) !== null && _batter$gameStatus !== void 0 && _batter$gameStatus.isSubstitute ? ' ' : '';
|
|
12
|
+
return `${prefix}${name} (${positions})`;
|
|
13
|
+
});
|
|
14
|
+
return [...batters.map((batter, idx) => [batterNames[idx], batter.stats.batting.atBats.toString(), batter.stats.batting.runs.toString(), batter.stats.batting.hits.toString(), batter.stats.batting.rbi.toString(), batter.stats.batting.baseOnBalls.toString(), batter.stats.batting.strikeOuts.toString(), batter.seasonStats.batting.avg, batter.seasonStats.batting.ops]), ['Totals', boxscoreTeam.teamStats.batting.atBats.toString(), boxscoreTeam.teamStats.batting.runs.toString(), boxscoreTeam.teamStats.batting.hits.toString(), boxscoreTeam.teamStats.batting.rbi.toString(), boxscoreTeam.teamStats.batting.baseOnBalls.toString(), boxscoreTeam.teamStats.batting.strikeOuts.toString(), '', '']];
|
|
15
|
+
}
|
|
16
|
+
function getPitcherRows(boxscoreTeam, players) {
|
|
17
|
+
const pitchers = boxscoreTeam.pitchers.map(pitcherId => boxscoreTeam.players['ID' + pitcherId]);
|
|
18
|
+
const pitcherNames = pitchers.map(pitcher => {
|
|
19
|
+
const name = players[`ID${pitcher.person.id}`].boxscoreName;
|
|
20
|
+
const note = pitcher.stats.pitching.note ? ` ${pitcher.stats.pitching.note}` : '';
|
|
21
|
+
return `${name}${note}`;
|
|
22
|
+
});
|
|
23
|
+
return [...pitchers.map((pitcher, idx) => [pitcherNames[idx], pitcher.stats.pitching.inningsPitched, pitcher.stats.pitching.hits.toString(), pitcher.stats.pitching.runs.toString(), pitcher.stats.pitching.earnedRuns.toString(), pitcher.stats.pitching.baseOnBalls.toString(), pitcher.stats.pitching.strikeOuts.toString(), pitcher.stats.pitching.homeRuns.toString(), pitcher.seasonStats.pitching.era]), ['Totals', boxscoreTeam.teamStats.pitching.inningsPitched, boxscoreTeam.teamStats.pitching.hits.toString(), boxscoreTeam.teamStats.pitching.runs.toString(), boxscoreTeam.teamStats.pitching.earnedRuns.toString(), boxscoreTeam.teamStats.pitching.baseOnBalls.toString(), boxscoreTeam.teamStats.pitching.strikeOuts.toString(), boxscoreTeam.teamStats.pitching.homeRuns.toString(), '']];
|
|
24
|
+
}
|
|
25
|
+
function BoxScore({
|
|
26
|
+
...props
|
|
27
|
+
}) {
|
|
28
|
+
const boxscore = useSelector(selectBoxscore);
|
|
29
|
+
const players = useSelector(selectPlayers);
|
|
30
|
+
const batterHeader = ['Batters', 'AB', 'R', 'H', 'RBI', 'BB', 'K', 'AVG', 'OPS'];
|
|
31
|
+
const batterWidths = ['auto', 4, 4, 4, 4, 4, 4, 6, 6];
|
|
32
|
+
const awayBatterRows = getBatterRows(boxscore.away, players);
|
|
33
|
+
const homeBatterRows = getBatterRows(boxscore.home, players);
|
|
34
|
+
const pitcherHeader = ['Pitchers', 'IP', 'H', 'R', 'ER', 'BB', 'K', 'HR', 'ERA'];
|
|
35
|
+
const pitcherWidths = ['auto', 5, 4, 4, 4, 4, 4, 4, 6];
|
|
36
|
+
const pitcherStart = Math.max(awayBatterRows.length, homeBatterRows.length) + 2;
|
|
37
|
+
const awayPitcherRows = getPitcherRows(boxscore.away, players);
|
|
38
|
+
const homePitcherRows = getPitcherRows(boxscore.home, players);
|
|
39
|
+
return /*#__PURE__*/React.createElement("element", props, /*#__PURE__*/React.createElement(Table, {
|
|
40
|
+
top: 0,
|
|
41
|
+
left: 0,
|
|
42
|
+
width: "50%-1",
|
|
43
|
+
headers: batterHeader,
|
|
44
|
+
widths: batterWidths,
|
|
45
|
+
rows: awayBatterRows
|
|
46
|
+
}), /*#__PURE__*/React.createElement(Table, {
|
|
47
|
+
top: 0,
|
|
48
|
+
left: "50%+1",
|
|
49
|
+
width: "50%-1",
|
|
50
|
+
headers: batterHeader,
|
|
51
|
+
widths: batterWidths,
|
|
52
|
+
rows: homeBatterRows
|
|
53
|
+
}), /*#__PURE__*/React.createElement(Table, {
|
|
54
|
+
top: pitcherStart,
|
|
55
|
+
left: 0,
|
|
56
|
+
width: "50%-1",
|
|
57
|
+
headers: pitcherHeader,
|
|
58
|
+
widths: pitcherWidths,
|
|
59
|
+
rows: awayPitcherRows
|
|
60
|
+
}), /*#__PURE__*/React.createElement(Table, {
|
|
61
|
+
top: pitcherStart,
|
|
62
|
+
left: "50%+1",
|
|
63
|
+
width: "50%-1",
|
|
64
|
+
headers: pitcherHeader,
|
|
65
|
+
widths: pitcherWidths,
|
|
66
|
+
rows: homePitcherRows
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
export default BoxScore;
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import React, { useEffect } from 'react';
|
|
2
|
-
import { useSelector } from "react-redux/lib/alternate-renderers.js";
|
|
1
|
+
import React, { useEffect, useMemo } from 'react';
|
|
2
|
+
import { useDispatch, useSelector } from "react-redux/lib/alternate-renderers.js";
|
|
3
|
+
import figlet from 'figlet';
|
|
3
4
|
import LineScore from "./LineScore.js";
|
|
4
5
|
import { get } from "../config.js";
|
|
5
|
-
import {
|
|
6
|
+
import { selectBoxscore, selectDecisions, selectGameStatus, selectLineScore, selectSelectedId, selectTeams, setReplayGame } from "../features/games.js";
|
|
6
7
|
import { resetTitle, setTitle } from "../screen.js";
|
|
8
|
+
import useKey from "../hooks/useKey.js";
|
|
9
|
+
import BoxScore from "./BoxScore.js";
|
|
10
|
+
import AllPlays from "./AllPlays.js";
|
|
11
|
+
const BOX_SCORE = 'BOX_SCORE';
|
|
12
|
+
const ALL_PLAYS = 'ALL_PLAYS';
|
|
13
|
+
const SCORING_PLAYS = 'SCORING_PLAYS';
|
|
7
14
|
const getPlayer = (id, boxscore) => {
|
|
8
15
|
var _boxscore$home, _boxscore$away;
|
|
9
16
|
const homePlayer = (_boxscore$home = boxscore.home) === null || _boxscore$home === void 0 || (_boxscore$home = _boxscore$home.players) === null || _boxscore$home === void 0 ? void 0 : _boxscore$home['ID' + id];
|
|
@@ -37,24 +44,31 @@ const formatDecisions = (decisions, boxscore) => {
|
|
|
37
44
|
}
|
|
38
45
|
return content.join('\n');
|
|
39
46
|
};
|
|
40
|
-
const formatScore = (status, linescore) => {
|
|
41
|
-
let display = '';
|
|
42
|
-
if (status.detailedState === 'Postponed') {
|
|
43
|
-
display = status.detailedState;
|
|
44
|
-
if (status.reason) {
|
|
45
|
-
display += '\n' + status.reason;
|
|
46
|
-
}
|
|
47
|
-
} else {
|
|
48
|
-
display = `\n${linescore.teams.away.runs} - ${linescore.teams.home.runs}`;
|
|
49
|
-
}
|
|
50
|
-
return display;
|
|
51
|
-
};
|
|
52
47
|
function FinishedGame() {
|
|
48
|
+
const dispatch = useDispatch();
|
|
49
|
+
const id = useSelector(selectSelectedId);
|
|
53
50
|
const boxscore = useSelector(selectBoxscore);
|
|
54
51
|
const decisions = useSelector(selectDecisions);
|
|
55
52
|
const linescore = useSelector(selectLineScore);
|
|
56
53
|
const status = useSelector(selectGameStatus);
|
|
57
54
|
const teams = useSelector(selectTeams);
|
|
55
|
+
const [view, setView] = React.useState(BOX_SCORE);
|
|
56
|
+
useKey('r', () => dispatch(setReplayGame(id)), {
|
|
57
|
+
key: 'R',
|
|
58
|
+
label: 'Replay'
|
|
59
|
+
});
|
|
60
|
+
useKey('b', () => setView(BOX_SCORE), {
|
|
61
|
+
key: 'B',
|
|
62
|
+
label: 'Box Score'
|
|
63
|
+
});
|
|
64
|
+
useKey('a', () => setView(ALL_PLAYS), {
|
|
65
|
+
key: 'A',
|
|
66
|
+
label: 'All Plays'
|
|
67
|
+
});
|
|
68
|
+
useKey('p', () => setView(SCORING_PLAYS), {
|
|
69
|
+
key: 'P',
|
|
70
|
+
label: 'Scoring Plays'
|
|
71
|
+
});
|
|
58
72
|
useEffect(() => {
|
|
59
73
|
if (get('title')) {
|
|
60
74
|
const homeRuns = linescore.teams['home'].runs;
|
|
@@ -67,38 +81,61 @@ function FinishedGame() {
|
|
|
67
81
|
};
|
|
68
82
|
}
|
|
69
83
|
}, [get, linescore, resetTitle, setTitle, teams]);
|
|
84
|
+
const bigTextOptions = {
|
|
85
|
+
font: 'Small Block'
|
|
86
|
+
};
|
|
70
87
|
const awayTeam = `${teams.away.teamName}\n(${teams.away.record.wins}-${teams.away.record.losses})`;
|
|
88
|
+
const awayRuns = useMemo(() => figlet.textSync(linescore.teams.away.runs, bigTextOptions), [linescore.teams.away.runs]);
|
|
71
89
|
const homeTeam = `${teams.home.teamName}\n(${teams.home.record.wins}-${teams.home.record.losses})`;
|
|
72
|
-
|
|
73
|
-
|
|
90
|
+
const homeRuns = useMemo(() => figlet.textSync(linescore.teams.home.runs, bigTextOptions), [linescore.teams.home.runs]);
|
|
91
|
+
return /*#__PURE__*/React.createElement("element", null, status.detailedState === 'Postponed' ? /*#__PURE__*/React.createElement("element", null, /*#__PURE__*/React.createElement("box", {
|
|
92
|
+
top: 1
|
|
93
|
+
}, "status.detailedState"), /*#__PURE__*/React.createElement("box", {
|
|
94
|
+
top: 2
|
|
95
|
+
}, status.reason)) : /*#__PURE__*/React.createElement("element", {
|
|
96
|
+
top: 1
|
|
74
97
|
}, /*#__PURE__*/React.createElement("box", {
|
|
98
|
+
top: 1,
|
|
99
|
+
left: 0,
|
|
100
|
+
width: "25%",
|
|
75
101
|
content: awayTeam,
|
|
76
|
-
|
|
77
|
-
|
|
102
|
+
align: "right"
|
|
103
|
+
}), /*#__PURE__*/React.createElement("box", {
|
|
104
|
+
top: 0,
|
|
105
|
+
left: "25%",
|
|
106
|
+
width: "25%",
|
|
107
|
+
content: awayRuns,
|
|
78
108
|
align: "center"
|
|
79
109
|
}), /*#__PURE__*/React.createElement("box", {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
110
|
+
top: 0,
|
|
111
|
+
left: "50%",
|
|
112
|
+
width: "25%",
|
|
113
|
+
content: homeRuns,
|
|
84
114
|
align: "center"
|
|
85
115
|
}), /*#__PURE__*/React.createElement("box", {
|
|
116
|
+
top: 1,
|
|
117
|
+
left: "75%",
|
|
118
|
+
width: "25%",
|
|
86
119
|
content: homeTeam,
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
top: "60%+1",
|
|
93
|
-
height: 3
|
|
120
|
+
align: "left"
|
|
121
|
+
}), /*#__PURE__*/React.createElement("element", {
|
|
122
|
+
top: 5,
|
|
123
|
+
left: 0,
|
|
124
|
+
width: "50%-2"
|
|
94
125
|
}, /*#__PURE__*/React.createElement(LineScore, {
|
|
95
|
-
align: "
|
|
126
|
+
align: "right",
|
|
96
127
|
final: true
|
|
97
128
|
})), /*#__PURE__*/React.createElement("element", {
|
|
98
|
-
top:
|
|
99
|
-
left: "50
|
|
129
|
+
top: 5,
|
|
130
|
+
left: "50%+2",
|
|
131
|
+
width: "50%-2",
|
|
132
|
+
align: "right"
|
|
100
133
|
}, /*#__PURE__*/React.createElement("box", {
|
|
101
134
|
content: formatDecisions(decisions, boxscore)
|
|
102
|
-
}))
|
|
135
|
+
})), /*#__PURE__*/React.createElement("element", {
|
|
136
|
+
top: 9
|
|
137
|
+
}, view === BOX_SCORE && /*#__PURE__*/React.createElement(BoxScore, null), view === ALL_PLAYS && /*#__PURE__*/React.createElement(AllPlays, null), view === SCORING_PLAYS && /*#__PURE__*/React.createElement(AllPlays, {
|
|
138
|
+
scoringOnly: true
|
|
139
|
+
}))));
|
|
103
140
|
}
|
|
104
141
|
export default FinishedGame;
|
package/dist/components/Game.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useRef } from 'react';
|
|
2
2
|
import { useDispatch, useSelector } from "react-redux/lib/alternate-renderers.js";
|
|
3
|
-
import { fetchGame, selectGame, selectSelectedId, selectFullUpdateRequired } from "../features/games.js";
|
|
3
|
+
import { fetchGame, selectGame, selectSelectedId, selectFullUpdateRequired, selectDelay } from "../features/games.js";
|
|
4
4
|
import PreviewGame from "./PreviewGame.js";
|
|
5
5
|
import LiveGame from "./LiveGame.js";
|
|
6
6
|
import FinishedGame from "./FinishedGame.js";
|
|
@@ -11,13 +11,15 @@ function Game() {
|
|
|
11
11
|
const game = useSelector(selectGame);
|
|
12
12
|
const fullUpdateRequired = useSelector(selectFullUpdateRequired);
|
|
13
13
|
const id = useSelector(selectSelectedId);
|
|
14
|
+
const delay = useSelector(selectDelay);
|
|
14
15
|
const timerRef = useRef(null);
|
|
15
16
|
const timestampRef = useRef();
|
|
16
17
|
timestampRef.current = fullUpdateRequired ? null : game === null || game === void 0 || (_game$metaData = game.metaData) === null || _game$metaData === void 0 ? void 0 : _game$metaData.timeStamp;
|
|
17
18
|
const updateGameData = () => {
|
|
18
19
|
dispatch(fetchGame({
|
|
19
20
|
id,
|
|
20
|
-
start: timestampRef.current
|
|
21
|
+
start: timestampRef.current,
|
|
22
|
+
delay
|
|
21
23
|
})).unwrap().then(result => {
|
|
22
24
|
var _result$metaData;
|
|
23
25
|
const wait = (result && ((_result$metaData = result.metaData) === null || _result$metaData === void 0 ? void 0 : _result$metaData.wait) || 10) * 1000;
|
|
@@ -29,7 +31,7 @@ function Game() {
|
|
|
29
31
|
return () => {
|
|
30
32
|
clearTimeout(timerRef.current);
|
|
31
33
|
};
|
|
32
|
-
}, [id]);
|
|
34
|
+
}, [id, delay]);
|
|
33
35
|
if (!game) {
|
|
34
36
|
return /*#__PURE__*/React.createElement("element", null);
|
|
35
37
|
}
|
|
@@ -2,6 +2,7 @@ import React, { useEffect } from 'react';
|
|
|
2
2
|
import { useSelector } from "react-redux/lib/alternate-renderers.js";
|
|
3
3
|
import Count from "./Count.js";
|
|
4
4
|
import Bases from "./Bases.js";
|
|
5
|
+
import BoxScore from "./BoxScore.js";
|
|
5
6
|
import LineScore from "./LineScore.js";
|
|
6
7
|
import Matchup from "./Matchup.js";
|
|
7
8
|
import AtBat from "./AtBat.js";
|
|
@@ -10,10 +11,27 @@ import InningDisplay from "./InningDisplay.js";
|
|
|
10
11
|
import { get } from "../config.js";
|
|
11
12
|
import { selectGameStatus, selectLineScore, selectTeams } from "../features/games.js";
|
|
12
13
|
import { resetTitle, setTitle } from "../screen.js";
|
|
14
|
+
import useKey from "../hooks/useKey.js";
|
|
15
|
+
const GAME_STATUS = 'GAME_STATUS';
|
|
16
|
+
const BOX_SCORE = 'BOX_SCORE';
|
|
17
|
+
const SCORING_PLAYS = 'SCORING_PLAYS';
|
|
13
18
|
function LiveGame() {
|
|
14
19
|
const gameStatus = useSelector(selectGameStatus);
|
|
15
20
|
const linescore = useSelector(selectLineScore);
|
|
16
21
|
const teams = useSelector(selectTeams);
|
|
22
|
+
const [view, setView] = React.useState(GAME_STATUS);
|
|
23
|
+
useKey('g', () => setView(GAME_STATUS), {
|
|
24
|
+
key: 'G',
|
|
25
|
+
label: 'Game Status'
|
|
26
|
+
});
|
|
27
|
+
useKey('b', () => setView(BOX_SCORE), {
|
|
28
|
+
key: 'B',
|
|
29
|
+
label: 'Box Score'
|
|
30
|
+
});
|
|
31
|
+
useKey('p', () => setView(SCORING_PLAYS), {
|
|
32
|
+
key: 'P',
|
|
33
|
+
label: 'Scoring Plays'
|
|
34
|
+
});
|
|
17
35
|
useEffect(() => {
|
|
18
36
|
if (get('title')) {
|
|
19
37
|
const homeRuns = linescore.teams['home'].runs;
|
|
@@ -62,7 +80,7 @@ function LiveGame() {
|
|
|
62
80
|
type: "line",
|
|
63
81
|
top: 3,
|
|
64
82
|
width: "100%"
|
|
65
|
-
}), /*#__PURE__*/React.createElement("element", {
|
|
83
|
+
}), view === GAME_STATUS && /*#__PURE__*/React.createElement("element", {
|
|
66
84
|
top: 4,
|
|
67
85
|
left: 1
|
|
68
86
|
}, /*#__PURE__*/React.createElement("element", {
|
|
@@ -79,6 +97,16 @@ function LiveGame() {
|
|
|
79
97
|
}), /*#__PURE__*/React.createElement("element", {
|
|
80
98
|
left: "50%+2",
|
|
81
99
|
width: "50%-2"
|
|
82
|
-
}, /*#__PURE__*/React.createElement(AllPlays,
|
|
100
|
+
}, /*#__PURE__*/React.createElement(AllPlays, {
|
|
101
|
+
reverse: true
|
|
102
|
+
}))), view === BOX_SCORE && /*#__PURE__*/React.createElement("element", {
|
|
103
|
+
top: 4,
|
|
104
|
+
left: 1
|
|
105
|
+
}, /*#__PURE__*/React.createElement(BoxScore, null)), view === SCORING_PLAYS && /*#__PURE__*/React.createElement("element", {
|
|
106
|
+
top: 4,
|
|
107
|
+
left: 1
|
|
108
|
+
}, /*#__PURE__*/React.createElement(AllPlays, {
|
|
109
|
+
scoringOnly: true
|
|
110
|
+
})));
|
|
83
111
|
}
|
|
84
112
|
export default LiveGame;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useEffect } from 'react';
|
|
2
2
|
import { useSelector } from "react-redux/lib/alternate-renderers.js";
|
|
3
|
-
import { format } from 'date-fns';
|
|
3
|
+
import { format, isSameDay } from 'date-fns';
|
|
4
4
|
import { get } from "../config.js";
|
|
5
5
|
import { selectTeams, selectVenue, selectStartTime, selectBoxscore, selectProbablePitchers, selectGameStatus } from "../features/games.js";
|
|
6
6
|
import { resetTitle, setTitle } from "../screen.js";
|
|
@@ -42,10 +42,9 @@ function PreviewGame() {
|
|
|
42
42
|
|
|
43
43
|
// Only show the date if it's not today.
|
|
44
44
|
const startDate = new Date(startTime);
|
|
45
|
-
const today =
|
|
46
|
-
const gameDay = format(startDate, 'yyyy-DDD');
|
|
45
|
+
const today = new Date();
|
|
47
46
|
let start = format(startDate, 'p');
|
|
48
|
-
if (today
|
|
47
|
+
if (!isSameDay(startDate, today)) {
|
|
49
48
|
start = `${format(startDate, 'MMMM d, yyy')} ${start}`;
|
|
50
49
|
}
|
|
51
50
|
setTitle(`${away} - ${home} @ ${start}`);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
function formatRow(row, widths) {
|
|
4
|
+
return row.map((cell, idx) => idx === 0 ? cell.padEnd(widths[idx]) : cell.padStart(widths[idx])).join('');
|
|
5
|
+
}
|
|
6
|
+
function Table({
|
|
7
|
+
headers,
|
|
8
|
+
widths,
|
|
9
|
+
rows,
|
|
10
|
+
...rest
|
|
11
|
+
}) {
|
|
12
|
+
const resolvedWidths = widths.map((width, idx) => {
|
|
13
|
+
if (width !== 'auto') {
|
|
14
|
+
return width;
|
|
15
|
+
}
|
|
16
|
+
return Math.max((headers[idx] || '').length, ...rows.map(row => (row[idx] || '').length));
|
|
17
|
+
});
|
|
18
|
+
const headerRow = formatRow(headers, resolvedWidths);
|
|
19
|
+
const contentRows = rows.map(row => formatRow(row, resolvedWidths));
|
|
20
|
+
return /*#__PURE__*/React.createElement("element", rest, /*#__PURE__*/React.createElement("box", {
|
|
21
|
+
top: 0,
|
|
22
|
+
left: 0,
|
|
23
|
+
width: "100%",
|
|
24
|
+
height: 1,
|
|
25
|
+
fg: "black",
|
|
26
|
+
bg: "white",
|
|
27
|
+
wrap: false,
|
|
28
|
+
content: headerRow
|
|
29
|
+
}), /*#__PURE__*/React.createElement("box", {
|
|
30
|
+
top: 1,
|
|
31
|
+
left: 0,
|
|
32
|
+
width: "100%",
|
|
33
|
+
height: rows.length,
|
|
34
|
+
wrap: false,
|
|
35
|
+
content: contentRows.join('\n')
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
Table.propTypes = {
|
|
39
|
+
headers: PropTypes.arrayOf(PropTypes.string),
|
|
40
|
+
widths: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.oneOf(['auto'])])),
|
|
41
|
+
rows: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)),
|
|
42
|
+
top: PropTypes.number
|
|
43
|
+
};
|
|
44
|
+
export default Table;
|
package/dist/config.js
CHANGED
|
@@ -79,6 +79,11 @@ const schema = {
|
|
|
79
79
|
'title': {
|
|
80
80
|
type: 'boolean',
|
|
81
81
|
default: false
|
|
82
|
+
},
|
|
83
|
+
'live-delay': {
|
|
84
|
+
type: 'number',
|
|
85
|
+
default: 0,
|
|
86
|
+
minimum: 0
|
|
82
87
|
}
|
|
83
88
|
};
|
|
84
89
|
const config = new Conf({
|
|
@@ -92,11 +97,13 @@ function serialize(value) {
|
|
|
92
97
|
return value;
|
|
93
98
|
}
|
|
94
99
|
function deserialize(key, value) {
|
|
95
|
-
var _schema$key, _schema$key2;
|
|
100
|
+
var _schema$key, _schema$key2, _schema$key3;
|
|
96
101
|
if (value && ((_schema$key = schema[key]) === null || _schema$key === void 0 ? void 0 : _schema$key.type) === 'array') {
|
|
97
102
|
return value.split(/\s*,\s*/);
|
|
98
103
|
} else if (((_schema$key2 = schema[key]) === null || _schema$key2 === void 0 ? void 0 : _schema$key2.type) === 'boolean') {
|
|
99
104
|
return value === 'true';
|
|
105
|
+
} else if (((_schema$key3 = schema[key]) === null || _schema$key3 === void 0 ? void 0 : _schema$key3.type) === 'number') {
|
|
106
|
+
return parseInt(value);
|
|
100
107
|
}
|
|
101
108
|
return value;
|
|
102
109
|
}
|
package/dist/features/games.js
CHANGED
|
@@ -6,19 +6,42 @@ const {
|
|
|
6
6
|
createSelector
|
|
7
7
|
} = reduxjsToolkit;
|
|
8
8
|
import jsonpatch from 'json-patch';
|
|
9
|
+
import { UTCDate } from '@date-fns/utc';
|
|
10
|
+
import { addSeconds, differenceInSeconds, format } from 'date-fns';
|
|
11
|
+
import logger from "../logger.js";
|
|
12
|
+
import { get } from "../config.js";
|
|
9
13
|
const initialState = {
|
|
10
14
|
loading: false,
|
|
11
15
|
fullUpdateRequired: false,
|
|
12
16
|
error: null,
|
|
13
17
|
selectedId: null,
|
|
18
|
+
delay: 0,
|
|
14
19
|
games: {}
|
|
15
20
|
};
|
|
21
|
+
function makeDiffParams(start, end) {
|
|
22
|
+
const endParam = start ? '&endTimecode=' : '?timecode=';
|
|
23
|
+
const startParam = start ? `/diffPatch?startTimecode=${start}` : '';
|
|
24
|
+
if (end) {
|
|
25
|
+
return `${startParam}${endParam}${end}`;
|
|
26
|
+
} else {
|
|
27
|
+
return startParam;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
16
30
|
export const fetchGame = createAsyncThunk('games/fetch', async ({
|
|
17
31
|
id,
|
|
18
|
-
start
|
|
32
|
+
start,
|
|
33
|
+
delay
|
|
19
34
|
}) => {
|
|
20
|
-
const
|
|
35
|
+
const end = delay > 0 ? format(addSeconds(new UTCDate(Date.now()), -delay), 'yyyyMMdd_HHmmss') : null;
|
|
36
|
+
const diffParams = makeDiffParams(start, end);
|
|
21
37
|
const url = `https://statsapi.mlb.com/api/v1.1/game/${id}/feed/live${diffParams}`;
|
|
38
|
+
logger.info(`GET ${url}`);
|
|
39
|
+
const response = await axios.get(url);
|
|
40
|
+
return response.data;
|
|
41
|
+
});
|
|
42
|
+
export const setReplayGame = createAsyncThunk('games/setReplay', async id => {
|
|
43
|
+
const url = `https://statsapi.mlb.com/api/v1.1/game/${id}/feed/live?fields=gamePk,gameData,datetime,gameInfo,dateTime,firstPitch`;
|
|
44
|
+
logger.info(`GET ${url}`);
|
|
22
45
|
const response = await axios.get(url);
|
|
23
46
|
return response.data;
|
|
24
47
|
});
|
|
@@ -26,11 +49,28 @@ export const gamesSlice = createSlice({
|
|
|
26
49
|
name: 'games',
|
|
27
50
|
initialState,
|
|
28
51
|
reducers: {
|
|
29
|
-
|
|
52
|
+
setLiveGame(state, action) {
|
|
30
53
|
state.selectedId = action.payload;
|
|
54
|
+
state.delay = get('live-delay') || 0;
|
|
31
55
|
}
|
|
32
56
|
},
|
|
33
57
|
extraReducers: builder => {
|
|
58
|
+
builder.addCase(setReplayGame.pending, state => {
|
|
59
|
+
state.loading = true;
|
|
60
|
+
});
|
|
61
|
+
builder.addCase(setReplayGame.fulfilled, (state, action) => {
|
|
62
|
+
state.loading = false;
|
|
63
|
+
state.error = null;
|
|
64
|
+
state.selectedId = action.payload.gamePk;
|
|
65
|
+
state.fullUpdateRequired = true;
|
|
66
|
+
const start = action.payload.gameData.gameInfo.firstPitch || action.payload.gameData.datetime.dateTime;
|
|
67
|
+
state.delay = differenceInSeconds(new Date(), start);
|
|
68
|
+
});
|
|
69
|
+
builder.addCase(setReplayGame.rejected, (state, action) => {
|
|
70
|
+
state.loading = false;
|
|
71
|
+
state.fullUpdateRequired = true;
|
|
72
|
+
state.error = action.error;
|
|
73
|
+
});
|
|
34
74
|
builder.addCase(fetchGame.pending, state => {
|
|
35
75
|
state.loading = true;
|
|
36
76
|
});
|
|
@@ -70,13 +110,14 @@ export const gamesSlice = createSlice({
|
|
|
70
110
|
}
|
|
71
111
|
});
|
|
72
112
|
export const {
|
|
73
|
-
|
|
113
|
+
setLiveGame
|
|
74
114
|
} = gamesSlice.actions;
|
|
75
115
|
const gamesRoot = state => state.games;
|
|
76
116
|
export const selectLoading = createSelector(gamesRoot, root => root.loading);
|
|
77
117
|
export const selectError = createSelector(gamesRoot, root => root.error);
|
|
78
118
|
export const selectFullUpdateRequired = createSelector(gamesRoot, root => root.fullUpdateRequired);
|
|
79
119
|
export const selectSelectedId = createSelector(gamesRoot, root => root.selectedId);
|
|
120
|
+
export const selectDelay = createSelector(gamesRoot, root => root.delay);
|
|
80
121
|
export const selectGame = createSelector([gamesRoot, selectSelectedId], (root, id) => root.games[id]);
|
|
81
122
|
const selectLiveData = createSelector(selectGame, game => game.liveData);
|
|
82
123
|
const selectPlays = createSelector(selectLiveData, data => data.plays);
|
|
@@ -90,6 +131,7 @@ export const selectLineScore = createSelector(selectLiveData, data => data.lines
|
|
|
90
131
|
export const selectDecisions = createSelector(selectLiveData, data => data.decisions);
|
|
91
132
|
const selectGameData = createSelector(selectGame, game => game.gameData);
|
|
92
133
|
export const selectGameStatus = createSelector(selectGameData, game => game.status);
|
|
134
|
+
export const selectPlayers = createSelector(selectGameData, gameData => gameData.players);
|
|
93
135
|
export const selectTeams = createSelector(selectGameData, gameData => gameData.teams);
|
|
94
136
|
export const selectVenue = createSelector(selectGameData, gameData => gameData.venue);
|
|
95
137
|
export const selectStartTime = createSelector(selectGameData, gameData => {
|
package/dist/main.js
CHANGED
|
@@ -5,7 +5,7 @@ import screen from "./screen.js";
|
|
|
5
5
|
import store from "./store/index.js";
|
|
6
6
|
import log from "./logger.js";
|
|
7
7
|
import App from "./components/App.js";
|
|
8
|
-
export default async function startInterface() {
|
|
8
|
+
export default async function startInterface(options) {
|
|
9
9
|
raf.polyfill();
|
|
10
10
|
process.on('uncaughtException', function (error) {
|
|
11
11
|
log.error('UNCAUGHT EXCEPTION\n' + JSON.stringify(error) + '\n' + error.stack);
|
|
@@ -16,5 +16,7 @@ export default async function startInterface() {
|
|
|
16
16
|
const reactBlessed = await import('react-blessed');
|
|
17
17
|
reactBlessed.render( /*#__PURE__*/React.createElement(Provider, {
|
|
18
18
|
store: store
|
|
19
|
-
}, /*#__PURE__*/React.createElement(App,
|
|
19
|
+
}, /*#__PURE__*/React.createElement(App, {
|
|
20
|
+
replayId: options.replay
|
|
21
|
+
})), screen());
|
|
20
22
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "playball",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "Watch MLB games from the comfort of your terminal",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"MLB",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"lint": "eslint --ext .jsx,.js src",
|
|
17
17
|
"start": "babel src --out-dir dist --watch",
|
|
18
18
|
"prepublishOnly": "npm run build",
|
|
19
|
-
"react-devtools": "react-devtools"
|
|
19
|
+
"react-devtools": "react-devtools",
|
|
20
|
+
"test": "echo \"No tests yet\""
|
|
20
21
|
},
|
|
21
22
|
"bin": {
|
|
22
23
|
"playball": "./bin/playball.js"
|
|
@@ -37,12 +38,14 @@
|
|
|
37
38
|
"preferGlobal": true,
|
|
38
39
|
"license": "MIT",
|
|
39
40
|
"dependencies": {
|
|
41
|
+
"@date-fns/utc": "^2.1.1",
|
|
40
42
|
"@reduxjs/toolkit": "^1.8.0",
|
|
41
43
|
"axios": "^0.26.1",
|
|
42
44
|
"blessed": "^0.1.81",
|
|
43
45
|
"commander": "^11.0.0",
|
|
44
46
|
"conf": "^11.0.1",
|
|
45
|
-
"date-fns": "^
|
|
47
|
+
"date-fns": "^4.1.0",
|
|
48
|
+
"figlet": "^1.10.0",
|
|
46
49
|
"json-patch": "^0.7.0",
|
|
47
50
|
"prop-types": "^15.8.1",
|
|
48
51
|
"raf": "^3.4.1",
|