const React = require('react'); const r = require('r-dom'); const d3 = require('d3'); const width = 300; const height = 18; const clamp = x => Math.min( width - (height / 2), Math.max( (height / 2), x, ), ); const vol2pix = (v, maxVolume) => (v / maxVolume) * (width - height); const pix2vol = (x, maxVolume) => (x * maxVolume) / (width - height); module.exports = class VolumeSlider extends React.Component { constructor(props) { super(props); this.svg = React.createRef(); this.state = { draggingX: null, }; Object.assign(this, { handleDragStart: this.handleDragStart.bind(this), handleDrag: this.handleDrag.bind(this), handleDragEnd: this.handleDragEnd.bind(this), }); } componentDidMount() { const dragFunction = d3 .drag() .on('start', this.handleDragStart) .on('drag', this.handleDrag) .on('end', this.handleDragEnd); this._selection = d3 .select(this.svg.current.querySelector('.volume-slider-handle')) .call(dragFunction); } componentWillUnmount() { this._selection.on('.node', null); } handleDragStart() { this._startX = d3.event.x; this._offsetX = d3.event.sourceEvent.offsetX || (this._lastRenderedX); this.setState({ draggingX: clamp(this._offsetX), }); } handleDrag() { if (this.state.draggingX !== null) { const draggingX = ((d3.event.x - this._startX) + this._offsetX); this.setState({ draggingX: clamp(draggingX), }); } } handleDragEnd() { this.setState({ draggingX: null, }); } componentDidUpdate() { const { draggingX } = this.state; const { maxVolume } = this.props; if (draggingX === null) { return; } const targetValue = Math.floor(pix2vol(draggingX - (height / 2), maxVolume)); this.props.onChange(targetValue); } render() { const { muted, baseVolume, normVolume, maxVolume, value, } = this.props; const { draggingX, } = this.state; const x = draggingX === null ? ((height / 2) + vol2pix(value, maxVolume)) : draggingX; this._lastRenderedX = x; const baseX = (height / 2) + vol2pix(baseVolume, maxVolume); const normX = (height / 2) + vol2pix(normVolume, maxVolume); return r.svg({ ref: this.svg, classSet: { 'volume-slider': true, 'volume-slider-muted': muted, }, width, height, }, [ baseVolume && r.line({ className: 'volume-slider-base-mark', x1: baseX, x2: baseX, y1: 0, y2: height, }), r.line({ className: 'volume-slider-norm-mark', x1: normX, x2: normX, y1: 0, y2: height, }), r.line({ className: 'volume-slider-bg', x1: height / 2, x2: width - (height / 2), y1: height / 2, y2: height / 2, }), !muted && r.line({ className: 'volume-slider-fill', x1: height / 2, x2: x, y1: height / 2, y2: height / 2, }), r.circle({ className: 'volume-slider-handle', cx: x, cy: height / 2, r: (height - 2) / 2, }), ]); } };