import React, { useState, useEffect } from 'react'; import { connect, useSelector, useDispatch } from 'react-redux'; import { bindActionCreators } from 'redux'; import { find, groupBy, map } from 'lodash'; import VolumeControl from './VolumeControl'; import MuteControl from './MuteControl'; import Icon from '../Icon'; import Thumbnail from '../Thumbnail'; import LinksSentence from '../LinksSentence'; import DropdownField from './DropdownField'; import * as coreActions from '../../services/core/actions'; import * as mopidyActions from '../../services/mopidy/actions'; import * as pusherActions from '../../services/pusher/actions'; import * as snapcastActions from '../../services/snapcast/actions'; import { sortItems, indexToArray } from '../../util/arrays'; import { formatImages, digestMopidyImages } from '../../util/format'; import { titleCase } from '../../util/helpers'; import { I18n } from '../../locale'; const Header = ({ stream, server }) => { const dispatch = useDispatch(); const { id, meta, status, uri: { path, }, } = stream || {}; let { control_url, images, name, artists, } = meta || {}; const controlURL = meta?.control_url ? new URL(meta.control_url) : null; const controlServer = controlURL ? { url: meta.control_url, ssl: controlURL.protocol === 'https:', host: controlURL.hostname, port: controlURL.port || (controlURL.protocol === 'https:' ? '443' : '80'), } : null; const current_server_id = useSelector((state) => state.mopidy.current_server); const current_server = useSelector((state) => state.mopidy.servers[current_server_id]); const isCurrentServer = control_url === current_server.url; const isControlSwitchable = controlServer && !isCurrentServer; if (images) images = formatImages(digestMopidyImages(controlServer, images)); const onClick = () => { if (isControlSwitchable) { dispatch(mopidyActions.setCurrentServer(controlServer)); } }; return (
{server?.name || id} {isCurrentServer && } {status === 'playing' && } {control_url &&
{control_url}
}
  • {name || path}
  • {artists &&
  • }
); } const Group = ({ group: { id: groupId, name: groupName, stream_id, clients_ids = [], } = {}, }) => { const allClients = useSelector((state) => state.snapcast.clients || {}); const allStreams = indexToArray(useSelector((state) => state.snapcast.streams || {})); const clients = clients_ids.length > 0 ? clients_ids.map((c) => allClients[c]) : []; const dispatch = useDispatch(); return (
{titleCase(groupName)}
({ value: s.id, label: titleCase(s.id) }))} noLabel handleChange={ (value) => dispatch(snapcastActions.setGroupStream(groupId, value)) } />
{clients.map((client) => { const { id: clientId, name: clientName, mute, volume, } = client; return (
{titleCase(clientName)}
dispatch(snapcastActions.setClientMute(clientId, value)) } /> dispatch(snapcastActions.setClientVolume(clientId, value)) } />
); })}
); }; const Outputs = () => { const allGroups = indexToArray(useSelector((state) => state.snapcast.groups || {})); const allStreams = useSelector((state) => state.snapcast.streams || {}); const allServers = indexToArray(useSelector((state) => state.mopidy.servers || {})); const groupsByStream = groupBy(allGroups, 'stream_id'); return ( <> {map(groupsByStream, (groups, id) => { const stream = { id, ...find(allStreams, (s) => s.id === id) || {}, server: find(allServers, (s) => s.snapcast_stream === id), groups, }; return (
{groups.map((group) => )}
); })} ); } const Commands = () => { const dispatch = useDispatch(); const commandsObj = useSelector((state) => state.pusher.commands || {}); if (!commandsObj) return null; let items = indexToArray(commandsObj); if (items.length <= 0) return null; items = sortItems(items, 'sort_order'); return (
{ items.map((command) => (
dispatch(pusherActions.runCommand(command.id))} >
)) }
); }; const OutputControl = ({ force_expanded, pusher_commands, snapcast_enabled, }) => { const [expanded, setExpanded] = useState(false); const handleClick = (e) => { if (!force_expanded && $(e.target).closest('.output-control').length <= 0) { setExpanded(false); } }; useEffect(() => { if (force_expanded && !expanded) { setExpanded(true); } }, [force_expanded]); useEffect(() => { if (expanded) { window.addEventListener('click', handleClick, false); } else { window.removeEventListener('click', handleClick, false); } }, [expanded]); // No customisable outputs if (!snapcast_enabled && !pusher_commands) { return ( ); } if (expanded) { const outputs = ; const commands = ; return ( {!outputs && !commands ? (

) : (
{commands} {outputs}
)}
); } return ( ); } const mapStateToProps = (state) => ({ pusher_connected: state.pusher.connected, snapcast_enabled: (state.pusher.config ? state.pusher.config.snapcast_enabled : null), }); const mapDispatchToProps = (dispatch) => ({ coreActions: bindActionCreators(coreActions, dispatch), snapcastActions: bindActionCreators(snapcastActions, dispatch), pusherActions: bindActionCreators(pusherActions, dispatch), }); export default connect(mapStateToProps, mapDispatchToProps)(OutputControl);