2025-02-07 18:57:54 +08:00
|
|
|
import { css } from '@emotion/css';
|
|
|
|
import { useMemo } from 'react';
|
|
|
|
|
|
|
|
import { config } from '@grafana/runtime';
|
2025-08-18 18:02:45 +08:00
|
|
|
import { SceneComponentProps } from '@grafana/scenes';
|
2025-02-07 18:57:54 +08:00
|
|
|
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN } from 'app/core/constants';
|
|
|
|
|
|
|
|
import { DashboardGridItem, RepeatDirection } from './DashboardGridItem';
|
|
|
|
|
|
|
|
export function DashboardGridItemRenderer({ model }: SceneComponentProps<DashboardGridItem>) {
|
2025-08-18 18:02:45 +08:00
|
|
|
const { repeatedPanels = [], itemHeight, variableName, body } = model.useState();
|
|
|
|
const layoutStyle = useLayoutStyle(
|
|
|
|
model.getRepeatDirection(),
|
|
|
|
model.getPanelCount(),
|
|
|
|
model.getMaxPerRow(),
|
|
|
|
itemHeight ?? 10
|
|
|
|
);
|
2025-02-07 18:57:54 +08:00
|
|
|
|
|
|
|
if (!variableName) {
|
2025-08-18 18:02:45 +08:00
|
|
|
return (
|
|
|
|
<div className={panelWrapper} ref={model.containerRef}>
|
|
|
|
<body.Component model={body} key={body.state.key} />
|
|
|
|
</div>
|
|
|
|
);
|
2025-02-07 18:57:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
2025-04-03 16:35:34 +08:00
|
|
|
<div className={layoutStyle} ref={model.containerRef}>
|
2025-08-18 18:02:45 +08:00
|
|
|
<div className={panelWrapper} key={body.state.key}>
|
|
|
|
<body.Component model={body} key={body.state.key} />
|
|
|
|
</div>
|
2025-02-07 18:57:54 +08:00
|
|
|
{repeatedPanels.map((panel) => (
|
2025-04-03 16:35:34 +08:00
|
|
|
<div className={panelWrapper} key={panel.state.key}>
|
2025-02-07 18:57:54 +08:00
|
|
|
<panel.Component model={panel} key={panel.state.key} />
|
|
|
|
</div>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function useLayoutStyle(direction: RepeatDirection, itemCount: number, maxPerRow: number, itemHeight: number) {
|
|
|
|
return useMemo(() => {
|
|
|
|
const theme = config.theme2;
|
|
|
|
|
|
|
|
// In mobile responsive layout we have to calculate the absolute height
|
|
|
|
const mobileHeight = itemHeight * GRID_CELL_HEIGHT * itemCount + (itemCount - 1) * GRID_CELL_VMARGIN;
|
|
|
|
|
|
|
|
if (direction === 'h') {
|
|
|
|
const rowCount = Math.ceil(itemCount / maxPerRow);
|
|
|
|
const columnCount = Math.min(itemCount, maxPerRow);
|
|
|
|
|
|
|
|
return css({
|
|
|
|
display: 'grid',
|
|
|
|
height: '100%',
|
|
|
|
width: '100%',
|
|
|
|
gridTemplateColumns: `repeat(${columnCount}, 1fr)`,
|
|
|
|
gridTemplateRows: `repeat(${rowCount}, 1fr)`,
|
|
|
|
gridColumnGap: theme.spacing(1),
|
|
|
|
gridRowGap: theme.spacing(1),
|
|
|
|
|
|
|
|
[theme.breakpoints.down('md')]: {
|
|
|
|
display: 'flex',
|
|
|
|
flexDirection: 'column',
|
|
|
|
height: mobileHeight,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Vertical is a bit simpler
|
|
|
|
return css({
|
|
|
|
display: 'flex',
|
|
|
|
height: '100%',
|
|
|
|
width: '100%',
|
|
|
|
flexDirection: 'column',
|
|
|
|
gap: theme.spacing(1),
|
|
|
|
[theme.breakpoints.down('md')]: {
|
|
|
|
height: mobileHeight,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}, [direction, itemCount, maxPerRow, itemHeight]);
|
|
|
|
}
|
|
|
|
|
2025-04-03 16:35:34 +08:00
|
|
|
const panelWrapper = css({
|
2025-02-07 18:57:54 +08:00
|
|
|
display: 'flex',
|
|
|
|
flexGrow: 1,
|
|
|
|
position: 'relative',
|
2025-04-03 16:35:34 +08:00
|
|
|
width: '100%',
|
|
|
|
height: '100%',
|
2025-02-07 18:57:54 +08:00
|
|
|
});
|