Spaces:
				
			
			
	
			
			
		Runtime error
		
	
	
	
			
			
	
	
	
	
		
		
		Runtime error
		
	File size: 5,619 Bytes
			
			6bcb42f  | 
								1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157  | 
								import PropTypes from 'prop-types';
import bindAll from 'lodash.bindall';
import React from 'react';
import {getEventXY} from '../../lib/touch-utils';
import styles from './dial.css';
import dialFace from './icon--dial.svg';
import dialHandle from './icon--handle.svg';
class Dial extends React.Component {
    constructor (props) {
        super(props);
        bindAll(this, [
            'handleMouseDown',
            'handleMouseMove',
            'containerRef',
            'handleRef',
            'unbindMouseEvents'
        ]);
    }
    componentDidMount () {
        // Manually add touch/mouse handlers so that preventDefault can be used
        // to prevent scrolling on touch.
        // Tracked as a react issue https://github.com/facebook/react/issues/6436
        this.handleElement.addEventListener('mousedown', this.handleMouseDown);
        this.handleElement.addEventListener('touchstart', this.handleMouseDown);
    }
    componentWillUnmount () {
        this.unbindMouseEvents();
        this.handleElement.removeEventListener('mousedown', this.handleMouseDown);
        this.handleElement.removeEventListener('touchstart', this.handleMouseDown);
    }
    /**
     * Get direction from dial center to mouse move event.
     * @param {Event} e - Mouse move event.
     * @returns {number} Direction in degrees, clockwise, 90=horizontal.
     */
    directionToMouseEvent (e) {
        const {x: mx, y: my} = getEventXY(e);
        const bbox = this.containerElement.getBoundingClientRect();
        const cy = bbox.top + (bbox.height / 2);
        const cx = bbox.left + (bbox.width / 2);
        const angle = Math.atan2(my - cy, mx - cx);
        const degrees = angle * (180 / Math.PI);
        return degrees + 90; // To correspond with scratch coordinate system
    }
    /**
     * Create SVG path data string for the dial "gauge", the overlaid arc slice.
     * @param {number} radius - The radius of the dial.
     * @param {number} direction - Direction in degrees, clockwise, 90=horizontal.
     * @returns {string} Path data string for the gauge.
     */
    gaugePath (radius, direction) {
        const rads = (direction) * (Math.PI / 180);
        const path = [];
        path.push(`M ${radius} 0`);
        path.push(`L ${radius} ${radius}`);
        path.push(`L ${radius + (radius * Math.sin(rads))} ${radius - (radius * Math.cos(rads))}`);
        path.push(`A ${radius} ${radius} 0 0 ${direction < 0 ? 1 : 0} ${radius} 0`);
        path.push(`Z`);
        return path.join(' ');
    }
    handleMouseMove (e) {
        this.props.onChange(this.directionToMouseEvent(e) + this.directionOffset);
        e.preventDefault();
    }
    unbindMouseEvents () {
        window.removeEventListener('mousemove', this.handleMouseMove);
        window.removeEventListener('mouseup', this.unbindMouseEvents);
        window.removeEventListener('touchmove', this.handleMouseMove);
        window.removeEventListener('touchend', this.unbindMouseEvents);
    }
    handleMouseDown (e) {
        // Because the drag handle is not a single point, there is some initial
        // difference between the current sprite direction and the direction to the mouse
        // Store this offset to prevent jumping when the mouse is moved.
        this.directionOffset = this.props.direction - this.directionToMouseEvent(e);
        window.addEventListener('mousemove', this.handleMouseMove);
        window.addEventListener('mouseup', this.unbindMouseEvents);
        window.addEventListener('touchmove', this.handleMouseMove);
        window.addEventListener('touchend', this.unbindMouseEvents);
        e.preventDefault();
    }
    containerRef (el) {
        this.containerElement = el;
    }
    handleRef (el) {
        this.handleElement = el;
    }
    render () {
        const {direction, radius} = this.props;
        return (
            <div className={styles.container}>
                <div
                    className={styles.dialContainer}
                    ref={this.containerRef}
                    style={{
                        width: `${radius * 2}px`,
                        height: `${radius * 2}px`
                    }}
                >
                    <img
                        className={styles.dialFace}
                        draggable={false}
                        src={dialFace}
                    />
                    <svg
                        className={styles.gauge}
                        height={radius * 2}
                        width={radius * 2}
                    >
                        <path
                            className={styles.gaugePath}
                            d={this.gaugePath(radius, direction)}
                        />
                    </svg>
                    <img
                        className={styles.dialHandle}
                        draggable={false}
                        ref={this.handleRef}
                        src={dialHandle}
                        style={{
                            top: `${radius - (radius * Math.cos(direction * (Math.PI / 180)))}px`,
                            left: `${radius + (radius * Math.sin(direction * (Math.PI / 180)))}px`,
                            transform: `rotate(${direction}deg)`
                        }}
                    />
                </div>
            </div>
        );
    }
}
Dial.propTypes = {
    direction: PropTypes.number,
    onChange: PropTypes.func.isRequired,
    radius: PropTypes.number
};
Dial.defaultProps = {
    direction: 90, // degrees
    radius: 56 // px
};
export default Dial;
 |