2025-04-01 10:38:02 +09:00

255 lines
7.7 KiB
TypeScript

import { SceneGridItemLike, SceneGridRow, SceneObjectBase, SceneObjectState, VizPanel } from '@grafana/scenes';
import { t } from 'app/core/internationalization';
import { isClonedKey } from '../../utils/clone';
import { dashboardSceneGraph } from '../../utils/dashboardSceneGraph';
import { DashboardGridItem } from '../layout-default/DashboardGridItem';
import { DefaultGridLayoutManager } from '../layout-default/DefaultGridLayoutManager';
import { RowRepeaterBehavior } from '../layout-default/RowRepeaterBehavior';
import { DashboardLayoutManager } from '../types/DashboardLayoutManager';
import { LayoutRegistryItem } from '../types/LayoutRegistryItem';
import { RowItem } from './RowItem';
import { RowItemRepeaterBehavior } from './RowItemRepeaterBehavior';
import { RowLayoutManagerRenderer } from './RowsLayoutManagerRenderer';
interface RowsLayoutManagerState extends SceneObjectState {
rows: RowItem[];
}
export class RowsLayoutManager extends SceneObjectBase<RowsLayoutManagerState> implements DashboardLayoutManager {
public static Component = RowLayoutManagerRenderer;
public readonly isDashboardLayoutManager = true;
public static readonly descriptor: LayoutRegistryItem = {
get name() {
return t('dashboard.rows-layout.name', 'Rows');
},
get description() {
return t('dashboard.rows-layout.description', 'Rows layout');
},
id: 'rows-layout',
createFromLayout: RowsLayoutManager.createFromLayout,
kind: 'RowsLayout',
};
public readonly descriptor = RowsLayoutManager.descriptor;
public addPanel(vizPanel: VizPanel) {
// Try to add new panels to the selected row
const selectedRows = dashboardSceneGraph.getAllSelectedObjects(this).filter((obj) => obj instanceof RowItem);
if (selectedRows.length > 0) {
return selectedRows.forEach((row) => row.onAddPanel(vizPanel));
}
// If we don't have selected row add it to the first row
if (this.state.rows.length > 0) {
return this.state.rows[0].onAddPanel(vizPanel);
}
// Otherwise fallback to adding a new row and a panel
this.addNewRow();
this.state.rows[this.state.rows.length - 1].onAddPanel(vizPanel);
}
public getVizPanels(): VizPanel[] {
const panels: VizPanel[] = [];
for (const row of this.state.rows) {
const innerPanels = row.getLayout().getVizPanels();
panels.push(...innerPanels);
}
return panels;
}
public hasVizPanels(): boolean {
for (const row of this.state.rows) {
if (row.getLayout().hasVizPanels()) {
return true;
}
}
return false;
}
public cloneLayout(ancestorKey: string, isSource: boolean): DashboardLayoutManager {
throw new Error('Method not implemented.');
}
public addNewRow() {
this.setState({ rows: [...this.state.rows, new RowItem()] });
}
public editModeChanged(isEditing: boolean) {
this.state.rows.forEach((row) => row.getLayout().editModeChanged?.(isEditing));
}
public activateRepeaters() {
this.state.rows.forEach((row) => {
if (!row.isActive) {
row.activate();
}
const behavior = (row.state.$behaviors ?? []).find((b) => b instanceof RowItemRepeaterBehavior);
if (!behavior?.isActive) {
behavior?.activate();
}
row.getLayout().activateRepeaters?.();
});
}
public addRowAbove(row: RowItem) {
const rows = this.state.rows;
const index = rows.indexOf(row);
rows.splice(index, 0, new RowItem());
this.setState({ rows });
}
public addRowBelow(row: RowItem) {
const rows = this.state.rows;
let index = rows.indexOf(row);
// Be sure we don't add a row between an original row and one of its clones
while (rows[index + 1] && isClonedKey(rows[index + 1].state.key!)) {
index = index + 1;
}
rows.splice(index + 1, 0, new RowItem());
this.setState({ rows });
}
public removeRow(row: RowItem) {
const rows = this.state.rows.filter((r) => r !== row);
this.setState({ rows: rows.length === 0 ? [new RowItem()] : rows });
}
public moveRowUp(row: RowItem) {
const rows = [...this.state.rows];
const originalIndex = rows.indexOf(row);
if (originalIndex === 0) {
return;
}
let moveToIndex = originalIndex - 1;
// Be sure we don't add a row between an original row and one of its clones
while (rows[moveToIndex] && isClonedKey(rows[moveToIndex].state.key!)) {
moveToIndex = moveToIndex - 1;
}
rows.splice(originalIndex, 1);
rows.splice(moveToIndex, 0, row);
this.setState({ rows });
}
public moveRowDown(row: RowItem) {
const rows = [...this.state.rows];
const originalIndex = rows.indexOf(row);
if (originalIndex === rows.length - 1) {
return;
}
let moveToIndex = originalIndex + 1;
// Be sure we don't add a row between an original row and one of its clones
while (rows[moveToIndex] && isClonedKey(rows[moveToIndex].state.key!)) {
moveToIndex = moveToIndex + 1;
}
rows.splice(moveToIndex + 1, 0, row);
rows.splice(originalIndex, 1);
this.setState({ rows });
}
public isFirstRow(row: RowItem): boolean {
return this.state.rows[0] === row;
}
public isLastRow(row: RowItem): boolean {
const filteredRow = this.state.rows.filter((r) => !isClonedKey(r.state.key!));
return filteredRow[filteredRow.length - 1] === row;
}
public static createEmpty(): RowsLayoutManager {
return new RowsLayoutManager({ rows: [new RowItem()] });
}
public static createFromLayout(layout: DashboardLayoutManager): RowsLayoutManager {
let rows: RowItem[];
if (layout instanceof DefaultGridLayoutManager) {
const config: Array<{
title?: string;
isCollapsed?: boolean;
isDraggable?: boolean;
isResizable?: boolean;
children: SceneGridItemLike[];
repeat?: string;
}> = [];
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 (!isClonedKey(child.state.key!)) {
const behaviour = child.state.$behaviors?.find((b) => b instanceof RowRepeaterBehavior);
config.push({
title: child.state.title,
isCollapsed: !!child.state.isCollapsed,
isDraggable: child.state.isDraggable ?? layout.state.grid.state.isDraggable,
isResizable: child.state.isResizable ?? layout.state.grid.state.isResizable,
children: child.state.children,
repeat: behaviour?.state.variableName,
});
// Since we encountered a row item, any subsequent panels should be added to a new row
children = undefined;
}
} else {
if (!children) {
children = [];
config.push({ children });
}
children.push(child);
}
});
rows = config.map(
(rowConfig) =>
new RowItem({
title: rowConfig.title,
isCollapsed: !!rowConfig.isCollapsed,
layout: DefaultGridLayoutManager.fromGridItems(
rowConfig.children,
rowConfig.isDraggable,
rowConfig.isResizable
),
$behaviors: rowConfig.repeat ? [new RowItemRepeaterBehavior({ variableName: rowConfig.repeat })] : [],
})
);
} else {
rows = [new RowItem({ layout: layout.clone() })];
}
// Ensure we always get at least one row
if (rows.length === 0) {
rows = [new RowItem()];
}
return new RowsLayoutManager({ rows });
}
}