diff --git a/fonts/ExtraCondensed-ExtraBoldItalic.ttf b/fonts/ExtraCondensed-ExtraBoldItalic.ttf new file mode 100644 index 0000000..10c3411 Binary files /dev/null and b/fonts/ExtraCondensed-ExtraBoldItalic.ttf differ diff --git a/src/common/components/Header/Header.tsx b/src/common/components/Header/Header.tsx new file mode 100644 index 0000000..b6b8db2 --- /dev/null +++ b/src/common/components/Header/Header.tsx @@ -0,0 +1,105 @@ +import * as React from 'react'; +import Sheet from '@mui/joy/Sheet'; +import {Button, FormControl, FormHelperText, IconButton, Stack, Typography, useTheme} from "@mui/joy"; +import InfoOutlined from "@mui/icons-material/InfoOutlined"; +import {useGameStatsStore} from "@common/stores/gameStatsStore"; +import {ChangeEvent, useState} from "react"; +import MenuIcon from '@mui/icons-material/Menu'; +import { toggleSidebar } from "./helper"; +import {observer} from "mobx-react"; + +export interface HeaderProps { + compact?: boolean +} + +const Header = observer(({ compact }: HeaderProps) => { + const theme = useTheme() + const { setJson, error } = useGameStatsStore() + + const [fileError, setFileError] = useState() + + const onFileInput = (e: ChangeEvent) => { + const reader = new FileReader(); + + reader.readAsText(e.target.files![0], "UTF-8"); + reader.onload = event => { + try { + setJson(JSON.parse(event.target!.result as string)) + setFileError(undefined) + } catch (e) { + console.log(e) + setFileError((e as Error).message) + } + } + reader.onerror = event => { + console.error(event) + setFileError("Error occured reading the file") + } + } + + return ( + + + {!compact && + THE FINALS TRACKER + } + {compact && toggleSidebar()} + variant="outlined" + color="neutral" + size="sm" + > + + } + + + + + {(error || fileError) && {error || fileError}} + + + + + + + + ); +}) + +export default Header diff --git a/src/common/components/Header/helper.ts b/src/common/components/Header/helper.ts new file mode 100644 index 0000000..58fdec5 --- /dev/null +++ b/src/common/components/Header/helper.ts @@ -0,0 +1,26 @@ +export function openSidebar() { + if (typeof document !== 'undefined') { + document.body.style.overflow = 'hidden'; + document.documentElement.style.setProperty('--SideNavigation-slideIn', '1'); + } +} + +export function closeSidebar() { + if (typeof document !== 'undefined') { + document.documentElement.style.removeProperty('--SideNavigation-slideIn'); + document.body.style.removeProperty('overflow'); + } +} + +export function toggleSidebar() { + if (typeof window !== 'undefined' && typeof document !== 'undefined') { + const slideIn = window + .getComputedStyle(document.documentElement) + .getPropertyValue('--SideNavigation-slideIn'); + if (slideIn) { + closeSidebar(); + } else { + openSidebar(); + } + } +} diff --git a/src/common/components/Header/index.ts b/src/common/components/Header/index.ts new file mode 100644 index 0000000..696cfbc --- /dev/null +++ b/src/common/components/Header/index.ts @@ -0,0 +1,3 @@ +import Header from "./Header" + +export default Header diff --git a/src/common/models/gameStatsJson/GameMode.ts b/src/common/models/GameMode.ts similarity index 100% rename from src/common/models/gameStatsJson/GameMode.ts rename to src/common/models/GameMode.ts diff --git a/src/common/models/gameStatsJson/MapVariant.ts b/src/common/models/MapVariant.ts similarity index 83% rename from src/common/models/gameStatsJson/MapVariant.ts rename to src/common/models/MapVariant.ts index 4022f51..b49a576 100644 --- a/src/common/models/gameStatsJson/MapVariant.ts +++ b/src/common/models/MapVariant.ts @@ -1,3 +1,10 @@ +export enum Map { + MONACO = "Monaco", + SOUL = "Seoul", + SKYWAY_STADIUM = "Skyway Stadium", + LAS_VEGAS = "Las Vegas" +} + export enum MapVariant { MONACO_BASE = "DA_MV_Monaco_01_Base", MONACO_DUCK_AND_COVER = "DA_MV_Monaco_01_DuckAndCover", diff --git a/src/common/models/index.ts b/src/common/models/index.ts index 39ef612..7915c17 100644 --- a/src/common/models/index.ts +++ b/src/common/models/index.ts @@ -1,4 +1,4 @@ -export * from "./gameStatsJson" - export * from "./Archetype" +export * from "./GameMode" +export * from "./MapVariant" export * from "./RoundType" diff --git a/src/common/stores/gameStatsStore/GameStatsStore.ts b/src/common/stores/gameStatsStore/GameStatsStore.ts index 99677e3..6c2d43b 100644 --- a/src/common/stores/gameStatsStore/GameStatsStore.ts +++ b/src/common/stores/gameStatsStore/GameStatsStore.ts @@ -1,20 +1,23 @@ import {action, computed, makeObservable, observable} from "mobx"; import { Archetype, - DiscoveryRoundStats, GameMode, - GamePerArchetype, - GameStatsJson, - MapVariant, - RoundStatSummary, RoundType } from "@common/models"; -import { getLoadoutAssetFromId, mapArchetype} from "@common/util"; -import {ArchetypeGameStats, MatchesStats, TournamentRound, TournamentStat, WeaponStat, WinRates} from "./models"; +import {MatchesStats, WinRates} from "./models"; import {Store} from "@common/stores"; +import { + ArchetypeStats, + GameModeStats, + GameStats, + GameStatsJson, LoadoutItemStat, + mapGameStats, + Tournament +} from "@common/util/mapGameStats"; export class GameStatsStore implements Store { - @observable.ref protected _json?: GameStatsJson = undefined + @observable.ref protected _stats?: GameStats + @observable.ref protected _error?: string @observable.ref protected _gameMode: GameMode = GameMode.TOTAL static new = () => new GameStatsStore() @@ -23,9 +26,27 @@ export class GameStatsStore implements Store { makeObservable(this) } + @action.bound + setJson(json: GameStatsJson | undefined) { + if (!json) return this.setStats(json) + + try { + this.setStats(mapGameStats(json)) + this.setError(undefined) + } catch (e) { + console.error(e) + this.setError("Unable to parse JSON") + } + } + + @computed + get error(): string | undefined { + return this._error + } + @computed get isJsonPresent(): boolean { - return this._json != undefined + return this._stats != undefined } @computed @@ -35,43 +56,17 @@ export class GameStatsStore implements Store { @computed get profile() { - return this.json["v1-shared-profile"] + return this.stats.profile } @computed - get roundStatsSummary(): RoundStatSummary { - return this.json["v1-discovery-roundstatsummary"][this._gameMode] + get roundStatsSummary(): GameModeStats { + return this.stats.statsPerRoundType[this._gameMode] } @computed - get tournaments(): TournamentStat[] { - const tournamentMap: Record = {} - for (const roundStat of this.json["v1-discovery-roundstats"].roundStats) { - if (tournamentMap[roundStat.tournamentId] === undefined) tournamentMap[roundStat.tournamentId] = [roundStat] - else tournamentMap[roundStat.tournamentId].push(roundStat) - } - - const tournaments: TournamentStat[] = [] - for (const [id, rounds] of Object.entries(tournamentMap)) { - tournaments.push({ - id, - won: rounds[0].tournamentWon, - rounds: rounds.map(round => ({ - id: round.roundId, - damageDone: round.damageDone, - deaths: round.deaths, - kills: round.kills, - respawns: round.respawns, - revives: round.revivesDone, - map: round.mapVariant as MapVariant, - start: new Date(round.startTime * 1000), - end: new Date(round.endTime * 1000), - won: round.roundWon - })) - }) - } - - return tournaments + get tournaments(): Tournament[] { + return this.stats.tournaments } @computed @@ -87,17 +82,8 @@ export class GameStatsStore implements Store { } @computed - //TODO: Better name needed, returns both gadgets and weapons that can deal damage - get weapons(): WeaponStat[] { - const { damagePerItem, killsPerItem } = this.roundStatsSummary - const weaponIds = Object.keys(damagePerItem).concat(Object.keys(killsPerItem)) - .filter((value, i, arr) => arr.indexOf(value) === i) - - return weaponIds.map(id => ({ - ...getLoadoutAssetFromId(id), - damage: damagePerItem[id] || 0, - kills: killsPerItem[id] || 0 - })) + get loadoutItems(): LoadoutItemStat[] { + return this.roundStatsSummary.loadoutItems } @computed @@ -127,48 +113,19 @@ export class GameStatsStore implements Store { } @computed - get archetypes(): ArchetypeGameStats[] { - const { - roundWinRatePerArchetype, - timePlayedPerArchetype, - tournamentWinRatePerArchetype - } = this.roundStatsSummary - - return ([ - ...Object.keys(roundWinRatePerArchetype), - ...Object.keys(timePlayedPerArchetype), - ...Object.keys(tournamentWinRatePerArchetype) - ].filter((value, i, arr) => arr.indexOf(value) === i && value != "") as Array) - .map(key => { - const type = mapArchetype(key) - const {kills, damage} = this.weapons - .filter(row => row.archetype === type) - .reduce((a, b) => ({ - kills: a.kills + b.kills, - damage: a.damage + (b.damage * 100_000) - }), {kills: 0, damage: 0}) - - return { - type, - kills, - damage: Math.round(damage / 100_000), - roundWinRate: roundWinRatePerArchetype[key] || 0, - tournamentWinRate: roundWinRatePerArchetype[key] || 0, - timePlayed: timePlayedPerArchetype[key] || 0 - } - } - ) + get archetypes(): ArchetypeStats[] { + return this.roundStatsSummary.archetypes } @computed - private get json(): GameStatsJson { + private get stats(): GameStats { if (!this.isJsonPresent) throw new Error("Trying to fetch JSON data when JSON is not available") - return this._json! + return this._stats! } @action setGameMode = (gameMode: GameMode) => this._gameMode = gameMode - @action - setJson = (json: GameStatsJson | undefined) => this._json = json + @action private setError = (error?: string) => this._error = error + @action private setStats = (stats?: GameStats) => this._stats = stats } diff --git a/src/common/stores/gameStatsStore/GameStatsStoreProvider.tsx b/src/common/stores/gameStatsStore/GameStatsStoreProvider.tsx index 5b0a23f..41ab4eb 100644 --- a/src/common/stores/gameStatsStore/GameStatsStoreProvider.tsx +++ b/src/common/stores/gameStatsStore/GameStatsStoreProvider.tsx @@ -1,6 +1,5 @@ -import React, {useContext} from "react"; +import React, {useContext, useState} from "react"; import {GameStatsStore} from "./GameStatsStore"; -import {useStore} from "@common/stores"; import {observer} from "mobx-react"; const GameStatsStoreContext = React.createContext({ @@ -14,7 +13,7 @@ export interface GameStatsProviderProps { } export const GameStatsProvider = observer(({ homeView: HomeView, statsView: StatsView }: GameStatsProviderProps) => { - const store = useStore(GameStatsStore.new) + const [store] = useState(GameStatsStore.new) return ( diff --git a/src/common/stores/gameStatsStore/models/ArchetypeGameStats.ts b/src/common/stores/gameStatsStore/models/ArchetypeGameStats.ts deleted file mode 100644 index ba9300e..0000000 --- a/src/common/stores/gameStatsStore/models/ArchetypeGameStats.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {Archetype} from "@common/models"; - -export interface ArchetypeGameStats { - type: Archetype - roundWinRate: number - tournamentWinRate: number - timePlayed: number - kills: number, - damage: number -} diff --git a/src/common/stores/gameStatsStore/models/index.ts b/src/common/stores/gameStatsStore/models/index.ts index 1b15ed5..def149f 100644 --- a/src/common/stores/gameStatsStore/models/index.ts +++ b/src/common/stores/gameStatsStore/models/index.ts @@ -1,4 +1,3 @@ -export * from "./ArchetypeGameStats" export * from "./MatchesStats" export * from "./TournamentStat" export * from "./WeaponStat" diff --git a/src/common/util/index.ts b/src/common/util/index.ts index 43efd80..5d4fc1d 100644 --- a/src/common/util/index.ts +++ b/src/common/util/index.ts @@ -2,5 +2,4 @@ export * from './fameToLeague' export * from './getLoadoutAssetFromId' export * from './getMapName' export * from './getWeaponName' -export * from "./mapArchetype" export * from './msToTimeString' diff --git a/src/common/util/mapArchetype.ts b/src/common/util/mapArchetype.ts deleted file mode 100644 index fc9a1fc..0000000 --- a/src/common/util/mapArchetype.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {Archetype, GamePerArchetype} from "@common/models"; - -export const mapArchetype = (key: keyof GamePerArchetype): Archetype => { - switch (key) { - case "DA_Archetype_Small": - return Archetype.LIGHT - case "DA_Archetype_Medium": - return Archetype.MEDIUM - case "DA_Archetype_Heavy": - return Archetype.HEAVY - default: - return Archetype.UNKNOWN - } -} diff --git a/src/common/util/mapGameStats/index.ts b/src/common/util/mapGameStats/index.ts new file mode 100644 index 0000000..21bff99 --- /dev/null +++ b/src/common/util/mapGameStats/index.ts @@ -0,0 +1,13 @@ +import {GameStatsJson, GameStats} from "./models"; +import {mapGameStatsV1} from "./mapGameStatsV1"; + +export * from "./models" + +export const mapGameStats = (json: GameStatsJson): GameStats => { + switch (json.version) { + case 1: + return mapGameStatsV1(json) + default: + throw new Error("Unknown JSON version") + } +} diff --git a/src/common/util/mapGameStats/mapGameStatsV1.ts b/src/common/util/mapGameStats/mapGameStatsV1.ts new file mode 100644 index 0000000..c808fed --- /dev/null +++ b/src/common/util/mapGameStats/mapGameStatsV1.ts @@ -0,0 +1,149 @@ +import { + DiscoveryRoundStats, DiscoveryRoundStatSummary, GameModeStats, GamePerArchetype, + GameStatsJsonV1, + GameStatsV1, + Profile, + SharedProfile, + Tournament, + TournamentRound +} from "./models"; +import {Archetype, GameMode, Map, MapVariant} from "@common/models"; +import {getLoadoutAssetFromId} from "@common/util"; + +function mapProfile(profile: SharedProfile): Profile { + return profile +} + +function mapGameMap(variant: MapVariant): Map { + switch (variant) { + case MapVariant.MONACO_BASE: + case MapVariant.MONACO_DUCK_AND_COVER: + case MapVariant.MONACO_SUSPENDED_STRUCTURES: + return Map.MONACO + case MapVariant.SOUL_BASE: + case MapVariant.SOUL_UNDER_CONSTRUCTION: + case MapVariant.SOUL_MOVING_PLATFORMS: + return Map.SOUL + case MapVariant.SKYWAY_STADIUM_BASE: + case MapVariant.SKYWAY_STADIUM_UDLR: + case MapVariant.SKYWAY_STADIUM_HIGH_RISE: + return Map.SKYWAY_STADIUM + case MapVariant.LAS_VEGAS_BASE: + case MapVariant.LAS_VEGAS_LOCKDOWN: + case MapVariant.LAS_VEGAS_SANDSTORM: + return Map.LAS_VEGAS + default: + return variant as Map + } +} + +function mapRound(round: DiscoveryRoundStats): TournamentRound { + return { + id: round.roundId, + won: round.roundWon, + squadName: round.squadName, + damage: round.damageDone, + kills: round.kills, + deaths: round.deaths, + respawns: round.respawns, + revives: round.revivesDone, + mapVariant: round.mapVariant, + startDateTime: new Date(round.startTime * 1000), + endDateTime: new Date(round.endTime * 1000), + } +} + +function mapRounds(rounds: DiscoveryRoundStats[]): Tournament[] { + const tournamentMap: Record = {} + for (const roundStat of rounds) { + if (tournamentMap[roundStat.tournamentId] === undefined) tournamentMap[roundStat.tournamentId] = [mapRound(roundStat)] + else tournamentMap[roundStat.tournamentId].push(mapRound(roundStat)) + } + + return Object.entries(tournamentMap).map(([id, rounds]) => ({ + id, + won: rounds[rounds.length - 1].won, + squadName: rounds[0].squadName, + map: mapGameMap(rounds[0].mapVariant), + rounds: rounds, + startDateTime: rounds[0].startDateTime, + endDateTime: rounds[rounds.length - 1].endDateTime + })) +} + +const mapArchetype = (key: keyof GamePerArchetype): Archetype => { + switch (key) { + case "DA_Archetype_Small": + return Archetype.LIGHT + case "DA_Archetype_Medium": + return Archetype.MEDIUM + case "DA_Archetype_Heavy": + return Archetype.HEAVY + default: + return Archetype.UNKNOWN + } +} + +function mapGameStats(gameStats: DiscoveryRoundStatSummary): Record { + const stats: Partial> = {} + + for (const [roundType, gameModeStats] of Object.entries(gameStats)) { + const loadoutItems = [ + ...Object.keys(gameModeStats.damagePerItem), + ...Object.keys(gameModeStats.killsPerItem) + ].filter((value, i, arr) => arr.indexOf(value) === i) + .map(id => ({ + ...getLoadoutAssetFromId(id), + damage: gameModeStats.damagePerItem[id] || 0, + kills: gameModeStats.killsPerItem[id] || 0 + })) + + const archetypes = ([ + ...Object.keys(gameModeStats.roundWinRatePerArchetype), + ...Object.keys(gameModeStats.timePlayedPerArchetype), + ...Object.keys(gameModeStats.tournamentWinRatePerArchetype) + ].filter((value, i, arr) => arr.indexOf(value) === i) as Array) + .map(key => { + const type = mapArchetype(key) + const {kills, damage} = loadoutItems + .filter(row => row.archetype === type) + .reduce((a, b) => ({ + kills: a.kills + b.kills, + damage: a.damage + (b.damage * 100_000) + }), {kills: 0, damage: 0}) + + return { + type, + kills, + damage, + roundWinRate: gameModeStats.roundWinRatePerArchetype[key] || 0, + tournamentWinRate: gameModeStats.roundWinRatePerArchetype[key] || 0, + timePlayed: gameModeStats.timePlayedPerArchetype[key] || 0 + } + } + ) + + stats[roundType as any as GameMode] = { + deaths: gameModeStats.deaths, + highestFameAmount: gameModeStats.highestFameAmount, + kills: gameModeStats.kills, + revivesDone: gameModeStats.revivesDone, + roundWinRate: gameModeStats.roundWinRate, + roundsCompleted: gameModeStats.roundsCompleted, + timePlayed: gameModeStats.timePlayed, + totalCashOut: gameModeStats.totalCashOut, + tournamentWinRate: gameModeStats.tournamentWinRate, + loadoutItems, + archetypes + } + } + + return stats as Record +} + +export const mapGameStatsV1 = (stats: GameStatsJsonV1): GameStatsV1 => ({ + version: 1, + profile: mapProfile(stats["v1-shared-profile"]), + tournaments: mapRounds(stats["v1-discovery-roundstats"].roundStats), + statsPerRoundType: mapGameStats(stats["v1-discovery-roundstatsummary"]) +}) diff --git a/src/common/util/mapGameStats/models/gameStats/BaseGameStats.ts b/src/common/util/mapGameStats/models/gameStats/BaseGameStats.ts new file mode 100644 index 0000000..fd3d263 --- /dev/null +++ b/src/common/util/mapGameStats/models/gameStats/BaseGameStats.ts @@ -0,0 +1,3 @@ +export interface BaseGameStats { + version: number +} diff --git a/src/common/util/mapGameStats/models/gameStats/GameStatsV1.ts b/src/common/util/mapGameStats/models/gameStats/GameStatsV1.ts new file mode 100644 index 0000000..12f6ac1 --- /dev/null +++ b/src/common/util/mapGameStats/models/gameStats/GameStatsV1.ts @@ -0,0 +1,67 @@ +import {BaseGameStats} from "./BaseGameStats"; +import {MapVariant, Map, Archetype, GameMode} from "@common/models"; +import {GameLoadoutAsset} from "@common/util"; + +export interface Profile { + embarkName: string + steamName: string +} + +export interface TournamentRound { + id: string + won: boolean + squadName: string + damage: number + kills: number + deaths: number + respawns: number + revives: number + mapVariant: MapVariant + startDateTime: Date + endDateTime: Date +} + +export interface Tournament { + id: string + won: boolean + squadName: string + map: Map + rounds: TournamentRound[] + startDateTime: Date + endDateTime: Date +} + +export interface LoadoutItemStat extends GameLoadoutAsset { + damage: number, + kills: number +} + +export interface ArchetypeStats { + type: Archetype + roundWinRate: number + tournamentWinRate: number + timePlayed: number + kills: number, + damage: number +} + +export interface GameModeStats { + deaths: number + highestFameAmount: number + kills: number + revivesDone: number + roundWinRate: number + roundsCompleted: number + timePlayed: number + totalCashOut: number + tournamentWinRate: number + loadoutItems: LoadoutItemStat[] + archetypes: ArchetypeStats[] +} + +export interface GameStatsV1 extends BaseGameStats { + version: 1 + profile: Profile + tournaments: Tournament[] + statsPerRoundType: Record +} diff --git a/src/common/util/mapGameStats/models/gameStats/index.ts b/src/common/util/mapGameStats/models/gameStats/index.ts new file mode 100644 index 0000000..420e488 --- /dev/null +++ b/src/common/util/mapGameStats/models/gameStats/index.ts @@ -0,0 +1,5 @@ +export * from "./GameStatsV1" + +import {GameStatsV1} from "./GameStatsV1"; + +export type GameStats = GameStatsV1 diff --git a/src/common/models/gameStatsJson/GameStatsJsonV1.ts b/src/common/util/mapGameStats/models/gameStatsJson/GameStatsJsonV1.ts similarity index 92% rename from src/common/models/gameStatsJson/GameStatsJsonV1.ts rename to src/common/util/mapGameStats/models/gameStatsJson/GameStatsJsonV1.ts index 077b6c5..93c54d2 100644 --- a/src/common/models/gameStatsJson/GameStatsJsonV1.ts +++ b/src/common/util/mapGameStats/models/gameStatsJson/GameStatsJsonV1.ts @@ -1,7 +1,6 @@ -import {MapVariant} from "./MapVariant" -import {GameMode} from "./GameMode" +import {MapVariant, GameMode} from "@common/models" -interface SharedProfile { +export interface SharedProfile { embarkName: string steamName: string } diff --git a/src/common/models/gameStatsJson/index.ts b/src/common/util/mapGameStats/models/gameStatsJson/index.ts similarity index 70% rename from src/common/models/gameStatsJson/index.ts rename to src/common/util/mapGameStats/models/gameStatsJson/index.ts index f8d3f2a..b56f8c5 100644 --- a/src/common/models/gameStatsJson/index.ts +++ b/src/common/util/mapGameStats/models/gameStatsJson/index.ts @@ -1,7 +1,5 @@ import {GameStatsJsonV1} from "./GameStatsJsonV1"; -export * from "./GameMode" export * from "./GameStatsJsonV1" -export * from "./MapVariant" export type GameStatsJson = GameStatsJsonV1 diff --git a/src/common/util/mapGameStats/models/index.ts b/src/common/util/mapGameStats/models/index.ts new file mode 100644 index 0000000..d156835 --- /dev/null +++ b/src/common/util/mapGameStats/models/index.ts @@ -0,0 +1,2 @@ +export * from "./gameStats" +export * from "./gameStatsJson" diff --git a/src/index.css b/src/index.css index ec2585e..54a6966 100644 --- a/src/index.css +++ b/src/index.css @@ -11,3 +11,12 @@ code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } + +@font-face { + font-family: Finals; + src: url(/fonts/ExtraCondensed-ExtraBoldItalic.ttf); +} + +.finals-title { + font-family: Finals !important; +} diff --git a/src/index.tsx b/src/index.tsx index d8dd9ad..93b832c 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -15,8 +15,41 @@ const theme = extendTheme({ colorSchemes: { light: { palette: { + primary: { + 50: '#fff1f1', + 100: '#ffe4e4', + 200: '#fdced1', + 300: '#fba6ab', + 400: '#f9737f', + 500: '#f14256', + 600: '#d31f3c', + 700: '#bb1534', + 800: '#9c1532', + 900: '#861531', + }, neutral: { + 50: '#f6f6f6', + 100: '#e7e7e7', + 200: '#d1d1d1', + 300: '#b0b0b0', + 400: '#888888', 500: '#1F1F1F', + 600: '#5d5d5d', + 700: '#4f4f4f', + 800: '#454545', + 900: '#3d3d3d', + }, + danger: { + "50": "#fefce8", + "100": "#fef9c3", + "200": "#fef08a", + "300": "#fde047", + "400": "#facc15", + "500": "#eab308", + "600": "#ca8a04", + "700": "#a16207", + "800": "#854d0e", + "900": "#713f12" } } } diff --git a/src/pages/UserStatsV2/UserStatsV2.tsx b/src/pages/UserStatsV2/UserStatsV2.tsx index 7c70883..5c9a9ce 100644 --- a/src/pages/UserStatsV2/UserStatsV2.tsx +++ b/src/pages/UserStatsV2/UserStatsV2.tsx @@ -7,6 +7,7 @@ import {Filter} from "@common/components"; import {useState} from "react"; import {useGameStatsStore} from "@common/stores/gameStatsStore"; import {GameMode} from "@common/models"; +import Header from "@common/components/Header"; export const UserStatsV2 = () => { const gameStatsStore = useGameStatsStore() @@ -14,33 +15,28 @@ export const UserStatsV2 = () => { return ( - - - - {activeTab !== "match-history" && gameStatsStore.setGameMode(x as GameMode)}/>} - {activeTab === "overview" && } - {activeTab === "weapons" && } - {activeTab === "match-history" && } - + +
+ + + {activeTab !== "match-history" && gameStatsStore.setGameMode(x as GameMode)}/>} + {activeTab === "overview" && } + {activeTab === "weapons" && } + {activeTab === "match-history" && } + ) } diff --git a/src/pages/UserStatsV2/components/sidebar/Sidebar.tsx b/src/pages/UserStatsV2/components/sidebar/Sidebar.tsx index a00581b..4f72d1c 100644 --- a/src/pages/UserStatsV2/components/sidebar/Sidebar.tsx +++ b/src/pages/UserStatsV2/components/sidebar/Sidebar.tsx @@ -29,10 +29,11 @@ export const Sidebar = ({ activeTab, setActiveTab }: SidebarProps) => { }, transition: 'transform 0.4s, width 0.4s', zIndex: 10000, - height: '100dvh', + height: '100vh', width: 'var(--Sidebar-width)', top: 0, - p: 2, + pr: 2, + pl: 2, flexShrink: 0, display: 'flex', flexDirection: 'column', @@ -64,13 +65,13 @@ export const Sidebar = ({ activeTab, setActiveTab }: SidebarProps) => { backgroundColor: 'var(--joy-palette-background-backdrop)', transition: 'opacity 0.4s', transform: { - xs: 'translateX(calc(100% * (var(--SideNavigation-slideIn, 0) - 1) + var(--SideNavigation-slideIn, 0) * var(--Sidebar-width, 0px)))', + xs: 'translateX(calc(100% * (var(--SideNavigation-slideIn, 0) - 1) + var(--SideNavigation-slideIn, 0) * calc(var(--Sidebar-width, 0px) + 33px)))', lg: 'translateX(-100%)', }, }} - onClick={() => closeSidebar} + onClick={() => closeSidebar()} /> - + The Finals Tracker { '--ListItem-radius': (theme) => theme.vars.radius.sm, }} > - setActiveTab("overview")}> + setActiveTab("overview")}> Overview @@ -101,7 +102,7 @@ export const Sidebar = ({ activeTab, setActiveTab }: SidebarProps) => { - setActiveTab("weapons")}> + setActiveTab("weapons")}> Weapons @@ -109,7 +110,7 @@ export const Sidebar = ({ activeTab, setActiveTab }: SidebarProps) => { - setActiveTab("match-history")}> + setActiveTab("match-history")}> Match History diff --git a/src/pages/UserStatsV2/components/sidebar/helpers.ts b/src/pages/UserStatsV2/components/sidebar/helpers.ts index d851a57..5d62987 100644 --- a/src/pages/UserStatsV2/components/sidebar/helpers.ts +++ b/src/pages/UserStatsV2/components/sidebar/helpers.ts @@ -1,26 +1,6 @@ -export function openSidebar() { - if (typeof document !== 'undefined') { - document.body.style.overflow = 'hidden'; - document.documentElement.style.setProperty('--SideNavigation-slideIn', '1'); - } -} - export function closeSidebar() { if (typeof document !== 'undefined') { document.documentElement.style.removeProperty('--SideNavigation-slideIn'); document.body.style.removeProperty('overflow'); } } - -export function toggleSidebar() { - if (typeof window !== 'undefined' && typeof document !== 'undefined') { - const slideIn = window - .getComputedStyle(document.documentElement) - .getPropertyValue('--SideNavigation-slideIn'); - if (slideIn) { - closeSidebar(); - } else { - openSidebar(); - } - } -} \ No newline at end of file diff --git a/src/pages/UserStatsV2/panels/matchHistoryPanel/RoundCard.tsx b/src/pages/UserStatsV2/panels/matchHistoryPanel/RoundCard.tsx index 5cc1f53..420bf68 100644 --- a/src/pages/UserStatsV2/panels/matchHistoryPanel/RoundCard.tsx +++ b/src/pages/UserStatsV2/panels/matchHistoryPanel/RoundCard.tsx @@ -2,7 +2,7 @@ import {AspectRatio, Card, CardContent, CardOverflow, Divider, Skeleton, Stack, import React from "react"; import {RoundCardText} from "./RoundCardText" import {getMapVariant} from "@common/util"; -import {TournamentRound} from "@common/stores/gameStatsStore"; +import {TournamentRound} from "@common/util/mapGameStats"; export interface RoundCardProps { data?: TournamentRound @@ -17,9 +17,9 @@ export const RoundCard = ({ data }: RoundCardProps) => { {data?.map @@ -31,7 +31,7 @@ export const RoundCard = ({ data }: RoundCardProps) => { textShadow: "1px 1px 5px white", position: "absolute", textAlign: "center" - }} level="h4" color={data.won ? "success" : "danger"}>{getMapVariant(data.map)} + }} level="h4" color={data.won ? "success" : "danger"}>{getMapVariant(data.mapVariant)} } diff --git a/src/pages/UserStatsV2/panels/matchHistoryPanel/TournamentCard.tsx b/src/pages/UserStatsV2/panels/matchHistoryPanel/TournamentCard.tsx index 3a43fd1..1586cc2 100644 --- a/src/pages/UserStatsV2/panels/matchHistoryPanel/TournamentCard.tsx +++ b/src/pages/UserStatsV2/panels/matchHistoryPanel/TournamentCard.tsx @@ -2,10 +2,10 @@ import {RoundCard} from "./RoundCard"; import {Divider, Stack, Table, Typography} from "@mui/joy"; import {useMemo} from "react"; import {getMapName} from "@common/util"; -import {TournamentStat} from "@common/stores/gameStatsStore"; +import {Tournament} from "@common/util/mapGameStats"; export interface TournamentCardProps { - data: TournamentStat + data: Tournament } const CARDS_IN_TOURNAMENT = 4 @@ -38,7 +38,7 @@ export const TournamentCard = ({ data }: TournamentCardProps) => { return ( - {getMapName(data.rounds[0].map)} + {getMapName(data.map)} ({data.id}) { - const { weapons } = useGameStatsStore() + const { loadoutItems } = useGameStatsStore() const [activeArchtypeFilters, setActiveArchtypeFilters] = useState("All") const [activeTypeFilters, setActiveTypeFilters] = useState("All") const filteredData = useMemo(() => - weapons.filter(data => + loadoutItems.filter(data => (activeArchtypeFilters === "All" || data.archetype === activeArchtypeFilters) && (activeTypeFilters === "All" || data.type === activeTypeFilters) ) - , [weapons, activeArchtypeFilters, activeTypeFilters]) + , [loadoutItems, activeArchtypeFilters, activeTypeFilters]) const archtypeFilters = [ "All", @@ -35,16 +35,16 @@ export const WeaponsPanel = observer(() => { ] const {disabledArchtype, disabledTypes} = useMemo(() => { - const archetypes = weapons + const archetypes = loadoutItems .map((data) => data.archetype) - const types = weapons + const types = loadoutItems .map((data) => data.type) return { disabledArchtype: [...archtypeFilters.filter(x => x !== "All" && !archetypes.includes(x as any))], disabledTypes: [...typeFilters.filter(x => x !== "All" && !types.includes(x as any))], } - }, [weapons]) + }, [loadoutItems]) return ( diff --git a/src/pages/homePage/HomePage.tsx b/src/pages/homePage/HomePage.tsx index ef4beea..faa48f7 100644 --- a/src/pages/homePage/HomePage.tsx +++ b/src/pages/homePage/HomePage.tsx @@ -1,68 +1,31 @@ -import {Button, FormControl, FormHelperText, Stack, Typography} from "@mui/joy"; -import InfoOutlined from "@mui/icons-material/InfoOutlined"; +import {Stack, Typography, useTheme} from "@mui/joy"; import {Box} from "@mui/material"; -import {ChangeEvent, useCallback, useState} from "react"; -import {useGameStatsStore} from "@common/stores/gameStatsStore"; +import Header from "@common/components/Header"; export const HomePage = () => { - const { setJson } = useGameStatsStore() - - const [fileError, setFileError] = useState() - - const onFileInput = useCallback((e: ChangeEvent) => { - const reader = new FileReader(); - setFileError(undefined) - - reader.readAsText(e.target.files![0], "UTF-8"); - reader.onload = event => { - try { - setJson(JSON.parse(event.target!.result as string)) - } catch (e) { - setFileError((e as Error).message) - } - } - reader.onerror = event => { - console.error(event) - setFileError("Error occured when parsing the file") - } - }, [setJson, setFileError]) + const theme = useTheme() return ( - - - The Finals Stats Tracker - - - - {fileError && {fileError}} - - - - - - - + +
+ + + THE FINALS TRACKER + )