diff --git a/src/js/App.js b/src/js/App.js index 90bf1d2a..dbd0e2b0 100755 --- a/src/js/App.js +++ b/src/js/App.js @@ -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 }} >
+ { + 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; diff --git a/src/js/services/pusher/actions.js b/src/js/services/pusher/actions.js index 07e2794c..e60b98d7 100755 --- a/src/js/services/pusher/actions.js +++ b/src/js/services/pusher/actions.js @@ -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', diff --git a/src/js/services/pusher/middleware.js b/src/js/services/pusher/middleware.js index 44f70b3c..76505869 100755 --- a/src/js/services/pusher/middleware.js +++ b/src/js/services/pusher/middleware.js @@ -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( diff --git a/src/js/services/ui/actions.js b/src/js/services/ui/actions.js index 85874595..b036b46e 100755 --- a/src/js/services/ui/actions.js +++ b/src/js/services/ui/actions.js @@ -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, }; } diff --git a/src/js/services/ui/middleware.js b/src/js/services/ui/middleware.js index 2bd13bf6..d19dfb2b 100755 --- a/src/js/services/ui/middleware.js +++ b/src/js/services/ui/middleware.js @@ -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': diff --git a/src/js/services/ui/reducer.js b/src/js/services/ui/reducer.js index f5c792b6..b2e3b082 100755 --- a/src/js/services/ui/reducer.js +++ b/src/js/services/ui/reducer.js @@ -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 * */ diff --git a/src/js/views/Modals/AddToPlaylist.js b/src/js/views/Modals/AddToPlaylist.js index 112b97f2..cd62d128 100755 --- a/src/js/views/Modals/AddToPlaylist.js +++ b/src/js/views/Modals/AddToPlaylist.js @@ -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 = () => { diff --git a/src/js/views/Modals/AddToQueue.js b/src/js/views/Modals/AddToQueue.js index cf52cd8b..2c923519 100755 --- a/src/js/views/Modals/AddToQueue.js +++ b/src/js/views/Modals/AddToQueue.js @@ -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) => { diff --git a/src/js/views/Modals/CreatePlaylist.js b/src/js/views/Modals/CreatePlaylist.js index bc1aa92d..114f2365 100755 --- a/src/js/views/Modals/CreatePlaylist.js +++ b/src/js/views/Modals/CreatePlaylist.js @@ -38,7 +38,7 @@ const CreatePlaylist = () => { dispatch( coreActions.createPlaylist({ ...playlist, tracks_uris }), ); - window.history.back(); + dispatch(uiActions.closeModal()); return false; }; diff --git a/src/js/views/Modals/EditCommand.js b/src/js/views/Modals/EditCommand.js index cfdeb8d8..81dda729 100755 --- a/src/js/views/Modals/EditCommand.js +++ b/src/js/views/Modals/EditCommand.js @@ -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 diff --git a/src/js/views/Modals/EditPlaylist.js b/src/js/views/Modals/EditPlaylist.js index 3a7b801f..398578da 100755 --- a/src/js/views/Modals/EditPlaylist.js +++ b/src/js/views/Modals/EditPlaylist.js @@ -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; } diff --git a/src/js/views/Modals/EditRadio.js b/src/js/views/Modals/EditRadio.js index 857b53e0..ba832748 100755 --- a/src/js/views/Modals/EditRadio.js +++ b/src/js/views/Modals/EditRadio.js @@ -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') }); } diff --git a/src/js/views/Modals/ImportConfiguration.js b/src/js/views/Modals/ImportConfiguration.js index a70a25c7..1bc030db 100644 --- a/src/js/views/Modals/ImportConfiguration.js +++ b/src/js/views/Modals/ImportConfiguration.js @@ -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 ( diff --git a/src/js/views/Modals/InitialSetup.js b/src/js/views/Modals/InitialSetup.js index 80d38a05..67801590 100755 --- a/src/js/views/Modals/InitialSetup.js +++ b/src/js/views/Modals/InitialSetup.js @@ -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 { - + . {!this.state.allow_reporting && ( diff --git a/src/js/views/Modals/Modal.js b/src/js/views/Modals/Modal.js index 2310e0d1..6fa7b3fe 100755 --- a/src/js/views/Modals/Modal.js +++ b/src/js/views/Modals/Modal.js @@ -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 ( +
- render() { - const { - extraControls = null, - noclose = false, - children, - className = '', - } = this.props; - - return ( -
- -
- {extraControls} - {!noclose && ( -
window.history.back()}> - -
- )} -
- -
- {children} -
+
+ {extraControls} + {!noclose && ( +
+ +
+ )}
- ); - } + +
+ {children} +
+
+ ); } -const mapStateToProps = (state, ownProps) => ({ - shortkeys_enabled: state.ui.shortkeys_enabled, -}); - -const mapDispatchToProps = (dispatch) => ({}); - -export default connect(mapStateToProps, mapDispatchToProps)(Modal); +export default Modal; diff --git a/src/js/views/Modals/Reset.js b/src/js/views/Modals/Reset.js index b56413a4..2916195b 100644 --- a/src/js/views/Modals/Reset.js +++ b/src/js/views/Modals/Reset.js @@ -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, ); }); diff --git a/src/js/views/Modals/ShareConfiguration.js b/src/js/views/Modals/ShareConfiguration.js index 98f51614..d674c3a5 100755 --- a/src/js/views/Modals/ShareConfiguration.js +++ b/src/js/views/Modals/ShareConfiguration.js @@ -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 = ({ onChange('server')} + checked={selected.includes('SERVER')} + onChange={() => onChange('SERVER')} />
@@ -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 = () => { onConfigurationChanged('spotify')} + checked={selectedConfigs.indexOf('spotify') > -1} + onChange={() => onSelectedConfigChanged('spotify')} />
@@ -214,8 +230,8 @@ const ShareConfiguration = () => { onConfigurationChanged('lastfm')} + checked={selectedConfigs.indexOf('lastfm') > -1} + onChange={() => onSelectedConfigChanged('lastfm')} />
@@ -243,8 +259,8 @@ const ShareConfiguration = () => { onConfigurationChanged('genius')} + checked={selectedConfigs.indexOf('genius') > -1} + onChange={() => onSelectedConfigChanged('genius')} />
@@ -271,8 +287,8 @@ const ShareConfiguration = () => { onConfigurationChanged('snapcast')} + checked={selectedConfigs.indexOf('snapcast') > -1} + onChange={() => onSelectedConfigChanged('snapcast')} />
@@ -292,8 +308,8 @@ const ShareConfiguration = () => { onConfigurationChanged('ui')} + checked={selectedConfigs.indexOf('ui') > -1} + onChange={() => onSelectedConfigChanged('ui')} />