diff --git a/src/components/editors/IconsEditor.tsx b/src/components/editors/IconsEditor.tsx new file mode 100644 index 0000000..391a0e4 --- /dev/null +++ b/src/components/editors/IconsEditor.tsx @@ -0,0 +1,222 @@ +import { IconPosition } from 'types'; + +import { GrafanaTheme, SelectableValue, StandardEditorProps } from '@grafana/data'; +import { Button, HorizontalGroup, IconButton, Input, RadioButtonGroup, Slider, stylesFactory, ThemeContext } from '@grafana/ui'; +import { FieldNamePicker } from '../../grafana/MatchersUI/FieldNamePicker'; + +import { css } from 'emotion'; +import React from 'react'; + +import * as _ from 'lodash'; + + +type IconConfig = { + position: IconPosition; + url: string; + metrics: string[]; + conditions: Condition[]; + values: number[]; + size: number; +} + +enum Condition { + EQUAL = '=', + GREATER = '>', + LESS = '<', + GREATER_OR_EQUAL = '>=', + LESS_OR_EQUAL = '<=', +} + + +const positionOptions: Array> = [ + { + label: 'Upper-Left', + value: IconPosition.UPPER_LEFT, + }, + { + label: 'Middle', + value: IconPosition.MIDDLE, + }, + { + label: 'Upper-Right', + value: IconPosition.UPPER_RIGHT, + }, +]; + +const conditionOptions: Array> = [ + { + label: '>=', + value: Condition.GREATER_OR_EQUAL, + }, + { + label: '>', + value: Condition.GREATER, + }, + { + label: '=', + value: Condition.EQUAL, + }, + { + label: '<', + value: Condition.LESS, + }, + { + label: '<=', + value: Condition.LESS_OR_EQUAL, + }, +]; + +const DEFAULT_ICON: IconConfig = { + position: IconPosition.UPPER_LEFT, + url: '', + size: 40, + metrics: [], + conditions: [], + values: [], +} + +const fieldNamePickerSettings = { + settings: { width: 24 }, +} as any; + +export function IconsEditor({ onChange, value, context }: StandardEditorProps>) { + const icons = value; + + const addIcon = () => { + onChange( + _.concat(icons, _.cloneDeep(DEFAULT_ICON)) + ); + }; + + const removeIcon = (idx: number) => { + onChange( + _.filter(icons, (icon, iconIdx) => iconIdx !== idx) + ); + }; + + const addCondition = (iconIdx:number) => { + icons[iconIdx].conditions.push(Condition.GREATER_OR_EQUAL); + icons[iconIdx].metrics.push(''); + icons[iconIdx].values.push(0); + + onChange(icons); + }; + + const removeCondition = (iconIdx: number, conditionIdx: number) => { + icons[iconIdx].conditions.splice(conditionIdx, 1); + icons[iconIdx].metrics.splice(conditionIdx, 1); + icons[iconIdx].values.splice(conditionIdx, 1); + + onChange(icons); + }; + + const onIconFieldChange = (iconIdx: number, field: keyof IconConfig, value: any) => { + // @ts-ignore + icons[iconIdx][field] = value; + + onChange(icons); + }; + + const onConditionChange = (iconIdx: number, conditionIdx: number, field: keyof IconConfig, value: any) => { + console.log(value) + // @ts-ignore + icons[iconIdx][field][conditionIdx] = value; + + onChange(icons); + }; + + return ( + + {(theme) => { + const styles = getStyles(theme.v1); + return ( +
+
+ { + icons.map((icon, iconIdx) => { + return ( +
+ removeIcon(iconIdx)}> + onIconFieldChange(iconIdx, 'url', (evt.target as any).value)} + /> + onIconFieldChange(iconIdx, 'position', newVal)} + /> + onIconFieldChange(iconIdx, 'size', newVal)} + /> + + { + icon.conditions.map((condition, conditionIdx) => { + return ( + + onConditionChange(iconIdx, conditionIdx, 'metrics', newVal) } + item={fieldNamePickerSettings} + /> + onConditionChange(iconIdx, conditionIdx, 'conditions', newVal)} + /> + onConditionChange(iconIdx, conditionIdx, 'values', (evt.target as any).value)} + /> + removeCondition(iconIdx, conditionIdx)}> + + ) + }) + } + +
+ ) + }) + } +
+ +
+ ) + } + } +
+ ) +} + +interface IconsEditorStyles { + icons: string; + icon: string; +} + +const getStyles = stylesFactory((theme: GrafanaTheme): IconsEditorStyles => { + return { + icons: css` + display: flex; + flex-direction: column; + margin-bottom: ${theme.spacing.formSpacingBase * 2}px; + `, + icon: css` + margin-bottom: ${theme.spacing.sm}; + border-bottom: 1px solid ${theme.colors.panelBorder}; + padding-bottom: ${theme.spacing.formSpacingBase * 2}px; + + &:last-child { + border: none; + margin-bottom: 0; + } + `, + } +}); diff --git a/src/grafana/MatchersUI/FieldNamePicker.tsx b/src/grafana/MatchersUI/FieldNamePicker.tsx new file mode 100644 index 0000000..044526f --- /dev/null +++ b/src/grafana/MatchersUI/FieldNamePicker.tsx @@ -0,0 +1,46 @@ +// copied from https://github.com/grafana/grafana/blob/3c6e0e8ef85048af952367751e478c08342e17b4/packages/grafana-ui/src/components/MatchersUI/FieldNamePicker.tsx +import React, { useCallback } from 'react'; + +import { FieldNamePickerConfigSettings, SelectableValue, StandardEditorProps } from '@grafana/data'; + +import { Select } from '@grafana/ui'; + +import { useFieldDisplayNames, useSelectOptions, frameHasName } from './utils'; + +// Pick a field name out of the fulds +export const FieldNamePicker: React.FC> = ({ + value, + onChange, + context, + item, +}) => { + const settings: FieldNamePickerConfigSettings = item.settings ?? {}; + const names = useFieldDisplayNames(context.data, settings?.filter); + const selectOptions = useSelectOptions(names, value); + + const onSelectChange = useCallback( + (selection?: SelectableValue) => { + if (selection && !frameHasName(selection.value, names)) { + return; // can not select name that does not exist? + } + return onChange(selection?.value); + }, + [names, onChange] + ); + + const selectedOption = selectOptions.find((v: any) => v.value === value); + return ( + <> +