TimePicker.tsx 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. import React, { PureComponent } from 'react';
  2. import moment from 'moment';
  3. import * as dateMath from 'app/core/utils/datemath';
  4. import * as rangeUtil from 'app/core/utils/rangeutil';
  5. const DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss';
  6. export const DEFAULT_RANGE = {
  7. from: 'now-6h',
  8. to: 'now',
  9. };
  10. export function parseTime(value, isUtc = false, asString = false) {
  11. if (value.indexOf('now') !== -1) {
  12. return value;
  13. }
  14. if (!isNaN(value)) {
  15. const epoch = parseInt(value, 10);
  16. const m = isUtc ? moment.utc(epoch) : moment(epoch);
  17. return asString ? m.format(DATE_FORMAT) : m;
  18. }
  19. return undefined;
  20. }
  21. export default class TimePicker extends PureComponent<any, any> {
  22. dropdownEl: any;
  23. constructor(props) {
  24. super(props);
  25. const fromRaw = props.range ? props.range.from : DEFAULT_RANGE.from;
  26. const toRaw = props.range ? props.range.to : DEFAULT_RANGE.to;
  27. const range = {
  28. from: parseTime(fromRaw),
  29. to: parseTime(toRaw),
  30. };
  31. this.state = {
  32. fromRaw: parseTime(fromRaw, props.isUtc, true),
  33. isOpen: props.isOpen,
  34. isUtc: props.isUtc,
  35. rangeString: rangeUtil.describeTimeRange(range),
  36. refreshInterval: '',
  37. toRaw: parseTime(toRaw, props.isUtc, true),
  38. };
  39. }
  40. move(direction) {
  41. const { onChangeTime } = this.props;
  42. const { fromRaw, toRaw } = this.state;
  43. const range = {
  44. from: dateMath.parse(fromRaw, false),
  45. to: dateMath.parse(toRaw, true),
  46. };
  47. const timespan = (range.to.valueOf() - range.from.valueOf()) / 2;
  48. let to, from;
  49. if (direction === -1) {
  50. to = range.to.valueOf() - timespan;
  51. from = range.from.valueOf() - timespan;
  52. } else if (direction === 1) {
  53. to = range.to.valueOf() + timespan;
  54. from = range.from.valueOf() + timespan;
  55. if (to > Date.now() && range.to < Date.now()) {
  56. to = Date.now();
  57. from = range.from.valueOf();
  58. }
  59. } else {
  60. to = range.to.valueOf();
  61. from = range.from.valueOf();
  62. }
  63. const rangeString = rangeUtil.describeTimeRange(range);
  64. // No need to convert to UTC again
  65. to = moment(to);
  66. from = moment(from);
  67. this.setState(
  68. {
  69. rangeString,
  70. fromRaw: from.format(DATE_FORMAT),
  71. toRaw: to.format(DATE_FORMAT),
  72. },
  73. () => {
  74. onChangeTime({ to, from });
  75. }
  76. );
  77. }
  78. handleChangeFrom = e => {
  79. this.setState({
  80. fromRaw: e.target.value,
  81. });
  82. };
  83. handleChangeTo = e => {
  84. this.setState({
  85. toRaw: e.target.value,
  86. });
  87. };
  88. handleClickApply = () => {
  89. const { onChangeTime } = this.props;
  90. const { toRaw, fromRaw } = this.state;
  91. const range = {
  92. from: dateMath.parse(fromRaw, false),
  93. to: dateMath.parse(toRaw, true),
  94. };
  95. const rangeString = rangeUtil.describeTimeRange(range);
  96. this.setState(
  97. {
  98. isOpen: false,
  99. rangeString,
  100. },
  101. () => {
  102. if (onChangeTime) {
  103. onChangeTime(range);
  104. }
  105. }
  106. );
  107. };
  108. handleClickLeft = () => this.move(-1);
  109. handleClickPicker = () => {
  110. this.setState(state => ({
  111. isOpen: !state.isOpen,
  112. }));
  113. };
  114. handleClickRight = () => this.move(1);
  115. handleClickRefresh = () => {};
  116. handleClickRelativeOption = range => {
  117. const { onChangeTime } = this.props;
  118. const rangeString = rangeUtil.describeTimeRange(range);
  119. this.setState(
  120. {
  121. toRaw: range.to,
  122. fromRaw: range.from,
  123. isOpen: false,
  124. rangeString,
  125. },
  126. () => {
  127. if (onChangeTime) {
  128. onChangeTime(range);
  129. }
  130. }
  131. );
  132. };
  133. getTimeOptions() {
  134. return rangeUtil.getRelativeTimesList({}, this.state.rangeString);
  135. }
  136. dropdownRef = el => {
  137. this.dropdownEl = el;
  138. };
  139. renderDropdown() {
  140. const { fromRaw, isOpen, toRaw } = this.state;
  141. if (!isOpen) {
  142. return null;
  143. }
  144. const timeOptions = this.getTimeOptions();
  145. return (
  146. <div ref={this.dropdownRef} className="gf-timepicker-dropdown">
  147. <div className="gf-timepicker-absolute-section">
  148. <h3 className="section-heading">Custom range</h3>
  149. <label className="small">From:</label>
  150. <div className="gf-form-inline">
  151. <div className="gf-form max-width-28">
  152. <input
  153. type="text"
  154. className="gf-form-input input-large timepicker-from"
  155. value={fromRaw}
  156. onChange={this.handleChangeFrom}
  157. />
  158. </div>
  159. </div>
  160. <label className="small">To:</label>
  161. <div className="gf-form-inline">
  162. <div className="gf-form max-width-28">
  163. <input
  164. type="text"
  165. className="gf-form-input input-large timepicker-to"
  166. value={toRaw}
  167. onChange={this.handleChangeTo}
  168. />
  169. </div>
  170. </div>
  171. {/* <label className="small">Refreshing every:</label>
  172. <div className="gf-form-inline">
  173. <div className="gf-form max-width-28">
  174. <select className="gf-form-input input-medium" ng-options="f.value as f.text for f in ctrl.refresh.options"></select>
  175. </div>
  176. </div> */}
  177. <div className="gf-form">
  178. <button className="btn gf-form-btn btn-secondary" onClick={this.handleClickApply}>
  179. Apply
  180. </button>
  181. </div>
  182. </div>
  183. <div className="gf-timepicker-relative-section">
  184. <h3 className="section-heading">Quick ranges</h3>
  185. {Object.keys(timeOptions).map(section => {
  186. const group = timeOptions[section];
  187. return (
  188. <ul key={section}>
  189. {group.map(option => (
  190. <li className={option.active ? 'active' : ''} key={option.display}>
  191. <a onClick={() => this.handleClickRelativeOption(option)}>{option.display}</a>
  192. </li>
  193. ))}
  194. </ul>
  195. );
  196. })}
  197. </div>
  198. </div>
  199. );
  200. }
  201. render() {
  202. const { isUtc, rangeString, refreshInterval } = this.state;
  203. return (
  204. <div className="timepicker">
  205. <div className="navbar-buttons">
  206. <button className="btn navbar-button navbar-button--tight timepicker-left" onClick={this.handleClickLeft}>
  207. <i className="fa fa-chevron-left" />
  208. </button>
  209. <button className="btn navbar-button gf-timepicker-nav-btn" onClick={this.handleClickPicker}>
  210. <i className="fa fa-clock-o" />
  211. <span className="timepicker-rangestring">{rangeString}</span>
  212. {isUtc ? <span className="gf-timepicker-utc">UTC</span> : null}
  213. {refreshInterval ? <span className="text-warning">&nbsp; Refresh every {refreshInterval}</span> : null}
  214. </button>
  215. <button className="btn navbar-button navbar-button--tight timepicker-right" onClick={this.handleClickRight}>
  216. <i className="fa fa-chevron-right" />
  217. </button>
  218. </div>
  219. {this.renderDropdown()}
  220. </div>
  221. );
  222. }
  223. }