mirror of
https://github.com/grafana/grafana.git
synced 2025-08-06 12:59:25 +08:00
Dashboards: Use IntersectionObserver to manage lazy loading of panels (#42834)
This commit is contained in:
@ -253,7 +253,7 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
|||||||
panel={panel}
|
panel={panel}
|
||||||
isEditing={true}
|
isEditing={true}
|
||||||
isViewing={false}
|
isViewing={false}
|
||||||
isInView={true}
|
lazy={false}
|
||||||
width={panelSize.width}
|
width={panelSize.width}
|
||||||
height={panelSize.height}
|
height={panelSize.height}
|
||||||
skipStateCleanUp={true}
|
skipStateCleanUp={true}
|
||||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { Props, UnthemedDashboardPage } from './DashboardPage';
|
import { Props, UnthemedDashboardPage } from './DashboardPage';
|
||||||
|
import { Props as LazyLoaderProps } from '../dashgrid/LazyLoader';
|
||||||
import { Router } from 'react-router-dom';
|
import { Router } from 'react-router-dom';
|
||||||
import { locationService, setDataSourceSrv } from '@grafana/runtime';
|
import { locationService, setDataSourceSrv } from '@grafana/runtime';
|
||||||
import { DashboardModel } from '../state';
|
import { DashboardModel } from '../state';
|
||||||
@ -14,6 +15,13 @@ import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps
|
|||||||
import { createTheme } from '@grafana/data';
|
import { createTheme } from '@grafana/data';
|
||||||
import { AutoSizerProps } from 'react-virtualized-auto-sizer';
|
import { AutoSizerProps } from 'react-virtualized-auto-sizer';
|
||||||
|
|
||||||
|
jest.mock('app/features/dashboard/dashgrid/LazyLoader', () => {
|
||||||
|
const LazyLoader = ({ children }: Pick<LazyLoaderProps, 'children'>) => {
|
||||||
|
return <>{typeof children === 'function' ? children({ isInView: true }) : children}</>;
|
||||||
|
};
|
||||||
|
return { LazyLoader };
|
||||||
|
});
|
||||||
|
|
||||||
jest.mock('app/features/dashboard/components/DashboardSettings/GeneralSettings', () => {
|
jest.mock('app/features/dashboard/components/DashboardSettings/GeneralSettings', () => {
|
||||||
class GeneralSettings extends React.Component<{}, {}> {
|
class GeneralSettings extends React.Component<{}, {}> {
|
||||||
render() {
|
render() {
|
||||||
|
@ -3,7 +3,7 @@ import { css } from '@emotion/css';
|
|||||||
import { connect, ConnectedProps } from 'react-redux';
|
import { connect, ConnectedProps } from 'react-redux';
|
||||||
import { locationService } from '@grafana/runtime';
|
import { locationService } from '@grafana/runtime';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { CustomScrollbar, ScrollbarPosition, stylesFactory, Themeable2, withTheme2 } from '@grafana/ui';
|
import { CustomScrollbar, stylesFactory, Themeable2, withTheme2 } from '@grafana/ui';
|
||||||
|
|
||||||
import { createErrorNotification } from 'app/core/copy/appNotification';
|
import { createErrorNotification } from 'app/core/copy/appNotification';
|
||||||
import { Branding } from 'app/core/components/Branding/Branding';
|
import { Branding } from 'app/core/components/Branding/Branding';
|
||||||
@ -75,7 +75,6 @@ export type Props = Themeable2 &
|
|||||||
export interface State {
|
export interface State {
|
||||||
editPanel: PanelModel | null;
|
editPanel: PanelModel | null;
|
||||||
viewPanel: PanelModel | null;
|
viewPanel: PanelModel | null;
|
||||||
scrollTop: number;
|
|
||||||
updateScrollTop?: number;
|
updateScrollTop?: number;
|
||||||
rememberScrollTop: number;
|
rememberScrollTop: number;
|
||||||
showLoadingState: boolean;
|
showLoadingState: boolean;
|
||||||
@ -92,7 +91,6 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
|
|||||||
editPanel: null,
|
editPanel: null,
|
||||||
viewPanel: null,
|
viewPanel: null,
|
||||||
showLoadingState: false,
|
showLoadingState: false,
|
||||||
scrollTop: 0,
|
|
||||||
rememberScrollTop: 0,
|
rememberScrollTop: 0,
|
||||||
panelNotFound: false,
|
panelNotFound: false,
|
||||||
editPanelAccessDenied: false,
|
editPanelAccessDenied: false,
|
||||||
@ -252,7 +250,6 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
viewPanel: panel,
|
viewPanel: panel,
|
||||||
rememberScrollTop: state.scrollTop,
|
|
||||||
updateScrollTop: 0,
|
updateScrollTop: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -273,10 +270,6 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
setScrollTop = ({ scrollTop }: ScrollbarPosition): void => {
|
|
||||||
this.setState({ scrollTop, updateScrollTop: undefined });
|
|
||||||
};
|
|
||||||
|
|
||||||
onAddPanel = () => {
|
onAddPanel = () => {
|
||||||
const { dashboard } = this.props;
|
const { dashboard } = this.props;
|
||||||
|
|
||||||
@ -320,7 +313,7 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { dashboard, isInitSlow, initError, queryParams, theme } = this.props;
|
const { dashboard, isInitSlow, initError, queryParams, theme } = this.props;
|
||||||
const { editPanel, viewPanel, scrollTop, updateScrollTop } = this.state;
|
const { editPanel, viewPanel, updateScrollTop } = this.state;
|
||||||
const kioskMode = getKioskMode(queryParams.kiosk);
|
const kioskMode = getKioskMode(queryParams.kiosk);
|
||||||
const styles = getStyles(theme, kioskMode);
|
const styles = getStyles(theme, kioskMode);
|
||||||
|
|
||||||
@ -332,8 +325,6 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only trigger render when the scroll has moved by 25
|
|
||||||
const approximateScrollTop = Math.round(scrollTop / 25) * 25;
|
|
||||||
const inspectPanel = this.getInspectPanel();
|
const inspectPanel = this.getInspectPanel();
|
||||||
const containerClassNames = classnames(styles.dashboardContainer, {
|
const containerClassNames = classnames(styles.dashboardContainer, {
|
||||||
'panel-in-fullscreen': viewPanel,
|
'panel-in-fullscreen': viewPanel,
|
||||||
@ -361,7 +352,6 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
|
|||||||
<div className={styles.dashboardScroll}>
|
<div className={styles.dashboardScroll}>
|
||||||
<CustomScrollbar
|
<CustomScrollbar
|
||||||
autoHeightMin="100%"
|
autoHeightMin="100%"
|
||||||
setScrollTop={this.setScrollTop}
|
|
||||||
scrollTop={updateScrollTop}
|
scrollTop={updateScrollTop}
|
||||||
hideHorizontalTrack={true}
|
hideHorizontalTrack={true}
|
||||||
updateAfterMountMs={500}
|
updateAfterMountMs={500}
|
||||||
@ -374,12 +364,7 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
|
|||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<DashboardGrid
|
<DashboardGrid dashboard={dashboard} viewPanel={viewPanel} editPanel={editPanel} />
|
||||||
dashboard={dashboard}
|
|
||||||
viewPanel={viewPanel}
|
|
||||||
editPanel={editPanel}
|
|
||||||
scrollTop={approximateScrollTop}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</CustomScrollbar>
|
</CustomScrollbar>
|
||||||
</div>
|
</div>
|
||||||
|
@ -115,7 +115,7 @@ export const SoloPanel = ({ dashboard, notFound, panel, panelId }: SoloPanelProp
|
|||||||
panel={panel}
|
panel={panel}
|
||||||
isEditing={false}
|
isEditing={false}
|
||||||
isViewing={false}
|
isViewing={false}
|
||||||
isInView={true}
|
lazy={false}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
@ -3,6 +3,13 @@ import { shallow, ShallowWrapper } from 'enzyme';
|
|||||||
import { DashboardGrid, Props } from './DashboardGrid';
|
import { DashboardGrid, Props } from './DashboardGrid';
|
||||||
import { DashboardModel } from '../state';
|
import { DashboardModel } from '../state';
|
||||||
|
|
||||||
|
jest.mock('app/features/dashboard/dashgrid/LazyLoader', () => {
|
||||||
|
const LazyLoader: React.FC = ({ children }) => {
|
||||||
|
return <>{children}</>;
|
||||||
|
};
|
||||||
|
return { LazyLoader };
|
||||||
|
});
|
||||||
|
|
||||||
interface ScenarioContext {
|
interface ScenarioContext {
|
||||||
props: Props;
|
props: Props;
|
||||||
wrapper?: ShallowWrapper<Props, any, DashboardGrid>;
|
wrapper?: ShallowWrapper<Props, any, DashboardGrid>;
|
||||||
@ -59,7 +66,6 @@ function dashboardGridScenario(description: string, scenarioFn: (ctx: ScenarioCo
|
|||||||
props: {
|
props: {
|
||||||
editPanel: null,
|
editPanel: null,
|
||||||
viewPanel: null,
|
viewPanel: null,
|
||||||
scrollTop: 0,
|
|
||||||
dashboard: getTestDashboard(),
|
dashboard: getTestDashboard(),
|
||||||
},
|
},
|
||||||
setProps: (props: Partial<Props>) => {
|
setProps: (props: Partial<Props>) => {
|
||||||
|
@ -21,7 +21,6 @@ export interface Props {
|
|||||||
dashboard: DashboardModel;
|
dashboard: DashboardModel;
|
||||||
editPanel: PanelModel | null;
|
editPanel: PanelModel | null;
|
||||||
viewPanel: PanelModel | null;
|
viewPanel: PanelModel | null;
|
||||||
scrollTop: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
@ -125,32 +124,6 @@ export class DashboardGrid extends PureComponent<Props, State> {
|
|||||||
this.updateGridPos(newItem, layout);
|
this.updateGridPos(newItem, layout);
|
||||||
};
|
};
|
||||||
|
|
||||||
isInView(panel: PanelModel, gridWidth: number) {
|
|
||||||
if (panel.isViewing || panel.isEditing) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const scrollTop = this.props.scrollTop;
|
|
||||||
const screenPos = this.getPanelScreenPos(panel, gridWidth);
|
|
||||||
|
|
||||||
// Show things that are almost in the view
|
|
||||||
const buffer = 100;
|
|
||||||
|
|
||||||
// The panel is above the viewport
|
|
||||||
if (scrollTop > screenPos.bottom + buffer) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const scrollViewBottom = scrollTop + this.windowHeight;
|
|
||||||
|
|
||||||
// Panel is below view
|
|
||||||
if (screenPos.top > scrollViewBottom + buffer) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return !this.props.dashboard.otherPanelInFullscreen(panel);
|
|
||||||
}
|
|
||||||
|
|
||||||
getPanelScreenPos(panel: PanelModel, gridWidth: number): { top: number; bottom: number } {
|
getPanelScreenPos(panel: PanelModel, gridWidth: number): { top: number; bottom: number } {
|
||||||
let top = 0;
|
let top = 0;
|
||||||
|
|
||||||
@ -185,9 +158,6 @@ export class DashboardGrid extends PureComponent<Props, State> {
|
|||||||
for (const panel of this.props.dashboard.panels) {
|
for (const panel of this.props.dashboard.panels) {
|
||||||
const panelClasses = classNames({ 'react-grid-item--fullscreen': panel.isViewing });
|
const panelClasses = classNames({ 'react-grid-item--fullscreen': panel.isViewing });
|
||||||
|
|
||||||
// Update is in view state
|
|
||||||
panel.isInView = this.isInView(panel, gridWidth);
|
|
||||||
|
|
||||||
panelElements.push(
|
panelElements.push(
|
||||||
<GrafanaGridItem
|
<GrafanaGridItem
|
||||||
key={panel.key}
|
key={panel.key}
|
||||||
@ -226,7 +196,6 @@ export class DashboardGrid extends PureComponent<Props, State> {
|
|||||||
dashboard={this.props.dashboard}
|
dashboard={this.props.dashboard}
|
||||||
isEditing={panel.isEditing}
|
isEditing={panel.isEditing}
|
||||||
isViewing={panel.isViewing}
|
isViewing={panel.isViewing}
|
||||||
isInView={panel.isInView}
|
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
/>
|
/>
|
||||||
@ -235,13 +204,14 @@ export class DashboardGrid extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { dashboard } = this.props;
|
const { dashboard } = this.props;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We have a parent with "flex: 1 1 0" we need to reset it to "flex: 1 1 auto" to have the AutoSizer
|
||||||
|
* properly working. For more information go here:
|
||||||
|
* https://github.com/bvaughn/react-virtualized/blob/master/docs/usingAutoSizer.md#can-i-use-autosizer-within-a-flex-container
|
||||||
|
*/
|
||||||
return (
|
return (
|
||||||
/**
|
<div style={{ flex: '1 1 auto', display: this.props.editPanel ? 'none' : undefined }}>
|
||||||
* We have a parent with "flex: 1 1 0" we need to reset it to "flex: 1 1 auto" to have the AutoSizer
|
|
||||||
* properly working. For more information go here:
|
|
||||||
* https://github.com/bvaughn/react-virtualized/blob/master/docs/usingAutoSizer.md#can-i-use-autosizer-within-a-flex-container
|
|
||||||
*/
|
|
||||||
<div style={{ flex: '1 1 auto' }}>
|
|
||||||
<AutoSizer disableHeight>
|
<AutoSizer disableHeight>
|
||||||
{({ width }) => {
|
{({ width }) => {
|
||||||
if (width === 0) {
|
if (width === 0) {
|
||||||
|
@ -7,6 +7,7 @@ import { StoreState } from 'app/types';
|
|||||||
import { PanelPlugin } from '@grafana/data';
|
import { PanelPlugin } from '@grafana/data';
|
||||||
import { cleanUpPanelState, setPanelInstanceState } from '../../panel/state/reducers';
|
import { cleanUpPanelState, setPanelInstanceState } from '../../panel/state/reducers';
|
||||||
import { initPanelState } from '../../panel/state/actions';
|
import { initPanelState } from '../../panel/state/actions';
|
||||||
|
import { LazyLoader } from './LazyLoader';
|
||||||
|
|
||||||
export interface OwnProps {
|
export interface OwnProps {
|
||||||
panel: PanelModel;
|
panel: PanelModel;
|
||||||
@ -14,14 +15,10 @@ export interface OwnProps {
|
|||||||
dashboard: DashboardModel;
|
dashboard: DashboardModel;
|
||||||
isEditing: boolean;
|
isEditing: boolean;
|
||||||
isViewing: boolean;
|
isViewing: boolean;
|
||||||
isInView: boolean;
|
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
skipStateCleanUp?: boolean;
|
skipStateCleanUp?: boolean;
|
||||||
}
|
lazy?: boolean;
|
||||||
|
|
||||||
export interface State {
|
|
||||||
isLazy: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state: StoreState, props: OwnProps) => {
|
const mapStateToProps = (state: StoreState, props: OwnProps) => {
|
||||||
@ -46,18 +43,15 @@ const connector = connect(mapStateToProps, mapDispatchToProps);
|
|||||||
|
|
||||||
export type Props = OwnProps & ConnectedProps<typeof connector>;
|
export type Props = OwnProps & ConnectedProps<typeof connector>;
|
||||||
|
|
||||||
export class DashboardPanelUnconnected extends PureComponent<Props, State> {
|
export class DashboardPanelUnconnected extends PureComponent<Props> {
|
||||||
|
static defaultProps: Partial<Props> = {
|
||||||
|
lazy: true,
|
||||||
|
};
|
||||||
|
|
||||||
specialPanels: { [key: string]: Function } = {};
|
specialPanels: { [key: string]: Function } = {};
|
||||||
|
|
||||||
constructor(props: Props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
isLazy: !props.isInView,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
this.props.panel.isInView = !this.props.lazy;
|
||||||
if (!this.props.plugin) {
|
if (!this.props.plugin) {
|
||||||
this.props.initPanelState(this.props.panel);
|
this.props.initPanelState(this.props.panel);
|
||||||
}
|
}
|
||||||
@ -70,21 +64,19 @@ export class DashboardPanelUnconnected extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
if (this.state.isLazy && this.props.isInView) {
|
|
||||||
this.setState({ isLazy: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onInstanceStateChange = (value: any) => {
|
onInstanceStateChange = (value: any) => {
|
||||||
this.props.setPanelInstanceState({ key: this.props.stateKey, value });
|
this.props.setPanelInstanceState({ key: this.props.stateKey, value });
|
||||||
};
|
};
|
||||||
|
|
||||||
renderPanel(plugin: PanelPlugin) {
|
onVisibilityChange = (v: boolean) => {
|
||||||
const { dashboard, panel, isViewing, isInView, isEditing, width, height } = this.props;
|
this.props.panel.isInView = v;
|
||||||
|
};
|
||||||
|
|
||||||
if (plugin.angularPanelCtrl) {
|
renderPanel(plugin: PanelPlugin) {
|
||||||
return (
|
const { dashboard, panel, isViewing, isEditing, width, height, lazy } = this.props;
|
||||||
|
|
||||||
|
const renderPanelChrome = (isInView: boolean) =>
|
||||||
|
plugin.angularPanelCtrl ? (
|
||||||
<PanelChromeAngular
|
<PanelChromeAngular
|
||||||
plugin={plugin}
|
plugin={plugin}
|
||||||
panel={panel}
|
panel={panel}
|
||||||
@ -95,38 +87,37 @@ export class DashboardPanelUnconnected extends PureComponent<Props, State> {
|
|||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<PanelChrome
|
||||||
|
plugin={plugin}
|
||||||
|
panel={panel}
|
||||||
|
dashboard={dashboard}
|
||||||
|
isViewing={isViewing}
|
||||||
|
isEditing={isEditing}
|
||||||
|
isInView={isInView}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
onInstanceStateChange={this.onInstanceStateChange}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return lazy ? (
|
||||||
<PanelChrome
|
<LazyLoader width={width} height={height} onChange={this.onVisibilityChange}>
|
||||||
plugin={plugin}
|
{({ isInView }) => renderPanelChrome(isInView)}
|
||||||
panel={panel}
|
</LazyLoader>
|
||||||
dashboard={dashboard}
|
) : (
|
||||||
isViewing={isViewing}
|
renderPanelChrome(true)
|
||||||
isEditing={isEditing}
|
|
||||||
isInView={isInView}
|
|
||||||
width={width}
|
|
||||||
height={height}
|
|
||||||
onInstanceStateChange={this.onInstanceStateChange}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { plugin } = this.props;
|
const { plugin } = this.props;
|
||||||
const { isLazy } = this.state;
|
|
||||||
|
|
||||||
// If we have not loaded plugin exports yet, wait
|
// If we have not loaded plugin exports yet, wait
|
||||||
if (!plugin) {
|
if (!plugin) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we are lazy state don't render anything
|
|
||||||
if (isLazy) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.renderPanel(plugin);
|
return this.renderPanel(plugin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
57
public/app/features/dashboard/dashgrid/LazyLoader.tsx
Normal file
57
public/app/features/dashboard/dashgrid/LazyLoader.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import React, { useRef, useState } from 'react';
|
||||||
|
import { useUniqueId } from 'app/plugins/datasource/influxdb/components/useUniqueId';
|
||||||
|
import { useEffectOnce } from 'react-use';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
children: React.ReactNode | (({ isInView }: { isInView: boolean }) => React.ReactNode);
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
onLoad?: () => void;
|
||||||
|
onChange?: (isInView: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LazyLoader({ children, width, height, onLoad, onChange }: Props) {
|
||||||
|
const id = useUniqueId();
|
||||||
|
const [loaded, setLoaded] = useState(false);
|
||||||
|
const [isInView, setIsInView] = useState(false);
|
||||||
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||||
|
useEffectOnce(() => {
|
||||||
|
LazyLoader.addCallback(id, (entry) => {
|
||||||
|
if (!loaded && entry.isIntersecting) {
|
||||||
|
setLoaded(true);
|
||||||
|
onLoad?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsInView(entry.isIntersecting);
|
||||||
|
onChange?.(entry.isIntersecting);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (wrapperRef.current) {
|
||||||
|
LazyLoader.observer.observe(wrapperRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
delete LazyLoader.callbacks[id];
|
||||||
|
if (Object.keys(LazyLoader.callbacks).length === 0) {
|
||||||
|
LazyLoader.observer.disconnect();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id={id} ref={wrapperRef} style={{ width, height }}>
|
||||||
|
{loaded && (typeof children === 'function' ? children({ isInView }) : children)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyLoader.callbacks = {} as Record<string, (e: IntersectionObserverEntry) => void>;
|
||||||
|
LazyLoader.addCallback = (id: string, c: (e: IntersectionObserverEntry) => void) => (LazyLoader.callbacks[id] = c);
|
||||||
|
LazyLoader.observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
LazyLoader.callbacks[entry.target.id](entry);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ rootMargin: '100px' }
|
||||||
|
);
|
@ -4,6 +4,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = `
|
|||||||
<div
|
<div
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
|
"display": undefined,
|
||||||
"flex": "1 1 auto",
|
"flex": "1 1 auto",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user