Files
weave-scope/client/app/scripts/components/time-travel.js
Filip Barl 2183a93916 Time Travel 3.0 (#2703)
* Experimental.

* Getting somewhere.

* Good zooming behaviour.

* Working timeline zooming & panning.

* Clickable timestamps.

* Dragging cursor

* Timeline panning buttons.

* Capping at current time.

* Scale limits.

* Better ticks.

* Time tags fading in smoothly.

* Removed seconds.

* Better tick spacing.

* Vertical panning as zooming.

* Organizing the code..

* Replaced d3-zoom with native events.

* Got rid of scaleX

* More code beautified.

* Almost done polishing the code.

* Some cleanup.

* Better request triggers.

* More cleaning up.

* Styled the timestamp input.

* Final cleanup.

* Update yarn.lock

* Zoom tracking.

* Animate timeline translations.

* Fixed the PAUSE button glitch and updating the time control info.

* Opacity fix and timeline arrows removed.

* Fixed the red vertical bar.

* Use preventDefault() on timeline scrolling.
2017-07-27 14:40:20 +02:00

150 lines
4.3 KiB
JavaScript

import React from 'react';
import moment from 'moment';
import classNames from 'classnames';
import { connect } from 'react-redux';
import { debounce } from 'lodash';
import TimeTravelTimeline from './time-travel-timeline';
import { trackMixpanelEvent } from '../utils/tracking-utils';
import { clampToNowInSecondsPrecision } from '../utils/time-utils';
import {
jumpToTime,
resumeTime,
timeTravelStartTransition,
} from '../actions/app-actions';
import { TIMELINE_DEBOUNCE_INTERVAL } from '../constants/timer';
const getTimestampStates = (timestamp) => {
timestamp = timestamp || moment();
return {
inputValue: moment(timestamp).utc().format(),
};
};
class TimeTravel extends React.Component {
constructor(props, context) {
super(props, context);
this.state = getTimestampStates(props.pausedAt);
this.handleInputChange = this.handleInputChange.bind(this);
this.handleTimelinePan = this.handleTimelinePan.bind(this);
this.handleTimelinePanEnd = this.handleTimelinePanEnd.bind(this);
this.handleInstantJump = this.handleInstantJump.bind(this);
this.trackTimestampEdit = this.trackTimestampEdit.bind(this);
this.trackTimelineClick = this.trackTimelineClick.bind(this);
this.trackTimelinePan = this.trackTimelinePan.bind(this);
this.instantUpdateTimestamp = this.instantUpdateTimestamp.bind(this);
this.debouncedUpdateTimestamp = debounce(
this.instantUpdateTimestamp.bind(this), TIMELINE_DEBOUNCE_INTERVAL);
}
componentWillReceiveProps(props) {
this.setState(getTimestampStates(props.pausedAt));
}
componentWillUnmount() {
// TODO: Get rid of this somehow. See: https://github.com/weaveworks/service-ui/issues/814
this.props.resumeTime();
}
handleInputChange(ev) {
const timestamp = moment(ev.target.value);
this.setState({ inputValue: ev.target.value });
if (timestamp.isValid()) {
const clampedTimestamp = clampToNowInSecondsPrecision(timestamp);
this.instantUpdateTimestamp(clampedTimestamp, this.trackTimestampEdit);
}
}
handleTimelinePan(timestamp) {
this.setState(getTimestampStates(timestamp));
this.debouncedUpdateTimestamp(timestamp);
}
handleTimelinePanEnd(timestamp) {
this.instantUpdateTimestamp(timestamp, this.trackTimelinePan);
}
handleInstantJump(timestamp) {
this.instantUpdateTimestamp(timestamp, this.trackTimelineClick);
}
instantUpdateTimestamp(timestamp, callback) {
if (!timestamp.isSame(this.props.pausedAt)) {
this.debouncedUpdateTimestamp.cancel();
this.setState(getTimestampStates(timestamp));
this.props.timeTravelStartTransition();
this.props.jumpToTime(moment(timestamp));
// Used for tracking.
if (callback) callback();
}
}
trackTimestampEdit() {
trackMixpanelEvent('scope.time.timestamp.edit', {
layout: this.props.topologyViewMode,
topologyId: this.props.currentTopology.get('id'),
parentTopologyId: this.props.currentTopology.get('parentId'),
});
}
trackTimelineClick() {
trackMixpanelEvent('scope.time.timeline.click', {
layout: this.props.topologyViewMode,
topologyId: this.props.currentTopology.get('id'),
parentTopologyId: this.props.currentTopology.get('parentId'),
});
}
trackTimelinePan() {
trackMixpanelEvent('scope.time.timeline.pan', {
layout: this.props.topologyViewMode,
topologyId: this.props.currentTopology.get('id'),
parentTopologyId: this.props.currentTopology.get('parentId'),
});
}
render() {
return (
<div className={classNames('time-travel', { visible: this.props.showingTimeTravel })}>
<TimeTravelTimeline
onTimelinePan={this.handleTimelinePan}
onTimelinePanEnd={this.handleTimelinePanEnd}
onInstantJump={this.handleInstantJump}
/>
<div className="time-travel-timestamp">
<input
value={this.state.inputValue}
onChange={this.handleInputChange}
/> UTC
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
showingTimeTravel: state.get('showingTimeTravel'),
topologyViewMode: state.get('topologyViewMode'),
currentTopology: state.get('currentTopology'),
pausedAt: state.get('pausedAt'),
};
}
export default connect(
mapStateToProps,
{
jumpToTime,
resumeTime,
timeTravelStartTransition,
}
)(TimeTravel);