import 'react-grid-layout/css/styles.css' ; import 'react-resizable/css/styles.css' ; import sizeMe from 'react-sizeme' ; import { injectI18n } from '@kbn/i18n-react' ; import classNames from 'classnames' ; import _ from 'lodash' ; import React from 'react' ; import { Subscription } from 'rxjs' ; import ReactGridLayout , { Layout , ReactGridLayoutProps } from 'react-grid-layout' ; import { GridData } from '../../../../common' ; import { ViewMode } from '../../../services/embeddable' ; import { DASHBOARD_GRID_COLUMN_COUNT , DASHBOARD_GRID_HEIGHT } from '../dashboard_constants' ; import { DashboardPanelState } from '../types' ; import { withKibana } from '../../../services/kibana_react' ; import { DashboardContainer , DashboardReactContextValue } from '../dashboard_container' ; import { DashboardGridItem } from './dashboard_grid_item' ; let lastValidGridSize = 0 ; /**
 * This is a fix for a bug that stopped the browser window from automatically scrolling down when panels were made
 * taller than the current grid.
 * see https://github.com/elastic/kibana/issues/14710.
 */ function ensureWindowScrollsToBottom ( event ) { const WINDOW_BUFFER = 10 ; if ( event . clientY > window . innerHeight - WINDOW_BUFFER ) { window . scrollTo ( 0 , event . pageY + WINDOW_BUFFER - window . innerHeight ) ; } } function ResponsiveGrid ( { size , isViewMode , layout , onLayoutChange , children , maximizedPanelId , useMargins , } ) { lastValidGridSize = size . width > 0 ? size . width : lastValidGridSize ; const classes = classNames ( { 'dshLayout--viewing' : isViewMode , 'dshLayout--editing' : ! isViewMode , 'dshLayout-isMaximizedPanel' : maximizedPanelId !== undefined , 'dshLayout-withoutMargins' : ! useMargins , } ) ; const MARGINS = useMargins ? 8 : 0 ; return ( < ReactGridLayout width = { lastValidGridSize } className = { classes } isDraggable = { true } isResizable = { true } useCSSTransforms = { false } margin = { [ MARGINS , MARGINS ] } cols = { DASHBOARD_GRID_COLUMN_COUNT } rowHeight = { DASHBOARD_GRID_HEIGHT } draggableHandle = { isViewMode ? '.doesnt-exist' : '.embPanel__dragger' } layout = { layout } onLayoutChange = { onLayoutChange } onResize = { ( { } , { } , { } , { } , event ) => ensureWindowScrollsToBottom ( event ) } > { children } < /ReactGridLayout>); } const config = { monitorWidth : true } ; const ResponsiveSizedGrid = sizeMe ( config ) ( ResponsiveGrid ) ; export interface DashboardGridProps extends ReactIntl . InjectedIntlProps { kibana : DashboardReactContextValue ; container : DashboardContainer ; } interface State { focusedPanelIndex ? : string ; isLayoutInvalid : boolean ; layout ? : GridData [ ] ; panels : { [ key ] : DashboardPanelState ; } ; viewMode : ViewMode ; useMargins : boolean ; expandedPanelId ? : string ; } interface PanelLayout extends Layout { i : string ; } class DashboardGridUi extends React . Component < DashboardGridProps , State > { private subscription ? : Subscription ; private mounted : boolean = false ; constructor ( props ) { super ( props ) ; this . state = { layout : [ ] , isLayoutInvalid : false , focusedPanelIndex : undefined , panels : this . props . container . getInput ( ) . panels , viewMode : this . props . container . getInput ( ) . viewMode , useMargins : this . props . container . getInput ( ) . useMargins , expandedPanelId : this . props . container . getInput ( ) . expandedPanelId , } ; } public componentDidMount ( ) { this . mounted = true ; let isLayoutInvalid = false ; let layout ; try { layout = this . buildLayoutFromPanels ( ) ; } catch ( error ) { console . error ( error ) ; isLayoutInvalid = true ; this . props . kibana . notifications . toasts . danger ( { title : this . props . intl . formatMessage ( { id : 'dashboard.dashboardGrid.toast.unableToLoadDashboardDangerMessage' , defaultMessage : 'Unable to load dashboard.' , } ) , body : ( error as { message : string ; } ) . message , toastLifeTimeMs : 5000 , } ) ; } this . setState ( { layout , isLayoutInvalid , } ) ; this . subscription = this . props . container . getInput$ ( ) . subscribe ( ( ) => { const { panels , viewMode , useMargins , expandedPanelId } = this . props . container . getInput ( ) ; if ( this . mounted ) { this . setState ( { panels , viewMode , useMargins , expandedPanelId , } ) ; } } ) ; } public componentWillUnmount ( ) { this . mounted = false ; if ( this . subscription ) { this . subscription . unsubscribe ( ) ; } } public buildLayoutFromPanels = ( ) : GridData [ ] => { return _ . map ( this . state . panels , ( panel ) => { return panel . gridData ; } ) ; } ; public onLayoutChange = ( layout ) => { const panels = this . state . panels ; const updatedPanels = layout . reduce ( ( updatedPanelsAcc , panelLayout ) => { updatedPanelsAcc [ panelLayout . i ] = { ... panels [ panelLayout . i ] , gridData : _ . pick ( panelLayout , [ 'x' , 'y' , 'w' , 'h' , 'i' ] ) , } ; return updatedPanelsAcc ; } , { } as { [ key ] : DashboardPanelState ; } ) ; this . onPanelsUpdated ( updatedPanels ) ; } ; public onPanelsUpdated = ( panels ) => { this . props . container . updateInput ( { panels , } ) ; } ; public onPanelFocused = ( focusedPanelIndex ) : void => { this . setState ( { focusedPanelIndex } ) ; } ; public onPanelBlurred = ( blurredPanelIndex ) : void => { if ( this . state . focusedPanelIndex === blurredPanelIndex ) { this . setState ( { focusedPanelIndex : undefined } ) ; } } ; public render ( ) { if ( this . state . isLayoutInvalid ) { return null ; } const { container , kibana } = this . props ; const { focusedPanelIndex , panels , expandedPanelId , viewMode } = this . state ; const isViewMode = viewMode === ViewMode . VIEW ; const panelsInOrder = Object . keys ( panels ) . map ( ( key ) => panels [ key ] as DashboardPanelState ) ; panelsInOrder . sort ( ( panelA , panelB ) => { if ( panelA . gridData . y === panelB . gridData . y ) { return panelA . gridData . x - panelB . gridData . x ; } else { return panelA . gridData . y - panelB . gridData . y ; } } ) ; const dashboardPanels = _ . map ( panelsInOrder , ( { explicitInput , type } ) => ( < DashboardGridItem id = { explicitInput . id } key = { explicitInput . id } type = { type } container = { container } PanelComponent = { kibana . services . embeddable . EmbeddablePanel } expandedPanelId = { expandedPanelId } focusedPanelId = { focusedPanelIndex } / > ) ) ; if ( viewMode === ViewMode . PRINT ) { return < > { dashboardPanels } < />; } return ( < ResponsiveSizedGrid isViewMode = { isViewMode } layout = { this . buildLayoutFromPanels ( ) } onLayoutChange = { this . onLayoutChange } maximizedPanelId = { expandedPanelId } useMargins = { this . state . useMargins } > { dashboardPanels } < /ResponsiveSizedGrid>); } } export const DashboardGrid = injectI18n ( withKibana ( DashboardGridUi ) ) ;