Renaming Content to I18n to avoid naming clashes; Artist and Debug views mapped
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -100,7 +100,7 @@
|
||||
|
||||
// Release details
|
||||
// These are automatically injected to built HTML
|
||||
var build = "1594021564";
|
||||
var build = "1594107635";
|
||||
var version = "3.50.0";
|
||||
|
||||
// Construct the script tag
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
|
||||
import { memo } from 'react';
|
||||
import { content } from '../locale';
|
||||
import { i18n } from '../locale';
|
||||
|
||||
/**
|
||||
* Format time duration
|
||||
@ -51,10 +51,10 @@ const durationSentence = (milliseconds = null) => {
|
||||
const totalMinutes = Math.floor(milliseconds / (1000 * 60));
|
||||
const totalHours = Math.floor(milliseconds / (1000 * 60 * 60));
|
||||
|
||||
if (totalHours > 1) return `${totalHours}+ ${content('time.hours.short')}`;
|
||||
if (totalMinutes > 1) return `${totalMinutes} ${content('time.minutes.short')}`;
|
||||
if (totalSeconds) return `${totalSeconds} ${content('time.seconds.short')}`;
|
||||
return `0 ${content('time.minutes.short')}`;
|
||||
if (totalHours > 1) return `${totalHours}+ ${i18n('time.hours.short')}`;
|
||||
if (totalMinutes > 1) return `${totalMinutes} ${i18n('time.minutes.short')}`;
|
||||
if (totalSeconds) return `${totalSeconds} ${i18n('time.seconds.short')}`;
|
||||
return `0 ${i18n('time.minutes.short')}`;
|
||||
};
|
||||
|
||||
const dater = (type, data) => {
|
||||
@ -99,17 +99,17 @@ const dater = (type, data) => {
|
||||
var years = Math.floor(diff / (1000 * 60 * 60 * 24 * 7 * 52));
|
||||
|
||||
if (seconds < 60) {
|
||||
return `${seconds} ${content(`time.seconds.${seconds > 1 ? 'singular' : 'plural'}`)}`;
|
||||
return `${seconds} ${i18n(`time.seconds.${seconds > 1 ? 'singular' : 'plural'}`)}`;
|
||||
} if (minutes < 60) {
|
||||
return `${minutes} ${content(`time.minutes.${minutes > 1 ? 'singular' : 'plural'}`)}`;
|
||||
return `${minutes} ${i18n(`time.minutes.${minutes > 1 ? 'singular' : 'plural'}`)}`;
|
||||
} if (hours < 24) {
|
||||
return `${hours} ${content(`time.hours.${hours > 1 ? 'singular' : 'plural'}`)}`;
|
||||
return `${hours} ${i18n(`time.hours.${hours > 1 ? 'singular' : 'plural'}`)}`;
|
||||
} if (days < 7) {
|
||||
return `${days} ${content(`time.days.${days > 1 ? 'singular' : 'plural'}`)}`;
|
||||
return `${days} ${i18n(`time.days.${days > 1 ? 'singular' : 'plural'}`)}`;
|
||||
} if (weeks < 54) {
|
||||
return `${weeks} ${content(`time.weeks.${weeks > 1 ? 'singular' : 'plural'}`)}`;
|
||||
return `${weeks} ${i18n(`time.weeks.${weeks > 1 ? 'singular' : 'plural'}`)}`;
|
||||
}
|
||||
return `${years} ${content(`time.years.${years > 1 ? 'singular' : 'plural'}`)}`;
|
||||
return `${years} ${i18n(`time.years.${years > 1 ? 'singular' : 'plural'}`)}`;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -5,19 +5,20 @@ import { bindActionCreators } from 'redux';
|
||||
import * as uiActions from '../../services/ui/actions';
|
||||
import * as spotifyActions from '../../services/spotify/actions';
|
||||
import { isLoading, getFromUri } from '../../util/helpers';
|
||||
import { i18n } from '../../locale';
|
||||
|
||||
class FollowButton extends React.Component {
|
||||
remove() {
|
||||
remove = () => {
|
||||
const { spotifyActions: actions, uri } = this.props;
|
||||
actions.following(uri, 'DELETE');
|
||||
}
|
||||
|
||||
add() {
|
||||
add = () => {
|
||||
const { spotifyActions: actions, uri } = this.props;
|
||||
actions.following(uri, 'PUT');
|
||||
}
|
||||
|
||||
render() {
|
||||
render = () => {
|
||||
const {
|
||||
uri,
|
||||
addText,
|
||||
@ -49,7 +50,7 @@ class FollowButton extends React.Component {
|
||||
className={`${className} button--disabled`}
|
||||
onClick={() => actions.createNotification({ content: 'You must authorize Spotify first', level: 'warning' })}
|
||||
>
|
||||
{addText}
|
||||
{addText || i18n('actions.add_to_library')}
|
||||
</button>
|
||||
);
|
||||
} if (is_following === true) {
|
||||
@ -57,9 +58,9 @@ class FollowButton extends React.Component {
|
||||
<button
|
||||
type="button"
|
||||
className={`${className} button--destructive`}
|
||||
onClick={() => this.remove()}
|
||||
onClick={this.remove}
|
||||
>
|
||||
{removeText}
|
||||
{removeText || i18n('actions.remove_from_library')}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@ -67,15 +68,15 @@ class FollowButton extends React.Component {
|
||||
<button
|
||||
type="button"
|
||||
className={`${className} button--default`}
|
||||
onClick={() => this.add()}
|
||||
onClick={this.add}
|
||||
>
|
||||
{addText}
|
||||
{addText || i18n('actions.add_to_library')}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => ({
|
||||
const mapStateToProps = (state) => ({
|
||||
load_queue: state.ui.load_queue,
|
||||
spotify_authorized: state.spotify.authorization,
|
||||
});
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import LinksSentence from './LinksSentence';
|
||||
import Dater from './Dater';
|
||||
import { Dater } from './Dater';
|
||||
import URILink from './URILink';
|
||||
import ContextMenuTrigger from './ContextMenuTrigger';
|
||||
import Icon from './Icon';
|
||||
|
||||
@ -8,7 +8,7 @@ import ProgressSlider from './Fields/ProgressSlider';
|
||||
import VolumeControl from './Fields/VolumeControl';
|
||||
import MuteControl from './Fields/MuteControl';
|
||||
import OutputControl from './Fields/OutputControl';
|
||||
import Dater from './Dater';
|
||||
import { Dater } from './Dater';
|
||||
import LinksSentence from './LinksSentence';
|
||||
import Thumbnail from './Thumbnail';
|
||||
import Icon from './Icon';
|
||||
|
||||
@ -8,7 +8,7 @@ import Icon from './Icon';
|
||||
import Dropzones from './Fields/Dropzones';
|
||||
import * as uiActions from '../services/ui/actions';
|
||||
import * as mopidyActions from '../services/mopidy/actions';
|
||||
import { content, Content } from '../locale';
|
||||
import { I18n } from '../locale';
|
||||
|
||||
class Sidebar extends React.Component {
|
||||
closeSidebar = () => uiActions.toggleSidebar(false);
|
||||
@ -47,19 +47,19 @@ class Sidebar extends React.Component {
|
||||
<Icon name="warning" className="red-text" />
|
||||
<span className="tooltip__content">
|
||||
{!mopidy_connected && (
|
||||
<Content path="sidebar.not_connected" params={{ name: 'Mopidy' }}>
|
||||
<I18n path="sidebar.not_connected" params={{ name: 'Mopidy' }}>
|
||||
<br />
|
||||
</Content>
|
||||
</I18n>
|
||||
)}
|
||||
{!pusher_connected && (
|
||||
<Content path="sidebar.not_connected" params={{ name: 'Pusher' }}>
|
||||
<I18n path="sidebar.not_connected" params={{ name: 'Pusher' }}>
|
||||
<br />
|
||||
</Content>
|
||||
</I18n>
|
||||
)}
|
||||
{!snapcast_connected && snapcast_enabled && (
|
||||
<Content path="sidebar.not_connected" params={{ name: 'Snapcast' }}>
|
||||
<I18n path="sidebar.not_connected" params={{ name: 'Snapcast' }}>
|
||||
<br />
|
||||
</Content>
|
||||
</I18n>
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
@ -84,68 +84,68 @@ class Sidebar extends React.Component {
|
||||
<section className="sidebar__menu__section">
|
||||
<Link to="/queue" history={history} className="sidebar__menu__item" activeClassName="sidebar__menu__item--active">
|
||||
<Icon name="play_arrow" type="material" />
|
||||
<Content path="sidebar.now_playing" />
|
||||
<I18n path="sidebar.now_playing" />
|
||||
</Link>
|
||||
<Link to="/search" history={history} className="sidebar__menu__item" activeClassName="sidebar__menu__item--active">
|
||||
<Icon name="search" type="material" />
|
||||
<Content path="sidebar.search" />
|
||||
<I18n path="sidebar.search" />
|
||||
</Link>
|
||||
</section>
|
||||
|
||||
{spotify_available && (
|
||||
<section className="sidebar__menu__section">
|
||||
<title className="sidebar__menu__section__title">
|
||||
<Content path="sidebar.discover" />
|
||||
<I18n path="sidebar.discover" />
|
||||
</title>
|
||||
<Link to="/discover/recommendations" history={history} className="sidebar__menu__item" activeClassName="sidebar__menu__item--active">
|
||||
<Icon name="explore" type="material" />
|
||||
<Content path="sidebar.discover" />
|
||||
<I18n path="sidebar.discover" />
|
||||
</Link>
|
||||
<Link to="/discover/categories" history={history} className="sidebar__menu__item" activeClassName="sidebar__menu__item--active">
|
||||
<Icon name="mood" type="material" />
|
||||
<Content path="sidebar.genre" />
|
||||
<I18n path="sidebar.genre" />
|
||||
</Link>
|
||||
<Link to="/discover/featured" history={history} className="sidebar__menu__item" activeClassName="sidebar__menu__item--active">
|
||||
<Icon name="star" type="material" />
|
||||
<Content path="sidebar.featured_playlists" />
|
||||
<I18n path="sidebar.featured_playlists" />
|
||||
</Link>
|
||||
<Link to="/discover/new-releases" history={history} className="sidebar__menu__item" activeClassName="sidebar__menu__item--active">
|
||||
<Icon name="new_releases" type="material" />
|
||||
<Content path="sidebar.new_releases" />
|
||||
<I18n path="sidebar.new_releases" />
|
||||
</Link>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<section className="sidebar__menu__section">
|
||||
<title className="sidebar__menu__section__title">
|
||||
<Content path="sidebar.my_music" />
|
||||
<I18n path="sidebar.my_music" />
|
||||
</title>
|
||||
<Link to="/library/playlists" history={history} className="sidebar__menu__item" activeClassName="sidebar__menu__item--active">
|
||||
<Icon name="queue_music" type="material" />
|
||||
<Content path="sidebar.playlists" />
|
||||
<I18n path="sidebar.playlists" />
|
||||
</Link>
|
||||
<Link to="/library/artists" history={history} className="sidebar__menu__item" activeClassName="sidebar__menu__item--active">
|
||||
<Icon name="recent_actors" type="material" />
|
||||
<Content path="sidebar.artists" />
|
||||
<I18n path="sidebar.artists" />
|
||||
</Link>
|
||||
<Link to="/library/albums" history={history} className="sidebar__menu__item" activeClassName="sidebar__menu__item--active">
|
||||
<Icon name="album" type="material" />
|
||||
<Content path="sidebar.albums" />
|
||||
<I18n path="sidebar.albums" />
|
||||
</Link>
|
||||
<Link to="/library/tracks" history={history} className="sidebar__menu__item" activeClassName="sidebar__menu__item--active">
|
||||
<Icon name="music_note" type="material" />
|
||||
<Content path="sidebar.tracks" />
|
||||
<I18n path="sidebar.tracks" />
|
||||
</Link>
|
||||
<Link to="/library/browse" history={history} className="sidebar__menu__item" activeClassName="sidebar__menu__item--active">
|
||||
<Icon name="folder" type="material" />
|
||||
<Content path="sidebar.browse" />
|
||||
<I18n path="sidebar.browse" />
|
||||
</Link>
|
||||
</section>
|
||||
|
||||
<section className="sidebar__menu__section">
|
||||
<Link to="/settings" history={history} className="sidebar__menu__item" activeClassName="sidebar__menu__item--active">
|
||||
<Icon name="settings" type="material" />
|
||||
<Content path="sidebar.settings" />
|
||||
<I18n path="sidebar.settings" />
|
||||
{this.renderStatusIcon()}
|
||||
</Link>
|
||||
</section>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import React from 'react';
|
||||
import Icon from './Icon';
|
||||
import LinksSentence from './LinksSentence';
|
||||
import Dater from './Dater';
|
||||
import { Dater } from './Dater';
|
||||
import URILink from './URILink';
|
||||
import ContextMenuTrigger from './ContextMenuTrigger';
|
||||
import ErrorBoundary from './ErrorBoundary';
|
||||
|
||||
@ -1,16 +1,22 @@
|
||||
errors:
|
||||
uri_not_found: Could not find anything with URI %{uri}
|
||||
specs:
|
||||
tracks: %{count} tracks
|
||||
followers: %{count} followers
|
||||
listeners: %{count} listeners
|
||||
plays: %{count} plays
|
||||
tracks: '%{count} tracks'
|
||||
followers: '%{count} followers'
|
||||
listeners: '%{count} listeners'
|
||||
plays: '%{count} plays'
|
||||
popularity: '%{percent}% popularity'
|
||||
actions:
|
||||
play: Play
|
||||
pause: Pause
|
||||
stop: Stop
|
||||
add_to_library: Add to library
|
||||
remove_from_library: Remove from library
|
||||
reset: Reset
|
||||
back: Back
|
||||
enable: Enable
|
||||
disable: Disable
|
||||
send: Send
|
||||
time:
|
||||
seconds:
|
||||
short: secs
|
||||
@ -36,6 +42,13 @@ time:
|
||||
short: yrs
|
||||
singular: year
|
||||
plural: years
|
||||
services:
|
||||
mopidy:
|
||||
title: Mopidy
|
||||
pusher:
|
||||
title: Pusher
|
||||
snapcast:
|
||||
title: Snapcast
|
||||
sidebar:
|
||||
now_playing: Now playing
|
||||
search: Search
|
||||
@ -50,7 +63,7 @@ sidebar:
|
||||
tracks: Tracks
|
||||
browse: Browse
|
||||
settings: Settings
|
||||
not_connected: %{name} not connected
|
||||
not_connected: '%{name} not connected'
|
||||
now_playing:
|
||||
title: Now playing
|
||||
context_actions:
|
||||
@ -64,25 +77,63 @@ now_playing:
|
||||
radio: Radio
|
||||
album:
|
||||
title: Album
|
||||
title_window: %{name} by %{artist} (album)
|
||||
title_window: '%{name} by %{artist} (album)'
|
||||
wiki:
|
||||
title: About
|
||||
published: 'Published: %{date}'
|
||||
artist:
|
||||
title: Artist
|
||||
title_window: '%{name} (artist)'
|
||||
overview:
|
||||
title: Overview
|
||||
top_tracks: Top tracks
|
||||
related_artists:
|
||||
title: Related artists
|
||||
more: All related artists
|
||||
tracks:
|
||||
title: Tracks
|
||||
related_artists:
|
||||
title: Related artists
|
||||
about:
|
||||
title: About
|
||||
source: '%{source} artist'
|
||||
wiki:
|
||||
published: 'Published: %{date}'
|
||||
origin: 'Origin: '
|
||||
debug:
|
||||
title: Debug
|
||||
ui:
|
||||
title: User interface
|
||||
debug:
|
||||
title: Debug
|
||||
overlay: Show debug overlay
|
||||
logging:
|
||||
title: Logging
|
||||
mopidy: Log Mopidy
|
||||
pusher: Log Pusher
|
||||
actions: Log Actions
|
||||
snapcast: Log Snapcast
|
||||
create_notification: Create notification
|
||||
create_process: Create process notification
|
||||
run_test: Run test process
|
||||
call: Call
|
||||
data: Data
|
||||
examples: Examples
|
||||
search:
|
||||
title: 'Search: %{term}'
|
||||
context_actions:
|
||||
sort: Sort
|
||||
source: Sources (%{count})
|
||||
source: 'Sources (%{count})'
|
||||
placeholder: Search
|
||||
artists:
|
||||
title: Artists
|
||||
more: All artists (%{count})
|
||||
more: 'All artists (%{count})'
|
||||
albums:
|
||||
title: Albums
|
||||
more: All albums (%{count})
|
||||
more: 'All albums (%{count})'
|
||||
playlists:
|
||||
title: Playlists
|
||||
more: All playlists (%{count})
|
||||
more: 'All playlists (%{count})'
|
||||
tracks:
|
||||
title: Tracks
|
||||
settings:
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import get from 'lodash/get';
|
||||
import dictionaries from './dictionaries';
|
||||
import { titleCase } from '../util/helpers';
|
||||
@ -7,7 +6,7 @@ import { titleCase } from '../util/helpers';
|
||||
const PARAMS_REG_EXP = '%{(.*?)}';
|
||||
const paramsRegExp = new RegExp(PARAMS_REG_EXP, 'g');
|
||||
|
||||
const content = (path, params = {}, transform) => {
|
||||
const i18n = (path, params = {}, transform) => {
|
||||
const dictionary = dictionaries[window.language || 'en'] || dictionaries.en;
|
||||
|
||||
let value = get((dictionary), path, '');
|
||||
@ -33,19 +32,19 @@ const content = (path, params = {}, transform) => {
|
||||
return value;
|
||||
};
|
||||
|
||||
const Content = ({ path, params = {}, transform, children }) => (
|
||||
const I18n = ({ path, transform, children, ...params }) => (
|
||||
<Fragment>
|
||||
{content(path, params, transform)}
|
||||
{i18n(path, params, transform)}
|
||||
{children}
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
export default {
|
||||
Content,
|
||||
content,
|
||||
I18n,
|
||||
i18n,
|
||||
};
|
||||
|
||||
export {
|
||||
Content,
|
||||
content,
|
||||
I18n,
|
||||
i18n,
|
||||
};
|
||||
|
||||
@ -8,12 +8,12 @@ import Thumbnail from '../components/Thumbnail';
|
||||
import LinksSentence from '../components/LinksSentence';
|
||||
import Loader from '../components/Loader';
|
||||
import FollowButton from '../components/Fields/FollowButton';
|
||||
import NiceNumber, { nice_number } from '../components/NiceNumber';
|
||||
import Dater from '../components/Dater';
|
||||
import { nice_number } from '../components/NiceNumber';
|
||||
import { Dater } from '../components/Dater';
|
||||
import LazyLoadListener from '../components/LazyLoadListener';
|
||||
import ContextMenuTrigger from '../components/ContextMenuTrigger';
|
||||
import Icon from '../components/Icon';
|
||||
import { content } from '../locale';
|
||||
import { i18n, I18n } from '../locale';
|
||||
import * as coreActions from '../services/core/actions';
|
||||
import * as uiActions from '../services/ui/actions';
|
||||
import * as mopidyActions from '../services/mopidy/actions';
|
||||
@ -111,9 +111,9 @@ export class Album extends React.Component {
|
||||
}
|
||||
}
|
||||
}
|
||||
setWindowTitle(content('album.title_window', { name: album.name, artist: artistNames.join() }));
|
||||
setWindowTitle(i18n('album.title_window', { name: album.name, artist: artistNames.join() }));
|
||||
} else {
|
||||
setWindowTitle(content('album.title'));
|
||||
setWindowTitle(i18n('album.title'));
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,6 +174,7 @@ export class Album extends React.Component {
|
||||
tracks,
|
||||
artists,
|
||||
load_queue,
|
||||
slim_mode,
|
||||
} = this.props;
|
||||
|
||||
if (!albumProp) {
|
||||
@ -187,7 +188,7 @@ export class Album extends React.Component {
|
||||
return (
|
||||
<ErrorMessage type="not-found" title="Not found">
|
||||
<p>
|
||||
{content('errors.uri_not_found', { uri: encodeURIComponent(uri) })}
|
||||
{i18n('errors.uri_not_found', { uri: encodeURIComponent(uri) })}
|
||||
</p>
|
||||
</ErrorMessage>
|
||||
);
|
||||
@ -195,15 +196,11 @@ export class Album extends React.Component {
|
||||
|
||||
const album = collate(albumProp, { tracks, artists });
|
||||
|
||||
if (
|
||||
const is_loading_tracks = (
|
||||
!album.tracks_uris
|
||||
|| (album.tracks_uris && !album.tracks)
|
||||
|| album.tracks_uris.length !== album.tracks.length
|
||||
) {
|
||||
var is_loading_tracks = true;
|
||||
} else {
|
||||
var is_loading_tracks = false;
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="view album-view content-wrapper preserve-3d">
|
||||
@ -215,7 +212,7 @@ export class Album extends React.Component {
|
||||
<h1>{album.name}</h1>
|
||||
|
||||
<ul className="details">
|
||||
{!this.props.slim_mode ? (
|
||||
{!slim_mode ? (
|
||||
<li className="source">
|
||||
<Icon type="fontawesome" name={sourceIcon(album.uri)} />
|
||||
</li>
|
||||
@ -232,28 +229,28 @@ export class Album extends React.Component {
|
||||
) : null}
|
||||
{album.tracks ? (
|
||||
<li>
|
||||
{content(
|
||||
{i18n(
|
||||
'specs.tracks',
|
||||
{ count: album.tracks_total || album.tracks.length },
|
||||
)}
|
||||
</li>
|
||||
) : null}
|
||||
{!this.props.slim_mode && album.tracks ? (
|
||||
{!slim_mode && album.tracks ? (
|
||||
<li>
|
||||
<Dater type="total-time" data={album.tracks} />
|
||||
</li>
|
||||
) : null}
|
||||
{!this.props.slim_mode && album.play_count ? (
|
||||
{!slim_mode && album.play_count ? (
|
||||
<li>
|
||||
{content(
|
||||
{i18n(
|
||||
'specs.plays',
|
||||
{ count: nice_number(album.play_count) },
|
||||
)}
|
||||
</li>
|
||||
) : null}
|
||||
{!this.props.slim_mode && album.listeners ? (
|
||||
{!slim_mode && album.listeners ? (
|
||||
<li>
|
||||
{content(
|
||||
{i18n(
|
||||
'specs.plays',
|
||||
{ count: nice_number(album.listeners) },
|
||||
)}
|
||||
@ -263,19 +260,17 @@ export class Album extends React.Component {
|
||||
</div>
|
||||
|
||||
<div className="actions">
|
||||
<button type="button" className="button button--primary" onClick={(e) => this.play()}>
|
||||
{content('actions.play')}
|
||||
<button type="button" className="button button--primary" onClick={this.play}>
|
||||
<I18n path="actions.play" />
|
||||
</button>
|
||||
{uriSource(this.props.uri) == 'spotify' ? (
|
||||
{uriSource(uri) === 'spotify' && (
|
||||
<FollowButton
|
||||
className="secondary"
|
||||
uri={this.props.uri}
|
||||
addText={content('actions.add_to_library')}
|
||||
removeText={content('actions.remove_from_library')}
|
||||
uri={uri}
|
||||
is_following={this.inLibrary()}
|
||||
/>
|
||||
) : null}
|
||||
<ContextMenuTrigger onTrigger={(e) => this.handleContextMenu(e)} />
|
||||
)}
|
||||
<ContextMenuTrigger onTrigger={this.handleContextMenu} />
|
||||
</div>
|
||||
|
||||
<section className="list-wrapper">
|
||||
@ -287,21 +282,18 @@ export class Album extends React.Component {
|
||||
<LazyLoadListener
|
||||
loadKey={album.tracks_more}
|
||||
showLoader={is_loading_tracks}
|
||||
loadMore={() => this.loadMore()}
|
||||
loadMore={this.loadMore}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{album.wiki ? (
|
||||
<section className="wiki">
|
||||
<h4 className="wiki__title">{content('album.wiki.title')}</h4>
|
||||
<h4 className="wiki__title">{i18n('album.wiki.title')}</h4>
|
||||
<div className="wiki__text">
|
||||
<p>{album.wiki}</p>
|
||||
<br />
|
||||
<div className="mid_grey-text">
|
||||
{content(
|
||||
'album.wiki.published',
|
||||
{ date: album.wiki_publish_date },
|
||||
)}
|
||||
<I18n path="album.wiki.published" params={{ date: album.wiki_publish_date }} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -32,6 +32,7 @@ import {
|
||||
} from '../util/helpers';
|
||||
import { collate } from '../util/format';
|
||||
import { sortItems, applyFilter } from '../util/arrays';
|
||||
import { i18n, I18n } from '../locale';
|
||||
|
||||
class Artist extends React.Component {
|
||||
componentDidMount() {
|
||||
@ -56,7 +57,7 @@ class Artist extends React.Component {
|
||||
if (uri !== prevUri) {
|
||||
loadArtist(uri);
|
||||
} else if (!prev_mopidy_connected && mopidy_connected) {
|
||||
if (uriSource(uri) != 'spotify') {
|
||||
if (uriSource(uri) !== 'spotify') {
|
||||
loadArtist(uri);
|
||||
}
|
||||
}
|
||||
@ -65,74 +66,127 @@ class Artist extends React.Component {
|
||||
if (prevUri !== uri && artist) this.setWindowTitle(artist);
|
||||
}
|
||||
|
||||
setWindowTitle(artist = this.props.artist) {
|
||||
onResetFilters = () => {
|
||||
this.setFilter(null);
|
||||
this.setSort(null);
|
||||
}
|
||||
|
||||
onChangeFilter = (value) => {
|
||||
const { uiActions: { set, hideContextMenu } } = this.props;
|
||||
set({ artist_albums_filter: value });
|
||||
hideContextMenu();
|
||||
}
|
||||
|
||||
onChangeSort = (value) => {
|
||||
const {
|
||||
sort,
|
||||
sort_reverse,
|
||||
uiActions: {
|
||||
set,
|
||||
hideContextMenu,
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
let reverse = false;
|
||||
if (value !== null && sort == value) {
|
||||
reverse = !sort_reverse;
|
||||
}
|
||||
|
||||
set({
|
||||
artist_albums_sort_reverse: reverse,
|
||||
artist_albums_sort: value,
|
||||
});
|
||||
hideContextMenu();
|
||||
}
|
||||
|
||||
onPlay = () => {
|
||||
const {
|
||||
artist: {
|
||||
uri,
|
||||
tracks_uris,
|
||||
albums_uris,
|
||||
},
|
||||
mopidyActions: { playURIs },
|
||||
} = this.props;
|
||||
playURIs(tracks_uris || albums_uris, uri);
|
||||
}
|
||||
|
||||
setWindowTitle = (artist = this.props.artist) => {
|
||||
const { uiActions: { setWindowTitle } } = this.props;
|
||||
|
||||
if (artist) {
|
||||
this.props.uiActions.setWindowTitle(`${artist.name} (artist)`);
|
||||
setWindowTitle(i18n('artist.title_window', { name: artist.name }));
|
||||
} else {
|
||||
this.props.uiActions.setWindowTitle('Artist');
|
||||
setWindowTitle(i18n('artist.title'));
|
||||
}
|
||||
}
|
||||
|
||||
handleContextMenu(e) {
|
||||
const data = {
|
||||
handleContextMenu = (e) => {
|
||||
const {
|
||||
artist,
|
||||
uri,
|
||||
uiActions: { showContextMenu },
|
||||
} = this.props;
|
||||
|
||||
showContextMenu({
|
||||
e,
|
||||
context: 'artist',
|
||||
items: [this.props.artist],
|
||||
uris: [this.props.uri],
|
||||
};
|
||||
this.props.uiActions.showContextMenu(data);
|
||||
items: [artist],
|
||||
uris: [uri],
|
||||
});
|
||||
}
|
||||
|
||||
loadMore() {
|
||||
this.props.spotifyActions.getMore(
|
||||
this.props.artist.albums_more,
|
||||
loadMore = () => {
|
||||
const {
|
||||
spotifyActions: { getMore },
|
||||
artist: { albums_more },
|
||||
uri,
|
||||
} = this.props;
|
||||
|
||||
getMore(
|
||||
albums_more,
|
||||
{
|
||||
parent_type: 'artist',
|
||||
parent_key: this.props.uri,
|
||||
parent_key: uri,
|
||||
records_type: 'album',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
inLibrary() {
|
||||
const library = `${uriSource(this.props.uri)}_library_artists`;
|
||||
return (this.props[library] && this.props[library].indexOf(this.props.uri) > -1);
|
||||
inLibrary = () => {
|
||||
const { uri } = this.props;
|
||||
const libraryName = `${uriSource(uri)}_library_artists`;
|
||||
const { [libraryName]: library = [] } = this.props;
|
||||
return library.indexOf(uri) > -1;
|
||||
}
|
||||
|
||||
setSort(value) {
|
||||
let reverse = false;
|
||||
if (value !== null && this.props.sort == value) {
|
||||
reverse = !this.props.sort_reverse;
|
||||
}
|
||||
|
||||
const data = {
|
||||
artist_albums_sort_reverse: reverse,
|
||||
artist_albums_sort: value,
|
||||
};
|
||||
this.props.uiActions.set(data);
|
||||
}
|
||||
|
||||
setFilter(value) {
|
||||
this.props.uiActions.set({ artist_albums_filter: value });
|
||||
}
|
||||
|
||||
renderOverview() {
|
||||
const { uiActions } = this.props;
|
||||
renderOverview = () => {
|
||||
const {
|
||||
uri,
|
||||
uiActions,
|
||||
artist: artistProp,
|
||||
artists,
|
||||
albums,
|
||||
tracks,
|
||||
sort,
|
||||
sort_reverse,
|
||||
filter,
|
||||
} = this.props;
|
||||
const artist = collate(
|
||||
this.props.artist,
|
||||
artistProp,
|
||||
{
|
||||
artists: this.props.artists,
|
||||
albums: this.props.albums,
|
||||
tracks: this.props.tracks,
|
||||
artists,
|
||||
albums,
|
||||
tracks,
|
||||
},
|
||||
);
|
||||
|
||||
if (this.props.sort && artist.albums) {
|
||||
artist.albums = sortItems(artist.albums, this.props.sort, this.props.sort_reverse);
|
||||
if (sort && artist.albums) {
|
||||
artist.albums = sortItems(artist.albums, sort, sort_reverse);
|
||||
}
|
||||
|
||||
if (this.props.filter && artist.albums) {
|
||||
artist.albums = applyFilter('type', this.props.filter, artist.albums);
|
||||
if (filter && artist.albums) {
|
||||
artist.albums = applyFilter('type', filter, artist.albums);
|
||||
}
|
||||
|
||||
const sort_options = [
|
||||
@ -173,16 +227,16 @@ class Artist extends React.Component {
|
||||
},
|
||||
];
|
||||
|
||||
if (!artist.tracks_uris || (artist.tracks_uris && !artist.tracks) || (artist.tracks_uris.length !== artist.tracks.length)) {
|
||||
var is_loading_tracks = true;
|
||||
} else {
|
||||
var is_loading_tracks = false;
|
||||
}
|
||||
const is_loading_tracks = (
|
||||
!artist.tracks_uris
|
||||
|| (artist.tracks_uris && !artist.tracks)
|
||||
|| (artist.tracks_uris.length !== artist.tracks.length)
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="body overview">
|
||||
<div className={`top-tracks col col--w${artist.related_artists && artist.related_artists.length > 0 ? '70' : '100'}`}>
|
||||
{artist.tracks ? <h4>Top tracks</h4> : null}
|
||||
{artist.tracks && <h4><I18n path="artist.overview.top_tracks" /></h4>}
|
||||
<div className="list-wrapper">
|
||||
<TrackList className="artist-track-list" uri={artist.uri} tracks={artist.tracks ? artist.tracks.splice(0, 10) : []} />
|
||||
<LazyLoadListener showLoader={is_loading_tracks} />
|
||||
@ -193,7 +247,7 @@ class Artist extends React.Component {
|
||||
|
||||
{artist.related_artists && artist.related_artists.length > 0 && (
|
||||
<div className="col col--w25 related-artists">
|
||||
<h4>Related artists</h4>
|
||||
<h4><I18n path="artist.overview.related_artists.title" /></h4>
|
||||
<div className="list-wrapper">
|
||||
<RelatedArtists
|
||||
artists={artist.related_artists.slice(0, 6)}
|
||||
@ -201,11 +255,11 @@ class Artist extends React.Component {
|
||||
/>
|
||||
</div>
|
||||
<Link
|
||||
to={`/artist/${encodeURIComponent(this.props.uri)}/related-artists`}
|
||||
to={`/artist/${encodeURIComponent(uri)}/related-artists`}
|
||||
scrollTo="#sub-views-menu"
|
||||
className="button button--default"
|
||||
>
|
||||
All related artists
|
||||
<I18n path="artist.overview.related_artists.more" />
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
@ -215,28 +269,28 @@ class Artist extends React.Component {
|
||||
{artist.albums && (
|
||||
<div className="albums">
|
||||
<h4>
|
||||
<div>Albums</div>
|
||||
<div><I18n path="artist.overview.albums" /></div>
|
||||
<DropdownField
|
||||
icon="swap_vert"
|
||||
name="Sort"
|
||||
value={this.props.sort}
|
||||
value={sort}
|
||||
valueAsLabel
|
||||
options={sort_options}
|
||||
selected_icon={this.props.sort ? (this.props.sort_reverse ? 'keyboard_arrow_up' : 'keyboard_arrow_down') : null}
|
||||
handleChange={(value) => { this.setSort(value); this.props.uiActions.hideContextMenu(); }}
|
||||
selected_icon={sort ? (sort_reverse ? 'keyboard_arrow_up' : 'keyboard_arrow_down') : null}
|
||||
handleChange={this.onChangeSort}
|
||||
/>
|
||||
<DropdownField
|
||||
icon="filter_list"
|
||||
name="Filter"
|
||||
value={this.props.filter}
|
||||
value={filter}
|
||||
valueAsLabel
|
||||
options={filter_options}
|
||||
handleChange={(value) => { this.setFilter(value); this.props.uiActions.hideContextMenu(); }}
|
||||
handleChange={this.onFilterChange}
|
||||
/>
|
||||
{(this.props.sort || this.props.filter) && (
|
||||
<a className="button button--discrete button--small" onClick={(e) => { this.setFilter(null); this.setSort(null); }}>
|
||||
{(sort || filter) && (
|
||||
<a className="button button--discrete button--small" onClick={this.onResetFilters}>
|
||||
<Icon name="clear" />
|
||||
Reset
|
||||
<I18n path="actions.reset" />
|
||||
</a>
|
||||
)}
|
||||
</h4>
|
||||
@ -246,7 +300,7 @@ class Artist extends React.Component {
|
||||
<LazyLoadListener
|
||||
loadKey={artist.albums_more}
|
||||
showLoader={artist.albums_more}
|
||||
loadMore={() => this.loadMore()}
|
||||
loadMore={this.loadMore}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
@ -255,20 +309,26 @@ class Artist extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderTracks() {
|
||||
renderTracks = () => {
|
||||
const {
|
||||
artist: artistProp,
|
||||
artists,
|
||||
tracks,
|
||||
} = this.props;
|
||||
|
||||
const artist = collate(
|
||||
this.props.artist,
|
||||
artistProp,
|
||||
{
|
||||
artists: this.props.artists,
|
||||
tracks: this.props.tracks,
|
||||
artists,
|
||||
tracks,
|
||||
},
|
||||
);
|
||||
|
||||
if (!artist.tracks_uris || (artist.tracks_uris && !artist.tracks) || (artist.tracks_uris.length !== artist.tracks.length)) {
|
||||
var is_loading_tracks = true;
|
||||
} else {
|
||||
var is_loading_tracks = false;
|
||||
}
|
||||
const is_loading_tracks = (
|
||||
!artist.tracks_uris
|
||||
|| (artist.tracks_uris && !artist.tracks)
|
||||
|| (artist.tracks_uris.length !== artist.tracks.length)
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="body related-artists">
|
||||
@ -280,11 +340,16 @@ class Artist extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderRelatedArtists() {
|
||||
renderRelatedArtists = () => {
|
||||
const {
|
||||
artist: artistProp,
|
||||
artists,
|
||||
} = this.props;
|
||||
|
||||
const artist = collate(
|
||||
this.props.artist,
|
||||
artistProp,
|
||||
{
|
||||
artists: this.props.artists,
|
||||
artists,
|
||||
},
|
||||
);
|
||||
|
||||
@ -297,45 +362,48 @@ class Artist extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderAbout() {
|
||||
renderAbout = () => {
|
||||
const {
|
||||
artist: artistProp,
|
||||
artists,
|
||||
} = this.props;
|
||||
|
||||
const artist = collate(
|
||||
this.props.artist,
|
||||
artistProp,
|
||||
{
|
||||
artists: this.props.artists,
|
||||
artists,
|
||||
},
|
||||
);
|
||||
|
||||
const thumbnails = [];
|
||||
if (artist.images && artist.images.length > 0) {
|
||||
for (const images of artist.images) {
|
||||
// Capture potential 'null' images
|
||||
// TODO: Prevent these from existing in the first place
|
||||
if (images.huge) {
|
||||
thumbnails.push(
|
||||
<div className="tile thumbnail-wrapper" key={images.huge}>
|
||||
<Thumbnail size="huge" canZoom fill images={images} />
|
||||
</div>,
|
||||
);
|
||||
}
|
||||
const thumbnails = artist.images && artist.images.map(
|
||||
(image) => {
|
||||
if (!image.huge) return null;
|
||||
return (
|
||||
<div className="tile thumbnail-wrapper" key={image.huge}>
|
||||
<Thumbnail size="huge" canZoom fill images={image} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="body about">
|
||||
|
||||
<div className="col col--w40 tiles artist-stats">
|
||||
{thumbnails}
|
||||
<div className="tile">
|
||||
<span className="content">
|
||||
<Icon type="fontawesome" name={sourceIcon(artist.uri)} />
|
||||
{`${titleCase(uriSource(artist.uri))} artist`}
|
||||
<I18n
|
||||
path="artist.about.source"
|
||||
source={titleCase(uriSource(artist.uri))}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
{artist.followers && (
|
||||
<div className="tile">
|
||||
<span className="content">
|
||||
<Icon type="fontawesome" name="users" />
|
||||
{`${artist.followers.toLocaleString()} followers`}
|
||||
<I18n path="specs.followers" count={artist.followers.toLocaleString()} />
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@ -343,7 +411,7 @@ class Artist extends React.Component {
|
||||
<div className="tile">
|
||||
<span className="content">
|
||||
<Icon type="fontawesome" name="fire" />
|
||||
{`${artist.popularity} % popularity`}
|
||||
<I18n path="specs.popularity" percent={artist.popularity} />
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@ -351,7 +419,7 @@ class Artist extends React.Component {
|
||||
<div className="tile">
|
||||
<span className="content">
|
||||
<Icon type="fontawesome" name="headphones" />
|
||||
{`${artist.listeners.toLocaleString()} listeners`}
|
||||
<I18n path="specs.listeners" count={artist.listeners.toLocaleString()} />
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@ -365,11 +433,17 @@ class Artist extends React.Component {
|
||||
<p>{artist.biography}</p>
|
||||
<br />
|
||||
<div className="mid_grey-text">
|
||||
{`Published: ${artist.biography_publish_date}`}
|
||||
<I18n path="artist.about.wiki.published" date={artist.biography_publish_date} />
|
||||
</div>
|
||||
<div className="mid_grey-text">
|
||||
{`Origin: `}
|
||||
<a href={artist.biography_link} target="_blank">{ artist.biography_link }</a>
|
||||
<I18n path="artist.about.wiki.origin" />
|
||||
<a
|
||||
href={artist.biography_link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{artist.biography_link}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@ -379,35 +453,35 @@ class Artist extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const scheme = uriSource(this.props.uri);
|
||||
render = () => {
|
||||
const {
|
||||
uri,
|
||||
load_queue,
|
||||
artist,
|
||||
history,
|
||||
} = this.props;
|
||||
|
||||
if (!this.props.artist) {
|
||||
if (isLoading(this.props.load_queue, [`spotify_artists/${getFromUri('artistid', this.props.uri)}`, 'lastfm_method=artist.getInfo'])) {
|
||||
if (!artist) {
|
||||
if (
|
||||
isLoading(
|
||||
load_queue,
|
||||
[`spotify_artists/${getFromUri('artistid', uri)}`, 'lastfm_method=artist.getInfo'],
|
||||
)
|
||||
) {
|
||||
return <Loader body loading />;
|
||||
}
|
||||
return (
|
||||
<ErrorMessage type="not-found" title="Not found">
|
||||
<p>
|
||||
{`Could not find artist with URI "${encodeURIComponent(this.props.uri)}"`}
|
||||
<I18n path="errors.uri_not_found" uri={encodeURIComponent(uri)} />
|
||||
</p>
|
||||
</ErrorMessage>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.artist && this.props.artist.images && this.props.artist.images.length > 0) {
|
||||
var image = this.props.artist.images[0].huge;
|
||||
} else {
|
||||
var image = null;
|
||||
}
|
||||
|
||||
const is_spotify = (scheme == 'spotify');
|
||||
|
||||
if (this.props.artist.tracks_uris && this.props.artist.tracks_uris.length > 0) {
|
||||
var uris_to_play = this.props.artist.tracks_uris;
|
||||
} else {
|
||||
var uris_to_play = this.props.artist.albums_uris;
|
||||
}
|
||||
const scheme = uriSource(uri);
|
||||
const image = (artist.images && artist.images.length) ? artist.images[0].huge : null;
|
||||
const is_spotify = (scheme === 'spotify');
|
||||
|
||||
return (
|
||||
<div className="view artist-view preserve-3d">
|
||||
@ -417,64 +491,69 @@ class Artist extends React.Component {
|
||||
|
||||
<div className="liner">
|
||||
<div className="heading">
|
||||
|
||||
<div className="heading__thumbnail">
|
||||
<Thumbnail size="medium" circle canZoom type="artist" image={image} />
|
||||
</div>
|
||||
|
||||
<div className="heading__content">
|
||||
<h1>{this.props.artist ? this.props.artist.name : null}</h1>
|
||||
<div className="actions">
|
||||
<button className="button button--primary" onClick={(e) => this.props.mopidyActions.playURIs(uris_to_play, this.props.artist.uri)}>Play</button>
|
||||
{is_spotify ? <FollowButton uri={this.props.uri} removeText="Remove from library" addText="Add to library" is_following={this.inLibrary()} /> : null}
|
||||
<ContextMenuTrigger className="white" onTrigger={(e) => this.handleContextMenu(e)} />
|
||||
<button className="button button--primary" onClick={this.onPlay}>
|
||||
<I18n path="actions.play" />
|
||||
</button>
|
||||
{is_spotify && (
|
||||
<FollowButton
|
||||
uri={uri}
|
||||
is_following={this.inLibrary()}
|
||||
/>
|
||||
)}
|
||||
<ContextMenuTrigger className="white" onTrigger={this.handleContextMenu} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sub-views" id="sub-views-menu">
|
||||
<Link
|
||||
exact
|
||||
history={this.props.history}
|
||||
history={history}
|
||||
activeClassName="sub-views__option--active"
|
||||
className="sub-views__option"
|
||||
to={`/artist/${encodeURIComponent(this.props.uri)}`}
|
||||
to={`/artist/${encodeURIComponent(uri)}`}
|
||||
scrollTo="#sub-views-menu"
|
||||
>
|
||||
<h4>Overview</h4>
|
||||
<h4><I18n path="artist.overview.title" /></h4>
|
||||
</Link>
|
||||
{this.props.artist.tracks_uris && this.props.artist.tracks_uris.length > 10 && (
|
||||
{artist.tracks_uris && artist.tracks_uris.length > 10 && (
|
||||
<Link
|
||||
exact
|
||||
history={this.props.history}
|
||||
history={history}
|
||||
activeClassName="sub-views__option--active"
|
||||
className="sub-views__option"
|
||||
to={`/artist/${encodeURIComponent(this.props.uri)}/tracks`}
|
||||
to={`/artist/${encodeURIComponent(uri)}/tracks`}
|
||||
scrollTo="#sub-views-menu"
|
||||
>
|
||||
<h4>Tracks</h4>
|
||||
<h4><I18n path="artist.tracks.title" /></h4>
|
||||
</Link>
|
||||
)}
|
||||
{this.props.artist.related_artists_uris && (
|
||||
{artist.related_artists_uris && (
|
||||
<Link
|
||||
exact
|
||||
history={this.props.history}
|
||||
history={history}
|
||||
activeClassName="sub-views__option--active"
|
||||
className="sub-views__option"
|
||||
to={`/artist/${encodeURIComponent(this.props.uri)}/related-artists`}
|
||||
to={`/artist/${encodeURIComponent(uri)}/related-artists`}
|
||||
scrollTo="#sub-views-menu"
|
||||
>
|
||||
<h4>Related artists</h4>
|
||||
<h4><I18n path="artist.related_artists.title" /></h4>
|
||||
</Link>
|
||||
)}
|
||||
<Link
|
||||
exact
|
||||
history={this.props.history}
|
||||
history={history}
|
||||
activeClassName="sub-views__option--active"
|
||||
className="sub-views__option"
|
||||
to={`/artist/${encodeURIComponent(this.props.uri)}/about`}
|
||||
to={`/artist/${encodeURIComponent(uri)}/about`}
|
||||
scrollTo="#sub-views-menu"
|
||||
>
|
||||
<h4>About</h4>
|
||||
<h4><I18n path="artist.about.title" /></h4>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -9,6 +9,7 @@ import * as pusherActions from '../services/pusher/actions';
|
||||
import * as mopidyActions from '../services/mopidy/actions';
|
||||
import * as spotifyActions from '../services/spotify/actions';
|
||||
import * as snapcastActions from '../services/snapcast/actions';
|
||||
import { I18n, i18n } from '../locale';
|
||||
|
||||
class Debug extends React.Component {
|
||||
constructor(props) {
|
||||
@ -24,152 +25,196 @@ class Debug extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.uiActions.setWindowTitle('Debug');
|
||||
const { uiActions: { setWindowTitle } } = this.props;
|
||||
setWindowTitle(i18n('debug.title'));
|
||||
}
|
||||
|
||||
callMopidy(e) {
|
||||
onBack = () => {
|
||||
const { history } = this.props;
|
||||
history.push('/settings');
|
||||
}
|
||||
|
||||
callMopidy = (e) => {
|
||||
const { mopidyActions: { debug } } = this.props;
|
||||
const { mopidy_call, mopidy_data } = this.state;
|
||||
e.preventDefault();
|
||||
this.props.mopidyActions.debug(this.state.mopidy_call, JSON.parse(this.state.mopidy_data));
|
||||
debug(mopidy_call, JSON.parse(mopidy_data));
|
||||
}
|
||||
|
||||
callPusher(e) {
|
||||
callPusher = (e) => {
|
||||
const { pusherActions: { debug } } = this.props;
|
||||
const { pusher_data } = this.state;
|
||||
e.preventDefault();
|
||||
this.props.pusherActions.debug(JSON.parse(this.state.pusher_data));
|
||||
debug(JSON.parse(pusher_data));
|
||||
}
|
||||
|
||||
callSnapcast(e) {
|
||||
callSnapcast = (e) => {
|
||||
const { snapcastActions: { debug } } = this.props;
|
||||
const { snapcast_data } = this.state;
|
||||
e.preventDefault();
|
||||
this.props.snapcastActions.debug(JSON.parse(this.state.snapcast_data));
|
||||
debug(JSON.parse(snapcast_data));
|
||||
}
|
||||
|
||||
toggleTestMode(e) {
|
||||
toggleTestMode = (e) => {
|
||||
const { uiActions: { set }, test_mode } = this.props;
|
||||
this.setState({ toggling_test_mode: true });
|
||||
this.props.uiActions.set({ test_mode: !this.props.test_mode });
|
||||
set({ test_mode: !test_mode });
|
||||
|
||||
// Wait a second to allow state to update, and then refresh
|
||||
setTimeout(location.reload(), 1000);
|
||||
}
|
||||
|
||||
render() {
|
||||
render = () => {
|
||||
const {
|
||||
uiActions,
|
||||
spotifyActions,
|
||||
history,
|
||||
test_mode,
|
||||
debug_response,
|
||||
debug_info,
|
||||
log_actions,
|
||||
log_mopidy,
|
||||
log_pusher,
|
||||
log_snapcast,
|
||||
access_token,
|
||||
} = this.props;
|
||||
const {
|
||||
toggling_test_mode,
|
||||
mopidy_call,
|
||||
mopidy_data,
|
||||
pusher_data,
|
||||
snapcast_data,
|
||||
} = this.state;
|
||||
|
||||
const options = (
|
||||
<a className="button button--discrete button--no-hover" onClick={(e) => this.props.history.push('/settings')}>
|
||||
<a className="button button--discrete button--no-hover" onClick={this.onBack}>
|
||||
<Icon name="keyboard_backspace" />
|
||||
Back
|
||||
<I18n path="actions.back" />
|
||||
</a>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="view debugger-view">
|
||||
<Header options={options} uiActions={this.props.uiActions}>
|
||||
<Header options={options} uiActions={uiActions}>
|
||||
<Icon name="settings" type="material" />
|
||||
Debug
|
||||
<I18n path="debug.title" />
|
||||
</Header>
|
||||
|
||||
<div className="content-wrapper">
|
||||
|
||||
<h4 className="underline">User interface</h4>
|
||||
<h4 className="underline"><I18n path="debug.ui.title" /></h4>
|
||||
<form>
|
||||
<div className="field checkbox">
|
||||
<div className="name">Test mode</div>
|
||||
<div className="input">
|
||||
{this.state.toggling_test_mode ? <span className="button button--working">Applying...</span> : (this.props.test_mode ? <span className="button button--destructive" onClick={(e) => this.toggleTestMode(e)}>Disable</span> : <span className="button button--primary" onClick={(e) => this.toggleTestMode(e)}>Enable</span>)}
|
||||
{toggling_test_mode && (
|
||||
<span className="button button--working">Applying...</span>
|
||||
)}
|
||||
{!toggling_test_mode && (
|
||||
<span
|
||||
className={`button button--${test_mode ? 'destructive' : 'default'}`}
|
||||
onClick={this.toggleTestMode}
|
||||
>
|
||||
<I18n path={`actions.${test_mode ? 'disable' : 'enable'}`} />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="field checkbox">
|
||||
<div className="name">Debug</div>
|
||||
<div className="name"><I18n path="debug.debug.title" /></div>
|
||||
<div className="input">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="debug_info"
|
||||
checked={this.props.debug_info}
|
||||
onChange={(e) => this.props.uiActions.set({ debug_info: !this.props.debug_info })}
|
||||
checked={debug_info}
|
||||
onChange={() => uiActions.set({ debug_info: !debug_info })}
|
||||
/>
|
||||
<span className="label">Show debug overlay</span>
|
||||
<span className="label"><I18n path="debug.debug.overlay" /></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field checkbox">
|
||||
<div className="name">Logging</div>
|
||||
<div className="name"><I18n path="debug.logging.title" /></div>
|
||||
<div className="input">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="log_actions"
|
||||
checked={this.props.log_actions}
|
||||
onChange={(e) => this.props.uiActions.set({ log_actions: !this.props.log_actions })}
|
||||
checked={log_actions}
|
||||
onChange={() => uiActions.set({ log_actions: !log_actions })}
|
||||
/>
|
||||
<span className="label">Log actions</span>
|
||||
<span className="label"><I18n path="debug.logging.actions" /></span>
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="log_mopidy"
|
||||
checked={this.props.log_mopidy}
|
||||
onChange={(e) => this.props.uiActions.set({ log_mopidy: !this.props.log_mopidy })}
|
||||
checked={log_mopidy}
|
||||
onChange={() => uiActions.set({ log_mopidy: !log_mopidy })}
|
||||
/>
|
||||
<span className="label">Log Mopidy</span>
|
||||
<span className="label"><I18n path="debug.logging.mopidy" /></span>
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="log_pusher"
|
||||
checked={this.props.log_pusher}
|
||||
onChange={(e) => this.props.uiActions.set({ log_pusher: !this.props.log_pusher })}
|
||||
checked={log_pusher}
|
||||
onChange={() => uiActions.set({ log_pusher: !log_pusher })}
|
||||
/>
|
||||
<span className="label">Log Pusher</span>
|
||||
<span className="label"><I18n path="debug.logging.pusher" /></span>
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="log_pusher"
|
||||
checked={this.props.log_snapcast}
|
||||
onChange={(e) => this.props.uiActions.set({ log_snapcast: !this.props.log_snapcast })}
|
||||
checked={log_snapcast}
|
||||
onChange={() => uiActions.set({ log_snapcast: !log_snapcast })}
|
||||
/>
|
||||
<span className="label">Log Snapcast</span>
|
||||
<span className="label"><I18n path="debug.logging.snapcast" /></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="name" />
|
||||
<div className="input">
|
||||
<a className="button button--default" onClick={(e) => this.props.uiActions.createNotification({ content: 'Test notification' })}>Create notification</a>
|
||||
<a className="button button--default" onClick={(e) => this.props.uiActions.startProcess('test_process', "Test process", {remaining: 68, total: 100})}>Create process notification</a>
|
||||
<a className="button button--default" onClick={(e) => this.props.pusherActions.request('test')}>Run test process</a>
|
||||
<a className="button button--default" onClick={(e) => this.props.uiActions.createNotification({ content: 'Test notification' })}><I18n path="debug.create_notification" /></a>
|
||||
<a className="button button--default" onClick={(e) => this.props.uiActions.startProcess('test_process', "Test process", {remaining: 68, total: 100})}><I18n path="debug.create_process" /></a>
|
||||
<a className="button button--default" onClick={(e) => this.props.pusherActions.request('test')}><I18n path="debug.run_test" /></a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<h4 className="underline">Spotify</h4>
|
||||
<label className="field">
|
||||
<div className="name">Access token</div>
|
||||
<div className="name"><I18n path="debug.access_token" /></div>
|
||||
<div className="input">
|
||||
<input
|
||||
type="text"
|
||||
onChange={(e) => this.props.spotifyActions.authorizationGranted({ access_token: e.target.value })}
|
||||
value={this.state.access_token}
|
||||
onChange={(e) => spotifyActions.authorizationGranted({ access_token: e.target.value })}
|
||||
value={access_token}
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<h4 className="underline">Mopidy</h4>
|
||||
<form onSubmit={(e) => this.callMopidy(e)}>
|
||||
<h4 className="underline"><I18n path="services.mopidy.title" /></h4>
|
||||
<form onSubmit={this.callMopidy}>
|
||||
<label className="field">
|
||||
<div className="name">Call</div>
|
||||
<div className="name"><I18n path="debug.call" /></div>
|
||||
<div className="input">
|
||||
<input
|
||||
type="text"
|
||||
onChange={(e) => this.setState({ mopidy_call: e.target.value })}
|
||||
value={this.state.mopidy_call}
|
||||
value={mopidy_call}
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
<label className="field">
|
||||
<div className="name">Data</div>
|
||||
<div className="name"><I18n path="debug.data" /></div>
|
||||
<div className="input">
|
||||
<textarea
|
||||
onChange={(e) => this.setState({ mopidy_data: e.target.value })}
|
||||
value={this.state.mopidy_data}
|
||||
value={mopidy_data}
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
@ -181,10 +226,10 @@ Back
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<h4 className="underline">Pusher</h4>
|
||||
<form onSubmit={(e) => this.callPusher(e)}>
|
||||
<h4 className="underline"><I18n path="services.pusher.title" /></h4>
|
||||
<form onSubmit={this.callPusher}>
|
||||
<label className="field">
|
||||
<div className="name">Examples</div>
|
||||
<div className="name"><I18n path="debug.examples" /></div>
|
||||
<div className="input">
|
||||
<select onChange={(e) => this.setState({ pusher_data: e.target.value })}>
|
||||
<option value='{"method":"get_config"}'>Get config</option>
|
||||
@ -202,44 +247,44 @@ Back
|
||||
</div>
|
||||
</label>
|
||||
<label className="field">
|
||||
<div className="name">Data</div>
|
||||
<div className="name"><I18n path="debug.data" /></div>
|
||||
<div className="input">
|
||||
<textarea
|
||||
onChange={(e) => this.setState({ pusher_data: e.target.value })}
|
||||
value={this.state.pusher_data}
|
||||
value={pusher_data}
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
<div className="field">
|
||||
<div className="name" />
|
||||
<div className="input">
|
||||
<button type="submit" className="button button--default">Send</button>
|
||||
<button type="submit" className="button button--default"><I18n path="actions.send" /></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<h4 className="underline">Snapcast</h4>
|
||||
<h4 className="underline"><I18n path="services.snapcast.title" /></h4>
|
||||
<form onSubmit={(e) => this.callSnapcast(e)}>
|
||||
<label className="field">
|
||||
<div className="name">Data</div>
|
||||
<div className="name"><I18n path="debug.data" /></div>
|
||||
<div className="input">
|
||||
<textarea
|
||||
onChange={(e) => this.setState({ snapcast_data: e.target.value })}
|
||||
value={this.state.snapcast_data}
|
||||
value={snapcast_data}
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
<div className="field">
|
||||
<div className="name" />
|
||||
<div className="input">
|
||||
<button type="submit" className="button button--default">Send</button>
|
||||
<button type="submit" className="button button--default"><I18n path="actions.send" /></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<h4 className="underline">Response</h4>
|
||||
<h4 className="underline"><I18n path="debug.response" /></h4>
|
||||
<pre>
|
||||
{ this.props.debug_response ? JSON.stringify(this.props.debug_response, null, 2) : null }
|
||||
{debug_response && JSON.stringify(debug_response, null, 2)}
|
||||
</pre>
|
||||
|
||||
</div>
|
||||
@ -248,7 +293,7 @@ Back
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => ({
|
||||
const mapStateToProps = (state) => ({
|
||||
connection_id: state.pusher.connection_id,
|
||||
access_token: (state.spotify.access_token ? state.spotify.access_token : ''),
|
||||
log_actions: (state.ui.log_actions ? state.ui.log_actions : false),
|
||||
|
||||
@ -9,7 +9,7 @@ import TrackList from '../components/TrackList';
|
||||
import Thumbnail from '../components/Thumbnail';
|
||||
import Parallax from '../components/Parallax';
|
||||
import NiceNumber from '../components/NiceNumber';
|
||||
import Dater from '../components/Dater';
|
||||
import { Dater } from '../components/Dater';
|
||||
import LazyLoadListener from '../components/LazyLoadListener';
|
||||
import FollowButton from '../components/Fields/FollowButton';
|
||||
import Loader from '../components/Loader';
|
||||
|
||||
@ -5,7 +5,7 @@ import Link from '../components/Link';
|
||||
import Icon from '../components/Icon';
|
||||
import Parallax from '../components/Parallax';
|
||||
import TrackList from '../components/TrackList';
|
||||
import Dater from '../components/Dater';
|
||||
import { Dater } from '../components/Dater';
|
||||
import LinksSentence from '../components/LinksSentence';
|
||||
import Thumbnail from '../components/Thumbnail';
|
||||
import Header from '../components/Header';
|
||||
@ -20,7 +20,7 @@ import {
|
||||
getFromUri,
|
||||
uriType,
|
||||
} from '../util/helpers';
|
||||
import { content, Content } from '../locale';
|
||||
import { i18n, I18n } from '../locale';
|
||||
|
||||
class Queue extends React.Component {
|
||||
constructor(props) {
|
||||
@ -40,7 +40,7 @@ class Queue extends React.Component {
|
||||
if (limit) {
|
||||
this.setState({ limit });
|
||||
}
|
||||
setWindowTitle(content('now_playing.title'));
|
||||
setWindowTitle(i18n('now_playing.title'));
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
@ -77,7 +77,7 @@ class Queue extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
loadMore() {
|
||||
loadMore = () => {
|
||||
const { limit, per_page } = this.state;
|
||||
const { location, history } = this.props;
|
||||
const new_limit = limit + per_page;
|
||||
@ -92,7 +92,7 @@ class Queue extends React.Component {
|
||||
history.replace({ state });
|
||||
}
|
||||
|
||||
removeTracks(track_indexes) {
|
||||
removeTracks = (track_indexes) => {
|
||||
const { queue_tracks, mopidyActions } = this.props;
|
||||
const tlids = [];
|
||||
for (let i = 0; i < track_indexes.length; i++) {
|
||||
@ -107,21 +107,23 @@ class Queue extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
playTrack(track) {
|
||||
this.props.mopidyActions.changeTrack(track.tlid);
|
||||
playTrack = (track) => {
|
||||
const { mopidyActions: { changeTrack } } = this.props;
|
||||
changeTrack(track.tlid);
|
||||
}
|
||||
|
||||
playTracks(tracks) {
|
||||
this.props.mopidyActions.changeTrack(tracks[0].tlid);
|
||||
playTracks = (tracks) => {
|
||||
const { mopidyActions: { changeTrack } } = this.props;
|
||||
changeTrack(tracks[0].tlid);
|
||||
}
|
||||
|
||||
reorderTracks(indexes, index) {
|
||||
this.props.mopidyActions.reorderTracklist(indexes, index);
|
||||
reorderTracks = (indexes, index) => {
|
||||
const { mopidyActions: { reorderTracklist } } = this.props;
|
||||
reorderTracklist(indexes, index);
|
||||
}
|
||||
|
||||
renderArtwork(image) {
|
||||
const { current_track } = this.props;
|
||||
const uri = (current_track && current_track.album && current_track.album.uri) ? current_track.album.uri : null;
|
||||
renderArtwork = (image) => {
|
||||
const { current_track: { album: { uri } = {} } = {} } = this.props;
|
||||
|
||||
if (!image) {
|
||||
return (
|
||||
@ -145,7 +147,7 @@ class Queue extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderAddedFrom() {
|
||||
renderAddedFrom = () => {
|
||||
const { added_from_uri } = this.props;
|
||||
if (!added_from_uri) return null;
|
||||
|
||||
@ -203,7 +205,7 @@ class Queue extends React.Component {
|
||||
<LinksSentence items={items} />
|
||||
{uri_type === 'radio' && (
|
||||
<span className="flag flag--blue">
|
||||
{content('now_playing.current_track.radio')}
|
||||
{i18n('now_playing.current_track.radio')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@ -211,7 +213,7 @@ class Queue extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
render = () => {
|
||||
const { current_track, queue_tracks, theme } = this.props;
|
||||
const { limit } = this.state;
|
||||
const total_queue_tracks = queue_tracks.length;
|
||||
@ -229,16 +231,16 @@ class Queue extends React.Component {
|
||||
{this.props.spotify_enabled && (
|
||||
<Link className="button button--no-hover" to="/queue/radio">
|
||||
<Icon name="radio" />
|
||||
{content('now_playing.context_actions.radio')}
|
||||
<I18n path="now_playing.context_actions.radio" />
|
||||
</Link>
|
||||
)}
|
||||
<Link className="button button--no-hover" to="/queue/history">
|
||||
<Icon name="history" />
|
||||
{content('now_playing.context_actions.history')}
|
||||
<I18n path="now_playing.context_actions.history" />
|
||||
</Link>
|
||||
<Link className="button button--no-hover" to="/queue/add-uri">
|
||||
<Icon name="playlist_add" />
|
||||
{content('now_playing.context_actions.add_uri')}
|
||||
<I18n path="now_playing.context_actions.add_uri" />
|
||||
</Link>
|
||||
</span>
|
||||
);
|
||||
@ -247,7 +249,7 @@ class Queue extends React.Component {
|
||||
<div className="view queue-view preserve-3d">
|
||||
<Header options={options} uiActions={this.props.uiActions}>
|
||||
<Icon name="play_arrow" type="material" />
|
||||
{content('now_playing.title')}
|
||||
<I18n path="now_playing.title" />
|
||||
</Header>
|
||||
{theme === 'dark' && <Parallax image={current_track_image} blur />}
|
||||
<div className="content-wrapper">
|
||||
@ -283,7 +285,7 @@ class Queue extends React.Component {
|
||||
<li>
|
||||
<a onClick={this.props.mopidyActions.shuffleTracklist}>
|
||||
<Icon name="shuffle" />
|
||||
{content('now_playing.current_track.shuffle')}
|
||||
<I18n path="now_playing.current_track.shuffle" />
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
@ -291,7 +293,7 @@ class Queue extends React.Component {
|
||||
<li>
|
||||
<a onClick={this.props.mopidyActions.clearTracklist}>
|
||||
<Icon name="delete_sweep" />
|
||||
{content('now_playing.current_track.clear')}
|
||||
<I18n path="now_playing.current_track.clear" />
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
@ -307,10 +309,10 @@ class Queue extends React.Component {
|
||||
track_context="queue"
|
||||
className="queue-track-list"
|
||||
tracks={tracks}
|
||||
removeTracks={(track_indexes) => this.removeTracks(track_indexes)}
|
||||
playTracks={(tracks) => this.playTracks(tracks)}
|
||||
playTrack={(track) => this.playTrack(track)}
|
||||
reorderTracks={(indexes, index) => this.reorderTracks(indexes, index)}
|
||||
removeTracks={this.removeTracks}
|
||||
playTracks={this.playTracks}
|
||||
playTrack={this.playTrack}
|
||||
reorderTracks={this.reorderTracks}
|
||||
/>
|
||||
</section>
|
||||
|
||||
@ -321,7 +323,7 @@ class Queue extends React.Component {
|
||||
: total_queue_tracks
|
||||
}
|
||||
showLoader={limit < total_queue_tracks}
|
||||
loadMore={() => this.loadMore()}
|
||||
loadMore={this.loadMore}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -20,7 +20,7 @@ import * as mopidyActions from '../services/mopidy/actions';
|
||||
import * as lastfmActions from '../services/lastfm/actions';
|
||||
import * as spotifyActions from '../services/spotify/actions';
|
||||
import { isHosted } from '../util/helpers';
|
||||
import { content } from '../locale';
|
||||
import { i18n } from '../locale';
|
||||
|
||||
class Settings extends React.Component {
|
||||
constructor(props) {
|
||||
@ -37,7 +37,7 @@ class Settings extends React.Component {
|
||||
|
||||
componentDidMount() {
|
||||
const { uiActions: { setWindowTitle } } = this.props;
|
||||
setWindowTitle(content('settings.title'));
|
||||
setWindowTitle(i18n('settings.title'));
|
||||
}
|
||||
|
||||
componentDidUpdate = () => {
|
||||
|
||||
@ -8,7 +8,7 @@ import Header from '../components/Header';
|
||||
import Thumbnail from '../components/Thumbnail';
|
||||
import LinksSentence from '../components/LinksSentence';
|
||||
import LastfmLoveButton from '../components/Fields/LastfmLoveButton';
|
||||
import Dater from '../components/Dater';
|
||||
import { Dater } from '../components/Dater';
|
||||
import SelectField from '../components/Fields/SelectField';
|
||||
import ContextMenuTrigger from '../components/ContextMenuTrigger';
|
||||
import Icon from '../components/Icon';
|
||||
|
||||
Reference in New Issue
Block a user