Kiosk mode; Placeholder for unloaded albums/artists/playlists; Gmusic integration part 1
This commit is contained in:
62
src/js/components/Modal/KioskModeModal.js
Executable file
62
src/js/components/Modal/KioskModeModal.js
Executable file
@ -0,0 +1,62 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { Link } from 'react-router'
|
||||
import { createStore, bindActionCreators } from 'redux'
|
||||
|
||||
import * as uiActions from '../../services/ui/actions'
|
||||
import * as helpers from '../../helpers'
|
||||
|
||||
import Icon from '../Icon'
|
||||
import Thumbnail from '../Thumbnail'
|
||||
import ArtistSentence from '../ArtistSentence'
|
||||
|
||||
class KioskModeModal extends React.Component{
|
||||
|
||||
constructor(props){
|
||||
super(props)
|
||||
}
|
||||
|
||||
handleClick(e, connectionid){
|
||||
e.preventDefault()
|
||||
this.props.uiActions.closeModal()
|
||||
return false;
|
||||
}
|
||||
|
||||
render(){
|
||||
if (this.props.current_track && this.props.current_track.album && this.props.current_track.album.images){
|
||||
var images = this.props.current_track.album.images
|
||||
} else {
|
||||
var images = []
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="kisk-mode-modal">
|
||||
|
||||
<Thumbnail className="background" images={images} />
|
||||
<Thumbnail className="foreground" images={images} />
|
||||
|
||||
<div className="player">
|
||||
<div className="current-track">
|
||||
<div className="title">{ this.props.current_track ? this.props.current_track.name : <span>-</span> }</div>
|
||||
{ this.props.current_track ? <ArtistSentence artists={ this.props.current_track.artists } /> : <ArtistSentence /> }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {
|
||||
current_track: state.ui.current_track
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
uiActions: bindActionCreators(uiActions, dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(KioskModeModal)
|
||||
@ -11,6 +11,7 @@ import EditPlaylistModal from './EditPlaylistModal'
|
||||
import SendAuthorizationModal from './SendAuthorizationModal'
|
||||
import EditRadioModal from './EditRadioModal'
|
||||
import ImageZoomModal from './ImageZoomModal'
|
||||
import KioskModeModal from './KioskModeModal'
|
||||
|
||||
import * as uiActions from '../../services/ui/actions'
|
||||
import * as mopidyActions from '../../services/mopidy/actions'
|
||||
@ -39,6 +40,7 @@ class Modal extends React.Component{
|
||||
{ this.props.modal.name == 'send_authorization' ? <SendAuthorizationModal uiActions={this.props.uiActions} pusherActions={this.props.pusherActions} data={this.props.modal.data} /> : null }
|
||||
{ this.props.modal.name == 'edit_radio' ? <EditRadioModal uiActions={this.props.uiActions} pusherActions={this.props.pusherActions} spotifyActions={this.props.spotifyActions} data={this.props.modal.data} radio={this.props.radio} artists={this.props.artists} tracks={this.props.tracks} /> : null }
|
||||
{ this.props.modal.name == 'image_zoom' ? <ImageZoomModal uiActions={this.props.uiActions} data={this.props.modal.data} /> : null }
|
||||
{ this.props.modal.name == 'kiosk_mode' ? <KioskModeModal uiActions={this.props.uiActions} data={this.props.modal.data} /> : null }
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -68,6 +68,7 @@ class Thumbnail extends React.Component{
|
||||
var style = { backgroundImage: 'url("'+image+'")' }
|
||||
var class_name = 'thumbnail '+this.props.size;
|
||||
if( this.props.circle ) class_name += ' circle';
|
||||
if( this.props.className ) class_name += ' '+this.props.className;
|
||||
|
||||
var zoom_icon = null
|
||||
if (this.props.canZoom){
|
||||
|
||||
@ -141,6 +141,9 @@ export let sourceIcon = function(uri,source = null){
|
||||
case 'm3u':
|
||||
return 'folder'
|
||||
break
|
||||
case 'gmusic':
|
||||
return 'google'
|
||||
break
|
||||
default:
|
||||
return source
|
||||
}
|
||||
|
||||
@ -360,13 +360,14 @@ const MopidyMiddleware = (function(){
|
||||
for (var i = 0; i < response.length; i++ ){
|
||||
instruct( socket, store, 'playlists.lookup', { uri: response[i].uri })
|
||||
.then( response => {
|
||||
var source = helpers.uriSource(response.uri)
|
||||
var playlist = Object.assign(
|
||||
{},
|
||||
{
|
||||
type: 'playlist',
|
||||
name: response.name,
|
||||
uri: response.uri,
|
||||
source: 'local',
|
||||
source: (source == 'spotify' ? 'local' : source),
|
||||
last_modified: response.last_modified,
|
||||
can_edit: (response.uri.startsWith('m3u:')),
|
||||
tracks_total: ( response.tracks ? response.tracks.length : 0 )
|
||||
@ -739,7 +740,7 @@ const MopidyMiddleware = (function(){
|
||||
|
||||
var artist = Object.assign(
|
||||
{},
|
||||
response[0].artists[0],
|
||||
(response ? response[0].artists[0] : {}),
|
||||
{
|
||||
albums_uris: helpers.asURIs(albums),
|
||||
tracks: response.slice(0,10)
|
||||
|
||||
@ -60,7 +60,7 @@ class Album extends React.Component{
|
||||
}
|
||||
break;
|
||||
|
||||
case 'local':
|
||||
default:
|
||||
if (props.mopidy_connected){
|
||||
if (props.album && props.album.tracks){
|
||||
console.info('Loading album from index')
|
||||
@ -81,53 +81,76 @@ class Album extends React.Component{
|
||||
}
|
||||
|
||||
render(){
|
||||
if( !this.props.album ) return null
|
||||
|
||||
var artists = []
|
||||
if (this.props.album.artists_uris && this.props.artists){
|
||||
for (var i = 0; i < this.props.album.artists_uris.length; i++){
|
||||
var uri = this.props.album.artists_uris[i]
|
||||
if (this.props.artists.hasOwnProperty(uri)){
|
||||
artists.push(this.props.artists[uri])
|
||||
if (this.props.album){
|
||||
var artists = []
|
||||
if (this.props.album.artists_uris && this.props.artists){
|
||||
for (var i = 0; i < this.props.album.artists_uris.length; i++){
|
||||
var uri = this.props.album.artists_uris[i]
|
||||
if (this.props.artists.hasOwnProperty(uri)){
|
||||
artists.push(this.props.artists[uri])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className="view album-view">
|
||||
<SidebarToggleButton />
|
||||
|
||||
return (
|
||||
<div className="view album-view">
|
||||
<SidebarToggleButton />
|
||||
<Thumbnail size="large" canZoom images={ this.props.album.images } />
|
||||
|
||||
<Thumbnail size="large" canZoom images={ this.props.album.images } />
|
||||
<div className="title">
|
||||
<div className="source grey-text">
|
||||
<FontAwesome name={helpers.sourceIcon( this.props.params.uri )} /> {helpers.uriSource( this.props.params.uri )} album
|
||||
</div>
|
||||
|
||||
<div className="title">
|
||||
<div className="source grey-text">
|
||||
<FontAwesome name={helpers.sourceIcon( this.props.params.uri )} /> {helpers.uriSource( this.props.params.uri )} album
|
||||
<h1>{ this.props.album.name }</h1>
|
||||
|
||||
<ul className="details">
|
||||
{ artists.length > 0 ? <li><ArtistSentence artists={artists} /></li> : null }
|
||||
{ this.props.album.release_date ? <li><Dater type="date" data={ this.props.album.release_date } /></li> : null }
|
||||
<li>
|
||||
{ this.props.album.tracks_total ? this.props.album.tracks_total : '0' } tracks,
|
||||
{ this.props.album.tracks ? <Dater type="total-time" data={this.props.album.tracks} /> : '0 mins' }
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h1>{ this.props.album.name }</h1>
|
||||
<div className="actions">
|
||||
<button className="primary" onClick={e => this.play()}>Play</button>
|
||||
{ helpers.uriSource(this.props.params.uri) == 'spotify' ? <FollowButton className="secondary" uri={this.props.params.uri} addText="Add to library" removeText="Remove from library" is_following={this.props.album.is_following} /> : null }
|
||||
</div>
|
||||
|
||||
<section className="list-wrapper">
|
||||
{ this.props.album.tracks ? <TrackList tracks={ this.props.album.tracks } uri={this.props.params.uri} /> : null }
|
||||
<LazyLoadListener enabled={this.props.album.tracks_more} loadMore={ () => this.loadMore() }/>
|
||||
</section>
|
||||
|
||||
<ul className="details">
|
||||
{ artists.length > 0 ? <li><ArtistSentence artists={artists} /></li> : null }
|
||||
{ this.props.album.release_date ? <li><Dater type="date" data={ this.props.album.release_date } /></li> : null }
|
||||
<li>
|
||||
{ this.props.album.tracks_total ? this.props.album.tracks_total : '0' } tracks,
|
||||
{ this.props.album.tracks ? <Dater type="total-time" data={this.props.album.tracks} /> : '0 mins' }
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
||||
<div className="actions">
|
||||
<button className="primary" onClick={e => this.play()}>Play</button>
|
||||
{ helpers.uriSource(this.props.params.uri) == 'spotify' ? <FollowButton className="secondary" uri={this.props.params.uri} addText="Add to library" removeText="Remove from library" is_following={this.props.album.is_following} /> : null }
|
||||
} else {
|
||||
return (
|
||||
<div className="view album-view">
|
||||
<SidebarToggleButton />
|
||||
<Thumbnail size="large" />
|
||||
<div className="title">
|
||||
<div className="source grey-text">
|
||||
<FontAwesome name={helpers.sourceIcon( this.props.params.uri )} /> {helpers.uriSource( this.props.params.uri )} album
|
||||
</div>
|
||||
<h1 className="grey-text">{ this.props.params.uri }</h1>
|
||||
<ul className="details">
|
||||
<li>
|
||||
0 tracks,
|
||||
0 mins
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="actions">
|
||||
<button className="primary" onClick={e => this.play()}>Play</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
<section className="list-wrapper">
|
||||
{ this.props.album.tracks ? <TrackList tracks={ this.props.album.tracks } uri={this.props.params.uri} /> : null }
|
||||
<LazyLoadListener enabled={this.props.album.tracks_more} loadMore={ () => this.loadMore() }/>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -65,7 +65,7 @@ class Artist extends React.Component{
|
||||
}
|
||||
break
|
||||
|
||||
case 'local':
|
||||
default:
|
||||
if (props.mopidy_connected){
|
||||
if (props.artist && props.artist.images){
|
||||
console.info('Loading local artist from index')
|
||||
@ -177,33 +177,52 @@ class Artist extends React.Component{
|
||||
}
|
||||
|
||||
render(){
|
||||
if( !this.props.artist ) return null
|
||||
var scheme = helpers.uriSource( this.props.params.uri );
|
||||
|
||||
var image = null
|
||||
if( this.props.artist.images ) image = helpers.sizedImages( this.props.artist.images ).huge
|
||||
if ( this.props.artist && this.props.artist.images ){
|
||||
var image = helpers.sizedImages( this.props.artist.images ).huge
|
||||
} else {
|
||||
var image = null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="view artist-view">
|
||||
if (this.props.artist){
|
||||
return (
|
||||
<div className="view artist-view">
|
||||
|
||||
<SidebarToggleButton />
|
||||
<SidebarToggleButton />
|
||||
|
||||
<div className="intro">
|
||||
<div className="intro">
|
||||
|
||||
<Parallax image={image} />
|
||||
<Parallax image={image} />
|
||||
|
||||
<div className="liner">
|
||||
<Thumbnail image={image} circle />
|
||||
<h1>{ this.props.artist.name }</h1>
|
||||
{ scheme == 'spotify' ? <div className="actions"><button className="primary" onClick={e => this.props.pusherActions.startRadio([this.props.artist.uri])}>Start radio</button><FollowButton className="white" uri={this.props.params.uri} removeText="Unfollow" addText="Follow" is_following={this.props.artist.is_following} /></div> : null}
|
||||
{ this.renderSubViewMenu() }
|
||||
<div className="liner">
|
||||
<Thumbnail image={image} circle />
|
||||
<h1>{this.props.artist ? this.props.artist.name : null}</h1>
|
||||
{ scheme == 'spotify' ? <div className="actions"><button className="primary" onClick={e => this.props.pusherActions.startRadio([this.props.artist.uri])}>Start radio</button><FollowButton className="white" uri={this.props.params.uri} removeText="Unfollow" addText="Follow" is_following={this.props.artist.is_following} /></div> : null}
|
||||
{ this.renderSubViewMenu() }
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{this.props.artist ? this.renderBody() : null}
|
||||
|
||||
</div>
|
||||
);
|
||||
|
||||
} else {
|
||||
return (
|
||||
<div className="view artist-view">
|
||||
<SidebarToggleButton />
|
||||
<div className="intro">
|
||||
<Parallax />
|
||||
<div className="liner">
|
||||
<Thumbnail circle />
|
||||
<h1 className="grey-text">{this.props.params.uri}</h1>
|
||||
{ this.renderSubViewMenu() }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{ this.renderBody() }
|
||||
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -46,7 +46,7 @@ class Playlist extends React.Component{
|
||||
this.props.spotifyActions.getPlaylist( props.params.uri );
|
||||
break
|
||||
|
||||
case 'm3u':
|
||||
default:
|
||||
if( props.mopidy_connected ) this.props.mopidyActions.getPlaylist( props.params.uri );
|
||||
break
|
||||
|
||||
@ -128,48 +128,72 @@ class Playlist extends React.Component{
|
||||
}
|
||||
|
||||
render(){
|
||||
if( !this.props.playlist || !this.props.playlist.name ) return null
|
||||
var scheme = helpers.uriSource( this.props.playlist.uri )
|
||||
|
||||
var scheme = helpers.uriSource( this.props.params.uri )
|
||||
var context = 'playlist-track'
|
||||
if (this.props.playlist.can_edit) context = 'editable-playlist-track'
|
||||
|
||||
return (
|
||||
<div className="view playlist-view">
|
||||
|
||||
<SidebarToggleButton />
|
||||
if (this.props.playlist){
|
||||
if (this.props.playlist.can_edit) context = 'editable-playlist-track'
|
||||
|
||||
<Thumbnail size="large" canZoom images={ this.props.playlist.images } />
|
||||
return (
|
||||
<div className="view playlist-view">
|
||||
|
||||
<SidebarToggleButton />
|
||||
|
||||
<div className="title">
|
||||
<div className="source grey-text">
|
||||
<FontAwesome name={helpers.sourceIcon( this.props.params.uri )} /> {helpers.uriSource( this.props.params.uri )} playlist
|
||||
<Thumbnail size="large" canZoom images={ this.props.playlist.images } />
|
||||
|
||||
<div className="title">
|
||||
<div className="source grey-text">
|
||||
<FontAwesome name={helpers.sourceIcon( this.props.params.uri )} /> {helpers.uriSource( this.props.params.uri )} playlist
|
||||
</div>
|
||||
<h1>{ this.props.playlist.name }</h1>
|
||||
{ this.props.playlist.description ? <div className="description grey-text" dangerouslySetInnerHTML={{__html: this.props.playlist.description}}></div> : null }
|
||||
|
||||
<ul className="details">
|
||||
{ this.props.playlist.owner ? <li><Link to={'/user/'+this.props.playlist.owner.uri}>{this.props.playlist.owner.id}</Link></li> : null }
|
||||
|
||||
{ this.props.playlist.followers ? <li>{this.props.playlist.followers.total.toLocaleString()} followers</li> : null }
|
||||
|
||||
{ this.props.playlist.last_modified ? <li><Dater type="ago" data={this.props.playlist.last_modified} /></li> : null }
|
||||
|
||||
<li>
|
||||
{ this.props.playlist.tracks_total ? this.props.playlist.tracks_total : '0'} tracks,
|
||||
{ this.props.playlist.tracks ? <Dater type="total-time" data={this.props.playlist.tracks} /> : '0 mins' }
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<h1>{ this.props.playlist.name }</h1>
|
||||
{ this.props.playlist.description ? <div className="description grey-text" dangerouslySetInnerHTML={{__html: this.props.playlist.description}}></div> : null }
|
||||
|
||||
<ul className="details">
|
||||
{ this.props.playlist.owner ? <li><Link to={'/user/'+this.props.playlist.owner.uri}>{this.props.playlist.owner.id}</Link></li> : null }
|
||||
{ this.renderActions() }
|
||||
|
||||
{ this.props.playlist.followers ? <li>{this.props.playlist.followers.total.toLocaleString()} followers</li> : null }
|
||||
|
||||
{ this.props.playlist.last_modified ? <li><Dater type="ago" data={this.props.playlist.last_modified} /></li> : null }
|
||||
|
||||
<li>
|
||||
{ this.props.playlist.tracks_total ? this.props.playlist.tracks_total : '0'} tracks,
|
||||
{ this.props.playlist.tracks ? <Dater type="total-time" data={this.props.playlist.tracks} /> : '0 mins' }
|
||||
</li>
|
||||
</ul>
|
||||
<section className="list-wrapper">
|
||||
{ this.props.playlist.tracks ? <TrackList uri={this.props.params.uri} context={context} tracks={this.props.playlist.tracks} removeTracks={ tracks_indexes => this.removeTracks(tracks_indexes) } reorderTracks={ (indexes, index) => this.reorderTracks(indexes, index) } /> : null }
|
||||
<LazyLoadListener enabled={this.props.playlist.tracks_more} loadMore={ () => this.loadMore() }/>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
|
||||
{ this.renderActions() }
|
||||
|
||||
<section className="list-wrapper">
|
||||
{ this.props.playlist.tracks ? <TrackList uri={this.props.params.uri} context={context} tracks={this.props.playlist.tracks} removeTracks={ tracks_indexes => this.removeTracks(tracks_indexes) } reorderTracks={ (indexes, index) => this.reorderTracks(indexes, index) } /> : null }
|
||||
<LazyLoadListener enabled={this.props.playlist.tracks_more} loadMore={ () => this.loadMore() }/>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div className="view playlist-view">
|
||||
<SidebarToggleButton />
|
||||
<Thumbnail size="large" />
|
||||
<div className="title">
|
||||
<div className="source grey-text">
|
||||
<FontAwesome name={helpers.sourceIcon( this.props.params.uri )} /> {helpers.uriSource( this.props.params.uri )} playlist
|
||||
</div>
|
||||
<h1 className="grey-text">{ this.props.params.uri }</h1>
|
||||
<ul className="details">
|
||||
<li>
|
||||
0 tracks,
|
||||
0 mins
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="actions">
|
||||
<button className="primary" onClick={ e => this.play() }>Play</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ import * as mopidyActions from '../services/mopidy/actions'
|
||||
class Queue extends React.Component{
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
super(props)
|
||||
}
|
||||
|
||||
removeTracks( tracks_indexes ){
|
||||
@ -59,6 +59,10 @@ class Queue extends React.Component{
|
||||
render(){
|
||||
var actions = (
|
||||
<span>
|
||||
<button onClick={e => this.props.uiActions.openModal('kiosk_mode')}>
|
||||
<FontAwesome name="television" />
|
||||
Kiosk mode
|
||||
</button>
|
||||
<button onClick={e => this.props.uiActions.openModal('edit_radio')}>
|
||||
<FontAwesome name="spotify" />
|
||||
Radio
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
opacity: 0.5;
|
||||
cursor: pointer;
|
||||
padding: 20px;
|
||||
z-index: 99;
|
||||
|
||||
img {
|
||||
width: 30px;
|
||||
@ -153,6 +154,46 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.kiosk_mode {
|
||||
background: #000000;
|
||||
overflow: hidden;
|
||||
|
||||
.content {
|
||||
padding: 5%;
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
text-align: center;
|
||||
|
||||
.background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
opacity: 0.2;
|
||||
max-width: none;
|
||||
|
||||
.image {
|
||||
@include blur();
|
||||
width: 110vw;
|
||||
padding-bottom: 110vh;
|
||||
}
|
||||
}
|
||||
|
||||
.foreground {
|
||||
width: 70vw;
|
||||
margin: 0 auto;
|
||||
max-width: 70vh;
|
||||
max-height: 70vh;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.current-track {
|
||||
padding-top: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
padding-top: 50px;
|
||||
text-align: center;
|
||||
|
||||
Reference in New Issue
Block a user