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