import React from 'react'
import PropTypes from 'prop-types'
import Component from '../utils/Component'
import classnames from 'classnames'
import Logger from '../utils/logger'
import Drag from '../drag/'
import '../style'
import 'phoenix-styles/less/modules/slider.less'
/**
* 滑动输入条组件<br/>
* - 滑动进度条确定当前进度的百分比。
* - 可通过设置process确定初始进度百分比, 范围从0-100。
* - 可通过tipMode选择当前查看进度的方式,可选default和tooltip。
* - 可通过placement设置当前进度提示框的位置, 可选top/bottoom(tipMode为tooltip时生效)。
* - 可通过tipStay设置初始和松开按钮时提示是否消失,默认false不显示(tipMode为tooltip时生效)。
* - 可通过range制定范围,默认0-100,必需是长度为2的数组,第一个数字表示初始,第二个数字表示终点。
* - 可通过showRange判断是否在进度条前后显示范围,默认不显示。
* - 可通过duration设置固定移动的距离,默认1。
* - 可通过slideCallback设置拖拽进度条松开时的回调函数。
* - 可通过disabled设置进度条只读。
* - 使用Slider前确保父级是有宽度的元素;使用flex需要加一层宽度100%的外壳。
*
* 主要属性和接口:
* - process:初始进度百分比, 默认0 <br/>
* 如: `<Slider progress={10}/>`
* - placement:进度提示框的位置, 默认top <br/>
* 如: `<Slider placement='bottom' />`
* - tipStay:初始和松开按钮时提示是否消失,默认false <br/>
* 如: `<Slider tipStay />`
* - range:范围,默认[0,100]。 <br/>
* 如: `<Slider range={[20,50]} />`
* - showRange:是否在进度条前后显示范围,默认不显示。 <br/>
* 如: `<Slider showRange />`
* - duration:固定移动的距离,默认1。 <br/>
* 如: `<Slider duration={20} />`
* - slideCallback:拖拽进度条松开时的回调函数 <br/>
* 如: `<Slider slideCallback={(progress)=>{console.log(progress);} />`
* - disabled:进度条只读, 不可操作 <br/>
* 如: `<Slider disabled/>`
*
* @class Slider
* @module 操作类组件
* @extends Component
* @constructor
* @since 1.0.0
* @demo slider|slider.js {展示}
* @show true
* */
export default class Slider extends Component{
static propTypes = {
/**
* 样式前缀
* @property classPrefix
* @type String
* @default 'slider'
* */
classPrefix: PropTypes.string,
/**
* 标签tagName
* @property componentTag
* @type String
* */
componentTag:PropTypes.string,
/**
* 初始进程,默认0
* @property progress
* @type String
* */
progress:PropTypes.number,
/**
* 进程提示的位置,默认top
* @property placement
* @type String
* @default 'top'
* */
placement: PropTypes.string,
/**
* 范围,默认0-100,可传固定范围的数组如:[25,50]
* @property range
* @type Array
* @default [0,100]
* */
range: PropTypes.array,
/**
* 是否在进度条前后显示范围
* @property showRange
* @type Boolean
* @default false
* */
showRange: PropTypes.bool,
/**
* 显示提示的模式,可选[default,tooltip]
* @property tipMode
* @type String
* @default 'default'
* */
tipMode: PropTypes.string,
/**
* 每次移动的固定距离,默认1
* @property duration
* @type Number
* @default 1
* */
duration: PropTypes.number,
/**
* 初始及松开按钮时是否显示tooltip
* @property tipStay
* @type Boolean
* @default false
* */
tipStay: PropTypes.bool,
/**
* 改变进程时的回调函数
* @method slideCallback
* @param {number} progress 进度
* @type Function
* */
slideCallback: PropTypes.func
};
static defaultProps = {
placement: 'top',
progress: 0,
range: [0,100],
showRange: false,
duration: 1,
tipMode: 'default',
tipStay: false,
classPrefix:'slider',
componentTag:'div',
classMapping : {
'disabled': 'disabled',
'top': 'tip-top',
'bottom': 'tip-bottom'
}
};
constructor(props, context) {
super(props, context)
new Logger('Slider')
this.init(props);
this.state = {
realProgress: props.progress || this.range[0],
tipVisible: props.tipStay || false
}
}
init(props){
this.range = this.validateRange(props);
this.rangeDiff = this.range[1]-this.range[0];
this.duration = this.validateDuration(props);
this.eachDur = (this.range[1]-this.range[0])/this.duration;
}
validateRange(props){
let {range, progress} = props,
defaultRange = [0,100];
if(!range instanceof Array) return defaultRange;
if(range.length != 2){
console.error('Invalid prop `range` of length not equal to 2.');
return defaultRange;
}
if(range[0] >= range[1]){
console.error('Invalid prop `range[0]` must be less than or equal to `range[1]`.');
return defaultRange;
}
if(progress)
if(progress<range[0] || progress>range[1]){
console.error('`Progress prop` have to between `range[0]` and `range[1]`.');
return range;
}
return range;
}
validateDuration(props){
let {duration} = props,
defaultDuration = 1;
if(duration<=0) {
console.error('Invalid prop `duration` have to be Positive.');
return defaultDuration;
}
if((this.range[1] - this.range[0])%duration != 0){ // 不能整除的情况
console.error('Prop `duration` can not be divided by `range`.');
return defaultDuration;
}
return duration;
}
componentDidMount(){
this.sliderLength = parseInt(this.sliderLine.offsetWidth);
this.eachSection = this.sliderLength/this.rangeDiff*this.duration;
// if(this.eachSection<1) this.eachSection = 1; // 最小1px
this.newProgressWidth = this.getNewProgressWidth(this.state.realProgress);
this.setSliderPosition(this.newProgressWidth + 'px');
}
componentWillReceiveProps(nextProps){
if(nextProps.progress!=undefined && this.state.realProgress != nextProps.progress){
this.setState({
realProgress: nextProps.progress
});
this.setNewProgress(nextProps)
}
if(nextProps.range!=undefined && this.range != nextProps.range){
this.init(nextProps)
this.setNewProgress(nextProps)
}
}
setNewProgress(props){
this.newProgressWidth = this.getNewProgressWidth(props.progress);
this.setSliderPosition(this.newProgressWidth + 'px');
}
getNewProgressWidth(realProgress){ // 保留2位小数
let per = Math.round((realProgress-this.range[0])/this.rangeDiff*100)/100
if(per>=1) per = 1
if(per<=0) per = 0
return this.sliderLength * per
}
setSliderPosition(distance){
this.sliderProgress.style.width = distance;
this.sliderBtn.style.left = distance;
}
dragCallback(event, position){
let newProgress, nowSec;
this.preX = position.start.x;
this.X = position.move.x;
this.distance = this.X - this.preX;
this.prevProgressWidth = this.newProgressWidth + this.distance;
if(this.prevProgressWidth <= 0) this.prevProgressWidth = 0;
if(this.prevProgressWidth >= this.sliderLength) this.prevProgressWidth = this.sliderLength;
nowSec = Math.round(this.prevProgressWidth/this.eachSection, 0);
this.prevProgressWidth = this.eachSection*nowSec;
newProgress = this.prevProgressWidth/this.sliderLength * this.rangeDiff + this.range[0];
this.setSliderPosition(this.prevProgressWidth + 'px');
this.setState({
tipVisible: true,
realProgress: parseInt(newProgress)
});
}
dropCallback(event, position){
let {tipStay, slideCallback} = this.props
if(!tipStay){
this.setState({
tipVisible: false
});
}
this.newProgressWidth = this.prevProgressWidth;
if(slideCallback) slideCallback(this.state.realProgress);
}
renderSliderText(showTipMode){
if(showTipMode){
return <div className={this.setPhPrefix('text')}>{this.state.realProgress}</div>
}
}
renderSliderRange(){
if(this.props.showRange){
return (
<div className={this.setPhPrefix('range')}>
<strong className={this.setPhPrefix('range-start')}>{this.range[0]}</strong>
<strong className={this.setPhPrefix('range-end')}>{this.range[1]}</strong>
</div>
);
}else{
return '';
}
}
renderSlider(){
let {componentTag:Component, className, showRange, tipMode} = this.props,
showTipMode = tipMode=='default';
return (
<Component {...this.otherProps} className={classnames(
this.getProperty(true),
className,
showRange? this.setPhPrefix('keep-range',true):''
)}>
{this.renderSliderText(showTipMode)}
{this.renderSliderRange()}
<div className={this.setPhPrefix('line')} ref={(sliderLine)=>{this.sliderLine=sliderLine}}>
<div className={this.setPhPrefix('progress')} ref={(sliderProgress)=>{this.sliderProgress=sliderProgress}}></div>
<div className={this.setPhPrefix('content')} ref={(sliderBtn)=>{this.sliderBtn=sliderBtn}}>
<div className={classnames(this.setPhPrefix('tip'), this.state.tipVisible && !showTipMode?'show':'hide')}>{this.state.realProgress}</div>
<Drag className={classnames(this.setPhPrefix('btn'),'hardware')} dragCallback={this.dragCallback.bind(this)} dropCallback={this.dropCallback.bind(this)}></Drag>
</div>
</div>
</Component>
);
}
render(){
return this.renderSlider()
}
}