Nicer sliders
This commit is contained in:
@ -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>
|
||||
{ this.renderPlayButton() }
|
||||
<a onClick={() => this.props.mopidyActions.next()}>
|
||||
<FontAwesome name="step-forward" />
|
||||
</a>
|
||||
<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
71
src/js/components/MiniPlayer.js
Executable 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>
|
||||
<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)
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
52
src/js/components/VolumeControl.js
Executable file
52
src/js/components/VolumeControl.js
Executable 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)
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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',
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user