DashboardGrid.tsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. // Libaries
  2. import React, { PureComponent } from 'react';
  3. import { hot } from 'react-hot-loader';
  4. import ReactGridLayout, { ItemCallback } from 'react-grid-layout';
  5. import classNames from 'classnames';
  6. import sizeMe from 'react-sizeme';
  7. // Types
  8. import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT } from 'app/core/constants';
  9. import { DashboardPanel } from './DashboardPanel';
  10. import { DashboardModel, PanelModel } from '../state';
  11. let lastGridWidth = 1200;
  12. let ignoreNextWidthChange = false;
  13. interface GridWrapperProps {
  14. size: { width: number };
  15. layout: ReactGridLayout.Layout[];
  16. onLayoutChange: (layout: ReactGridLayout.Layout[]) => void;
  17. children: JSX.Element | JSX.Element[];
  18. onDragStop: ItemCallback;
  19. onResize: ItemCallback;
  20. onResizeStop: ItemCallback;
  21. onWidthChange: () => void;
  22. className: string;
  23. isResizable?: boolean;
  24. isDraggable?: boolean;
  25. isFullscreen?: boolean;
  26. }
  27. function GridWrapper({
  28. size,
  29. layout,
  30. onLayoutChange,
  31. children,
  32. onDragStop,
  33. onResize,
  34. onResizeStop,
  35. onWidthChange,
  36. className,
  37. isResizable,
  38. isDraggable,
  39. isFullscreen,
  40. }: GridWrapperProps) {
  41. const width = size.width > 0 ? size.width : lastGridWidth;
  42. // logic to ignore width changes (optimization)
  43. if (width !== lastGridWidth) {
  44. if (ignoreNextWidthChange) {
  45. ignoreNextWidthChange = false;
  46. } else if (!isFullscreen && Math.abs(width - lastGridWidth) > 8) {
  47. onWidthChange();
  48. lastGridWidth = width;
  49. }
  50. }
  51. return (
  52. <ReactGridLayout
  53. width={lastGridWidth}
  54. className={className}
  55. isDraggable={isDraggable}
  56. isResizable={isResizable}
  57. containerPadding={[0, 0]}
  58. useCSSTransforms={false}
  59. margin={[GRID_CELL_VMARGIN, GRID_CELL_VMARGIN]}
  60. cols={GRID_COLUMN_COUNT}
  61. rowHeight={GRID_CELL_HEIGHT}
  62. draggableHandle=".grid-drag-handle"
  63. layout={layout}
  64. onResize={onResize}
  65. onResizeStop={onResizeStop}
  66. onDragStop={onDragStop}
  67. onLayoutChange={onLayoutChange}
  68. >
  69. {children}
  70. </ReactGridLayout>
  71. );
  72. }
  73. const SizedReactLayoutGrid = sizeMe({ monitorWidth: true })(GridWrapper);
  74. export interface Props {
  75. dashboard: DashboardModel;
  76. isEditing: boolean;
  77. isFullscreen: boolean;
  78. }
  79. export class DashboardGrid extends PureComponent<Props> {
  80. gridToPanelMap: any;
  81. panelMap: { [id: string]: PanelModel };
  82. componentDidMount() {
  83. const { dashboard } = this.props;
  84. dashboard.on('panel-added', this.triggerForceUpdate);
  85. dashboard.on('panel-removed', this.triggerForceUpdate);
  86. dashboard.on('repeats-processed', this.triggerForceUpdate);
  87. dashboard.on('view-mode-changed', this.onViewModeChanged);
  88. dashboard.on('row-collapsed', this.triggerForceUpdate);
  89. dashboard.on('row-expanded', this.triggerForceUpdate);
  90. }
  91. componentWillUnmount() {
  92. const { dashboard } = this.props;
  93. dashboard.off('panel-added', this.triggerForceUpdate);
  94. dashboard.off('panel-removed', this.triggerForceUpdate);
  95. dashboard.off('repeats-processed', this.triggerForceUpdate);
  96. dashboard.off('view-mode-changed', this.onViewModeChanged);
  97. dashboard.off('row-collapsed', this.triggerForceUpdate);
  98. dashboard.off('row-expanded', this.triggerForceUpdate);
  99. }
  100. buildLayout() {
  101. const layout = [];
  102. this.panelMap = {};
  103. for (const panel of this.props.dashboard.panels) {
  104. const stringId = panel.id.toString();
  105. this.panelMap[stringId] = panel;
  106. if (!panel.gridPos) {
  107. console.log('panel without gridpos');
  108. continue;
  109. }
  110. const panelPos: any = {
  111. i: stringId,
  112. x: panel.gridPos.x,
  113. y: panel.gridPos.y,
  114. w: panel.gridPos.w,
  115. h: panel.gridPos.h,
  116. };
  117. if (panel.type === 'row') {
  118. panelPos.w = GRID_COLUMN_COUNT;
  119. panelPos.h = 1;
  120. panelPos.isResizable = false;
  121. panelPos.isDraggable = panel.collapsed;
  122. }
  123. layout.push(panelPos);
  124. }
  125. return layout;
  126. }
  127. onLayoutChange = (newLayout: ReactGridLayout.Layout[]) => {
  128. for (const newPos of newLayout) {
  129. this.panelMap[newPos.i].updateGridPos(newPos);
  130. }
  131. this.props.dashboard.sortPanelsByGridPos();
  132. };
  133. triggerForceUpdate = () => {
  134. this.forceUpdate();
  135. };
  136. onWidthChange = () => {
  137. for (const panel of this.props.dashboard.panels) {
  138. panel.resizeDone();
  139. }
  140. };
  141. onViewModeChanged = () => {
  142. ignoreNextWidthChange = true;
  143. };
  144. updateGridPos = (item: ReactGridLayout.Layout, layout: ReactGridLayout.Layout[]) => {
  145. this.panelMap[item.i].updateGridPos(item);
  146. // react-grid-layout has a bug (#670), and onLayoutChange() is only called when the component is mounted.
  147. // So it's required to call it explicitly when panel resized or moved to save layout changes.
  148. this.onLayoutChange(layout);
  149. };
  150. onResize: ItemCallback = (layout, oldItem, newItem) => {
  151. console.log();
  152. this.panelMap[newItem.i].updateGridPos(newItem);
  153. };
  154. onResizeStop: ItemCallback = (layout, oldItem, newItem) => {
  155. this.updateGridPos(newItem, layout);
  156. this.panelMap[newItem.i].resizeDone();
  157. };
  158. onDragStop: ItemCallback = (layout, oldItem, newItem) => {
  159. this.updateGridPos(newItem, layout);
  160. };
  161. renderPanels() {
  162. const panelElements = [];
  163. for (const panel of this.props.dashboard.panels) {
  164. const panelClasses = classNames({ 'react-grid-item--fullscreen': panel.fullscreen });
  165. panelElements.push(
  166. <div key={panel.id.toString()} className={panelClasses} id={`panel-${panel.id}`}>
  167. <DashboardPanel
  168. panel={panel}
  169. dashboard={this.props.dashboard}
  170. isEditing={panel.isEditing}
  171. isFullscreen={panel.fullscreen}
  172. />
  173. </div>
  174. );
  175. }
  176. return panelElements;
  177. }
  178. render() {
  179. const { dashboard, isFullscreen } = this.props;
  180. return (
  181. <SizedReactLayoutGrid
  182. className={classNames({ layout: true })}
  183. layout={this.buildLayout()}
  184. isResizable={dashboard.meta.canEdit}
  185. isDraggable={dashboard.meta.canEdit}
  186. onLayoutChange={this.onLayoutChange}
  187. onWidthChange={this.onWidthChange}
  188. onDragStop={this.onDragStop}
  189. onResize={this.onResize}
  190. onResizeStop={this.onResizeStop}
  191. isFullscreen={isFullscreen}
  192. >
  193. {this.renderPanels()}
  194. </SizedReactLayoutGrid>
  195. );
  196. }
  197. }
  198. export default hot(module)(DashboardGrid);