Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POC of using kbn grid layout in Dashboard. #22

Open
wants to merge 1 commit into
base: spaceTime/htmlDragDrop
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,131 +6,86 @@
* Side Public License, v 1.
*/

import 'react-resizable/css/styles.css';
import 'react-grid-layout/css/styles.css';
import 'react-resizable/css/styles.css';

import { pick } from 'lodash';
import { ReactEmbeddableRenderer, ViewMode } from '@kbn/embeddable-plugin/public';
import { GridLayout, GridLayoutData, GridPanelData } from '@kbn/grid-layout';
import classNames from 'classnames';
import React, { useState, useMemo, useCallback, useEffect } from 'react';
import { Layout, Responsive as ResponsiveReactGridLayout } from 'react-grid-layout';

import { ViewMode } from '@kbn/embeddable-plugin/public';

import { DashboardPanelState } from '../../../../common';
import { DashboardGridItem } from './dashboard_grid_item';
import { useDashboardGridSettings } from './use_dashboard_grid_settings';
import React from 'react';
import {
DASHBOARD_GRID_COLUMN_COUNT,
DASHBOARD_GRID_HEIGHT,
DASHBOARD_MARGIN_SIZE,
} from '../../../dashboard_constants';
import { useDashboardContainer } from '../../embeddable/dashboard_container';
import { getPanelLayoutsAreEqual } from '../../state/diffing/dashboard_diffing_utils';
import { DASHBOARD_GRID_HEIGHT, DASHBOARD_MARGIN_SIZE } from '../../../dashboard_constants';

export const DashboardGrid = ({ viewportWidth }: { viewportWidth: number }) => {
const dashboard = useDashboardContainer();
const panels = dashboard.select((state) => state.explicitInput.panels);
const viewMode = dashboard.select((state) => state.explicitInput.viewMode);
const useMargins = dashboard.select((state) => state.explicitInput.useMargins);
const expandedPanelId = dashboard.select((state) => state.componentState.expandedPanelId);
const focusedPanelId = dashboard.select((state) => state.componentState.focusedPanelId);
const animatePanelTransforms = dashboard.select(
(state) => state.componentState.animatePanelTransforms
);

/**
* Track panel maximized state delayed by one tick and use it to prevent
* panel sliding animations on maximize and minimize.
*/
const [delayedIsPanelExpanded, setDelayedIsPanelMaximized] = useState(false);
useEffect(() => {
if (expandedPanelId) {
setDelayedIsPanelMaximized(true);
} else {
setTimeout(() => setDelayedIsPanelMaximized(false), 0);
}
}, [expandedPanelId]);

const panelsInOrder: string[] = useMemo(() => {
return Object.keys(panels).sort((embeddableIdA, embeddableIdB) => {
const panelA = panels[embeddableIdA];
const panelB = panels[embeddableIdB];

// need to manually sort the panels by position because we want the panels to be collapsed from the left to the
// right when switching to the single column layout, but RGL sorts by ID which can cause unexpected behaviour between
// by-reference and by-value panels + we want the HTML order to align with this in the multi-panel view
if (panelA.gridData.y === panelB.gridData.y) {
return panelA.gridData.x - panelB.gridData.x;
} else {
return panelA.gridData.y - panelB.gridData.y;
}
});
}, [panels]);

const panelComponents = useMemo(() => {
return panelsInOrder.map((embeddableId, index) => {
const type = panels[embeddableId].type;
return (
<DashboardGridItem
data-grid={panels[embeddableId].gridData}
key={embeddableId}
id={embeddableId}
index={index + 1}
type={type}
expandedPanelId={expandedPanelId}
focusedPanelId={focusedPanelId}
/>
);
});
}, [expandedPanelId, panels, panelsInOrder, focusedPanelId]);

const onLayoutChange = useCallback(
(newLayout: Array<Layout & { i: string }>) => {
if (viewMode !== ViewMode.EDIT) return;

const updatedPanels: { [key: string]: DashboardPanelState } = newLayout.reduce(
(updatedPanelsAcc, panelLayout) => {
updatedPanelsAcc[panelLayout.i] = {
...panels[panelLayout.i],
gridData: pick(panelLayout, ['x', 'y', 'w', 'h', 'i']),
};
return updatedPanelsAcc;
},
{} as { [key: string]: DashboardPanelState }
);
if (!getPanelLayoutsAreEqual(panels, updatedPanels)) {
dashboard.dispatch.setPanels(updatedPanels);
}
},
[dashboard, panels, viewMode]
);

const classes = classNames({
'dshLayout-withoutMargins': !useMargins,
'dshLayout--viewing': viewMode === ViewMode.VIEW,
'dshLayout--editing': viewMode !== ViewMode.VIEW,
'dshLayout--noAnimation': !animatePanelTransforms || delayedIsPanelExpanded,
'dshLayout-isMaximizedPanel': expandedPanelId !== undefined,
});

const { layouts, breakpoints, columns } = useDashboardGridSettings(panelsInOrder);

// in print mode, dashboard layout is not controlled by React Grid Layout
if (viewMode === ViewMode.PRINT) {
return <>{panelComponents}</>;
}
const panelProps = {
showBadges: true,
showBorder: useMargins,
showNotifications: true,
showShadow: false,
};

return (
<ResponsiveReactGridLayout
cols={columns}
layouts={layouts}
className={classes}
width={viewportWidth}
breakpoints={breakpoints}
onLayoutChange={onLayoutChange}
isResizable={!expandedPanelId && !focusedPanelId}
isDraggable={!expandedPanelId && !focusedPanelId}
rowHeight={DASHBOARD_GRID_HEIGHT}
margin={useMargins ? [DASHBOARD_MARGIN_SIZE, DASHBOARD_MARGIN_SIZE] : [0, 0]}
draggableHandle={'.embPanel--dragHandle'}
>
{panelComponents}
</ResponsiveReactGridLayout>
<div className={classes}>
<GridLayout
getCreationOptions={() => {
const layoutPanels = Object.values(panels).reduce((acc, panel) => {
const gridData: GridPanelData = {
id: panel.explicitInput.id,
row: panel.gridData.y,
column: panel.gridData.x,
width: panel.gridData.w,
height: panel.gridData.h,
};
acc[panel.explicitInput.id] = gridData;
return acc;
}, {} as { [key: string]: GridPanelData });
const initialLayout: GridLayoutData = [
{
title: 'DEFAULT DASHBOARD',
isCollapsed: false,
panels: layoutPanels,
},
];
return {
initialLayout,
gridSettings: {
gutterSize: DASHBOARD_MARGIN_SIZE,
rowHeight: DASHBOARD_GRID_HEIGHT,
columnCount: DASHBOARD_GRID_COLUMN_COUNT,
},
};
}}
renderPanelContents={(embeddableId) => {
const type = panels[embeddableId].type;
return (
<ReactEmbeddableRenderer
type={type}
maybeId={embeddableId}
panelProps={panelProps}
getParentApi={() => dashboard}
key={`${type}_${embeddableId}`}
onApiAvailable={(api) => dashboard.registerChildApi(api)}
/>
);
}}
></GridLayout>
</div>
);
};
Loading