{
private subscription?: Subscription;
private mounted: boolean = false;
// A mapping of panelIndexes to grid items so we can set the zIndex appropriately on the last focused
// item.
private gridItems = {} as { [key: string]: HTMLDivElement | null };
constructor(props: DashboardGridProps) {
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: any) {
console.error(error); // eslint-disable-line no-console
isLayoutInvalid = true;
this.props.opensearchDashboards.notifications.toasts.danger({
title: this.props.intl.formatMessage({
id: 'dashboard.dashboardGrid.toast.unableToLoadDashboardDangerMessage',
defaultMessage: 'Unable to load dashboard.',
}),
body: error.message,
toastLifeTimeMs: 5000,
});
}
this.setState({
layout,
isLayoutInvalid,
});
this.subscription = this.props.container
.getInput$()
.subscribe((input: DashboardContainerInput) => {
if (this.mounted) {
this.setState({
panels: input.panels,
viewMode: input.viewMode,
useMargins: input.useMargins,
expandedPanelId: input.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: PanelLayout[]) => {
const panels = this.state.panels;
const updatedPanels: { [key: string]: DashboardPanelState } = layout.reduce(
(updatedPanelsAcc, panelLayout) => {
updatedPanelsAcc[panelLayout.i] = {
...panels[panelLayout.i],
gridData: _.pick(panelLayout, ['x', 'y', 'w', 'h', 'i']),
};
return updatedPanelsAcc;
},
{} as { [key: string]: DashboardPanelState }
);
this.onPanelsUpdated(updatedPanels);
};
public onPanelsUpdated = (panels: { [key: string]: DashboardPanelState }) => {
this.props.container.updateInput({
panels,
});
};
public onPanelFocused = (focusedPanelIndex: string): void => {
this.setState({ focusedPanelIndex });
};
public onPanelBlurred = (blurredPanelIndex: string): void => {
if (this.state.focusedPanelIndex === blurredPanelIndex) {
this.setState({ focusedPanelIndex: undefined });
}
};
public renderPanels() {
const { focusedPanelIndex, panels, expandedPanelId } = this.state;
// Part of our unofficial API - need to render in a consistent order for plugins.
const panelsInOrder = Object.keys(panels).map(
(key: string) => 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;
}
});
return _.map(panelsInOrder, (panel) => {
const expandPanel =
expandedPanelId !== undefined && expandedPanelId === panel.explicitInput.id;
const hidePanel = expandedPanelId !== undefined && expandedPanelId !== panel.explicitInput.id;
const classes = classNames({
// eslint-disable-next-line @typescript-eslint/naming-convention
'dshDashboardGrid__item--expanded': expandPanel,
// eslint-disable-next-line @typescript-eslint/naming-convention
'dshDashboardGrid__item--hidden': hidePanel,
});
return (
{
this.gridItems[panel.explicitInput.id] = reactGridItem;
}}
>
);
});
}
public render() {
if (this.state.isLayoutInvalid) {
return null;
}
const { viewMode } = this.state;
const isViewMode = viewMode === ViewMode.VIEW;
return (
{this.renderPanels()}
);
}
}
export const DashboardGrid = injectI18n(withOpenSearchDashboards(DashboardGridUi));