nhl-tui 0.1.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/.claude/settings.local.json +11 -0
- package/.github/workflows/release.yml +37 -0
- package/CONTRIBUTING.md +38 -0
- package/LICENSE +21 -0
- package/README.md +222 -0
- package/dist/api/nhl.js +37 -0
- package/dist/app/dates.js +36 -0
- package/dist/app/input.js +97 -0
- package/dist/app/polling.js +241 -0
- package/dist/app/store.js +39 -0
- package/dist/app/timers.js +15 -0
- package/dist/domain/diff.js +57 -0
- package/dist/domain/events.js +22 -0
- package/dist/domain/normalize.js +677 -0
- package/dist/domain/reducer.js +313 -0
- package/dist/domain/types.js +1 -0
- package/dist/index.js +14 -0
- package/dist/ui/App.js +77 -0
- package/dist/ui/components/Banner.js +8 -0
- package/dist/ui/components/Footer.js +10 -0
- package/dist/ui/components/GameList.js +20 -0
- package/dist/ui/components/GameRow.js +25 -0
- package/dist/ui/components/StatusLine.js +49 -0
- package/dist/ui/screens/GameDetailScreen.js +37 -0
- package/dist/ui/screens/LeadersScreen.js +36 -0
- package/dist/ui/screens/ScoreboardScreen.js +6 -0
- package/dist/ui/screens/StandingsScreen.js +27 -0
- package/package.json +28 -0
- package/src/api/nhl.ts +53 -0
- package/src/app/dates.ts +54 -0
- package/src/app/input.ts +130 -0
- package/src/app/polling.ts +333 -0
- package/src/app/store.ts +55 -0
- package/src/app/timers.ts +23 -0
- package/src/domain/diff.ts +107 -0
- package/src/domain/events.ts +31 -0
- package/src/domain/normalize.ts +966 -0
- package/src/domain/reducer.ts +458 -0
- package/src/domain/types.ts +270 -0
- package/src/index.tsx +15 -0
- package/src/ui/App.tsx +151 -0
- package/src/ui/components/Banner.tsx +23 -0
- package/src/ui/components/Footer.tsx +17 -0
- package/src/ui/components/GameList.tsx +45 -0
- package/src/ui/components/GameRow.tsx +60 -0
- package/src/ui/components/StatusLine.tsx +83 -0
- package/src/ui/screens/GameDetailScreen.tsx +199 -0
- package/src/ui/screens/LeadersScreen.tsx +92 -0
- package/src/ui/screens/ScoreboardScreen.tsx +36 -0
- package/src/ui/screens/StandingsScreen.tsx +95 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { AppEvent, NormalizedGame } from "./types.js";
|
|
2
|
+
|
|
3
|
+
function buildGoalEvents(
|
|
4
|
+
gameId: number,
|
|
5
|
+
team: string,
|
|
6
|
+
delta: number,
|
|
7
|
+
timestamp: number,
|
|
8
|
+
): AppEvent[] {
|
|
9
|
+
if (delta <= 0) {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return Array.from({ length: delta }, () => ({
|
|
14
|
+
type: "goal_scored" as const,
|
|
15
|
+
gameId,
|
|
16
|
+
team,
|
|
17
|
+
timestamp,
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function diffSingleGame(
|
|
22
|
+
previousGame: NormalizedGame | undefined,
|
|
23
|
+
nextGame: NormalizedGame,
|
|
24
|
+
timestamp: number,
|
|
25
|
+
): AppEvent[] {
|
|
26
|
+
if (!previousGame) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const events: AppEvent[] = [];
|
|
31
|
+
|
|
32
|
+
if (previousGame.phase !== "live" && nextGame.phase === "live") {
|
|
33
|
+
events.push({
|
|
34
|
+
type: "game_started",
|
|
35
|
+
gameId: nextGame.id,
|
|
36
|
+
timestamp,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (previousGame.phase === "live" && nextGame.phase === "final") {
|
|
41
|
+
events.push({
|
|
42
|
+
type: "game_ended",
|
|
43
|
+
gameId: nextGame.id,
|
|
44
|
+
timestamp,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (
|
|
49
|
+
previousGame.phase === "live" &&
|
|
50
|
+
nextGame.phase === "live" &&
|
|
51
|
+
previousGame.periodLabel &&
|
|
52
|
+
nextGame.periodLabel &&
|
|
53
|
+
previousGame.periodLabel !== nextGame.periodLabel
|
|
54
|
+
) {
|
|
55
|
+
events.push({
|
|
56
|
+
type: "period_changed",
|
|
57
|
+
gameId: nextGame.id,
|
|
58
|
+
period: nextGame.periodLabel,
|
|
59
|
+
timestamp,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
events.push(
|
|
64
|
+
...buildGoalEvents(
|
|
65
|
+
nextGame.id,
|
|
66
|
+
nextGame.away.abbrev,
|
|
67
|
+
nextGame.away.score - previousGame.away.score,
|
|
68
|
+
timestamp,
|
|
69
|
+
),
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
events.push(
|
|
73
|
+
...buildGoalEvents(
|
|
74
|
+
nextGame.id,
|
|
75
|
+
nextGame.home.abbrev,
|
|
76
|
+
nextGame.home.score - previousGame.home.score,
|
|
77
|
+
timestamp,
|
|
78
|
+
),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
return events;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function diffGames(
|
|
85
|
+
previousGames: NormalizedGame[],
|
|
86
|
+
nextGames: NormalizedGame[],
|
|
87
|
+
timestamp: number,
|
|
88
|
+
): AppEvent[] {
|
|
89
|
+
const previousById = new Map(previousGames.map((game) => [game.id, game]));
|
|
90
|
+
const events: AppEvent[] = [];
|
|
91
|
+
|
|
92
|
+
for (const nextGame of nextGames) {
|
|
93
|
+
events.push(
|
|
94
|
+
...diffSingleGame(previousById.get(nextGame.id), nextGame, timestamp),
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return events;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function diffGame(
|
|
102
|
+
previousGame: NormalizedGame | undefined,
|
|
103
|
+
nextGame: NormalizedGame,
|
|
104
|
+
timestamp: number,
|
|
105
|
+
): AppEvent[] {
|
|
106
|
+
return diffSingleGame(previousGame, nextGame, timestamp);
|
|
107
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { AppEvent, Banner, NormalizedGame } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export function createGoalBanners(
|
|
4
|
+
events: AppEvent[],
|
|
5
|
+
games: NormalizedGame[],
|
|
6
|
+
createdAt: number,
|
|
7
|
+
): Banner[] {
|
|
8
|
+
const gamesById = new Map(games.map((game) => [game.id, game]));
|
|
9
|
+
|
|
10
|
+
return events.flatMap((event, index) => {
|
|
11
|
+
if (event.type !== "goal_scored") {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const game = gamesById.get(event.gameId);
|
|
16
|
+
|
|
17
|
+
return [
|
|
18
|
+
{
|
|
19
|
+
id: `${event.gameId}:${event.timestamp}:${index}`,
|
|
20
|
+
type: "goal",
|
|
21
|
+
gameId: event.gameId,
|
|
22
|
+
team: event.team,
|
|
23
|
+
title: `${event.team} GOAL`,
|
|
24
|
+
subtitle: game
|
|
25
|
+
? `${game.away.abbrev} ${game.away.score} - ${game.home.score} ${game.home.abbrev}`
|
|
26
|
+
: undefined,
|
|
27
|
+
createdAt,
|
|
28
|
+
},
|
|
29
|
+
];
|
|
30
|
+
});
|
|
31
|
+
}
|