Better UI for snapcast

This commit is contained in:
James Barnsley
2018-04-18 17:01:57 +12:00
parent e1b8836c53
commit 4e049d403e
10 changed files with 26297 additions and 23735 deletions

View File

@ -0,0 +1,48 @@
import React from 'react'
export default class TextField extends React.Component{
constructor(props){
super(props);
this.state = {
in_focus: false,
value: (this.props.value ? this.props.value : '')
}
}
componentWillReceiveProps(newProps){
if (!this.state.in_focus){
this.setState({value: newProps.value})
}
}
handleChange(e){
this.setState({value: e.target.value});
}
handleFocus(e){
this.setState({in_focus: true});
}
handleBlur(e){
this.setState({in_focus: false});
if (this.state.value !== this.props.value){
this.props.onChange(this.state.value);
}
}
render(){
return (
<input
className={this.props.className ? this.props.className : ''}
type="text"
onChange={e => this.handleChange(e)}
onFocus={e => this.handleFocus(e)}
onBlur={e => this.handleBlur(e)}
value={this.state.value}
/>
);
}
}

View File

@ -0,0 +1,107 @@
import React, { PropTypes } from 'react';
export default class LatencyControl extends React.Component{
constructor(props){
super(props);
}
handleClick(e){
var slider = e.target;
if (slider.className != 'slider'){
slider = slider.parentElement;
}
var sliderX = e.clientX - slider.getBoundingClientRect().left;
var sliderWidth = slider.getBoundingClientRect().width;
// Clicked left half
if (sliderX < (sliderWidth/2)){
var percent = Math.round((sliderX / (sliderWidth/2) ) * 100);
// Invert our percentage
percent = 100 - percent;
var value = -(this.props.max * (percent / 100));
// Handle maximum value limits
if (value < -this.props.max){
value = -this.props.max;
}
// Second half
} else {
// Subtract half the slider's width from our click position
sliderX = sliderX - (sliderWidth/2);
var percent = Math.round((sliderX / (sliderWidth/2) ) * 100);
var value = this.props.max * (percent / 100);
// Handle maximum value limits
if (value > this.props.max){
value = this.props.max;
}
}
this.props.onChange(value);
}
handleWheel(e){
// Identify which direction we've scrolled (inverted)
// This is simplified and doesn't consider momentum as it varies wildly
// between browsers and devices
var direction = (e.deltaY > 0 ? -1 : 1)
var value = this.props.value;
value += direction * 5
if (value > this.props.max){
value = this.props.max;
} else if (value < -this.props.max){
value = -this.props.max;
}
this.props.onChange(value);
e.preventDefault();
}
render(){
// Zero, or positive value
if (this.props.value >= 0){
var value = this.props.value;
if (value > this.props.max){
value = this.props.max;
}
var percentage = Math.round((value / this.props.max) * 100 / 2);
var left = 50;
var width = percentage;
// Negative value
// We reverse it to a positive for easier maths and style rules
} else {
var value = -this.props.value;
if (value < -this.props.max){
value = -this.props.max;
}
var percentage = Math.round((value / this.props.max) * 100 / 2);
var left = 50 - percentage;
var width = percentage;
}
return (
<span className="latency-control" onWheel={e => this.handleWheel(e)}>
<div className="slider-wrapper">
<div className="slider horizontal" onClick={e => this.handleClick(e)}>
<div className="track">
<div className="progress" style={{ width: width+'%', left: left+'%' }}></div>
</div>
</div>
</div>
</span>
);
}
}

View File

@ -7,6 +7,8 @@ import FontAwesome from 'react-fontawesome'
import ArtistSentence from './ArtistSentence'
import VolumeControl from './VolumeControl'
import LatencyControl from './LatencyControl'
import TextField from './Forms/TextField'
import * as helpers from '../helpers'
import * as coreActions from '../services/core/actions'
@ -17,10 +19,6 @@ class Snapcast extends React.Component{
constructor(props){
super(props);
this.state = {
editing_client: null
}
}
componentDidMount(){
@ -37,11 +35,6 @@ class Snapcast extends React.Component{
}
}
saveEditingClient(){
this.props.pusherActions.setSnapcastClientName(this.state.editing_client.id, this.state.editing_client.name);
this.setState({editing_client: null});
}
render(){
if (!this.props.snapcast_enabled){
return (
@ -88,39 +81,59 @@ class Snapcast extends React.Component{
{
groups.map(group => {
return (
<div className="group" key={group.id}>
<div className="inner">
<div className="list group" key={group.id}>
<div className="list-item header">
<div className="name">
{group.name ? group.name : 'Untitled group'}
</div>
<div className="volume">
Volume
</div>
<div className="latency">
Latency
</div>
</div>
<div className="clients">
{
group.clients.map(client => {
var name = client.config.name ? client.config.name : client.host.name;
return (
<div className={"client "+(client.connected ? 'connected' : 'disconnected')} key={client.id}>
<div className="inner">
<div className="name">
{name} {!client.connected ? '(disconnected)' : null}
</div>
<div className="controls">
<div className="control edit" onClick={e => this.props.uiActions.openModal('edit_snapcast_client',{id: client.id})}>
<FontAwesome name="cog" />
</div>
<VolumeControl
volume={client.config.volume.percent}
mute={client.config.volume.muted}
onVolumeChange={percent => this.props.pusherActions.setSnapcastClientVolume(client.id, client.config.volume.muted, percent)}
onMuteChange={mute => this.props.pusherActions.setSnapcastClientVolume(client.id, mute, client.config.volume.percent)}
/>
</div>
</div>
{
group.clients.map(client => {
var name = client.config.name ? client.config.name : client.host.name;
return (
<div className={"list-item client "+(client.connected ? 'connected' : 'disconnected')} key={client.id}>
<div className="name">
<TextField
onChange={value => this.props.pusherActions.setSnapcastClientName(client.id, value)}
value={name}
/>
{!client.connected ? <span className="disconnected flag grey">DISCONNECTED</span> : null}
</div>
);
})
}
</div>
<div className="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="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={client.config.latency}
/>
</div>
</div>
);
})
}
</div>
);
})