fix lp_change event values and display in front
All checks were successful
record-daemon / Build, check and test (push) Successful in 3m14s
All checks were successful
record-daemon / Build, check and test (push) Successful in 3m14s
This commit is contained in:
@@ -124,16 +124,34 @@ pub fn describe_event(event_type: &str, raw_data: &serde_json::Value) -> String
|
|||||||
format!("Phase changed to: {}", phase)
|
format!("Phase changed to: {}", phase)
|
||||||
}
|
}
|
||||||
EVENT_TYPE_LP_CHANGE => {
|
EVENT_TYPE_LP_CHANGE => {
|
||||||
let lp_change = raw_data
|
// Prefer leaguePointsDelta from raw_data (actual LP change amount)
|
||||||
.get("lpChange")
|
let lp_delta = raw_data
|
||||||
|
.get("leaguePointsDelta")
|
||||||
.and_then(|v| v.as_i64())
|
.and_then(|v| v.as_i64())
|
||||||
|
.or_else(|| raw_data.get("lpChange").and_then(|v| v.as_i64()))
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
let tier = raw_data
|
let tier = raw_data
|
||||||
.get("tier")
|
.get("tier")
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.unwrap_or("UNRANKED");
|
.unwrap_or("UNRANKED");
|
||||||
let sign = if lp_change >= 0 { "+" } else { "" };
|
let division = raw_data
|
||||||
format!("LP Change: {}{} LP ({})", sign, lp_change, tier)
|
.get("division")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("");
|
||||||
|
let lp_after = raw_data
|
||||||
|
.get("leaguePoints")
|
||||||
|
.and_then(|v| v.as_i64())
|
||||||
|
.unwrap_or(0);
|
||||||
|
let sign = if lp_delta >= 0 { "+" } else { "" };
|
||||||
|
let rank = if division.is_empty() {
|
||||||
|
tier.to_string()
|
||||||
|
} else {
|
||||||
|
format!("{} {}", tier, division)
|
||||||
|
};
|
||||||
|
format!(
|
||||||
|
"LP Change: {}{} LP ({} → {} LP)",
|
||||||
|
sign, lp_delta, rank, lp_after
|
||||||
|
)
|
||||||
}
|
}
|
||||||
_ => "Unknown event".to_string(),
|
_ => "Unknown event".to_string(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ import {
|
|||||||
getFinalStats,
|
getFinalStats,
|
||||||
getSummonerSpells,
|
getSummonerSpells,
|
||||||
getItems,
|
getItems,
|
||||||
|
getLpChange,
|
||||||
|
formatLpDelta,
|
||||||
|
formatRank,
|
||||||
} from "../types/timeline";
|
} from "../types/timeline";
|
||||||
|
|
||||||
// Helper to get video timestamp in seconds from tuple format
|
// Helper to get video timestamp in seconds from tuple format
|
||||||
@@ -123,6 +126,13 @@ onMounted(() => {
|
|||||||
<!-- Result Banner -->
|
<!-- Result Banner -->
|
||||||
<div class="result-banner">
|
<div class="result-banner">
|
||||||
<span class="result-text">{{ getGameResult(game) }}</span>
|
<span class="result-text">{{ getGameResult(game) }}</span>
|
||||||
|
<span
|
||||||
|
v-if="getLpChange(game)"
|
||||||
|
class="lp-badge"
|
||||||
|
:class="{ gain: getLpChange(game)!.leaguePointsDelta > 0, loss: getLpChange(game)!.leaguePointsDelta < 0 }"
|
||||||
|
>
|
||||||
|
{{ formatLpDelta(getLpChange(game)!.leaguePointsDelta) }} LP
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
@@ -178,6 +188,10 @@ onMounted(() => {
|
|||||||
<div class="game-time">
|
<div class="game-time">
|
||||||
{{ formatRelativeTime(game.start_time) }}
|
{{ formatRelativeTime(game.start_time) }}
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="getLpChange(game)" class="game-rank">
|
||||||
|
{{ formatRank(getLpChange(game)!.tier, getLpChange(game)!.division) }}
|
||||||
|
<span class="game-rank-lp">· {{ getLpChange(game)!.leaguePoints }} LP</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right: KDA Score -->
|
<!-- Right: KDA Score -->
|
||||||
@@ -288,6 +302,22 @@ onMounted(() => {
|
|||||||
<span class="detail-label">Summoner:</span>
|
<span class="detail-label">Summoner:</span>
|
||||||
<span class="detail-value">{{ getSummonerName(selectedGame) }}</span>
|
<span class="detail-value">{{ getSummonerName(selectedGame) }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="detail-row" v-if="getLpChange(selectedGame)">
|
||||||
|
<span class="detail-label">Rank:</span>
|
||||||
|
<span class="detail-value">
|
||||||
|
{{ formatRank(getLpChange(selectedGame)!.tier, getLpChange(selectedGame)!.division) }}
|
||||||
|
· {{ getLpChange(selectedGame)!.leaguePoints }} LP
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-row" v-if="getLpChange(selectedGame)">
|
||||||
|
<span class="detail-label">LP Change:</span>
|
||||||
|
<span
|
||||||
|
class="detail-value lp-change"
|
||||||
|
:class="{ gain: getLpChange(selectedGame)!.leaguePointsDelta > 0, loss: getLpChange(selectedGame)!.leaguePointsDelta < 0 }"
|
||||||
|
>
|
||||||
|
{{ formatLpDelta(getLpChange(selectedGame)!.leaguePointsDelta) }} LP
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -537,6 +567,7 @@ onMounted(() => {
|
|||||||
padding: 0.35rem 0.75rem;
|
padding: 0.35rem 0.75rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.result-text {
|
.result-text {
|
||||||
@@ -546,6 +577,33 @@ onMounted(() => {
|
|||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lp-badge {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 0.1rem 0.4rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lp-badge.gain {
|
||||||
|
color: #4ade80;
|
||||||
|
background: rgba(74, 222, 128, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lp-badge.loss {
|
||||||
|
color: #f87171;
|
||||||
|
background: rgba(248, 113, 113, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-rank {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: #c8aa6e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-rank-lp {
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
/* Game Content */
|
/* Game Content */
|
||||||
.game-content {
|
.game-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -908,6 +966,16 @@ onMounted(() => {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.detail-value.lp-change.gain {
|
||||||
|
color: #4ade80;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-value.lp-change.loss {
|
||||||
|
color: #f87171;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
.stats-highlight {
|
.stats-highlight {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ import {
|
|||||||
getQueueId,
|
getQueueId,
|
||||||
getItemImageUrl,
|
getItemImageUrl,
|
||||||
getResultColor,
|
getResultColor,
|
||||||
|
getLpChange,
|
||||||
|
formatLpDelta,
|
||||||
|
formatRank,
|
||||||
} from "../types/timeline";
|
} from "../types/timeline";
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
@@ -508,6 +511,18 @@ onUnmounted(() => {
|
|||||||
<span class="result" :style="{ color: getResultColor(getGameResult(game)) }">
|
<span class="result" :style="{ color: getResultColor(getGameResult(game)) }">
|
||||||
{{ getGameResult(game) }}
|
{{ getGameResult(game) }}
|
||||||
</span>
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="getLpChange(game)"
|
||||||
|
class="lp-change-badge"
|
||||||
|
:class="{ gain: getLpChange(game)!.leaguePointsDelta > 0, loss: getLpChange(game)!.leaguePointsDelta < 0 }"
|
||||||
|
>
|
||||||
|
{{ formatLpDelta(getLpChange(game)!.leaguePointsDelta) }} LP
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="getLpChange(game)" class="header-rank">
|
||||||
|
{{ formatRank(getLpChange(game)!.tier, getLpChange(game)!.division) }}
|
||||||
|
· {{ getLpChange(game)!.leaguePoints }} LP
|
||||||
|
<span v-if="getLpChange(game)!.inPromos" class="promo-badge">In Promos</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -958,6 +973,40 @@ onUnmounted(() => {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lp-change-badge {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 0.1rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lp-change-badge.gain {
|
||||||
|
color: #4ade80;
|
||||||
|
background: rgba(74, 222, 128, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lp-change-badge.loss {
|
||||||
|
color: #f87171;
|
||||||
|
background: rgba(248, 113, 113, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-rank {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #c8aa6e;
|
||||||
|
margin-top: 0.15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.promo-badge {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #fbbf24;
|
||||||
|
background: rgba(251, 191, 36, 0.15);
|
||||||
|
padding: 0.1rem 0.35rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin-left: 0.35rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
.toggle-stats-btn {
|
.toggle-stats-btn {
|
||||||
background: rgba(255, 255, 255, 0.1);
|
background: rgba(255, 255, 255, 0.1);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
|||||||
@@ -23,6 +23,30 @@ export interface TimestampedEvent {
|
|||||||
uri: string;
|
uri: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LP change information extracted from an lp_change event's raw_data.
|
||||||
|
*/
|
||||||
|
export interface LpChangeInfo {
|
||||||
|
/** LP delta (positive = gain, negative = loss). */
|
||||||
|
leaguePointsDelta: number;
|
||||||
|
/** Current league points after the change. */
|
||||||
|
leaguePoints: number;
|
||||||
|
/** Tier (e.g. "EMERALD", "DIAMOND", "GOLD"). */
|
||||||
|
tier: string;
|
||||||
|
/** Division (e.g. "I", "II", "III", "IV"). */
|
||||||
|
division: string;
|
||||||
|
/** Queue type (e.g. "RANKED_SOLO_5x5", "RANKED_FLEX_SR"). */
|
||||||
|
queueType: string;
|
||||||
|
/** Total wins. */
|
||||||
|
wins: number;
|
||||||
|
/** Total losses. */
|
||||||
|
losses: number;
|
||||||
|
/** Current win streak. */
|
||||||
|
winStreak: number;
|
||||||
|
/** Whether the player is in promos. */
|
||||||
|
inPromos: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Final game statistics for the player.
|
* Final game statistics for the player.
|
||||||
*/
|
*/
|
||||||
@@ -377,6 +401,75 @@ export function getItems(game: GameHistoryItem): (ItemInfo | null)[] {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract LP change info from lp_change events in a game's timeline.
|
||||||
|
* Multiple lp_change events may exist — we pick the one with a ranked tier
|
||||||
|
* (non-UNRANKED) or a non-zero leaguePointsDelta, since the others record zeros.
|
||||||
|
* Returns null if no lp_change event is found or data is incomplete.
|
||||||
|
*/
|
||||||
|
export function getLpChange(game: GameHistoryItem): LpChangeInfo | null {
|
||||||
|
const lpEvents = game.events.filter(e => e.event_type === 'lp_change');
|
||||||
|
if (lpEvents.length === 0) return null;
|
||||||
|
|
||||||
|
// Prefer the event with a non-UNRANKED tier (the actual ranked one)
|
||||||
|
let lpEvent = lpEvents.find(e => {
|
||||||
|
const raw = e.raw_data as Record<string, unknown>;
|
||||||
|
return raw?.tier && raw.tier !== 'UNRANKED';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fallback: pick the event with a non-zero leaguePointsDelta
|
||||||
|
if (!lpEvent) {
|
||||||
|
lpEvent = lpEvents.find(e => {
|
||||||
|
const raw = e.raw_data as Record<string, unknown>;
|
||||||
|
return typeof raw?.leaguePointsDelta === 'number' && raw.leaguePointsDelta !== 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last resort: use the first event
|
||||||
|
if (!lpEvent) {
|
||||||
|
lpEvent = lpEvents[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const raw = lpEvent.raw_data as Record<string, unknown>;
|
||||||
|
if (!raw) return null;
|
||||||
|
|
||||||
|
// Use leaguePointsDelta from raw_data (the actual LP change amount)
|
||||||
|
const leaguePointsDelta = typeof raw.leaguePointsDelta === 'number' ? raw.leaguePointsDelta : null;
|
||||||
|
if (leaguePointsDelta === null) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
leaguePointsDelta,
|
||||||
|
leaguePoints: typeof raw.leaguePoints === 'number' ? raw.leaguePoints : 0,
|
||||||
|
tier: typeof raw.tier === 'string' ? raw.tier : 'UNRANKED',
|
||||||
|
division: typeof raw.division === 'string' ? raw.division : '',
|
||||||
|
queueType: typeof raw.queueType === 'string' ? raw.queueType : '',
|
||||||
|
wins: typeof raw.wins === 'number' ? raw.wins : 0,
|
||||||
|
losses: typeof raw.losses === 'number' ? raw.losses : 0,
|
||||||
|
winStreak: typeof raw.winStreak === 'number' ? raw.winStreak : 0,
|
||||||
|
inPromos: typeof raw.miniseriesProgress === 'string' ? raw.miniseriesProgress.length > 0 : false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format an LP delta as a signed string (e.g. "+19", "-15").
|
||||||
|
*/
|
||||||
|
export function formatLpDelta(delta: number): string {
|
||||||
|
if (delta > 0) return `+${delta}`;
|
||||||
|
return `${delta}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a rank string from tier and division (e.g. "Emerald II", "Diamond I").
|
||||||
|
*/
|
||||||
|
export function formatRank(tier: string, division: string): string {
|
||||||
|
if (tier === 'UNRANKED' || !tier) return 'Unranked';
|
||||||
|
// Capitalize first letter of tier, lowercase rest
|
||||||
|
const tierDisplay = tier.charAt(0) + tier.slice(1).toLowerCase();
|
||||||
|
if (!division) return tierDisplay;
|
||||||
|
// Convert Roman numerals: I, II, III, IV
|
||||||
|
return `${tierDisplay} ${division}`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the result color class.
|
* Get the result color class.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user