diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx index 878b53e2824..d05e4e9e052 100644 --- a/public/app/features/dashboard/containers/DashboardPage.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.tsx @@ -270,6 +270,9 @@ export class DashboardPage extends PureComponent { 'dashboard-container--has-submenu': dashboard.meta.submenuEnabled, }); + // Only trigger render when the scroll has moved by 25 + const approximateScrollTop = Math.round(scrollTop / 25) * 25; + return (
{
{dashboard.meta.submenuEnabled && } - +
diff --git a/public/app/features/dashboard/containers/SoloPanelPage.tsx b/public/app/features/dashboard/containers/SoloPanelPage.tsx index 30088d73cb5..59f364b28f0 100644 --- a/public/app/features/dashboard/containers/SoloPanelPage.tsx +++ b/public/app/features/dashboard/containers/SoloPanelPage.tsx @@ -89,7 +89,7 @@ export class SoloPanelPage extends Component { return (
- +
); } diff --git a/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap b/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap index 745bac2a20d..20fc00cafc0 100644 --- a/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap +++ b/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap @@ -211,6 +211,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1` } isEditing={false} isFullscreen={false} + scrollTop={0} /> @@ -540,6 +541,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti } isEditing={false} isFullscreen={false} + scrollTop={0} /> diff --git a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx index 601341d2657..4169270fd35 100644 --- a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx @@ -83,11 +83,12 @@ export interface Props { dashboard: DashboardModel; isEditing: boolean; isFullscreen: boolean; + scrollTop: number; } export class DashboardGrid extends PureComponent { - gridToPanelMap: any; panelMap: { [id: string]: PanelModel }; + panelRef: { [id: string]: HTMLElement } = {}; componentDidMount() { const { dashboard } = this.props; @@ -149,6 +150,9 @@ export class DashboardGrid extends PureComponent { } this.props.dashboard.sortPanelsByGridPos(); + + // Call render() after any changes. This is called when the layour loads + this.forceUpdate(); }; triggerForceUpdate = () => { @@ -174,7 +178,6 @@ export class DashboardGrid extends PureComponent { }; onResize: ItemCallback = (layout, oldItem, newItem) => { - console.log(); this.panelMap[newItem.i].updateGridPos(newItem); }; @@ -187,18 +190,64 @@ export class DashboardGrid extends PureComponent { this.updateGridPos(newItem, layout); }; + isInView = (panel: PanelModel): boolean => { + if (panel.fullscreen || panel.isEditing) { + return true; + } + + // elem is set *after* the first render + const elem = this.panelRef[panel.id.toString()]; + if (!elem) { + // NOTE the gridPos is also not valid until after the first render + // since it is passed to the layout engine and made to be valid + // for example, you can have Y=0 for everything and it will stack them + // down vertically in the second call + return false; + } + + const top = parseInt(elem.style.top.replace('px', ''), 10); + const height = panel.gridPos.h * GRID_CELL_HEIGHT + 40; + const bottom = top + height; + + // Show things that are almost in the view + const buffer = 250; + + const viewTop = this.props.scrollTop; + if (viewTop > bottom + buffer) { + return false; // The panel is above the viewport + } + + // Use the whole browser height (larger than real value) + // TODO? is there a better way + const viewHeight = isNaN(window.innerHeight) ? (window as any).clientHeight : window.innerHeight; + const viewBot = viewTop + viewHeight; + if (top > viewBot + buffer) { + return false; + } + + return !this.props.dashboard.otherPanelInFullscreen(panel); + }; + renderPanels() { const panelElements = []; - for (const panel of this.props.dashboard.panels) { const panelClasses = classNames({ 'react-grid-item--fullscreen': panel.fullscreen }); + const id = panel.id.toString(); panelElements.push( -
+
{ + this.panelRef[id] = elem; + }} + >
); diff --git a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx index e3b0b93f31d..8b5b6138c75 100644 --- a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx @@ -23,11 +23,13 @@ export interface Props { dashboard: DashboardModel; isEditing: boolean; isFullscreen: boolean; + isInView: boolean; } export interface State { plugin: PanelPlugin; angularPanel: AngularComponent; + isLazy: boolean; } export class DashboardPanel extends PureComponent { @@ -40,6 +42,7 @@ export class DashboardPanel extends PureComponent { this.state = { plugin: null, angularPanel: null, + isLazy: !props.isInView, }; this.specialPanels['row'] = this.renderRow.bind(this); @@ -90,7 +93,11 @@ export class DashboardPanel extends PureComponent { this.loadPlugin(this.props.panel.type); } - componentDidUpdate() { + componentDidUpdate(prevProps: Props, prevState: State) { + if (this.state.isLazy && this.props.isInView) { + this.setState({ isLazy: false }); + } + if (!this.element || this.state.angularPanel) { return; } @@ -123,7 +130,7 @@ export class DashboardPanel extends PureComponent { }; renderReactPanel() { - const { dashboard, panel, isFullscreen } = this.props; + const { dashboard, panel, isFullscreen, isInView } = this.props; const { plugin } = this.state; return ( @@ -138,6 +145,7 @@ export class DashboardPanel extends PureComponent { panel={panel} dashboard={dashboard} isFullscreen={isFullscreen} + isInView={isInView} width={width} height={height} /> @@ -153,7 +161,7 @@ export class DashboardPanel extends PureComponent { render() { const { panel, dashboard, isFullscreen, isEditing } = this.props; - const { plugin, angularPanel } = this.state; + const { plugin, angularPanel, isLazy } = this.state; if (this.isSpecial(panel.type)) { return this.specialPanels[panel.type](); @@ -164,6 +172,11 @@ export class DashboardPanel extends PureComponent { return null; } + // If we are lazy state don't render anything + if (isLazy) { + return null; + } + const containerClass = classNames({ 'panel-editor-container': isEditing, 'panel-height-helper': !isEditing }); const panelWrapperClass = classNames({ 'panel-wrapper': true, diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index b6f9bed46aa..23ffc5c88ca 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -31,6 +31,7 @@ export interface Props { dashboard: DashboardModel; plugin: PanelPlugin; isFullscreen: boolean; + isInView: boolean; width: number; height: number; } @@ -39,6 +40,7 @@ export interface State { isFirstLoad: boolean; renderCounter: number; errorMessage: string | null; + refreshWhenInView: boolean; // Current state of all events data: PanelData; @@ -47,7 +49,6 @@ export interface State { export class PanelChrome extends PureComponent { timeSrv: TimeSrv = getTimeSrv(); querySubscription: Unsubscribable; - delayedStateUpdate: Partial; constructor(props: Props) { super(props); @@ -55,6 +56,7 @@ export class PanelChrome extends PureComponent { isFirstLoad: true, renderCounter: 0, errorMessage: null, + refreshWhenInView: false, data: { state: LoadingState.NotStarted, series: [], @@ -90,17 +92,46 @@ export class PanelChrome extends PureComponent { } } + componentDidUpdate(prevProps: Props) { + const { isInView } = this.props; + + // View state has changed + if (isInView !== prevProps.isInView) { + if (isInView) { + // Subscribe will kick of a notice of the last known state + if (!this.querySubscription && this.wantsQueryExecution) { + const runner = this.props.panel.getQueryRunner(); + this.querySubscription = runner.subscribe(this.panelDataObserver); + } + + // Check if we need a delayed refresh + if (this.state.refreshWhenInView) { + this.onRefresh(); + } + } else if (this.querySubscription) { + this.querySubscription.unsubscribe(); + this.querySubscription = null; + } + } + } + // Updates the response with information from the stream // The next is outside a react synthetic event so setState is not batched // So in this context we can only do a single call to setState panelDataObserver = { next: (data: PanelData) => { + if (!this.props.isInView) { + // Ignore events when not visible. + // The call will be repeated when the panel comes into view + return; + } + let { errorMessage, isFirstLoad } = this.state; if (data.state === LoadingState.Error) { const { error } = data; if (error) { - if (this.state.errorMessage !== error.message) { + if (errorMessage !== error.message) { errorMessage = error.message; } } @@ -113,30 +144,26 @@ export class PanelChrome extends PureComponent { if (this.props.dashboard.snapshot) { this.props.panel.snapshotData = data.series; } - if (this.state.isFirstLoad) { + if (isFirstLoad) { isFirstLoad = false; } } - const stateUpdate = { isFirstLoad, errorMessage, data }; - - if (this.isVisible) { - this.setState(stateUpdate); - } else { - // if we are getting data while another panel is in fullscreen / edit mode - // we need to store the data but not update state yet - this.delayedStateUpdate = stateUpdate; - } + this.setState({ isFirstLoad, errorMessage, data }); }, }; onRefresh = () => { - console.log('onRefresh'); - if (!this.isVisible) { + const { panel, isInView, width } = this.props; + + console.log('onRefresh', panel.id); + + if (!isInView) { + console.log('Refresh when panel is visible', panel.id); + this.setState({ refreshWhenInView: true }); return; } - const { panel, width } = this.props; const timeData = applyPanelTimeOverrides(panel, this.timeSrv.timeRange()); // Issue Query @@ -172,12 +199,6 @@ export class PanelChrome extends PureComponent { onRender = () => { const stateUpdate = { renderCounter: this.state.renderCounter + 1 }; - // If we have received a data update while hidden copy over that state as well - if (this.delayedStateUpdate) { - Object.assign(stateUpdate, this.delayedStateUpdate); - this.delayedStateUpdate = null; - } - this.setState(stateUpdate); }; @@ -199,10 +220,6 @@ export class PanelChrome extends PureComponent { } }; - get isVisible() { - return !this.props.dashboard.otherPanelInFullscreen(this.props.panel); - } - get hasPanelSnapshot() { const { panel } = this.props; return panel.snapshotData && panel.snapshotData.length;