import { css, cx } from '@emotion/css';
import { useMemo, useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { SceneObject, VizPanel } from '@grafana/scenes';
import { Box, Icon, IconButton, Stack, Text, useElementSelection, useStyles2 } from '@grafana/ui';
import { t, Trans } from 'app/core/internationalization';
import { DashboardGridItem } from '../scene/layout-default/DashboardGridItem';
import { isInCloneChain } from '../utils/clone';
import { getDashboardSceneFor } from '../utils/utils';
import { DashboardEditPane } from './DashboardEditPane';
import { getEditableElementFor, hasEditableElement } from './shared';
export interface Props {
editPane: DashboardEditPane;
}
export function DashboardOutline({ editPane }: Props) {
const dashboard = getDashboardSceneFor(editPane);
return (
);
}
function DashboardOutlineNode({ sceneObject, expandable }: { sceneObject: SceneObject; expandable: boolean }) {
const [isExpanded, setIsExpanded] = useState(true);
const { key } = sceneObject.useState();
const styles = useStyles2(getStyles);
const { isSelected, onSelect } = useElementSelection(key);
const isCloned = useMemo(() => isInCloneChain(key!), [key]);
const editableElement = useMemo(() => getEditableElementFor(sceneObject)!, [sceneObject]);
const children = collectEditableElementChildren(sceneObject);
const elementInfo = editableElement.getEditableElementInfo();
return (
<>
{expandable && (
setIsExpanded(!isExpanded)}
aria-label={
isExpanded
? t('dashboard.outline.tree.item.collapse', 'Collapse item')
: t('dashboard.outline.tree.item.expand', 'Expand item')
}
/>
)}
{expandable && isExpanded && (
{children.length > 0 ? (
children.map((child) => (
))
) : (
(empty)
)}
)}
>
);
}
function getStyles(theme: GrafanaTheme2) {
return {
container: css({
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(1),
marginLeft: theme.spacing(1),
paddingLeft: theme.spacing(1.5),
borderLeft: `1px solid ${theme.colors.border.medium}`,
}),
nodeButton: css({
boxShadow: 'none',
border: 'none',
background: 'transparent',
padding: theme.spacing(0.25, 1),
borderRadius: theme.shape.radius.default,
display: 'flex',
alignItems: 'center',
gap: theme.spacing(1),
overflow: 'hidden',
'&:hover': {
backgroundColor: theme.colors.action.hover,
},
'> span': {
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
},
}),
nodeButtonSelected: css({
color: theme.colors.primary.text,
}),
nodeButtonClone: css({
color: theme.colors.text.secondary,
cursor: 'not-allowed',
}),
};
}
interface EditableElementConfig {
sceneObject: SceneObject;
expandable: boolean;
}
function collectEditableElementChildren(
sceneObject: SceneObject,
children: EditableElementConfig[] = []
): EditableElementConfig[] {
sceneObject.forEachChild((child) => {
if (child instanceof DashboardGridItem) {
// DashboardGridItem is a special case as it can contain repeated panels
// In this case, we want to show the repeated panels as separate items, otherwise show the body panel
if (child.state.repeatedPanels?.length) {
children.push(...child.state.repeatedPanels.map((panel) => ({ sceneObject: panel, expandable: false })));
} else {
children.push({ sceneObject: child.state.body, expandable: false });
}
} else if (child instanceof VizPanel) {
children.push({ sceneObject: child, expandable: false });
} else if (hasEditableElement(child)) {
children.push({ sceneObject: child, expandable: true });
} else {
collectEditableElementChildren(child, children);
}
});
return children;
}