mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
OptionsUI: support string list (#25134)
This commit is contained in:
parent
d526647005
commit
53b9f325c0
@ -75,6 +75,10 @@ export interface OptionsUIRegistryBuilderAPI<
|
||||
config: OptionEditorConfig<TOptions, TSettings, string>
|
||||
): this;
|
||||
|
||||
addStringArray?<TSettings extends StringFieldConfigSettings = StringFieldConfigSettings>(
|
||||
config: OptionEditorConfig<TOptions, TSettings, string[]>
|
||||
): this;
|
||||
|
||||
addSelect?<TOption, TSettings extends SelectFieldConfigSettings<TOption>>(
|
||||
config: OptionEditorConfig<TOptions, TSettings, TOption>
|
||||
): this;
|
||||
|
@ -143,6 +143,16 @@ export class PanelOptionsEditorBuilder<TOptions> extends OptionsUIRegistryBuilde
|
||||
});
|
||||
}
|
||||
|
||||
addStringArray<TSettings>(
|
||||
config: PanelOptionsEditorConfig<TOptions, TSettings & StringFieldConfigSettings, string[]>
|
||||
) {
|
||||
return this.addCustomEditor({
|
||||
...config,
|
||||
id: config.path,
|
||||
editor: standardEditorsRegistry.get('strings').editor as any,
|
||||
});
|
||||
}
|
||||
|
||||
addSelect<TOption, TSettings extends SelectFieldConfigSettings<TOption>>(
|
||||
config: PanelOptionsEditorConfig<TOptions, TSettings, TOption>
|
||||
) {
|
||||
|
112
packages/grafana-ui/src/components/OptionsUI/strings.tsx
Normal file
112
packages/grafana-ui/src/components/OptionsUI/strings.tsx
Normal file
@ -0,0 +1,112 @@
|
||||
import React from 'react';
|
||||
import { FieldConfigEditorProps, StringFieldConfigSettings, GrafanaTheme } from '@grafana/data';
|
||||
import { Input } from '../Input/Input';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { stylesFactory, getTheme } from '../../themes';
|
||||
import { css } from 'emotion';
|
||||
import { Button } from '../Button';
|
||||
|
||||
type Props = FieldConfigEditorProps<string[], StringFieldConfigSettings>;
|
||||
interface State {
|
||||
showAdd: boolean;
|
||||
}
|
||||
|
||||
export class StringArrayEditor extends React.PureComponent<Props, State> {
|
||||
state = {
|
||||
showAdd: false,
|
||||
};
|
||||
|
||||
onRemoveString = (index: number) => {
|
||||
const { value, onChange } = this.props;
|
||||
const copy = [...value];
|
||||
copy.splice(index, 1);
|
||||
onChange(copy);
|
||||
};
|
||||
|
||||
onValueChange = (e: React.SyntheticEvent, idx: number) => {
|
||||
const evt = e as React.KeyboardEvent<HTMLInputElement>;
|
||||
if (e.hasOwnProperty('key')) {
|
||||
if (evt.key !== 'Enter') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const { value, onChange } = this.props;
|
||||
|
||||
// Form event, or Enter
|
||||
const v = evt.currentTarget.value.trim();
|
||||
if (idx < 0) {
|
||||
if (v) {
|
||||
evt.currentTarget.value = ''; // reset last value
|
||||
onChange([...value, v]);
|
||||
}
|
||||
this.setState({ showAdd: false });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!v) {
|
||||
return this.onRemoveString(idx);
|
||||
}
|
||||
|
||||
const copy = [...value];
|
||||
copy[idx] = v;
|
||||
onChange(copy);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { value, item } = this.props;
|
||||
const { showAdd } = this.state;
|
||||
const styles = getStyles(getTheme());
|
||||
const placeholder = item.settings?.placeholder || 'Add text';
|
||||
return (
|
||||
<div>
|
||||
{value.map((v, index) => {
|
||||
return (
|
||||
<Input
|
||||
className={styles.textInput}
|
||||
key={`${index}/${v}`}
|
||||
defaultValue={v || ''}
|
||||
onBlur={e => this.onValueChange(e, index)}
|
||||
onKeyDown={e => this.onValueChange(e, index)}
|
||||
suffix={<Icon className={styles.trashIcon} name="trash-alt" onClick={() => this.onRemoveString(index)} />}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{showAdd ? (
|
||||
<Input
|
||||
autoFocus
|
||||
className={styles.textInput}
|
||||
placeholder={placeholder}
|
||||
defaultValue={''}
|
||||
onBlur={e => this.onValueChange(e, -1)}
|
||||
onKeyDown={e => this.onValueChange(e, -1)}
|
||||
suffix={<Icon name="plus-circle" />}
|
||||
/>
|
||||
) : (
|
||||
<Button icon="plus" size="sm" variant="secondary" onClick={() => this.setState({ showAdd: true })}>
|
||||
{placeholder}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
return {
|
||||
textInput: css`
|
||||
margin-bottom: 5px;
|
||||
&:hover {
|
||||
border: 1px solid ${theme.colors.formInputBorderHover};
|
||||
}
|
||||
`,
|
||||
trashIcon: css`
|
||||
color: ${theme.colors.textWeak};
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: ${theme.colors.text};
|
||||
}
|
||||
`,
|
||||
};
|
||||
});
|
@ -118,6 +118,7 @@ export { Slider } from './Slider/Slider';
|
||||
|
||||
// TODO: namespace!!
|
||||
export { StringValueEditor } from './OptionsUI/string';
|
||||
export { StringArrayEditor } from './OptionsUI/strings';
|
||||
export { NumberValueEditor } from './OptionsUI/number';
|
||||
export { SelectValueEditor } from './OptionsUI/select';
|
||||
export { FieldConfigItemHeaderTitle } from './FieldConfigs/FieldConfigItemHeaderTitle';
|
||||
|
@ -20,7 +20,13 @@ import {
|
||||
} from '@grafana/data';
|
||||
|
||||
import { Switch } from '../components/Switch/Switch';
|
||||
import { NumberValueEditor, RadioButtonGroup, StringValueEditor, SelectValueEditor } from '../components';
|
||||
import {
|
||||
NumberValueEditor,
|
||||
RadioButtonGroup,
|
||||
StringValueEditor,
|
||||
StringArrayEditor,
|
||||
SelectValueEditor,
|
||||
} from '../components';
|
||||
import { ValueMappingsValueEditor } from '../components/OptionsUI/mappings';
|
||||
import { ThresholdsValueEditor } from '../components/OptionsUI/thresholds';
|
||||
import { UnitValueEditor } from '../components/OptionsUI/units';
|
||||
@ -227,6 +233,13 @@ export const getStandardOptionEditors = () => {
|
||||
editor: StringValueEditor as any,
|
||||
};
|
||||
|
||||
const strings: StandardEditorsRegistryItem<string[]> = {
|
||||
id: 'strings',
|
||||
name: 'String array',
|
||||
description: 'An array of strings',
|
||||
editor: StringArrayEditor as any,
|
||||
};
|
||||
|
||||
const boolean: StandardEditorsRegistryItem<boolean> = {
|
||||
id: 'boolean',
|
||||
name: 'Boolean',
|
||||
@ -290,5 +303,5 @@ export const getStandardOptionEditors = () => {
|
||||
description: '',
|
||||
};
|
||||
|
||||
return [text, number, boolean, radio, select, unit, mappings, thresholds, links, color, statsPicker];
|
||||
return [text, number, boolean, radio, select, unit, mappings, thresholds, links, color, statsPicker, strings];
|
||||
};
|
||||
|
@ -19,6 +19,15 @@ export const plugin = new PanelPlugin<TextOptions>(TextPanel)
|
||||
},
|
||||
defaultValue: 'markdown',
|
||||
})
|
||||
.addStringArray({
|
||||
path: 'strings',
|
||||
name: 'String Array',
|
||||
description: 'list of strings',
|
||||
settings: {
|
||||
placeholder: 'Add a string value (text2 demo)',
|
||||
},
|
||||
defaultValue: ['hello', 'world'],
|
||||
})
|
||||
.addTextInput({
|
||||
path: 'content',
|
||||
name: 'Content',
|
||||
|
Loading…
Reference in New Issue
Block a user