Kiosk mode; Placeholder for unloaded albums/artists/playlists; Gmusic integration part 1

This commit is contained in:
James Barnsley
2017-02-23 22:06:43 +13:00
parent 5aad7e0d45
commit 4bb52070f8
10 changed files with 272 additions and 92 deletions

View 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)

View File

@ -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>

View File

@ -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){

View File

@ -141,6 +141,9 @@ export let sourceIcon = function(uri,source = null){
case 'm3u':
return 'folder'
break
case 'gmusic':
return 'google'
break
default:
return source
}

View File

@ -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)

View File

@ -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,&nbsp;
{ 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,&nbsp;
{ 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>
);
}
}
}

View File

@ -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>
);
);
}
}
}

View File

@ -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,&nbsp;
{ 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,&nbsp;
{ 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>
)
}
}
}

View File

@ -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" />&nbsp;
Kiosk mode
</button>
<button onClick={e => this.props.uiActions.openModal('edit_radio')}>
<FontAwesome name="spotify" />&nbsp;
Radio

View File

@ -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;