Commit 59467d3c by Dominik Prokop Committed by GitHub

NewPanelEdit: add search, scroll and some layout to vis tab (#23253)

parent 540d1d9b
import React, { FC } from 'react'; import React, { FC, useState } from 'react';
import { css } from 'emotion'; import { css } from 'emotion';
import { GrafanaTheme, PanelPlugin, PanelPluginMeta } from '@grafana/data'; import { GrafanaTheme, PanelPlugin, PanelPluginMeta } from '@grafana/data';
import { useTheme, stylesFactory } from '@grafana/ui'; import { CustomScrollbar, useTheme, stylesFactory, Forms, Icon } from '@grafana/ui';
import { changePanelPlugin } from '../../state/actions'; import { changePanelPlugin } from '../../state/actions';
import { StoreState } from 'app/types'; import { StoreState } from 'app/types';
import { PanelModel } from '../../state/PanelModel'; import { PanelModel } from '../../state/PanelModel';
...@@ -23,6 +23,7 @@ interface DispatchProps { ...@@ -23,6 +23,7 @@ interface DispatchProps {
type Props = OwnProps & ConnectedProps & DispatchProps; type Props = OwnProps & ConnectedProps & DispatchProps;
export const VisualizationTabUnconnected: FC<Props> = ({ panel, plugin, changePanelPlugin }) => { export const VisualizationTabUnconnected: FC<Props> = ({ panel, plugin, changePanelPlugin }) => {
const [searchQuery, setSearchQuery] = useState('');
const theme = useTheme(); const theme = useTheme();
const styles = getStyles(theme); const styles = getStyles(theme);
...@@ -36,15 +37,51 @@ export const VisualizationTabUnconnected: FC<Props> = ({ panel, plugin, changePa ...@@ -36,15 +37,51 @@ export const VisualizationTabUnconnected: FC<Props> = ({ panel, plugin, changePa
return ( return (
<div className={styles.wrapper}> <div className={styles.wrapper}>
<VizTypePicker current={plugin.meta} onTypeChange={onPluginTypeChange} searchQuery={''} onClose={() => {}} /> <div className={styles.search}>
<Forms.Input
value={searchQuery}
onChange={e => setSearchQuery(e.currentTarget.value)}
prefix={<Icon name="filter" className={styles.icon} />}
placeholder="Filter visualisations"
autoFocus
/>
</div>
<div className={styles.visList}>
<CustomScrollbar>
<VizTypePicker
current={plugin.meta}
onTypeChange={onPluginTypeChange}
searchQuery={searchQuery}
onClose={() => {}}
/>
</CustomScrollbar>
</div>
</div> </div>
); );
}; };
const getStyles = stylesFactory((theme: GrafanaTheme) => { const getStyles = stylesFactory((theme: GrafanaTheme) => {
return { return {
icon: css`
color: ${theme.colors.gray33};
`,
wrapper: css` wrapper: css`
padding: ${theme.spacing.md}; display: flex;
flex-direction: column;
flex-grow: 1;
max-height: 100%;
`,
search: css`
padding: ${theme.spacing.sm} ${theme.spacing.md};
flex-grow: 0;
flex-shrink: 1;
margin-bottom: ${theme.spacing.sm};
`,
visList: css`
flex-grow: 1;
height: 100%;
overflow: hidden;
padding-left: ${theme.spacing.md};
`, `,
}; };
}); });
......
import React, { PureComponent } from 'react'; import React, { useMemo } from 'react';
import config from 'app/core/config'; import config from 'app/core/config';
import VizTypePickerPlugin from './VizTypePickerPlugin'; import VizTypePickerPlugin from './VizTypePickerPlugin';
import { EmptySearchResult } from '@grafana/ui'; import { EmptySearchResult, stylesFactory, useTheme } from '@grafana/ui';
import { PanelPluginMeta } from '@grafana/data'; import { GrafanaTheme, PanelPluginMeta } from '@grafana/data';
import { css } from 'emotion';
export interface Props { export interface Props {
current: PanelPluginMeta; current: PanelPluginMeta;
...@@ -12,62 +13,68 @@ export interface Props { ...@@ -12,62 +13,68 @@ export interface Props {
onClose: () => void; onClose: () => void;
} }
export class VizTypePicker extends PureComponent<Props> { export const VizTypePicker: React.FC<Props> = ({ searchQuery, onTypeChange, current }) => {
searchInput: HTMLElement; const theme = useTheme();
pluginList = this.getPanelPlugins; const styles = getStyles(theme);
const pluginsList: PanelPluginMeta[] = useMemo(() => {
constructor(props: Props) {
super(props);
}
get maxSelectedIndex() {
const filteredPluginList = this.getFilteredPluginList();
return filteredPluginList.length - 1;
}
get getPanelPlugins(): PanelPluginMeta[] {
const allPanels = config.panels; const allPanels = config.panels;
return Object.keys(allPanels) return Object.keys(allPanels)
.filter(key => allPanels[key]['hideFromList'] === false) .filter(key => allPanels[key]['hideFromList'] === false)
.map(key => allPanels[key]) .map(key => allPanels[key])
.sort((a: PanelPluginMeta, b: PanelPluginMeta) => a.sort - b.sort); .sort((a: PanelPluginMeta, b: PanelPluginMeta) => a.sort - b.sort);
} }, []);
renderVizPlugin = (plugin: PanelPluginMeta, index: number) => { const renderVizPlugin = (plugin: PanelPluginMeta, index: number) => {
const { onTypeChange } = this.props; const isCurrent = plugin.id === current.id;
const isCurrent = plugin.id === this.props.current.id; const filteredPluginList = getFilteredPluginList();
const matchesQuery = filteredPluginList.indexOf(plugin) > -1;
return ( return (
<VizTypePickerPlugin key={plugin.id} isCurrent={isCurrent} plugin={plugin} onClick={() => onTypeChange(plugin)} /> <VizTypePickerPlugin
disabled={!matchesQuery}
key={plugin.id}
isCurrent={isCurrent}
plugin={plugin}
onClick={() => onTypeChange(plugin)}
/>
); );
}; };
getFilteredPluginList = (): PanelPluginMeta[] => { const getFilteredPluginList = (): PanelPluginMeta[] => {
const { searchQuery } = this.props;
const regex = new RegExp(searchQuery, 'i'); const regex = new RegExp(searchQuery, 'i');
const pluginList = this.pluginList;
const filtered = pluginList.filter(item => { return pluginsList.filter(item => {
return regex.test(item.name); return regex.test(item.name);
}); });
return filtered;
}; };
render() { const filteredPluginList = getFilteredPluginList();
const filteredPluginList = this.getFilteredPluginList();
const hasResults = filteredPluginList.length > 0; const hasResults = filteredPluginList.length > 0;
return ( return (
<div className="viz-picker"> <div className={styles.wrapper}>
<div className="viz-picker-list"> <div className={styles.grid}>
{hasResults ? ( {hasResults ? (
filteredPluginList.map((plugin, index) => this.renderVizPlugin(plugin, index)) pluginsList.map((plugin, index) => renderVizPlugin(plugin, index))
) : ( ) : (
<EmptySearchResult>Could not find anything matching your query</EmptySearchResult> <EmptySearchResult>Could not find anything matching your query</EmptySearchResult>
)} )}
</div> </div>
</div> </div>
); );
} };
}
const getStyles = stylesFactory((theme: GrafanaTheme) => {
return {
wrapper: css`
padding-right: ${theme.spacing.md};
`,
grid: css`
max-width: 100%;
display: grid;
grid-gap: ${theme.spacing.md};
grid-template-columns: repeat(auto-fit, minmax(145px, 1fr));
`,
};
});
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import { GrafanaTheme, PanelPluginMeta } from '@grafana/data';
import { PanelPluginMeta } from '@grafana/data'; import { stylesFactory, useTheme } from '@grafana/ui';
import { css, cx } from 'emotion';
import tinycolor from 'tinycolor2';
interface Props { interface Props {
isCurrent: boolean; isCurrent: boolean;
plugin: PanelPluginMeta; plugin: PanelPluginMeta;
onClick: () => void; onClick: () => void;
disabled: boolean;
} }
const VizTypePickerPlugin = React.memo( const VizTypePickerPlugin: React.FC<Props> = ({ isCurrent, plugin, onClick, disabled }) => {
({ isCurrent, plugin, onClick }: Props) => { const theme = useTheme();
const cssClass = classNames({ const styles = getStyles(theme);
'viz-picker__item': true, const cssClass = cx({
'viz-picker__item--current': isCurrent, [styles.item]: true,
[styles.current]: isCurrent,
[styles.disabled]: disabled,
}); });
return ( return (
<div className={cssClass} onClick={onClick} title={plugin.name}> <div className={cssClass} onClick={disabled ? () => {} : onClick} title={plugin.name}>
<div className="viz-picker__item-name">{plugin.name}</div> <div className={styles.name}>{plugin.name}</div>
<img className="viz-picker__item-img" src={plugin.info.logos.small} /> <img className={styles.img} src={plugin.info.logos.small} />
</div> </div>
); );
}, };
(prevProps, nextProps) => {
if (prevProps.isCurrent === nextProps.isCurrent) { const getStyles = stylesFactory((theme: GrafanaTheme) => {
return true; return {
} item: css`
return false; background: ${theme.isLight ? theme.colors.white : theme.colors.gray05};
border: 1px solid ${theme.isLight ? theme.colors.gray3 : theme.colors.dark10};
border-radius: 3px;
height: 100px;
width: 100%;
max-width: 200px;
flex-shrink: 0;
flex-direction: column;
text-align: center;
cursor: pointer;
display: flex;
margin-right: 10px;
margin-bottom: 10px;
align-items: center;
justify-content: center;
padding-bottom: 6px;
transition: transform 1 ease;
&:hover {
box-shadow: 0 0 4px ${theme.colors.blueLight};
background: ${theme.isLight
? tinycolor(theme.colors.blueBase)
.lighten(45)
.toHexString()
: tinycolor(theme.colors.blueBase)
.darken(46)
.toHexString()};
border: 1px solid ${theme.colors.blueLight};
} }
); `,
current: css`
box-shadow: 0 0 6px ${theme.colors.orange} !important;
border: 1px solid ${theme.colors.orange} !important;
background: ${theme.isLight ? theme.colors.white : theme.colors.gray05};
`,
disabled: css`
opacity: 0.2;
filter: grayscale(1);
cursor: pointer;
`,
name: css`
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
font-size: ${theme.typography.size.sm};
display: flex;
flex-direction: column;
align-self: center;
height: 23px;
font-weight: ${theme.typography.weight.semibold};
`,
img: css`
height: 55px;
`,
};
});
export default VizTypePickerPlugin; export default VizTypePickerPlugin;
...@@ -121,62 +121,6 @@ ...@@ -121,62 +121,6 @@
} }
} }
.viz-picker {
position: relative;
}
.viz-picker-list {
display: flex;
flex-wrap: wrap;
}
.viz-picker__item {
background: $panel-editor-viz-item-bg;
border: $panel-editor-viz-item-border;
border-radius: 3px;
height: 100px;
width: 145px;
flex-shrink: 0;
flex-direction: column;
text-align: center;
cursor: pointer;
display: flex;
margin-right: 10px;
margin-bottom: 10px;
align-items: center;
justify-content: center;
padding-bottom: 6px;
transition: transform 1 ease;
&:hover {
box-shadow: $panel-editor-viz-item-shadow-hover;
background: $panel-editor-viz-item-bg-hover;
border: $panel-editor-viz-item-border-hover;
}
&--current {
box-shadow: 0 0 6px $orange !important;
border: 1px solid $orange !important;
background: $panel-editor-viz-item-bg !important;
}
}
.viz-picker__item-name {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
font-size: $font-size-sm;
display: flex;
flex-direction: column;
align-self: center;
height: 23px;
font-weight: $font-weight-semi-bold;
}
.viz-picker__item-img {
height: 55px;
}
.panel-editor-tabs { .panel-editor-tabs {
z-index: 2; z-index: 2;
display: flex; display: flex;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment