feat: live game tab

This commit is contained in:
Valentin Kaelin 2020-01-14 22:04:45 +01:00
parent 305818be23
commit b0169576c9
14 changed files with 484 additions and 75 deletions

View file

@ -22,4 +22,31 @@
rgba(44, 82, 130, 0) 45%
);
}
.ban::after {
content: "";
position: absolute;
left: 0px;
top: 50%;
width: calc(100% + 1px);
height: 2px;
transform: rotate(-45deg);
}
.ban-blue::after {
background: #38b2ac;
}
.ban-red::after {
background: #f56565;
}
.ban-img {
filter: grayscale(100%);
}
.ban-order {
left: -7px;
top: -5px;
}
/* purgecss end ignore */

View file

@ -104,32 +104,3 @@ export default {
}
}
</script>
<style scoped>
.ban::after {
content: "";
position: absolute;
left: 0px;
top: 50%;
width: calc(100% + 1px);
height: 2px;
transform: rotate(-45deg);
}
.ban-blue::after {
background: #38b2ac;
}
.ban-red::after {
background: #f56565;
}
.ban-img {
filter: grayscale(100%);
}
.ban-order {
left: -7px;
top: -5px;
}
</style>

View file

@ -51,46 +51,10 @@
</template>
<script>
import { compareSummonernames } from '@/helpers/functions.js'
import { gameModes } from '@/data/data.js'
import { mapState } from 'vuex'
import { liveGame } from '@/mixins/liveGame'
export default {
data() {
return {
gameLength: 0
}
},
computed: {
allyTeam() {
return this.current.participants.filter(p => p.teamId === this.teamColor)
},
enemyTeam() {
return this.current.participants.filter(p => p.teamId !== this.teamColor)
},
gamemode() {
return gameModes[this.current.gameQueueConfigId]
},
teamColor() {
return this.current.participants.find(p => compareSummonernames(p.summonerName, this.$route.params.name)).teamId
},
...mapState({
current: state => state.summoner.basic.current,
})
},
created() {
this.gameLength = this.current.gameLength
setInterval(() => {
this.gameLength++
}, 1000)
},
methods: {
compareSummonernames
}
mixins: [liveGame],
}
</script>

View file

@ -0,0 +1,226 @@
<template>
<div class="mt-2 bg-blue-800 px-5 py-4 rounded-lg">
<table
class="w-full table-fixed text-center leading-none"
style="border-collapse:separate; border-spacing:0 0.5rem;"
>
<thead>
<tr class="text-left">
<th
:class="[ally ? 'text-teal-400 ' : 'text-red-400 ']"
class="w-team font-semibold"
>{{ ally ? 'Ally' : 'Enemy' }} Team</th>
<th class="w-ranked text-blue-200 text-sm font-normal">SoloQ Stats</th>
<th class="w-ranked text-blue-200 text-sm font-normal">Flex Stats</th>
<th class="w-bans px-2 text-right text-blue-200 text-sm font-normal">Bans</th>
</tr>
</thead>
<tbody v-if="liveLoaded">
<tr
v-for="(player, index) in team"
:key="player.summonerId"
:style="{
backgroundImage:
`linear-gradient(90deg, rgba(42, 67, 101, 0.3) 0%, rgba(42, 67, 101, 0.8) 40%, rgba(42, 67, 101, 1) 100%),
url('https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/champion-splashes/${player.championId}/${player.championId}000.jpg')`
}"
class="bg-cover bg-center"
>
<td class="py-1 pl-2 rounded-l-lg">
<div class="flex items-center">
<div class="flex flex-col items-center">
<div
:style="{backgroundImage: `url('${player.runes.primaryRune}')`}"
class="w-6 h-6 bg-cover bg-center"
></div>
<div
:style="{backgroundImage: `url('https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/perk-images/styles/7202_sorcery.png')`}"
class="mt-1 w-3 h-3 bg-cover bg-center"
></div>
</div>
<div
:style="{backgroundImage: `url('https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/champion-icons/${player.championId}.png')`}"
:class="[ally ? 'border-teal-400' : 'border-red-400']"
class="ml-2 w-12 h-12 bg-cover bg-center bg-blue-1000 border-2 rounded-full"
></div>
<div class="ml-2 flex flex-col">
<div
:style="{backgroundImage: `url(${getSummonerLink(player.spell1Id)})`}"
class="w-4 h-4 bg-blue-1000 rounded-md bg-center bg-cover"
></div>
<div
:style="{backgroundImage: `url(${getSummonerLink(player.spell2Id)})`}"
class="mt-1 w-4 h-4 bg-blue-1000 rounded-md bg-center bg-cover"
></div>
</div>
<div class="ml-3 text-left text-sm leading-tight">
<router-link
v-if="!player.bot"
:to="{ name: 'summoner', params: { region: $route.params.region, name: player.summonerName }}"
class="font-semibold hover:text-blue-200"
>{{ player.summonerName }}</router-link>
<div class="text-xs">Level {{ player.level }}</div>
</div>
</div>
</td>
<td class="py-1 text-left">
<div class="px-2">
<div v-if="player.rank.soloQ" class="flex items-center">
<div class="inline-block text-center">
<svg class="w-5 h-5">
<use :xlink:href="`#rank-${player.rank.soloQ.tier.toLowerCase()}`" />
</svg>
<div
class="mt-2px text-blue-300 text-xs font-semibold"
>{{ player.rank.soloQ.shortName }}</div>
</div>
<div class="ml-5 text-center">
<div class="font-semibold">{{ player.rank.soloQ.winrate }}</div>
<div
class="mt-1 text-xs text-blue-300"
>{{ player.rank.soloQ.wins + player.rank.soloQ.losses }} games</div>
</div>
</div>
<div v-else class="w-5 h-5">
<div class="-mt-1 text-blue-300 text-2xl">-</div>
</div>
</div>
</td>
<td class="py-1 text-left">
<div class="px-2">
<div v-if="player.rank.flex5v5" class="flex items-center">
<div class="inline-block text-center">
<svg class="w-5 h-5">
<use :xlink:href="`#rank-${player.rank.flex5v5.tier.toLowerCase()}`" />
</svg>
<div
class="mt-2px text-blue-300 text-xs font-semibold"
>{{ player.rank.flex5v5.shortName }}</div>
</div>
<div class="ml-5 text-center">
<div class="font-semibold">{{ player.rank.flex5v5.winrate }}</div>
<div
class="mt-1 text-xs text-blue-300"
>{{ player.rank.flex5v5.wins + player.rank.flex5v5.losses }} games</div>
</div>
</div>
<div v-else class="w-5 h-5">
<div class="-mt-1 text-blue-300 text-2xl">-</div>
</div>
</div>
</td>
<td class="py-1 text-right">
<div class="px-2 inline-block">
<div
v-if="live.bannedChampions.length"
:class="[ally ? 'ban-blue border-teal-500' : 'ban-red border-red-500']"
class="relative ban border-2 rounded-full"
>
<div
:style="[
banChamp(index, player.teamId) ?
{backgroundImage: `url('https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/champion-icons/${banChamp(index, player.teamId).championId}.png')`}
: ''
]"
class="ban-img w-6 h-6 bg-cover bg-center bg-blue-1000 rounded-full"
></div>
<div
:class="[ally ? 'text-teal-100 bg-teal-500' : 'text-red-100 bg-red-500']"
class="absolute ban-order w-4 h-4 flex items-center justify-center text-xs font-bold rounded-full"
>{{ banChamp(index, player.teamId).pickTurn }}</div>
</div>
<div v-else class="w-5 h-5">
<div class="-mt-1 text-blue-300 text-2xl">-</div>
</div>
</div>
</td>
</tr>
</tbody>
<tbody v-else>
<tr v-for="index in 5" :key="index" class="bg-blu">
<td colspan="4" class="bg-blue-760 rounded-lg">
<content-loader
:height="54"
:width="1160"
:speed="2"
primary-color="#17314f"
secondary-color="#2b6cb0"
>
<rect x="12" y="12" rx="3" ry="3" width="14" height="14" />
<rect x="12" y="32" rx="3" ry="3" width="14" height="14" />
<circle cx="64" cy="28" r="24" />
<rect x="96" y="10" rx="3" ry="3" width="16" height="16" />
<rect x="96" y="31" rx="3" ry="3" width="16" height="16" />
<rect x="124" y="32" rx="3" ry="3" width="50" height="12" />
<rect x="124" y="13" rx="3" ry="3" width="70" height="14" />
<rect x="640" y="35" rx="3" ry="3" width="40" height="10" />
<rect x="691" y="33" rx="3" ry="3" width="55" height="10" />
<rect x="647" y="8" rx="3" ry="3" width="25" height="20" />
<rect x="696" y="12" rx="3" ry="3" width="41" height="15" />
<rect x="860" y="35" rx="3" ry="3" width="40" height="10" />
<rect x="911" y="33" rx="3" ry="3" width="55" height="10" />
<rect x="867" y="8" rx="3" ry="3" width="25" height="20" />
<rect x="916" y="12" rx="3" ry="3" width="41" height="15" />
<circle cx="1137" cy="27" r="14" />
</content-loader>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import { mapState } from 'vuex'
import { getSummonerLink } from '@/helpers/summoner.js'
import { ContentLoader } from 'vue-content-loader'
export default {
components: {
ContentLoader,
},
props: {
team: {
type: Array,
required: true
},
ally: {
type: Boolean,
default: true,
}
},
computed: {
...mapState({
live: state => state.summoner.live.match,
liveLoaded: state => state.summoner.live.liveLoaded,
})
},
methods: {
banChamp(index, teamId) {
index++
if (teamId === 200) {
index += 5
}
return this.live.bannedChampions.find(b => b.pickTurn === index)
},
getSummonerLink,
}
}
</script>
<style scoped>
.w-team {
width: 40rem;
}
.w-ranked {
width: 13.75rem;
}
.w-bans {
width: 5rem;
}
</style>

View file

@ -92,7 +92,7 @@ export function getRankImg(leagueData) {
return `https://res.cloudinary.com/kln/image/upload/v1571671133/ranks/${leagueData.tier}_${leaguesNumbers[leagueData.rank]}.png`
}
function getSummonerLink(id) {
export function getSummonerLink(id) {
if (id === 0) return null
const spellName = summonersJSON.find(s => s.id === id).iconPath.split('/assets/')[1].toLowerCase()
return `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${spellName}`

View file

@ -78,6 +78,11 @@
class="ml-4 pb-2 border-b-2 border-transparent text-blue-300 cursor-pointer hover:text-blue-100"
exact
>records</router-link>
<router-link
:to="{ name: 'summonerLive', params: { region: $route.params.region, name: $route.params.name }}"
class="ml-4 pb-2 border-b-2 border-transparent text-blue-300 cursor-pointer hover:text-blue-100"
exact
>live game</router-link>
</template>
<!-- View -->
<transition :name="tabTransition">

View file

@ -0,0 +1,42 @@
import { compareSummonernames } from '@/helpers/functions.js'
import { gameModes } from '@/data/data.js'
import { mapState } from 'vuex'
export const liveGame = {
data() {
return {
gameLength: 0
}
},
computed: {
allyTeam() {
return this.current.participants.filter(p => p.teamId === this.teamColor)
},
enemyTeam() {
return this.current.participants.filter(p => p.teamId !== this.teamColor)
},
gamemode() {
return gameModes[this.current.gameQueueConfigId]
},
teamColor() {
return this.current.participants.find(p => p.summonerId === this.account.id).teamId
},
...mapState({
account: state => state.summoner.basic.account,
current: state => state.summoner.basic.current,
})
},
created() {
this.gameLength = this.current ? this.current.gameLength : 0
setInterval(() => {
this.gameLength++
}, 1000)
},
methods: {
compareSummonernames,
}
}

View file

@ -5,6 +5,7 @@ import { axios } from './plugins/axios'
import Home from '@/views/Home.vue'
import Summoner from '@/views/Summoner.vue'
import SummonerChampions from '@/views/SummonerChampions.vue'
import SummonerLive from '@/views/SummonerLive.vue'
import SummonerRecords from '@/views/SummonerRecords.vue'
Vue.use(Router)
@ -36,6 +37,11 @@ const router = new Router({
name: 'summonerRecords',
component: SummonerRecords
},
{
path: '/summoner/:region/:name/live',
name: 'summonerLive',
component: SummonerLive
},
]
})

View file

@ -27,6 +27,10 @@ export const state = {
list: [],
recordsLoaded: false
},
live: {
match: {},
liveLoaded: false
},
}
export const mutations = {
@ -43,6 +47,10 @@ export const mutations = {
state.champions.list = champions
state.champions.championsLoaded = true
},
LIVE_FOUND(state, { live }) {
state.live.match = live
state.live.liveLoaded = true
},
MATCHES_LOADING(state) {
state.overview.matchesLoading = true
},
@ -114,6 +122,13 @@ export const actions = {
commit('CHAMPIONS_FOUND', { champions: resp.data })
},
async liveMatchRequest({ commit, rootState }) {
const resp = await axios(({ url: 'summoner-live', data: { account: state.basic.account, region: rootState.currentRegion }, method: 'POST' })).catch(() => { })
console.log('---LIVE---')
console.log(resp.data)
commit('LIVE_FOUND', { live: resp.data })
},
async moreMatches({ commit }) {
commit('MATCHES_LOADING')

View file

@ -0,0 +1,75 @@
<template>
<div key="live-game">
<div v-if="playing || summonerLoading">
<div v-if="playing" class="flex items-center justify-end text-blue-200 text-base">
<div>{{ gamemode.type }} {{ gamemode.name }}</div>
<div class="mx-2">-</div>
<div>{{ gameLength|secToTime(true) }}</div>
</div>
<div v-else class="flex items-center justify-end text-blue-200 text-base">
<div>Loading</div>
</div>
<LiveTeam :team="allyTeam" :ally="true" />
<LiveTeam :team="enemyTeam" :ally="false" class="mt-4" />
</div>
<div v-else>
<div class="mt-16 flex justify-center">
<div class="bg-gradient px-4 py-3 rounded-lg text-center text-lg text-blue-100 font-bold">
<div>This summoner is not in game.</div>
<div>🕊</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapActions, mapGetters, mapState } from 'vuex'
import { liveGame } from '@/mixins/liveGame'
import LiveTeam from '@/components/Summoner/Live/LiveTeam.vue'
export default {
components: {
LiveTeam,
},
mixins: [liveGame],
computed: {
allyTeam() {
return this.live.participants ? this.live.participants.filter(p => p.teamId === this.teamColor) : []
},
enemyTeam() {
return this.live.participants ? this.live.participants.filter(p => p.teamId !== this.teamColor) : []
},
...mapGetters('summoner', ['summonerLoading', 'summonerFound']),
...mapState({
current: state => state.summoner.basic.current,
live: state => state.summoner.live.match,
liveLoaded: state => state.summoner.live.liveLoaded,
playing: state => state.summoner.basic.playing,
})
},
watch: {
summonerFound() {
this.fetchData()
this.gameLength = this.current ? this.current.gameLength : 0
}
},
created() {
this.fetchData()
},
methods: {
fetchData() {
if (this.playing && !this.liveLoaded && this.summonerFound) {
this.liveMatchRequest()
}
},
...mapActions('summoner', ['liveMatchRequest']),
}
}
</script>

View file

@ -1,6 +1,7 @@
'use strict'
const Jax = use('Jax')
const LiveMatchTransformer = use('App/Transformers/LiveMatchTransformer')
const MatchRepository = make('App/Repositories/MatchRepository')
const MatchService = use('App/Services/MatchService')
const SummonerService = use('App/Services/SummonerService')
@ -115,6 +116,27 @@ class SummonerController {
console.timeEnd('recordsRequest')
return response.json(records[0])
}
/**
* POST - Return live match details
*/
async liveMatchDetails({ request, response }) {
console.time('liveMatchDetails')
const account = request.input('account')
const region = request.input('region')
// CURRENT GAME
let currentGame = await Jax.Spectator.summonerID(account.id, region)
if (!currentGame) {
response.json(null)
}
currentGame = await LiveMatchTransformer.transform(currentGame, { region })
console.timeEnd('liveMatchDetails')
return response.json(currentGame)
}
}
module.exports = SummonerController

View file

@ -0,0 +1,44 @@
'use strict'
const MatchTransformer = use('App/Transformers/MatchTransformer')
const SummonerService = use('App/Services/SummonerService')
/**
* LiveMatchTransformer class
*
* @class LiveMatchTransformer
*/
class LiveMatchTransformer extends MatchTransformer {
async _getPlayerDatq(participant, region) {
const account = await SummonerService.getAccount(participant.summonerName, region)
if (account) {
participant.level = account.summonerLevel
const ranked = await SummonerService.getRanked(account, region)
participant.rank = ranked
} else {
participant.rank = null
}
return participant
}
/**
* Transform raw data from Riot API
* @param match data from Riot API, one live match
*/
async transform(match, { region }) {
await super.getContext()
// Perks
for (const participant of match.participants) {
participant.runes = super.getPerksImages(participant.perks.perkIds[0], participant.perks.perkSubStyle)
}
const requestsParticipants = match.participants.map(p => this._getPlayerDatq(p, region))
match.participants = await Promise.all(requestsParticipants)
return match
}
}
module.exports = new LiveMatchTransformer()

View file

@ -111,13 +111,7 @@ class MatchTransformer {
let primaryRune = null
let secondaryRune = null
if (player.stats.perkPrimaryStyle) {
const firstRune = this.perks.find(p => p.id === player.stats.perk0)
const firstRuneUrl = firstRune.iconPath.split('/assets/')[1].toLowerCase()
primaryRune = `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${firstRuneUrl}`
const secondRuneStyle = this.perkstyles.find(p => p.id === player.stats.perkSubStyle)
const secondRuneStyleUrl = secondRuneStyle.iconPath.split('/assets/')[1].toLowerCase()
secondaryRune = `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${secondRuneStyleUrl}`
({ primaryRune, secondaryRune } = this.getPerksImages(player.stats.perk0, player.stats.perkSubStyle))
}
const items = []
@ -157,6 +151,23 @@ class MatchTransformer {
}
}
/**
* Return the icons of the primary rune and secondary category
* @param perk0 primary perks id
* @param perkSubStyle secondary perks category
*/
getPerksImages(perk0, perkSubStyle) {
const firstRune = this.perks.find(p => p.id === perk0)
const firstRuneUrl = firstRune.iconPath.split('/assets/')[1].toLowerCase()
const primaryRune = `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${firstRuneUrl}`
const secondRuneStyle = this.perkstyles.find(p => p.id === perkSubStyle)
const secondRuneStyleUrl = secondRuneStyle.iconPath.split('/assets/')[1].toLowerCase()
const secondaryRune = `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${secondRuneStyleUrl}`
return { primaryRune, secondaryRune }
}
/**
* Return the lane of the summoner according to timeline
* @param timeline from Riot Api

View file

@ -29,6 +29,7 @@ Route.post('/summoner-basic', 'SummonerController.basic')
Route.post('/summoner-overview', 'SummonerController.overview')
Route.post('/summoner-champions', 'SummonerController.champions')
Route.post('/summoner-records', 'SummonerController.records')
Route.post('/summoner-live', 'SummonerController.liveMatchDetails')
Route.post('/ddragon', 'DDragonController.index')
Route.post('/match', 'MatchController.index')
Route.post('/match-details', 'MatchController.show')