Commit 3ec88a01 by Ryan McKinley Committed by GitHub

NewPanelEdit: Move field defaults to own tab, and merge general and panel…

NewPanelEdit: Move field defaults to own tab, and merge general and panel options to first options tab (#23350)

* use dropdown

* keep padding the same

* keep padding the same

* Refactoring and moving to components

* Updated

* Alt names

Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
parent 3fae28be
......@@ -5,10 +5,11 @@ import { useTheme, Icon, stylesFactory } from '@grafana/ui';
interface Props {
title: string;
defaultToClosed?: boolean;
}
export const OptionsGroup: FC<Props> = ({ title, children }) => {
const [isExpanded, toggleExpand] = useState(true);
export const OptionsGroup: FC<Props> = ({ title, children, defaultToClosed }) => {
const [isExpanded, toggleExpand] = useState(defaultToClosed ? false : true);
const theme = useTheme();
const styles = getStyles(theme);
......
import React, { useCallback, useState, CSSProperties } from 'react';
import Transition from 'react-transition-group/Transition';
import { FieldConfigSource, GrafanaTheme, PanelData, PanelPlugin } from '@grafana/data';
import { FieldConfigSource, GrafanaTheme, PanelData, PanelPlugin, SelectableValue } from '@grafana/data';
import { DashboardModel, PanelModel } from '../../state';
import {
CustomScrollbar,
......@@ -8,22 +8,22 @@ import {
Tab,
TabContent,
TabsBar,
Select,
useTheme,
Container,
Icon,
Input,
} from '@grafana/ui';
import { DefaultFieldConfigEditor, OverrideFieldConfigEditor } from './FieldConfigEditor';
import { AngularPanelOptions } from './AngularPanelOptions';
import { css } from 'emotion';
import { GeneralPanelOptions } from './GeneralPanelOptions';
import { PanelOptionsEditor } from './PanelOptionsEditor';
import { PanelOptionsTab } from './PanelOptionsTab';
import { DashNavButton } from 'app/features/dashboard/components/DashNav/DashNavButton';
export const OptionsPaneContent: React.FC<{
plugin?: PanelPlugin;
plugin: PanelPlugin;
panel: PanelModel;
data: PanelData;
width: number;
dashboard: DashboardModel;
onClose: () => void;
onFieldConfigsChange: (config: FieldConfigSource) => void;
......@@ -33,6 +33,7 @@ export const OptionsPaneContent: React.FC<{
plugin,
panel,
data,
width,
onFieldConfigsChange,
onPanelOptionsChanged,
onPanelConfigChange,
......@@ -41,7 +42,7 @@ export const OptionsPaneContent: React.FC<{
}) => {
const theme = useTheme();
const styles = getStyles(theme);
const [activeTab, setActiveTab] = useState('defaults');
const [activeTab, setActiveTab] = useState('options');
const [isSearching, setSearchMode] = useState(false);
const renderFieldOptions = useCallback(
......@@ -54,7 +55,6 @@ export const OptionsPaneContent: React.FC<{
return (
<Container padding="md">
{renderCustomPanelSettings(plugin)}
<DefaultFieldConfigEditor
config={fieldConfig}
plugin={plugin}
......@@ -88,49 +88,54 @@ export const OptionsPaneContent: React.FC<{
[data, plugin, panel, onFieldConfigsChange]
);
const renderCustomPanelSettings = useCallback(
(plugin: PanelPlugin) => {
const editors: JSX.Element[] = [];
if (plugin.editor && panel) {
editors.push(
<div className={styles.legacyOptions} key="plugin custom panel settings">
<plugin.editor
data={data}
options={panel.getOptions()}
onOptionsChange={onPanelOptionsChanged}
fieldConfig={panel.getFieldConfig()}
onFieldConfigChange={onFieldConfigsChange}
return (
<div className={styles.panelOptionsPane}>
{plugin && (
<div className={styles.wrapper}>
<TabsBar className={styles.tabsBar}>
<TabsBarContent
width={width}
isSearching={isSearching}
styles={styles}
activeTab={activeTab}
onClose={onClose}
setSearchMode={setSearchMode}
setActiveTab={setActiveTab}
/>
</div>
);
}
// When editor created declaratively
if (plugin.optionEditors && panel) {
editors.push(
<PanelOptionsEditor
key="panel options"
options={panel.getOptions()}
onChange={onPanelOptionsChanged}
plugin={plugin}
/>
);
}
if (editors.length > 0) {
return editors;
}
return (
<div className={styles.legacyOptions}>
<AngularPanelOptions panel={panel} dashboard={dashboard} plugin={plugin} />
</TabsBar>
<TabContent className={styles.tabContent}>
<CustomScrollbar>
{activeTab === 'options' && (
<PanelOptionsTab
panel={panel}
plugin={plugin}
dashboard={dashboard}
data={data}
onPanelConfigChange={onPanelConfigChange}
onFieldConfigsChange={onFieldConfigsChange}
onPanelOptionsChanged={onPanelOptionsChanged}
/>
)}
{activeTab === 'defaults' && renderFieldOptions(plugin)}
{activeTab === 'overrides' && renderFieldOverrideOptions(plugin)}
</CustomScrollbar>
</TabContent>
</div>
);
},
[data, plugin, panel, onFieldConfigsChange]
)}
</div>
);
};
const renderSearchInput = useCallback(() => {
export const TabsBarContent: React.FC<{
width: number;
isSearching: boolean;
activeTab: string;
styles: OptionsPaneStyles;
onClose: () => void;
setSearchMode: (mode: boolean) => void;
setActiveTab: (tab: string) => void;
}> = ({ width, isSearching, activeTab, onClose, setSearchMode, setActiveTab, styles }) => {
if (isSearching) {
const defaultStyles = {
transition: 'width 50ms ease-in-out',
width: '50%',
......@@ -163,56 +168,66 @@ export const OptionsPaneContent: React.FC<{
}}
</Transition>
);
}, []);
}
return (
<div className={styles.panelOptionsPane}>
{plugin && (
<div className={styles.wrapper}>
<TabsBar className={styles.tabsBar}>
{isSearching && renderSearchInput()}
{!isSearching && (
<>
<Tab label="Options" active={activeTab === 'defaults'} onChangeTab={() => setActiveTab('defaults')} />
<Tab
label="Overrides"
active={activeTab === 'overrides'}
onChangeTab={() => setActiveTab('overrides')}
/>
<Tab label="General" active={activeTab === 'panel'} onChangeTab={() => setActiveTab('panel')} />
<div className="flex-grow-1" />
<div className={styles.tabsButton}>
<DashNavButton
icon="fa fa-search"
tooltip="Search all options"
classSuffix="search-options"
onClick={() => setSearchMode(true)}
/>
</div>
<div className={styles.tabsButton}>
<DashNavButton
icon="fa fa-chevron-right"
tooltip="Close options pane"
classSuffix="close-options"
onClick={onClose}
/>
</div>
</>
)}
</TabsBar>
<TabContent className={styles.tabContent}>
<CustomScrollbar>
{activeTab === 'defaults' && renderFieldOptions(plugin)}
{activeTab === 'overrides' && renderFieldOverrideOptions(plugin)}
{activeTab === 'panel' && <GeneralPanelOptions panel={panel} onPanelConfigChange={onPanelConfigChange} />}
</CustomScrollbar>
</TabContent>
<>
{width < 377 ? (
<div className="flex-grow-1">
<Select
options={tabSelections}
value={tabSelections.find(v => v.value === activeTab)}
onChange={v => {
setActiveTab(v.value);
}}
/>
</div>
) : (
<>
{tabSelections.map(item => {
return (
<Tab label={item.label} active={activeTab === item.value} onChangeTab={() => setActiveTab(item.value)} />
);
})}
<div className="flex-grow-1" />
</>
)}
</div>
<div className={styles.tabsButton}>
<DashNavButton
icon="fa fa-search"
tooltip="Search all options"
classSuffix="search-options"
onClick={() => setSearchMode(true)}
/>
</div>
<div className={styles.tabsButton}>
<DashNavButton
icon="fa fa-chevron-right"
tooltip="Close options pane"
classSuffix="close-options"
onClick={onClose}
/>
</div>
</>
);
};
const tabSelections: Array<SelectableValue<string>> = [
{
label: 'Panel',
value: 'options',
},
{
label: 'Data',
value: 'defaults',
},
{
label: 'Overrides',
value: 'overrides',
},
];
const getStyles = stylesFactory((theme: GrafanaTheme) => {
return {
wrapper: css`
......@@ -274,3 +289,5 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
`,
};
});
type OptionsPaneStyles = ReturnType<typeof getStyles>;
......@@ -30,11 +30,6 @@ import { VariableModel } from 'app/features/templating/types';
import { getVariables } from 'app/features/variables/state/selectors';
import { SubMenuItems } from 'app/features/dashboard/components/SubMenu/SubMenuItems';
enum Pane {
Right,
Top,
}
interface OwnProps {
dashboard: DashboardModel;
sourcePanel: PanelModel;
......@@ -255,7 +250,7 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
}
renderOptionsPane() {
const { plugin, dashboard, data, panel } = this.props;
const { plugin, dashboard, data, panel, uiState } = this.props;
if (!plugin) {
return <div />;
......@@ -267,6 +262,7 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
dashboard={dashboard}
data={data}
panel={panel}
width={uiState.rightPaneSize as number}
onClose={this.onTogglePanelOptions}
onFieldConfigsChange={this.onFieldConfigChange}
onPanelOptionsChanged={this.onPanelOptionsChanged}
......@@ -342,6 +338,11 @@ const mapDispatchToProps: MapDispatchToProps<DispatchProps, OwnProps> = {
export const PanelEditor = connect(mapStateToProps, mapDispatchToProps)(PanelEditorUnconnected);
enum Pane {
Right,
Top,
}
/*
* Styles
*/
......
import React, { FC, useMemo } from 'react';
import { PanelModel } from '../../state';
import { SelectableValue } from '@grafana/data';
import { PanelModel, DashboardModel } from '../../state';
import { SelectableValue, PanelPlugin, FieldConfigSource, PanelData } from '@grafana/data';
import { Forms, Select, DataLinksInlineEditor, Input } from '@grafana/ui';
import { OptionsGroup } from './OptionsGroup';
import { getPanelLinksVariableSuggestions } from '../../../panel/panellinks/link_srv';
import { getVariables } from '../../../variables/state/selectors';
import { PanelOptionsEditor } from './PanelOptionsEditor';
import { AngularPanelOptions } from '../../panel_editor/AngularPanelOptions';
export const GeneralPanelOptions: FC<{
interface Props {
panel: PanelModel;
plugin: PanelPlugin;
data: PanelData;
dashboard: DashboardModel;
onPanelConfigChange: (configKey: string, value: any) => void;
}> = ({ panel, onPanelConfigChange }) => {
onPanelOptionsChanged: (options: any) => void;
onFieldConfigsChange: (config: FieldConfigSource) => void;
}
export const PanelOptionsTab: FC<Props> = ({
panel,
plugin,
data,
dashboard,
onPanelConfigChange,
onPanelOptionsChanged,
onFieldConfigsChange,
}) => {
const elements: JSX.Element[] = [];
const linkVariablesSuggestions = useMemo(() => getPanelLinksVariableSuggestions(), []);
const variableOptions = getVariableOptions();
......@@ -20,26 +38,66 @@ export const GeneralPanelOptions: FC<{
const maxPerRowOptions = [2, 3, 4, 6, 8, 12].map(value => ({ label: value.toString(), value }));
return (
<div>
<OptionsGroup title="Panel settings">
<Forms.Field label="Panel title">
<Input defaultValue={panel.title} onBlur={e => onPanelConfigChange('title', e.currentTarget.value)} />
</Forms.Field>
<Forms.Field label="Description" description="Panel description supports markdown and links.">
<Forms.TextArea
defaultValue={panel.description}
onBlur={e => onPanelConfigChange('description', e.currentTarget.value)}
/>
</Forms.Field>
<Forms.Field label="Transparent" description="Display panel without background.">
<Forms.Switch
value={panel.transparent}
onChange={e => onPanelConfigChange('transparent', e.currentTarget.checked)}
/>
</Forms.Field>
// Fist common panel settings Title, description
elements.push(
<OptionsGroup title="Basic" key="basic settings">
<Forms.Field label="Panel title">
<Input defaultValue={panel.title} onBlur={e => onPanelConfigChange('title', e.currentTarget.value)} />
</Forms.Field>
<Forms.Field label="Description" description="Panel description supports markdown and links.">
<Forms.TextArea
defaultValue={panel.description}
onBlur={e => onPanelConfigChange('description', e.currentTarget.value)}
/>
</Forms.Field>
<Forms.Field label="Transparent" description="Display panel without background.">
<Forms.Switch
value={panel.transparent}
onChange={e => onPanelConfigChange('transparent', e.currentTarget.checked)}
/>
</Forms.Field>
</OptionsGroup>
);
// Old legacy react editor
if (plugin.editor && panel && !plugin.optionEditors) {
elements.push(
<OptionsGroup title="Display" key="legacy react editor">
<plugin.editor
data={data}
options={panel.getOptions()}
onOptionsChange={onPanelOptionsChanged}
fieldConfig={panel.getFieldConfig()}
onFieldConfigChange={onFieldConfigsChange}
/>
</OptionsGroup>
);
}
if (plugin.optionEditors && panel) {
elements.push(
<OptionsGroup title="Display" key="panel plugin options">
<PanelOptionsEditor
key="panel options"
options={panel.getOptions()}
onChange={onPanelOptionsChanged}
plugin={plugin}
/>
</OptionsGroup>
<OptionsGroup title="Panel links">
);
}
if (plugin.angularPanelCtrl) {
elements.push(
<OptionsGroup title="Display" key="angular plugin editor">
<AngularPanelOptions panel={panel} dashboard={dashboard} plugin={plugin} />
</OptionsGroup>
);
}
elements.push(
<>
<OptionsGroup title="Panel links" key="panel links" defaultToClosed={true}>
<DataLinksInlineEditor
links={panel.links}
onChange={links => onPanelConfigChange('links', links)}
......@@ -47,7 +105,7 @@ export const GeneralPanelOptions: FC<{
data={[]}
/>
</OptionsGroup>
<OptionsGroup title="Panel repeats">
<OptionsGroup title="Panel repeats" key="panel repeats" defaultToClosed={true}>
<Forms.Field
label="Repeat by variable"
description="Repeat this panel for each value in the selected variable.
......@@ -80,8 +138,10 @@ export const GeneralPanelOptions: FC<{
</Forms.Field>
)}
</OptionsGroup>
</div>
</>
);
return <>{elements}</>;
};
function getVariableOptions(): Array<SelectableValue<string>> {
......
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