frontend: fix item tree arrows (Fix #9)

This commit is contained in:
2026-02-28 13:13:01 +01:00
parent ea27a0d6f8
commit 3e9a8295b2

View File

@@ -9,13 +9,18 @@ defineProps<{
const emit = defineEmits<{
mount: [end: Element]
refresh: []
parentReady: []
}>()
const { data: items } = useFetch<Array<{ id: number; iconPath: string }>>('/api/cdragon/items', {
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
})
// 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 }>())
watch(
@@ -38,12 +43,72 @@ function getItemIconPath(itemId: number): string {
const start: Ref<Element | null> = useTemplateRef('start')
const arrows: Array<svgdomarrowsLinePath> = []
const pendingChildMounts: Array<Element> = []
onMounted(() => {
// Only refresh arrows and emit if start element is available
// Function to wait for an image to load
function waitForImageLoad(imgElement: HTMLImageElement): Promise<void> {
return new Promise((resolve) => {
if (imgElement.complete) {
requestAnimationFrame(() => resolve())
return
}
imgElement.addEventListener('load', () => {
requestAnimationFrame(() => resolve())
}, { once: true })
imgElement.addEventListener('error', () => {
requestAnimationFrame(() => resolve())
}, { once: true })
})
}
onMounted(async () => {
// Wait for next tick to ensure DOM is ready
await nextTick()
// Wait for items to be loaded
await new Promise<void>((resolve) => {
if (!itemsLoading.value) {
resolve()
} else {
const unwatch = watch(itemsLoading, (loading) => {
if (!loading) {
unwatch()
resolve()
}
})
}
})
if (start.value) {
refreshArrows()
emit('mount', start.value)
const imgElement = start.value as HTMLImageElement
imageElement.value = imgElement
// Wait for own image to load
await waitForImageLoad(imgElement)
// Now that image is loaded and DOM is ready, draw arrows
imagesLoaded.value = true
// Notify children that parent is ready
emit('parentReady')
// Draw any pending arrows from children that mounted before we were ready
if (pendingChildMounts.length > 0) {
await nextTick()
for (const childEnd of pendingChildMounts) {
drawArrow(start.value!, childEnd)
}
pendingChildMounts.length = 0
}
// Use multiple requestAnimationFrame to ensure rendering is complete
requestAnimationFrame(() => {
requestAnimationFrame(() => {
refreshArrows()
emit('mount', start.value!)
})
})
}
})
@@ -54,10 +119,17 @@ onBeforeUpdate(() => {
arrows.splice(0, arrows.length)
})
onUpdated(() => {
if (start.value) {
refreshArrows()
emit('mount', start.value)
onUpdated(async () => {
await nextTick()
if (start.value && imagesLoaded.value) {
// Redraw arrows after DOM update
requestAnimationFrame(() => {
requestAnimationFrame(() => {
refreshArrows()
emit('mount', start.value!)
})
})
}
})
@@ -89,16 +161,17 @@ function drawArrow(start: Element, end: Element) {
appendTo: document.body
})
arrows.push(arrow)
// Redraw immediately after creation to ensure correct position
requestAnimationFrame(() => {
arrow.redraw()
})
}
function refreshArrows() {
for (const arrow of arrows) {
arrow.redraw()
}
// Double requestAnimationFrame to ensure layout is complete
requestAnimationFrame(() => {
requestAnimationFrame(() => {
for (const arrow of arrows) {
arrow.redraw()
}
})
})
}
// Redraw arrows on window resize
@@ -111,11 +184,35 @@ addEventListener('scroll', _ => {
function handleSubtreeMount(end: Element) {
if (start.value) {
drawArrow(start.value, end)
refreshArrows()
if (imagesLoaded.value) {
// Parent is ready, draw arrow immediately
requestAnimationFrame(() => {
requestAnimationFrame(() => {
drawArrow(start.value!, end)
refreshArrows()
})
})
} else {
// Parent not ready yet, store for later
pendingChildMounts.push(end)
}
}
emit('refresh')
}
function handleParentReady() {
// Parent became ready, redraw all our arrows
if (start.value && imagesLoaded.value) {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
refreshArrows()
})
})
}
// Propagate to children
emit('parentReady')
}
function handleRefresh() {
refreshArrows()
emit('refresh')
@@ -141,6 +238,7 @@ function handleRefresh() {
:parent-count="tree.count"
@refresh="handleRefresh"
@mount="end => handleSubtreeMount(end)"
@parent-ready="handleParentReady"
/>
</div>
</div>
@@ -193,4 +291,4 @@ function handleRefresh() {
margin-left: 20px;
}
}
</style>
</style>