Moving providers into subset 'Services'

This commit is contained in:
James Barnsley
2018-03-30 22:37:38 +13:00
parent 96d4ec3bdf
commit 15a03affce
17 changed files with 4230 additions and 3969 deletions

View File

@ -5463,6 +5463,71 @@ main header {
white-space: wrap;
max-width: 200px; }
.services .menu {
margin: 0 -10px;
display: flex;
flex-wrap: wrap; }
.services .menu::after {
content: '';
clear: both;
display: block; }
.services .menu .menu-item-wrapper {
width: 25%;
max-width: 200px;
padding: 10px;
display: block;
box-sizing: border-box; }
.services .menu .menu-item-wrapper .menu-item {
-webkit-transition: all 0.2s ease-in-out;
-moz-transition: all 0.2s ease-in-out;
-o-transition: all 0.2s ease-in-out;
transition: all 0.2s ease-in-out;
position: relative;
padding: 30px 10px;
border-radius: 3px;
background: #121212;
display: block;
text-align: center;
height: 100%;
box-sizing: border-box;
border: 0; }
.services .menu .menu-item-wrapper .menu-item:hover {
background: #222222; }
.services .menu .menu-item-wrapper .menu-item.active {
background: #222222;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0; }
.services .menu .menu-item-wrapper .menu-item.active:after {
height: 10px;
bottom: -10px; }
.services .menu .menu-item-wrapper .menu-item:after {
content: '';
display: block;
width: 100%;
height: 0;
position: absolute;
bottom: 0;
left: 0;
background: #222222; }
.services .menu .menu-item-wrapper .menu-item .thumbnail {
margin-bottom: 15px;
display: inline-block; }
.services .menu .menu-item-wrapper .menu-item .fa {
font-size: 40px;
height: 40px;
margin-top: 10px;
margin-bottom: 15px; }
.services .menu .menu-item-wrapper .menu-item .title {
margin: 0;
padding: 0;
font-weight: bold;
font-size: 16px; }
.services .service {
padding: 20px 20px 10px 20px;
border-radius: 0 0 3px 3px;
background: #222222; }
.artist-view .body-loader:not(.lazy-loader) {
padding: 15vh 0; }
@ -6149,36 +6214,6 @@ main .track-view {
.settings-view .content-wrapper {
padding-top: 0; }
.settings-view .services {
margin-bottom: -20px; }
.settings-view .services::after {
content: '';
clear: both;
display: block; }
.settings-view .services .service {
margin-right: 20px;
margin-bottom: 20px;
padding-left: 46px;
box-sizing: border-box;
position: relative;
float: left;
min-width: 150px; }
.settings-view .services .service .title {
margin: 0;
padding: 0; }
.settings-view .services .service .icon {
position: absolute;
top: 2px;
left: 0;
width: 34px;
height: 25px;
padding-top: 9px;
border: 2px solid #383734;
text-align: center;
border-radius: 50%; }
.settings-view .services .service .icon .fa {
font-size: 16px; }
.settings-view .field.current-user .text .user {
border: 0;
position: relative; }
@ -6208,19 +6243,6 @@ main .track-view {
padding: 15vh 20px 0; }
.settings-view .content-wrapper {
padding-bottom: 60px; }
.settings-view .services {
text-align: center; }
.settings-view .services .service {
padding: 40px 0 0 0;
text-align: center;
min-width: 100px;
display: inline-block;
margin-right: 0;
float: none; }
.settings-view .services .service .icon {
top: 0;
left: 50%;
margin-left: -18px; }
.settings-view .field.pusher-connections .connection .col {
width: auto;
display: inline-block;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -38,7 +38,7 @@
// Release details
// These are automatically injected to built HTML
var build = "1521931467";
var build = "1522395485";
var version = "3.15.0";
// Construct the script tag

View File

@ -93,7 +93,7 @@ class App extends React.Component{
// TODO: Detect if we've gone BACK, and then restore to previous
// scroll position. We'll need to keep a running history of locations
// and scroll positions, which may be performance-hindering
$(window).scrollTop(0)
//$(window).scrollTop(0)
}
}

374
src/js/components/Services.js Executable file
View File

@ -0,0 +1,374 @@
import React, { PropTypes } from 'react'
import { connect } from 'react-redux'
import { Link } from 'react-router'
import { bindActionCreators } from 'redux'
import FontAwesome from 'react-fontawesome'
import Thumbnail from './Thumbnail'
import Icon from './Icon'
import URILink from './URILink'
import SpotifyAuthenticationFrame from '../components/SpotifyAuthenticationFrame'
import LastfmAuthenticationFrame from '../components/LastfmAuthenticationFrame'
import Snapcast from '../components/Snapcast'
import * as uiActions from '../services/ui/actions'
import * as coreActions from '../services/core/actions'
import * as mopidyActions from '../services/mopidy/actions'
import * as pusherActions from '../services/pusher/actions'
import * as spotifyActions from '../services/spotify/actions'
import * as lastfmActions from '../services/lastfm/actions'
class Services extends React.Component{
constructor(props){
super(props);
this.state = {
country: this.props.spotify.country,
locale: this.props.spotify.locale,
input_in_focus: null
}
}
componentDidMount(){
if (this.props.lastfm.session && this.props.core.users["lastfm:user:"+this.props.lastfm.session.name] === undefined){
this.props.lastfmActions.getMe();
}
}
componentWillReceiveProps(newProps){
var changed = false
var state = this.state
if (newProps.spotify.country != this.state.country && this.state.input_in_focus != 'country'){
state.country = newProps.spotify.country
changed = true
}
if (newProps.spotify.locale != this.state.locale && this.state.input_in_focus != 'locale'){
state.locale = newProps.spotify.locale
changed = true
}
if (changed){
this.setState(state)
}
}
handleBlur(name, value){
this.setState({input_in_focus: null})
var data = {}
data[name] = value
this.props.coreActions.set(data)
}
renderSpotify(){
var authorization_button = null;
var send_authorization_button = null;
if (this.props.spotify.authorization){
authorization_button = (
<button onClick={e => this.props.uiActions.openModal('send_authorization', {}) }>
Share authentication
</button>
);
send_authorization_button = (
<button onClick={e => this.props.uiActions.openModal('send_authorization', {}) }>
Share authentication
</button>
);
}
var user_object = this.props.spotify.me;
if (user_object){
var user = (
<URILink className="user" type="user" uri={user_object.uri}>
<Thumbnail circle={true} size="small" images={user_object.images} />
<span className="user-name">
{user_object.display_name ? user_object.display_name : user_object.id}
{!this.props.spotify.authorization ? <span className="grey-text">&nbsp;&nbsp;(Limited access)</span> : null}
</span>
</URILink>
)
} else {
var user = (
<URILink className="user">
<Thumbnail circle={true} size="small" />
<span className="user-name">
Unknown
</span>
</URILink>
)
}
var not_installed = null;
if (!this.props.mopidy.uri_schemes || !this.props.mopidy.uri_schemes.includes('spotify:')){
not_installed = (
<div>
<p className="message warning"><em>Mopidy-Spotify</em> extension is not running - you will not be able to play any Spotify tracks</p>
<br />
</div>
);
}
return (
<div>
{not_installed}
<div className="field">
<div className="name">Country</div>
<div className="input">
<input
type="text"
onChange={e => this.setState({country: e.target.value})}
onFocus={e => this.setState({input_in_focus: 'country'})}
onBlur={e => this.props.spotifyActions.set({country: e.target.value})}
value={ this.state.country } />
<div className="description">
An <a href="http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2" target="_blank">ISO 3166-1 alpha-2</a> country code (eg <em>NZ</em>)
</div>
</div>
</div>
<div className="field">
<div className="name">Locale</div>
<div className="input">
<input
type="text"
onChange={e => this.setState({locale: e.target.value})}
onFocus={e => this.setState({input_in_focus: 'locale'})}
onBlur={e => this.props.spotifyActions.set({locale: e.target.value})}
value={this.state.locale} />
<div className="description">
Lowercase <a href="http://en.wikipedia.org/wiki/ISO_639" target="_blank">ISO 639 language code</a> and an uppercase <a href="http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2" target="_blank">ISO 3166-1 alpha-2 country code</a>, joined by an underscore (eg <em>en_NZ</em>)
</div>
</div>
</div>
<div className="field current-user">
<div className="name">Current user</div>
<div className="input">
<div className="text">
{user}
</div>
</div>
</div>
<div className="field">
<div className="name">Authorization</div>
<div className="input">
<SpotifyAuthenticationFrame />
{send_authorization_button}
{this.props.spotify.refreshing_token ? <button className="working">Refreshing...</button> : <button onClick={e => this.props.spotifyActions.refreshingToken()}>Force token refresh</button>}
</div>
</div>
</div>
);
}
renderLastfm(){
var user_object = this.props.core.users["lastfm:user:"+this.props.lastfm.session.name];
if (user_object){
var user = (
<span className="user">
<Thumbnail circle={true} size="small" images={user_object.image} />
<span className="user-name">
{user_object.realname ? user_object.realname : user_object.name}
</span>
</span>
)
} else {
var user = (
<span className="user">
<Thumbnail circle={true} size="small" />
<span className="user-name">
Unknown
</span>
</span>
)
}
return (
<div>
{this.props.lastfm.session ? <div className="field current-user">
<div className="name">Current user</div>
<div className="input">
<div className="text">
{user}
</div>
</div>
</div> : null}
<div className="field">
<div className="name">Authorization</div>
<div className="input">
<LastfmAuthenticationFrame />
</div>
</div>
</div>
);
}
renderIcecast(){
return (
<div>
<div className="field checkbox">
<div className="name">Enable</div>
<div className="input">
<label>
<input
type="checkbox"
name="ssl"
checked={this.props.core.http_streaming_enabled}
onChange={e => this.props.coreActions.set({http_streaming_enabled: !this.props.core.http_streaming_enabled})} />
<span className="label">
Stream audio to this browser
</span>
</label>
</div>
</div>
<div className="field radio">
<div className="name">Encoding</div>
<div className="input">
<label>
<input
type="radio"
name="http_streaming_encoding"
checked={this.props.core.http_streaming_encoding == 'mpeg'}
onChange={e => this.props.coreActions.set({http_streaming_encoding: 'mpeg'})} />
<span className="label">mpeg (mp3)</span>
</label>
<label>
<input
type="radio"
name="http_streaming_encoding"
checked={this.props.core.http_streaming_encoding == 'ogg'}
onChange={e => this.props.coreActions.set({http_streaming_encoding: 'ogg'})} />
<span className="label">ogg</span>
</label>
</div>
</div>
<div className="field">
<div className="name">Location</div>
<div className="input">
<input
type="text"
onChange={e => this.props.coreActions.set({http_streaming_url: e.target.value})}
value={this.props.core.http_streaming_url} />
<div className="description">
The full URL to your stream endpoint
</div>
</div>
</div>
</div>
);
}
renderMenu(){
if (this.props.spotify.me){
var spotify_user = <Thumbnail circle={true} size="small" images={this.props.spotify.me.images} />
} else {
var spotify_user = <Thumbnail circle={true} size="small" />
}
if (this.props.core.users["lastfm:user:"+this.props.lastfm.session.name]){
var lastfm_user = <Thumbnail circle={true} size="small" images={this.props.core.users["lastfm:user:"+this.props.lastfm.session.name].image} />
} else {
var lastfm_user = <Thumbnail circle={true} size="small" />
}
return (
<div className="menu">
<div className="menu-item-wrapper">
<Link className={"menu-item"+(this.props.active == 'spotify' ? ' active' : '')} to={this.props.active == 'spotify' ? global.baseURL+'settings' : global.baseURL+'settings/service/spotify'}>
{spotify_user}
<div className="title">
Spotify
</div>
{this.props.spotify.authorization ? <span className="status green-text">Authorized</span> : <span className="status grey-text">Read-only</span>}
</Link>
</div>
<div className="menu-item-wrapper">
<Link className={"menu-item"+(this.props.active == 'lastfm' ? ' active' : '')} to={this.props.active == 'lastfm' ? global.baseURL+'settings' : global.baseURL+'settings/service/lastfm'}>
{lastfm_user}
<div className="title">
LastFM
</div>
{this.props.lastfm.session ? <span className="status green-text">Authorized</span> : <span className="status grey-text">Read-only</span>}
</Link>
</div>
<div className="menu-item-wrapper">
<Link className={"menu-item"+(this.props.active == 'snapcast' ? ' active' : '')} to={this.props.active == 'snapcast' ? global.baseURL+'settings' : global.baseURL+'settings/service/snapcast'}>
<FontAwesome name="rss" />
<div className="title">
Snapcast
</div>
{this.props.pusher.config.snapcast_enabled ? <span className="status green-text">Enabled</span> : <span className="status grey-text">Disabled</span>}
</Link>
</div>
<div className="menu-item-wrapper">
<Link className={"menu-item"+(this.props.active == 'icecast' ? ' active' : '')} to={this.props.active == 'icecast' ? global.baseURL+'settings' : global.baseURL+'settings/service/icecast'}>
<FontAwesome name="rss" />
<div className="title">
Icecast
</div>
{this.props.core.http_streaming_enabled ? <span className="status green-text">Enabled</span> : <span className="status grey-text">Disabled</span>}
</Link>
</div>
</div>
);
}
renderService(){
var service = null;
switch (this.props.active){
case 'spotify':
service = this.renderSpotify();
break;
case 'lastfm':
service = this.renderLastfm();
break;
case 'icecast':
service = this.renderIcecast();
break;
case 'snapcast':
service = <Snapcast />
break;
}
if (service){
return (
<div className="service">
{service}
</div>
);
} else {
return null;
}
}
render(){
return (
<div className="services">
{this.renderMenu()}
{this.renderService()}
</div>
);
}
}
const mapStateToProps = (state, ownProps) => {
return state
}
const mapDispatchToProps = (dispatch) => {
return {
coreActions: bindActionCreators(coreActions, dispatch),
uiActions: bindActionCreators(uiActions, dispatch),
pusherActions: bindActionCreators(pusherActions, dispatch),
mopidyActions: bindActionCreators(mopidyActions, dispatch),
spotifyActions: bindActionCreators(spotifyActions, dispatch),
lastfmActions: bindActionCreators(lastfmActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Services)

View File

@ -1,43 +0,0 @@
import React, { PropTypes } from 'react'
import { connect } from 'react-redux'
import { createStore, bindActionCreators } from 'redux'
import FontAwesome from 'react-fontawesome'
class URISchemesList extends React.Component{
constructor(props){
super(props)
}
render(){
if (!this.props.uri_schemes ) return null;
return (
<div className="uri-schemes-list">
{
this.props.uri_schemes.map((scheme, index) => {
scheme = scheme.replace(':','')
return (
<span key={index+'_'+scheme}>
{ index > 0 ? <span>, </span> : null }
<span className="uri-scheme">
{ scheme }
</span>
</span>
)
})
}
</div>
);
}
}
const mapStateToProps = (state, ownProps) => {
return {
uri_schemes: (state.mopidy.uri_schemes ? state.mopidy.uri_schemes : [])
}
}
export default connect(mapStateToProps)(URISchemesList)

View File

@ -20,9 +20,9 @@ import User from './views/User'
import Track from './views/Track'
import Queue from './views/Queue'
import QueueHistory from './views/QueueHistory'
import Settings from './views/Settings'
import Debug from './views/Debug'
import Search from './views/Search'
import Settings from './views/Settings'
import DiscoverRecommendations from './views/discover/DiscoverRecommendations'
import DiscoverFeatured from './views/discover/DiscoverFeatured'
@ -61,9 +61,8 @@ ReactDOM.render(
<IndexRoute component={Queue} />
<Route path="queue" component={Queue} />
<Route path="queue/history" component={QueueHistory} />
<Route path="settings" component={Settings} />
<Route path="settings/debug" component={Debug} />
<Route path="settings(/:sub_view)" component={Settings} />
<Route path="settings(/service/:service)" component={Settings} />
<Route path="search(/:query)" component={Search} />
<Route path="album/:uri" component={Album} />

View File

@ -20,14 +20,6 @@ export function disconnect(){
}
}
export function instruct(call, value){
return {
type: 'MOPIDY_INSTRUCT',
call: call,
value: value
}
}
export function debug(call, value){
return {
type: 'MOPIDY_DEBUG',

View File

@ -128,7 +128,7 @@ const MopidyMiddleware = (function(){
* @param string value (optional) = value of the property to pass
* @return promise
**/
const instruct = (ws, store, call, value = {}) => {
const request = (ws, store, call, value = {}) => {
if (!store.getState().mopidy.connected){
return false;
}
@ -227,7 +227,7 @@ const MopidyMiddleware = (function(){
break;
case 'MOPIDY_DEBUG':
instruct(socket, store, action.call, action.value )
request(socket, store, action.call, action.value )
.then(response => {
store.dispatch({type: 'DEBUG', response: response});
})
@ -239,7 +239,7 @@ const MopidyMiddleware = (function(){
**/
case 'MOPIDY_GET_PLAY_STATE':
instruct(socket, store, 'playback.getState')
request(socket, store, 'playback.getState')
.then(
response => {
store.dispatch({
@ -251,7 +251,7 @@ const MopidyMiddleware = (function(){
break
case 'MOPIDY_PLAY':
instruct(socket, store, 'playback.play')
request(socket, store, 'playback.play')
.then(
response => {
store.dispatch({
@ -274,7 +274,7 @@ const MopidyMiddleware = (function(){
break;
case 'MOPIDY_PAUSE':
instruct(socket, store, 'playback.pause')
request(socket, store, 'playback.pause')
.then(
response => {
store.dispatch({
@ -297,7 +297,7 @@ const MopidyMiddleware = (function(){
break
case 'MOPIDY_NEXT':
instruct(socket, store, 'playback.next');
request(socket, store, 'playback.next');
store.dispatch(pusherActions.deliverBroadcast(
'notification',
@ -312,7 +312,7 @@ const MopidyMiddleware = (function(){
break
case 'MOPIDY_STOP':
instruct(socket, store, 'playback.stop');
request(socket, store, 'playback.stop');
store.dispatch(pusherActions.deliverBroadcast(
'notification',
@ -327,7 +327,7 @@ const MopidyMiddleware = (function(){
break
case 'MOPIDY_CHANGE_TRACK':
instruct(socket, store, 'playback.play', {tlid: action.tlid});
request(socket, store, 'playback.play', {tlid: action.tlid});
store.dispatch(pusherActions.deliverBroadcast(
'notification',
@ -341,7 +341,7 @@ const MopidyMiddleware = (function(){
break;
case 'MOPIDY_REMOVE_TRACKS':
instruct(socket, store, 'tracklist.remove', {tlid: action.tlids});
request(socket, store, 'tracklist.remove', {tlid: action.tlids});
store.dispatch(pusherActions.deliverBroadcast(
'notification',
{
@ -354,7 +354,7 @@ const MopidyMiddleware = (function(){
break;
case 'MOPIDY_GET_REPEAT':
instruct(socket, store, 'tracklist.getRepeat')
request(socket, store, 'tracklist.getRepeat')
.then(
response => {
store.dispatch({
@ -366,11 +366,11 @@ const MopidyMiddleware = (function(){
break;
case 'MOPIDY_SET_REPEAT':
instruct(socket, store, 'tracklist.setRepeat', [action.repeat]);
request(socket, store, 'tracklist.setRepeat', [action.repeat]);
break;
case 'MOPIDY_GET_RANDOM':
instruct(socket, store, 'tracklist.getRandom')
request(socket, store, 'tracklist.getRandom')
.then(
response => {
store.dispatch({
@ -382,11 +382,11 @@ const MopidyMiddleware = (function(){
break;
case 'MOPIDY_SET_RANDOM':
instruct(socket, store, 'tracklist.setRandom', [action.random]);
request(socket, store, 'tracklist.setRandom', [action.random]);
break;
case 'MOPIDY_GET_CONSUME':
instruct(socket, store, 'tracklist.getConsume')
request(socket, store, 'tracklist.getConsume')
.then(
response => {
store.dispatch({
@ -398,11 +398,11 @@ const MopidyMiddleware = (function(){
break;
case 'MOPIDY_SET_CONSUME':
instruct(socket, store, 'tracklist.setConsume', [action.consume]);
request(socket, store, 'tracklist.setConsume', [action.consume]);
break;
case 'MOPIDY_GET_MUTE':
instruct(socket, store, 'mixer.getMute')
request(socket, store, 'mixer.getMute')
.then(
response => {
store.dispatch({
@ -414,7 +414,7 @@ const MopidyMiddleware = (function(){
break;
case 'MOPIDY_SET_MUTE':
instruct(socket, store, 'mixer.setMute', [action.mute]);
request(socket, store, 'mixer.setMute', [action.mute]);
store.dispatch(pusherActions.deliverBroadcast(
'notification',
{
@ -427,7 +427,7 @@ const MopidyMiddleware = (function(){
break;
case 'MOPIDY_GET_VOLUME':
instruct(socket, store, 'playback.getVolume')
request(socket, store, 'playback.getVolume')
.then(
response => {
store.dispatch({
@ -439,7 +439,7 @@ const MopidyMiddleware = (function(){
break;
case 'MOPIDY_SET_VOLUME':
instruct(socket, store, 'playback.setVolume', {volume: action.volume})
request(socket, store, 'playback.setVolume', {volume: action.volume})
.then(
response => {
store.dispatch({
@ -451,7 +451,7 @@ const MopidyMiddleware = (function(){
break;
case 'MOPIDY_SET_TIME_POSITION':
instruct(socket, store, 'playback.seek', {time_position: action.time_position})
request(socket, store, 'playback.seek', {time_position: action.time_position})
.then(
response => {
store.dispatch({
@ -463,7 +463,7 @@ const MopidyMiddleware = (function(){
break;
case 'MOPIDY_GET_TIME_POSITION':
instruct(socket, store, 'playback.getTimePosition')
request(socket, store, 'playback.getTimePosition')
.then(
response => {
store.dispatch({
@ -475,7 +475,7 @@ const MopidyMiddleware = (function(){
break;
case 'MOPIDY_GET_URI_SCHEMES':
instruct(socket, store, 'getUriSchemes')
request(socket, store, 'getUriSchemes')
.then(
response => {
var uri_schemes = response;
@ -546,7 +546,7 @@ const MopidyMiddleware = (function(){
// fetch the playlist tracks via backend
// add each track by URI
instruct(socket, store, 'playlists.lookup', {uri: action.uri})
request(socket, store, 'playlists.lookup', {uri: action.uri})
.then(
response => {
if (response.tracks === undefined){
@ -679,7 +679,7 @@ const MopidyMiddleware = (function(){
params.at_position = batch.at_position + batch.offset
}
instruct(socket, store, 'tracklist.add', params)
request(socket, store, 'tracklist.add', params)
.then(
response => {
// add metadata to queue
@ -729,7 +729,7 @@ const MopidyMiddleware = (function(){
var first_uri = action.uris[0];
// add our first track
instruct(socket, store, 'tracklist.add', { uri: first_uri, at_position: 0 })
request(socket, store, 'tracklist.add', { uri: first_uri, at_position: 0 })
.then(
response => {
// play it (only if we got a successful lookup)
@ -774,7 +774,7 @@ const MopidyMiddleware = (function(){
case 'MOPIDY_REORDER_TRACKLIST':
// add our first track
instruct(socket, store, 'tracklist.move', { start: action.range_start, end: action.range_start + action.range_length, to_position: action.insert_before } )
request(socket, store, 'tracklist.move', { start: action.range_start, end: action.range_start + action.range_length, to_position: action.insert_before } )
.then(
response => {
// TODO: when complete, send event to confirm success/failure
@ -789,7 +789,7 @@ const MopidyMiddleware = (function(){
break;
case 'MOPIDY_CLEAR_TRACKLIST':
instruct(socket, store, 'tracklist.clear');
request(socket, store, 'tracklist.clear');
store.dispatch(pusherActions.deliverBroadcast(
'notification',
{
@ -894,7 +894,7 @@ const MopidyMiddleware = (function(){
))
}
instruct(socket, store, 'library.search', {query: {album: [action.data.query]}, uris: [action.data.uri_scheme]})
request(socket, store, 'library.search', {query: {album: [action.data.query]}, uris: [action.data.uri_scheme]})
.then(
response => {
if (response.length > 0){
@ -963,7 +963,7 @@ const MopidyMiddleware = (function(){
))
}
instruct(socket, store, 'library.search', {query: {artist: [action.data.query]}, uris: [action.data.uri_scheme]})
request(socket, store, 'library.search', {query: {artist: [action.data.query]}, uris: [action.data.uri_scheme]})
.then(
response => {
if (response.length > 0){
@ -1029,7 +1029,7 @@ const MopidyMiddleware = (function(){
store.dispatch(uiActions.processFinishing('MOPIDY_GET_SEARCH_RESULTS_PROCESSOR'))
}
instruct(socket, store, 'playlists.asList')
request(socket, store, 'playlists.asList')
.then(
response => {
if (response.length > 0){
@ -1087,7 +1087,7 @@ const MopidyMiddleware = (function(){
))
}
instruct(socket, store, 'library.search', {query: {any: [action.data.query]}, uris: [action.data.uri_scheme]})
request(socket, store, 'library.search', {query: {any: [action.data.query]}, uris: [action.data.uri_scheme]})
.then(
response => {
if (response.length > 0 && response[0].tracks !== undefined){
@ -1122,7 +1122,7 @@ const MopidyMiddleware = (function(){
remaining: (action.data.uri_schemes.length) + 1
}
));
instruct(socket, store, 'library.search', {query: {any: [action.data.query]}, uris: [action.data.uri_scheme]})
request(socket, store, 'library.search', {query: {any: [action.data.query]}, uris: [action.data.uri_scheme]})
.then(
response => {
if (response.length > 0 && response[0].tracks !== undefined){
@ -1154,7 +1154,7 @@ const MopidyMiddleware = (function(){
remaining: (action.data.uri_schemes.length) + 0.75
}
));
instruct(socket, store, 'library.search', {query: {album: [action.data.query]}, uris: [action.data.uri_scheme]})
request(socket, store, 'library.search', {query: {album: [action.data.query]}, uris: [action.data.uri_scheme]})
.then(
response => {
if (response.length > 0){
@ -1210,7 +1210,7 @@ const MopidyMiddleware = (function(){
remaining: (action.data.uri_schemes.length) + 0.5
}
));
instruct(socket, store, 'library.search', {query: {artist: [action.data.query]}, uris: [action.data.uri_scheme]})
request(socket, store, 'library.search', {query: {artist: [action.data.query]}, uris: [action.data.uri_scheme]})
.then(
response => {
if (response.length > 0){
@ -1288,7 +1288,7 @@ const MopidyMiddleware = (function(){
remaining: (action.data.uri_schemes.length) + 0.25
}
))
instruct(socket, store, 'playlists.asList')
request(socket, store, 'playlists.asList')
.then(
response => {
if (response.length > 0){
@ -1341,7 +1341,7 @@ const MopidyMiddleware = (function(){
**/
case 'MOPIDY_GET_LIBRARY_PLAYLISTS':
instruct(socket, store, 'playlists.asList' )
request(socket, store, 'playlists.asList' )
.then(response => {
// drop in our URI list
@ -1360,7 +1360,7 @@ const MopidyMiddleware = (function(){
// get the full playlist objects
for (var i = 0; i < playlist_uris_filtered.length; i++){
instruct(socket, store, 'playlists.lookup', { uri: playlist_uris_filtered[i] })
request(socket, store, 'playlists.lookup', { uri: playlist_uris_filtered[i] })
.then(response => {
var source = helpers.uriSource(response.uri)
var playlist = Object.assign(
@ -1387,7 +1387,7 @@ const MopidyMiddleware = (function(){
break;
case 'MOPIDY_GET_PLAYLIST':
instruct(socket, store, 'playlists.lookup', action.data )
request(socket, store, 'playlists.lookup', action.data )
.then(response => {
var playlist = Object.assign(
{},
@ -1422,7 +1422,7 @@ const MopidyMiddleware = (function(){
var tracks = Object.assign([], action.tracks)
var uris = helpers.arrayOf('uri',tracks)
instruct(socket, store, 'library.lookup', { uris: uris })
request(socket, store, 'library.lookup', { uris: uris })
.then(response => {
for(var uri in response){
if (response.hasOwnProperty(uri)){
@ -1455,7 +1455,7 @@ const MopidyMiddleware = (function(){
case 'MOPIDY_ADD_PLAYLIST_TRACKS':
instruct(socket, store, 'playlists.lookup', { uri: action.key })
request(socket, store, 'playlists.lookup', { uri: action.key })
.then(response => {
var tracks = [];
for(var i = 0; i < action.tracks_uris.length; i++){
@ -1472,7 +1472,7 @@ const MopidyMiddleware = (function(){
playlist.tracks = tracks
}
instruct(socket, store, 'playlists.save', { playlist: playlist } )
request(socket, store, 'playlists.save', { playlist: playlist } )
.then(response => {
store.dispatch({
type: 'PLAYLIST_TRACKS_ADDED',
@ -1492,13 +1492,13 @@ const MopidyMiddleware = (function(){
var indexes = Object.assign([], action.tracks_indexes)
indexes.sort(descending);
instruct(socket, store, 'playlists.lookup', { uri: action.key })
request(socket, store, 'playlists.lookup', { uri: action.key })
.then(response => {
var playlist = Object.assign({}, response)
for(var i = 0; i < indexes.length; i++){
playlist.tracks.splice(indexes[i], 1);
}
instruct(socket, store, 'playlists.save', { playlist: playlist } )
request(socket, store, 'playlists.save', { playlist: playlist } )
.then(response => {
store.dispatch({
type: 'PLAYLIST_TRACKS_REMOVED',
@ -1512,10 +1512,10 @@ const MopidyMiddleware = (function(){
case 'MOPIDY_SAVE_PLAYLIST':
var uri = action.key
instruct(socket, store, 'playlists.lookup', { uri: action.key })
request(socket, store, 'playlists.lookup', { uri: action.key })
.then(response => {
var playlist = Object.assign({}, response, { name: action.name })
instruct(socket, store, 'playlists.save', { playlist: playlist } )
request(socket, store, 'playlists.save', { playlist: playlist } )
.then(response => {
store.dispatch({
@ -1541,7 +1541,7 @@ const MopidyMiddleware = (function(){
break
case 'MOPIDY_REORDER_PLAYLIST_TRACKS':
instruct(socket, store, 'playlists.lookup', { uri: action.key })
request(socket, store, 'playlists.lookup', { uri: action.key })
.then(response => {
var playlist = Object.assign({}, response)
@ -1571,7 +1571,7 @@ const MopidyMiddleware = (function(){
// update playlist
playlist = Object.assign({}, playlist, { tracks: tracks })
instruct(socket, store, 'playlists.save', { playlist: playlist } )
request(socket, store, 'playlists.save', { playlist: playlist } )
.then(response => {
store.dispatch({
type: 'MOPIDY_RESOLVE_PLAYLIST_TRACKS',
@ -1583,7 +1583,7 @@ const MopidyMiddleware = (function(){
break
case 'MOPIDY_CREATE_PLAYLIST':
instruct(socket, store, 'playlists.create', { name: action.name, uri_scheme: action.scheme })
request(socket, store, 'playlists.create', { name: action.name, uri_scheme: action.scheme })
.then(response => {
store.dispatch(uiActions.createNotification({content: 'Created playlist'}))
@ -1602,7 +1602,7 @@ const MopidyMiddleware = (function(){
break
case 'MOPIDY_DELETE_PLAYLIST':
instruct(socket, store, 'playlists.delete', { uri: action.uri })
request(socket, store, 'playlists.delete', { uri: action.uri })
.then(response => {
store.dispatch(uiActions.createNotification({content: 'Deleted playlist'}))
store.dispatch({
@ -1622,7 +1622,7 @@ const MopidyMiddleware = (function(){
var last_run = store.getState().ui.processes.MOPIDY_LIBRARY_ALBUMS_PROCESSOR
if (!last_run){
instruct(socket, store, 'library.browse', { uri: 'local:directory?type=album' } )
request(socket, store, 'library.browse', { uri: 'local:directory?type=album' } )
.then(response => {
if (response.length <= 0) return
@ -1682,7 +1682,7 @@ const MopidyMiddleware = (function(){
break
case 'MOPIDY_GET_ALBUMS':
instruct(socket, store, 'library.lookup', { uris: action.uris } )
request(socket, store, 'library.lookup', { uris: action.uris } )
.then(response => {
if (response.length <= 0) return
@ -1725,7 +1725,7 @@ const MopidyMiddleware = (function(){
break;
case 'MOPIDY_GET_ALBUM':
instruct(socket, store, 'library.lookup', action.data )
request(socket, store, 'library.lookup', action.data )
.then(response => {
if (response.length <= 0) return
@ -1756,7 +1756,7 @@ const MopidyMiddleware = (function(){
}
}
instruct(socket, store, 'library.lookup', { uris: uris } )
request(socket, store, 'library.lookup', { uris: uris } )
.then(response => {
for(var uri in response){
@ -1793,7 +1793,7 @@ const MopidyMiddleware = (function(){
* ======================================================================================
**/
case 'MOPIDY_GET_LIBRARY_ARTISTS':
instruct(socket, store, 'library.browse', { uri: 'local:directory?type=artist' } )
request(socket, store, 'library.browse', { uri: 'local:directory?type=artist' } )
.then(response => {
if (response.length <= 0) return
@ -1821,7 +1821,7 @@ const MopidyMiddleware = (function(){
var last_run = store.getState().ui.processes.MOPIDY_LIBRARY_ARTISTS_PROCESSOR
if (!last_run){
instruct(socket, store, 'library.browse', { uri: 'local:directory?type=artist' } )
request(socket, store, 'library.browse', { uri: 'local:directory?type=artist' } )
.then(response => {
if (response.length <= 0) return;
@ -1868,7 +1868,7 @@ const MopidyMiddleware = (function(){
**/
case 'MOPIDY_GET_ARTIST':
instruct(socket, store, 'library.lookup', action.data )
request(socket, store, 'library.lookup', action.data )
.then(response => {
if (response.length <= 0) return
@ -1928,7 +1928,7 @@ const MopidyMiddleware = (function(){
break;
case 'MOPIDY_GET_ARTISTS':
instruct(socket, store, 'library.lookup', {uris: action.uris})
request(socket, store, 'library.lookup', {uris: action.uris})
.then(response => {
if (response.length <= 0) return
@ -1973,7 +1973,7 @@ const MopidyMiddleware = (function(){
**/
case 'MOPIDY_GET_QUEUE':
instruct(socket, store, 'tracklist.getTlTracks')
request(socket, store, 'tracklist.getTlTracks')
.then(
response => {
store.dispatch({
@ -1985,7 +1985,7 @@ const MopidyMiddleware = (function(){
break;
case 'MOPIDY_GET_QUEUE_HISTORY':
instruct(socket, store, 'history.getHistory')
request(socket, store, 'history.getHistory')
.then(
response => {
store.dispatch({
@ -1997,7 +1997,7 @@ const MopidyMiddleware = (function(){
break;
case 'MOPIDY_GET_CURRENT_TRACK':
instruct(socket, store, 'playback.getCurrentTlTrack')
request(socket, store, 'playback.getCurrentTlTrack')
.then(
response => {
if (response && response.track){
@ -2024,7 +2024,7 @@ const MopidyMiddleware = (function(){
break;
case 'MOPIDY_GET_TRACK':
instruct(socket, store, 'library.lookup', action.data )
request(socket, store, 'library.lookup', action.data )
.then(
response => {
if (response.length > 0){
@ -2052,7 +2052,7 @@ const MopidyMiddleware = (function(){
case 'MOPIDY_GET_IMAGES':
instruct(socket, store, 'library.getImages', {uris: action.uris})
request(socket, store, 'library.getImages', {uris: action.uris})
.then(response => {
var records = [];
@ -2091,7 +2091,7 @@ const MopidyMiddleware = (function(){
case 'MOPIDY_GET_DIRECTORY':
store.dispatch({ type: 'MOPIDY_DIRECTORY_LOADED', data: false })
instruct(socket, store, 'library.browse', action.data)
request(socket, store, 'library.browse', action.data)
.then(response => {
store.dispatch({
type: 'MOPIDY_DIRECTORY_LOADED',

View File

@ -321,7 +321,7 @@ const PusherMiddleware = (function(){
var core = store.getState().core;
if (!core.country || !core.locale){
store.dispatch(coreActions.set({
store.dispatch(spotifyActions.set({
country: response.config.country,
locale: response.config.locale
}))

View File

@ -358,7 +358,7 @@ export function getFeaturedPlaylists(){
var timestamp = year+'-'+month+'-'+day+'T'+hour+':'+min+':'+sec;
sendRequest(dispatch, getState, 'browse/featured-playlists?limit=50&country='+getState().core.country+'&locale='+getState().core.locale+'timestamp='+timestamp)
sendRequest(dispatch, getState, 'browse/featured-playlists?limit=50&country='+getState().spotify.country+'&locale='+getState().spotify.locale+'timestamp='+timestamp)
.then(
response => {
var playlists = []
@ -402,7 +402,7 @@ export function getFeaturedPlaylists(){
export function getCategories(){
return (dispatch, getState) => {
sendRequest(dispatch, getState, 'browse/categories?limit=50&country='+getState().core.country+'&locale='+getState().core.locale )
sendRequest(dispatch, getState, 'browse/categories?limit=50&country='+getState().spotify.country+'&locale='+getState().spotify.locale )
.then(
response => {
dispatch({
@ -432,7 +432,7 @@ export function getCategory(id){
});
// get the category
sendRequest(dispatch, getState, 'browse/categories/'+id+'?country='+getState().core.country+'&locale='+getState().core.locale )
sendRequest(dispatch, getState, 'browse/categories/'+id+'?country='+getState().spotify.country+'&locale='+getState().spotify.locale )
.then(
response => {
var category = Object.assign({}, response)
@ -451,7 +451,7 @@ export function getCategory(id){
)
// and the category's playlists
sendRequest(dispatch, getState, 'browse/categories/'+id+'/playlists?limit=50&country='+getState().core.country+'&locale='+getState().core.locale )
sendRequest(dispatch, getState, 'browse/categories/'+id+'/playlists?limit=50&country='+getState().spotify.country+'&locale='+getState().spotify.locale )
.then(
response => {
var playlists = []
@ -490,7 +490,7 @@ export function getCategory(id){
export function getNewReleases(){
return (dispatch, getState) => {
sendRequest(dispatch, getState, 'browse/new-releases?country='+getState().core.country+'&limit=50' )
sendRequest(dispatch, getState, 'browse/new-releases?country='+getState().spotify.country+'&limit=50' )
.then(
response => {
dispatch({
@ -578,7 +578,7 @@ export function getSearchResults(type, query, limit = 50, offset = 0){
var url = 'search?q='+query
url += '&type='+type
url += '&country='+getState().core.country
url += '&country='+getState().spotify.country
url += '&limit='+limit
url += '&offset='+offset
@ -670,7 +670,7 @@ export function getAutocompleteResults(field_id, query, types = ['album','artist
var endpoint = 'search?q='+query
endpoint += '&type='+types.join(',')
endpoint += '&country='+getState().core.country
endpoint += '&country='+getState().spotify.country
sendRequest(dispatch, getState, endpoint)
.then(
@ -1112,7 +1112,7 @@ export function getArtist(uri, full = false){
if (full){
requests.push(
sendRequest(dispatch, getState, 'artists/'+ helpers.getFromUri('artistid', uri) +'/top-tracks?country='+getState().core.country )
sendRequest(dispatch, getState, 'artists/'+ helpers.getFromUri('artistid', uri) +'/top-tracks?country='+getState().spotify.country )
.then(
response => {
Object.assign(artist, response);
@ -1163,7 +1163,7 @@ export function getArtist(uri, full = false){
// Now go get our artist albums
if (full){
sendRequest(dispatch, getState, 'artists/'+ helpers.getFromUri('artistid', uri) +'/albums?market='+getState().core.country )
sendRequest(dispatch, getState, 'artists/'+ helpers.getFromUri('artistid', uri) +'/albums?market='+getState().spotify.country )
.then(
response => {
dispatch({
@ -1235,7 +1235,7 @@ export function playArtistTopTracks(uri){
// We need to load the artist's top tracks first
} else {
sendRequest(dispatch, getState, 'artists/'+ helpers.getFromUri('artistid', uri) +'/top-tracks?country='+getState().core.country )
sendRequest(dispatch, getState, 'artists/'+ helpers.getFromUri('artistid', uri) +'/top-tracks?country='+getState().spotify.country )
.then(
response => {
const uris = helpers.arrayOf('uri',response.tracks)
@ -1531,7 +1531,7 @@ export function getPlaylist(uri){
return (dispatch, getState) => {
// get the main playlist object
sendRequest(dispatch, getState, 'users/'+ helpers.getFromUri('userid',uri) +'/playlists/'+ helpers.getFromUri('playlistid',uri) +'?market='+getState().core.country )
sendRequest(dispatch, getState, 'users/'+ helpers.getFromUri('userid',uri) +'/playlists/'+ helpers.getFromUri('playlistid',uri) +'?market='+getState().spotify.country )
.then(
response => {
@ -1665,7 +1665,7 @@ export function getPlaylistTracksAndPlay(uri){
'Loading playlist tracks',
{
uri: uri,
next: 'users/'+ helpers.getFromUri('userid',uri) +'/playlists/'+ helpers.getFromUri('playlistid',uri) +'/tracks?market='+getState().core.country
next: 'users/'+ helpers.getFromUri('userid',uri) +'/playlists/'+ helpers.getFromUri('playlistid',uri) +'/tracks?market='+getState().spotify.country
}
))
}

View File

@ -5,14 +5,8 @@ import { Link, hashHistory } from 'react-router'
import { bindActionCreators } from 'redux'
import FontAwesome from 'react-fontawesome'
import SpotifyAuthenticationFrame from '../components/SpotifyAuthenticationFrame'
import ConfirmationButton from '../components/ConfirmationButton'
import PusherConnectionList from '../components/PusherConnectionList'
import URISchemesList from '../components/URISchemesList'
import VersionManager from '../components/VersionManager'
import Header from '../components/Header'
import Thumbnail from '../components/Thumbnail'
import Snapcast from '../components/Snapcast'
import * as uiActions from '../services/ui/actions'
import * as pusherActions from '../services/pusher/actions'
@ -27,7 +21,8 @@ class Debug extends React.Component{
mopidy_call: 'playlists.asList',
mopidy_data: '{}',
pusher_data: '{"method":"get_config"}',
access_token: this.props.access_token
access_token: this.props.access_token,
toggling_test_mode: false
}
}
@ -41,6 +36,14 @@ class Debug extends React.Component{
this.props.pusherActions.debug(JSON.parse(this.state.pusher_data) )
}
toggleTestMode(e){
this.setState({toggling_test_mode: true});
this.props.uiActions.set({ test_mode: !this.props.test_mode });
// Wait a second to allow state to update, and then refresh
setTimeout(location.reload(), 1000);
}
render(){
var options = (
@ -60,24 +63,22 @@ class Debug extends React.Component{
<h4 className="underline">User interface</h4>
<form>
<div className="field checkbox">
<div className="name">Test mode</div>
<div className="input">
{this.state.toggling_test_mode ? <span className="button working">Applying...</span> : (this.props.test_mode ? <span className="button destructive" onClick={e => this.toggleTestMode(e)}>Disable</span> : <span className="button primary" onClick={e => this.toggleTestMode(e)}>Enable</span>)}
</div>
</div>
<div className="field checkbox">
<div className="name">Debug</div>
<div className="input">
<label>
<input
type="checkbox"
name="debug_info"
checked={ this.props.test_mode }
onChange={ e => this.props.uiActions.set({ test_mode: !this.props.test_mode })} />
<span className="label">Test mode</span>
</label>
<label>
<input
type="checkbox"
name="debug_info"
checked={ this.props.debug_info }
onChange={ e => this.props.uiActions.set({ debug_info: !this.props.debug_info })} />
<span className="label">Debug info</span>
<span className="label">Show debug overlay</span>
</label>
</div>
</div>
@ -120,11 +121,6 @@ class Debug extends React.Component{
</div>
</form>
<h4 className="underline">Snapcast (beta)</h4>
<div className="field">
<Snapcast />
</div>
<h4 className="underline">Spotify</h4>
<div className="field">
<div className="name">Access token</div>

View File

@ -5,17 +5,15 @@ import { hashHistory, Link } from 'react-router'
import { bindActionCreators } from 'redux'
import FontAwesome from 'react-fontawesome'
import SpotifyAuthenticationFrame from '../components/SpotifyAuthenticationFrame'
import LastfmAuthenticationFrame from '../components/LastfmAuthenticationFrame'
import ConfirmationButton from '../components/ConfirmationButton'
import PusherConnectionList from '../components/PusherConnectionList'
import URISchemesList from '../components/URISchemesList'
import VersionManager from '../components/VersionManager'
import Header from '../components/Header'
import Parallax from '../components/Parallax'
import Icon from '../components/Icon'
import Thumbnail from '../components/Thumbnail'
import URILink from '../components/URILink'
import Services from '../components/Services'
import * as coreActions from '../services/core/actions'
import * as uiActions from '../services/ui/actions'
@ -29,8 +27,6 @@ class Settings extends React.Component {
constructor(props){
super(props);
this.state = {
country: this.props.core.country,
locale: this.props.core.locale,
mopidy_host: this.props.mopidy.host,
mopidy_port: this.props.mopidy.port,
mopidy_ssl: this.props.mopidy.ssl,
@ -39,26 +35,10 @@ class Settings extends React.Component {
}
}
componentDidMount(){
if (this.props.lastfm.session && this.props.core.users["lastfm:user:"+this.props.lastfm.session.name] === undefined){
this.props.lastfmActions.getMe();
}
}
componentWillReceiveProps(newProps){
var changed = false
var state = this.state
if (newProps.core.country != this.state.country && this.state.input_in_focus != 'country'){
state.country = newProps.core.country
changed = true
}
if (newProps.core.locale != this.state.locale && this.state.input_in_focus != 'locale'){
state.locale = newProps.core.locale
changed = true
}
if (newProps.pusher.username != this.state.pusher_username && this.state.input_in_focus != 'pusher_username'){
state.pusher_username = newProps.pusher.username
changed = true
@ -123,65 +103,6 @@ class Settings extends React.Component {
)
}
renderSpotifyUser(){
var user = this.props.spotify.me;
if (user){
return (
<URILink className="user" type="user" uri={user.uri}>
<Thumbnail circle={true} size="small" images={user.images} />
<span className="user-name">
{user.display_name ? user.display_name : user.id}
{!this.props.spotify.authorization ? <span className="grey-text">&nbsp;&nbsp;(Limited access)</span> : null}
</span>
</URILink>
)
} else {
return (
<URILink className="user">
<Thumbnail circle={true} size="small" />
<span className="user-name">
Unknown
</span>
</URILink>
)
}
}
renderLastfmUser(){
var user = this.props.core.users["lastfm:user:"+this.props.lastfm.session.name];
if (user){
return (
<span className="user">
<Thumbnail circle={true} size="small" images={user.image} />
<span className="user-name">
{user.realname ? user.realname : user.name}
</span>
</span>
)
} else {
return (
<span className="user">
<Thumbnail circle={true} size="small" />
<span className="user-name">
Unknown
</span>
</span>
)
}
}
renderSendAuthorizationButton(){
if (!this.props.spotify.authorization) return null
return (
<button onClick={e => this.props.uiActions.openModal('send_authorization', {}) }>
Share authentication
</button>
)
}
renderServerStatus(){
var colour = 'grey';
var icon = 'question-circle';
@ -207,36 +128,6 @@ class Settings extends React.Component {
);
}
renderSpotifyStatus(){
var colour = 'grey';
var icon = 'question-circle';
var status = 'Unknown';
if (this.props.spotify.connecting){
icon = 'plug';
status = 'Connecting...'
} else if (!this.props.spotify.connected){
colour = 'red';
icon = 'close';
status = 'Disconnected';
} else if (this.props.mopidy.connected){
colour = 'green';
icon = 'check';
status = 'Connected';
}
if (!this.props.mopidy.uri_schemes || !this.props.mopidy.uri_schemes.includes('spotify:')){
colour = 'orange';
status += ' (Mopidy-Spotify extension not installed/enabled!)';
}
return (
<span className={colour+'-text'}>
<FontAwesome name={icon} />&nbsp; {status}
</span>
);
}
render(){
var options = (
@ -331,138 +222,10 @@ class Settings extends React.Component {
</div>
</div>
{this.renderApplyButton()}
</form>
<h4 className="underline">Streaming</h4>
</form>
<div className="field checkbox">
<div className="name">Enable</div>
<div className="input">
<label>
<input
type="checkbox"
name="ssl"
checked={this.props.core.http_streaming_enabled}
onChange={e => this.props.coreActions.set({http_streaming_enabled: !this.props.core.http_streaming_enabled})} />
<span className="label has-tooltip">
Stream audio to this browser
<span className="tooltip">Requires streaming service like Icecast2</span>
</span>
</label>
</div>
</div>
<div className="field radio">
<div className="name">Encoding</div>
<div className="input">
<label>
<input
type="radio"
name="http_streaming_encoding"
checked={this.props.core.http_streaming_encoding == 'mpeg'}
onChange={e => this.props.coreActions.set({http_streaming_encoding: 'mpeg'})} />
<span className="label">mpeg (mp3)</span>
</label>
<label>
<input
type="radio"
name="http_streaming_encoding"
checked={this.props.core.http_streaming_encoding == 'ogg'}
onChange={e => this.props.coreActions.set({http_streaming_encoding: 'ogg'})} />
<span className="label">ogg</span>
</label>
</div>
</div>
<div className="field">
<div className="name">Location</div>
<div className="input">
<input
type="text"
onChange={e => this.props.coreActions.set({http_streaming_url: e.target.value})}
value={this.props.core.http_streaming_url} />
<div className="description">
The full URL to your stream endpoint
</div>
</div>
</div>
<h4 className="underline">Localization</h4>
<div className="field">
<div className="name">Country</div>
<div className="input">
<input
type="text"
onChange={e => this.setState({country: e.target.value})}
onFocus={e => this.setState({input_in_focus: 'country'})}
onBlur={e => this.handleBlur('country',e.target.value)}
value={ this.state.country } />
<div className="description">
An <a href="http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2" target="_blank">ISO 3166-1 alpha-2</a> country code (eg <em>NZ</em>)
</div>
</div>
</div>
<div className="field">
<div className="name">Locale</div>
<div className="input">
<input
type="text"
onChange={e => this.setState({locale: e.target.value})}
onFocus={e => this.setState({input_in_focus: 'locale'})}
onBlur={e => this.handleBlur('locale',e.target.value)}
value={this.state.locale} />
<div className="description">
Lowercase <a href="http://en.wikipedia.org/wiki/ISO_639" target="_blank">ISO 639 language code</a> and an uppercase <a href="http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2" target="_blank">ISO 3166-1 alpha-2 country code</a>, joined by an underscore (eg <em>en_NZ</em>)
</div>
</div>
</div>
<h4 className="underline">Spotify</h4>
<div className="field">
<div className="name">Status</div>
<div className="input">
<div className="text">
{this.renderSpotifyStatus()}
</div>
</div>
</div>
<div className="field current-user">
<div className="name">Current user</div>
<div className="input">
<div className="text">
{ this.renderSpotifyUser() }
</div>
</div>
</div>
<div className="field">
<div className="name">Authorization</div>
<div className="input">
<SpotifyAuthenticationFrame />
{ this.renderSendAuthorizationButton() }
{this.props.spotify.refreshing_token ? <button className="working">Refreshing...</button> : <button onClick={e => this.props.spotifyActions.refreshingToken()}>Force token refresh</button>}
</div>
</div>
<h4 className="underline">LastFM</h4>
{this.props.lastfm.session ? <div className="field current-user">
<div className="name">Current user</div>
<div className="input">
<div className="text">
{ this.renderLastfmUser() }
</div>
</div>
</div> : null}
<div className="field">
<div className="name">Authorization</div>
<div className="input">
<LastfmAuthenticationFrame />
</div>
</div>
<h4 className="underline">Services</h4>
<Services active={this.props.params.service} />
<h4 className="underline">Advanced</h4>
@ -492,15 +255,6 @@ class Settings extends React.Component {
</div>
</div>
<div className="field">
<div className="name">Extensions</div>
<div className="input">
<div className="text">
<URISchemesList />
</div>
</div>
</div>
<div className="field">
<div className="name">Version</div>
<div className="input">

View File

@ -27,6 +27,7 @@
@import 'components/sub-views';
@import 'components/debug';
@import 'components/tooltip';
@import 'components/services';
@import 'views/artist';
@import 'views/user';

View File

@ -0,0 +1,82 @@
.services {
.menu {
@include clearfix();
margin: 0 -10px;
display: flex;
flex-wrap: wrap;
.menu-item-wrapper {
width: 25%;
max-width: 200px;
padding: 10px;
display: block;
box-sizing: border-box;
.menu-item {
@include animate();
position: relative;
padding: 30px 10px;
border-radius: 3px;
background: $darkest_grey;
display: block;
text-align: center;
height: 100%;
box-sizing: border-box;
border: 0;
&:hover {
background: lighten($dark_grey, 4%);
}
&.active {
background: lighten($dark_grey, 4%);
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
&:after {
height: 10px;
bottom: -10px;
}
}
&:after {
content: '';
display: block;
width: 100%;
height: 0;
position: absolute;
bottom: 0;
left: 0;
background: lighten($dark_grey, 4%);
}
.thumbnail {
margin-bottom: 15px;
display: inline-block;
}
.fa {
font-size: 40px;
height: 40px;
margin-top: 10px;
margin-bottom: 15px;
}
.title {
margin: 0;
padding: 0;
font-weight: bold;
font-size: 16px;
}
}
}
}
.service {
padding: 20px 20px 10px 20px;
border-radius: 0 0 3px 3px;
background: lighten($dark_grey, 4%);
}
}

View File

@ -30,42 +30,6 @@
.content-wrapper {
padding-top: 0;
}
.services {
@include clearfix();
margin-bottom: -20px;
.service {
margin-right: 20px;
margin-bottom: 20px;
padding-left: 46px;
box-sizing: border-box;
position: relative;
float: left;
min-width: 150px;
.title {
margin: 0;
padding: 0;
}
.icon {
position: absolute;
top: 2px;
left: 0;
width: 34px;
height: 25px;
padding-top: 9px;
border: 2px solid $grey;
text-align: center;
border-radius: 50%;
.fa {
font-size: 16px;
}
}
}
}
.field.current-user {
.text {
@ -115,25 +79,6 @@
padding-bottom: 60px;
}
.services {
text-align: center;
.service {
padding: 40px 0 0 0;
text-align: center;
min-width: 100px;
display: inline-block;
margin-right: 0;
float: none;
.icon {
top: 0;
left: 50%;
margin-left: -18px;
}
}
}
.field.pusher-connections {
.connection {
.col {