TimePicker.tsx 7.6 KB

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