166 lines
3.0 KiB
JavaScript
166 lines
3.0 KiB
JavaScript
/* global window */
|
|
|
|
const React = require('react');
|
|
|
|
const r = require('r-dom');
|
|
|
|
const d3 = require('d3');
|
|
|
|
const { devicePixelRatio } = window;
|
|
|
|
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 / devicePixelRatio);
|
|
this.setState({
|
|
draggingX: clamp(this._offsetX * devicePixelRatio),
|
|
});
|
|
}
|
|
|
|
handleDrag() {
|
|
if (this.state.draggingX !== null) {
|
|
const draggingX = ((d3.event.x - this._startX) + this._offsetX) * devicePixelRatio;
|
|
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,
|
|
}),
|
|
]);
|
|
}
|
|
};
|