feat: display match details (1st version: code needs some work)

This commit is contained in:
Valentin Kaelin 2019-11-03 18:49:58 +01:00
parent 42ac3f6bcc
commit cce91efb67
18 changed files with 910 additions and 166 deletions

View file

@ -1,6 +1,7 @@
<template> <template>
<div id="app" class="font-sans bg-blue-900 antialiased min-h-screen"> <div id="app" class="font-sans bg-blue-900 antialiased min-h-screen">
<SVGContainer />
<NotificationsContainer /> <NotificationsContainer />
<router-view /> <router-view />
@ -9,10 +10,12 @@
<script> <script>
import NotificationsContainer from '@/components/NotificationsContainer.vue' import NotificationsContainer from '@/components/NotificationsContainer.vue'
import SVGContainer from '@/components/SVGContainer.vue'
export default { export default {
components: { components: {
NotificationsContainer NotificationsContainer,
SVGContainer
}, },
} }
</script> </script>

View file

@ -0,0 +1,125 @@
<template>
<transition name="slide">
<div v-if="data.status === 'loaded' && detailsOpen" class="bg-blue-800 rounded-b-lg">
<DetailedMatchTeam :data="data.blueTeam" />
<div class="py-5">
<div class="px-3 flex justify-between">
<div v-if="data.blueTeam.bans">
<div
v-for="ban in data.blueTeam.bans"
:key="'ban-' + ban.pickTurn"
:class="{'ml-2': ban.pickTurn !== 1}"
class="relative ban ban-blue inline-block border-2 border-teal-500 rounded-full"
>
<div
:style="[ban.champion.id ? {backgroundImage: `url('https://ddragon.leagueoflegends.com/cdn/${version}/img/champion/${ban.champion.id}.png')`} : '']"
class="ban-img w-6 h-6 bg-cover bg-center bg-blue-1000 rounded-full"
></div>
<div
class="absolute ban-order w-4 h-4 flex items-center justify-center bg-teal-500 text-xs text-teal-100 font-bold rounded-full"
>{{ ban.pickTurn }}</div>
</div>
</div>
<div v-if="data.redTeam.bans">
<div
v-for="ban in data.redTeam.bans"
:key="'ban-' + ban.pickTurn"
:class="{'ml-2': ban.pickTurn !== 6}"
class="relative ban ban-red inline-block border-2 border-red-500 rounded-full"
>
<div
:style="[ban.champion.id ? {backgroundImage: `url('https://ddragon.leagueoflegends.com/cdn/${version}/img/champion/${ban.champion.id}.png')`} : '']"
class="ban-img w-6 h-6 bg-cover bg-center bg-blue-1000 rounded-full"
></div>
<div
class="absolute ban-order w-4 h-4 flex items-center justify-center bg-red-500 text-xs text-red-100 font-bold rounded-full"
>{{ ban.pickTurn }}</div>
</div>
</div>
</div>
</div>
<DetailedMatchTeam :data="data.redTeam" />
</div>
<div v-else-if="data.status === 'loading' && detailsOpen">
<p class="bg-blue-800 py-5 text-blue-100 text-lg font-semibold">Loading...</p>
</div>
</transition>
</template>
<script>
import { mapGetters } from 'vuex'
import DetailedMatchTeam from '@/components/Match/DetailedMatchTeam.vue'
export default {
components: {
DetailedMatchTeam
},
props: {
data: {
type: Object,
required: true
},
detailsOpen: {
type: Boolean,
required: true
}
},
computed: {
...mapGetters('ddragon', ['version']),
}
}
</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;
}
.slide-enter-active {
transition-duration: 0.3s;
transition-timing-function: ease-in;
}
.slide-leave-active {
transition-duration: 0.3s;
transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
}
.slide-enter-to,
.slide-leave {
max-height: 737px;
overflow: hidden;
}
.slide-enter,
.slide-leave-to {
overflow: hidden;
max-height: 0;
}
</style>

View file

@ -0,0 +1,258 @@
<template>
<table :class="{'rounded-b-lg overflow-hidden': !blueTeam}" class="w-full table-auto">
<thead class="leading-none">
<tr :class="`heading-${data.result}`" class="heading text-blue-200 font-semibold">
<th class="py-5 border-r border-blue-700">
<div class="flex justify-between">
<span
:class="data.color === 'Blue' ? 'text-teal-400' : 'text-red-400'"
class="pl-2"
>{{ data.color }} Team</span>
<span>{{ `${data.teamStats.kills}/${data.teamStats.deaths}/${data.teamStats.assists}` }}</span>
<div class="flex pr-2">
<svg class="w-4 h-4 items-center">
<use xlink:href="#gold" />
</svg>
<span class="ml-2px">{{ +(data.teamStats.gold / 1000).toFixed(2) + 'k' }}</span>
</div>
</div>
</th>
<th class="px-2 py-5 text-sm">K</th>
<th class="px-2 py-5 text-sm">D</th>
<th class="px-2 py-5 text-sm">A</th>
<th class="px-2 py-5 text-sm">cs/m</th>
<th class="px-2 py-5 text-sm">vs/m</th>
<th class="px-2 py-5 text-sm">gold</th>
<th class="px-2 py-5 text-sm">
dmg
<br />champ
</th>
<th class="px-2 py-5 text-sm">
dmg
<br />obj
</th>
<th class="px-2 py-5 text-sm">
dmg
<br />taken
</th>
<th class="px-2 py-5 text-sm">kp</th>
</tr>
</thead>
<tbody :class="[{'border-b border-blue-700': blueTeam}, data.result]" class="leading-none">
<tr v-for="(player, index) in data.players" :key="player.name + index">
<td class="py-2 border-r border-blue-700">
<div class="px-2 flex justify-between">
<div class="flex">
<div class="flex items-center">
<div
v-if="player.role !== 'NONE'"
:style="{backgroundImage: `url(${require('@/assets/img/roles/' + player.role + '.png')})`}"
class="w-4 h-4 bg-center bg-cover"
></div>
</div>
<div
:style="{backgroundImage: `url('https://ddragon.leagueoflegends.com/cdn/${version}/img/champion/${player.champion.id}.png')`}"
class="ml-3 relative w-8 h-8 bg-cover bg-center bg-blue-1000 rounded-full"
>
<div
class="absolute level-position bottom-0 w-5 h-5 bg-blue-900 rounded-full text-teal-100 text-xs"
>
<span class="leading-relaxed">{{ player.level }}</span>
</div>
</div>
<div class="ml-1 flex flex-col justify-around">
<div
:style="{backgroundImage: `url(${player.firstSum})`}"
class="w-4 h-4 bg-blue-1000 rounded-md bg-center bg-cover"
></div>
<div
:style="{backgroundImage: `url(${player.secondSum})`}"
class="w-4 h-4 bg-blue-1000 rounded-md bg-center bg-cover"
></div>
</div>
<div class="ml-2px flex flex-col justify-around">
<div
:style="[player.primaryRune ? {background: `url(${player.primaryRune}) center/cover`} : '']"
class="w-4 h-4 bg-blue-1000 rounded-md"
></div>
<div
:style="[player.secondaryRune ? {background: `url(${player.secondaryRune}) center/cover`} : '']"
class="w-4 h-4 bg-blue-1000 rounded-md"
></div>
</div>
<div class="ml-1 flex flex-col items-start justify-center leading-none">
<router-link
v-if="player.firstSum"
:to="{ name: 'summoner', params: { region: $route.params.region, name: player.name }}"
:class="{'font-semibold text-yellow-400': $route.params.name.toLowerCase() === player.name.toLowerCase()}"
class="w-24 text-sm text-white text-left overflow-hidden text-overflow whitespace-no-wrap hover:text-blue-200"
>{{ player.name }}</router-link>
<div
v-else
class="w-24 text-sm text-white text-left overflow-hidden text-overflow whitespace-no-wrap"
>{{ player.name }}</div>
<div class="text-xs text-teal-500">{{ player.champion.name }}</div>
</div>
</div>
<div>
<div v-if="false" class="ml-2">
<svg class="w-6 h-6">
<use xlink:href="#rank-silver" />
</svg>
<div class="text-blue-200 text-xs">S2</div>
</div>
<div class="ml-2 flex items-center">
<div
v-for="(item, indexItem) in player.items"
:key="indexItem"
:style="{backgroundImage: item}"
class="ml-2px w-6 h-6 rounded-md bg-blue-1000 bg-center bg-cover"
></div>
</div>
</div>
</div>
</td>
<td
:class="{'border-b border-blue-700': displayBorderbottom(index)}"
class="p-2 text-white text-xs font-semibold"
>{{ player.kills }}</td>
<td
:class="{'border-b border-blue-700': displayBorderbottom(index)}"
class="p-2 text-white text-xs font-semibold"
>{{ player.deaths }}</td>
<td
:class="{'border-b border-blue-700': displayBorderbottom(index)}"
class="p-2 text-white text-xs font-semibold"
>{{ player.assists }}</td>
<td
:class="{'border-b border-blue-700': displayBorderbottom(index)}"
class="p-2 text-white text-xs font-semibold"
>{{ player.percentStats.minions }}</td>
<td
:class="{'border-b border-blue-700': displayBorderbottom(index)}"
class="p-2 text-white text-xs font-semibold"
>{{ player.percentStats.vision }}</td>
<td
:class="{'border-b border-blue-700': displayBorderbottom(index)}"
class="p-2 text-white text-xs font-semibold"
>{{ player.stats.gold }}</td>
<td
:class="{'border-b border-blue-700': displayBorderbottom(index)}"
class="p-2 text-white text-xs font-semibold"
>{{ player.stats.dmgChamp }}</td>
<td
:class="{'border-b border-blue-700': displayBorderbottom(index)}"
class="p-2 text-white text-xs font-semibold"
>{{ player.stats.dmgObj }}</td>
<td
:class="{'border-b border-blue-700': displayBorderbottom(index)}"
class="p-2 text-white text-xs font-semibold"
>{{ player.stats.dmgTaken }}</td>
<td
:class="{'border-b border-blue-700': displayBorderbottom(index)}"
class="p-2 text-white text-xs font-semibold"
>{{ player.kp }}</td>
</tr>
</tbody>
</table>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
props: {
data: {
type: Object,
required: true
},
},
data() {
return {
blueTeam: this.data.color === 'Blue'
}
},
computed: {
...mapGetters('ddragon', ['version']),
},
methods: {
displayBorderbottom(index) {
return this.blueTeam || index !== this.data.players.length - 1
}
}
}
</script>
<style scoped>
.heading {
box-shadow: #2b6cb0 0px -1px inset;
}
.heading-Win {
background-image: linear-gradient(
90deg,
rgba(1, 97, 28, 0.3) 0%,
rgba(44, 82, 130, 0) 45%
),
linear-gradient(#2a4365 0%, #2b4c77 55%, #235a93 100%);
}
.heading-Fail {
background-image: linear-gradient(
90deg,
rgba(140, 0, 0, 0.3) 0%,
rgba(44, 82, 130, 0) 45%
),
linear-gradient(#2a4365 0%, #2b4c77 55%, #235a93 100%);
}
.heading-Remake {
background-image: linear-gradient(
90deg,
rgba(233, 169, 75, 0.3) 0%,
rgba(44, 82, 130, 0) 45%
),
linear-gradient(#2a4365 0%, #2b4c77 55%, #235a93 100%);
}
.team::before {
content: "";
position: absolute;
z-index: 1;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.Win {
background-image: linear-gradient(
90deg,
rgba(1, 97, 28, 0.3) 0%,
rgba(44, 82, 130, 0) 45%
);
}
.Fail {
background-image: linear-gradient(
90deg,
rgba(140, 0, 0, 0.3) 0%,
rgba(44, 82, 130, 0) 45%
);
}
.Remake {
background-image: linear-gradient(
90deg,
rgba(233, 169, 75, 0.3) 0%,
rgba(44, 82, 130, 0) 45%
);
}
.level-position {
left: -10px;
}
</style>

View file

@ -1,11 +1,9 @@
<template> <template>
<li class="relative"> <li class="ml-4 relative">
<!-- <div class="game-status absolute left-0 h-32 w-32">
<div class="text-2xl text-teal-500 uppercase font-extrabold">{{ data.status }}</div>
</div> -->
<div <div
:class="matchResultClass" @click="displayDetails"
class="ml-4 match relative mt-4 bg-blue-800 rounded-lg text-white text-base" :class="[matchResultClass, showDetails ? 'rounded-t-lg' : 'rounded-lg']"
class="match relative mt-4 bg-blue-800 text-white text-base cursor-pointer hover:shadow-xl"
> >
<div class="relative z-20 flex flex-wrap px-5 py-3"> <div class="relative z-20 flex flex-wrap px-5 py-3">
<div class="first w-4/12 text-left"> <div class="first w-4/12 text-left">
@ -116,11 +114,10 @@
:key="'player-' + index" :key="'player-' + index"
class="ml-4 flex items-center leading-none" class="ml-4 flex items-center leading-none"
> >
<router-link <div
:to="{ name: 'summoner', params: { region: $route.params.region, name: ally.name }}"
:class="isSummonerProfile(ally.name)" :class="isSummonerProfile(ally.name)"
class="w-16 text-right overflow-hidden text-overflow whitespace-no-wrap text-xs text-blue-200 font-medium hover:text-blue-100" class="w-16 text-right overflow-hidden text-overflow whitespace-no-wrap text-xs text-blue-200 font-medium"
>{{ ally.name }}</router-link> >{{ ally.name }}</div>
<div <div
:class="index !== 0 ? '-mt-1': ''" :class="index !== 0 ? '-mt-1': ''"
:style="{backgroundImage: `url('https://ddragon.leagueoflegends.com/cdn/${version}/img/champion/${ally.champion.id}.png')`}" :style="{backgroundImage: `url('https://ddragon.leagueoflegends.com/cdn/${version}/img/champion/${ally.champion.id}.png')`}"
@ -135,10 +132,9 @@
:style="{backgroundImage: `url('https://ddragon.leagueoflegends.com/cdn/${version}/img/champion/${data.enemyTeam[index].champion.id}.png')`}" :style="{backgroundImage: `url('https://ddragon.leagueoflegends.com/cdn/${version}/img/champion/${data.enemyTeam[index].champion.id}.png')`}"
class="w-6 h-6 bg-blue-1000 bg-center bg-cover rounded-full" class="w-6 h-6 bg-blue-1000 bg-center bg-cover rounded-full"
></div> ></div>
<router-link <div
:to="{ name: 'summoner', params: { region: $route.params.region, name: data.enemyTeam[index].name }}"
class="ml-1 w-16 text-left overflow-hidden text-overflow whitespace-no-wrap text-xs text-blue-200 font-medium hover:text-blue-100" class="ml-1 w-16 text-left overflow-hidden text-overflow whitespace-no-wrap text-xs text-blue-200 font-medium hover:text-blue-100"
>{{ data.enemyTeam[index].name }}</router-link> >{{ data.enemyTeam[index].name }}</div>
</div> </div>
</div> </div>
<div class="ml-auto flex flex-col items-center justify-center"> <div class="ml-auto flex flex-col items-center justify-center">
@ -149,13 +145,19 @@
</div> </div>
</div> </div>
</div> </div>
<DetailedMatch :data="getMatchDetails(data.gameId) || {}" :details-open="showDetails" />
</li> </li>
</template> </template>
<script> <script>
import { mapState, mapGetters } from 'vuex' import { mapActions, mapState, mapGetters } from 'vuex'
import DetailedMatch from '@/components/Match/DetailedMatch'
export default { export default {
components: {
DetailedMatch
},
props: { props: {
data: { data: {
type: Object, type: Object,
@ -163,6 +165,12 @@ export default {
}, },
}, },
data() {
return {
showDetails: false
}
},
computed: { computed: {
matchResultClass() { matchResultClass() {
return { return {
@ -175,39 +183,34 @@ export default {
roles: state => state.roles roles: state => state.roles
}), }),
...mapGetters('ddragon', ['version']), ...mapGetters('ddragon', ['version']),
...mapGetters('detailedMatch', ['getMatchDetails']),
}, },
methods: { methods: {
displayDetails() {
this.showDetails = !this.showDetails
if (!this.getMatchDetails(this.data.gameId)) {
this.matchDetails(this.data.gameId)
}
},
isSummonerProfile(allyName) { isSummonerProfile(allyName) {
return { return {
'font-bold': this.$route.params.name.toLowerCase() === allyName.toLowerCase() 'font-bold': this.$route.params.name.toLowerCase() === allyName.toLowerCase()
} }
} },
...mapActions('detailedMatch', ['matchDetails']),
} }
} }
</script> </script>
<style scoped> <style scoped>
.match { .match {
/* background-image: linear-gradient( transition-duration: 0.3s;
90deg, transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
#2c5282 0%,
rgba(44, 82, 130, 0) 100%
); */
} }
.match::after { .loss {
content: "";
position: absolute;
z-index: 10;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 0.5rem;
}
.loss::after {
background-image: linear-gradient( background-image: linear-gradient(
90deg, 90deg,
rgba(140, 0, 0, 0.3) 0%, rgba(140, 0, 0, 0.3) 0%,
@ -215,7 +218,7 @@ export default {
); );
} }
.remake::after { .remake {
background-image: linear-gradient( background-image: linear-gradient(
90deg, 90deg,
rgba(233, 169, 75, 0.3) 0%, rgba(233, 169, 75, 0.3) 0%,
@ -223,7 +226,7 @@ export default {
); );
} }
.win::after { .win {
background-image: linear-gradient( background-image: linear-gradient(
90deg, 90deg,
rgba(1, 97, 28, 0.3) 0%, rgba(1, 97, 28, 0.3) 0%,

View file

@ -0,0 +1,6 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" class="hidden">
<symbol id="gold" class="fill-current" viewBox="0 0 15 15" fill="fill-current"><title>gold</title><path d="M14 5.63324C14 3.57057 11.7157 1.8999 8.89286 1.8999C6.07 1.8999 3.78571 3.57057 3.78571 5.63324C3.7765 5.76685 3.7765 5.90095 3.78571 6.03457C2.12357 6.65057 1 7.90124 1 9.36657C1 11.4292 3.28429 13.0999 6.10714 13.0999C8.93 13.0999 11.2143 11.4292 11.2143 9.36657C11.2235 9.23295 11.2235 9.09885 11.2143 8.96524C12.8486 8.34924 14 7.08924 14 5.63324ZM6.10714 11.3172C4.315 11.3172 2.85714 10.2999 2.85714 8.94657C2.90787 8.55215 3.06303 8.17865 3.3064 7.86507C3.54978 7.55149 3.87244 7.30934 4.24071 7.1639C4.77538 7.8881 5.47991 8.46817 6.29158 8.85247C7.10324 9.23677 7.99686 9.41338 8.89286 9.36657H9.30143C9.03214 10.4679 7.71357 11.3172 6.10714 11.3172Z" /></symbol>
<symbol id="rank-silver" viewBox="0 0 32 32"><title>rank-silver</title><path fill="#80989d" d="M16 6.667c0 0 0.986 1.479 2.534 3.165-0.781-0.321-1.637-0.498-2.534-0.498s-1.753 0.177-2.534 0.498c1.548-1.686 2.534-3.165 2.534-3.165z"></path><path fill="#80989d" d="M22.667 16c0-2.22-1.085-4.186-2.753-5.398 1.976-0.343 6.753-1.935 6.753-1.935 0 1.761-1.034 6.626-4.013 7.761 0.009-0.141 0.013-0.284 0.013-0.428z"></path><path fill="#80989d" d="M19.308 21.789c1.752-1.003 3.005-2.78 3.295-4.864 1.185 0.187 2.73 0.408 2.73 0.408s-1.135 3.406-6.025 4.456z"></path><path fill="#80989d" d="M16 22.667c0.617 0 1.215-0.084 1.782-0.241-0.406 0.781-1.282 2.407-1.782 2.907-0.5-0.5-1.376-2.127-1.782-2.907 0.567 0.157 1.165 0.241 1.782 0.241z"></path><path fill="#80989d" d="M9.392 16.89c0.28 2.1 1.537 3.89 3.3 4.899-4.89-1.050-6.025-4.456-6.025-4.456s1.554-0.25 2.726-0.444z"></path><path fill="#80989d" d="M9.333 16c0 0.129 0.004 0.258 0.011 0.386-2.977-1.324-4.011-6.91-4.011-7.719 0 0 4.777 1.592 6.753 1.935-1.669 1.212-2.753 3.178-2.753 5.398z"></path><path fill="#80989d" d="M20 16c0 2.209-1.791 4-4 4s-4-1.791-4-4c0-2.209 1.791-4 4-4s4 1.791 4 4z"></path></symbol>
</svg>
</template>

View file

@ -81,8 +81,6 @@ export default {
}, },
created() { created() {
console.log('activity')
this.createGrid() this.createGrid()
}, },

View file

@ -6,6 +6,29 @@ import store from '@/store'
const uniqueLeagues = ['CHALLENGER', 'GRANDMASTER', 'MASTER'] const uniqueLeagues = ['CHALLENGER', 'GRANDMASTER', 'MASTER']
const leaguesNumbers = { 'I': 1, 'II': 2, 'III': 3, 'IV': 4 } const leaguesNumbers = { 'I': 1, 'II': 2, 'III': 3, 'IV': 4 }
/**
* Return all the infos about a detailed match
* @param detailedMatch : all data about the match from the Riot API
*/
export function createDetailedMatchData(detailedMatch) {
detailedMatch.blueTeam.players = detailedMatch.blueTeam.players.map(p => getPlayerData(p))
detailedMatch.redTeam.players = detailedMatch.redTeam.players.map(p => getPlayerData(p))
function getPlayerData(p) {
// Items
for (let i = 0; i < p.items.length; i++) {
p.items[i] = getItemLink(p.items[i])
}
// Summoner Spells
p.firstSum = getSummonerLink(p.firstSum)
p.secondSum = getSummonerLink(p.secondSum)
return p
}
return detailedMatch
}
/** /**
* Return all the infos about a list of matches built with the Riot API data * Return all the infos about a list of matches built with the Riot API data
* @param {Object} RiotData : all data from the Riot API * @param {Object} RiotData : all data from the Riot API
@ -107,6 +130,7 @@ export function getRankImg(leagueData) {
} }
function getSummonerLink(id) { function getSummonerLink(id) {
if(id === 0) return null
const spellName = Object.entries(summonersJSON.data).find(([, spell]) => Number(spell.key) === id)[0] const spellName = Object.entries(summonersJSON.data).find(([, spell]) => Number(spell.key) === id)[0]
return `https://ddragon.leagueoflegends.com/cdn/${store.getters['ddragon/version']}/img/spell/${spellName}.png` return `https://ddragon.leagueoflegends.com/cdn/${store.getters['ddragon/version']}/img/spell/${spellName}.png`
} }

View file

@ -1,6 +1,7 @@
import Vue from 'vue' import Vue from 'vue'
import Vuex from 'vuex' import Vuex from 'vuex'
import * as ddragon from '@/store/modules/ddragon' import * as ddragon from '@/store/modules/ddragon'
import * as detailedMatch from '@/store/modules/detailedMatch'
import * as notification from '@/store/modules/notification' import * as notification from '@/store/modules/notification'
import * as summoner from '@/store/modules/summoner' import * as summoner from '@/store/modules/summoner'
@ -11,10 +12,12 @@ const debug = process.env.NODE_ENV !== 'production'
export default new Vuex.Store({ export default new Vuex.Store({
modules: { modules: {
ddragon, ddragon,
detailedMatch,
notification, notification,
summoner summoner
}, },
state: { state: {
currentRegion: 'euw1',
regionsList: { regionsList: {
'br': 'br1', 'br': 'br1',
'eune': 'eun1', 'eune': 'eun1',
@ -30,5 +33,15 @@ export default new Vuex.Store({
}, },
roles: ['TOP', 'JUNGLE', 'MIDDLE', 'BOTTOM', 'SUPPORT'] roles: ['TOP', 'JUNGLE', 'MIDDLE', 'BOTTOM', 'SUPPORT']
}, },
mutations: {
UPDATE_REGION(state, newRegion) {
state.currentRegion = state.regionsList[newRegion]
}
},
actions: {
updateCurrentRegion({ commit }, newRegion) {
commit('UPDATE_REGION', newRegion)
}
},
strict: debug strict: debug
}) })

View file

@ -0,0 +1,41 @@
import Vue from 'vue'
import { axios } from '@/plugins/axios'
import { createDetailedMatchData } from '@/helpers/summoner'
export const namespaced = true
export const state = {
matches: []
}
export const mutations = {
MATCHE_LOADING(state, gameId) {
const alreadyIn = state.matches.find(m => m.gameId === gameId)
if (!alreadyIn) {
state.matches.push({ gameId: gameId, status: 'loading' })
}
},
MATCHE_FOUND(state, matchDetails) {
matchDetails.status = 'loaded'
const index = state.matches.findIndex(m => m.gameId === matchDetails.gameId)
Vue.set(state.matches, index, matchDetails)
},
}
export const actions = {
async matchDetails({ commit, rootState }, gameId) {
commit('MATCHE_LOADING', gameId)
console.log('MATCH DETAILS STORE', gameId, rootState.currentRegion)
const resp = await axios(({ url: 'match-details', data: { gameId, region: rootState.currentRegion }, method: 'POST' })).catch(() => { })
console.log('--- DETAILS INFOS ---')
console.log(resp.data)
const detailedMatch = createDetailedMatchData(resp.data.matchDetails)
commit('MATCHE_FOUND', detailedMatch)
}
}
export const getters = {
getMatchDetails: state => gameId => state.matches.find(m => m.gameId === gameId),
}

View file

@ -105,7 +105,7 @@ import { mapState, mapActions, mapGetters } from 'vuex'
import LazyBackground from '@/components/LazyBackgroundImage.vue' import LazyBackground from '@/components/LazyBackgroundImage.vue'
import LoadingButton from '@/components/LoadingButton.vue' import LoadingButton from '@/components/LoadingButton.vue'
import MainFooter from '@/components/MainFooter.vue' import MainFooter from '@/components/MainFooter.vue'
import Match from '@/components/Match.vue' import Match from '@/components/Match/Match.vue'
import RecentActivity from '@/components/Summoner/RecentActivity.vue' import RecentActivity from '@/components/Summoner/RecentActivity.vue'
import SearchForm from '@/components/SearchForm.vue' import SearchForm from '@/components/SearchForm.vue'
import SummonerLoader from '@/components/Summoner/SummonerLoader.vue' import SummonerLoader from '@/components/Summoner/SummonerLoader.vue'
@ -145,11 +145,13 @@ export default {
watch: { watch: {
$route() { $route() {
console.log('route changed') console.log('route changed')
this.updateCurrentRegion(this.region)
this.apiCall() this.apiCall()
} }
}, },
mounted() { mounted() {
this.updateCurrentRegion(this.region)
this.apiCall() this.apiCall()
}, },
@ -160,6 +162,7 @@ export default {
redirect(summoner, region) { redirect(summoner, region) {
this.$router.push(`/summoner/${region}/${summoner}`) this.$router.push(`/summoner/${region}/${summoner}`)
}, },
...mapActions(['updateCurrentRegion']),
...mapActions('summoner', ['summonerRequest', 'moreMatches']), ...mapActions('summoner', ['summonerRequest', 'moreMatches']),
} }
} }

View file

@ -1,5 +1,8 @@
'use strict' 'use strict'
const Jax = use('Jax')
const DetailedMatch = use('App/Models/DetailedMatch')
const DetailedMatchTransformer = use('App/Transformers/DetailedMatchTransformer')
const MatchHelper = use('App/Helpers/MatchHelper') const MatchHelper = use('App/Helpers/MatchHelper')
const Summoner = use('App/Models/Summoner') const Summoner = use('App/Models/Summoner')
@ -22,6 +25,34 @@ class MatchController {
mates: summonerDB.mates.filter(m => m.wins + m.losses > 1) mates: summonerDB.mates.filter(m => m.wins + m.losses > 1)
}) })
} }
/**
* POST - Return details data for one specific match
*/
async show({ request, response }) {
console.log('Match details request')
const gameId = request.input('gameId')
const region = request.input('region')
console.log(gameId, region)
let matchFromRiot = await Jax.Match.get(gameId)
const champions = await Jax.DDragon.Champion.list()
const runes = await Jax.DDragon.Rune.list()
const ctx = {
champions: champions.data,
runes,
MatchHelper
}
matchFromRiot = DetailedMatchTransformer.transform(matchFromRiot, ctx)
// DetailedMatch.save(matchFromRiot)
return response.json({
matchDetails: matchFromRiot
})
}
} }
module.exports = MatchController module.exports = MatchController

View file

@ -1,11 +1,8 @@
'use strict' 'use strict'
const Bumblebee = use('Adonis/Addons/Bumblebee')
const Logger = use('Logger') const Logger = use('Logger')
const Match = use('App/Models/Match')
const Summoner = use('App/Models/Summoner')
const Jax = use('Jax') const Jax = use('Jax')
const MatchTransformer = use('App/Transformers/MatchTransformer') const BasicMatchTransformer = use('App/Transformers/BasicMatchTransformer')
const SummonerHelper = use('App/Helpers/SummonerHelper') const SummonerHelper = use('App/Helpers/SummonerHelper')
class MatchHelper { class MatchHelper {
@ -107,10 +104,20 @@ class MatchHelper {
MatchHelper: this MatchHelper: this
} }
matchesFromApi = await Bumblebee.create().collection(matchesFromApi) // matchesFromApi = await Bumblebee.create().collection(matchesFromApi)
.transformWith(MatchTransformer) // .transformWith(MatchTransformer)
.withContext(ctx) // .withContext(ctx)
.toJSON() // .toJSON()
console.time('newTransformer')
matchesFromApi = matchesFromApi.map(m => {
if (m) return BasicMatchTransformer.transform(m, ctx)
})
console.timeEnd('newTransformer')
console.log(matchesFromApi.length)
Logger.transport('file').info(matchesFromApi)
// Update teamMates // Update teamMates
SummonerHelper.updatePlayedWith(account, summonerDB, matchesFromApi) SummonerHelper.updatePlayedWith(account, summonerDB, matchesFromApi)

View file

@ -0,0 +1,9 @@
'use strict'
/** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */
const Model = use('Model')
class DetailedMatch extends Model {
}
module.exports = DetailedMatch

View file

@ -0,0 +1,132 @@
'use strict'
const MatchTransformer = use('App/Transformers/MatchTransformer')
/**
* BasicMatchTransformer class
*
* @class BasicMatchTransformer
*/
class BasicMatchTransformer extends MatchTransformer {
/**
* This method is used to transform the data.
*/
transform(match, { account, champions, runes, MatchHelper }) {
const participantId = match.participantIdentities.find((p) => p.player.currentAccountId === account.accountId).participantId
const player = match.participants[participantId - 1]
const teamId = player.teamId
let win = match.teams.find((t) => t.teamId === teamId).win
// Match less than 5min
if (match.gameDuration < 300) {
win = 'Remake'
}
const map = match.mapId
const mode = match.queueId
const champion = (({ id, name }) => ({ id, name }))(Object.entries(champions).find(([, champion]) => Number(champion.key) === player.championId)[1])
const role = MatchHelper.getRoleName(player.timeline)
const gameCreation = match.gameCreation
const time = MatchHelper.secToTime(match.gameDuration)
const kills = player.stats.kills
const deaths = player.stats.deaths
const assists = player.stats.assists
let kda
if (kills + assists !== 0 && deaths === 0) {
kda = '∞'
} else {
kda = +(deaths === 0 ? 0 : ((kills + assists) / deaths)).toFixed(2)
}
const level = player.stats.champLevel
const damage = +(player.stats.totalDamageDealtToChampions / 1000).toFixed(1) + 'k'
const totalKills = match.participants.reduce((prev, current) => {
if (current.teamId !== teamId) {
return prev
}
return prev + current.stats.kills
}, 0)
const kp = totalKills === 0 ? '0%' : +((kills + assists) * 100 / totalKills).toFixed(1) + '%'
let primaryRune = null
let secondaryRune = null
if (player.stats.perkPrimaryStyle) {
const primaryRuneCategory = runes.find(r => r.id === player.stats.perkPrimaryStyle)
for (const subCat of primaryRuneCategory.slots) {
primaryRune = subCat.runes.find(r => r.id === player.stats.perk0)
if (primaryRune) {
break
}
}
primaryRune = `https://ddragon.leagueoflegends.com/cdn/img/${primaryRune.icon}`
secondaryRune = runes.find(r => r.id === player.stats.perkSubStyle)
secondaryRune = `https://ddragon.leagueoflegends.com/cdn/img/${secondaryRune.icon}`
}
const items = []
for (let i = 0; i < 6; i++) {
const currentItem = 'item' + i
items.push(player.stats[currentItem])
}
const gold = +(player.stats.goldEarned / 1000).toFixed(1) + 'k'
const minions = player.stats.totalMinionsKilled + player.stats.neutralMinionsKilled
const firstSum = player.spell1Id
const secondSum = player.spell2Id
const allyTeam = []
const enemyTeam = []
for (let summoner of match.participantIdentities) {
const allData = match.participants[summoner.participantId - 1]
const playerInfos = {
name: summoner.player.summonerName,
role: MatchHelper.getRoleName(allData.timeline),
champion: (({ id, name }) => ({ id, name }))(Object.entries(champions).find(([, champion]) => Number(champion.key) === allData.championId)[1])
}
if (allData.teamId === teamId) {
allyTeam.push(playerInfos)
} else {
enemyTeam.push(playerInfos)
}
}
allyTeam.sort(MatchHelper.sortTeamByRole)
enemyTeam.sort(MatchHelper.sortTeamByRole)
return {
summoner_puuid: account.puuid,
gameId: match.gameId,
result: win,
map,
gamemode: mode,
champion,
role,
primaryRune,
secondaryRune,
date: gameCreation,
time,
kills,
deaths,
assists,
kda,
level,
damage,
kp,
items,
gold,
minions,
firstSum,
secondSum,
allyTeam,
enemyTeam
}
}
}
module.exports = new BasicMatchTransformer()

View file

@ -0,0 +1,188 @@
'use strict'
const MatchTransformer = use('App/Transformers/MatchTransformer')
/**
* DetailedMatchTransformer class
*
* @class DetailedMatchTransformer
*/
class DetailedMatchTransformer extends MatchTransformer {
/**
* This method is used to transform the data.
*/
transform(match, { champions, runes, MatchHelper }) {
this.match = match
this.champions = champions
this.runes = runes
this.MatchHelper = MatchHelper
// Global data
const map = match.mapId
const mode = match.queueId
const gameCreation = match.gameCreation
const time = this.MatchHelper.secToTime(match.gameDuration)
// Teams
const firstTeam = this.getTeamData(match.teams[0])
const secondTeam = this.getTeamData(match.teams[1])
return {
gameId: match.gameId,
map,
gamemode: mode,
date: gameCreation,
season: match.seasonId,
time,
blueTeam: firstTeam.color === 'Blue' ? firstTeam : secondTeam,
redTeam: firstTeam.color === 'Blue' ? secondTeam : firstTeam,
}
}
/**
* Get all data of one team
* @param team raw team data from Riot API
*/
getTeamData(team) {
let win = team.win
if (this.match.gameDuration < 300) {
win = 'Remake'
}
// Global stats of the team
const teamPlayers = this.match.participants.filter(p => p.teamId === team.teamId)
const teamStats = teamPlayers.reduce((prev, cur) => {
prev.kills += cur.stats.kills
prev.deaths += cur.stats.deaths
prev.assists += cur.stats.assists
prev.gold += cur.stats.goldEarned
prev.dmgChamp += cur.stats.totalDamageDealtToChampions
prev.dmgObj += cur.stats.damageDealtToObjectives
prev.dmgTaken += cur.stats.totalDamageTaken
return prev;
}, { kills: 0, deaths: 0, assists: 0, gold: 0, dmgChamp: 0, dmgObj: 0, dmgTaken: 0 });
// Bans
let bans = null
if (team.bans) {
bans = team.bans.map(b => {
if (b.championId === -1) {
b.champion = {
id: null,
name: null
}
} else {
b.champion = (({ id, name }) => ({ id, name }))(Object.entries(this.champions).find(([, champion]) => Number(champion.key) === b.championId)[1])
}
return b
})
}
// Players
const players = teamPlayers
.map(p => this.getPlayerData(p, teamStats))
.sort(this.MatchHelper.sortTeamByRole)
return {
bans,
barons: team.baronKills,
color: team.teamId === 100 ? 'Blue' : 'Red',
dragons: team.dragonKills,
inhibitors: team.inhibitorKills,
players,
result: win,
riftHerald: team.riftHeraldKills,
teamStats,
towers: team.towerKills,
}
}
/**
* Get all data for one player
* @param player raw player data from Riot API
* @param teamStats global stats of the team
*/
getPlayerData(player, teamStats) {
const identity = this.match.participantIdentities.find(p => p.participantId === player.participantId)
const name = identity.player.summonerName
const champion = (({ id, name }) => ({ id, name }))(Object.entries(this.champions).find(([, champion]) => Number(champion.key) === player.championId)[1])
const role = this.MatchHelper.getRoleName(player.timeline)
const kills = player.stats.kills
const deaths = player.stats.deaths
const assists = player.stats.assists
let kda
if (kills + assists !== 0 && deaths === 0) {
kda = '∞'
} else {
kda = +(deaths === 0 ? 0 : ((kills + assists) / deaths)).toFixed(2)
}
const level = player.stats.champLevel
// Regular stats / Full match stats
const stats = {
minions: player.stats.totalMinionsKilled + player.stats.neutralMinionsKilled,
vision: player.stats.visionScore,
gold: +(player.stats.goldEarned / 1000).toFixed(1) + 'k',
dmgChamp: +(player.stats.totalDamageDealtToChampions / 1000).toFixed(1) + 'k',
dmgObj: +(player.stats.damageDealtToObjectives / 1000).toFixed(1) + 'k',
dmgTaken: +(player.stats.totalDamageTaken / 1000).toFixed(1) + 'k',
}
// Percent stats / Per minute stats
const percentStats = {
minions: +(stats.minions / (this.match.gameDuration / 60)).toFixed(2),
vision: +(stats.vision / (this.match.gameDuration / 60)).toFixed(2),
dmgChamp: +(player.stats.totalDamageDealtToChampions * 100 / teamStats.dmgChamp).toFixed(1) + '%',
dmgObj: +(player.stats.damageDealtToObjectives * 100 / teamStats.dmgObj).toFixed(1) + '%',
dmgTaken: +(player.stats.totalDamageTaken * 100 / teamStats.dmgTaken).toFixed(1) + '%',
}
const kp = teamStats.kills === 0 ? '0%' : +((kills + assists) * 100 / teamStats.kills).toFixed(1) + '%'
let primaryRune = null
let secondaryRune = null
if (player.stats.perkPrimaryStyle) {
const primaryRuneCategory = this.runes.find(r => r.id === player.stats.perkPrimaryStyle)
for (const subCat of primaryRuneCategory.slots) {
primaryRune = subCat.runes.find(r => r.id === player.stats.perk0)
if (primaryRune) {
break
}
}
primaryRune = `https://ddragon.leagueoflegends.com/cdn/img/${primaryRune.icon}`
secondaryRune = this.runes.find(r => r.id === player.stats.perkSubStyle)
secondaryRune = `https://ddragon.leagueoflegends.com/cdn/img/${secondaryRune.icon}`
}
const items = []
for (let i = 0; i < 6; i++) {
const currentItem = 'item' + i
items.push(player.stats[currentItem])
}
const firstSum = player.spell1Id
const secondSum = player.spell2Id
return {
name,
champion,
role,
primaryRune,
secondaryRune,
kills,
deaths,
assists,
kda,
level,
kp,
items,
firstSum,
secondSum,
stats,
percentStats,
}
}
}
module.exports = new DetailedMatchTransformer()

View file

@ -1,134 +1,18 @@
'use strict' 'use strict'
const BumblebeeTransformer = use('Bumblebee/Transformer')
/** /**
* MatchTransformer class * MatchTransformer class
* *
* @class MatchTransformer * @class MatchTransformer
* @constructor
*/ */
class MatchTransformer extends BumblebeeTransformer { class MatchTransformer {
/** /**
* This method is used to transform the data. * This method is used to transform the data.
*/ */
transform(match, { account, champions, runes, MatchHelper }) { transform (match) {
const participantId = match.participantIdentities.find((p) => p.player.currentAccountId === account.accountId).participantId // TODO : implement this method
const player = match.participants[participantId - 1]
const teamId = player.teamId
let win = match.teams.find((t) => t.teamId === teamId).win
let status = win === 'Win' ? 'Victory' : 'Defeat'
// Match less than 5min
if (match.gameDuration < 300) {
win = 'Remake'
status = 'Remake'
}
const map = match.mapId
const mode = match.queueId
const champion = (({ id, name }) => ({ id, name }))(Object.entries(champions).find(([, champion]) => Number(champion.key) === player.championId)[1])
const role = MatchHelper.getRoleName(player.timeline)
const gameCreation = match.gameCreation
const time = MatchHelper.secToTime(match.gameDuration)
const kills = player.stats.kills
const deaths = player.stats.deaths
const assists = player.stats.assists
let kda
if (kills + assists !== 0 && deaths === 0) {
kda = '∞'
} else {
kda = +(deaths === 0 ? 0 : ((kills + assists) / deaths)).toFixed(2)
}
const level = player.stats.champLevel
const damage = +(player.stats.totalDamageDealtToChampions / 1000).toFixed(1) + 'k'
const totalKills = match.participants.reduce((prev, current) => {
if (current.teamId !== teamId) {
return prev
}
return prev + current.stats.kills
}, 0)
const kp = totalKills === 0 ? '0%' : +((kills + assists) * 100 / totalKills).toFixed(1) + '%'
let primaryRune = null
let secondaryRune = null
if (player.stats.perkPrimaryStyle) {
const primaryRuneCategory = runes.find(r => r.id === player.stats.perkPrimaryStyle)
for (const subCat of primaryRuneCategory.slots) {
primaryRune = subCat.runes.find(r => r.id === player.stats.perk0)
if (primaryRune) {
break
}
}
primaryRune = `https://ddragon.leagueoflegends.com/cdn/img/${primaryRune.icon}`
secondaryRune = runes.find(r => r.id === player.stats.perkSubStyle)
secondaryRune = `https://ddragon.leagueoflegends.com/cdn/img/${secondaryRune.icon}`
}
const items = []
for (let i = 0; i < 6; i++) {
const currentItem = 'item' + i
items.push(player.stats[currentItem])
}
const gold = +(player.stats.goldEarned / 1000).toFixed(1) + 'k'
const minions = player.stats.totalMinionsKilled + player.stats.neutralMinionsKilled
const firstSum = player.spell1Id
const secondSum = player.spell2Id
const allyTeam = []
const enemyTeam = []
for (let summoner of match.participantIdentities) {
const allData = match.participants[summoner.participantId - 1]
const playerInfos = {
name: summoner.player.summonerName,
role: MatchHelper.getRoleName(allData.timeline),
champion: (({ id, name }) => ({ id, name }))(Object.entries(champions).find(([, champion]) => Number(champion.key) === allData.championId)[1])
}
if (allData.teamId === teamId) {
allyTeam.push(playerInfos)
} else {
enemyTeam.push(playerInfos)
}
}
allyTeam.sort(MatchHelper.sortTeamByRole)
enemyTeam.sort(MatchHelper.sortTeamByRole)
return { return {
summoner_puuid: account.puuid, // add your transformation object here
gameId: match.gameId,
result: win,
status,
map,
gamemode: mode,
champion,
role,
primaryRune,
secondaryRune,
date: gameCreation,
time,
kills,
deaths,
assists,
kda,
level,
damage,
kp,
items,
gold,
minions,
firstSum,
secondSum,
allyTeam,
enemyTeam
} }
} }
} }

View file

@ -0,0 +1,18 @@
'use strict'
/** @type {import('@adonisjs/lucid/src/Schema')} */
const Schema = use('Schema')
class DetailedMatchSchema extends Schema {
up () {
this.create('detailed_matches', (collection) => {
collection.index('gameId', {gameId: 1})
})
}
down () {
this.drop('detailed_matches')
}
}
module.exports = DetailedMatchSchema

View file

@ -28,3 +28,4 @@ Route.get('/', async () => {
Route.post('/api', 'SummonerController.api') Route.post('/api', 'SummonerController.api')
Route.post('/ddragon', 'DDragonController.index') Route.post('/ddragon', 'DDragonController.index')
Route.post('/match', 'MatchController.index') Route.post('/match', 'MatchController.index')
Route.post('/match-details', 'MatchController.show')