Fix camera jumping when opening InfoPanel
All checks were successful
Build Docker Image / build (push) Successful in 4m17s
All checks were successful
Build Docker Image / build (push) Successful in 4m17s
Replace setTimeout/debounce with event-based approach: - Add isInfoLoading computed that tracks all info loading states - Pass infoLoading prop through CatalogPage to CatalogMap - Watch infoLoading transition from true->false to trigger fitBounds - Remove setTimeout hack in favor of proper loading state detection
This commit is contained in:
@@ -51,6 +51,7 @@ const props = withDefaults(defineProps<{
|
|||||||
entityType?: 'offer' | 'hub' | 'supplier'
|
entityType?: 'offer' | 'hub' | 'supplier'
|
||||||
initialCenter?: [number, number]
|
initialCenter?: [number, number]
|
||||||
initialZoom?: number
|
initialZoom?: number
|
||||||
|
infoLoading?: boolean
|
||||||
relatedPoints?: Array<{
|
relatedPoints?: Array<{
|
||||||
uuid: string
|
uuid: string
|
||||||
name: string
|
name: string
|
||||||
@@ -64,6 +65,7 @@ const props = withDefaults(defineProps<{
|
|||||||
initialCenter: () => [37.64, 55.76],
|
initialCenter: () => [37.64, 55.76],
|
||||||
initialZoom: 2,
|
initialZoom: 2,
|
||||||
useServerClustering: false,
|
useServerClustering: false,
|
||||||
|
infoLoading: false,
|
||||||
items: () => [],
|
items: () => [],
|
||||||
clusteredPoints: () => [],
|
clusteredPoints: () => [],
|
||||||
relatedPoints: () => []
|
relatedPoints: () => []
|
||||||
@@ -693,11 +695,8 @@ watch(() => props.hoveredItem, () => {
|
|||||||
}
|
}
|
||||||
}, { deep: true })
|
}, { deep: true })
|
||||||
|
|
||||||
// Debounced fitBounds to avoid camera jumping when points load incrementally
|
// Update related points layer when relatedPoints changes
|
||||||
let fitBoundsTimeout: ReturnType<typeof setTimeout> | null = null
|
watch(() => props.relatedPoints, () => {
|
||||||
|
|
||||||
// Update related points layer when relatedPoints changes + fit bounds (debounced)
|
|
||||||
watch(() => props.relatedPoints, (points) => {
|
|
||||||
if (!mapRef.value || !mapInitialized.value) return
|
if (!mapRef.value || !mapInitialized.value) return
|
||||||
|
|
||||||
// Update the source data immediately
|
// Update the source data immediately
|
||||||
@@ -705,26 +704,23 @@ watch(() => props.relatedPoints, (points) => {
|
|||||||
if (source) {
|
if (source) {
|
||||||
source.setData(relatedPointsGeoJson.value)
|
source.setData(relatedPointsGeoJson.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debounce fitBounds - wait for all points to load before zooming
|
|
||||||
if (fitBoundsTimeout) {
|
|
||||||
clearTimeout(fitBoundsTimeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (points && points.length > 0) {
|
|
||||||
fitBoundsTimeout = setTimeout(() => {
|
|
||||||
if (!mapRef.value) return
|
|
||||||
const bounds = new LngLatBounds()
|
|
||||||
points.forEach(p => {
|
|
||||||
bounds.extend([p.longitude, p.latitude])
|
|
||||||
})
|
|
||||||
if (!bounds.isEmpty()) {
|
|
||||||
mapRef.value.fitBounds(bounds, { padding: 80, maxZoom: 12 })
|
|
||||||
}
|
|
||||||
}, 300) // Wait 300ms for all points to load
|
|
||||||
}
|
|
||||||
}, { deep: true })
|
}, { deep: true })
|
||||||
|
|
||||||
|
// Fit bounds when info loading finishes (all related data loaded)
|
||||||
|
watch(() => props.infoLoading, (loading, wasLoading) => {
|
||||||
|
// Only fit bounds when loading changes from true to false (data finished loading)
|
||||||
|
if (wasLoading && !loading && props.relatedPoints && props.relatedPoints.length > 0) {
|
||||||
|
if (!mapRef.value) return
|
||||||
|
const bounds = new LngLatBounds()
|
||||||
|
props.relatedPoints.forEach(p => {
|
||||||
|
bounds.extend([p.longitude, p.latitude])
|
||||||
|
})
|
||||||
|
if (!bounds.isEmpty()) {
|
||||||
|
mapRef.value.fitBounds(bounds, { padding: 80, maxZoom: 12 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Watch for pointColor or entityType changes - update colors and icons
|
// Watch for pointColor or entityType changes - update colors and icons
|
||||||
watch([() => props.pointColor, () => props.entityType], async ([newColor, newType]) => {
|
watch([() => props.pointColor, () => props.entityType], async ([newColor, newType]) => {
|
||||||
if (!mapRef.value || !mapInitialized.value) return
|
if (!mapRef.value || !mapInitialized.value) return
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
:hovered-item-id="hoveredId"
|
:hovered-item-id="hoveredId"
|
||||||
:hovered-item="hoveredItem"
|
:hovered-item="hoveredItem"
|
||||||
:related-points="relatedPoints"
|
:related-points="relatedPoints"
|
||||||
|
:info-loading="infoLoading"
|
||||||
@select-item="onMapSelect"
|
@select-item="onMapSelect"
|
||||||
@bounds-change="onBoundsChange"
|
@bounds-change="onBoundsChange"
|
||||||
/>
|
/>
|
||||||
@@ -250,6 +251,7 @@ const props = withDefaults(defineProps<{
|
|||||||
items?: MapItem[]
|
items?: MapItem[]
|
||||||
showPanel?: boolean
|
showPanel?: boolean
|
||||||
filterByBounds?: boolean
|
filterByBounds?: boolean
|
||||||
|
infoLoading?: boolean
|
||||||
relatedPoints?: Array<{
|
relatedPoints?: Array<{
|
||||||
uuid: string
|
uuid: string
|
||||||
name: string
|
name: string
|
||||||
@@ -266,6 +268,7 @@ const props = withDefaults(defineProps<{
|
|||||||
items: () => [],
|
items: () => [],
|
||||||
showPanel: false,
|
showPanel: false,
|
||||||
filterByBounds: false,
|
filterByBounds: false,
|
||||||
|
infoLoading: false,
|
||||||
relatedPoints: () => []
|
relatedPoints: () => []
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
:show-panel="showPanel"
|
:show-panel="showPanel"
|
||||||
:filter-by-bounds="filterByBounds"
|
:filter-by-bounds="filterByBounds"
|
||||||
:related-points="relatedPoints"
|
:related-points="relatedPoints"
|
||||||
|
:info-loading="isInfoLoading"
|
||||||
@select="onMapSelect"
|
@select="onMapSelect"
|
||||||
@bounds-change="onBoundsChange"
|
@bounds-change="onBoundsChange"
|
||||||
@update:filter-by-bounds="$event ? setBoundsInUrl(currentMapBounds) : clearBoundsFromUrl()"
|
@update:filter-by-bounds="$event ? setBoundsInUrl(currentMapBounds) : clearBoundsFromUrl()"
|
||||||
@@ -356,6 +357,11 @@ watch(searchTrigger, () => {
|
|||||||
// Loading state
|
// Loading state
|
||||||
const isLoading = computed(() => offersLoading.value || selectionLoading.value)
|
const isLoading = computed(() => offersLoading.value || selectionLoading.value)
|
||||||
|
|
||||||
|
// Info loading state for map fitBounds (true while any info data is still loading)
|
||||||
|
const isInfoLoading = computed(() =>
|
||||||
|
infoLoading.value || isLoadingProducts.value || isLoadingHubs.value || isLoadingSuppliers.value || isLoadingOffers.value
|
||||||
|
)
|
||||||
|
|
||||||
// Show panel when selecting OR when showing info OR when showing quote results
|
// Show panel when selecting OR when showing info OR when showing quote results
|
||||||
const showPanel = computed(() => {
|
const showPanel = computed(() => {
|
||||||
return selectMode.value !== null || infoId.value !== null || showQuoteResults.value
|
return selectMode.value !== null || infoId.value !== null || showQuoteResults.value
|
||||||
|
|||||||
Reference in New Issue
Block a user