Rift.IO OSM R1 Initial Submission
[osm/UI.git] / skyquake / framework / widgets / input-range-slider / react-rangeslider.jsx
diff --git a/skyquake/framework/widgets/input-range-slider/react-rangeslider.jsx b/skyquake/framework/widgets/input-range-slider/react-rangeslider.jsx
new file mode 100644 (file)
index 0000000..feff1eb
--- /dev/null
@@ -0,0 +1,251 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+/**
+ * https://github.com/larsmaultsby/react-rangeslider
+ *
+ *
+ * Forked from: https://github.com/whoisandie/react-rangeslider
+ *
+ *
+    The MIT License (MIT)
+
+    Copyright (c) 2015 Bhargav Anand
+
+    Permission is hereby granted, free of charge, to any person obtaining a copy
+    of this software and associated documentation files (the "Software"), to deal
+    in the Software without restriction, including without limitation the rights
+    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+    copies of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be included in all
+    copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+    SOFTWARE.
+ *
+ */
+
+import React, { PropTypes, Component, findDOMNode } from 'react';
+import joinClasses from 'react/lib/joinClasses';
+
+function capitalize(str) {
+  return str.charAt(0).toUpperCase() + str.substr(1);
+}
+
+function maxmin(pos, min, max) {
+  if (pos < min) { return min; }
+  if (pos > max) { return max; }
+  return pos;
+}
+
+const constants = {
+  orientation: {
+    horizontal: {
+      dimension: 'width',
+      direction: 'left',
+      coordinate: 'x',
+    },
+    vertical: {
+      dimension: 'height',
+      direction: 'top',
+      coordinate: 'y',
+    }
+  }
+};
+
+class Slider extends Component {
+  static propTypes = {
+    min: PropTypes.number,
+    max: PropTypes.number,
+    step: PropTypes.number,
+    value: PropTypes.number,
+    orientation: PropTypes.string,
+    onChange: PropTypes.func,
+    className: PropTypes.string,
+  }
+
+  static defaultProps = {
+    min: 0,
+    max: 100,
+    step: 1,
+    value: 0,
+    orientation: 'horizontal',
+  }
+
+  state = {
+    limit: 0,
+    grab: 0
+  }
+
+  // Add window resize event listener here
+  componentDidMount() {
+    this.calculateDimensions();
+    window.addEventListener('resize', this.calculateDimensions);
+  }
+
+  componentWillUnmount() {
+    window.removeEventListener('resize', this.calculateDimensions);
+  }
+
+  handleSliderMouseDown = (e) => {
+    let value, { onChange } = this.props;
+    if (!onChange) return;
+    value = this.position(e);
+    onChange && onChange(value);
+  }
+
+  handleKnobMouseDown = () => {
+    document.addEventListener('mousemove', this.handleDrag);
+    document.addEventListener('mouseup', this.handleDragEnd);
+  }
+
+  handleDrag = (e) => {
+    let value, { onChange } = this.props;
+    if (!onChange) return;
+    value = this.position(e);
+    onChange && onChange(value);
+  }
+
+  handleDragEnd = () => {
+    document.removeEventListener('mousemove', this.handleDrag);
+    document.removeEventListener('mouseup', this.handleDragEnd);
+  }
+
+  handleNoop = (e) => {
+    e.stopPropagation();
+    e.preventDefault();
+  }
+
+  calculateDimensions = () => {
+    let { orientation } = this.props;
+    let dimension = capitalize(constants.orientation[orientation].dimension);
+    const sliderPos = findDOMNode(this.refs.slider)['offset' + dimension];
+    const handlePos = findDOMNode(this.refs.handle)['offset' + dimension]
+    this.setState({
+      limit: sliderPos - handlePos,
+      grab: handlePos / 2,
+    });
+  }
+  getPositionFromValue = (value) => {
+    let percentage, pos;
+    let { limit } = this.state;
+    let { min, max } = this.props;
+    percentage = (value - min) / (max - min);
+    pos = Math.round(percentage * limit);
+
+    return pos;
+  }
+
+  getValueFromPosition = (pos) => {
+    let percentage, value;
+    let { limit } = this.state;
+    let { orientation, min, max, step } = this.props;
+    percentage = (maxmin(pos, 0, limit) / (limit || 1));
+
+    if (orientation === 'horizontal') {
+      value = step * Math.round(percentage * (max - min) / step) + min;
+    } else {
+      value = max - (step * Math.round(percentage * (max - min) / step) + min);
+    }
+
+    return value;
+  }
+
+  position = (e) => {
+    let pos, value, { grab } = this.state;
+    let { orientation } = this.props;
+    const node = findDOMNode(this.refs.slider);
+    const coordinateStyle = constants.orientation[orientation].coordinate;
+    const directionStyle = constants.orientation[orientation].direction;
+    const coordinate = e['client' + capitalize(coordinateStyle)];
+    const direction = node.getBoundingClientRect()[directionStyle];
+
+    pos = coordinate - direction - grab;
+    value = this.getValueFromPosition(pos);
+
+    return value;
+  }
+
+  coordinates = (pos) => {
+    let value, fillPos, handlePos;
+    let { limit, grab } = this.state;
+    let { orientation } = this.props;
+    value = this.getValueFromPosition(pos);
+    handlePos = this.getPositionFromValue(value);
+
+    if (orientation === 'horizontal') {
+      fillPos = handlePos + grab;
+    } else {
+      fillPos = limit - handlePos + grab;
+    }
+
+    return {
+      fill: fillPos,
+      handle: handlePos,
+    };
+  }
+
+  render() {
+    let dimension, direction, position, coords, fillStyle, handleStyle, displayValue;
+    let { value, orientation, className } = this.props;
+
+    dimension = constants.orientation[orientation].dimension;
+    direction = constants.orientation[orientation].direction;
+
+    position = this.getPositionFromValue(value);
+    coords = this.coordinates(position);
+
+    fillStyle = {[dimension]: `${coords.fill}px`};
+    handleStyle = {[direction]: `${coords.handle}px`};
+
+    if(this.props.displayValue) {
+      displayValue = <div>{this.props.value}</div>;
+    }
+
+    return (
+      <div
+        ref="slider"
+        className={joinClasses('rangeslider ', 'rangeslider-' + orientation, className)}
+        onMouseDown={this.handleSliderMouseDown}
+        onClick={this.handleNoop}
+        style={{display:'flex'}}>
+        <div
+          ref="fill"
+          className="rangeslider__fill"
+          style={fillStyle} />
+        <div
+          ref="handle"
+          className="rangeslider__handle"
+          onMouseDown={this.handleKnobMouseDown}
+          onClick={this.handleNoop}
+          style={handleStyle}>
+            {displayValue}
+          </div>
+      </div>
+    );
+  }
+}
+
+export default Slider;