From b09f669e7353ef423b647c628b309633b3efbbc9 Mon Sep 17 00:00:00 2001 From: Valentin Haudiquet Date: Thu, 26 Mar 2026 23:42:31 +0100 Subject: [PATCH] tauri-app: fix end-of-game stats reading, pass raw json file to front --- tauri-app/src-tauri/src/lib.rs | 181 ++--------------------- tauri-app/src/components/GameHistory.vue | 8 +- tauri-app/src/types/timeline.ts | 21 +-- 3 files changed, 30 insertions(+), 180 deletions(-) diff --git a/tauri-app/src-tauri/src/lib.rs b/tauri-app/src-tauri/src/lib.rs index e662d51..5b8e448 100644 --- a/tauri-app/src-tauri/src/lib.rs +++ b/tauri-app/src-tauri/src/lib.rs @@ -1,163 +1,6 @@ -use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; +use serde_json::Value; use std::fs; use std::path::PathBuf; -use uuid::Uuid; - -/// A timestamped event in the timeline. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TimestampedEvent { - /// Video timestamp (offset from recording start) as [seconds, nanos]. - pub video_timestamp: (i64, i32), - /// Game timestamp (in-game time) as [seconds, nanos]. - pub game_timestamp: Option<(i64, i32)>, - /// Real-world timestamp. - pub timestamp: DateTime, - /// Event type name. - pub event_type: String, - /// Human-readable description. - pub description: String, -} - -impl TimestampedEvent { - /// Get video timestamp in seconds. - pub fn video_timestamp_secs(&self) -> i64 { - self.video_timestamp.0 - } -} - -/// Final game statistics for the player. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct GameFinalStats { - pub kills: u32, - pub deaths: u32, - pub assists: u32, - pub creep_score: u32, - pub gold_earned: u32, - pub damage_dealt: u64, - pub damage_taken: u64, - pub vision_score: f64, - pub game_duration: f64, -} - -/// Rune page configuration. -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RunePage { - pub primary_style_id: u32, - pub secondary_style_id: u32, - pub selected_perks: Vec, - #[serde(default)] - pub stat_modifiers: Vec, - pub name: Option, - #[serde(default)] - pub current: bool, -} - -/// Summoner spell information. -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SummonerSpells { - pub spell1_id: u32, - pub spell2_id: u32, - pub spell1_name: Option, - pub spell2_name: Option, -} - -/// Individual item information. -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ItemInfo { - pub item_id: u32, - pub name: Option, - pub slot: u32, -} - -/// Item build at game end. -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ItemBuild { - pub items: Vec, - pub trinket: Option, -} - -/// Player identity information. -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct PlayerIdentityInfo { - pub puuid: String, - #[serde(default)] - pub summoner_name: String, - pub champion_name: Option, - pub team: Option, -} - -/// A timeline of events for a recording. -/// This matches the full JSON structure from record-daemon. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Timeline { - /// Recording ID. - pub recording_id: Uuid, - /// Recording start time. - pub start_time: DateTime, - /// Recording end time. - pub end_time: Option>, - /// Total duration in seconds. - pub duration_secs: i64, - /// Events in the timeline. - pub events: Vec, - /// Champion played. - #[serde(default)] - pub champion: Option, - /// Skin name. - #[serde(default)] - pub skin_name: Option, - /// Queue type. - #[serde(default)] - pub queue_type: Option, - /// Queue ID. - #[serde(default)] - pub queue_id: Option, - /// Game mode. - #[serde(default)] - pub game_mode: Option, - /// Map name. - #[serde(default)] - pub map_name: Option, - /// Summoner name. - #[serde(default)] - pub summoner_name: Option, - /// Player's PUUID. - #[serde(default)] - pub puuid: Option, - /// Team (100 = blue, 200 = red). - #[serde(default)] - pub team: Option, - /// Whether the game was won. - #[serde(default)] - pub victory: Option, - /// Final player stats. - #[serde(default)] - pub final_stats: Option, - /// Game ID. - #[serde(default)] - pub game_id: Option, - /// Match ID. - #[serde(default)] - pub match_id: Option, - /// Rune page at game start. - #[serde(default)] - pub runes: Option, - /// Summoner spells. - #[serde(default)] - pub summoner_spells: Option, - /// Final item build at game end. - #[serde(default)] - pub final_items: Option, - /// All players in the game. - #[serde(default)] - pub all_players: Vec, -} /// Get the default output directory for recordings. /// Uses the same directory structure as the record-daemon. @@ -174,8 +17,8 @@ fn get_timeline_dir() -> PathBuf { .unwrap_or_else(|| PathBuf::from("./recordings/timelines")) } -/// Load all timelines from disk. -fn load_timelines() -> Vec { +/// Load all timelines from disk as raw JSON values. +fn load_timelines() -> Vec { let timeline_dir = get_timeline_dir(); let mut timelines = Vec::new(); @@ -188,7 +31,7 @@ fn load_timelines() -> Vec { let path = entry.path(); if path.extension().map(|e| e == "json").unwrap_or(false) { if let Ok(contents) = fs::read_to_string(&path) { - if let Ok(timeline) = serde_json::from_str::(&contents) { + if let Ok(timeline) = serde_json::from_str::(&contents) { timelines.push(timeline); } } @@ -197,25 +40,29 @@ fn load_timelines() -> Vec { } // Sort by start time, newest first - timelines.sort_by(|a, b| b.start_time.cmp(&a.start_time)); + timelines.sort_by(|a, b| { + let a_time = a.get("start_time").and_then(|v| v.as_str()).unwrap_or(""); + let b_time = b.get("start_time").and_then(|v| v.as_str()).unwrap_or(""); + b_time.cmp(a_time) + }); timelines } -/// Get game history - returns full timeline data for each game. +/// Get game history - returns full timeline data for each game as raw JSON. #[tauri::command] -fn get_game_history() -> Vec { +fn get_game_history() -> Vec { load_timelines() } -/// Get a specific timeline by recording ID. +/// Get a specific timeline by recording ID as raw JSON. #[tauri::command] -fn get_timeline(recording_id: String) -> Option { +fn get_timeline(recording_id: String) -> Option { let timeline_dir = get_timeline_dir(); let path = timeline_dir.join(format!("{}.json", recording_id)); if path.exists() { if let Ok(contents) = fs::read_to_string(&path) { - return serde_json::from_str::(&contents).ok(); + return serde_json::from_str::(&contents).ok(); } } diff --git a/tauri-app/src/components/GameHistory.vue b/tauri-app/src/components/GameHistory.vue index 154f663..90226f8 100644 --- a/tauri-app/src/components/GameHistory.vue +++ b/tauri-app/src/components/GameHistory.vue @@ -53,9 +53,9 @@ function closeDetail() { function getLocalPlayer(stats: RawEndGameStats | null): EndGamePlayer | null { if (!stats) return null; - // Try local_player field first - if (stats.local_player) { - return stats.local_player; + // Try localPlayer field first (camelCase from API) + if (stats.localPlayer) { + return stats.localPlayer; } // Try teams @@ -63,7 +63,7 @@ function getLocalPlayer(stats: RawEndGameStats | null): EndGamePlayer | null { for (const team of stats.teams) { if (team.players) { for (const player of team.players) { - if (player.is_local_player) { + if (player.isLocalPlayer) { return player; } } diff --git a/tauri-app/src/types/timeline.ts b/tauri-app/src/types/timeline.ts index 1f85342..d188c91 100644 --- a/tauri-app/src/types/timeline.ts +++ b/tauri-app/src/types/timeline.ts @@ -180,30 +180,32 @@ export interface Timeline { /** * Raw end-of-game stats from the League Client API. * This is the full response from the end-of-game stats endpoint. + * Note: Properties use camelCase to match the actual API response. */ export interface RawEndGameStats { /** Game ID. */ - game_id?: number; + gameId?: number; /** Game length in seconds. */ - game_length?: number; + gameLength?: number; /** Match ID. */ - match_id?: number; + matchId?: number | null; /** Game result. */ - game_result?: string; + gameResult?: string | null; /** Local player data. */ - local_player?: EndGamePlayer; + localPlayer?: EndGamePlayer; /** Teams data. */ teams?: EndGameTeam[]; /** Players (legacy format). */ - players?: EndGamePlayer[]; + players?: EndGamePlayer[] | null; } /** * Player in end-of-game stats. + * Note: Properties use camelCase to match the actual API response. */ export interface EndGamePlayer { /** Whether this is the local player. */ - is_local_player?: boolean; + isLocalPlayer?: boolean; /** Player stats. */ stats?: PlayerStats; /** Items (array of item IDs). */ @@ -212,12 +214,13 @@ export interface EndGamePlayer { /** * Team in end-of-game stats. + * Note: Properties use camelCase to match the actual API response. */ export interface EndGameTeam { /** Whether this is the player's team. */ - is_player_team?: boolean; + isPlayerTeam?: boolean; /** Whether this is the winning team. */ - is_winning_team?: boolean; + isWinningTeam?: boolean; /** Players on the team. */ players?: EndGamePlayer[]; }