Refactoring App to functional component, to run pre-ready actions (preconfiguration, etc)
This commit is contained in:
@ -79,8 +79,6 @@
|
||||
height: 1rem;
|
||||
position: relative;
|
||||
width: 100%; }
|
||||
|
||||
/*# sourceMappingURL=index.css.map */
|
||||
@charset "UTF-8";
|
||||
/*!
|
||||
* Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
30
mopidy_iris/static/app.min.js
vendored
30
mopidy_iris/static/app.min.js
vendored
File diff suppressed because one or more lines are too long
@ -64,7 +64,7 @@
|
||||
max-width: 100px;
|
||||
opacity: 0.25;
|
||||
}
|
||||
#app-loading-loader {
|
||||
#app-loading-loader.loading {
|
||||
position: relative;
|
||||
margin: 30px auto 0;
|
||||
width: 80vw;
|
||||
@ -74,7 +74,7 @@
|
||||
overflow: hidden;
|
||||
background-color: #1A1A1A;
|
||||
}
|
||||
#app-loading-loader::before {
|
||||
#app-loading-loader.loading::before {
|
||||
position: absolute;
|
||||
display: block;
|
||||
content: '';
|
||||
@ -85,6 +85,13 @@
|
||||
animation-timing-function: ease-in-out;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
#app-loading-message {
|
||||
color: white;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 12px;
|
||||
padding: 10px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
@ -94,7 +101,12 @@
|
||||
<!-- ReactJS app gets injected here, replacing the loader -->
|
||||
<div id="app-loading">
|
||||
<img src="/iris/assets/app-icon.svg" id="app-loading-logo" />
|
||||
<div id="app-loading-loader"></div>
|
||||
<div id="app-loading-loader" class="loading"></div>
|
||||
<div id="app-loading-message">
|
||||
<div id="app-loading-message-base"></div>
|
||||
<div id="app-loading-message-js"></div>
|
||||
<div id="app-loading-message-css"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -112,10 +124,22 @@
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
var setMessage = function(type = '', content = '') {
|
||||
var element = document.getElementById(`app-loading-message-${type}`);
|
||||
if (element) {
|
||||
element.innerHTML += content;
|
||||
}
|
||||
}
|
||||
var setLoading = function(loading) {
|
||||
var element = document.getElementById('app-loading-loader');
|
||||
if (element) {
|
||||
element.className = loading ? 'loading' : '';
|
||||
}
|
||||
}
|
||||
|
||||
// Release details
|
||||
// These are automatically injected to built HTML
|
||||
var build = "1635321372";
|
||||
var build = "1635473511";
|
||||
var version = "3.60.1";
|
||||
|
||||
// Construct the script tag
|
||||
@ -137,19 +161,35 @@
|
||||
} else {
|
||||
window.test_mode = localStorage.getItem('test_mode');
|
||||
}
|
||||
if (window.test_mode) {
|
||||
setMessage('base', 'Test mode detected<br />');
|
||||
}
|
||||
|
||||
// Test mode, use un-minified code
|
||||
if (window.test_mode){
|
||||
console.debug("Test mode enabled, using un-minified code");
|
||||
if (window.test_mode){ // Test mode, use un-minified code
|
||||
css.href = '/iris/app.css?v='+build;
|
||||
js.src = '/iris/app.js?v='+build;
|
||||
|
||||
// Production-grade, minified app
|
||||
} else {
|
||||
} else { // Production-grade, minified app
|
||||
css.href = '/iris/app.min.css?v='+build;
|
||||
js.src = '/iris/app.min.js?v='+build;
|
||||
}
|
||||
|
||||
setMessage('js', 'Loading core... ');
|
||||
setMessage('css', 'Loading interface... ');
|
||||
js.onload = function(){
|
||||
setMessage('js', 'done');
|
||||
}
|
||||
js.onerror = function(){
|
||||
setLoading(false);
|
||||
setMessage('js', 'failed');
|
||||
}
|
||||
css.onload = function(){
|
||||
setMessage('css', 'done');
|
||||
}
|
||||
css.onerror = function(){
|
||||
setLoading(false);
|
||||
setMessage('css', 'failed');
|
||||
}
|
||||
|
||||
// And finally inject our CSS/JS tags
|
||||
document.body.appendChild(css);
|
||||
document.body.appendChild(js);
|
||||
|
||||
@ -101,8 +101,12 @@
|
||||
<!-- ReactJS app gets injected here, replacing the loader -->
|
||||
<div id="app-loading">
|
||||
<img src="/iris/assets/app-icon.svg" id="app-loading-logo" />
|
||||
<div id="app-loading-loader"></div>
|
||||
<div id="app-loading-message"></div>
|
||||
<div id="app-loading-loader" class="loading"></div>
|
||||
<div id="app-loading-message">
|
||||
<div id="app-loading-message-base"></div>
|
||||
<div id="app-loading-message-js"></div>
|
||||
<div id="app-loading-message-css"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -120,17 +124,17 @@
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
var setMessage = function(message, append = false) {
|
||||
var element = document.getElementById('app-loading-message');
|
||||
if (append) {
|
||||
element.innerHTML += message;
|
||||
} else {
|
||||
element.innerHTML = message;
|
||||
var setMessage = function(type = '', content = '') {
|
||||
var element = document.getElementById(`app-loading-message-${type}`);
|
||||
if (element) {
|
||||
element.innerHTML += content;
|
||||
}
|
||||
}
|
||||
var setLoading = function(loading) {
|
||||
var element = document.getElementById('app-loading-loader');
|
||||
element.className = loading ? 'loading' : '';
|
||||
if (element) {
|
||||
element.className = loading ? 'loading' : '';
|
||||
}
|
||||
}
|
||||
|
||||
// Release details
|
||||
@ -158,34 +162,32 @@
|
||||
window.test_mode = localStorage.getItem('test_mode');
|
||||
}
|
||||
if (window.test_mode) {
|
||||
setMessage('Loading (TEST MODE)');
|
||||
} else {
|
||||
setMessage('Loading');
|
||||
}
|
||||
setMessage('base', 'Test mode detected<br />');
|
||||
}
|
||||
|
||||
// Test mode, use un-minified code
|
||||
if (window.test_mode){
|
||||
console.debug("Test mode enabled, using un-minified code");
|
||||
if (window.test_mode){ // Test mode, use un-minified code
|
||||
css.href = '/iris/app.css?v='+build;
|
||||
js.src = '/iris/app.js?v='+build;
|
||||
|
||||
// Production-grade, minified app
|
||||
} else {
|
||||
} else { // Production-grade, minified app
|
||||
css.href = '/iris/app.min.css?v='+build;
|
||||
js.src = '/iris/app.min.js?v='+build;
|
||||
}
|
||||
|
||||
setMessage('js', 'Loading core... ');
|
||||
setMessage('css', 'Loading interface... ');
|
||||
js.onload = function(){
|
||||
setMessage('Initializing...');
|
||||
setMessage('js', 'done');
|
||||
}
|
||||
js.onerror = function(){
|
||||
setMessage('<br />Could not load javascript', true);
|
||||
setLoading(false);
|
||||
setMessage('js', 'failed');
|
||||
}
|
||||
css.onload = function(){
|
||||
setMessage('Initializing...');
|
||||
setMessage('css', 'done');
|
||||
}
|
||||
css.onerror = function(){
|
||||
setMessage('<br />Could not load CSS', true);
|
||||
setLoading(false);
|
||||
setMessage('css', 'failed');
|
||||
}
|
||||
|
||||
// And finally inject our CSS/JS tags
|
||||
|
||||
612
src/js/App.js
612
src/js/App.js
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import { connect } from 'react-redux';
|
||||
@ -67,48 +67,46 @@ import * as snapcastActions from './services/snapcast/actions';
|
||||
import MediaSession from './components/MediaSession';
|
||||
import ErrorBoundary from './components/ErrorBoundary';
|
||||
|
||||
export class App extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { allow_reporting } = this.props;
|
||||
this.state = {
|
||||
userHasInteracted: false,
|
||||
};
|
||||
const App = ({
|
||||
language,
|
||||
allow_reporting,
|
||||
initial_setup_complete,
|
||||
snapcast_enabled,
|
||||
context_menu,
|
||||
history,
|
||||
location,
|
||||
debug_info,
|
||||
theme,
|
||||
dragging,
|
||||
touch_dragging,
|
||||
sidebar_open,
|
||||
slim_mode,
|
||||
wide_scrollbar_enabled,
|
||||
hide_scrollbars,
|
||||
smooth_scrolling_enabled,
|
||||
hotkeys_enabled,
|
||||
...props
|
||||
}) => {
|
||||
const {
|
||||
coreActions,
|
||||
mopidyActions,
|
||||
pusherActions,
|
||||
uiActions,
|
||||
} = props;
|
||||
const {
|
||||
pathname,
|
||||
state: {
|
||||
scroll_position,
|
||||
} = {},
|
||||
} = location;
|
||||
|
||||
if (allow_reporting) {
|
||||
ReactGA.initialize('UA-64701652-3');
|
||||
Sentry.init({
|
||||
dsn: 'https://ca99fb6662fe40ae8ec4c18a466e4b4b@o99789.ingest.sentry.io/219026',
|
||||
sampleRate: 0.25,
|
||||
beforeSend: (event, hint) => {
|
||||
const {
|
||||
originalException: {
|
||||
message,
|
||||
} = {},
|
||||
} = hint;
|
||||
window.language = language;
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
const [hasInteracted, setHasInteracted] = useState(false);
|
||||
|
||||
// Filter out issues that destroy our quota and that are not informative enough to
|
||||
// actually resolve.
|
||||
if (
|
||||
message
|
||||
&& (
|
||||
message.match(/Websocket/i)
|
||||
|| message.match(/NotSupportedError/i)
|
||||
|| message.match(/NotSupportedError: The element has no supported sources./i)
|
||||
|| message.match(/Non-Error promise rejection captured with keys: call, message, value/i)
|
||||
|| message.match(/Cannot read property 'addChunk' of undefined/i)
|
||||
)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return event;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Accept incoming preconfiguration via URL parameters and inject into application state.
|
||||
// For example: iris?snapcast={"enabled":true,"host":"myserver.local"}
|
||||
// Accept incoming preconfiguration via URL parameters and inject into application state.
|
||||
// For example: iris?snapcast={"enabled":true,"host":"myserver.local"}
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
if (params) {
|
||||
params.forEach((value, key) => {
|
||||
@ -120,7 +118,7 @@ export class App extends React.Component {
|
||||
case 'pusher':
|
||||
case 'snapcast':
|
||||
case 'mopidy':
|
||||
this.props[`${key}Actions`].set(json);
|
||||
props[`${key}Actions`].set(json);
|
||||
console.info(`Applying preconfiguration for ${key}:`, value)
|
||||
break;
|
||||
default:
|
||||
@ -131,91 +129,96 @@ export class App extends React.Component {
|
||||
return;
|
||||
}
|
||||
});
|
||||
// Wait a moment to allow actions to complete before we proceed
|
||||
setTimeout(
|
||||
() => setIsReady(true),
|
||||
250,
|
||||
);
|
||||
} else {
|
||||
setIsReady(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
this.handleInstallPrompt = this.handleInstallPrompt.bind(this);
|
||||
this.handleFocusAndBlur = this.handleFocusAndBlur.bind(this);
|
||||
window.language = props.language;
|
||||
}
|
||||
// Event listeners
|
||||
useEffect(() => {
|
||||
window.addEventListener('beforeinstallprompt', handleInstallPrompt, false);
|
||||
window.addEventListener('focus', handleFocusAndBlur, false);
|
||||
window.addEventListener('blur', handleFocusAndBlur, false);
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
history,
|
||||
snapcast_enabled,
|
||||
initial_setup_complete,
|
||||
mopidyActions,
|
||||
pusherActions,
|
||||
snapcastActions,
|
||||
} = this.props;
|
||||
|
||||
window.addEventListener(
|
||||
'beforeinstallprompt',
|
||||
this.handleInstallPrompt,
|
||||
false,
|
||||
);
|
||||
window.addEventListener('focus', this.handleFocusAndBlur, false);
|
||||
window.addEventListener('blur', this.handleFocusAndBlur, false);
|
||||
|
||||
// Fire up our services
|
||||
mopidyActions.connect();
|
||||
pusherActions.connect();
|
||||
if (snapcast_enabled) {
|
||||
snapcastActions.connect();
|
||||
return () => {
|
||||
window.removeEventListener('beforeinstallprompt', handleInstallPrompt, false);
|
||||
window.removeEventListener('focus', handleFocusAndBlur, false);
|
||||
window.removeEventListener('blur', handleFocusAndBlur, false);
|
||||
}
|
||||
uiActions.getBroadcasts();
|
||||
}, []);
|
||||
|
||||
if (!initial_setup_complete) {
|
||||
history.push('/initial-setup');
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate({
|
||||
location: {
|
||||
pathname: prevPathname,
|
||||
},
|
||||
}) {
|
||||
const {
|
||||
location: {
|
||||
pathname,
|
||||
state: {
|
||||
scroll_position,
|
||||
} = {},
|
||||
} = {},
|
||||
allow_reporting,
|
||||
uiActions,
|
||||
context_menu,
|
||||
} = this.props;
|
||||
|
||||
// When we have navigated to a new route
|
||||
if (pathname !== prevPathname) {
|
||||
// Log our pageview
|
||||
// Primary engines
|
||||
useEffect(() => {
|
||||
if (isReady) {
|
||||
if (allow_reporting) {
|
||||
ReactGA.set({ page: pathname });
|
||||
ReactGA.pageview(pathname);
|
||||
ReactGA.initialize('UA-64701652-3');
|
||||
Sentry.init({
|
||||
dsn: 'https://ca99fb6662fe40ae8ec4c18a466e4b4b@o99789.ingest.sentry.io/219026',
|
||||
sampleRate: 0.25,
|
||||
beforeSend: (event, hint) => {
|
||||
const {
|
||||
originalException: {
|
||||
message,
|
||||
} = {},
|
||||
} = hint;
|
||||
|
||||
// Filter out issues that destroy our quota and that are not informative enough to
|
||||
// actually resolve.
|
||||
if (
|
||||
message
|
||||
&& (
|
||||
message.match(/Websocket/i)
|
||||
|| message.match(/NotSupportedError/i)
|
||||
|| message.match(/NotSupportedError: The element has no supported sources./i)
|
||||
|| message.match(/Non-Error promise rejection captured with keys: call, message, value/i)
|
||||
|| message.match(/Cannot read property 'addChunk' of undefined/i)
|
||||
)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return event;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// If the location has a "scroll_position" state variable, scroll to it.
|
||||
// This is invisibly injected to the history by the Link component when navigating, so
|
||||
// hitting back in the browser allows us to restore the position
|
||||
if (scroll_position) {
|
||||
scrollTo(parseInt(scroll_position, 10), false);
|
||||
mopidyActions.connect();
|
||||
pusherActions.connect();
|
||||
uiActions.getBroadcasts();
|
||||
if (snapcast_enabled) {
|
||||
snapcastActions.connect();
|
||||
}
|
||||
if (!initial_setup_complete) {
|
||||
history.push('/initial-setup');
|
||||
}
|
||||
|
||||
uiActions.toggleSidebar(false);
|
||||
uiActions.setSelectedTracks([]);
|
||||
if (context_menu) uiActions.hideContextMenu();
|
||||
}
|
||||
}
|
||||
}, [isReady])
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener(
|
||||
'beforeinstallprompt',
|
||||
this.handleInstallPrompt,
|
||||
false,
|
||||
);
|
||||
window.removeEventListener('focus', this.handleFocusAndBlur, false);
|
||||
window.removeEventListener('blur', this.handleFocusAndBlur, false);
|
||||
}
|
||||
// Path changed (aka app navigation)
|
||||
useEffect(() => {
|
||||
if (allow_reporting) {
|
||||
ReactGA.set({ page: pathname });
|
||||
ReactGA.pageview(pathname);
|
||||
}
|
||||
|
||||
// If the location has a "scroll_position" state variable, scroll to it.
|
||||
// This is invisibly injected to the history by the Link component when navigating, so
|
||||
// hitting back in the browser allows us to restore the position
|
||||
if (scroll_position) {
|
||||
scrollTo(parseInt(scroll_position, 10), false);
|
||||
}
|
||||
|
||||
uiActions.toggleSidebar(false);
|
||||
uiActions.setSelectedTracks([]);
|
||||
if (context_menu) {
|
||||
uiActions.hideContextMenu();
|
||||
}
|
||||
}, [pathname]);
|
||||
|
||||
/**
|
||||
* Using Visibility API, detect whether the browser is in focus or not
|
||||
@ -226,227 +229,196 @@ export class App extends React.Component {
|
||||
*
|
||||
* @param e Event
|
||||
* */
|
||||
handleFocusAndBlur() {
|
||||
const { uiActions: { setWindowFocus } } = this.props;
|
||||
setWindowFocus(document.hasFocus());
|
||||
const handleFocusAndBlur = () => {
|
||||
uiActions.setWindowFocus(document.hasFocus());
|
||||
}
|
||||
|
||||
handleInstallPrompt(e) {
|
||||
const { uiActions: { installPrompt } } = this.props;
|
||||
const handleInstallPrompt = (e) => {
|
||||
e.preventDefault();
|
||||
console.log('Install prompt detected');
|
||||
installPrompt(e);
|
||||
uiActions.installPrompt(e);
|
||||
}
|
||||
|
||||
userInteracted = () => {
|
||||
const { userHasInteracted } = this.state;
|
||||
|
||||
if (userHasInteracted) return;
|
||||
this.setState({ userHasInteracted: true });
|
||||
let className = `${theme}-theme app-inner`;
|
||||
className += ` ${navigator.onLine ? 'online' : 'offline'}`;
|
||||
if (wide_scrollbar_enabled) {
|
||||
className += ' wide-scrollbar';
|
||||
}
|
||||
if (dragging) {
|
||||
className += ' dragging';
|
||||
}
|
||||
if (sidebar_open) {
|
||||
className += ' sidebar-open';
|
||||
}
|
||||
if (touch_dragging) {
|
||||
className += ' touch-dragging';
|
||||
}
|
||||
if (context_menu) {
|
||||
className += ' context-menu-open';
|
||||
}
|
||||
if (slim_mode) {
|
||||
className += ' slim-mode';
|
||||
}
|
||||
if (smooth_scrolling_enabled) {
|
||||
className += ' smooth-scrolling-enabled';
|
||||
}
|
||||
if (hide_scrollbars) {
|
||||
className += ' hide-scrollbars';
|
||||
}
|
||||
if (isTouchDevice()) {
|
||||
className += ' touch';
|
||||
} else {
|
||||
className += ' notouch';
|
||||
}
|
||||
|
||||
render = () => {
|
||||
const {
|
||||
uiActions,
|
||||
location,
|
||||
history,
|
||||
debug_info,
|
||||
theme,
|
||||
dragging,
|
||||
touch_dragging,
|
||||
context_menu,
|
||||
sidebar_open,
|
||||
slim_mode,
|
||||
wide_scrollbar_enabled,
|
||||
hide_scrollbars,
|
||||
smooth_scrolling_enabled,
|
||||
hotkeys_enabled,
|
||||
} = this.props;
|
||||
const {
|
||||
userHasInteracted,
|
||||
} = this.state;
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
onClick={() => hasInteracted ? null : setHasInteracted(true)}
|
||||
onKeyDown={() => hasInteracted ? null : setHasInteracted(true)}
|
||||
>
|
||||
<div className="body">
|
||||
<Switch>
|
||||
<Route path="/initial-setup" component={InitialSetup} />
|
||||
<Route path="/kiosk-mode" component={KioskMode} />
|
||||
<Route path="/add-to-playlist/:uris" component={AddToPlaylist} />
|
||||
<Route path="/image-zoom" component={ImageZoom} />
|
||||
<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} />
|
||||
|
||||
let className = `${theme}-theme app-inner`;
|
||||
className += ` ${navigator.onLine ? 'online' : 'offline'}`;
|
||||
if (wide_scrollbar_enabled) {
|
||||
className += ' wide-scrollbar';
|
||||
}
|
||||
if (dragging) {
|
||||
className += ' dragging';
|
||||
}
|
||||
if (sidebar_open) {
|
||||
className += ' sidebar-open';
|
||||
}
|
||||
if (touch_dragging) {
|
||||
className += ' touch-dragging';
|
||||
}
|
||||
if (context_menu) {
|
||||
className += ' context-menu-open';
|
||||
}
|
||||
if (slim_mode) {
|
||||
className += ' slim-mode';
|
||||
}
|
||||
if (smooth_scrolling_enabled) {
|
||||
className += ' smooth-scrolling-enabled';
|
||||
}
|
||||
if (hide_scrollbars) {
|
||||
className += ' hide-scrollbars';
|
||||
}
|
||||
if (isTouchDevice()) {
|
||||
className += ' touch';
|
||||
} else {
|
||||
className += ' notouch';
|
||||
}
|
||||
<Route path="/queue/radio" component={EditRadio} />
|
||||
<Route path="/queue/add-uri" component={AddToQueue} />
|
||||
<Route path="/playlist/create/:uris?" component={CreatePlaylist} />
|
||||
<Route path="/playlist/:uri/edit" component={EditPlaylist} />
|
||||
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
onClick={userHasInteracted ? null : this.userInteracted}
|
||||
onKeyDown={userHasInteracted ? null : this.userInteracted}
|
||||
>
|
||||
<div className="body">
|
||||
<Switch>
|
||||
<Route path="/initial-setup" component={InitialSetup} />
|
||||
<Route path="/kiosk-mode" component={KioskMode} />
|
||||
<Route path="/add-to-playlist/:uris" component={AddToPlaylist} />
|
||||
<Route path="/image-zoom" component={ImageZoom} />
|
||||
<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>
|
||||
<div>
|
||||
<Sidebar
|
||||
location={location}
|
||||
history={history}
|
||||
tabIndex="3"
|
||||
/>
|
||||
<PlaybackControls
|
||||
history={history}
|
||||
slim_mode={slim_mode}
|
||||
tabIndex="2"
|
||||
/>
|
||||
|
||||
<Route path="/queue/radio" component={EditRadio} />
|
||||
<Route path="/queue/add-uri" component={AddToQueue} />
|
||||
<Route path="/playlist/create/:uris?" component={CreatePlaylist} />
|
||||
<Route path="/playlist/:uri/edit" component={EditPlaylist} />
|
||||
<main id="main" className="smooth-scroll" tabIndex="1">
|
||||
<Switch>
|
||||
<Route exact path="/" component={Queue} />
|
||||
|
||||
<Route>
|
||||
<div>
|
||||
<Sidebar
|
||||
location={location}
|
||||
history={history}
|
||||
tabIndex="3"
|
||||
/>
|
||||
<PlaybackControls
|
||||
history={history}
|
||||
slim_mode={slim_mode}
|
||||
tabIndex="2"
|
||||
/>
|
||||
<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} />
|
||||
|
||||
<main id="main" className="smooth-scroll" tabIndex="1">
|
||||
<Switch>
|
||||
<Route exact path="/" component={Queue} />
|
||||
<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 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="/discover/recommendations/:uri?"
|
||||
component={DiscoverRecommendations}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/discover/featured"
|
||||
component={DiscoverFeatured}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/discover/categories/:uri"
|
||||
component={DiscoverCategory}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/discover/categories"
|
||||
component={DiscoverCategories}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/discover/new-releases"
|
||||
component={DiscoverNewReleases}
|
||||
/>
|
||||
|
||||
<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
|
||||
exact
|
||||
path="/library/artists"
|
||||
component={LibraryArtists}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/library/albums"
|
||||
component={LibraryAlbums}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/library/tracks"
|
||||
component={LibraryTracks}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/library/playlists"
|
||||
component={LibraryPlaylists}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/library/browse"
|
||||
component={LibraryBrowse}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/library/browse/:name/:uri"
|
||||
component={LibraryBrowseDirectory}
|
||||
/>
|
||||
|
||||
<Route
|
||||
exact
|
||||
path="/discover/recommendations/:uri?"
|
||||
component={DiscoverRecommendations}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/discover/featured"
|
||||
component={DiscoverFeatured}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/discover/categories/:uri"
|
||||
component={DiscoverCategory}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/discover/categories"
|
||||
component={DiscoverCategories}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/discover/new-releases"
|
||||
component={DiscoverNewReleases}
|
||||
/>
|
||||
|
||||
<Route
|
||||
exact
|
||||
path="/library/artists"
|
||||
component={LibraryArtists}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/library/albums"
|
||||
component={LibraryAlbums}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/library/tracks"
|
||||
component={LibraryTracks}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/library/playlists"
|
||||
component={LibraryPlaylists}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/library/browse"
|
||||
component={LibraryBrowse}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/library/browse/:name/:uri"
|
||||
component={LibraryBrowseDirectory}
|
||||
/>
|
||||
|
||||
<Route>
|
||||
<ErrorMessage type="not-found" title="Not found">
|
||||
<p>Oops, that link could not be found</p>
|
||||
</ErrorMessage>
|
||||
</Route>
|
||||
</Switch>
|
||||
</main>
|
||||
</div>
|
||||
</Route>
|
||||
</Switch>
|
||||
</div>
|
||||
|
||||
<ResizeListener
|
||||
uiActions={uiActions}
|
||||
slim_mode={slim_mode}
|
||||
/>
|
||||
{hotkeys_enabled && <Hotkeys history={history} />}
|
||||
<ContextMenu />
|
||||
<Dragger />
|
||||
<Notifications />
|
||||
{userHasInteracted && <ErrorBoundary silent><Stream /></ErrorBoundary>}
|
||||
{userHasInteracted && ('mediaSession' in navigator) && <MediaSession />}
|
||||
{debug_info && <DebugInfo />}
|
||||
<Route>
|
||||
<ErrorMessage type="not-found" title="Not found">
|
||||
<p>Oops, that link could not be found</p>
|
||||
</ErrorMessage>
|
||||
</Route>
|
||||
</Switch>
|
||||
</main>
|
||||
</div>
|
||||
</Route>
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
<ResizeListener
|
||||
uiActions={uiActions}
|
||||
slim_mode={slim_mode}
|
||||
/>
|
||||
{hotkeys_enabled && <Hotkeys history={history} />}
|
||||
<ContextMenu />
|
||||
<Dragger />
|
||||
<Notifications />
|
||||
{hasInteracted && <ErrorBoundary silent><Stream /></ErrorBoundary>}
|
||||
{hasInteracted && ('mediaSession' in navigator) && <MediaSession />}
|
||||
{debug_info && <DebugInfo />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const {
|
||||
|
||||
Reference in New Issue
Block a user