Nicer sliders

This commit is contained in:
James Barnsley
2016-11-04 13:51:17 +13:00
parent 54718b3a7b
commit 4ef7d935dd
14 changed files with 835 additions and 414 deletions

View File

@ -11,7 +11,7 @@ import Thumbnail from './Thumbnail'
import * as mopidyActions from '../services/mopidy/actions'
class Player extends React.Component{
class FullPlayer extends React.Component{
constructor(props) {
super(props);
@ -49,33 +49,7 @@ class Player extends React.Component{
return button;
}
renderMiniPlayer(){
var mopidy_track = false;
if( typeof(this.props.mopidy.current_tltrack) !== 'undefined' && typeof(this.props.mopidy.current_tltrack.track) !== 'undefined' ) mopidy_track = this.props.mopidy.current_tltrack;
return (
<div className="player">
<div className="current-track">
<div className="title">{ mopidy_track ? mopidy_track.track.name : null }</div>
{ mopidy_track ? <ArtistSentence artists={ mopidy_track.track.artists } /> : null }
</div>
<div className="controls">
<a onClick={() => this.props.mopidyActions.previous()}>
<FontAwesome name="step-backward" />
</a>&nbsp;
{ this.renderPlayButton() }
<a onClick={() => this.props.mopidyActions.next()}>
<FontAwesome name="step-forward" />
</a>&nbsp;
<ProgressSlider />
</div>
</div>
);
}
renderFullPlayer(){
render(){
var mopidy_track = false;
var images = [];
if( typeof(this.props.mopidy.current_tltrack) !== 'undefined' && typeof(this.props.mopidy.current_tltrack.track) !== 'undefined' ){
@ -114,14 +88,6 @@ class Player extends React.Component{
</div>
);
}
render(){
if( this.props.mini ){
return this.renderMiniPlayer()
}else{
return this.renderFullPlayer()
}
}
}
@ -141,4 +107,4 @@ const mapDispatchToProps = (dispatch) => {
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Player)
export default connect(mapStateToProps, mapDispatchToProps)(FullPlayer)

71
src/js/components/MiniPlayer.js Executable file
View File

@ -0,0 +1,71 @@
import React, { PropTypes } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { Link } from 'react-router'
import FontAwesome from 'react-fontawesome'
import ProgressSlider from './ProgressSlider'
import VolumeControl from './VolumeControl'
import ArtistSentence from './ArtistSentence'
import Thumbnail from './Thumbnail'
import * as mopidyActions from '../services/mopidy/actions'
class MiniPlayer extends React.Component{
constructor(props) {
super(props);
}
renderPlayButton(){
var button = <a onClick={() => this.props.mopidyActions.play()}><FontAwesome name="play" /> </a>
if( this.props.mopidy.state == 'playing' ){
button = <a onClick={() => this.props.mopidyActions.pause()}><FontAwesome name="pause" /> </a>
}
return button;
}
render(){
var mopidy_track = false;
if( typeof(this.props.mopidy.current_tltrack) !== 'undefined' && typeof(this.props.mopidy.current_tltrack.track) !== 'undefined' ) mopidy_track = this.props.mopidy.current_tltrack;
return (
<div className="player">
<div className="current-track">
<div className="title">{ mopidy_track ? mopidy_track.track.name : null }</div>
{ mopidy_track ? <ArtistSentence artists={ mopidy_track.track.artists } /> : null }
</div>
<div className="controls">
{ this.renderPlayButton() }
<a onClick={() => this.props.mopidyActions.next()}>
<FontAwesome name="step-forward" />
</a>&nbsp;
<VolumeControl />
<ProgressSlider />
</div>
</div>
);
}
}
/**
* Export our component
*
* We also integrate our global store, using connect()
**/
const mapStateToProps = (state, ownProps) => {
return state;
}
const mapDispatchToProps = (dispatch) => {
return {
mopidyActions: bindActionCreators(mopidyActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MiniPlayer)

View File

@ -57,7 +57,7 @@ class ProgressSlider extends React.Component{
}
render(){
var className = 'slider'
var className = 'slider horizontal'
if( this.state.animating ) className += ' animating'
var percent = 0
if( this.props.mopidy.connected && typeof(this.props.mopidy.current_tltrack) !== 'undefined' && typeof(this.props.mopidy.current_tltrack.track) !== 'undefined' ){
@ -68,7 +68,9 @@ class ProgressSlider extends React.Component{
return (
<div className={className} onClick={ (e) => this.handleClick(e) } >
<div className="progress" style={{ width: (percent)+'%' }}></div>
<div className="track">
<div className="progress" style={{ width: (percent)+'%' }}></div>
</div>
</div>
);
}

View File

@ -4,7 +4,7 @@ import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { Link } from 'react-router'
import Player from './Player'
import MiniPlayer from './MiniPlayer'
import Icon from './Icon'
import Thumbnail from './Thumbnail'
import SearchForm from './SearchForm'
@ -88,7 +88,7 @@ class Sidebar extends React.Component{
</nav>
<Player mini={ true } />
<MiniPlayer />
</aside>
);

View File

@ -0,0 +1,52 @@
import React, { PropTypes } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { Link } from 'react-router'
import FontAwesome from 'react-fontawesome'
import * as mopidyActions from '../services/mopidy/actions'
class VolumeControl extends React.Component{
constructor(props) {
super(props);
}
handleClick(e){
var slider = e.target;
var sliderY = e.clientY - slider.getBoundingClientRect().top;
var sliderHeight = slider.getBoundingClientRect().height;
var percent = 100 - parseInt( sliderY / sliderHeight * 100 );
this.props.mopidyActions.setVolume( percent )
}
render(){
return (
<span className="volume-control">
<a onClick={() => this.props.mopidyActions.toggleMute()}><FontAwesome name="volume-off" /> </a>
<div className="slider-wrapper">
<div className="slider vertical" onClick={ (e) => this.handleClick(e) } >
<div className="track">
<div className="progress" style={{ height: this.props.mopidy.volume+'%' }}></div>
</div>
</div>
</div>
</span>
);
}
}
const mapStateToProps = (state, ownProps) => {
return state;
}
const mapDispatchToProps = (dispatch) => {
return {
mopidyActions: bindActionCreators(mopidyActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(VolumeControl)

View File

@ -1,38 +0,0 @@
import React, { PropTypes } from 'react'
import FontAwesome from 'react-fontawesome'
export default class VolumeSlider extends React.Component{
constructor(props) {
super(props);
this.state = {
volume: 0
}
}
componentWillReceiveProps( nextProps ){
this.setState({ volume: nextProps.volume })
}
handleClick(e){
var slider = e.target;
if( slider.className != 'slider' ) slider = slider.parentElement;
var sliderX = e.clientX - slider.getBoundingClientRect().left;
var sliderWidth = slider.getBoundingClientRect().width;
var percent = parseInt( sliderX / sliderWidth * 100 );
if( this.props.volume != percent ){
this.props.onChange( volume )
}
}
render(){
return (
<div className="slider" onClick={ (e) => this.handleClick(e) } >
<div className="progress" style={{ width: this.props.volume+'%' }}></div>
</div>
);
}
}

View File

@ -89,6 +89,14 @@ export function previous(){
return instruct('playback.previous');
}
export function setVolume( volume ){
return {
type: 'MOPIDY_INSTRUCT',
call: 'playback.setVolume',
value: { volume: volume }
}
}
export function seek( time_position ){
return {
type: 'MOPIDY_INSTRUCT',

View File

@ -6,7 +6,7 @@ import { bindActionCreators } from 'redux'
import FontAwesome from 'react-fontawesome'
import TrackList from '../components/TrackList'
import Track from '../components/Track'
import Player from '../components/Player'
import FullPlayer from '../components/FullPlayer'
import ArtistSentence from '../components/ArtistSentence'
import AlbumLink from '../components/AlbumLink'
import Header from '../components/Header'
@ -55,7 +55,7 @@ class Queue extends React.Component{
render(){
return (
<div className="view queue-view">
<Player />
<FullPlayer />
{ this.renderTrackList() }
</div>
);

View File

@ -48,4 +48,30 @@
/* @include animate(10s); */
}
}
}
.volume-control {
position: relative;
a {
position: relative;
z-index: 99;
}
&:hover {
.slider-wrapper {
display: block;
}
}
.slider-wrapper {
display: none;
position: absolute;
bottom: 0;
left: -4px;
height: 80px;
padding: 5px 5px 35px 5px;
background: rgba(0,0,0,0.8);
z-index: 98;
}
}

View File

@ -98,17 +98,61 @@ label {
}
.slider {
margin: 10px 0;
height: 5px;
position: relative;
background: rgba(255,255,255,0.3);
cursor: pointer;
.progress {
left: 0;
top: 0;
.track {
position: absolute;
background: rgba(255,255,255,0.3);
@include animate()
.progress {
position: absolute;
background: $turquoise;
pointer-events: none;
left: 0;
}
}
&.horizontal {
margin: 10px 0;
height: 20px;
.track {
height: 2px;
top: 9px;
right: 0;
left: 0;
.progress {
top: 0;
height: 100%;
}
}
&:hover {
.track {
top: 7px;
height: 6px;
}
}
}
&.vertical {
margin: 10px 0;
width: 20px;
height: 100%;
background: $turquoise;
pointer-events: none;
.track {
width: 2px;
top: 0;
bottom: 0;
left: 9px;
.progress {
bottom: 0;
width: 100%;
}
}
}
}