Handling empty client lists; Safari icon, fixes #273
@ -4370,7 +4370,8 @@ input[type="submit"] {
|
|||||||
@media (max-width: 800px) {
|
@media (max-width: 800px) {
|
||||||
.snapcast .group .clients .client {
|
.snapcast .group .clients .client {
|
||||||
padding-left: 0 !important;
|
padding-left: 0 !important;
|
||||||
padding-right: 0 !important; }
|
padding-right: 0 !important;
|
||||||
|
padding-top: 0 !important; }
|
||||||
.snapcast .group .clients .client .col {
|
.snapcast .group .clients .client .col {
|
||||||
clear: both;
|
clear: both;
|
||||||
display: block !important;
|
display: block !important;
|
||||||
@ -4410,7 +4411,7 @@ aside {
|
|||||||
height: 50px;
|
height: 50px;
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
opacity: 0.2;
|
opacity: 0.2;
|
||||||
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMS4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiDQoJIHZpZXdCb3g9IjAgMCAxNDEuNyAxNDEuNyIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMTQxLjcgMTQxLjc7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+DQoJLnN0MHtmaWxsOiNGRkZGRkY7fQ0KPC9zdHlsZT4NCjxwb2x5Z29uIGNsYXNzPSJzdDAiIHBvaW50cz0iNTguMyw0Ny4xIDE4LjIsOC4yIDE4LjIsMTMzLjUgNTguMyw5NSAiLz4NCjxwb2x5Z29uIGNsYXNzPSJzdDAiIHBvaW50cz0iNTguMyw4LjIgNTguMywyNy4xIDEwMy44LDcwLjcgNTguMywxMTUgNTguMywxMzMuNSAxMjMuNSw3MC43ICIvPg0KPC9zdmc+DQo=);
|
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjxzdmcgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgdmlld0JveD0iMCAwIDE2IDE2IiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAxNiAxNjsiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPHBhdGggZD0iTTYuNiwxMC43bC00LjQsNC4zVjEuMWw0LjQsNC4zVjEwLjd6IE02LjYsMS4xdjIuMWw1LDQuOGwtNSw0Ljl2MkwxMy44LDhMNi42LDEuMXoiLz4NCjwvc3ZnPg0K);
|
||||||
background-position: 50% 50%;
|
background-position: 50% 50%;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: contain; }
|
background-size: contain; }
|
||||||
|
|||||||
@ -1,10 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||||
viewBox="0 0 141.7 141.7" style="enable-background:new 0 0 141.7 141.7;" xml:space="preserve">
|
<path d="M6.6,10.7l-4.4,4.3V1.1l4.4,4.3V10.7z M6.6,1.1v2.1l5,4.8l-5,4.9v2L13.8,8L6.6,1.1z"/>
|
||||||
<style type="text/css">
|
|
||||||
.st0{fill:#FFFFFF;}
|
|
||||||
</style>
|
|
||||||
<polygon class="st0" points="58.3,47.1 18.2,8.2 18.2,133.5 58.3,95 "/>
|
|
||||||
<polygon class="st0" points="58.3,8.2 58.3,27.1 103.8,70.7 58.3,115 58.3,133.5 123.5,70.7 "/>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 593 B After Width: | Height: | Size: 342 B |
5
mopidy_iris/static/assets/icon_black.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||||
|
<path fill="#000000" d="M6.6,10.7l-4.4,4.3V1.1l4.4,4.3V10.7z M6.6,1.1v2.1l5,4.8l-5,4.9v2L13.8,8L6.6,1.1z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 357 B |
5
mopidy_iris/static/assets/icon_coloured.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||||
|
<path fill="#08d58f" d="M6.6,10.7l-4.4,4.3V1.1l4.4,4.3V10.7z M6.6,1.1v2.1l5,4.8l-5,4.9v2L13.8,8L6.6,1.1z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 357 B |
@ -24,6 +24,7 @@
|
|||||||
<link rel="apple-touch-startup-image" href="/iris/assets/logo-full-512.png" />
|
<link rel="apple-touch-startup-image" href="/iris/assets/logo-full-512.png" />
|
||||||
<link rel="apple-touch-icon" sizes="192x192" href="/iris/assets/logo-app-192.png" />
|
<link rel="apple-touch-icon" sizes="192x192" href="/iris/assets/logo-app-192.png" />
|
||||||
<link rel="apple-touch-icon" sizes="512x512" href="/iris/assets/logo-app-512.png" />
|
<link rel="apple-touch-icon" sizes="512x512" href="/iris/assets/logo-app-512.png" />
|
||||||
|
<link rel="mask-icon" href="/iris/assets/icon_coloured.svg" color="#08d58f" />
|
||||||
|
|
||||||
<!-- Raven Sentry -->
|
<!-- Raven Sentry -->
|
||||||
<script src="https://cdn.ravenjs.com/3.17.0/raven.min.js" crossorigin="anonymous"></script>
|
<script src="https://cdn.ravenjs.com/3.17.0/raven.min.js" crossorigin="anonymous"></script>
|
||||||
@ -76,7 +77,7 @@
|
|||||||
|
|
||||||
// Release details
|
// Release details
|
||||||
// These are automatically injected to built HTML
|
// These are automatically injected to built HTML
|
||||||
var build = "1525077243";
|
var build = "1525121769";
|
||||||
var version = "3.17.4";
|
var version = "3.17.4";
|
||||||
|
|
||||||
// Construct the script tag
|
// Construct the script tag
|
||||||
|
|||||||
@ -1,10 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||||
viewBox="0 0 141.7 141.7" style="enable-background:new 0 0 141.7 141.7;" xml:space="preserve">
|
<path d="M6.6,10.7l-4.4,4.3V1.1l4.4,4.3V10.7z M6.6,1.1v2.1l5,4.8l-5,4.9v2L13.8,8L6.6,1.1z"/>
|
||||||
<style type="text/css">
|
|
||||||
.st0{fill:#FFFFFF;}
|
|
||||||
</style>
|
|
||||||
<polygon class="st0" points="58.3,47.1 18.2,8.2 18.2,133.5 58.3,95 "/>
|
|
||||||
<polygon class="st0" points="58.3,8.2 58.3,27.1 103.8,70.7 58.3,115 58.3,133.5 123.5,70.7 "/>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 593 B After Width: | Height: | Size: 342 B |
5
src/assets/icon_coloured.svg
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||||
|
<path fill="#08d58f" d="M6.6,10.7l-4.4,4.3V1.1l4.4,4.3V10.7z M6.6,1.1v2.1l5,4.8l-5,4.9v2L13.8,8L6.6,1.1z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 357 B |
@ -24,6 +24,7 @@
|
|||||||
<link rel="apple-touch-startup-image" href="/iris/assets/logo-full-512.png" />
|
<link rel="apple-touch-startup-image" href="/iris/assets/logo-full-512.png" />
|
||||||
<link rel="apple-touch-icon" sizes="192x192" href="/iris/assets/logo-app-192.png" />
|
<link rel="apple-touch-icon" sizes="192x192" href="/iris/assets/logo-app-192.png" />
|
||||||
<link rel="apple-touch-icon" sizes="512x512" href="/iris/assets/logo-app-512.png" />
|
<link rel="apple-touch-icon" sizes="512x512" href="/iris/assets/logo-app-512.png" />
|
||||||
|
<link rel="mask-icon" href="/iris/assets/icon_coloured.svg" color="#08d58f" />
|
||||||
|
|
||||||
<!-- Raven Sentry -->
|
<!-- Raven Sentry -->
|
||||||
<script src="https://cdn.ravenjs.com/3.17.0/raven.min.js" crossorigin="anonymous"></script>
|
<script src="https://cdn.ravenjs.com/3.17.0/raven.min.js" crossorigin="anonymous"></script>
|
||||||
|
|||||||
@ -42,6 +42,91 @@ class Snapcast extends React.Component{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render(){
|
render(){
|
||||||
if (!this.props.snapcast_enabled){
|
if (!this.props.snapcast_enabled){
|
||||||
return (
|
return (
|
||||||
@ -113,23 +198,23 @@ class Snapcast extends React.Component{
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
groups.map(group => {
|
groups.map((group, i) => {
|
||||||
return (
|
return (
|
||||||
<div className="group" key={group.id}>
|
<div className="group" key={group.id}>
|
||||||
<div className="field">
|
<div className="field">
|
||||||
<div className="name">
|
<div className="name">
|
||||||
ID
|
Name
|
||||||
</div>
|
</div>
|
||||||
<div className="input">
|
<div className="input">
|
||||||
<div className="text">
|
<div className="text">
|
||||||
{group.id}
|
{group.name ? group.name : 'Group '+(i+1)}
|
||||||
{group.name ? '('+group.name+')' : null}
|
<span className="grey-text">({group.id})</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="field dropdown">
|
<div className="field dropdown">
|
||||||
<div className="name">
|
<div className="name">
|
||||||
Source
|
Stream
|
||||||
</div>
|
</div>
|
||||||
<div className="input">
|
<div className="input">
|
||||||
<select onChange={e => this.props.pusherActions.setSnapcastGroupStream(group.id, e.target.value)} value={group.stream_id}>
|
<select onChange={e => this.props.pusherActions.setSnapcastGroupStream(group.id, e.target.value)} value={group.stream_id}>
|
||||||
@ -150,77 +235,7 @@ class Snapcast extends React.Component{
|
|||||||
Clients
|
Clients
|
||||||
</div>
|
</div>
|
||||||
<div className="input">
|
<div className="input">
|
||||||
<div className="list clients">
|
{this.renderClientsList(group, groups)}
|
||||||
{
|
|
||||||
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 : groups[i].stream_id),
|
|
||||||
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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -116,6 +116,7 @@
|
|||||||
@include responsive($bp_medium){
|
@include responsive($bp_medium){
|
||||||
padding-left: 0 !important;
|
padding-left: 0 !important;
|
||||||
padding-right: 0 !important;
|
padding-right: 0 !important;
|
||||||
|
padding-top: 0 !important;
|
||||||
|
|
||||||
.col {
|
.col {
|
||||||
@include clearfix();
|
@include clearfix();
|
||||||
|
|||||||