Added lanes (first try)
@@ -1,20 +1,68 @@
|
|||||||
<script setup>
|
<script setup lang="ts">
|
||||||
const {data: championsData} = await useFetch(CDRAGON_BASE + "plugins/rcp-be-lol-game-data/global/default/v1/champion-summary.json")
|
type ChampionResponse = {
|
||||||
|
data: Ref<Array<Champion>>
|
||||||
|
}
|
||||||
|
type Champion = {
|
||||||
|
name: string
|
||||||
|
alias: string
|
||||||
|
squarePortraitPath: string
|
||||||
|
}
|
||||||
|
const {data: championsData} : ChampionResponse = await useFetch(CDRAGON_BASE + "plugins/rcp-be-lol-game-data/global/default/v1/champion-summary.json")
|
||||||
const champions = championsData.value.slice(1).sort((a, b) => {
|
const champions = championsData.value.slice(1).sort((a, b) => {
|
||||||
if(a.name < b.name) return -1;
|
if(a.name < b.name) return -1;
|
||||||
if(a.name > b.name) return 1;
|
if(a.name > b.name) return 1;
|
||||||
return 0;
|
return 0;
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const {data: championsLanes} : {data: Ref<any>} = await useFetch("/api/champions")
|
||||||
|
const lanesMap = new Map()
|
||||||
|
for(let champion of championsLanes.value) {
|
||||||
|
lanesMap.set(champion.alias, champion.lanes)
|
||||||
|
}
|
||||||
|
console.log(lanesMap)
|
||||||
|
|
||||||
const filteredChampions = ref(champions)
|
const filteredChampions = ref(champions)
|
||||||
|
|
||||||
const searchBar = ref(null)
|
const searchBar = useTemplateRef("searchBar")
|
||||||
watch(searchBar, (newS, oldS) => {searchBar.value.focus()})
|
watch(searchBar, (newS, oldS) => {searchBar.value?.focus()})
|
||||||
const searchText = ref("")
|
const searchText = ref("")
|
||||||
watch(searchText, (newT, oldT) => {
|
watch(searchText, (newT, oldT) => {
|
||||||
filteredChampions.value = champions.filter((champion) => champion.name.toLowerCase().includes(searchText.value.toLowerCase()))
|
filteredChampions.value = champions.filter((champion) => champion.name.toLowerCase().includes(searchText.value.toLowerCase()))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function filterToLane(filter: number) {
|
||||||
|
switch(filter) {
|
||||||
|
case 0:
|
||||||
|
return "TOP";
|
||||||
|
case 1:
|
||||||
|
return "JUNGLE";
|
||||||
|
case 2:
|
||||||
|
return "MIDDLE";
|
||||||
|
case 3:
|
||||||
|
return "BOTTOM";
|
||||||
|
case 4:
|
||||||
|
return "UTILITY";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onLaneFilterChange(newValue: number) {
|
||||||
|
if(newValue != -1) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
filteredChampions.value = champions.filter((champion) => {
|
||||||
|
const lanes : Array<any> = lanesMap.get(champion.alias.toLowerCase())
|
||||||
|
if(lanes == undefined) return false;
|
||||||
|
|
||||||
|
return lanes.reduce((acc : boolean, current : {data:string, count:number}) =>
|
||||||
|
acc || (current.data == filterToLane(newValue)), false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
filteredChampions.value = champions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function submit() {
|
async function submit() {
|
||||||
await navigateTo("/champion/" + filteredChampions.value[0].alias.toLowerCase())
|
await navigateTo("/champion/" + filteredChampions.value[0].alias.toLowerCase())
|
||||||
}
|
}
|
||||||
@@ -23,7 +71,8 @@ async function submit() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div style="width: fit-content; margin: auto;">
|
<div style="width: fit-content; margin: auto; display: flex; align-items: center;">
|
||||||
|
<LaneFilter @filter-change="(value : number) => onLaneFilterChange(value)" style="margin-right: 20px;"/>
|
||||||
<input @keyup.enter="submit" v-model="searchText" ref="searchBar" class="search-bar" type="text" placeholder="Search a champion"/>
|
<input @keyup.enter="submit" v-model="searchText" ref="searchBar" class="search-bar" type="text" placeholder="Search a champion"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="champion-container">
|
<div class="champion-container">
|
||||||
@@ -39,15 +88,15 @@ async function submit() {
|
|||||||
<style>
|
<style>
|
||||||
.search-bar {
|
.search-bar {
|
||||||
width: 400px;
|
width: 400px;
|
||||||
height: 40px;
|
height: 60px;
|
||||||
|
|
||||||
background-color: var(--color-surface-darker);
|
background-color: var(--color-surface-darker);
|
||||||
|
|
||||||
font-size: 20px;
|
font-size: 28px;
|
||||||
|
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
border: none;
|
border: none;
|
||||||
padding-left: 5px;
|
padding-left: 10px;
|
||||||
}
|
}
|
||||||
.search-bar:focus {
|
.search-bar:focus {
|
||||||
border: 2px solid var(--color-on-surface);
|
border: 2px solid var(--color-on-surface);
|
||||||
|
|||||||
62
frontend/components/LaneFilter.vue
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const emit = defineEmits<{
|
||||||
|
filterChange: [value: number]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const POSITIONS = ["top", "jungle", "middle", "bottom", "utility"]
|
||||||
|
const LANE_IMAGES = Array(5).fill("").map((_, index) => "/img/lanes/icon-position-" + POSITIONS[index] + ".png")
|
||||||
|
const LANE_IMAGES_HOVER = Array(5).fill("").map((_, index) => "/img/lanes/icon-position-" + POSITIONS[index] + "-hover.png")
|
||||||
|
const LANE_IMAGES_SELECTED = Array(5).fill("").map((_, index) => "/img/lanes/icon-position-" + POSITIONS[index] + "-blue.png")
|
||||||
|
|
||||||
|
const laneImgs = Array(5).fill(ref("")).map((_, index) => ref(LANE_IMAGES[index]))
|
||||||
|
const laneFilter = ref(-1)
|
||||||
|
|
||||||
|
function selectLaneFilter(index: number) {
|
||||||
|
// Unselect previous filter
|
||||||
|
if(laneFilter.value != -1) {
|
||||||
|
laneImgs[laneFilter.value].value = LANE_IMAGES[laneFilter.value]
|
||||||
|
|
||||||
|
// This is a deselection.
|
||||||
|
if(laneFilter.value == index) {
|
||||||
|
laneFilter.value = -1;
|
||||||
|
emit('filterChange', laneFilter.value)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select new one
|
||||||
|
laneImgs[index].value = LANE_IMAGES_SELECTED[index]
|
||||||
|
laneFilter.value = index
|
||||||
|
emit('filterChange', laneFilter.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseOut(laneImg: Ref<string>, index: number) {
|
||||||
|
if(laneImg.value == LANE_IMAGES_HOVER[index])
|
||||||
|
laneImg.value = LANE_IMAGES[index]
|
||||||
|
}
|
||||||
|
function handleHover(laneImg: Ref<string>, index: number) {
|
||||||
|
if(laneImg.value == LANE_IMAGES[index])
|
||||||
|
laneImg.value = LANE_IMAGES_HOVER[index]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div style="width: fit-content; margin: auto;">
|
||||||
|
<img v-for="(laneImg, index) in laneImgs"
|
||||||
|
class="lane-img" :src="laneImg.value"
|
||||||
|
@mouseout="handleMouseOut(laneImg, index)"
|
||||||
|
@mouseover="handleHover(laneImg, index)"
|
||||||
|
@click="selectLaneFilter(index)"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.lane-img {
|
||||||
|
width: 64px;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
.lane-img:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -5,7 +5,7 @@ export default defineNuxtConfig({
|
|||||||
css: ['~/assets/css/main.css'],
|
css: ['~/assets/css/main.css'],
|
||||||
|
|
||||||
routeRules: {
|
routeRules: {
|
||||||
'/' : {prerender: true},
|
'/' : {prerender: false, swr: true},
|
||||||
'/champion/**' : {swr: true}
|
'/champion/**' : {swr: true}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
BIN
frontend/public/img/lanes/icon-position-bottom-blue.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
frontend/public/img/lanes/icon-position-bottom-hover.png
Normal file
|
After Width: | Height: | Size: 665 B |
BIN
frontend/public/img/lanes/icon-position-bottom.png
Normal file
|
After Width: | Height: | Size: 665 B |
BIN
frontend/public/img/lanes/icon-position-jungle-blue.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
frontend/public/img/lanes/icon-position-jungle-hover.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
frontend/public/img/lanes/icon-position-jungle.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
frontend/public/img/lanes/icon-position-middle-blue.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
frontend/public/img/lanes/icon-position-middle-hover.png
Normal file
|
After Width: | Height: | Size: 577 B |
BIN
frontend/public/img/lanes/icon-position-middle.png
Normal file
|
After Width: | Height: | Size: 574 B |
BIN
frontend/public/img/lanes/icon-position-top-blue.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
frontend/public/img/lanes/icon-position-top-hover.png
Normal file
|
After Width: | Height: | Size: 581 B |
BIN
frontend/public/img/lanes/icon-position-top.png
Normal file
|
After Width: | Height: | Size: 582 B |
BIN
frontend/public/img/lanes/icon-position-utility-blue.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
frontend/public/img/lanes/icon-position-utility-hover.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
frontend/public/img/lanes/icon-position-utility.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
@@ -1,17 +1,5 @@
|
|||||||
import { MongoClient } from 'mongodb'
|
import {connectToDatabase, fetchLatestPatch} from '../../utils/mongo'
|
||||||
|
|
||||||
async function connectToDatabase() {
|
|
||||||
// Create a MongoClient with a MongoClientOptions object to set the Stable API version
|
|
||||||
const client = new MongoClient(`mongodb://${process.env.MONGO_USER}:${process.env.MONGO_PASS}@mongo:27017`)
|
|
||||||
await client.connect()
|
|
||||||
return client
|
|
||||||
}
|
|
||||||
async function fetchLatestPatch(client) {
|
|
||||||
const database = client.db("patches");
|
|
||||||
const patches = database.collection("patches");
|
|
||||||
const latestPatch = await patches.find().limit(1).sort({date:-1}).next()
|
|
||||||
return latestPatch.patch
|
|
||||||
}
|
|
||||||
async function championInfos(client, patch, championAlias) {
|
async function championInfos(client, patch, championAlias) {
|
||||||
const database = client.db("champions");
|
const database = client.db("champions");
|
||||||
const collection = database.collection(patch);
|
const collection = database.collection(patch);
|
||||||
|
|||||||
23
frontend/server/api/champions.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import {connectToDatabase, fetchLatestPatch} from '../utils/mongo'
|
||||||
|
|
||||||
|
async function champions(client, patch) {
|
||||||
|
const database = client.db("champions");
|
||||||
|
const collection = database.collection(patch);
|
||||||
|
const data = await collection.find().toArray()
|
||||||
|
data.map((x) => {
|
||||||
|
delete x.runes
|
||||||
|
delete x.builds
|
||||||
|
})
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
const client = await connectToDatabase();
|
||||||
|
const latestPatch = await fetchLatestPatch(client);
|
||||||
|
|
||||||
|
const data = await champions(client, latestPatch);
|
||||||
|
|
||||||
|
await client.close()
|
||||||
|
|
||||||
|
return data
|
||||||
|
})
|
||||||
17
frontend/server/utils/mongo.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { MongoClient } from 'mongodb'
|
||||||
|
|
||||||
|
async function connectToDatabase() {
|
||||||
|
// Create a MongoClient with a MongoClientOptions object to set the Stable API version
|
||||||
|
const client = new MongoClient(`mongodb://${process.env.MONGO_USER}:${process.env.MONGO_PASS}@mongo:27017`)
|
||||||
|
await client.connect()
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchLatestPatch(client) {
|
||||||
|
const database = client.db("patches");
|
||||||
|
const patches = database.collection("patches");
|
||||||
|
const latestPatch = await patches.find().limit(1).sort({date:-1}).next()
|
||||||
|
return latestPatch.patch
|
||||||
|
}
|
||||||
|
|
||||||
|
export {connectToDatabase, fetchLatestPatch}
|
||||||
@@ -141,6 +141,7 @@ async function championInfos(client, patch: number, champion: Champion) {
|
|||||||
let winningMatches = 0;
|
let winningMatches = 0;
|
||||||
let losingMatches = 0;
|
let losingMatches = 0;
|
||||||
let totalMatches = 0;
|
let totalMatches = 0;
|
||||||
|
const lanes : Array<{data: string, count: number}> = [];
|
||||||
const runes : Array<Rune> = [];
|
const runes : Array<Rune> = [];
|
||||||
const builds : Builds = {tree:treeInit(), start: [], bootsFirst: 0, boots: [], lateGame: []}
|
const builds : Builds = {tree:treeInit(), start: [], bootsFirst: 0, boots: [], lateGame: []}
|
||||||
for await (let match of allMatches) {
|
for await (let match of allMatches) {
|
||||||
@@ -156,6 +157,12 @@ async function championInfos(client, patch: number, champion: Champion) {
|
|||||||
else
|
else
|
||||||
losingMatches += 1;
|
losingMatches += 1;
|
||||||
|
|
||||||
|
// Lanes
|
||||||
|
// TODO: make stats lane-dependant
|
||||||
|
const already = lanes.find((x) => x.data == participant.teamPosition)
|
||||||
|
if(already == undefined) lanes.push({count:1, data: participant.teamPosition})
|
||||||
|
else already.count += 1
|
||||||
|
|
||||||
// Runes
|
// Runes
|
||||||
handleParticipantRunes(participant, runes)
|
handleParticipantRunes(participant, runes)
|
||||||
|
|
||||||
@@ -168,6 +175,9 @@ async function championInfos(client, patch: number, champion: Champion) {
|
|||||||
|
|
||||||
let totalChampionMatches = winningMatches + losingMatches;
|
let totalChampionMatches = winningMatches + losingMatches;
|
||||||
|
|
||||||
|
lanes.sort((a, b) => b.count - a.count)
|
||||||
|
arrayRemovePercentage(lanes, totalChampionMatches, 0.15)
|
||||||
|
|
||||||
// Filter runes to keep 3 most played
|
// Filter runes to keep 3 most played
|
||||||
runes.sort((a, b) => b.count - a.count)
|
runes.sort((a, b) => b.count - a.count)
|
||||||
if(runes.length > 3)
|
if(runes.length > 3)
|
||||||
@@ -198,6 +208,7 @@ async function championInfos(client, patch: number, champion: Champion) {
|
|||||||
return {name: champion.name,
|
return {name: champion.name,
|
||||||
alias: champion.alias.toLowerCase(),
|
alias: champion.alias.toLowerCase(),
|
||||||
id: championId,
|
id: championId,
|
||||||
|
lanes: lanes,
|
||||||
winrate: winningMatches / totalChampionMatches,
|
winrate: winningMatches / totalChampionMatches,
|
||||||
gameCount: totalChampionMatches,
|
gameCount: totalChampionMatches,
|
||||||
pickrate: totalChampionMatches/totalMatches,
|
pickrate: totalChampionMatches/totalMatches,
|
||||||
@@ -236,7 +247,6 @@ async function makeChampionsStats(client, patch : number) {
|
|||||||
|
|
||||||
const database = client.db("champions")
|
const database = client.db("champions")
|
||||||
const collection = database.collection(patch)
|
const collection = database.collection(patch)
|
||||||
await collection.createIndex({id:1})
|
|
||||||
await collection.createIndex({alias:1})
|
await collection.createIndex({alias:1})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||