Relocating stream checkbox to Outputs panel

- Making it easier to switch current device streaming on/off
- Refactor `Stream` to functional component
This commit is contained in:
James Barnsley
2023-08-12 10:06:07 +12:00
parent 45cd06b2e1
commit 41fcfa9556
6 changed files with 78 additions and 98 deletions

View File

@ -8,7 +8,13 @@ import Thumbnail from '../Thumbnail';
import LinksSentence from '../LinksSentence'; import LinksSentence from '../LinksSentence';
import DropdownField from './DropdownField'; import DropdownField from './DropdownField';
import * as pusherActions from '../../services/pusher/actions'; import * as pusherActions from '../../services/pusher/actions';
import * as snapcastActions from '../../services/snapcast/actions'; import {
setGroupStream,
setClientMute,
setClientVolume,
setStreamingEnabled,
controlStream,
} from '../../services/snapcast/actions';
import { sortItems, indexToArray } from '../../util/arrays'; import { sortItems, indexToArray } from '../../util/arrays';
import { titleCase } from '../../util/helpers'; import { titleCase } from '../../util/helpers';
import { I18n, i18n } from '../../locale'; import { I18n, i18n } from '../../locale';
@ -38,10 +44,10 @@ const Header = ({
let onClick = null; let onClick = null;
switch (playbackStatus) { switch (playbackStatus) {
case 'playing': case 'playing':
if (canPause) onClick = () => dispatch(snapcastActions.controlStream(id, 'pause')); if (canPause) onClick = () => dispatch(controlStream(id, 'pause'));
break; break;
default: default:
if (canPlay) onClick = () => dispatch(snapcastActions.controlStream(id, 'play')); if (canPlay) onClick = () => dispatch(controlStream(id, 'play'));
break; break;
} }
@ -88,6 +94,7 @@ const Header = ({
}; };
const Group = ({ const Group = ({
setExpanded,
group: { group: {
id: groupId, id: groupId,
name: groupName, name: groupName,
@ -111,6 +118,7 @@ const Group = ({
className="text" className="text"
to={`/settings/services/snapcast/${groupId}`} to={`/settings/services/snapcast/${groupId}`}
scrollTo="#services-snapcast-groups" scrollTo="#services-snapcast-groups"
onClick={() => setExpanded(false)}
> >
{titleCase(groupName)} {titleCase(groupName)}
</Link> </Link>
@ -121,7 +129,7 @@ const Group = ({
options={allStreams.map((s) => ({ value: s.id, label: titleCase(s.id) }))} options={allStreams.map((s) => ({ value: s.id, label: titleCase(s.id) }))}
noLabel noLabel
handleChange={ handleChange={
(value) => dispatch(snapcastActions.setGroupStream(groupId, value)) (value) => dispatch(setGroupStream(groupId, value))
} }
/> />
</h5> </h5>
@ -143,14 +151,14 @@ const Group = ({
noTooltip noTooltip
mute={mute} mute={mute}
onMuteChange={ onMuteChange={
(value) => dispatch(snapcastActions.setClientMute(clientId, value)) (value) => dispatch(setClientMute(clientId, value))
} }
/> />
<VolumeControl <VolumeControl
volume={volume} volume={volume}
mute={mute} mute={mute}
onVolumeChange={ onVolumeChange={
(value) => dispatch(snapcastActions.setClientVolume(clientId, value)) (value) => dispatch(setClientVolume(clientId, value))
} }
/> />
</div> </div>
@ -162,11 +170,13 @@ const Group = ({
); );
}; };
const Outputs = () => { const Outputs = ({ setExpanded }) => {
const dispatch = useDispatch();
const allGroups = indexToArray(useSelector((state) => state.snapcast.groups || {})); const allGroups = indexToArray(useSelector((state) => state.snapcast.groups || {}));
const allStreams = useSelector((state) => state.snapcast.streams || {}); const allStreams = useSelector((state) => state.snapcast.streams || {});
const allServers = indexToArray(useSelector((state) => state.mopidy.servers || {})); const allServers = indexToArray(useSelector((state) => state.mopidy.servers || {}));
const groupsByStream = groupBy(allGroups, 'stream_id'); const groupsByStream = groupBy(allGroups, 'stream_id');
const { streaming_enabled } = useSelector((state) => state?.snapcast || {});
return ( return (
<ErrorBoundary> <ErrorBoundary>
@ -180,10 +190,29 @@ const Outputs = () => {
return ( return (
<div className="output-control__stream" key={`stream_${id}`}> <div className="output-control__stream" key={`stream_${id}`}>
<Header stream={stream} /> <Header stream={stream} />
{groups.map((group) => <Group group={group} key={`group_${group.id}`} />)} {
groups.map(
(group) => (
<Group setExpanded={setExpanded} group={group} key={`group_${group.id}`} />
)
)
}
</div> </div>
); );
})} })}
<div className="field checkbox" style={{ paddingLeft: 12 }}>
<label>
<input
type="checkbox"
name="streaming_enabled"
checked={streaming_enabled}
onChange={() => dispatch(setStreamingEnabled(!streaming_enabled))}
/>
<span className="label">
<I18n path="snapcast.stream_on_this_device" />
</span>
</label>
</div>
</ErrorBoundary> </ErrorBoundary>
); );
} }
@ -241,7 +270,7 @@ const OutputControl = ({ force_expanded }) => {
</button> </button>
<div className="output-control__inner"> <div className="output-control__inner">
{!isEmpty(commands) && <Commands commands={commands} />} {!isEmpty(commands) && <Commands commands={commands} />}
{snapcastEnabled && <Outputs />} {snapcastEnabled && <Outputs setExpanded={setExpanded} />}
</div> </div>
</span> </span>
); );

View File

@ -41,18 +41,6 @@ const Snapcast = (props) => {
<I18n path="snapcast.enabled" /> <I18n path="snapcast.enabled" />
</span> </span>
</label> </label>
<label>
<input
type="checkbox"
name="streaming_enabled"
checked={streaming_enabled}
disabled={!enabled}
onChange={() => actions.setStreamingEnabled(!streaming_enabled)}
/>
<span className="label">
<I18n path="snapcast.streaming_enabled" />
</span>
</label>
<label> <label>
<input <input
type="checkbox" type="checkbox"

View File

@ -1,85 +1,43 @@
import React from 'react'; import { useEffect, useState } from 'react';
import { connect } from 'react-redux'; import { useSelector } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as coreActions from '../services/core/actions';
import { SnapStream } from './SnapStream.tsx'; import { SnapStream } from './SnapStream.tsx';
class Stream extends React.Component { const Stream = () => {
constructor(props) { const [stream, setStream] = useState(null);
super(props);
if (props.enabled && props.streaming_enabled) {
this.start();
}
}
start = () => {
const {
host,
port,
ssl,
} = this.props;
if (this.snapstream) {
this.snapstream.play();
} else {
const baseUrl = `${ssl ? 'wss' : 'ws'}://${host}:${port}`;
this.snapstream = new SnapStream(baseUrl);
}
}
stop = () => {
if (this.snapstream) {
this.snapstream.stop();
this.snapstream = null;
}
}
componentDidUpdate = ({
streaming_enabled: prevStreamingEnabled,
}) => {
const {
enabled,
streaming_enabled,
} = this.props;
if (!prevStreamingEnabled && enabled && streaming_enabled) {
this.start();
}
if (!enabled || !streaming_enabled) {
this.stop();
}
}
render = () => null;
}
const mapStateToProps = (state) => {
const { const {
snapcast: {
enabled,
streaming_enabled,
host,
port,
ssl,
},
pusher: {
username,
},
} = state;
return {
enabled, enabled,
streaming_enabled, streaming_enabled,
host, host,
port, port,
ssl, ssl,
username, } = useSelector((state) => state?.snapcast || {});
};
};
const mapDispatchToProps = (dispatch) => ({ const start = () => {
coreActions: bindActionCreators(coreActions, dispatch), if (stream) {
}); stream.play();
} else {
setStream(new SnapStream(`${ssl ? 'wss' : 'ws'}://${host}:${port}`));
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Stream); const stop = () => {
if (stream) {
stream.stop();
setStream(null);
}
}
useEffect(
() => {
if (enabled && streaming_enabled) {
start();
} else {
stop();
}
}, [enabled, streaming_enabled],
);
return null;
}
export default Stream;

View File

@ -213,6 +213,7 @@ playback_controls:
snapcast: snapcast:
enabled: Enabled enabled: Enabled
streaming_enabled: Streaming enabled streaming_enabled: Streaming enabled
stream_on_this_device: Stream on this device
show_disconnected_clients: Show disconnected clients show_disconnected_clients: Show disconnected clients
no_connected_clients: No connected clients no_connected_clients: No connected clients
host: Host host: Host

View File

@ -207,7 +207,7 @@ class Settings extends React.Component {
<I18n path="settings.services.title" /> <I18n path="settings.services.title" />
<a name="services" /> <a name="services" />
</h4> </h4>
<Services /> <Services />
<h4 className="underline"> <h4 className="underline">
@ -456,7 +456,7 @@ class Settings extends React.Component {
<a <a
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="flag flag--dark" className="flag flag--dark flag--inline"
href={`https://github.com/jaedb/Iris/releases/tag/${pusher.version.latest}`} href={`https://github.com/jaedb/Iris/releases/tag/${pusher.version.latest}`}
> >
<Icon name="cloud_download" className="blue-text" /> <Icon name="cloud_download" className="blue-text" />

View File

@ -678,6 +678,10 @@ footer {
font-size: 12px; font-size: 12px;
} }
&--inline {
vertical-align: middle;
}
h1 &, h1 &,
h2 & { h2 & {
line-height: 1.4em; line-height: 1.4em;