frontend: fix item tree arrows (Fix #9)
This commit is contained in:
@@ -9,13 +9,18 @@ defineProps<{
|
|||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
mount: [end: Element]
|
mount: [end: Element]
|
||||||
refresh: []
|
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
|
lazy: true, // Don't block rendering
|
||||||
server: false // Client-side only
|
server: false // Client-side only
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Track image loading state
|
||||||
|
const imagesLoaded = ref(false)
|
||||||
|
const imageElement: Ref<HTMLImageElement | null> = ref(null)
|
||||||
|
|
||||||
// Create item map reactively
|
// Create item map reactively
|
||||||
const itemMap = reactive(new Map<number, { id: number; iconPath: string }>())
|
const itemMap = reactive(new Map<number, { id: number; iconPath: string }>())
|
||||||
watch(
|
watch(
|
||||||
@@ -38,12 +43,72 @@ function getItemIconPath(itemId: number): string {
|
|||||||
|
|
||||||
const start: Ref<Element | null> = useTemplateRef('start')
|
const start: Ref<Element | null> = useTemplateRef('start')
|
||||||
const arrows: Array<svgdomarrowsLinePath> = []
|
const arrows: Array<svgdomarrowsLinePath> = []
|
||||||
|
const pendingChildMounts: Array<Element> = []
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
// Only refresh arrows and emit if start element is available
|
|
||||||
if (start.value) {
|
if (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()
|
refreshArrows()
|
||||||
emit('mount', start.value)
|
emit('mount', start.value!)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -54,10 +119,17 @@ onBeforeUpdate(() => {
|
|||||||
arrows.splice(0, arrows.length)
|
arrows.splice(0, arrows.length)
|
||||||
})
|
})
|
||||||
|
|
||||||
onUpdated(() => {
|
onUpdated(async () => {
|
||||||
if (start.value) {
|
await nextTick()
|
||||||
|
|
||||||
|
if (start.value && imagesLoaded.value) {
|
||||||
|
// Redraw arrows after DOM update
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
refreshArrows()
|
refreshArrows()
|
||||||
emit('mount', start.value)
|
emit('mount', start.value!)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -89,16 +161,17 @@ function drawArrow(start: Element, end: Element) {
|
|||||||
appendTo: document.body
|
appendTo: document.body
|
||||||
})
|
})
|
||||||
arrows.push(arrow)
|
arrows.push(arrow)
|
||||||
// Redraw immediately after creation to ensure correct position
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
arrow.redraw()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshArrows() {
|
function refreshArrows() {
|
||||||
|
// Double requestAnimationFrame to ensure layout is complete
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
for (const arrow of arrows) {
|
for (const arrow of arrows) {
|
||||||
arrow.redraw()
|
arrow.redraw()
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redraw arrows on window resize
|
// Redraw arrows on window resize
|
||||||
@@ -111,11 +184,35 @@ addEventListener('scroll', _ => {
|
|||||||
|
|
||||||
function handleSubtreeMount(end: Element) {
|
function handleSubtreeMount(end: Element) {
|
||||||
if (start.value) {
|
if (start.value) {
|
||||||
drawArrow(start.value, end)
|
if (imagesLoaded.value) {
|
||||||
|
// Parent is ready, draw arrow immediately
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
drawArrow(start.value!, end)
|
||||||
refreshArrows()
|
refreshArrows()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Parent not ready yet, store for later
|
||||||
|
pendingChildMounts.push(end)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
emit('refresh')
|
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() {
|
function handleRefresh() {
|
||||||
refreshArrows()
|
refreshArrows()
|
||||||
emit('refresh')
|
emit('refresh')
|
||||||
@@ -141,6 +238,7 @@ function handleRefresh() {
|
|||||||
:parent-count="tree.count"
|
:parent-count="tree.count"
|
||||||
@refresh="handleRefresh"
|
@refresh="handleRefresh"
|
||||||
@mount="end => handleSubtreeMount(end)"
|
@mount="end => handleSubtreeMount(end)"
|
||||||
|
@parent-ready="handleParentReady"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user