Files
grafana/public/app/features/dashboard-scene/scene/AddLibraryPanelWidget.tsx
Dominik Prokop a4ff2abd9d PanelGridItemRepeater: Refactor (#84710)
* wip

* serialisation tests updates

* VizPanelManager test updates

* ests updates

* Making it green

* Use DashboardGridItem instead of SceneGridItem

* Cleanup tests that unnecessarily depend on dashboard grid items

* Fix row repeater behavior test

* Update public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx

Co-authored-by: Oscar Kilhed <oscar.kilhed@grafana.com>

* Fix unnecessary library panel changes detection

* Fix test

---------

Co-authored-by: Oscar Kilhed <oscar.kilhed@grafana.com>
2024-03-21 14:38:00 +01:00

164 lines
5.0 KiB
TypeScript

import { css, cx, keyframes } from '@emotion/css';
import React from 'react';
import tinycolor from 'tinycolor2';
import { GrafanaTheme2 } from '@grafana/data';
import { SceneComponentProps, SceneGridLayout, SceneGridRow, SceneObjectBase, SceneObjectState } from '@grafana/scenes';
import { LibraryPanel } from '@grafana/schema';
import { IconButton, useStyles2 } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import {
LibraryPanelsSearch,
LibraryPanelsSearchVariant,
} from 'app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch';
import { getDashboardSceneFor } from '../utils/utils';
import { DashboardGridItem } from './DashboardGridItem';
import { DashboardScene } from './DashboardScene';
import { LibraryVizPanel } from './LibraryVizPanel';
export interface AddLibraryPanelWidgetState extends SceneObjectState {
key: string;
}
export class AddLibraryPanelWidget extends SceneObjectBase<AddLibraryPanelWidgetState> {
public constructor(state: AddLibraryPanelWidgetState) {
super({
...state,
});
}
private get _dashboard(): DashboardScene {
return getDashboardSceneFor(this);
}
public getDashboard(): DashboardScene {
return this._dashboard;
}
public onCancelAddPanel = (evt: React.MouseEvent<HTMLButtonElement>) => {
evt.preventDefault();
if (!(this._dashboard.state.body instanceof SceneGridLayout)) {
throw new Error('Trying to remove the library panel widget in a layout that is not SceneGridLayout');
}
const sceneGridLayout = this._dashboard.state.body;
const children = [];
for (const child of sceneGridLayout.state.children) {
if (child.state.key !== this.parent?.state.key) {
children.push(child);
}
if (child instanceof SceneGridRow) {
const rowChildren = [];
for (const rowChild of child.state.children) {
if (rowChild instanceof DashboardGridItem && rowChild.state.key !== this.parent?.state.key) {
rowChildren.push(rowChild);
}
}
child.setState({ children: rowChildren });
}
}
sceneGridLayout.setState({ children });
};
public onAddLibraryPanel = (panelInfo: LibraryPanel) => {
if (!(this._dashboard.state.body instanceof SceneGridLayout)) {
throw new Error('Trying to add a library panel in a layout that is not SceneGridLayout');
}
const body = new LibraryVizPanel({
title: 'Panel Title',
uid: panelInfo.uid,
name: panelInfo.name,
panelKey: this.state.key,
});
if (this.parent instanceof DashboardGridItem) {
this.parent.setState({ body });
}
};
static Component = ({ model }: SceneComponentProps<AddLibraryPanelWidget>) => {
const dashboard = model.getDashboard();
const styles = useStyles2(getStyles);
return (
<div className={styles.wrapper}>
<div className={cx('panel-container', styles.callToAction)}>
<div className={cx(styles.headerRow, `grid-drag-handle-${dashboard.state.body.state.key}`)}>
<span>
<Trans i18nKey="library-panel.add-widget.title">Add panel from panel library</Trans>
</span>
<div className="flex-grow-1" />
<IconButton
aria-label="Close 'Add Panel' widget"
name="times"
onClick={model.onCancelAddPanel}
tooltip="Close widget"
/>
</div>
<LibraryPanelsSearch
onClick={model.onAddLibraryPanel}
variant={LibraryPanelsSearchVariant.Tight}
showPanelFilter
/>
</div>
</div>
);
};
}
const getStyles = (theme: GrafanaTheme2) => {
const pulsate = keyframes({
'0%': {
boxShadow: `0 0 0 2px ${theme.colors.background.canvas}, 0 0 0px 4px ${theme.colors.primary.main}`,
},
'50%': {
boxShadow: `0 0 0 2px ${theme.components.dashboard.background}, 0 0 0px 4px ${tinycolor(theme.colors.primary.main)
.darken(20)
.toHexString()}`,
},
'100%': {
boxShadow: `0 0 0 2px ${theme.components.dashboard.background}, 0 0 0px 4px ${theme.colors.primary.main}`,
},
});
return {
// wrapper is used to make sure box-shadow animation isn't cut off in dashboard page
wrapper: css({
height: '100%',
paddingTop: `${theme.spacing(0.5)}`,
}),
headerRow: css({
display: 'flex',
alignItems: 'center',
height: '38px',
flexShrink: 0,
width: '100%',
fontSize: theme.typography.fontSize,
fontWeight: theme.typography.fontWeightMedium,
paddingLeft: `${theme.spacing(1)}`,
transition: 'background-color 0.1s ease-in-out',
cursor: 'move',
'&:hover': {
background: `${theme.colors.background.secondary}`,
},
}),
callToAction: css({
overflow: 'hidden',
outline: '2px dotted transparent',
outlineOffset: '2px',
boxShadow: '0 0 0 2px black, 0 0 0px 4px #1f60c4',
animation: `${pulsate} 2s ease infinite`,
}),
};
};