From 59467d3cb3063d99ed256937733aae3f4722f3d9 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Wed, 1 Apr 2020 22:09:14 +0200 Subject: [PATCH] NewPanelEdit: add search, scroll and some layout to vis tab (#23253) --- .../PanelEditor/VisualizationTab.tsx | 45 +++++++- .../dashboard/panel_editor/VizTypePicker.tsx | 93 ++++++++-------- .../panel_editor/VizTypePickerPlugin.tsx | 101 ++++++++++++++---- public/sass/components/_panel_editor.scss | 56 ---------- 4 files changed, 170 insertions(+), 125 deletions(-) diff --git a/public/app/features/dashboard/components/PanelEditor/VisualizationTab.tsx b/public/app/features/dashboard/components/PanelEditor/VisualizationTab.tsx index 9122a8eccd9..834a19442fd 100644 --- a/public/app/features/dashboard/components/PanelEditor/VisualizationTab.tsx +++ b/public/app/features/dashboard/components/PanelEditor/VisualizationTab.tsx @@ -1,7 +1,7 @@ -import React, { FC } from 'react'; +import React, { FC, useState } from 'react'; import { css } from 'emotion'; 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 { StoreState } from 'app/types'; import { PanelModel } from '../../state/PanelModel'; @@ -23,6 +23,7 @@ interface DispatchProps { type Props = OwnProps & ConnectedProps & DispatchProps; export const VisualizationTabUnconnected: FC = ({ panel, plugin, changePanelPlugin }) => { + const [searchQuery, setSearchQuery] = useState(''); const theme = useTheme(); const styles = getStyles(theme); @@ -36,15 +37,51 @@ export const VisualizationTabUnconnected: FC = ({ panel, plugin, changePa return (
- {}} /> +
+ setSearchQuery(e.currentTarget.value)} + prefix={} + placeholder="Filter visualisations" + autoFocus + /> +
+
+ + {}} + /> + +
); }; const getStyles = stylesFactory((theme: GrafanaTheme) => { return { + icon: css` + color: ${theme.colors.gray33}; + `, 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}; `, }; }); diff --git a/public/app/features/dashboard/panel_editor/VizTypePicker.tsx b/public/app/features/dashboard/panel_editor/VizTypePicker.tsx index 44b25d41b7f..0a31f4b69ec 100644 --- a/public/app/features/dashboard/panel_editor/VizTypePicker.tsx +++ b/public/app/features/dashboard/panel_editor/VizTypePicker.tsx @@ -1,9 +1,10 @@ -import React, { PureComponent } from 'react'; +import React, { useMemo } from 'react'; import config from 'app/core/config'; import VizTypePickerPlugin from './VizTypePickerPlugin'; -import { EmptySearchResult } from '@grafana/ui'; -import { PanelPluginMeta } from '@grafana/data'; +import { EmptySearchResult, stylesFactory, useTheme } from '@grafana/ui'; +import { GrafanaTheme, PanelPluginMeta } from '@grafana/data'; +import { css } from 'emotion'; export interface Props { current: PanelPluginMeta; @@ -12,62 +13,68 @@ export interface Props { onClose: () => void; } -export class VizTypePicker extends PureComponent { - searchInput: HTMLElement; - pluginList = this.getPanelPlugins; - - constructor(props: Props) { - super(props); - } - - get maxSelectedIndex() { - const filteredPluginList = this.getFilteredPluginList(); - return filteredPluginList.length - 1; - } - - get getPanelPlugins(): PanelPluginMeta[] { +export const VizTypePicker: React.FC = ({ searchQuery, onTypeChange, current }) => { + const theme = useTheme(); + const styles = getStyles(theme); + const pluginsList: PanelPluginMeta[] = useMemo(() => { const allPanels = config.panels; return Object.keys(allPanels) .filter(key => allPanels[key]['hideFromList'] === false) .map(key => allPanels[key]) .sort((a: PanelPluginMeta, b: PanelPluginMeta) => a.sort - b.sort); - } + }, []); - renderVizPlugin = (plugin: PanelPluginMeta, index: number) => { - const { onTypeChange } = this.props; - const isCurrent = plugin.id === this.props.current.id; + const renderVizPlugin = (plugin: PanelPluginMeta, index: number) => { + const isCurrent = plugin.id === current.id; + const filteredPluginList = getFilteredPluginList(); + const matchesQuery = filteredPluginList.indexOf(plugin) > -1; return ( - onTypeChange(plugin)} /> + onTypeChange(plugin)} + /> ); }; - getFilteredPluginList = (): PanelPluginMeta[] => { - const { searchQuery } = this.props; + const getFilteredPluginList = (): PanelPluginMeta[] => { 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 filtered; }; - render() { - const filteredPluginList = this.getFilteredPluginList(); - const hasResults = filteredPluginList.length > 0; - return ( -
-
- {hasResults ? ( - filteredPluginList.map((plugin, index) => this.renderVizPlugin(plugin, index)) - ) : ( - Could not find anything matching your query - )} -
+ const filteredPluginList = getFilteredPluginList(); + const hasResults = filteredPluginList.length > 0; + + return ( +
+
+ {hasResults ? ( + pluginsList.map((plugin, index) => renderVizPlugin(plugin, index)) + ) : ( + Could not find anything matching your query + )}
- ); - } -} +
+ ); +}; + +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)); + `, + }; +}); diff --git a/public/app/features/dashboard/panel_editor/VizTypePickerPlugin.tsx b/public/app/features/dashboard/panel_editor/VizTypePickerPlugin.tsx index 3f13db037bc..6c3cc52983b 100644 --- a/public/app/features/dashboard/panel_editor/VizTypePickerPlugin.tsx +++ b/public/app/features/dashboard/panel_editor/VizTypePickerPlugin.tsx @@ -1,33 +1,90 @@ import React from 'react'; -import classNames from 'classnames'; -import { PanelPluginMeta } from '@grafana/data'; +import { GrafanaTheme, PanelPluginMeta } from '@grafana/data'; +import { stylesFactory, useTheme } from '@grafana/ui'; +import { css, cx } from 'emotion'; +import tinycolor from 'tinycolor2'; interface Props { isCurrent: boolean; plugin: PanelPluginMeta; onClick: () => void; + disabled: boolean; } -const VizTypePickerPlugin = React.memo( - ({ isCurrent, plugin, onClick }: Props) => { - const cssClass = classNames({ - 'viz-picker__item': true, - 'viz-picker__item--current': isCurrent, - }); +const VizTypePickerPlugin: React.FC = ({ isCurrent, plugin, onClick, disabled }) => { + const theme = useTheme(); + const styles = getStyles(theme); + const cssClass = cx({ + [styles.item]: true, + [styles.current]: isCurrent, + [styles.disabled]: disabled, + }); - return ( -
-
{plugin.name}
- -
- ); - }, - (prevProps, nextProps) => { - if (prevProps.isCurrent === nextProps.isCurrent) { - return true; - } - return false; - } -); + return ( +
{} : onClick} title={plugin.name}> +
{plugin.name}
+ +
+ ); +}; + +const getStyles = stylesFactory((theme: GrafanaTheme) => { + return { + item: css` + 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; diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index e53303d2841..9aa97d2abe2 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -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 { z-index: 2; display: flex;