mirror of https://github.com/grafana/grafana.git
234 lines
6.6 KiB
TypeScript
234 lines
6.6 KiB
TypeScript
import React, { PureComponent, ChangeEvent } from 'react';
|
|
import { Threshold } from '../../types';
|
|
import { ColorPicker } from '..';
|
|
import { PanelOptionsGroup } from '..';
|
|
import { colors } from '../../utils';
|
|
import { getColorFromHexRgbOrName, ThemeContext } from '@grafana/ui';
|
|
|
|
export interface Props {
|
|
thresholds: Threshold[];
|
|
onChange: (thresholds: Threshold[]) => void;
|
|
}
|
|
|
|
interface State {
|
|
thresholds: Threshold[];
|
|
}
|
|
|
|
export class ThresholdsEditor extends PureComponent<Props, State> {
|
|
constructor(props: Props) {
|
|
super(props);
|
|
|
|
const addDefaultThreshold = this.props.thresholds.length === 0;
|
|
const thresholds: Threshold[] = addDefaultThreshold
|
|
? [{ index: 0, value: -Infinity, color: colors[0] }]
|
|
: props.thresholds;
|
|
this.state = { thresholds };
|
|
|
|
if (addDefaultThreshold) {
|
|
this.onChange();
|
|
}
|
|
}
|
|
|
|
onAddThreshold = (index: number) => {
|
|
const { thresholds } = this.state;
|
|
const maxValue = 100;
|
|
const minValue = 0;
|
|
|
|
if (index === 0) {
|
|
return;
|
|
}
|
|
|
|
const newThresholds = thresholds.map(threshold => {
|
|
if (threshold.index >= index) {
|
|
const index = threshold.index + 1;
|
|
threshold = { ...threshold, index };
|
|
}
|
|
return threshold;
|
|
});
|
|
|
|
// Setting value to a value between the previous thresholds
|
|
const beforeThreshold = newThresholds.filter(t => t.index === index - 1 && t.index !== 0)[0];
|
|
const afterThreshold = newThresholds.filter(t => t.index === index + 1 && t.index !== 0)[0];
|
|
const beforeThresholdValue = beforeThreshold !== undefined ? beforeThreshold.value : minValue;
|
|
const afterThresholdValue = afterThreshold !== undefined ? afterThreshold.value : maxValue;
|
|
const value = afterThresholdValue - (afterThresholdValue - beforeThresholdValue) / 2;
|
|
|
|
// Set a color
|
|
const color = colors.filter(c => !newThresholds.some(t => t.color === c))[0];
|
|
|
|
this.setState(
|
|
{
|
|
thresholds: this.sortThresholds([
|
|
...newThresholds,
|
|
{
|
|
color,
|
|
index,
|
|
value: value as number,
|
|
},
|
|
]),
|
|
},
|
|
() => this.onChange()
|
|
);
|
|
};
|
|
|
|
onRemoveThreshold = (threshold: Threshold) => {
|
|
if (threshold.index === 0) {
|
|
return;
|
|
}
|
|
|
|
this.setState(
|
|
prevState => {
|
|
const newThresholds = prevState.thresholds.map(t => {
|
|
if (t.index > threshold.index) {
|
|
const index = t.index - 1;
|
|
t = { ...t, index };
|
|
}
|
|
return t;
|
|
});
|
|
|
|
return {
|
|
thresholds: newThresholds.filter(t => t !== threshold),
|
|
};
|
|
},
|
|
() => this.onChange()
|
|
);
|
|
};
|
|
|
|
onChangeThresholdValue = (event: ChangeEvent<HTMLInputElement>, threshold: Threshold) => {
|
|
if (threshold.index === 0) {
|
|
return;
|
|
}
|
|
|
|
const { thresholds } = this.state;
|
|
const cleanValue = event.target.value.replace(/,/g, '.');
|
|
const parsedValue = parseFloat(cleanValue);
|
|
const value = isNaN(parsedValue) ? '' : parsedValue;
|
|
|
|
const newThresholds = thresholds.map(t => {
|
|
if (t === threshold && t.index !== 0) {
|
|
t = { ...t, value: value as number };
|
|
}
|
|
|
|
return t;
|
|
});
|
|
|
|
this.setState({ thresholds: newThresholds });
|
|
};
|
|
|
|
onChangeThresholdColor = (threshold: Threshold, color: string) => {
|
|
const { thresholds } = this.state;
|
|
|
|
const newThresholds = thresholds.map(t => {
|
|
if (t === threshold) {
|
|
t = { ...t, color: color };
|
|
}
|
|
|
|
return t;
|
|
});
|
|
|
|
this.setState(
|
|
{
|
|
thresholds: newThresholds,
|
|
},
|
|
() => this.onChange()
|
|
);
|
|
};
|
|
|
|
onBlur = () => {
|
|
this.setState(prevState => {
|
|
const sortThresholds = this.sortThresholds([...prevState.thresholds]);
|
|
let index = 0;
|
|
sortThresholds.forEach(t => {
|
|
t.index = index++;
|
|
});
|
|
|
|
return { thresholds: sortThresholds };
|
|
});
|
|
|
|
this.onChange();
|
|
};
|
|
|
|
onChange = () => {
|
|
this.props.onChange(this.state.thresholds);
|
|
};
|
|
|
|
sortThresholds = (thresholds: Threshold[]) => {
|
|
return thresholds.sort((t1, t2) => {
|
|
return t1.value - t2.value;
|
|
});
|
|
};
|
|
|
|
renderInput = (threshold: Threshold) => {
|
|
return (
|
|
<div className="thresholds-row-input-inner">
|
|
<span className="thresholds-row-input-inner-arrow" />
|
|
<div className="thresholds-row-input-inner-color">
|
|
{threshold.color && (
|
|
<div className="thresholds-row-input-inner-color-colorpicker">
|
|
<ColorPicker color={threshold.color} onChange={color => this.onChangeThresholdColor(threshold, color)} />
|
|
</div>
|
|
)}
|
|
</div>
|
|
{threshold.index === 0 && (
|
|
<div className="thresholds-row-input-inner-value">
|
|
<input type="text" value="Base" readOnly />
|
|
</div>
|
|
)}
|
|
{threshold.index > 0 && (
|
|
<>
|
|
<div className="thresholds-row-input-inner-value">
|
|
<input
|
|
type="number"
|
|
step="0.0001"
|
|
onChange={event => this.onChangeThresholdValue(event, threshold)}
|
|
value={threshold.value}
|
|
onBlur={this.onBlur}
|
|
readOnly={threshold.index === 0}
|
|
/>
|
|
</div>
|
|
<div className="thresholds-row-input-inner-remove" onClick={() => this.onRemoveThreshold(threshold)}>
|
|
<i className="fa fa-times" />
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
render() {
|
|
const { thresholds } = this.state;
|
|
return (
|
|
<ThemeContext.Consumer>
|
|
{theme => {
|
|
return (
|
|
<PanelOptionsGroup title="Thresholds">
|
|
<div className="thresholds">
|
|
{thresholds
|
|
.slice(0)
|
|
.reverse()
|
|
.map((threshold, index) => {
|
|
return (
|
|
<div className="thresholds-row" key={`${threshold.index}-${index}`}>
|
|
<div
|
|
className="thresholds-row-add-button"
|
|
onClick={() => this.onAddThreshold(threshold.index + 1)}
|
|
>
|
|
<i className="fa fa-plus" />
|
|
</div>
|
|
<div
|
|
className="thresholds-row-color-indicator"
|
|
style={{ backgroundColor: getColorFromHexRgbOrName(threshold.color, theme.type) }}
|
|
/>
|
|
<div className="thresholds-row-input">{this.renderInput(threshold)}</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</PanelOptionsGroup>
|
|
);
|
|
}}
|
|
</ThemeContext.Consumer>
|
|
);
|
|
}
|
|
}
|