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:
@@ -30,6 +30,9 @@ import {
|
||||
getFinalStats,
|
||||
getSummonerSpells,
|
||||
getItems,
|
||||
getLpChange,
|
||||
formatLpDelta,
|
||||
formatRank,
|
||||
} from "../types/timeline";
|
||||
|
||||
// Helper to get video timestamp in seconds from tuple format
|
||||
@@ -123,6 +126,13 @@ onMounted(() => {
|
||||
<!-- Result Banner -->
|
||||
<div class="result-banner">
|
||||
<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>
|
||||
|
||||
<!-- Main Content -->
|
||||
@@ -178,6 +188,10 @@ onMounted(() => {
|
||||
<div class="game-time">
|
||||
{{ formatRelativeTime(game.start_time) }}
|
||||
</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>
|
||||
|
||||
<!-- Right: KDA Score -->
|
||||
@@ -288,6 +302,22 @@ onMounted(() => {
|
||||
<span class="detail-label">Summoner:</span>
|
||||
<span class="detail-value">{{ getSummonerName(selectedGame) }}</span>
|
||||
</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>
|
||||
|
||||
@@ -537,6 +567,7 @@ onMounted(() => {
|
||||
padding: 0.35rem 0.75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.result-text {
|
||||
@@ -546,6 +577,33 @@ onMounted(() => {
|
||||
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 {
|
||||
display: flex;
|
||||
@@ -908,6 +966,16 @@ onMounted(() => {
|
||||
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 {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
@@ -14,6 +14,9 @@ import {
|
||||
getQueueId,
|
||||
getItemImageUrl,
|
||||
getResultColor,
|
||||
getLpChange,
|
||||
formatLpDelta,
|
||||
formatRank,
|
||||
} from "../types/timeline";
|
||||
|
||||
// Props
|
||||
@@ -508,6 +511,18 @@ onUnmounted(() => {
|
||||
<span class="result" :style="{ color: getResultColor(getGameResult(game)) }">
|
||||
{{ getGameResult(game) }}
|
||||
</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>
|
||||
@@ -958,6 +973,40 @@ onUnmounted(() => {
|
||||
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 {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
|
||||
@@ -23,6 +23,30 @@ export interface TimestampedEvent {
|
||||
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.
|
||||
*/
|
||||
@@ -377,6 +401,75 @@ export function getItems(game: GameHistoryItem): (ItemInfo | null)[] {
|
||||
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.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user