@@ -33,7 +33,13 @@ import type {RouteComponentProps} from 'react-router-dom';
3333import { withRouter } from 'react-router-dom' ;
3434import { compose } from 'recompose' ;
3535import type { DashTab , DashTabLayout } from 'shared' ;
36- import { Feature , FixedHeaderQa , SCROLL_TITLE_DEBOUNCE_TIME } from 'shared' ;
36+ import {
37+ Feature ,
38+ FixedHeaderQa ,
39+ SCROLL_TITLE_DEBOUNCE_TIME ,
40+ SCR_USER_AGENT_HEADER_VALUE ,
41+ } from 'shared' ;
42+ import { getAllTabItems } from 'shared/utils/dash' ;
3743import type { DatalensGlobalState } from 'ui' ;
3844import {
3945 DEFAULT_DASH_MARGINS ,
@@ -63,6 +69,8 @@ import {FixedContainerWrapperWithContext, FixedControlsWrapperWithContext} from
6369
6470import './Body.scss' ;
6571
72+ const VIEWPORT_DASH_LOADED_EVENT_DEBOUNCE_TIME = 1000 ;
73+
6674// Do not change class name, the snapter service uses
6775const b = block ( 'dash-body' ) ;
6876
@@ -110,6 +118,7 @@ type DashBodyState = {
110118 margins : [ number , number ] ;
111119 renderers : DashKitGroup [ ] ;
112120 } ;
121+ totalItemsCount : number ;
113122} ;
114123
115124type BodyProps = StateProps & DispatchProps & RouteComponentProps & OwnProps ;
@@ -145,6 +154,11 @@ class Body extends React.PureComponent<BodyProps, DashBodyState> {
145154 isTabUnmount = true ;
146155 }
147156
157+ const newTotalItemsCount = getAllTabItems ( props . tabData ) . length ;
158+ if ( newTotalItemsCount !== state . totalItemsCount ) {
159+ updatedState . totalItemsCount = newTotalItemsCount ;
160+ }
161+
148162 const currentHash = props . location . hash ;
149163 const hasHashChanged = currentHash !== state . hash ;
150164
@@ -183,6 +197,10 @@ class Body extends React.PureComponent<BodyProps, DashBodyState> {
183197 }
184198 } , SCROLL_TITLE_DEBOUNCE_TIME ) ;
185199
200+ dispatchViewportDashLoadedEventDebounced = debounce ( ( ) => {
201+ return this . dispatchViewportDashLoadedEvent ( ) ;
202+ } , VIEWPORT_DASH_LOADED_EVENT_DEBOUNCE_TIME ) ;
203+
186204 _memoizedWidgetsMap : {
187205 layout : DashTabLayout [ ] | null ;
188206 byGroup : Record < string , DashTabLayout [ ] > ;
@@ -230,6 +248,7 @@ class Body extends React.PureComponent<BodyProps, DashBodyState> {
230248 } ,
231249 ] ,
232250 } ,
251+ totalItemsCount : 0 ,
233252 } ;
234253 }
235254
@@ -256,6 +275,8 @@ class Body extends React.PureComponent<BodyProps, DashBodyState> {
256275 componentWillUnmount ( ) {
257276 window . removeEventListener ( 'wheel' , this . interruptAutoScroll ) ;
258277 window . removeEventListener ( 'touchmove' , this . interruptAutoScroll ) ;
278+ this . scrollIntoViewWithDebounce . cancel ( ) ;
279+ this . dispatchViewportDashLoadedEventDebounced . cancel ( ) ;
259280 }
260281
261282 render ( ) {
@@ -705,20 +726,61 @@ class Body extends React.PureComponent<BodyProps, DashBodyState> {
705726 if ( isMounted ) {
706727 this . state . loadedItemsMap . set ( item . id , false ) ;
707728
708- if ( this . state . loadedItemsMap . size === this . props . tabData ?. items . length ) {
729+ if ( this . state . loadedItemsMap . size === this . state . totalItemsCount ) {
709730 this . scrollIntoViewWithDebounce ( ) ;
710731 }
711732 }
712733 } ;
713734
735+ private isElementOutsideViewport = ( element : Element ) : boolean => {
736+ const rect = element . getBoundingClientRect ( ) ;
737+ const viewportHeight = window . innerHeight || document . documentElement . clientHeight ;
738+ const viewportWidth = window . innerWidth || document . documentElement . clientWidth ;
739+
740+ return (
741+ rect . bottom < 0 ||
742+ rect . top > viewportHeight ||
743+ rect . right < 0 ||
744+ rect . left > viewportWidth
745+ ) ;
746+ } ;
747+
748+ private dispatchViewportDashLoadedEvent = ( ) => {
749+ const { loadedItemsMap, dashEl} = this . state ;
750+
751+ if ( ! dashEl ) {
752+ return ;
753+ }
754+
755+ const unloadedItemIds : string [ ] = [ ] ;
756+ loadedItemsMap . forEach ( ( isLoaded , itemId ) => {
757+ if ( isLoaded !== true ) {
758+ unloadedItemIds . push ( itemId ) ;
759+ }
760+ } ) ;
761+
762+ const allViewportItemsLoaded = unloadedItemIds . every ( ( itemId ) => {
763+ const itemElement = document . getElementById ( itemId ) ;
764+ if ( ! itemElement ) {
765+ return false ;
766+ }
767+ const result = this . isElementOutsideViewport ( itemElement ) ;
768+ return result ;
769+ } ) ;
770+
771+ if ( allViewportItemsLoaded ) {
772+ dispatchDashLoadedEvent ( ) ;
773+ }
774+ } ;
775+
714776 private handleItemRender = ( item : ConfigItem ) => {
715777 const { loadedItemsMap} = this . state ;
716778
717779 if ( loadedItemsMap . has ( item . id ) && loadedItemsMap . get ( item . id ) !== true ) {
718780 loadedItemsMap . set ( item . id , true ) ;
719781
720782 const isLoaded =
721- loadedItemsMap . size === this . props . tabData ?. items . length &&
783+ loadedItemsMap . size === this . state . totalItemsCount &&
722784 Array . from ( loadedItemsMap . values ( ) ) . every ( Boolean ) ;
723785
724786 if ( isLoaded && this . state . delayedScrollElement ) {
@@ -731,7 +793,10 @@ class Body extends React.PureComponent<BodyProps, DashBodyState> {
731793 }
732794
733795 if ( isLoaded ) {
796+ this . dispatchViewportDashLoadedEventDebounced . cancel ( ) ;
734797 dispatchDashLoadedEvent ( ) ;
798+ } else if ( navigator . userAgent === SCR_USER_AGENT_HEADER_VALUE ) {
799+ this . dispatchViewportDashLoadedEventDebounced ( ) ;
735800 }
736801
737802 this . setState ( { loaded : isLoaded } ) ;
0 commit comments