Skip to content

Commit b2a65ac

Browse files
Merge pull request #75 from qlemaire22/add-player-statistics-page
Add player statistics page
2 parents 5c7a4d7 + c8c7b3a commit b2a65ac

15 files changed

+265
-81
lines changed
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { createEffect, createSignal, onMount } from "solid-js"
2+
import { SelectButton, type SelectButtonOption } from "../ui/SelectButton"
3+
import infernals from "../../assets/game/factions/infernals-small-glow.png"
4+
import vanguard from "../../assets/game/factions/vanguard-small-glow.png"
5+
import { Race } from "../../lib/api"
6+
7+
const factionOptions: SelectButtonOption[] = [
8+
{
9+
label: "Infernals",
10+
value: Race.INFERNALS,
11+
icon: infernals.src,
12+
},
13+
{ label: "Vanguard", value: Race.VANGUARD, icon: vanguard.src },
14+
]
15+
16+
export function FactionDropdown(props: { queryParam: string; selected: (typeof factionOptions)[number]["value"] }) {
17+
const selected = props.selected ?? "all"
18+
const [faction, setFaction] = createSignal(
19+
factionOptions.find((option) => option.value === selected) || factionOptions[0]
20+
)
21+
22+
createEffect(() => {
23+
const urlParams = new URLSearchParams(window.location.search)
24+
if (faction()?.value === selected) return
25+
if (faction()?.value && faction()?.value != "all") urlParams.set(props.queryParam, faction()?.value!)
26+
else urlParams.delete(props.queryParam)
27+
window.location.href = `${window.location.pathname}${urlParams.size ? "?" + urlParams.toString() : ""}`
28+
})
29+
30+
return <SelectButton options={factionOptions} value={faction} setValue={setFaction} />
31+
}

src/components/widgets/GameLengthChart.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export function GameLengthChart(props: GameLengthProps) {
5555
intersect: false,
5656
mode: "index",
5757
},
58+
maxBarThickness: 50,
5859
plugins: {
5960
title: {
6061
display: false,
+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// @ts-nocheck
2+
3+
import { onMount } from "solid-js"
4+
import { Chart, Title, Tooltip, Colors, TimeScale, type ChartOptions, type ChartData } from "chart.js"
5+
import { Line } from "solid-chartjs"
6+
import "chartjs-adapter-date-fns"
7+
8+
type MmrHistoryChartProps = {
9+
labels: string[]
10+
data: number[]
11+
}
12+
13+
const longNumberFormatter = new Intl.NumberFormat("en-us")
14+
15+
export function MmrHistoryChart(props: MmrHistoryChartProps) {
16+
let canvas: HTMLCanvasElement
17+
onMount(() => {
18+
Chart.register(Title, Tooltip, Colors, TimeScale)
19+
})
20+
21+
const chartData: ChartData = {
22+
labels: props.labels,
23+
datasets: [
24+
{
25+
label: "MMR",
26+
data: props.data,
27+
tension: 0.3,
28+
borderColor: "#9E9E9E",
29+
pointRadius: 5,
30+
pointHoverRadius: 2,
31+
pointBorderColor: "transparent",
32+
},
33+
],
34+
}
35+
36+
const chartOptions: ChartOptions = {
37+
responsive: true,
38+
maintainAspectRatio: true,
39+
interaction: {
40+
intersect: false,
41+
mode: "index",
42+
},
43+
scales: {
44+
y: {
45+
ticks: {
46+
precision: 0,
47+
},
48+
offset: true,
49+
},
50+
x: {
51+
type: "time",
52+
time: { unit: "day", tooltipFormat: "d MMM, y" },
53+
},
54+
},
55+
plugins: {
56+
tooltip: {
57+
displayColors: false,
58+
callbacks: {
59+
label: (c) => `MMR: ${Math.round(c.parsed.y)}`,
60+
},
61+
},
62+
},
63+
}
64+
65+
return (
66+
<div class="relative w-full overflow-hidden">
67+
<Line data={chartData} options={chartOptions} height={100} width={300} ref={(c) => (canvas = c)} />
68+
</div>
69+
)
70+
}

src/layouts/PlayerLayout.astro

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const playerInfo = (await getCollection("players")).find((p) => p.data.playerId
4444
current={[
4545
{ href: `/players/${slug}`, label: "Overview" },
4646
{ href: `/players/${slug}/matches`, label: "Match History" },
47+
{ href: `/players/${slug}/statistics`, label: "Statistics" },
4748
]}
4849
/>
4950
<HeaderContent>

src/lib/api/index.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ export type { ActivityStatistics } from "./models/ActivityStatistics"
1111
export type { ActivityStatisticsActivity } from "./models/ActivityStatisticsActivity"
1212
export type { ActivityStatisticsEntry } from "./models/ActivityStatisticsEntry"
1313
export type { ActivityStatisticsServerEntry } from "./models/ActivityStatisticsServerEntry"
14+
export { Aggregation } from "./models/Aggregation"
1415
export type { ErrorResponse } from "./models/ErrorResponse"
1516
export { Leaderboard } from "./models/Leaderboard"
1617
export type { LeaderboardDumpResponse } from "./models/LeaderboardDumpResponse"
1718
export type { LeaderboardEntryHistory } from "./models/LeaderboardEntryHistory"
18-
export type { LeaderboardEntryHistoryEntry } from "./models/LeaderboardEntryHistoryEntry"
19+
export type { LeaderboardEntryHistoryRow } from "./models/LeaderboardEntryHistoryRow"
1920
export type { LeaderboardEntryResponse } from "./models/LeaderboardEntryResponse"
2021
export { LeaderboardOrder } from "./models/LeaderboardOrder"
2122
export type { LeaderboardResponse } from "./models/LeaderboardResponse"
@@ -35,13 +36,13 @@ export type { PlayerMatchupsStatsEntry } from "./models/PlayerMatchupsStatsEntry
3536
export type { PlayerMatchupsStatsMatchup } from "./models/PlayerMatchupsStatsMatchup"
3637
export type { PlayerOpponentsStats } from "./models/PlayerOpponentsStats"
3738
export type { PlayerOpponentsStatsOpponent } from "./models/PlayerOpponentsStatsOpponent"
38-
export type { PlayerPreferences } from "./models/PlayerPreferences"
3939
export type { PlayerResponse } from "./models/PlayerResponse"
4040
export type { PlayerStatsEntry } from "./models/PlayerStatsEntry"
4141
export type { PlayerStatsEntryAggregated } from "./models/PlayerStatsEntryAggregated"
4242
export type { PlayerStatsEntryNumBreakdown } from "./models/PlayerStatsEntryNumBreakdown"
4343
export { ProfilePrivacy } from "./models/ProfilePrivacy"
4444
export { Race } from "./models/Race"
45+
export { Resolution } from "./models/Resolution"
4546
export type { StatsByTime } from "./models/StatsByTime"
4647
export type { StatsByTimeEntry } from "./models/StatsByTimeEntry"
4748
export type { StatsByTimeHistoryPoint } from "./models/StatsByTimeHistoryPoint"

src/lib/api/models/PlayerPreferences.ts src/lib/api/models/Aggregation.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
/* istanbul ignore file */
33
/* tslint:disable */
44
/* eslint-disable */
5-
import type { ProfilePrivacy } from "./ProfilePrivacy"
6-
export type PlayerPreferences = {
7-
privacy_profile?: ProfilePrivacy | null
5+
export enum Aggregation {
6+
LAST = "last",
7+
MAX_MMR = "max_mmr",
8+
MAX_POINTS = "max_points",
89
}

src/lib/api/models/LeaderboardEntryHistory.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22
/* istanbul ignore file */
33
/* tslint:disable */
44
/* eslint-disable */
5-
import type { LeaderboardEntryHistoryEntry } from "./LeaderboardEntryHistoryEntry"
5+
import type { Aggregation } from "./Aggregation"
6+
import type { LeaderboardEntryHistoryRow } from "./LeaderboardEntryHistoryRow"
7+
import type { Resolution } from "./Resolution"
68
export type LeaderboardEntryHistory = {
7-
history: Array<LeaderboardEntryHistoryEntry>
9+
cached_at: string
10+
resolution: Resolution
11+
aggregation: Aggregation
12+
history: Array<LeaderboardEntryHistoryRow>
813
}

src/lib/api/models/LeaderboardEntryHistoryEntry.ts src/lib/api/models/LeaderboardEntryHistoryRow.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22
/* istanbul ignore file */
33
/* tslint:disable */
44
/* eslint-disable */
5-
export type LeaderboardEntryHistoryEntry = {
5+
export type LeaderboardEntryHistoryRow = {
66
time: string
7-
mmr: number
8-
max_confirmed_mmr?: number | null
7+
mmr?: number | null
98
points?: number | null
109
}

src/lib/api/models/Race.ts

-1
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,4 @@
55
export enum Race {
66
INFERNALS = "infernals",
77
VANGUARD = "vanguard",
8-
RANDOM = "random",
98
}

src/lib/api/models/Resolution.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/* generated using openapi-typescript-codegen -- do no edit */
2+
/* istanbul ignore file */
3+
/* tslint:disable */
4+
/* eslint-disable */
5+
export enum Resolution {
6+
MINUTE = "minute",
7+
HOUR = "hour",
8+
DAY = "day",
9+
WEEK = "week",
10+
}

src/lib/api/services/LeaderboardEntriesApi.ts

+10
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
/* istanbul ignore file */
33
/* tslint:disable */
44
/* eslint-disable */
5+
import type { Aggregation } from "../models/Aggregation"
56
import type { LeaderboardEntryHistory } from "../models/LeaderboardEntryHistory"
7+
import type { Resolution } from "../models/Resolution"
68
import type { CancelablePromise } from "../core/CancelablePromise"
79
import { OpenAPI } from "../core/OpenAPI"
810
import { request as __request } from "../core/request"
@@ -13,18 +15,26 @@ export class LeaderboardEntriesApi {
1315
*/
1416
public static getLeaderboardEntryHistory({
1517
leaderboardEntryId,
18+
resolution,
19+
aggregation,
1620
}: {
1721
/**
1822
* Player Leaderboard Entry ID
1923
*/
2024
leaderboardEntryId: string
25+
resolution?: Resolution | null
26+
aggregation?: Aggregation | null
2127
}): CancelablePromise<LeaderboardEntryHistory> {
2228
return __request(OpenAPI, {
2329
method: "GET",
2430
url: "/v0/leaderboard-entries/{leaderboard_entry_id}/history",
2531
path: {
2632
leaderboard_entry_id: leaderboardEntryId,
2733
},
34+
query: {
35+
resolution: resolution,
36+
aggregation: aggregation,
37+
},
2838
errors: {
2939
404: `Player leaderboard entry was not found`,
3040
500: `Server error`,

src/lib/api/services/PlayersApi.ts

+3-53
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import type { PlayerActivityStats } from "../models/PlayerActivityStats"
77
import type { PlayerMatchesResponse } from "../models/PlayerMatchesResponse"
88
import type { PlayerMatchupsStats } from "../models/PlayerMatchupsStats"
99
import type { PlayerOpponentsStats } from "../models/PlayerOpponentsStats"
10-
import type { PlayerPreferences } from "../models/PlayerPreferences"
1110
import type { PlayerResponse } from "../models/PlayerResponse"
1211
import type { Race } from "../models/Race"
1312
import type { CancelablePromise } from "../core/CancelablePromise"
@@ -45,6 +44,7 @@ export class PlayersApi {
4544
public static getPlayerMatches({
4645
playerId,
4746
race,
47+
opponentPlayerId,
4848
page,
4949
count,
5050
}: {
@@ -53,6 +53,7 @@ export class PlayersApi {
5353
*/
5454
playerId: string
5555
race?: Race | null
56+
opponentPlayerId?: string | null
5657
page?: number | null
5758
count?: number | null
5859
}): CancelablePromise<PlayerMatchesResponse> {
@@ -64,6 +65,7 @@ export class PlayersApi {
6465
},
6566
query: {
6667
race: race,
68+
opponent_player_id: opponentPlayerId,
6769
page: page,
6870
count: count,
6971
},
@@ -97,58 +99,6 @@ export class PlayersApi {
9799
},
98100
})
99101
}
100-
/**
101-
* @returns PlayerPreferences Player found successfully
102-
* @throws ApiError
103-
*/
104-
public static getPlayerPreferences({
105-
playerId,
106-
}: {
107-
/**
108-
* Player ID
109-
*/
110-
playerId: string
111-
}): CancelablePromise<PlayerPreferences> {
112-
return __request(OpenAPI, {
113-
method: "GET",
114-
url: "/v0/players/{player_id}/preferences",
115-
path: {
116-
player_id: playerId,
117-
},
118-
errors: {
119-
404: `Player was not found`,
120-
500: `Server error`,
121-
},
122-
})
123-
}
124-
/**
125-
* @returns PlayerPreferences Player preferences updated successfully
126-
* @throws ApiError
127-
*/
128-
public static updatePlayerPreferences({
129-
playerId,
130-
requestBody,
131-
}: {
132-
/**
133-
* Player ID
134-
*/
135-
playerId: string
136-
requestBody: PlayerPreferences
137-
}): CancelablePromise<PlayerPreferences> {
138-
return __request(OpenAPI, {
139-
method: "PUT",
140-
url: "/v0/players/{player_id}/preferences",
141-
path: {
142-
player_id: playerId,
143-
},
144-
body: requestBody,
145-
mediaType: "application/json",
146-
errors: {
147-
404: `Player was not found`,
148-
500: `Server error`,
149-
},
150-
})
151-
}
152102
/**
153103
* @returns PlayerActivityStats Player found successfully
154104
* @throws ApiError

src/lib/labels.ts

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { League, Race } from "./api"
33
export const raceLabels: Record<Race, string> = {
44
infernals: "Infernals",
55
vanguard: "Vanguard",
6-
random: "Random",
76
}
87

98
export const leagueLabels: Record<League, string> = {

src/pages/players/[id]-[username]/matches.astro

+2-16
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,13 @@ import PlayerLayout from "../../../layouts/PlayerLayout.astro"
44
import { MatchHistory } from "../../../components/widgets/MatchHistory.tsx"
55
import { PlayersApi, Race } from "../../../lib/api"
66
import Section from "../../../components/layout/Section.astro"
7+
import { getDataOrErrorResponse } from "../../../lib/utils"
78
89
const { page = 1, faction } = Object.fromEntries(new URL(Astro.request.url).searchParams.entries())
910
10-
// to be moved to own file
11-
async function getDataOrErrorResponse<T extends readonly unknown[] | []>(
12-
...values: T
13-
): Promise<[{ -readonly [P in keyof T]: Awaited<T[P]> }, error: Response | null]> {
14-
try {
15-
const result = await Promise.all(values)
16-
return [result, null]
17-
} catch (e) {
18-
return [[] as any, new Response(null, { status: 500, statusText: `${e}` })]
19-
}
20-
}
21-
2211
const playerId = Astro.params.id!
2312
24-
const [[player], error] = await getDataOrErrorResponse(
25-
PlayersApi.getPlayer({ playerId }),
26-
PlayersApi.getPlayerMatches({ playerId })
27-
)
13+
const [[player], error] = await getDataOrErrorResponse(PlayersApi.getPlayer({ playerId }))
2814
if (error) return error
2915
---
3016

0 commit comments

Comments
 (0)