mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
FieldOverrides: FieldOverrides UI (#22187)
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { IconType } from '../Icon/types';
|
import { IconType } from '../Icon/types';
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue } from '@grafana/data';
|
||||||
import { Button } from '../Forms/Button';
|
import { Button, ButtonVariant } from '../Forms/Button';
|
||||||
import { Select } from '../Forms/Select/Select';
|
import { Select } from '../Forms/Select/Select';
|
||||||
|
|
||||||
interface ValuePickerProps<T> {
|
interface ValuePickerProps<T> {
|
||||||
@@ -12,15 +12,16 @@ interface ValuePickerProps<T> {
|
|||||||
/** ValuePicker options */
|
/** ValuePicker options */
|
||||||
options: Array<SelectableValue<T>>;
|
options: Array<SelectableValue<T>>;
|
||||||
onChange: (value: SelectableValue<T>) => void;
|
onChange: (value: SelectableValue<T>) => void;
|
||||||
|
variant?: ButtonVariant;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ValuePicker<T>({ label, icon, options, onChange }: ValuePickerProps<T>) {
|
export function ValuePicker<T>({ label, icon, options, onChange, variant }: ValuePickerProps<T>) {
|
||||||
const [isPicking, setIsPicking] = useState(false);
|
const [isPicking, setIsPicking] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!isPicking && (
|
{!isPicking && (
|
||||||
<Button icon={`fa fa-${icon || 'plus'}`} onClick={() => setIsPicking(true)}>
|
<Button onClick={() => setIsPicking(true)} variant={variant} icon={`fa fa-${icon}`}>
|
||||||
{label}
|
{label}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { DynamicConfigValue, FieldConfigEditorRegistry, FieldOverrideContext, GrafanaTheme } from '@grafana/data';
|
||||||
|
import { selectThemeVariant, stylesFactory, useTheme } from '@grafana/ui';
|
||||||
|
|
||||||
|
import { OverrideHeader } from './OverrideHeader';
|
||||||
|
import { css } from 'emotion';
|
||||||
|
interface DynamicConfigValueEditorProps {
|
||||||
|
property: DynamicConfigValue;
|
||||||
|
editorsRegistry: FieldConfigEditorRegistry;
|
||||||
|
onChange: (value: DynamicConfigValue) => void;
|
||||||
|
context: FieldOverrideContext;
|
||||||
|
onRemove: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DynamicConfigValueEditor: React.FC<DynamicConfigValueEditorProps> = ({
|
||||||
|
property,
|
||||||
|
context,
|
||||||
|
editorsRegistry,
|
||||||
|
onChange,
|
||||||
|
onRemove,
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const styles = getStyles(theme);
|
||||||
|
const item = editorsRegistry?.getIfExists(property.prop);
|
||||||
|
return (
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
<OverrideHeader onRemove={onRemove} title={item.name} description={item.description} />
|
||||||
|
<div className={styles.property}>
|
||||||
|
<item.override
|
||||||
|
value={property.value}
|
||||||
|
onChange={value => {
|
||||||
|
onChange(value);
|
||||||
|
}}
|
||||||
|
item={item}
|
||||||
|
context={context}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
|
const borderColor = selectThemeVariant(
|
||||||
|
{
|
||||||
|
light: theme.colors.gray85,
|
||||||
|
dark: theme.colors.dark9,
|
||||||
|
},
|
||||||
|
theme.type
|
||||||
|
);
|
||||||
|
|
||||||
|
const highlightColor = selectThemeVariant(
|
||||||
|
{
|
||||||
|
light: theme.colors.blueLight,
|
||||||
|
dark: theme.colors.blueShade,
|
||||||
|
},
|
||||||
|
theme.type
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
wrapper: css`
|
||||||
|
border-top: 1px dashed ${borderColor};
|
||||||
|
position: relative;
|
||||||
|
&:hover {
|
||||||
|
&:before {
|
||||||
|
background: ${highlightColor};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
left: -1px;
|
||||||
|
width: 2px;
|
||||||
|
height: 100%;
|
||||||
|
transition: background 0.5s cubic-bezier(0.19, 1, 0.22, 1);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
property: css`
|
||||||
|
padding: ${theme.spacing.xs} ${theme.spacing.sm} ${theme.spacing.sm} ${theme.spacing.sm};
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -5,13 +5,14 @@ import {
|
|||||||
FieldConfigSource,
|
FieldConfigSource,
|
||||||
DataFrame,
|
DataFrame,
|
||||||
FieldPropertyEditorItem,
|
FieldPropertyEditorItem,
|
||||||
DynamicConfigValue,
|
|
||||||
VariableSuggestionsScope,
|
VariableSuggestionsScope,
|
||||||
standardFieldConfigEditorRegistry,
|
standardFieldConfigEditorRegistry,
|
||||||
|
SelectableValue,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { Forms, fieldMatchersUI, ValuePicker } from '@grafana/ui';
|
import { Forms, fieldMatchersUI, ValuePicker } from '@grafana/ui';
|
||||||
import { getDataLinksVariableSuggestions } from '../../../panel/panellinks/link_srv';
|
import { getDataLinksVariableSuggestions } from '../../../panel/panellinks/link_srv';
|
||||||
import { OptionsGroup } from './OptionsGroup';
|
import { OptionsGroup } from './OptionsGroup';
|
||||||
|
import { OverrideEditor } from './OverrideEditor';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
config: FieldConfigSource;
|
config: FieldConfigSource;
|
||||||
@@ -53,39 +54,34 @@ export class FieldConfigEditor extends React.PureComponent<Props> {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onMatcherConfigChange = (index: number, matcherConfig?: any) => {
|
onOverrideChange = (index: number, override: any) => {
|
||||||
const { config } = this.props;
|
const { config } = this.props;
|
||||||
let overrides = cloneDeep(config.overrides);
|
let overrides = cloneDeep(config.overrides);
|
||||||
if (matcherConfig === undefined) {
|
overrides[index] = override;
|
||||||
overrides = overrides.splice(index, 1);
|
|
||||||
} else {
|
|
||||||
overrides[index].matcher.options = matcherConfig;
|
|
||||||
}
|
|
||||||
this.props.onChange({ ...config, overrides });
|
this.props.onChange({ ...config, overrides });
|
||||||
};
|
};
|
||||||
|
|
||||||
onDynamicConfigValueAdd = (index: number, prop: string, custom?: boolean) => {
|
onOverrideRemove = (overrideIndex: number) => {
|
||||||
const { config } = this.props;
|
const { config } = this.props;
|
||||||
let overrides = cloneDeep(config.overrides);
|
let overrides = cloneDeep(config.overrides);
|
||||||
|
overrides.splice(overrideIndex, 1);
|
||||||
const propertyConfig: DynamicConfigValue = {
|
|
||||||
prop,
|
|
||||||
custom,
|
|
||||||
};
|
|
||||||
if (overrides[index].properties) {
|
|
||||||
overrides[index].properties.push(propertyConfig);
|
|
||||||
} else {
|
|
||||||
overrides[index].properties = [propertyConfig];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.onChange({ ...config, overrides });
|
this.props.onChange({ ...config, overrides });
|
||||||
};
|
};
|
||||||
|
|
||||||
onDynamicConfigValueChange = (overrideIndex: number, propertyIndex: number, value?: any) => {
|
onOverrideAdd = (value: SelectableValue<string>) => {
|
||||||
const { config } = this.props;
|
const { onChange, config } = this.props;
|
||||||
let overrides = cloneDeep(config.overrides);
|
onChange({
|
||||||
overrides[overrideIndex].properties[propertyIndex].value = value;
|
...config,
|
||||||
this.props.onChange({ ...config, overrides });
|
overrides: [
|
||||||
|
...config.overrides,
|
||||||
|
{
|
||||||
|
matcher: {
|
||||||
|
id: value.value!,
|
||||||
|
},
|
||||||
|
properties: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
renderEditor(item: FieldPropertyEditorItem, custom: boolean) {
|
renderEditor(item: FieldPropertyEditorItem, custom: boolean) {
|
||||||
@@ -151,55 +147,17 @@ export class FieldConfigEditor extends React.PureComponent<Props> {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{config.overrides.map((o, i) => {
|
{config.overrides.map((o, i) => {
|
||||||
const matcherUi = fieldMatchersUI.get(o.matcher.id);
|
|
||||||
// TODO: apply matcher to retrieve fields
|
// TODO: apply matcher to retrieve fields
|
||||||
return (
|
return (
|
||||||
<div key={`${o.matcher.id}/${i}`} style={{ border: `2px solid red`, marginBottom: '10px' }}>
|
<OverrideEditor
|
||||||
<Forms.Field label={matcherUi.name} description={matcherUi.description}>
|
key={`${o.matcher.id}/${i}`}
|
||||||
<>
|
data={data}
|
||||||
<matcherUi.component
|
override={o}
|
||||||
matcher={matcherUi.matcher}
|
onChange={value => this.onOverrideChange(i, value)}
|
||||||
data={data}
|
onRemove={() => this.onOverrideRemove(i)}
|
||||||
options={o.matcher.options}
|
configPropertiesOptions={configPropertiesOptions}
|
||||||
onChange={option => this.onMatcherConfigChange(i, option)}
|
customPropertiesRegistry={custom}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div style={{ border: `2px solid blue`, marginBottom: '5px' }}>
|
|
||||||
{o.properties.map((p, j) => {
|
|
||||||
const reg = p.custom ? custom : standardFieldConfigEditorRegistry;
|
|
||||||
const item = reg?.getIfExists(p.prop);
|
|
||||||
if (!item) {
|
|
||||||
return <div>Unknown property: {p.prop}</div>;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Forms.Field label={item.name} description={item.description}>
|
|
||||||
<item.override
|
|
||||||
value={p.value}
|
|
||||||
onChange={value => {
|
|
||||||
this.onDynamicConfigValueChange(i, j, value);
|
|
||||||
}}
|
|
||||||
item={item}
|
|
||||||
context={{
|
|
||||||
data,
|
|
||||||
getSuggestions: (scope?: VariableSuggestionsScope) =>
|
|
||||||
getDataLinksVariableSuggestions(data, scope),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Forms.Field>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<ValuePicker
|
|
||||||
icon="plus"
|
|
||||||
label="Set config property"
|
|
||||||
options={configPropertiesOptions}
|
|
||||||
onChange={o => {
|
|
||||||
this.onDynamicConfigValueAdd(i, o.value!, o.custom);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
</Forms.Field>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
@@ -211,22 +169,10 @@ export class FieldConfigEditor extends React.PureComponent<Props> {
|
|||||||
<ValuePicker
|
<ValuePicker
|
||||||
icon="plus"
|
icon="plus"
|
||||||
label="Add override"
|
label="Add override"
|
||||||
options={fieldMatchersUI.list().map(i => ({ label: i.name, value: i.id, description: i.description }))}
|
options={fieldMatchersUI
|
||||||
onChange={value => {
|
.list()
|
||||||
const { onChange, config } = this.props;
|
.map<SelectableValue<string>>(i => ({ label: i.name, value: i.id, description: i.description }))}
|
||||||
onChange({
|
onChange={value => this.onOverrideAdd(value)}
|
||||||
...config,
|
|
||||||
overrides: [
|
|
||||||
...config.overrides,
|
|
||||||
{
|
|
||||||
matcher: {
|
|
||||||
id: value.value!,
|
|
||||||
},
|
|
||||||
properties: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import {
|
||||||
|
ConfigOverrideRule,
|
||||||
|
DataFrame,
|
||||||
|
DynamicConfigValue,
|
||||||
|
FieldConfigEditorRegistry,
|
||||||
|
standardFieldConfigEditorRegistry,
|
||||||
|
VariableSuggestionsScope,
|
||||||
|
SelectableValue,
|
||||||
|
GrafanaTheme,
|
||||||
|
} from '@grafana/data';
|
||||||
|
import { fieldMatchersUI, stylesFactory, useTheme, ValuePicker, selectThemeVariant } from '@grafana/ui';
|
||||||
|
import { DynamicConfigValueEditor } from './DynamicConfigValueEditor';
|
||||||
|
import { OverrideHeader } from './OverrideHeader';
|
||||||
|
import { getDataLinksVariableSuggestions } from '../../../panel/panellinks/link_srv';
|
||||||
|
import { css } from 'emotion';
|
||||||
|
|
||||||
|
interface OverrideEditorProps {
|
||||||
|
data: DataFrame[];
|
||||||
|
override: ConfigOverrideRule;
|
||||||
|
onChange: (config: ConfigOverrideRule) => void;
|
||||||
|
onRemove: () => void;
|
||||||
|
customPropertiesRegistry?: FieldConfigEditorRegistry;
|
||||||
|
configPropertiesOptions: Array<SelectableValue<string>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OverrideEditor: React.FC<OverrideEditorProps> = ({
|
||||||
|
data,
|
||||||
|
override,
|
||||||
|
onChange,
|
||||||
|
onRemove,
|
||||||
|
customPropertiesRegistry,
|
||||||
|
configPropertiesOptions,
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const onMatcherConfigChange = useCallback(
|
||||||
|
(matcherConfig: any) => {
|
||||||
|
override.matcher.options = matcherConfig;
|
||||||
|
onChange(override);
|
||||||
|
},
|
||||||
|
[override, onChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onDynamicConfigValueChange = useCallback(
|
||||||
|
(index: number, value: DynamicConfigValue) => {
|
||||||
|
override.properties[index].value = value;
|
||||||
|
onChange(override);
|
||||||
|
},
|
||||||
|
[override, onChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onDynamicConfigValueRemove = useCallback(
|
||||||
|
(index: number) => {
|
||||||
|
override.properties.splice(index, 1);
|
||||||
|
onChange(override);
|
||||||
|
},
|
||||||
|
[override, onChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onDynamicConfigValueAdd = useCallback(
|
||||||
|
(prop: string, custom?: boolean) => {
|
||||||
|
const propertyConfig: DynamicConfigValue = {
|
||||||
|
prop,
|
||||||
|
custom,
|
||||||
|
};
|
||||||
|
if (override.properties) {
|
||||||
|
override.properties.push(propertyConfig);
|
||||||
|
} else {
|
||||||
|
override.properties = [propertyConfig];
|
||||||
|
}
|
||||||
|
onChange(override);
|
||||||
|
},
|
||||||
|
[override, onChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
const matcherUi = fieldMatchersUI.get(override.matcher.id);
|
||||||
|
const styles = getStyles(theme);
|
||||||
|
return (
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
<div className={styles.headerWrapper}>
|
||||||
|
<OverrideHeader onRemove={onRemove} title={matcherUi.name} description={matcherUi.description} />
|
||||||
|
<div className={styles.matcherUi}>
|
||||||
|
<matcherUi.component
|
||||||
|
matcher={matcherUi.matcher}
|
||||||
|
data={data}
|
||||||
|
options={override.matcher.options}
|
||||||
|
onChange={option => onMatcherConfigChange(option)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{override.properties.map((p, j) => {
|
||||||
|
const reg = p.custom ? customPropertiesRegistry : standardFieldConfigEditorRegistry;
|
||||||
|
const item = reg?.getIfExists(p.prop);
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
return <div>Unknown property: {p.prop}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={`${p.prop}/${j}`}>
|
||||||
|
<DynamicConfigValueEditor
|
||||||
|
onChange={value => onDynamicConfigValueChange(j, value)}
|
||||||
|
onRemove={() => onDynamicConfigValueRemove(j)}
|
||||||
|
property={p}
|
||||||
|
editorsRegistry={reg}
|
||||||
|
context={{
|
||||||
|
data,
|
||||||
|
getSuggestions: (scope?: VariableSuggestionsScope) => getDataLinksVariableSuggestions(data, scope),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<div className={styles.propertyPickerWrapper}>
|
||||||
|
<ValuePicker
|
||||||
|
label="Set config property"
|
||||||
|
icon="plus"
|
||||||
|
options={configPropertiesOptions}
|
||||||
|
variant={'link'}
|
||||||
|
onChange={o => {
|
||||||
|
onDynamicConfigValueAdd(o.value, o.custom);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
|
const borderColor = selectThemeVariant(
|
||||||
|
{
|
||||||
|
light: theme.colors.gray85,
|
||||||
|
dark: theme.colors.dark9,
|
||||||
|
},
|
||||||
|
theme.type
|
||||||
|
);
|
||||||
|
|
||||||
|
const headerBg = selectThemeVariant(
|
||||||
|
{
|
||||||
|
light: theme.colors.white,
|
||||||
|
dark: theme.colors.dark1,
|
||||||
|
},
|
||||||
|
theme.type
|
||||||
|
);
|
||||||
|
|
||||||
|
const shadow = selectThemeVariant(
|
||||||
|
{
|
||||||
|
light: theme.colors.gray85,
|
||||||
|
dark: theme.colors.black,
|
||||||
|
},
|
||||||
|
theme.type
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
wrapper: css`
|
||||||
|
border: 1px dashed ${borderColor};
|
||||||
|
margin-bottom: ${theme.spacing.md};
|
||||||
|
transition: box-shadow 0.5s cubic-bezier(0.19, 1, 0.22, 1);
|
||||||
|
box-shadow: none;
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 0 10px ${shadow};
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
headerWrapper: css`
|
||||||
|
background: ${headerBg};
|
||||||
|
padding: ${theme.spacing.xs} 0;
|
||||||
|
`,
|
||||||
|
matcherUi: css`
|
||||||
|
padding: ${theme.spacing.sm};
|
||||||
|
`,
|
||||||
|
propertyPickerWrapper: css`
|
||||||
|
border-top: 1px solid ${borderColor};
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Forms, Icon, stylesFactory, useTheme } from '@grafana/ui';
|
||||||
|
import { GrafanaTheme } from '@grafana/data';
|
||||||
|
import { css } from 'emotion';
|
||||||
|
|
||||||
|
interface OverrideHeaderProps {
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
onRemove: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OverrideHeader: React.FC<OverrideHeaderProps> = ({ title, description, onRemove }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const styles = getOverrideHeaderStyles(theme);
|
||||||
|
return (
|
||||||
|
<div className={styles.header}>
|
||||||
|
<Forms.Label description={description}>{title}</Forms.Label>
|
||||||
|
<div className={styles.remove} onClick={() => onRemove()}>
|
||||||
|
<Icon name="trash" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getOverrideHeaderStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
|
return {
|
||||||
|
header: css`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: ${theme.spacing.xs} ${theme.spacing.xs} 0 ${theme.spacing.xs};
|
||||||
|
`,
|
||||||
|
remove: css`
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
color: ${theme.colors.red88};
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { GrafanaTheme, FieldConfigSource, PanelData, PanelPlugin, SelectableValue } from '@grafana/data';
|
import { GrafanaTheme, FieldConfigSource, PanelData, PanelPlugin, SelectableValue } from '@grafana/data';
|
||||||
import { stylesFactory, Forms, CustomScrollbar, selectThemeVariant } from '@grafana/ui';
|
import { stylesFactory, Forms, CustomScrollbar, selectThemeVariant, Icon } from '@grafana/ui';
|
||||||
import { css, cx } from 'emotion';
|
import { css, cx } from 'emotion';
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
@@ -219,7 +219,7 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
|||||||
<div className={styles.toolbar}>
|
<div className={styles.toolbar}>
|
||||||
<div className={styles.toolbarLeft}>
|
<div className={styles.toolbarLeft}>
|
||||||
<button className="navbar-edit__back-btn" onClick={this.onPanelExit}>
|
<button className="navbar-edit__back-btn" onClick={this.onPanelExit}>
|
||||||
<i className="fa fa-arrow-left"></i>
|
<Icon name="arrow-left" />
|
||||||
</button>
|
</button>
|
||||||
<PanelTitle value={panel.title} onChange={this.onPanelTitleChange} />
|
<PanelTitle value={panel.title} onChange={this.onPanelTitleChange} />
|
||||||
</div>
|
</div>
|
||||||
@@ -265,7 +265,11 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
|||||||
>
|
>
|
||||||
{this.renderHorizontalSplit(styles)}
|
{this.renderHorizontalSplit(styles)}
|
||||||
<div className={styles.panelOptionsPane}>
|
<div className={styles.panelOptionsPane}>
|
||||||
<CustomScrollbar>
|
<CustomScrollbar
|
||||||
|
className={css`
|
||||||
|
height: 100% !important;
|
||||||
|
`}
|
||||||
|
>
|
||||||
{this.renderFieldOptions()}
|
{this.renderFieldOptions()}
|
||||||
<OptionsGroup title="Old settings">{this.renderVisSettings()}</OptionsGroup>
|
<OptionsGroup title="Old settings">{this.renderVisSettings()}</OptionsGroup>
|
||||||
</CustomScrollbar>
|
</CustomScrollbar>
|
||||||
|
|||||||
Reference in New Issue
Block a user