Cleaning close modal into actions; Pushing shared config to server

This commit is contained in:
James Barnsley
2022-03-28 21:18:55 +13:00
parent f76e985fad
commit 28953bf7bd
18 changed files with 205 additions and 118 deletions

View File

@ -30,6 +30,7 @@ import Search from './views/Search';
import Settings from './views/Settings';
import Discover from './views/Discover/Discover';
import Library from './views/Library/Library';
import ModalStateListener from './components/ModalStateListener';
import Modals from './views/Modals/Modals';
import { scrollTo, isTouchDevice } from './util/helpers';
@ -251,6 +252,7 @@ const App = () => {
options={{ enableMouseEvents: true }}
>
<div className="body">
<ModalStateListener />
<Switch>
<Route
path="/modal"

View File

@ -0,0 +1,16 @@
import { useSelector } from 'react-redux';
import { useHistory, useLocation } from 'react-router-dom';
const ModalStateListener = () => {
const { name } = useSelector((state) => state.ui.modal || {});
const history = useHistory();
const location = useLocation();
if (name && location.pathname.indexOf(`modal/${name}`) < 0) {
history.push(`modal/${name}`);
}
return null;
}
export default ModalStateListener;

View File

@ -106,6 +106,13 @@ export function getConfig() {
};
}
export function setSharedConfig(shared_config) {
return {
type: 'PUSHER_SET_SHARED_CONFIG',
shared_config,
};
}
export function getVersion() {
return {
type: 'PUSHER_GET_VERSION',

View File

@ -471,6 +471,25 @@ const PusherMiddleware = (function () {
);
break;
case 'PUSHER_SET_SHARED_CONFIG': {
const { shared_config } = action;
request(store, 'set_shared_config', { shared_config })
.then(
() => {
// No action required, the change will be broadcast
},
(error) => {
store.dispatch(coreActions.handleException(
'Could not share to server',
error,
));
},
);
next(action);
break;
}
case 'PUSHER_GET_CONNECTIONS':
request(store, 'get_connections')
.then(

View File

@ -269,7 +269,17 @@ export function openModal(name, data) {
return {
type: 'OPEN_MODAL',
name,
data,
data: {
name,
...data,
},
};
}
export function closeModal(url) {
return {
type: 'CLOSE_MODAL',
url,
};
}

View File

@ -36,11 +36,12 @@ const UIMiddleware = (function () {
next(action);
break;
case 'OPEN_MODAL':
const { name } = action;
window.location = `/iris/modal/${name}`;
console.debug(action);
next(action); // Save data to state
case 'CLOSE_MODAL':
if (action.url) {
window.location = action.url;
} else {
window.history.back();
}
break;
case 'HIDE_CONTEXT_MENU':

View File

@ -78,6 +78,9 @@ export default function reducer(ui = {}, action) {
case 'OPEN_MODAL':
return { ...ui, modal: action.data };
case 'CLOSE_MODAL':
return { ...ui, modal: null };
/**
* Notifications
* */

View File

@ -52,10 +52,13 @@ class AddToPlaylist extends React.Component {
coreActions: {
addTracksToPlaylist,
},
uiActions: {
closeModal,
},
uris,
} = this.props;
addTracksToPlaylist(playlist_uri, uris);
window.history.back();
closeModal();
}
renderList = () => {

View File

@ -76,6 +76,9 @@ class AddToQueue extends React.Component {
mopidyActions: {
enqueueURIs,
},
uiActions: {
closeModal,
},
view: {
uris = [],
},
@ -83,8 +86,7 @@ class AddToQueue extends React.Component {
e.preventDefault();
enqueueURIs({ uris, play_next });
this.reset();
window.history.back();
closeModal();
}
onChange = (text) => {

View File

@ -38,7 +38,7 @@ const CreatePlaylist = () => {
dispatch(
coreActions.createPlaylist({ ...playlist, tracks_uris }),
);
window.history.back();
dispatch(uiActions.closeModal());
return false;
};

View File

@ -40,7 +40,7 @@ class EditCommand extends React.Component {
this.props.pusherActions.setCommand(this.state);
window.history.back();
this.props.uiActions.closeModal();
// A bit hacky, but wait for a moment to allow the back navigation
// and then scroll down to our commands list

View File

@ -111,6 +111,9 @@ class EditPlaylist extends React.Component {
coreActions: {
savePlaylist,
},
uiActions: {
closeModal,
},
} = this.props;
e.preventDefault();
@ -127,7 +130,7 @@ class EditPlaylist extends React.Component {
collaborative,
image,
);
window.history.back();
closeModal();
return false;
}

View File

@ -96,7 +96,7 @@ class EditRadio extends React.Component {
if (valid_seeds) {
this.props.pusherActions.startRadio(this.state.seeds);
window.history.back();
this.props.uiActions.closeModal();
} else {
this.setState({ error_message: i18n('modal.edit_radio.invalid_seed_uri') });
}
@ -116,7 +116,7 @@ class EditRadio extends React.Component {
if (valid_seeds) {
this.props.pusherActions.updateRadio(this.state.seeds);
window.history.back();
this.props.uiActions.closeModal();
} else {
this.setState({ error_message: i18n('modal.edit_radio.invalid_seed_uri') });
}

View File

@ -2,7 +2,12 @@ import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { pick } from 'lodash';
import Modal from './Modal';
import { setWindowTitle, set as setUi, createNotification } from '../../services/ui/actions';
import {
setWindowTitle,
set as setUi,
createNotification,
closeModal,
} from '../../services/ui/actions';
import * as spotifyActions from '../../services/spotify/actions';
import * as snapcastActions from '../../services/snapcast/actions';
import * as lastfmActions from '../../services/lastfm/actions';
@ -61,7 +66,7 @@ const ImportConfiguration = () => {
content: i18n('modal.share_configuration.import.successful'),
}));
window.history.back();
dispatch(closeModal());
}
return (

View File

@ -69,6 +69,7 @@ class InitialSetup extends React.Component {
// Wait a jiffy to allow changes to apply to store
setTimeout(() => {
self.props.uiActions.closeModal();
// We've changed a connection setting, so need to reload
if (self.state.host !== self.props.host || self.state.port !== self.props.port) {
window.location = '/';
@ -150,7 +151,7 @@ class InitialSetup extends React.Component {
<I18n path="settings.interface.reporting.description" />
<a href="https://github.com/jaedb/Iris/wiki/Terms-of-use#privacy-policy" target="_blank">
<I18n path="settings.interface.reporting.privacy_policy" />
</a>
</a>
.
{!this.state.allow_reporting && (
<span className="red-text">

View File

@ -1,48 +1,44 @@
import React from 'react';
import { connect } from 'react-redux';
import React, { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { closeModal } from '../../services/ui/actions';
import Icon from '../../components/Icon';
class Modal extends React.Component {
componentDidMount() {
const Modal = ({
extraControls,
noclose,
children,
className = '',
}) => {
const dispatch = useDispatch();
useEffect(() => {
$('body').addClass('modal-open');
return () => {
$('body').removeClass('modal-open');
}
}, []);
const onClose = () => {
dispatch(closeModal());
}
componentWillUnmount() {
$('body').removeClass('modal-open');
}
return (
<div className={`modal ${className}`}>
render() {
const {
extraControls = null,
noclose = false,
children,
className = '',
} = this.props;
return (
<div className={`modal ${className}`}>
<div className="controls">
{extraControls}
{!noclose && (
<div className="control close" onClick={(e) => window.history.back()}>
<Icon name="close" className="white" />
</div>
)}
</div>
<div className="content">
{children}
</div>
<div className="controls">
{extraControls}
{!noclose && (
<div className="control close" onClick={onClose}>
<Icon name="close" className="white" />
</div>
)}
</div>
);
}
<div className="content">
{children}
</div>
</div>
);
}
const mapStateToProps = (state, ownProps) => ({
shortkeys_enabled: state.ui.shortkeys_enabled,
});
const mapDispatchToProps = (dispatch) => ({});
export default connect(mapStateToProps, mapDispatchToProps)(Modal);
export default Modal;

View File

@ -31,6 +31,9 @@ class Reset extends React.Component {
coreActions: {
resetState,
},
uiActions: {
closeModal,
},
} = this.props;
const {
preferences,
@ -137,7 +140,7 @@ class Reset extends React.Component {
console.log('Reset complete, refreshing...');
resetState(stateKeysToReset);
setTimeout(
() => window.location = `/iris/settings${test_mode ? '?test_mode=0' : ''}`,
() => closeModal(window.location = `/iris/settings${test_mode ? '?test_mode=0' : ''}`),
1000,
);
});

View File

@ -1,8 +1,9 @@
import React, { useState, useEffect } from 'react';
import { pick } from 'lodash';
import { useDispatch, useSelector } from 'react-redux';
import Modal from './Modal';
import { setWindowTitle } from '../../services/ui/actions';
import { deliverMessage } from '../../services/pusher/actions';
import { setWindowTitle, closeModal } from '../../services/ui/actions';
import { deliverMessage, setSharedConfig } from '../../services/pusher/actions';
import { i18n, I18n } from '../../locale';
import Button from '../../components/Button';
import { indexToArray } from '../../util/arrays';
@ -26,8 +27,8 @@ const RecipientsList = ({
<input
type="checkbox"
name="connection_server"
checked={selected.includes('server')}
onChange={() => onChange('server')}
checked={selected.includes('SERVER')}
onChange={() => onChange('SERVER')}
/>
<div className="label">
<div>
@ -68,7 +69,7 @@ const RecipientsList = ({
const ShareConfiguration = () => {
const dispatch = useDispatch();
const [recipients, setRecipients] = useState([]);
const [configuration, setConfiguration] = useState({});
const [selectedConfigs, setSelectedConfigs] = useState([]);
const spotify = useSelector((state) => state.spotify);
const genius = useSelector((state) => state.genius);
const lastfm = useSelector((state) => state.lastfm);
@ -92,64 +93,79 @@ const ShareConfiguration = () => {
});
}
const onConfigurationChanged = (id) => {
setConfiguration((prev) => {
const next = { ...prev };
if (next.id) {
delete next[id];
const onSelectedConfigChanged = (id) => {
setSelectedConfigs((prev) => {
const next = [...prev];
if (next.includes(id)) {
const index = next.indexOf(id);
next.splice(index, 1);
} else {
switch (id) {
case 'spotify':
next.spotify = {
authorization: spotify.authorization,
me: spotify.me,
};
break;
case 'genius':
next.genius = {
authorization: genius.authorization,
me: genius.me,
};
break;
case 'lastfm':
next.lastfm = {
authorization: lastfm.authorization,
me: lastfm.me,
};
break;
case 'ui':
next.ui = ui;
break;
case 'snapcast':
next.snapcast = {
enabled: snapcast.enabled,
host: snapcast.host,
port: snapcast.port,
};
break;
default:
break;
}
next.push(id);
}
return next;
});
}
const collateConfig = () => {
const obj = {};
selectedConfigs.forEach((name) => {
switch (name) {
case 'spotify':
obj.spotify = pick(spotify, ['authorization', 'me']);
break;
case 'genius':
obj.genius = pick(genius, ['authorization', 'me']);
break;
case 'lastfm':
obj.lastfm = pick(lastfm, ['authorization', 'me']);
break;
case 'snapcast':
obj.snapcast = pick(snapcast, ['enabled', 'host', 'port', 'ssl']);
break;
case 'ui':
obj.ui = pick(
ui,
[
'language',
'theme',
'smooth_scrolling_enabled',
'hotkeys_enabled',
'allow_reporting',
'wide_scrollbars_enabled',
'hide_scrollbars',
'grid_glow_enabled',
'sort',
'initial_setup_complete',
'uri_schemes_search_enabled',
],
);
break;
default:
break;
}
});
return obj;
};
const onSubmit = (e) => {
e.preventDefault();
const configuration = collateConfig();
for (const recipient of recipients) {
dispatch(
deliverMessage(
recipient,
'share_configuration_received',
configuration,
),
);
if (recipient === 'SERVER') {
dispatch(setSharedConfig(configuration));
} else {
dispatch(
deliverMessage(
recipient,
'share_configuration_received',
configuration,
),
);
}
}
window.history.back();
dispatch(closeModal());
}
return (
@ -185,8 +201,8 @@ const ShareConfiguration = () => {
<input
type="checkbox"
name="spotify"
checked={configuration.spotify}
onChange={() => onConfigurationChanged('spotify')}
checked={selectedConfigs.indexOf('spotify') > -1}
onChange={() => onSelectedConfigChanged('spotify')}
/>
<div className="label">
<div>
@ -214,8 +230,8 @@ const ShareConfiguration = () => {
<input
type="checkbox"
name="lastfm_authorization"
checked={configuration.lastfm}
onChange={() => onConfigurationChanged('lastfm')}
checked={selectedConfigs.indexOf('lastfm') > -1}
onChange={() => onSelectedConfigChanged('lastfm')}
/>
<div className="label">
<div>
@ -243,8 +259,8 @@ const ShareConfiguration = () => {
<input
type="checkbox"
name="genius_authorization"
checked={configuration.genius}
onChange={() => onConfigurationChanged('genius')}
checked={selectedConfigs.indexOf('genius') > -1}
onChange={() => onSelectedConfigChanged('genius')}
/>
<div className="label">
<div>
@ -271,8 +287,8 @@ const ShareConfiguration = () => {
<input
type="checkbox"
name="snapcast"
checked={configuration.snapcast}
onChange={() => onConfigurationChanged('snapcast')}
checked={selectedConfigs.indexOf('snapcast') > -1}
onChange={() => onSelectedConfigChanged('snapcast')}
/>
<div className="label">
<div>
@ -292,8 +308,8 @@ const ShareConfiguration = () => {
<input
type="checkbox"
name="interface"
checked={configuration.ui}
onChange={() => onConfigurationChanged('ui')}
checked={selectedConfigs.indexOf('ui') > -1}
onChange={() => onSelectedConfigChanged('ui')}
/>
<div className="label">
<div>