frontend: fix item tree arrows (Fix #9)
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user