piano/js/components/UI/Scroll.js

157 lines
4.2 KiB
JavaScript

/*jshint browser:true */
/*globals React, App */
(function () {
'use strict';
/**
* Display a scrollbar
*
* @prop {current: int} Current position
* @prop {max: int} Maximum number we can reach
* @prop {onChange: function} Called on scrolling attempt
* @prop {onDragStart: function} Called on start of scrolling
* @prop {onDragEnd: function} Called on end of scrolling
*/
App.components.create('UI', {
displayName: 'Scroll',
mixins: [React.addons.PureRenderMixin],
position: null,
dragging: false,
propTypes: {
current: React.PropTypes.number,
max: React.PropTypes.number,
onChange: React.PropTypes.func,
onDragStart: React.PropTypes.func,
onDragEnd: React.PropTypes.func
},
getDefaultProps: function () {
return {
current: 0,
max: 0,
onChange: function () {},
onDragStart: function () {},
onDragEnd: function () {}
};
},
componentDidMount: function () {
window.addEventListener('mousemove', this.mouseMove);
window.addEventListener('mouseup', this.mouseUp);
},
componentWillUnmount: function () {
window.removeEventListener('mousemove', this.mouseMove);
window.removeEventListener('mouseup', this.mouseUp);
},
/**
* Convert distance to value
*/
convert: function (input) {
return input * this.props.max / this.getDOMNode().clientHeight;
},
/**
* Convert value to distance
*/
reverseConvert: function (output) {
return output * this.getDOMNode().clientHeight / this.props.max;
},
/**
* Scroll by given position
*
* @param {position: int} Current position
*/
scrollBy: function (position) {
var current = this.props.max - this.props.current,
delta = this.convert(position - this.reverseConvert(current));
this.props.onChange(delta);
},
/**
* Start scroll dragging
*/
mouseDown: function (e) {
if (e.button !== 0) {
return;
}
this.position = e.clientY;
this.props.onDragStart();
},
/**
* Move scroll
*/
mouseMove: function (e) {
if (this.position === null) {
return;
}
if (Math.abs(e.clientY - this.position) >= 3) {
this.dragging = true;
}
if (!this.dragging) {
return;
}
this.scrollBy(e.clientY);
},
/**
* Mouse release
*/
mouseUp: function (e) {
if (this.dragging) {
this.props.onDragEnd();
this.dragging = false;
this.position = null;
}
},
/**
* Click on track
*/
click: function (e) {
if (e.button === 0) {
this.scrollBy(e.clientY);
}
},
/**
* Render scroll
*/
render: function () {
var bottom;
if (this.props.max === 0) {
bottom = '-100%';
} else {
bottom = 'calc(100% / ' + this.props.max + ' * ' +
this.props.current + ')';
}
return React.DOM.div({
className: 'scroll',
onClick: this.click
}, [
React.DOM.span({
key: 'position',
className: 'position',
onMouseDown: this.mouseDown,
style: {
bottom: bottom
}
})
]);
}
});
}());