playball 3.0.0 → 3.1.1

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 (65) hide show
  1. package/README.md +62 -0
  2. package/bin/playball.js +1 -1
  3. package/dist/cli.js +31 -0
  4. package/dist/components/AllPlays.js +20 -60
  5. package/dist/components/App.js +19 -45
  6. package/dist/components/AtBat.js +8 -33
  7. package/dist/components/Bases.js +10 -25
  8. package/dist/components/Count.js +10 -25
  9. package/dist/components/FinishedGame.js +26 -60
  10. package/dist/components/Game.js +25 -57
  11. package/dist/components/GameList.js +32 -83
  12. package/dist/components/Grid.js +20 -47
  13. package/dist/components/HelpBar.js +6 -20
  14. package/dist/components/InningDisplay.js +6 -20
  15. package/dist/components/LineScore.js +10 -31
  16. package/dist/components/LiveGame.js +22 -41
  17. package/dist/components/LoadingSpinner.js +12 -34
  18. package/dist/components/Matchup.js +11 -29
  19. package/dist/components/PreviewGame.js +18 -40
  20. package/dist/components/Standings.js +23 -45
  21. package/dist/config.js +126 -0
  22. package/dist/features/games.js +32 -67
  23. package/dist/features/keys.js +10 -25
  24. package/dist/features/schedule.js +16 -31
  25. package/dist/features/standings.js +14 -28
  26. package/dist/hooks/useKey.js +8 -20
  27. package/dist/logger.js +9 -20
  28. package/dist/main.js +20 -28
  29. package/dist/package.js +5 -0
  30. package/dist/screen.js +16 -22
  31. package/dist/store/index.js +14 -27
  32. package/dist/style/index.js +2 -9
  33. package/dist/utils.js +8 -0
  34. package/package.json +14 -6
  35. package/.eslintrc.json +0 -33
  36. package/CODE_OF_CONDUCT.md +0 -76
  37. package/demo.cast +0 -95
  38. package/demo.gif +0 -0
  39. package/src/components/AllPlays.jsx +0 -107
  40. package/src/components/App.jsx +0 -43
  41. package/src/components/AtBat.jsx +0 -41
  42. package/src/components/Bases.jsx +0 -22
  43. package/src/components/Count.jsx +0 -24
  44. package/src/components/FinishedGame.jsx +0 -76
  45. package/src/components/Game.jsx +0 -60
  46. package/src/components/GameList.jsx +0 -166
  47. package/src/components/Grid.jsx +0 -91
  48. package/src/components/HelpBar.jsx +0 -19
  49. package/src/components/InningDisplay.jsx +0 -19
  50. package/src/components/LineScore.jsx +0 -52
  51. package/src/components/LiveGame.jsx +0 -47
  52. package/src/components/LoadingSpinner.jsx +0 -49
  53. package/src/components/Matchup.jsx +0 -41
  54. package/src/components/PreviewGame.jsx +0 -54
  55. package/src/components/Standings.jsx +0 -78
  56. package/src/features/games.js +0 -165
  57. package/src/features/keys.js +0 -38
  58. package/src/features/schedule.js +0 -59
  59. package/src/features/standings.js +0 -60
  60. package/src/hooks/useKey.js +0 -13
  61. package/src/logger.js +0 -16
  62. package/src/main.js +0 -23
  63. package/src/screen.js +0 -14
  64. package/src/store/index.js +0 -18
  65. package/src/style/index.js +0 -15
@@ -1,78 +0,0 @@
1
- import React, { useEffect } from 'react';
2
- import PropTypes from 'prop-types';
3
- import { useDispatch, useSelector } from 'react-redux';
4
- import { fetchStandings, selectData } from '../features/standings';
5
-
6
- function formatHeaderRow(record) {
7
- return record.division.nameShort.padEnd(15) +
8
- ' W' +
9
- ' L' +
10
- ' PCT' +
11
- ' GB' +
12
- ' WCGB' +
13
- ' L10' +
14
- ' STRK';
15
- }
16
-
17
- function formatTeamRow(record) {
18
- const lastTen = record.records.splitRecords.find(o => o.type === 'lastTen');
19
- return record.team.teamName.padEnd(15) +
20
- record.wins.toString().padStart(5) +
21
- record.losses.toString().padStart(5) +
22
- record.winningPercentage.padStart(7) +
23
- record.gamesBack.padStart(6) +
24
- record.wildCardGamesBack.padStart(6) +
25
- `${lastTen.wins}-${lastTen.losses}`.padStart(6) +
26
- record.streak.streakCode.padStart(5);
27
- }
28
-
29
- function Division({record, top, left, width}) {
30
- return (
31
- <box top={top} left={left} height={6} width={width}>
32
- <box top={0} left={0} height={1} fg='black' bg='white' content={formatHeaderRow(record)} wrap={false} />
33
- <box top={1} left={0} height={5} content={record.teamRecords.map(formatTeamRow).join('\n')} wrap={false} />
34
- </box>
35
- );
36
- }
37
- Division.propTypes = {
38
- record: PropTypes.object,
39
- top: PropTypes.number,
40
- left: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
41
- width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
42
- };
43
-
44
- function Standings() {
45
- const dispatch = useDispatch();
46
- const standings = useSelector(selectData);
47
-
48
- useEffect(() => dispatch(fetchStandings()), []);
49
-
50
- if (!standings) {
51
- return <element />;
52
- }
53
-
54
- return (
55
- <element>
56
- {standings.records.filter(record => record.league.id === 103).map((record, idx) => (
57
- <Division
58
- top={idx * 7}
59
- left={0}
60
- width='50%-1'
61
- key={record.division.id}
62
- record={record}
63
- />
64
- ))}
65
- {standings.records.filter(record => record.league.id === 104).map((record, idx) => (
66
- <Division
67
- top={idx * 7}
68
- left='50%+1'
69
- width='50%-1'
70
- key={record.division.id}
71
- record={record}
72
- />
73
- ))}
74
- </element>
75
- );
76
- }
77
-
78
- export default Standings;
@@ -1,165 +0,0 @@
1
- import axios from 'axios';
2
- import { createAsyncThunk, createSlice, createSelector } from '@reduxjs/toolkit';
3
- import jsonpatch from 'json-patch';
4
-
5
- const initialState = {
6
- loading: false,
7
- fullUpdateRequired: false,
8
- error: null,
9
- selectedId: null,
10
- games: {},
11
- };
12
-
13
- export const fetchGame = createAsyncThunk(
14
- 'games/fetch',
15
- async ({id, start}) => {
16
- const diffParams = start ? `/diffPatch?startTimecode=${start}` : '';
17
- const url = `https://statsapi.mlb.com/api/v1.1/game/${id}/feed/live${diffParams}`;
18
- const response = await axios.get(url);
19
- return response.data;
20
- }
21
- );
22
-
23
- export const gamesSlice = createSlice({
24
- name: 'games',
25
- initialState,
26
- reducers: {
27
- setSelectedId(state, action) {
28
- state.selectedId = action.payload;
29
- }
30
- },
31
- extraReducers: (builder) => {
32
- builder.addCase(fetchGame.pending, (state) => {
33
- state.loading = true;
34
- });
35
- builder.addCase(fetchGame.fulfilled, (state, action) => {
36
- const id = state.selectedId;
37
- let game = state.games[id];
38
- let patchError = false;
39
- if (Array.isArray(action.payload)) {
40
- action.payload.forEach(obj => {
41
- if (patchError) {
42
- return;
43
- }
44
- try {
45
- jsonpatch.apply(game || {}, obj.diff);
46
- } catch (e) {
47
- patchError = true;
48
- return;
49
- }
50
- });
51
- } else {
52
- game = action.payload;
53
- }
54
- if (patchError) {
55
- state.fullUpdateRequired = true;
56
- } else {
57
- state.fullUpdateRequired = false;
58
- state.error = null;
59
- state.games[id] = game;
60
- }
61
- state.loading = false;
62
- });
63
- builder.addCase(fetchGame.rejected, (state, action) => {
64
- state.fullUpdateRequired = true;
65
- state.loading = false;
66
- state.error = action.error;
67
- });
68
- }
69
- });
70
-
71
- export const { setSelectedId } = gamesSlice.actions;
72
-
73
- const gamesRoot = state => state.games;
74
-
75
- export const selectLoading = createSelector(
76
- gamesRoot,
77
- root => root.loading
78
- );
79
-
80
- export const selectError = createSelector(
81
- gamesRoot,
82
- root => root.error
83
- );
84
-
85
- export const selectFullUpdateRequired = createSelector(
86
- gamesRoot,
87
- root => root.fullUpdateRequired
88
- );
89
-
90
- export const selectSelectedId = createSelector(
91
- gamesRoot,
92
- root => root.selectedId
93
- );
94
-
95
- export const selectGame = createSelector(
96
- [gamesRoot, selectSelectedId],
97
- (root, id) => root.games[id]
98
- );
99
-
100
- const selectLiveData = createSelector(
101
- selectGame,
102
- game => game.liveData
103
- );
104
-
105
- const selectPlays = createSelector(
106
- selectLiveData,
107
- data => data.plays
108
- );
109
-
110
- export const selectCurrentPlay = createSelector(
111
- selectPlays,
112
- plays => plays.currentPlay
113
- );
114
-
115
- export const selectAllPlays = createSelector(
116
- selectPlays,
117
- plays => plays.allPlays
118
- );
119
-
120
- export const selectBoxscore = createSelector(
121
- selectLiveData,
122
- data => data.boxscore?.teams
123
- );
124
-
125
- export const selectLineScore = createSelector(
126
- selectLiveData,
127
- data => data.linescore
128
- );
129
-
130
- export const selectDecisions = createSelector(
131
- selectLiveData,
132
- data => data.decisions
133
- );
134
-
135
- const selectGameData = createSelector(
136
- selectGame,
137
- game => game.gameData
138
- );
139
-
140
- export const selectGameStatus = createSelector(
141
- selectGameData,
142
- game => game.status
143
- );
144
-
145
- export const selectTeams = createSelector(
146
- selectGameData,
147
- gameData => gameData.teams
148
- );
149
-
150
- export const selectVenue = createSelector(
151
- selectGameData,
152
- gameData => gameData.venue
153
- );
154
-
155
- export const selectStartTime = createSelector(
156
- selectGameData,
157
- gameData => gameData.datetime?.dateTime
158
- );
159
-
160
- export const selectProbablePitchers = createSelector(
161
- selectGameData,
162
- gameData => gameData.probablePitchers
163
- );
164
-
165
- export default gamesSlice.reducer;
@@ -1,38 +0,0 @@
1
- import { createSlice } from '@reduxjs/toolkit';
2
- import screen from '../screen';
3
-
4
- export const keysSlice = createSlice({
5
- name: 'keys',
6
- initialState: [{ key: 'Q', label: 'Quit' }],
7
- reducers: {
8
- addKeyListener: {
9
- reducer: (state, action) => {
10
- if (action.payload) {
11
- state.push(action.payload);
12
- }
13
- },
14
- prepare: (key, listener, help) => {
15
- screen.key(key, listener);
16
- return { payload: help };
17
- }
18
- },
19
- removeKeyListener: {
20
- reducer: (state, action) => {
21
- if (action.payload) {
22
- const idx = state.findIndex(item => item.key === action.payload.key);
23
- if (idx >= 0) {
24
- state.splice(idx, 1);
25
- }
26
- }
27
- },
28
- prepare: (key, listener, help) => {
29
- screen.unkey(key, listener);
30
- return { payload: help };
31
- }
32
- }
33
- }
34
- });
35
-
36
- export const { addKeyListener, removeKeyListener } = keysSlice.actions;
37
-
38
- export default keysSlice.reducer;
@@ -1,59 +0,0 @@
1
- import axios from 'axios';
2
- import { createAsyncThunk, createSlice, createSelector } from '@reduxjs/toolkit';
3
- import { format } from 'date-fns';
4
-
5
- const initialState = {
6
- loading: false,
7
- error: null,
8
- data: null,
9
- };
10
-
11
- export const fetchSchedule = createAsyncThunk(
12
- 'schedule/fetch',
13
- async (date) => {
14
- const dateStr = format(date, 'MM/dd/yyyy');
15
- const response = await axios.get(`http://statsapi.mlb.com/api/v1/schedule?sportId=1&hydrate=team,linescore&date=${dateStr}`);
16
- return response.data;
17
- }
18
- );
19
-
20
- export const scheduleSlice = createSlice({
21
- name: 'schedule',
22
- initialState,
23
- reducers: { },
24
- extraReducers: (builder) => {
25
- builder.addCase(fetchSchedule.pending, (state) => {
26
- state.loading = true;
27
- });
28
- builder.addCase(fetchSchedule.fulfilled, (state, action) => {
29
- state.loading = false;
30
- state.data = action.payload;
31
- state.error = null;
32
- });
33
- builder.addCase(fetchSchedule.rejected, (state, action) => {
34
- state.loading = false;
35
- state.data = null;
36
- state.error = action.error;
37
- });
38
- }
39
- });
40
-
41
- const scheduleSelector = state => state.schedule;
42
-
43
- export const selectLoading = createSelector(
44
- scheduleSelector,
45
- schedule => schedule.loading
46
- );
47
-
48
- export const selectError = createSelector(
49
- scheduleSelector,
50
- schedule => schedule.error
51
- );
52
-
53
- export const selectData = createSelector(
54
- scheduleSelector,
55
- schedule => schedule.data
56
- );
57
-
58
-
59
- export default scheduleSlice.reducer;
@@ -1,60 +0,0 @@
1
- import axios from 'axios';
2
- import { createAsyncThunk, createSlice, createSelector } from '@reduxjs/toolkit';
3
-
4
- const initialState = {
5
- loading: false,
6
- error: null,
7
- data: null,
8
- };
9
-
10
- const SEASON = new Date().getFullYear();
11
-
12
- export const fetchStandings = createAsyncThunk(
13
- 'standings/fetch',
14
- async () => {
15
- const url = `https://statsapi.mlb.com/api/v1/standings?leagueId=103,104&season=${SEASON}&standingsTypes=regularSeason&hydrate=division,team`;
16
- const response = await axios.get(url);
17
- return response.data;
18
- }
19
- );
20
-
21
- export const standingsSlice = createSlice({
22
- name: 'standings',
23
- initialState,
24
- reducers: { },
25
- extraReducers: (builder) => {
26
- builder.addCase(fetchStandings.pending, (state) => {
27
- state.loading = true;
28
- });
29
- builder.addCase(fetchStandings.fulfilled, (state, action) => {
30
- state.loading = false;
31
- state.data = action.payload;
32
- state.error = null;
33
- });
34
- builder.addCase(fetchStandings.rejected, (state, action) => {
35
- state.loading = false;
36
- state.data = null;
37
- state.error = action.error;
38
- });
39
- }
40
- });
41
-
42
- const standingsSelector = state => state.standings;
43
-
44
- export const selectLoading = createSelector(
45
- standingsSelector,
46
- standings => standings.loading
47
- );
48
-
49
- export const selectError = createSelector(
50
- standingsSelector,
51
- standings => standings.error
52
- );
53
-
54
- export const selectData = createSelector(
55
- standingsSelector,
56
- standings => standings.data
57
- );
58
-
59
- export default standingsSlice.reducer;
60
-
@@ -1,13 +0,0 @@
1
- import { useEffect } from 'react';
2
- import { useDispatch } from 'react-redux';
3
- import { addKeyListener, removeKeyListener } from '../features/keys';
4
-
5
- function useKey(key, handler, help) {
6
- const dispatch = useDispatch();
7
- return useEffect(() => {
8
- dispatch(addKeyListener(key, handler, help));
9
- return () => dispatch(removeKeyListener(key, handler, help));
10
- }, [key, handler, help, dispatch]);
11
- }
12
-
13
- export default useKey;
package/src/logger.js DELETED
@@ -1,16 +0,0 @@
1
- import path from 'path';
2
- import winston from 'winston';
3
-
4
- winston.configure({
5
- transports: [
6
- new winston.transports.File({
7
- filename: path.resolve(__dirname, 'playball.log'),
8
- format: winston.format.combine(
9
- winston.format.timestamp(),
10
- winston.format.printf(info => `[${info.timestamp}] ${info.level}: ${info.message}`)
11
- )
12
- })
13
- ]
14
- });
15
-
16
- export default winston;
package/src/main.js DELETED
@@ -1,23 +0,0 @@
1
- import React from 'react';
2
- import { render } from 'react-blessed';
3
- import { Provider } from 'react-redux';
4
- import raf from 'raf';
5
-
6
- import screen from './screen';
7
- import store from './store';
8
- import log from './logger';
9
-
10
- import App from './components/App';
11
-
12
- raf.polyfill();
13
-
14
- process.on('uncaughtException', function(error) {
15
- log.error('UNCAUGHT EXCEPTION\n' + JSON.stringify(error) + '\n' + error.stack);
16
- });
17
-
18
- render(
19
- <Provider store={store}>
20
- <App />
21
- </Provider>,
22
- screen
23
- );
package/src/screen.js DELETED
@@ -1,14 +0,0 @@
1
-
2
- import blessed from 'blessed';
3
-
4
- const screen = blessed.screen({
5
- autoPadding: true,
6
- debug: true,
7
- smartCSR: true,
8
- title: 'Playball!',
9
- handleUncaughtExceptions: false,
10
- });
11
-
12
- screen.key(['escape', 'q', 'C-c'], () => process.exit(0));
13
-
14
- export default screen;
@@ -1,18 +0,0 @@
1
- import { configureStore } from '@reduxjs/toolkit';
2
-
3
- import schedule from '../features/schedule';
4
- import games from '../features/games';
5
- import keys from '../features/keys';
6
- import standings from '../features/standings';
7
-
8
- export default configureStore({
9
- reducer: {
10
- schedule,
11
- games,
12
- keys,
13
- standings,
14
- },
15
- middleware: (getDefaultMiddleware) => getDefaultMiddleware({
16
- serializableCheck: false
17
- })
18
- });
@@ -1,15 +0,0 @@
1
- export default {
2
- list: {
3
- selected: {
4
- bg: 'blue',
5
- fg: 'white'
6
- }
7
- },
8
- scrollbar: {
9
- ch: ' ',
10
- style: {
11
- fg: 'white',
12
- inverse: true
13
- }
14
- }
15
- };