350 lines
9.9 KiB
Vue
350 lines
9.9 KiB
Vue
<script setup lang="ts">
|
|
import { isEmpty, deepClone } from '~/utils/helpers'
|
|
|
|
const props = defineProps<{
|
|
builds: Builds
|
|
loading?: boolean
|
|
error?: boolean
|
|
}>()
|
|
|
|
// Constants
|
|
const ITEMS_API_URL = CDRAGON_BASE + 'plugins/rcp-be-lol-game-data/global/default/v1/items.json'
|
|
|
|
// State
|
|
const {
|
|
data: items,
|
|
pending: loadingItems,
|
|
error: itemsError
|
|
} = useFetch(ITEMS_API_URL, {
|
|
lazy: true, // Don't block rendering
|
|
server: false // Client-side only
|
|
})
|
|
const itemMap = ref<Map<number, unknown>>(new Map())
|
|
|
|
// Initialize item map
|
|
watch(
|
|
items,
|
|
newItems => {
|
|
try {
|
|
const itemsData = newItems || []
|
|
if (Array.isArray(itemsData)) {
|
|
const map = new Map<number, unknown>()
|
|
for (const item of itemsData) {
|
|
if (item?.id) {
|
|
map.set(item.id, item)
|
|
}
|
|
}
|
|
itemMap.value = map
|
|
}
|
|
} catch (error) {
|
|
console.error('Error initializing item map:', error)
|
|
}
|
|
},
|
|
{ immediate: true }
|
|
)
|
|
|
|
// Builds management
|
|
const builds = ref<Builds>(deepClone(props.builds))
|
|
|
|
watch(
|
|
() => props.builds,
|
|
newBuilds => {
|
|
builds.value = deepClone(newBuilds)
|
|
trimBuilds(builds.value)
|
|
trimLateGameItems(builds.value)
|
|
},
|
|
{ deep: true }
|
|
)
|
|
|
|
// Initialize with trimmed builds
|
|
onMounted(() => {
|
|
trimBuilds(builds.value)
|
|
trimLateGameItems(builds.value)
|
|
})
|
|
|
|
/**
|
|
* Trim builds tree to show only primary build paths
|
|
*/
|
|
function trimBuilds(builds: Builds): void {
|
|
if (!builds?.tree?.children) return
|
|
|
|
// Keep only the first child (primary build path)
|
|
builds.tree.children.splice(1, builds.tree.children.length - 1)
|
|
|
|
// For the primary path, keep only the first child of the first child
|
|
if (builds.tree.children[0]?.children) {
|
|
builds.tree.children[0].children.splice(1, builds.tree.children[0].children.length - 1)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove items from lateGame that are already in the build tree
|
|
*/
|
|
function trimLateGameItems(builds: Builds): void {
|
|
if (!builds?.tree || isEmpty(builds.lateGame)) return
|
|
|
|
function trimLateGameItemsFromTree(tree: ItemTree): void {
|
|
const foundIndex = builds.lateGame.findIndex(x => x.data === tree.data)
|
|
if (foundIndex !== -1) {
|
|
builds.lateGame.splice(foundIndex, 1)
|
|
}
|
|
|
|
for (const child of tree.children || []) {
|
|
trimLateGameItemsFromTree(child)
|
|
}
|
|
}
|
|
|
|
trimLateGameItemsFromTree(builds.tree)
|
|
}
|
|
|
|
// Error and loading states
|
|
const _hasError = computed(() => itemsError.value || props.error)
|
|
const _isLoading = computed(() => loadingItems.value || props.loading)
|
|
</script>
|
|
|
|
<template>
|
|
<div id="iv-container">
|
|
<div>
|
|
<!-- Start items -->
|
|
<ItemBox v-if="builds.suppItems == undefined || builds.suppItems == null" title="start">
|
|
<div class="iv-items-container">
|
|
<div
|
|
v-for="item in builds.start"
|
|
:key="item.data"
|
|
style="margin-left: 5px; margin-right: 5px"
|
|
>
|
|
<NuxtImg
|
|
v-if="item.data != null && item.data != undefined && itemMap.get(item.data)"
|
|
class="item-img"
|
|
width="64"
|
|
height="64"
|
|
:alt="item.data.toString()"
|
|
:src="CDRAGON_BASE + mapPath((itemMap.get(item.data) as any).iconPath)"
|
|
/>
|
|
<div
|
|
v-else
|
|
style="
|
|
width: 64px;
|
|
height: 64px;
|
|
margin: 10px;
|
|
background: var(--color-surface);
|
|
border: 1px solid var(--color-on-surface);
|
|
"
|
|
></div>
|
|
<h3 style="width: fit-content; margin: auto; margin-bottom: 10px">
|
|
{{ ((item.count / builds.tree.count) * 100).toFixed(0) }}%
|
|
</h3>
|
|
</div>
|
|
</div>
|
|
</ItemBox>
|
|
<!-- Supp items -->
|
|
<ItemBox v-if="builds.suppItems != undefined && builds.suppItems != null" title="supp">
|
|
<div class="iv-items-container">
|
|
<div
|
|
v-for="item in builds.suppItems"
|
|
:key="item.data"
|
|
style="margin-left: 5px; margin-right: 5px"
|
|
>
|
|
<NuxtImg
|
|
v-if="item.data != null && item.data != undefined && itemMap.get(item.data)"
|
|
class="item-img"
|
|
width="64"
|
|
height="64"
|
|
:alt="item.data.toString()"
|
|
:src="CDRAGON_BASE + mapPath((itemMap.get(item.data) as any).iconPath)"
|
|
/>
|
|
<div
|
|
v-else
|
|
style="
|
|
width: 64px;
|
|
height: 64px;
|
|
margin: 10px;
|
|
background: var(--color-surface);
|
|
border: 1px solid var(--color-on-surface);
|
|
"
|
|
></div>
|
|
<h3 style="width: fit-content; margin: auto; margin-bottom: 10px">
|
|
{{ ((item.count / builds.tree.count) * 100).toFixed(0) }}%
|
|
</h3>
|
|
</div>
|
|
</div>
|
|
</ItemBox>
|
|
</div>
|
|
|
|
<!-- Boots first : when champion rush boots -->
|
|
<ItemBox v-if="builds.bootsFirst > 0.5" title="boots rush" :boots-first="builds.bootsFirst">
|
|
<div class="iv-items-container">
|
|
<div
|
|
v-for="item in builds.boots"
|
|
:key="item.data"
|
|
style="margin-left: 5px; margin-right: 5px"
|
|
>
|
|
<NuxtImg
|
|
v-if="item.data != null && item.data != undefined && itemMap.get(item.data)"
|
|
class="item-img"
|
|
width="64"
|
|
height="64"
|
|
:alt="item.data.toString()"
|
|
:src="CDRAGON_BASE + mapPath((itemMap.get(item.data) as any).iconPath)"
|
|
/>
|
|
<div
|
|
v-else
|
|
style="
|
|
width: 64px;
|
|
height: 64px;
|
|
margin: 10px;
|
|
background: var(--color-surface);
|
|
border: 1px solid var(--color-on-surface);
|
|
"
|
|
></div>
|
|
<h3 style="width: fit-content; margin: auto; margin-bottom: 10px">
|
|
{{ ((item.count / builds.tree.count) * 100).toFixed(0) }}%
|
|
</h3>
|
|
</div>
|
|
</div>
|
|
</ItemBox>
|
|
|
|
<!-- Core items -->
|
|
<ItemBox title="core">
|
|
<ItemTree style="margin: auto; width: fit-content" :tree="builds.tree" />
|
|
</ItemBox>
|
|
|
|
<!-- Boots -->
|
|
<ItemBox v-if="builds.bootsFirst <= 0.5" title="boots">
|
|
<div class="iv-items-container">
|
|
<div
|
|
v-for="item in builds.boots.slice(0, 4)"
|
|
:key="item.data"
|
|
style="margin-left: 5px; margin-right: 5px"
|
|
>
|
|
<NuxtImg
|
|
v-if="item.data != null && item.data != undefined && itemMap.get(item.data)"
|
|
class="item-img"
|
|
width="64"
|
|
height="64"
|
|
:alt="item.data.toString()"
|
|
:src="CDRAGON_BASE + mapPath((itemMap.get(item.data) as any).iconPath)"
|
|
/>
|
|
<div
|
|
v-else
|
|
style="
|
|
width: 64px;
|
|
height: 64px;
|
|
margin: 10px;
|
|
background: var(--color-surface);
|
|
border: 1px solid var(--color-on-surface);
|
|
"
|
|
></div>
|
|
<h3 style="width: fit-content; margin: auto; margin-bottom: 10px">
|
|
{{ ((item.count / builds.tree.count) * 100).toFixed(0) }}%
|
|
</h3>
|
|
</div>
|
|
</div>
|
|
</ItemBox>
|
|
|
|
<!-- Late game items -->
|
|
<ItemBox title="late game">
|
|
<div id="iv-late-game-container">
|
|
<div class="iv-items-container">
|
|
<div
|
|
v-for="item in builds.lateGame.slice(0, 4)"
|
|
:key="item.data"
|
|
style="margin-left: 5px; margin-right: 5px"
|
|
>
|
|
<NuxtImg
|
|
v-if="item.data != null && item.data != undefined && itemMap.get(item.data)"
|
|
class="item-img"
|
|
width="64"
|
|
height="64"
|
|
:alt="item.data.toString()"
|
|
:src="CDRAGON_BASE + mapPath((itemMap.get(item.data) as any).iconPath)"
|
|
/>
|
|
<div
|
|
v-else
|
|
style="
|
|
width: 64px;
|
|
height: 64px;
|
|
margin: 10px;
|
|
background: var(--color-surface);
|
|
border: 1px solid var(--color-on-surface);
|
|
"
|
|
></div>
|
|
<h3 style="width: fit-content; margin: auto; margin-bottom: 10px">
|
|
{{ ((item.count / builds.tree.count) * 100).toFixed(0) }}%
|
|
</h3>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="builds.lateGame.length > 4" class="iv-items-container">
|
|
<div
|
|
v-for="item in builds.lateGame.slice(4, 8)"
|
|
:key="item.data"
|
|
style="margin-left: 5px; margin-right: 5px"
|
|
>
|
|
<NuxtImg
|
|
v-if="item.data != null && item.data != undefined && itemMap.get(item.data)"
|
|
class="item-img"
|
|
width="64"
|
|
height="64"
|
|
:alt="item.data.toString()"
|
|
:src="CDRAGON_BASE + mapPath((itemMap.get(item.data) as any).iconPath)"
|
|
/>
|
|
<div
|
|
v-else
|
|
style="
|
|
width: 64px;
|
|
height: 64px;
|
|
margin: 10px;
|
|
background: var(--color-surface);
|
|
border: 1px solid var(--color-on-surface);
|
|
"
|
|
></div>
|
|
<h3 style="width: fit-content; margin: auto; margin-bottom: 10px">
|
|
{{ ((item.count / builds.tree.count) * 100).toFixed(0) }}%
|
|
</h3>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</ItemBox>
|
|
</div>
|
|
</template>
|
|
|
|
<style>
|
|
#iv-container {
|
|
display: flex;
|
|
width: fit-content;
|
|
height: fit-content;
|
|
}
|
|
.iv-items-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
width: fit-content;
|
|
height: fit-content;
|
|
margin: auto;
|
|
}
|
|
.item-img {
|
|
border: 1px solid var(--color-on-surface);
|
|
margin: 10px;
|
|
}
|
|
#iv-late-game-container {
|
|
display: flex;
|
|
}
|
|
|
|
@media only screen and (max-width: 1000px) {
|
|
#iv-container {
|
|
flex-direction: column;
|
|
width: 100%;
|
|
}
|
|
.iv-items-container {
|
|
flex-direction: row;
|
|
}
|
|
.item-img {
|
|
width: 48px;
|
|
height: 48px;
|
|
}
|
|
#iv-late-game-container {
|
|
flex-direction: column;
|
|
}
|
|
}
|
|
</style>
|