import React from 'react'; import { connect } from 'react-redux'; import { debounce, pick } from 'lodash'; import { fromJS } from 'immutable'; import { event as d3Event, select } from 'd3-selection'; import { zoom, zoomIdentity } from 'd3-zoom'; import Logo from '../components/logo'; import ZoomControl from '../components/zoom-control'; import { cacheZoomState } from '../actions/app-actions'; import { transformToString } from '../utils/transform-utils'; import { activeTopologyZoomCacheKeyPathSelector } from '../selectors/zooming'; import { canvasMarginsSelector, canvasWidthSelector, canvasHeightSelector, } from '../selectors/canvas'; import { ZOOM_CACHE_DEBOUNCE_INTERVAL } from '../constants/timer'; class ZoomableCanvas extends React.Component { constructor(props, context) { super(props, context); this.state = { minTranslateX: 0, maxTranslateX: 0, minTranslateY: 0, maxTranslateY: 0, translateX: 0, translateY: 0, minScale: 1, maxScale: 1, scaleX: 1, scaleY: 1, }; this.debouncedCacheZoom = debounce(this.cacheZoom.bind(this), ZOOM_CACHE_DEBOUNCE_INTERVAL); this.handleZoomControlAction = this.handleZoomControlAction.bind(this); this.canChangeZoom = this.canChangeZoom.bind(this); this.zoomed = this.zoomed.bind(this); } componentDidMount() { this.zoomRestored = false; this.zoom = zoom().on('zoom', this.zoomed); this.svg = select('svg#canvas'); this.setZoomTriggers(!this.props.disabled); this.updateZoomLimits(this.props); this.restoreZoomState(this.props); } componentWillUnmount() { this.setZoomTriggers(false); this.debouncedCacheZoom.cancel(); } componentWillReceiveProps(nextProps) { const layoutChanged = nextProps.layoutId !== this.props.layoutId; const disabledChanged = nextProps.disabled !== this.props.disabled; // If the layout has changed (either active topology or its options) or // relayouting has been requested, stop pending zoom caching event and // ask for the new zoom settings to be restored again from the cache. if (layoutChanged || nextProps.forceRelayout) { this.debouncedCacheZoom.cancel(); this.zoomRestored = false; } // If the zooming has been enabled/disabled, update its triggers. if (disabledChanged) { this.setZoomTriggers(!nextProps.disabled); } this.updateZoomLimits(nextProps); if (!this.zoomRestored) { this.restoreZoomState(nextProps); } } handleZoomControlAction(scale) { // Update the canvas scale (not touching the translation). this.svg.call(this.zoom.scaleTo, scale); // Update the scale state and propagate to the global cache. this.setState(this.cachableState({ scaleX: scale, scaleY: scale, })); this.debouncedCacheZoom(); } render() { // `forwardTransform` says whether the zoom transform is forwarded to the child // component. The advantage of that is more control rendering control in the // children, while the disadvantage is that it's slower, as all the children // get updated on every zoom/pan action. const { children, forwardTransform } = this.props; const transform = forwardTransform ? '' : transformToString(this.state); return (