Skip to content

Commit 931022c

Browse files
SpikatrixGurinderRawala
authored andcommitted
Enable panel resizing (#105)
1 parent 7380d83 commit 931022c

File tree

1 file changed

+73
-142
lines changed

1 file changed

+73
-142
lines changed

public/app/features/dashboard/dashgrid/DashboardGrid.tsx

+73-142
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,35 @@
11
import classNames from 'classnames';
2-
import { PureComponent, CSSProperties } from 'react';
3-
import * as React from 'react';
2+
import React, { PureComponent, CSSProperties } from 'react';
43
import ReactGridLayout, { ItemCallback } from 'react-grid-layout';
4+
import { connect } from 'react-redux';
5+
import AutoSizer from 'react-virtualized-auto-sizer';
56
import { Subscription } from 'rxjs';
67

78
import { config } from '@grafana/runtime';
8-
import appEvents from 'app/core/app_events';
99
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT } from 'app/core/constants';
1010
import { contextSrv } from 'app/core/services/context_srv';
11-
import { VariablesChanged } from 'app/features/variables/types';
11+
import { StoreState } from 'app/types';
1212
import { DashboardPanelsChangedEvent } from 'app/types/events';
1313

1414
import { AddLibraryPanelWidget } from '../components/AddLibraryPanelWidget';
15+
import { AddPanelWidget } from '../components/AddPanelWidget';
1516
import { DashboardRow } from '../components/DashboardRow';
1617
import { DashboardModel, PanelModel } from '../state';
1718
import { GridPos } from '../state/PanelModel';
1819

1920
import DashboardEmpty from './DashboardEmpty';
2021
import { DashboardPanel } from './DashboardPanel';
2122

22-
export const PANEL_FILTER_VARIABLE = 'systemPanelFilterVar';
23-
2423
export interface Props {
2524
dashboard: DashboardModel;
2625
isEditable: boolean;
2726
editPanel: PanelModel | null;
2827
viewPanel: PanelModel | null;
2928
hidePanelMenus?: boolean;
29+
isFnDashboard?: boolean;
3030
}
3131

32-
interface State {
33-
panelFilter?: RegExp;
34-
width: number;
35-
}
36-
37-
export class DashboardGrid extends PureComponent<Props, State> {
32+
export class Component extends PureComponent<Props> {
3833
private panelMap: { [key: string]: PanelModel } = {};
3934
private eventSubs = new Subscription();
4035
private windowHeight = 1200;
@@ -46,67 +41,21 @@ export class DashboardGrid extends PureComponent<Props, State> {
4641

4742
constructor(props: Props) {
4843
super(props);
49-
this.state = {
50-
panelFilter: undefined,
51-
width: document.body.clientWidth, // initial very rough estimate
52-
};
5344
}
5445

5546
componentDidMount() {
5647
const { dashboard } = this.props;
57-
58-
if (config.featureToggles.panelFilterVariable) {
59-
// If panel filter variable is set on load then
60-
// update state to filter panels
61-
for (const variable of dashboard.getVariables()) {
62-
if (variable.id === PANEL_FILTER_VARIABLE) {
63-
if ('query' in variable) {
64-
this.setPanelFilter(variable.query);
65-
}
66-
break;
67-
}
68-
}
69-
70-
this.eventSubs.add(
71-
appEvents.subscribe(VariablesChanged, (e) => {
72-
if (e.payload.variable?.id === PANEL_FILTER_VARIABLE) {
73-
if ('current' in e.payload.variable) {
74-
let variable = e.payload.variable.current;
75-
if ('value' in variable && typeof variable.value === 'string') {
76-
this.setPanelFilter(variable.value);
77-
}
78-
}
79-
}
80-
})
81-
);
82-
}
83-
8448
this.eventSubs.add(dashboard.events.subscribe(DashboardPanelsChangedEvent, this.triggerForceUpdate));
8549
}
8650

8751
componentWillUnmount() {
8852
this.eventSubs.unsubscribe();
8953
}
9054

91-
setPanelFilter(regex: string) {
92-
// Only set the panels filter if the systemPanelFilterVar variable
93-
// is a non-empty string
94-
let panelFilter = undefined;
95-
if (regex.length > 0) {
96-
panelFilter = new RegExp(regex, 'i');
97-
}
98-
99-
this.setState({
100-
panelFilter: panelFilter,
101-
});
102-
}
103-
10455
buildLayout() {
10556
const layout: ReactGridLayout.Layout[] = [];
10657
this.panelMap = {};
107-
const { panelFilter } = this.state;
10858

109-
let count = 0;
11059
for (const panel of this.props.dashboard.panels) {
11160
if (!panel.key) {
11261
panel.key = `panel-${panel.id}-${Date.now()}`;
@@ -133,27 +82,13 @@ export class DashboardGrid extends PureComponent<Props, State> {
13382
panelPos.isDraggable = panel.collapsed;
13483
}
13584

136-
if (!panelFilter) {
137-
layout.push(panelPos);
138-
} else {
139-
if (panelFilter.test(panel.title)) {
140-
panelPos.isResizable = false;
141-
panelPos.isDraggable = false;
142-
panelPos.x = (count % 2) * GRID_COLUMN_COUNT;
143-
panelPos.y = Math.floor(count / 2);
144-
layout.push(panelPos);
145-
count++;
146-
}
147-
}
85+
layout.push(panelPos);
14886
}
14987

15088
return layout;
15189
}
15290

15391
onLayoutChange = (newLayout: ReactGridLayout.Layout[]) => {
154-
if (this.state.panelFilter) {
155-
return;
156-
}
15792
for (const newPos of newLayout) {
15893
this.panelMap[newPos.i!].updateGridPos(newPos, this.isLayoutInitialized);
15994
}
@@ -205,7 +140,6 @@ export class DashboardGrid extends PureComponent<Props, State> {
205140
}
206141

207142
renderPanels(gridWidth: number, isDashboardDraggable: boolean) {
208-
const { panelFilter } = this.state;
209143
const panelElements = [];
210144

211145
// Reset last panel bottom
@@ -222,7 +156,7 @@ export class DashboardGrid extends PureComponent<Props, State> {
222156
for (const panel of this.props.dashboard.panels) {
223157
const panelClasses = classNames({ 'react-grid-item--fullscreen': panel.isViewing });
224158

225-
const p = (
159+
panelElements.push(
226160
<GrafanaGridItem
227161
key={panel.key}
228162
className={panelClasses}
@@ -238,14 +172,6 @@ export class DashboardGrid extends PureComponent<Props, State> {
238172
}}
239173
</GrafanaGridItem>
240174
);
241-
242-
if (!panelFilter) {
243-
panelElements.push(p);
244-
} else {
245-
if (panelFilter.test(panel.title)) {
246-
panelElements.push(p);
247-
}
248-
}
249175
}
250176

251177
return panelElements;
@@ -256,6 +182,11 @@ export class DashboardGrid extends PureComponent<Props, State> {
256182
return <DashboardRow key={panel.key} panel={panel} dashboard={this.props.dashboard} />;
257183
}
258184

185+
// Todo: Remove this when we remove the emptyDashboardPage toggle
186+
if (panel.type === 'add-panel') {
187+
return <AddPanelWidget key={panel.key} panel={panel} dashboard={this.props.dashboard} />;
188+
}
189+
259190
if (panel.type === 'add-library-panel') {
260191
return <AddLibraryPanelWidget key={panel.key} panel={panel} dashboard={this.props.dashboard} />;
261192
}
@@ -288,69 +219,61 @@ export class DashboardGrid extends PureComponent<Props, State> {
288219
}
289220
};
290221

291-
private resizeObserver?: ResizeObserver;
292-
private rootEl: HTMLDivElement | null = null;
293-
onMeasureRef = (rootEl: HTMLDivElement | null) => {
294-
if (!rootEl) {
295-
if (this.rootEl && this.resizeObserver) {
296-
this.resizeObserver.unobserve(this.rootEl);
297-
}
298-
return;
299-
}
300-
301-
this.rootEl = rootEl;
302-
this.resizeObserver = new ResizeObserver((entries) => {
303-
entries.forEach((entry) => {
304-
this.setState({ width: entry.contentRect.width });
305-
});
306-
});
307-
308-
this.resizeObserver.observe(rootEl);
309-
};
310-
311222
render() {
312-
const { isEditable, dashboard } = this.props;
313-
const { width } = this.state;
223+
const { isEditable, dashboard, isFnDashboard } = this.props;
314224

315-
if (dashboard.panels.length === 0) {
225+
if (config.featureToggles.emptyDashboardPage && dashboard.panels.length === 0) {
316226
return <DashboardEmpty dashboard={dashboard} canCreate={isEditable} />;
317227
}
318228

319-
const draggable = width <= config.theme2.breakpoints.values.md ? false : isEditable;
320-
321-
// pos: rel + z-index is required to create a new stacking context to contain
322-
// the escalating z-indexes of the panels
229+
/**
230+
* We have a parent with "flex: 1 1 0" we need to reset it to "flex: 1 1 auto" to have the AutoSizer
231+
* properly working. For more information go here:
232+
* https://github.com/bvaughn/react-virtualized/blob/master/docs/usingAutoSizer.md#can-i-use-autosizer-within-a-flex-container
233+
*/
323234
return (
324-
<div
325-
ref={this.onMeasureRef}
326-
style={{
327-
flex: '1 1 auto',
328-
position: 'relative',
329-
zIndex: 1,
330-
display: this.props.editPanel ? 'none' : undefined,
331-
}}
332-
>
333-
<div style={{ width: width, height: '100%' }} ref={this.onGetWrapperDivRef}>
334-
<ReactGridLayout
335-
width={width}
336-
isDraggable={draggable}
337-
isResizable={isEditable}
338-
containerPadding={[0, 0]}
339-
useCSSTransforms={true}
340-
margin={[GRID_CELL_VMARGIN, GRID_CELL_VMARGIN]}
341-
cols={GRID_COLUMN_COUNT}
342-
rowHeight={GRID_CELL_HEIGHT}
343-
draggableHandle=".grid-drag-handle"
344-
draggableCancel=".grid-drag-cancel"
345-
layout={this.buildLayout()}
346-
onDragStop={this.onDragStop}
347-
onResize={this.onResize}
348-
onResizeStop={this.onResizeStop}
349-
onLayoutChange={this.onLayoutChange}
350-
>
351-
{this.renderPanels(width, draggable)}
352-
</ReactGridLayout>
353-
</div>
235+
<div style={{ flex: '1 1 auto', display: this.props.editPanel ? 'none' : undefined }}>
236+
<AutoSizer disableHeight>
237+
{({ width }) => {
238+
if (width === 0) {
239+
return null;
240+
}
241+
242+
// Disable draggable if mobile device, solving an issue with unintentionally
243+
// moving panels. https://github.com/grafana/grafana/issues/18497
244+
const isLg = width <= config.theme2.breakpoints.values.md;
245+
const draggable = isLg ? false : isEditable;
246+
247+
return (
248+
/**
249+
* The children is using a width of 100% so we need to guarantee that it is wrapped
250+
* in an element that has the calculated size given by the AutoSizer. The AutoSizer
251+
* has a width of 0 and will let its content overflow its div.
252+
*/
253+
<div style={{ width: width, height: '100%' }} ref={this.onGetWrapperDivRef}>
254+
<ReactGridLayout
255+
width={width}
256+
isDraggable={isFnDashboard ? false : draggable}
257+
isResizable={isFnDashboard ? false : isEditable}
258+
containerPadding={[0, 0]}
259+
useCSSTransforms={true}
260+
margin={[GRID_CELL_VMARGIN, GRID_CELL_VMARGIN]}
261+
cols={GRID_COLUMN_COUNT}
262+
rowHeight={GRID_CELL_HEIGHT}
263+
draggableHandle=".grid-drag-handle"
264+
draggableCancel=".grid-drag-cancel"
265+
layout={this.buildLayout()}
266+
onDragStop={this.onDragStop}
267+
onResize={this.onResize}
268+
onResizeStop={this.onResizeStop}
269+
onLayoutChange={this.onLayoutChange}
270+
>
271+
{this.renderPanels(width, draggable)}
272+
</ReactGridLayout>
273+
</div>
274+
);
275+
}}
276+
</AutoSizer>
354277
</div>
355278
);
356279
}
@@ -362,7 +285,7 @@ interface GrafanaGridItemProps extends React.HTMLAttributes<HTMLDivElement> {
362285
isViewing: boolean;
363286
windowHeight: number;
364287
windowWidth: number;
365-
children: any; // eslint-disable-line @typescript-eslint/no-explicit-any
288+
children: any;
366289
}
367290

368291
/**
@@ -403,7 +326,7 @@ const GrafanaGridItem = React.forwardRef<HTMLDivElement, GrafanaGridItemProps>((
403326

404327
// props.children[0] is our main children. RGL adds the drag handle at props.children[1]
405328
return (
406-
<div {...divProps} style={{ ...divProps.style }} ref={ref}>
329+
<div {...divProps} ref={ref}>
407330
{/* Pass width and height to children as render props */}
408331
{[props.children[0](width, height), props.children.slice(1)]}
409332
</div>
@@ -418,3 +341,11 @@ function translateGridHeightToScreenHeight(gridHeight: number): number {
418341
}
419342

420343
GrafanaGridItem.displayName = 'GridItemWithDimensions';
344+
345+
function mapStateToProps() {
346+
return (state: StoreState) => ({
347+
isFnDashboard: state.fnGlobalState.FNDashboard,
348+
});
349+
}
350+
351+
export const DashboardGrid = connect(mapStateToProps)(Component);

0 commit comments

Comments
 (0)