mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-03-28 21:18:47 +00:00
control bar styles refactored
This commit is contained in:
parent
99104f416f
commit
f41c6cd69b
3 changed files with 84 additions and 234 deletions
|
|
@ -1,10 +1,10 @@
|
|||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import debounce from 'lodash.debounce';
|
||||
import Icon from 'stremio-icons/dom';
|
||||
import { Slider, Popup } from 'stremio-common';
|
||||
import colors from 'stremio-colors';
|
||||
import { Popup } from 'stremio-common';
|
||||
import TimeSlider from './TimeSlider';
|
||||
import VolumeSlider from './VolumeSlider';
|
||||
import styles from './styles';
|
||||
|
||||
class ControlBar extends Component {
|
||||
|
|
@ -12,9 +12,6 @@ class ControlBar extends Component {
|
|||
super(props);
|
||||
|
||||
this.state = {
|
||||
time: null,
|
||||
volume: null,
|
||||
volumePopupOpen: false,
|
||||
sharePopupOpen: false
|
||||
};
|
||||
}
|
||||
|
|
@ -25,69 +22,21 @@ class ControlBar extends Component {
|
|||
nextProps.time !== this.props.time ||
|
||||
nextProps.duration !== this.props.duration ||
|
||||
nextProps.volume !== this.props.volume ||
|
||||
nextState.time !== this.state.time ||
|
||||
nextState.volume !== this.state.volume ||
|
||||
nextState.volumePopupOpen !== this.state.volumePopupOpen ||
|
||||
nextState.sharePopupOpen !== this.state.sharePopupOpen;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.resetTime.cancel();
|
||||
this.resetVolume.cancel();
|
||||
}
|
||||
|
||||
resetTime = debounce(() => {
|
||||
this.setState({ time: null });
|
||||
}, 1500)
|
||||
|
||||
resetVolume = debounce(() => {
|
||||
this.setState({ volume: null });
|
||||
}, 100)
|
||||
|
||||
onTimeSliderSlide = (time) => {
|
||||
this.resetTime.cancel();
|
||||
this.setState({ time });
|
||||
}
|
||||
|
||||
onTimeSliderComplete = (time) => {
|
||||
this.setState({ time });
|
||||
setTime = (time) => {
|
||||
this.props.setTime(time);
|
||||
this.resetTime();
|
||||
}
|
||||
|
||||
onTimeSliderCancel = () => {
|
||||
this.resetTime.cancel();
|
||||
this.setState({ time: null });
|
||||
}
|
||||
|
||||
onVolumeSliderSlide = (volume) => {
|
||||
this.resetVolume.cancel();
|
||||
this.setState({ volume });
|
||||
}
|
||||
|
||||
onVolumeSliderComplete = (volume) => {
|
||||
this.setState({ volume });
|
||||
setVolume = (volume) => {
|
||||
this.props.setVolume(volume);
|
||||
this.resetVolume();
|
||||
}
|
||||
|
||||
onVolumeSliderCancel = () => {
|
||||
this.resetVolume.cancel();
|
||||
this.setState({ volume: null });
|
||||
}
|
||||
|
||||
onPlayPauseButtonClick = () => {
|
||||
this.props.paused ? this.props.play() : this.props.pause();
|
||||
}
|
||||
|
||||
onVolumePopupOpen = () => {
|
||||
this.setState({ volumePopupOpen: true });
|
||||
}
|
||||
|
||||
onVolumePopupClose = () => {
|
||||
this.setState({ volumePopupOpen: false });
|
||||
}
|
||||
|
||||
onSharePopupOpen = () => {
|
||||
this.setState({ sharePopupOpen: true });
|
||||
}
|
||||
|
|
@ -96,31 +45,34 @@ class ControlBar extends Component {
|
|||
this.setState({ sharePopupOpen: false });
|
||||
}
|
||||
|
||||
formatTime = (time) => {
|
||||
const hours = ('0' + Math.floor((time / (1000 * 60 * 60)) % 24)).slice(-2);
|
||||
const minutes = ('0' + Math.floor((time / (1000 * 60)) % 60)).slice(-2);
|
||||
const seconds = ('0' + Math.floor((time / 1000) % 60)).slice(-2);
|
||||
return `${hours}:${minutes}:${seconds}`;
|
||||
renderShareButton() {
|
||||
return (
|
||||
<Popup className={styles['popup-container']} border={true} onOpen={this.onSharePopupOpen} onClose={this.onSharePopupClose}>
|
||||
<Popup.Label>
|
||||
<div className={classnames(styles['control-bar-button'], { [styles['active']]: this.state.sharePopupOpen })}>
|
||||
<Icon className={styles['icon']} icon={'ic_share'} />
|
||||
</div>
|
||||
</Popup.Label>
|
||||
<Popup.Menu>
|
||||
<div className={classnames(styles['popup-content'], styles['share-popup-content'])} />
|
||||
</Popup.Menu>
|
||||
</Popup>
|
||||
);
|
||||
}
|
||||
|
||||
renderTimeSlider() {
|
||||
if (this.props.time === null || this.props.duration === null) {
|
||||
renderVolumeButton() {
|
||||
if (this.props.volume === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const time = this.state.time !== null ? this.state.time : this.props.time;
|
||||
const icon = this.props.volume === 0 ? 'ic_volume0' :
|
||||
this.props.volume < 50 ? 'ic_volume1' :
|
||||
this.props.volume < 100 ? 'ic_volume2' :
|
||||
'ic_volume3';
|
||||
return (
|
||||
<Slider
|
||||
containerClassName={styles['time-slider']}
|
||||
thumbClassName={styles['time-thumb']}
|
||||
value={time}
|
||||
minimumValue={0}
|
||||
maximumValue={this.props.duration}
|
||||
orientation={'horizontal'}
|
||||
onSlide={this.onTimeSliderSlide}
|
||||
onComplete={this.onTimeSliderComplete}
|
||||
onCancel={this.onTimeSliderCancel}
|
||||
/>
|
||||
<div className={styles['control-bar-button']}>
|
||||
<Icon className={styles['icon']} icon={icon} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -131,89 +83,34 @@ class ControlBar extends Component {
|
|||
|
||||
const icon = this.props.paused ? 'ic_play' : 'ic_pause';
|
||||
return (
|
||||
<div className={styles['button']} onClick={this.onPlayPauseButtonClick}>
|
||||
<div className={styles['control-bar-button']} onClick={this.onPlayPauseButtonClick}>
|
||||
<Icon className={styles['icon']} icon={icon} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderTimeLabel() {
|
||||
if (this.props.time === null || this.props.duration === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const time = this.state.time !== null ? this.state.time : this.props.time;
|
||||
return (
|
||||
<div className={styles['time-label']}>{this.formatTime(time)} / {this.formatTime(this.props.duration)}</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderVolumeButton() {
|
||||
if (this.props.volume === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const volume = this.state.volume !== null ? this.state.volume : this.props.volume;
|
||||
const icon = volume === 0 ? 'ic_volume0' :
|
||||
volume < 50 ? 'ic_volume1' :
|
||||
volume < 100 ? 'ic_volume2' :
|
||||
'ic_volume3';
|
||||
|
||||
return (
|
||||
<Popup borderColor={colors.primlight} onOpen={this.onVolumePopupOpen} onClose={this.onVolumePopupClose}>
|
||||
<Popup.Label>
|
||||
<div className={classnames(styles['button'], { [styles['active']]: this.state.volumePopupOpen })}>
|
||||
<Icon className={styles['icon']} icon={icon} />
|
||||
</div>
|
||||
</Popup.Label>
|
||||
<Popup.Menu>
|
||||
<div className={classnames(styles['popup-container'], styles['volume-popup-container'])}>
|
||||
<Slider
|
||||
containerClassName={styles['volume-slider']}
|
||||
thumbClassName={styles['volume-thumb']}
|
||||
value={volume}
|
||||
minimumValue={0}
|
||||
maximumValue={100}
|
||||
orientation={'vertical'}
|
||||
onSlide={this.onVolumeSliderSlide}
|
||||
onComplete={this.onVolumeSliderComplete}
|
||||
onCancel={this.onVolumeSliderCancel}
|
||||
/>
|
||||
</div>
|
||||
</Popup.Menu>
|
||||
</Popup>
|
||||
);
|
||||
}
|
||||
|
||||
renderShareButton() {
|
||||
return (
|
||||
<Popup borderColor={colors.primlight} onOpen={this.onSharePopupOpen} onClose={this.onSharePopupClose}>
|
||||
<Popup.Label>
|
||||
<div className={classnames(styles['button'], { [styles['active']]: this.state.sharePopupOpen })}>
|
||||
<Icon className={styles['icon']} icon={'ic_share'} />
|
||||
</div>
|
||||
</Popup.Label>
|
||||
<Popup.Menu>
|
||||
<div className={classnames(styles['popup-container'], styles['share-popup-container'])} />
|
||||
</Popup.Menu>
|
||||
</Popup>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (['paused', 'time', 'duration', 'volume'].every(propName => this.props[propName] === null)) {
|
||||
if (['paused', 'time', 'duration', 'volume', 'subtitles'].every(propName => this.props[propName] === null)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classnames(styles['control-bar-container'], this.props.className)}>
|
||||
{this.renderTimeSlider()}
|
||||
<div className={styles['buttons-bar-container']}>
|
||||
<TimeSlider
|
||||
className={styles['time-slider']}
|
||||
time={this.props.time}
|
||||
duration={this.props.duration}
|
||||
setTime={this.setTime}
|
||||
/>
|
||||
<div className={styles['control-bar-buttons-container']}>
|
||||
{this.renderPlayPauseButton()}
|
||||
<div className={styles['separator']} />
|
||||
{this.renderTimeLabel()}
|
||||
<div className={styles['spacing']} />
|
||||
{this.renderVolumeButton()}
|
||||
<VolumeSlider
|
||||
className={styles['volume-slider']}
|
||||
volume={this.props.volume}
|
||||
setVolume={this.setVolume}
|
||||
/>
|
||||
<div className={styles['flex-spacing']} />
|
||||
{this.renderShareButton()}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -227,6 +124,11 @@ ControlBar.propTypes = {
|
|||
time: PropTypes.number,
|
||||
duration: PropTypes.number,
|
||||
volume: PropTypes.number,
|
||||
subtitles: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
language: PropTypes.string.isRequired
|
||||
})),
|
||||
play: PropTypes.func.isRequired,
|
||||
pause: PropTypes.func.isRequired,
|
||||
setTime: PropTypes.func.isRequired,
|
||||
|
|
|
|||
|
|
@ -35,9 +35,9 @@ class TimeSlider extends Component {
|
|||
}
|
||||
|
||||
onComplete = (time) => {
|
||||
this.resetTimeDebounced();
|
||||
this.setState({ time });
|
||||
this.props.setTime(time);
|
||||
this.resetTimeDebounced();
|
||||
}
|
||||
|
||||
onCancel = () => {
|
||||
|
|
|
|||
|
|
@ -1,127 +1,75 @@
|
|||
@import 'stremio-colors';
|
||||
|
||||
@time-slider-height: 26px;
|
||||
@buttons-bar-height: 58px;
|
||||
|
||||
.control-bar-container {
|
||||
height: (@time-slider-height + @buttons-bar-height);
|
||||
|
||||
.time-slider {
|
||||
height: @time-slider-height;
|
||||
|
||||
.time-thumb {
|
||||
width: @time-slider-height;
|
||||
height: @time-slider-height;
|
||||
}
|
||||
}
|
||||
|
||||
.buttons-bar-container {
|
||||
height: @buttons-bar-height;
|
||||
|
||||
.button {
|
||||
height: @buttons-bar-height;
|
||||
width: @buttons-bar-height;
|
||||
|
||||
.icon {
|
||||
width: floor((@buttons-bar-height * 0.6));
|
||||
height: floor((@buttons-bar-height * 0.6));
|
||||
}
|
||||
|
||||
&.active {
|
||||
padding-top: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.time-label {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.separator {
|
||||
width: 1px;
|
||||
margin-left: floor((@buttons-bar-height * 0.25));
|
||||
margin-right: floor((@buttons-bar-height * 0.25));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popup-container {
|
||||
&.volume-popup-container {
|
||||
padding: ceil((@buttons-bar-height * 0.4)) 0;
|
||||
width: @buttons-bar-height;
|
||||
|
||||
.volume-slider {
|
||||
width: floor((@buttons-bar-height * 0.4));
|
||||
height: floor((@buttons-bar-height * 3));
|
||||
|
||||
.volume-thumb {
|
||||
width: floor((@buttons-bar-height * 0.4));
|
||||
height: floor((@buttons-bar-height * 0.4));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.share-popup-container {
|
||||
width: (@buttons-bar-height * 5);
|
||||
height: (@buttons-bar-height * 3);
|
||||
}
|
||||
.control-bar-container, .popup-container {
|
||||
--control-bar-button-height: 60px;
|
||||
}
|
||||
|
||||
.control-bar-container {
|
||||
top: initial !important;
|
||||
padding: 0 2%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
align-items: stretch;
|
||||
|
||||
.buttons-bar-container {
|
||||
.time-slider {
|
||||
--time-slider-thumb-size: calc(var(--control-bar-button-height) * 0.4);
|
||||
height: var(--time-slider-thumb-size);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.control-bar-buttons-container {
|
||||
height: var(--control-bar-button-height);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.button {
|
||||
.control-bar-button {
|
||||
width: var(--control-bar-button-height);
|
||||
height: var(--control-bar-button-height);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
.icon {
|
||||
fill: @colorwhite80;
|
||||
width: 60%;
|
||||
height: 60%;
|
||||
fill: var(--color-surfacelight);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: @colorbgmain;
|
||||
background-color: var(--color-backgrounddarker);
|
||||
|
||||
.icon {
|
||||
fill: @colorwhite;
|
||||
fill: var(--color-surfacelighter);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.icon {
|
||||
fill: @colorwhite;
|
||||
fill: var(--color-surfacelighter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.separator {
|
||||
height: 70%;
|
||||
background-color: @colorneutrallight80;
|
||||
.volume-slider {
|
||||
--volume-slider-thumb-size: calc(var(--control-bar-button-height) * 0.3);
|
||||
height: var(--volume-slider-thumb-size);
|
||||
width: calc(var(--control-bar-button-height) * 3);
|
||||
}
|
||||
|
||||
.time-label {
|
||||
color: @colorwhite;
|
||||
}
|
||||
|
||||
.spacing {
|
||||
.flex-spacing {
|
||||
flex: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popup-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: @colorbgmain;
|
||||
--border-color: var(--color-primarylight);
|
||||
|
||||
.popup-content {
|
||||
background-color: var(--color-backgrounddark);
|
||||
|
||||
&.share-popup-content {
|
||||
width: calc(var(--control-bar-button-height) * 5);
|
||||
height: calc(var(--control-bar-button-height) * 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue