mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-03-11 21:27:05 +00:00
add support for vertical slider
This commit is contained in:
parent
3838ad022c
commit
2335e6d7a0
4 changed files with 142 additions and 33 deletions
|
|
@ -4,6 +4,12 @@ import classnames from 'classnames';
|
|||
import styles from './styles';
|
||||
|
||||
class Slider extends Component {
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.orientation !== nextProps.orientation) {
|
||||
console.warn('changing orientation property at runtime is not supported');
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return nextProps.value !== this.props.value ||
|
||||
nextProps.minValue !== this.props.minValue ||
|
||||
|
|
@ -30,14 +36,17 @@ class Slider extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
calculateSlidingValue = (mouseX, sliderElement) => {
|
||||
const { x: sliderX, width: sliderWidth } = sliderElement.getBoundingClientRect();
|
||||
const thumbX = Math.min(Math.max(mouseX - sliderX, 0), sliderWidth);
|
||||
const slidingValue = (thumbX / sliderWidth) * (this.props.maxValue - this.props.minValue) + this.props.minValue;
|
||||
calculateSlidingValue = ({ mouseX, mouseY, sliderElement }) => {
|
||||
const { x: sliderX, y: sliderY, width: sliderWidth, height: sliderHeight } = sliderElement.getBoundingClientRect();
|
||||
const sliderStart = this.props.orientation === 'horizontal' ? sliderX : sliderY;
|
||||
const sliderLength = this.props.orientation === 'horizontal' ? sliderWidth : sliderHeight;
|
||||
const mouseStart = this.props.orientation === 'horizontal' ? mouseX : mouseY;
|
||||
const thumbStart = Math.min(Math.max(mouseStart - sliderStart, 0), sliderLength);
|
||||
const slidingValue = (thumbStart / sliderLength) * (this.props.maxValue - this.props.minValue) + this.props.minValue;
|
||||
return Math.floor(slidingValue);
|
||||
}
|
||||
|
||||
onStartSliding = ({ currentTarget: sliderElement, clientX: mouseX, button }) => {
|
||||
onStartSliding = ({ currentTarget: sliderElement, clientX: mouseX, clientY: mouseY, button }) => {
|
||||
if (button !== 0) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -54,12 +63,14 @@ class Slider extends Component {
|
|||
releaseThumb();
|
||||
this.onSlidingAborted();
|
||||
};
|
||||
const onMouseUp = ({ clientX: mouseX }) => {
|
||||
const onMouseUp = ({ clientX: mouseX, clientY: mouseY }) => {
|
||||
releaseThumb();
|
||||
this.onSlidingCompleted(this.calculateSlidingValue(mouseX, sliderElement));
|
||||
const slidingValue = this.calculateSlidingValue({ mouseX, mouseY, sliderElement });
|
||||
this.onSlidingCompleted(slidingValue);
|
||||
};
|
||||
const onMouseMove = ({ clientX: mouseX }) => {
|
||||
this.onSliding(this.calculateSlidingValue(mouseX, sliderElement));
|
||||
const onMouseMove = ({ clientX: mouseX, clientY: mouseY }) => {
|
||||
const slidingValue = this.calculateSlidingValue({ mouseX, mouseY, sliderElement });
|
||||
this.onSliding(slidingValue);
|
||||
};
|
||||
|
||||
window.addEventListener('blur', onBlur);
|
||||
|
|
@ -68,17 +79,18 @@ class Slider extends Component {
|
|||
document.body.style['pointer-events'] = 'none';
|
||||
document.documentElement.style.cursor = 'pointer';
|
||||
sliderElement.classList.add(styles['active']);
|
||||
onMouseMove({ clientX: mouseX });
|
||||
onMouseMove({ clientX: mouseX, clientY: mouseY });
|
||||
}
|
||||
|
||||
render() {
|
||||
const thumbLeft = (this.props.value - this.props.minValue) / (this.props.maxValue - this.props.minValue);
|
||||
const thumbStartProp = this.props.orientation === 'horizontal' ? 'left' : 'top';
|
||||
const thumbStart = (this.props.value - this.props.minValue) / (this.props.maxValue - this.props.minValue);
|
||||
return (
|
||||
<div className={classnames(styles['slider-container'], this.props.containerClassName)} onMouseDown={this.onStartSliding}>
|
||||
<div className={classnames(styles['slider-container'], styles[this.props.orientation], this.props.containerClassName)} onMouseDown={this.onStartSliding}>
|
||||
<div className={styles['line']} />
|
||||
<div
|
||||
className={classnames(styles['thumb'], this.props.thumbClassName)}
|
||||
style={{ left: `calc(100% * ${thumbLeft})` }}
|
||||
style={{ [thumbStartProp]: `calc(100% * ${thumbStart})` }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -91,6 +103,7 @@ Slider.propTypes = {
|
|||
value: PropTypes.number.isRequired,
|
||||
minValue: PropTypes.number.isRequired,
|
||||
maxValue: PropTypes.number.isRequired,
|
||||
orientation: PropTypes.oneOf(['horizontal', 'vertical']).isRequired,
|
||||
onSliding: PropTypes.func,
|
||||
onSlidingCompleted: PropTypes.func,
|
||||
onSlidingAborted: PropTypes.func
|
||||
|
|
@ -98,7 +111,8 @@ Slider.propTypes = {
|
|||
Slider.defaultProps = {
|
||||
value: 0,
|
||||
minValue: 0,
|
||||
maxValue: 100
|
||||
maxValue: 100,
|
||||
orientation: 'horizontal'
|
||||
};
|
||||
|
||||
export default Slider;
|
||||
|
|
|
|||
|
|
@ -6,10 +6,6 @@
|
|||
|
||||
.line {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 40%;
|
||||
bottom: 40%;
|
||||
z-index: 1;
|
||||
background-color: @colorprim;
|
||||
}
|
||||
|
|
@ -19,8 +15,6 @@
|
|||
z-index: 2;
|
||||
border-radius: 50%;
|
||||
background-color: @colormedium;
|
||||
transform: translateX(-50%);
|
||||
transition-property: left;
|
||||
transition-duration: 0.06s;
|
||||
|
||||
&:hover {
|
||||
|
|
@ -28,6 +22,34 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.horizontal {
|
||||
.line {
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 40%;
|
||||
bottom: 40%;
|
||||
}
|
||||
|
||||
.thumb {
|
||||
transform: translateX(-50%);
|
||||
transition-property: left;
|
||||
}
|
||||
}
|
||||
|
||||
&.vertical {
|
||||
.line {
|
||||
left: 40%;
|
||||
right: 40%;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.thumb {
|
||||
transform: translateY(-50%);
|
||||
transition-property: top;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
.thumb {
|
||||
background-color: @colorprimlight;
|
||||
|
|
|
|||
|
|
@ -59,18 +59,19 @@ class ControlBar extends Component {
|
|||
return `${('0' + hours).slice(-2)}:${('0' + minutes).slice(-2)}:${('0' + seconds).slice(-2)}`;
|
||||
}
|
||||
|
||||
renderSeekBar() {
|
||||
renderSeekSlider() {
|
||||
if (this.props.time === null || this.props.duration === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Slider
|
||||
containerClassName={styles['seek-bar']}
|
||||
thumbClassName={styles['thumb']}
|
||||
containerClassName={styles['seek-slider']}
|
||||
thumbClassName={styles['seek-thumb']}
|
||||
value={this.state.seekTime !== -1 ? this.state.seekTime : this.props.time}
|
||||
maxValue={this.props.duration}
|
||||
minValue={0}
|
||||
orientation={'horizontal'}
|
||||
onSliding={this.onSliding}
|
||||
onSlidingAborted={this.onSlidingAborted}
|
||||
onSlidingCompleted={this.onSlidingCompleted}
|
||||
|
|
@ -108,14 +109,49 @@ class ControlBar extends Component {
|
|||
);
|
||||
}
|
||||
|
||||
renderVolumeButton() {
|
||||
if (this.props.volume === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const volumeIcon = this.props.volume === 0 ? 'ic_volume0' :
|
||||
this.props.volume < 50 ? 'ic_volume1' :
|
||||
this.props.volume < 100 ? 'ic_volume2' :
|
||||
'ic_volume3';
|
||||
|
||||
return (
|
||||
<div className={classnames(styles['button'], styles['volume-button'])}>
|
||||
<Icon
|
||||
className={styles['icon']}
|
||||
icon={volumeIcon}
|
||||
/>
|
||||
<div className={styles['volume-slider-container']}>
|
||||
<Slider
|
||||
containerClassName={styles['volume-slider']}
|
||||
thumbClassName={styles['volume-thumb']}
|
||||
value={50}
|
||||
maxValue={110}
|
||||
minValue={0}
|
||||
orientation={'vertical'}
|
||||
// onSliding={this.onSliding}
|
||||
// onSlidingAborted={this.onSlidingAborted}
|
||||
// onSlidingCompleted={this.onSlidingCompleted}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={classnames(styles['control-bar-container'], this.props.className)}>
|
||||
{this.renderSeekBar()}
|
||||
{this.renderSeekSlider()}
|
||||
<div className={styles['buttons-bar-container']}>
|
||||
{this.renderPlayPauseButton()}
|
||||
<div className={styles['separator']} />
|
||||
{this.renderTimeLabel()}
|
||||
<div className={styles['spacing']} />
|
||||
{this.renderVolumeButton()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
@import 'stremio-colors';
|
||||
|
||||
@control-bar-height: 84px;
|
||||
@seek-bar-height: 26px;
|
||||
@buttons-bar-height: (@control-bar-height - @seek-bar-height);
|
||||
@seek-slider-height: 26px;
|
||||
@buttons-bar-height: (@control-bar-height - @seek-slider-height);
|
||||
|
||||
.control-bar-container {
|
||||
height: @control-bar-height;
|
||||
|
|
@ -13,13 +13,14 @@
|
|||
padding-left: 2%;
|
||||
padding-right: 2%;
|
||||
|
||||
.seek-bar {
|
||||
.seek-slider {
|
||||
width: 100%;
|
||||
height: @seek-bar-height;
|
||||
height: @seek-slider-height;
|
||||
z-index: 1;
|
||||
|
||||
.thumb {
|
||||
width: @seek-bar-height;
|
||||
height: @seek-bar-height;
|
||||
.seek-thumb {
|
||||
width: @seek-slider-height;
|
||||
height: @seek-slider-height;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -29,6 +30,7 @@
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
z-index: 2;
|
||||
|
||||
.button {
|
||||
height: @buttons-bar-height;
|
||||
|
|
@ -39,8 +41,8 @@
|
|||
cursor: pointer;
|
||||
|
||||
.icon {
|
||||
width: 64%;
|
||||
height: 64%;
|
||||
width: 60%;
|
||||
height: 60%;
|
||||
fill: @colorwhite80;
|
||||
}
|
||||
|
||||
|
|
@ -49,6 +51,37 @@
|
|||
fill: @colorwhite;
|
||||
}
|
||||
}
|
||||
|
||||
&.volume-button {
|
||||
position: relative;
|
||||
|
||||
.volume-slider-container {
|
||||
display: none;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 300%;
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
|
||||
.volume-slider {
|
||||
width: round((@buttons-bar-height * 0.4));
|
||||
height: 100%;
|
||||
|
||||
.volume-thumb {
|
||||
width: round((@buttons-bar-height * 0.4));
|
||||
height: round((@buttons-bar-height * 0.4));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.volume-slider-container {
|
||||
display: flex;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.separator {
|
||||
|
|
@ -63,5 +96,9 @@
|
|||
color: @colorwhite;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.spacing {
|
||||
flex: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue