mirror of
https://github.com/vkaelin/LeagueStats.git
synced 2026-03-25 12:57:28 +00:00
feat: add live game tab back
This commit is contained in:
parent
0b0aa48e69
commit
5a7c38281f
11 changed files with 249 additions and 42 deletions
|
|
@ -30,11 +30,11 @@
|
|||
class="flex flex-col items-center runes"
|
||||
>
|
||||
<div
|
||||
:style="{backgroundImage: `url('${player.runes.primaryRune}')`}"
|
||||
:style="{backgroundImage: `url('${getPrimarRune(player.perks)}')`}"
|
||||
class="w-6 h-6 bg-center bg-cover"
|
||||
></div>
|
||||
<div
|
||||
:style="{backgroundImage: `url('${player.runes.secondaryRune}')`}"
|
||||
:style="{backgroundImage: `url('${getSecondaryRune(player.perks)}')`}"
|
||||
class="w-3 h-3 mt-1 bg-center bg-cover"
|
||||
></div>
|
||||
</div>
|
||||
|
|
@ -72,7 +72,11 @@
|
|||
:class="[player.summonerId === account.id ? 'text-yellow-500' : 'hover:text-blue-200']"
|
||||
class="font-semibold"
|
||||
>{{ player.summonerName }}</router-link>
|
||||
<div class="text-xs">Level {{ player.level }}</div>
|
||||
<div
|
||||
:class="[ally ? 'text-teal-300 ' : 'text-red-400 ']"
|
||||
class="text-xs"
|
||||
>{{ player.champion.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
|
@ -185,7 +189,7 @@
|
|||
|
||||
<script>
|
||||
import { mapActions, mapState } from 'vuex'
|
||||
import { getSummonerLink } from '@/helpers/summoner.js'
|
||||
import { getSummonerLink, getPrimarRune, getSecondaryRune } from '@/helpers/summoner.js'
|
||||
import { ContentLoader } from 'vue-content-loader'
|
||||
|
||||
export default {
|
||||
|
|
@ -264,17 +268,13 @@ export default {
|
|||
}
|
||||
},
|
||||
selectRunes(player) {
|
||||
if(!player.perks) {
|
||||
if(!player.perks)
|
||||
return
|
||||
}
|
||||
|
||||
this.displayOrHideRunes({
|
||||
primaryStyle: player.perks.perkStyle,
|
||||
secondaryStyle: player.perks.perkSubStyle,
|
||||
selected: player.perks.perkIds
|
||||
})
|
||||
this.displayOrHideRunes(player.perks)
|
||||
},
|
||||
getSummonerLink,
|
||||
getPrimarRune,
|
||||
getSecondaryRune,
|
||||
...mapActions('cdragon', ['displayOrHideRunes']),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,17 +6,21 @@ import store from '@/store'
|
|||
const leaguesNumbers = { 'I': 1, 'II': 2, 'III': 3, 'IV': 4 }
|
||||
|
||||
/**
|
||||
* Get the url of the of the player runes
|
||||
* Get the url of the of the player primary rune
|
||||
* @param {Object} perks : from the API
|
||||
*/
|
||||
export function getPrimaryAndSecondaryRune(perks) {
|
||||
export function getPrimarRune(perks) {
|
||||
const primaryRune = perks.selected.length ? store.state.cdragon.runes.perks[perks.selected[0]] : null
|
||||
const secondaryRune = store.state.cdragon.runes.perkstyles[perks.secondaryStyle]
|
||||
return primaryRune ? createCDragonAssetUrl(primaryRune.icon) : null
|
||||
}
|
||||
|
||||
return {
|
||||
primaryRune: primaryRune ? createCDragonAssetUrl(primaryRune.icon) : null,
|
||||
secondaryRune: secondaryRune ? createCDragonAssetUrl(secondaryRune.icon) : null
|
||||
}
|
||||
/**
|
||||
* Get the url of the of the player secondary rune
|
||||
* @param {Object} perks : from the API
|
||||
*/
|
||||
export function getSecondaryRune(perks) {
|
||||
const secondaryRune = store.state.cdragon.runes.perkstyles[perks.secondaryStyle]
|
||||
return secondaryRune ? createCDragonAssetUrl(secondaryRune.icon) : null
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -26,9 +30,8 @@ export function getPrimaryAndSecondaryRune(perks) {
|
|||
export function createMatchData(matches) {
|
||||
for (const match of matches) {
|
||||
// Runes
|
||||
const runes = getPrimaryAndSecondaryRune(match.perks)
|
||||
match.primaryRune = runes.primaryRune
|
||||
match.secondaryRune = runes.secondaryRune
|
||||
match.primaryRune = getPrimarRune(match.perks)
|
||||
match.secondaryRune = getSecondaryRune(match.perks)
|
||||
|
||||
const date = new Date(match.date)
|
||||
const dateOptions = { day: '2-digit', month: '2-digit', year: 'numeric' }
|
||||
|
|
|
|||
|
|
@ -59,6 +59,8 @@ export default {
|
|||
|
||||
created() {
|
||||
this.fetchData()
|
||||
|
||||
this.getRunes()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
|
@ -67,6 +69,7 @@ export default {
|
|||
this.liveMatchRequest()
|
||||
}
|
||||
},
|
||||
...mapActions('cdragon', ['getRunes']),
|
||||
...mapActions('summoner', ['liveMatchRequest']),
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,13 @@ import { getCurrentSeason } from 'App/helpers'
|
|||
import Summoner from 'App/Models/Summoner'
|
||||
import MatchRepository from 'App/Repositories/MatchRepository'
|
||||
import BasicMatchSerializer from 'App/Serializers/BasicMatchSerializer'
|
||||
import LiveMatchSerializer from 'App/Serializers/LiveMatchSerializer'
|
||||
import Jax from 'App/Services/Jax'
|
||||
import MatchService from 'App/Services/MatchService'
|
||||
import StatsService from 'App/Services/StatsService'
|
||||
import SummonerService from 'App/Services/SummonerService'
|
||||
import SummonerBasicValidator from 'App/Validators/SummonerBasicValidator'
|
||||
import SummonerLiveValidator from 'App/Validators/SummonerLiveValidator'
|
||||
import SummonerOverviewValidator from 'App/Validators/SummonerOverviewValidator'
|
||||
import SummonerRecordValidator from 'App/Validators/SummonerRecordValidator'
|
||||
|
||||
|
|
@ -104,4 +106,25 @@ export default class SummonersController {
|
|||
console.timeEnd('recordsRequest')
|
||||
return response.json(recordsSerialized)
|
||||
}
|
||||
|
||||
/**
|
||||
* POST - Return live match detail
|
||||
* @param ctx
|
||||
*/
|
||||
public async liveMatchDetails({ request, response }: HttpContextContract) {
|
||||
console.time('liveMatchDetails')
|
||||
const { id, region } = await request.validate(SummonerLiveValidator)
|
||||
|
||||
// CURRENT GAME
|
||||
const currentGame = await Jax.Spectator.summonerID(id, region)
|
||||
|
||||
if (!currentGame) {
|
||||
return response.json(null)
|
||||
}
|
||||
|
||||
const currentGameSerialized = await LiveMatchSerializer.serializeOneMatch(currentGame, region)
|
||||
console.timeEnd('liveMatchDetails')
|
||||
|
||||
return response.json(currentGameSerialized)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
81
server-v2/app/Serializers/LiveMatchSerializer.ts
Normal file
81
server-v2/app/Serializers/LiveMatchSerializer.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import { PlayerRole, queuesWithRole } from 'App/helpers'
|
||||
import CDragonService from 'App/Services/CDragonService'
|
||||
import { CurrentGameInfoDTO } from 'App/Services/Jax/src/Endpoints/SpectatorEndpoint'
|
||||
import { RoleComposition } from 'App/Services/RoleIdentificationService'
|
||||
import SummonerService from 'App/Services/SummonerService'
|
||||
import MatchSerializer from './MatchSerializer'
|
||||
import { SerializedLiveMatch, SerializedLiveMatchPlayer } from './SerializedTypes'
|
||||
|
||||
class LiveMatchSerializer extends MatchSerializer {
|
||||
public async serializeOneMatch(
|
||||
liveMatch: CurrentGameInfoDTO,
|
||||
region: string
|
||||
): Promise<SerializedLiveMatch> {
|
||||
// Roles
|
||||
const blueTeam: PlayerRole[] = [] // 100
|
||||
const redTeam: PlayerRole[] = [] // 200
|
||||
let blueRoles: RoleComposition = {}
|
||||
let redRoles: RoleComposition = {}
|
||||
const needsRole =
|
||||
CDragonService.championRoles &&
|
||||
(queuesWithRole.includes(liveMatch.gameQueueConfigId) ||
|
||||
(liveMatch.gameType === 'CUSTOM_GAME' && liveMatch.participants.length === 10))
|
||||
|
||||
if (needsRole) {
|
||||
liveMatch.participants.map((p) => {
|
||||
const playerRole = {
|
||||
champion: p.championId,
|
||||
jungle: p.spell1Id === 11 || p.spell2Id === 11,
|
||||
}
|
||||
p.teamId === 100 ? blueTeam.push(playerRole) : redTeam.push(playerRole)
|
||||
})
|
||||
|
||||
blueRoles = super.getTeamRoles(blueTeam)
|
||||
redRoles = super.getTeamRoles(redTeam)
|
||||
}
|
||||
|
||||
// Ranks
|
||||
const requestsRanks = liveMatch.participants.map((p) =>
|
||||
SummonerService.getRanked(p.summonerId, region)
|
||||
)
|
||||
const ranks = await Promise.all(requestsRanks)
|
||||
|
||||
// Players
|
||||
const players: SerializedLiveMatchPlayer[] = liveMatch.participants.map((player, index) => {
|
||||
let role: string | undefined
|
||||
|
||||
// Roles
|
||||
if (needsRole) {
|
||||
const roles = player.teamId === 100 ? blueRoles : redRoles
|
||||
role = Object.entries(roles).find(([, champion]) => player.championId === champion)![0]
|
||||
}
|
||||
return {
|
||||
...player,
|
||||
role,
|
||||
rank: ranks[index],
|
||||
champion: this.getChampion(player.championId),
|
||||
perks: {
|
||||
primaryStyle: player.perks.perkStyle,
|
||||
secondaryStyle: player.perks.perkSubStyle,
|
||||
selected: player.perks.perkIds,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
gameId: liveMatch.gameId,
|
||||
gameType: liveMatch.gameType,
|
||||
gameStartTime: liveMatch.gameStartTime,
|
||||
mapId: liveMatch.mapId,
|
||||
gameLength: liveMatch.gameLength,
|
||||
platformId: liveMatch.platformId,
|
||||
gameMode: liveMatch.gameMode,
|
||||
bannedChampions: liveMatch.bannedChampions,
|
||||
gameQueueConfigId: liveMatch.gameQueueConfigId,
|
||||
observers: liveMatch.observers,
|
||||
participants: players,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new LiveMatchSerializer()
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
import { PlayerRole } from 'App/helpers'
|
||||
import MatchPlayer from 'App/Models/MatchPlayer'
|
||||
import MatchPlayerRank from 'App/Models/MatchPlayerRank'
|
||||
import { PlayerRankParsed, TeamPosition } from 'App/Parsers/ParsedType'
|
||||
import CDragonService from 'App/Services/CDragonService'
|
||||
import RoleIdentificationService, { RoleComposition } from 'App/Services/RoleIdentificationService'
|
||||
import SummonerService from 'App/Services/SummonerService'
|
||||
import {
|
||||
SerializedBasePlayer,
|
||||
|
|
@ -112,4 +114,22 @@ export default abstract class MatchSerializer {
|
|||
shortName: SummonerService.getRankedShortName(rank),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the 5 roles of a team based on champions
|
||||
* @param team 5 champions + smite from a team
|
||||
*/
|
||||
protected getTeamRoles(team: PlayerRole[]): RoleComposition {
|
||||
const teamJunglers = team.filter((p) => p.jungle && !p.support)
|
||||
const jungle = teamJunglers.length === 1 ? teamJunglers[0].champion : undefined
|
||||
const teamSupports = team.filter((p) => p.support && !p.jungle)
|
||||
const support = teamSupports.length === 1 ? teamSupports[0].champion : undefined
|
||||
|
||||
return RoleIdentificationService.getRoles(
|
||||
CDragonService.championRoles,
|
||||
team.map((p) => p.champion),
|
||||
jungle,
|
||||
support
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
import {
|
||||
CurrentGameInfoDTO,
|
||||
GameCustomizationObjectDTO,
|
||||
} from 'App/Services/Jax/src/Endpoints/SpectatorEndpoint'
|
||||
import { LeagueEntriesByQueue } from 'App/Services/SummonerService'
|
||||
|
||||
export interface SerializedBasePlayer {
|
||||
champion: SerializedMatchChampion
|
||||
items: Array<SerializedMatchItem | null>
|
||||
|
|
@ -178,3 +184,28 @@ export interface SerializedPlayerRank {
|
|||
losses: number
|
||||
shortName: number | string
|
||||
}
|
||||
|
||||
/* ============================
|
||||
|
||||
Live Match
|
||||
|
||||
============================ */
|
||||
export interface SerializedLiveMatch extends Omit<CurrentGameInfoDTO, 'participants'> {
|
||||
participants: SerializedLiveMatchPlayer[]
|
||||
}
|
||||
|
||||
export interface SerializedLiveMatchPlayer {
|
||||
bot: boolean
|
||||
champion: SerializedMatchChampion
|
||||
championId: number
|
||||
gameCustomizationObjects: GameCustomizationObjectDTO[]
|
||||
perks: SerializedMatchPerks
|
||||
profileIconId: number
|
||||
rank: LeagueEntriesByQueue
|
||||
role?: string
|
||||
spell1Id: number
|
||||
spell2Id: number
|
||||
summonerId: string
|
||||
summonerName: string
|
||||
teamId: number
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter'
|
||||
import RiotRateLimiter from 'riot-ratelimiter'
|
||||
import { LeagueEntriesByQueue } from 'App/Services/SummonerService'
|
||||
import { JaxConfig } from '../../JaxConfig'
|
||||
import JaxRequest from '../JaxRequest'
|
||||
|
||||
export interface CurrentGameInfo {
|
||||
export interface CurrentGameInfoDTO {
|
||||
gameId: number
|
||||
gameType: string
|
||||
gameStartTime: number
|
||||
|
|
@ -12,25 +11,25 @@ export interface CurrentGameInfo {
|
|||
gameLength: number
|
||||
platformId: string
|
||||
gameMode: string
|
||||
bannedChampions: BannedChampion[]
|
||||
bannedChampions: BannedChampionDTO[]
|
||||
gameQueueConfigId: number
|
||||
observers: Observer
|
||||
participants: CurrentGameParticipant[]
|
||||
observers: ObserverDTO
|
||||
participants: CurrentGameParticipantDTO[]
|
||||
}
|
||||
|
||||
export interface BannedChampion {
|
||||
export interface BannedChampionDTO {
|
||||
pickTurn: number
|
||||
championId: number
|
||||
teamId: number
|
||||
}
|
||||
|
||||
export interface Observer {
|
||||
export interface ObserverDTO {
|
||||
encryptionKey: string
|
||||
}
|
||||
|
||||
export interface CurrentGameParticipant {
|
||||
export interface CurrentGameParticipantDTO {
|
||||
championId: number
|
||||
perks: Perks
|
||||
perks: PerksDTO
|
||||
profileIconId: number
|
||||
bot: boolean
|
||||
teamId: number
|
||||
|
|
@ -38,21 +37,16 @@ export interface CurrentGameParticipant {
|
|||
summonerId: string
|
||||
spell1Id: number
|
||||
spell2Id: number
|
||||
gameCustomizationObjects: GameCustomizationObject[]
|
||||
// Custom types from here
|
||||
role?: string
|
||||
runes?: { primaryRune: string; secondaryRune: string } | {}
|
||||
level?: number
|
||||
rank?: LeagueEntriesByQueue
|
||||
gameCustomizationObjects: GameCustomizationObjectDTO[]
|
||||
}
|
||||
|
||||
export interface Perks {
|
||||
export interface PerksDTO {
|
||||
perkIds: number[]
|
||||
perkStyle: number
|
||||
perkSubStyle: number
|
||||
}
|
||||
|
||||
export interface GameCustomizationObject {
|
||||
export interface GameCustomizationObjectDTO {
|
||||
category: string
|
||||
content: string
|
||||
}
|
||||
|
|
@ -66,7 +60,7 @@ export default class SpectatorEndpoint {
|
|||
this.limiter = limiter
|
||||
}
|
||||
|
||||
public summonerID(summonerID: string, region: string): Promise<CurrentGameInfo | undefined> {
|
||||
public summonerID(summonerID: string, region: string): Promise<CurrentGameInfoDTO | undefined> {
|
||||
return new JaxRequest(
|
||||
region,
|
||||
this.config,
|
||||
|
|
|
|||
43
server-v2/app/Validators/SummonerLiveValidator.ts
Normal file
43
server-v2/app/Validators/SummonerLiveValidator.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { schema } from '@ioc:Adonis/Core/Validator'
|
||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||
|
||||
export default class SummonerLiveValidator {
|
||||
constructor(protected ctx: HttpContextContract) {}
|
||||
|
||||
/*
|
||||
* Define schema to validate the "shape", "type", "formatting" and "integrity" of data.
|
||||
*
|
||||
* For example:
|
||||
* 1. The username must be of data type string. But then also, it should
|
||||
* not contain special characters or numbers.
|
||||
* ```
|
||||
* schema.string({}, [ rules.alpha() ])
|
||||
* ```
|
||||
*
|
||||
* 2. The email must be of data type string, formatted as a valid
|
||||
* email. But also, not used by any other user.
|
||||
* ```
|
||||
* schema.string({}, [
|
||||
* rules.email(),
|
||||
* rules.unique({ table: 'users', column: 'email' }),
|
||||
* ])
|
||||
* ```
|
||||
*/
|
||||
public schema = schema.create({
|
||||
id: schema.string(),
|
||||
region: schema.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Custom messages for validation failures. You can make use of dot notation `(.)`
|
||||
* for targeting nested fields and array expressions `(*)` for targeting all
|
||||
* children of an array. For example:
|
||||
*
|
||||
* {
|
||||
* 'profile.username.required': 'Username is required',
|
||||
* 'scores.*.number': 'Define scores as valid numbers'
|
||||
* }
|
||||
*
|
||||
*/
|
||||
public messages = {}
|
||||
}
|
||||
|
|
@ -50,6 +50,15 @@ export function getRiotRegion(region: string): RiotRegion {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to help define a player's role
|
||||
*/
|
||||
export interface PlayerRole {
|
||||
champion: number
|
||||
jungle?: boolean
|
||||
support?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* League of Legends queues with defined role for each summoner
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ Route.post('/summoner/overview', 'SummonersController.overview')
|
|||
|
||||
// Route.post('/summoner/champions', 'SummonersController.champions')
|
||||
Route.post('/summoner/records', 'SummonersController.records')
|
||||
// Route.post('/summoner/live', 'SummonersController.liveMatchDetails')
|
||||
Route.post('/summoner/live', 'SummonersController.liveMatchDetails')
|
||||
|
||||
Route.post('/match', 'MatchesController.index')
|
||||
Route.post('/match/details', 'MatchesController.show')
|
||||
|
|
|
|||
Loading…
Reference in a new issue