mirror of
https://github.com/vkaelin/LeagueStats.git
synced 2026-03-25 12:57:28 +00:00
feat: display match details (1st version: code needs some work)
This commit is contained in:
parent
42ac3f6bcc
commit
cce91efb67
18 changed files with 910 additions and 166 deletions
|
|
@ -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>
|
||||||
|
|
|
||||||
125
client/src/components/Match/DetailedMatch.vue
Normal file
125
client/src/components/Match/DetailedMatch.vue
Normal 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>
|
||||||
258
client/src/components/Match/DetailedMatchTeam.vue
Normal file
258
client/src/components/Match/DetailedMatchTeam.vue
Normal 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>
|
||||||
|
|
@ -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%,
|
||||||
6
client/src/components/SVGContainer.vue
Normal file
6
client/src/components/SVGContainer.vue
Normal 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>
|
||||||
|
|
@ -81,8 +81,6 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
console.log('activity')
|
|
||||||
|
|
||||||
this.createGrid()
|
this.createGrid()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
})
|
})
|
||||||
|
|
|
||||||
41
client/src/store/modules/detailedMatch.js
Normal file
41
client/src/store/modules/detailedMatch.js
Normal 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),
|
||||||
|
}
|
||||||
|
|
@ -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']),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
9
server/app/Models/DetailedMatch.js
Normal file
9
server/app/Models/DetailedMatch.js
Normal 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
|
||||||
132
server/app/Transformers/BasicMatchTransformer.js
Normal file
132
server/app/Transformers/BasicMatchTransformer.js
Normal 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()
|
||||||
188
server/app/Transformers/DetailedMatchTransformer.js
Normal file
188
server/app/Transformers/DetailedMatchTransformer.js
Normal 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()
|
||||||
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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')
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue