mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Dashboards: Prevent rows nesting (#99246)
This commit is contained in:
parent
51b4ac50aa
commit
d2d6dd2e5f
@ -8,6 +8,7 @@ import {
|
|||||||
sceneGraph,
|
sceneGraph,
|
||||||
sceneUtils,
|
sceneUtils,
|
||||||
SceneComponentProps,
|
SceneComponentProps,
|
||||||
|
SceneGridItemLike,
|
||||||
} from '@grafana/scenes';
|
} from '@grafana/scenes';
|
||||||
import { GRID_COLUMN_COUNT } from 'app/core/constants';
|
import { GRID_COLUMN_COUNT } from 'app/core/constants';
|
||||||
|
|
||||||
@ -385,6 +386,28 @@ export class DefaultGridLayoutManager
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Useful for preserving items positioning when switching layouts
|
||||||
|
* @param gridItems
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public static fromGridItems(gridItems: SceneGridItemLike[]): DefaultGridLayoutManager {
|
||||||
|
const children = gridItems.reduce<SceneGridItemLike[]>((acc, gridItem) => {
|
||||||
|
gridItem.clearParent();
|
||||||
|
acc.push(gridItem);
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return new DefaultGridLayoutManager({
|
||||||
|
grid: new SceneGridLayout({
|
||||||
|
children,
|
||||||
|
isDraggable: true,
|
||||||
|
isResizable: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public static Component = ({ model }: SceneComponentProps<DefaultGridLayoutManager>) => {
|
public static Component = ({ model }: SceneComponentProps<DefaultGridLayoutManager>) => {
|
||||||
return <model.state.grid.Component model={model.state.grid} />;
|
return <model.state.grid.Component model={model.state.grid} />;
|
||||||
};
|
};
|
||||||
|
@ -3,7 +3,8 @@ import { SceneComponentProps, SceneCSSGridLayout, SceneObjectBase, SceneObjectSt
|
|||||||
import { Select } from '@grafana/ui';
|
import { Select } from '@grafana/ui';
|
||||||
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
|
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
|
||||||
|
|
||||||
import { getPanelIdForVizPanel, getVizPanelKeyForPanelId } from '../../utils/utils';
|
import { getDashboardSceneFor, getPanelIdForVizPanel, getVizPanelKeyForPanelId } from '../../utils/utils';
|
||||||
|
import { RowsLayoutManager } from '../layout-rows/RowsLayoutManager';
|
||||||
import { DashboardLayoutManager, LayoutRegistryItem } from '../types';
|
import { DashboardLayoutManager, LayoutRegistryItem } from '../types';
|
||||||
|
|
||||||
import { ResponsiveGridItem } from './ResponsiveGridItem';
|
import { ResponsiveGridItem } from './ResponsiveGridItem';
|
||||||
@ -32,7 +33,9 @@ export class ResponsiveGridLayoutManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
public addNewRow(): void {
|
public addNewRow(): void {
|
||||||
throw new Error('Method not implemented.');
|
const rowsLayout = RowsLayoutManager.createFromLayout(this);
|
||||||
|
rowsLayout.addNewRow();
|
||||||
|
getDashboardSceneFor(this).switchLayout(rowsLayout);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getNextPanelId(): number {
|
public getNextPanelId(): number {
|
||||||
|
@ -95,13 +95,13 @@ export class RowItem extends SceneObjectBase<RowItemState> implements LayoutPare
|
|||||||
};
|
};
|
||||||
|
|
||||||
public static Component = ({ model }: SceneComponentProps<RowItem>) => {
|
public static Component = ({ model }: SceneComponentProps<RowItem>) => {
|
||||||
const { layout, title, isCollapsed, height = 'expand', isHeaderHidden } = model.useState();
|
const { layout, title, isCollapsed, height = 'expand', isHeaderHidden, key } = model.useState();
|
||||||
const { isEditing, showHiddenElements } = getDashboardSceneFor(model).useState();
|
const { isEditing, showHiddenElements } = getDashboardSceneFor(model).useState();
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
const titleInterpolated = sceneGraph.interpolate(model, title, undefined, 'text');
|
const titleInterpolated = sceneGraph.interpolate(model, title, undefined, 'text');
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const shouldGrow = !isCollapsed && height === 'expand';
|
const shouldGrow = !isCollapsed && height === 'expand';
|
||||||
const { isSelected, onSelect } = useElementSelection(model.state.key);
|
const { isSelected, onSelect } = useElementSelection(key);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -1,10 +1,20 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { SceneComponentProps, sceneGraph, SceneObjectBase, SceneObjectState, VizPanel } from '@grafana/scenes';
|
import {
|
||||||
|
SceneComponentProps,
|
||||||
|
sceneGraph,
|
||||||
|
SceneGridItemLike,
|
||||||
|
SceneGridRow,
|
||||||
|
SceneObjectBase,
|
||||||
|
SceneObjectState,
|
||||||
|
VizPanel,
|
||||||
|
} from '@grafana/scenes';
|
||||||
import { useStyles2 } from '@grafana/ui';
|
import { useStyles2 } from '@grafana/ui';
|
||||||
|
|
||||||
import { DashboardScene } from '../DashboardScene';
|
import { DashboardScene } from '../DashboardScene';
|
||||||
|
import { DashboardGridItem } from '../layout-default/DashboardGridItem';
|
||||||
|
import { DefaultGridLayoutManager } from '../layout-default/DefaultGridLayoutManager';
|
||||||
import { ResponsiveGridLayoutManager } from '../layout-responsive-grid/ResponsiveGridLayoutManager';
|
import { ResponsiveGridLayoutManager } from '../layout-responsive-grid/ResponsiveGridLayoutManager';
|
||||||
import { DashboardLayoutManager, LayoutRegistryItem } from '../types';
|
import { DashboardLayoutManager, LayoutRegistryItem } from '../types';
|
||||||
|
|
||||||
@ -101,6 +111,50 @@ export class RowsLayoutManager extends SceneObjectBase<RowsLayoutManagerState> i
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static createFromLayout(layout: DashboardLayoutManager): RowsLayoutManager {
|
public static createFromLayout(layout: DashboardLayoutManager): RowsLayoutManager {
|
||||||
|
if (layout instanceof DefaultGridLayoutManager) {
|
||||||
|
const config: Array<{
|
||||||
|
title?: string;
|
||||||
|
isCollapsed?: boolean;
|
||||||
|
children: SceneGridItemLike[];
|
||||||
|
}> = [];
|
||||||
|
let children: SceneGridItemLike[] | undefined;
|
||||||
|
|
||||||
|
layout.state.grid.forEachChild((child) => {
|
||||||
|
if (!(child instanceof DashboardGridItem) && !(child instanceof SceneGridRow)) {
|
||||||
|
throw new Error('Child is not a DashboardGridItem or SceneGridRow, invalid scene');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (child instanceof SceneGridRow) {
|
||||||
|
if (!child.state.key?.includes('-clone-')) {
|
||||||
|
config.push({
|
||||||
|
title: child.state.title,
|
||||||
|
isCollapsed: !!child.state.isCollapsed,
|
||||||
|
children: child.state.children,
|
||||||
|
});
|
||||||
|
children = undefined;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!children) {
|
||||||
|
children = [];
|
||||||
|
config.push({ children });
|
||||||
|
}
|
||||||
|
|
||||||
|
children.push(child);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const rows = config.map(
|
||||||
|
(rowConfig) =>
|
||||||
|
new RowItem({
|
||||||
|
title: rowConfig.title ?? 'Row title',
|
||||||
|
isCollapsed: !!rowConfig.isCollapsed,
|
||||||
|
layout: DefaultGridLayoutManager.fromGridItems(rowConfig.children),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return new RowsLayoutManager({ rows });
|
||||||
|
}
|
||||||
|
|
||||||
const row = new RowItem({ layout: layout.clone(), title: 'Row title' });
|
const row = new RowItem({ layout: layout.clone(), title: 'Row title' });
|
||||||
|
|
||||||
return new RowsLayoutManager({ rows: [row] });
|
return new RowsLayoutManager({ rows: [row] });
|
||||||
|
@ -7,26 +7,38 @@ import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/Pan
|
|||||||
import { DashboardLayoutManager, isLayoutParent, LayoutRegistryItem } from '../types';
|
import { DashboardLayoutManager, isLayoutParent, LayoutRegistryItem } from '../types';
|
||||||
|
|
||||||
import { layoutRegistry } from './layoutRegistry';
|
import { layoutRegistry } from './layoutRegistry';
|
||||||
|
import { findParentLayout } from './utils';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
layoutManager: DashboardLayoutManager;
|
layoutManager: DashboardLayoutManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DashboardLayoutSelector({ layoutManager }: { layoutManager: DashboardLayoutManager }) {
|
export function DashboardLayoutSelector({ layoutManager }: Props) {
|
||||||
const layouts = layoutRegistry.list();
|
const options = useMemo(() => {
|
||||||
const options = layouts.map((layout) => ({
|
const parentLayout = findParentLayout(layoutManager);
|
||||||
label: layout.name,
|
const parentLayoutId = parentLayout?.getDescriptor().id;
|
||||||
value: layout,
|
|
||||||
}));
|
return layoutRegistry
|
||||||
|
.list()
|
||||||
|
.filter((layout) => layout.id !== parentLayoutId)
|
||||||
|
.map((layout) => ({
|
||||||
|
label: layout.name,
|
||||||
|
value: layout,
|
||||||
|
}));
|
||||||
|
}, [layoutManager]);
|
||||||
|
|
||||||
const currentLayoutId = layoutManager.getDescriptor().id;
|
const currentLayoutId = layoutManager.getDescriptor().id;
|
||||||
const currentLayoutOption = options.find((option) => option.value.id === currentLayoutId);
|
const currentOption = options.find((option) => option.value.id === currentLayoutId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
options={options}
|
options={options}
|
||||||
value={currentLayoutOption}
|
value={currentOption}
|
||||||
onChange={(option) => changeLayoutTo(layoutManager, option.value!)}
|
onChange={(option) => {
|
||||||
|
if (option.value?.id !== currentOption?.value.id) {
|
||||||
|
changeLayoutTo(layoutManager, option.value!);
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
import { SceneObject } from '@grafana/scenes';
|
||||||
|
|
||||||
|
import { DashboardLayoutManager, isDashboardLayoutManager } from '../types';
|
||||||
|
|
||||||
|
export function findParentLayout(sceneObject: SceneObject): DashboardLayoutManager | null {
|
||||||
|
let parent = sceneObject.parent;
|
||||||
|
|
||||||
|
while (parent) {
|
||||||
|
if (isDashboardLayoutManager(parent)) {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent = parent.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user