grafana/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx
Hugo Häggmark b09b49fb37
e2e: creates a separate package for selectors (#23858)
* Initial commit

* Chore: fixes after merge

* Chore: removes todos

* Chore: uncomment test

* Chore: adds missing externals to rollup config

* Refactor: selectors is master for everything

* Docs: updates Docs

* Chore: adds e2e-selectors to publish
2020-04-27 09:09:05 +02:00

248 lines
7.0 KiB
TypeScript

// Libraries
import React, { useMemo } from 'react';
import _ from 'lodash';
import { LocationUpdate } from '@grafana/runtime';
import { Button, HorizontalGroup, IconButton, stylesFactory, useTheme } from '@grafana/ui';
import { selectors } from '@grafana/e2e-selectors';
import { connect, MapDispatchToProps } from 'react-redux';
// Utils
import config from 'app/core/config';
import store from 'app/core/store';
// Store
import { updateLocation } from 'app/core/actions';
import { addPanel } from 'app/features/dashboard/state/reducers';
// Types
import { DashboardModel, PanelModel } from '../../state';
import { LS_PANEL_COPY_KEY } from 'app/core/constants';
import { css, cx, keyframes } from 'emotion';
import { GrafanaTheme } from '@grafana/data';
import tinycolor from 'tinycolor2';
export type PanelPluginInfo = { id: any; defaults: { gridPos: { w: any; h: any }; title: any } };
export interface OwnProps {
panel: PanelModel;
dashboard: DashboardModel;
}
export interface DispatchProps {
addPanel: typeof addPanel;
updateLocation: typeof updateLocation;
}
export type Props = OwnProps & DispatchProps;
const getCopiedPanelPlugins = () => {
const panels = _.chain(config.panels)
.filter({ hideFromList: false })
.map(item => item)
.value();
const copiedPanels = [];
const copiedPanelJson = store.get(LS_PANEL_COPY_KEY);
if (copiedPanelJson) {
const copiedPanel = JSON.parse(copiedPanelJson);
const pluginInfo: any = _.find(panels, { id: copiedPanel.type });
if (pluginInfo) {
const pluginCopy = _.cloneDeep(pluginInfo);
pluginCopy.name = copiedPanel.title;
pluginCopy.sort = -1;
pluginCopy.defaults = copiedPanel;
copiedPanels.push(pluginCopy);
}
}
return _.sortBy(copiedPanels, 'sort');
};
export const AddPanelWidgetUnconnected: React.FC<Props> = ({ panel, dashboard, updateLocation, addPanel }) => {
const theme = useTheme();
const onCancelAddPanel = (evt: any) => {
evt.preventDefault();
dashboard.removePanel(panel);
};
const onCreateNewPanel = () => {
const { gridPos } = panel;
const newPanel: any = {
type: 'graph',
title: 'Panel Title',
gridPos: { x: gridPos.x, y: gridPos.y, w: gridPos.w, h: gridPos.h },
};
dashboard.addPanel(newPanel);
dashboard.removePanel(panel);
const location: LocationUpdate = {
query: {
editPanel: newPanel.id,
},
partial: true,
};
updateLocation(location);
};
const onPasteCopiedPanel = (panelPluginInfo: PanelPluginInfo) => {
const { gridPos } = panel;
const newPanel: any = {
type: panelPluginInfo.id,
title: 'Panel Title',
gridPos: {
x: gridPos.x,
y: gridPos.y,
w: panelPluginInfo.defaults.gridPos.w,
h: panelPluginInfo.defaults.gridPos.h,
},
};
// apply panel template / defaults
if (panelPluginInfo.defaults) {
_.defaults(newPanel, panelPluginInfo.defaults);
newPanel.title = panelPluginInfo.defaults.title;
store.delete(LS_PANEL_COPY_KEY);
}
dashboard.addPanel(newPanel);
dashboard.removePanel(panel);
};
const onCreateNewRow = () => {
const newRow: any = {
type: 'row',
title: 'Row title',
gridPos: { x: 0, y: 0 },
};
dashboard.addPanel(newRow);
dashboard.removePanel(panel);
};
const styles = getStyles(theme);
return (
<div className={cx('panel-container', styles.wrapper)}>
<AddPanelWidgetHandle onCancel={onCancelAddPanel} />
<div className={styles.actionsWrapper}>
<AddPanelWidgetCreate onCreate={onCreateNewPanel} onPasteCopiedPanel={onPasteCopiedPanel} />
<div>
<HorizontalGroup justify="center">
<Button onClick={onCreateNewRow} variant="secondary" size="sm">
Convert to row
</Button>
</HorizontalGroup>
</div>
</div>
</div>
);
};
const mapDispatchToProps: MapDispatchToProps<DispatchProps, OwnProps> = { addPanel, updateLocation };
export const AddPanelWidget = connect(null, mapDispatchToProps)(AddPanelWidgetUnconnected);
interface AddPanelWidgetHandleProps {
onCancel: (e: React.MouseEvent<HTMLButtonElement>) => void;
}
const AddPanelWidgetHandle: React.FC<AddPanelWidgetHandleProps> = ({ onCancel }) => {
const theme = useTheme();
const styles = getAddPanelWigetHandleStyles(theme);
return (
<div className={cx(styles.handle, 'grid-drag-handle')}>
<IconButton name="times" onClick={onCancel} surface="header" className="add-panel-widget__close" />
</div>
);
};
interface AddPanelWidgetCreateProps {
onCreate: () => void;
onPasteCopiedPanel: (panelPluginInfo: PanelPluginInfo) => void;
}
const AddPanelWidgetCreate: React.FC<AddPanelWidgetCreateProps> = ({ onCreate, onPasteCopiedPanel }) => {
const copiedPanelPlugins = useMemo(() => getCopiedPanelPlugins(), []);
const theme = useTheme();
const styles = getAddPanelWidgetCreateStyles(theme);
return (
<div className={styles.wrapper}>
<HorizontalGroup>
<Button icon="plus" size="md" onClick={onCreate} aria-label={selectors.pages.AddDashboard.addNewPanel}>
Add new panel
</Button>
{copiedPanelPlugins.length === 1 && (
<Button variant="secondary" size="md" onClick={() => onPasteCopiedPanel(copiedPanelPlugins[0])}>
Paste copied panel
</Button>
)}
</HorizontalGroup>
</div>
);
};
const getStyles = stylesFactory((theme: GrafanaTheme) => {
const pulsate = keyframes`
0% {box-shadow: 0 0 0 2px ${theme.colors.bodyBg}, 0 0 0px 4px ${theme.colors.formFocusOutline};}
50% {box-shadow: 0 0 0 2px ${theme.colors.bodyBg}, 0 0 0px 4px ${tinycolor(theme.colors.formFocusOutline)
.darken(20)
.toHexString()};}
100% {box-shadow: 0 0 0 2px ${theme.colors.bodyBg}, 0 0 0px 4px ${theme.colors.formFocusOutline};}
`;
return {
wrapper: css`
overflow: hidden;
outline: 2px dotted transparent;
outline-offset: 2px;
box-shadow: 0 0 0 2px black, 0 0 0px 4px #1f60c4;
animation: ${pulsate} 2s ease infinite;
`,
actionsWrapper: css`
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
justify-content: center;
`,
};
});
const getAddPanelWigetHandleStyles = stylesFactory((theme: GrafanaTheme) => {
return {
handle: css`
position: absolute;
cursor: grab;
top: 0;
left: 0;
height: 26px;
padding: 0 ${theme.spacing.xs};
width: 100%;
display: flex;
justify-content: flex-end;
align-items: center;
transition: background-color 0.1s ease-in-out;
&:hover {
background: ${theme.colors.bg2};
}
`,
};
});
const getAddPanelWidgetCreateStyles = stylesFactory((theme: GrafanaTheme) => {
return {
wrapper: css`
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: ${theme.spacing.lg};
&:hover {
background: ${theme.colors.bg2};
}
`,
icon: css`
color: ${theme.colors.textWeak};
`,
};
});