diff --git a/client/app/scripts/charts/edge-container.js b/client/app/scripts/charts/edge-container.js index 49e391d97..c410097a7 100644 --- a/client/app/scripts/charts/edge-container.js +++ b/client/app/scripts/charts/edge-container.js @@ -4,8 +4,9 @@ import { Repeat, fromJS, Map as makeMap } from 'immutable'; import { line, curveBasis } from 'd3-shape'; import { times } from 'lodash'; +import { weakSpring } from 'weaveworks-ui-components/lib/utils/animation'; + import { NODE_BASE_SIZE, EDGE_WAYPOINTS_CAP } from '../constants/styles'; -import { weakSpring } from '../utils/animation-utils'; import Edge from './edge'; diff --git a/client/app/scripts/charts/node-container.js b/client/app/scripts/charts/node-container.js index f8d9c389a..73b7ed74e 100644 --- a/client/app/scripts/charts/node-container.js +++ b/client/app/scripts/charts/node-container.js @@ -1,7 +1,8 @@ import React from 'react'; import { Motion } from 'react-motion'; -import { weakSpring } from '../utils/animation-utils'; +import { weakSpring } from 'weaveworks-ui-components/lib/utils/animation'; + import Node from './node'; diff --git a/client/app/scripts/components/app.js b/client/app/scripts/components/app.js index ceca9925e..332722a8e 100644 --- a/client/app/scripts/components/app.js +++ b/client/app/scripts/components/app.js @@ -35,8 +35,8 @@ import { } from '../actions/app-actions'; import Details from './details'; import Nodes from './nodes'; -import TimeTravel from './time-travel'; import TimeControl from './time-control'; +import TimeTravelWrapper from './time-travel-wrapper'; import ViewModeSelector from './view-mode-selector'; import NetworkSelector from './networks-selector'; import DebugToolbar, { showingDebugToolbar, toggleDebugToolbar } from './debug-toolbar'; @@ -85,7 +85,7 @@ class App extends React.Component { } componentWillUnmount() { - window.addEventListener('resize', this.handleResize); + window.removeEventListener('resize', this.handleResize); window.removeEventListener('keypress', this.onKeyPress); window.removeEventListener('keyup', this.onKeyUp); this.props.dispatch(shutdown()); @@ -191,7 +191,7 @@ class App extends React.Component { {showingDetails &&
}
- +
{!isIframe && diff --git a/client/app/scripts/components/time-travel-component.js b/client/app/scripts/components/time-travel-component.js deleted file mode 100644 index 3fa8c53bd..000000000 --- a/client/app/scripts/components/time-travel-component.js +++ /dev/null @@ -1,578 +0,0 @@ -import React from 'react'; -import moment from 'moment'; -import styled from 'styled-components'; -import { map, clamp, find, last, debounce } from 'lodash'; -import { drag } from 'd3-drag'; -import { scaleUtc } from 'd3-scale'; -import { event as d3Event, select } from 'd3-selection'; -import { Motion } from 'react-motion'; - -import { zoomFactor } from '../utils/zoom-utils'; -import { strongSpring } from '../utils/animation-utils'; -import { linearGradientValue } from '../utils/math-utils'; -import { - nowInSecondsPrecision, - clampToNowInSecondsPrecision, - scaleDuration, -} from '../utils/time-utils'; - - -const TICK_SETTINGS_PER_PERIOD = { - year: { - format: 'YYYY', - childPeriod: 'month', - intervals: [ - moment.duration(1, 'year'), - ], - }, - month: { - format: 'MMMM', - parentPeriod: 'year', - childPeriod: 'day', - intervals: [ - moment.duration(1, 'month'), - moment.duration(3, 'months'), - ], - }, - day: { - format: 'Do', - parentPeriod: 'month', - childPeriod: 'minute', - intervals: [ - moment.duration(1, 'day'), - moment.duration(1, 'week'), - ], - }, - minute: { - format: 'HH:mm', - parentPeriod: 'day', - intervals: [ - moment.duration(1, 'minute'), - moment.duration(5, 'minutes'), - moment.duration(15, 'minutes'), - moment.duration(1, 'hour'), - moment.duration(3, 'hours'), - moment.duration(6, 'hours'), - ], - }, -}; - -const ZOOM_TRACK_DEBOUNCE_INTERVAL = 5000; -const TIMELINE_DEBOUNCE_INTERVAL = 500; -const TIMELINE_TICK_INTERVAL = 1000; - -const TIMELINE_HEIGHT = '55px'; -const MIN_DURATION_PER_PX = moment.duration(250, 'milliseconds'); -const INIT_DURATION_PER_PX = moment.duration(1, 'minute'); -const MAX_DURATION_PER_PX = moment.duration(3, 'days'); -const MIN_TICK_SPACING_PX = 70; -const MAX_TICK_SPACING_PX = 415; -const FADE_OUT_FACTOR = 1.4; -const TICKS_ROW_SPACING = 16; -const MAX_TICK_ROWS = 3; - - -// From https://stackoverflow.com/a/18294634 -const FullyPannableCanvas = styled.svg` - width: 100%; - height: 100%; - cursor: move; - cursor: grab; - cursor: -moz-grab; - cursor: -webkit-grab; - - ${props => props.panning && ` - cursor: grabbing; - cursor: -moz-grabbing; - cursor: -webkit-grabbing; - `} -`; - -const TimeTravelContainer = styled.div` - transition: all .15s ease-in-out; - position: relative; - margin-bottom: 15px; - overflow: hidden; - z-index: 2001; - height: 0; - - ${props => props.visible && ` - height: calc(${TIMELINE_HEIGHT} + 35px); - margin-bottom: 15px; - margin-top: -5px; - `} -`; - -const TimelineContainer = styled.div` - align-items: center; - display: flex; - height: ${TIMELINE_HEIGHT}; - - &:before, &:after { - border: 1px solid ${props => props.theme.colors.white}; - background-color: ${props => props.theme.colors.accent.orange}; - content: ''; - position: absolute; - display: block; - left: 50%; - border-top: 0; - border-bottom: 0; - margin-left: -1px; - width: 3px; - } - - &:before { - top: 0; - height: ${TIMELINE_HEIGHT}; - } - - &:after { - top: ${TIMELINE_HEIGHT}; - height: 9px; - opacity: 0.15; - } -`; - -const Timeline = FullyPannableCanvas.extend` - background-color: rgba(255, 255, 255, 0.85); - box-shadow: inset 0 0 7px ${props => props.theme.colors.gray}; - pointer-events: all; - margin: 0 7px; -`; - -const DisabledRange = styled.rect` - fill: ${props => props.theme.colors.gray}; - fill-opacity: 0.15; -`; - -const ShallowButton = styled.button` - background-color: transparent; - border: 0; - color: ${props => props.theme.colors.primary.lavender}; - cursor: pointer; - padding: 0; - outline: 0; - - &:hover { - color: ${props => props.theme.colors.primary.charcoal}; - } -`; - -const TimestampLabel = ShallowButton.extend` - margin-left: 2px; - padding: 3px; - - &[disabled] { - color: ${props => props.theme.colors.gray}; - cursor: inherit; - } -`; - -const TimelinePanButton = ShallowButton.extend` - pointer-events: all; - padding: 2px; -`; - -const TimestampContainer = styled.div` - background-color: ${props => props.theme.colors.white}; - border: 1px solid ${props => props.theme.colors.gray}; - border-radius: 4px; - padding: 2px 8px; - pointer-events: all; - margin: 8px 0 25px 50%; - transform: translateX(-50%); - opacity: 0.8; - display: inline-block; -`; - -const TimestampInput = styled.input` - background-color: transparent; - font-family: "Roboto", sans-serif; - text-align: center; - font-size: 1rem; - width: 165px; - border: 0; - outline: 0; -`; - - -function getTimeScale({ focusedTimestamp, durationPerPixel }) { - const roundedTimestamp = moment(focusedTimestamp).utc().startOf('second'); - const startDate = moment(roundedTimestamp).subtract(durationPerPixel); - const endDate = moment(roundedTimestamp).add(durationPerPixel); - return scaleUtc() - .domain([startDate, endDate]) - .range([-1, 1]); -} - -function findOptimalDurationFit(durations, { durationPerPixel }) { - const minimalDuration = scaleDuration(durationPerPixel, 1.1 * MIN_TICK_SPACING_PX); - return find(durations, d => d >= minimalDuration); -} - -function getInputValue(timestamp) { - return { - inputValue: (timestamp ? moment(timestamp) : moment()).utc().format(), - }; -} - -export default class TimeTravelComponent extends React.Component { - constructor(props, context) { - super(props, context); - - this.state = { - timestampNow: nowInSecondsPrecision(), - focusedTimestamp: nowInSecondsPrecision(), - durationPerPixel: INIT_DURATION_PER_PX, - boundingRect: { width: 0, height: 0 }, - isPanning: false, - ...getInputValue(props.timestamp), - }; - - this.jumpRelativePixels = this.jumpRelativePixels.bind(this); - this.jumpForward = this.jumpForward.bind(this); - this.jumpBackward = this.jumpBackward.bind(this); - this.jumpTo = this.jumpTo.bind(this); - - this.handleZoom = this.handleZoom.bind(this); - this.handlePanStart = this.handlePanStart.bind(this); - this.handlePanEnd = this.handlePanEnd.bind(this); - this.handlePan = this.handlePan.bind(this); - - this.saveSvgRef = this.saveSvgRef.bind(this); - this.debouncedTrackZoom = debounce(this.trackZoom.bind(this), ZOOM_TRACK_DEBOUNCE_INTERVAL); - - 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.instantUpdateTimestamp = this.instantUpdateTimestamp.bind(this); - this.debouncedUpdateTimestamp = debounce( - this.instantUpdateTimestamp.bind(this), - TIMELINE_DEBOUNCE_INTERVAL - ); - } - - componentDidMount() { - this.svg = select('.time-travel-timeline svg'); - this.drag = drag() - .on('start', this.handlePanStart) - .on('end', this.handlePanEnd) - .on('drag', this.handlePan); - this.svg.call(this.drag); - - // Force periodic updates of the availability range as time goes by. - this.timer = setInterval(() => { - this.setState({ timestampNow: nowInSecondsPrecision() }); - }, TIMELINE_TICK_INTERVAL); - } - - componentWillUnmount() { - clearInterval(this.timer); - } - - componentWillReceiveProps(nextProps) { - // Update the input value - this.setState(getInputValue(nextProps.timestamp)); - // Don't update the focused timestamp if we're not paused (so the timeline is hidden). - if (nextProps.timestamp) { - this.setState({ focusedTimestamp: nextProps.timestamp }); - } - // Always update the timeline dimension information. - this.setState({ boundingRect: this.svgRef.getBoundingClientRect() }); - } - - 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.props.trackTimestampEdit); - } - } - - handleTimelinePan(timestamp) { - this.setState(getInputValue(timestamp)); - this.debouncedUpdateTimestamp(timestamp); - } - - handleTimelinePanEnd(timestamp) { - this.instantUpdateTimestamp(timestamp, this.props.trackTimelinePan); - } - - handleInstantJump(timestamp) { - this.instantUpdateTimestamp(timestamp, this.props.trackTimelineClick); - } - - handlePanStart() { - this.setState({ isPanning: true }); - } - - handlePanEnd() { - this.handleTimelinePanEnd(this.state.focusedTimestamp); - this.setState({ isPanning: false }); - } - - handlePan() { - const dragDuration = scaleDuration(this.state.durationPerPixel, -d3Event.dx); - const timestamp = moment(this.state.focusedTimestamp).add(dragDuration); - const focusedTimestamp = clampToNowInSecondsPrecision(timestamp); - this.handleTimelinePan(focusedTimestamp); - this.setState({ focusedTimestamp }); - } - - handleZoom(ev) { - let durationPerPixel = scaleDuration(this.state.durationPerPixel, 1 / zoomFactor(ev)); - if (durationPerPixel > MAX_DURATION_PER_PX) durationPerPixel = MAX_DURATION_PER_PX; - if (durationPerPixel < MIN_DURATION_PER_PX) durationPerPixel = MIN_DURATION_PER_PX; - - this.setState({ durationPerPixel }); - this.debouncedTrackZoom(); - ev.preventDefault(); - } - - instantUpdateTimestamp(timestamp, callback) { - if (!timestamp.isSame(this.props.timestamp)) { - this.debouncedUpdateTimestamp.cancel(); - this.setState(getInputValue(timestamp)); - this.props.changeTimestamp(moment(timestamp)); - - // Used for tracking. - if (callback) callback(); - } - } - - saveSvgRef(ref) { - this.svgRef = ref; - } - - trackZoom() { - const periods = ['years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds']; - const duration = scaleDuration(this.state.durationPerPixel, MAX_TICK_SPACING_PX); - const zoomedPeriod = find(periods, period => Math.floor(duration.get(period)) && period); - this.props.trackTimelineZoom(zoomedPeriod); - } - - jumpTo(timestamp) { - const focusedTimestamp = clampToNowInSecondsPrecision(timestamp); - this.handleInstantJump(focusedTimestamp); - this.setState({ focusedTimestamp }); - } - - jumpRelativePixels(pixels) { - const duration = scaleDuration(this.state.durationPerPixel, pixels); - const timestamp = moment(this.state.focusedTimestamp).add(duration); - this.jumpTo(timestamp); - } - - jumpForward() { - this.jumpRelativePixels(this.state.boundingRect.width / 4); - } - - jumpBackward() { - this.jumpRelativePixels(-this.state.boundingRect.width / 4); - } - - getVerticalShiftForPeriod(period, { durationPerPixel }) { - const { childPeriod, parentPeriod } = TICK_SETTINGS_PER_PERIOD[period]; - - let shift = 1; - if (parentPeriod) { - const durationMultiplier = 1 / MAX_TICK_SPACING_PX; - const parentPeriodStartInterval = TICK_SETTINGS_PER_PERIOD[parentPeriod].intervals[0]; - const fadedInDuration = scaleDuration(parentPeriodStartInterval, durationMultiplier); - const fadedOutDuration = scaleDuration(fadedInDuration, FADE_OUT_FACTOR); - - const durationLog = d => Math.log(d.asMilliseconds()); - const transitionFactor = durationLog(fadedOutDuration) - durationLog(durationPerPixel); - const transitionLength = durationLog(fadedOutDuration) - durationLog(fadedInDuration); - - shift = clamp(transitionFactor / transitionLength, 0, 1); - } - - if (childPeriod) { - shift += this.getVerticalShiftForPeriod(childPeriod, { durationPerPixel }); - } - - return shift; - } - - getTicksForPeriod(period, timelineTransform) { - // First find the optimal duration between the ticks - if no satisfactory - // duration could be found, don't render any ticks for the given period. - const { parentPeriod, intervals } = TICK_SETTINGS_PER_PERIOD[period]; - const duration = findOptimalDurationFit(intervals, timelineTransform); - if (!duration) return []; - - // Get the boundary values for the displayed part of the timeline. - const timeScale = getTimeScale(timelineTransform); - const startPosition = -this.state.boundingRect.width / 2; - const endPosition = this.state.boundingRect.width / 2; - const startDate = moment(timeScale.invert(startPosition)); - const endDate = moment(timeScale.invert(endPosition)); - - // Start counting the timestamps from the most recent timestamp that is not shown - // on screen. The values are always rounded up to the timestamps of the next bigger - // period (e.g. for days it would be months, for months it would be years). - let timestamp = moment(startDate).utc().startOf(parentPeriod || period); - while (timestamp.isBefore(startDate)) { - timestamp = moment(timestamp).add(duration); - } - timestamp = moment(timestamp).subtract(duration); - - // Make that hidden timestamp the first one in the list, but position - // it inside the visible range with a prepended arrow to the past. - const ticks = [{ - timestamp: moment(timestamp), - position: startPosition, - isBehind: true, - }]; - - // Continue adding ticks till the end of the visible range. - do { - // If the new timestamp enters into a new bigger period, we round it down to the - // beginning of that period. E.g. instead of going [Jan 22nd, Jan 29th, Feb 5th], - // we output [Jan 22nd, Jan 29th, Feb 1st]. Right now this case only happens between - // days and months, but in theory it could happen whenever bigger periods are not - // divisible by the duration we are using as a step between the ticks. - let newTimestamp = moment(timestamp).add(duration); - if (parentPeriod && newTimestamp.get(parentPeriod) !== timestamp.get(parentPeriod)) { - newTimestamp = moment(newTimestamp).utc().startOf(parentPeriod); - } - timestamp = newTimestamp; - - // If the new tick is too close to the previous one, drop that previous tick. - const position = timeScale(timestamp); - const previousPosition = last(ticks) && last(ticks).position; - if (position - previousPosition < MIN_TICK_SPACING_PX) { - ticks.pop(); - } - - ticks.push({ timestamp, position }); - } while (timestamp.isBefore(endDate)); - - return ticks; - } - - renderTimestampTick({ timestamp, position, isBehind }, periodFormat, opacity) { - // Ticks are disabled if they are in the future or if they are too transparent. - const disabled = timestamp.isAfter(this.state.timestampNow) || opacity < 0.4; - const handleClick = () => this.jumpTo(timestamp); - - return ( - - {!isBehind && } - {!disabled && Jump to {timestamp.utc().format()}} - - - {timestamp.utc().format(periodFormat)} - - - - ); - } - - renderPeriodTicks(period, timelineTransform) { - const periodFormat = TICK_SETTINGS_PER_PERIOD[period].format; - const ticks = this.getTicksForPeriod(period, timelineTransform); - - const ticksRow = MAX_TICK_ROWS - this.getVerticalShiftForPeriod(period, timelineTransform); - const transform = `translate(0, ${ticksRow * TICKS_ROW_SPACING})`; - - // Ticks quickly fade in from the bottom and then slowly start - // fading out towards the top until they are pushed out of canvas. - const focusedRow = MAX_TICK_ROWS - 1; - const opacity = ticksRow > focusedRow ? - linearGradientValue(ticksRow, [MAX_TICK_ROWS, focusedRow]) : - linearGradientValue(ticksRow, [-2, focusedRow]); - - return ( - - {map(ticks, tick => this.renderTimestampTick(tick, periodFormat, opacity))} - - ); - } - - renderDisabledShadow(timelineTransform) { - const timeScale = getTimeScale(timelineTransform); - const nowShift = timeScale(this.state.timestampNow); - const { width, height } = this.state.boundingRect; - - return ( - - ); - } - - renderAxis(timelineTransform) { - const { width, height } = this.state.boundingRect; - - return ( - - - {this.renderDisabledShadow(timelineTransform)} - - {this.renderPeriodTicks('year', timelineTransform)} - {this.renderPeriodTicks('month', timelineTransform)} - {this.renderPeriodTicks('day', timelineTransform)} - {this.renderPeriodTicks('minute', timelineTransform)} - - - ); - } - - renderAnimatedContent() { - const focusedTimestampValue = this.state.focusedTimestamp.valueOf(); - const durationPerPixelValue = this.state.durationPerPixel.asMilliseconds(); - - return ( - - {interpolated => this.renderAxis({ - focusedTimestamp: moment(interpolated.focusedTimestampValue), - durationPerPixel: moment.duration(interpolated.durationPerPixelValue), - })} - - ); - } - - render() { - const { isPanning, boundingRect } = this.state; - const halfWidth = boundingRect.width / 2; - - return ( - - - - - - - - Scroll to zoom, drag to pan - {this.renderAnimatedContent()} - - - - - - - - UTC - - - ); - } -} diff --git a/client/app/scripts/components/time-travel.js b/client/app/scripts/components/time-travel-wrapper.js similarity index 76% rename from client/app/scripts/components/time-travel.js rename to client/app/scripts/components/time-travel-wrapper.js index d52b4b5ab..992e362fa 100644 --- a/client/app/scripts/components/time-travel.js +++ b/client/app/scripts/components/time-travel-wrapper.js @@ -1,12 +1,13 @@ import React from 'react'; +import moment from 'moment'; import { connect } from 'react-redux'; +import { TimeTravel } from 'weaveworks-ui-components'; -import TimeTravelComponent from './time-travel-component'; import { trackAnalyticsEvent } from '../utils/tracking-utils'; import { jumpToTime } from '../actions/app-actions'; -class TimeTravel extends React.Component { +class TimeTravelWrapper extends React.Component { constructor(props, context) { super(props, context); @@ -55,18 +56,17 @@ class TimeTravel extends React.Component { } render() { - const { visible, timestamp, viewportWidth } = this.props; + const { visible, timestamp } = this.props; return ( - ); } @@ -78,12 +78,10 @@ function mapStateToProps(state) { topologyViewMode: state.get('topologyViewMode'), currentTopology: state.get('currentTopology'), timestamp: state.get('pausedAt'), - // Used only to trigger recalculations on window resize. - viewportWidth: state.getIn(['viewport', 'width']), }; } export default connect( mapStateToProps, { jumpToTime }, -)(TimeTravel); +)(TimeTravelWrapper); diff --git a/client/app/scripts/components/zoomable-canvas.js b/client/app/scripts/components/zoomable-canvas.js index d8c172b6d..6e12a3ea1 100644 --- a/client/app/scripts/components/zoomable-canvas.js +++ b/client/app/scripts/components/zoomable-canvas.js @@ -6,11 +6,11 @@ import { fromJS } from 'immutable'; import { drag } from 'd3-drag'; import { event as d3Event, select } from 'd3-selection'; +import { zoomFactor } from 'weaveworks-ui-components/lib/utils/zooming'; import Logo from '../components/logo'; import ZoomControl from '../components/zoom-control'; import { cacheZoomState } from '../actions/app-actions'; -import { zoomFactor } from '../utils/zoom-utils'; import { applyTransform, inverseTransform } from '../utils/transform-utils'; import { activeTopologyZoomCacheKeyPathSelector } from '../selectors/zooming'; import { diff --git a/client/app/scripts/reducers/root.js b/client/app/scripts/reducers/root.js index c669fa6dc..9bbe144f1 100644 --- a/client/app/scripts/reducers/root.js +++ b/client/app/scripts/reducers/root.js @@ -9,6 +9,8 @@ import { OrderedMap as makeOrderedMap, } from 'immutable'; +import { nowInSecondsPrecision } from 'weaveworks-ui-components/lib/utils/time'; + import ActionTypes from '../constants/action-types'; import { GRAPH_VIEW_MODE, @@ -20,7 +22,7 @@ import { } from '../selectors/topology'; import { isPausedSelector } from '../selectors/time-travel'; import { activeTopologyZoomCacheKeyPathSelector } from '../selectors/zooming'; -import { nowInSecondsPrecision, timestampsEqual } from '../utils/time-utils'; +import { timestampsEqual } from '../utils/time-utils'; import { applyPinnedSearches } from '../utils/search-utils'; import { findTopologyById, diff --git a/client/app/scripts/utils/animation-utils.js b/client/app/scripts/utils/animation-utils.js deleted file mode 100644 index 24498154c..000000000 --- a/client/app/scripts/utils/animation-utils.js +++ /dev/null @@ -1,10 +0,0 @@ -import { spring } from 'react-motion'; - - -export function weakSpring(value) { - return spring(value, { stiffness: 100, damping: 18, precision: 1 }); -} - -export function strongSpring(value) { - return spring(value, { stiffness: 800, damping: 50, precision: 1 }); -} diff --git a/client/app/scripts/utils/math-utils.js b/client/app/scripts/utils/math-utils.js index e2ca2512c..7840f4110 100644 --- a/client/app/scripts/utils/math-utils.js +++ b/client/app/scripts/utils/math-utils.js @@ -39,8 +39,3 @@ export function minEuclideanDistanceBetweenPoints(points) { }); return minDistance; } - -// A linear mapping [a, b] -> [0, 1] (maps value x=a into 0 and x=b into 1). -export function linearGradientValue(x, [a, b]) { - return (x - a) / (b - a); -} diff --git a/client/app/scripts/utils/time-utils.js b/client/app/scripts/utils/time-utils.js index e0b576488..f2474fb97 100644 --- a/client/app/scripts/utils/time-utils.js +++ b/client/app/scripts/utils/time-utils.js @@ -1,4 +1,3 @@ -import moment from 'moment'; // Replacement for timely dependency export function timer(fn) { @@ -11,20 +10,6 @@ export function timer(fn) { return timedFn; } -export function nowInSecondsPrecision() { - return moment().startOf('second'); -} - -export function clampToNowInSecondsPrecision(timestamp) { - const now = nowInSecondsPrecision(); - return timestamp.isAfter(now) ? now : timestamp; -} - -// This is unfortunately not there in moment.js -export function scaleDuration(duration, scale) { - return moment.duration(duration.asMilliseconds() * scale); -} - export function timestampsEqual(timestampA, timestampB) { const stringifiedTimestampA = timestampA ? timestampA.toISOString() : ''; const stringifiedTimestampB = timestampB ? timestampB.toISOString() : ''; diff --git a/client/app/scripts/utils/zoom-utils.js b/client/app/scripts/utils/zoom-utils.js deleted file mode 100644 index 1b58a99ae..000000000 --- a/client/app/scripts/utils/zoom-utils.js +++ /dev/null @@ -1,15 +0,0 @@ - -const ZOOM_SENSITIVITY = 0.0025; -const DOM_DELTA_LINE = 1; - -// See https://github.com/d3/d3-zoom/blob/807f02c7a5fe496fbd08cc3417b62905a8ce95fa/src/zoom.js -function wheelDelta(ev) { - // Only Firefox seems to use the line unit (which we assume to - // be 25px), otherwise the delta is already measured in pixels. - const unitInPixels = (ev.deltaMode === DOM_DELTA_LINE ? 25 : 1); - return -ev.deltaY * unitInPixels * ZOOM_SENSITIVITY; -} - -export function zoomFactor(ev) { - return Math.exp(wheelDelta(ev)); -} diff --git a/client/package.json b/client/package.json index f31ed083e..0afbe1190 100644 --- a/client/package.json +++ b/client/package.json @@ -44,7 +44,7 @@ "reselect": "3.0.1", "reselect-map": "1.0.3", "styled-components": "^2.2.1", - "weaveworks-ui-components": "git+https://github.com/weaveworks/ui-components.git#v0.1.45", + "weaveworks-ui-components": "git+https://github.com/weaveworks/ui-components.git#v0.1.47", "whatwg-fetch": "2.0.3", "xterm": "2.9.2" }, diff --git a/client/yarn.lock b/client/yarn.lock index f148c56af..d90dc1020 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -6666,9 +6666,9 @@ wd@^0.4.0: underscore.string "~3.0.3" vargs "~0.1.0" -"weaveworks-ui-components@git+https://github.com/weaveworks/ui-components.git#v0.1.45": - version "0.1.45" - resolved "git+https://github.com/weaveworks/ui-components.git#9b700e6231599d0f7b353d8e2bb5f466394dc081" +"weaveworks-ui-components@git+https://github.com/weaveworks/ui-components.git#v0.1.47": + version "0.1.47" + resolved "git+https://github.com/weaveworks/ui-components.git#1a112eef36f282d754502f77ab798381f5d3a995" dependencies: babel-cli "^6.18.0" babel-plugin-transform-export-extensions "6.8.0" @@ -6676,9 +6676,14 @@ wd@^0.4.0: babel-preset-es2015 "6.18.0" babel-preset-react "6.16.0" classnames "^2.2.5" + d3-drag "1.2.1" + d3-scale "1.0.6" + d3-selection "1.1.0" + moment "2.19.1" node-sass "4.5.3" polished "^1.7.0" prop-types "^15.5.8" + react-motion "0.5.2" webidl-conversions@^3.0.0: version "3.0.1"