2018-03-12 21:39:27 +13:00
|
|
|
|
|
|
|
|
import React, { PropTypes } from 'react'
|
|
|
|
|
import { connect } from 'react-redux'
|
|
|
|
|
import { bindActionCreators } from 'redux'
|
|
|
|
|
import { Link, hashHistory } from 'react-router'
|
|
|
|
|
import FontAwesome from 'react-fontawesome'
|
|
|
|
|
|
|
|
|
|
import ArtistSentence from './ArtistSentence'
|
2018-04-19 13:34:16 +12:00
|
|
|
import VolumeControl from './Fields/VolumeControl'
|
|
|
|
|
import LatencyControl from './Fields/LatencyControl'
|
|
|
|
|
import TextField from './Fields/TextField'
|
2018-04-29 21:10:57 +12:00
|
|
|
import DropdownField from './Fields/DropdownField'
|
2018-03-23 16:54:56 +13:00
|
|
|
|
2018-03-12 21:39:27 +13:00
|
|
|
import * as helpers from '../helpers'
|
|
|
|
|
import * as coreActions from '../services/core/actions'
|
|
|
|
|
import * as uiActions from '../services/ui/actions'
|
|
|
|
|
import * as pusherActions from '../services/pusher/actions'
|
|
|
|
|
|
|
|
|
|
class Snapcast extends React.Component{
|
|
|
|
|
|
|
|
|
|
constructor(props){
|
2018-03-23 16:54:56 +13:00
|
|
|
super(props);
|
2018-03-12 21:39:27 +13:00
|
|
|
}
|
|
|
|
|
|
2018-03-21 21:40:47 +13:00
|
|
|
componentDidMount(){
|
2018-03-26 10:36:13 +13:00
|
|
|
if (this.props.pusher_connected && this.props.snapcast_enabled){
|
2018-03-21 21:40:47 +13:00
|
|
|
this.props.pusherActions.getSnapcast();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
componentWillReceiveProps(newProps){
|
2018-03-26 10:36:13 +13:00
|
|
|
|
|
|
|
|
// Just connected
|
2018-04-03 17:01:40 +12:00
|
|
|
if (newProps.snapcast_enabled && !this.props.pusher_connected && newProps.pusher_connected){
|
2018-03-26 10:36:13 +13:00
|
|
|
this.props.pusherActions.getSnapcast();
|
2018-04-19 06:20:54 +12:00
|
|
|
|
|
|
|
|
// Just enabled
|
|
|
|
|
// This is the more probable scenario as we don't know if we're enabled until pusher connects
|
|
|
|
|
// and then gets the config from the server
|
|
|
|
|
} else if (!this.props.snapcast_enabled && newProps.snapcast_enabled && newProps.pusher_connected){
|
|
|
|
|
this.props.pusherActions.getSnapcast();
|
2018-03-21 21:40:47 +13:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-01 08:57:09 +12:00
|
|
|
renderClientsList(group, groups){
|
|
|
|
|
|
|
|
|
|
if (!group.clients || group.clients.length <= 0){
|
|
|
|
|
return (
|
|
|
|
|
<div className="text grey-text">
|
|
|
|
|
No clients
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="list clients">
|
|
|
|
|
{
|
|
|
|
|
group.clients.map(client => {
|
|
|
|
|
var name = client.config.name ? client.config.name : client.host.name;
|
|
|
|
|
var groups_dropdown = [];
|
|
|
|
|
for (var i = 0; i < groups.length; i++){
|
|
|
|
|
|
|
|
|
|
// Don't add our existing group
|
|
|
|
|
if (groups[i].id !== group.id){
|
|
|
|
|
groups_dropdown.push({
|
|
|
|
|
label: (groups[i].name ? groups[i].name : 'Group '+(i+1)),
|
|
|
|
|
value: groups[i].id
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// And append with 'New group' (which is actually
|
|
|
|
|
// the existing group and middleware handles the behavior shift)
|
|
|
|
|
groups_dropdown.push({
|
|
|
|
|
label: 'New group',
|
|
|
|
|
value: group.id
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="list-item client" key={client.id}>
|
|
|
|
|
<div className="col name">
|
|
|
|
|
<DropdownField
|
|
|
|
|
className="group-dropdown-field"
|
|
|
|
|
icon="cog"
|
|
|
|
|
name="Group"
|
|
|
|
|
no_label
|
|
|
|
|
no_status_icon
|
|
|
|
|
value={group.id}
|
|
|
|
|
options={groups_dropdown}
|
|
|
|
|
handleChange={value => {this.props.pusherActions.setSnapcastClientGroup(client.id, value); this.props.uiActions.hideContextMenu()}}
|
|
|
|
|
/>
|
|
|
|
|
<TextField
|
|
|
|
|
onChange={value => this.props.pusherActions.setSnapcastClientName(client.id, value)}
|
|
|
|
|
value={name}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="col volume">
|
|
|
|
|
<VolumeControl
|
|
|
|
|
volume={client.config.volume.percent}
|
|
|
|
|
mute={client.config.volume.muted}
|
|
|
|
|
onVolumeChange={percent => this.props.pusherActions.setSnapcastClientVolume(client.id, percent)}
|
|
|
|
|
onMuteChange={mute => this.props.pusherActions.setSnapcastClientMute(client.id, mute)}
|
|
|
|
|
/>
|
|
|
|
|
<TextField
|
|
|
|
|
className="tiny"
|
|
|
|
|
onChange={value => this.props.pusherActions.setSnapcastClientVolume(client.id, parseInt(value))}
|
|
|
|
|
value={client.config.volume.percent}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="col latency">
|
|
|
|
|
<LatencyControl
|
|
|
|
|
max="100"
|
|
|
|
|
value={client.config.latency}
|
|
|
|
|
onChange={value => this.props.pusherActions.setSnapcastClientLatency(client.id, parseInt(value))}
|
|
|
|
|
/>
|
|
|
|
|
<TextField
|
|
|
|
|
className="tiny"
|
|
|
|
|
onChange={value => this.props.pusherActions.setSnapcastClientLatency(client.id, parseInt(value))}
|
|
|
|
|
value={String(client.config.latency)}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-12 21:39:27 +13:00
|
|
|
render(){
|
2018-04-03 17:01:40 +12:00
|
|
|
if (!this.props.snapcast_enabled){
|
|
|
|
|
return (
|
|
|
|
|
<div>
|
|
|
|
|
To enable Snapcast, edit your <code>mopidy.conf</code> file
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-22 16:59:04 +13:00
|
|
|
if (!this.props.snapcast_clients || !this.props.snapcast_groups){
|
2018-04-03 17:01:40 +12:00
|
|
|
return (
|
|
|
|
|
<div className="lazy-loader body-loader loading">
|
|
|
|
|
<div className="loader"></div>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
2018-03-12 21:39:27 +13:00
|
|
|
}
|
|
|
|
|
|
2018-04-29 21:10:57 +12:00
|
|
|
var streams = [];
|
|
|
|
|
for (var key in this.props.snapcast_streams){
|
|
|
|
|
if (this.props.snapcast_streams.hasOwnProperty(key)){
|
|
|
|
|
streams.push(this.props.snapcast_streams[key]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-22 16:59:04 +13:00
|
|
|
// Construct a simple array of our groups index
|
|
|
|
|
var groups = [];
|
|
|
|
|
for (var group_id in this.props.snapcast_groups){
|
|
|
|
|
if (this.props.snapcast_groups.hasOwnProperty(group_id)){
|
|
|
|
|
var group = this.props.snapcast_groups[group_id];
|
2018-03-21 22:08:53 +13:00
|
|
|
|
2018-03-22 16:59:04 +13:00
|
|
|
// Merge the group's clients into this group (also as a simple array)
|
|
|
|
|
var clients = [];
|
|
|
|
|
for (var i = 0; i < group.clients_ids.length; i++){
|
|
|
|
|
if (this.props.snapcast_clients.hasOwnProperty(group.clients_ids[i])){
|
2018-04-29 10:03:19 +12:00
|
|
|
var client = this.props.snapcast_clients[group.clients_ids[i]];
|
|
|
|
|
if (client.connected || this.props.snapcast_show_disconnected_clients){
|
|
|
|
|
clients.push(client);
|
|
|
|
|
}
|
2018-03-21 22:08:53 +13:00
|
|
|
}
|
2018-03-22 16:59:04 +13:00
|
|
|
}
|
2018-03-21 22:08:53 +13:00
|
|
|
|
2018-03-22 16:59:04 +13:00
|
|
|
groups.push(Object.assign(
|
|
|
|
|
{},
|
|
|
|
|
group,
|
|
|
|
|
{
|
|
|
|
|
clients: clients
|
|
|
|
|
}
|
|
|
|
|
));
|
2018-03-21 22:08:53 +13:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-12 21:39:27 +13:00
|
|
|
return (
|
|
|
|
|
<div className="snapcast">
|
2018-04-29 10:03:19 +12:00
|
|
|
|
|
|
|
|
<div className="field checkbox">
|
|
|
|
|
<div className="input">
|
|
|
|
|
<button onClick={e => this.props.pusherActions.getSnapcast()}>Refresh</button>
|
|
|
|
|
<label>
|
|
|
|
|
<input
|
|
|
|
|
type="checkbox"
|
|
|
|
|
name="snapcast_show_disconnected_clients"
|
|
|
|
|
checked={this.props.snapcast_show_disconnected_clients}
|
|
|
|
|
onChange={e => this.props.uiActions.set({ snapcast_show_disconnected_clients: !this.props.snapcast_show_disconnected_clients })} />
|
|
|
|
|
<span className="label">
|
|
|
|
|
Show disconnected clients
|
|
|
|
|
</span>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2018-03-12 21:39:27 +13:00
|
|
|
{
|
2018-05-01 08:57:09 +12:00
|
|
|
groups.map((group, i) => {
|
2018-03-12 21:39:27 +13:00
|
|
|
return (
|
2018-04-29 21:10:57 +12:00
|
|
|
<div className="group" key={group.id}>
|
2018-04-30 20:34:15 +12:00
|
|
|
<div className="field">
|
|
|
|
|
<div className="name">
|
2018-05-01 08:57:09 +12:00
|
|
|
Name
|
2018-04-30 20:34:15 +12:00
|
|
|
</div>
|
|
|
|
|
<div className="input">
|
|
|
|
|
<div className="text">
|
2018-05-01 08:57:09 +12:00
|
|
|
{group.name ? group.name : 'Group '+(i+1)}
|
|
|
|
|
<span className="grey-text">({group.id})</span>
|
2018-04-30 20:34:15 +12:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2018-04-29 21:10:57 +12:00
|
|
|
<div className="field dropdown">
|
|
|
|
|
<div className="name">
|
2018-05-01 08:57:09 +12:00
|
|
|
Stream
|
2018-04-29 21:10:57 +12:00
|
|
|
</div>
|
|
|
|
|
<div className="input">
|
|
|
|
|
<select onChange={e => this.props.pusherActions.setSnapcastGroupStream(group.id, e.target.value)} value={group.stream_id}>
|
|
|
|
|
{
|
|
|
|
|
streams.map(stream => {
|
|
|
|
|
return (
|
|
|
|
|
<option value={stream.id} key={stream.id}>
|
|
|
|
|
{stream.id} ({stream.status})
|
|
|
|
|
</option>
|
|
|
|
|
);
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
</select>
|
2018-03-23 16:54:56 +13:00
|
|
|
</div>
|
2018-04-29 21:10:57 +12:00
|
|
|
</div>
|
|
|
|
|
<div className="field">
|
|
|
|
|
<div className="name">
|
2018-05-01 21:03:05 +12:00
|
|
|
Volume
|
|
|
|
|
</div>
|
|
|
|
|
<div className="input">
|
|
|
|
|
<VolumeControl
|
|
|
|
|
volume={group.volume}
|
|
|
|
|
mute={group.muted}
|
|
|
|
|
onVolumeChange={percent => this.props.pusherActions.setSnapcastGroupVolume(group.id, percent)}
|
|
|
|
|
onMuteChange={mute => this.props.pusherActions.setSnapcastGroupMute(group.id, mute)}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="field">
|
|
|
|
|
<div className="name">
|
2018-04-29 21:10:57 +12:00
|
|
|
Clients
|
2018-04-18 17:01:57 +12:00
|
|
|
</div>
|
2018-04-29 21:10:57 +12:00
|
|
|
<div className="input">
|
2018-05-01 08:57:09 +12:00
|
|
|
{this.renderClientsList(group, groups)}
|
2018-04-18 17:01:57 +12:00
|
|
|
</div>
|
2018-03-23 16:54:56 +13:00
|
|
|
</div>
|
2018-03-12 21:39:27 +13:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const mapStateToProps = (state, ownProps) => {
|
|
|
|
|
return {
|
2018-03-26 10:36:13 +13:00
|
|
|
snapcast_enabled: state.pusher.config.snapcast_enabled,
|
2018-03-21 21:40:47 +13:00
|
|
|
pusher_connected: state.pusher.connected,
|
2018-04-29 10:03:19 +12:00
|
|
|
snapcast_show_disconnected_clients: (state.ui.snapcast_show_disconnected_clients !== undefined ? state.ui.snapcast_show_disconnected_clients : false),
|
2018-04-29 21:10:57 +12:00
|
|
|
snapcast_streams: state.pusher.snapcast_streams,
|
2018-03-21 22:08:53 +13:00
|
|
|
snapcast_groups: state.pusher.snapcast_groups,
|
|
|
|
|
snapcast_clients: state.pusher.snapcast_clients
|
2018-03-12 21:39:27 +13:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const mapDispatchToProps = (dispatch) => {
|
|
|
|
|
return {
|
|
|
|
|
coreActions: bindActionCreators(coreActions, dispatch),
|
|
|
|
|
uiActions: bindActionCreators(uiActions, dispatch),
|
|
|
|
|
pusherActions: bindActionCreators(pusherActions, dispatch)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default connect(mapStateToProps, mapDispatchToProps)(Snapcast)
|