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:
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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" />
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user