@git-temporal/git-temporal-react
Version:
<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
272 lines (271 loc) • 12.1 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const react_1 = __importDefault(require("react"));
const react_redux_1 = require("react-redux");
const lodash_1 = require("lodash");
const logger_1 = require("app/utilities/logger");
const styles_1 = require("app/styles");
const selectors_1 = require("app/selectors");
const setDates_1 = require("app/actions/setDates");
const commits_1 = require("app/actions/commits");
const debounce_1 = require("app/utilities/debounce");
const throttle_1 = require("app/utilities/throttle");
const ZoomContainer_1 = require("app/components/ZoomContainer");
const TimeplotGraph_1 = require("app/components/TimeplotGraph");
const EpochSpan_1 = require("app/components/EpochSpan");
const CommaNumber_1 = require("app/components/CommaNumber");
const SpinnerContainer_1 = require("app/components/SpinnerContainer");
const TimeplotPopup_1 = require("app/components/TimeplotPopup");
const commits_2 = require("app/utilities/commits");
const initialState = {
hoverMarkerLeft: -40,
scrollLeft: 0,
popupOpen: false,
timeplotRenders: 0,
popupCommits: [],
popupStartDate: null,
popupEndDate: null,
zoom: 100,
customZooms: [],
mouseInPopover: false,
};
const GRAPH_HEIGHT = 120;
const outerStyle = {
overflow: 'visible',
flex: `0 0 ${GRAPH_HEIGHT}px`,
};
const graphContainerStyle = {
_extends: ['altPanel', 'flexColumn'],
flex: `0 0 ${GRAPH_HEIGHT}px`,
minHeight: `${GRAPH_HEIGHT}px`,
position: 'relative',
marginTop: 5,
overflow: 'hidden',
};
const statsStyle = {
_extends: 'normalText',
width: '100%',
textAlign: 'center',
paddingTop: '@margins.small+px',
paddingBottom: '@margins.small+px',
position: 'absolute',
top: '@margins.medium+px',
textShadow: '2px 2px @colors.background',
};
const statsTextStyle = {
opacity: 0.8,
backgroundColor: '@colors.background',
};
const zoomStyle = {
position: 'absolute',
top: '@margins.small+px',
zIndex: 1,
};
const timeplotStyle = {
_extends: 'fill',
background: '@colors.background',
color: '@colors.text',
};
const markerStyle = {
position: 'absolute',
height: GRAPH_HEIGHT,
width: 10,
opacity: 0.5,
zIndex: 1,
top: 0,
};
const hoverMarkerStyle = {
_extends: markerStyle,
backgroundColor: '@colors.selectable',
};
class Timeplot extends react_1.default.Component {
constructor(props) {
super(props);
this.state = initialState;
this.onPopupClose = () => {
this.setState({ popupOpen: false });
};
this.onScroll = scrollLeft => {
this.setState({ scrollLeft });
};
this.onZoom = newZoom => {
this.setState({
timeplotRenders: this.state.timeplotRenders + 1,
zoom: newZoom,
});
};
this.onMouseEnterPopup = evt => {
this.setState({ mouseInPopover: true });
};
this.onMouseLeavePopup = evt => {
this.setState({ mouseInPopover: false, popupOpen: false });
};
this.onMouseLeave = _evt => {
lodash_1.delay(() => {
if (!this.state.mouseInPopover) {
this.setState({ popupOpen: false });
}
}, 250);
};
this.onMouseMove = (evt, { startDate, endDate, relativeLeft }) => {
const popupCommits = commits_2.filterCommitsForSpan(this.props.commits, startDate, endDate);
const { pageX, pageY } = evt;
if (!this.lastMouseMoveCoords || pageY >= this.lastMouseMoveCoords.pageY) {
this.setState({
popupCommits,
popupOpen: true,
popupStartDate: startDate,
popupEndDate: endDate,
hoverMarkerLeft: relativeLeft + 3,
});
}
this.lastMouseMoveCoords = { pageX, pageY };
};
this.onMouseDown = (evt, { startDate }) => {
logger_1.debug('Timeplot: onMouseDown', evt.shiftKey, startDate);
evt.preventDefault();
this.lastMouseDownDate = startDate;
const endDate = this.getEndDateForEvent(evt);
this.setDates(startDate, endDate);
};
this.onMouseUp = (evt, { startDate }) => {
logger_1.debug('Timeplot: onMouseUp', evt.shiftKey, startDate, this.lastMouseDownDate);
evt.preventDefault();
if (!this.lastMouseDownDate) {
return;
}
if (startDate.toUTCString() !== this.lastMouseDownDate.toUTCString()) {
this.setDates(this.lastMouseDownDate, startDate);
}
this.lastMouseDownDate = null;
};
this.onMouseHoverMarker = evt => {
this.setState({
hoverMarkerLeft: this.state.hoverMarkerLeft + (evt.pageX - evt.clientX) + 3,
});
};
this.onCommitSelected = (evt, commit, single) => {
evt.stopPropagation();
const { dispatch, commits } = this.props;
if (single) {
dispatch(commits_1.selectSingleCommit(commit));
}
else {
const epochAuthorDate = commit.authorDate * 1000;
const endDate = this.getEndDateForEvent(evt);
this.setDates(epochAuthorDate, endDate);
}
};
this.lastRerenderRequestedAt = Date.now();
this.timeplotRef = react_1.default.createRef();
this.outerRef = react_1.default.createRef();
this.debouncedOnMouseLeave = debounce_1.debounce(this.onMouseLeave, 100);
this.debouncedOnMouseMove = throttle_1.throttle(this.onMouseMove, 100);
}
componentDidUpdate(prevProps, prevState) {
if (!this.timeplotRef.current) {
return;
}
this.timeplotRef.current.focus();
if (prevProps.startDate !== this.props.startDate ||
prevProps.endDate !== this.props.endDate) {
this.addCustomZooms();
}
if (this.lastRerenderRequestedAt < this.props.rerenderRequestedAt) {
logger_1.debug('forcing timeplot rerender');
this.lastRerenderRequestedAt = this.props.rerenderRequestedAt;
this.setState({ timeplotRenders: this.state.timeplotRenders + 1 });
}
if (prevState.zoom !== this.state.zoom) {
this.scrollToStartDate();
}
}
render() {
const { commits = [], isFetching, startDate, endDate, earliestCommitDate, latestCommitDate, highlightedCommitIds, totalCommits, } = this.props;
const outerLeft = (this.outerRef.current &&
this.outerRef.current.getBoundingClientRect().x) ||
0;
const popupLeft = this.state.hoverMarkerLeft - this.state.scrollLeft <
TimeplotPopup_1.TIMEPLOT_POPUP_WIDTH + 20
?
this.state.hoverMarkerLeft - this.state.scrollLeft + outerLeft - 20
:
this.state.hoverMarkerLeft -
this.state.scrollLeft -
TimeplotPopup_1.TIMEPLOT_POPUP_WIDTH +
outerLeft +
20;
return (react_1.default.createElement("div", { style: styles_1.style(outerStyle), ref: this.outerRef },
react_1.default.createElement("div", { style: styles_1.style(graphContainerStyle) },
react_1.default.createElement(SpinnerContainer_1.SpinnerContainer, { isSpinning: !commits || commits.length === 0, spinnerImageSize: 0 },
react_1.default.createElement(ZoomContainer_1.ZoomContainer, { onZoom: this.onZoom, onMouseLeave: this.debouncedOnMouseLeave, onScroll: this.onScroll, customZooms: this.state.customZooms, scrollLeft: this.state.scrollLeft, style: styles_1.style(zoomStyle) },
react_1.default.createElement(TimeplotGraph_1.TimeplotGraph, { forceRender: this.state.timeplotRenders, commits: this.props.commits, style: styles_1.style(timeplotStyle), ref: this.timeplotRef, height: GRAPH_HEIGHT, highlightedCommitIds: highlightedCommitIds, startDate: startDate, endDate: endDate, earliestCommitDate: earliestCommitDate, latestCommitDate: latestCommitDate, onMouseMove: this.debouncedOnMouseMove, onMouseDown: this.onMouseDown, onMouseUp: this.onMouseUp }),
react_1.default.createElement("div", { style: styles_1.style(hoverMarkerStyle, {
left: this.state.hoverMarkerLeft,
}), onMouseMove: this.onMouseHoverMarker, onMouseEnter: this.onMouseHoverMarker })),
react_1.default.createElement("div", { style: styles_1.style(statsStyle) },
react_1.default.createElement("span", { style: styles_1.style(statsTextStyle) },
!isFetching ? 'Total of ' : 'Retrieving ',
react_1.default.createElement(CommaNumber_1.CommaNumber, { value: commits.length }),
isFetching && (react_1.default.createElement("span", null,
' ',
"of ",
react_1.default.createElement(CommaNumber_1.CommaNumber, { value: totalCommits }))),
react_1.default.createElement("span", null, " commits by "),
react_1.default.createElement(CommaNumber_1.CommaNumber, { value: this.props.authors }),
react_1.default.createElement("span", null, " authors spanning "),
react_1.default.createElement(EpochSpan_1.EpochSpan, { firstEpochTime: earliestCommitDate, secondEpochTime: latestCommitDate }))))),
react_1.default.createElement(TimeplotPopup_1.TimeplotPopup, { commits: this.state.popupCommits, isOpen: this.state.popupOpen, startDate: this.state.popupStartDate, endDate: this.state.popupEndDate, left: popupLeft, onClose: this.onPopupClose, onCommitSelected: this.onCommitSelected, onMouseEnter: this.onMouseEnterPopup, onMouseLeave: this.onMouseLeavePopup })));
}
setDates(startDate, endDate) {
const { dispatch } = this.props;
dispatch(setDates_1.setDates(startDate, endDate));
}
scrollToStartDate() {
const { startDate } = this.props;
if (this.state.zoom <= 100) {
this.setState({ scrollLeft: 0 });
}
else if (startDate && this.timeplotRef.current) {
const { xScale } = this.timeplotRef.current;
this.setState({ scrollLeft: xScale(startDate * 1000) - 30 });
}
}
addCustomZooms() {
const { startDate, endDate } = this.props;
if (!startDate || !this.timeplotRef.current) {
this.setState({ customZooms: [] });
return;
}
const { xScale, getScrollWidth } = this.timeplotRef.current;
const defaultedEndDate = endDate ? endDate * 1000 : new Date();
const spanLeft = xScale(startDate * 1000);
const spanRight = xScale(defaultedEndDate);
const span = spanRight - spanLeft + 15;
if (span <= 0) {
this.setState({ customZooms: [] });
return;
}
const scrollWidth = getScrollWidth();
this.setState({
customZooms: [
{
value: Math.floor((scrollWidth / span) * 100),
label: 'Zoom to selected time span',
},
],
});
}
getEndDateForEvent(evt) {
return evt.shiftKey && this.props.endDate
? this.props.endDate * 1000
: evt.shiftKey && this.props.startDate
? this.props.startDate * 1000
: null;
}
}
exports.Timeplot = Timeplot;
exports.default = react_redux_1.connect(selectors_1.getTimeplotContainerState)(Timeplot);