UNPKG

terriajs

Version:

Geospatial data visualization platform.

181 lines (146 loc) 7.06 kB
import React from 'react'; import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import Icon from '../Icon.jsx'; import Styles from './splitter.scss'; import ObserveModelMixin from '../ObserveModelMixin'; // Feature detect support for passive: true in event subscriptions. // See https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support let passiveSupported = false; try { const options = Object.defineProperty({}, 'passive', { get: function () { passiveSupported = true; } }); window.addEventListener('test', null, options); window.removeEventListener('test', null, options); } catch (err) { } const notPassive = passiveSupported ? { passive: false } : false; const Splitter = createReactClass({ displayName: 'Splitter', mixins: [ObserveModelMixin], propTypes: { terria: PropTypes.object.isRequired, thumbSize: PropTypes.number, padding: PropTypes.number }, getDefaultProps() { return { thumbSize: 42, padding: 0 }; }, componentDidMount() { const that = this; window.addEventListener('resize', function() {that.forceRefresh();}); }, componentWillUnmount() { this.unsubscribe(); }, forceRefresh() { const smallChange = (this.props.terria.splitPosition < 0.5) ? 0.0001 : -0.0001; // Make sure never <0 or >1. this.props.terria.splitPosition += smallChange; }, startDrag(event) { const viewer = this.props.terria.currentViewer; viewer.pauseMapInteraction(); // While dragging is in progress, subscribe to document-level movement and up events. document.addEventListener('mousemove', this.drag, notPassive); document.addEventListener('touchmove', this.drag, notPassive); document.addEventListener('mouseup', this.stopDrag, notPassive); document.addEventListener('touchend', this.stopDrag, notPassive); event.preventDefault(); event.stopPropagation(); }, drag(event) { let clientX = event.clientX; let clientY = event.clientY; if (event.targetTouches && event.targetTouches.length > 0) { clientX = event.targetTouches.item(0).clientX; clientY = event.targetTouches.item(0).clientY; } const viewer = this.props.terria.currentViewer; const container = viewer.getContainer(); const mapRect = container.getBoundingClientRect(); const that = this; function computeSplitFraction(startBound, endBound, position) { const difference = endBound - startBound; const fraction = (position - startBound) / difference; const min = startBound + that.props.padding + (that.props.thumbSize * 0.5); const max = endBound - that.props.padding - (that.props.thumbSize * 0.5); const minFraction = (min - startBound) / difference; const maxFraction = (max - startBound) / difference; return Math.min(maxFraction, Math.max(minFraction, fraction)); } let splitFractionX = computeSplitFraction(mapRect.left, mapRect.right, clientX); let splitFractionY = computeSplitFraction(mapRect.top, mapRect.bottom, clientY); // We compute the maximum and minium windows bounds as a percentage so that we can always apply the bounds // restriction as a percentage for consistency (we currently use absolute values for X and percentage values for // Y, but always apply the constraint as a percentage). // We use absolute pixel values for horizontal restriction because of the fixed UI elements which occupy an // absolute amount of screen relestate and 100 px seems like a fine amount for the current UI. const minX = computeSplitFraction(mapRect.left, mapRect.right, mapRect.left + 100); const maxX = computeSplitFraction(mapRect.left, mapRect.right, mapRect.right - 100); // Resctrict to within +/-30% of the center vertically (so we don't run into the top and bottom UI elements). const minY = 0.20; const maxY = 0.80; splitFractionX = Math.min(maxX, Math.max(minX, splitFractionX)); splitFractionY = Math.min(maxY, Math.max(minY, splitFractionY)); this.props.terria.splitPosition = splitFractionX; this.props.terria.splitPositionVertical = splitFractionY; event.preventDefault(); event.stopPropagation(); }, stopDrag(event) { this.unsubscribe(); const viewer = this.props.terria.currentViewer; viewer.resumeMapInteraction(); event.preventDefault(); event.stopPropagation(); }, unsubscribe() { document.removeEventListener('mousemove', this.drag, notPassive); document.removeEventListener('touchmove', this.drag, notPassive); document.removeEventListener('mouseup', this.stopDrag, notPassive); document.removeEventListener('touchend', this.stopDrag, notPassive); window.removeEventListener('resize', this.forceRefresh); }, getPosition() { const canvasWidth = this.props.terria.currentViewer.getContainer().clientWidth; const canvasHeight = this.props.terria.currentViewer.getContainer().clientHeight; return {x: this.props.terria.splitPosition * canvasWidth, y: this.props.terria.splitPositionVertical * canvasHeight}; }, render() { if (!this.props.terria.showSplitter || !this.props.terria.currentViewer.canShowSplitter || !this.props.terria.currentViewer.getContainer()) { return null; } const thumbWidth = this.props.thumbSize; const position = this.getPosition(); const dividerStyle = { left: position.x + 'px', backgroundColor: this.props.terria.baseMapContrastColor }; const thumbStyle = { left: position.x + 'px', top: position.y + 'px', width: thumbWidth + 'px', height: thumbWidth + 'px', marginLeft: '-' + (thumbWidth * 0.5) + 'px', marginTop: '-' + (thumbWidth * 0.5) + 'px', lineHeight: (thumbWidth - 2) + 'px', borderRadius: (thumbWidth * 0.5) + 'px', fontSize: (thumbWidth - 12) + 'px' }; return ( <div> <div className={Styles.dividerWrapper}> <div className={Styles.divider} style={dividerStyle}></div> </div> <div className={Styles.thumb} style={thumbStyle} onMouseDown={this.startDrag} onTouchStart={this.startDrag}><Icon glyph={Icon.GLYPHS.splitter}/></div> </div> ); } }); module.exports = Splitter;