Merging PR #853
This commit is contained in:
109
src/js/App.js
109
src/js/App.js
@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Route, Switch, useLocation, useHistory } from 'react-router-dom';
|
||||
import { Route, Routes, useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import ReactGA from 'react-ga';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
@ -42,12 +42,50 @@ import * as snapcastActions from './services/snapcast/actions';
|
||||
import MediaSession from './components/MediaSession';
|
||||
import ErrorBoundary from './components/ErrorBoundary';
|
||||
|
||||
const Content = () => (
|
||||
<>
|
||||
<Sidebar tabIndex="3" />
|
||||
<PlaybackControls tabIndex="2"/>
|
||||
<main id="main" className="smooth-scroll" tabIndex="1">
|
||||
<Routes>
|
||||
<Route path="" element={<Queue />} />
|
||||
<Route path="queue/" element={<Queue />} />
|
||||
<Route path="queue/history" element={<QueueHistory />}/>
|
||||
<Route path="settings/debug" element={<Debug />} />
|
||||
<Route path="settings/*" element={<Settings />} />
|
||||
<Route path="search/" element={<Search />} />
|
||||
<Route path="search/:type/:term" element={<Search />} />
|
||||
<Route path="artist/:uri/*" element={<Artist />} />
|
||||
<Route path="album/:uri/" element={<Album />} />
|
||||
<Route path="album/:uri/:name" element={<Album />} />
|
||||
<Route path="playlist/:uri/" element={<Playlist />} />
|
||||
<Route path="playlist/:uri/:name" element={<Playlist />} />
|
||||
<Route path="user/:uri/" element={<User />} />
|
||||
<Route path="user/:uri/:name" element={<User />} />
|
||||
<Route path="track/:uri/" element={<Track />} />
|
||||
<Route path="track/:uri/:name" element={<Track />} />
|
||||
<Route path="uri/:uri/" element={<UriRedirect />} />
|
||||
<Route path="uri/:uri/:name" element={<UriRedirect />} />
|
||||
<Route path="discover/*" element={<Discover />} />
|
||||
<Route path="library/*" element={<Library />} />
|
||||
<Route element={
|
||||
<ErrorMessage type="not-found" title="Not found">
|
||||
<p>Oops, that link could not be found</p>
|
||||
</ErrorMessage>
|
||||
} />
|
||||
</Routes>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
|
||||
const App = () => {
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
const [hasInteracted, setHasInteracted] = useState(false);
|
||||
const { pathname, state: { scroll_position } = {} } = useLocation();
|
||||
const location = useLocation();
|
||||
const { pathname, state } = location;
|
||||
const scroll_position = state?.scroll_position;
|
||||
const dispatch = useDispatch();
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const language = useSelector((state) => state.ui.language);
|
||||
window.language = language;
|
||||
@ -165,7 +203,7 @@ const App = () => {
|
||||
dispatch(snapcastActions.connect());
|
||||
}
|
||||
if (!initial_setup_complete) {
|
||||
history.push('/modal/initial-setup');
|
||||
navigate('/modal/initial-setup');
|
||||
}
|
||||
}
|
||||
}, [isReady]);
|
||||
@ -253,65 +291,10 @@ const App = () => {
|
||||
>
|
||||
<div className="body">
|
||||
<ModalStateListener />
|
||||
<Switch>
|
||||
<Route
|
||||
path="/modal"
|
||||
component={Modals}
|
||||
/>
|
||||
<Route>
|
||||
<div>
|
||||
<Sidebar tabIndex="3" />
|
||||
<PlaybackControls tabIndex="2"/>
|
||||
|
||||
<main id="main" className="smooth-scroll" tabIndex="1">
|
||||
<Switch>
|
||||
<Route exact path="/" component={Queue} />
|
||||
|
||||
<Route exact path="/queue" component={Queue} />
|
||||
<Route
|
||||
exact
|
||||
path="/queue/history"
|
||||
component={QueueHistory}
|
||||
/>
|
||||
<Route exact path="/settings/debug" component={Debug} />
|
||||
<Route path="/settings" component={Settings} />
|
||||
|
||||
<Route
|
||||
exact
|
||||
path="/search/:type?/:term?"
|
||||
component={Search}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/artist/:uri/:sub_view?"
|
||||
component={Artist}
|
||||
/>
|
||||
<Route exact path="/album/:uri/:name?" component={Album} />
|
||||
<Route exact path="/playlist/:uri/:name?" component={Playlist} />
|
||||
<Route exact path="/user/:uri/:name?" component={User} />
|
||||
<Route exact path="/track/:uri/:name?" component={Track} />
|
||||
<Route exact path="/uri/:uri/:name?" component={UriRedirect} />
|
||||
|
||||
<Route
|
||||
path="/discover"
|
||||
component={Discover}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/library"
|
||||
component={Library}
|
||||
/>
|
||||
|
||||
<Route>
|
||||
<ErrorMessage type="not-found" title="Not found">
|
||||
<p>Oops, that link could not be found</p>
|
||||
</ErrorMessage>
|
||||
</Route>
|
||||
</Switch>
|
||||
</main>
|
||||
</div>
|
||||
</Route>
|
||||
</Switch>
|
||||
<Routes>
|
||||
<Route path="modal/*" element={<Modals />} />
|
||||
<Route path="*" element={<Content />} />
|
||||
</Routes>
|
||||
</div>
|
||||
|
||||
<ResizeListener />
|
||||
|
||||
@ -1,25 +1,7 @@
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { withRouter } from 'react-router';
|
||||
import { compact } from 'lodash';
|
||||
import {
|
||||
uriSource,
|
||||
uriType,
|
||||
getFromUri,
|
||||
buildLink,
|
||||
isLoading,
|
||||
throttle,
|
||||
titleCase,
|
||||
} from '../../util/helpers';
|
||||
import {
|
||||
arrayOf,
|
||||
sortItems,
|
||||
} from '../../util/arrays';
|
||||
import Link from '../Link';
|
||||
import Icon from '../Icon';
|
||||
import Loader from '../Loader';
|
||||
import URILink from '../URILink';
|
||||
|
||||
import * as coreActions from '../../services/core/actions';
|
||||
import * as uiActions from '../../services/ui/actions';
|
||||
@ -27,8 +9,6 @@ import * as pusherActions from '../../services/pusher/actions';
|
||||
import * as mopidyActions from '../../services/mopidy/actions';
|
||||
import * as lastfmActions from '../../services/lastfm/actions';
|
||||
import * as spotifyActions from '../../services/spotify/actions';
|
||||
import { I18n } from '../../locale';
|
||||
import { encodeUri } from '../../util/format';
|
||||
import {
|
||||
makeProcessProgressSelector,
|
||||
makeProvidersSelector,
|
||||
@ -207,4 +187,4 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
mopidyActions: bindActionCreators(mopidyActions, dispatch),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(ContextMenu));
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ContextMenu);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { uniq } from 'lodash';
|
||||
import { I18n } from '../../locale';
|
||||
import Link from '../Link';
|
||||
@ -266,12 +266,12 @@ const Discover = ({
|
||||
uris,
|
||||
disabled,
|
||||
}) => {
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const onClick = () => {
|
||||
dispatch(hideContextMenu());
|
||||
const discoverUri = encodeUri(`iris:discover:${uris.map((uri) => encodeUri(uri)).join(',')}`);
|
||||
history.push(`/discover/recommendations/${discoverUri}`);
|
||||
navigate(`/discover/recommendations/${discoverUri}`);
|
||||
};
|
||||
return (
|
||||
<div className={`context-menu__item ${disabled && 'context-menu__item--disabled'}`}>
|
||||
@ -380,11 +380,11 @@ const GoTo = ({
|
||||
type,
|
||||
uri,
|
||||
}) => {
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const onClick = () => {
|
||||
dispatch(hideContextMenu());
|
||||
history.push(`/${type}/${encodeUri(uri)}`);
|
||||
navigate(`/${type}/${encodeUri(uri)}`);
|
||||
};
|
||||
return (
|
||||
<div className="context-menu__item">
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useDragLayer, useDrop } from 'react-dnd';
|
||||
import * as mopidyActions from '../../services/mopidy/actions';
|
||||
import Icon from '../Icon';
|
||||
@ -53,7 +53,7 @@ const Dropzone = ({
|
||||
accept,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
const [{ handlerId, isOver, canDrop }, drop] = useDrop({
|
||||
accept,
|
||||
collect: (monitor) => {
|
||||
@ -73,10 +73,10 @@ const Dropzone = ({
|
||||
dispatch(mopidyActions.enqueueURIs({ uris, from: context, play_next: true }));
|
||||
break;
|
||||
case 'add_to_playlist':
|
||||
history.push(`/modal/add-to-playlist/${encodeUri(uris.join(','))}`);
|
||||
navigate(`/modal/add-to-playlist/${encodeUri(uris.join(','))}`);
|
||||
break;
|
||||
case 'create_playlist_and_add':
|
||||
history.push(`/modal/create-playlist/${encodeUri(uris.join(','))}`);
|
||||
navigate(`/modal/create-playlist/${encodeUri(uris.join(','))}`);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { uriType } from '../../util/helpers';
|
||||
import Icon from '../Icon';
|
||||
import { i18n } from '../../locale';
|
||||
@ -12,7 +11,7 @@ const SearchForm = ({
|
||||
onReset: doReset,
|
||||
onSubmit: doSubmit,
|
||||
}) => {
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
const [term, setTerm] = useState(termProp);
|
||||
const [dirty, setDirty] = useState();
|
||||
|
||||
@ -43,19 +42,19 @@ const SearchForm = ({
|
||||
// check for uri type matching
|
||||
switch (uriType(term)) {
|
||||
case 'album':
|
||||
history.push(`/album/${encodeUri(term)}`);
|
||||
navigate(`/album/${encodeUri(term)}`);
|
||||
break;
|
||||
|
||||
case 'artist':
|
||||
history.push(`/artist/${encodeUri(term)}`);
|
||||
navigate(`/artist/${encodeUri(term)}`);
|
||||
break;
|
||||
|
||||
case 'playlist':
|
||||
history.push(`/playlist/${encodeUri(term)}`);
|
||||
navigate(`/playlist/${encodeUri(term)}`);
|
||||
break;
|
||||
|
||||
case 'track':
|
||||
history.push(`/track/${encodeUri(term)}`);
|
||||
navigate(`/track/${encodeUri(term)}`);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
@ -32,6 +32,7 @@ const SecondaryLine = ({
|
||||
const items = () => {
|
||||
switch (type) {
|
||||
case 'mood':
|
||||
case 'directory':
|
||||
case 'playlist_group':
|
||||
return null;
|
||||
case 'playlist':
|
||||
@ -78,7 +79,7 @@ const GridItem = ({
|
||||
type: item?.type?.toUpperCase() || 'UNKNOWN',
|
||||
item: { item, context: item },
|
||||
});
|
||||
const tile = ['playlist_group', 'mood', 'directory', 'category'].indexOf(item?.type) > -1
|
||||
const tile = ['playlist_group', 'mood', 'category'].indexOf(item?.type) > -1
|
||||
|
||||
const onContextMenu = (e) => {
|
||||
e.preventDefault();
|
||||
@ -142,7 +143,10 @@ const GridItem = ({
|
||||
type={item.type}
|
||||
/>
|
||||
<div className="grid__item__name">
|
||||
{item.name ? item.name : <span className="opaque-text">{item.uri}</span>}
|
||||
{item.name
|
||||
? <span title={item.name}>{item.name}</span>
|
||||
: <span className="opaque-text">{item.uri}</span>
|
||||
}
|
||||
</div>
|
||||
<div className="grid__item__secondary">
|
||||
<SecondaryLine item={item} sourceIcon={sourceIcon} />
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import ReactGA from 'react-ga';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { indexToArray, sortItems } from '../util/arrays';
|
||||
import { collate } from '../util/format';
|
||||
@ -10,7 +10,7 @@ import * as mopidyActions from '../services/mopidy/actions';
|
||||
import * as snapcastActions from '../services/snapcast/actions';
|
||||
|
||||
const Hotkeys = () => {
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const volume = useSelector((state) => state.mopidy.volume);
|
||||
const mute = useSelector((state) => state.mopidy.mute);
|
||||
@ -94,7 +94,7 @@ const Hotkeys = () => {
|
||||
e,
|
||||
label: 'Hotkey info',
|
||||
callback: () => {
|
||||
history.push('/modal/hotkeys');
|
||||
navigate('/modal/hotkeys');
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
import { Link as RouterLink, useHistory, useLocation } from 'react-router-dom';
|
||||
import { Link as RouterLink, useNavigate, useLocation } from 'react-router-dom';
|
||||
import { scrollTo } from '../util/helpers';
|
||||
|
||||
const updateScrollPosition = ({
|
||||
retainScroll = false,
|
||||
scrollTarget = 0,
|
||||
history,
|
||||
navigate,
|
||||
location: {
|
||||
state,
|
||||
pathname,
|
||||
@ -16,14 +16,17 @@ const updateScrollPosition = ({
|
||||
// This doesn't trigger lazy-load elements (unless scrolling exposes the LazyLoader component).
|
||||
const main = document.getElementById('main');
|
||||
|
||||
history.replace(
|
||||
navigate(
|
||||
pathname,
|
||||
{
|
||||
...state,
|
||||
scroll_position: main.scrollTop,
|
||||
previous: {
|
||||
pathname,
|
||||
state: {
|
||||
...state,
|
||||
scroll_position: main.scrollTop,
|
||||
previous: {
|
||||
pathname,
|
||||
},
|
||||
},
|
||||
replace: true,
|
||||
},
|
||||
);
|
||||
|
||||
@ -48,23 +51,23 @@ const Link = ({
|
||||
children,
|
||||
}) => {
|
||||
if (!to) return <span className={className}>{children}</span>;
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const handleClick = (e) => {
|
||||
updateScrollPosition({
|
||||
history, location, retainScroll, scrollTarget,
|
||||
navigate, location, retainScroll, scrollTarget,
|
||||
});
|
||||
if (onClick) onClick(e);
|
||||
}
|
||||
|
||||
// Decode both links. This handles issues where one link is encoded and the other isn't, but
|
||||
// they're otherwise identical
|
||||
const currentLink = history?.location?.pathname || '';
|
||||
const currentLink = location?.pathname || '';
|
||||
const isLinkActive = exact ? currentLink === to : currentLink.startsWith(to);
|
||||
|
||||
// We have an active detector method
|
||||
// This is used almost solely by the Sidebar navigation
|
||||
const active = history && isLinkActive ? (activeClassName || 'active') : '';
|
||||
const active = navigate && isLinkActive ? (activeClassName || 'active') : '';
|
||||
return (
|
||||
<RouterLink
|
||||
onClick={handleClick}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { useDrag } from 'react-dnd';
|
||||
import LinksSentence from './LinksSentence';
|
||||
import { dater } from './Dater';
|
||||
@ -98,7 +98,7 @@ const ListItem = ({
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const spotify_available = useSelector((state) => state.spotify.access_token);
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [_, drag] = useDrag({
|
||||
type: item?.type?.toUpperCase() || 'UNKNOWN',
|
||||
@ -154,9 +154,9 @@ const ListItem = ({
|
||||
}
|
||||
|
||||
if (e.target.tagName.toLowerCase() !== 'a') {
|
||||
updateScrollPosition({ location, history });
|
||||
updateScrollPosition({ location, navigate });
|
||||
e.preventDefault();
|
||||
history.push(to);
|
||||
navigate(to);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
|
||||
const ModalStateListener = () => {
|
||||
const { name } = useSelector((state) => state.ui.modal || {});
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
if (name && location.pathname.indexOf(`modal/${name}`) < 0) {
|
||||
history.push(`modal/${name}`);
|
||||
navigate(`modal/${name}`);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Route, Switch, useParams, useHistory } from 'react-router-dom';
|
||||
import { Route, Routes, useParams, useNavigate } from 'react-router-dom';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import Link from './Link';
|
||||
import Icon from './Icon';
|
||||
@ -116,7 +116,7 @@ const Server = () => {
|
||||
};
|
||||
|
||||
const Menu = () => {
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const servers = indexToArray(useSelector((state) => state.mopidy.servers));
|
||||
|
||||
@ -133,7 +133,7 @@ const Menu = () => {
|
||||
const addServer = () => {
|
||||
const action = mopidyActions.addServer();
|
||||
dispatch(action);
|
||||
history.push(`/settings/servers/${action.server.id}`);
|
||||
navigate(`/settings/servers/${action.server.id}`);
|
||||
};
|
||||
|
||||
return (
|
||||
@ -168,7 +168,6 @@ const Menu = () => {
|
||||
}
|
||||
return (
|
||||
<Link
|
||||
history={history}
|
||||
className="menu-item"
|
||||
activeClassName="menu-item--active"
|
||||
to={`/settings/servers/${server.id}`}
|
||||
@ -207,13 +206,12 @@ const Menu = () => {
|
||||
const Servers = () => (
|
||||
<div className="sub-tabs sub-tabs--servers">
|
||||
<Menu />
|
||||
<Switch>
|
||||
<Routes>
|
||||
<Route
|
||||
exact
|
||||
path="/settings/servers/:id"
|
||||
component={Server}
|
||||
path=":id"
|
||||
component={<Server />}
|
||||
/>
|
||||
</Switch>
|
||||
</Routes>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@ -18,6 +18,7 @@ import * as mopidyActions from '../services/mopidy/actions';
|
||||
import * as pusherActions from '../services/pusher/actions';
|
||||
import * as spotifyActions from '../services/spotify/actions';
|
||||
import * as lastfmActions from '../services/lastfm/actions';
|
||||
import { withRouter } from '../util';
|
||||
import * as geniusActions from '../services/genius/actions';
|
||||
import { I18n, i18n } from '../locale';
|
||||
import Button from './Button';
|
||||
@ -291,7 +292,6 @@ class Services extends React.Component {
|
||||
me: geniusUser,
|
||||
authorization: geniusAuthorization,
|
||||
},
|
||||
history,
|
||||
snapcast: {
|
||||
enabled: snapcastEnabled,
|
||||
connected: snapcastConnected,
|
||||
@ -335,7 +335,6 @@ class Services extends React.Component {
|
||||
<div className="sub-tabs__menu menu" id="services-menu">
|
||||
<div className="menu__inner">
|
||||
<Link
|
||||
history={history}
|
||||
className="menu-item menu-item--snapcast"
|
||||
activeClassName="menu-item--active"
|
||||
to="/settings/services/snapcast"
|
||||
@ -364,7 +363,6 @@ class Services extends React.Component {
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
history={history}
|
||||
className="menu-item menu-item--spotify"
|
||||
activeClassName="menu-item--active"
|
||||
to="/settings/services/spotify"
|
||||
@ -387,7 +385,6 @@ class Services extends React.Component {
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
history={history}
|
||||
className="menu-item menu-item--lastfm"
|
||||
activeClassName="menu-item--active"
|
||||
to="/settings/services/lastfm"
|
||||
@ -410,7 +407,6 @@ class Services extends React.Component {
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
history={history}
|
||||
className="menu-item menu-item--genius"
|
||||
activeClassName="menu-item--active"
|
||||
to="/settings/services/genius"
|
||||
@ -438,8 +434,8 @@ class Services extends React.Component {
|
||||
}
|
||||
|
||||
renderService() {
|
||||
const { match } = this.props;
|
||||
switch (match.params.service) {
|
||||
const { params } = this.props;
|
||||
switch (params.service) {
|
||||
case 'spotify':
|
||||
return <div className="sub-tabs__content">{this.renderSpotify()}</div>;
|
||||
case 'lastfm':
|
||||
@ -447,7 +443,7 @@ class Services extends React.Component {
|
||||
case 'genius':
|
||||
return <div className="sub-tabs__content">{this.renderGenius()}</div>;
|
||||
case 'snapcast':
|
||||
return <div className="sub-tabs__content"><Snapcast match={this.props.match} /></div>;
|
||||
return <div className="sub-tabs__content"><Snapcast params={params} /></div>;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@ -475,4 +471,4 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
geniusActions: bindActionCreators(geniusActions, dispatch),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Services);
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Services));
|
||||
|
||||
@ -6,15 +6,12 @@ import SnapcastGroups from './SnapcastGroups';
|
||||
import * as uiActions from '../services/ui/actions';
|
||||
import * as actions from '../services/snapcast/actions';
|
||||
import { I18n } from '../locale';
|
||||
import Button from './Button';
|
||||
|
||||
const Snapcast = (props) => {
|
||||
const {
|
||||
actions,
|
||||
show_disconnected_clients,
|
||||
uiActions,
|
||||
match,
|
||||
history,
|
||||
snapcast: {
|
||||
host,
|
||||
port,
|
||||
@ -125,7 +122,7 @@ const Snapcast = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{connected && enabled && <SnapcastGroups match={match} history={history} />}
|
||||
{connected && enabled && <SnapcastGroups />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { collate } from '../util/format';
|
||||
@ -21,10 +22,9 @@ const SnapcastGroups = (props) => {
|
||||
streams,
|
||||
groups,
|
||||
clients,
|
||||
history,
|
||||
match: { params: { id: groupId } },
|
||||
} = props;
|
||||
|
||||
const { id: groupId } = useParams();
|
||||
const streamsArray = Object.keys(streams).map((id) => streams[id]);
|
||||
const groupsArray = Object.keys(groups).map((id) => groups[id]);
|
||||
|
||||
@ -128,7 +128,6 @@ const SnapcastGroups = (props) => {
|
||||
className={`snapcast__groups__menu-item menu-item${noClients ? ' menu-item--no-clients' : ''}`}
|
||||
activeClassName="menu-item--active"
|
||||
key={group.id}
|
||||
history={history}
|
||||
to={`/settings/services/snapcast/${group.id}`}
|
||||
scrollTo="#services-snapcast-groups"
|
||||
>
|
||||
|
||||
@ -1,21 +1,24 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { Provider } from 'react-redux';
|
||||
import { BrowserRouter, Route } from 'react-router-dom';
|
||||
import { BrowserRouter, Route, Routes } from 'react-router-dom';
|
||||
import { PersistGate } from 'redux-persist/integration/react';
|
||||
|
||||
import { store, persistor } from './store/index';
|
||||
import App from './App';
|
||||
import { _ } from 'core-js';
|
||||
|
||||
require('../scss/app.scss');
|
||||
|
||||
ReactDOM.render(
|
||||
const root = createRoot(document.getElementById('app'));
|
||||
root.render(
|
||||
<Provider store={store}>
|
||||
<PersistGate loading={null} persistor={persistor}>
|
||||
<BrowserRouter basename="/iris">
|
||||
<Route path="/" component={App} />
|
||||
<Routes>
|
||||
<Route path="*" element={<App />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</PersistGate>
|
||||
</Provider>,
|
||||
document.getElementById('app'),
|
||||
);
|
||||
|
||||
@ -15,9 +15,7 @@ import {
|
||||
formatAlbum,
|
||||
formatTrack,
|
||||
formatTracks,
|
||||
formatPlaylists,
|
||||
formatPlaylistGroup,
|
||||
formatPlaylistGroups,
|
||||
getTrackIcon,
|
||||
formatArtists,
|
||||
formatArtist,
|
||||
@ -412,7 +410,17 @@ const MopidyMiddleware = (function () {
|
||||
break;
|
||||
}
|
||||
|
||||
socket.on((type, data) => handleMessage(socket, store, type, data));
|
||||
socket.on('state:online', (data) => handleMessage(socket, store, 'state:online', data));
|
||||
socket.on('state:offline', (data) => handleMessage(socket, store, 'state:offline', data));
|
||||
socket.on('event:tracklistChanged', (data) => handleMessage(socket, store, 'event:tracklistChanged', data));
|
||||
socket.on('event:playbackStateChanged', (data) => handleMessage(socket, store, 'event:playbackStateChanged', data));
|
||||
socket.on('event:seeked', (data) => handleMessage(socket, store, 'event:seeked', data));
|
||||
socket.on('event:trackPlaybackEnded', (data) => handleMessage(socket, store, 'event:trackPlaybackEnded', data));
|
||||
socket.on('event:trackPlaybackStarted', (data) => handleMessage(socket, store, 'event:trackPlaybackStarted', data));
|
||||
socket.on('event:volumeChanged', (data) => handleMessage(socket, store, 'event:volumeChanged', data));
|
||||
socket.on('event:muteChanged', (data) => handleMessage(socket, store, 'event:muteChanged', data));
|
||||
socket.on('event:optionsChanged', (data) => handleMessage(socket, store, 'event:optionsChanged', data));
|
||||
socket.on('event:streamTitleChanged', (data) => handleMessage(socket, store, 'event:streamTitleChanged', data));
|
||||
|
||||
break;
|
||||
}
|
||||
@ -1803,6 +1811,7 @@ const MopidyMiddleware = (function () {
|
||||
const subdirectories = [];
|
||||
const trackUrisToLoad = [];
|
||||
const subdirectoryImagesToLoad = [];
|
||||
const playlistsToLoad = [];
|
||||
|
||||
results.forEach((item) => {
|
||||
if (item.__model__ === 'Track') {
|
||||
@ -1829,8 +1838,10 @@ const MopidyMiddleware = (function () {
|
||||
} else {
|
||||
subdirectories.push(formatPlaylist({ ...item, loading: true }));
|
||||
subdirectoryImagesToLoad.push(item.uri);
|
||||
playlistsToLoad.push(item.uri);
|
||||
}
|
||||
} else {
|
||||
subdirectoryImagesToLoad.push(item.uri);
|
||||
subdirectories.push(item);
|
||||
}
|
||||
});
|
||||
@ -1862,30 +1873,75 @@ const MopidyMiddleware = (function () {
|
||||
});
|
||||
}
|
||||
|
||||
if (subdirectoryImagesToLoad.length) {
|
||||
console.info(`Loading ${subdirectoryImagesToLoad.length} subdirectory URIs`);
|
||||
request(store, 'library.getImages', { uris: subdirectoryImagesToLoad })
|
||||
.then((response) => {
|
||||
const subdirectoriesWithImages = subdirectories.map((subdir) => {
|
||||
let images = response[subdir.uri] || undefined;
|
||||
if (images) {
|
||||
images = formatImages(digestMopidyImages(store.getState().mopidy, images));
|
||||
}
|
||||
return {
|
||||
...subdir,
|
||||
images,
|
||||
};
|
||||
const imagesLoaded = new Promise((resolve) => {
|
||||
if (subdirectoryImagesToLoad.length) {
|
||||
console.info(`Loading ${subdirectoryImagesToLoad.length} subdirectory URIs`);
|
||||
request(store, 'library.getImages', { uris: subdirectoryImagesToLoad })
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
});
|
||||
} else {
|
||||
resolve({});
|
||||
}
|
||||
});
|
||||
|
||||
store.dispatch({
|
||||
type: 'MOPIDY_DIRECTORY_LOADED',
|
||||
directory: {
|
||||
uri,
|
||||
subdirectories: subdirectoriesWithImages,
|
||||
},
|
||||
});
|
||||
const playlistsLoaded = new Promise((resolve) => {
|
||||
const playlists = {};
|
||||
if (playlistsToLoad.length) {
|
||||
console.info(`Loading ${playlistsToLoad.length} playlist URIs`);
|
||||
const toLoad = playlistsToLoad.length;
|
||||
let loaded = 0;
|
||||
playlistsToLoad.map((uri) => {
|
||||
request(store, 'playlists.lookup', { uri })
|
||||
.then((playlist) => {
|
||||
playlists[uri] = formatPlaylist({
|
||||
name: playlist.name,
|
||||
uri: playlist.uri,
|
||||
tracks: formatTracks(playlist.tracks),
|
||||
});
|
||||
loaded += 1;
|
||||
if (loaded === toLoad) {
|
||||
resolve(playlists);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
} else {
|
||||
resolve(playlists);
|
||||
}
|
||||
});
|
||||
|
||||
imagesLoaded.then((response) => {
|
||||
|
||||
const subdirectoriesWithImages = subdirectories.map((subdir) => {
|
||||
let images = response[subdir.uri] || undefined;
|
||||
if (images) {
|
||||
images = formatImages(digestMopidyImages(store.getState().mopidy, images));
|
||||
}
|
||||
return {
|
||||
...subdir,
|
||||
images,
|
||||
};
|
||||
});
|
||||
|
||||
playlistsLoaded.then((playlists) => {
|
||||
const subdirectoriesWithPlaylists = subdirectoriesWithImages.map((subdir) => {
|
||||
const playlist = playlists[subdir.uri] || {};
|
||||
return {
|
||||
...subdir,
|
||||
...playlist,
|
||||
};
|
||||
});
|
||||
|
||||
store.dispatch({
|
||||
type: 'MOPIDY_DIRECTORY_LOADED',
|
||||
directory: {
|
||||
uri,
|
||||
subdirectories: subdirectoriesWithPlaylists,
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const getBrowse = () => request(store, 'library.browse', { uri })
|
||||
|
||||
@ -538,8 +538,8 @@ const formatPlaylist = function (data) {
|
||||
playlist.followers = data.followers.total;
|
||||
}
|
||||
|
||||
if (data.tracks && data.tracks.total !== undefined) {
|
||||
playlist.tracks_total = data.tracks.total;
|
||||
if (data.tracks && data.tracks_total !== undefined) {
|
||||
playlist.tracks_total = data.tracks_total;
|
||||
}
|
||||
|
||||
if (playlist.last_modified && playlist.added_at === undefined) {
|
||||
|
||||
@ -4,6 +4,7 @@ import * as selectors from './selectors';
|
||||
import * as storage from './storage';
|
||||
import * as format from './format';
|
||||
import * as debug from './debug';
|
||||
import withRouter from './withRouter';
|
||||
|
||||
export {
|
||||
debug,
|
||||
@ -12,6 +13,7 @@ export {
|
||||
selectors,
|
||||
storage,
|
||||
format,
|
||||
withRouter,
|
||||
};
|
||||
|
||||
export default {
|
||||
@ -21,4 +23,5 @@ export default {
|
||||
selectors,
|
||||
storage,
|
||||
format,
|
||||
withRouter,
|
||||
};
|
||||
|
||||
18
src/js/util/withRouter.js
Normal file
18
src/js/util/withRouter.js
Normal file
@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import { useParams, useNavigate, useLocation } from 'react-router-dom';
|
||||
|
||||
const withRouter = (WrappedComponent) => (props) => {
|
||||
const params = useParams();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
return (
|
||||
<WrappedComponent
|
||||
{...props}
|
||||
params={params}
|
||||
navigate={navigate}
|
||||
location={location}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default withRouter;
|
||||
@ -23,7 +23,7 @@ import { uriSource } from '../util/helpers';
|
||||
import Button from '../components/Button';
|
||||
import { makeItemSelector, makeSortSelector } from '../util/selectors';
|
||||
import { applyFilter, sortItems } from '../util/arrays';
|
||||
import { decodeUri, formatSimpleObject, formatContext } from '../util/format';
|
||||
import { decodeUri, formatContext } from '../util/format';
|
||||
|
||||
const SORT_KEY = 'album_tracks';
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ import {
|
||||
uriSource,
|
||||
titleCase,
|
||||
} from '../../util/helpers';
|
||||
import { i18n, I18n } from '../../locale';
|
||||
import { I18n } from '../../locale';
|
||||
import { nice_number } from '../../components/NiceNumber';
|
||||
|
||||
export default ({
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { Route, Switch, useParams, useHistory } from 'react-router-dom';
|
||||
import { Route, Routes, useParams, useNavigate } from 'react-router-dom';
|
||||
import ErrorMessage from '../../components/ErrorMessage';
|
||||
import Link from '../../components/Link';
|
||||
import Thumbnail from '../../components/Thumbnail';
|
||||
@ -26,7 +25,6 @@ import Button from '../../components/Button';
|
||||
import { makeItemSelector } from '../../util/selectors';
|
||||
|
||||
const Artist = () => {
|
||||
const history = useHistory();
|
||||
const dispatch = useDispatch();
|
||||
const { uri: encodedUri } = useParams();
|
||||
const uri = decodeUri(encodedUri);
|
||||
@ -143,7 +141,6 @@ const Artist = () => {
|
||||
<div className="sub-views" id="sub-views-menu">
|
||||
<Link
|
||||
exact
|
||||
history={history}
|
||||
activeClassName="sub-views__option--active"
|
||||
className="sub-views__option"
|
||||
to={`/artist/${encodeUri(uri)}`}
|
||||
@ -154,7 +151,6 @@ const Artist = () => {
|
||||
{artist.tracks && artist.tracks.length > 10 && (
|
||||
<Link
|
||||
exact
|
||||
history={history}
|
||||
activeClassName="sub-views__option--active"
|
||||
className="sub-views__option"
|
||||
to={`/artist/${encodeUri(uri)}/tracks`}
|
||||
@ -166,7 +162,6 @@ const Artist = () => {
|
||||
{artist.related_artists && (
|
||||
<Link
|
||||
exact
|
||||
history={history}
|
||||
activeClassName="sub-views__option--active"
|
||||
className="sub-views__option"
|
||||
to={`/artist/${encodeUri(uri)}/related-artists`}
|
||||
@ -177,7 +172,6 @@ const Artist = () => {
|
||||
)}
|
||||
<Link
|
||||
exact
|
||||
history={history}
|
||||
activeClassName="sub-views__option--active"
|
||||
className="sub-views__option"
|
||||
to={`/artist/${encodeUri(uri)}/about`}
|
||||
@ -189,20 +183,20 @@ const Artist = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="content-wrapper">
|
||||
<Switch>
|
||||
<Route exact path="/artist/:id/related-artists">
|
||||
<Routes>
|
||||
<Route path="related-artists" element={
|
||||
<Related artist={artist} />
|
||||
</Route>
|
||||
<Route exact path="/artist/:id/tracks">
|
||||
}/>
|
||||
<Route path="tracks" element={
|
||||
<Tracks artist={artist} />
|
||||
</Route>
|
||||
<Route exact path="/artist/:id/about">
|
||||
}/>
|
||||
<Route path="about" element={
|
||||
<About artist={artist} />
|
||||
</Route>
|
||||
<Route exact path="/artist/:id/:name?">
|
||||
}/>
|
||||
<Route path="" element={
|
||||
<Overview artist={artist} albums={albums} />
|
||||
</Route>
|
||||
</Switch>
|
||||
}/>
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Grid } from '../../components/Grid';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import TrackList from '../../components/TrackList';
|
||||
import FilterField from '../../components/Fields/FilterField';
|
||||
@ -16,11 +15,7 @@ import { formatContext } from '../../util/format';
|
||||
const SORT_KEY = 'artist_tracks';
|
||||
|
||||
export default ({ artist }) => {
|
||||
const {
|
||||
uri,
|
||||
name,
|
||||
tracks: tracksProp,
|
||||
} = artist || {};
|
||||
const { tracks: tracksProp } = artist || {};
|
||||
const dispatch = useDispatch();
|
||||
const [search, setSearch] = useState('');
|
||||
const [sortField, sortReverse] = useSelector(
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import Recommendations from './Recommendations';
|
||||
import FeaturedPlaylists from './FeaturedPlaylists';
|
||||
import NewReleases from './NewReleases';
|
||||
@ -8,41 +8,50 @@ import PlaylistGroup from './PlaylistGroup';
|
||||
import Playlist from '../Playlist';
|
||||
|
||||
export default () => (
|
||||
<Switch>
|
||||
<Routes>
|
||||
<Route
|
||||
exact
|
||||
path="/discover/recommendations/:uri?"
|
||||
component={Recommendations}
|
||||
path="recommendations/"
|
||||
element={<Recommendations />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/discover/moods"
|
||||
component={Moods}
|
||||
path="recommendations/:uri"
|
||||
element={<Recommendations />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/discover/moods/:uri/:name?"
|
||||
component={PlaylistGroup}
|
||||
path="moods"
|
||||
element={<Moods />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/discover/featured-playlists"
|
||||
component={FeaturedPlaylists}
|
||||
path="moods/:uri/"
|
||||
element={<PlaylistGroup />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/discover/featured-playlists/playlist/:uri/:name?"
|
||||
component={Playlist}
|
||||
path="moods/:uri/:name"
|
||||
element={<PlaylistGroup />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/discover/featured-playlists/playlist_group/:uri/:name?"
|
||||
component={PlaylistGroup}
|
||||
path="featured-playlists"
|
||||
element={<FeaturedPlaylists />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/discover/new-releases"
|
||||
component={NewReleases}
|
||||
path="featured-playlists/playlist/:uri/"
|
||||
element={<Playlist />}
|
||||
/>
|
||||
</Switch>
|
||||
<Route
|
||||
path="featured-playlists/playlist/:uri/:name/"
|
||||
element={<Playlist />}
|
||||
/>
|
||||
<Route
|
||||
path="featured-playlists/playlist_group/:uri/"
|
||||
element={<PlaylistGroup />}
|
||||
/>
|
||||
<Route
|
||||
path="featured-playlists/playlist_group/:uri/:name"
|
||||
element={<PlaylistGroup />}
|
||||
/>
|
||||
<Route
|
||||
path="new-releases/"
|
||||
element={<NewReleases />}
|
||||
/>
|
||||
</Routes>
|
||||
);
|
||||
|
||||
@ -16,7 +16,7 @@ import {
|
||||
makeItemSelector,
|
||||
makeLoadingSelector,
|
||||
} from '../../util/selectors';
|
||||
import { formatSimpleObject, formatContext } from '../../util/format';
|
||||
import { formatContext } from '../../util/format';
|
||||
|
||||
class NewReleases extends React.Component {
|
||||
componentDidMount() {
|
||||
|
||||
@ -86,11 +86,11 @@ class Browse extends React.Component {
|
||||
case 'SomaFM':
|
||||
subdirectory.icons = ['/iris/assets/backgrounds/browse-somafm.jpg'];
|
||||
break;
|
||||
|
||||
case 'Jellyfin':
|
||||
|
||||
case 'Jellyfin':
|
||||
subdirectory.icons = ['/iris/assets/backgrounds/browse-jellyfin.jpg'];
|
||||
break;
|
||||
|
||||
|
||||
case 'Podcasts':
|
||||
subdirectory.icons = ['/iris/assets/backgrounds/browse-podcasts.jpg'];
|
||||
break;
|
||||
@ -109,8 +109,17 @@ class Browse extends React.Component {
|
||||
subdirectory.icons = ['/iris/assets/backgrounds/browse-youtube.jpg'];
|
||||
break;
|
||||
|
||||
default:
|
||||
case 'bandcamp':
|
||||
case 'Bandcamp':
|
||||
subdirectory.icons = ['/iris/assets/backgrounds/browse-default.jpg'];
|
||||
break;
|
||||
|
||||
default:
|
||||
if (subdirectory.images?.medium) {
|
||||
subdirectory.icons = [subdirectory.images?.medium];
|
||||
} else {
|
||||
subdirectory.icons = ['/iris/assets/backgrounds/browse-default.jpg'];
|
||||
}
|
||||
}
|
||||
|
||||
grid_items.push({
|
||||
|
||||
@ -10,7 +10,6 @@ import DropdownField from '../../components/Fields/DropdownField';
|
||||
import FilterField from '../../components/Fields/FilterField';
|
||||
import Icon from '../../components/Icon';
|
||||
import ErrorBoundary from '../../components/ErrorBoundary';
|
||||
import LazyLoadListener from '../../components/LazyLoadListener';
|
||||
import * as uiActions from '../../services/ui/actions';
|
||||
import * as mopidyActions from '../../services/mopidy/actions';
|
||||
import * as spotifyActions from '../../services/spotify/actions';
|
||||
@ -20,6 +19,7 @@ import Button from '../../components/Button';
|
||||
import { encodeUri, decodeUri } from '../../util/format';
|
||||
import { makeLoadingSelector } from '../../util/selectors';
|
||||
import ErrorMessage from '../../components/ErrorMessage';
|
||||
import { withRouter } from '../../util';
|
||||
|
||||
const Breadcrumbs = ({ uri }) => {
|
||||
let parent_uri = uri || null;
|
||||
@ -258,7 +258,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
library_directory_view: view,
|
||||
},
|
||||
} = state;
|
||||
const uri = decodeUri(ownProps.match.params.uri);
|
||||
const uri = decodeUri(ownProps.params.uri);
|
||||
const uriMatcher = [uri, decodeURIComponent(uri)]; // Lenient matching due to encoding diffs
|
||||
const directory = _directory && uriMatcher.includes(_directory.uri)
|
||||
? _directory
|
||||
@ -266,7 +266,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
|
||||
return {
|
||||
uri,
|
||||
name: decodeURIComponent(ownProps.match.params.name),
|
||||
name: decodeURIComponent(ownProps.params.name),
|
||||
loading: loadingSelector(state),
|
||||
directory,
|
||||
view,
|
||||
@ -279,4 +279,4 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
spotifyActions: bindActionCreators(spotifyActions, dispatch),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(BrowseDirectory);
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(BrowseDirectory));
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import Artists from './Artists';
|
||||
import Albums from './Albums';
|
||||
import Tracks from './Tracks';
|
||||
@ -8,36 +8,30 @@ import Browse from './Browse';
|
||||
import BrowseDirectory from './BrowseDirectory';
|
||||
|
||||
export default () => (
|
||||
<Switch>
|
||||
<Routes>
|
||||
<Route
|
||||
exact
|
||||
path="/library/artists"
|
||||
component={Artists}
|
||||
path="artists/"
|
||||
element={<Artists />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/library/albums"
|
||||
component={Albums}
|
||||
path="albums/"
|
||||
element={<Albums />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/library/tracks"
|
||||
component={Tracks}
|
||||
path="tracks/"
|
||||
element={<Tracks />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/library/playlists"
|
||||
component={Playlists}
|
||||
path="playlists/"
|
||||
element={<Playlists />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/library/browse"
|
||||
component={Browse}
|
||||
path="browse/"
|
||||
element={<Browse />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/library/browse/:name/:uri"
|
||||
component={BrowseDirectory}
|
||||
path="browse/:name/:uri"
|
||||
element={<BrowseDirectory />}
|
||||
/>
|
||||
</Switch>
|
||||
</Routes>
|
||||
);
|
||||
|
||||
@ -18,6 +18,7 @@ import {
|
||||
makeProvidersSelector,
|
||||
makeLibrarySelector,
|
||||
} from '../../util/selectors';
|
||||
import { withRouter } from '../../util';
|
||||
|
||||
const processKeys = [
|
||||
'MOPIDY_GET_LIBRARY_PLAYLISTS',
|
||||
@ -136,7 +137,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
},
|
||||
} = state;
|
||||
|
||||
const unencodedUris = ownProps.match.params?.uris;
|
||||
const unencodedUris = ownProps.params?.uris;
|
||||
const uris = unencodedUris ? decodeUri(unencodedUris).split(',') : [];
|
||||
|
||||
return {
|
||||
@ -156,4 +157,4 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
spotifyActions: bindActionCreators(spotifyActions, dispatch),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AddToPlaylist);
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(AddToPlaylist));
|
||||
|
||||
@ -10,6 +10,7 @@ import * as uiActions from '../../services/ui/actions';
|
||||
import { scrollTo, generateGuid } from '../../util/helpers';
|
||||
import { i18n, I18n } from '../../locale';
|
||||
import Button from '../../components/Button';
|
||||
import { withRouter } from '../../util';
|
||||
|
||||
class EditCommand extends React.Component {
|
||||
constructor(props) {
|
||||
@ -264,7 +265,7 @@ class EditCommand extends React.Component {
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const { id } = ownProps.match.params;
|
||||
const { id } = ownProps.params;
|
||||
return {
|
||||
id,
|
||||
command: (id && state.pusher.commands && state.pusher.commands[id] !== undefined ? state.pusher.commands[id] : null),
|
||||
@ -276,4 +277,4 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
uiActions: bindActionCreators(uiActions, dispatch),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(EditCommand);
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(EditCommand));
|
||||
|
||||
@ -3,6 +3,7 @@ import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import Modal from './Modal';
|
||||
import * as uiActions from '../../services/ui/actions';
|
||||
import { withRouter } from '../../util';
|
||||
|
||||
class ImageZoom extends React.Component {
|
||||
componentDidMount() {
|
||||
@ -24,4 +25,4 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
uiActions: bindActionCreators(uiActions, dispatch),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ImageZoom);
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(ImageZoom));
|
||||
|
||||
@ -8,6 +8,7 @@ import * as pusherActions from '../../services/pusher/actions';
|
||||
import * as mopidyActions from '../../services/mopidy/actions';
|
||||
import { queryString, isHosted } from '../../util/helpers';
|
||||
import { I18n, i18n } from '../../locale';
|
||||
import { withRouter } from '../../util';
|
||||
import Button from '../../components/Button';
|
||||
|
||||
class InitialSetup extends React.Component {
|
||||
@ -69,14 +70,11 @@ 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 = '/';
|
||||
|
||||
// Safe to just close modal
|
||||
} else {
|
||||
self.props.history.push('/');
|
||||
self.props.navigate('/');
|
||||
}
|
||||
},
|
||||
200);
|
||||
@ -196,4 +194,4 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
mopidyActions: bindActionCreators(mopidyActions, dispatch),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(InitialSetup);
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(InitialSetup));
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import EditPlaylist from './EditPlaylist';
|
||||
import CreatePlaylist from './CreatePlaylist';
|
||||
import EditRadio from './EditRadio';
|
||||
@ -16,20 +16,23 @@ import Reset from './Reset';
|
||||
import Servers from './Servers';
|
||||
|
||||
export default () => (
|
||||
<Switch>
|
||||
<Route path="/modal/initial-setup" component={InitialSetup} />
|
||||
<Route path="/modal/kiosk-mode" component={KioskMode} />
|
||||
<Route path="/modal/add-to-playlist/:uris" component={AddToPlaylist} />
|
||||
<Route path="/modal/image-zoom" component={ImageZoom} />
|
||||
<Route path="/modal/hotkeys" component={HotkeysInfo} />
|
||||
<Route path="/modal/share-config" component={ShareConfig} />
|
||||
<Route path="/modal/import-config/:source?" component={ImportConfig} />
|
||||
<Route path="/modal/reset" component={Reset} />
|
||||
<Route path="/modal/servers" component={Servers} />
|
||||
<Route path="/modal/edit-command/:id?" component={EditCommand} />
|
||||
<Route path="/modal/radio" component={EditRadio} />
|
||||
<Route path="/modal/add-uri" component={AddToQueue} />
|
||||
<Route path="/modal/create-playlist/:uris?" component={CreatePlaylist} />
|
||||
<Route path="/modal/edit-playlist/:uri" component={EditPlaylist} />
|
||||
</Switch>
|
||||
<Routes>
|
||||
<Route path="initial-setup" element={<InitialSetup />} />
|
||||
<Route path="kiosk-mode" element={<KioskMode />} />
|
||||
<Route path="add-to-playlist/:uris" element={<AddToPlaylist />} />
|
||||
<Route path="image-zoom" element={<ImageZoom />} />
|
||||
<Route path="hotkeys" element={<HotkeysInfo />} />
|
||||
<Route path="share-config" element={<ShareConfig />} />
|
||||
<Route path="import-config/" element={<ImportConfig />} />
|
||||
<Route path="import-config/:source" element={<ImportConfig />} />
|
||||
<Route path="reset" element={<Reset />} />
|
||||
<Route path="servers" element={<Servers />} />
|
||||
<Route path="edit-command/" element={<EditCommand />} />
|
||||
<Route path="edit-command/:id" element={<EditCommand />} />
|
||||
<Route path="radio" element={<EditRadio />} />
|
||||
<Route path="add-uri" element={<AddToQueue />} />
|
||||
<Route path="create-playlist/" element={<CreatePlaylist />} />
|
||||
<Route path="create-playlist/:uris" element={<CreatePlaylist />} />
|
||||
<Route path="edit-playlist/:uri" element={<EditPlaylist />} />
|
||||
</Routes>
|
||||
);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import Thumbnail from '../../components/Thumbnail';
|
||||
import LinksSentence from '../../components/LinksSentence';
|
||||
@ -10,7 +10,7 @@ import { i18n, I18n } from '../../locale';
|
||||
import { indexToArray } from '../../util/arrays';
|
||||
|
||||
const Servers = () => {
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const servers = indexToArray(useSelector((state) => state.mopidy.servers || {}));
|
||||
const current_server = useSelector((state) => state.mopidy.current_server);
|
||||
@ -24,7 +24,7 @@ const Servers = () => {
|
||||
|
||||
const onClick = (server) => {
|
||||
dispatch(mopidyActions.setCurrentServer(server));
|
||||
history.push('/queue');
|
||||
navigate('/queue');
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useParams, useHistory } from 'react-router-dom';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import ErrorMessage from '../components/ErrorMessage';
|
||||
@ -136,7 +136,7 @@ const Playlist = ({
|
||||
playPlaylist,
|
||||
},
|
||||
}) => {
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
const { uri: encodedUri, name } = useParams();
|
||||
const uri = decodeUri(encodedUri);
|
||||
const [filter, setFilter] = useState('');
|
||||
@ -156,7 +156,7 @@ const Playlist = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (playlist && playlist.moved_to) {
|
||||
history.push(`/playlist/${encodeUri(playlist.moved_to)}/${encodeURIComponent(playlist.name.replace('%', ''))}`);
|
||||
navigate(`/playlist/${encodeUri(playlist.moved_to)}/${encodeURIComponent(playlist.name.replace('%', ''))}`);
|
||||
}
|
||||
}, [playlist]);
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { Switch, Route } from 'react-router-dom';
|
||||
import Header from '../components/Header';
|
||||
import Icon from '../components/Icon';
|
||||
import DropdownField from '../components/Fields/DropdownField';
|
||||
@ -12,16 +11,13 @@ import * as uiActions from '../services/ui/actions';
|
||||
import * as mopidyActions from '../services/mopidy/actions';
|
||||
import * as spotifyActions from '../services/spotify/actions';
|
||||
import { titleCase } from '../util/helpers';
|
||||
import { withRouter } from '../util';
|
||||
import { i18n } from '../locale';
|
||||
|
||||
class Search extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
type: props.type || 'all',
|
||||
term: props.term || '',
|
||||
};
|
||||
this.state = { term: props.term || '' };
|
||||
}
|
||||
|
||||
componentDidMount = () => {
|
||||
@ -38,37 +34,28 @@ class Search extends React.Component {
|
||||
this.digestUri();
|
||||
}
|
||||
|
||||
componentDidUpdate = ({
|
||||
type: prevType,
|
||||
term: prevTerm,
|
||||
}) => {
|
||||
const {
|
||||
type: typeProp,
|
||||
term: termProp,
|
||||
} = this.props;
|
||||
const { type, term } = this.state;
|
||||
|
||||
if (prevType !== typeProp || prevTerm !== termProp) {
|
||||
this.search(type, term);
|
||||
componentDidUpdate = ({ term: prevTerm }) => {
|
||||
const { term: termProp } = this.props;
|
||||
if (prevTerm !== termProp) {
|
||||
this.search();
|
||||
}
|
||||
}
|
||||
|
||||
onSubmit = (term) => {
|
||||
const { type } = this.state;
|
||||
const { history } = this.props;
|
||||
const { navigate, type } = this.props;
|
||||
const encodedTerm = encodeURIComponent(term);
|
||||
|
||||
this.setState(
|
||||
{ term },
|
||||
() => {
|
||||
history.push(`/search/${type}/${encodedTerm}`);
|
||||
navigate(`/search/${type}/${encodedTerm}`);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
onReset = () => {
|
||||
const { history } = this.props;
|
||||
history.push('/search');
|
||||
const { navigate } = this.props;
|
||||
navigate('/search');
|
||||
}
|
||||
|
||||
onSortChange = (value) => {
|
||||
@ -93,16 +80,10 @@ class Search extends React.Component {
|
||||
};
|
||||
|
||||
digestUri = () => {
|
||||
const {
|
||||
type,
|
||||
term,
|
||||
} = this.props;
|
||||
|
||||
if (type && term) {
|
||||
this.setState({ type, term }, () => {
|
||||
this.search();
|
||||
});
|
||||
} else if (!term || term === '') {
|
||||
const { term } = this.props;
|
||||
if (term) {
|
||||
this.setState({ term }, this.search);
|
||||
} else {
|
||||
this.clearSearch();
|
||||
}
|
||||
}
|
||||
@ -130,11 +111,9 @@ class Search extends React.Component {
|
||||
type: existingType,
|
||||
term: existingTerm,
|
||||
},
|
||||
} = this.props;
|
||||
const {
|
||||
type,
|
||||
term,
|
||||
} = this.state;
|
||||
} = this.props;
|
||||
const { term } = this.state;
|
||||
|
||||
setWindowTitle(i18n('search.title_window', { term: decodeURIComponent(term) }));
|
||||
|
||||
@ -163,17 +142,14 @@ class Search extends React.Component {
|
||||
}
|
||||
|
||||
render = () => {
|
||||
const {
|
||||
term,
|
||||
type,
|
||||
} = this.state;
|
||||
const { term } = this.state;
|
||||
const {
|
||||
uri_schemes,
|
||||
sort,
|
||||
sort_reverse,
|
||||
history,
|
||||
uri_schemes_search_enabled,
|
||||
uiActions,
|
||||
type,
|
||||
} = this.props;
|
||||
|
||||
const sort_options = [
|
||||
@ -211,6 +187,46 @@ class Search extends React.Component {
|
||||
</>
|
||||
);
|
||||
|
||||
let searchResults;
|
||||
|
||||
switch (type) {
|
||||
case 'artists':
|
||||
searchResults = <SearchResults type="artists" query={{ term, type: 'artists' }} />;
|
||||
break;
|
||||
case 'albums':
|
||||
searchResults = <SearchResults type="albums" query={{ term, type: 'albums' }} />;
|
||||
break;
|
||||
case 'playlists':
|
||||
searchResults = <SearchResults type="playlists" query={{ term, type: 'playlists' }} />;
|
||||
break;
|
||||
case 'tracks':
|
||||
searchResults = <SearchResults type="tracks" query={{ term, type: 'tracks' }} />
|
||||
break;
|
||||
default:
|
||||
searchResults = (
|
||||
<>
|
||||
<div className="search-result-sections cf">
|
||||
<section className="search-result-sections__item">
|
||||
<div className="inner">
|
||||
<SearchResults type="artists" query={{ term, type: 'artists' }} all />
|
||||
</div>
|
||||
</section>
|
||||
<section className="search-result-sections__item">
|
||||
<div className="inner">
|
||||
<SearchResults type="albums" query={{ term, type: 'albums' }} all />
|
||||
</div>
|
||||
</section>
|
||||
<section className="search-result-sections__item">
|
||||
<div className="inner">
|
||||
<SearchResults type="playlists" query={{ term, type: 'playlists' }} all />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<SearchResults type="tracks" query={{ term, type: 'tracks' }} all />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="view search-view">
|
||||
<Header options={options} uiActions={uiActions}>
|
||||
@ -219,53 +235,13 @@ class Search extends React.Component {
|
||||
|
||||
<SearchForm
|
||||
key={`search_form_${type}_${term}`}
|
||||
history={history}
|
||||
term={term}
|
||||
onSubmit={this.onSubmit}
|
||||
onReset={this.onReset}
|
||||
/>
|
||||
|
||||
<div className="content-wrapper">
|
||||
<Switch>
|
||||
|
||||
<Route path="/search/artists/:term">
|
||||
<SearchResults type="artists" query={{ term, type }} />
|
||||
</Route>
|
||||
|
||||
<Route path="/search/albums/:term">
|
||||
<SearchResults type="albums" query={{ term, type }} />
|
||||
</Route>
|
||||
|
||||
<Route path="/search/playlists/:term">
|
||||
<SearchResults type="playlists" query={{ term, type }} />
|
||||
</Route>
|
||||
|
||||
<Route path="/search/tracks/:term">
|
||||
<SearchResults type="tracks" query={{ term, type }} />
|
||||
</Route>
|
||||
|
||||
<Route path="/search">
|
||||
<div className="search-result-sections cf">
|
||||
<section className="search-result-sections__item">
|
||||
<div className="inner">
|
||||
<SearchResults type="artists" query={{ term, type }} all />
|
||||
</div>
|
||||
</section>
|
||||
<section className="search-result-sections__item">
|
||||
<div className="inner">
|
||||
<SearchResults type="albums" query={{ term, type }} all />
|
||||
</div>
|
||||
</section>
|
||||
<section className="search-result-sections__item">
|
||||
<div className="inner">
|
||||
<SearchResults type="playlists" query={{ term, type }} all />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<SearchResults type="tracks" query={{ term, type }} all />
|
||||
</Route>
|
||||
|
||||
</Switch>
|
||||
{searchResults}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -274,13 +250,13 @@ class Search extends React.Component {
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const {
|
||||
match: {
|
||||
params: {
|
||||
type,
|
||||
term,
|
||||
},
|
||||
params: {
|
||||
type,
|
||||
term,
|
||||
},
|
||||
navigation,
|
||||
} = ownProps;
|
||||
|
||||
const {
|
||||
mopidy: {
|
||||
uri_schemes = [],
|
||||
@ -298,8 +274,9 @@ const mapStateToProps = (state, ownProps) => {
|
||||
} = state;
|
||||
|
||||
return {
|
||||
type,
|
||||
type: type || 'all',
|
||||
term,
|
||||
navigation,
|
||||
uri_schemes,
|
||||
uri_schemes_search_enabled,
|
||||
sort,
|
||||
@ -315,4 +292,4 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
spotifyActions: bindActionCreators(spotifyActions, dispatch),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Search);
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Search));
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
import React from 'react';
|
||||
import { connect, useDispatch } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { Route, Link } from 'react-router-dom';
|
||||
import localForage from 'localforage';
|
||||
import ConfirmationButton from '../components/Fields/ConfirmationButton';
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
import PusherConnectionList from '../components/PusherConnectionList';
|
||||
import SourcesPriority from '../components/Fields/SourcesPriority';
|
||||
import Commands from '../components/Fields/Commands';
|
||||
@ -205,14 +203,19 @@ class Settings extends React.Component {
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<Route path="/settings/:server?/:id?" component={Servers} />
|
||||
|
||||
<Routes>
|
||||
<Route path=":server/*" element={<Servers />} />
|
||||
</Routes>
|
||||
<h4 className="underline">
|
||||
<I18n path="settings.services.title" />
|
||||
<a name="services" />
|
||||
</h4>
|
||||
|
||||
<Route path="/settings/:services?/:service?/:id?" component={Services} />
|
||||
<Routes>
|
||||
<Route path="" element={<Services />} />
|
||||
<Route path=":services/" element={<Services />} />
|
||||
<Route path=":services/:service/" element={<Services />} />
|
||||
<Route path=":services/:service/:id" element={<Services />} />
|
||||
</Routes>
|
||||
|
||||
<h4 className="underline">
|
||||
<I18n path="settings.interface.title" />
|
||||
|
||||
Reference in New Issue
Block a user