ThresholdsEditor.tsx 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. import React, { PureComponent, ChangeEvent } from 'react';
  2. import { Threshold } from '../../types';
  3. import { ColorPicker } from '..';
  4. import { PanelOptionsGroup } from '..';
  5. import { colors } from '../../utils';
  6. import { ThemeContext } from '../../themes/ThemeContext';
  7. import { getColorFromHexRgbOrName } from '../../utils/namedColorsPalette';
  8. export interface Props {
  9. thresholds: Threshold[];
  10. onChange: (thresholds: Threshold[]) => void;
  11. }
  12. interface State {
  13. thresholds: Threshold[];
  14. }
  15. export class ThresholdsEditor extends PureComponent<Props, State> {
  16. constructor(props: Props) {
  17. super(props);
  18. const addDefaultThreshold = this.props.thresholds.length === 0;
  19. const thresholds: Threshold[] = addDefaultThreshold
  20. ? [{ index: 0, value: -Infinity, color: colors[0] }]
  21. : props.thresholds;
  22. this.state = { thresholds };
  23. if (addDefaultThreshold) {
  24. this.onChange();
  25. }
  26. }
  27. onAddThreshold = (index: number) => {
  28. const { thresholds } = this.state;
  29. const maxValue = 100;
  30. const minValue = 0;
  31. if (index === 0) {
  32. return;
  33. }
  34. const newThresholds = thresholds.map(threshold => {
  35. if (threshold.index >= index) {
  36. const index = threshold.index + 1;
  37. threshold = { ...threshold, index };
  38. }
  39. return threshold;
  40. });
  41. // Setting value to a value between the previous thresholds
  42. const beforeThreshold = newThresholds.filter(t => t.index === index - 1 && t.index !== 0)[0];
  43. const afterThreshold = newThresholds.filter(t => t.index === index + 1 && t.index !== 0)[0];
  44. const beforeThresholdValue = beforeThreshold !== undefined ? beforeThreshold.value : minValue;
  45. const afterThresholdValue = afterThreshold !== undefined ? afterThreshold.value : maxValue;
  46. const value = afterThresholdValue - (afterThresholdValue - beforeThresholdValue) / 2;
  47. // Set a color
  48. const color = colors.filter(c => !newThresholds.some(t => t.color === c))[1];
  49. this.setState(
  50. {
  51. thresholds: this.sortThresholds([
  52. ...newThresholds,
  53. {
  54. color,
  55. index,
  56. value: value as number,
  57. },
  58. ]),
  59. },
  60. () => this.onChange()
  61. );
  62. };
  63. onRemoveThreshold = (threshold: Threshold) => {
  64. if (threshold.index === 0) {
  65. return;
  66. }
  67. this.setState(
  68. prevState => {
  69. const newThresholds = prevState.thresholds.map(t => {
  70. if (t.index > threshold.index) {
  71. const index = t.index - 1;
  72. t = { ...t, index };
  73. }
  74. return t;
  75. });
  76. return {
  77. thresholds: newThresholds.filter(t => t !== threshold),
  78. };
  79. },
  80. () => this.onChange()
  81. );
  82. };
  83. onChangeThresholdValue = (event: ChangeEvent<HTMLInputElement>, threshold: Threshold) => {
  84. if (threshold.index === 0) {
  85. return;
  86. }
  87. const { thresholds } = this.state;
  88. const cleanValue = event.target.value.replace(/,/g, '.');
  89. const parsedValue = parseFloat(cleanValue);
  90. const value = isNaN(parsedValue) ? '' : parsedValue;
  91. const newThresholds = thresholds.map(t => {
  92. if (t === threshold && t.index !== 0) {
  93. t = { ...t, value: value as number };
  94. }
  95. return t;
  96. });
  97. this.setState({ thresholds: newThresholds });
  98. };
  99. onChangeThresholdColor = (threshold: Threshold, color: string) => {
  100. const { thresholds } = this.state;
  101. const newThresholds = thresholds.map(t => {
  102. if (t === threshold) {
  103. t = { ...t, color: color };
  104. }
  105. return t;
  106. });
  107. this.setState(
  108. {
  109. thresholds: newThresholds,
  110. },
  111. () => this.onChange()
  112. );
  113. };
  114. onBlur = () => {
  115. this.setState(prevState => {
  116. const sortThresholds = this.sortThresholds([...prevState.thresholds]);
  117. let index = 0;
  118. sortThresholds.forEach(t => {
  119. t.index = index++;
  120. });
  121. return { thresholds: sortThresholds };
  122. });
  123. this.onChange();
  124. };
  125. onChange = () => {
  126. this.props.onChange(this.state.thresholds);
  127. };
  128. sortThresholds = (thresholds: Threshold[]) => {
  129. return thresholds.sort((t1, t2) => {
  130. return t1.value - t2.value;
  131. });
  132. };
  133. renderInput = (threshold: Threshold) => {
  134. return (
  135. <div className="thresholds-row-input-inner">
  136. <span className="thresholds-row-input-inner-arrow" />
  137. <div className="thresholds-row-input-inner-color">
  138. {threshold.color && (
  139. <div className="thresholds-row-input-inner-color-colorpicker">
  140. <ColorPicker color={threshold.color} onChange={color => this.onChangeThresholdColor(threshold, color)} />
  141. </div>
  142. )}
  143. </div>
  144. {threshold.index === 0 && (
  145. <div className="thresholds-row-input-inner-value">
  146. <input type="text" value="Base" readOnly />
  147. </div>
  148. )}
  149. {threshold.index > 0 && (
  150. <>
  151. <div className="thresholds-row-input-inner-value">
  152. <input
  153. type="number"
  154. step="0.0001"
  155. onChange={event => this.onChangeThresholdValue(event, threshold)}
  156. value={threshold.value}
  157. onBlur={this.onBlur}
  158. readOnly={threshold.index === 0}
  159. />
  160. </div>
  161. <div className="thresholds-row-input-inner-remove" onClick={() => this.onRemoveThreshold(threshold)}>
  162. <i className="fa fa-times" />
  163. </div>
  164. </>
  165. )}
  166. </div>
  167. );
  168. };
  169. render() {
  170. const { thresholds } = this.state;
  171. return (
  172. <ThemeContext.Consumer>
  173. {theme => {
  174. return (
  175. <PanelOptionsGroup title="Thresholds">
  176. <div className="thresholds">
  177. {thresholds
  178. .slice(0)
  179. .reverse()
  180. .map((threshold, index) => {
  181. return (
  182. <div className="thresholds-row" key={`${threshold.index}-${index}`}>
  183. <div
  184. className="thresholds-row-add-button"
  185. onClick={() => this.onAddThreshold(threshold.index + 1)}
  186. >
  187. <i className="fa fa-plus" />
  188. </div>
  189. <div
  190. className="thresholds-row-color-indicator"
  191. style={{ backgroundColor: getColorFromHexRgbOrName(threshold.color, theme.type) }}
  192. />
  193. <div className="thresholds-row-input">{this.renderInput(threshold)}</div>
  194. </div>
  195. );
  196. })}
  197. </div>
  198. </PanelOptionsGroup>
  199. );
  200. }}
  201. </ThemeContext.Consumer>
  202. );
  203. }
  204. }