New internal state for CORS requests, prototype server switcher modal

This commit is contained in:
James Barnsley
2021-10-09 16:40:27 +13:00
parent 2e8205bd26
commit e96344d4f2
15 changed files with 677 additions and 562 deletions

View File

@ -53,6 +53,7 @@ import ImageZoom from './views/modals/ImageZoom';
import HotkeysInfo from './views/modals/HotkeysInfo';
import EditCommand from './views/modals/EditCommand';
import Reset from './views/modals/Reset';
import Servers from './views/modals/Servers';
import { scrollTo, isTouchDevice } from './util/helpers';
import storage from './util/storage';
@ -312,6 +313,7 @@ export class App extends React.Component {
<Route path="/hotkeys" component={HotkeysInfo} />
<Route path="/share-configuration" component={ShareConfiguration} />
<Route path="/reset" component={Reset} />
<Route path="/servers" component={Servers} />
<Route path="/edit-command/:id?" component={EditCommand} />
<Route path="/queue/radio" component={EditRadio} />

View File

@ -5,6 +5,7 @@ import Icon from './Icon';
import TextField from './Fields/TextField';
import { indexToArray } from '../util/arrays';
import { Button } from './Button';
import * as pusherActions from '../services/pusher/actions';
import * as mopidyActions from '../services/mopidy/actions';
import { iconFromKeyword } from '../util/helpers';
import { I18n } from '../locale';
@ -15,25 +16,25 @@ import {
} from '../util/format';
import Thumbnail from './Thumbnail';
const callServer = ({ endpoint, method, params }) => new Promise((resolve, reject) => {
const callServer = ({ server, method, params }) => new Promise((resolve, reject) => {
const body = {
jsonrpc: '2.0',
id: 1,
host: server.host,
port: server.port,
method,
params,
};
fetch(endpoint, {
method: 'POST',
mode: 'no-cors',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
fetch(
`http://192.168.1.201:6680/iris/http/get_from_peer`,
{
method: 'POST',
body: JSON.stringify(body),
},
body: JSON.stringify(body),
})
.then((response) => response.json())
.then(({ result }) => resolve(result))
.catch((error) => { console.debug({ error }); reject() });
)
.then((response) => response.json())
.then(({ result }) => resolve(result))
.catch(() => reject());
});
const Server = ({
@ -41,63 +42,29 @@ const Server = ({
current_server,
}) => {
if (!server) return null;
const { id } = server;
const { id, current_track, playback_state } = 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);
if (id !== 'default') dispatch(mopidyActions.getServerState(id));
}, [id]);
const remove = () => {
dispatch(mopidyActions.removeServer(server.id));
};
const setAsCurrent = () => {
dispatch(mopidyActions.setCurrentServer(server));
};
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})` : ''}
{playback_state ? ` (${playback_state})` : ''}
</div>
<div className="input">
<div style={{ display: 'flex', alignItems: 'center' }}>
<Thumbnail images={currentTrack?.images} size="small" />
<Thumbnail images={current_track?.images} size="small" />
<div style={{ paddingLeft: '1rem' }}>
<div>{currentTrack?.name}</div>
<div>{current_track?.name}</div>
<em>
<LinksSentence items={currentTrack?.artists} type="artist" nolinks />
<LinksSentence items={current_track?.artists} type="artist" nolinks />
</em>
</div>
</div>

View File

@ -1,5 +1,5 @@
import React from 'react';
import { connect, useSelector, useDispatch } from 'react-redux';
import { connect, useSelector } from 'react-redux';
import { bindActionCreators } from 'redux';
import { withRouter } from 'react-router';
import Link from './Link';
@ -7,37 +7,10 @@ import Icon from './Icon';
import Dropzones from './Fields/Dropzones';
import PinList from './Fields/PinList';
import DropdownField from './Fields/DropdownField';
import Button from './Button';
import * as uiActions from '../services/ui/actions';
import * as mopidyActions from '../services/mopidy/actions';
import { I18n, i18n } from '../locale';
import { indexToArray } from '../util/arrays';
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 = () => {
@ -118,8 +91,6 @@ 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">
<Icon name="play_arrow" type="material" />
@ -188,6 +159,10 @@ class Sidebar extends React.Component {
<I18n path="sidebar.settings" />
{this.renderStatusIcon()}
</Link>
<Link to="/servers" history={history} className="sidebar__menu__item" activeClassName="sidebar__menu__item--active">
<Icon name="dns" type="material" />
<I18n path="sidebar.servers" />
</Link>
</section>
</nav>

View File

@ -171,6 +171,7 @@ sidebar:
tracks: Tracks
browse: Browse
settings: Settings
servers: Servers
not_connected: '%{name} not connected'
browser_offline: Browser offline
update_available: Update available
@ -610,3 +611,5 @@ modal:
snapcast_volume_up: Volume up for Snapcast client (where n is 1-9)
snapcast_volume_down: Volume down for Snapcast client (where n is 1-9)
snapcast_mute: Toggle mute for Snapcast client (where n is 1-9)
servers:
title: Server selector

View File

@ -42,6 +42,13 @@ export function setCurrentServer(server) {
};
}
export function getServerState(id) {
return {
type: 'MOPIDY_GET_SERVER_STATE',
id,
};
}
export function removeServer(id) {
return {
type: 'MOPIDY_REMOVE_SERVER',

View File

@ -478,6 +478,17 @@ const MopidyMiddleware = (function () {
);
break;
case 'MOPIDY_GET_SERVER_STATE': {
const server = store.getState().mopidy.servers[action.id];
fetch(`http://${server.host}:${server.port}/iris/http/get_server_state`)
.then((response) => response.json())
.then(({ result }) => {
store.dispatch(mopidyActions.updateServer({ ...server, ...result }));
});
break;
}
case 'MOPIDY_REMOVE_SERVER': {
const servers = { ...store.getState().mopidy.servers };
delete servers[action.id];

View File

@ -1,6 +1,10 @@
import ReactGA from 'react-ga';
import { uriType, generateGuid } from '../../util/helpers';
import { trackEvent } from '../../components/Trackable';
import {
digestMopidyImages,
formatImages,
} from '../../util/format';
const coreActions = require('../core/actions');
const uiActions = require('../ui/actions');
@ -8,8 +12,9 @@ const pusherActions = require('./actions');
const lastfmActions = require('../lastfm/actions');
const geniusActions = require('../genius/actions');
const spotifyActions = require('../spotify/actions');
const mopidyActions = require('../mopidy/actions');
const PusherMiddleware = (function () {
const PusherMiddleware = (function () {
// container for the actual websocket
let socket = null;

View File

@ -0,0 +1,70 @@
import React, { useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import Thumbnail from '../../components/Thumbnail';
import LinksSentence from '../../components/LinksSentence';
import Modal from './Modal';
import * as uiActions from '../../services/ui/actions';
import * as mopidyActions from '../../services/mopidy/actions';
import { i18n, I18n } from '../../locale';
import { indexToArray } from '../../util/arrays';
const Servers = () => {
const history = useHistory();
const dispatch = useDispatch();
const servers = indexToArray(useSelector((state) => state.mopidy.servers || {}));
useEffect(() => dispatch(uiActions.setWindowTitle(i18n('modal.servers.title'))), []);
useEffect(() => {
servers.forEach(({ id }) => {
dispatch(mopidyActions.getServerState(id));
});
}, []);
const onClick = (server) => {
dispatch(mopidyActions.setCurrentServer(server));
history.push('/queue');
}
return (
<Modal className="modal--servers">
<h1>
<I18n path="modal.servers.title" />
</h1>
<form>
<>
{servers.map((server) => {
const {
id,
name,
playback_state,
current_track,
} = server;
return (
<div key={id} onClick={() => onClick(server)}>
<h3>
{name}
{playback_state ? ` (${playback_state})` : ''}
</h3>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Thumbnail images={current_track?.images} size="small" />
<div style={{ paddingLeft: '1rem' }}>
<div>{current_track?.name}</div>
<em>
<LinksSentence items={current_track?.artists} type="artist" nolinks />
</em>
</div>
</div>
</div>
);
})}
</>
</form>
</Modal>
);
}
export default Servers;