frontend: add item tooltip, refactor with itemicon component
This commit is contained in:
@@ -12,20 +12,16 @@ const emit = defineEmits<{
|
||||
parentReady: []
|
||||
}>()
|
||||
|
||||
const { data: items, pending: itemsLoading } = useFetch<Array<{ id: number; iconPath: string }>>(
|
||||
'/api/cdragon/items',
|
||||
{
|
||||
lazy: true, // Don't block rendering
|
||||
server: false // Client-side only
|
||||
}
|
||||
)
|
||||
const { data: items, pending: itemsLoading } = useFetch<Array<Item>>('/api/cdragon/items', {
|
||||
lazy: true, // Don't block rendering
|
||||
server: false // Client-side only
|
||||
})
|
||||
|
||||
// Track image loading state
|
||||
const imagesLoaded = ref(false)
|
||||
const imageElement: Ref<HTMLImageElement | null> = ref(null)
|
||||
|
||||
// Create item map reactively
|
||||
const itemMap = reactive(new Map<number, { id: number; iconPath: string }>())
|
||||
const itemMap = reactive(new Map<number, Item>())
|
||||
watch(
|
||||
items,
|
||||
newItems => {
|
||||
@@ -39,15 +35,13 @@ watch(
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
function getItemIconPath(itemId: number): string {
|
||||
const item = itemMap.get(itemId)
|
||||
return item ? CDRAGON_BASE + mapPath(item.iconPath) : ''
|
||||
}
|
||||
|
||||
const start: Ref<Element | null> = useTemplateRef('start')
|
||||
const startTreeItem = useTemplateRef('start')
|
||||
const arrows: Array<svgdomarrowsLinePath> = []
|
||||
const pendingChildMounts: Array<Element> = []
|
||||
|
||||
// Get the actual icon element for arrow drawing
|
||||
const startElement = computed(() => startTreeItem.value?.iconElement ?? null)
|
||||
|
||||
// Function to wait for an image to load
|
||||
function waitForImageLoad(imgElement: HTMLImageElement): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
@@ -91,12 +85,12 @@ onMounted(async () => {
|
||||
}
|
||||
})
|
||||
|
||||
if (start.value) {
|
||||
const imgElement = start.value as HTMLImageElement
|
||||
imageElement.value = imgElement
|
||||
|
||||
// Wait for own image to load
|
||||
await waitForImageLoad(imgElement)
|
||||
if (startElement.value) {
|
||||
// Wait for the ItemIcon to load its image
|
||||
const imgElement = startElement.value.querySelector('img')
|
||||
if (imgElement) {
|
||||
await waitForImageLoad(imgElement as HTMLImageElement)
|
||||
}
|
||||
|
||||
// Now that image is loaded and DOM is ready, draw arrows
|
||||
imagesLoaded.value = true
|
||||
@@ -108,7 +102,7 @@ onMounted(async () => {
|
||||
if (pendingChildMounts.length > 0) {
|
||||
await nextTick()
|
||||
for (const childEnd of pendingChildMounts) {
|
||||
drawArrow(start.value!, childEnd)
|
||||
drawArrow(startElement.value!, childEnd)
|
||||
}
|
||||
pendingChildMounts.length = 0
|
||||
}
|
||||
@@ -117,7 +111,7 @@ onMounted(async () => {
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
refreshArrows()
|
||||
emit('mount', start.value!)
|
||||
emit('mount', startElement.value!)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -133,12 +127,12 @@ onBeforeUpdate(() => {
|
||||
onUpdated(async () => {
|
||||
await nextTick()
|
||||
|
||||
if (start.value && imagesLoaded.value) {
|
||||
if (startElement.value && imagesLoaded.value) {
|
||||
// Redraw arrows after DOM update
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
refreshArrows()
|
||||
emit('mount', start.value!)
|
||||
emit('mount', startElement.value!)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -168,7 +162,7 @@ function drawArrow(start: Element, end: Element) {
|
||||
left: 0
|
||||
}
|
||||
},
|
||||
style: 'stroke:var(--color-on-surface);stroke-width:2;fill:transparent;',
|
||||
style: 'stroke:var(--color-on-surface);stroke-width:2;fill:transparent;pointer-events:none;',
|
||||
appendTo: document.body
|
||||
})
|
||||
arrows.push(arrow)
|
||||
@@ -194,12 +188,12 @@ addEventListener('scroll', _ => {
|
||||
})
|
||||
|
||||
function handleSubtreeMount(end: Element) {
|
||||
if (start.value) {
|
||||
if (startElement.value) {
|
||||
if (imagesLoaded.value) {
|
||||
// Parent is ready, draw arrow immediately
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
drawArrow(start.value!, end)
|
||||
drawArrow(startElement.value!, end)
|
||||
refreshArrows()
|
||||
})
|
||||
})
|
||||
@@ -213,7 +207,7 @@ function handleSubtreeMount(end: Element) {
|
||||
|
||||
function handleParentReady() {
|
||||
// Parent became ready, redraw all our arrows
|
||||
if (start.value && imagesLoaded.value) {
|
||||
if (startElement.value && imagesLoaded.value) {
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
refreshArrows()
|
||||
@@ -233,13 +227,15 @@ function handleRefresh() {
|
||||
<template>
|
||||
<div class="item-tree-container">
|
||||
<div v-if="tree.data != undefined && tree.data != null" class="item-tree-node">
|
||||
<img
|
||||
<ItemIcon
|
||||
v-if="itemMap.get(tree.data)"
|
||||
ref="start"
|
||||
:item="itemMap.get(tree.data)!"
|
||||
:show-pickrate="true"
|
||||
:pickrate="parentCount ? tree.count / parentCount : 0"
|
||||
:size="48"
|
||||
class="item-tree-img"
|
||||
:alt="tree.data.toString()"
|
||||
:src="getItemIconPath(tree.data)"
|
||||
/>
|
||||
<span class="item-tree-pickrate">{{ ((tree.count / parentCount!!) * 100).toFixed(0) }}%</span>
|
||||
</div>
|
||||
|
||||
<div class="item-tree-children">
|
||||
@@ -268,36 +264,23 @@ function handleRefresh() {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.item-tree-img {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--color-on-surface);
|
||||
}
|
||||
|
||||
.item-tree-pickrate {
|
||||
font-size: 0.65rem;
|
||||
color: var(--color-on-surface);
|
||||
opacity: 0.6;
|
||||
margin-top: 2px;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.item-tree-children {
|
||||
margin-left: 32px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.item-tree-child {
|
||||
width: fit-content;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Mobile responsive */
|
||||
@media only screen and (max-width: 900px) {
|
||||
.item-tree-img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.item-tree-children {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user