Experimenting with mulitple server selector
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useStore, useDispatch } from 'react-redux';
|
||||
import Link from './Link';
|
||||
import Icon from './Icon';
|
||||
@ -8,6 +8,185 @@ import { Button } from './Button';
|
||||
import * as mopidyActions from '../services/mopidy/actions';
|
||||
import { iconFromKeyword } from '../util/helpers';
|
||||
import { I18n } from '../locale';
|
||||
import LinksSentence from './LinksSentence';
|
||||
import {
|
||||
digestMopidyImages,
|
||||
formatImages,
|
||||
} from '../util/format';
|
||||
import Thumbnail from './Thumbnail';
|
||||
|
||||
const callServer = ({ endpoint, method, params }) => new Promise((resolve, reject) => {
|
||||
const body = {
|
||||
jsonrpc: '2.0',
|
||||
id: 1,
|
||||
method,
|
||||
params,
|
||||
};
|
||||
fetch(endpoint, {
|
||||
method: 'POST',
|
||||
mode: 'no-cors',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ result }) => resolve(result))
|
||||
.catch((error) => { console.debug({ error }); reject() });
|
||||
});
|
||||
|
||||
const Server = ({
|
||||
server,
|
||||
current_server,
|
||||
}) => {
|
||||
if (!server) return null;
|
||||
const { id } = server;
|
||||
const dispatch = useDispatch();
|
||||
const [currentTrack, setCurrentTrack] = useState();
|
||||
const [playState, setPlayState] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
const endpoint = `http://${server.host}:${server.port}/mopidy/rpc`;
|
||||
callServer({
|
||||
endpoint,
|
||||
method: 'core.playback.get_current_track',
|
||||
}).then(
|
||||
(track) => {
|
||||
callServer({
|
||||
endpoint,
|
||||
method: 'core.library.get_images',
|
||||
params: {
|
||||
uris: [track.uri],
|
||||
},
|
||||
}).then(({ [track.uri]: images }) => {
|
||||
console.debug({ track })
|
||||
setCurrentTrack({
|
||||
...track,
|
||||
images: images ? formatImages(digestMopidyImages(server, images)) : null,
|
||||
});
|
||||
});
|
||||
},
|
||||
() => setCurrentTrack(null),
|
||||
);
|
||||
|
||||
callServer({
|
||||
endpoint,
|
||||
method: 'core.playback.get_state',
|
||||
}).then(setPlayState, setPlayState);
|
||||
}, [id]);
|
||||
|
||||
const remove = () => {
|
||||
dispatch(mopidyActions.removeServer(server.id));
|
||||
};
|
||||
|
||||
const setAsCurrent = () => {
|
||||
dispatch(mopidyActions.setCurrentServer(server));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="sub-tabs__content">
|
||||
<label className="field">
|
||||
<div className="name">
|
||||
<I18n path="settings.servers.current_track" />
|
||||
{playState ? ` (${playState})` : ''}
|
||||
</div>
|
||||
<div className="input">
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Thumbnail images={currentTrack?.images} size="small" />
|
||||
<div style={{ paddingLeft: '1rem' }}>
|
||||
<div>{currentTrack?.name}</div>
|
||||
<em>
|
||||
<LinksSentence items={currentTrack?.artists} type="artist" nolinks />
|
||||
</em>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<label className="field">
|
||||
<div className="name">
|
||||
<I18n path="settings.servers.name" />
|
||||
</div>
|
||||
<div className="input">
|
||||
<TextField
|
||||
type="text"
|
||||
value={server.name}
|
||||
onChange={(value) => dispatch(mopidyActions.updateServer({ id: server.id, name: value }))}
|
||||
autosave
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
<label className="field">
|
||||
<div className="name">
|
||||
<I18n path="settings.servers.host" />
|
||||
</div>
|
||||
<div className="input">
|
||||
<TextField
|
||||
value={server.host}
|
||||
onChange={(value) => dispatch(mopidyActions.updateServer({ id: server.id, host: value }))}
|
||||
autosave
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
<label className="field">
|
||||
<div className="name">
|
||||
<I18n path="settings.servers.port" />
|
||||
</div>
|
||||
<div className="input">
|
||||
<TextField
|
||||
type="text"
|
||||
value={server.port}
|
||||
onChange={(value) => dispatch(mopidyActions.updateServer({ id: server.id, port: value }))}
|
||||
autosave
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<div className="field checkbox">
|
||||
<div className="name">
|
||||
<I18n path="settings.servers.encryption.label" />
|
||||
</div>
|
||||
<div className="input">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="ssl"
|
||||
value={server.ssl}
|
||||
checked={server.ssl}
|
||||
onChange={() => dispatch(mopidyActions.updateServer({ id: server.id, ssl: !server.ssl }))}
|
||||
/>
|
||||
<span className="label tooltip">
|
||||
<I18n path="settings.servers.encryption.sublabel" />
|
||||
<span className="tooltip__content">
|
||||
<I18n path="settings.servers.encryption.description" />
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={setAsCurrent}
|
||||
tracking={{
|
||||
category: 'Servers',
|
||||
action: 'SetAsCurrent',
|
||||
label: (server.id === current_server ? 'Reconnect' : 'Switch'),
|
||||
}}
|
||||
>
|
||||
<I18n path={`settings.servers.${server.id === current_server ? 'reconnect' : 'switch'}`} />
|
||||
</Button>
|
||||
<Button
|
||||
type="destructive"
|
||||
disabled={server.id === current_server}
|
||||
onClick={remove}
|
||||
tracking={{ category: 'Servers', action: 'Delete' }}
|
||||
>
|
||||
<I18n path="actions.remove" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Servers = ({
|
||||
match: { params: { id: serverId } },
|
||||
@ -19,7 +198,6 @@ const Servers = ({
|
||||
mopidy: {
|
||||
servers,
|
||||
current_server,
|
||||
ssl,
|
||||
connected: mopidyConnected,
|
||||
connecting: mopidyConnecting,
|
||||
},
|
||||
@ -35,7 +213,7 @@ const Servers = ({
|
||||
history.push(`/settings/servers/${action.server.id}`);
|
||||
};
|
||||
|
||||
const renderMenu = () => (
|
||||
const Menu = () => (
|
||||
<div className="sub-tabs__menu" id="servers-menu">
|
||||
<div className="menu__inner">
|
||||
{indexToArray(servers).map((server) => {
|
||||
@ -102,110 +280,13 @@ const Servers = ({
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderServer = () => {
|
||||
if (!serverId) return null;
|
||||
const server = servers[serverId];
|
||||
if (!server) return null;
|
||||
|
||||
const remove = () => {
|
||||
dispatch(mopidyActions.removeServer(server.id));
|
||||
};
|
||||
|
||||
const setAsCurrent = () => {
|
||||
dispatch(mopidyActions.setCurrentServer(server));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="sub-tabs__content">
|
||||
<label className="field">
|
||||
<div className="name">
|
||||
<I18n path="settings.servers.name" />
|
||||
</div>
|
||||
<div className="input">
|
||||
<TextField
|
||||
type="text"
|
||||
value={server.name}
|
||||
onChange={(value) => dispatch(mopidyActions.updateServer({ id: server.id, name: value }))}
|
||||
autosave
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
<label className="field">
|
||||
<div className="name">
|
||||
<I18n path="settings.servers.host" />
|
||||
</div>
|
||||
<div className="input">
|
||||
<TextField
|
||||
value={server.host}
|
||||
onChange={(value) => dispatch(mopidyActions.updateServer({ id: server.id, host: value }))}
|
||||
autosave
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
<label className="field">
|
||||
<div className="name">
|
||||
<I18n path="settings.servers.port" />
|
||||
</div>
|
||||
<div className="input">
|
||||
<TextField
|
||||
type="text"
|
||||
value={server.port}
|
||||
onChange={(value) => dispatch(mopidyActions.updateServer({ id: server.id, port: value }))}
|
||||
autosave
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<div className="field checkbox">
|
||||
<div className="name">
|
||||
<I18n path="settings.servers.encryption.label" />
|
||||
</div>
|
||||
<div className="input">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="ssl"
|
||||
value={server.ssl}
|
||||
checked={server.ssl}
|
||||
onChange={() => dispatch(mopidyActions.updateServer({ id: server.id, ssl: !server.ssl }))}
|
||||
/>
|
||||
<span className="label tooltip">
|
||||
<I18n path="settings.servers.encryption.sublabel" />
|
||||
<span className="tooltip__content">
|
||||
<I18n path="settings.servers.encryption.description" />
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={setAsCurrent}
|
||||
tracking={{
|
||||
category: 'Servers',
|
||||
action: 'SetAsCurrent',
|
||||
label: (server.id === current_server ? 'Reconnect' : 'Switch'),
|
||||
}}
|
||||
>
|
||||
<I18n path={`settings.servers.${server.id === current_server ? 'reconnect' : 'switch'}`} />
|
||||
</Button>
|
||||
<Button
|
||||
type="destructive"
|
||||
disabled={server.id === current_server}
|
||||
onClick={remove}
|
||||
tracking={{ category: 'Servers', action: 'Delete' }}
|
||||
>
|
||||
<I18n path="actions.remove" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="sub-tabs sub-tabs--servers">
|
||||
{renderMenu()}
|
||||
{renderServer()}
|
||||
<Menu />
|
||||
<Server
|
||||
server={servers[serverId]}
|
||||
current_server={current_server}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,16 +1,45 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { connect, useSelector, useDispatch } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { withRouter } from 'react-router';
|
||||
import Link from './Link';
|
||||
import Icon from './Icon';
|
||||
import Dropzones from './Fields/Dropzones';
|
||||
import PinList from './Fields/PinList';
|
||||
import DropdownField from './Fields/DropdownField';
|
||||
import * as uiActions from '../services/ui/actions';
|
||||
import * as mopidyActions from '../services/mopidy/actions';
|
||||
import { I18n, i18n } from '../locale';
|
||||
import { indexToArray } from '../util/arrays';
|
||||
|
||||
class Sidebar extends React.Component {
|
||||
const ServerSwitcher = () => {
|
||||
const servers = useSelector((state) => state.mopidy.servers);
|
||||
const serversArray = indexToArray(servers);
|
||||
const current_server = useSelector((state) => state.mopidy.current_server);
|
||||
const dispatch = useDispatch();
|
||||
if (!serversArray || serversArray.length <= 1) return null;
|
||||
|
||||
const onChange = (id) => dispatch(mopidyActions.setCurrentServer(servers[id]));
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
width: '100%',
|
||||
color: 'black',
|
||||
}}>
|
||||
<DropdownField
|
||||
className="sidebar__menu__item"
|
||||
icon="dns"
|
||||
name="Server"
|
||||
value={current_server}
|
||||
valueAsLabel
|
||||
options={serversArray.map((server) => ({ value: server.id, label: server.name }))}
|
||||
handleChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
class Sidebar extends React.Component {
|
||||
closeSidebar = () => {
|
||||
const {
|
||||
uiActions: {
|
||||
@ -89,6 +118,7 @@ class Sidebar extends React.Component {
|
||||
<aside className="sidebar">
|
||||
<div className="sidebar__liner">
|
||||
<nav className="sidebar__menu">
|
||||
<ServerSwitcher />
|
||||
|
||||
<section className="sidebar__menu__section">
|
||||
<Link to="/queue" history={history} className="sidebar__menu__item" activeClassName="sidebar__menu__item--active">
|
||||
|
||||
@ -424,6 +424,7 @@ settings:
|
||||
no_connections: No connections
|
||||
you: ' (you)'
|
||||
servers:
|
||||
current_track: Current track
|
||||
inactive: Inactive
|
||||
connecting: Connecting
|
||||
disconnected: Disconnected
|
||||
|
||||
Reference in New Issue
Block a user