Splitting out mute, cleaning up mobile output controls

This commit is contained in:
James Barnsley
2018-10-02 16:58:15 +13:00
parent 85c5786d9e
commit d67069c75c
15 changed files with 14298 additions and 10906 deletions

View File

@ -4445,7 +4445,7 @@ $icon-websocket: \e920;
position: relative; position: relative;
cursor: pointer; cursor: pointer;
height: 20px; } height: 20px; }
.slider input[type="range"] { .slider__input {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
@ -4454,16 +4454,16 @@ $icon-websocket: \e920;
opacity: 0; opacity: 0;
z-index: 2; z-index: 2;
cursor: pointer; } cursor: pointer; }
.slider input[type="range"]::-webkit-slider-thumb { .slider__input::-webkit-slider-thumb {
width: 1px; width: 1px;
border: 0; } border: 0; }
.slider input[type="range"]::-moz-range-thumb { .slider__input::-moz-range-thumb {
width: 1px; width: 1px;
border: 0; } border: 0; }
.slider input[type="range"]::-ms-thumb { .slider__input::-ms-thumb {
width: 1px; width: 1px;
border: 0; } border: 0; }
.slider .track { .slider__track {
position: absolute; position: absolute;
background: rgba(0, 0, 0, 0.2); background: rgba(0, 0, 0, 0.2);
border-radius: 2px; border-radius: 2px;
@ -4472,7 +4472,7 @@ $icon-websocket: \e920;
right: 0; right: 0;
left: 0; left: 0;
margin-top: -3px; } margin-top: -3px; }
.slider .track .progress { .slider__track__progress {
position: absolute; position: absolute;
background: #08d58f; background: #08d58f;
pointer-events: none; pointer-events: none;
@ -4482,7 +4482,7 @@ $icon-websocket: \e920;
height: 100%; height: 100%;
max-width: 100%; max-width: 100%;
border-radius: 4px; } border-radius: 4px; }
.slider .track .progress:after { .slider__track__progress:after {
-webkit-transition: all 0.1s ease-in-out; -webkit-transition: all 0.1s ease-in-out;
-moz-transition: all 0.1s ease-in-out; -moz-transition: all 0.1s ease-in-out;
-o-transition: all 0.1s ease-in-out; -o-transition: all 0.1s ease-in-out;
@ -4496,11 +4496,11 @@ $icon-websocket: \e920;
width: 4px; width: 4px;
height: 4px; height: 4px;
border-radius: 50%; } border-radius: 50%; }
.slider.disabled .progress { .slider--disabled .slider__track__progress, .slider--muted .slider__track__progress {
background: #a3a19f; } background: #a3a19f; }
.slider.disabled .progress:after { .slider--disabled .slider__track__progress:after, .slider--muted .slider__track__progress:after {
background: #a3a19f !important; } background: #a3a19f !important; }
.slider:hover .track .progress:after { .slider:hover .slider__track__progress:after {
width: 12px; width: 12px;
height: 12px; height: 12px;
top: -4px; top: -4px;
@ -4684,15 +4684,11 @@ $icon-websocket: \e920;
right: 20px; right: 20px;
width: 150px; width: 150px;
font-size: 18px; } font-size: 18px; }
.playback-controls section.volume .modal-trigger { .playback-controls section.volume .slider__wrapper {
display: none; }
.playback-controls section.volume .slider-wrapper {
position: absolute; position: absolute;
bottom: 6px; bottom: 6px;
right: 0; right: 0;
left: 30px; } left: 30px; }
.playback-controls section.volume .muted .slider .progress {
background: #a3a19f; }
.playback-controls section.triggers { .playback-controls section.triggers {
display: none; } display: none; }
@media (max-width: 800px) { @media (max-width: 800px) {
@ -4700,8 +4696,8 @@ $icon-websocket: \e920;
width: auto; width: auto;
bottom: auto; bottom: auto;
bottom: 8px; bottom: 8px;
left: 70px; left: 80px;
right: 70px; right: 80px;
text-align: center; text-align: center;
font-size: 12px; font-size: 12px;
color: #FFFFFF !important; } color: #FFFFFF !important; }
@ -4743,20 +4739,29 @@ $icon-websocket: \e920;
height: 150px; } height: 150px; }
.playback-controls.expanded section.settings { .playback-controls.expanded section.settings {
display: block; display: block;
right: 20px; right: 0;
bottom: 60px; bottom: 100px;
text-align: right; } text-align: right; }
.playback-controls.expanded section.volume { .playback-controls.expanded section.volume {
width: auto; width: auto;
display: block; display: block;
right: 130px; right: 110px;
left: 22px; left: 22px;
bottom: 60px; } bottom: 100px; }
.playback-controls.expanded section.progress { .playback-controls.expanded section.progress {
display: block; display: block;
bottom: 110px; bottom: 55px;
left: 0; left: -10px;
right: 0; } } right: -10px; }
.playback-controls.expanded .output-control {
position: absolute;
bottom: 150px;
left: 0px;
right: 0px; }
.playback-controls.expanded .output-control > .control {
display: none; }
.playback-controls.expanded .output-control__items {
width: 100%; } }
.output-control { .output-control {
position: relative; } position: relative; }
@ -4798,8 +4803,6 @@ $icon-websocket: \e920;
position: relative; } position: relative; }
.output-control__item:not(:first-child) { .output-control__item:not(:first-child) {
border-top: 1px solid rgba(0, 0, 0, 0.05); } border-top: 1px solid rgba(0, 0, 0, 0.05); }
.output-control__item--has-power-button {
padding-left: 56px; }
.output-control__item__actions { .output-control__item__actions {
float: right; } float: right; }
.output-control__item__actions .action { .output-control__item__actions .action {
@ -4811,16 +4814,56 @@ $icon-websocket: \e920;
color: #a3a19f; } color: #a3a19f; }
.output-control__item__actions .action:hover { .output-control__item__actions .action:hover {
color: #181818; } color: #181818; }
.output-control__item--has-power-button .output-control__item__volume {
padding-left: 50px; }
.output-control__item__volume {
padding-top: 2px;
padding-left: 25px; }
.output-control__item__mute.control {
position: relative;
padding: 2px;
color: #000000;
float: left;
font-size: 16px; }
@keyframes click_feedback {
0% {
top: 50%;
left: 0;
width: 100%;
padding-bottom: 100%;
opacity: 1; }
100% {
top: 25%;
left: -25%;
width: 150%;
padding-bottom: 150%;
opacity: 0; } }
.output-control__item__mute.control:after {
content: '';
display: block;
position: absolute;
background: rgba(0, 0, 0, 0.5);
z-index: 1;
opacity: 0;
border-radius: 50%;
margin-top: -50%;
pointer-events: none; }
.output-control__item__mute.control:focus:after, .output-control__item__mute.control:active:after {
animation-name: click_feedback;
animation-duration: 0.2s;
animation-timing-function: ease-out;
animation-iteration-count: 1; }
.output-control__item__power { .output-control__item__power {
position: relative; position: relative;
position: absolute !important; width: 18px;
top: 12px; height: 18px;
left: 12px;
width: 30px;
height: 30px;
text-align: center; text-align: center;
color: #FFFFFF; color: #FFFFFF;
cursor: pointer; } cursor: pointer;
float: left;
margin-top: -2px;
margin-right: 6px; }
@keyframes click_feedback { @keyframes click_feedback {
0% { 0% {
@ -4851,13 +4894,13 @@ $icon-websocket: \e920;
animation-timing-function: ease-out; animation-timing-function: ease-out;
animation-iteration-count: 1; } animation-iteration-count: 1; }
.output-control__item__power .icon { .output-control__item__power .icon {
padding-top: 10px; padding-top: 5px;
font-size: 16px; } font-size: 12px; }
.output-control__item__power:before { .output-control__item__power:before {
content: ''; content: '';
display: block; display: block;
position: absolute; position: absolute;
top: 3px; top: 2px;
left: 0; left: 0;
width: 100%; width: 100%;
padding-bottom: 100%; padding-bottom: 100%;
@ -4865,46 +4908,6 @@ $icon-websocket: \e920;
z-index: -1; z-index: -1;
border-radius: 50%; border-radius: 50%;
opacity: 1; } opacity: 1; }
.output-control__item__volume {
position: relative;
padding-left: 22px;
display: block; }
.output-control__item__volume .mute-control {
position: relative;
position: absolute !important;
top: -2px;
left: -2px;
padding: 2px;
color: #000000; }
@keyframes click_feedback {
0% {
top: 50%;
left: 0;
width: 100%;
padding-bottom: 100%;
opacity: 1; }
100% {
top: 25%;
left: -25%;
width: 150%;
padding-bottom: 150%;
opacity: 0; } }
.output-control__item__volume .mute-control:after {
content: '';
display: block;
position: absolute;
background: rgba(0, 0, 0, 0.5);
z-index: 1;
opacity: 0;
border-radius: 50%;
margin-top: -50%;
pointer-events: none; }
.output-control__item__volume .mute-control:focus:after, .output-control__item__volume .mute-control:active:after {
animation-name: click_feedback;
animation-duration: 0.2s;
animation-timing-function: ease-out;
animation-iteration-count: 1; }
@media (max-width: 800px) { @media (max-width: 800px) {
.output-control__items { .output-control__items {
width: 86vw; width: 86vw;
@ -6155,13 +6158,13 @@ main header {
width: 1px; width: 1px;
background: #383734; background: #383734;
z-index: 0; } z-index: 0; }
.snapcast__client__latency .latency-control .progress.positive { .snapcast__client__latency .latency-control .slider__track__progress--positive {
border-top-left-radius: 0; border-top-left-radius: 0;
border-bottom-left-radius: 0; } border-bottom-left-radius: 0; }
.snapcast__client__latency .latency-control .progress.negative { .snapcast__client__latency .latency-control .slider__track__progress--negative {
border-top-right-radius: 0; border-top-right-radius: 0;
border-bottom-right-radius: 0; } border-bottom-right-radius: 0; }
.snapcast__client__latency .latency-control .progress.negative:after { .snapcast__client__latency .latency-control .slider__track__progress--negative:after {
right: auto; right: auto;
left: -1px; } left: -1px; }
@media (max-width: 800px) { @media (max-width: 800px) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -98,7 +98,7 @@
// Release details // Release details
// These are automatically injected to built HTML // These are automatically injected to built HTML
var build = "1538382441"; var build = "1538419845";
var version = "3.26.2"; var version = "3.26.2";
// Construct the script tag // Construct the script tag

View File

@ -48,18 +48,19 @@ export default class LatencyControl extends React.Component{
return ( return (
<span className="latency-control"> <span className="latency-control">
<div className="slider-wrapper"> <div className="slider__wrapper">
<div className="slider horizontal"> <div className="slider slider--latency">
<input <input
type="range" type="range"
min="-100" min="-100"
max="100" max="100"
className="slider__input"
value={this.props.value} value={this.props.value}
onChange={e => this.handleChange(parseInt(e.target.value))} onChange={e => this.handleChange(parseInt(e.target.value))}
/> />
<div className="zero"></div> <div className="zero"></div>
<div className="track"> <div className="slider__track">
<div className={"progress "+(negative ? 'negative' : 'positive')} style={{ width: width+'%', left: left+'%' }}></div> <div className={"slider__track__progress slider__track__progress--"+(negative ? 'negative' : 'positive')} style={{ width: width+'%', left: left+'%' }}></div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,30 @@
import React from 'react';
import Icon from '../Icon';
import * as helpers from '../../helpers';
export default class MuteControl extends React.Component{
constructor(props){
super(props);
}
render(){
if (this.props.mute){
return (
<a className={"control mute-control "+(this.props.noTooltip ? "" : "tooltip ")+(this.props.className ? this.props.className : "")} onClick={() => this.props.onMuteChange(false)}>
<Icon className="red-text" name="volume_off" />
{this.props.noTooltip ? null : <span className="tooltip__content">Unmute</span>}
</a>
)
} else {
return (
<a className={"control mute-control "+(this.props.noTooltip ? "" : "tooltip ")+(this.props.className ? this.props.className : "")} onClick={() => this.props.onMuteChange(true)}>
<Icon className="muted" name="volume_mute" />
{this.props.noTooltip ? null : <span className="tooltip__content">Mute</span>}
</a>
)
}
}
}

View File

@ -4,6 +4,7 @@ import { connect } from 'react-redux'
import { bindActionCreators } from 'redux' import { bindActionCreators } from 'redux'
import VolumeControl from './VolumeControl' import VolumeControl from './VolumeControl'
import MuteControl from './MuteControl'
import SnapcastPowerButton from './SnapcastPowerButton' import SnapcastPowerButton from './SnapcastPowerButton'
import Icon from '../Icon' import Icon from '../Icon'
@ -73,21 +74,28 @@ class OutputControl extends React.Component{
return ( return (
<div className={"output-control__item outputs__item--snapcast"+(commands && commands.power ? " output-control__item--has-power-button": '')} key={client.id}> <div className={"output-control__item outputs__item--snapcast"+(commands && commands.power ? " output-control__item--has-power-button": '')} key={client.id}>
{commands && commands.power ? <SnapcastPowerButton
className="output-control__item__power"
client={client}
onClick={e => this.props.snapcastActions.sendClientCommand(client.id, commands.power)}
/> : null}
<div className="output-control__item__name"> <div className="output-control__item__name">
{client.name} {client.name}
</div> </div>
<VolumeControl <div className="output-control__item__controls">
className="output-control__item__volume" {commands && commands.power ? <SnapcastPowerButton
volume={client.volume} className="output-control__item__power"
mute={client.mute} client={client}
onVolumeChange={percent => this.props.snapcastActions.setClientVolume(client.id, percent)} onClick={e => this.props.snapcastActions.sendClientCommand(client.id, commands.power)}
onMuteChange={mute => this.props.snapcastActions.setClientMute(client.id, mute)} /> : null}
/> <MuteControl
className="output-control__item__mute"
noTooltip={true}
mute={client.mute}
onMuteChange={mute => this.props.snapcastActions.setClientMute(client.id, mute)}
/>
<VolumeControl
className="output-control__item__volume"
volume={client.volume}
mute={client.mute}
onVolumeChange={percent => this.props.snapcastActions.setClientVolume(client.id, percent)}
/>
</div>
</div> </div>
); );
}) })
@ -138,7 +146,7 @@ class OutputControl extends React.Component{
} }
render(){ render(){
if (this.state.expanded){ if (this.state.expanded || this.props.force_expanded){
return ( return (
<span className="output-control"> <span className="output-control">
<a className="control speakers active" onClick={e => this.setExpanded()}><Icon name="speaker" /></a> <a className="control speakers active" onClick={e => this.setExpanded()}><Icon name="speaker" /></a>

View File

@ -29,16 +29,17 @@ class ProgressSlider extends React.Component{
} }
return ( return (
<div className={"progress slider horizontal "+this.props.play_state}> <div className={"slider slider--playback-progress slider--"+this.props.play_state}>
<div className="track"> <div className="slider__track">
<input <input
type="range" type="range"
min="0" min="0"
max="100" max="100"
value={percent} value={percent}
className="slider__input"
onChange={e => this.handleChange(parseInt(e.target.value))} onChange={e => this.handleChange(parseInt(e.target.value))}
/> />
<div className="progress" style={{ width: (percent)+'%' }}></div> <div className="slider__track__progress" style={{ width: (percent)+'%' }}></div>
</div> </div>
</div> </div>
); );

View File

@ -16,51 +16,23 @@ export default class VolumeControl extends React.Component{
this.props.onVolumeChange(value, this.props.volume); this.props.onVolumeChange(value, this.props.volume);
} }
renderMuteButton(){
if (this.props.mute){
return (
<a className="control mute-control tooltip" onClick={() => this.props.onMuteChange(false)}>
<Icon className="red-text" name="volume_off" />
<span className="tooltip__content">Unmute</span>
</a>
)
} else {
return (
<a className="control mute-control tooltip" onClick={() => this.props.onMuteChange(true)}>
<Icon className="muted" name="volume_mute" />
<span className="tooltip__content">Mute</span>
</a>
)
}
}
render(){ render(){
var className = "volume-control";
if (this.props.mute){
className += " muted";
}
if (this.props.className){
className += " "+this.props.className;
}
return ( return (
<span className={className}> <div className={"slider__wrapper "+(this.props.className ? this.props.className : "")}>
{this.props.NoMuteButton ? null : this.renderMuteButton()} <div className={"slider slider--volume "+(this.props.mute ? "slider--muted" : "")}>
<div className="slider-wrapper"> <input
<div className={"slider horizontal"+(this.props.mute ? " disabled" : "")}> className="slider__input"
<input type="range"
type="range" min="0"
min="0" max="25"
max="25" value={this.props.volume/4}
value={this.props.volume/4} onChange={e => this.handleChange(parseInt(e.target.value)*4)}
onChange={e => this.handleChange(parseInt(e.target.value)*4)} />
/> <div className="slider__track">
<div className="track"> <div className="slider__track__progress" style={{ width: this.props.volume+'%' }}></div>
<div className="progress" style={{ width: this.props.volume+'%' }}></div>
</div>
</div> </div>
</div> </div>
</span> </div>
); );
} }
} }

View File

@ -6,6 +6,7 @@ import { bindActionCreators } from 'redux'
import ProgressSlider from './Fields/ProgressSlider' import ProgressSlider from './Fields/ProgressSlider'
import VolumeControl from './Fields/VolumeControl' import VolumeControl from './Fields/VolumeControl'
import MuteControl from './Fields/MuteControl'
import OutputControl from './Fields/OutputControl' import OutputControl from './Fields/OutputControl'
import Dater from './Dater' import Dater from './Dater'
import ArtistSentence from './ArtistSentence' import ArtistSentence from './ArtistSentence'
@ -161,7 +162,7 @@ class PlaybackControls extends React.Component{
{this.renderConsumeButton()} {this.renderConsumeButton()}
{this.renderRandomButton()} {this.renderRandomButton()}
{this.renderRepeatButton()} {this.renderRepeatButton()}
<OutputControl /> <OutputControl force_expanded={this.state.expanded} />
</section> </section>
<section className="progress"> <section className="progress">
@ -171,12 +172,15 @@ class PlaybackControls extends React.Component{
</section> </section>
<section className="volume"> <section className="volume">
<MuteControl
mute={this.props.mute}
onMuteChange={mute => this.props.mopidyActions.setMute(mute)}
/>
<VolumeControl <VolumeControl
scrollWheel scrollWheel
volume={this.props.volume} volume={this.props.volume}
mute={this.props.mute} mute={this.props.mute}
onVolumeChange={percent => this.props.mopidyActions.setVolume(percent)} onVolumeChange={percent => this.props.mopidyActions.setVolume(percent)}
onMuteChange={mute => this.props.mopidyActions.setMute(mute)}
/> />
</section> </section>

View File

@ -0,0 +1 @@

View File

@ -53,10 +53,6 @@
border-top: 1px solid rgba(0,0,0,0.05); border-top: 1px solid rgba(0,0,0,0.05);
} }
&--has-power-button {
padding-left: 56px;
}
&__actions { &__actions {
float: right; float: right;
@ -71,27 +67,46 @@
} }
} }
&--has-power-button {
.output-control__item__volume {
padding-left: 50px;
}
}
&__volume {
padding-top: 2px;
padding-left: 25px;
}
&__mute.control {
@include click_feedback(rgba(0,0,0,0.5));
padding: 2px;
color: $black;
float: left;
font-size: 16px;
}
&__power { &__power {
@include click_feedback(rgba(0,0,0,0.5)); @include click_feedback(rgba(0,0,0,0.5));
position: absolute !important; width: 18px;
top: 12px; height: 18px;
left: 12px;
width: 30px;
height: 30px;
text-align: center; text-align: center;
color: $white; color: $white;
cursor: pointer; cursor: pointer;
float: left;
margin-top: -2px;
margin-right: 6px;
.icon { .icon {
padding-top: 10px; padding-top: 5px;
font-size: 16px; font-size: 12px;
} }
&:before { &:before {
content: ''; content: '';
display: block; display: block;
position: absolute; position: absolute;
top: 3px; top: 2px;
left: 0; left: 0;
width: 100%; width: 100%;
padding-bottom: 100%; padding-bottom: 100%;
@ -101,21 +116,6 @@
opacity: 1; opacity: 1;
} }
} }
&__volume {
position: relative;
padding-left: 22px;
display: block;
.mute-control {
@include click_feedback(rgba(0,0,0,0.5));
position: absolute !important;
top: -2px;
left: -2px;
padding: 2px;
color: $black;
}
}
} }
@include responsive($bp_medium){ @include responsive($bp_medium){

View File

@ -176,22 +176,12 @@
width: 150px; width: 150px;
font-size: 18px; font-size: 18px;
.modal-trigger { .slider__wrapper {
display: none;
}
.slider-wrapper {
position: absolute; position: absolute;
bottom: 6px; bottom: 6px;
right: 0; right: 0;
left: 30px; left: 30px;
} }
.muted {
.slider .progress {
background: $mid_grey;
}
}
} }
&.triggers { &.triggers {
@ -205,8 +195,8 @@
width: auto; width: auto;
bottom: auto; bottom: auto;
bottom: 8px; bottom: 8px;
left: 70px; left: 80px;
right: 70px; right: 80px;
text-align: center; text-align: center;
font-size: 12px; font-size: 12px;
color: $white !important; color: $white !important;
@ -277,27 +267,41 @@
height: 150px; height: 150px;
section { section {
&.settings { &.settings {
display: block; display: block;
right: 20px; right: 0;
bottom: 60px; bottom: 100px;
text-align: right; text-align: right;
} }
&.volume { &.volume {
width: auto; width: auto;
display: block; display: block;
right: 130px; right: 110px;
left: 22px; left: 22px;
bottom: 60px; bottom: 100px;
} }
&.progress { &.progress {
display: block; display: block;
bottom: 110px; bottom: 55px;
left: 0; left: -10px;
right: 0; right: -10px;
}
}
.output-control {
position: absolute;
bottom: 150px;
left: 0px;
right: 0px;
& > .control {
display: none;
}
&__items {
width: 100%;
} }
} }
} }

View File

@ -4,7 +4,7 @@
cursor: pointer; cursor: pointer;
height: 20px; height: 20px;
input[type="range"]{ &__input {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
@ -30,7 +30,7 @@
} }
} }
.track { &__track {
position: absolute; position: absolute;
background: rgba(0,0,0,0.2); background: rgba(0,0,0,0.2);
border-radius: 2px; border-radius: 2px;
@ -40,7 +40,7 @@
left: 0; left: 0;
margin-top: -3px; margin-top: -3px;
.progress { &__progress {
position: absolute; position: absolute;
background: $turquoise; background: $turquoise;
pointer-events: none; pointer-events: none;
@ -66,8 +66,9 @@
} }
} }
&.disabled { &--disabled,
.progress { &--muted {
.slider__track__progress {
background: $mid_grey; background: $mid_grey;
&:after { &:after {
@ -77,8 +78,8 @@
} }
&:hover { &:hover {
.track { .slider__track {
.progress { &__progress {
&:after { &:after {
width: 12px; width: 12px;
height: 12px; height: 12px;

View File

@ -145,18 +145,22 @@
z-index: 0; z-index: 0;
} }
.progress.positive { .slider__track {
border-top-left-radius: 0; &__progress {
border-bottom-left-radius: 0; &--positive {
} border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.progress.negative { &--negative {
border-top-right-radius: 0; border-top-right-radius: 0;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
&:after { &:after {
right: auto; right: auto;
left: -1px; left: -1px;
}
}
} }
} }
} }