diff --git a/src/js/App.js b/src/js/App.js index af78d3e6..301ad58c 100755 --- a/src/js/App.js +++ b/src/js/App.js @@ -1,63 +1,63 @@ -import React from "react"; -import { bindActionCreators } from "redux"; -import { Route, Switch } from "react-router-dom"; -import { connect } from "react-redux"; -import ReactGA from "react-ga"; +import React from 'react'; +import { bindActionCreators } from 'redux'; +import { Route, Switch } from 'react-router-dom'; +import { connect } from 'react-redux'; +import ReactGA from 'react-ga'; -import Sidebar from "./components/Sidebar"; -import PlaybackControls from "./components/PlaybackControls"; -import ContextMenu from "./components/ContextMenu"; -import Dragger from "./components/Dragger"; -import Notifications from "./components/Notifications"; -import ResizeListener from "./components/ResizeListener"; -import Hotkeys from "./components/Hotkeys"; -import DebugInfo from "./components/DebugInfo"; -import ErrorMessage from "./components/ErrorMessage"; +import Sidebar from './components/Sidebar'; +import PlaybackControls from './components/PlaybackControls'; +import ContextMenu from './components/ContextMenu'; +import Dragger from './components/Dragger'; +import Notifications from './components/Notifications'; +import ResizeListener from './components/ResizeListener'; +import Hotkeys from './components/Hotkeys'; +import DebugInfo from './components/DebugInfo'; +import ErrorMessage from './components/ErrorMessage'; -import Album from "./views/Album"; -import Artist from "./views/Artist"; -import Playlist from "./views/Playlist"; -import User from "./views/User"; -import Track from "./views/Track"; -import Queue from "./views/Queue"; -import QueueHistory from "./views/QueueHistory"; -import Debug from "./views/Debug"; -import Search from "./views/Search"; -import Settings from "./views/Settings"; +import Album from './views/Album'; +import Artist from './views/Artist'; +import Playlist from './views/Playlist'; +import User from './views/User'; +import Track from './views/Track'; +import Queue from './views/Queue'; +import QueueHistory from './views/QueueHistory'; +import Debug from './views/Debug'; +import Search from './views/Search'; +import Settings from './views/Settings'; -import DiscoverRecommendations from "./views/discover/DiscoverRecommendations"; -import DiscoverFeatured from "./views/discover/DiscoverFeatured"; -import DiscoverCategories from "./views/discover/DiscoverCategories"; -import DiscoverCategory from "./views/discover/DiscoverCategory"; -import DiscoverNewReleases from "./views/discover/DiscoverNewReleases"; +import DiscoverRecommendations from './views/discover/DiscoverRecommendations'; +import DiscoverFeatured from './views/discover/DiscoverFeatured'; +import DiscoverCategories from './views/discover/DiscoverCategories'; +import DiscoverCategory from './views/discover/DiscoverCategory'; +import DiscoverNewReleases from './views/discover/DiscoverNewReleases'; -import LibraryArtists from "./views/library/LibraryArtists"; -import LibraryAlbums from "./views/library/LibraryAlbums"; -import LibraryTracks from "./views/library/LibraryTracks"; -import LibraryPlaylists from "./views/library/LibraryPlaylists"; -import LibraryBrowse from "./views/library/LibraryBrowse"; -import LibraryBrowseDirectory from "./views/library/LibraryBrowseDirectory"; +import LibraryArtists from './views/library/LibraryArtists'; +import LibraryAlbums from './views/library/LibraryAlbums'; +import LibraryTracks from './views/library/LibraryTracks'; +import LibraryPlaylists from './views/library/LibraryPlaylists'; +import LibraryBrowse from './views/library/LibraryBrowse'; +import LibraryBrowseDirectory from './views/library/LibraryBrowseDirectory'; -import EditPlaylist from "./views/modals/EditPlaylist"; -import CreatePlaylist from "./views/modals/CreatePlaylist"; -import EditRadio from "./views/modals/EditRadio"; -import AddToQueue from "./views/modals/AddToQueue"; -import InitialSetup from "./views/modals/InitialSetup"; -import KioskMode from "./views/modals/KioskMode"; -import ShareConfiguration from "./views/modals/ShareConfiguration"; -import AddToPlaylist from "./views/modals/AddToPlaylist"; -import ImageZoom from "./views/modals/ImageZoom"; -import EditCommand from "./views/modals/EditCommand"; +import EditPlaylist from './views/modals/EditPlaylist'; +import CreatePlaylist from './views/modals/CreatePlaylist'; +import EditRadio from './views/modals/EditRadio'; +import AddToQueue from './views/modals/AddToQueue'; +import InitialSetup from './views/modals/InitialSetup'; +import KioskMode from './views/modals/KioskMode'; +import ShareConfiguration from './views/modals/ShareConfiguration'; +import AddToPlaylist from './views/modals/AddToPlaylist'; +import ImageZoom from './views/modals/ImageZoom'; +import EditCommand from './views/modals/EditCommand'; -import * as helpers from "./helpers"; -import * as coreActions from "./services/core/actions"; -import * as uiActions from "./services/ui/actions"; -import * as pusherActions from "./services/pusher/actions"; -import * as mopidyActions from "./services/mopidy/actions"; -import * as spotifyActions from "./services/spotify/actions"; -import * as lastfmActions from "./services/lastfm/actions"; -import * as geniusActions from "./services/genius/actions"; -import * as snapcastActions from "./services/snapcast/actions"; +import * as helpers from './helpers'; +import * as coreActions from './services/core/actions'; +import * as uiActions from './services/ui/actions'; +import * as pusherActions from './services/pusher/actions'; +import * as mopidyActions from './services/mopidy/actions'; +import * as spotifyActions from './services/spotify/actions'; +import * as lastfmActions from './services/lastfm/actions'; +import * as geniusActions from './services/genius/actions'; +import * as snapcastActions from './services/snapcast/actions'; export class App extends React.Component { constructor(props) { @@ -68,27 +68,27 @@ export class App extends React.Component { componentWillMount() { window.addEventListener( - "beforeinstallprompt", + 'beforeinstallprompt', this.handleInstallPrompt, - false + false, ); - window.addEventListener("focus", this.handleFocusAndBlur, false); - window.addEventListener("blur", this.handleFocusAndBlur, false); + window.addEventListener('focus', this.handleFocusAndBlur, false); + window.addEventListener('blur', this.handleFocusAndBlur, false); } componentWillUnmount() { window.removeEventListener( - "beforeinstallprompt", + 'beforeinstallprompt', this.handleInstallPrompt, - false + false, ); - window.removeEventListener("focus", this.handleFocusAndBlur, false); - window.removeEventListener("blur", this.handleFocusAndBlur, false); + window.removeEventListener('focus', this.handleFocusAndBlur, false); + window.removeEventListener('blur', this.handleFocusAndBlur, false); } componentDidMount() { if (this.props.allow_reporting) { - ReactGA.initialize("UA-64701652-3"); + ReactGA.initialize('UA-64701652-3'); } // Fire up our services @@ -97,10 +97,10 @@ export class App extends React.Component { this.props.coreActions.getBroadcasts(); // Check for url-parsed configuration values - var url_vars = this.props.location.query; + const url_vars = this.props.location.query; if (url_vars) { - var has_values = false; - var values = {}; + let has_values = false; + const values = {}; if (url_vars.host !== undefined) { has_values = true; values.host = url_vars.host; @@ -115,14 +115,14 @@ export class App extends React.Component { // Allow 100ms for the action above to complete before we re-route setTimeout(() => { - this.props.history.push("/"); + this.props.history.push('/'); }, 100); } } // show initial setup if required if (!this.props.initial_setup_complete) { - this.props.history.push("/initial-setup"); + this.props.history.push('/initial-setup'); } } @@ -138,7 +138,7 @@ export class App extends React.Component { // If the location has a "scroll_position" state variable, scroll to it. // This is invisibly injected to the history by the Link component when navigating, so // hitting back in the browser allows us to restore the position - var location_state = this.props.location.state + const location_state = this.props.location.state ? this.props.location.state : {}; if (location_state.scroll_position) { @@ -166,45 +166,45 @@ export class App extends React.Component { * quota significantly: https://developers.google.com/web/updates/2017/03/background_tabs * * @param e Event - **/ + * */ handleFocusAndBlur(e) { this.props.uiActions.setWindowFocus(document.hasFocus()); } handleInstallPrompt(e) { e.preventDefault(); - console.log("Install prompt detected"); + console.log('Install prompt detected'); this.props.uiActions.installPrompt(e); } render() { - let className = this.props.theme + "-theme app-inner"; + let className = `${this.props.theme}-theme app-inner`; if (this.props.wide_scrollbar_enabled) { - className += " wide-scrollbar"; + className += ' wide-scrollbar'; } if (this.props.dragging) { - className += " dragging"; + className += ' dragging'; } if (this.props.sidebar_open) { - className += " sidebar-open"; + className += ' sidebar-open'; } if (this.props.touch_dragging) { - className += " touch-dragging"; - console.log("TOUCH DRAGGING"); + className += ' touch-dragging'; + console.log('TOUCH DRAGGING'); } if (this.props.context_menu) { - className += " context-menu-open"; + className += ' context-menu-open'; } if (this.props.slim_mode) { - className += " slim-mode"; + className += ' slim-mode'; } if (this.props.smooth_scrolling_enabled) { - className += " smooth-scrolling-enabled"; + className += ' smooth-scrolling-enabled'; } if (helpers.isTouchDevice()) { - className += " touch"; + className += ' touch'; } else { - className += " notouch"; + className += ' notouch'; } return ( @@ -348,39 +348,35 @@ export class App extends React.Component { } } -const mapStateToProps = (state, ownProps) => { - return { - theme: state.ui.theme, - wide_scrollbar_enabled: state.ui.wide_scrollbar_enabled, - smooth_scrolling_enabled: state.ui.smooth_scrolling_enabled, - hotkeys_enabled: state.ui.hotkeys_enabled, - allow_reporting: state.ui.allow_reporting, - touch_dragging: state.ui.touch_dragging, - initial_setup_complete: state.ui.initial_setup_complete, - slim_mode: state.ui.slim_mode, - mopidy_connected: state.mopidy.connected, - spotify_authorized: state.spotify.authorization, - sidebar_open: state.ui.sidebar_open, - dragging: state.ui.dragger && state.ui.dragger.active, - context_menu: state.ui.context_menu, - debug_info: state.ui.debug_info - }; -}; +const mapStateToProps = (state, ownProps) => ({ + theme: state.ui.theme, + wide_scrollbar_enabled: state.ui.wide_scrollbar_enabled, + smooth_scrolling_enabled: state.ui.smooth_scrolling_enabled, + hotkeys_enabled: state.ui.hotkeys_enabled, + allow_reporting: state.ui.allow_reporting, + touch_dragging: state.ui.touch_dragging, + initial_setup_complete: state.ui.initial_setup_complete, + slim_mode: state.ui.slim_mode, + mopidy_connected: state.mopidy.connected, + spotify_authorized: state.spotify.authorization, + sidebar_open: state.ui.sidebar_open, + dragging: state.ui.dragger && state.ui.dragger.active, + context_menu: state.ui.context_menu, + debug_info: state.ui.debug_info, +}); -const mapDispatchToProps = dispatch => { - return { - coreActions: bindActionCreators(coreActions, dispatch), - uiActions: bindActionCreators(uiActions, dispatch), - pusherActions: bindActionCreators(pusherActions, dispatch), - mopidyActions: bindActionCreators(mopidyActions, dispatch), - spotifyActions: bindActionCreators(spotifyActions, dispatch), - lastfmActions: bindActionCreators(lastfmActions, dispatch), - geniusActions: bindActionCreators(geniusActions, dispatch), - snapcastActions: bindActionCreators(snapcastActions, dispatch) - }; -}; +const mapDispatchToProps = (dispatch) => ({ + coreActions: bindActionCreators(coreActions, dispatch), + uiActions: bindActionCreators(uiActions, dispatch), + pusherActions: bindActionCreators(pusherActions, dispatch), + mopidyActions: bindActionCreators(mopidyActions, dispatch), + spotifyActions: bindActionCreators(spotifyActions, dispatch), + lastfmActions: bindActionCreators(lastfmActions, dispatch), + geniusActions: bindActionCreators(geniusActions, dispatch), + snapcastActions: bindActionCreators(snapcastActions, dispatch), +}); export default connect( mapStateToProps, - mapDispatchToProps + mapDispatchToProps, )(App); diff --git a/src/js/components/AlbumGrid.js b/src/js/components/AlbumGrid.js index d4673462..0c00afc7 100755 --- a/src/js/components/AlbumGrid.js +++ b/src/js/components/AlbumGrid.js @@ -9,66 +9,58 @@ import * as lastfmActions from '../services/lastfm/actions'; import GridItem from './GridItem'; -class AlbumGrid extends React.Component{ +class AlbumGrid extends React.Component { + constructor(props) { + super(props); + } - constructor(props){ - super(props) - } + handleContextMenu(e, item) { + e.preventDefault(); + const data = { + e, + context: 'album', + uris: [item.uri], + items: [item], + tracklist_uri: item.uri, + }; + this.props.uiActions.showContextMenu(data); + } - handleContextMenu(e,item){ - e.preventDefault() - var data = { - e: e, - context: 'album', - uris: [item.uri], - items: [item], - tracklist_uri: item.uri - } - this.props.uiActions.showContextMenu(data) - } + render() { + if (this.props.albums) { + let className = 'grid grid--albums'; + if (this.props.className) className += ` ${this.props.className}`; + if (this.props.single_row) className += ' grid--single-row'; + if (this.props.mini) className += ' grid--mini'; - render(){ - if (this.props.albums){ - var className = "grid grid--albums"; - if (this.props.className) className += ' '+this.props.className; - if (this.props.single_row) className += ' grid--single-row'; - if (this.props.mini) className += ' grid--mini'; - - return ( -
- { - this.props.albums.map(album => { - return ( - this.handleContextMenu(e,album)} - /> - )} - ) + return ( +
+ { + this.props.albums.map((album) => ( + this.handleContextMenu(e, album)} + /> + )) } -
- ); - } - return null; - } +
+ ); + } + return null; + } } -const mapStateToProps = (state, ownProps) => { - return { - artists: state.core.artists - } -} +const mapStateToProps = (state, ownProps) => ({ + artists: state.core.artists, +}); -const mapDispatchToProps = (dispatch) => { - return { - uiActions: bindActionCreators(uiActions, dispatch), - lastfmActions: bindActionCreators(lastfmActions, dispatch) - } -} - -export default connect(mapStateToProps, mapDispatchToProps)(AlbumGrid) +const mapDispatchToProps = (dispatch) => ({ + uiActions: bindActionCreators(uiActions, dispatch), + lastfmActions: bindActionCreators(lastfmActions, dispatch), +}); +export default connect(mapStateToProps, mapDispatchToProps)(AlbumGrid); diff --git a/src/js/components/ArtistGrid.js b/src/js/components/ArtistGrid.js index 345ff903..dada755c 100755 --- a/src/js/components/ArtistGrid.js +++ b/src/js/components/ArtistGrid.js @@ -11,70 +11,63 @@ import * as uiActions from '../services/ui/actions'; import * as lastfmActions from '../services/lastfm/actions'; import * as discogsActions from '../services/discogs/actions'; -class ArtistGrid extends React.Component{ +class ArtistGrid extends React.Component { + constructor(props) { + super(props); + } - constructor(props){ - super(props); - } + handleContextMenu(e, item) { + e.preventDefault(); + const data = { + e, + context: 'artist', + uris: [item.uri], + items: [item], + }; + this.props.uiActions.showContextMenu(data); + } - handleContextMenu(e,item){ - e.preventDefault(); - var data = { - e: e, - context: 'artist', - uris: [item.uri], - items: [item] - } - this.props.uiActions.showContextMenu(data) - } + render() { + if (this.props.artists) { + let className = 'grid grid--artists'; + if (this.props.className) className += ` ${this.props.className}`; + if (this.props.single_row) className += ' grid--single-row'; + if (this.props.mini) className += ' grid--mini'; - render(){ - if (this.props.artists){ - var className = "grid grid--artists"; - if (this.props.className) className += ' '+this.props.className; - if (this.props.single_row) className += ' grid--single-row'; - if (this.props.mini) className += ' grid--mini'; - - return ( -
- { - this.props.artists.map(item => { - var artist = helpers.collate(item, {albums: this.props.albums}); - return ( - {this.props.history.push('/artist/'+encodeURIComponent(artist.uri))}} - discogsActions={this.props.discogsActions} - lastfmActions={this.props.lastfmActions} - onContextMenu={e => this.handleContextMenu(e,artist)} - /> - ) - } - ) + return ( +
+ { + this.props.artists.map((item) => { + const artist = helpers.collate(item, { albums: this.props.albums }); + return ( + { this.props.history.push(`/artist/${encodeURIComponent(artist.uri)}`); }} + discogsActions={this.props.discogsActions} + lastfmActions={this.props.lastfmActions} + onContextMenu={(e) => this.handleContextMenu(e, artist)} + /> + ); + }) } -
- ); - } - return null; - } +
+ ); + } + return null; + } } -const mapStateToProps = (state, ownProps) => { - return { - albums: state.core.albums - } -} +const mapStateToProps = (state, ownProps) => ({ + albums: state.core.albums, +}); -const mapDispatchToProps = (dispatch) => { - return { - uiActions: bindActionCreators(uiActions, dispatch), - lastfmActions: bindActionCreators(lastfmActions, dispatch), - discogsActions: bindActionCreators(discogsActions, dispatch), - } -} - -export default connect(mapStateToProps, mapDispatchToProps)(ArtistGrid) +const mapDispatchToProps = (dispatch) => ({ + uiActions: bindActionCreators(uiActions, dispatch), + lastfmActions: bindActionCreators(lastfmActions, dispatch), + discogsActions: bindActionCreators(discogsActions, dispatch), +}); +export default connect(mapStateToProps, mapDispatchToProps)(ArtistGrid); diff --git a/src/js/components/ArtistSentence.js b/src/js/components/ArtistSentence.js index ea49fb27..5daf576b 100755 --- a/src/js/components/ArtistSentence.js +++ b/src/js/components/ArtistSentence.js @@ -1,45 +1,43 @@ -import React, { memo } from 'react' -import URILink from './URILink' +import React, { memo } from 'react'; +import URILink from './URILink'; -export default memo((props) => { +export default memo((props) => { + if (!props.artists) { + return -; + } - if (!props.artists){ - return -; - } + return ( + + { + props.artists.map((artist, index) => { + if (!artist) { + return -; + } - return ( - - { - props.artists.map((artist, index) => { + let separator = null; + if (index == props.artists.length - 2) { + separator = ' and '; + } else if (index < props.artists.length - 2) { + separator = ', '; + } - if (!artist){ - return -; - } + if (!artist.name) { + var content = -; + } else if (!artist.uri || props.nolinks) { + var content = { artist.name }; + } else { + var content = { artist.name }; + } - var separator = null; - if (index == props.artists.length - 2){ - separator = ' and '; - } else if (index < props.artists.length - 2){ - separator = ', '; - } - - if (!artist.name){ - var content = - - } else if (!artist.uri || props.nolinks){ - var content = { artist.name } - } else { - var content = { artist.name } - } - - return ( - - { content } - { separator } - - ); + return ( + + { content } + { separator } + + ); }) } - - ); -}); \ No newline at end of file + + ); +}); diff --git a/src/js/components/CategoryGrid.js b/src/js/components/CategoryGrid.js index c2189bd4..76dd587a 100755 --- a/src/js/components/CategoryGrid.js +++ b/src/js/components/CategoryGrid.js @@ -1,32 +1,28 @@ -import React, { memo } from 'react' -import GridItem from './GridItem' +import React, { memo } from 'react'; +import GridItem from './GridItem'; -export default memo((props) => { - - if (!props.categories){ - return null; - } +export default memo((props) => { + if (!props.categories) { + return null; + } - let className = "grid grid--tiles"; - if (props.className) className += ' '+props.className; - if (props.mini) className += ' grid--mini'; + let className = 'grid grid--tiles'; + if (props.className) className += ` ${props.className}`; + if (props.mini) className += ' grid--mini'; - return ( -
- { - props.categories.map(category => { - return ( - - ) - }) + return ( +
+ { + props.categories.map((category) => ( + + )) } -
- ); +
+ ); }); - diff --git a/src/js/components/ContextMenu.js b/src/js/components/ContextMenu.js index 516bb43a..6f293ac4 100755 --- a/src/js/components/ContextMenu.js +++ b/src/js/components/ContextMenu.js @@ -18,851 +18,853 @@ import * as mopidyActions from '../services/mopidy/actions'; import * as lastfmActions from '../services/lastfm/actions'; import * as spotifyActions from '../services/spotify/actions'; -class ContextMenu extends React.Component{ +class ContextMenu extends React.Component { + constructor(props) { + super(props); + this.state = { + submenu: null, + }; + this.handleScroll = this.handleScroll.bind(this); + this.handleMouseDown = this.handleMouseDown.bind(this); + this.handleTouchStart = this.handleTouchStart.bind(this); + } - constructor(props){ - super(props) - this.state = { - submenu: null - } - this.handleScroll = this.handleScroll.bind(this); - this.handleMouseDown = this.handleMouseDown.bind(this); - this.handleTouchStart = this.handleTouchStart.bind(this); - } + componentDidMount() { + window.addEventListener('scroll', this.handleScroll, false); + window.addEventListener('mousedown', this.handleMouseDown, false); + window.addEventListener('touchstart', this.handleTouchStart, false); + } - componentDidMount(){ - window.addEventListener("scroll", this.handleScroll, false); - window.addEventListener("mousedown", this.handleMouseDown, false); - window.addEventListener("touchstart", this.handleTouchStart, false); - } + componentWillUnmount() { + window.removeEventListener('scroll', this.handleScroll, false); + window.removeEventListener('mousedown', this.handleMouseDown, false); + window.removeEventListener('touchstart', this.handleTouchStart, false); + } - componentWillUnmount(){ - window.removeEventListener("scroll", this.handleScroll, false); - window.removeEventListener("mousedown", this.handleMouseDown, false); - window.removeEventListener("touchstart", this.handleTouchStart, false); - } + componentWillReceiveProps(nextProps) { + // if we've been given a menu object (ie activated) when we didn't have one prior + if (nextProps.menu && !this.props.menu) { + this.setState({ submenu: null }); - componentWillReceiveProps(nextProps){ + const context = this.getContext(nextProps); - // if we've been given a menu object (ie activated) when we didn't have one prior - if (nextProps.menu && !this.props.menu){ - this.setState({ submenu: null }); + // if we're able to be in the library, run a check + if (nextProps.spotify_authorized && context.source == 'spotify') { + switch (nextProps.menu.context) { + case 'artist': + case 'album': + case 'playlist': + case 'editable-playlist': + case 'track': + case 'playlist-track': + case 'editable-playlist-track': + case 'queue-track': + this.props.spotifyActions.following(nextProps.menu.items[0].uri); + break; + } + } - var context = this.getContext(nextProps); + // if we're able to be in the LastFM library, run a check + if (nextProps.lastfm_authorized && context.is_track && context.items_count == 1) { + if (nextProps.menu.items[0].uri && this.props.tracks[nextProps.menu.items[0].uri] !== undefined && this.props.tracks[nextProps.menu.items[0].uri].userloved === undefined) { + this.props.lastfmActions.getTrack(nextProps.menu.items[0].uri); + } + } + } + } - // if we're able to be in the library, run a check - if (nextProps.spotify_authorized && context.source == 'spotify'){ - switch (nextProps.menu.context){ - case 'artist': - case 'album': - case 'playlist': - case 'editable-playlist': - case 'track': - case 'playlist-track': - case 'editable-playlist-track': - case 'queue-track': - this.props.spotifyActions.following(nextProps.menu.items[0].uri) - break - } - } + handleScroll(e) { + if (this.props.menu) { + this.props.uiActions.hideContextMenu(); + } + } - // if we're able to be in the LastFM library, run a check - if (nextProps.lastfm_authorized && context.is_track && context.items_count == 1){ - if (nextProps.menu.items[0].uri && this.props.tracks[nextProps.menu.items[0].uri] !== undefined && this.props.tracks[nextProps.menu.items[0].uri].userloved === undefined){ - this.props.lastfmActions.getTrack(nextProps.menu.items[0].uri); - } - } - } - } + handleMouseDown(e) { + // if we click (touch or mouse) outside of the context menu or context menu trigger, kill it + if ($(e.target).closest('.context-menu').length <= 0 && $(e.target).closest('.context-menu-trigger').length <= 0) { + this.props.uiActions.hideContextMenu(); + } + } - handleScroll(e){ - if (this.props.menu){ - this.props.uiActions.hideContextMenu(); - } - } + handleTouchStart(e) { + // if we click (touch or mouse) outside of the context menu or context menu trigger, kill it + if ($(e.target).closest('.context-menu').length <= 0 && $(e.target).closest('.context-menu-trigger').length <= 0) { + this.props.uiActions.hideContextMenu(); + } + } - handleMouseDown(e){ - // if we click (touch or mouse) outside of the context menu or context menu trigger, kill it - if ($(e.target).closest('.context-menu').length <= 0 && $(e.target).closest('.context-menu-trigger').length <= 0){ - this.props.uiActions.hideContextMenu(); - } - } + getContext(props = this.props) { + const context = { + name: null, + nice_name: 'Unknown', + is_track: false, + }; - handleTouchStart(e){ + if (props.menu && props.menu.context) { + context.name = props.menu.context; + context.nice_name = props.menu.context; - // if we click (touch or mouse) outside of the context menu or context menu trigger, kill it - if ($(e.target).closest('.context-menu').length <= 0 && $(e.target).closest('.context-menu-trigger').length <= 0){ - this.props.uiActions.hideContextMenu(); - } - } + // handle ugly labels + switch (props.menu.context) { + case 'playlist': + case 'editable-playlist': + context.nice_name = 'playlist'; + break; - getContext(props = this.props){ - var context = { - name: null, - nice_name: 'Unknown', - is_track: false - } + case 'track': + case 'queue-track': + case 'playlist-track': + case 'editable-playlist-track': + context.nice_name = 'track'; + context.is_track = true; + break; + } - if (props.menu && props.menu.context){ - context.name = props.menu.context; - context.nice_name = props.menu.context; + // Consider the object(s) themselves + // We can only really accommodate the first item. The only instances where + // there is multiple is tracklists, when they're all of the same source (except search?) + if (props.menu.items && props.menu.items.length > 0) { + const item = props.menu.items[0]; + context.item = item; + context.items_count = props.menu.items.length; + context.source = helpers.uriSource(item.uri); + context.type = helpers.uriType(item.uri); + context.in_library = this.inLibrary(item); + context.is_loved = this.isLoved(item); + } + } - // handle ugly labels - switch (props.menu.context){ - case 'playlist': - case 'editable-playlist': - context.nice_name = 'playlist'; - break; + return context; + } - case 'track': - case 'queue-track': - case 'playlist-track': - case 'editable-playlist-track': - context.nice_name = 'track'; - context.is_track = true; - break; - } + inLibrary(item = null) { + if (!item) { + return false; + } - // Consider the object(s) themselves - // We can only really accommodate the first item. The only instances where - // there is multiple is tracklists, when they're all of the same source (except search?) - if (props.menu.items && props.menu.items.length > 0){ - var item = props.menu.items[0] - context.item = item; - context.items_count = props.menu.items.length; - context.source = helpers.uriSource(item.uri); - context.type = helpers.uriType(item.uri); - context.in_library = this.inLibrary(item); - context.is_loved = this.isLoved(item); - } - } + switch (helpers.uriType(item.uri)) { + case 'artist': + return (this.props.spotify_library_artists && this.props.spotify_library_artists.indexOf(item.uri) > -1); + break; + case 'album': + return (this.props.spotify_library_albums && this.props.spotify_library_albums.indexOf(item.uri) > -1); + break; + case 'playlist': + return (this.props.spotify_library_playlists && this.props.spotify_library_playlists.indexOf(item.uri) > -1); + break; + case 'track': + return (this.props.spotify_library_tracks && this.props.spotify_library_tracks.indexOf(item.uri) > -1); + break; + } + return false; + } - return context - } - - inLibrary(item = null){ - if (!item){ - return false; - } - - switch (helpers.uriType(item.uri)){ - case 'artist': - return (this.props.spotify_library_artists && this.props.spotify_library_artists.indexOf(item.uri) > -1); - break; - case 'album': - return (this.props.spotify_library_albums && this.props.spotify_library_albums.indexOf(item.uri) > -1); - break; - case 'playlist': - return (this.props.spotify_library_playlists && this.props.spotify_library_playlists.indexOf(item.uri) > -1); - break; - case 'track': - return (this.props.spotify_library_tracks && this.props.spotify_library_tracks.indexOf(item.uri) > -1); - break; - } - return false; - } - - /** + /** * TODO: Currently the select track keys are the only details available. We need * the actual track object reference (including name and artists) to getTrack from LastFM - **/ - isLoved(item = null){ - if (!item){ - return false - } + * */ + isLoved(item = null) { + if (!item) { + return false; + } - if (this.props.tracks[item.uri] === undefined){ - return false; - } - var track = this.props.tracks[item.uri]; + if (this.props.tracks[item.uri] === undefined) { + return false; + } + const track = this.props.tracks[item.uri]; - return (track.userloved !== undefined && track.userloved == "1"); - } + return (track.userloved !== undefined && track.userloved == '1'); + } - canBeInLibrary(){ - if (!this.props.spotify_authorized){ - return false - } - return (helpers.uriSource(this.props.menu.items[0].uri) == 'spotify') - } + canBeInLibrary() { + if (!this.props.spotify_authorized) { + return false; + } + return (helpers.uriSource(this.props.menu.items[0].uri) == 'spotify'); + } - toggleInLibrary(e, in_library){ - this.props.uiActions.hideContextMenu() - if (in_library){ - this.props.spotifyActions.following(this.props.menu.items[0].uri, 'DELETE') - } else { - this.props.spotifyActions.following(this.props.menu.items[0].uri, 'PUT') - } - } + toggleInLibrary(e, in_library) { + this.props.uiActions.hideContextMenu(); + if (in_library) { + this.props.spotifyActions.following(this.props.menu.items[0].uri, 'DELETE'); + } else { + this.props.spotifyActions.following(this.props.menu.items[0].uri, 'PUT'); + } + } - playQueueItem(e){ - this.props.uiActions.hideContextMenu() - var tracks = this.props.menu.items; - this.props.mopidyActions.changeTrack(tracks[0].tlid); - } + playQueueItem(e) { + this.props.uiActions.hideContextMenu(); + const tracks = this.props.menu.items; + this.props.mopidyActions.changeTrack(tracks[0].tlid); + } - removeFromQueue(e){ - this.props.uiActions.hideContextMenu() - var tracks = this.props.menu.items; - var tracks_tlids = []; - for(var i = 0; i < tracks.length; i++){ - tracks_tlids.push(tracks[i].tlid ); - } - this.props.mopidyActions.removeTracks(tracks_tlids); - } + removeFromQueue(e) { + this.props.uiActions.hideContextMenu(); + const tracks = this.props.menu.items; + const tracks_tlids = []; + for (let i = 0; i < tracks.length; i++) { + tracks_tlids.push(tracks[i].tlid); + } + this.props.mopidyActions.removeTracks(tracks_tlids); + } - playURIs(e){ - this.props.uiActions.hideContextMenu(); - this.props.mopidyActions.playURIs(this.props.menu.uris, this.props.menu.tracklist_uri); - } + playURIs(e) { + this.props.uiActions.hideContextMenu(); + this.props.mopidyActions.playURIs(this.props.menu.uris, this.props.menu.tracklist_uri); + } - playPlaylist(e){ - this.props.uiActions.hideContextMenu(); - this.props.mopidyActions.playPlaylist(this.props.menu.uris[0]); - } + playPlaylist(e) { + this.props.uiActions.hideContextMenu(); + this.props.mopidyActions.playPlaylist(this.props.menu.uris[0]); + } - enqueuePlaylist(e, play_next = false){ - this.props.uiActions.hideContextMenu(); - this.props.mopidyActions.enqueuePlaylist(this.props.menu.uris[0], play_next); - } + enqueuePlaylist(e, play_next = false) { + this.props.uiActions.hideContextMenu(); + this.props.mopidyActions.enqueuePlaylist(this.props.menu.uris[0], play_next); + } - shufflePlayPlaylist(e){ - this.props.uiActions.hideContextMenu(); - this.props.mopidyActions.playPlaylist(this.props.menu.uris[0], true); - } + shufflePlayPlaylist(e) { + this.props.uiActions.hideContextMenu(); + this.props.mopidyActions.playPlaylist(this.props.menu.uris[0], true); + } - playArtistTopTracks(e){ - this.props.uiActions.hideContextMenu(); - this.props.spotifyActions.playArtistTopTracks(this.props.menu.uris[0]); - } + playArtistTopTracks(e) { + this.props.uiActions.hideContextMenu(); + this.props.spotifyActions.playArtistTopTracks(this.props.menu.uris[0]); + } - addToQueue(e, play_next = false){ - this.props.uiActions.hideContextMenu(); - this.props.mopidyActions.enqueueURIs(this.props.menu.uris, this.props.menu.tracklist_uri, play_next); - } + addToQueue(e, play_next = false) { + this.props.uiActions.hideContextMenu(); + this.props.mopidyActions.enqueueURIs(this.props.menu.uris, this.props.menu.tracklist_uri, play_next); + } - addTracksToPlaylist(e, playlist_uri){ - this.props.uiActions.hideContextMenu(); - this.props.coreActions.addTracksToPlaylist(playlist_uri, this.props.menu.uris); - } + addTracksToPlaylist(e, playlist_uri) { + this.props.uiActions.hideContextMenu(); + this.props.coreActions.addTracksToPlaylist(playlist_uri, this.props.menu.uris); + } - toggleLoved(e, is_loved){ - this.props.uiActions.hideContextMenu(); - if (is_loved){ - this.props.lastfmActions.unloveTrack(this.props.menu.items[0].uri); - } else { - this.props.lastfmActions.loveTrack(this.props.menu.items[0].uri); - } - } + toggleLoved(e, is_loved) { + this.props.uiActions.hideContextMenu(); + if (is_loved) { + this.props.lastfmActions.unloveTrack(this.props.menu.items[0].uri); + } else { + this.props.lastfmActions.loveTrack(this.props.menu.items[0].uri); + } + } - unloveTrack(e){ - this.props.uiActions.hideContextMenu(); - this.props.lastfmActions.unloveTrack(this.props.menu.items[0]); - } + unloveTrack(e) { + this.props.uiActions.hideContextMenu(); + this.props.lastfmActions.unloveTrack(this.props.menu.items[0]); + } - removeFromPlaylist(e){ - this.props.uiActions.hideContextMenu(); - this.props.coreActions.removeTracksFromPlaylist(this.props.menu.tracklist_uri, this.props.menu.indexes); - } + removeFromPlaylist(e) { + this.props.uiActions.hideContextMenu(); + this.props.coreActions.removeTracksFromPlaylist(this.props.menu.tracklist_uri, this.props.menu.indexes); + } - deletePlaylist(e){ - this.props.uiActions.hideContextMenu(); - this.props.coreActions.deletePlaylist(this.props.menu.uris[0]); - } + deletePlaylist(e) { + this.props.uiActions.hideContextMenu(); + this.props.coreActions.deletePlaylist(this.props.menu.uris[0]); + } - startRadio(e){ - this.props.uiActions.hideContextMenu(); - this.props.pusherActions.startRadio(this.props.menu.uris); - } + startRadio(e) { + this.props.uiActions.hideContextMenu(); + this.props.pusherActions.startRadio(this.props.menu.uris); + } - goToRecommendations(e){ - this.props.uiActions.hideContextMenu(); - var uris_string = helpers.arrayOf('uri',this.props.menu.items).join(','); - this.props.history.push('/discover/recommendations/'+ uris_string ); - } + goToRecommendations(e) { + this.props.uiActions.hideContextMenu(); + const uris_string = helpers.arrayOf('uri', this.props.menu.items).join(','); + this.props.history.push(`/discover/recommendations/${uris_string}`); + } - goToArtist(e){ - if (!this.props.menu.items || this.props.menu.items.length <= 0 || !this.props.menu.items[0].artists_uris || this.props.menu.items[0].artists_uris.length <= 0){ - return null; - } else { - this.props.uiActions.hideContextMenu(); - - // note: we can only go to one artist (even if this item has multiple artists, just go to the first one) - this.props.history.push(helpers.buildLink(this.props.menu.items[0].artists_uris[0])); - } - } + goToArtist(e) { + if (!this.props.menu.items || this.props.menu.items.length <= 0 || !this.props.menu.items[0].artists_uris || this.props.menu.items[0].artists_uris.length <= 0) { + return null; + } + this.props.uiActions.hideContextMenu(); - goToUser(e){ - if (!this.props.menu.items || this.props.menu.items.length <= 0 || !this.props.menu.items[0].user_uri){ - return null; - } else { - this.props.uiActions.hideContextMenu(); - this.props.history.push(helpers.buildLink(this.props.menu.items[0].user_uri)); - } - } + // note: we can only go to one artist (even if this item has multiple artists, just go to the first one) + this.props.history.push(helpers.buildLink(this.props.menu.items[0].artists_uris[0])); + } - goToTrack(e){ - if (!this.props.menu.items || this.props.menu.items.length <= 0){ - return null; - } else { - this.props.uiActions.hideContextMenu(); - this.props.history.push(helpers.buildLink(this.props.menu.items[0].uri)); - } - } + goToUser(e) { + if (!this.props.menu.items || this.props.menu.items.length <= 0 || !this.props.menu.items[0].user_uri) { + return null; + } + this.props.uiActions.hideContextMenu(); + this.props.history.push(helpers.buildLink(this.props.menu.items[0].user_uri)); + } - copyURIs(e){ - var temp = $(""); - $("body").append(temp); - temp.val(this.props.menu.uris.join(',')).select(); - document.execCommand("copy"); - temp.remove(); + goToTrack(e) { + if (!this.props.menu.items || this.props.menu.items.length <= 0) { + return null; + } + this.props.uiActions.hideContextMenu(); + this.props.history.push(helpers.buildLink(this.props.menu.items[0].uri)); + } - this.props.uiActions.createNotification({content: "Copied "+this.props.menu.uris.length+" URIs"}); - this.props.uiActions.hideContextMenu() - } + copyURIs(e) { + const temp = $(''); + $('body').append(temp); + temp.val(this.props.menu.uris.join(',')).select(); + document.execCommand('copy'); + temp.remove(); - renderTitle(){ - var context = this.getContext(); + this.props.uiActions.createNotification({ content: `Copied ${this.props.menu.uris.length} URIs` }); + this.props.uiActions.hideContextMenu(); + } - if (context.items_count > 1){ - return ( -
-
- {context.items_count} {context.nice_name}{context.items_count > 1 ? 's' : null} selected {this.props.uiActions.setSelectedTracks([]); this.props.uiActions.hideContextMenu()}}> -
-
- ) - } + renderTitle() { + const context = this.getContext(); - if (context.items_count == 1 && context.name == 'queue-track' && context.item !== undefined){ - if (this.props.queue_metadata["tlid_"+context.item.tlid] !== undefined){ - var metadata = this.props.queue_metadata["tlid_"+context.item.tlid]; + if (context.items_count > 1) { + return ( +
+
+ {context.items_count} + {' '} + {context.nice_name} + {context.items_count > 1 ? 's' : null} + {' '} +selected + { this.props.uiActions.setSelectedTracks([]); this.props.uiActions.hideContextMenu(); }}> +
+
+ ); + } - if (metadata.added_from && metadata.added_by){ - var type = (metadata.added_from ? helpers.uriType(metadata.added_from) : null); + if (context.items_count == 1 && context.name == 'queue-track' && context.item !== undefined) { + if (this.props.queue_metadata[`tlid_${context.item.tlid}`] !== undefined) { + const metadata = this.props.queue_metadata[`tlid_${context.item.tlid}`]; - switch (type){ - case "discover": - var link = discover - break; + if (metadata.added_from && metadata.added_by) { + const type = (metadata.added_from ? helpers.uriType(metadata.added_from) : null); - case "browse": - var link = browse - break; + switch (type) { + case 'discover': + var link = discover; + break; - case "search": - var link = search - break; + case 'browse': + var link = browse; + break; - default: - var link = {type}; - } + case 'search': + var link = search; + break; - return ( -
-
- {metadata.added_by} added from {link} -
-
- ) - } - } - } + default: + var link = {type}; + } - if (context.name == 'custom'){ - if (!this.props.menu.title){ - return null; - } + return ( +
+
+ {metadata.added_by} + {' '} +added from + {link} +
+
+ ); + } + } + } - return ( -
-
- {this.props.menu.title} -
-
- ) - } - } + if (context.name == 'custom') { + if (!this.props.menu.title) { + return null; + } - setSubmenu(name){ + return ( +
+
+ {this.props.menu.title} +
+
+ ); + } + } - if (this.state.submenu !== name && name == 'add-to-playlist'){ - if (!this.props.spotify_library_playlists_loaded_all){ - this.props.spotifyActions.getLibraryPlaylists() - } - if (!this.props.mopidy_library_playlists_loaded_all){ - this.props.mopidyActions.getLibraryPlaylists() - } - } + setSubmenu(name) { + if (this.state.submenu !== name && name == 'add-to-playlist') { + if (!this.props.spotify_library_playlists_loaded_all) { + this.props.spotifyActions.getLibraryPlaylists(); + } + if (!this.props.mopidy_library_playlists_loaded_all) { + this.props.mopidyActions.getLibraryPlaylists(); + } + } - this.setState({submenu: name}); - } + this.setState({ submenu: name }); + } - renderSubmenu(){ - var list = null; - var loader = null; + renderSubmenu() { + let list = null; + let loader = null; - switch (this.state.submenu){ + switch (this.state.submenu) { + case 'add-to-playlist': - case 'add-to-playlist': + var playlists = []; + for (const uri in this.props.playlists) { + if (this.props.playlists[uri].can_edit) playlists.push(this.props.playlists[uri]); + } - var playlists = [] - for (var uri in this.props.playlists){ - if (this.props.playlists[uri].can_edit) playlists.push(this.props.playlists[uri]) - } + playlists = helpers.sortItems(playlists, 'name'); - playlists = helpers.sortItems(playlists, 'name') + if (this.props.processes.SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR && this.props.processes.SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR.status == 'running') { + loader = ( +
+
+
+
+
+ ); + } - if (this.props.processes.SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR && this.props.processes.SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR.status == 'running'){ - loader = ( -
-
-
-
-
- ) - } + list = No writable playlists; + if (playlists.length > 0) { + list = playlists.map((playlist) => ( + + this.addTracksToPlaylist(e, playlist.uri)}> + { playlist.name } + + + )); + } + } - list = No writable playlists - if (playlists.length > 0){ - list = playlists.map(playlist => { - return ( - - this.addTracksToPlaylist(e,playlist.uri) }> - { playlist.name } - - - ) - }) - } + return ( +
+
+ this.setState({ submenu: null })}> + + + {' '} +Back + + +
+ {list} + {loader} +
+ ); + } - } + renderItems() { + const context = this.getContext(); - return ( -
-
- this.setState({submenu: null})}> - - Back - - -
- {list} - {loader} -
- ); - } + const play_uris = ( +
+ this.playURIs(e)}> + Play + +
+ ); - renderItems(){ - var context = this.getContext() + const play_playlist = ( +
+ this.playPlaylist(e)}> + Play + +
+ ); - var play_uris = ( -
- this.playURIs(e)}> - Play - -
- ) + const shuffle_play_playlist = ( +
+ this.shufflePlayPlaylist(e)}> + Shuffle play + +
+ ); - var play_playlist = ( -
- this.playPlaylist(e)}> - Play - -
- ) + const play_queue_item = ( +
+ this.playQueueItem(e)}> + Play + +
+ ); - var shuffle_play_playlist = ( -
- this.shufflePlayPlaylist(e)}> - Shuffle play - -
- ) + const play_uris_next = ( +
+ this.addToQueue(e, true)}> + Play next + +
+ ); - var play_queue_item = ( -
- this.playQueueItem(e)}> - Play - -
- ) + const play_artist_top_tracks = ( +
+ this.playArtistTopTracks(e)}> + Play top tracks + +
+ ); - var play_uris_next = ( -
- this.addToQueue(e, true)}> - Play next - -
- ) + const add_to_queue = ( +
+ this.addToQueue(e)}> + Add to queue + +
+ ); - var play_artist_top_tracks = ( -
- this.playArtistTopTracks(e)}> - Play top tracks - -
- ) + const add_playlist_to_queue = ( +
+ this.enqueuePlaylist(e)}> + Add to queue + +
+ ); - var add_to_queue = ( -
- this.addToQueue(e)}> - Add to queue - -
- ) + const play_playlist_next = ( +
+ this.enqueuePlaylist(e, true)}> + Play next + +
+ ); - var add_playlist_to_queue = ( -
- this.enqueuePlaylist(e)}> - Add to queue - -
- ) + const add_to_playlist = ( +
+ this.setSubmenu('add-to-playlist')}> + Add to playlist + + +
+ ); - var play_playlist_next = ( -
- this.enqueuePlaylist(e, true)}> - Play next - -
- ) + var toggle_in_library = ( +
+ this.toggleInLibrary(e, context.in_library)}> + + {context.in_library ? 'Remove from library' : 'Add to library'} + + +
+ ); - var add_to_playlist = ( -
- this.setSubmenu('add-to-playlist')}> - Add to playlist - - -
- ) - - var toggle_in_library = ( -
- this.toggleInLibrary(e, context.in_library)}> - - {context.in_library ? 'Remove from library' : 'Add to library'} - - -
- ) - - if (!this.props.spotify_authorized){ - var toggle_in_library = null; - } else if (helpers.isLoading(this.props.load_queue,['spotify_me/tracks/contains','spotify_me/playlists/contains','spotify_me/albums/contains','spotify_me/artists/contains'])){ - var toggle_in_library = ( -
- - + if (!this.props.spotify_authorized) { + var toggle_in_library = null; + } else if (helpers.isLoading(this.props.load_queue, ['spotify_me/tracks/contains', 'spotify_me/playlists/contains', 'spotify_me/albums/contains', 'spotify_me/artists/contains'])) { + var toggle_in_library = ( +
+ + Add to library - - -
- ) - } else { - var toggle_in_library = ( -
- this.toggleInLibrary(e, context.in_library)}> - - {context.in_library ? 'Remove from library' : 'Add to library'} - - -
- ) - } + + +
+ ); + } else { + var toggle_in_library = ( +
+ this.toggleInLibrary(e, context.in_library)}> + + {context.in_library ? 'Remove from library' : 'Add to library'} + + +
+ ); + } - if (!this.props.lastfm_authorized){ - var toggle_loved = null; - } else if (helpers.isLoading(this.props.load_queue,['lastfm_track.getInfo'])){ - var toggle_loved = ( -
- - + if (!this.props.lastfm_authorized) { + var toggle_loved = null; + } else if (helpers.isLoading(this.props.load_queue, ['lastfm_track.getInfo'])) { + var toggle_loved = ( +
+ + Love track - - -
- ) - } else { - var toggle_loved = ( -
- this.toggleLoved(e, context.is_loved)}> - - {context.is_loved ? 'Unlove' : 'Love'} track - - -
- ) - } + + +
+ ); + } else { + var toggle_loved = ( +
+ this.toggleLoved(e, context.is_loved)}> + + {context.is_loved ? 'Unlove' : 'Love'} + {' '} +track + + +
+ ); + } - var go_to_artist = ( -
- this.goToArtist(e)}> - Go to artist - -
- ) + const go_to_artist = ( +
+ this.goToArtist(e)}> + Go to artist + +
+ ); - var go_to_user = ( -
- this.goToUser(e)}> - Go to user - -
- ) + const go_to_user = ( +
+ this.goToUser(e)}> + Go to user + +
+ ); - var go_to_track = ( -
- this.goToTrack(e)}> - Track info - -
- ) + const go_to_track = ( +
+ this.goToTrack(e)}> + Track info + +
+ ); - var go_to_recommendations = ( -
- this.goToRecommendations(e)}> - Discover similar - -
- ) + const go_to_recommendations = ( +
+ this.goToRecommendations(e)}> + Discover similar + +
+ ); - var start_radio = ( -
- this.startRadio(e)}> - Start radio - -
- ) + const start_radio = ( +
+ this.startRadio(e)}> + Start radio + +
+ ); - var remove_from_queue = ( -
- this.removeFromQueue(e)}> - Remove - -
- ) + const remove_from_queue = ( +
+ this.removeFromQueue(e)}> + Remove + +
+ ); - var remove_from_playlist = ( -
- this.removeFromPlaylist(e)}> - Remove - -
- ) + const remove_from_playlist = ( +
+ this.removeFromPlaylist(e)}> + Remove + +
+ ); - var edit_playlist = ( -
- - Edit - -
- ) + const edit_playlist = ( +
+ + Edit + +
+ ); - var delete_playlist = ( -
- this.deletePlaylist(e)}> - Delete - -
- ) + const delete_playlist = ( +
+ this.deletePlaylist(e)}> + Delete + +
+ ); - var copy_uris = ( -
- this.copyURIs(e)}> - Copy URI{context.items_count > 1 ? 's' : ''} - -
- ) + const copy_uris = ( +
+ this.copyURIs(e)}> + +Copy URI + {context.items_count > 1 ? 's' : ''} + + +
+ ); - switch (context.name){ + switch (context.name) { + case 'album': + return ( +
+ {play_uris} + {play_uris_next} + {add_to_queue} + {this.canBeInLibrary() ?
: null} + {this.canBeInLibrary() ? toggle_in_library : null} +
+ {go_to_artist} + {copy_uris} +
+ ); + break; - case 'album': - return ( -
- {play_uris} - {play_uris_next} - {add_to_queue} - {this.canBeInLibrary() ?
: null} - {this.canBeInLibrary() ? toggle_in_library : null} -
- {go_to_artist} - {copy_uris} -
- ) - break + case 'artist': + return ( +
+ {context.source == 'spotify' ? play_artist_top_tracks : null} + {context.source == 'spotify' ? start_radio : null} + {this.canBeInLibrary() ?
: null} + {this.canBeInLibrary() ? toggle_in_library : null} +
+ {context.source == 'spotify' ? go_to_recommendations : null} + {copy_uris} +
+ ); + break; - case 'artist': - return ( -
- {context.source == 'spotify' ? play_artist_top_tracks : null} - {context.source == 'spotify' ? start_radio : null} - {this.canBeInLibrary() ?
: null} - {this.canBeInLibrary() ? toggle_in_library : null} -
- {context.source == 'spotify' ? go_to_recommendations : null} - {copy_uris} -
- ) - break + case 'playlist': + return ( +
+ {play_playlist} + {play_playlist_next} + {shuffle_play_playlist} + {add_playlist_to_queue} + {this.canBeInLibrary() ?
: null} + {this.canBeInLibrary() ? toggle_in_library : null} +
+ {context.source == 'spotify' ? go_to_user : null} + {copy_uris} + {context.items_count == 1 && context.item.can_edit ? ( +
+
+ {edit_playlist} + {delete_playlist} +
+ ) : null} +
+ ); + break; - case 'playlist': - return ( -
- {play_playlist} - {play_playlist_next} - {shuffle_play_playlist} - {add_playlist_to_queue} - {this.canBeInLibrary() ?
: null} - {this.canBeInLibrary() ? toggle_in_library : null} -
- {context.source == 'spotify' ? go_to_user : null} - {copy_uris} - {context.items_count == 1 && context.item.can_edit ?
-
- {edit_playlist} - {delete_playlist} -
: null} -
- ) - break + case 'queue-track': + return ( +
+ {context.items_count == 1 ? play_queue_item : null} +
+ {add_to_playlist} + {this.canBeInLibrary() ? toggle_in_library : null} + {toggle_loved} +
+ {context.source == 'spotify' && context.items_count <= 5 ? go_to_recommendations : null} + {context.items_count == 1 ? go_to_track : null} + {copy_uris} +
+ {remove_from_queue} +
+ ); + break; - case 'queue-track': - return ( -
- {context.items_count == 1 ? play_queue_item : null} -
- {add_to_playlist} - {this.canBeInLibrary() ? toggle_in_library : null} - {toggle_loved} -
- {context.source == 'spotify' && context.items_count <= 5 ? go_to_recommendations : null} - {context.items_count == 1 ? go_to_track : null} - {copy_uris} -
- {remove_from_queue} -
- ) - break + case 'editable-playlist-track': + return ( +
+ {play_uris} + {play_uris_next} + {add_to_queue} + {context.source == 'spotify' && context.items_count == 1 ? start_radio : null} +
+ {add_to_playlist} + {this.canBeInLibrary() ? toggle_in_library : null} + {toggle_loved} +
+ {context.source == 'spotify' && context.items_count <= 5 ? go_to_recommendations : null} + {context.items_count == 1 ? go_to_track : null} + {copy_uris} +
+ {remove_from_playlist} +
+ ); + break; - case 'editable-playlist-track': - return ( -
- {play_uris} - {play_uris_next} - {add_to_queue} - {context.source == 'spotify' && context.items_count == 1 ? start_radio : null} -
- {add_to_playlist} - {this.canBeInLibrary() ? toggle_in_library : null} - {toggle_loved} -
- {context.source == 'spotify' && context.items_count <= 5 ? go_to_recommendations : null} - {context.items_count == 1 ? go_to_track : null} - {copy_uris} -
- {remove_from_playlist} -
- ) - break + default: + return ( +
+ {play_uris} + {play_uris_next} + {add_to_queue} + {context.source == 'spotify' && context.items_count == 1 ? start_radio : null} +
+ {add_to_playlist} + {this.canBeInLibrary() ? toggle_in_library : null} + {toggle_loved} +
+ {context.source == 'spotify' && context.items_count <= 5 ? go_to_recommendations : null} + {context.items_count == 1 ? go_to_track : null} +
+ {copy_uris} +
+ ); + break; + } + } - default: - return ( -
- {play_uris} - {play_uris_next} - {add_to_queue} - {context.source == 'spotify' && context.items_count == 1 ? start_radio : null} -
- {add_to_playlist} - {this.canBeInLibrary() ? toggle_in_library : null} - {toggle_loved} -
- {context.source == 'spotify' && context.items_count <= 5 ? go_to_recommendations : null} - {context.items_count == 1 ? go_to_track : null} -
- {copy_uris} -
- ) - break - } - } + render() { + if (!this.props.menu) { + return null; + } - render(){ - if (!this.props.menu){ - return null; - } + const style = { + left: this.props.menu.position_x, + top: this.props.menu.position_y, + }; + const height = 200; // TODO: use jquery to detect height + let className = `context-menu ${this.props.menu.context}`; + if (this.state.submenu) { + className += ' context-menu--submenu-expanded'; + } - var style = { - left: this.props.menu.position_x, - top: this.props.menu.position_y, - } - var height = 200 // TODO: use jquery to detect height - var className = "context-menu "+this.props.menu.context - if (this.state.submenu){ - className += ' context-menu--submenu-expanded'; - } - - if (this.props.menu.closing){ - className += ' context-menu--closing'; - } + if (this.props.menu.closing) { + className += ' context-menu--closing'; + } - if (this.props.menu.position_x > (window.innerWidth - 174)){ - style.left = 'auto'; - style.right = 10; - } + if (this.props.menu.position_x > (window.innerWidth - 174)) { + style.left = 'auto'; + style.right = 10; + } - if (this.props.menu.position_y > (window.innerHeight - height)){ - style.top = 'auto'; - style.bottom = 10; - } + if (this.props.menu.position_y > (window.innerHeight - height)) { + style.top = 'auto'; + style.bottom = 10; + } - return ( -
-
- {this.renderTitle()} - {this.props.menu.context == 'custom' ? this.props.menu.options : this.renderItems()} -
- {this.renderSubmenu()} -
this.props.uiActions.hideContextMenu()}>
-
- ); - } + return ( +
+
+ {this.renderTitle()} + {this.props.menu.context == 'custom' ? this.props.menu.options : this.renderItems()} +
+ {this.renderSubmenu()} +
this.props.uiActions.hideContextMenu()} /> +
+ ); + } } -const mapStateToProps = (state, ownProps) => { - return { - menu: state.ui.context_menu, - load_queue: state.ui.load_queue, - processes: state.ui.processes, - current_track: state.core.current_track, - current_tracklist: state.core.current_tracklist, - queue_metadata: state.core.queue_metadata, - spotify_library_playlists: state.spotify.library_playlists, - spotify_library_playlists_loaded_all: state.spotify.library_playlists_loaded_all, - mopidy_library_playlists: state.mopidy.library_playlists, - mopidy_library_playlists_loaded_all: state.mopidy.library_playlists_loaded_all, - spotify_library_artists: state.spotify.library_artists, - mopidy_library_artists: state.mopidy.library_artists, - spotify_library_albums: state.spotify.library_albums, - mopidy_library_albums: state.mopidy.library_albums, - spotify_library_tracks: state.spotify.library_tracks, - playlists: state.core.playlists, - tracks: state.core.tracks, - spotify_authorized: state.spotify.authorization, - lastfm_authorized: state.lastfm.authorization - } -} +const mapStateToProps = (state, ownProps) => ({ + menu: state.ui.context_menu, + load_queue: state.ui.load_queue, + processes: state.ui.processes, + current_track: state.core.current_track, + current_tracklist: state.core.current_tracklist, + queue_metadata: state.core.queue_metadata, + spotify_library_playlists: state.spotify.library_playlists, + spotify_library_playlists_loaded_all: state.spotify.library_playlists_loaded_all, + mopidy_library_playlists: state.mopidy.library_playlists, + mopidy_library_playlists_loaded_all: state.mopidy.library_playlists_loaded_all, + spotify_library_artists: state.spotify.library_artists, + mopidy_library_artists: state.mopidy.library_artists, + spotify_library_albums: state.spotify.library_albums, + mopidy_library_albums: state.mopidy.library_albums, + spotify_library_tracks: state.spotify.library_tracks, + playlists: state.core.playlists, + tracks: state.core.tracks, + spotify_authorized: state.spotify.authorization, + lastfm_authorized: state.lastfm.authorization, +}); -const mapDispatchToProps = (dispatch) => { - return { - coreActions: bindActionCreators(coreActions, dispatch), - uiActions: bindActionCreators(uiActions, dispatch), - pusherActions: bindActionCreators(pusherActions, dispatch), - spotifyActions: bindActionCreators(spotifyActions, dispatch), - lastfmActions: bindActionCreators(lastfmActions, dispatch), - mopidyActions: bindActionCreators(mopidyActions, dispatch) - } -} +const mapDispatchToProps = (dispatch) => ({ + coreActions: bindActionCreators(coreActions, dispatch), + uiActions: bindActionCreators(uiActions, dispatch), + pusherActions: bindActionCreators(pusherActions, dispatch), + spotifyActions: bindActionCreators(spotifyActions, dispatch), + lastfmActions: bindActionCreators(lastfmActions, dispatch), + mopidyActions: bindActionCreators(mopidyActions, dispatch), +}); export default connect(mapStateToProps, mapDispatchToProps)(withRouter(ContextMenu)); diff --git a/src/js/components/ContextMenuTrigger.js b/src/js/components/ContextMenuTrigger.js index cc830d45..a95ad5fe 100755 --- a/src/js/components/ContextMenuTrigger.js +++ b/src/js/components/ContextMenuTrigger.js @@ -2,22 +2,21 @@ import React, { memo } from 'react'; import Icon from './Icon'; -export default memo((props) => { +export default memo((props) => { + const handleClick = (e) => { + e.preventDefault(); + e.stopPropagation(); + props.onTrigger(e); + }; - const handleClick = (e) => { - e.preventDefault(); - e.stopPropagation(); - props.onTrigger(e); - } + let className = 'context-menu-trigger mouse-contextable touch-contextable'; + if (props.className) { + className += ` ${props.className}`; + } - let className = 'context-menu-trigger mouse-contextable touch-contextable' - if (props.className){ - className += ' '+props.className - } - - return ( - handleClick(e)}> - - - ); + return ( + handleClick(e)}> + + + ); }); diff --git a/src/js/components/Dater.js b/src/js/components/Dater.js index d8a5f5e8..f2dd5b10 100755 --- a/src/js/components/Dater.js +++ b/src/js/components/Dater.js @@ -1,115 +1,113 @@ -import React, { memo } from 'react' +import React, { memo } from 'react'; /** * Format time duration * * @param milliseconds int * @return string (HH:mm:ss) - **/ + * */ const durationTime = (milliseconds = null) => { - if (!milliseconds ) return null + if (!milliseconds) return null; - var string = ''; - var total_hours, total_minutes, total_seconds, minutes, seconds; + let string = ''; + let total_hours; let total_minutes; let total_seconds; let minutes; let + seconds; - // get total values for each - total_seconds = Math.floor(milliseconds / 1000 ) - total_minutes = Math.floor(milliseconds / (1000 * 60) ) - total_hours = Math.floor(milliseconds / (1000 * 60 * 60)) + // get total values for each + total_seconds = Math.floor(milliseconds / 1000); + total_minutes = Math.floor(milliseconds / (1000 * 60)); + total_hours = Math.floor(milliseconds / (1000 * 60 * 60)); - // get left-over number of seconds - seconds = total_seconds - (total_minutes * 60 ) - if (seconds <= 9) seconds = '0'+ seconds - if (seconds == 0) seconds = '00' + // get left-over number of seconds + seconds = total_seconds - (total_minutes * 60); + if (seconds <= 9) seconds = `0${seconds}`; + if (seconds == 0) seconds = '00'; - // get left-over number of minutes - minutes = total_minutes - (total_hours * 60 ) - if (minutes <= 9 && total_hours) minutes = '0'+ minutes - if (minutes == 0) minutes = '0' + // get left-over number of minutes + minutes = total_minutes - (total_hours * 60); + if (minutes <= 9 && total_hours) minutes = `0${minutes}`; + if (minutes == 0) minutes = '0'; - if (total_hours) string += total_hours+':' - if (minutes) string += minutes+':' - if (seconds) string += seconds - - return string -} + if (total_hours) string += `${total_hours}:`; + if (minutes) string += `${minutes}:`; + if (seconds) string += seconds; + + return string; +}; /** * Format time duration as a human-friendly sentence * * @param milliseconds int * @return string (eg 2+ hours) - **/ + * */ const durationSentence = (milliseconds = null) => { - if (!milliseconds ) return null + if (!milliseconds) return null; - var string = ''; - var total_hours, total_minutes, total_seconds, minutes, seconds; + const string = ''; + let total_hours; let total_minutes; let total_seconds; let minutes; let + seconds; - // get total values for each - total_seconds = Math.floor(milliseconds / 1000 ) - total_minutes = Math.floor(milliseconds / (1000 * 60) ) - total_hours = Math.floor(milliseconds / (1000 * 60 * 60)) + // get total values for each + total_seconds = Math.floor(milliseconds / 1000); + total_minutes = Math.floor(milliseconds / (1000 * 60)); + total_hours = Math.floor(milliseconds / (1000 * 60 * 60)); - if (total_hours > 1 ) return total_hours+'+ hrs' - if (total_minutes > 1 ) return total_minutes+' mins' - if (total_seconds ) return total_seconds+' sec' -} + if (total_hours > 1) return `${total_hours}+ hrs`; + if (total_minutes > 1) return `${total_minutes} mins`; + if (total_seconds) return `${total_seconds} sec`; +}; -export default memo((props) => { +export default memo((props) => { + if (!props.data) { + return null; + } - if (!props.data){ - return null; - } + switch (props.type) { + case 'total-time': + var duration = 0; + var tracks = props.data; + for (let i = 0; i < tracks.length; i++) { + if (tracks[i].duration) { + duration += parseInt(tracks[i].duration); + } + } + return durationSentence(duration); - switch(props.type){ + case 'length': + return durationTime(props.data); - case 'total-time': - var duration = 0; - var tracks = props.data - for(var i = 0; i < tracks.length; i++){ - if (tracks[i].duration ){ - duration += parseInt(tracks[i].duration); - } - } - return durationSentence(duration); + case 'date': - case 'length': - return durationTime(props.data); + // A four-character date indicates just a year (rather than a full date) + if (props.data.length == 4) { + return props.data; - case 'date': + // Digest as a date string + } + var date = new Date(props.data); + return `${date.getDate()}/${date.getMonth() + 1}/${date.getFullYear()}`; - // A four-character date indicates just a year (rather than a full date) - if (props.data.length == 4){ - return props.data; - // Digest as a date string - } else { - var date = new Date(props.data); - return date.getDate()+'/'+(date.getMonth()+1)+'/'+date.getFullYear(); - } + case 'ago': + var date = new Date(props.data); + var diff = new Date() - date; - case 'ago': - var date = new Date(props.data) - var diff = new Date() - date - - var seconds = Math.floor(diff / 1000) - var minutes = Math.floor(diff / (1000 * 60)) - var hours = Math.floor(diff / (1000 * 60 * 60)) - var days = Math.floor(diff / (1000 * 60 * 60 * 24)) + var seconds = Math.floor(diff / 1000); + var minutes = Math.floor(diff / (1000 * 60)); + var hours = Math.floor(diff / (1000 * 60 * 60)); + var days = Math.floor(diff / (1000 * 60 * 60 * 24)); - if (seconds < 60){ - return seconds + " seconds"; - }else if (minutes < 60){ - return minutes + " minutes"; - }else if (hours < 24){ - return hours + " hours"; - } else { - return days + " days" - }; - - default: - return null; - } + if (seconds < 60) { + return `${seconds} seconds`; + } if (minutes < 60) { + return `${minutes} minutes`; + } if (hours < 24) { + return `${hours} hours`; + } + return `${days} days`; + default: + return null; + } }); diff --git a/src/js/components/DebugInfo.js b/src/js/components/DebugInfo.js index b2a089d4..9b12076b 100755 --- a/src/js/components/DebugInfo.js +++ b/src/js/components/DebugInfo.js @@ -1,168 +1,194 @@ -import React from 'react' -import { connect } from 'react-redux' -import { bindActionCreators } from 'redux' +import React from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; -import * as helpers from '../helpers' -import * as uiActions from '../services/ui/actions' +import * as helpers from '../helpers'; +import * as uiActions from '../services/ui/actions'; -class DebugInfo extends React.Component{ +class DebugInfo extends React.Component { + constructor(props) { + super(props); + } - constructor(props){ - super(props); - } + localStorageSize() { + let data = ''; - localStorageSize(){ - var data = ''; - - for (var key in window.localStorage){ - if(window.localStorage.hasOwnProperty(key)){ - data += window.localStorage[key]; - } - } + for (const key in window.localStorage) { + if (window.localStorage.hasOwnProperty(key)) { + data += window.localStorage[key]; + } + } - var used = 0; - var total = 5000; - if (data !== ''){ - used = ((data.length * 16)/(8 * 1024)).toFixed(2); - } - - return { - used: used, - percent: (used / total * 100).toFixed(2) - }; - } + let used = 0; + const total = 5000; + if (data !== '') { + used = ((data.length * 16) / (8 * 1024)).toFixed(2); + } - renderLoadQueue(){ - if (!this.props.ui.load_queue){ - return null - } + return { + used, + percent: (used / total * 100).toFixed(2), + }; + } - var load_queue = this.props.ui.load_queue; - var queue = []; + renderLoadQueue() { + if (!this.props.ui.load_queue) { + return null; + } - return ( -
- {queue} -
- ) - } + const { load_queue } = this.props.ui; + const queue = []; - renderLoadQueue(){ - if (!this.props.ui.load_queue){ - return
Nothing loading
; - } + return ( +
+ {queue} +
+ ); + } - var load_queue = this.props.ui.load_queue - var queue = [] - for (var key in load_queue){ - if (load_queue.hasOwnProperty(key)){ - queue.push(
{load_queue[key]}
) - } - } + renderLoadQueue() { + if (!this.props.ui.load_queue) { + return
Nothing loading
; + } - if (queue.length > 0){ - return ( -
- {queue} -
- ) - } else { - return
Nothing loading
; - } - } + const { load_queue } = this.props.ui; + const queue = []; + for (const key in load_queue) { + if (load_queue.hasOwnProperty(key)) { + queue.push(
{load_queue[key]}
); + } + } - render(){ - var localStorageUsage = this.localStorageSize(); + if (queue.length > 0) { + return ( +
+ {queue} +
+ ); + } + return
Nothing loading
; + } - return ( -
+ render() { + const localStorageUsage = this.localStorageSize(); -
-
- Version: {version} -
-
- Build: {build} -
-
+ return ( +
-
-
Indexes
-
- Albums: {this.props.core.albums ? Object.keys(this.props.core.albums).length : '0'} -
-
- Artists: {this.props.core.artists ? Object.keys(this.props.core.artists).length : '0'} -
-
- Playlists: {this.props.core.playlists ? Object.keys(this.props.core.playlists).length : '0'} -
-
- Tracks: {this.props.core.tracks ? Object.keys(this.props.core.tracks).length : '0'} -
-
- Users: {this.props.core.users ? Object.keys(this.props.core.users).length : '0'} -
-
- Notifications: {this.props.ui.notifications ? Object.keys(this.props.ui.notifications).length : '0'} -
-
- Processes: {this.props.ui.processes ? Object.keys(this.props.ui.processes).length : '0'} -
-
- Enqueue batches: {this.props.mopidy.enqueue_uris_batches ? this.props.mopidy.enqueue_uris_batches.length : '0'} -
-
+
+
+ Version: + {' '} + {version} +
+
+ Build: + {' '} + {build} +
+
-
-
Config
-
- Slim mode: {this.props.ui.slim_mode ? 'on' : 'off'} -
-
- Test mode: {this.props.ui.test_mode ? 'on' : 'off'} -
-
- Touch: {helpers.isTouchDevice() ? 'on' : 'off'} -
-
- LocalStorage usage: {localStorageUsage.used}kb (~{localStorageUsage.percent}%) -
-
- Selected tracks: {this.props.ui.selected_tracks.length}
- { - this.props.ui.selected_tracks.map((track_key, index) => { - return ( -
{track_key}
- ) - }) +
+
Indexes
+
+ Albums: + {' '} + {this.props.core.albums ? Object.keys(this.props.core.albums).length : '0'} +
+
+ Artists: + {' '} + {this.props.core.artists ? Object.keys(this.props.core.artists).length : '0'} +
+
+ Playlists: + {' '} + {this.props.core.playlists ? Object.keys(this.props.core.playlists).length : '0'} +
+
+ Tracks: + {' '} + {this.props.core.tracks ? Object.keys(this.props.core.tracks).length : '0'} +
+
+ Users: + {' '} + {this.props.core.users ? Object.keys(this.props.core.users).length : '0'} +
+
+ Notifications: + {' '} + {this.props.ui.notifications ? Object.keys(this.props.ui.notifications).length : '0'} +
+
+ Processes: + {' '} + {this.props.ui.processes ? Object.keys(this.props.ui.processes).length : '0'} +
+
+ Enqueue batches: + {' '} + {this.props.mopidy.enqueue_uris_batches ? this.props.mopidy.enqueue_uris_batches.length : '0'} +
+
+ +
+
Config
+
+ Slim mode: + {' '} + {this.props.ui.slim_mode ? 'on' : 'off'} +
+
+ Test mode: + {' '} + {this.props.ui.test_mode ? 'on' : 'off'} +
+
+ Touch: + {' '} + {helpers.isTouchDevice() ? 'on' : 'off'} +
+
+ LocalStorage usage: + {' '} + {localStorageUsage.used} +kb (~ + {localStorageUsage.percent} +%) +
+
+ Selected tracks: + {' '} + {this.props.ui.selected_tracks.length} +
+ { + this.props.ui.selected_tracks.map((track_key, index) => ( +
{track_key}
+ )) } -
-
+
+
-
-
Load queue
- {this.renderLoadQueue()} -
+
+
Load queue
+ {this.renderLoadQueue()} +
-
- ); - } +
+ ); + } } -const mapStateToProps = (state, ownProps) => { - return { - core: state.core, - ui: state.ui, - mopidy: state.mopidy - } -} +const mapStateToProps = (state, ownProps) => ({ + core: state.core, + ui: state.ui, + mopidy: state.mopidy, +}); -const mapDispatchToProps = (dispatch) => { - return { - uiActions: bindActionCreators(uiActions, dispatch) - } -} +const mapDispatchToProps = (dispatch) => ({ + uiActions: bindActionCreators(uiActions, dispatch), +}); -export default connect(mapStateToProps, mapDispatchToProps)(DebugInfo) \ No newline at end of file +export default connect(mapStateToProps, mapDispatchToProps)(DebugInfo); diff --git a/src/js/components/Dragger.js b/src/js/components/Dragger.js index 35adc611..e0674166 100755 --- a/src/js/components/Dragger.js +++ b/src/js/components/Dragger.js @@ -1,98 +1,96 @@ -import React from 'react' -import { connect } from 'react-redux' -import { bindActionCreators } from 'redux' +import React from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; -import * as uiActions from '../services/ui/actions' -import * as mopidyActions from '../services/mopidy/actions' -import * as spotifyActions from '../services/spotify/actions' +import * as uiActions from '../services/ui/actions'; +import * as mopidyActions from '../services/mopidy/actions'; +import * as spotifyActions from '../services/spotify/actions'; -class Dragger extends React.Component{ +class Dragger extends React.Component { + constructor(props) { + super(props); + this.handleMouseMove = this.handleMouseMove.bind(this); + this.handleMouseUp = this.handleMouseUp.bind(this); - constructor(props){ - super(props); - this.handleMouseMove = this.handleMouseMove.bind(this) - this.handleMouseUp = this.handleMouseUp.bind(this) + this.state = { + active: false, + position_x: 0, + position_y: 0, + }; + } - this.state = { - active: false, - position_x: 0, - position_y: 0 - } - } + componentDidMount() { + window.addEventListener('mousemove', this.handleMouseMove, false); + window.addEventListener('mouseup', this.handleMouseUp, false); + } - componentDidMount(){ - window.addEventListener("mousemove", this.handleMouseMove, false); - window.addEventListener("mouseup", this.handleMouseUp, false); - } + componentWillUnmount() { + window.removeEventListener('mousemove', this.handleMouseMove, false); + window.removeEventListener('mouseup', this.handleMouseUp, false); + } - componentWillUnmount(){ - window.removeEventListener("mousemove", this.handleMouseMove, false); - window.removeEventListener("mouseup", this.handleMouseUp, false); - } + handleMouseMove(e) { + if (!this.props.dragger) return null; - handleMouseMove(e){ - if (!this.props.dragger ) return null; + const threshold = 10; + if ( + e.clientX > this.props.dragger.start_x + threshold + || e.clientX < this.props.dragger.start_x - threshold + || e.clientY > this.props.dragger.start_y + threshold + || e.clientY < this.props.dragger.start_y - threshold) { + this.setState({ + position_x: e.clientX, + position_y: e.clientY, + }); - var threshold = 10 - if ( - e.clientX > this.props.dragger.start_x + threshold || - e.clientX < this.props.dragger.start_x - threshold || - e.clientY > this.props.dragger.start_y + threshold || - e.clientY < this.props.dragger.start_y - threshold){ + const dropzones = document.getElementsByClassName('dropzone'); + for (let i = 0; i < dropzones.length; i++) { + dropzones[i].classList.remove('hover'); + } - this.setState({ - position_x: e.clientX, - position_y: e.clientY - }) + if (e.target.classList.contains('dropzone') && !e.target.classList.contains('hover')) { + e.target.className += ' hover'; + } - var dropzones = document.getElementsByClassName('dropzone') - for(var i = 0; i < dropzones.length; i++){ - dropzones[i].classList.remove('hover') - } + // if not already, activate + if (!this.props.dragger.active) this.props.uiActions.dragActive(); + } + } - if (e.target.classList.contains('dropzone') && !e.target.classList.contains('hover')){ - e.target.className += ' hover' - } + handleMouseUp(e) { + if (!this.props.dragger) return null; + this.props.uiActions.dragEnd(e); + } - // if not already, activate - if (!this.props.dragger.active ) this.props.uiActions.dragActive() - } - } + render() { + if (!this.props.dragger || !this.props.dragger.active) return null; - handleMouseUp(e){ - if (!this.props.dragger ) return null; - this.props.uiActions.dragEnd(e ) - } + const style = { + left: this.state.position_x, + top: this.state.position_y, + }; - render(){ - if (!this.props.dragger || !this.props.dragger.active ) return null; - - var style = { - left: this.state.position_x, - top: this.state.position_y, - } - - return ( -
- Dragging { this.props.dragger.victims.length } things -
- ); - } + return ( +
+ Dragging + {' '} + { this.props.dragger.victims.length } + {' '} +things +
+ ); + } } -const mapStateToProps = (state, ownProps) => { - return { - dragger: state.ui.dragger - } -} +const mapStateToProps = (state, ownProps) => ({ + dragger: state.ui.dragger, +}); -const mapDispatchToProps = (dispatch) => { - return { - uiActions: bindActionCreators(uiActions, dispatch), - spotifyActions: bindActionCreators(spotifyActions, dispatch), - mopidyActions: bindActionCreators(mopidyActions, dispatch) - } -} +const mapDispatchToProps = (dispatch) => ({ + uiActions: bindActionCreators(uiActions, dispatch), + spotifyActions: bindActionCreators(spotifyActions, dispatch), + mopidyActions: bindActionCreators(mopidyActions, dispatch), +}); -export default connect(mapStateToProps, mapDispatchToProps)(Dragger) \ No newline at end of file +export default connect(mapStateToProps, mapDispatchToProps)(Dragger); diff --git a/src/js/components/ErrorBoundary.js b/src/js/components/ErrorBoundary.js index 004e206d..cdc3c1c5 100755 --- a/src/js/components/ErrorBoundary.js +++ b/src/js/components/ErrorBoundary.js @@ -2,35 +2,33 @@ import React from 'react'; import ErrorMessage from './ErrorMessage'; -export default class ErrorBoundary extends React.Component { +export default class ErrorBoundary extends React.Component { + constructor(props) { + super(props); + this.state = { + hasError: false, + error: null, + info: null, + }; + } - constructor(props) { - super(props); - this.state = { - hasError: false, - error: null, - info: null - }; - } + componentDidCatch(error, info) { + this.setState({ + hasError: true, + error, + info, + }); + console.error(error, info); + } - componentDidCatch(error, info){ - this.setState({ - hasError: true, - error: error, - info: info - }); - console.error(error, info); - } - - render() { - if (this.state.hasError){ - return ( - - {this.state.info ?
{this.state.info.componentStack}
: null} -
- ); - } - return this.props.children; - } + render() { + if (this.state.hasError) { + return ( + + {this.state.info ?
{this.state.info.componentStack}
: null} +
+ ); + } + return this.props.children; + } } - diff --git a/src/js/components/ErrorMessage.js b/src/js/components/ErrorMessage.js index 910d6c6a..164d8b88 100755 --- a/src/js/components/ErrorMessage.js +++ b/src/js/components/ErrorMessage.js @@ -1,21 +1,18 @@ import React, { memo } from 'react'; -export default memo((props) => { - return ( -
- - error +export default memo((props) => ( +
-

- {props.title ? props.title : "Unknown error"} -

+ error -
- {props.children} -
+

+ {props.title ? props.title : 'Unknown error'} +

-
- ); -}); +
+ {props.children} +
+
+)); diff --git a/src/js/components/Fields/AddSeedField.js b/src/js/components/Fields/AddSeedField.js index 52115b24..059e26e8 100755 --- a/src/js/components/Fields/AddSeedField.js +++ b/src/js/components/Fields/AddSeedField.js @@ -1,142 +1,140 @@ -import React from 'react' -import { connect } from 'react-redux' -import { bindActionCreators } from 'redux' +import React from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; import Link from '../Link'; -import ArtistSentence from './../ArtistSentence' -import * as helpers from '../../helpers' -import * as coreActions from '../../services/core/actions' -import * as uiActions from '../../services/ui/actions' -import * as spotifyActions from '../../services/spotify/actions' +import ArtistSentence from '../ArtistSentence'; +import * as helpers from '../../helpers'; +import * as coreActions from '../../services/core/actions'; +import * as uiActions from '../../services/ui/actions'; +import * as spotifyActions from '../../services/spotify/actions'; -class AddSeedField extends React.Component{ +class AddSeedField extends React.Component { + constructor(props) { + super(props); - constructor(props){ - super(props) + this.state = { + value: '', + }; + this.id = helpers.generateGuid(); + this.timer = null; + this.handleClick = this.handleClick.bind(this); + } - this.state = { - value: '' - } - this.id = helpers.generateGuid() - this.timer = null - this.handleClick = this.handleClick.bind(this) - } + componentDidMount() { + window.addEventListener('click', this.handleClick, false); - componentDidMount(){ - window.addEventListener("click", this.handleClick, false) + if (!this.props.genres) { + this.props.spotifyActions.getGenres(); + } + } - if (!this.props.genres){ - this.props.spotifyActions.getGenres() - } - } + componentWillUnmount() { + window.removeEventListener('click', this.handleClick, false); + } - componentWillUnmount(){ - window.removeEventListener("click", this.handleClick, false) - } + handleClick(e) { + if ($(e.target).closest('.add-seed-field').length <= 0) { + this.props.spotifyActions.clearAutocompleteResults(this.id); + } + } - handleClick(e){ - if ($(e.target).closest('.add-seed-field').length <= 0){ - this.props.spotifyActions.clearAutocompleteResults(this.id) - } - } + handleChange(e, value) { + const self = this; - handleChange(e,value){ - var self = this + // update our local state + this.setState({ value }); - // update our local state - this.setState({value: value}) - - // start a timer to perform the actual search - // this provides a wee delay between key presses to avoid request spamming - clearTimeout(this.timer) - this.timer = setTimeout( - function(){ - self.setState({searching: true}) - self.props.spotifyActions.getAutocompleteResults(self.id,value,['artist','track','genre']) - }, - 500 - ) - } + // start a timer to perform the actual search + // this provides a wee delay between key presses to avoid request spamming + clearTimeout(this.timer); + this.timer = setTimeout( + () => { + self.setState({ searching: true }); + self.props.spotifyActions.getAutocompleteResults(self.id, value, ['artist', 'track', 'genre']); + }, + 500, + ); + } - handleSelect(e,item){ - this.setState({value: ''}) - this.props.onSelect(e,item.uri) - this.props.spotifyActions.clearAutocompleteResults(this.id) - } + handleSelect(e, item) { + this.setState({ value: '' }); + this.props.onSelect(e, item.uri); + this.props.spotifyActions.clearAutocompleteResults(this.id); + } - results(){ - if (this.props.results === undefined){ - return null; - } else if (this.props.results[this.id] === undefined){ - return null; - } else { - return this.props.results[this.id]; - } - } + results() { + if (this.props.results === undefined) { + return null; + } if (this.props.results[this.id] === undefined) { + return null; + } + return this.props.results[this.id]; + } - renderResults(type){ - var results = this.results() - if (!results || typeof(results[type]) === 'undefined' || results[type].length <= 0) return null - - // only show the first 3 - var items = results[type].slice(0,3) + renderResults(type) { + const results = this.results(); + if (!results || typeof (results[type]) === 'undefined' || results[type].length <= 0) return null; - return ( -
-

{type}

- { - items.map(item => { - return ( -
this.handleSelect(e,item)}> - {item.name} - {type == 'tracks' ? : null} -
- ) - }) + // only show the first 3 + const items = results[type].slice(0, 3); + + return ( +
+

{type}

+ { + items.map((item) => ( +
this.handleSelect(e, item)}> + {item.name} + {type == 'tracks' ? ( + + {' '} + + + ) : null} +
+ )) } -
- ) - } +
+ ); + } - render(){ - var className = "field autocomplete-field add-seed-field" - if (this.results() && this.results().loading){ - className += " loading" - } - return ( -
-
- this.handleChange(e,e.target.value)} - placeholder={this.props.placeholder ? this.props.placeholder : "Start typing..."} /> -
-
- {this.renderResults('artists')} - {this.renderResults('tracks')} - {this.renderResults('genres')} -
-
- ); - } + render() { + let className = 'field autocomplete-field add-seed-field'; + if (this.results() && this.results().loading) { + className += ' loading'; + } + return ( +
+
+ this.handleChange(e, e.target.value)} + placeholder={this.props.placeholder ? this.props.placeholder : 'Start typing...'} + /> +
+
+ {this.renderResults('artists')} + {this.renderResults('tracks')} + {this.renderResults('genres')} +
+
+ ); + } } -const mapStateToProps = (state, ownProps) => { - return { - genres: (state.spotify.genres ? state.spotify.genres : null), - results: (state.spotify.autocomplete_results ? state.spotify.autocomplete_results : {}) - } -} +const mapStateToProps = (state, ownProps) => ({ + genres: (state.spotify.genres ? state.spotify.genres : null), + results: (state.spotify.autocomplete_results ? state.spotify.autocomplete_results : {}), +}); -const mapDispatchToProps = (dispatch) => { - return { - coreActions: bindActionCreators(coreActions, dispatch), - uiActions: bindActionCreators(uiActions, dispatch), - spotifyActions: bindActionCreators(spotifyActions, dispatch) - } -} +const mapDispatchToProps = (dispatch) => ({ + coreActions: bindActionCreators(coreActions, dispatch), + uiActions: bindActionCreators(uiActions, dispatch), + spotifyActions: bindActionCreators(spotifyActions, dispatch), +}); -export default connect(mapStateToProps, mapDispatchToProps)(AddSeedField) \ No newline at end of file +export default connect(mapStateToProps, mapDispatchToProps)(AddSeedField); diff --git a/src/js/components/Fields/AddTunabilityField.js b/src/js/components/Fields/AddTunabilityField.js index 3697e75b..5b49d7e3 100755 --- a/src/js/components/Fields/AddTunabilityField.js +++ b/src/js/components/Fields/AddTunabilityField.js @@ -1,154 +1,151 @@ -import React from 'react' -import { connect } from 'react-redux' -import { bindActionCreators } from 'redux' +import React from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; -import Link from './Link' +import Link from './Link'; -import ArtistSentence from './../ArtistSentence' -import * as helpers from '../../helpers' -import * as coreActions from '../../services/core/actions' -import * as uiActions from '../../services/ui/actions' -import * as spotifyActions from '../../services/spotify/actions' +import ArtistSentence from '../ArtistSentence'; +import * as helpers from '../../helpers'; +import * as coreActions from '../../services/core/actions'; +import * as uiActions from '../../services/ui/actions'; +import * as spotifyActions from '../../services/spotify/actions'; -class AddSeedField extends React.Component{ +class AddSeedField extends React.Component { + constructor(props) { + super(props); - constructor(props){ - super(props) + this.state = { + value: '', + }; + this.id = helpers.generateGuid(); + this.timer = null; + this.handleClick = this.handleClick.bind(this); + } - this.state = { - value: '' - } - this.id = helpers.generateGuid() - this.timer = null - this.handleClick = this.handleClick.bind(this) - } + componentDidMount() { + window.addEventListener('click', this.handleClick, false); - componentDidMount(){ - window.addEventListener("click", this.handleClick, false) + if (!this.props.genres) { + this.props.spotifyActions.getGenres(); + } + } - if (!this.props.genres){ - this.props.spotifyActions.getGenres() - } - } + componentWillUnmount() { + window.removeEventListener('click', this.handleClick, false); + } - componentWillUnmount(){ - window.removeEventListener("click", this.handleClick, false) - } + handleClick(e) { + if ($(e.target).closest('.add-seed-field').length <= 0) { + this.props.spotifyActions.clearAutocompleteResults(this.id); + } + } - handleClick(e){ - if ($(e.target).closest('.add-seed-field').length <= 0){ - this.props.spotifyActions.clearAutocompleteResults(this.id) - } - } + handleChange(e, value) { + const self = this; - handleChange(e,value){ - var self = this + // update our local state + this.setState({ value }); - // update our local state - this.setState({value: value}) - - // start a timer to perform the actual search - // this provides a wee delay between key presses to avoid request spamming - clearTimeout(this.timer) - this.timer = setTimeout( - function(){ - self.setState({searching: true}) - self.props.spotifyActions.getAutocompleteResults(self.id,value,['artist','track','genre']) - }, - 500 - ) - } + // start a timer to perform the actual search + // this provides a wee delay between key presses to avoid request spamming + clearTimeout(this.timer); + this.timer = setTimeout( + () => { + self.setState({ searching: true }); + self.props.spotifyActions.getAutocompleteResults(self.id, value, ['artist', 'track', 'genre']); + }, + 500, + ); + } - handleSelect(e,item){ - this.setState({value: ''}) - this.props.onSelect(e,item.uri) - this.props.spotifyActions.clearAutocompleteResults(this.id) + handleSelect(e, item) { + this.setState({ value: '' }); + this.props.onSelect(e, item.uri); + this.props.spotifyActions.clearAutocompleteResults(this.id); - // Add our selected item to our global index - switch (helpers.uriType(item.uri)){ + // Add our selected item to our global index + switch (helpers.uriType(item.uri)) { + case 'artist': + this.props.coreActions.artistsLoaded(item); + break; - case 'artist': - this.props.coreActions.artistsLoaded(item); - break; + case 'track': + this.props.coreActions.tracksLoaded(item); + break; + } + } - case 'track': - this.props.coreActions.tracksLoaded(item); - break; - } - } + results() { + if (typeof (this.props.results) === 'undefined') { + return null; + } if (typeof (this.props.results[this.id]) === 'undefined') { + return null; + } + return this.props.results[this.id]; + } - results(){ - if (typeof(this.props.results) === 'undefined'){ - return null - } else if (typeof(this.props.results[this.id]) === 'undefined'){ - return null - } else { - return this.props.results[this.id] - } - } + renderResults(type) { + const results = this.results(); + if (!results || typeof (results[type]) === 'undefined' || results[type].length <= 0) return null; - renderResults(type){ - var results = this.results() - if (!results || typeof(results[type]) === 'undefined' || results[type].length <= 0) return null - - // only show the first 3 - var items = results[type].slice(0,3) + // only show the first 3 + const items = results[type].slice(0, 3); - return ( -
-

{type}

- { - items.map(item => { - return ( -
this.handleSelect(e,item)}> - {item.name} - {type == 'tracks' ? : null} -
- ) - }) + return ( +
+

{type}

+ { + items.map((item) => ( +
this.handleSelect(e, item)}> + {item.name} + {type == 'tracks' ? ( + + {' '} + + + ) : null} +
+ )) } -
- ) - } +
+ ); + } - render(){ - var className = "field autocomplete-field add-seed-field" - if (this.results() && this.results().loading){ - className += " loading" - } - return ( -
-
- this.handleChange(e,e.target.value)} - placeholder={this.props.placeholder ? this.props.placeholder : "Start typing..."} /> -
-
- {this.renderResults('artists')} - {this.renderResults('tracks')} - {this.renderResults('genres')} -
-
- ); - } + render() { + let className = 'field autocomplete-field add-seed-field'; + if (this.results() && this.results().loading) { + className += ' loading'; + } + return ( +
+
+ this.handleChange(e, e.target.value)} + placeholder={this.props.placeholder ? this.props.placeholder : 'Start typing...'} + /> +
+
+ {this.renderResults('artists')} + {this.renderResults('tracks')} + {this.renderResults('genres')} +
+
+ ); + } } -const mapStateToProps = (state, ownProps) => { - return { - genres: (state.ui.genres ? state.ui.genres : null), - results: (state.spotify.autocomplete_results ? state.spotify.autocomplete_results : {}) - } -} +const mapStateToProps = (state, ownProps) => ({ + genres: (state.ui.genres ? state.ui.genres : null), + results: (state.spotify.autocomplete_results ? state.spotify.autocomplete_results : {}), +}); -const mapDispatchToProps = (dispatch) => { - return { - coreActions: bindActionCreators(coreActions, dispatch), - uiActions: bindActionCreators(uiActions, dispatch), - spotifyActions: bindActionCreators(spotifyActions, dispatch) - } -} +const mapDispatchToProps = (dispatch) => ({ + coreActions: bindActionCreators(coreActions, dispatch), + uiActions: bindActionCreators(uiActions, dispatch), + spotifyActions: bindActionCreators(spotifyActions, dispatch), +}); -export default connect(mapStateToProps, mapDispatchToProps)(AddSeedField) \ No newline at end of file +export default connect(mapStateToProps, mapDispatchToProps)(AddSeedField); diff --git a/src/js/components/Fields/ColourField.js b/src/js/components/Fields/ColourField.js index 8e85018d..f3323f0f 100755 --- a/src/js/components/Fields/ColourField.js +++ b/src/js/components/Fields/ColourField.js @@ -2,48 +2,48 @@ import React, { memo } from 'react'; import Icon from '../Icon'; -export default memo((props) => { +export default memo((props) => { + const colours = [ + '', + 'white', + 'mid_grey', + 'grey', + 'dark_grey', + 'black', + 'turquoise', + 'green', + 'blue', + 'light_blue', + 'yellow', + 'orange', + 'red', + ]; - const colours = [ - '', - 'white', - 'mid_grey', - 'grey', - 'dark_grey', - 'black', - 'turquoise', - 'green', - 'blue', - 'light_blue', - 'yellow', - 'orange', - 'red' - ]; + return ( +
+ { + colours.map((colour) => { + let text_colour = 'white'; - return ( -
- { - colours.map(colour => { - let text_colour = "white"; + switch (colour) { + case 'yellow': + case 'white': + case 'light_blue': + text_colour = 'black'; + break; + } - switch (colour){ - case 'yellow': - case 'white': - case 'light_blue': - text_colour = "black"; - break; - } - - return ( -
props.onChange(colour)}> - {props.colour == colour ? : null} -
- ); + return ( +
props.onChange(colour)} + > + {props.colour == colour ? : null} +
+ ); }) } -
- ); -}); \ No newline at end of file +
+ ); +}); diff --git a/src/js/components/Fields/Commands.js b/src/js/components/Fields/Commands.js index 11124e9e..5bec4620 100644 --- a/src/js/components/Fields/Commands.js +++ b/src/js/components/Fields/Commands.js @@ -1,84 +1,82 @@ -import React from 'react' +import React from 'react'; import Sortable from 'react-sortablejs'; import Icon from '../Icon'; import Link from '../Link'; import * as helpers from '../../helpers'; -export default class Commands extends React.Component{ +export default class Commands extends React.Component { + constructor(props) { + super(props); + } - constructor(props){ - super(props); - } + onChange(order) { + const commands = {}; + for (let i = 0; i <= order.length; i++) { + const command = this.props.commands[order[i]]; + if (command) { + commands[command.id] = { ...command, ...{ sort_order: i } }; + } + } - onChange(order){ - let commands = {}; - for (let i = 0; i <= order.length; i++){ - let command = this.props.commands[order[i]]; - if (command){ - commands[command.id] = {...command, ...{sort_order: i}}; - } - } + this.props.onChange(commands); + } - this.props.onChange(commands); - } + commands() { + let commands = []; - commands(){ - let commands = []; - - if (this.props.commands){ - for (let key of Object.keys(this.props.commands)){ - commands.push({...this.props.commands[key]}); - } - } - - commands = helpers.sortItems(commands, 'sort_order'); - - return commands; - } + if (this.props.commands) { + for (const key of Object.keys(this.props.commands)) { + commands.push({ ...this.props.commands[key] }); + } + } - render(){ - const commands = this.commands(); - - if (!commands){ - return null; - } + commands = helpers.sortItems(commands, 'sort_order'); - return ( - {this.onChange(order)}}> - { - commands.map(command => { - return ( -
-
- -
- - -
-
- {command.name ? command.name : {command.url}} -
-
- -
- ); - }) + return commands; + } + + render() { + const commands = this.commands(); + + if (!commands) { + return null; + } + + return ( + { this.onChange(order); }} + > + { + commands.map((command) => ( +
+
+ +
+ + +
+
+ {command.name ? command.name : {command.url}} +
+
+ +
+ )) } -
- ); - } -} \ No newline at end of file +
+ ); + } +} diff --git a/src/js/components/Fields/ConfirmationButton.js b/src/js/components/Fields/ConfirmationButton.js index bf993f68..ec3e8fe1 100755 --- a/src/js/components/Fields/ConfirmationButton.js +++ b/src/js/components/Fields/ConfirmationButton.js @@ -1,81 +1,80 @@ -import React from 'react' +import React from 'react'; -export default class ConfirmationButton extends React.Component{ +export default class ConfirmationButton extends React.Component { + constructor(props) { + super(props); + this.state = { + timing_out: false, + confirming: false, + }; + this.confirming = false; + this.unconfirmTimer = false; + } - constructor(props){ - super(props); - this.state = { - timing_out: false, - confirming: false - } - this.confirming = false; - this.unconfirmTimer = false; - } + componentWillUnmount() { + clearTimeout(this.unconfirmTimer); + } - componentWillUnmount(){ - clearTimeout(this.unconfirmTimer ); - } + handleClick(e) { + if (this.state.confirming) { + this.setState({ confirming: false }); + this.props.onConfirm(); + } else { + this.setState({ confirming: true }); + } + } - handleClick(e){ - if (this.state.confirming){ - this.setState({ confirming: false }); - this.props.onConfirm(); - } else { - this.setState({ confirming: true }); - } - } + handleMouseEnter(e) { + this.setState({ timing_out: false }); + clearTimeout(this.unconfirmTimer); + } - handleMouseEnter(e){ - this.setState({ timing_out: false }); - clearTimeout(this.unconfirmTimer ); - } + handleMouseLeave(e) { + if (this.state.confirming) { + this.setState({ timing_out: true }); + this.unconfirmTimer = setTimeout( + () => { + this.setState({ confirming: false }); + }, + 2000, + ); + } + } - handleMouseLeave(e){ - if (this.state.confirming){ - this.setState({ timing_out: true }); - this.unconfirmTimer = setTimeout( - function(){ - this.setState({ confirming: false }); - }.bind(this), - 2000 - ); - } - } + render() { + let className = 'button'; + let { content } = this.props; - render(){ + if (this.state.confirming) { + className += ' button--confirming'; + content = this.props.confirmingContent; + if (this.state.timing_out) { + className += ' button--timing-out'; + } + } - var className = 'button'; - var content = this.props.content; + if (this.props.working) { + className += ' button--working'; - if (this.state.confirming){ - className += ' button--confirming'; - content = this.props.confirmingContent; - if (this.state.timing_out){ - className += ' button--timing-out'; - } - } + if (this.props.workingContent) { + content = this.props.workingContent; + } + } - if (this.props.working){ - className += ' button--working'; + if (this.props.className) { + className += ` ${this.props.className}`; + } - if (this.props.workingContent){ - content = this.props.workingContent; - } - } - - if (this.props.className){ - className += ' '+this.props.className; - } - - return ( - - ); - } -} \ No newline at end of file + return ( + + ); + } +} diff --git a/src/js/components/Fields/DropdownField.js b/src/js/components/Fields/DropdownField.js index 37b78477..5783586d 100755 --- a/src/js/components/Fields/DropdownField.js +++ b/src/js/components/Fields/DropdownField.js @@ -1,156 +1,154 @@ -import React from 'react' -import { connect } from 'react-redux' -import { bindActionCreators } from 'redux' +import React from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; import Icon from '../Icon'; -export default class DropdownField extends React.Component{ +export default class DropdownField extends React.Component { + constructor(props) { + super(props); - constructor(props){ - super(props); + // Create a "unique" id. This is human-controlled to avoid requiring + // other libraries for a very simple purpose: clicking outside + this.uid = this.props.name.replace(' ', '_').toLowerCase(); + if (this.props.uid) { + this.uid += `_${this.props.uid}`; + } - // Create a "unique" id. This is human-controlled to avoid requiring - // other libraries for a very simple purpose: clicking outside - this.uid = this.props.name.replace(' ','_').toLowerCase(); - if (this.props.uid){ - this.uid += "_"+this.props.uid; - } + this.state = { + expanded: false, + }; - this.state = { - expanded: false - } + this.handleClick = this.handleClick.bind(this); + } - this.handleClick = this.handleClick.bind(this); - } + componentDidMount() { + window.addEventListener('click', this.handleClick, false); + } - componentDidMount(){ - window.addEventListener("click", this.handleClick, false); - } + componentWillUnmount() { + window.removeEventListener('click', this.handleClick, false); + } - componentWillUnmount(){ - window.removeEventListener("click", this.handleClick, false); - } + setExpanded(expanded = !this.state.expanded) { + if (expanded) { + this.setState({ expanded }); + window.addEventListener('click', this.handleClick, false); + } else { + this.setState({ expanded }); + window.removeEventListener('click', this.handleClick, false); + } + } - setExpanded(expanded = !this.state.expanded){ - if (expanded){ - this.setState({expanded: expanded}); - window.addEventListener("click", this.handleClick, false); - } else { - this.setState({expanded: expanded}); - window.removeEventListener("click", this.handleClick, false); - } - } + handleClick(e) { + // TODO: remove dependency on jQuery and explore the performance of this functionality + if ($(e.target).closest('.dropdown-field').attr('data-uid') != this.uid && this.state.expanded) { + this.setExpanded(false); + } + } - handleClick(e){ - // TODO: remove dependency on jQuery and explore the performance of this functionality - if ($(e.target).closest('.dropdown-field').attr('data-uid') != this.uid && this.state.expanded){ - this.setExpanded(false); - } - } + handleChange(value, is_selected) { + const current_value = this.props.value; + if (this.isMultiSelect()) { + if (value == 'select-all') { + var new_value = []; + for (let i = 0; i < this.props.options.length; i++) { + new_value.push(this.props.options[i].value); + } + } else if (is_selected) { + const index = current_value.indexOf(value); + current_value.splice(index, 1); + var new_value = current_value; + } else { + current_value.push(value); + var new_value = current_value; + } + } else { + var new_value = value; - handleChange(value, is_selected){ + // Collapse our menu + this.setExpanded(false); + } - var current_value = this.props.value; - if (this.isMultiSelect()){ - if (value == 'select-all'){ - var new_value = []; - for (var i = 0; i < this.props.options.length; i++){ - new_value.push(this.props.options[i].value); - } - } else if (is_selected){ - var index = current_value.indexOf(value); - current_value.splice(index, 1); - var new_value = current_value; - } else { - current_value.push(value); - var new_value = current_value; - } - } else { - var new_value = value; + return this.props.handleChange(new_value); + } - // Collapse our menu - this.setExpanded(false); - } + isMultiSelect() { + return this.props.value instanceof Array; + } - return this.props.handleChange(new_value); - } + render() { + if (!this.props.options) { + return null; + } - isMultiSelect(){ - return this.props.value instanceof Array; - } + let className = 'dropdown-field'; + if (this.state.expanded) { + className += ' expanded'; + } + if (this.props.no_status_icon) { + className += ' no-status-icon'; + } + if (this.props.no_label) { + className += ' no-label'; + } + if (this.props.button) { + className += ' buttonify'; + } + if (this.props.className) { + className += ` ${this.props.className}`; + } + let current_value = null; + if (this.props.value !== undefined) { + current_value = this.props.value; + } else if (this.props.options.length > 0) { + current_value = this.props.options[0].value; + } - render(){ - if (!this.props.options){ - return null; - } + let selected_icon = ; + if (this.props.selected_icon) { + selected_icon = ; + } - var className = 'dropdown-field'; - if (this.state.expanded){ - className += ' expanded'; - } - if (this.props.no_status_icon){ - className += ' no-status-icon'; - } - if (this.props.no_label){ - className += ' no-label'; - } - if (this.props.button){ - className += ' buttonify'; - } - if (this.props.className){ - className += ' '+this.props.className; - } - var current_value = null; - if (this.props.value !== undefined){ - current_value = this.props.value; - } else if (this.props.options.length > 0){ - current_value = this.props.options[0].value; - } + const options = Object.assign([], this.props.options); + if (this.isMultiSelect()) { + options.push({ + value: 'select-all', + label: 'Select all', + className: 'mid_grey-text', + }); + } - var selected_icon = - if (this.props.selected_icon){ - selected_icon = - } - - var options = Object.assign([], this.props.options); - if (this.isMultiSelect()){ - options.push({ - value: 'select-all', - label: 'Select all', - className: 'mid_grey-text' - }); - } - - return ( -
-
this.setExpanded()}> - {this.props.icon ? : null} - - {this.props.name} - {this.isMultiSelect() ? ' ('+current_value.length+')' : null} - -
-
-
- { - options.map(option => { - if (this.isMultiSelect()){ - var is_selected = current_value.indexOf(option.value) > -1; - } else { - var is_selected = current_value === option.value; - } - return ( -
this.handleChange(option.value, is_selected)}> - {!this.props.no_status_icon && is_selected ? selected_icon : null} - {option.label} -
- ) + return ( +
+
this.setExpanded()}> + {this.props.icon ? : null} + + {this.props.name} + {this.isMultiSelect() ? ` (${current_value.length})` : null} + +
+
+
+ { + options.map((option) => { + if (this.isMultiSelect()) { + var is_selected = current_value.indexOf(option.value) > -1; + } else { + var is_selected = current_value === option.value; + } + return ( +
this.handleChange(option.value, is_selected)}> + {!this.props.no_status_icon && is_selected ? selected_icon : null} + {option.label} +
+ ); }) } -
-
-
- ) - } -} \ No newline at end of file +
+
+
+ ); + } +} diff --git a/src/js/components/Fields/Dropzone.js b/src/js/components/Fields/Dropzone.js index 6568a5f9..12a63e18 100755 --- a/src/js/components/Fields/Dropzone.js +++ b/src/js/components/Fields/Dropzone.js @@ -1,49 +1,48 @@ -import React from 'react' -import { connect } from 'react-redux' -import { bindActionCreators } from 'redux' +import React from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; -import Icon from './../Icon' +import Icon from '../Icon'; -export default class Dropzone extends React.Component{ +export default class Dropzone extends React.Component { + constructor(props) { + super(props); - constructor(props){ - super(props) + this.state = { + hover: false, + }; - this.state = { - hover: false - } + this.handleMouseOver = this.handleMouseOver.bind(this); + this.handleMouseOut = this.handleMouseOut.bind(this); + } - this.handleMouseOver = this.handleMouseOver.bind(this) - this.handleMouseOut = this.handleMouseOut.bind(this) - } + componentWillMount() { + window.addEventListener('mouseover', this.handleMouseOver, false); + window.addEventListener('mouseout', this.handleMouseOut, false); + } - componentWillMount(){ - window.addEventListener("mouseover", this.handleMouseOver, false); - window.addEventListener("mouseout", this.handleMouseOut, false); - } + componentWillUnmount() { + window.removeEventListener('mouseover', this.handleMouseOver, false); + window.removeEventListener('mouseout', this.handleMouseOut, false); + } - componentWillUnmount(){ - window.removeEventListener("mouseover", this.handleMouseOver, false); - window.removeEventListener("mouseout", this.handleMouseOut, false); - } + handleMouseOver(e) { + this.setState({ hover: true }); + } - handleMouseOver(e){ - this.setState({hover: true}) - } + handleMouseOut(e) { + this.setState({ hover: false }); + } - handleMouseOut(e){ - this.setState({hover: false}) - } + render() { + if (!this.props.data) return null; - render(){ - if (!this.props.data ) return null - - return ( -
this.props.handleMouseUp(e) }> - - { this.props.data.title } -
- ); - } -} \ No newline at end of file + return ( +
this.props.handleMouseUp(e)}> + + { this.props.data.title } +
+ ); + } +} diff --git a/src/js/components/Fields/Dropzones.js b/src/js/components/Fields/Dropzones.js index 6f122053..8028e432 100755 --- a/src/js/components/Fields/Dropzones.js +++ b/src/js/components/Fields/Dropzones.js @@ -4,93 +4,86 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import { withRouter } from 'react-router'; -import Dropzone from './Dropzone' +import Dropzone from './Dropzone'; -import * as uiActions from '../../services/ui/actions' -import * as mopidyActions from '../../services/mopidy/actions' -import * as spotifyActions from '../../services/spotify/actions' +import * as uiActions from '../../services/ui/actions'; +import * as mopidyActions from '../../services/mopidy/actions'; +import * as spotifyActions from '../../services/spotify/actions'; -class Dropzones extends React.Component{ +class Dropzones extends React.Component { + constructor(props) { + super(props); - constructor(props){ - super(props); + this._zones = [ + { + title: 'Add to queue', + icon: 'play_arrow', + action: 'enqueue', + }, + { + title: 'Play next', + icon: 'play_arrow', + action: 'enqueue_next', + }, + { + title: 'Add to playlist', + icon: 'playlist_add', + action: 'add_to_playlist', + accepts: ['tltrack', 'track', 'album', 'playlist', 'artist'], + }, + ]; + } - this._zones = [ - { - title: 'Add to queue', - icon: 'play_arrow', - action: 'enqueue' - }, - { - title: 'Play next', - icon: 'play_arrow', - action: 'enqueue_next' - }, - { - title: 'Add to playlist', - icon: 'playlist_add', - action: 'add_to_playlist', - accepts: ['tltrack','track','album','playlist','artist'] - } - ] - } + handleMouseMove(e) { + if (!this.props.dragger || !this.props.dragger.active) return null; + this.props.uiActions.dragMove(e); + } - handleMouseMove(e){ - if (!this.props.dragger || !this.props.dragger.active ) return null; - this.props.uiActions.dragMove(e ) - } + handleMouseUp(e, index) { + const target = this._zones[index]; + const { victims } = this.props.dragger; + const uris = []; + for (let i = 0; i < victims.length; i++) { + uris.push(victims[i].uri); + } - handleMouseUp(e, index){ - var target = this._zones[index] - var victims = this.props.dragger.victims - var uris = [] - for(var i = 0; i < victims.length; i++){ - uris.push(victims[i].uri ) - } + switch (target.action) { + case 'enqueue': + this.props.mopidyActions.enqueueURIs(uris, this.props.dragger.from_uri); + break; - switch(target.action){ - case 'enqueue': - this.props.mopidyActions.enqueueURIs(uris, this.props.dragger.from_uri) - break + case 'enqueue_next': + this.props.mopidyActions.enqueueURIs(uris, this.props.dragger.from_uri, true); + break; - case 'enqueue_next': - this.props.mopidyActions.enqueueURIs(uris, this.props.dragger.from_uri, true) - break + case 'add_to_playlist': + this.props.history.push(`/add-to-playlist/${encodeURIComponent(uris.join(','))}`); + // uris + break; + } + } - case 'add_to_playlist': - this.props.history.push('/add-to-playlist/'+encodeURIComponent(uris.join(','))); - //uris - break - } - } + render() { + if (!this.props.dragger || !this.props.dragger.active) return null; - render(){ - if (!this.props.dragger || !this.props.dragger.active ) return null - - return ( -
- { - this._zones.map((zone, index) => { - return this.handleMouseUp(e, index) }/> - }) + return ( +
+ { + this._zones.map((zone, index) => this.handleMouseUp(e, index)} />) } -
- ); - } +
+ ); + } } -const mapStateToProps = (state, ownProps) => { - return { - dragger: state.ui.dragger - } -} +const mapStateToProps = (state, ownProps) => ({ + dragger: state.ui.dragger, +}); -const mapDispatchToProps = (dispatch) => { - return { - uiActions: bindActionCreators(uiActions, dispatch), - spotifyActions: bindActionCreators(spotifyActions, dispatch), - mopidyActions: bindActionCreators(mopidyActions, dispatch) - } -} +const mapDispatchToProps = (dispatch) => ({ + uiActions: bindActionCreators(uiActions, dispatch), + spotifyActions: bindActionCreators(spotifyActions, dispatch), + mopidyActions: bindActionCreators(mopidyActions, dispatch), +}); -export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Dropzones)); \ No newline at end of file +export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Dropzones)); diff --git a/src/js/components/Fields/FilterField.js b/src/js/components/Fields/FilterField.js index d0a85421..ec04e03f 100755 --- a/src/js/components/Fields/FilterField.js +++ b/src/js/components/Fields/FilterField.js @@ -1,76 +1,75 @@ -import React from 'react' +import React from 'react'; import Link from '../Link'; import Icon from '../Icon'; -export default class FilterField extends React.Component{ +export default class FilterField extends React.Component { + constructor(props) { + super(props); + this.state = { + value: '', + active: (!!this.props.slim_mode), + }; - constructor(props){ - super(props) - this.state = { - value: '', - active: (this.props.slim_mode ? true : false) - } + this.handleKeyUp = this.handleKeyUp.bind(this); + } - this.handleKeyUp = this.handleKeyUp.bind(this) - } + componentWillMount() { + window.addEventListener('keyup', this.handleKeyUp, false); + this.setState({ value: this.props.initialValue }); + } - componentWillMount(){ - window.addEventListener("keyup", this.handleKeyUp, false); - this.setState({value: this.props.initialValue}); - } + componentWillUnmount() { + window.removeEventListener('keyup', this.handleKeyUp, false); + } - componentWillUnmount(){ - window.removeEventListener("keyup", this.handleKeyUp, false); - } + handleKeyUp(e) { + if (e.keyCode == 27 && !this.props.slim_mode) { + e.preventDefault(); - handleKeyUp(e){ - if (e.keyCode == 27 && !this.props.slim_mode){ - e.preventDefault() + this.setState({ + value: '', + active: false, + }); - this.setState({ - value: '', - active: false - }) + this.handleChange(''); + } + } - this.handleChange('') - } - } + activate() { + this.setState({ active: true }); + } - activate(){ - this.setState({active: true}) - } + handleChange(value) { + this.setState({ + value, + active: (this.props.slim_mode ? true : (value != '')), + }); + this.props.handleChange(value); + } - handleChange(value){ - this.setState({ - value: value, - active: (this.props.slim_mode ? true : (value != '')) - }) - this.props.handleChange(value) - } + handleBlur() { + if (this.state.value == '' && !this.props.slim_mode) { + this.setState({ active: false }); + } + } - handleBlur(){ - if (this.state.value == '' && !this.props.slim_mode){ - this.setState({active: false}) - } - } - - render(){ - return ( - this.activate()}> -
- this.activate()} - onBlur={e => this.handleBlur()} - onChange={e => this.handleChange(e.target.value)} - /> - - -
- ) - } -} \ No newline at end of file + render() { + return ( + this.activate()}> +
+ this.activate()} + onBlur={(e) => this.handleBlur()} + onChange={(e) => this.handleChange(e.target.value)} + /> + + +
+ ); + } +} diff --git a/src/js/components/Fields/FollowButton.js b/src/js/components/Fields/FollowButton.js index 678a2e19..9c073df1 100755 --- a/src/js/components/Fields/FollowButton.js +++ b/src/js/components/Fields/FollowButton.js @@ -1,60 +1,54 @@ -import React from 'react' -import { connect } from 'react-redux' -import { createStore, bindActionCreators } from 'redux' +import React from 'react'; +import { connect } from 'react-redux'; +import { createStore, bindActionCreators } from 'redux'; import Link from '../Link'; -import * as helpers from '../../helpers' -import * as uiActions from '../../services/ui/actions' -import * as spotifyActions from '../../services/spotify/actions' +import * as helpers from '../../helpers'; +import * as uiActions from '../../services/ui/actions'; +import * as spotifyActions from '../../services/spotify/actions'; -class FollowButton extends React.Component{ +class FollowButton extends React.Component { + constructor(props) { + super(props); + } - constructor(props){ - super(props); - } + remove() { + this.props.spotifyActions.following(this.props.uri, 'DELETE'); + } - remove(){ - this.props.spotifyActions.following(this.props.uri, 'DELETE'); - } + add() { + this.props.spotifyActions.following(this.props.uri, 'PUT'); + } - add(){ - this.props.spotifyActions.following(this.props.uri, 'PUT'); - } + render() { + if (!this.props.uri) { + return false; + } - render(){ - if (!this.props.uri){ - return false; - } + let className = 'button'; + if (this.props.className) { + className += ` ${this.props.className}`; + } - var className = 'button'; - if (this.props.className){ - className += ' '+this.props.className; - } - - if (!this.props.spotify_authorized){ - return - } else if (this.props.is_following === true){ - return - } else { - return - } - } + if (!this.props.spotify_authorized) { + return ; + } if (this.props.is_following === true) { + return ; + } + return ; + } } -const mapStateToProps = (state, ownProps) => { - return { - load_queue: state.ui.load_queue, - spotify_authorized: state.spotify.authorization - } -} +const mapStateToProps = (state, ownProps) => ({ + load_queue: state.ui.load_queue, + spotify_authorized: state.spotify.authorization, +}); -const mapDispatchToProps = (dispatch) => { - return { - uiActions: bindActionCreators(uiActions, dispatch), - spotifyActions: bindActionCreators(spotifyActions, dispatch) - } -} +const mapDispatchToProps = (dispatch) => ({ + uiActions: bindActionCreators(uiActions, dispatch), + spotifyActions: bindActionCreators(spotifyActions, dispatch), +}); -export default connect(mapStateToProps, mapDispatchToProps)(FollowButton) \ No newline at end of file +export default connect(mapStateToProps, mapDispatchToProps)(FollowButton); diff --git a/src/js/components/Fields/GeniusAuthenticationFrame.js b/src/js/components/Fields/GeniusAuthenticationFrame.js index 8b4fbce4..6e99b2f9 100755 --- a/src/js/components/Fields/GeniusAuthenticationFrame.js +++ b/src/js/components/Fields/GeniusAuthenticationFrame.js @@ -1,124 +1,116 @@ -import React from 'react' -import { connect } from 'react-redux' -import { bindActionCreators } from 'redux' +import React from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; import * as helpers from '../../helpers'; -import * as uiActions from '../../services/ui/actions' -import * as geniusActions from '../../services/genius/actions' +import * as uiActions from '../../services/ui/actions'; +import * as geniusActions from '../../services/genius/actions'; -class GeniusAuthenticationFrame extends React.Component{ +class GeniusAuthenticationFrame extends React.Component { + constructor(props) { + super(props); - constructor(props){ - super(props); + this.state = { + authorizing: false, + }; - this.state = { - authorizing: false - } + this.handleMessage = this.handleMessage.bind(this); + } - this.handleMessage = this.handleMessage.bind(this); - } + componentDidMount() { + window.addEventListener('message', this.handleMessage, false); + } - componentDidMount(){ - window.addEventListener('message', this.handleMessage, false); - } + componentWillUnmount() { + window.removeEventListener('message', this.handleMessage, false); + } - componentWillUnmount(){ - window.removeEventListener("message", this.handleMessage, false); - } + handleMessage(event) { + const data = helpers.toJSON(event.data); - handleMessage(event){ - let data = helpers.toJSON(event.data); + // Only digest messages relevant to us + if (data.origin != 'auth_genius') { + return; + } - // Only digest messages relevant to us - if (data.origin != 'auth_genius'){ - return; - } + // Only allow incoming data from our authorized authenticator proxy + const authorization_domain = this.props.authorization_url.substring(0, this.props.authorization_url.indexOf('/', 8)); + if (event.origin != authorization_domain) { + this.props.uiActions.createNotification({ content: `Authorization failed. ${event.origin} is not the configured authorization_url.`, type: 'bad' }); + return false; + } - // Only allow incoming data from our authorized authenticator proxy - var authorization_domain = this.props.authorization_url.substring(0,this.props.authorization_url.indexOf('/',8)) - if (event.origin != authorization_domain){ - this.props.uiActions.createNotification({content: 'Authorization failed. '+event.origin+' is not the configured authorization_url.', type: 'bad'}); - return false - } + // Bounced with an error + if (data.error !== undefined) { + this.props.uiActions.createNotification({ content: data.message, type: 'bad' }); - // Bounced with an error - if (data.error !== undefined){ - this.props.uiActions.createNotification({content: data.message, type: 'bad'}); + // No errors? We're in! + } else { + this.props.geniusActions.authorizationGranted(data); + this.props.geniusActions.getMe(); + } - // No errors? We're in! - } else { - this.props.geniusActions.authorizationGranted(data) - this.props.geniusActions.getMe(); - } + // Turn off our authorizing switch + this.setState({ authorizing: false }); + } - // Turn off our authorizing switch - this.setState({authorizing: false}); - } + startAuthorization() { + const self = this; + this.setState({ authorizing: true }); - startAuthorization(){ - - var self = this - this.setState({authorizing: true}) - - // Open an authentication request window - var url = this.props.authorization_url+'?action=authorize&scope=me' - var popup = window.open(url,"popup","height=580,width=350"); - popup.name = "GeniusAuthenticationWindow"; - - // Start timer to check our popup's state - var timer = setInterval(checkPopup, 1000); - function checkPopup(){ + // Open an authentication request window + const url = `${this.props.authorization_url}?action=authorize&scope=me`; + const popup = window.open(url, 'popup', 'height=580,width=350'); + popup.name = 'GeniusAuthenticationWindow'; + // Start timer to check our popup's state + const timer = setInterval(checkPopup, 1000); + function checkPopup() { // Popup has been closed - if (typeof(popup) !== 'undefined' && popup){ - if (popup.closed){ - self.setState({authorizing: false}) - clearInterval(timer); - } - - // Popup does not exist, so must have been blocked - } else { - self.props.uiActions.createNotification({content: 'Popup blocked. Please allow popups and try again.', type: 'bad'}); - self.setState({authorizing: false}) - clearInterval(timer); - } + if (typeof (popup) !== 'undefined' && popup) { + if (popup.closed) { + self.setState({ authorizing: false }); + clearInterval(timer); } - } - render(){ - if (this.state.authorizing){ - return ( - + // Popup does not exist, so must have been blocked + } else { + self.props.uiActions.createNotification({ content: 'Popup blocked. Please allow popups and try again.', type: 'bad' }); + self.setState({ authorizing: false }); + clearInterval(timer); + } + } + } + + render() { + if (this.state.authorizing) { + return ( + Authorizing... - - ) - } else if (this.props.authorized){ - return ( - this.props.geniusActions.revokeAuthorization()}>Log out - ) - } else { - return ( - this.startAuthorization()}>Log in - ) - } - } + + ); + } if (this.props.authorized) { + return ( + this.props.geniusActions.revokeAuthorization()}>Log out + ); + } + return ( + this.startAuthorization()}>Log in + ); + } } -const mapStateToProps = (state, ownProps) => { - return { - authorization_url: state.genius.authorization_url, - authorized: state.genius.authorization, - authorizing: state.genius.authorizing - } -} +const mapStateToProps = (state, ownProps) => ({ + authorization_url: state.genius.authorization_url, + authorized: state.genius.authorization, + authorizing: state.genius.authorizing, +}); -const mapDispatchToProps = (dispatch) => { - return { - uiActions: bindActionCreators(uiActions, dispatch), - geniusActions: bindActionCreators(geniusActions, dispatch) - } -} +const mapDispatchToProps = (dispatch) => ({ + uiActions: bindActionCreators(uiActions, dispatch), + geniusActions: bindActionCreators(geniusActions, dispatch), +}); -export default connect(mapStateToProps, mapDispatchToProps)(GeniusAuthenticationFrame) +export default connect(mapStateToProps, mapDispatchToProps)(GeniusAuthenticationFrame); diff --git a/src/js/components/Fields/IconField.js b/src/js/components/Fields/IconField.js index d9023b19..41510922 100755 --- a/src/js/components/Fields/IconField.js +++ b/src/js/components/Fields/IconField.js @@ -2,957 +2,955 @@ import React, { memo } from 'react'; import Icon from '../Icon'; -export default memo((props) => { +export default memo((props) => { + const icons = (props.icons ? props.icons : [ + '3d_rotation', + 'ac_unit', + 'access_alarm', + 'access_alarms', + 'access_time', + 'accessibility', + 'accessible', + 'account_balance', + 'account_balance_wallet', + 'account_box', + 'account_circle', + 'adb', + 'add', + 'add_a_photo', + 'add_alarm', + 'add_alert', + 'add_box', + 'add_circle', + 'add_circle_outline', + 'add_location', + 'add_shopping_cart', + 'add_to_photos', + 'add_to_queue', + 'adjust', + 'airline_seat_flat', + 'airline_seat_flat_angled', + 'airline_seat_individual_suite', + 'airline_seat_legroom_extra', + 'airline_seat_legroom_normal', + 'airline_seat_legroom_reduced', + 'airline_seat_recline_extra', + 'airline_seat_recline_normal', + 'airplanemode_active', + 'airplanemode_inactive', + 'airplay', + 'airport_shuttle', + 'alarm', + 'alarm_add', + 'alarm_off', + 'alarm_on', + 'album', + 'all_inclusive', + 'all_out', + 'android', + 'announcement', + 'apps', + 'archive', + 'arrow_back', + 'arrow_downward', + 'arrow_drop_down', + 'arrow_drop_down_circle', + 'arrow_drop_up', + 'arrow_forward', + 'arrow_upward', + 'art_track', + 'aspect_ratio', + 'assessment', + 'assignment', + 'assignment_ind', + 'assignment_late', + 'assignment_return', + 'assignment_returned', + 'assignment_turned_in', + 'assistant', + 'assistant_photo', + 'attach_file', + 'attach_money', + 'attachment', + 'audiotrack', + 'autorenew', + 'av_timer', + 'backspace', + 'backup', + 'battery_alert', + 'battery_charging_full', + 'battery_full', + 'battery_std', + 'battery_unknown', + 'beach_access', + 'beenhere', + 'block', + 'bluetooth', + 'bluetooth_audio', + 'bluetooth_connected', + 'bluetooth_disabled', + 'bluetooth_searching', + 'blur_circular', + 'blur_linear', + 'blur_off', + 'blur_on', + 'book', + 'bookmark', + 'bookmark_border', + 'border_all', + 'border_bottom', + 'border_clear', + 'border_color', + 'border_horizontal', + 'border_inner', + 'border_left', + 'border_outer', + 'border_right', + 'border_style', + 'border_top', + 'border_vertical', + 'branding_watermark', + 'brightness_1', + 'brightness_2', + 'brightness_3', + 'brightness_4', + 'brightness_5', + 'brightness_6', + 'brightness_7', + 'brightness_auto', + 'brightness_high', + 'brightness_low', + 'brightness_medium', + 'broken_image', + 'brush', + 'bubble_chart', + 'bug_report', + 'build', + 'burst_mode', + 'business', + 'business_center', + 'cached', + 'cake', + 'call', + 'call_end', + 'call_made', + 'call_merge', + 'call_missed', + 'call_missed_outgoing', + 'call_received', + 'call_split', + 'call_to_action', + 'camera', + 'camera_alt', + 'camera_enhance', + 'camera_front', + 'camera_rear', + 'camera_roll', + 'cancel', + 'card_giftcard', + 'card_membership', + 'card_travel', + 'casino', + 'cast', + 'cast_connected', + 'center_focus_strong', + 'center_focus_weak', + 'change_history', + 'chat', + 'chat_bubble', + 'chat_bubble_outline', + 'check', + 'check_box', + 'check_box_outline_blank', + 'check_circle', + 'chevron_left', + 'chevron_right', + 'child_care', + 'child_friendly', + 'chrome_reader_mode', + 'class', + 'clear', + 'clear_all', + 'close', + 'closed_caption', + 'cloud', + 'cloud_circle', + 'cloud_done', + 'cloud_download', + 'cloud_off', + 'cloud_queue', + 'cloud_upload', + 'code', + 'collections', + 'collections_bookmark', + 'color_lens', + 'colorize', + 'comment', + 'compare', + 'compare_arrows', + 'computer', + 'confirmation_number', + 'contact_mail', + 'contact_phone', + 'contacts', + 'content_copy', + 'content_cut', + 'content_paste', + 'control_point', + 'control_point_duplicate', + 'copyright', + 'create', + 'create_new_folder', + 'credit_card', + 'crop', + 'crop_16_9', + 'crop_3_2', + 'crop_5_4', + 'crop_7_5', + 'crop_din', + 'crop_free', + 'crop_landscape', + 'crop_original', + 'crop_portrait', + 'crop_rotate', + 'crop_square', + 'dashboard', + 'data_usage', + 'date_range', + 'dehaze', + 'delete', + 'delete_forever', + 'delete_sweep', + 'description', + 'desktop_mac', + 'desktop_windows', + 'details', + 'developer_board', + 'developer_mode', + 'device_hub', + 'devices', + 'devices_other', + 'dialer_sip', + 'dialpad', + 'directions', + 'directions_bike', + 'directions_boat', + 'directions_bus', + 'directions_car', + 'directions_railway', + 'directions_run', + 'directions_subway', + 'directions_transit', + 'directions_walk', + 'disc_full', + 'dns', + 'do_not_disturb', + 'do_not_disturb_alt', + 'do_not_disturb_off', + 'do_not_disturb_on', + 'dock', + 'domain', + 'done', + 'done_all', + 'donut_large', + 'donut_small', + 'drafts', + 'drag_handle', + 'drive_eta', + 'dvr', + 'edit', + 'edit_location', + 'eject', + 'email', + 'enhanced_encryption', + 'equalizer', + 'error', + 'error_outline', + 'euro_symbol', + 'ev_station', + 'event', + 'event_available', + 'event_busy', + 'event_note', + 'event_seat', + 'exit_to_app', + 'expand_less', + 'expand_more', + 'explicit', + 'explore', + 'exposure', + 'exposure_neg_1', + 'exposure_neg_2', + 'exposure_plus_1', + 'exposure_plus_2', + 'exposure_zero', + 'extension', + 'face', + 'fast_forward', + 'fast_rewind', + 'favorite', + 'favorite_border', + 'featured_play_list', + 'featured_video', + 'feedback', + 'fiber_dvr', + 'fiber_manual_record', + 'fiber_new', + 'fiber_pin', + 'fiber_smart_record', + 'file_download', + 'file_upload', + 'filter', + 'filter_1', + 'filter_2', + 'filter_3', + 'filter_4', + 'filter_5', + 'filter_6', + 'filter_7', + 'filter_8', + 'filter_9', + 'filter_9_plus', + 'filter_b_and_w', + 'filter_center_focus', + 'filter_drama', + 'filter_frames', + 'filter_hdr', + 'filter_list', + 'filter_none', + 'filter_tilt_shift', + 'filter_vintage', + 'find_in_page', + 'find_replace', + 'fingerprint', + 'first_page', + 'fitness_center', + 'flag', + 'flare', + 'flash_auto', + 'flash_off', + 'flash_on', + 'flight', + 'flight_land', + 'flight_takeoff', + 'flip', + 'flip_to_back', + 'flip_to_front', + 'folder', + 'folder_open', + 'folder_shared', + 'folder_special', + 'font_download', + 'format_align_center', + 'format_align_justify', + 'format_align_left', + 'format_align_right', + 'format_bold', + 'format_clear', + 'format_color_fill', + 'format_color_reset', + 'format_color_text', + 'format_indent_decrease', + 'format_indent_increase', + 'format_italic', + 'format_line_spacing', + 'format_list_bulleted', + 'format_list_numbered', + 'format_paint', + 'format_quote', + 'format_shapes', + 'format_size', + 'format_strikethrough', + 'format_textdirection_l_to_r', + 'format_textdirection_r_to_l', + 'format_underlined', + 'forum', + 'forward', + 'forward_10', + 'forward_30', + 'forward_5', + 'free_breakfast', + 'fullscreen', + 'fullscreen_exit', + 'functions', + 'g_translate', + 'gamepad', + 'games', + 'gavel', + 'gesture', + 'get_app', + 'gif', + 'golf_course', + 'gps_fixed', + 'gps_not_fixed', + 'gps_off', + 'grade', + 'gradient', + 'grain', + 'graphic_eq', + 'grid_off', + 'grid_on', + 'group', + 'group_add', + 'group_work', + 'hd', + 'hdr_off', + 'hdr_on', + 'hdr_strong', + 'hdr_weak', + 'headset', + 'headset_mic', + 'healing', + 'hearing', + 'help', + 'help_outline', + 'high_quality', + 'highlight', + 'highlight_off', + 'history', + 'home', + 'hot_tub', + 'hotel', + 'hourglass_empty', + 'hourglass_full', + 'http', + 'https', + 'image', + 'image_aspect_ratio', + 'import_contacts', + 'import_export', + 'important_devices', + 'inbox', + 'indeterminate_check_box', + 'info', + 'info_outline', + 'input', + 'insert_chart', + 'insert_comment', + 'insert_drive_file', + 'insert_emoticon', + 'insert_invitation', + 'insert_link', + 'insert_photo', + 'invert_colors', + 'invert_colors_off', + 'iso', + 'keyboard', + 'keyboard_arrow_down', + 'keyboard_arrow_left', + 'keyboard_arrow_right', + 'keyboard_arrow_up', + 'keyboard_backspace', + 'keyboard_capslock', + 'keyboard_hide', + 'keyboard_return', + 'keyboard_tab', + 'keyboard_voice', + 'kitchen', + 'label', + 'label_outline', + 'landscape', + 'language', + 'laptop', + 'laptop_chromebook', + 'laptop_mac', + 'laptop_windows', + 'last_page', + 'launch', + 'layers', + 'layers_clear', + 'leak_add', + 'leak_remove', + 'lens', + 'library_add', + 'library_books', + 'library_music', + 'lightbulb_outline', + 'line_style', + 'line_weight', + 'linear_scale', + 'link', + 'linked_camera', + 'list', + 'live_help', + 'live_tv', + 'local_activity', + 'local_airport', + 'local_atm', + 'local_bar', + 'local_cafe', + 'local_car_wash', + 'local_convenience_store', + 'local_dining', + 'local_drink', + 'local_florist', + 'local_gas_station', + 'local_grocery_store', + 'local_hospital', + 'local_hotel', + 'local_laundry_service', + 'local_library', + 'local_mall', + 'local_movies', + 'local_offer', + 'local_parking', + 'local_pharmacy', + 'local_phone', + 'local_pizza', + 'local_play', + 'local_post_office', + 'local_printshop', + 'local_see', + 'local_shipping', + 'local_taxi', + 'location_city', + 'location_disabled', + 'location_off', + 'location_on', + 'location_searching', + 'lock', + 'lock_open', + 'lock_outline', + 'looks', + 'looks_3', + 'looks_4', + 'looks_5', + 'looks_6', + 'looks_one', + 'looks_two', + 'loop', + 'loupe', + 'low_priority', + 'loyalty', + 'mail', + 'mail_outline', + 'map', + 'markunread', + 'markunread_mailbox', + 'memory', + 'menu', + 'merge_type', + 'message', + 'mic', + 'mic_none', + 'mic_off', + 'mms', + 'mode_comment', + 'mode_edit', + 'monetization_on', + 'money_off', + 'monochrome_photos', + 'mood', + 'mood_bad', + 'more', + 'more_horiz', + 'more_vert', + 'motorcycle', + 'mouse', + 'move_to_inbox', + 'movie', + 'movie_creation', + 'movie_filter', + 'multiline_chart', + 'music_note', + 'music_video', + 'my_location', + 'nature', + 'nature_people', + 'navigate_before', + 'navigate_next', + 'navigation', + 'near_me', + 'network_cell', + 'network_check', + 'network_locked', + 'network_wifi', + 'new_releases', + 'next_week', + 'nfc', + 'no_encryption', + 'no_sim', + 'not_interested', + 'note', + 'note_add', + 'notifications', + 'notifications_active', + 'notifications_none', + 'notifications_off', + 'notifications_paused', + 'offline_pin', + 'ondemand_video', + 'opacity', + 'open_in_browser', + 'open_in_new', + 'open_with', + 'pages', + 'pageview', + 'palette', + 'pan_tool', + 'panorama', + 'panorama_fish_eye', + 'panorama_horizontal', + 'panorama_vertical', + 'panorama_wide_angle', + 'party_mode', + 'pause', + 'pause_circle_filled', + 'pause_circle_outline', + 'payment', + 'people', + 'people_outline', + 'perm_camera_mic', + 'perm_contact_calendar', + 'perm_data_setting', + 'perm_device_information', + 'perm_identity', + 'perm_media', + 'perm_phone_msg', + 'perm_scan_wifi', + 'person', + 'person_add', + 'person_outline', + 'person_pin', + 'person_pin_circle', + 'personal_video', + 'pets', + 'phone', + 'phone_android', + 'phone_bluetooth_speaker', + 'phone_forwarded', + 'phone_in_talk', + 'phone_iphone', + 'phone_locked', + 'phone_missed', + 'phone_paused', + 'phonelink', + 'phonelink_erase', + 'phonelink_lock', + 'phonelink_off', + 'phonelink_ring', + 'phonelink_setup', + 'photo', + 'photo_album', + 'photo_camera', + 'photo_filter', + 'photo_library', + 'photo_size_select_actual', + 'photo_size_select_large', + 'photo_size_select_small', + 'picture_as_pdf', + 'picture_in_picture', + 'picture_in_picture_alt', + 'pie_chart', + 'pie_chart_outlined', + 'pin_drop', + 'place', + 'play_arrow', + 'play_circle_filled', + 'play_circle_outline', + 'play_for_work', + 'playlist_add', + 'playlist_add_check', + 'playlist_play', + 'plus_one', + 'poll', + 'polymer', + 'pool', + 'portable_wifi_off', + 'portrait', + 'power', + 'power_input', + 'power_settings_new', + 'pregnant_woman', + 'present_to_all', + 'print', + 'priority_high', + 'public', + 'publish', + 'query_builder', + 'question_answer', + 'queue', + 'queue_music', + 'queue_play_next', + 'radio', + 'radio_button_checked', + 'radio_button_unchecked', + 'rate_review', + 'receipt', + 'recent_actors', + 'record_voice_over', + 'redeem', + 'redo', + 'refresh', + 'remove', + 'remove_circle', + 'remove_circle_outline', + 'remove_from_queue', + 'remove_red_eye', + 'remove_shopping_cart', + 'reorder', + 'repeat', + 'repeat_one', + 'replay', + 'replay_10', + 'replay_30', + 'replay_5', + 'reply', + 'reply_all', + 'report', + 'report_problem', + 'restaurant', + 'restaurant_menu', + 'restore', + 'restore_page', + 'ring_volume', + 'room', + 'room_service', + 'rotate_90_degrees_ccw', + 'rotate_left', + 'rotate_right', + 'rounded_corner', + 'router', + 'rowing', + 'rss_feed', + 'rv_hookup', + 'satellite', + 'save', + 'scanner', + 'schedule', + 'school', + 'screen_lock_landscape', + 'screen_lock_portrait', + 'screen_lock_rotation', + 'screen_rotation', + 'screen_share', + 'sd_card', + 'sd_storage', + 'search', + 'security', + 'select_all', + 'send', + 'sentiment_dissatisfied', + 'sentiment_neutral', + 'sentiment_satisfied', + 'sentiment_very_dissatisfied', + 'sentiment_very_satisfied', + 'settings', + 'settings_applications', + 'settings_backup_restore', + 'settings_bluetooth', + 'settings_brightness', + 'settings_cell', + 'settings_ethernet', + 'settings_input_antenna', + 'settings_input_component', + 'settings_input_composite', + 'settings_input_hdmi', + 'settings_input_svideo', + 'settings_overscan', + 'settings_phone', + 'settings_power', + 'settings_remote', + 'settings_system_daydream', + 'settings_voice', + 'share', + 'shop', + 'shop_two', + 'shopping_basket', + 'shopping_cart', + 'short_text', + 'show_chart', + 'shuffle', + 'signal_cellular_4_bar', + 'signal_cellular_connected_no_internet_4_bar', + 'signal_cellular_no_sim', + 'signal_cellular_null', + 'signal_cellular_off', + 'signal_wifi_4_bar', + 'signal_wifi_4_bar_lock', + 'signal_wifi_off', + 'sim_card', + 'sim_card_alert', + 'skip_next', + 'skip_previous', + 'slideshow', + 'slow_motion_video', + 'smartphone', + 'smoke_free', + 'smoking_rooms', + 'sms', + 'sms_failed', + 'snooze', + 'sort', + 'sort_by_alpha', + 'spa', + 'space_bar', + 'speaker', + 'speaker_group', + 'speaker_notes', + 'speaker_notes_off', + 'speaker_phone', + 'spellcheck', + 'star', + 'star_border', + 'star_half', + 'stars', + 'stay_current_landscape', + 'stay_current_portrait', + 'stay_primary_landscape', + 'stay_primary_portrait', + 'stop', + 'stop_screen_share', + 'storage', + 'store', + 'store_mall_directory', + 'straighten', + 'streetview', + 'strikethrough_s', + 'style', + 'subdirectory_arrow_left', + 'subdirectory_arrow_right', + 'subject', + 'subscriptions', + 'subtitles', + 'subway', + 'supervisor_account', + 'surround_sound', + 'swap_calls', + 'swap_horiz', + 'swap_vert', + 'swap_vertical_circle', + 'switch_camera', + 'switch_video', + 'sync', + 'sync_disabled', + 'sync_problem', + 'system_update', + 'system_update_alt', + 'tab', + 'tab_unselected', + 'tablet', + 'tablet_android', + 'tablet_mac', + 'tag_faces', + 'tap_and_play', + 'terrain', + 'text_fields', + 'text_format', + 'textsms', + 'texture', + 'theaters', + 'thumb_down', + 'thumb_up', + 'thumbs_up_down', + 'time_to_leave', + 'timelapse', + 'timeline', + 'timer', + 'timer_10', + 'timer_3', + 'timer_off', + 'title', + 'toc', + 'today', + 'toll', + 'tonality', + 'touch_app', + 'toys', + 'track_changes', + 'traffic', + 'train', + 'tram', + 'transfer_within_a_station', + 'transform', + 'translate', + 'trending_down', + 'trending_flat', + 'trending_up', + 'tune', + 'turned_in', + 'turned_in_not', + 'tv', + 'unarchive', + 'undo', + 'unfold_less', + 'unfold_more', + 'update', + 'usb', + 'verified_user', + 'vertical_align_bottom', + 'vertical_align_center', + 'vertical_align_top', + 'vibration', + 'video_call', + 'video_label', + 'video_library', + 'videocam', + 'videocam_off', + 'videogame_asset', + 'view_agenda', + 'view_array', + 'view_carousel', + 'view_column', + 'view_comfy', + 'view_compact', + 'view_day', + 'view_headline', + 'view_list', + 'view_module', + 'view_quilt', + 'view_stream', + 'view_week', + 'vignette', + 'visibility', + 'visibility_off', + 'voice_chat', + 'voicemail', + 'volume_down', + 'volume_mute', + 'volume_off', + 'volume_up', + 'vpn_key', + 'vpn_lock', + 'wallpaper', + 'warning', + 'watch', + 'watch_later', + 'wb_auto', + 'wb_cloudy', + 'wb_incandescent', + 'wb_iridescent', + 'wb_sunny', + 'wc', + 'web', + 'web_asset', + 'weekend', + 'whatshot', + 'widgets', + 'wifi', + 'wifi_lock', + 'wifi_tethering', + 'work', + 'wrap_text', + 'youtube_searched_for', + 'zoom_in', + 'zoom_out', + 'zoom_out_map', + ]); - const icons = (props.icons ? props.icons : [ - "3d_rotation", - "ac_unit", - "access_alarm", - "access_alarms", - "access_time", - "accessibility", - "accessible", - "account_balance", - "account_balance_wallet", - "account_box", - "account_circle", - "adb", - "add", - "add_a_photo", - "add_alarm", - "add_alert", - "add_box", - "add_circle", - "add_circle_outline", - "add_location", - "add_shopping_cart", - "add_to_photos", - "add_to_queue", - "adjust", - "airline_seat_flat", - "airline_seat_flat_angled", - "airline_seat_individual_suite", - "airline_seat_legroom_extra", - "airline_seat_legroom_normal", - "airline_seat_legroom_reduced", - "airline_seat_recline_extra", - "airline_seat_recline_normal", - "airplanemode_active", - "airplanemode_inactive", - "airplay", - "airport_shuttle", - "alarm", - "alarm_add", - "alarm_off", - "alarm_on", - "album", - "all_inclusive", - "all_out", - "android", - "announcement", - "apps", - "archive", - "arrow_back", - "arrow_downward", - "arrow_drop_down", - "arrow_drop_down_circle", - "arrow_drop_up", - "arrow_forward", - "arrow_upward", - "art_track", - "aspect_ratio", - "assessment", - "assignment", - "assignment_ind", - "assignment_late", - "assignment_return", - "assignment_returned", - "assignment_turned_in", - "assistant", - "assistant_photo", - "attach_file", - "attach_money", - "attachment", - "audiotrack", - "autorenew", - "av_timer", - "backspace", - "backup", - "battery_alert", - "battery_charging_full", - "battery_full", - "battery_std", - "battery_unknown", - "beach_access", - "beenhere", - "block", - "bluetooth", - "bluetooth_audio", - "bluetooth_connected", - "bluetooth_disabled", - "bluetooth_searching", - "blur_circular", - "blur_linear", - "blur_off", - "blur_on", - "book", - "bookmark", - "bookmark_border", - "border_all", - "border_bottom", - "border_clear", - "border_color", - "border_horizontal", - "border_inner", - "border_left", - "border_outer", - "border_right", - "border_style", - "border_top", - "border_vertical", - "branding_watermark", - "brightness_1", - "brightness_2", - "brightness_3", - "brightness_4", - "brightness_5", - "brightness_6", - "brightness_7", - "brightness_auto", - "brightness_high", - "brightness_low", - "brightness_medium", - "broken_image", - "brush", - "bubble_chart", - "bug_report", - "build", - "burst_mode", - "business", - "business_center", - "cached", - "cake", - "call", - "call_end", - "call_made", - "call_merge", - "call_missed", - "call_missed_outgoing", - "call_received", - "call_split", - "call_to_action", - "camera", - "camera_alt", - "camera_enhance", - "camera_front", - "camera_rear", - "camera_roll", - "cancel", - "card_giftcard", - "card_membership", - "card_travel", - "casino", - "cast", - "cast_connected", - "center_focus_strong", - "center_focus_weak", - "change_history", - "chat", - "chat_bubble", - "chat_bubble_outline", - "check", - "check_box", - "check_box_outline_blank", - "check_circle", - "chevron_left", - "chevron_right", - "child_care", - "child_friendly", - "chrome_reader_mode", - "class", - "clear", - "clear_all", - "close", - "closed_caption", - "cloud", - "cloud_circle", - "cloud_done", - "cloud_download", - "cloud_off", - "cloud_queue", - "cloud_upload", - "code", - "collections", - "collections_bookmark", - "color_lens", - "colorize", - "comment", - "compare", - "compare_arrows", - "computer", - "confirmation_number", - "contact_mail", - "contact_phone", - "contacts", - "content_copy", - "content_cut", - "content_paste", - "control_point", - "control_point_duplicate", - "copyright", - "create", - "create_new_folder", - "credit_card", - "crop", - "crop_16_9", - "crop_3_2", - "crop_5_4", - "crop_7_5", - "crop_din", - "crop_free", - "crop_landscape", - "crop_original", - "crop_portrait", - "crop_rotate", - "crop_square", - "dashboard", - "data_usage", - "date_range", - "dehaze", - "delete", - "delete_forever", - "delete_sweep", - "description", - "desktop_mac", - "desktop_windows", - "details", - "developer_board", - "developer_mode", - "device_hub", - "devices", - "devices_other", - "dialer_sip", - "dialpad", - "directions", - "directions_bike", - "directions_boat", - "directions_bus", - "directions_car", - "directions_railway", - "directions_run", - "directions_subway", - "directions_transit", - "directions_walk", - "disc_full", - "dns", - "do_not_disturb", - "do_not_disturb_alt", - "do_not_disturb_off", - "do_not_disturb_on", - "dock", - "domain", - "done", - "done_all", - "donut_large", - "donut_small", - "drafts", - "drag_handle", - "drive_eta", - "dvr", - "edit", - "edit_location", - "eject", - "email", - "enhanced_encryption", - "equalizer", - "error", - "error_outline", - "euro_symbol", - "ev_station", - "event", - "event_available", - "event_busy", - "event_note", - "event_seat", - "exit_to_app", - "expand_less", - "expand_more", - "explicit", - "explore", - "exposure", - "exposure_neg_1", - "exposure_neg_2", - "exposure_plus_1", - "exposure_plus_2", - "exposure_zero", - "extension", - "face", - "fast_forward", - "fast_rewind", - "favorite", - "favorite_border", - "featured_play_list", - "featured_video", - "feedback", - "fiber_dvr", - "fiber_manual_record", - "fiber_new", - "fiber_pin", - "fiber_smart_record", - "file_download", - "file_upload", - "filter", - "filter_1", - "filter_2", - "filter_3", - "filter_4", - "filter_5", - "filter_6", - "filter_7", - "filter_8", - "filter_9", - "filter_9_plus", - "filter_b_and_w", - "filter_center_focus", - "filter_drama", - "filter_frames", - "filter_hdr", - "filter_list", - "filter_none", - "filter_tilt_shift", - "filter_vintage", - "find_in_page", - "find_replace", - "fingerprint", - "first_page", - "fitness_center", - "flag", - "flare", - "flash_auto", - "flash_off", - "flash_on", - "flight", - "flight_land", - "flight_takeoff", - "flip", - "flip_to_back", - "flip_to_front", - "folder", - "folder_open", - "folder_shared", - "folder_special", - "font_download", - "format_align_center", - "format_align_justify", - "format_align_left", - "format_align_right", - "format_bold", - "format_clear", - "format_color_fill", - "format_color_reset", - "format_color_text", - "format_indent_decrease", - "format_indent_increase", - "format_italic", - "format_line_spacing", - "format_list_bulleted", - "format_list_numbered", - "format_paint", - "format_quote", - "format_shapes", - "format_size", - "format_strikethrough", - "format_textdirection_l_to_r", - "format_textdirection_r_to_l", - "format_underlined", - "forum", - "forward", - "forward_10", - "forward_30", - "forward_5", - "free_breakfast", - "fullscreen", - "fullscreen_exit", - "functions", - "g_translate", - "gamepad", - "games", - "gavel", - "gesture", - "get_app", - "gif", - "golf_course", - "gps_fixed", - "gps_not_fixed", - "gps_off", - "grade", - "gradient", - "grain", - "graphic_eq", - "grid_off", - "grid_on", - "group", - "group_add", - "group_work", - "hd", - "hdr_off", - "hdr_on", - "hdr_strong", - "hdr_weak", - "headset", - "headset_mic", - "healing", - "hearing", - "help", - "help_outline", - "high_quality", - "highlight", - "highlight_off", - "history", - "home", - "hot_tub", - "hotel", - "hourglass_empty", - "hourglass_full", - "http", - "https", - "image", - "image_aspect_ratio", - "import_contacts", - "import_export", - "important_devices", - "inbox", - "indeterminate_check_box", - "info", - "info_outline", - "input", - "insert_chart", - "insert_comment", - "insert_drive_file", - "insert_emoticon", - "insert_invitation", - "insert_link", - "insert_photo", - "invert_colors", - "invert_colors_off", - "iso", - "keyboard", - "keyboard_arrow_down", - "keyboard_arrow_left", - "keyboard_arrow_right", - "keyboard_arrow_up", - "keyboard_backspace", - "keyboard_capslock", - "keyboard_hide", - "keyboard_return", - "keyboard_tab", - "keyboard_voice", - "kitchen", - "label", - "label_outline", - "landscape", - "language", - "laptop", - "laptop_chromebook", - "laptop_mac", - "laptop_windows", - "last_page", - "launch", - "layers", - "layers_clear", - "leak_add", - "leak_remove", - "lens", - "library_add", - "library_books", - "library_music", - "lightbulb_outline", - "line_style", - "line_weight", - "linear_scale", - "link", - "linked_camera", - "list", - "live_help", - "live_tv", - "local_activity", - "local_airport", - "local_atm", - "local_bar", - "local_cafe", - "local_car_wash", - "local_convenience_store", - "local_dining", - "local_drink", - "local_florist", - "local_gas_station", - "local_grocery_store", - "local_hospital", - "local_hotel", - "local_laundry_service", - "local_library", - "local_mall", - "local_movies", - "local_offer", - "local_parking", - "local_pharmacy", - "local_phone", - "local_pizza", - "local_play", - "local_post_office", - "local_printshop", - "local_see", - "local_shipping", - "local_taxi", - "location_city", - "location_disabled", - "location_off", - "location_on", - "location_searching", - "lock", - "lock_open", - "lock_outline", - "looks", - "looks_3", - "looks_4", - "looks_5", - "looks_6", - "looks_one", - "looks_two", - "loop", - "loupe", - "low_priority", - "loyalty", - "mail", - "mail_outline", - "map", - "markunread", - "markunread_mailbox", - "memory", - "menu", - "merge_type", - "message", - "mic", - "mic_none", - "mic_off", - "mms", - "mode_comment", - "mode_edit", - "monetization_on", - "money_off", - "monochrome_photos", - "mood", - "mood_bad", - "more", - "more_horiz", - "more_vert", - "motorcycle", - "mouse", - "move_to_inbox", - "movie", - "movie_creation", - "movie_filter", - "multiline_chart", - "music_note", - "music_video", - "my_location", - "nature", - "nature_people", - "navigate_before", - "navigate_next", - "navigation", - "near_me", - "network_cell", - "network_check", - "network_locked", - "network_wifi", - "new_releases", - "next_week", - "nfc", - "no_encryption", - "no_sim", - "not_interested", - "note", - "note_add", - "notifications", - "notifications_active", - "notifications_none", - "notifications_off", - "notifications_paused", - "offline_pin", - "ondemand_video", - "opacity", - "open_in_browser", - "open_in_new", - "open_with", - "pages", - "pageview", - "palette", - "pan_tool", - "panorama", - "panorama_fish_eye", - "panorama_horizontal", - "panorama_vertical", - "panorama_wide_angle", - "party_mode", - "pause", - "pause_circle_filled", - "pause_circle_outline", - "payment", - "people", - "people_outline", - "perm_camera_mic", - "perm_contact_calendar", - "perm_data_setting", - "perm_device_information", - "perm_identity", - "perm_media", - "perm_phone_msg", - "perm_scan_wifi", - "person", - "person_add", - "person_outline", - "person_pin", - "person_pin_circle", - "personal_video", - "pets", - "phone", - "phone_android", - "phone_bluetooth_speaker", - "phone_forwarded", - "phone_in_talk", - "phone_iphone", - "phone_locked", - "phone_missed", - "phone_paused", - "phonelink", - "phonelink_erase", - "phonelink_lock", - "phonelink_off", - "phonelink_ring", - "phonelink_setup", - "photo", - "photo_album", - "photo_camera", - "photo_filter", - "photo_library", - "photo_size_select_actual", - "photo_size_select_large", - "photo_size_select_small", - "picture_as_pdf", - "picture_in_picture", - "picture_in_picture_alt", - "pie_chart", - "pie_chart_outlined", - "pin_drop", - "place", - "play_arrow", - "play_circle_filled", - "play_circle_outline", - "play_for_work", - "playlist_add", - "playlist_add_check", - "playlist_play", - "plus_one", - "poll", - "polymer", - "pool", - "portable_wifi_off", - "portrait", - "power", - "power_input", - "power_settings_new", - "pregnant_woman", - "present_to_all", - "print", - "priority_high", - "public", - "publish", - "query_builder", - "question_answer", - "queue", - "queue_music", - "queue_play_next", - "radio", - "radio_button_checked", - "radio_button_unchecked", - "rate_review", - "receipt", - "recent_actors", - "record_voice_over", - "redeem", - "redo", - "refresh", - "remove", - "remove_circle", - "remove_circle_outline", - "remove_from_queue", - "remove_red_eye", - "remove_shopping_cart", - "reorder", - "repeat", - "repeat_one", - "replay", - "replay_10", - "replay_30", - "replay_5", - "reply", - "reply_all", - "report", - "report_problem", - "restaurant", - "restaurant_menu", - "restore", - "restore_page", - "ring_volume", - "room", - "room_service", - "rotate_90_degrees_ccw", - "rotate_left", - "rotate_right", - "rounded_corner", - "router", - "rowing", - "rss_feed", - "rv_hookup", - "satellite", - "save", - "scanner", - "schedule", - "school", - "screen_lock_landscape", - "screen_lock_portrait", - "screen_lock_rotation", - "screen_rotation", - "screen_share", - "sd_card", - "sd_storage", - "search", - "security", - "select_all", - "send", - "sentiment_dissatisfied", - "sentiment_neutral", - "sentiment_satisfied", - "sentiment_very_dissatisfied", - "sentiment_very_satisfied", - "settings", - "settings_applications", - "settings_backup_restore", - "settings_bluetooth", - "settings_brightness", - "settings_cell", - "settings_ethernet", - "settings_input_antenna", - "settings_input_component", - "settings_input_composite", - "settings_input_hdmi", - "settings_input_svideo", - "settings_overscan", - "settings_phone", - "settings_power", - "settings_remote", - "settings_system_daydream", - "settings_voice", - "share", - "shop", - "shop_two", - "shopping_basket", - "shopping_cart", - "short_text", - "show_chart", - "shuffle", - "signal_cellular_4_bar", - "signal_cellular_connected_no_internet_4_bar", - "signal_cellular_no_sim", - "signal_cellular_null", - "signal_cellular_off", - "signal_wifi_4_bar", - "signal_wifi_4_bar_lock", - "signal_wifi_off", - "sim_card", - "sim_card_alert", - "skip_next", - "skip_previous", - "slideshow", - "slow_motion_video", - "smartphone", - "smoke_free", - "smoking_rooms", - "sms", - "sms_failed", - "snooze", - "sort", - "sort_by_alpha", - "spa", - "space_bar", - "speaker", - "speaker_group", - "speaker_notes", - "speaker_notes_off", - "speaker_phone", - "spellcheck", - "star", - "star_border", - "star_half", - "stars", - "stay_current_landscape", - "stay_current_portrait", - "stay_primary_landscape", - "stay_primary_portrait", - "stop", - "stop_screen_share", - "storage", - "store", - "store_mall_directory", - "straighten", - "streetview", - "strikethrough_s", - "style", - "subdirectory_arrow_left", - "subdirectory_arrow_right", - "subject", - "subscriptions", - "subtitles", - "subway", - "supervisor_account", - "surround_sound", - "swap_calls", - "swap_horiz", - "swap_vert", - "swap_vertical_circle", - "switch_camera", - "switch_video", - "sync", - "sync_disabled", - "sync_problem", - "system_update", - "system_update_alt", - "tab", - "tab_unselected", - "tablet", - "tablet_android", - "tablet_mac", - "tag_faces", - "tap_and_play", - "terrain", - "text_fields", - "text_format", - "textsms", - "texture", - "theaters", - "thumb_down", - "thumb_up", - "thumbs_up_down", - "time_to_leave", - "timelapse", - "timeline", - "timer", - "timer_10", - "timer_3", - "timer_off", - "title", - "toc", - "today", - "toll", - "tonality", - "touch_app", - "toys", - "track_changes", - "traffic", - "train", - "tram", - "transfer_within_a_station", - "transform", - "translate", - "trending_down", - "trending_flat", - "trending_up", - "tune", - "turned_in", - "turned_in_not", - "tv", - "unarchive", - "undo", - "unfold_less", - "unfold_more", - "update", - "usb", - "verified_user", - "vertical_align_bottom", - "vertical_align_center", - "vertical_align_top", - "vibration", - "video_call", - "video_label", - "video_library", - "videocam", - "videocam_off", - "videogame_asset", - "view_agenda", - "view_array", - "view_carousel", - "view_column", - "view_comfy", - "view_compact", - "view_day", - "view_headline", - "view_list", - "view_module", - "view_quilt", - "view_stream", - "view_week", - "vignette", - "visibility", - "visibility_off", - "voice_chat", - "voicemail", - "volume_down", - "volume_mute", - "volume_off", - "volume_up", - "vpn_key", - "vpn_lock", - "wallpaper", - "warning", - "watch", - "watch_later", - "wb_auto", - "wb_cloudy", - "wb_incandescent", - "wb_iridescent", - "wb_sunny", - "wc", - "web", - "web_asset", - "weekend", - "whatshot", - "widgets", - "wifi", - "wifi_lock", - "wifi_tethering", - "work", - "wrap_text", - "youtube_searched_for", - "zoom_in", - "zoom_out", - "zoom_out_map" - ]); - - return ( -
- { - icons.map(icon => { - return ( -
props.onChange(icon)}> - -
- ); - }) + return ( +
+ { + icons.map((icon) => ( +
props.onChange(icon)} + > + +
+ )) } -
- ); -}); \ No newline at end of file +
+ ); +}); diff --git a/src/js/components/Fields/LastfmAuthenticationFrame.js b/src/js/components/Fields/LastfmAuthenticationFrame.js index ad4c62e8..a9ee3790 100755 --- a/src/js/components/Fields/LastfmAuthenticationFrame.js +++ b/src/js/components/Fields/LastfmAuthenticationFrame.js @@ -8,117 +8,109 @@ import * as helpers from '../../helpers'; import * as uiActions from '../../services/ui/actions'; import * as lastfmActions from '../../services/lastfm/actions'; -class LastfmAuthenticationFrame extends React.Component{ +class LastfmAuthenticationFrame extends React.Component { + constructor(props) { + super(props); - constructor(props){ - super(props); + this.state = { + authorizing: false, + }; - this.state = { - authorizing: false - } + this.handleMessage = this.handleMessage.bind(this); + } - this.handleMessage = this.handleMessage.bind(this); - } + componentDidMount() { + window.addEventListener('message', this.handleMessage, false); + } - componentDidMount(){ - window.addEventListener('message', this.handleMessage, false); - } + componentWillUnmount() { + window.removeEventListener('message', this.handleMessage, false); + } - componentWillUnmount(){ - window.removeEventListener("message", this.handleMessage, false); - } + handleMessage(event) { + const data = helpers.toJSON(event.data); - handleMessage(event){ - let data = helpers.toJSON(event.data); + // Only digest messages relevant to us + if (data.origin != 'auth_lastfm') { + return; + } - // Only digest messages relevant to us - if (data.origin != 'auth_lastfm'){ - return; - } - - // Only allow incoming data from our authorized authenticator proxy - var authorization_domain = this.props.authorization_url.substring(0,this.props.authorization_url.indexOf('/',8)) - if (event.origin != authorization_domain){ - this.props.uiActions.createNotification({content: 'Authorization failed. '+event.origin+' is not the configured authorization_url.', type: 'bad'}); - return false - } + // Only allow incoming data from our authorized authenticator proxy + const authorization_domain = this.props.authorization_url.substring(0, this.props.authorization_url.indexOf('/', 8)); + if (event.origin != authorization_domain) { + this.props.uiActions.createNotification({ content: `Authorization failed. ${event.origin} is not the configured authorization_url.`, type: 'bad' }); + return false; + } - // Bounced with an error - if (data.error !== undefined){ - this.props.uiActions.createNotification({content: data.message, type: 'bad'}); + // Bounced with an error + if (data.error !== undefined) { + this.props.uiActions.createNotification({ content: data.message, type: 'bad' }); - // No errors? We're in! - } else { - this.props.lastfmActions.authorizationGranted(data) - this.props.lastfmActions.getMe() - } + // No errors? We're in! + } else { + this.props.lastfmActions.authorizationGranted(data); + this.props.lastfmActions.getMe(); + } - // Turn off our authorizing switch - this.setState({authorizing: false}); - } + // Turn off our authorizing switch + this.setState({ authorizing: false }); + } - startAuthorization(){ + startAuthorization() { + const self = this; + this.setState({ authorizing: true }); - var self = this - this.setState({authorizing: true}) - - // Open an authentication request window - var url = this.props.authorization_url+'?action=authorize' - var popup = window.open(url,"popup","height=580,width=350"); - popup.name = "LastfmAuthenticationWindow"; - - // Start timer to check our popup's state - var timer = setInterval(checkPopup, 1000); - function checkPopup(){ + // Open an authentication request window + const url = `${this.props.authorization_url}?action=authorize`; + const popup = window.open(url, 'popup', 'height=580,width=350'); + popup.name = 'LastfmAuthenticationWindow'; + // Start timer to check our popup's state + const timer = setInterval(checkPopup, 1000); + function checkPopup() { // Popup has been closed - if (typeof(popup) !== 'undefined' && popup){ - if (popup.closed){ - self.setState({authorizing: false}) - clearInterval(timer); - } - - // Popup does not exist, so must have been blocked - } else { - self.props.uiActions.createNotification({content: 'Popup blocked. Please allow popups and try again.', type: 'bad'}); - self.setState({authorizing: false}) - clearInterval(timer); - } + if (typeof (popup) !== 'undefined' && popup) { + if (popup.closed) { + self.setState({ authorizing: false }); + clearInterval(timer); } - } - render(){ - if (this.state.authorizing){ - return ( - + // Popup does not exist, so must have been blocked + } else { + self.props.uiActions.createNotification({ content: 'Popup blocked. Please allow popups and try again.', type: 'bad' }); + self.setState({ authorizing: false }); + clearInterval(timer); + } + } + } + + render() { + if (this.state.authorizing) { + return ( + Authorizing... - - ) - } else if (this.props.authorization){ - return ( - this.props.lastfmActions.revokeAuthorization()}>Log out - ) - } else { - return ( - this.startAuthorization()}>Log in - ) - } - } + + ); + } if (this.props.authorization) { + return ( + this.props.lastfmActions.revokeAuthorization()}>Log out + ); + } + return ( + this.startAuthorization()}>Log in + ); + } } -const mapStateToProps = (state, ownProps) => { - return { - authorization_url: state.lastfm.authorization_url, - authorization: state.lastfm.authorization, - authorizing: state.lastfm.authorizing - } -} +const mapStateToProps = (state, ownProps) => ({ + authorization_url: state.lastfm.authorization_url, + authorization: state.lastfm.authorization, + authorizing: state.lastfm.authorizing, +}); -const mapDispatchToProps = (dispatch) => { - return { - uiActions: bindActionCreators(uiActions, dispatch), - lastfmActions: bindActionCreators(lastfmActions, dispatch) - } -} +const mapDispatchToProps = (dispatch) => ({ + uiActions: bindActionCreators(uiActions, dispatch), + lastfmActions: bindActionCreators(lastfmActions, dispatch), +}); -export default connect(mapStateToProps, mapDispatchToProps)(LastfmAuthenticationFrame) \ No newline at end of file +export default connect(mapStateToProps, mapDispatchToProps)(LastfmAuthenticationFrame); diff --git a/src/js/components/Fields/LastfmLoveButton.js b/src/js/components/Fields/LastfmLoveButton.js index ab3a5468..09f9e189 100755 --- a/src/js/components/Fields/LastfmLoveButton.js +++ b/src/js/components/Fields/LastfmLoveButton.js @@ -1,61 +1,55 @@ -import React from 'react' -import { connect } from 'react-redux' -import { createStore, bindActionCreators } from 'redux' +import React from 'react'; +import { connect } from 'react-redux'; +import { createStore, bindActionCreators } from 'redux'; import Link from '../Link'; -import * as helpers from '../../helpers' -import * as uiActions from '../../services/ui/actions' -import * as lastfmActions from '../../services/lastfm/actions' +import * as helpers from '../../helpers'; +import * as uiActions from '../../services/ui/actions'; +import * as lastfmActions from '../../services/lastfm/actions'; -class FollowButton extends React.Component{ +class FollowButton extends React.Component { + constructor(props) { + super(props); + } - constructor(props){ - super(props); - } + remove() { + this.props.lastfmActions.unloveTrack(this.props.uri, this.props.artist, this.props.track); + } - remove(){ - this.props.lastfmActions.unloveTrack(this.props.uri, this.props.artist, this.props.track); - } + add() { + this.props.lastfmActions.loveTrack(this.props.uri, this.props.artist, this.props.track); + } - add(){ - this.props.lastfmActions.loveTrack(this.props.uri, this.props.artist, this.props.track); - } + render() { + if (!this.props.uri) { + return false; + } - render(){ - if (!this.props.uri){ - return false; - } + let className = 'button'; - var className = 'button'; + // Inherit passed-down classes + if (this.props.className) { + className += ` ${this.props.className}`; + } - // Inherit passed-down classes - if (this.props.className){ - className += ' '+this.props.className; - } - - if (!this.props.lastfm_authorized){ - return - } else if (this.props.is_loved && this.props.is_loved !== "0"){ - return - } else { - return - } - } + if (!this.props.lastfm_authorized) { + return ; + } if (this.props.is_loved && this.props.is_loved !== '0') { + return ; + } + return ; + } } -const mapStateToProps = (state, ownProps) => { - return { - load_queue: state.ui.load_queue, - lastfm_authorized: state.lastfm.authorization - } -} +const mapStateToProps = (state, ownProps) => ({ + load_queue: state.ui.load_queue, + lastfm_authorized: state.lastfm.authorization, +}); -const mapDispatchToProps = (dispatch) => { - return { - uiActions: bindActionCreators(uiActions, dispatch), - lastfmActions: bindActionCreators(lastfmActions, dispatch) - } -} +const mapDispatchToProps = (dispatch) => ({ + uiActions: bindActionCreators(uiActions, dispatch), + lastfmActions: bindActionCreators(lastfmActions, dispatch), +}); -export default connect(mapStateToProps, mapDispatchToProps)(FollowButton) \ No newline at end of file +export default connect(mapStateToProps, mapDispatchToProps)(FollowButton); diff --git a/src/js/components/Fields/LatencyControl.js b/src/js/components/Fields/LatencyControl.js index df52161f..57a45077 100755 --- a/src/js/components/Fields/LatencyControl.js +++ b/src/js/components/Fields/LatencyControl.js @@ -3,67 +3,65 @@ import React from 'react'; import * as helpers from '../../helpers'; -export default class LatencyControl extends React.Component{ +export default class LatencyControl extends React.Component { + constructor(props) { + super(props); + this.handleChange = helpers.throttle(this.handleChange.bind(this), 100); + } - constructor(props){ - super(props); - this.handleChange = helpers.throttle(this.handleChange.bind(this), 100); - } + handleChange(value) { + this.props.onChange(value, this.props.value); + } - handleChange(value){ - this.props.onChange(value, this.props.value); - } + render() { + // Zero, or positive value + if (this.props.value >= 0) { + var { value } = this.props; + if (value > this.props.max) { + value = this.props.max; + } + var percentage = Math.round((value / this.props.max) * 100 / 2); + var left = 50; + var width = percentage; + var negative = false; - render(){ + if (width > (this.props.max / 2)) width = this.props.max / 2; - // Zero, or positive value - if (this.props.value >= 0){ - var value = this.props.value; - if (value > this.props.max){ - value = this.props.max; - } - var percentage = Math.round((value / this.props.max) * 100 / 2); - var left = 50; - var width = percentage; - var negative = false; + // Negative value + // We reverse it to a positive for easier maths and style rules + } else { + var value = -this.props.value; + if (value < -this.props.max) { + value = -this.props.max; + } + var percentage = Math.round((value / this.props.max) * 100 / 2); + var left = 50 - percentage; + var width = percentage; + var negative = true; - if (width > (this.props.max / 2)) width = this.props.max / 2; + if (left < 0) left = 0; + if (width > (this.props.max / 2)) width = this.props.max / 2; + } - // Negative value - // We reverse it to a positive for easier maths and style rules - } else { - var value = -this.props.value; - if (value < -this.props.max){ - value = -this.props.max; - } - var percentage = Math.round((value / this.props.max) * 100 / 2); - var left = 50 - percentage; - var width = percentage; - var negative = true; - - if (left < 0) left = 0; - if (width > (this.props.max / 2)) width = this.props.max / 2; - } - - return ( - -
-
- this.handleChange(parseInt(e.target.value))} - /> -
-
-
-
-
-
-
- ); - } + return ( + +
+
+ this.handleChange(parseInt(e.target.value))} + /> +
+
+
+
+
+
+ + ); + } } diff --git a/src/js/components/Fields/MuteControl.js b/src/js/components/Fields/MuteControl.js index cfaf7010..c1c34b31 100755 --- a/src/js/components/Fields/MuteControl.js +++ b/src/js/components/Fields/MuteControl.js @@ -2,21 +2,19 @@ import React, { memo } from 'react'; import Icon from '../Icon'; -export default memo((props) => { - - if (props.mute){ - return ( - - ) - } else { - return ( - - ) - } +export default memo((props) => { + if (props.mute) { + return ( + + ); + } + return ( + + ); }); diff --git a/src/js/components/Fields/OutputControl.js b/src/js/components/Fields/OutputControl.js index f9ad2522..0b289ac0 100755 --- a/src/js/components/Fields/OutputControl.js +++ b/src/js/components/Fields/OutputControl.js @@ -12,220 +12,207 @@ import * as coreActions from '../../services/core/actions'; import * as pusherActions from '../../services/pusher/actions'; import * as snapcastActions from '../../services/snapcast/actions'; -class OutputControl extends React.Component{ +class OutputControl extends React.Component { + constructor(props) { + super(props); - constructor(props){ - super(props); + this.state = { + expanded: false, + }; - this.state = { - expanded: false - } + this.handleClick = this.handleClick.bind(this); + } - this.handleClick = this.handleClick.bind(this); - } + handleClick(e) { + if (!this.props.force_expanded && $(e.target).closest('.output-control').length <= 0) { + this.setExpanded(false); + } + } - handleClick(e){ - if (!this.props.force_expanded && $(e.target).closest('.output-control').length <= 0){ - this.setExpanded(false); - } - } + componentWillReceiveProps(nextProps) { + // When force expanded triggered + if (!this.props.force_expanded && nextProps.force_expanded) { + this.setExpanded(true, nextProps); + } + } - componentWillReceiveProps(nextProps){ + setExpanded(expanded = !this.state.expanded, props = this.props) { + if (expanded) { + this.setState({ expanded }); + window.addEventListener('click', this.handleClick, false); - // When force expanded triggered - if (!this.props.force_expanded && nextProps.force_expanded){ - this.setExpanded(true, nextProps); - } - } + // Re-check our snapcast clients + // TODO: Once we have push events, remove this as it'll (marginally) + // slow down the reveal/render + if (props.pusher_connected && props.snapcast_enabled) { + this.props.snapcastActions.getServer(); + } + } else { + this.setState({ expanded }); + window.removeEventListener('click', this.handleClick, false); + } + } - setExpanded(expanded = !this.state.expanded, props = this.props){ - if (expanded){ - this.setState({expanded: expanded}); - window.addEventListener("click", this.handleClick, false); + renderOutputs() { + let has_outputs = false; - // Re-check our snapcast clients - // TODO: Once we have push events, remove this as it'll (marginally) - // slow down the reveal/render - if (props.pusher_connected && props.snapcast_enabled){ - this.props.snapcastActions.getServer(); - } - } else { - this.setState({expanded: expanded}); - window.removeEventListener("click", this.handleClick, false); - } - } + const clients = []; + for (var key in this.props.snapcast_clients) { + if (this.props.snapcast_clients.hasOwnProperty(key)) { + const client = this.props.snapcast_clients[key]; + if (client.connected || this.props.show_disconnected_clients) { + clients.push(client); + } + } + } - renderOutputs(){ - var has_outputs = false; - - var clients = []; - for (var key in this.props.snapcast_clients){ - if (this.props.snapcast_clients.hasOwnProperty(key)){ - var client = this.props.snapcast_clients[key]; - if (client.connected || this.props.show_disconnected_clients){ - clients.push(client); - } - } - } - - var snapcast_clients = null; - if (clients.length > 0){ - has_outputs = true; - snapcast_clients = ( -
- { - clients.map(client => { - return ( -
-
- {client.name} -
-
- this.props.snapcastActions.setClientMute(client.id, mute)} - /> - this.props.snapcastActions.setClientVolume(client.id, percent)} - /> -
-
- ); - }) + let snapcast_clients = null; + if (clients.length > 0) { + has_outputs = true; + snapcast_clients = ( +
+ { + clients.map((client) => ( +
+
+ {client.name} +
+
+ this.props.snapcastActions.setClientMute(client.id, mute)} + /> + this.props.snapcastActions.setClientVolume(client.id, percent)} + /> +
+
+ )) } -
- ); - } +
+ ); + } - var local_streaming = null; - if (this.props.http_streaming_enabled){ - has_outputs = true; - local_streaming = ( -
-
- this.props.coreActions.cachebustHttpStream()}> - - -
-
+ let local_streaming = null; + if (this.props.http_streaming_enabled) { + has_outputs = true; + local_streaming = ( +
+
+ this.props.coreActions.cachebustHttpStream()}> + + +
+
Local browser -
-
- this.props.coreActions.set({http_streaming_volume: percent})} - onMuteChange={mute => this.props.coreActions.set({http_streaming_mute: mute})} - /> -
-
- ); - } +
+
+ this.props.coreActions.set({ http_streaming_volume: percent })} + onMuteChange={(mute) => this.props.coreActions.set({ http_streaming_mute: mute })} + /> +
+
+ ); + } - var commands = null; - if (this.props.pusher_commands){ + let commands = null; + if (this.props.pusher_commands) { + let commands_items = []; + for (var key in this.props.pusher_commands) { + if (this.props.pusher_commands.hasOwnProperty(key)) { + commands_items.push(this.props.pusher_commands[key]); + } + } - var commands_items = []; - for (var key in this.props.pusher_commands){ - if (this.props.pusher_commands.hasOwnProperty(key)){ - commands_items.push(this.props.pusher_commands[key]); - } - } - - commands_items = helpers.sortItems(commands_items, 'sort_order'); + commands_items = helpers.sortItems(commands_items, 'sort_order'); - if (commands_items.length > 0){ - has_outputs = true; - commands = ( -
- { - commands_items.map(command => { - return ( -
this.props.pusherActions.runCommand(command.id)}> - - -
- ); - }) + if (commands_items.length > 0) { + has_outputs = true; + commands = ( +
+ { + commands_items.map((command) => ( +
this.props.pusherActions.runCommand(command.id)} + > + + +
+ )) } -
- ); - } - } +
+ ); + } + } - if (!has_outputs){ - return ( -
-

No outputs

-
- ); - } else { - return ( -
- {commands} - {local_streaming} - {snapcast_clients} -
- ); - } - } + if (!has_outputs) { + return ( +
+

No outputs

+
+ ); + } + return ( +
+ {commands} + {local_streaming} + {snapcast_clients} +
+ ); + } - render(){ - if (this.state.expanded){ - return ( - - - {this.renderOutputs()} - - ); - } else { - - // No customisable outputs - if (!this.props.http_streaming_enabled && !this.props.snapcast_enabled && !this.props.pusher_commands){ - return ( - - - - ); - } else { - return ( - - - - ); - } - } - } + render() { + if (this.state.expanded) { + return ( + + + {this.renderOutputs()} + + ); + } + + // No customisable outputs + if (!this.props.http_streaming_enabled && !this.props.snapcast_enabled && !this.props.pusher_commands) { + return ( + + + + ); + } + return ( + + + + ); + } } -const mapStateToProps = (state, ownProps) => { - return { - http_streaming_enabled: state.core.http_streaming_enabled, - http_streaming_volume: state.core.http_streaming_volume, - http_streaming_mute: state.core.http_streaming_mute, - pusher_connected: state.pusher.connected, - snapcast_enabled: (state.pusher.config ? state.pusher.config.snapcast_enabled : null), - show_disconnected_clients: (state.ui.snapcast_show_disconnected_clients !== undefined ? state.ui.snapcast_show_disconnected_clients : false), - snapcast_clients: state.snapcast.clients, - pusher_commands: (state.pusher.commands ? state.pusher.commands : {}) - } -} +const mapStateToProps = (state, ownProps) => ({ + http_streaming_enabled: state.core.http_streaming_enabled, + http_streaming_volume: state.core.http_streaming_volume, + http_streaming_mute: state.core.http_streaming_mute, + pusher_connected: state.pusher.connected, + snapcast_enabled: (state.pusher.config ? state.pusher.config.snapcast_enabled : null), + show_disconnected_clients: (state.ui.snapcast_show_disconnected_clients !== undefined ? state.ui.snapcast_show_disconnected_clients : false), + snapcast_clients: state.snapcast.clients, + pusher_commands: (state.pusher.commands ? state.pusher.commands : {}), +}); -const mapDispatchToProps = (dispatch) => { - return { - coreActions: bindActionCreators(coreActions, dispatch), - snapcastActions: bindActionCreators(snapcastActions, dispatch), - pusherActions: bindActionCreators(pusherActions, dispatch) - } -} +const mapDispatchToProps = (dispatch) => ({ + coreActions: bindActionCreators(coreActions, dispatch), + snapcastActions: bindActionCreators(snapcastActions, dispatch), + pusherActions: bindActionCreators(pusherActions, dispatch), +}); -export default connect(mapStateToProps, mapDispatchToProps)(OutputControl) \ No newline at end of file +export default connect(mapStateToProps, mapDispatchToProps)(OutputControl); diff --git a/src/js/components/Fields/ProgressSlider.js b/src/js/components/Fields/ProgressSlider.js index dc0043ac..5269f7d2 100755 --- a/src/js/components/Fields/ProgressSlider.js +++ b/src/js/components/Fields/ProgressSlider.js @@ -6,65 +6,60 @@ import { bindActionCreators } from 'redux'; import * as helpers from '../../helpers'; import * as mopidyActions from '../../services/mopidy/actions'; -class ProgressSlider extends React.Component{ +class ProgressSlider extends React.Component { + constructor(props) { + super(props); - constructor(props){ - super(props); - - this.handleChange = helpers.throttle(this.handleChange.bind(this), 250); - } + this.handleChange = helpers.throttle(this.handleChange.bind(this), 250); + } - handleChange(value){ - this.props.mopidyActions.setTimePosition(this.props.current_track.duration * (value / 100)); - } + handleChange(value) { + this.props.mopidyActions.setTimePosition(this.props.current_track.duration * (value / 100)); + } - render(){ - var percent = 0; - if (this.props.connected && this.props.time_position && this.props.current_track && this.props.current_track.duration){ - percent = this.props.time_position / this.props.current_track.duration; - percent = percent * 100; - if (percent > 1000){ - percent = 100; - } - } + render() { + let percent = 0; + if (this.props.connected && this.props.time_position && this.props.current_track && this.props.current_track.duration) { + percent = this.props.time_position / this.props.current_track.duration; + percent *= 100; + if (percent > 1000) { + percent = 100; + } + } - return ( -
- this.handleChange(parseInt(e.target.value))} - /> -
-
-
-
- ); - } + return ( +
+ this.handleChange(parseInt(e.target.value))} + /> +
+
+
+
+ ); + } } /** * Export our component * * We also integrate our global store, using connect() - **/ + * */ -const mapStateToProps = (state, ownProps) => { - return { - current_track: (state.core.current_track && state.core.tracks[state.core.current_track.uri] !== undefined ? state.core.tracks[state.core.current_track.uri] : null), - connected: state.mopidy.connected, - time_position: state.mopidy.time_position, - play_state: state.mopidy.play_state - } -} +const mapStateToProps = (state, ownProps) => ({ + current_track: (state.core.current_track && state.core.tracks[state.core.current_track.uri] !== undefined ? state.core.tracks[state.core.current_track.uri] : null), + connected: state.mopidy.connected, + time_position: state.mopidy.time_position, + play_state: state.mopidy.play_state, +}); -const mapDispatchToProps = (dispatch) => { - return { - mopidyActions: bindActionCreators(mopidyActions, dispatch) - } -} +const mapDispatchToProps = (dispatch) => ({ + mopidyActions: bindActionCreators(mopidyActions, dispatch), +}); -export default connect(mapStateToProps, mapDispatchToProps)(ProgressSlider) \ No newline at end of file +export default connect(mapStateToProps, mapDispatchToProps)(ProgressSlider); diff --git a/src/js/components/Fields/SearchForm.js b/src/js/components/Fields/SearchForm.js index 0d41881b..9f65647b 100755 --- a/src/js/components/Fields/SearchForm.js +++ b/src/js/components/Fields/SearchForm.js @@ -1,90 +1,86 @@ -import React from 'react' -import { connect } from 'react-redux' -import { bindActionCreators } from 'redux' +import React from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; -import * as helpers from '../../helpers' -import * as uiActions from '../../services/ui/actions' +import * as helpers from '../../helpers'; +import * as uiActions from '../../services/ui/actions'; -class SearchForm extends React.Component{ +class SearchForm extends React.Component { + constructor(props) { + super(props); - constructor(props){ - super(props); + this.state = { + term: this.props.term, + pristine: true, + }; + } - this.state = { - term: this.props.term, - pristine: true, - } - } + componentWillReceiveProps(nextProps) { + if (this.state.pristine && this.state.term == '' && this.state.term !== nextProps.term) { + this.setState({ term: nextProps.term, pristine: false }); + } + } - componentWillReceiveProps(nextProps) { - if (this.state.pristine && this.state.term == "" && this.state.term !== nextProps.term) { - this.setState({term: nextProps.term, pristine: false}); - } - } + handleBlur(e) { + this.setState({ pristine: false }); + if (this.props.onBlur) { + this.props.onBlur(this.state.term); + } + } - handleBlur(e) { - this.setState({pristine: false}); - if (this.props.onBlur) { - this.props.onBlur(this.state.term); - } - } + handleFocus(e) { + this.setState({ pristine: false }); + } - handleFocus(e) { - this.setState({pristine: false}); - } + handleSubmit(e) { + e.preventDefault(); - handleSubmit(e){ - e.preventDefault(); + // check for uri type matching + switch (helpers.uriType(this.state.term)) { + case 'album': + this.props.history.push(`/album/${encodeURIComponent(this.state.term)}`); + break; - // check for uri type matching - switch (helpers.uriType(this.state.term)){ + case 'artist': + this.props.history.push(`/artist/${encodeURIComponent(this.state.term)}`); + break; - case 'album': - this.props.history.push('/album/'+encodeURIComponent(this.state.term)) - break + case 'playlist': + this.props.history.push(`/playlist/${encodeURIComponent(this.state.term)}`); + break; - case 'artist': - this.props.history.push('/artist/'+encodeURIComponent(this.state.term)) - break + case 'track': + this.props.history.push(`/track/${encodeURIComponent(this.state.term)}`); + break; - case 'playlist': - this.props.history.push('/playlist/'+encodeURIComponent(this.state.term)) - break + default: + this.props.onSubmit(this.state.term); + break; + } - case 'track': - this.props.history.push('/track/'+encodeURIComponent(this.state.term)) - break + return false; + } - default: - this.props.onSubmit(this.state.term); - break - } - - return false - } - - render(){ - return ( -
this.handleSubmit(e)}> - -
- ) - } + render() { + return ( +
this.handleSubmit(e)}> + +
+ ); + } } -const mapDispatchToProps = (dispatch) => { - return { - uiActions: bindActionCreators(uiActions, dispatch) - } -} +const mapDispatchToProps = (dispatch) => ({ + uiActions: bindActionCreators(uiActions, dispatch), +}); -export default connect(mapDispatchToProps)(SearchForm) \ No newline at end of file +export default connect(mapDispatchToProps)(SearchForm); diff --git a/src/js/components/Fields/SourcesPriority.js b/src/js/components/Fields/SourcesPriority.js index 0015505e..a66076ba 100755 --- a/src/js/components/Fields/SourcesPriority.js +++ b/src/js/components/Fields/SourcesPriority.js @@ -1,60 +1,60 @@ -import React from 'react' +import React from 'react'; import Sortable from 'react-sortablejs'; -import Icon from '../Icon' -import * as helpers from '../../helpers' +import Icon from '../Icon'; +import * as helpers from '../../helpers'; -export default class SourcesPriority extends React.Component{ +export default class SourcesPriority extends React.Component { + constructor(props) { + super(props); + } - constructor(props){ - super(props); - } + handleSort(order) { + this.props.uiActions.set({ uri_schemes_priority: order }); + } - handleSort(order){ - this.props.uiActions.set({uri_schemes_priority: order}); - } + render() { + const className = 'sources-priority-field'; + const ordered_schemes = []; + const unordered_schemes = []; - render(){ - var className = "sources-priority-field"; - var ordered_schemes = []; - var unordered_schemes = []; + for (var i = 0; i < this.props.uri_schemes.length; i++) { + const index = this.props.uri_schemes_priority.indexOf(this.props.uri_schemes[i]); - for (var i = 0; i < this.props.uri_schemes.length; i++){ - var index = this.props.uri_schemes_priority.indexOf(this.props.uri_schemes[i]); + if (index > -1) { + ordered_schemes[index] = this.props.uri_schemes[i]; + } else { + unordered_schemes.push(this.props.uri_schemes[i]); + } + } - if (index > -1){ - ordered_schemes[index] = this.props.uri_schemes[i]; - } else { - unordered_schemes.push(this.props.uri_schemes[i]); - } - } + for (var i = 0; i < unordered_schemes.length; i++) { + ordered_schemes.push(unordered_schemes[i]); + } - for (var i = 0; i < unordered_schemes.length; i++){ - ordered_schemes.push(unordered_schemes[i]); - } + return ( + { + this.handleSort(order); + }} + > + { + ordered_schemes.map((scheme) => { + const name = helpers.titleCase(scheme.replace(':', '').replace('+', ' ')); - return ( - { - this.handleSort(order) - }}> - { - ordered_schemes.map(scheme => { - var name = helpers.titleCase(scheme.replace(':','').replace('+',' ')); - - return ( - - - {name} - - ); + return ( + + + {name} + + ); }) } - - ); - } -} \ No newline at end of file + + ); + } +} diff --git a/src/js/components/Fields/SpotifyAuthenticationFrame.js b/src/js/components/Fields/SpotifyAuthenticationFrame.js index b1858534..f36a1bff 100755 --- a/src/js/components/Fields/SpotifyAuthenticationFrame.js +++ b/src/js/components/Fields/SpotifyAuthenticationFrame.js @@ -1,139 +1,131 @@ -import React from 'react' -import { connect } from 'react-redux' +import React from 'react'; +import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import * as helpers from '../../helpers'; -import * as uiActions from '../../services/ui/actions' -import * as spotifyActions from '../../services/spotify/actions' +import * as uiActions from '../../services/ui/actions'; +import * as spotifyActions from '../../services/spotify/actions'; -class SpotifyAuthenticationFrame extends React.Component{ +class SpotifyAuthenticationFrame extends React.Component { + constructor(props) { + super(props); - constructor(props){ - super(props); + this.state = { + authorizing: false, + }; - this.state = { - authorizing: false - } + this.handleMessage = this.handleMessage.bind(this); + } - this.handleMessage = this.handleMessage.bind(this); - } + componentDidMount() { + window.addEventListener('message', this.handleMessage, false); + } - componentDidMount(){ - window.addEventListener('message', this.handleMessage, false); - } + componentWillUnmount() { + window.removeEventListener('message', this.handleMessage, false); + } - componentWillUnmount(){ - window.removeEventListener("message", this.handleMessage, false); - } + handleMessage(event) { + const data = helpers.toJSON(event.data); - handleMessage(event){ - let data = helpers.toJSON(event.data); + // Only digest messages relevant to us + if (data.origin != 'auth_spotify') { + return; + } - // Only digest messages relevant to us - if (data.origin != 'auth_spotify'){ - return; - } - - // Only allow incoming data from our authorized authenticator proxy - var authorization_domain = this.props.authorization_url.substring(0,this.props.authorization_url.indexOf('/',8)) - if (event.origin != authorization_domain){ - this.props.uiActions.createNotification({content: 'Authorization failed. '+event.origin+' is not the configured authorization_url.', type: 'bad'}); - return false - } + // Only allow incoming data from our authorized authenticator proxy + const authorization_domain = this.props.authorization_url.substring(0, this.props.authorization_url.indexOf('/', 8)); + if (event.origin != authorization_domain) { + this.props.uiActions.createNotification({ content: `Authorization failed. ${event.origin} is not the configured authorization_url.`, type: 'bad' }); + return false; + } - // Spotify bounced with an error - if (data.error !== undefined){ - this.props.uiActions.createNotification({content: data.error, type: 'bad'}); + // Spotify bounced with an error + if (data.error !== undefined) { + this.props.uiActions.createNotification({ content: data.error, type: 'bad' }); - // No errors? We're in! - } else { - this.props.spotifyActions.authorizationGranted(data) - this.props.spotifyActions.getMe() - } + // No errors? We're in! + } else { + this.props.spotifyActions.authorizationGranted(data); + this.props.spotifyActions.getMe(); + } - // Turn off our authorizing switch - this.setState({authorizing: false}) - } + // Turn off our authorizing switch + this.setState({ authorizing: false }); + } - startAuthorization(){ + startAuthorization() { + const self = this; + this.setState({ authorizing: true }); - var self = this - this.setState({authorizing: true}) - - // Open an authentication request window (to spotify) - var url = this.props.authorization_url+'?action=authorize' - var scopes = [ - 'playlist-modify-private', - 'playlist-modify-public', - 'playlist-read-private', - 'playlist-modify-private', - 'user-library-read', - 'user-library-modify', - 'user-follow-modify', - 'user-follow-read', - 'user-read-email', - 'user-top-read', - 'user-read-currently-playing', - 'user-read-playback-state', - 'playlist-read-collaborative', - 'ugc-image-upload' // playlist image uploading - ] - var popup = window.open(url+'&scope='+scopes.join('%20'),"popup","height=580,width=350"); - - // Start timer to check our popup's state - var timer = setInterval(checkPopup, 1000); - function checkPopup(){ + // Open an authentication request window (to spotify) + const url = `${this.props.authorization_url}?action=authorize`; + const scopes = [ + 'playlist-modify-private', + 'playlist-modify-public', + 'playlist-read-private', + 'playlist-modify-private', + 'user-library-read', + 'user-library-modify', + 'user-follow-modify', + 'user-follow-read', + 'user-read-email', + 'user-top-read', + 'user-read-currently-playing', + 'user-read-playback-state', + 'playlist-read-collaborative', + 'ugc-image-upload', // playlist image uploading + ]; + const popup = window.open(`${url}&scope=${scopes.join('%20')}`, 'popup', 'height=580,width=350'); + // Start timer to check our popup's state + const timer = setInterval(checkPopup, 1000); + function checkPopup() { // Popup has been closed - if (typeof(popup) !== 'undefined' && popup){ - if (popup.closed){ - self.setState({authorizing: false}) - clearInterval(timer); - } - - // Popup does not exist, so must have been blocked - } else { - self.props.uiActions.createNotification({content: 'Popup blocked. Please allow popups and try again.', type: 'bad'}); - self.setState({authorizing: false}) - clearInterval(timer); - } + if (typeof (popup) !== 'undefined' && popup) { + if (popup.closed) { + self.setState({ authorizing: false }); + clearInterval(timer); } - } - render(){ - if (this.state.authorizing){ - return ( - + // Popup does not exist, so must have been blocked + } else { + self.props.uiActions.createNotification({ content: 'Popup blocked. Please allow popups and try again.', type: 'bad' }); + self.setState({ authorizing: false }); + clearInterval(timer); + } + } + } + + render() { + if (this.state.authorizing) { + return ( + Authorizing... - - ) - } else if (this.props.authorized){ - return ( - this.props.spotifyActions.revokeAuthorization()}>Log out - ) - } else { - return ( - this.startAuthorization()}>Log in - ) - } - } + + ); + } if (this.props.authorized) { + return ( + this.props.spotifyActions.revokeAuthorization()}>Log out + ); + } + return ( + this.startAuthorization()}>Log in + ); + } } -const mapStateToProps = (state, ownProps) => { - return { - authorization_url: state.spotify.authorization_url, - authorized: state.spotify.authorization, - authorizing: state.spotify.authorizing - } -} +const mapStateToProps = (state, ownProps) => ({ + authorization_url: state.spotify.authorization_url, + authorized: state.spotify.authorization, + authorizing: state.spotify.authorizing, +}); -const mapDispatchToProps = (dispatch) => { - return { - uiActions: bindActionCreators(uiActions, dispatch), - spotifyActions: bindActionCreators(spotifyActions, dispatch) - } -} +const mapDispatchToProps = (dispatch) => ({ + uiActions: bindActionCreators(uiActions, dispatch), + spotifyActions: bindActionCreators(spotifyActions, dispatch), +}); -export default connect(mapStateToProps, mapDispatchToProps)(SpotifyAuthenticationFrame) \ No newline at end of file +export default connect(mapStateToProps, mapDispatchToProps)(SpotifyAuthenticationFrame); diff --git a/src/js/components/Fields/TextField.js b/src/js/components/Fields/TextField.js index 260b6b4c..c2adfd2f 100755 --- a/src/js/components/Fields/TextField.js +++ b/src/js/components/Fields/TextField.js @@ -1,49 +1,48 @@ -import React from 'react' +import React from 'react'; -export default class TextField extends React.Component{ +export default class TextField extends React.Component { + constructor(props) { + super(props); - constructor(props){ - super(props); + this.state = { + in_focus: false, + value: (this.props.value ? this.props.value : ''), + }; + } - this.state = { - in_focus: false, - value: (this.props.value ? this.props.value : '') - } - } + componentWillReceiveProps(newProps) { + if (!this.state.in_focus) { + this.setState({ value: newProps.value }); + } + } - componentWillReceiveProps(newProps){ - if (!this.state.in_focus){ - this.setState({value: newProps.value}) - } - } + handleChange(e) { + this.setState({ value: e.target.value }); + } - handleChange(e){ - this.setState({value: e.target.value}); - } + handleFocus(e) { + this.setState({ in_focus: true }); + } - handleFocus(e){ - this.setState({in_focus: true}); - } + handleBlur(e) { + this.setState({ in_focus: false }); + if (this.state.value !== this.props.value) { + this.props.onChange(this.state.value); + } + } - handleBlur(e){ - this.setState({in_focus: false}); - if (this.state.value !== this.props.value){ - this.props.onChange(this.state.value); - } - } - - render(){ - return ( - this.handleChange(e)} - onFocus={e => this.handleFocus(e)} - onBlur={e => this.handleBlur(e)} - value={this.state.value} - placeholder={this.props.placeholder ? this.props.placeholder : null} - /> - ); - } -} \ No newline at end of file + render() { + return ( + this.handleChange(e)} + onFocus={(e) => this.handleFocus(e)} + onBlur={(e) => this.handleBlur(e)} + value={this.state.value} + placeholder={this.props.placeholder ? this.props.placeholder : null} + /> + ); + } +} diff --git a/src/js/components/Fields/VolumeControl.js b/src/js/components/Fields/VolumeControl.js index ed854b18..e092e70e 100755 --- a/src/js/components/Fields/VolumeControl.js +++ b/src/js/components/Fields/VolumeControl.js @@ -4,35 +4,34 @@ import React from 'react'; import Icon from '../Icon'; import * as helpers from '../../helpers'; -export default class VolumeControl extends React.Component{ +export default class VolumeControl extends React.Component { + constructor(props) { + super(props); - constructor(props){ - super(props); - - this.handleChange = helpers.throttle(this.handleChange.bind(this), 100); - } + this.handleChange = helpers.throttle(this.handleChange.bind(this), 100); + } - handleChange(value){ - this.props.onVolumeChange(value, this.props.volume); - } + handleChange(value) { + this.props.onVolumeChange(value, this.props.volume); + } - render(){ - return ( -
-
- this.handleChange(parseInt(e.target.value)*4)} - /> -
-
-
-
-
- ); - } + render() { + return ( +
+
+ this.handleChange(parseInt(e.target.value) * 4)} + /> +
+
+
+
+
+ ); + } } diff --git a/src/js/components/GridItem.js b/src/js/components/GridItem.js index a274f001..5a97457f 100755 --- a/src/js/components/GridItem.js +++ b/src/js/components/GridItem.js @@ -7,128 +7,129 @@ import Icon from './Icon'; import Thumbnail from './Thumbnail'; import ArtistSentence from './ArtistSentence'; -export default class GridItem extends React.Component{ - componentDidMount(){ - const { discogsActions, lastfmActions, item } = this.props; - if (!item) return; +export default class GridItem extends React.Component { + componentDidMount() { + const { discogsActions, lastfmActions, item } = this.props; + if (!item) return; - // If the item that has just been mounted doesn't have images, - // try fetching them from LastFM or Discogs - if (!item.images && discogsActions){ - switch (helpers.uriType(item.uri)){ + // If the item that has just been mounted doesn't have images, + // try fetching them from LastFM or Discogs + if (!item.images && discogsActions) { + switch (helpers.uriType(item.uri)) { + case 'artist': + if (discogsActions) { + discogsActions.getArtistImages(item.uri, item); + } + break; - case 'artist': - if (discogsActions) { - discogsActions.getArtistImages(item.uri, item); - } - break; + case 'album': + if (lastfmActions && item.artists && item.artists.length > 0) { + lastfmActions.getAlbum(item.uri, item.artists[0].name, item.name, (item.mbid ? item.mbid : null)); + } + break; + } + } + } - case 'album': - if (lastfmActions && item.artists && item.artists.length > 0){ - lastfmActions.getAlbum(item.uri, item.artists[0].name, item.name, (item.mbid ? item.mbid : null)); - } - break; - } - } - } + onContextMenu(e) { + if (this.props.onContextMenu) { + this.props.onContextMenu(e); + } + } - onContextMenu(e){ - if (this.props.onContextMenu){ - this.props.onContextMenu(e) - } - } + shouldComponentUpdate(nextProps, nextState) { + return nextProps.item != this.props.item; + } - shouldComponentUpdate(nextProps, nextState){ - return nextProps.item != this.props.item; - } + renderSecondary(item) { + const output = ''; + const link_to = null; - renderSecondary(item){ - var output = ''; - var link_to = null; + switch (helpers.uriType(item.uri)) { + case 'playlist': + if (item.tracks_total) { + return ( + + {item.tracks_total} + {' '} +tracks + + ); + } + break; - switch (helpers.uriType(item.uri)){ - case 'playlist': - if (item.tracks_total){ - return ( - - {item.tracks_total} tracks - - ); - } - break + case 'artist': + return ( + + {item.followers !== undefined ? `${item.followers.toLocaleString()} followers ` : null} + {item.albums_uris !== undefined ? `${item.albums_uris.length} albums` : null} + + ); + break; - case 'artist': - return ( - - {item.followers !== undefined ? item.followers.toLocaleString()+' followers ' : null} - {item.albums_uris !== undefined ? item.albums_uris.length+' albums' : null} - - ) - break + case 'album': + return ( + + {item.artists !== undefined ? : null} + + ); + break; - case 'album': - return ( - - {item.artists !== undefined ? : null} - - ) - break + default: + return ( + + { item.artists !== undefined ? : null } + { item.followers !== undefined ? `${item.followers.toLocaleString()} followers` : null } + + ); + } - default: - return ( - - { item.artists !== undefined ? : null } - { item.followers !== undefined ? item.followers.toLocaleString()+' followers' : null } - - ) - } + return output; + } - return output; - } + render() { + if (!this.props.item) { + return null; + } - render(){ - if (!this.props.item){ - return null; - } + let { item } = this.props; + if (item.album !== undefined) { + item.album.added_at = item.added_at; + item = item.album; + } + let images = null; + if (this.props.item.images) { + if (Array.isArray(this.props.item.images)) { + images = this.props.item.images[0]; + } else { + images = this.props.item.images; + } + } else if (this.props.item.icons) { + images = this.props.item.icons; + } - var item = this.props.item; - if (item.album !== undefined){ - item.album.added_at = item.added_at; - item = item.album; - } - var images = null; - if (this.props.item.images){ - if (Array.isArray(this.props.item.images)){ - images = this.props.item.images[0]; - } else { - images = this.props.item.images; - } - } else if (this.props.item.icons){ - images = this.props.item.icons; - } + if (this.props.link) { + var { link } = this.props; + } else { + var link = `/${this.props.type}/${encodeURIComponent(item.uri)}`; + } - if (this.props.link){ - var link = this.props.link; - } else { - var link = '/'+this.props.type+'/'+encodeURIComponent(item.uri); - } - - return ( - helpers.scrollTo()} - onContextMenu={e => this.onContextMenu(e)}> - -
- {item.name ? item.name : {item.uri}} -
-
- {this.props.show_source_icon ? : null} - {this.renderSecondary(item)} -
- - ); - } + return ( + helpers.scrollTo()} + onContextMenu={(e) => this.onContextMenu(e)} + > + +
+ {item.name ? item.name : {item.uri}} +
+
+ {this.props.show_source_icon ? : null} + {this.renderSecondary(item)} +
+ + ); + } } - diff --git a/src/js/components/GridSlider.js b/src/js/components/GridSlider.js index 64ef4305..3d145ec2 100755 --- a/src/js/components/GridSlider.js +++ b/src/js/components/GridSlider.js @@ -1,108 +1,103 @@ -import React from 'react' -import { connect } from 'react-redux' -import { createStore, bindActionCreators } from 'redux' +import React from 'react'; +import { connect } from 'react-redux'; +import { createStore, bindActionCreators } from 'redux'; -import Link from './Link' +import Link from './Link'; -import ArtistSentence from './ArtistSentence' -import Thumbnail from './Thumbnail' +import ArtistSentence from './ArtistSentence'; +import Thumbnail from './Thumbnail'; -import * as uiActions from '../services/ui/actions' +import * as uiActions from '../services/ui/actions'; -class GridSlider extends React.Component{ +class GridSlider extends React.Component { + constructor(props) { + super(props); - constructor(props){ - super(props) + this._pagelimit = 3; - this._pagelimit = 3 + this.state = { + page: 0, + }; + } - this.state = { - page: 0 - } - } + handleClick(e, link) { + if (e.target.tagName.toLowerCase() !== 'a') { + this.props.history.push(link); + } + } - handleClick(e,link){ - if (e.target.tagName.toLowerCase() !== 'a'){ - this.props.history.push(link) - } - } + handleContextMenu(e, item) { + e.preventDefault(); + const data = { + uris: [item.uri], + item, + }; + this.props.uiActions.showContextMenu(e, data, 'album', 'click'); + } - handleContextMenu(e,item){ - e.preventDefault() - var data = { - uris: [item.uri], - item: item - } - this.props.uiActions.showContextMenu(e, data, 'album', 'click' ) - } + next() { + if (this.state.page >= this._pagelimit) return false; + this.setState({ page: this.state.page + 1 }); + } - next(){ - if (this.state.page >= this._pagelimit) return false - this.setState({ page: this.state.page + 1 }) - } + previous() { + if (this.state.page <= 0) return false; + this.setState({ page: this.state.page - 1 }); + } - previous(){ - if (this.state.page <= 0) return false - this.setState({ page: this.state.page - 1 }) - } + render() { + if (this.props.tracks) { + let className = 'grid-slider-wrapper'; + if (this.props.className) className += ` ${this.props.className}`; - render(){ - if (this.props.tracks){ + const style = { + left: `-${this.state.page * 100}%`, + }; - var className = "grid-slider-wrapper" - if (this.props.className ) className += ' '+this.props.className - - var style = { - left: '-'+(this.state.page * 100)+'%' - } - - return ( -
- { this.props.title ? this.props.title : null } -
- this.previous() } /> - = this._pagelimit} onClick={ () => this.next() } /> -
-
-
- { + return ( +
+ { this.props.title ? this.props.title : null } +
+ this.previous()} /> + = this._pagelimit} onClick={() => this.next()} /> +
+
+
+ { this.props.tracks.map( - (track, index) => { - var album = Object.assign({}, track.album, { artists: track.artists }) - return ( -
this.handleClick(e,'/album/'+album.uri) } - onContextMenu={e => this.handleContextMenu(e,album)}> - -
{ album.name }
-
- { album.artists ? : - } -
-
- ) - } + (track, index) => { + const album = { ...track.album, artists: track.artists }; + return ( +
this.handleClick(e, `/album/${album.uri}`)} + onContextMenu={(e) => this.handleContextMenu(e, album)} + > + +
{ album.name }
+
+ { album.artists ? : - } +
+
+ ); + }, ) } -
-
-
- ); - } - return null; - } +
+
+
+ ); + } + return null; + } } -const mapStateToProps = (state, ownProps) => { - return {} -} +const mapStateToProps = (state, ownProps) => ({}); -const mapDispatchToProps = (dispatch) => { - return { - uiActions: bindActionCreators(uiActions, dispatch) - } -} - -export default connect(mapStateToProps, mapDispatchToProps)(GridSlider) +const mapDispatchToProps = (dispatch) => ({ + uiActions: bindActionCreators(uiActions, dispatch), +}); +export default connect(mapStateToProps, mapDispatchToProps)(GridSlider); diff --git a/src/js/components/Header.js b/src/js/components/Header.js index ccc2c17d..161cce2f 100755 --- a/src/js/components/Header.js +++ b/src/js/components/Header.js @@ -1,71 +1,67 @@ -import React from 'react' +import React from 'react'; import Icon from './Icon'; import ContextMenuTrigger from './ContextMenuTrigger'; -export default class Header extends React.Component{ +export default class Header extends React.Component { + constructor(props) { + super(props); - constructor(props){ - super(props); + this.state = { + expanded: false, + }; + } - this.state = { - expanded: false - } - } + handleContextMenuTrigger(e, options) { + // We have an override trigger (eg Album, Playlist) + if (this.props.handleContextMenuTrigger) { + return this.props.handleContextMenuTrigger(e); + } + e.preventDefault(); + const data = { + e, + context: 'custom', + title: this.props.title, + options, + }; + this.props.uiActions.showContextMenu(data); + } - handleContextMenuTrigger(e,options){ + renderContextMenuTrigger() { + // No custom trigger, nor any options + if (!this.props.handleContextMenuTrigger && !this.props.options) { + return null; + } - // We have an override trigger (eg Album, Playlist) - if (this.props.handleContextMenuTrigger){ - return this.props.handleContextMenuTrigger(e) - } else { - e.preventDefault() - var data = { - e: e, - context: 'custom', - title: this.props.title, - options: options - } - this.props.uiActions.showContextMenu(data) - } - } + return this.handleContextMenuTrigger(e, this.props.options)} />; + } - renderContextMenuTrigger(){ + renderOptions() { + if (!this.props.options && !this.props.handleContextMenuTrigger) { + return null; + } - // No custom trigger, nor any options - if (!this.props.handleContextMenuTrigger && !this.props.options){ - return null; - } + return ( +
+ {this.renderContextMenuTrigger()} + + + {this.props.options ? this.props.options : null} + + +
+ ); + } - return this.handleContextMenuTrigger(e,this.props.options)} /> - } - - renderOptions(){ - if (!this.props.options && !this.props.handleContextMenuTrigger){ - return null; - } - - return ( -
- {this.renderContextMenuTrigger()} - - - {this.props.options ? this.props.options : null} - - -
- ) - } - - render(){ - return ( -
-

- {this.props.children ? this.props.children : null} -

- {this.renderOptions()} -
- ) - } -} \ No newline at end of file + render() { + return ( +
+

+ {this.props.children ? this.props.children : null} +

+ {this.renderOptions()} +
+ ); + } +} diff --git a/src/js/components/Hotkeys.js b/src/js/components/Hotkeys.js index 9a0d8e34..802cdb94 100644 --- a/src/js/components/Hotkeys.js +++ b/src/js/components/Hotkeys.js @@ -5,185 +5,178 @@ import { bindActionCreators } from 'redux'; import * as uiActions from '../services/ui/actions'; import * as mopidyActions from '../services/mopidy/actions'; -class Hotkeys extends React.Component { - - constructor(props){ - super(props); - this.handleKeyDown = this.handleKeyDown.bind(this); - } +class Hotkeys extends React.Component { + constructor(props) { + super(props); + this.handleKeyDown = this.handleKeyDown.bind(this); + } - componentWillMount(){ - window.addEventListener("keydown", this.handleKeyDown, false); - } + componentWillMount() { + window.addEventListener('keydown', this.handleKeyDown, false); + } - componentWillUnmount(){ - window.removeEventListener("keydown", this.handleKeyDown, false); - } + componentWillUnmount() { + window.removeEventListener('keydown', this.handleKeyDown, false); + } - handleKeyDown(e) { + handleKeyDown(e) { + // When we're focussed on certian elements, don't fire any shortcuts + // Typically form inputs + const ignoreNodes = ['INPUT', 'TEXTAREA', 'BUTTON']; + if (ignoreNodes.indexOf(e.target.nodeName) > -1) { + return; + } - // When we're focussed on certian elements, don't fire any shortcuts - // Typically form inputs - let ignoreNodes = ['INPUT', 'TEXTAREA', 'BUTTON']; - if (ignoreNodes.indexOf(e.target.nodeName) > -1){ - return; + // Ignore when there are any key modifiers. This enables us to avoid interfering + // with browser- and OS-default functions. + if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { + return; + } + + let prevent = false; + switch (e.key.toLowerCase()) { + case ' ': + case 'p': // Super-useful once you get used to it. This negates the issue where interactive elements + // are in focus (ie slider) and is reserved for that field's interactivity. + if (this.props.play_state == 'playing') { + this.props.mopidyActions.pause(); + this.props.uiActions.createNotification({ content: 'pause', type: 'shortcut' }); + } else { + this.props.mopidyActions.play(); + this.props.uiActions.createNotification({ content: 'play_arrow', type: 'shortcut' }); } + prevent = true; + break; - // Ignore when there are any key modifiers. This enables us to avoid interfering - // with browser- and OS-default functions. - if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey){ - return; - } - - let prevent = false; - switch(e.key.toLowerCase()){ - - case " ": - case "p": // Super-useful once you get used to it. This negates the issue where interactive elements - // are in focus (ie slider) and is reserved for that field's interactivity. - if (this.props.play_state == 'playing'){ - this.props.mopidyActions.pause(); - this.props.uiActions.createNotification({content: 'pause', type: 'shortcut'}); - } else { - this.props.mopidyActions.play(); - this.props.uiActions.createNotification({content: 'play_arrow', type: 'shortcut'}); - } - prevent = true; - break; - - case "escape": - if (this.props.dragging){ - this.props.uiActions.dragEnd(); - prevent = true; - } else if (this.props.modal){ + case 'escape': + if (this.props.dragging) { + this.props.uiActions.dragEnd(); + prevent = true; + } else if (this.props.modal) { window.history.back(); - prevent = true; - } - break; - - case "s": - this.props.history.push('/search'); - prevent = true; - break; - - case "q": - this.props.history.push('/queue'); - prevent = true; - break; - - case "k": - this.props.history.push('/kiosk-mode'); - prevent = true; - break; - - case ",": - window.history.back(); - prevent = true; - break; - - case ".": - window.history.forward(); - prevent = true; - break; - - case "=": - var volume = this.props.volume - if (volume !== 'false'){ - volume += 5; - if (volume > 100){ - volume = 100 - } - this.props.mopidyActions.setVolume(volume); - if (this.props.mute){ - this.props.mopidyActions.setMute(false); - } - this.props.uiActions.createNotification({content: 'volume_up', type: 'shortcut'}); - } - prevent = true; - break; - - case "-": - var volume = this.props.volume; - if (volume !== 'false'){ - volume -= 5; - if (volume < 0){ - volume = 0; - } - this.props.mopidyActions.setVolume(volume); - if (this.props.mute){ - this.props.mopidyActions.setMute(false); - } - } - this.props.uiActions.createNotification({content: 'volume_down', type: 'shortcut'}); - prevent = true; - break; - - case "0": - if (this.props.mute){ - this.props.mopidyActions.setMute(false); - this.props.uiActions.createNotification({content: 'volume_up', type: 'shortcut'}); - } else { - this.props.mopidyActions.setMute(true); - this.props.uiActions.createNotification({content: 'volume_off', type: 'shortcut'}); - } - prevent = true; - break; - - case ";": - var new_position = this.props.play_time_position - 30000; - if (new_position < 0){ - new_position = 0;; - } - this.props.mopidyActions.setTimePosition(new_position); - this.props.uiActions.createNotification({content: 'fast_rewind', type: 'shortcut'}); - prevent = true; - break; - - case "'": - this.props.mopidyActions.setTimePosition(this.props.play_time_position + 30000); - this.props.uiActions.createNotification({content: 'fast_forward', type: 'shortcut'}); - prevent = true; - break; - - case "[": - this.props.mopidyActions.previous(); - this.props.uiActions.createNotification({content: 'skip_previous', type: 'shortcut'}); - prevent = true; - break; - - case "]": - this.props.mopidyActions.next(); - this.props.uiActions.createNotification({content: 'skip_next', type: 'shortcut'}); - prevent = true; - break; + prevent = true; } - - if (prevent){ - e.preventDefault(); - return false; + break; + + case 's': + this.props.history.push('/search'); + prevent = true; + break; + + case 'q': + this.props.history.push('/queue'); + prevent = true; + break; + + case 'k': + this.props.history.push('/kiosk-mode'); + prevent = true; + break; + + case ',': + window.history.back(); + prevent = true; + break; + + case '.': + window.history.forward(); + prevent = true; + break; + + case '=': + var { volume } = this.props; + if (volume !== 'false') { + volume += 5; + if (volume > 100) { + volume = 100; + } + this.props.mopidyActions.setVolume(volume); + if (this.props.mute) { + this.props.mopidyActions.setMute(false); + } + this.props.uiActions.createNotification({ content: 'volume_up', type: 'shortcut' }); } + prevent = true; + break; + + case '-': + var { volume } = this.props; + if (volume !== 'false') { + volume -= 5; + if (volume < 0) { + volume = 0; + } + this.props.mopidyActions.setVolume(volume); + if (this.props.mute) { + this.props.mopidyActions.setMute(false); + } + } + this.props.uiActions.createNotification({ content: 'volume_down', type: 'shortcut' }); + prevent = true; + break; + + case '0': + if (this.props.mute) { + this.props.mopidyActions.setMute(false); + this.props.uiActions.createNotification({ content: 'volume_up', type: 'shortcut' }); + } else { + this.props.mopidyActions.setMute(true); + this.props.uiActions.createNotification({ content: 'volume_off', type: 'shortcut' }); + } + prevent = true; + break; + + case ';': + var new_position = this.props.play_time_position - 30000; + if (new_position < 0) { + new_position = 0; + } + this.props.mopidyActions.setTimePosition(new_position); + this.props.uiActions.createNotification({ content: 'fast_rewind', type: 'shortcut' }); + prevent = true; + break; + + case "'": + this.props.mopidyActions.setTimePosition(this.props.play_time_position + 30000); + this.props.uiActions.createNotification({ content: 'fast_forward', type: 'shortcut' }); + prevent = true; + break; + + case '[': + this.props.mopidyActions.previous(); + this.props.uiActions.createNotification({ content: 'skip_previous', type: 'shortcut' }); + prevent = true; + break; + + case ']': + this.props.mopidyActions.next(); + this.props.uiActions.createNotification({ content: 'skip_next', type: 'shortcut' }); + prevent = true; + break; } - render(){ - return null; + if (prevent) { + e.preventDefault(); + return false; } + } + + render() { + return null; + } } -const mapStateToProps = (state, ownProps) => { - return { - volume: (state.mopidy.volume ? state.mopidy.volume : false), - mute: state.mopidy.mute, - play_state: state.mopidy.play_state, - play_time_position: parseInt(state.mopidy.time_position), - dragging: state.ui.dragger && state.ui.dragger.dragging, - }; -} +const mapStateToProps = (state, ownProps) => ({ + volume: (state.mopidy.volume ? state.mopidy.volume : false), + mute: state.mopidy.mute, + play_state: state.mopidy.play_state, + play_time_position: parseInt(state.mopidy.time_position), + dragging: state.ui.dragger && state.ui.dragger.dragging, +}); -const mapDispatchToProps = (dispatch) => { - return { - uiActions: bindActionCreators(uiActions, dispatch), - mopidyActions: bindActionCreators(mopidyActions, dispatch), - } -} +const mapDispatchToProps = (dispatch) => ({ + uiActions: bindActionCreators(uiActions, dispatch), + mopidyActions: bindActionCreators(mopidyActions, dispatch), +}); -export default connect(mapStateToProps, mapDispatchToProps)(Hotkeys) +export default connect(mapStateToProps, mapDispatchToProps)(Hotkeys); diff --git a/src/js/components/Icon.js b/src/js/components/Icon.js index f7c2947e..9348b4a8 100755 --- a/src/js/components/Icon.js +++ b/src/js/components/Icon.js @@ -3,32 +3,38 @@ import React, { memo } from 'react'; import FontAwesome from 'react-fontawesome'; export default memo((props) => { - if (!props.name || props.name === ""){ - return null; - } + if (!props.name || props.name === '') { + return null; + } - let className = "icon icon--"+(props.type ? props.type : 'material'); - if (props.className){ - className += ' '+props.className; - } + let className = `icon icon--${props.type ? props.type : 'material'}`; + if (props.className) { + className += ` ${props.className}`; + } - switch (props.type){ - case 'svg': - return (props.onClick ? props.onClick(e) : null)} />; + switch (props.type) { + case 'svg': + return (props.onClick ? props.onClick(e) : null)} />; - case 'gif': - return (props.onClick ? props.onClick(e) : null)} />; + case 'gif': + return (props.onClick ? props.onClick(e) : null)} />; - case 'fontawesome': - return (props.onClick ? props.onClick(e) : null)} />; + case 'fontawesome': + return (props.onClick ? props.onClick(e) : null)} />; - case 'css': - switch (props.name){ - case 'playing': - return - } + case 'css': + switch (props.name) { + case 'playing': + return ( + + + + + + ); + } - default: - return (props.onClick ? props.onClick(e) : null)}>{props.name}; - } -}); \ No newline at end of file + default: + return (props.onClick ? props.onClick(e) : null)}>{props.name}; + } +}); diff --git a/src/js/components/LazyLoadListener.js b/src/js/components/LazyLoadListener.js index 15cce491..dd23e7d6 100755 --- a/src/js/components/LazyLoadListener.js +++ b/src/js/components/LazyLoadListener.js @@ -2,62 +2,58 @@ import React from 'react'; import * as helpers from '../helpers'; -export default class LazyLoadListener extends React.Component{ +export default class LazyLoadListener extends React.Component { + constructor(props) { + super(props); + this.state = { + listening: (!!this.props.loadKey), + loadKey: this.props.loadKey, + }; - constructor(props){ - super(props); - this.state = { - listening: (this.props.loadKey ? true : false), - loadKey: this.props.loadKey - } + this.handleScroll = helpers.throttle(this.handleScroll.bind(this), 50); + } - this.handleScroll = helpers.throttle(this.handleScroll.bind(this), 50); - } + componentDidMount() { + this.element = document.getElementById('main'); + this.element.addEventListener('scroll', this.handleScroll, false); + } - componentDidMount(){ - this.element = document.getElementById('main'); - this.element.addEventListener("scroll", this.handleScroll, false); - } + componentWillUnmount() { + this.element.removeEventListener('scroll', this.handleScroll, false); + } - componentWillUnmount(){ - this.element.removeEventListener("scroll", this.handleScroll, false); - } + componentWillReceiveProps(nextProps) { + if (nextProps.loadKey && nextProps.loadKey !== this.state.loadKey) { + this.setState({ + loadKey: nextProps.loadKey, + listening: true, + }); + } + } - componentWillReceiveProps(nextProps){ - if (nextProps.loadKey && nextProps.loadKey !== this.state.loadKey){ - this.setState({ - loadKey: nextProps.loadKey, - listening: true - }); - } - } - - handleScroll(e){ - if (this.state.listening){ - - var window_height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); - - // At, or half a screen from bottom of the page - if (this.element.scrollTop > (this.element.scrollHeight - this.element.offsetHeight - (window_height / 2))){ - - // Immediately stop listening to avoid duplicating pagination requests - this.setState( - {listening: false}, - () => { - console.info('Loading more: '+this.props.loadKey); - this.props.loadMore(); - } - ); + handleScroll(e) { + if (this.state.listening) { + const window_height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); + // At, or half a screen from bottom of the page + if (this.element.scrollTop > (this.element.scrollHeight - this.element.offsetHeight - (window_height / 2))) { + // Immediately stop listening to avoid duplicating pagination requests + this.setState( + { listening: false }, + () => { + console.info(`Loading more: ${this.props.loadKey}`); + this.props.loadMore(); + }, + ); } - } - } + } + } - render(){ - return ( -
- {this.props.showLoader ?
: null} -
- ) - } -} \ No newline at end of file + render() { + return ( +
+ {this.props.showLoader ?
: null} +
+ ); + } +} diff --git a/src/js/components/Link.js b/src/js/components/Link.js index e2fb8502..98f2c953 100755 --- a/src/js/components/Link.js +++ b/src/js/components/Link.js @@ -8,86 +8,83 @@ import * as helpers from '../helpers'; /** * Extends react-router's Link but provides the ability to hook in to the navigation event * which lets us scroll to the top of our
for a more traditional navigation experience - **/ -class CustomLink extends React.Component{ + * */ +class CustomLink extends React.Component { + constructor(props) { + super(props); + } - constructor(props){ - super(props); - } + handleClick(e) { + // Fetch the current scroll position of our #main element and + // save to our history's state, so clicking 'back' etc will restore + // the previous scroll position. + // Note that this doesn't trigger lazy-load elements (unless scrolling + // exposes the LazyLoader component). + const main = document.getElementById('main'); + const state = (this.props.location && this.props.location.state ? this.props.location.state : {}); + state.scroll_position = main.scrollTop; - handleClick(e){ + this.props.history.replace({ state }); - // Fetch the current scroll position of our #main element and - // save to our history's state, so clicking 'back' etc will restore - // the previous scroll position. - // Note that this doesn't trigger lazy-load elements (unless scrolling - // exposes the LazyLoader component). - var main = document.getElementById('main'); - var state = (this.props.location && this.props.location.state ? this.props.location.state : {}); - state.scroll_position = main.scrollTop; + // Allow a link to disable auto-scrolling to the top of the page + // on navigation. Useful for tabs, etc. + if (!this.props.retainScroll) { + helpers.scrollTo(this.props.scrollTo, (this.props.scrollTo)); + } + } - this.props.history.replace({state: state}); + handleContextMenu(e) { + if (this.props.onContextMenu) { + this.props.onContextMenu(e); + } + } - // Allow a link to disable auto-scrolling to the top of the page - // on navigation. Useful for tabs, etc. - if (!this.props.retainScroll){ - helpers.scrollTo(this.props.scrollTo, (this.props.scrollTo)); - } - } + isLinkActive(link) { + // Decode both links + // This handles issues where one link is encoded and the other isn't, + // but they're otherwise identical + link = decodeURIComponent(link); + const current_link = decodeURIComponent(this.props.history.location.pathname); - handleContextMenu(e){ - if (this.props.onContextMenu){ - this.props.onContextMenu(e); - } - } + if (this.props.exact) { + return current_link === link; + } + return current_link.startsWith(link); + } - isLinkActive(link){ + render() { + let className = ''; + if (this.props.className) { + className += this.props.className; + } - // Decode both links - // This handles issues where one link is encoded and the other isn't, - // but they're otherwise identical - link = decodeURIComponent(link); - var current_link = decodeURIComponent(this.props.history.location.pathname); + if (!this.props.to) { + return {this.props.children}; + } - if (this.props.exact){ - return current_link === link; - } else { - return current_link.startsWith(link); - } - } + // We have an active detector method + // This is used almost solely by the Sidebar navigation + if (this.props.history !== undefined) { + if (this.isLinkActive(this.props.to)) { + if (this.props.activeClassName) { + className += ` ${this.props.activeClassName}`; + } else { + className += ' active'; + } + } + } - render(){ - var className = ""; - if (this.props.className){ - className += this.props.className; - } - - if (!this.props.to){ - return {this.props.children}; - } - - // We have an active detector method - // This is used almost solely by the Sidebar navigation - if (this.props.history !== undefined){ - if (this.isLinkActive(this.props.to)){ - if (this.props.activeClassName){ - className += " "+this.props.activeClassName; - } else { - className += " active"; - } - } - } - - return ( - this.handleClick(e)} - onContextMenu={e => this.handleContextMenu(e)} - className={className} - to={this.props.to}> - {this.props.children} - - ); - } + return ( + this.handleClick(e)} + onContextMenu={(e) => this.handleContextMenu(e)} + className={className} + to={this.props.to} + > + {this.props.children} + + ); + } } -export default withRouter(CustomLink); \ No newline at end of file +export default withRouter(CustomLink); diff --git a/src/js/components/List.js b/src/js/components/List.js index 8b4fe604..24a808eb 100755 --- a/src/js/components/List.js +++ b/src/js/components/List.js @@ -11,62 +11,55 @@ import * as uiActions from '../services/ui/actions'; import * as lastfmActions from '../services/lastfm/actions'; import * as discogsActions from '../services/discogs/actions'; -class List extends React.Component{ +class List extends React.Component { + constructor(props) { + super(props); + } - constructor(props){ - super(props); - } + handleContextMenu(e, item) { + if (this.props.handleContextMenu) { + e.preventDefault(); + this.props.handleContextMenu(e, item); + } + } - handleContextMenu(e,item){ - if (this.props.handleContextMenu){ - e.preventDefault(); - this.props.handleContextMenu(e,item); - } - } + render() { + if (!this.props.rows) return null; - render(){ - if (!this.props.rows) return null + let className = 'list'; + if (this.props.className) { + className += ` ${this.props.className}`; + } - var className = 'list' - if (this.props.className){ - className += ' '+this.props.className - } - - return ( -
- { - this.props.rows.map((item, index) => { - return ( - this.handleContextMenu(e, item)} - thumbnail={this.props.thumbnail} - details={this.props.details} - nocontext={this.props.nocontext} - /> - ); - }) + return ( +
+ { + this.props.rows.map((item, index) => ( + this.handleContextMenu(e, item)} + thumbnail={this.props.thumbnail} + details={this.props.details} + nocontext={this.props.nocontext} + /> + )) } -
- ); - } +
+ ); + } } -const mapStateToProps = (state, ownProps) => { - return {} -} +const mapStateToProps = (state, ownProps) => ({}); -const mapDispatchToProps = (dispatch) => { - return { - uiActions: bindActionCreators(uiActions, dispatch), - lastfmActions: bindActionCreators(lastfmActions, dispatch), - discogsActions: bindActionCreators(discogsActions, dispatch), - } -} +const mapDispatchToProps = (dispatch) => ({ + uiActions: bindActionCreators(uiActions, dispatch), + lastfmActions: bindActionCreators(lastfmActions, dispatch), + discogsActions: bindActionCreators(discogsActions, dispatch), +}); export default withRouter(connect(mapStateToProps, mapDispatchToProps)(List)); diff --git a/src/js/components/ListItem.js b/src/js/components/ListItem.js index f31cff1c..9760ca43 100755 --- a/src/js/components/ListItem.js +++ b/src/js/components/ListItem.js @@ -11,173 +11,195 @@ import Popularity from './Popularity'; import * as helpers from '../helpers'; -export default class ListItem extends React.Component{ +export default class ListItem extends React.Component { + constructor(props) { + super(props); + } - constructor(props){ - super(props); - } + componentDidMount() { + const { item, lastfmActions, discogsActions } = this.props; + if (!item) return; - componentDidMount(){ - const { item, lastfmActions, discogsActions } = this.props; - if (!item) return; + // If the item that has just been mounted doesn't have images, + // try fetching them from LastFM + if (!item.images) { + switch (helpers.uriType(item.uri)) { + case 'artist': + if (discogsActions) { + discogsActions.getArtistImages(item.uri, item); + } + break; - // If the item that has just been mounted doesn't have images, - // try fetching them from LastFM - if (!item.images){ - switch (helpers.uriType(item.uri)){ + case 'album': + if (lastfmActions && item.artists && item.artists.length > 0) { + lastfmActions.getAlbum(item.uri, item.artists[0].name, item.name, (item.mbid ? item.mbid : null)); + } + break; + } + } + } - case 'artist': - if (discogsActions) { - discogsActions.getArtistImages(item.uri, item); - } - break; + handleClick(e) { + // make sure we haven't clicked a nested link (ie Artist name) + if (e.target.tagName.toLowerCase() !== 'a') { + e.preventDefault(); + this.props.history.push((this.props.link_prefix ? this.props.link_prefix : '') + encodeURIComponent(this.props.item.uri)); + helpers.scrollTo(); + } + } - case 'album': - if (lastfmActions && item.artists && item.artists.length > 0){ - lastfmActions.getAlbum(item.uri, item.artists[0].name, item.name, (item.mbid ? item.mbid : null)); - } - break; - } - } - } + handleMouseDown(e) { + // make sure we haven't clicked a nested link (ie Artist name) + if (e.target.tagName.toLowerCase() !== 'a') { + e.preventDefault(); + this.props.history.push((this.props.link_prefix ? this.props.link_prefix : '') + encodeURIComponent(this.props.item.uri)); + helpers.scrollTo(); + } + } - handleClick(e){ + handleContextMenu(e) { + if (this.props.handleContextMenu) { + e.preventDefault(); + this.props.handleContextMenu(e, this.props.item); + } + } - // make sure we haven't clicked a nested link (ie Artist name) - if (e.target.tagName.toLowerCase() !== 'a'){ - e.preventDefault(); - this.props.history.push((this.props.link_prefix ? this.props.link_prefix : '') + encodeURIComponent(this.props.item.uri)); - helpers.scrollTo(); - } - } + renderValue(key_string) { + const key = key_string.split('.'); + let value = Object.assign(this.props.item); - handleMouseDown(e){ + for (let i = 0; i < key.length; i++) { + if (value[key[i]] === undefined) { + return null; + } if (typeof (value[key[i]]) === 'string' && value[key[i]].replace(' ', '') == '') { + return null; + } + value = value[key[i]]; + } - // make sure we haven't clicked a nested link (ie Artist name) - if (e.target.tagName.toLowerCase() !== 'a'){ - e.preventDefault(); - this.props.history.push((this.props.link_prefix ? this.props.link_prefix : '') + encodeURIComponent(this.props.item.uri)); - helpers.scrollTo(); - } - } + if (key_string === 'tracks_total' || key_string === 'tracks_uris.length') { + return ( + + {value} + {' '} +tracks + + ); + } + if (key_string === 'followers') { + return ( + + {value.toLocaleString()} + {' '} +followers + + ); + } + if (key_string === 'added_at') { + return ( + +Added + + {' '} +ago + + ); + } + if (key_string === 'owner') return {value.id}; + if (key_string === 'popularity') return ; + if (key_string === 'artists') return ; + if (value === true) return ; + if (typeof (value) === 'number') return {value.toLocaleString()}; + return {value}; + } - handleContextMenu(e){ - if (this.props.handleContextMenu){ - e.preventDefault(); - this.props.handleContextMenu(e,this.props.item); - } - } + render() { + const { item } = this.props; + if (!item) { + return null; + } - renderValue(key_string){ - var key = key_string.split('.'); - var value = Object.assign(this.props.item); + let class_name = 'list__item'; + if (item.type) { + class_name += ` list__item--${item.type}`; + } - for (var i = 0; i < key.length; i++){ - if (value[key[i]] === undefined){ - return null - } else if (typeof(value[key[i]]) === 'string' && value[key[i]].replace(' ','') == ''){ - return null - } else { - value = value[key[i]] - } - } + if (this.props.middle_column) { + class_name += ' list__item--has-middle-column'; + } - if (key_string === 'tracks_total' || key_string === 'tracks_uris.length') return {value} tracks - if (key_string === 'followers') return {value.toLocaleString()} followers - if (key_string === 'added_at') return Added ago - if (key_string === 'owner') return {value.id} - if (key_string === 'popularity') return - if (key_string === 'artists') return - if (value === true) return - if (typeof(value) === 'number') return {value.toLocaleString()} - return {value} - } + if (this.props.thumbnail) { + class_name += ' list__item--has-thumbnail'; + } - render(){ - var item = this.props.item; - if (!item){ - return null; - } + if (this.props.details) { + class_name += ' list__item--has-details'; + } - var class_name = 'list__item' - if (item.type){ - class_name += ' list__item--'+item.type; - } + return ( +
this.handleClick(e)} + onContextMenu={(e) => this.handleContextMenu(e)} + > - if (this.props.middle_column){ - class_name += " list__item--has-middle-column"; - } - - if (this.props.thumbnail){ - class_name += " list__item--has-thumbnail"; - } - - if (this.props.details){ - class_name += " list__item--has-details"; - } - - return ( -
this.handleClick(e)} - onContextMenu={e => this.handleContextMenu(e)}> - - {this.props.right_column && !this.props.nocontext && -
- { - (this.props.right_column ? this.props.right_column.map((column, index) => { - return ( - - {this.renderValue(column, item)} - - ) - }) : null) + {this.props.right_column && !this.props.nocontext + && ( +
+ { + (this.props.right_column ? this.props.right_column.map((column, index) => ( + + {this.renderValue(column, item)} + + )) : null) } - {this.props.nocontext ? null : this.handleContextMenu(e)} />} + {this.props.nocontext ? null : this.handleContextMenu(e)} />} -
- } +
+ )} -
+
- {this.props.thumbnail ? : null} + {this.props.thumbnail ? : null} -
- {item.name !== undefined ? this.renderValue('name') : {item.uri}} -
+
+ {item.name !== undefined ? this.renderValue('name') : {item.uri}} +
- {this.props.details ?
    - { + {this.props.details ? ( +
      + { this.props.details.map((detail, index) => { - var value = this.renderValue(detail); + const value = this.renderValue(detail); - if (!value){ - return null; - } + if (!value) { + return null; + } - return ( -
    • - {value} -
    • - ) - }) + return ( +
    • + {value} +
    • + ); + }) } -
    : null} -
+ + ) : null} +
- {this.props.middle_column ?
- { - (this.props.middle_column ? this.props.middle_column.map((column, index) => { - return ( - - {this.renderValue(column)} - - ) - }) : null) + {this.props.middle_column ? ( +
+ { + (this.props.middle_column ? this.props.middle_column.map((column, index) => ( + + {this.renderValue(column)} + + )) : null) } -
: null} -
- ); - } +
+ ) : null} +
+ ); + } } diff --git a/src/js/components/NiceNumber.js b/src/js/components/NiceNumber.js index 016bfc97..d9359b2b 100755 --- a/src/js/components/NiceNumber.js +++ b/src/js/components/NiceNumber.js @@ -1,25 +1,23 @@ -import React, { memo } from 'react' +import React, { memo } from 'react'; -export default memo((props) => { +export default memo((props) => { + let formatted = parseInt(props.value); - let formatted = parseInt(props.value); + // > 1 million + if (formatted > 1000000) { + formatted /= 1000000; + formatted = Math.round(formatted * 10) / 10; + formatted = `${formatted}m`; - // > 1 million - if (formatted > 1000000){ - formatted = formatted / 1000000; - formatted = Math.round( formatted * 10 ) / 10; - formatted = formatted+'m'; + // > 1 thousand + } else if (formatted > 1000) { + formatted /= 1000; + formatted = Math.round(formatted * 10) / 10; + formatted = `${formatted}k`; + } else { + formatted = formatted.toLocaleString(); + } - // > 1 thousand - } else if (formatted > 1000){ - formatted = formatted / 1000; - formatted = Math.round( formatted * 10 ) / 10; - formatted = formatted+'k'; - - } else { - formatted = formatted.toLocaleString(); - } - - return formatted; -}); \ No newline at end of file + return formatted; +}); diff --git a/src/js/components/Notifications.js b/src/js/components/Notifications.js index a82f5e4c..2001700b 100755 --- a/src/js/components/Notifications.js +++ b/src/js/components/Notifications.js @@ -11,212 +11,205 @@ import * as snapcastActions from '../services/snapcast/actions'; import Icon from './Icon'; -class Notifications extends React.Component{ +class Notifications extends React.Component { + constructor(props) { + super(props); + } - constructor(props){ - super(props) - } + importConfiguration(notification_key, configuration) { + console.log('Importing configuration', configuration); + const configurations = ''; - importConfiguration(notification_key, configuration){ - console.log("Importing configuration", configuration); - var configurations = ""; + if (configuration.interface) { + this.props.uiActions.set(configuration.interface); + } - if (configuration.interface){ - this.props.uiActions.set(configuration.interface); - } + if (configuration.spotify) { + this.props.spotifyActions.importAuthorization(configuration.spotify.authorization, configuration.spotify.me); + } - if (configuration.spotify){ - this.props.spotifyActions.importAuthorization(configuration.spotify.authorization, configuration.spotify.me); - } + if (configuration.lastfm) { + this.props.lastfmActions.importAuthorization(configuration.lastfm.authorization, configuration.lastfm.me); + } - if (configuration.lastfm){ - this.props.lastfmActions.importAuthorization(configuration.lastfm.authorization, configuration.lastfm.me); - } + if (configuration.genius) { + this.props.geniusActions.importAuthorization(configuration.genius.authorization, configuration.genius.me); + } - if (configuration.genius){ - this.props.geniusActions.importAuthorization(configuration.genius.authorization, configuration.genius.me); - } + this.props.uiActions.removeNotification(notification_key, true); + this.props.uiActions.createNotification({ type: 'info', content: 'Import successful' }); + } - this.props.uiActions.removeNotification(notification_key, true); - this.props.uiActions.createNotification({type: 'info', content: 'Import successful'}); - } + renderNotifications() { + if (!this.props.notifications || this.props.notifications.length <= 0) return null; - renderNotifications(){ - if (!this.props.notifications || this.props.notifications.length <= 0) return null + const notifications = []; + for (const key in this.props.notifications) { + if (this.props.notifications.hasOwnProperty(key)) { + notifications.push(this.props.notifications[key]); + } + } - var notifications = [] - for (var key in this.props.notifications){ - if (this.props.notifications.hasOwnProperty(key)){ - notifications.push(this.props.notifications[key]) - } - } + return ( + + { + notifications.map((notification) => { + switch (notification.type) { + case 'shortcut': + return ( +
+ +
+ ); - return ( - - { - notifications.map(notification => { - switch (notification.type){ - case 'shortcut': - return ( -
- -
- ) + case 'share-configuration-received': + return ( +
+ this.props.uiActions.removeNotification(notification.key, true)} /> - case 'share-configuration-received': - return ( -
- this.props.uiActions.removeNotification(notification.key, true) } /> - -

Configuration shared

-
-

Another user has shared their configuration with you. This includes:

-
    - {notification.configuration.ui ?
  • User interface
  • : null} - {notification.configuration.spotify ?
  • Spotify
  • : null} - {notification.configuration.lastfm ?
  • LastFM
  • : null} - {notification.configuration.genius ?
  • Genius
  • : null} -
-

Do you want to import this?

-
- -
- ) +

Configuration shared

+
+

Another user has shared their configuration with you. This includes:

+
    + {notification.configuration.ui ?
  • User interface
  • : null} + {notification.configuration.spotify ?
  • Spotify
  • : null} + {notification.configuration.lastfm ?
  • LastFM
  • : null} + {notification.configuration.genius ?
  • Genius
  • : null} +
+

Do you want to import this?

+
+ +
+ ); - default: - return ( -
- this.props.uiActions.removeNotification(notification.key, true) } /> - {notification.title ?

{notification.title}

: null} - {notification.content ?
{notification.content}
: null} - {notification.description ?
{notification.description}
: null } - {notification.links ?
{notification.links.map((link, i) => { - return {link.text} - })}
: null } -
- ) - } + default: + return ( +
+ this.props.uiActions.removeNotification(notification.key, true)} /> + {notification.title ?

{notification.title}

: null} + {notification.content ?
{notification.content}
: null} + {notification.description ?
{notification.description}
: null } + {notification.links ? ( +
+ {notification.links.map((link, i) => {link.text})} +
+ ) : null } +
+ ); + } }) } -
- ) - } +
+ ); + } - renderProcess(process){ + renderProcess(process) { + let progress = 0; + if (process.data.total && process.data.remaining) { + progress = ((process.data.total - process.data.remaining) / process.data.total * 100).toFixed(); + } - var progress = 0; - if (process.data.total && process.data.remaining){ - progress = ((process.data.total - process.data.remaining) / process.data.total * 100).toFixed() - } + switch (process.status) { + case 'running': + return ( +
+
+
+
+
+
+ {process.message} + { this.props.uiActions.cancelProcess(process.key); }} /> +
+ ); - switch (process.status){ - case 'running': - return( -
-
-
-
-
-
- {process.message} - {this.props.uiActions.cancelProcess(process.key)}} /> -
- ) - - case 'cancelling': - return( -
-
+ case 'cancelling': + return ( +
+
Cancelling -
- ) +
+ ); - case 'cancelled': - case 'finished': - return null - } - } + case 'cancelled': + case 'finished': + return null; + } + } - renderProcesses(){ - if (!this.props.processes || this.props.processes.length <= 0) return null + renderProcesses() { + if (!this.props.processes || this.props.processes.length <= 0) return null; - var processes = [] - for (var key in this.props.processes){ - if (this.props.processes.hasOwnProperty(key)){ - processes.push(this.props.processes[key]) - } - } + const processes = []; + for (const key in this.props.processes) { + if (this.props.processes.hasOwnProperty(key)) { + processes.push(this.props.processes[key]); + } + } - return ( - - {processes.map(process => { - return this.renderProcess(process) - })} - - ) - } + return ( + + {processes.map((process) => this.renderProcess(process))} + + ); + } - // do we want the loading of everything to be displayed? - // not likely... - renderLoader(){ - if (!this.props.load_queue){ - return null - } + // do we want the loading of everything to be displayed? + // not likely... + renderLoader() { + if (!this.props.load_queue) { + return null; + } - var load_queue = this.props.load_queue - var load_count = 0 - for (var key in load_queue){ - if (load_queue.hasOwnProperty(key)){ - load_count++ - } - } + const { load_queue } = this.props; + let load_count = 0; + for (const key in load_queue) { + if (load_queue.hasOwnProperty(key)) { + load_count++; + } + } - if (load_count > 0){ - var className = "loading " - if (load_count > 20){ - className += 'high' - } else if (load_count > 5){ - className += 'medium' - } else { - className += 'low' - } - return ( -
- ) - } else { - return null - } - } + if (load_count > 0) { + let className = 'loading '; + if (load_count > 20) { + className += 'high'; + } else if (load_count > 5) { + className += 'medium'; + } else { + className += 'low'; + } + return ( +
+ ); + } + return null; + } - render(){ - return ( -
- {this.renderLoader()} - {this.renderNotifications()} - {this.renderProcesses()} -
- ) - } + render() { + return ( +
+ {this.renderLoader()} + {this.renderNotifications()} + {this.renderProcesses()} +
+ ); + } } -const mapStateToProps = (state, ownProps) => { - return { - broadcasts: (state.ui.broadcasts ? state.ui.broadcasts : []), - notifications: (state.ui.notifications ? state.ui.notifications : []), - processes: (state.ui.processes ? state.ui.processes : {}), - }; -} +const mapStateToProps = (state, ownProps) => ({ + broadcasts: (state.ui.broadcasts ? state.ui.broadcasts : []), + notifications: (state.ui.notifications ? state.ui.notifications : []), + processes: (state.ui.processes ? state.ui.processes : {}), +}); -const mapDispatchToProps = (dispatch) => { - return { - uiActions: bindActionCreators(uiActions, dispatch), - spotifyActions: bindActionCreators(spotifyActions, dispatch), - geniusActions: bindActionCreators(geniusActions, dispatch), - lastfmActions: bindActionCreators(lastfmActions, dispatch), - snapcastActions: bindActionCreators(snapcastActions, dispatch), - } -} +const mapDispatchToProps = (dispatch) => ({ + uiActions: bindActionCreators(uiActions, dispatch), + spotifyActions: bindActionCreators(spotifyActions, dispatch), + geniusActions: bindActionCreators(geniusActions, dispatch), + lastfmActions: bindActionCreators(lastfmActions, dispatch), + snapcastActions: bindActionCreators(snapcastActions, dispatch), +}); -export default connect(mapStateToProps, mapDispatchToProps)(Notifications) +export default connect(mapStateToProps, mapDispatchToProps)(Notifications); diff --git a/src/js/components/Parallax.js b/src/js/components/Parallax.js index da383fd5..ec29a536 100755 --- a/src/js/components/Parallax.js +++ b/src/js/components/Parallax.js @@ -1,88 +1,83 @@ -import React from 'react' +import React from 'react'; import * as helpers from '../helpers'; -export default class Parallax extends React.Component{ +export default class Parallax extends React.Component { + constructor(props) { + super(props); - constructor(props){ - super(props); + this.state = { + loaded: false, + url: null, + }; + } - this.state = { - loaded: false, - url: null - } - } + componentWillMount() { + if (this.props.image) { + this.loadImage(this.props.image); + } + } - componentWillMount(){ - if (this.props.image){ - this.loadImage(this.props.image); - } - } + componentWillReceiveProps(nextProps) { + if (nextProps.image != this.state.url) { + this.loadImage(nextProps.image); + } + } - componentWillReceiveProps(nextProps){ - if (nextProps.image != this.state.url){ - this.loadImage(nextProps.image); - } - } + loadImage(url) { + if (url && url !== '') { + this.setState({ + loaded: helpers.isCached(url), + url, + }); - loadImage(url){ - if (url && url !== ""){ + const self = this; + const imageObject = new Image(); + imageObject.src = url; - this.setState({ - loaded: helpers.isCached(url), - url: url - }); + imageObject.onload = function () { + self.setState({ + loaded: true, + url, + }); + }; - var self = this; - var imageObject = new Image(); - imageObject.src = url; + // No Image, so reset it + } else { + this.setState({ + loaded: false, + url: null, + }); + } + } - imageObject.onload = function(){ - self.setState({ - loaded: true, - url: url - }); - } + render() { + let class_name = 'parallax preserve-3d'; + if (this.props.blur) { + class_name += ' parallax--blur'; + } + if (this.state.loaded) { + class_name += ' parallax--loaded'; + } + if (this.props.fixedHeight) { + class_name += ' parallax--fixed-height'; + } else { + class_name += ' parallax--flexible-height'; + } - // No Image, so reset it - } else { - this.setState({ - loaded: false, - url: null - }); - } - } + let style = {}; + if (this.state.loaded && this.state.url) { + style = { backgroundImage: `url("${this.state.url}")` }; + } - render(){ - var class_name = "parallax preserve-3d"; - if (this.props.blur){ - class_name += " parallax--blur"; - } - if (this.state.loaded){ - class_name += " parallax--loaded"; - } - if (this.props.fixedHeight){ - class_name += " parallax--fixed-height"; - } else { - class_name += " parallax--flexible-height"; - } - - var style = {}; - if (this.state.loaded && this.state.url){ - style = {backgroundImage: `url("${this.state.url}")`}; - } - - return ( -
-
-
-
-
-
- ); - } + return ( +
+
+
+
+
+
+ ); + } } - - - diff --git a/src/js/components/PlaybackControls.js b/src/js/components/PlaybackControls.js index e788ec30..db5da5a5 100755 --- a/src/js/components/PlaybackControls.js +++ b/src/js/components/PlaybackControls.js @@ -4,345 +4,366 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import Link from './Link'; -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 ArtistSentence from './ArtistSentence' -import Thumbnail from './Thumbnail' -import Icon from './Icon' +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 ArtistSentence from './ArtistSentence'; +import Thumbnail from './Thumbnail'; +import Icon from './Icon'; -import * as helpers from '../helpers' -import * as uiActions from '../services/ui/actions' -import * as coreActions from '../services/core/actions' -import * as mopidyActions from '../services/mopidy/actions' +import * as helpers from '../helpers'; +import * as uiActions from '../services/ui/actions'; +import * as coreActions from '../services/core/actions'; +import * as mopidyActions from '../services/mopidy/actions'; -class PlaybackControls extends React.Component{ +class PlaybackControls extends React.Component { + constructor(props) { + super(props); + this.stream = null; + this.state = { + expanded: false, + current_track: null, + transition_track: null, + transition_direction: null, + }; + } - constructor(props){ - super(props) - this.stream = null; - this.state = { - expanded: false, - current_track: null, - transition_track: null, - transition_direction: null - } - } + componentDidMount() { + if (this.props.http_streaming_enabled) { + // Bust our cache, and by consequence, play our stream + this.props.coreActions.cachebustHttpStream(); + } - componentDidMount(){ - if (this.props.http_streaming_enabled){ + if (this.props.current_track) { + this.setState({ current_track: this.props.current_track }); + } + } - // Bust our cache, and by consequence, play our stream - this.props.coreActions.cachebustHttpStream(); - } + playStream(props = this.props) { + if (!this.stream) { + this.stream = new Audio(); + } else { + this.stream.src = null; + } - if (this.props.current_track){ - this.setState({current_track: this.props.current_track}); - } - } + if (!props.http_streaming_enabled || !props.http_streaming_url) { + return false; + } - playStream(props = this.props){ - - if (!this.stream){ - this.stream = new Audio(); - } else { - this.stream.src = null; - } + this.stream.src = `${props.http_streaming_url}?cb=${props.http_streaming_cachebuster}`; + this.stream.muted = props.http_streaming_mute; + this.stream.volume = props.http_streaming_volume / 100; + this.stream.play(); - if (!props.http_streaming_enabled || !props.http_streaming_url){ - return false; - } + console.log(`Playing stream: ${this.stream.src}`); + } - this.stream.src = props.http_streaming_url+"?cb="+props.http_streaming_cachebuster; - this.stream.muted = props.http_streaming_mute; - this.stream.volume = props.http_streaming_volume / 100; - this.stream.play(); + componentWillReceiveProps(nextProps) { + // Cachebuster changed + // This happens when playback changes, so that the stream is "new", rather + // than the original stream. This prevents the browser cache from starting + // the stream right from the beginning (which could be hours of continuous playback). + if (this.props.http_streaming_cachebuster !== nextProps.http_streaming_cachebuster) { + this.playStream(nextProps); + } - console.log("Playing stream: "+this.stream.src); - } + // Just been enabled + if (!this.props.http_streaming_enabled && nextProps.http_streaming_enabled) { + this.playStream(nextProps); + } - componentWillReceiveProps(nextProps){ + if (this.stream) { + // Just been muted + if (this.props.http_streaming_mute !== nextProps.http_streaming_mute) { + this.stream.muted = nextProps.http_streaming_mute; + } - // Cachebuster changed - // This happens when playback changes, so that the stream is "new", rather - // than the original stream. This prevents the browser cache from starting - // the stream right from the beginning (which could be hours of continuous playback). - if (this.props.http_streaming_cachebuster !== nextProps.http_streaming_cachebuster){ - this.playStream(nextProps); - } + // Just had volume changed + if (this.props.http_streaming_volume !== nextProps.http_streaming_volume) { + this.stream.volume = nextProps.http_streaming_volume / 100; + } - // Just been enabled - if (!this.props.http_streaming_enabled && nextProps.http_streaming_enabled){ - this.playStream(nextProps); - } + // Just been disabled + if (!nextProps.http_streaming_enabled) { + this.stream = null; + } + } - if (this.stream){ + // Started a track or changed to no track + if ((!this.props.current_track && nextProps.current_track) || (this.props.current_track && !nextProps.current_track)) { + this.setState({ current_track: nextProps.current_track }); + } - // Just been muted - if (this.props.http_streaming_mute !== nextProps.http_streaming_mute){ - this.stream.muted = nextProps.http_streaming_mute; - } + // Direct swap-out of a track (with no 'null' intermediate state) + // This is precautionary and I've never been able to trigger it, but it's a good safety + if (this.props.current_track && nextProps.current_track && this.props.current_track.uri !== nextProps.current_track.uri) { + this.setState({ current_track: nextProps.current_track }); + } - // Just had volume changed - if (this.props.http_streaming_volume !== nextProps.http_streaming_volume){ - this.stream.volume = nextProps.http_streaming_volume / 100; - } + // Images have just loaded + // A bit niggly to have to deeply check this... + if (this.props.current_track && nextProps.current_track) { + if ((this.props.current_track.images && !nextProps.current_track.images) || (!this.props.current_track.images && nextProps.current_track.images)) { + this.setState({ current_track: nextProps.current_track }); + } + } + } - // Just been disabled - if (!nextProps.http_streaming_enabled){ - this.stream = null; - } - } + handleTouchStart(e) { + const timestamp = Math.floor(Date.now()); - // Started a track or changed to no track - if ((!this.props.current_track && nextProps.current_track) || (this.props.current_track && !nextProps.current_track)){ - this.setState({current_track: nextProps.current_track}); - } + // Save touch start details + this.start_time = timestamp; + this.start_position = { + x: e.touches[0].clientX, + }; - // Direct swap-out of a track (with no 'null' intermediate state) - // This is precautionary and I've never been able to trigger it, but it's a good safety - if (this.props.current_track && nextProps.current_track && this.props.current_track.uri !== nextProps.current_track.uri){ - this.setState({current_track: nextProps.current_track}); - } + return false; + } - // Images have just loaded - // A bit niggly to have to deeply check this... - if (this.props.current_track && nextProps.current_track){ + handleTouchEnd(e) { + const timestamp = Math.floor(Date.now()); + const tap_distance_threshold = 10; // Max distance (px) between touchstart and touchend to qualify as a tap + const tap_time_threshold = 200; // Max time (ms) between touchstart and touchend to qualify as a tap + const end_position = { + x: e.changedTouches[0].clientX, + }; - if ((this.props.current_track.images && !nextProps.current_track.images) || (!this.props.current_track.images && nextProps.current_track.images)){ - this.setState({current_track: nextProps.current_track}); - } - } - } + // Too long between touchstart and touchend + if (this.start_time + tap_time_threshold < timestamp) { + return false; + } - handleTouchStart(e){ - var timestamp = Math.floor(Date.now()); + // Make sure there's enough distance between start and end before we handle + // this event as a 'tap' + if (this.start_position.x + tap_distance_threshold > end_position.x + && this.start_position.x - tap_distance_threshold < end_position.x) { + // Scroll to top (without smooth_scroll) + helpers.scrollTo(null, false); + this.props.history.push('/queue'); + } else { + // Swipe to the left = previous track + if (this.start_position.x < end_position.x) { + this.setTransition('previous'); + this.props.mopidyActions.previous(); - // Save touch start details - this.start_time = timestamp; - this.start_position = { - x: e.touches[0].clientX - } + // Swipe to the right = skip track + } else if (this.start_position.x > end_position.x) { + this.setTransition('next'); + this.props.mopidyActions.next(); + } + } - return false; - } + this.end_time = timestamp; + e.preventDefault(); + } - handleTouchEnd(e){ - var timestamp = Math.floor(Date.now()); - var tap_distance_threshold = 10; // Max distance (px) between touchstart and touchend to qualify as a tap - var tap_time_threshold = 200; // Max time (ms) between touchstart and touchend to qualify as a tap - var end_position = { - x: e.changedTouches[0].clientX - } + setTransition(direction) { + this.setState({ + current_track: null, + transition_track: this.state.current_track, + transition_direction: direction, + }); - // Too long between touchstart and touchend - if (this.start_time + tap_time_threshold < timestamp){ - return false; - } + // Allow time for the animation to complete, then remove + // the transitioning track from state + setTimeout(() => { + this.setState({ + transition_track: null, + transition_direction: null, + }); + }, + 250); + } - // Make sure there's enough distance between start and end before we handle - // this event as a 'tap' - if (this.start_position.x + tap_distance_threshold > end_position.x && - this.start_position.x - tap_distance_threshold < end_position.x){ + renderPlayButton() { + let button = ; + if (this.props.play_state == 'playing') { + button = ; + } + return button; + } - // Scroll to top (without smooth_scroll) - helpers.scrollTo(null, false); - this.props.history.push('/queue'); - } else { + renderConsumeButton() { + let button = ( + + ); + if (this.props.consume) { + button = ( + + ); + } + return button; + } - // Swipe to the left = previous track - if (this.start_position.x < end_position.x){ - this.setTransition('previous'); - this.props.mopidyActions.previous(); + renderRandomButton() { + let button = ( + + ); + if (this.props.random) { + button = ( + + ); + } + return button; + } - // Swipe to the right = skip track - } else if (this.start_position.x > end_position.x){ - this.setTransition('next'); - this.props.mopidyActions.next(); - } - } + renderRepeatButton() { + let button = ( + + ); + if (this.props.repeat) { + button = ( + + ); + } + return button; + } - this.end_time = timestamp; - e.preventDefault(); - } + render() { + const { next_track, touch_enabled, time_position } = this.props; + const { current_track, expanded } = this.state; - setTransition(direction){ - this.setState({ - current_track: null, - transition_track: this.state.current_track, - transition_direction: direction - }); + let images = false; + if (current_track && current_track.images) { + images = current_track.images; + } - // Allow time for the animation to complete, then remove - // the transitioning track from state - setTimeout(() => { - this.setState({ - transition_track: null, - transition_direction: null - }); - }, - 250 - ); - } + return ( +
- renderPlayButton(){ - var button = - if (this.props.play_state == 'playing'){ - button = - } - return button; - } + {next_track && next_track.images ? : null} - renderConsumeButton(){ - var button = - if (this.props.consume){ - button = - } - return button; - } + {this.state.transition_track && this.state.transition_direction ? ( +
+
+
+ {this.state.transition_track.name} +
+
+ +
+
+
+ ) : null} - renderRandomButton(){ - var button = - if (this.props.random){ - button = - } - return button; - } +
touch_enabled && this.handleTouchStart(e)} + onTouchEnd={(e) => touch_enabled && this.handleTouchEnd(e)} + tabIndex="-1" + > + + + +
+
+ {current_track ? current_track.name : -} +
+
+ {current_track ? : } +
+
+
- renderRepeatButton(){ - var button = - if (this.props.repeat){ - button = - } - return button; - } +
+ + { this.renderPlayButton() } + +
- render(){ - const { next_track, touch_enabled, time_position } = this.props; - const { current_track, expanded } = this.state; +
+ + { time_position ? : '-' } + { current_track ? : '-' } +
- var images = false - if (current_track && current_track.images){ - images = current_track.images - } +
+ {this.renderConsumeButton()} + {this.renderRandomButton()} + {this.renderRepeatButton()} + +
- return ( -
+
+ this.props.mopidyActions.setMute(mute)} + /> + this.props.mopidyActions.setVolume(percent)} + /> +
- {next_track && next_track.images ? : null} - - {this.state.transition_track && this.state.transition_direction ?
-
-
- {this.state.transition_track.name} -
-
- -
-
-
: null} - -
touch_enabled && this.handleTouchStart(e)} - onTouchEnd={e => touch_enabled && this.handleTouchEnd(e)} - tabIndex="-1"> - - - -
-
- {current_track ? current_track.name : -} -
-
- {current_track ? : } -
-
-
+
+ + +
-
- - { this.renderPlayButton() } - -
- -
- - { time_position ? : '-' } - { current_track ? : '-' } -
- -
- {this.renderConsumeButton()} - {this.renderRandomButton()} - {this.renderRepeatButton()} - -
- -
- this.props.mopidyActions.setMute(mute)} - /> - this.props.mopidyActions.setVolume(percent)} - /> -
- -
- - -
- -
- ); - } +
+ ); + } } -const mapStateToProps = (state, ownProps) => { - return { - snapcast_enabled: state.pusher.config.snapcast_enabled, - http_streaming_enabled: state.core.http_streaming_enabled, - http_streaming_volume: (state.core.http_streaming_volume ? state.core.http_streaming_volume : 50), - http_streaming_mute: (state.core.http_streaming_mute ? state.core.http_streaming_mute : false), - http_streaming_url: (state.core.http_streaming_url ? state.core.http_streaming_url : null), - http_streaming_cachebuster: state.core.http_streaming_cachebuster, - current_track: (state.core.current_track && state.core.tracks[state.core.current_track.uri] !== undefined ? state.core.tracks[state.core.current_track.uri] : null), - next_track: (state.core.next_track_uri && state.core.tracks[state.core.next_track_uri] !== undefined ? state.core.tracks[state.core.next_track_uri] : null), - radio_enabled: (state.ui.radio && state.ui.radio.enabled ? true : false), - play_state: state.mopidy.play_state, - time_position: state.mopidy.time_position, - consume: state.mopidy.consume, - repeat: state.mopidy.repeat, - random: state.mopidy.random, - volume: state.mopidy.volume, - mute: state.mopidy.mute, - sidebar_open: state.ui.sidebar_open, - slim_mode: state.ui.slim_mode, - touch_enabled: state.ui.playback_controls_touch_enabled - } -} +const mapStateToProps = (state, ownProps) => ({ + snapcast_enabled: state.pusher.config.snapcast_enabled, + http_streaming_enabled: state.core.http_streaming_enabled, + http_streaming_volume: (state.core.http_streaming_volume ? state.core.http_streaming_volume : 50), + http_streaming_mute: (state.core.http_streaming_mute ? state.core.http_streaming_mute : false), + http_streaming_url: (state.core.http_streaming_url ? state.core.http_streaming_url : null), + http_streaming_cachebuster: state.core.http_streaming_cachebuster, + current_track: (state.core.current_track && state.core.tracks[state.core.current_track.uri] !== undefined ? state.core.tracks[state.core.current_track.uri] : null), + next_track: (state.core.next_track_uri && state.core.tracks[state.core.next_track_uri] !== undefined ? state.core.tracks[state.core.next_track_uri] : null), + radio_enabled: (!!(state.ui.radio && state.ui.radio.enabled)), + play_state: state.mopidy.play_state, + time_position: state.mopidy.time_position, + consume: state.mopidy.consume, + repeat: state.mopidy.repeat, + random: state.mopidy.random, + volume: state.mopidy.volume, + mute: state.mopidy.mute, + sidebar_open: state.ui.sidebar_open, + slim_mode: state.ui.slim_mode, + touch_enabled: state.ui.playback_controls_touch_enabled, +}); -const mapDispatchToProps = (dispatch) => { - return { - coreActions: bindActionCreators(coreActions, dispatch), - uiActions: bindActionCreators(uiActions, dispatch), - mopidyActions: bindActionCreators(mopidyActions, dispatch) - } -} +const mapDispatchToProps = (dispatch) => ({ + coreActions: bindActionCreators(coreActions, dispatch), + uiActions: bindActionCreators(uiActions, dispatch), + mopidyActions: bindActionCreators(mopidyActions, dispatch), +}); -export default connect(mapStateToProps, mapDispatchToProps)(PlaybackControls) \ No newline at end of file +export default connect(mapStateToProps, mapDispatchToProps)(PlaybackControls); diff --git a/src/js/components/PlaylistGrid.js b/src/js/components/PlaylistGrid.js index e73ef1f6..c38fb191 100755 --- a/src/js/components/PlaylistGrid.js +++ b/src/js/components/PlaylistGrid.js @@ -1,69 +1,61 @@ -import React from 'react' -import { connect } from 'react-redux' -import { createStore, bindActionCreators } from 'redux' +import React from 'react'; +import { connect } from 'react-redux'; +import { createStore, bindActionCreators } from 'redux'; -import * as helpers from '../helpers' -import * as uiActions from '../services/ui/actions' -import GridItem from './GridItem' +import * as helpers from '../helpers'; +import * as uiActions from '../services/ui/actions'; +import GridItem from './GridItem'; -class PlaylistGrid extends React.Component{ +class PlaylistGrid extends React.Component { + constructor(props) { + super(props); + } - constructor(props){ - super(props); - } + handleContextMenu(e, item) { + e.preventDefault(); + const data = { + e, + context: 'playlist', + uris: [item.uri], + items: [item], + tracklist_uri: item.uri, + }; + this.props.uiActions.showContextMenu(data); + } - handleContextMenu(e,item){ - e.preventDefault() - var data = { - e: e, - context: 'playlist', - uris: [item.uri], - items: [item], - tracklist_uri: item.uri - } - this.props.uiActions.showContextMenu(data) - } + render() { + if (!this.props.playlists) return null; - render(){ - if (!this.props.playlists ) return null + let className = 'grid grid--playlists'; + if (this.props.className) className += ` ${this.props.className}`; + if (this.props.single_row) className += ' grid--single-row'; + if (this.props.mini) className += ' grid--mini'; - var className = "grid grid--playlists"; - if (this.props.className) className += ' '+this.props.className; - if (this.props.single_row) className += ' grid--single-row'; - if (this.props.mini) className += ' grid--mini'; - - return ( -
- { - this.props.playlists.map(playlist => { - return ( - {this.props.history.push('/playlist/'+encodeURIComponent(playlist.uri))}} - onContextMenu={e => this.handleContextMenu(e,playlist)} - /> - )} - ) + return ( +
+ { + this.props.playlists.map((playlist) => ( + { this.props.history.push(`/playlist/${encodeURIComponent(playlist.uri)}`); }} + onContextMenu={(e) => this.handleContextMenu(e, playlist)} + /> + )) } -
- ); - } +
+ ); + } } -const mapStateToProps = (state, ownProps) => { - return {} -} +const mapStateToProps = (state, ownProps) => ({}); -const mapDispatchToProps = (dispatch) => { - return { - uiActions: bindActionCreators(uiActions, dispatch) - } -} - -export default connect(mapStateToProps, mapDispatchToProps)(PlaylistGrid) +const mapDispatchToProps = (dispatch) => ({ + uiActions: bindActionCreators(uiActions, dispatch), +}); +export default connect(mapStateToProps, mapDispatchToProps)(PlaylistGrid); diff --git a/src/js/components/Popularity.js b/src/js/components/Popularity.js index 73f3aa8c..c9328644 100755 --- a/src/js/components/Popularity.js +++ b/src/js/components/Popularity.js @@ -2,22 +2,23 @@ import React, { memo } from 'react'; export default memo((props) => { - if (props.popularity === undefined || props.popularity === null){ - return null; - } + if (props.popularity === undefined || props.popularity === null) { + return null; + } - return ( - - - 10 ? " filled" : "")}> - 30 ? " filled" : "")}> - 50 ? " filled" : "")}> - 70 ? " filled" : "")}> - 90 ? " filled" : "")}> - - - {props.popularity}% popularity - - - ); -}); \ No newline at end of file + return ( + + + 10 ? ' filled' : ''}`} /> + 30 ? ' filled' : ''}`} /> + 50 ? ' filled' : ''}`} /> + 70 ? ' filled' : ''}`} /> + 90 ? ' filled' : ''}`} /> + + + {props.popularity} +% popularity + + + ); +}); diff --git a/src/js/components/PusherConnectionList.js b/src/js/components/PusherConnectionList.js index 32035de2..2ddcf166 100755 --- a/src/js/components/PusherConnectionList.js +++ b/src/js/components/PusherConnectionList.js @@ -1,82 +1,86 @@ -import React from 'react' -import { connect } from 'react-redux' -import { createStore, bindActionCreators } from 'redux' +import React from 'react'; +import { connect } from 'react-redux'; +import { createStore, bindActionCreators } from 'redux'; -import * as pusherActions from '../services/pusher/actions' +import * as pusherActions from '../services/pusher/actions'; -class PusherConnectionList extends React.Component{ +class PusherConnectionList extends React.Component { + constructor(props) { + super(props); + } - constructor(props){ - super(props); - } + componentDidMount() { + if (this.props.connected) { + this.props.pusherActions.getConnections(); + } + } - componentDidMount(){ - if (this.props.connected){ - this.props.pusherActions.getConnections(); - } - } + componentWillReceiveProps(newProps) { + if (!this.props.connected && newProps.connected) { + this.props.pusherActions.getConnections(); + } + } - componentWillReceiveProps(newProps){ - if (!this.props.connected && newProps.connected){ - this.props.pusherActions.getConnections(); - } - } + render() { + if (!this.props.connected) { + return
Not connected
; + } - render(){ - if (!this.props.connected){ - return
Not connected
; - } + const connections = []; + for (const connection_id in this.props.connections) { + if (this.props.connections.hasOwnProperty(connection_id)) { + connections.push(this.props.connections[connection_id]); + } + } - var connections = []; - for (var connection_id in this.props.connections){ - if (this.props.connections.hasOwnProperty(connection_id)){ - connections.push(this.props.connections[connection_id]); - } - } + if (connections.length <= 0) { + return
No connections
; + } - if (connections.length <= 0){ - return
No connections
; - } + return ( +
+ { + connections.map((connection) => { + let is_me = false; + if (connection.connection_id == this.props.connection_id) { + is_me = true; + } - return ( -
- { - connections.map(connection => { - var is_me = false; - if (connection.connection_id == this.props.connection_id){ - is_me = true; - } - - return ( -
-
{ connection.username } {is_me ? (you) : null }
-
- {connection.ip} - ({connection.connection_id}) -
-
- ); + return ( +
+
+ { connection.username } + {' '} + {is_me ? (you) : null } +
+
+ {connection.ip} + + {' '} +( + {connection.connection_id} +) + +
+
+ ); }) } -
- ); - } +
+ ); + } } -const mapStateToProps = (state, ownProps) => { - return { - connected: state.pusher.connected, - connection_id: state.pusher.connection_id, - connections: state.pusher.connections - } -} +const mapStateToProps = (state, ownProps) => ({ + connected: state.pusher.connected, + connection_id: state.pusher.connection_id, + connections: state.pusher.connections, +}); -const mapDispatchToProps = (dispatch) => { - return { - pusherActions: bindActionCreators(pusherActions, dispatch) - } -} +const mapDispatchToProps = (dispatch) => ({ + pusherActions: bindActionCreators(pusherActions, dispatch), +}); -export default connect(mapStateToProps, mapDispatchToProps)(PusherConnectionList) \ No newline at end of file +export default connect(mapStateToProps, mapDispatchToProps)(PusherConnectionList); diff --git a/src/js/components/RelatedArtists.js b/src/js/components/RelatedArtists.js index 58a8a701..53151069 100755 --- a/src/js/components/RelatedArtists.js +++ b/src/js/components/RelatedArtists.js @@ -1,40 +1,38 @@ -import React, { memo } from 'react' -import Thumbnail from './Thumbnail' -import URILink from './URILink' +import React, { memo } from 'react'; +import Thumbnail from './Thumbnail'; +import URILink from './URILink'; export default memo((props) => { - if (!props.artists){ - return null; - } + if (!props.artists) { + return null; + } - return ( -
- { - props.artists.map((artist, index) => { + return ( +
+ { + props.artists.map((artist, index) => { + let { images } = artist; + if (Array.isArray(images)) { + images = images[0]; + } - var images = artist.images; - if (Array.isArray(images)){ - images = images[0]; - } - - if (artist.uri){ - return ( - - - { artist.name } - - ) - } else { - return ( - - - { artist.name } - - ) - } + if (artist.uri) { + return ( + + + { artist.name } + + ); + } + return ( + + + { artist.name } + + ); }) } -
- ); -}); \ No newline at end of file +
+ ); +}); diff --git a/src/js/components/ResizeListener.js b/src/js/components/ResizeListener.js index 9e8c2826..c2dcc653 100644 --- a/src/js/components/ResizeListener.js +++ b/src/js/components/ResizeListener.js @@ -1,20 +1,18 @@ export default (props) => { - window.addEventListener("resize", handleWindowResize, false); - - const handleWindowResize = e => { - var width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); + window.addEventListener('resize', handleWindowResize, false); - if (width <= 800){ - if (!props.slim_mode){ - props.uiActions.setSlimMode(true) - } - } else { - if (props.slim_mode){ - props.uiActions.setSlimMode(false) - } - } - } - - return null; -} \ No newline at end of file + const handleWindowResize = (e) => { + const width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); + + if (width <= 800) { + if (!props.slim_mode) { + props.uiActions.setSlimMode(true); + } + } else if (props.slim_mode) { + props.uiActions.setSlimMode(false); + } + }; + + return null; +}; diff --git a/src/js/components/Services.js b/src/js/components/Services.js index 0fc0191e..12e51f3e 100755 --- a/src/js/components/Services.js +++ b/src/js/components/Services.js @@ -1,407 +1,430 @@ -import React from 'react' -import { connect } from 'react-redux' -import Link from './Link' -import { bindActionCreators } from 'redux' +import React from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import Link from './Link'; -import Thumbnail from './Thumbnail' -import Icon from './Icon' -import URILink from './URILink' -import SpotifyAuthenticationFrame from '../components/Fields/SpotifyAuthenticationFrame' -import LastfmAuthenticationFrame from '../components/Fields/LastfmAuthenticationFrame' -import GeniusAuthenticationFrame from '../components/Fields/GeniusAuthenticationFrame' -import Snapcast from '../components/Snapcast' +import Thumbnail from './Thumbnail'; +import Icon from './Icon'; +import URILink from './URILink'; +import SpotifyAuthenticationFrame from './Fields/SpotifyAuthenticationFrame'; +import LastfmAuthenticationFrame from './Fields/LastfmAuthenticationFrame'; +import GeniusAuthenticationFrame from './Fields/GeniusAuthenticationFrame'; +import Snapcast from './Snapcast'; -import * as uiActions from '../services/ui/actions' -import * as coreActions from '../services/core/actions' -import * as mopidyActions from '../services/mopidy/actions' -import * as pusherActions from '../services/pusher/actions' -import * as spotifyActions from '../services/spotify/actions' -import * as lastfmActions from '../services/lastfm/actions' -import * as geniusActions from '../services/genius/actions' +import * as uiActions from '../services/ui/actions'; +import * as coreActions from '../services/core/actions'; +import * as mopidyActions from '../services/mopidy/actions'; +import * as pusherActions from '../services/pusher/actions'; +import * as spotifyActions from '../services/spotify/actions'; +import * as lastfmActions from '../services/lastfm/actions'; +import * as geniusActions from '../services/genius/actions'; -class Services extends React.Component{ +class Services extends React.Component { + constructor(props) { + super(props); + this.state = { + country: this.props.spotify.country, + locale: this.props.spotify.locale, + input_in_focus: null, + }; + } - constructor(props){ - super(props); - this.state = { - country: this.props.spotify.country, - locale: this.props.spotify.locale, - input_in_focus: null - } - } + componentDidMount() { + if ((this.props.spotify.enabled || this.props.spotify.authorization) && (!this.props.spotify.me || this.props.core.users[this.props.spotify.me.id] === undefined)) { + this.props.spotifyActions.getMe(); + } + if (this.props.lastfm.authorization && this.props.core.users[`lastfm:user:${this.props.lastfm.authorization.name}`] === undefined) { + this.props.lastfmActions.getMe(); + } + if (this.props.genius.authorization && (!this.props.genius.me || this.props.core.users[`genius:user:${this.props.genius.me.id}`] === undefined)) { + this.props.geniusActions.getMe(); + } + } - componentDidMount(){ - if ((this.props.spotify.enabled || this.props.spotify.authorization) && (!this.props.spotify.me || this.props.core.users[this.props.spotify.me.id] === undefined)){ - this.props.spotifyActions.getMe(); - } - if (this.props.lastfm.authorization && this.props.core.users["lastfm:user:"+this.props.lastfm.authorization.name] === undefined){ - this.props.lastfmActions.getMe(); - } - if (this.props.genius.authorization && (!this.props.genius.me || this.props.core.users["genius:user:"+this.props.genius.me.id] === undefined)){ - this.props.geniusActions.getMe(); - } - } - componentWillReceiveProps(newProps){ - var changed = false - var state = this.state + componentWillReceiveProps(newProps) { + let changed = false; + const { state } = this; - if (newProps.spotify.country != this.state.country && this.state.input_in_focus != 'country'){ - state.country = newProps.spotify.country - changed = true - } + if (newProps.spotify.country != this.state.country && this.state.input_in_focus != 'country') { + state.country = newProps.spotify.country; + changed = true; + } - if (newProps.spotify.locale != this.state.locale && this.state.input_in_focus != 'locale'){ - state.locale = newProps.spotify.locale - changed = true - } + if (newProps.spotify.locale != this.state.locale && this.state.input_in_focus != 'locale') { + state.locale = newProps.spotify.locale; + changed = true; + } - if (changed){ - this.setState(state) - } - } + if (changed) { + this.setState(state); + } + } - handleBlur(name, value){ - this.setState({input_in_focus: null}) - var data = {} - data[name] = value - this.props.coreActions.set(data) - } + handleBlur(name, value) { + this.setState({ input_in_focus: null }); + const data = {}; + data[name] = value; + this.props.coreActions.set(data); + } - renderSpotify(){ - var user_object = (this.props.spotify.me && this.props.core.users[this.props.spotify.me.uri] ? this.props.core.users[this.props.spotify.me.uri] : null); - if (user_object){ - var user = ( - - - - {user_object.name ? user_object.name : user_object.id} - {!this.props.spotify.authorization ?   (Limited access) : null} - - - ) - } else { - var user = ( - - - + renderSpotify() { + const user_object = (this.props.spotify.me && this.props.core.users[this.props.spotify.me.uri] ? this.props.core.users[this.props.spotify.me.uri] : null); + if (user_object) { + var user = ( + + + + {user_object.name ? user_object.name : user_object.id} + {!this.props.spotify.authorization ?   (Limited access) : null} + + + ); + } else { + var user = ( + + + Unknown - - - ) - } + + + ); + } - var not_installed = null; + let not_installed = null; - if (!this.props.mopidy.uri_schemes || !this.props.mopidy.uri_schemes.includes('spotify:')){ - not_installed = ( -
-

Mopidy-Spotify extension is not running - you will not be able to play any Spotify tracks

-
-
- ); - } + if (!this.props.mopidy.uri_schemes || !this.props.mopidy.uri_schemes.includes('spotify:')) { + not_installed = ( +
+

+ Mopidy-Spotify + {' '} +extension is not running - you will not be able to play any Spotify tracks +

+
+
+ ); + } - return ( -
- {not_installed} - - + return ( +
+ {not_installed} + + -
-
Current user
-
-
- {user} -
-
-
+
+
Current user
+
+
+ {user} +
+
+
-
-
Authorization
- -
-
- ); - } +
+
Authorization
+ +
+
+ ); + } - renderLastfm(){ - var user_object = (this.props.lastfm.me ? this.props.core.users["lastfm:user:"+this.props.lastfm.me.name] : null); - if (user_object){ - var user = ( - - - - {user_object.name} - - - ) - } else { - var user = ( - - - + renderLastfm() { + const user_object = (this.props.lastfm.me ? this.props.core.users[`lastfm:user:${this.props.lastfm.me.name}`] : null); + if (user_object) { + var user = ( + + + + {user_object.name} + + + ); + } else { + var user = ( + + + Unknown - - - ) - } + + + ); + } - return ( -
- {this.props.lastfm.authorization ?
-
Current user
-
-
- {user} -
-
-
: null} + return ( +
+ {this.props.lastfm.authorization ? ( +
+
Current user
+
+
+ {user} +
+
+
+ ) : null} -
-
Authorization
-
- -
-
-
- ); - } +
+
Authorization
+
+ +
+
+
+ ); + } - renderGenius(){ - var user_object = (this.props.genius.me ? this.props.core.users[this.props.genius.me.uri] : null); - if (user_object){ - var user = ( - - - - {user_object.name} - - - ) - } else { - var user = ( - - - + renderGenius() { + const user_object = (this.props.genius.me ? this.props.core.users[this.props.genius.me.uri] : null); + if (user_object) { + var user = ( + + + + {user_object.name} + + + ); + } else { + var user = ( + + + Unknown - - - ) - } + + + ); + } - return ( -
- {this.props.genius.authorization ?
-
Current user
-
-
- {user} -
-
-
: null} + return ( +
+ {this.props.genius.authorization ? ( +
+
Current user
+
+
+ {user} +
+
+
+ ) : null} -
-
Authorization
-
- -
-
-
- ); - } +
+
Authorization
+
+ +
+
+
+ ); + } - renderIcecast(){ - return ( -
-
-
Enable
-
-
+
+ +
+ ); + } - renderMenu(){ + renderMenu() { + if (this.props.spotify.me && this.props.core.users[this.props.spotify.me.uri]) { + var spotify_icon = ; + } else { + var spotify_icon = ; + } - if (this.props.spotify.me && this.props.core.users[this.props.spotify.me.uri]){ - var spotify_icon = - } else { - var spotify_icon = - } + if (this.props.lastfm.me && this.props.core.users[this.props.lastfm.me.uri]) { + var lastfm_icon = ; + } else { + var lastfm_icon = ; + } - if (this.props.lastfm.me && this.props.core.users[this.props.lastfm.me.uri]){ - var lastfm_icon = - } else { - var lastfm_icon = - } + if (this.props.genius.me && this.props.core.users[this.props.genius.me.uri]) { + var genius_icon = ; + } else { + var genius_icon = ; + } - if (this.props.genius.me && this.props.core.users[this.props.genius.me.uri]){ - var genius_icon = - } else { - var genius_icon = - } - - return ( -
-
- -
- {spotify_icon} -
+ return ( +
+
+ +
+ {spotify_icon} +
Spotify -
- {this.props.spotify.authorization ? Authorized : Read-only} -
- - -
- {lastfm_icon} -
+
+ {this.props.spotify.authorization ? Authorized : Read-only} +
+ + +
+ {lastfm_icon} +
LastFM -
- {this.props.lastfm.authorization ? Authorized : Read-only} -
- - -
- {genius_icon} -
+
+ {this.props.lastfm.authorization ? Authorized : Read-only} +
+ + +
+ {genius_icon} +
Genius -
- {this.props.genius.authorization ? Authorized : Unauthorized} -
- - -
- -
+
+ {this.props.genius.authorization ? Authorized : Unauthorized} +
+ + +
+ +
Snapcast -
- {this.props.pusher.config.snapcast_enabled ? Enabled : Disabled} -
- - -
- -
+
+ {this.props.pusher.config.snapcast_enabled ? Enabled : Disabled} +
+ + +
+ +
Icecast -
- {this.props.core.http_streaming_enabled ? Enabled : Disabled} -
- -
-
- ); - } +
+ {this.props.core.http_streaming_enabled ? Enabled : Disabled} +
+ +
+
+ ); + } - renderService(){ - var service = null; - switch (this.props.match.params.service){ - case 'spotify': - service = this.renderSpotify(); - break; - case 'lastfm': - service = this.renderLastfm(); - break; - case 'genius': - service = this.renderGenius(); - break; - case 'icecast': - service = this.renderIcecast(); - break; - case 'snapcast': - service = - break; - } + renderService() { + let service = null; + switch (this.props.match.params.service) { + case 'spotify': + service = this.renderSpotify(); + break; + case 'lastfm': + service = this.renderLastfm(); + break; + case 'genius': + service = this.renderGenius(); + break; + case 'icecast': + service = this.renderIcecast(); + break; + case 'snapcast': + service = ; + break; + } - if (service){ - return ( -
- {service} -
- ); - } else { - return null; - } - } + if (service) { + return ( +
+ {service} +
+ ); + } + return null; + } - render(){ - return ( -
- {this.renderMenu()} - {this.renderService()} -
- ); - } + render() { + return ( +
+ {this.renderMenu()} + {this.renderService()} +
+ ); + } } -const mapStateToProps = (state, ownProps) => { - return state -} +const mapStateToProps = (state, ownProps) => state; -const mapDispatchToProps = (dispatch) => { - return { - coreActions: bindActionCreators(coreActions, dispatch), - uiActions: bindActionCreators(uiActions, dispatch), - pusherActions: bindActionCreators(pusherActions, dispatch), - mopidyActions: bindActionCreators(mopidyActions, dispatch), - spotifyActions: bindActionCreators(spotifyActions, dispatch), - lastfmActions: bindActionCreators(lastfmActions, dispatch), - geniusActions: bindActionCreators(geniusActions, dispatch) - } -} +const mapDispatchToProps = (dispatch) => ({ + coreActions: bindActionCreators(coreActions, dispatch), + uiActions: bindActionCreators(uiActions, dispatch), + pusherActions: bindActionCreators(pusherActions, dispatch), + mopidyActions: bindActionCreators(mopidyActions, dispatch), + spotifyActions: bindActionCreators(spotifyActions, dispatch), + lastfmActions: bindActionCreators(lastfmActions, dispatch), + geniusActions: bindActionCreators(geniusActions, dispatch), +}); -export default connect(mapStateToProps, mapDispatchToProps)(Services) +export default connect(mapStateToProps, mapDispatchToProps)(Services); diff --git a/src/js/components/Sidebar.js b/src/js/components/Sidebar.js index 68509172..ba371999 100755 --- a/src/js/components/Sidebar.js +++ b/src/js/components/Sidebar.js @@ -12,113 +12,133 @@ import Thumbnail from './Thumbnail'; import * as uiActions from '../services/ui/actions'; import * as mopidyActions from '../services/mopidy/actions'; -class Sidebar extends React.Component{ +class Sidebar extends React.Component { + constructor(props) { + super(props); + } - constructor(props){ - super(props); - } + render() { + return ( + - ); - } + + ); + } } -const mapStateToProps = (state, ownProps) => { - return { - mopidy_connected: state.mopidy.connected, - pusher_connected: state.pusher.connected, - spotify_enabled: state.spotify.enabled, - spotify_authorized: state.spotify.authorization, - update_available: (state.pusher.version && state.pusher.version.update_available ? state.pusher.version.update_available : false), - test_mode: (state.ui.test_mode ? state.ui.test_mode : false), - dragger: state.ui.dragger - } -} +const mapStateToProps = (state, ownProps) => ({ + mopidy_connected: state.mopidy.connected, + pusher_connected: state.pusher.connected, + spotify_enabled: state.spotify.enabled, + spotify_authorized: state.spotify.authorization, + update_available: (state.pusher.version && state.pusher.version.update_available ? state.pusher.version.update_available : false), + test_mode: (state.ui.test_mode ? state.ui.test_mode : false), + dragger: state.ui.dragger, +}); -const mapDispatchToProps = (dispatch) => { - return { - uiActions: bindActionCreators(uiActions, dispatch), - mopidyActions: bindActionCreators(mopidyActions, dispatch) - } -} +const mapDispatchToProps = (dispatch) => ({ + uiActions: bindActionCreators(uiActions, dispatch), + mopidyActions: bindActionCreators(mopidyActions, dispatch), +}); -export default connect(mapStateToProps, mapDispatchToProps)(withRouter(Sidebar)); \ No newline at end of file +export default connect(mapStateToProps, mapDispatchToProps)(withRouter(Sidebar)); diff --git a/src/js/components/Snapcast.js b/src/js/components/Snapcast.js index ee1e1888..4887e843 100755 --- a/src/js/components/Snapcast.js +++ b/src/js/components/Snapcast.js @@ -17,321 +17,325 @@ import * as uiActions from '../services/ui/actions'; import * as pusherActions from '../services/pusher/actions'; import * as snapcastActions from '../services/snapcast/actions'; -class Snapcast extends React.Component{ +class Snapcast extends React.Component { + constructor(props) { + super(props); - constructor(props){ - super(props); + this.state = { + clients_expanded: [], + }; + } - this.state = { - clients_expanded: [] - } - } + componentDidMount() { + if (this.props.pusher_connected && this.props.snapcast_enabled) { + this.props.snapcastActions.getServer(); + } + } - componentDidMount(){ - if (this.props.pusher_connected && this.props.snapcast_enabled){ - this.props.snapcastActions.getServer(); - } - } + componentWillReceiveProps(newProps) { + // Just connected + if (newProps.snapcast_enabled && !this.props.pusher_connected && newProps.pusher_connected) { + this.props.snapcastActions.getServer(); - componentWillReceiveProps(newProps){ + // Just enabled + // This is the more probable scenario as we don't know if we're enabled until pusher connects + // and then gets the config from the server + } else if (!this.props.snapcast_enabled && newProps.snapcast_enabled && newProps.pusher_connected) { + this.props.snapcastActions.getServer(); + } + } - // Just connected - if (newProps.snapcast_enabled && !this.props.pusher_connected && newProps.pusher_connected){ - this.props.snapcastActions.getServer(); + toggleClientExpanded(client_id) { + const { clients_expanded } = this.state; + const index = clients_expanded.indexOf(client_id); - // Just enabled - // This is the more probable scenario as we don't know if we're enabled until pusher connects - // and then gets the config from the server - } else if (!this.props.snapcast_enabled && newProps.snapcast_enabled && newProps.pusher_connected){ - this.props.snapcastActions.getServer(); - } - } + if (index >= 0) { + clients_expanded.splice(index, 1); + } else { + clients_expanded.push(client_id); + } - toggleClientExpanded(client_id){ - var clients_expanded = this.state.clients_expanded; - var index = clients_expanded.indexOf(client_id); + this.setState({ clients_expanded }); + } - if (index >= 0){ - clients_expanded.splice(index, 1); - } else { - clients_expanded.push(client_id); - } + renderClientsList(group, groups) { + if (!this.props.show_disconnected_clients && group.clients) { + var clients = helpers.applyFilter('connected', true, group.clients); + } else { + var { clients } = group; + } - this.setState({clients_expanded: clients_expanded}); - } - - renderClientsList(group, groups){ - - if (!this.props.show_disconnected_clients && group.clients){ - var clients = helpers.applyFilter('connected', true, group.clients); - } else { - var clients = group.clients; - } - - if (!clients || clients.length <= 0){ - return ( -
+ if (!clients || clients.length <= 0) { + return ( +
No clients -
- ); - } +
+ ); + } - return ( -
- { - clients.map(client => { - var class_name = "list__item list__item--no-interaction snapcast__client"; - if (client.connected){ - class_name += " snapcast__client--connected"; - } else { - class_name += " snapcast__client--disconnected"; - } + return ( +
+ { + clients.map((client) => { + let class_name = 'list__item list__item--no-interaction snapcast__client'; + if (client.connected) { + class_name += ' snapcast__client--connected'; + } else { + class_name += ' snapcast__client--disconnected'; + } - if (this.state.clients_expanded.includes(client.id)){ - return ( -
-
this.toggleClientExpanded(client.id)}> - {client.name} -
- {!client.connected ? : null} - -
-
-
-
+
+ ); + } + return ( +
+
this.toggleClientExpanded(client.id)}> + {client.name} +
+ {!client.connected ? : null} + +
+
+
+ ); }) } -
- ); - } +
+ ); + } - render(){ - if (!this.props.snapcast_enabled){ - return ( -

To enable Snapcast, edit your mopidy.conf file

- ) - } + render() { + if (!this.props.snapcast_enabled) { + return ( +

+To enable Snapcast, edit your + mopidy.conf + {' '} +file +

+ ); + } - var streams = []; - for (var id in this.props.streams){ - if (this.props.streams.hasOwnProperty(id)){ - streams.push(this.props.streams[id]); - } - } + const streams = []; + for (var id in this.props.streams) { + if (this.props.streams.hasOwnProperty(id)) { + streams.push(this.props.streams[id]); + } + } - var groups = []; - for (var id in this.props.groups){ - if (this.props.groups.hasOwnProperty(id)){ - groups.push(this.props.groups[id]); - } - } + const groups = []; + for (var id in this.props.groups) { + if (this.props.groups.hasOwnProperty(id)) { + groups.push(this.props.groups[id]); + } + } - return ( -
+ return ( +
-
-
+
+
Display -
-
-
+
+ -
-
+ + +
+
-
- { - groups.map(group => { +
+ { + groups.map((group) => { + group = helpers.collate(group, { clients: this.props.clients }); - group = helpers.collate(group, {clients: this.props.clients}); + // Average our clients' volume for an overall group volume + let group_volume = 0; + for (let i = 0; i < group.clients.length; i++) { + const client = group.clients[i]; + group_volume += client.volume; + } + group_volume /= group.clients.length; - // Average our clients' volume for an overall group volume - var group_volume = 0; - for (var i = 0; i < group.clients.length; i++){ - var client = group.clients[i]; - group_volume += client.volume; - } - group_volume = group_volume / group.clients.length; - - return ( -
-
-
+ return ( +
+
+
Name -
-
-
- {group.name}   - ({group.id}) -
-
-
-
-
+
+
+
+ {group.name} + {' '} +  + +( + {group.id} +) + +
+
+
+
+
Stream -
-
- this.props.snapcastActions.setGroupStream(group.id, e.target.value)} value={group.stream_id}> + { + streams.map((stream) => ( + + )) } - -
-
-
-
+ +
+
+
+
Volume -
-
- this.props.snapcastActions.setGroupMute(group.id, mute)} - /> - this.props.snapcastActions.setGroupVolume(group.id, percent, old_percent)} - /> -
-
-
-
+
+
+ this.props.snapcastActions.setGroupMute(group.id, mute)} + /> + this.props.snapcastActions.setGroupVolume(group.id, percent, old_percent)} + /> +
+
+
+
Clients -
-
- {this.renderClientsList(group, groups)} -
-
-
- ); +
+
+ {this.renderClientsList(group, groups)} +
+
+
+ ); }) } -
-
- ); - } +
+
+ ); + } } -const mapStateToProps = (state, ownProps) => { - return { - snapcast_enabled: state.pusher.config.snapcast_enabled, - pusher_connected: state.pusher.connected, - show_disconnected_clients: (state.ui.snapcast_show_disconnected_clients !== undefined ? state.ui.snapcast_show_disconnected_clients : false), - streams: (state.snapcast.streams ? state.snapcast.streams : null), - groups: (state.snapcast.groups ? state.snapcast.groups : null), - clients: (state.snapcast.clients ? state.snapcast.clients : null) - } -} +const mapStateToProps = (state, ownProps) => ({ + snapcast_enabled: state.pusher.config.snapcast_enabled, + pusher_connected: state.pusher.connected, + show_disconnected_clients: (state.ui.snapcast_show_disconnected_clients !== undefined ? state.ui.snapcast_show_disconnected_clients : false), + streams: (state.snapcast.streams ? state.snapcast.streams : null), + groups: (state.snapcast.groups ? state.snapcast.groups : null), + clients: (state.snapcast.clients ? state.snapcast.clients : null), +}); -const mapDispatchToProps = (dispatch) => { - return { - coreActions: bindActionCreators(coreActions, dispatch), - uiActions: bindActionCreators(uiActions, dispatch), - snapcastActions: bindActionCreators(snapcastActions, dispatch), - pusherActions: bindActionCreators(pusherActions, dispatch) - } -} +const mapDispatchToProps = (dispatch) => ({ + coreActions: bindActionCreators(coreActions, dispatch), + uiActions: bindActionCreators(uiActions, dispatch), + snapcastActions: bindActionCreators(snapcastActions, dispatch), + pusherActions: bindActionCreators(pusherActions, dispatch), +}); -export default connect(mapStateToProps, mapDispatchToProps)(Snapcast) \ No newline at end of file +export default connect(mapStateToProps, mapDispatchToProps)(Snapcast); diff --git a/src/js/components/Thumbnail.js b/src/js/components/Thumbnail.js index b8195460..57f0b3ab 100755 --- a/src/js/components/Thumbnail.js +++ b/src/js/components/Thumbnail.js @@ -3,62 +3,60 @@ import React, { memo } from 'react'; import Link from './Link'; import Icon from './Icon'; -export default memo((props) => { +export default memo((props) => { + const mapImageSizes = () => { + // Single image + if (props.image) { + return props.image; - const mapImageSizes = () => { - - // Single image - if (props.image){ - return props.image; - - // Multiple images - } else if (props.images){ - var images = props.images; - - // An array of image objects (eg Artists), so just pick the first one - if (Array.isArray(images) && images.length > 0){ - images = images[0]; - } - - // Default to medium-sized image, but accept size property as override - var size = 'medium'; - if (props.size){ - size = props.size; - } - - // Return the requested size - if (images[size]){ - return images[size]; - } - } - - // No images - return null; - } + // Multiple images + } if (props.images) { + let { images } = props; - var image = mapImageSizes(); - var class_name = 'thumbnail thumbnail--loaded'; + // An array of image objects (eg Artists), so just pick the first one + if (Array.isArray(images) && images.length > 0) { + images = images[0]; + } - if (props.size){ - class_name += ' thumbnail--'+props.size; - } - if (props.circle){ - class_name += ' thumbnail--circle'; - } - if (props.className){ - class_name += ' '+props.className; - } - - var zoom_icon = null; - if (props.canZoom && image){ - zoom_icon = ; - } + // Default to medium-sized image, but accept size property as override + let size = 'medium'; + if (props.size) { + size = props.size; + } - return ( -
-
- {props.glow && image &&
} - {zoom_icon} -
- ); -}); \ No newline at end of file + // Return the requested size + if (images[size]) { + return images[size]; + } + } + + // No images + return null; + }; + + const image = mapImageSizes(); + let class_name = 'thumbnail thumbnail--loaded'; + + if (props.size) { + class_name += ` thumbnail--${props.size}`; + } + if (props.circle) { + class_name += ' thumbnail--circle'; + } + if (props.className) { + class_name += ` ${props.className}`; + } + + let zoom_icon = null; + if (props.canZoom && image) { + zoom_icon = ; + } + + return ( +
+
+ {props.glow && image &&
} + {zoom_icon} +
+ ); +}); diff --git a/src/js/components/Track.js b/src/js/components/Track.js index 6468d3a4..9496af7d 100755 --- a/src/js/components/Track.js +++ b/src/js/components/Track.js @@ -1,316 +1,325 @@ -import React from 'react' -import Link from './Link' +import React from 'react'; +import Link from './Link'; -import Icon from './Icon' -import ArtistSentence from './ArtistSentence' -import Dater from './Dater' -import URILink from './URILink' -import ContextMenuTrigger from './ContextMenuTrigger' -import Popularity from './Popularity' -import ErrorBoundary from './ErrorBoundary' +import Icon from './Icon'; +import ArtistSentence from './ArtistSentence'; +import Dater from './Dater'; +import URILink from './URILink'; +import ContextMenuTrigger from './ContextMenuTrigger'; +import Popularity from './Popularity'; +import ErrorBoundary from './ErrorBoundary'; -import * as helpers from '../helpers' +import * as helpers from '../helpers'; -export default class Track extends React.Component{ +export default class Track extends React.Component { + constructor(props) { + super(props); - constructor(props){ - super(props) + this.state = { + hover: false, + }; - this.state = { - hover: false - } + this.start_time = 0; + this.end_time = 0; + this.start_position = false; + } - this.start_time = 0; - this.end_time = 0; - this.start_position = false - } + handleMouseEnter(e) { + this.setState({ hover: true }); + } - handleMouseEnter(e){ - this.setState({hover: true}); - } + handleMouseLeave(e) { + this.setState({ hover: false }); + } - handleMouseLeave(e){ - this.setState({hover: false}); - } + handleMouseDown(e) { + const target = $(e.target); - handleMouseDown(e){ - var target = $(e.target); + // Clicked a nested link (ie Artist name), so no dragging required + if (target.is('a')) { + return false; + } - // Clicked a nested link (ie Artist name), so no dragging required - if (target.is('a')){ - return false; - } + // Only listen for left mouse clicks + if (e.button === 0) { + this.start_position = { + x: e.pageX, + y: e.pageY, + }; - // Only listen for left mouse clicks - if (e.button === 0){ - this.start_position = { - x: e.pageX, - y: e.pageY - } + // Not left click, then ensure no dragging + } else { + this.start_position = false; + } + } - // Not left click, then ensure no dragging - } else { - this.start_position = false - } - } + handleMouseMove(e) { + const target = $(e.target); - handleMouseMove(e){ - var target = $(e.target); + // No drag handling means NO + if (this.props.handleDrag === undefined) { + return false; + } - // No drag handling means NO - if (this.props.handleDrag === undefined){ - return false - } + if (this.start_position) { + const start_x = this.start_position.x; + const start_y = this.start_position.y; + const threshold = 5; - if (this.start_position){ - let start_x = this.start_position.x - let start_y = this.start_position.y - let threshold = 5 + // Have we dragged outside of our threshold zone? + if (e.pageX > start_x + threshold || e.pageX < start_x - threshold || e.pageY > start_y + threshold || e.pageY < start_y - threshold) { + // Handover to parent for dragging. We can unset all our behaviour now. + this.props.handleDrag(e); + this.start_position = false; + } + } + } - // Have we dragged outside of our threshold zone? - if (e.pageX > start_x + threshold || e.pageX < start_x - threshold || e.pageY > start_y + threshold || e.pageY < start_y - threshold){ + handleMouseUp(e) { + const target = $(e.target); - // Handover to parent for dragging. We can unset all our behaviour now. - this.props.handleDrag(e) - this.start_position = false - } - } - } + // Only listen for left clicks + if (e.button === 0) { + if (this.props.dragger) { + e.preventDefault(); - handleMouseUp(e){ - var target = $(e.target); + if (this.props.handleDrop !== undefined) { + this.props.handleDrop(e); + } + } else if (!target.is('a') && target.closest('a').length <= 0) { + this.props.handleClick(e); + this.start_position = false; + } - // Only listen for left clicks - if (e.button === 0){ - if (this.props.dragger){ - e.preventDefault() + // Not left click, then ensure no dragging + } else { + this.start_position = false; + return false; + } + } - if (this.props.handleDrop !== undefined){ - this.props.handleDrop(e); - } - } else { - if (!target.is('a') && target.closest('a').length <= 0){ - this.props.handleClick(e); - this.start_position = false; - } - } + handleDoubleClick(e) { + this.props.handleDoubleClick(e); + } - // Not left click, then ensure no dragging - } else { - this.start_position = false; - return false; - } - } + handleTouchStart(e) { + const target = $(e.target); + const timestamp = Math.floor(Date.now()); - handleDoubleClick(e){ - this.props.handleDoubleClick(e); - } + // Touch-drag zone + if (target.hasClass('drag-zone')) { + this.props.handleTouchDrag(e); + e.preventDefault(); + } - handleTouchStart(e){ - var target = $(e.target); - var timestamp = Math.floor(Date.now()); + // Save touch start details + this.start_time = timestamp; + this.start_position = { + x: e.touches[0].clientX, + y: e.touches[0].clientY, + }; - // Touch-drag zone - if (target.hasClass('drag-zone')){ - this.props.handleTouchDrag(e); - e.preventDefault(); - } + return false; + } - // Save touch start details - this.start_time = timestamp; - this.start_position = { - x: e.touches[0].clientX, - y: e.touches[0].clientY - } + handleTouchEnd(e) { + const target = $(e.target); + const timestamp = Math.floor(Date.now()); + const tap_distance_threshold = 10; // Max distance (px) between touchstart and touchend to qualify as a tap + const tap_time_threshold = 200; // Max time (ms) between touchstart and touchend to qualify as a tap + const end_position = { + x: e.changedTouches[0].clientX, + y: e.changedTouches[0].clientY, + }; - return false; - } + // Too long between touchstart and touchend + if (this.start_time + tap_time_threshold < timestamp) { + return false; + } - handleTouchEnd(e){ - var target = $(e.target); - var timestamp = Math.floor(Date.now()); - var tap_distance_threshold = 10; // Max distance (px) between touchstart and touchend to qualify as a tap - var tap_time_threshold = 200; // Max time (ms) between touchstart and touchend to qualify as a tap - var end_position = { - x: e.changedTouches[0].clientX, - y: e.changedTouches[0].clientY - } + // Make sure there's enough distance between start and end before we handle + // this event as a 'tap' + if (this.start_position.x + tap_distance_threshold > end_position.x + && this.start_position.x - tap_distance_threshold < end_position.x + && this.start_position.y + tap_distance_threshold > end_position.y + && this.start_position.y - tap_distance_threshold < end_position.y) { + // Clicked a nested link (ie Artist name), so no dragging required + if (!target.is('a')) { + e.preventDefault(); + } - // Too long between touchstart and touchend - if (this.start_time + tap_time_threshold < timestamp){ - return false; - } + // Context trigger + if (target.hasClass('touch-contextable')) { + // Update our selection. By not passing touch = true selection will work like a regular click + // this.props.handleSelection(e); + this.props.handleContextMenu(e); + return false; + } - // Make sure there's enough distance between start and end before we handle - // this event as a 'tap' - if (this.start_position.x + tap_distance_threshold > end_position.x && - this.start_position.x - tap_distance_threshold < end_position.x && - this.start_position.y + tap_distance_threshold > end_position.y && - this.start_position.y - tap_distance_threshold < end_position.y){ + // We received a touchend within 300ms ago, so handle as double-tap + if ((timestamp - this.end_time) > 0 && (timestamp - this.end_time) <= 300) { + this.props.handleDoubleTap(e); + e.preventDefault(); + return false; + } - // Clicked a nested link (ie Artist name), so no dragging required - if (!target.is('a')){ - e.preventDefault(); - } + this.props.handleTap(e); + } - // Context trigger - if (target.hasClass('touch-contextable')){ + this.end_time = timestamp; + } - // Update our selection. By not passing touch = true selection will work like a regular click - //this.props.handleSelection(e); - this.props.handleContextMenu(e); - return false; - } + render() { + if (!this.props.track) { + return null; + } - // We received a touchend within 300ms ago, so handle as double-tap - if ((timestamp - this.end_time) > 0 && (timestamp - this.end_time) <= 300){ - this.props.handleDoubleTap(e); - e.preventDefault(); - return false; - } + const { track } = this.props; + let className = 'list__item list__item--track mouse-draggable mouse-selectable mouse-contextable'; + const track_details = []; + const track_actions = []; - this.props.handleTap(e); - } + if (track.artists) { + track_details.push( +
  • + {track.artists ? : '-'} +
  • , + ); + } - this.end_time = timestamp; - } + if (track.album) { + if (track.album.uri) { + var album = {track.album.name}; + } else { + var album = {track.album.name}; + } - render(){ - if (!this.props.track){ - return null; - } + track_details.push( +
  • + {album} +
  • , + ); + } - var track = this.props.track; - var className = 'list__item list__item--track mouse-draggable mouse-selectable mouse-contextable'; - var track_details = []; - var track_actions = []; + if (this.props.track_context == 'history') { + var track_middle_column = ( +
    + {track.played_at ? ( + + + {' '} +ago + + ) : '-'} +
    + ); + } else if (this.props.track_context == 'queue') { + if (track.added_from && track.added_by) { + const type = (track.added_from ? helpers.uriType(track.added_from) : null); - if (track.artists){ - track_details.push( -
  • - {track.artists ? : '-'} -
  • - ) - } + switch (type) { + case 'discover': + var link = discover; + break; - if (track.album){ + case 'browse': + var link = browse; + break; - if (track.album.uri){ - var album = {track.album.name}; - } else { - var album = {track.album.name}; - } + case 'search': + var link = search; + break; - track_details.push( -
  • - {album} -
  • - ) - } + default: + var link = {type}; + } - if (this.props.track_context == 'history'){ - var track_middle_column = ( -
    - {track.played_at ? ago : '-'} -
    - ) + var track_middle_column = ( +
    + + {track.added_by} + {' '} + + +from + {link} + +
    + ); + } else if (track.added_by) { + var track_middle_column = ( +
    + {track.added_by} +
    + ); + } + } - } else if (this.props.track_context == 'queue'){ - if (track.added_from && track.added_by){ - var type = (track.added_from ? helpers.uriType(track.added_from) : null); + // If we're touchable, and can sort this tracklist + let drag_zone = null; + if (helpers.isTouchDevice() && this.props.can_sort) { + className += ' list__item--has-drag-zone'; - switch (type){ - case "discover": - var link = discover - break; + drag_zone = ( + + + + ); + } - case "browse": - var link = browse - break; + if (this.props.selected) className += ' list__item--selected'; + if (this.props.can_sort) className += ' list__item--can-sort'; + if (track.type !== undefined) className += ` list__item--${track.type}`; + if (track.playing) className += ' list__item--playing'; + if (this.state.hover) className += ' list__item--hover'; + if (track_middle_column) className += ' list__item--has-middle-column'; + if (track_details.length > 0) className += ' list__item--has-details'; - case "search": - var link = search - break; - - default: - var link = {type}; - } - - var track_middle_column = ( -
    - {track.added_by} - from {link} -
    - ); - - } else if (track.added_by){ - var track_middle_column = ( -
    - {track.added_by} -
    - ); - } - } - - // If we're touchable, and can sort this tracklist - var drag_zone = null; - if (helpers.isTouchDevice() && this.props.can_sort){ - className += " list__item--has-drag-zone" - - drag_zone = ( - - - - ); - } - - if (this.props.selected) className += ' list__item--selected'; - if (this.props.can_sort) className += ' list__item--can-sort'; - if (track.type !== undefined) className += ' list__item--'+track.type; - if (track.playing) className += ' list__item--playing'; - if (this.state.hover) className += ' list__item--hover'; - if (track_middle_column) className += " list__item--has-middle-column"; - if (track_details.length > 0) className += " list__item--has-details"; - - return ( - -
    this.handleMouseEnter(e)} - onMouseLeave={e => this.handleMouseLeave(e)} - onMouseDown={e => this.handleMouseDown(e)} - onMouseUp={e => this.handleMouseUp(e)} - onMouseMove={e => this.handleMouseMove(e)} - onDoubleClick={e => this.handleDoubleClick(e)} - onContextMenu={e => this.props.handleContextMenu(e)} - onTouchStart={e => this.handleTouchStart(e)} - onTouchEnd={e => this.handleTouchEnd(e)}> -
    -
    - {track.name ? track.name : {track.uri}} - {track.explicit ? EXPLICIT : null} - {track.playing ? : null} -
    - {track_details ?
      - {track_details} -
    : null} -
    - {track_middle_column ?
    {track_middle_column}
    : null} -
    - {drag_zone} - - {track.duration ? : '-'} - - {this.props.show_source_icon ? - - : null} - this.props.handleContextMenu(e)} /> -
    -
    -
    - ); - } -} \ No newline at end of file + return ( + +
    this.handleMouseEnter(e)} + onMouseLeave={(e) => this.handleMouseLeave(e)} + onMouseDown={(e) => this.handleMouseDown(e)} + onMouseUp={(e) => this.handleMouseUp(e)} + onMouseMove={(e) => this.handleMouseMove(e)} + onDoubleClick={(e) => this.handleDoubleClick(e)} + onContextMenu={(e) => this.props.handleContextMenu(e)} + onTouchStart={(e) => this.handleTouchStart(e)} + onTouchEnd={(e) => this.handleTouchEnd(e)} + > +
    +
    + {track.name ? track.name : {track.uri}} + {track.explicit ? EXPLICIT : null} + {track.playing ? : null} +
    + {track_details ? ( +
      + {track_details} +
    + ) : null} +
    + {track_middle_column ?
    {track_middle_column}
    : null} +
    + {drag_zone} + + {track.duration ? : '-'} + + {this.props.show_source_icon ? ( + + + + ) : null} + this.props.handleContextMenu(e)} /> +
    +
    +
    + ); + } +} diff --git a/src/js/components/TrackList.js b/src/js/components/TrackList.js index 85fb9c56..f952baf7 100755 --- a/src/js/components/TrackList.js +++ b/src/js/components/TrackList.js @@ -10,457 +10,437 @@ import * as helpers from '../helpers'; import * as mopidyActions from '../services/mopidy/actions'; import * as uiActions from '../services/ui/actions'; -class TrackList extends React.Component{ +class TrackList extends React.Component { + constructor(props) { + super(props); - constructor(props){ - super(props); + this.touch_dragging_tracks_keys = false; - this.touch_dragging_tracks_keys = false; + this.handleKeyDown = this.handleKeyDown.bind(this); + this.handleTouchMove = this.handleTouchMove.bind(this); + this.handleTouchEnd = this.handleTouchEnd.bind(this); + } - this.handleKeyDown = this.handleKeyDown.bind(this); - this.handleTouchMove = this.handleTouchMove.bind(this); - this.handleTouchEnd = this.handleTouchEnd.bind(this); - } + componentWillMount() { + window.addEventListener('keydown', this.handleKeyDown, false); + window.addEventListener('touchmove', this.handleTouchMove, false); + window.addEventListener('touchend', this.handleTouchEnd, false); + } - componentWillMount(){ - window.addEventListener("keydown", this.handleKeyDown, false); - window.addEventListener("touchmove", this.handleTouchMove, false); - window.addEventListener("touchend", this.handleTouchEnd, false); - } + componentWillUnmount() { + window.removeEventListener('keydown', this.handleKeyDown, false); + window.removeEventListener('touchmove', this.handleTouchMove, false); + window.removeEventListener('touchend', this.handleTouchEnd, false); + } - componentWillUnmount(){ - window.removeEventListener("keydown", this.handleKeyDown, false); - window.removeEventListener("touchmove", this.handleTouchMove, false); - window.removeEventListener("touchend", this.handleTouchEnd, false); - } + handleKeyDown(e) { + // When we're focussed on certian elements, don't fire any shortcuts + // Typically form inputs + const ignoreNodes = ['INPUT', 'TEXTAREA']; + if (ignoreNodes.indexOf(e.target.nodeName) > -1) { + return; + } - handleKeyDown(e){ + // Ignore when there are any key modifiers. This enables us to avoid interfering + // with browser- and OS-default functions. + if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { + return; + } - // When we're focussed on certian elements, don't fire any shortcuts - // Typically form inputs - let ignoreNodes = ['INPUT', 'TEXTAREA']; - if (ignoreNodes.indexOf(e.target.nodeName) > -1){ - return; + const tracks_keys = this.digestTracksKeys(); + + + let prevent = false; + switch (e.key.toLowerCase()) { + case 'enter': + if (tracks_keys && tracks_keys.length > 0) { + this.playTracks(); } + prevent = true; + break; - // Ignore when there are any key modifiers. This enables us to avoid interfering - // with browser- and OS-default functions. - if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey){ - return; + case 'backspace': + case 'delete': + if (tracks_keys && tracks_keys.length > 0) { + this.removeTracks(); } + prevent = true; + break; - var tracks_keys = this.digestTracksKeys() - - - let prevent = false; - switch(e.key.toLowerCase()){ - case "enter": - if (tracks_keys && tracks_keys.length > 0){ - this.playTracks(); - } - prevent = true; - break; - - case "backspace": - case "delete": - if (tracks_keys && tracks_keys.length > 0){ - this.removeTracks(); - } - prevent = true; - break; - - case "a": - var all_tracks = [] - for (var i = 0; i < this.props.tracks.length; i++){ - all_tracks.push(this.buildTrackKey(this.props.tracks[i], i)) - } - this.props.uiActions.setSelectedTracks(all_tracks); - prevent = true; - break; - } - - if (prevent){ - e.preventDefault(); - return false; + case 'a': + var all_tracks = []; + for (let i = 0; i < this.props.tracks.length; i++) { + all_tracks.push(this.buildTrackKey(this.props.tracks[i], i)); } - } + this.props.uiActions.setSelectedTracks(all_tracks); + prevent = true; + break; + } - handleDrag(e,track_key){ + if (prevent) { + e.preventDefault(); + return false; + } + } - let selected_tracks = [] + handleDrag(e, track_key) { + let selected_tracks = []; - // Dragging a non-selected track. We need to deselect everything - // else and select only this track - if (!this.props.selected_tracks.includes(track_key)){ - this.props.uiActions.setSelectedTracks([track_key]) - selected_tracks = this.digestTracksKeys([track_key]) - } else { - selected_tracks = this.digestTracksKeys() - } + // Dragging a non-selected track. We need to deselect everything + // else and select only this track + if (!this.props.selected_tracks.includes(track_key)) { + this.props.uiActions.setSelectedTracks([track_key]); + selected_tracks = this.digestTracksKeys([track_key]); + } else { + selected_tracks = this.digestTracksKeys(); + } - let selected_tracks_indexes = helpers.arrayOf('index',selected_tracks) + const selected_tracks_indexes = helpers.arrayOf('index', selected_tracks); - this.props.uiActions.dragStart( - e, - this.props.track_context, - this.props.uri, - selected_tracks, - selected_tracks_indexes - ) - } + this.props.uiActions.dragStart( + e, + this.props.track_context, + this.props.uri, + selected_tracks, + selected_tracks_indexes, + ); + } - handleDrop(e,track_key){ - if (this.props.dragger && this.props.dragger.active){ - - // if this tracklist handles sorting, handle it - if (this.props.reorderTracks !== undefined){ - let indexes = this.props.dragger.victims_indexes - let tracks = this.digestTracksKeys([track_key]) - return this.props.reorderTracks(indexes, tracks[0].index) - } - } - this.touch_dragging_tracks_keys = false - } + handleDrop(e, track_key) { + if (this.props.dragger && this.props.dragger.active) { + // if this tracklist handles sorting, handle it + if (this.props.reorderTracks !== undefined) { + const indexes = this.props.dragger.victims_indexes; + const tracks = this.digestTracksKeys([track_key]); + return this.props.reorderTracks(indexes, tracks[0].index); + } + } + this.touch_dragging_tracks_keys = false; + } - handleTouchDrag(e,track_key){ + handleTouchDrag(e, track_key) { + console.log('touchDragging', e, track_key); - console.log("touchDragging",e,track_key); + // Drag initiated on a selected track + if (this.props.selected_tracks.includes(track_key)) { + // They're all dragging + this.touch_dragging_tracks_keys = this.props.selected_tracks; - // Drag initiated on a selected track - if (this.props.selected_tracks.includes(track_key)){ + // Not already selected, so just dragging the one track + } else { + this.touch_dragging_tracks_keys = [track_key]; + this.props.uiActions.setSelectedTracks([track_key]); + } + } - // They're all dragging - this.touch_dragging_tracks_keys = this.props.selected_tracks - - // Not already selected, so just dragging the one track - } else { - this.touch_dragging_tracks_keys = [track_key] - this.props.uiActions.setSelectedTracks([track_key]) - } - } - - handleTouchMove(e){ - if (this.touch_dragging_tracks_keys){ - - let touch = e.touches[0]; - let over = $(document.elementFromPoint(touch.clientX, touch.clientY)); - if (!over.is('.track')){ - over = over.closest('.list__item--track'); - } - $(document).find('.touch-drag-hover').removeClass('touch-drag-hover'); - if (over.length > 0){ - over.addClass('touch-drag-hover'); - } + handleTouchMove(e) { + if (this.touch_dragging_tracks_keys) { + const touch = e.touches[0]; + let over = $(document.elementFromPoint(touch.clientX, touch.clientY)); + if (!over.is('.track')) { + over = over.closest('.list__item--track'); + } + $(document).find('.touch-drag-hover').removeClass('touch-drag-hover'); + if (over.length > 0) { + over.addClass('touch-drag-hover'); + } e.returnValue = false; e.cancelBubble = true; - e.preventDefault(); - e.stopPropagation(); + e.preventDefault(); + e.stopPropagation(); return false; - } - } + } + } - handleTouchEnd(e){ - if (this.touch_dragging_tracks_keys){ - let touch = e.changedTouches[0]; - let over = $(document.elementFromPoint(touch.clientX, touch.clientY)); - if (!over.is('.list__item--track')){ - over = over.closest('.list__item--track'); - } - if (over.length > 0){ - let siblings = over.parent().children('.list__item--track'); - let dropped_at = siblings.index(over); + handleTouchEnd(e) { + if (this.touch_dragging_tracks_keys) { + const touch = e.changedTouches[0]; + let over = $(document.elementFromPoint(touch.clientX, touch.clientY)); + if (!over.is('.list__item--track')) { + over = over.closest('.list__item--track'); + } + if (over.length > 0) { + const siblings = over.parent().children('.list__item--track'); + const dropped_at = siblings.index(over); - if (this.props.reorderTracks !== undefined){ - this.props.reorderTracks(helpers.arrayOf('index',this.digestTracksKeys()),dropped_at); - this.props.uiActions.setSelectedTracks([]); - } - } + if (this.props.reorderTracks !== undefined) { + this.props.reorderTracks(helpers.arrayOf('index', this.digestTracksKeys()), dropped_at); + this.props.uiActions.setSelectedTracks([]); + } + } - $(document).find('.touch-drag-hover').removeClass('touch-drag-hover'); - $('body').removeClass('touch-dragging'); - } - - this.touch_dragging_tracks_keys = false; - } + $(document).find('.touch-drag-hover').removeClass('touch-drag-hover'); + $('body').removeClass('touch-dragging'); + } - handleTap(e, track_key){ - this.updateSelection(e, track_key, true); - } + this.touch_dragging_tracks_keys = false; + } - handleDoubleTap(e,track_key){ - this.playTracks([track_key]); - this.updateSelection(e, track_key); - } + handleTap(e, track_key) { + this.updateSelection(e, track_key, true); + } - handleClick(e, track_key){ - this.updateSelection(e, track_key); - } + handleDoubleTap(e, track_key) { + this.playTracks([track_key]); + this.updateSelection(e, track_key); + } - handleDoubleClick(e,track_key){ - if (this.props.context_menu){ - this.props.uiActions.hideContextMenu(); - } - this.playTracks([track_key]); - this.updateSelection(e, track_key); - } + handleClick(e, track_key) { + this.updateSelection(e, track_key); + } - handleContextMenu(e, track_key = null){ + handleDoubleClick(e, track_key) { + if (this.props.context_menu) { + this.props.uiActions.hideContextMenu(); + } + this.playTracks([track_key]); + this.updateSelection(e, track_key); + } - // Do our best to stop any flow-on events - e.preventDefault(); - e.stopPropagation(); - e.cancelBubble = true; + handleContextMenu(e, track_key = null) { + // Do our best to stop any flow-on events + e.preventDefault(); + e.stopPropagation(); + e.cancelBubble = true; - let selected_tracks = this.props.selected_tracks; + let { selected_tracks } = this.props; - // Not already selected, so select it prior to triggering menu - if (track_key && !selected_tracks.includes(track_key)){ - selected_tracks = [track_key]; - this.props.uiActions.setSelectedTracks(selected_tracks); - } + // Not already selected, so select it prior to triggering menu + if (track_key && !selected_tracks.includes(track_key)) { + selected_tracks = [track_key]; + this.props.uiActions.setSelectedTracks(selected_tracks); + } - let selected_tracks_digested = this.digestTracksKeys(selected_tracks); - let selected_tracks_uris = helpers.arrayOf('uri',selected_tracks_digested); - let selected_tracks_indexes = helpers.arrayOf('index',selected_tracks_digested); + const selected_tracks_digested = this.digestTracksKeys(selected_tracks); + const selected_tracks_uris = helpers.arrayOf('uri', selected_tracks_digested); + const selected_tracks_indexes = helpers.arrayOf('index', selected_tracks_digested); - let data = { - e: e, - context: (this.props.track_context ? this.props.track_context+'-track' : 'track'), - tracklist_uri: (this.props.uri ? this.props.uri : null), - items: selected_tracks_digested, - uris: selected_tracks_uris, - indexes: selected_tracks_indexes - } + const data = { + e, + context: (this.props.track_context ? `${this.props.track_context}-track` : 'track'), + tracklist_uri: (this.props.uri ? this.props.uri : null), + items: selected_tracks_digested, + uris: selected_tracks_uris, + indexes: selected_tracks_indexes, + }; - this.props.uiActions.showContextMenu(data); - } + this.props.uiActions.showContextMenu(data); + } - updateSelection(e, track_key, touched = false){ - let selected_tracks = this.props.selected_tracks; + updateSelection(e, track_key, touched = false) { + let { selected_tracks } = this.props; - if ((e.ctrlKey || e.metaKey) || touched){ + if ((e.ctrlKey || e.metaKey) || touched) { + // Already selected, so unselect it + if (selected_tracks.includes(track_key)) { + const index = selected_tracks.indexOf(track_key); + selected_tracks.splice(index, 1); - // Already selected, so unselect it - if (selected_tracks.includes(track_key)){ - var index = selected_tracks.indexOf(track_key); - selected_tracks.splice(index, 1); + // Not selected, so add it + } else { + selected_tracks.push(track_key); + } + } else if (e.shiftKey) { + const last_selected_track = this.digestTracksKeys(selected_tracks[selected_tracks.length - 1]); + const last_selected_track_index = last_selected_track.index; + const newly_selected_track = this.digestTracksKeys(track_key); + const newly_selected_track_index = newly_selected_track.index; - // Not selected, so add it - } else { - selected_tracks.push(track_key); - } + // We've selected a track further down the list, + // so proceed normally + if (last_selected_track_index < newly_selected_track_index) { + var start = last_selected_track_index + 1; + var end = newly_selected_track_index; - } else if (e.shiftKey){ + // Selected a track up the list, so + // our last selected is the END of our range + } else { + var start = newly_selected_track_index; + var end = last_selected_track_index - 1; + } - let last_selected_track = this.digestTracksKeys(selected_tracks[selected_tracks.length-1]); - let last_selected_track_index = last_selected_track.index; - let newly_selected_track = this.digestTracksKeys(track_key); - let newly_selected_track_index = newly_selected_track.index; + if (start !== false && start >= 0 && end !== false && end >= 0) { + for (let i = start; i <= end; i++) { + selected_tracks.push(this.buildTrackKey(this.props.tracks[i], i)); + } + } - // We've selected a track further down the list, - // so proceed normally - if (last_selected_track_index < newly_selected_track_index){ - var start = last_selected_track_index + 1; - var end = newly_selected_track_index; + // Regular, unmodified left click + } else { + selected_tracks = [track_key]; + } - // Selected a track up the list, so - // our last selected is the END of our range - } else { - var start = newly_selected_track_index; - var end = last_selected_track_index - 1; - } + this.props.uiActions.setSelectedTracks(selected_tracks); + } - if (start !== false && start >= 0 && end !== false && end >= 0){ - for (let i = start; i <= end; i++){ - selected_tracks.push(this.buildTrackKey(this.props.tracks[i], i)); - } - } + isRightClick(e) { + if ('which' in e) { + return e.which == 3; + } if ('button' in e) { + return e.button == 2; + } + return false; + } - // Regular, unmodified left click - } else { - selected_tracks = [track_key]; - } + playTracks(tracks_keys = null) { + if (tracks_keys !== null) { + var selected_tracks = this.digestTracksKeys(tracks_keys); + } else { + var selected_tracks = this.digestTracksKeys(); + } + const selected_tracks_indexes = helpers.arrayOf('index', selected_tracks); - this.props.uiActions.setSelectedTracks(selected_tracks); - } + if (selected_tracks.length <= 0) { + return this.props.uiActions.createNotification({ content: 'No tracks selected', type: 'bad' }); + } - isRightClick(e){ - if ('which' in e ){ - return e.which == 3 - } else if ('button' in e){ - return e.button == 2; - } - return false; - } + // Our parent handles playing + if (this.props.playTracks !== undefined) { + return this.props.playTracks(selected_tracks); - playTracks(tracks_keys = null){ - if (tracks_keys !== null){ - var selected_tracks = this.digestTracksKeys(tracks_keys); - } else { - var selected_tracks = this.digestTracksKeys(); - } - var selected_tracks_indexes = helpers.arrayOf('index', selected_tracks); + // Default to playing the URIs + } + const selected_tracks_uris = helpers.arrayOf('uri', selected_tracks); + return this.props.mopidyActions.playURIs(selected_tracks_uris, this.props.uri); + } - if (selected_tracks.length <= 0){ - return this.props.uiActions.createNotification({content: 'No tracks selected', type: 'bad'}); - } + removeTracks() { + const selected_tracks = this.digestTracksKeys(); - // Our parent handles playing - if (this.props.playTracks !== undefined){ - return this.props.playTracks(selected_tracks); + // Our parent has a handler for this + if (this.props.removeTracks !== undefined) { + const selected_tracks_indexes = helpers.arrayOf('index', selected_tracks); + return this.props.removeTracks(selected_tracks_indexes); - // Default to playing the URIs - } else { - let selected_tracks_uris = helpers.arrayOf('uri',selected_tracks); - return this.props.mopidyActions.playURIs(selected_tracks_uris, this.props.uri); - } - } - - removeTracks(){ - let selected_tracks = this.digestTracksKeys(); - - // Our parent has a handler for this - if (this.props.removeTracks !== undefined){ - let selected_tracks_indexes = helpers.arrayOf('index',selected_tracks); - return this.props.removeTracks(selected_tracks_indexes); - - // No handler? We can't really do anything then, so notify user - } else { - this.props.uiActions.createNotification({content: 'Cannot delete '+(selected_tracks.length > 1 ? 'these tracks' : 'this track'), type: 'bad'}); - } - } + // No handler? We can't really do anything then, so notify user + } + this.props.uiActions.createNotification({ content: `Cannot delete ${selected_tracks.length > 1 ? 'these tracks' : 'this track'}`, type: 'bad' }); + } - /** + /** * Build the track key * This is our unique reference to a track in a particular tracklist * * @param track = Track obj * @param index = int (position of track in tracklist) * @return string - **/ - buildTrackKey(track, index){ - let key = index - key += '@@'+(track.tlid ? track.tlid : 'none') - key += '@@'+track.uri - key += '@@'+(this.props.uri ? this.props.uri : 'none') - key += '@@'+(this.props.track_context ? this.props.track_context : 'none') - return key - } + * */ + buildTrackKey(track, index) { + let key = index; + key += `@@${track.tlid ? track.tlid : 'none'}`; + key += `@@${track.uri}`; + key += `@@${this.props.uri ? this.props.uri : 'none'}`; + key += `@@${this.props.track_context ? this.props.track_context : 'none'}`; + return key; + } - /** + /** * Digest our selected tracks * * @param tracks = mixed (defaults to stored value) * @param indexex_only = boolean (do we just want an array of indexes) * @return mixed - **/ - digestTracksKeys(keys = this.props.selected_tracks, indexes_only = false){ - if (!keys){ - return false - } + * */ + digestTracksKeys(keys = this.props.selected_tracks, indexes_only = false) { + if (!keys) { + return false; + } - // Accommodate a single key - var singleton = false - if (!(keys instanceof Array)){ - singleton = true - keys = [keys] - } + // Accommodate a single key + let singleton = false; + if (!(keys instanceof Array)) { + singleton = true; + keys = [keys]; + } - // Construct a basic track object, based on our unique track key - // This is enough to perform interactions (dragging, selecting, etc) - var array = []; - for (var key of keys){ - var key_components = key.split('@@') + // Construct a basic track object, based on our unique track key + // This is enough to perform interactions (dragging, selecting, etc) + const array = []; + for (const key of keys) { + const key_components = key.split('@@'); - if (indexes_only){ - array.push(key_components[0]) + if (indexes_only) { + array.push(key_components[0]); + } else { + array.push({ + key, + index: parseInt(key_components[0]), + tlid: parseInt(key_components[1]), + uri: key_components[2], + context: key_components[3], + context_uri: key_components[4], + }); + } + } - } else { - array.push({ - key: key, - index: parseInt(key_components[0]), - tlid: parseInt(key_components[1]), - uri: key_components[2], - context: key_components[3], - context_uri: key_components[4] - }) - } - } - - if (singleton && array.length > 0){ - return array[0] - } else { - return array - } - } + if (singleton && array.length > 0) { + return array[0]; + } + return array; + } - render(){ - if (!this.props.tracks || Object.prototype.toString.call(this.props.tracks) !== '[object Array]'){ - return null; - } + render() { + if (!this.props.tracks || Object.prototype.toString.call(this.props.tracks) !== '[object Array]') { + return null; + } - var className = 'list list--tracks '+this.props.track_context - if (this.props.className){ - className += ' '+this.props.className - } + let className = `list list--tracks ${this.props.track_context}`; + if (this.props.className) { + className += ` ${this.props.className}`; + } - return ( -
    - { + return ( +
    + { this.props.tracks.map( - (track, index) => { - var track_key = this.buildTrackKey(track, index); - track.key = track_key; - return ( - this.handleClick(e, track_key)} - handleDoubleClick={e => this.handleDoubleClick(e, track_key)} - handleContextMenu={e => this.handleContextMenu(e, track_key)} - handleDrag={e => this.handleDrag(e, track_key)} - handleDrop={e => this.handleDrop(e, track_key)} - handleTap={e => this.handleTap(e, track_key)} - handleDoubleTap={e => this.handleDoubleTap(e, track_key)} - handleTouchDrag={e => this.handleTouchDrag(e, track_key)} - /> - ) - } + (track, index) => { + const track_key = this.buildTrackKey(track, index); + track.key = track_key; + return ( + this.handleClick(e, track_key)} + handleDoubleClick={(e) => this.handleDoubleClick(e, track_key)} + handleContextMenu={(e) => this.handleContextMenu(e, track_key)} + handleDrag={(e) => this.handleDrag(e, track_key)} + handleDrop={(e) => this.handleDrop(e, track_key)} + handleTap={(e) => this.handleTap(e, track_key)} + handleDoubleTap={(e) => this.handleDoubleTap(e, track_key)} + handleTouchDrag={(e) => this.handleTouchDrag(e, track_key)} + /> + ); + }, ) } -
    - ); - } +
    + ); + } } -const mapStateToProps = (state, ownProps) => { - return { - play_state: state.mopidy.play_state, - slim_mode: state.ui.slim_mode, - selected_tracks: state.ui.selected_tracks, - dragger: state.ui.dragger, - current_track: state.core.current_track, - context_menu: state.ui.context_menu - } -} +const mapStateToProps = (state, ownProps) => ({ + play_state: state.mopidy.play_state, + slim_mode: state.ui.slim_mode, + selected_tracks: state.ui.selected_tracks, + dragger: state.ui.dragger, + current_track: state.core.current_track, + context_menu: state.ui.context_menu, +}); -const mapDispatchToProps = (dispatch) => { - return { - mopidyActions: bindActionCreators(mopidyActions, dispatch), - uiActions: bindActionCreators(uiActions, dispatch) - } -} - -export default connect(mapStateToProps, mapDispatchToProps)(TrackList) +const mapDispatchToProps = (dispatch) => ({ + mopidyActions: bindActionCreators(mopidyActions, dispatch), + uiActions: bindActionCreators(uiActions, dispatch), +}); +export default connect(mapStateToProps, mapDispatchToProps)(TrackList); diff --git a/src/js/components/URILink.js b/src/js/components/URILink.js index 6d2e1435..f6075036 100755 --- a/src/js/components/URILink.js +++ b/src/js/components/URILink.js @@ -2,66 +2,65 @@ import React, { memo } from 'react'; import Link from './Link'; -export default memo((props) => { +export default memo((props) => { + let to = null; + let { uri } = props; + if (!props.unencoded) { + uri = encodeURIComponent(uri); + } - var to = null; - var uri = props.uri; - if (!props.unencoded){ - uri = encodeURIComponent(uri); - } + switch (props.type) { + case 'playlist': + to = `/playlist/${uri}`; + break; - switch (props.type){ - case 'playlist': - to = '/playlist/'+uri; - break; - - case 'artist': - to = '/artist/'+uri; - break; + case 'artist': + to = `/artist/${uri}`; + break; - case 'album': - to = '/album/'+uri; - break; + case 'album': + to = `/album/${uri}`; + break; - case 'track': - to = '/track/'+uri; - break; + case 'track': + to = `/track/${uri}`; + break; - case 'user': - to = '/user/'+uri; - break; + case 'user': + to = `/user/${uri}`; + break; - case 'browse': - to = '/library/browse/'+uri; - break; + case 'browse': + to = `/library/browse/${uri}`; + break; - case 'recommendations': - to = '/discover/recommendations/'+uri; - break; + case 'recommendations': + to = `/discover/recommendations/${uri}`; + break; - case 'search': - var exploded = uri.split('%3A'); - to = '/search/'+exploded[1]+'/'+exploded[2]; - break; + case 'search': + var exploded = uri.split('%3A'); + to = `/search/${exploded[1]}/${exploded[2]}`; + break; - default: - to = null; - } + default: + to = null; + } - if (uri){ - return ( - (props.handleContextMenu ? props.handleContextMenu(e) : null)}> - {props.children} - - ); - } else { - return ( - - {props.children} - - ); - } -}); \ No newline at end of file + if (uri) { + return ( + (props.handleContextMenu ? props.handleContextMenu(e) : null)} + > + {props.children} + + ); + } + return ( + + {props.children} + + ); +}); diff --git a/src/js/helpers.js b/src/js/helpers.js index d780835a..3262f54e 100755 --- a/src/js/helpers.js +++ b/src/js/helpers.js @@ -1,66 +1,66 @@ - -export let isTouchDevice = function(){ - return 'ontouchstart' in document.documentElement -} +export const isTouchDevice = function () { + return 'ontouchstart' in document.documentElement; +}; /** * Returns a function, that, as long as it continues to be invoked, will not * be triggered. The function will be called after it stops being called for * N milliseconds. If `immediate` is passed, trigger the function on the * leading edge, instead of the trailing. - **/ -export let debounce = function(fn, wait, immediate){ - var timeout; - return function(){ - var context = this, args = arguments; + * */ +export const debounce = function (fn, wait, immediate) { + let timeout; + return function () { + const context = this; const + args = arguments; - var later = function(){ - timeout = null; - if (!immediate){ - fn.apply(context, args); - } - }; + const later = function () { + timeout = null; + if (!immediate) { + fn.apply(context, args); + } + }; - var callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); + const callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); - if (callNow){ - fn.apply(context, args); - } - }; + if (callNow) { + fn.apply(context, args); + } + }; }; -export let throttle = function(fn, delay){ - let lastCall = 0; - return function (...args){ - const now = (new Date).getTime(); - if (now - lastCall < delay) { - return; - } - lastCall = now; - return fn(...args); - } -} +export const throttle = function (fn, delay) { + let lastCall = 0; + return function (...args) { + const now = (new Date()).getTime(); + if (now - lastCall < delay) { + return; + } + lastCall = now; + return fn(...args); + }; +}; /** * Storage handler * All localStorage tasks are handled below. This means we can detect for localStorage issues in one place - **/ + * */ -var storage = (function() { - var uid = new Date; - var storage; - var result; - try { - (storage = window.localStorage).setItem(uid, uid); - result = storage.getItem(uid) == uid; - storage.removeItem(uid); - return result && storage; - } catch (exception) {} +const storage = (function () { + const uid = new Date(); + let storage; + let result; + try { + (storage = window.localStorage).setItem(uid, uid); + result = storage.getItem(uid) == uid; + storage.removeItem(uid); + return result && storage; + } catch (exception) {} }()); /** @@ -68,21 +68,18 @@ var storage = (function() { * * @param key = string * @param default_value = mixed (optional, if localStorage key doesn't exist, return this) - **/ -export let getStorage = function(key, default_value = {}){ - if (storage){ - var value = storage.getItem(key); - if (value){ - return JSON.parse(value); - } else { - return default_value; - } - - } else { - console.warn("localStorage not available. Using default value for '"+key+"'."); - return default_value; - } -} + * */ +export const getStorage = function (key, default_value = {}) { + if (storage) { + const value = storage.getItem(key); + if (value) { + return JSON.parse(value); + } + return default_value; + } + console.warn(`localStorage not available. Using default value for '${key}'.`); + return default_value; +}; /** * Set a storage value @@ -90,30 +87,28 @@ export let getStorage = function(key, default_value = {}){ * @param key = string * @param value = object * @param replace = boolean (optional, completely replace our local value rather than merging it) - **/ -export let setStorage = function(key, value, replace = false){ - if (storage){ - var stored_value = storage.getItem(key); + * */ +export const setStorage = function (key, value, replace = false) { + if (storage) { + const stored_value = storage.getItem(key); - // We have nothing to merge with, or we want to completely replace previous value - if (!stored_value || replace){ - var new_value = value; + // We have nothing to merge with, or we want to completely replace previous value + if (!stored_value || replace) { + var new_value = value; - // Merge new value with existing - } else { - var new_value = Object.assign( - {}, - JSON.parse(stored_value), - value - ); - } - storage.setItem(key, JSON.stringify(new_value)); - return; - } else { - console.warn("localStorage not available. '"+key+"'' will not perist when you close your browser."); - return; - } -} + // Merge new value with existing + } else { + var new_value = { + + ...JSON.parse(stored_value), + ...value, + }; + } + storage.setItem(key, JSON.stringify(new_value)); + } else { + console.warn(`localStorage not available. '${key}'' will not perist when you close your browser.`); + } +}; /** @@ -122,51 +117,47 @@ export let setStorage = function(key, value, replace = false){ * * @param data String or Object * @return Object - **/ -export let toJSON = function(data){ + * */ +export const toJSON = function (data) { + // Parse it + try { + const json = JSON.parse(data); + return json; - // Parse it - try { - let json = JSON.parse(data); - return json; - - // Could not parse string - } catch (e){ - - // Check if it's JSON already - if (data.constructor === {}.constructor){ - return data; - } else { - console.error("Could not convert non-JSON", string); - } - } - return {}; -} + // Could not parse string + } catch (e) { + // Check if it's JSON already + if (data.constructor === {}.constructor) { + return data; + } + console.error('Could not convert non-JSON', string); + } + return {}; +}; /** * Set the app's favicon to a specific image. - * + * * @param filename String */ -export let setFavicon = function(filename){ - let links = document.getElementsByClassName('favicon'); - for (let link of links){ +export const setFavicon = function (filename) { + const links = document.getElementsByClassName('favicon'); + for (const link of links) { + // Construct new + const new_link = document.createElement('link'); + new_link.className = link.className; + new_link.rel = link.rel; + new_link.href = `/iris/assets/${filename}`; + if (link.type) { + new_link.type = link.type; + } - // Construct new - let new_link = document.createElement('link'); - new_link.className = link.className; - new_link.rel = link.rel; - new_link.href = "/iris/assets/"+filename; - if (link.type){ - new_link.type = link.type; - } - - // Remove the old one and add the new one - document.head.removeChild(link); - document.head.appendChild(new_link); - } -} + // Remove the old one and add the new one + document.head.removeChild(link); + document.head.appendChild(new_link); + } +}; /** @@ -175,13 +166,12 @@ export let setFavicon = function(filename){ * * @param url String * @return Boolean - **/ -export let isCached = function(url){ - var image = new Image(); - image.src = url; - return image.complete; -} - + * */ +export const isCached = function (url) { + const image = new Image(); + image.src = url; + return image.complete; +}; /** @@ -191,84 +181,83 @@ export let isCached = function(url){ * @param mopidy = obj (mopidy store object) * @param images = array * @return array - **/ -export let digestMopidyImages = function(mopidy, images){ - var digested = []; + * */ +export const digestMopidyImages = function (mopidy, images) { + const digested = []; - for (var i = 0; i < images.length; i++){ - - // Image object (ie from images.get) - if (typeof images[i] === 'object'){ - // Accommodate backends that provide URIs vs URLs - var url = images[i].url - if (!url && images[i].uri){ - url = images[i].uri - } + for (let i = 0; i < images.length; i++) { + // Image object (ie from images.get) + if (typeof images[i] === 'object') { + // Accommodate backends that provide URIs vs URLs + let { url } = images[i]; + if (!url && images[i].uri) { + url = images[i].uri; + } // Amend our URL - images[i].url = url + images[i].url = url; - // Replace local images to point directly to our Mopidy server - if (url && url.startsWith('/images/')){ - url = '//'+mopidy.host+':'+mopidy.port+url + // Replace local images to point directly to our Mopidy server + if (url && url.startsWith('/images/')) { + url = `//${mopidy.host}:${mopidy.port}${url}`; } // String-based image - } else if (typeof images[i] === 'string'){ - // Replace local images to point directly to our Mopidy server - if (images[i].startsWith('/images/')){ - images[i] = '//'+mopidy.host+':'+mopidy.port+images[i] + } else if (typeof images[i] === 'string') { + // Replace local images to point directly to our Mopidy server + if (images[i].startsWith('/images/')) { + images[i] = `//${mopidy.host}:${mopidy.port}${images[i]}`; } - } + } - digested.push(images[i]) - } + digested.push(images[i]); + } - return digested -} + return digested; +}; -export let generateGuid = function(type = 'numeric'){ - // numeric - if (type == 'numeric'){ - var date = new Date().valueOf().toString(); - var random_number = Math.floor((Math.random() * 100)).toString(); - return parseInt(date+random_number); - } else { - var format = 'xxxxxxxxxx'; - return format.replace(/[xy]/g, function(c){ - var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); - return v.toString(16); - }); - } -} +export const generateGuid = function (type = 'numeric') { + // numeric + if (type == 'numeric') { + const date = new Date().valueOf().toString(); + const random_number = Math.floor((Math.random() * 100)).toString(); + return parseInt(date + random_number); + } + const format = 'xxxxxxxxxx'; + return format.replace(/[xy]/g, (c) => { + const r = Math.random() * 16 | 0; const + v = c == 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); +}; -export let getCurrentPusherConnection = function(connections, connectionid){ - function isCurrentConnection(connection){ - return connection.connectionid == newProps.pusher.connectionid; - } - - var currentConnection = newProps.pusher.connections.find(isCurrentConnection); - if (!currentConnection ) return false; +export const getCurrentPusherConnection = function (connections, connectionid) { + function isCurrentConnection(connection) { + return connection.connectionid == newProps.pusher.connectionid; + } - return currentConnection; -} + const currentConnection = newProps.pusher.connections.find(isCurrentConnection); + if (!currentConnection) return false; + + return currentConnection; +}; /** * Get a track's icon * @param track object * @return string - **/ -export let getTrackIcon = function(current_track = false, core = false){ - if (!core) return false - if (!current_track) return false - if (typeof(current_track.uri) == 'undefined') return false - if (typeof(core.tracks[current_track.uri]) === 'undefined') return false - var track = core.tracks[current_track.uri] - if (!track.images) return false - return formatImages(track.images).small -} + * */ +export const getTrackIcon = function (current_track = false, core = false) { + if (!core) return false; + if (!current_track) return false; + if (typeof (current_track.uri) === 'undefined') return false; + if (typeof (core.tracks[current_track.uri]) === 'undefined') return false; + const track = core.tracks[current_track.uri]; + if (!track.images) return false; + return formatImages(track.images).small; +}; /** * Format image URLs into a consistent size-based object @@ -276,108 +265,104 @@ export let getTrackIcon = function(current_track = false, core = false){ * * @param $data mixed * @return Object - **/ -export let formatImages = function(data){ + * */ +export let formatImages = function (data) { + const sizes = { + formatted: true, + small: null, + medium: null, + large: null, + huge: null, + }; - var sizes = { - formatted: true, - small: null, - medium: null, - large: null, - huge: null - } + if (!data) { + return sizes; + } - if (!data){ - return sizes; - } + // An array of images has been provided + if (Array.isArray(data)) { + if (data.length <= 0) { + return sizes; + } - // An array of images has been provided - if (Array.isArray(data)){ + for (let i = 0; i < data.length; i++) { + const image = data[i]; - if (data.length <= 0){ - return sizes; - } + // Already-formatted + if (image.formatted) { + return image; - for (var i = 0; i < data.length; i++){ - let image = data[i] + // Mopidy image object + } if (image.__model__ && image.__model__ == 'Image') { + if (image.width < 400) { + sizes.small = image.url; + } else if (image.width < 800) { + sizes.medium = image.url; + } else if (image.width < 1000) { + sizes.large = image.url; + } else { + sizes.huge = image.url; + } - // Already-formatted - if (image.formatted){ - return image; + // Mopidy image string + } else if (typeof (image) === 'string') { + sizes.small = image; - // Mopidy image object - } else if (image.__model__ && image.__model__ == 'Image'){ + // spotify-styled images + } else if (image.width !== undefined) { + if (image.width < 400) { + sizes.small = image.url; + } else if (image.width < 800) { + sizes.medium = image.url; + } else if (image.width < 1000) { + sizes.large = image.url; + } else { + sizes.huge = image.url; + } - if (image.width < 400){ - sizes.small = image.url; - }else if (image.width < 800){ - sizes.medium = image.url; - }else if (image.width < 1000){ - sizes.large = image.url; - } else { - sizes.huge = image.url; - } + // lastfm-styled images + } else if (image.size !== undefined) { + switch (image.size) { + case 'mega': + case 'extralarge': + case 'large': + sizes.medium = image['#text']; + break; + case 'medium': + case 'small': + sizes.small = image['#text']; + break; + } + } + } - // Mopidy image string - } else if (typeof(image) == 'string'){ - sizes.small = image - - // spotify-styled images - } else if (image.width !== undefined){ + // An object of images has been provided + // The Genius avatar object is an example of this + } else { + if (data.small) sizes.small = data.small.url; + if (data.medium) sizes.medium = data.medium.url; + if (data.large) sizes.large = data.large.url; + if (data.huge) sizes.huge = data.huge.url; + } - if (image.width < 400){ - sizes.small = image.url; - }else if (image.width < 800){ - sizes.medium = image.url; - }else if (image.width < 1000){ - sizes.large = image.url; - } else { - sizes.huge = image.url; - } + // Inherit images where we haven't been given the appropriate size + // Ie small duplicated to tiny, large duplicated to medium, etc + if (!sizes.small) { + if (sizes.medium) sizes.small = sizes.medium; + else if (sizes.large) sizes.small = sizes.large; + else if (sizes.huge) sizes.small = sizes.huge; + else sizes.small = null; + } + if (!sizes.medium) { + if (sizes.large) sizes.medium = sizes.large; + else if (sizes.huge) sizes.medium = sizes.huge; + else sizes.medium = sizes.small; + } + if (!sizes.large) sizes.large = sizes.medium; + if (!sizes.huge) sizes.huge = sizes.large; - // lastfm-styled images - } else if (image.size !== undefined){ - switch(image.size){ - case 'mega': - case 'extralarge': - case 'large': - sizes.medium = image['#text'] - break; - case 'medium': - case 'small': - sizes.small = image['#text'] - break; - } - } - } - - // An object of images has been provided - // The Genius avatar object is an example of this - } else { - if (data.small) sizes.small = data.small.url; - if (data.medium) sizes.medium = data.medium.url; - if (data.large) sizes.large = data.large.url; - if (data.huge) sizes.huge = data.huge.url; - } - - // Inherit images where we haven't been given the appropriate size - // Ie small duplicated to tiny, large duplicated to medium, etc - if (!sizes.small){ - if (sizes.medium) sizes.small = sizes.medium - else if (sizes.large) sizes.small = sizes.large - else if (sizes.huge) sizes.small = sizes.huge - else sizes.small = null - } - if (!sizes.medium){ - if (sizes.large) sizes.medium = sizes.large - else if (sizes.huge) sizes.medium = sizes.huge - else sizes.medium = sizes.small - } - if (!sizes.large) sizes.large = sizes.medium; - if (!sizes.huge) sizes.huge = sizes.large; - - return sizes; -} + return sizes; +}; /** @@ -387,64 +372,64 @@ export let formatImages = function(data){ * * @param data obj * @return obj - **/ -export let formatSimpleObject = function(data){ - var simple_object = {} - var fields = [ - 'uri', - 'name' - ]; + * */ +export const formatSimpleObject = function (data) { + const simple_object = {}; + const fields = [ + 'uri', + 'name', + ]; - for (var field of fields){ - if (data.hasOwnProperty(field)){ - simple_object[field] = data[field]; - } - } + for (const field of fields) { + if (data.hasOwnProperty(field)) { + simple_object[field] = data[field]; + } + } - return simple_object; -} + return simple_object; +}; /** * Format multiple items * * @param tracks Array * @return Array - **/ -export let formatTracks = function(records = []){ - var formatted = []; - for (var record of records){ + * */ +export const formatTracks = function (records = []) { + const formatted = []; + for (const record of records) { formatted.push(formatTrack(record)); - } - return formatted; -} -export let formatAlbums = function(records = []){ - var formatted = []; - for (var record of records){ + } + return formatted; +}; +export const formatAlbums = function (records = []) { + const formatted = []; + for (const record of records) { formatted.push(formatAlbum(record)); - } - return formatted; -} -export let formatArtists = function(records = []){ - var formatted = []; - for (var record of records){ + } + return formatted; +}; +export const formatArtists = function (records = []) { + const formatted = []; + for (const record of records) { formatted.push(formatArtist(record)); - } - return formatted; -} -export let formatPlaylists = function(records = []){ - var formatted = []; - for (var record of records){ + } + return formatted; +}; +export const formatPlaylists = function (records = []) { + const formatted = []; + for (const record of records) { formatted.push(formatTrack(record)); - } - return formatted; -} -export let formatUsers = function(records = []){ - var formatted = []; - for (var record of records){ + } + return formatted; +}; +export const formatUsers = function (records = []) { + const formatted = []; + for (const record of records) { formatted.push(formatUser(record)); - } - return formatted; -} + } + return formatted; +}; /** @@ -452,50 +437,50 @@ export let formatUsers = function(records = []){ * * @param data obj * @return obj - **/ -export let formatAlbum = function(data){ - var album = {}; - var fields = [ - 'uri', - 'provider', - 'name', - 'type', - 'added_at', - 'release_date', - 'listeners', - 'play_count', - 'wiki', - 'wiki_publish_date', - 'popularity', - 'images', - 'artists_uris', - 'tracks_uris', - 'artists' // Array of simple records - ]; + * */ +export let formatAlbum = function (data) { + const album = {}; + const fields = [ + 'uri', + 'provider', + 'name', + 'type', + 'added_at', + 'release_date', + 'listeners', + 'play_count', + 'wiki', + 'wiki_publish_date', + 'popularity', + 'images', + 'artists_uris', + 'tracks_uris', + 'artists', // Array of simple records + ]; - // Loop fields and import from data - for (var field of fields){ - if (data.hasOwnProperty(field)){ - album[field] = data[field]; - } - } + // Loop fields and import from data + for (const field of fields) { + if (data.hasOwnProperty(field)) { + album[field] = data[field]; + } + } - if (album.images && !album.images.formatted){ - album.images = formatImages(album.images); - } + if (album.images && !album.images.formatted) { + album.images = formatImages(album.images); + } - if (data.date && !album.date){ - album.release_date = data.date; - } - if (data.album_type){ - album.type = data.album_type; - } - if (album.provider === undefined && album.uri !== undefined){ - album.provider = uriSource(album.uri); - } + if (data.date && !album.date) { + album.release_date = data.date; + } + if (data.album_type) { + album.type = data.album_type; + } + if (album.provider === undefined && album.uri !== undefined) { + album.provider = uriSource(album.uri); + } - return album; -} + return album; +}; /** @@ -503,64 +488,64 @@ export let formatAlbum = function(data){ * * @param data obj * @return obj - **/ -export let formatArtist = function(data){ - var artist = {} - var fields = [ - 'uri', - 'provider', - 'mbid', - 'name', - 'type', - 'popularity', - 'followers', - 'listeners', - 'added_at', - 'biography', - 'biography_link', - 'biography_publish_date', - 'related_artists_uris', - 'albums_uris', - 'albums_total', - 'albums_more', - 'tracks_uris', - 'tracks_total', - 'tracks_more' - ]; + * */ +export let formatArtist = function (data) { + const artist = {}; + const fields = [ + 'uri', + 'provider', + 'mbid', + 'name', + 'type', + 'popularity', + 'followers', + 'listeners', + 'added_at', + 'biography', + 'biography_link', + 'biography_publish_date', + 'related_artists_uris', + 'albums_uris', + 'albums_total', + 'albums_more', + 'tracks_uris', + 'tracks_total', + 'tracks_more', + ]; - // Loop fields and import from data - for (var field of fields){ - if (data.hasOwnProperty(field)){ - artist[field] = data[field]; - } - } + // Loop fields and import from data + for (const field of fields) { + if (data.hasOwnProperty(field)) { + artist[field] = data[field]; + } + } - if (data.images && data.images.length > 0){ - artist.images = [formatImages(data.images)]; - } + if (data.images && data.images.length > 0) { + artist.images = [formatImages(data.images)]; + } - if (data.followers && data.followers.total !== undefined){ - artist.followers = data.followers.total; - } + if (data.followers && data.followers.total !== undefined) { + artist.followers = data.followers.total; + } - if (data.bio){ - if (data.bio.content && !artist.biography){ - artist.biography = data.bio.content; - } - if (data.bio.links && data.bio.links.link && data.bio.links.link.href && !artist.biography_link){ - artist.biography_link = data.bio.links.link.href; - } - if (data.bio.published && !artist.biography_publish_date){ - artist.biography_publish_date = data.bio.published; - } - } - - if (artist.provider === undefined && artist.uri !== undefined){ - artist.provider = uriSource(artist.uri); - } + if (data.bio) { + if (data.bio.content && !artist.biography) { + artist.biography = data.bio.content; + } + if (data.bio.links && data.bio.links.link && data.bio.links.link.href && !artist.biography_link) { + artist.biography_link = data.bio.links.link.href; + } + if (data.bio.published && !artist.biography_publish_date) { + artist.biography_publish_date = data.bio.published; + } + } - return artist; -} + if (artist.provider === undefined && artist.uri !== undefined) { + artist.provider = uriSource(artist.uri); + } + + return artist; +}; /** @@ -568,68 +553,68 @@ export let formatArtist = function(data){ * * @param data obj * @return obj - **/ -export let formatPlaylist = function(data){ - var playlist = {} - var fields = [ - 'uri', - 'snapshot_id', - 'provider', - 'type', - 'collaborative', - 'public', - 'name', - 'description', - 'images', - 'popularity', - 'followers', - 'added_at', - 'last_modified_date', - 'can_edit', - 'owner', - 'user_uri', - 'tracks_uris', - 'tracks_total', - 'tracks_more' - ]; + * */ +export const formatPlaylist = function (data) { + const playlist = {}; + const fields = [ + 'uri', + 'snapshot_id', + 'provider', + 'type', + 'collaborative', + 'public', + 'name', + 'description', + 'images', + 'popularity', + 'followers', + 'added_at', + 'last_modified_date', + 'can_edit', + 'owner', + 'user_uri', + 'tracks_uris', + 'tracks_total', + 'tracks_more', + ]; - // Loop fields and import from data - for (var field of fields){ - if (data.hasOwnProperty(field)){ - playlist[field] = data[field]; - } - } + // Loop fields and import from data + for (const field of fields) { + if (data.hasOwnProperty(field)) { + playlist[field] = data[field]; + } + } - if (playlist.images && !playlist.images.formatted){ - playlist.images = formatImages(playlist.images); - } + if (playlist.images && !playlist.images.formatted) { + playlist.images = formatImages(playlist.images); + } - if (data.followers && data.followers.total !== undefined){ - playlist.followers = data.followers.total; - } + if (data.followers && data.followers.total !== undefined) { + playlist.followers = data.followers.total; + } - if (data.tracks && data.tracks.total !== undefined){ - playlist.tracks_total = data.tracks.total; - } + if (data.tracks && data.tracks.total !== undefined) { + playlist.tracks_total = data.tracks.total; + } - if (data.owner){ - playlist.owner = { - id: data.owner.id, - uri: data.owner.uri, - name: (data.owner.display_name ? data.owner.display_name : null) - } - playlist.user_uri = data.owner.uri; - } + if (data.owner) { + playlist.owner = { + id: data.owner.id, + uri: data.owner.uri, + name: (data.owner.display_name ? data.owner.display_name : null), + }; + playlist.user_uri = data.owner.uri; + } - // Spotify upgraded their playlists URI to remove user component (Sept 2018) - playlist.uri = upgradeSpotifyPlaylistUri(playlist.uri); + // Spotify upgraded their playlists URI to remove user component (Sept 2018) + playlist.uri = upgradeSpotifyPlaylistUri(playlist.uri); - if (playlist.provider === undefined && playlist.uri !== undefined){ - playlist.provider = uriSource(playlist.uri); - } + if (playlist.provider === undefined && playlist.uri !== undefined) { + playlist.provider = uriSource(playlist.uri); + } - return playlist; -} + return playlist; +}; /** @@ -637,54 +622,54 @@ export let formatPlaylist = function(data){ * * @param data obj * @return obj - **/ -export let formatUser = function(data){ - var user = {} - var fields = [ - 'id', - 'uri', - 'provider', - 'name', - 'images', - 'followers', - 'playlists_uris', - 'playlists_total', - 'playlists_more' - ]; + * */ +export let formatUser = function (data) { + const user = {}; + const fields = [ + 'id', + 'uri', + 'provider', + 'name', + 'images', + 'followers', + 'playlists_uris', + 'playlists_total', + 'playlists_more', + ]; - // Loop fields and import from data - for (var field of fields){ - if (data.hasOwnProperty(field)){ - user[field] = data[field]; - } - } + // Loop fields and import from data + for (const field of fields) { + if (data.hasOwnProperty(field)) { + user[field] = data[field]; + } + } - if (!user.images && data.image){ - user.images = formatImages(data.image); - } else if (!user.images && data.avatar){ - user.images = formatImages(data.avatar); - } else if (user.images && !user.images.formatted){ - user.images = formatImages(user.images); - } + if (!user.images && data.image) { + user.images = formatImages(data.image); + } else if (!user.images && data.avatar) { + user.images = formatImages(data.avatar); + } else if (user.images && !user.images.formatted) { + user.images = formatImages(user.images); + } - if (data.followers && data.followers.total !== undefined){ - user.followers = data.followers.total; - } - if (data.realname){ - user.name = data.realname; - } - if (data.display_name && !user.name){ - user.name = data.display_name; - } - if (data.id && !user.name){ - user.name = data.id; - } - if (user.provider === undefined && user.uri !== undefined){ - user.provider = uriSource(user.uri); - } + if (data.followers && data.followers.total !== undefined) { + user.followers = data.followers.total; + } + if (data.realname) { + user.name = data.realname; + } + if (data.display_name && !user.name) { + user.name = data.display_name; + } + if (data.id && !user.name) { + user.name = data.id; + } + if (user.provider === undefined && user.uri !== undefined) { + user.provider = uriSource(user.uri); + } - return user; -} + return user; +}; /** @@ -692,181 +677,180 @@ export let formatUser = function(data){ * * @param data obj * @return obj - **/ -export let formatTrack = function(data){ - var track = {} - var fields = [ - 'uri', - 'tlid', - 'provider', - 'name', - 'images', - 'release_date', - 'disc_number', - 'track_number', - 'duration', - 'followers', - 'popularity', - 'userloved', - 'is_explicit', - 'is_local', - 'lyrics', - 'lyrics_path', - 'lyrics_results', - 'artists', // Array of simple records - 'album' // Array of simple records - ]; + * */ +export let formatTrack = function (data) { + const track = {}; + const fields = [ + 'uri', + 'tlid', + 'provider', + 'name', + 'images', + 'release_date', + 'disc_number', + 'track_number', + 'duration', + 'followers', + 'popularity', + 'userloved', + 'is_explicit', + 'is_local', + 'lyrics', + 'lyrics_path', + 'lyrics_results', + 'artists', // Array of simple records + 'album', // Array of simple records + ]; - // Nested track object (eg in spotify playlist) - if (data && data.track && isObject(data.track)){ + // Nested track object (eg in spotify playlist) + if (data && data.track && isObject(data.track)) { + // Copy wrapper's details (if applicable) + if (data.added_by) { + data.track.added_by = data.added_by; + } + if (data.added_at) { + data.track.added_at = data.added_at; + } + if (data.tlid) { + data.track.tlid = data.tlid; + } - // Copy wrapper's details (if applicable) - if (data.added_by){ - data.track.added_by = data.added_by; - } - if (data.added_at){ - data.track.added_at = data.added_at; - } - if (data.tlid){ - data.track.tlid = data.tlid; - } + // And now flatten + data = data.track; + } - // And now flatten - data = data.track; - } + // Loop fields and import from data + for (const field of fields) { + if (data.hasOwnProperty(field)) { + track[field] = data[field]; + } + } - // Loop fields and import from data - for (var field of fields){ - if (data.hasOwnProperty(field)){ - track[field] = data[field]; - } - } + if (data.followers && data.followers.total) { + track.followers = data.followers.total; + } - if (data.followers && data.followers.total){ - track.followers = data.followers.total; - } + if (track.duration === undefined && data.duration_ms !== undefined) { + track.duration = data.duration_ms; + } else if (track.duration === undefined && data.length !== undefined) { + track.duration = data.length; + } - if (track.duration === undefined && data.duration_ms !== undefined){ - track.duration = data.duration_ms; - } else if (track.duration === undefined && data.length !== undefined){ - track.duration = data.length; - } - - if (track.track_number === undefined && data.track_no !== undefined){ + if (track.track_number === undefined && data.track_no !== undefined) { track.track_number = data.track_no; - } + } - if (track.disc_number === undefined && data.disc_no !== undefined){ + if (track.disc_number === undefined && data.disc_no !== undefined) { track.disc_number = data.disc_no; - } + } - if (track.release_date === undefined && data.date !== undefined){ + if (track.release_date === undefined && data.date !== undefined) { track.release_date = data.date; - } + } - if (track.explicit === undefined && data.explicit !== undefined){ + if (track.explicit === undefined && data.explicit !== undefined) { track.is_explicit = data.explicit; - } + } - // Copy images from albums (if applicable) - // TOOD: Identify if we stil need this... - if (data.album && data.album.images){ - if (track.images === undefined || !track.images.formatted){ + // Copy images from albums (if applicable) + // TOOD: Identify if we stil need this... + if (data.album && data.album.images) { + if (track.images === undefined || !track.images.formatted) { track.images = formatImages(data.album.images); } + } + + if (track.provider === undefined && track.uri !== undefined) { + track.provider = uriSource(track.uri); + } + + return track; +}; + +/** + * Format a snapcast client object into a universal format + * + * @param data obj + * @return obj + * */ +export const formatClient = function (data) { + const client = {}; + const fields = [ + 'id', + 'connected', + 'name', + 'host_name', + 'volume', + 'mute', + 'latency', + 'power_on_command', + 'power_off_command', + ]; + + for (const field of fields) { + if (data.hasOwnProperty(field)) { + client[field] = data[field]; + } + } + + if (data.config) { + if (client.latency === undefined && data.config.latency !== undefined) { + client.latency = data.config.latency; } - if (track.provider === undefined && track.uri !== undefined){ - track.provider = uriSource(track.uri); - } + if (!client.name && data.config.name) { + client.name = data.config.name; + } - return track; -} + if (data.config.volume) { + if (data.config.volume.percent) { + client.volume = data.config.volume.percent; + } + if (data.config.volume.muted) { + client.mute = data.config.volume.muted; + } + } + } + + if (client.name === undefined && data.host && data.host.name) { + client.name = data.host.name; + } + + return client; +}; /** * Format a snapcast client object into a universal format * * @param data obj * @return obj - **/ -export let formatClient = function(data){ - var client = {} - var fields = [ - 'id', - 'connected', - 'name', - 'host_name', - 'volume', - 'mute', - 'latency', - 'power_on_command', - 'power_off_command' - ]; + * */ +export const formatGroup = function (data) { + const group = {}; + const fields = [ + 'id', + 'name', + 'mute', + 'stream_id', + 'clients_ids', + ]; - for (var field of fields){ - if (data.hasOwnProperty(field)){ - client[field] = data[field]; - } - } + for (const field of fields) { + if (data.hasOwnProperty(field)) { + group[field] = data[field]; + } + } - if (data.config){ - if (client.latency === undefined && data.config.latency !== undefined){ - client.latency = data.config.latency; - } + if (group.name === undefined || group.name === '') { + group.name = `Group ${data.id.substring(0, 3)}`; + } - if (!client.name && data.config.name){ - client.name = data.config.name; - } + if (group.mute === undefined && data.mute !== undefined) { + group.mute = data.mute; + } - if (data.config.volume){ - if (data.config.volume.percent){ - client.volume = data.config.volume.percent; - } - if (data.config.volume.muted){ - client.mute = data.config.volume.muted; - } - } - } - - if (client.name === undefined && data.host && data.host.name){ - client.name = data.host.name; - } - - return client; -} - -/** - * Format a snapcast client object into a universal format - * - * @param data obj - * @return obj - **/ -export let formatGroup = function(data){ - var group = {} - var fields = [ - 'id', - 'name', - 'mute', - 'stream_id', - 'clients_ids' - ]; - - for (var field of fields){ - if (data.hasOwnProperty(field)){ - group[field] = data[field]; - } - } - - if (group.name === undefined || group.name === ""){ - group.name = 'Group '+data.id.substring(0,3); - } - - if (group.mute === undefined && data.mute !== undefined){ - group.mute = data.mute; - } - - return group; -} + return group; +}; /** @@ -876,287 +860,282 @@ export let formatGroup = function(data){ * @param object Obj * @param indexes Obj (the relevant core indexes) * @return object Obj - **/ -export let collate = function(obj, indexes = {}){ + * */ +export const collate = function (obj, indexes = {}) { + // First, let's reset this object + // This is important because by changing this object, we inadvertently + // change the source object (ie the indexed record), which undoes the + // efficiencies of a lean index object + obj = { ...obj }; - // First, let's reset this object - // This is important because by changing this object, we inadvertently - // change the source object (ie the indexed record), which undoes the - // efficiencies of a lean index object - obj = Object.assign({}, obj); + // Setup empty arrays for the appropriate reference objects + // This helps create a consistent object structure + if (obj.artists_uris !== undefined) obj.artists = []; + if (obj.albums_uris !== undefined) obj.albums = []; + if (obj.tracks_uris !== undefined) obj.tracks = []; + if (obj.users_uris !== undefined) obj.users = []; + if (obj.playlists_uris !== undefined) obj.playlists = []; + if (obj.related_artists_uris !== undefined) obj.related_artists = []; + if (obj.clients_ids !== undefined) obj.clients = []; - // Setup empty arrays for the appropriate reference objects - // This helps create a consistent object structure - if (obj.artists_uris !== undefined) obj.artists = []; - if (obj.albums_uris !== undefined) obj.albums = []; - if (obj.tracks_uris !== undefined) obj.tracks = []; - if (obj.users_uris !== undefined) obj.users = []; - if (obj.playlists_uris !== undefined) obj.playlists = []; - if (obj.related_artists_uris !== undefined) obj.related_artists = []; - if (obj.clients_ids !== undefined) obj.clients = []; + if (indexes.artists) { + if (obj.artists_uris) { + for (var uri of obj.artists_uris) { + if (indexes.artists[uri]) { + obj.artists.push(indexes.artists[uri]); + } + } + } + if (obj.related_artists_uris) { + for (var uri of obj.related_artists_uris) { + if (indexes.artists[uri]) { + obj.related_artists.push(indexes.artists[uri]); + } + } + } + if (obj.artist_uri) { + if (indexes.artists[obj.artist_uri]) { + obj.artist = indexes.artists[obj.artist_uri]; + } + } + } - if (indexes.artists){ - if (obj.artists_uris){ - for (var uri of obj.artists_uris){ - if (indexes.artists[uri]){ - obj.artists.push(indexes.artists[uri]); - } - } - } - if (obj.related_artists_uris){ - for (var uri of obj.related_artists_uris){ - if (indexes.artists[uri]){ - obj.related_artists.push(indexes.artists[uri]); - } - } - } - if (obj.artist_uri){ - if (indexes.artists[obj.artist_uri]){ - obj.artist = indexes.artists[obj.artist_uri]; - } - } - } + if (indexes.albums) { + if (obj.albums_uris) { + for (var uri of obj.albums_uris) { + if (indexes.albums[uri]) { + obj.albums.push(indexes.albums[uri]); + } + } + } + if (obj.album_uri) { + if (indexes.albums[obj.album_uri]) { + obj.album = indexes.albums[obj.album_uri]; + } + } + } - if (indexes.albums){ - if (obj.albums_uris){ - for (var uri of obj.albums_uris){ - if (indexes.albums[uri]){ - obj.albums.push(indexes.albums[uri]); - } - } - } - if (obj.album_uri){ - if (indexes.albums[obj.album_uri]){ - obj.album = indexes.albums[obj.album_uri]; - } - } - } + if (indexes.tracks) { + if (obj.tracks_uris) { + for (var uri of obj.tracks_uris) { + if (indexes.tracks[uri]) { + obj.tracks.push(indexes.tracks[uri]); + } + } + } + if (obj.track_uri) { + if (indexes.tracks[obj.track_uri]) { + obj.track = indexes.tracks[obj.track_uri]; + } + } + } - if (indexes.tracks){ - if (obj.tracks_uris){ - for (var uri of obj.tracks_uris){ - if (indexes.tracks[uri]){ - obj.tracks.push(indexes.tracks[uri]); - } - } - } - if (obj.track_uri){ - if (indexes.tracks[obj.track_uri]){ - obj.track = indexes.tracks[obj.track_uri]; - } - } - } + if (indexes.users) { + if (obj.users_uris) { + for (var uri of obj.users_uris) { + if (indexes.users[uri]) { + obj.users.push(indexes.users[uri]); + } + } + } + if (obj.user_uri) { + if (indexes.users[obj.user_uri]) { + obj.user = indexes.users[obj.user_uri]; + } + } + } - if (indexes.users){ - if (obj.users_uris){ - for (var uri of obj.users_uris){ - if (indexes.users[uri]){ - obj.users.push(indexes.users[uri]); - } - } - } - if (obj.user_uri){ - if (indexes.users[obj.user_uri]){ - obj.user = indexes.users[obj.user_uri]; - } - } - } - - if (indexes.playlists){ - if (obj.playlists_uris){ - for (var uri of obj.playlists_uris){ - if (indexes.playlists[uri]){ - obj.playlists.push(indexes.playlists[uri]); - } - } - } - if (obj.playlist_uri){ - if (indexes.playlists[obj.playlist_uri]){ - obj.playlist = indexes.playlists[obj.playlist_uri]; - } - } - } - - if (indexes.clients){ - if (obj.clients_ids){ - for (var id of obj.clients_ids){ - if (indexes.clients[id]){ - obj.clients.push(indexes.clients[id]); - } - } - } - } - - return obj; -} + if (indexes.playlists) { + if (obj.playlists_uris) { + for (var uri of obj.playlists_uris) { + if (indexes.playlists[uri]) { + obj.playlists.push(indexes.playlists[uri]); + } + } + } + if (obj.playlist_uri) { + if (indexes.playlists[obj.playlist_uri]) { + obj.playlist = indexes.playlists[obj.playlist_uri]; + } + } + } + if (indexes.clients) { + if (obj.clients_ids) { + for (const id of obj.clients_ids) { + if (indexes.clients[id]) { + obj.clients.push(indexes.clients[id]); + } + } + } + } + return obj; +}; /** * Figure out a URI's source namespace * @param uri = string - **/ -export let uriSource = function(uri){ - if (!uri){ - return false; - } - var exploded = uri.split(':'); - return exploded[0] -} + * */ +export let uriSource = function (uri) { + if (!uri) { + return false; + } + const exploded = uri.split(':'); + return exploded[0]; +}; /** * Identify what kind of asset a URI is (playlist, album, etc) * * @param uri = string * @return string - **/ -export let uriType = function(uri){ - if (!uri) return null; + * */ +export const uriType = function (uri) { + if (!uri) return null; - var exploded = uri.split(':') + const exploded = uri.split(':'); - if (exploded[0] == 'm3u'){ - return 'playlist' - } + if (exploded[0] == 'm3u') { + return 'playlist'; + } - if (exploded[0] == 'iris'){ - switch (exploded[1]){ + if (exploded[0] == 'iris') { + switch (exploded[1]) { case 'search': case 'discover': case 'browse': return exploded[1]; break; } - } + } - switch (exploded[1]){ + switch (exploded[1]) { case 'track': case 'artist': case 'album': case 'playlist': case 'genre': - return exploded[1] - break + return exploded[1]; + break; case 'user': - if (exploded.length > 3 && exploded[3] == 'playlist'){ - return 'playlist' + if (exploded.length > 3 && exploded[3] == 'playlist') { + return 'playlist'; } - return exploded[1] - break - } -} + return exploded[1]; + break; + } +}; -export let sourceIcon = function(uri,source = null){ - if (uri) source = uriSource(uri) - switch(source){ +export const sourceIcon = function (uri, source = null) { + if (uri) source = uriSource(uri); + switch (source) { + case 'local': + case 'm3u': + return 'folder'; - case 'local': - case 'm3u': - return 'folder' + case 'gmusic': + return 'google'; - case 'gmusic': - return 'google' + case 'podcast': + case 'podcast+file': + case 'podcast+http': + case 'podcast+https': + case 'podcast+itunes': + return 'podcast'; - case 'podcast': - case 'podcast+file': - case 'podcast+http': - case 'podcast+https': - case 'podcast+itunes': - return 'podcast' - - case 'tunein': - case 'somafm': - case 'dirble': - return 'microphone' - - default: - return source - } -} + case 'tunein': + case 'somafm': + case 'dirble': + return 'microphone'; + default: + return source; + } +}; /** * Get an element from a URI * @param element = string, the element we wish to extract * @param uri = string - **/ -export let getFromUri = function(element, uri = ""){ - var exploded = uri.split(':'); - var namespace = exploded[0] + * */ +export const getFromUri = function (element, uri = '') { + const exploded = uri.split(':'); + const namespace = exploded[0]; - switch (element){ + switch (element) { case 'mbid': - var index = exploded.indexOf('mbid') - if (index > -1 ) return exploded[index+1] - break + var index = exploded.indexOf('mbid'); + if (index > -1) return exploded[index + 1]; + break; case 'artistid': - if (exploded[1] == 'artist'){ - return exploded[2] + if (exploded[1] == 'artist') { + return exploded[2]; } - break + break; case 'albumid': - if (exploded[1] == 'album'){ - return exploded[2] + if (exploded[1] == 'album') { + return exploded[2]; } - break + break; case 'playlistid': - if (exploded[1] == 'playlist'){ - return exploded[2] - } else if (exploded[1] == 'user' && exploded[3] == 'playlist'){ - return exploded[4] + if (exploded[1] == 'playlist') { + return exploded[2]; + } if (exploded[1] == 'user' && exploded[3] == 'playlist') { + return exploded[4]; } - break + break; case 'playlistowner': - if (exploded[1] == 'user' && exploded[3] == 'playlist'){ - return exploded[2] + if (exploded[1] == 'user' && exploded[3] == 'playlist') { + return exploded[2]; } - break + break; case 'trackid': - if (exploded[1] == 'track'){ - return exploded[2] + if (exploded[1] == 'track') { + return exploded[2]; } - break + break; case 'userid': - if (exploded[1] == 'user'){ - return exploded[2] + if (exploded[1] == 'user') { + return exploded[2]; } - break + break; case 'genreid': - if (exploded[1] == 'genre'){ - return exploded[2] + if (exploded[1] == 'genre') { + return exploded[2]; } - break + break; case 'seeds': - if (exploded[1] == 'discover'){ - return exploded[2] + if (exploded[1] == 'discover') { + return exploded[2]; } - break + break; case 'searchtype': - if (exploded[0] == "search"){ - return exploded[1]; + if (exploded[0] == 'search') { + return exploded[1]; } - break + break; case 'searchterm': - if (exploded[0] == "search"){ - return exploded[2]; + if (exploded[0] == 'search') { + return exploded[2]; } - break - } - return null -} + break; + } + return null; +}; /** @@ -1165,41 +1144,39 @@ export let getFromUri = function(element, uri = ""){ * * @param $uri = String * @return String - **/ -export let buildLink = function (uri){ + * */ +export const buildLink = function (uri) { + // Start the link with the URI type + const type = uriType(uri); + let link = `/${type}/`; - // Start the link with the URI type - var type = uriType(uri); - var link = "/"+type+"/"; + // Encode the whole URI as though it's a component. This makes it URL friendly for + // all Mopidy backends (some use URIs like local:track:http://rss.com/stuff.mp3) which + // is never going to work nicely. + uri = encodeURIComponent(uri); + link += uri; - // Encode the whole URI as though it's a component. This makes it URL friendly for - // all Mopidy backends (some use URIs like local:track:http://rss.com/stuff.mp3) which - // is never going to work nicely. - uri = encodeURIComponent(uri); - link += uri; - - return link; -} + return link; +}; /** * Digest an array of objects and pull into simple array of one property - * + * * @param property = string * @param items = Array * @return Array - **/ -export let arrayOf = function(property, items){ - let array = []; - for (var item of items){ - - // Make sure the property is defined - if (item[property] !== undefined && item[property] != null){ - array.push(item[property]); - } - } - return array; -} + * */ +export const arrayOf = function (property, items) { + const array = []; + for (const item of items) { + // Make sure the property is defined + if (item[property] !== undefined && item[property] != null) { + array.push(item[property]); + } + } + return array; +}; /** @@ -1207,43 +1184,43 @@ export let arrayOf = function(property, items){ * * @param list Array the unclean array * @param key string = the unique key (id, uri, tlid, etc) - **/ -export let mergeDuplicates = function(list, key){ - var clean_list = []; - var keyed_list = {}; + * */ +export const mergeDuplicates = function (list, key) { + const clean_list = []; + const keyed_list = {}; - for(var i in list){ - var item = list[i] - if (item[key] in keyed_list){ - item = Object.assign({}, keyed_list[item[key]], item) - } - keyed_list[item[key]] = item; - } + for (var i in list) { + let item = list[i]; + if (item[key] in keyed_list) { + item = { ...keyed_list[item[key]], ...item }; + } + keyed_list[item[key]] = item; + } - for(i in keyed_list){ - clean_list.push(keyed_list[i]); - } + for (i in keyed_list) { + clean_list.push(keyed_list[i]); + } - return clean_list; -} + return clean_list; +}; /** * Remove duplicate items in a simple array * * @param list Array the unclean array - **/ -export let removeDuplicates = function(array){ - var unique = []; + * */ +export const removeDuplicates = function (array) { + const unique = []; - for (var i in array){ - if (unique.indexOf(array[i]) <= -1){ - unique.push(array[i]) - } - } + for (const i in array) { + if (unique.indexOf(array[i]) <= -1) { + unique.push(array[i]); + } + } - return unique; -} + return unique; +}; /** @@ -1254,55 +1231,52 @@ export let removeDuplicates = function(array){ * @param array = array of objects to search * @param singular = boolean (just return the first result) * @return array - **/ -export let applyFilter = function(field, value, array, singular = false){ - var results = []; + * */ +export const applyFilter = function (field, value, array, singular = false) { + const results = []; - for (var i = 0; i < array.length; i++){ - if (array[i][field] && String(array[i][field]).toLowerCase().includes(String(value).toLowerCase())){ - if (singular){ - return array[i]; - } else { - results.push(array[i]); - } - } - } + for (let i = 0; i < array.length; i++) { + if (array[i][field] && String(array[i][field]).toLowerCase().includes(String(value).toLowerCase())) { + if (singular) { + return array[i]; + } + results.push(array[i]); + } + } - return results; -} + return results; +}; /** * Convert a list of indexes to a useable range * We ignore stragglers, and only attend to the first 'bunch' of consecutive indexes - * + * * @param indexes array of int - **/ -export let createRange = function (indexes){ + * */ +export const createRange = function (indexes) { + // sort our indexes smallest to largest + function sortAsc(a, b) { + return a - b; + } + indexes.sort(sortAsc); - // sort our indexes smallest to largest - function sortAsc(a,b){ - return a - b + // iterate indexes to build the first 'bunch' + const first_bunch = []; + let previous_index = false; + for (let i = 0; i < indexes.length; i++) { + if (!previous_index || previous_index == indexes[i] - 1) { + first_bunch.push(indexes[i]); + previous_index = indexes[i]; } - indexes.sort(sortAsc); + // TODO: break when we find an integer step for better performance + } - // iterate indexes to build the first 'bunch' - var first_bunch = [] - var previous_index = false - for(var i = 0; i < indexes.length; i++){ - if (!previous_index || previous_index == indexes[i]-1){ - first_bunch.push(indexes[i]) - previous_index = indexes[i] - } - // TODO: break when we find an integer step for better performance - } - - return { + return { start: first_bunch[0], - length: first_bunch.length - } -} - + length: first_bunch.length, + }; +}; /** @@ -1312,133 +1286,131 @@ export let createRange = function (indexes){ * @param reverse = boolean * @param sort_map = array of value ordering (rather than alphabetical, numerical, etc) * @return array - **/ -export let sortItems = function (array, property, reverse = false, sort_map = null){ + * */ +export const sortItems = function (array, property, reverse = false, sort_map = null) { + if (!array || array.length <= 0) { + return []; + } - if (!array || array.length <= 0){ - return []; - } + function get_value(value) { + const split = property.split('.'); + for (const property_element of split) { + // Apply sort on a property of the first item of an array + if (property_element == 'first') { + if (Array.isArray(value) && value.length > 0) { + value = value[0]; + continue; + } else { + return null; + } - function get_value(value){ - var split = property.split('.'); - for (var property_element of split){ + // Just need the length of an array + } else if (property_element == 'length') { + if (Array.isArray(value)) { + return value.length; + } + return 0; - // Apply sort on a property of the first item of an array - if (property_element == 'first'){ - if (Array.isArray(value) && value.length > 0){ - value = value[0]; - continue; - } else { - return null; - } - // Just need the length of an array - } else if (property_element == 'length'){ - if (Array.isArray(value)){ - return value.length; - } else { - return 0; - } + // No value here + } else if (typeof (value[property_element]) === 'undefined') { + return null; + } - // No value here - } else if (typeof(value[property_element]) === 'undefined'){ - return null; - } - - // Otherwise continue looping to the end of the split property - value = value[property_element]; - } + // Otherwise continue looping to the end of the split property + value = value[property_element]; + } - return value; - } + return value; + } - function compare(a,b){ + function compare(a, b) { + let a_value = get_value(a); + let b_value = get_value(b); - var a_value = get_value(a); - var b_value = get_value(b); + // Sorting by URI as a reference for sorting by uri source (first component of URI) + if (property == 'uri') { + a_value = uriSource(a_value); + b_value = uriSource(b_value); + } - // Sorting by URI as a reference for sorting by uri source (first component of URI) - if (property == 'uri'){ - a_value = uriSource(a_value); - b_value = uriSource(b_value); - } + // Map sorting + // Use the index of the string as a sorting mechanism + if (sort_map) { + const a_index = sort_map.indexOf(`${a_value}:`); + const b_index = sort_map.indexOf(`${b_value}:`); + if (a_index < b_index) return 1; + if (a_index > b_index) return -1; - // Map sorting - // Use the index of the string as a sorting mechanism - if (sort_map){ - var a_index = sort_map.indexOf(a_value+':'); - var b_index = sort_map.indexOf(b_value+':'); - if (a_index < b_index) return 1; - if (a_index > b_index) return -1; + // Boolean sorting + } else if (typeof a_value === 'boolean' && typeof b_value === 'boolean') { + if (a_value && !b_value) return -1; + if (!a_value && b_value) return 1; + return 0; - // Boolean sorting - } else if (typeof a_value == 'boolean' && typeof b_value == 'boolean'){ - if (a_value && !b_value) return -1; - if (!a_value && b_value) return 1; - return 0 + // Numeric sorting + } else if (typeof a_value === 'number' && typeof b_value === 'number') { + if (a_value == null && b_value == null) return 0; + if (a_value == null) return -1; + if (b_value == null) return 1; + if (parseInt(a_value) > parseInt(b_value)) return 1; + if (parseInt(a_value) < parseInt(b_value)) return -1; + return 0; - // Numeric sorting - } else if (typeof a_value == 'number' && typeof b_value == 'number'){ - if (a_value == null && b_value == null) return 0; - if (a_value == null) return -1; - if (b_value == null) return 1; - if (parseInt(a_value) > parseInt(b_value)) return 1; - if (parseInt(a_value) < parseInt(b_value)) return -1; - return 0 + // Alphabetic sorting + } else { + if (a_value && !b_value) return -1; + if (!a_value && b_value) return 1; + if (!a_value && !b_value) return 0; + if (a_value.toLowerCase() > b_value.toLowerCase()) return 1; + if (a_value.toLowerCase() < b_value.toLowerCase()) return -1; + return 0; + } + } - // Alphabetic sorting - } else { - if (a_value && !b_value ) return -1; - if (!a_value && b_value ) return 1; - if (!a_value && !b_value ) return 0; - if (a_value.toLowerCase() > b_value.toLowerCase()) return 1; - if (a_value.toLowerCase() < b_value.toLowerCase()) return -1; - return 0 - } - } - - var sorted = Object.assign([], array.sort(compare)); - if (reverse){ - sorted.reverse(); - } - return sorted; -} + const sorted = Object.assign([], array.sort(compare)); + if (reverse) { + sorted.reverse(); + } + return sorted; +}; /** * Shuffle items in place * * @param Array items * @return Array - **/ -export let shuffle = function(array){ - var j, x, i; - for (i = array.length - 1; i > 0; i--) { - j = Math.floor(Math.random() * (i + 1)); - x = array[i]; - array[i] = array[j]; - array[j] = x; - } - return array; -} + * */ +export const shuffle = function (array) { + let j; let x; let + i; + for (i = array.length - 1; i > 0; i--) { + j = Math.floor(Math.random() * (i + 1)); + x = array[i]; + array[i] = array[j]; + array[j] = x; + } + return array; +}; /** * Figure out if a value is a number * @param value = mixed * @return boolean - **/ -export let isNumeric = function (value){ - return !isNaN(parseFloat(value)) && isFinite(value) -} + * */ +export const isNumeric = function (value) { + return !isNaN(parseFloat(value)) && isFinite(value); +}; -/** +/** * Figure out if a value is an object * @param value = mixed * @return boolean - **/ -export let isObject = function(value){ - return value instanceof Object && value.constructor === Object; -} + * */ +export let isObject = function (value) { + return value instanceof Object && value.constructor === Object; +}; /** @@ -1451,25 +1423,22 @@ export let isObject = function(value){ * @param load_queue = obj (passed from store) * @param key = string (the string to lookup) * @return boolean - **/ -export let isLoading = function(load_queue = [], keys = []){ - - // Loop all of our load queue items - for (var load_queue_key in load_queue){ - - // Make sure it's not a root object method - if (load_queue.hasOwnProperty(load_queue_key)){ - - // Loop all the keys we're looking for - for (var i = 0; i < keys.length; i++){ - if (load_queue[load_queue_key].includes(keys[i])){ - return true - } - } - } - } - return false -} + * */ +export const isLoading = function (load_queue = [], keys = []) { + // Loop all of our load queue items + for (const load_queue_key in load_queue) { + // Make sure it's not a root object method + if (load_queue.hasOwnProperty(load_queue_key)) { + // Loop all the keys we're looking for + for (let i = 0; i < keys.length; i++) { + if (load_queue[load_queue_key].includes(keys[i])) { + return true; + } + } + } + } + return false; +}; /** @@ -1478,12 +1447,11 @@ export let isLoading = function(load_queue = [], keys = []){ * * @param Array hosts = valid hosted domain names * @return Boolean - **/ -export let isHosted = function(hosts = ['jaedb.github.io']){ - var hostname = window.location.hostname; - return hosts.includes(hostname); -} - + * */ +export const isHosted = function (hosts = ['jaedb.github.io']) { + const { hostname } = window.location; + return hosts.includes(hostname); +}; /** @@ -1492,23 +1460,23 @@ export let isHosted = function(hosts = ['jaedb.github.io']){ * @param store = obj * @param uris = mixed (array or string) * @return array - **/ -export let getIndexedRecords = function(index, uris){ - var records = [] + * */ +export const getIndexedRecords = function (index, uris) { + const records = []; - // Wrap in array, if we've only got one URI - if (!uris instanceof Array){ - uris = [uris] - } + // Wrap in array, if we've only got one URI + if (!(uris instanceof Array)) { + uris = [uris]; + } - for (var i = 0; i < uris.length; i++){ - if (index.hasOwnProperty(uris[i])){ - records.push(index[uris[i]]) - } - } + for (let i = 0; i < uris.length; i++) { + if (index.hasOwnProperty(uris[i])) { + records.push(index[uris[i]]); + } + } - return records -} + return records; +}; /** @@ -1516,10 +1484,10 @@ export let getIndexedRecords = function(index, uris){ * * @param string String * @return String - **/ -export let titleCase = function(string){ - return string.charAt(0).toUpperCase() + string.slice(1) -} + * */ +export const titleCase = function (string) { + return string.charAt(0).toUpperCase() + string.slice(1); +}; /** @@ -1530,74 +1498,73 @@ export let titleCase = function(string){ * * @param target String (element ID, optional) * @param smooth_scroll Boolean (optional) - **/ -export let scrollTo = function(target = null, smooth_scroll = false){ - var main = document.getElementById('main'); + * */ +export const scrollTo = function (target = null, smooth_scroll = false) { + const main = document.getElementById('main'); - // Remove our smooth-scroll class - if (!smooth_scroll){ - main.classList.remove("smooth-scroll"); - } + // Remove our smooth-scroll class + if (!smooth_scroll) { + main.classList.remove('smooth-scroll'); + } - // Target is a number, so treat as pixel position - if (target && Number.isInteger(target)){ - if (typeof main.scrollTo === "function"){ - main.scrollTop = target; - } + // Target is a number, so treat as pixel position + if (target && Number.isInteger(target)) { + if (typeof main.scrollTo === 'function') { + main.scrollTop = target; + } - // Target is a string representing a DOM element by class/id - } else if (target){ + // Target is a string representing a DOM element by class/id + } else if (target) { + let element = null; - var element = null; + if (target.charAt(0) == '#') { + element = document.getElementById(target.substring(1)); + } else if (target.charAt(0) == '.') { + element = document.getElementsByClassName(target.substring(1)); + if (element.length > 0) { + element = element[0]; + } + } else { + console.error(`Invalid target type '${target}'. Must start with '#' or '.'.`); + } - if (target.charAt(0) == '#'){ - element = document.getElementById(target.substring(1)); - } else if (target.charAt(0) == '.'){ - element = document.getElementsByClassName(target.substring(1)); - if (element.length > 0){ - element = element[0]; - } - } else { - console.error("Invalid target type '"+target+"'. Must start with '#' or '.'."); - } + if (element && typeof element.scrollIntoView === 'function') { + element.scrollIntoView(); + } + } else { + main.scrollTop = 0; + } - if (element && typeof element.scrollIntoView === "function"){ - element.scrollIntoView(); - } - } else { - main.scrollTop = 0; - } - - // Now reinstate smooth scroll - if (!smooth_scroll){ - main.classList.add("smooth-scroll"); - } -} + // Now reinstate smooth scroll + if (!smooth_scroll) { + main.classList.add('smooth-scroll'); + } +}; /** - * Upgrade one or many Spotify Playlist URIs + * Upgrade one or many Spotify Playlist URIs * This is their new, simplified syntax (September 2018) but they haven't updated it everywhere * So we need to manually strip user:abc to keep things consistent * * @param uris Array|String * @return Array|String - **/ -export let upgradeSpotifyPlaylistUris = function(uris){ - var upgraded = []; + * */ +export const upgradeSpotifyPlaylistUris = function (uris) { + const upgraded = []; - for (var uri of uris){ - if (uri.includes("spotify:user:")){ - uri = uri.replace(/spotify:user:([^:]*?):/i, "spotify:"); - } - upgraded.push(uri); - } + for (let uri of uris) { + if (uri.includes('spotify:user:')) { + uri = uri.replace(/spotify:user:([^:]*?):/i, 'spotify:'); + } + upgraded.push(uri); + } - return upgraded; -} + return upgraded; +}; // As above, but for a single URI -export let upgradeSpotifyPlaylistUri = function(uri){ - return upgradeSpotifyPlaylistUris([uri])[0]; -} \ No newline at end of file +export let upgradeSpotifyPlaylistUri = function (uri) { + return upgradeSpotifyPlaylistUris([uri])[0]; +}; diff --git a/src/js/index.js b/src/js/index.js index e971bc5b..26e0d2ea 100755 --- a/src/js/index.js +++ b/src/js/index.js @@ -1,8 +1,8 @@ -import React from 'react';; -import ReactDOM from 'react-dom';; +import React from 'react'; +import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; -import { BrowserRouter, Route, IndexRoute } from "react-router-dom"; +import { BrowserRouter, Route, IndexRoute } from 'react-router-dom'; import store from './store/index'; import App from './App'; @@ -10,10 +10,10 @@ import App from './App'; require('../scss/app.scss'); ReactDOM.render( - - - - - , - document.getElementById('app') -); \ No newline at end of file + + + + + , + document.getElementById('app'), +); diff --git a/src/js/services/core/actions.js b/src/js/services/core/actions.js index 0f548912..7634e516 100755 --- a/src/js/services/core/actions.js +++ b/src/js/services/core/actions.js @@ -1,101 +1,102 @@ -import * as helpers from '../../helpers' -var spotifyActions = require('../../services/spotify/actions') -var mopidyActions = require('../../services/mopidy/actions') +import * as helpers from '../../helpers'; -export function getBroadcasts(){ - return (dispatch, getState) => { - var config = { - method: 'GET', - timeout: 15000 - } +const spotifyActions = require('../../services/spotify/actions'); +const mopidyActions = require('../../services/mopidy/actions'); - // Fetch the "iris_broadcasts.json" file from Gist (or "_test" for test mode) - if (getState().ui.test_mode){ - config.url = 'https://gist.githubusercontent.com/jaedb/cb3a5ee6909632abb2e0fe66d4c8c311/raw' - } else { - config.url = 'https://gist.githubusercontent.com/jaedb/b677dccf80daf3ccb2ef12e96e495677/raw' - } +export function getBroadcasts() { + return (dispatch, getState) => { + const config = { + method: 'GET', + timeout: 15000, + }; - $.ajax(config).then( - response => { - dispatch({ - type: 'BROADCASTS_LOADED', - broadcasts: JSON.parse(response) - }) - }, - (xhr, status, error) => { - dispatch( - handleException( - 'Could not fetch broadcasts from GitHub', - { - config: config, - xhr: xhr, - status: status, - error: error - }, - null, - false - ) - ); - } - ) + // Fetch the "iris_broadcasts.json" file from Gist (or "_test" for test mode) + if (getState().ui.test_mode) { + config.url = 'https://gist.githubusercontent.com/jaedb/cb3a5ee6909632abb2e0fe66d4c8c311/raw'; + } else { + config.url = 'https://gist.githubusercontent.com/jaedb/b677dccf80daf3ccb2ef12e96e495677/raw'; } + + $.ajax(config).then( + (response) => { + dispatch({ + type: 'BROADCASTS_LOADED', + broadcasts: JSON.parse(response), + }); + }, + (xhr, status, error) => { + dispatch( + handleException( + 'Could not fetch broadcasts from GitHub', + { + config, + xhr, + status, + error, + }, + null, + false, + ), + ); + }, + ); + }; } -export function startSearch(search_type, query, only_mopidy = false){ - return { - type: 'SEARCH_STARTED', - search_type: search_type, - query: query, - only_mopidy: only_mopidy - } +export function startSearch(search_type, query, only_mopidy = false) { + return { + type: 'SEARCH_STARTED', + search_type, + query, + only_mopidy, + }; } -export function handleException(message, data = {}, description = null, show_notification = true){ - if (!message && data.message){ - message = data.message; - } else if (!message && data.error.message){ - message = data.error.message; - } - if (!description && data.description){ - description = data.description; - } else if (!description && data.error && data.error.description){ - description = data.error.description; - } - return { - type: 'HANDLE_EXCEPTION', - message: message, - description: description, - data: data, - show_notification: show_notification - } +export function handleException(message, data = {}, description = null, show_notification = true) { + if (!message && data.message) { + message = data.message; + } else if (!message && data.error.message) { + message = data.error.message; + } + if (!description && data.description) { + description = data.description; + } else if (!description && data.error && data.error.description) { + description = data.error.description; + } + return { + type: 'HANDLE_EXCEPTION', + message, + description, + data, + show_notification, + }; } -export function debugResponse(response){ - return { - type: 'DEBUG', - response: response - } +export function debugResponse(response) { + return { + type: 'DEBUG', + response, + }; } -export function set(data){ - return { - type: 'CORE_SET', - data: data - } +export function set(data) { + return { + type: 'CORE_SET', + data, + }; } -export function clearCurrentTrack(){ - return { - type: 'CLEAR_CURRENT_TRACK' - } +export function clearCurrentTrack() { + return { + type: 'CLEAR_CURRENT_TRACK', + }; } -export function cachebustHttpStream(){ - return { - type: 'CACHEBUST_HTTP_STREAM' - } +export function cachebustHttpStream() { + return { + type: 'CACHEBUST_HTTP_STREAM', + }; } /** @@ -104,54 +105,54 @@ export function cachebustHttpStream(){ * Calling this through the common core enables us to detect whether we've already * got the record in the state or persistent storage. Failing that, we pass off to the * relevant service to load the record - all from one neat package. - **/ + * */ -export function loadTrack(uri, force_reload = false){ - return { - type: 'LOAD_TRACK', - uri: uri, - force_reload: force_reload - } +export function loadTrack(uri, force_reload = false) { + return { + type: 'LOAD_TRACK', + uri, + force_reload, + }; } -export function loadAlbum(uri, force_reload = false){ - return { - type: 'LOAD_ALBUM', - uri: uri, - force_reload: force_reload - } +export function loadAlbum(uri, force_reload = false) { + return { + type: 'LOAD_ALBUM', + uri, + force_reload, + }; } -export function loadArtist(uri, force_reload = false){ - return { - type: 'LOAD_ARTIST', - uri: uri, - force_reload: force_reload - } +export function loadArtist(uri, force_reload = false) { + return { + type: 'LOAD_ARTIST', + uri, + force_reload, + }; } -export function loadPlaylist(uri, force_reload = false){ - return { - type: 'LOAD_PLAYLIST', - uri: uri, - force_reload: force_reload - } +export function loadPlaylist(uri, force_reload = false) { + return { + type: 'LOAD_PLAYLIST', + uri, + force_reload, + }; } -export function loadUser(uri, force_reload = false){ - return { - type: 'LOAD_USER', - uri: uri, - force_reload: force_reload - } +export function loadUser(uri, force_reload = false) { + return { + type: 'LOAD_USER', + uri, + force_reload, + }; } -export function loadUserPlaylists(uri, force_reload = false){ - return { - type: 'LOAD_USER_PLAYLISTS', - uri: uri, - force_reload: force_reload - } +export function loadUserPlaylists(uri, force_reload = false) { + return { + type: 'LOAD_USER_PLAYLISTS', + uri, + force_reload, + }; } @@ -159,225 +160,218 @@ export function loadUserPlaylists(uri, force_reload = false){ * Record loaders * * We've got a loaded record, now we just need to plug it in to our state and stores. - **/ + * */ -export function trackLoaded(track){ - return tracksLoaded([track]); +export function trackLoaded(track) { + return tracksLoaded([track]); } -export function tracksLoaded(tracks){ - return { - type: 'TRACKS_LOADED', - tracks: tracks - } +export function tracksLoaded(tracks) { + return { + type: 'TRACKS_LOADED', + tracks, + }; } -export function artistLoaded(artist){ - return artistsLoaded([artist]); +export function artistLoaded(artist) { + return artistsLoaded([artist]); } -export function artistsLoaded(artists){ - return { - type: 'ARTISTS_LOADED', - artists: artists - } +export function artistsLoaded(artists) { + return { + type: 'ARTISTS_LOADED', + artists, + }; } -export function albumLoaded(album){ - return albumsLoaded([album]); +export function albumLoaded(album) { + return albumsLoaded([album]); } -export function albumsLoaded(albums){ - return { - type: 'ALBUMS_LOADED', - albums: albums - } +export function albumsLoaded(albums) { + return { + type: 'ALBUMS_LOADED', + albums, + }; } -export function playlistLoaded(playlist){ - return playlistsLoaded([playlist]); +export function playlistLoaded(playlist) { + return playlistsLoaded([playlist]); } -export function playlistsLoaded(playlists){ - return { - type: 'PLAYLISTS_LOADED', - playlists: playlists - } +export function playlistsLoaded(playlists) { + return { + type: 'PLAYLISTS_LOADED', + playlists, + }; } -export function userLoaded(user){ - return usersLoaded([user]); +export function userLoaded(user) { + return usersLoaded([user]); } -export function usersLoaded(users){ - return { - type: 'USERS_LOADED', - users: users - } +export function usersLoaded(users) { + return { + type: 'USERS_LOADED', + users, + }; } -export function userPlaylistsLoaded(uri, playlists, more = null, total = null){ - return { - type: 'USER_PLAYLISTS_LOADED', - uri: uri, - playlists: playlists, - more: more, - total: total - } +export function userPlaylistsLoaded(uri, playlists, more = null, total = null) { + return { + type: 'USER_PLAYLISTS_LOADED', + uri, + playlists, + more, + total, + }; } -export function loadedMore(parent_type, parent_key, records_type, records_data){ - return { - type: 'LOADED_MORE', - parent_type: parent_type, - parent_key: parent_key, - records_type: records_type, - records_data: records_data - } +export function loadedMore(parent_type, parent_key, records_type, records_data) { + return { + type: 'LOADED_MORE', + parent_type, + parent_key, + records_type, + records_data, + }; } -export function removeFromIndex(index_name, key, new_key = null){ - return { - type: 'REMOVE_FROM_INDEX', - index_name: index_name, - key: key, - new_key: new_key - } +export function removeFromIndex(index_name, key, new_key = null) { + return { + type: 'REMOVE_FROM_INDEX', + index_name, + key, + new_key, + }; } - /** * Playlist manipulation - **/ + * */ -export function reorderPlaylistTracks(uri, indexes, insert_before, snapshot_id = false){ - var range = helpers.createRange(indexes); - switch(helpers.uriSource(uri)){ +export function reorderPlaylistTracks(uri, indexes, insert_before, snapshot_id = false) { + const range = helpers.createRange(indexes); + switch (helpers.uriSource(uri)) { + case 'spotify': + return { + type: 'SPOTIFY_REORDER_PLAYLIST_TRACKS', + key: uri, + range_start: range.start, + range_length: range.length, + insert_before, + snapshot_id, + }; - case 'spotify': - return { - type: 'SPOTIFY_REORDER_PLAYLIST_TRACKS', - key: uri, - range_start: range.start, - range_length: range.length, - insert_before: insert_before, - snapshot_id: snapshot_id - } - - case 'm3u': - return { - type: 'MOPIDY_REORDER_PLAYLIST_TRACKS', - key: uri, - range_start: range.start, - range_length: range.length, - insert_before: insert_before - } - } + case 'm3u': + return { + type: 'MOPIDY_REORDER_PLAYLIST_TRACKS', + key: uri, + range_start: range.start, + range_length: range.length, + insert_before, + }; + } } -export function savePlaylist(uri, name, description = '', is_public = false, is_collaborative = false, image = null){ - switch (helpers.uriSource(uri)){ +export function savePlaylist(uri, name, description = '', is_public = false, is_collaborative = false, image = null) { + switch (helpers.uriSource(uri)) { + case 'spotify': + return { + type: 'SPOTIFY_SAVE_PLAYLIST', + key: uri, + name, + description: (description == '' ? null : description), + image, + is_public, + is_collaborative, + }; - case 'spotify': - return { - type: 'SPOTIFY_SAVE_PLAYLIST', - key: uri, - name: name, - description: (description == '' ? null : description), - image: image, - is_public: is_public, - is_collaborative: is_collaborative - } - - case 'm3u': - return { - type: 'MOPIDY_SAVE_PLAYLIST', - key: uri, - name: name - } - } - return false + case 'm3u': + return { + type: 'MOPIDY_SAVE_PLAYLIST', + key: uri, + name, + }; + } + return false; } -export function createPlaylist(scheme, name, description = '', is_public = false, is_collaborative = false){ - switch (scheme){ +export function createPlaylist(scheme, name, description = '', is_public = false, is_collaborative = false) { + switch (scheme) { + case 'spotify': + if (description == '') { + description = null; + } + return spotifyActions.createPlaylist(name, description, is_public, is_collaborative); - case 'spotify': - if (description == ''){ - description = null - } - return spotifyActions.createPlaylist(name, description, is_public, is_collaborative ) - - default: - return mopidyActions.createPlaylist(name, scheme) - } - return false + default: + return mopidyActions.createPlaylist(name, scheme); + } + return false; } -export function deletePlaylist(uri){ - switch (helpers.uriSource(uri)){ +export function deletePlaylist(uri) { + switch (helpers.uriSource(uri)) { + case 'spotify': + return spotifyActions.following(uri, 'DELETE'); - case 'spotify': - return spotifyActions.following(uri, 'DELETE') - - default: - return mopidyActions.deletePlaylist(uri) - } - return false + default: + return mopidyActions.deletePlaylist(uri); + } + return false; } -export function removeTracksFromPlaylist(uri, tracks_indexes){ - switch(helpers.uriSource(uri )){ +export function removeTracksFromPlaylist(uri, tracks_indexes) { + switch (helpers.uriSource(uri)) { + case 'spotify': + return { + type: 'SPOTIFY_REMOVE_PLAYLIST_TRACKS', + key: uri, + tracks_indexes, + }; - case 'spotify': - return { - type: 'SPOTIFY_REMOVE_PLAYLIST_TRACKS', - key: uri, - tracks_indexes: tracks_indexes - } - - case 'm3u': - return { - type: 'MOPIDY_REMOVE_PLAYLIST_TRACKS', - key: uri, - tracks_indexes: tracks_indexes - } - } + case 'm3u': + return { + type: 'MOPIDY_REMOVE_PLAYLIST_TRACKS', + key: uri, + tracks_indexes, + }; + } } -export function addTracksToPlaylist(uri, tracks_uris){ - switch(helpers.uriSource(uri )){ +export function addTracksToPlaylist(uri, tracks_uris) { + switch (helpers.uriSource(uri)) { + case 'spotify': + return { + type: 'SPOTIFY_ADD_PLAYLIST_TRACKS', + key: uri, + tracks_uris, + }; - case 'spotify': - return { - type: 'SPOTIFY_ADD_PLAYLIST_TRACKS', - key: uri, - tracks_uris: tracks_uris - } - - case 'm3u': - return { - type: 'MOPIDY_ADD_PLAYLIST_TRACKS', - key: uri, - tracks_uris: tracks_uris - } - } + case 'm3u': + return { + type: 'MOPIDY_ADD_PLAYLIST_TRACKS', + key: uri, + tracks_uris, + }; + } } /** * Asset libraries - **/ + * */ -export function getLibraryPlaylists(){ - return { - type: 'GET_LIBRARY_PLAYLISTS' - } +export function getLibraryPlaylists() { + return { + type: 'GET_LIBRARY_PLAYLISTS', + }; } -export function getLibraryAlbums(){ - return { - type: 'GET_LIBRARY_ALBUMS' - } +export function getLibraryAlbums() { + return { + type: 'GET_LIBRARY_ALBUMS', + }; } -export function getLibraryArtists(){ - return { - type: 'GET_LIBRARY_ARTISTS' - } +export function getLibraryArtists() { + return { + type: 'GET_LIBRARY_ARTISTS', + }; } diff --git a/src/js/services/core/middleware.js b/src/js/services/core/middleware.js index 3132671f..d91dca13 100755 --- a/src/js/services/core/middleware.js +++ b/src/js/services/core/middleware.js @@ -1,813 +1,803 @@ -import ReactGA from 'react-ga' +import ReactGA from 'react-ga'; -var coreActions = require('./actions.js'); -var uiActions = require('../ui/actions.js'); -var pusherActions = require('../pusher/actions.js'); -var mopidyActions = require('../mopidy/actions.js'); -var spotifyActions = require('../spotify/actions.js'); -var lastfmActions = require('../lastfm/actions.js'); -var helpers = require('../../helpers.js'); +const coreActions = require('./actions.js'); +const uiActions = require('../ui/actions.js'); +const pusherActions = require('../pusher/actions.js'); +const mopidyActions = require('../mopidy/actions.js'); +const spotifyActions = require('../spotify/actions.js'); +const lastfmActions = require('../lastfm/actions.js'); +const helpers = require('../../helpers.js'); -const CoreMiddleware = (function(){ - - /** +const CoreMiddleware = (function () { + /** * The actual middleware inteceptor - **/ - return store => next => action => { - var core = store.getState().core; + * */ + return (store) => (next) => (action) => { + const { core } = store.getState(); - switch(action.type){ + switch (action.type) { + case 'HANDLE_EXCEPTION': - case 'HANDLE_EXCEPTION': + // Construct meaningful message and description + var { message } = action; + if (action.description) { + var { description } = action; + } else if (action.data.xhr && action.data.xhr.responseText) { + const xhr_response = JSON.parse(action.data.xhr.responseText); + if (xhr_response.error && xhr_response.error.message) { + var description = xhr_response.error.message; + } + } else if (action.data.xhr) { + var description = `${action.data.xhr.status} ${action.data.xhr.statusText}`; + } else { + var description = null; + } - // Construct meaningful message and description - var message = action.message; - if (action.description){ - var description = action.description; - } else if (action.data.xhr && action.data.xhr.responseText){ - var xhr_response = JSON.parse(action.data.xhr.responseText); - if (xhr_response.error && xhr_response.error.message){ - var description = xhr_response.error.message; - } - } else if (action.data.xhr){ - var description = action.data.xhr.status+' '+action.data.xhr.statusText; - } else { - var description = null; - } + // Prepare a summary dump of our state + var state = store.getState(); + var exported_state = { + core: { ...state.core }, + ui: { ...state.ui }, + spotify: { ...state.spotify }, + mopidy: { ...state.mopidy }, + pusher: { ...state.pusher }, + }; - // Prepare a summary dump of our state - var state = store.getState(); - var exported_state = { - core: Object.assign({},state.core), - ui: Object.assign({},state.ui), - spotify: Object.assign({},state.spotify), - mopidy: Object.assign({},state.mopidy), - pusher: Object.assign({},state.pusher) - } + // Strip out non-essential store info + delete exported_state.core.albums; + delete exported_state.core.artists; + delete exported_state.core.playlists; + delete exported_state.core.users; + delete exported_state.core.queue_metadata; + delete exported_state.core.current_tracklist; + delete exported_state.spotify.library_albums; + delete exported_state.spotify.library_artists; + delete exported_state.spotify.library_playlists; + delete exported_state.spotify.autocomplete_results; + delete exported_state.mopidy.library_albums; + delete exported_state.mopidy.library_artists; + delete exported_state.mopidy.library_playlists; - // Strip out non-essential store info - delete exported_state.core.albums - delete exported_state.core.artists - delete exported_state.core.playlists - delete exported_state.core.users - delete exported_state.core.queue_metadata - delete exported_state.core.current_tracklist - delete exported_state.spotify.library_albums - delete exported_state.spotify.library_artists - delete exported_state.spotify.library_playlists - delete exported_state.spotify.autocomplete_results - delete exported_state.mopidy.library_albums - delete exported_state.mopidy.library_artists - delete exported_state.mopidy.library_playlists + var data = { - var data = Object.assign( - {}, - action.data, - { - message: message, - description: description, - state: exported_state - } - ); + ...action.data, + message, + description, + state: exported_state, + }; - // Log with Analytics - if (store.getState().ui.allow_reporting){ + // Log with Analytics + if (store.getState().ui.allow_reporting) { ReactGA.event({ - category: "Error", + category: 'Error', action: message, label: description, - nonInteraction: true + nonInteraction: true, }); } - if (action.show_notification){ - store.dispatch(uiActions.createNotification({content: message, type: 'bad', description: description})); + if (action.show_notification) { + store.dispatch(uiActions.createNotification({ content: message, type: 'bad', description })); } - console.error(message, description, data); - - break; + console.error(message, description, data); - case 'PLAY_PLAYLIST': - if (store.getState().ui.allow_reporting){ + break; + + case 'PLAY_PLAYLIST': + if (store.getState().ui.allow_reporting) { ReactGA.event({ category: 'Playlist', action: 'Play', label: action.uri }); } - next(action) - break + next(action); + break; - case 'SAVE_PLAYLIST': - if (store.getState().ui.allow_reporting){ + case 'SAVE_PLAYLIST': + if (store.getState().ui.allow_reporting) { ReactGA.event({ category: 'Playlist', action: 'Save', label: action.key }); } - next(action) - break + next(action); + break; - case 'CREATE_PLAYLIST': - if (store.getState().ui.allow_reporting){ + case 'CREATE_PLAYLIST': + if (store.getState().ui.allow_reporting) { ReactGA.event({ category: 'Playlist', action: 'Create', label: +action.name }); } - next(action) - break + next(action); + break; - case 'REORDER_PLAYLIST_TRACKS': - if (store.getState().ui.allow_reporting){ + case 'REORDER_PLAYLIST_TRACKS': + if (store.getState().ui.allow_reporting) { ReactGA.event({ category: 'Playlist', action: 'Reorder tracks', label: action.key }); } - next(action) - break + next(action); + break; - case 'ADD_PLAYLIST_TRACKS': - if (store.getState().ui.allow_reporting){ + case 'ADD_PLAYLIST_TRACKS': + if (store.getState().ui.allow_reporting) { ReactGA.event({ category: 'Playlist', action: 'Add tracks', label: action.playlist_uri }); } - next(action) - break + next(action); + break; - case 'REMOVE_PLAYLIST_TRACKS': - if (store.getState().ui.allow_reporting){ + case 'REMOVE_PLAYLIST_TRACKS': + if (store.getState().ui.allow_reporting) { ReactGA.event({ category: 'Playlist', action: 'Remove tracks', label: action.playlist_uri }); } - next(action) - break + next(action); + break; - case 'DELETE_PLAYLIST': - if (store.getState().ui.allow_reporting){ + case 'DELETE_PLAYLIST': + if (store.getState().ui.allow_reporting) { ReactGA.event({ category: 'Playlist', action: 'Delete', label: action.uri }); } - next(action) - break + next(action); + break; - case 'SEARCH_STARTED': - if (store.getState().ui.allow_reporting){ - ReactGA.event({ category: 'Search', action: 'Started', label: action.type+': '+action.query }); + case 'SEARCH_STARTED': + if (store.getState().ui.allow_reporting) { + ReactGA.event({ category: 'Search', action: 'Started', label: `${action.type}: ${action.query}` }); } - next(action) + next(action); - var state = store.getState() - if (state.ui.search_uri_schemes){ - var uri_schemes = state.ui.search_uri_schemes - } else { - var uri_schemes = state.mopidy.uri_schemes - } + var state = store.getState(); + if (state.ui.search_uri_schemes) { + var uri_schemes = state.ui.search_uri_schemes; + } else { + var { uri_schemes } = state.mopidy; + } - // backends that can handle more than just track results - // make sure they are available and respect our settings - var available_full_uri_schemes = ['local:','file:','gmusic:'] - var full_uri_schemes = [] - for (var i = 0; i < available_full_uri_schemes.length; i++){ - var index = uri_schemes.indexOf(available_full_uri_schemes[i]) - if (index > -1){ - full_uri_schemes.push(available_full_uri_schemes[i]) - } - } + // backends that can handle more than just track results + // make sure they are available and respect our settings + var available_full_uri_schemes = ['local:', 'file:', 'gmusic:']; + var full_uri_schemes = []; + for (var i = 0; i < available_full_uri_schemes.length; i++) { + const index = uri_schemes.indexOf(available_full_uri_schemes[i]); + if (index > -1) { + full_uri_schemes.push(available_full_uri_schemes[i]); + } + } - // initiate spotify searching - if (!action.only_mopidy){ - if (!state.ui.search_settings || state.ui.search_settings.spotify){ - store.dispatch(spotifyActions.getSearchResults(action.query)) - } - } + // initiate spotify searching + if (!action.only_mopidy) { + if (!state.ui.search_settings || state.ui.search_settings.spotify) { + store.dispatch(spotifyActions.getSearchResults(action.query)); + } + } - // backend searching (mopidy) - if (state.mopidy.connected){ - store.dispatch(mopidyActions.getSearchResults(action.search_type, action.query, 100, full_uri_schemes)) - } + // backend searching (mopidy) + if (state.mopidy.connected) { + store.dispatch(mopidyActions.getSearchResults(action.search_type, action.query, 100, full_uri_schemes)); + } - break + break; - // Get assets from all of our providers - case 'GET_LIBRARY_PLAYLISTS': - if (store.getState().spotify.connected){ - store.dispatch(spotifyActions.getLibraryPlaylists()) - } - if (store.getState().mopidy.connected){ - store.dispatch(mopidyActions.getLibraryPlaylists()) - } - next(action) - break + // Get assets from all of our providers + case 'GET_LIBRARY_PLAYLISTS': + if (store.getState().spotify.connected) { + store.dispatch(spotifyActions.getLibraryPlaylists()); + } + if (store.getState().mopidy.connected) { + store.dispatch(mopidyActions.getLibraryPlaylists()); + } + next(action); + break; - // Get assets from all of our providers - case 'GET_LIBRARY_ALBUMS': - if (store.getState().spotify.connected){ - store.dispatch(spotifyActions.getLibraryAlbums()) - } - if (store.getState().mopidy.connected){ - store.dispatch(mopidyActions.getLibraryAlbums()) - } - next(action) - break + // Get assets from all of our providers + case 'GET_LIBRARY_ALBUMS': + if (store.getState().spotify.connected) { + store.dispatch(spotifyActions.getLibraryAlbums()); + } + if (store.getState().mopidy.connected) { + store.dispatch(mopidyActions.getLibraryAlbums()); + } + next(action); + break; - // Get assets from all of our providers - case 'GET_LIBRARY_ARTISTS': - if (store.getState().spotify.connected){ - store.dispatch(spotifyActions.getLibraryArtists()) - } - if (store.getState().mopidy.connected){ - store.dispatch(mopidyActions.getLibraryArtists()) - } - next(action) - break + // Get assets from all of our providers + case 'GET_LIBRARY_ARTISTS': + if (store.getState().spotify.connected) { + store.dispatch(spotifyActions.getLibraryArtists()); + } + if (store.getState().mopidy.connected) { + store.dispatch(mopidyActions.getLibraryArtists()); + } + next(action); + break; - case 'RESTART': - location.reload(); - break; + case 'RESTART': + location.reload(); + break; - /** + /** * Playlist manipulation - **/ + * */ - case 'PLAYLIST_TRACKS': - var tracks = helpers.formatTracks(action.tracks); - action.tracks_uris = helpers.arrayOf('uri', tracks); + case 'PLAYLIST_TRACKS': + var tracks = helpers.formatTracks(action.tracks); + action.tracks_uris = helpers.arrayOf('uri', tracks); - store.dispatch({ - type: 'TRACKS_LOADED', - tracks: tracks - }); + store.dispatch({ + type: 'TRACKS_LOADED', + tracks, + }); - next(action); - break; + next(action); + break; - case 'PLAYLIST_TRACKS_ADDED': - store.dispatch(uiActions.createNotification({type: 'info', content: 'Added '+action.tracks_uris.length+' tracks to playlist'})) - switch(helpers.uriSource(action.key)){ + case 'PLAYLIST_TRACKS_ADDED': + store.dispatch(uiActions.createNotification({ type: 'info', content: `Added ${action.tracks_uris.length} tracks to playlist` })); + switch (helpers.uriSource(action.key)) { + case 'spotify': + store.dispatch(spotifyActions.getPlaylist(action.key)); + break; - case 'spotify': - store.dispatch(spotifyActions.getPlaylist(action.key)); - break + case 'm3u': + if (store.getState().mopidy.connected) store.dispatch(mopidyActions.getPlaylist(action.key)); + break; + } + next(action); + break; - case 'm3u': - if (store.getState().mopidy.connected ) store.dispatch(mopidyActions.getPlaylist(action.key)); - break - } - next(action) - break + case 'PLAYLIST_TRACKS_REORDERED': + var playlists = { ...core.playlists }; + var playlist = { ...playlists[action.key] }; + var tracks_uris = Object.assign([], playlist.tracks_uris); - case 'PLAYLIST_TRACKS_REORDERED': - var playlists = Object.assign({}, core.playlists); - var playlist = Object.assign({}, playlists[action.key]); - var tracks_uris = Object.assign([], playlist.tracks_uris); + // handle insert_before offset if we're moving BENEATH where we're slicing tracks + var { insert_before } = action; + if (insert_before > action.range_start) { + insert_before -= action.range_length; + } - // handle insert_before offset if we're moving BENEATH where we're slicing tracks - var insert_before = action.insert_before - if (insert_before > action.range_start){ - insert_before = insert_before - action.range_length; - } + // cut our moved tracks into a new array + var tracks_to_move = tracks_uris.splice(action.range_start, action.range_length); + tracks_to_move.reverse(); - // cut our moved tracks into a new array - var tracks_to_move = tracks_uris.splice(action.range_start, action.range_length) - tracks_to_move.reverse() + for (i = 0; i < tracks_to_move.length; i++) { + tracks_uris.splice(insert_before, 0, tracks_to_move[i]); + } - for (i = 0; i < tracks_to_move.length; i++){ - tracks_uris.splice(insert_before, 0, tracks_to_move[i]) - } + var snapshot_id = null; + if (action.snapshot_id) { + snapshot_id = action.snapshot_id; + } - var snapshot_id = null; - if (action.snapshot_id){ - snapshot_id = action.snapshot_id; - } + // Update our playlist + playlist.tracks_uris = tracks_uris; + playlist.snapshot_id = snapshot_id; - // Update our playlist - playlist.tracks_uris = tracks_uris; - playlist.snapshot_id = snapshot_id; + // Trigger normal playlist updating + store.dispatch({ + type: 'PLAYLISTS_LOADED', + playlists: [playlist], + }); + break; - // Trigger normal playlist updating - store.dispatch({ - type: 'PLAYLISTS_LOADED', - playlists: [playlist] - }); - break; + case 'PLAYLIST_TRACKS_REMOVED': + var playlists = { ...core.playlists }; + var playlist = { ...playlists[action.key] }; + var tracks_uris = Object.assign([], playlist.tracks_uris); - case 'PLAYLIST_TRACKS_REMOVED': - var playlists = Object.assign({}, core.playlists); - var playlist = Object.assign({}, playlists[action.key]); - var tracks_uris = Object.assign([], playlist.tracks_uris); + var indexes = action.tracks_indexes.reverse(); + for (var i = 0; i < indexes.length; i++) { + tracks_uris.splice(indexes[i], 1); + } - var indexes = action.tracks_indexes.reverse() - for(var i = 0; i < indexes.length; i++){ - tracks_uris.splice(indexes[i], 1); - } + var snapshot_id = null; + if (action.snapshot_id) { + snapshot_id = action.snapshot_id; + } - var snapshot_id = null; - if (action.snapshot_id){ - snapshot_id = action.snapshot_id; - } + // Update our playlist + playlist.tracks_uris = tracks_uris; + playlist.snapshot_id = snapshot_id; - // Update our playlist - playlist.tracks_uris = tracks_uris; - playlist.snapshot_id = snapshot_id; - - // Trigger normal playlist updating - store.dispatch({ - type: 'PLAYLISTS_LOADED', - playlists: [playlist] - }); - break; + // Trigger normal playlist updating + store.dispatch({ + type: 'PLAYLISTS_LOADED', + playlists: [playlist], + }); + break; - /** + /** * Asset Load commands * * These are called from views and other middleware to load * assets. This is where we can return already indexed records * where appropriate - **/ + * */ - case 'LOAD_TRACK': + case 'LOAD_TRACK': if ( - !action.force_reload && - store.getState().core.tracks[action.uri]){ - console.info('Loading "'+action.uri+'" from index'); - break; - } - - switch (helpers.uriSource(action.uri)){ - case 'spotify': - store.dispatch(spotifyActions.getTrack(action.uri)); - - if (store.getState().spotify.me){ - store.dispatch(spotifyActions.following(action.uri)); - } - break; - - default: - if (store.getState().mopidy.connected){ - store.dispatch(mopidyActions.getTrack(action.uri)); - } - break; - } - - next(action); - break; - - case 'LOAD_ALBUM': - if ( - !action.force_reload && - store.getState().core.albums[action.uri] && - store.getState().core.albums[action.uri].tracks_uris){ - console.info('Loading "'+action.uri+'" from index'); - break; - } - - switch (helpers.uriSource(action.uri)){ - case 'spotify': - store.dispatch(spotifyActions.getAlbum(action.uri)); - - if (store.getState().spotify.me){ - store.dispatch(spotifyActions.following(action.uri)); - } - break; - - default: - if (store.getState().mopidy.connected){ - store.dispatch(mopidyActions.getAlbum(action.uri)); - } - break; - } - - next(action); - break; - - case 'LOAD_ARTIST': - if ( - !action.force_reload && - store.getState().core.artists[action.uri] && - store.getState().core.artists[action.uri].albums_uris && - store.getState().core.artists[action.uri].tracks_uris){ - console.info('Loading "'+action.uri+'" from index'); + !action.force_reload + && store.getState().core.tracks[action.uri]) { + console.info(`Loading "${action.uri}" from index`); break; } - switch (helpers.uriSource(action.uri)){ - case 'spotify': - store.dispatch(spotifyActions.getArtist(action.uri, true)); - - if (store.getState().spotify.me){ + switch (helpers.uriSource(action.uri)) { + case 'spotify': + store.dispatch(spotifyActions.getTrack(action.uri)); + + if (store.getState().spotify.me) { store.dispatch(spotifyActions.following(action.uri)); } - break; + break; - default: - if (store.getState().mopidy.connected){ - store.dispatch(mopidyActions.getArtist(action.uri)); - } - break; - } + default: + if (store.getState().mopidy.connected) { + store.dispatch(mopidyActions.getTrack(action.uri)); + } + break; + } - next(action); - break; + next(action); + break; - case 'LOAD_PLAYLIST': + case 'LOAD_ALBUM': if ( - !action.force_reload && - store.getState().core.playlists[action.uri] && - store.getState().core.playlists[action.uri].tracks_uris){ - console.info('Loading "'+action.uri+'" from index'); + !action.force_reload + && store.getState().core.albums[action.uri] + && store.getState().core.albums[action.uri].tracks_uris) { + console.info(`Loading "${action.uri}" from index`); break; } - switch (helpers.uriSource(action.uri)){ - case 'spotify': - store.dispatch(spotifyActions.getPlaylist(action.uri)); + switch (helpers.uriSource(action.uri)) { + case 'spotify': + store.dispatch(spotifyActions.getAlbum(action.uri)); - if (store.getState().spotify.me){ + if (store.getState().spotify.me) { store.dispatch(spotifyActions.following(action.uri)); } - break; + break; - default: - if (store.getState().mopidy.connected){ - store.dispatch(mopidyActions.getPlaylist(action.uri)); - } - break; - } + default: + if (store.getState().mopidy.connected) { + store.dispatch(mopidyActions.getAlbum(action.uri)); + } + break; + } - next(action); - break; + next(action); + break; - case 'LOAD_USER': + case 'LOAD_ARTIST': if ( - !action.force_reload && - store.getState().core.users[action.uri] && - store.getState().core.users[action.uri].playlists_uris ){ - console.info('Loading "'+action.uri+'" from index'); + !action.force_reload + && store.getState().core.artists[action.uri] + && store.getState().core.artists[action.uri].albums_uris + && store.getState().core.artists[action.uri].tracks_uris) { + console.info(`Loading "${action.uri}" from index`); break; } - switch (helpers.uriSource(action.uri)){ - case 'spotify': - store.dispatch(spotifyActions.getUser(action.uri)); - - if (store.getState().spotify.me){ + switch (helpers.uriSource(action.uri)) { + case 'spotify': + store.dispatch(spotifyActions.getArtist(action.uri, true)); + + if (store.getState().spotify.me) { store.dispatch(spotifyActions.following(action.uri)); } - break; + break; - default: - // No Mopidy mechanism for users - break; - } + default: + if (store.getState().mopidy.connected) { + store.dispatch(mopidyActions.getArtist(action.uri)); + } + break; + } - next(action); - break; + next(action); + break; - case 'LOAD_USER_PLAYLISTS': - if ( - !action.force_reload && - store.getState().core.users[action.uri] && - store.getState().core.users[action.uri].playlists_uris ){ - console.info('Loading "'+action.uri+'" playlists from index'); - break; - } + case 'LOAD_PLAYLIST': + if ( + !action.force_reload + && store.getState().core.playlists[action.uri] + && store.getState().core.playlists[action.uri].tracks_uris) { + console.info(`Loading "${action.uri}" from index`); + break; + } - switch (helpers.uriSource(action.uri)){ - case 'spotify': - store.dispatch(spotifyActions.getUserPlaylists(action.uri)); - break; + switch (helpers.uriSource(action.uri)) { + case 'spotify': + store.dispatch(spotifyActions.getPlaylist(action.uri)); - default: - // No Mopidy mechanism for users - break; - } + if (store.getState().spotify.me) { + store.dispatch(spotifyActions.following(action.uri)); + } + break; - next(action); - break; + default: + if (store.getState().mopidy.connected) { + store.dispatch(mopidyActions.getPlaylist(action.uri)); + } + break; + } + + next(action); + break; + + case 'LOAD_USER': + if ( + !action.force_reload + && store.getState().core.users[action.uri] + && store.getState().core.users[action.uri].playlists_uris) { + console.info(`Loading "${action.uri}" from index`); + break; + } + + switch (helpers.uriSource(action.uri)) { + case 'spotify': + store.dispatch(spotifyActions.getUser(action.uri)); + + if (store.getState().spotify.me) { + store.dispatch(spotifyActions.following(action.uri)); + } + break; + + default: + // No Mopidy mechanism for users + break; + } + + next(action); + break; + + case 'LOAD_USER_PLAYLISTS': + if ( + !action.force_reload + && store.getState().core.users[action.uri] + && store.getState().core.users[action.uri].playlists_uris) { + console.info(`Loading "${action.uri}" playlists from index`); + break; + } + + switch (helpers.uriSource(action.uri)) { + case 'spotify': + store.dispatch(spotifyActions.getUserPlaylists(action.uri)); + break; + + default: + // No Mopidy mechanism for users + break; + } + + next(action); + break; - /** + /** * Index actions * These modify our asset indexes, which are used globally - **/ + * */ - case 'CURRENT_TRACK_LOADED': - store.dispatch(coreActions.trackLoaded(action.track)); - action.track = helpers.formatTrack(action.track); - next(action); - break; + case 'CURRENT_TRACK_LOADED': + store.dispatch(coreActions.trackLoaded(action.track)); + action.track = helpers.formatTrack(action.track); + next(action); + break; - case 'QUEUE_LOADED': - store.dispatch(coreActions.tracksLoaded(action.tracks)); - action.tracks = helpers.formatTracks(action.tracks); - next(action); - break; + case 'QUEUE_LOADED': + store.dispatch(coreActions.tracksLoaded(action.tracks)); + action.tracks = helpers.formatTracks(action.tracks); + next(action); + break; - case 'TRACKS_LOADED': - var tracks_index = Object.assign({}, core.tracks); - var artists_index = core.artists; - var albums_index = core.albums; - var tracks_loaded = []; - var artists_loaded = []; - var albums_loaded = []; + case 'TRACKS_LOADED': + var tracks_index = { ...core.tracks }; + var artists_index = core.artists; + var albums_index = core.albums; + var tracks_loaded = []; + var artists_loaded = []; + var albums_loaded = []; - for (var raw_track of action.tracks){ - var track = helpers.formatTrack(raw_track); + for (const raw_track of action.tracks) { + var track = helpers.formatTrack(raw_track); - if (tracks_index[track.uri] !== undefined){ - track = Object.assign({}, tracks_index[track.uri], track); - } - - if (raw_track.album){ - track.album = helpers.formatSimpleObject(raw_track.album); + if (tracks_index[track.uri] !== undefined) { + track = { ...tracks_index[track.uri], ...track }; + } - if (!albums_index[raw_track.album.uri]){ - albums_loaded.push(raw_track.album); - } + if (raw_track.album) { + track.album = helpers.formatSimpleObject(raw_track.album); - // Copy the images to the track - /* + if (!albums_index[raw_track.album.uri]) { + albums_loaded.push(raw_track.album); + } + + // Copy the images to the track + /* if (raw_track.album.images){ track.images = helpers.digestMopidyImages(store.getState().mopidy, raw_track.album.images); - }*/ - } + } */ + } - if (raw_track.artists && raw_track.artists.length > 0){ - track.artists = []; + if (raw_track.artists && raw_track.artists.length > 0) { + track.artists = []; - for (var artist of raw_track.artists){ - track.artists.push(helpers.formatSimpleObject(artist)); + for (var artist of raw_track.artists) { + track.artists.push(helpers.formatSimpleObject(artist)); - // Not already in our index, so let's add it - if (!artists_index[artist.uri]){ - artists_loaded.push(artist); - } - } - } + // Not already in our index, so let's add it + if (!artists_index[artist.uri]) { + artists_loaded.push(artist); + } + } + } - tracks_loaded.push(track); - }; + tracks_loaded.push(track); + } - action.tracks = tracks_loaded; + action.tracks = tracks_loaded; - if (artists_loaded.length > 0){ - store.dispatch(coreActions.artistsLoaded(artists_loaded)); - } - if (albums_loaded.length > 0){ - store.dispatch(coreActions.albumsLoaded(albums_loaded)); - } + if (artists_loaded.length > 0) { + store.dispatch(coreActions.artistsLoaded(artists_loaded)); + } + if (albums_loaded.length > 0) { + store.dispatch(coreActions.albumsLoaded(albums_loaded)); + } - next(action); - break; + next(action); + break; - case 'ALBUMS_LOADED': - var albums_index = Object.assign({}, core.albums); - var albums_loaded = []; - var artists_loaded = []; - var tracks_loaded = []; + case 'ALBUMS_LOADED': + var albums_index = { ...core.albums }; + var albums_loaded = []; + var artists_loaded = []; + var tracks_loaded = []; - for (var raw_album of action.albums){ - var album = helpers.formatAlbum(raw_album); + for (const raw_album of action.albums) { + let album = helpers.formatAlbum(raw_album); - if (albums_index[album.uri]){ - album = Object.assign({}, albums_index[album.uri], album); - } -/* + if (albums_index[album.uri]) { + album = { ...albums_index[album.uri], ...album }; + } + /* if (raw_album.images && raw_album.images.length > 0){ album.images = helpers.digestMopidyImages(store.getState().mopidy, raw_album.images); - }*/ + } */ - if (raw_album.tracks){ - album.tracks_uris = []; + if (raw_album.tracks) { + album.tracks_uris = []; - for (var track of raw_album.tracks){ - if (!track.album){ - track.album = helpers.formatSimpleObject(album); - } - album.tracks_uris.push(track.uri); - tracks_loaded.push(track); - } - } + for (var track of raw_album.tracks) { + if (!track.album) { + track.album = helpers.formatSimpleObject(album); + } + album.tracks_uris.push(track.uri); + tracks_loaded.push(track); + } + } - if (raw_album.artists){ - album.artists_uris = helpers.arrayOf('uri', raw_album.artists); - artists_loaded = [...artists_loaded, ...raw_album.artists]; - } + if (raw_album.artists) { + album.artists_uris = helpers.arrayOf('uri', raw_album.artists); + artists_loaded = [...artists_loaded, ...raw_album.artists]; + } - albums_loaded.push(album); - }; + albums_loaded.push(album); + } - action.albums = albums_loaded; + action.albums = albums_loaded; - if (artists_loaded.length > 0){ - store.dispatch(coreActions.artistsLoaded(artists_loaded)); - } - if (tracks_loaded.length > 0){ - store.dispatch(coreActions.tracksLoaded(tracks_loaded)); - } + if (artists_loaded.length > 0) { + store.dispatch(coreActions.artistsLoaded(artists_loaded)); + } + if (tracks_loaded.length > 0) { + store.dispatch(coreActions.tracksLoaded(tracks_loaded)); + } - next(action); - break + next(action); + break; - case 'ARTISTS_LOADED': - var artists_index = Object.assign({}, core.artists); - var artists_loaded = []; - var tracks_loaded = []; - - for (var raw_artist of action.artists){ + case 'ARTISTS_LOADED': + var artists_index = { ...core.artists }; + var artists_loaded = []; + var tracks_loaded = []; + + for (const raw_artist of action.artists) { var artist = helpers.formatArtist(raw_artist); // Already have an artist in the index - if (artists_index[artist.uri]){ - + if (artists_index[artist.uri]) { // And we've already got some images, make sure we merge the arrays, // rather than overwriting - if (artists_index[artist.uri].images && artist.images){ - var existing_images = artists_index[artist.uri].images; - var are_new_images = true; + if (artists_index[artist.uri].images && artist.images) { + const existing_images = artists_index[artist.uri].images; + let are_new_images = true; // loop all extisting images to make sure we're not adding one that // we already have - for (var existing_image of existing_images){ - + for (const existing_image of existing_images) { // We only need to check one size, the formatter should insist on consistency // Note that we depend on having a one-item array of images provided per action - if (existing_image.huge == artist.images[0].huge){ + if (existing_image.huge == artist.images[0].huge) { are_new_images = false; } } // Only if they're new images should we merge them in - if (are_new_images){ + if (are_new_images) { artist.images = Object.assign([], [...existing_images, ...artist.images]); } } - artist = Object.assign({}, artists_index[artist.uri], artist); - } + artist = { ...artists_index[artist.uri], ...artist }; + } - // Migrate nested tracks objects into references to our tracks index - if (raw_artist.tracks){ - var tracks = helpers.formatTracks(raw_artist.tracks); - var tracks_uris = helpers.arrayOf('uri', tracks); - artist.tracks_uris = tracks_uris; - tracks_loaded = [...tracks_loaded, ...tracks]; - } + // Migrate nested tracks objects into references to our tracks index + if (raw_artist.tracks) { + var tracks = helpers.formatTracks(raw_artist.tracks); + var tracks_uris = helpers.arrayOf('uri', tracks); + artist.tracks_uris = tracks_uris; + tracks_loaded = [...tracks_loaded, ...tracks]; + } - artists_loaded.push(artist); - }; + artists_loaded.push(artist); + } - action.artists = artists_loaded; + action.artists = artists_loaded; - if (tracks_loaded.length > 0){ - store.dispatch(coreActions.tracksLoaded(tracks_loaded)); - } + if (tracks_loaded.length > 0) { + store.dispatch(coreActions.tracksLoaded(tracks_loaded)); + } - next(action); - break; + next(action); + break; - case 'PLAYLISTS_LOADED': - var playlists_index = Object.assign({}, core.playlists); - var playlists_loaded = []; - var tracks_loaded = []; - - for (var playlist of action.playlists){ + case 'PLAYLISTS_LOADED': + var playlists_index = { ...core.playlists }; + var playlists_loaded = []; + var tracks_loaded = []; + for (var playlist of action.playlists) { playlist = helpers.formatPlaylist(playlist); - // Detect editability - switch (helpers.uriSource(playlist.uri)){ + // Detect editability + switch (helpers.uriSource(playlist.uri)) { + case 'm3u': + playlist.can_edit = true; + break; - case 'm3u': - playlist.can_edit = true - break + case 'spotify': + if (store.getState().spotify.authorization && store.getState().spotify.me) { + playlist.can_edit = (playlist.owner.id == store.getState().spotify.me.id); + } + } - case 'spotify': - if (store.getState().spotify.authorization && store.getState().spotify.me){ - playlist.can_edit = (playlist.owner.id == store.getState().spotify.me.id) - } - } + // Already have this playlist partially in our index + if (playlists_index[playlist.uri]) { + playlist = { ...playlists_index[playlist.uri], ...playlist }; - // Already have this playlist partially in our index - if (playlists_index[playlist.uri]){ - playlist = Object.assign({}, playlists_index[playlist.uri], playlist); + // Setup placeholder tracks_uris + if (playlist.tracks_uris === undefined) { + playlist.tracks_uris = []; + } + } - // Setup placeholder tracks_uris - if (playlist.tracks_uris === undefined){ - playlist.tracks_uris = []; - } - } + // Load our tracks + if (playlist.tracks) { + var tracks = helpers.formatTracks(playlist.tracks); + var tracks_uris = helpers.arrayOf('uri', tracks); + playlist.tracks_uris = tracks_uris; + delete playlist.tracks; + tracks_loaded = [...tracks_loaded, ...tracks]; + } - // Load our tracks - if (playlist.tracks){ - var tracks = helpers.formatTracks(playlist.tracks); - var tracks_uris = helpers.arrayOf('uri', tracks); - playlist.tracks_uris = tracks_uris; - delete playlist.tracks; - tracks_loaded = [...tracks_loaded, ...tracks]; - } + // Update index + playlists_loaded.push(playlist); + } - // Update index - playlists_loaded.push(playlist); - } + action.playlists = playlists_loaded; - action.playlists = playlists_loaded; + if (tracks_loaded.length > 0) { + store.dispatch(coreActions.tracksLoaded(tracks_loaded)); + } - if (tracks_loaded.length > 0){ - store.dispatch(coreActions.tracksLoaded(tracks_loaded)); - } + next(action); + break; - next(action); - break; + case 'USERS_LOADED': + var users_index = { ...core.users }; + var users_loaded = []; - case 'USERS_LOADED': - var users_index = Object.assign({}, core.users); - var users_loaded = []; + for (let user of action.users) { + user = helpers.formatUser(user); - for (var user of action.users){ - user = helpers.formatUser(user); - - if (users_index[user.uri]){ - user = Object.assign({}, users_index[user.uri], user); - } + if (users_index[user.uri]) { + user = { ...users_index[user.uri], ...user }; + } - users_loaded.push(user); - } + users_loaded.push(user); + } - action.users = users_loaded; + action.users = users_loaded; - next(action); - break; + next(action); + break; - case 'USER_PLAYLISTS_LOADED': + case 'USER_PLAYLISTS_LOADED': store.dispatch(coreActions.playlistsLoaded(action.playlists)); - next(action); - break; + next(action); + break; - /** + /** * Loaded more linked assets * Often fired during lazy-loading or async asset grabbing. * We link the parent to these indexed records by {type}s_uris - **/ + * */ - case 'LOADED_MORE': - var parent_type_plural = action.parent_type+'s'; - var parent_index = Object.assign({}, core[action.parent_type+'s']); - var parent = Object.assign({}, parent_index[action.parent_key]); + case 'LOADED_MORE': + var parent_type_plural = `${action.parent_type}s`; + var parent_index = { ...core[`${action.parent_type}s`] }; + var parent = { ...parent_index[action.parent_key] }; - if (action.records_data.items !== undefined){ - var records = action.records_data.items; - } else if (action.records_data.tracks !== undefined){ - var records = action.records_data.tracks; - } else if (action.records_data.artists !== undefined){ - var records = action.records_data.artists; - } else if (action.records_data.albums !== undefined){ - var records = action.records_data.albums; - } else if (action.records_data.playlists !== undefined){ - var records = action.records_data.playlists; - } else { - var records = action.records_data; - } - - // Pre-emptively format tracks - // Providers give us tracks in all kinds of structures, so this cleans things first - if (action.records_type == 'track'){ - records = helpers.formatTracks(records); - } - - var records_type_plural = action.records_type+'s'; - var records_index = {}; - var records_uris = helpers.arrayOf('uri', records); - - // If we're a list of playlists, we need to manually filter Spotify's new URI structure - // Really poor form because they haven't updated it everywhere, yet - if (action.records_type == 'playlist'){ - records_uris = helpers.upgradeSpotifyPlaylistUris(records_uris); - } - - // Append our parent object's reference to these records - var uris = records_uris; - if (parent[records_type_plural+'_uris'] !== undefined){ - uris = [...parent[records_type_plural+'_uris'], ...uris]; - } - parent[records_type_plural+'_uris'] = uris; - if (action.records_data.next !== undefined){ - parent[records_type_plural+'_more'] = action.records_data.next; - } - - // Parent loaded (well, changed) - var parent_action = { - type: parent_type_plural.toUpperCase()+'_LOADED' - }; - parent_action[parent_type_plural] = [parent]; - store.dispatch(parent_action); - - // Records loaded - var records_action = { - type: records_type_plural.toUpperCase()+'_LOADED' - }; - records_action[records_type_plural] = records; - store.dispatch(records_action); - - next(action); - break; - - // This action is irrelevant to us, pass it on to the next middleware - default: - return next(action) + if (action.records_data.items !== undefined) { + var records = action.records_data.items; + } else if (action.records_data.tracks !== undefined) { + var records = action.records_data.tracks; + } else if (action.records_data.artists !== undefined) { + var records = action.records_data.artists; + } else if (action.records_data.albums !== undefined) { + var records = action.records_data.albums; + } else if (action.records_data.playlists !== undefined) { + var records = action.records_data.playlists; + } else { + var records = action.records_data; } + + // Pre-emptively format tracks + // Providers give us tracks in all kinds of structures, so this cleans things first + if (action.records_type == 'track') { + records = helpers.formatTracks(records); + } + + var records_type_plural = `${action.records_type}s`; + var records_index = {}; + var records_uris = helpers.arrayOf('uri', records); + + // If we're a list of playlists, we need to manually filter Spotify's new URI structure + // Really poor form because they haven't updated it everywhere, yet + if (action.records_type == 'playlist') { + records_uris = helpers.upgradeSpotifyPlaylistUris(records_uris); + } + + // Append our parent object's reference to these records + var uris = records_uris; + if (parent[`${records_type_plural}_uris`] !== undefined) { + uris = [...parent[`${records_type_plural}_uris`], ...uris]; + } + parent[`${records_type_plural}_uris`] = uris; + if (action.records_data.next !== undefined) { + parent[`${records_type_plural}_more`] = action.records_data.next; + } + + // Parent loaded (well, changed) + var parent_action = { + type: `${parent_type_plural.toUpperCase()}_LOADED`, + }; + parent_action[parent_type_plural] = [parent]; + store.dispatch(parent_action); + + // Records loaded + var records_action = { + type: `${records_type_plural.toUpperCase()}_LOADED`, + }; + records_action[records_type_plural] = records; + store.dispatch(records_action); + + next(action); + break; + + // This action is irrelevant to us, pass it on to the next middleware + default: + return next(action); } + }; +}()); -})(); - -export default CoreMiddleware +export default CoreMiddleware; diff --git a/src/js/services/core/reducer.js b/src/js/services/core/reducer.js index 854e5487..8ae72858 100755 --- a/src/js/services/core/reducer.js +++ b/src/js/services/core/reducer.js @@ -1,306 +1,291 @@ -import * as helpers from '../../helpers' +import * as helpers from '../../helpers'; -export default function reducer(core = {}, action){ - switch (action.type){ +export default function reducer(core = {}, action) { + switch (action.type) { + case 'CORE_SET': + return { ...core, ...action.data }; - case 'CORE_SET': - return Object.assign({}, core, action.data) + case 'CACHEBUST_HTTP_STREAM': + return { ...core, http_streaming_cachebuster: new Date().getTime() }; - case 'CACHEBUST_HTTP_STREAM': - return Object.assign({}, core, {http_streaming_cachebuster: new Date().getTime()}) - - /** + /** * Current track and tracklist - **/ + * */ - case 'CURRENT_TRACK_LOADED': - return Object.assign({}, core, { - current_track: action.track, - current_track_uri: action.uri - }); + case 'CURRENT_TRACK_LOADED': + return { + ...core, + current_track: action.track, + current_track_uri: action.uri, + }; - case 'CLEAR_CURRENT_TRACK': - return Object.assign({}, core, { - current_track: null, - current_track_uri: null - }); + case 'CLEAR_CURRENT_TRACK': + return { + ...core, + current_track: null, + current_track_uri: null, + }; - case 'NEXT_TRACK_LOADED': - return Object.assign({}, core, { - next_track_uri: action.uri - }); + case 'NEXT_TRACK_LOADED': + return { ...core, next_track_uri: action.uri }; - case 'QUEUE_LOADED': - return Object.assign({}, core, { - queue: action.tracks - }); + case 'QUEUE_LOADED': + return { ...core, queue: action.tracks }; - case 'PUSHER_QUEUE_METADATA': - case 'PUSHER_QUEUE_METADATA_CHANGED': - var tracklist = Object.assign([], core.current_tracklist) - for(var i = 0; i < tracklist.length; i++){ + case 'PUSHER_QUEUE_METADATA': + case 'PUSHER_QUEUE_METADATA_CHANGED': + var tracklist = Object.assign([], core.current_tracklist); + for (let i = 0; i < tracklist.length; i++) { + // load our metadata (if we have any for that tlid) + if (action.queue_metadata[`tlid_${tracklist[i].tlid}`] !== undefined) { + tracklist[i] = { - // load our metadata (if we have any for that tlid) - if (action.queue_metadata['tlid_'+tracklist[i].tlid] !== undefined){ - tracklist[i] = Object.assign( - {}, - tracklist[i], - action.queue_metadata['tlid_'+tracklist[i].tlid], - ) - } - } - return Object.assign({}, core, { current_tracklist: tracklist, queue_metadata: action.queue_metadata }); + ...tracklist[i], + ...action.queue_metadata[`tlid_${tracklist[i].tlid}`], + }; + } + } + return { ...core, current_tracklist: tracklist, queue_metadata: action.queue_metadata }; - case 'PUSHER_RADIO_LOADED': - case 'PUSHER_RADIO_STARTED': - case 'PUSHER_RADIO_CHANGED': - case 'PUSHER_RADIO_STOPPED': - return Object.assign({}, core, { seeds_resolved: false }, { radio: action.radio }) + case 'PUSHER_RADIO_LOADED': + case 'PUSHER_RADIO_STARTED': + case 'PUSHER_RADIO_CHANGED': + case 'PUSHER_RADIO_STOPPED': + return { ...core, seeds_resolved: false, radio: action.radio }; - case 'RADIO_SEEDS_RESOLVED': - var radio = Object.assign({}, core.radio, { resolved_seeds: action.resolved_seeds }) - return Object.assign({}, core, { radio: radio }) + case 'RADIO_SEEDS_RESOLVED': + var radio = { ...core.radio, resolved_seeds: action.resolved_seeds }; + return { ...core, radio }; - - - - - /** + /** * Index updates * These actions are only ever called by middleware after we've digested one more many assets * and appended to their relevant index. - **/ + * */ - case 'TRACKS_LOADED': - var tracks = Object.assign({}, core.tracks); - for (var track of action.tracks){ - tracks[track.uri] = track; - } - return Object.assign({}, core, { tracks: tracks }); + case 'TRACKS_LOADED': + var tracks = { ...core.tracks }; + for (const track of action.tracks) { + tracks[track.uri] = track; + } + return { ...core, tracks }; - case 'ALBUMS_LOADED': - var albums = Object.assign({}, core.albums); - for (var album of action.albums){ - albums[album.uri] = album; - } - return Object.assign({}, core, { albums: albums }); + case 'ALBUMS_LOADED': + var albums = { ...core.albums }; + for (const album of action.albums) { + albums[album.uri] = album; + } + return { ...core, albums }; - case 'ARTISTS_LOADED': - var artists = Object.assign({}, core.artists); - for (var artist of action.artists){ - artists[artist.uri] = artist; - } - return Object.assign({}, core, { artists: artists }); + case 'ARTISTS_LOADED': + var artists = { ...core.artists }; + for (var artist of action.artists) { + artists[artist.uri] = artist; + } + return { ...core, artists }; - case 'PLAYLISTS_LOADED': - var playlists = Object.assign({}, core.playlists); - for (var playlist of action.playlists){ - playlists[playlist.uri] = playlist; - } - return Object.assign({}, core, { playlists: playlists }); + case 'PLAYLISTS_LOADED': + var playlists = { ...core.playlists }; + for (var playlist of action.playlists) { + playlists[playlist.uri] = playlist; + } + return { ...core, playlists }; - case 'USERS_LOADED': - var users = Object.assign({}, core.users); - for (var user of action.users){ - users[user.uri] = user; - } - return Object.assign({}, core, { users: users }); + case 'USERS_LOADED': + var users = { ...core.users }; + for (var user of action.users) { + users[user.uri] = user; + } + return { ...core, users }; - case 'ARTIST_ALBUMS_LOADED': - var artists = Object.assign({}, core.artists) - var albums_uris = []; - if (artists[action.artist_uri].albums_uris){ - albums_uris = artists[action.artist_uri].albums_uris; - } + case 'ARTIST_ALBUMS_LOADED': + var artists = { ...core.artists }; + var albums_uris = []; + if (artists[action.artist_uri].albums_uris) { + albums_uris = artists[action.artist_uri].albums_uris; + } - var artist = Object.assign( - {}, - artists[action.artist_uri], - { - albums_uris: [...albums_uris, ...action.albums_uris], - albums_more: action.more, - albums_total: action.total - } - ) - artists[action.artist_uri] = artist - return Object.assign({}, core, { artists: artists }); + var artist = { + + ...artists[action.artist_uri], + albums_uris: [...albums_uris, ...action.albums_uris], + albums_more: action.more, + albums_total: action.total, + }; + artists[action.artist_uri] = artist; + return { ...core, artists }; - case 'USER_PLAYLISTS_LOADED': - var users = Object.assign({}, core.users) - var existing_playlists_uris = []; - if (users[action.uri] && users[action.uri].playlists_uris){ + case 'USER_PLAYLISTS_LOADED': + var users = { ...core.users }; + var existing_playlists_uris = []; + if (users[action.uri] && users[action.uri].playlists_uris) { existing_playlists_uris = users[action.uri].playlists_uris; - } + } - var playlists_uris = [...existing_playlists_uris, ...helpers.arrayOf('uri',action.playlists)] + var playlists_uris = [...existing_playlists_uris, ...helpers.arrayOf('uri', action.playlists)]; - var user = Object.assign( - {}, - users[action.uri], - { - playlists_uris: playlists_uris, - playlists_more: action.more, - playlists_total: action.total - } - ) - users[action.uri] = user; - return Object.assign({}, core, { users: users }); + var user = { + + ...users[action.uri], + playlists_uris, + playlists_more: action.more, + playlists_total: action.total, + }; + users[action.uri] = user; + return { ...core, users }; - - /** + /** * Remove an item from an index - **/ + * */ - case 'REMOVE_FROM_INDEX': - var index = Object.assign({}, core[action.index_name]); + case 'REMOVE_FROM_INDEX': + var index = { ...core[action.index_name] }; - // We have a new key to redirect to - if (action.new_key){ + // We have a new key to redirect to + if (action.new_key) { index[action.key] = { - moved_to: action.new_key - } + moved_to: action.new_key, + }; - // No redirection, so just a clean delete - } else { + // No redirection, so just a clean delete + } else { delete index[action.key]; - } + } - var updated_core = {}; - updated_core[action.index_name] = index; + var updated_core = {}; + updated_core[action.index_name] = index; - return Object.assign({}, core, updated_core); + return { ...core, ...updated_core }; - - - /** + /** * Playlists - **/ + * */ - case 'PLAYLIST_TRACKS': - var playlists = Object.assign({}, core.playlists) - var playlist = Object.assign({}, playlists[action.key], { tracks_uris: action.tracks_uris }) + case 'PLAYLIST_TRACKS': + var playlists = { ...core.playlists }; + var playlist = { ...playlists[action.key], tracks_uris: action.tracks_uris }; - playlists[action.key] = playlist - return Object.assign({}, core, { playlists: playlists }); + playlists[action.key] = playlist; + return { ...core, playlists }; - case 'LIBRARY_PLAYLISTS_LOADED': - if (core.library_playlists){ - var library_playlists = [...core.library_playlists, ...action.uris] - } else { - var library_playlists = action.uris - } + case 'LIBRARY_PLAYLISTS_LOADED': + if (core.library_playlists) { + var library_playlists = [...core.library_playlists, ...action.uris]; + } else { + var library_playlists = action.uris; + } - library_playlists = helpers.removeDuplicates(library_playlists) + library_playlists = helpers.removeDuplicates(library_playlists); - return Object.assign({}, core, { - library_playlists: library_playlists, - library_playlists_started: true - }); + return { + ...core, + library_playlists, + library_playlists_started: true, + }; - /** + /** * Genres - **/ + * */ - case 'SPOTIFY_GENRES_LOADED': - return Object.assign({}, core, {genres: action.genres}) + case 'SPOTIFY_GENRES_LOADED': + return { ...core, genres: action.genres }; - /** + /** * Search results - **/ + * */ - case 'SEARCH_STARTED': - return Object.assign({}, core, { - search_results: { - artists_uris: [], - albums_uris: [], - playlists_uris: [], - tracks: [], - } - }); + case 'SEARCH_STARTED': + return { + ...core, + search_results: { + artists_uris: [], + albums_uris: [], + playlists_uris: [], + tracks: [], + }, + }; - case 'SEARCH_RESULTS_LOADED': + case 'SEARCH_RESULTS_LOADED': - // artists - if (core.search_results && core.search_results.artists_uris){ - var artists_uris = core.search_results.artists_uris - } else { - var artists_uris = [] - } - if (action.artists_uris) artists_uris = [...artists_uris, ...action.artists_uris] + // artists + if (core.search_results && core.search_results.artists_uris) { + var { artists_uris } = core.search_results; + } else { + var artists_uris = []; + } + if (action.artists_uris) artists_uris = [...artists_uris, ...action.artists_uris]; - // more tracks - if (typeof(action.artists_more) !== 'undefined') var artists_more = action.artists_more - else if (core.search_results && core.search_results.artists_more) var artists_more = core.search_results.artists_more - else var artists_more = null + // more tracks + if (typeof (action.artists_more) !== 'undefined') var { artists_more } = action; + else if (core.search_results && core.search_results.artists_more) var { artists_more } = core.search_results; + else var artists_more = null; - // albums - if (core.search_results && core.search_results.albums_uris){ - var albums_uris = core.search_results.albums_uris - } else { - var albums_uris = [] - } - if (action.albums_uris) albums_uris = [...albums_uris, ...action.albums_uris] + // albums + if (core.search_results && core.search_results.albums_uris) { + var { albums_uris } = core.search_results; + } else { + var albums_uris = []; + } + if (action.albums_uris) albums_uris = [...albums_uris, ...action.albums_uris]; - // more tracks - if (typeof(action.albums_more) !== 'undefined') var albums_more = action.albums_more - else if (core.search_results && core.search_results.albums_more) var albums_more = core.search_results.albums_more - else var albums_more = null + // more tracks + if (typeof (action.albums_more) !== 'undefined') var { albums_more } = action; + else if (core.search_results && core.search_results.albums_more) var { albums_more } = core.search_results; + else var albums_more = null; - // playlists - if (core.search_results && core.search_results.playlists_uris){ - var playlists_uris = core.search_results.playlists_uris - } else { - var playlists_uris = [] - } - if (action.playlists_uris) playlists_uris = [...playlists_uris, ...action.playlists_uris] + // playlists + if (core.search_results && core.search_results.playlists_uris) { + var { playlists_uris } = core.search_results; + } else { + var playlists_uris = []; + } + if (action.playlists_uris) playlists_uris = [...playlists_uris, ...action.playlists_uris]; - // more tracks - if (typeof(action.playlists_more) !== 'undefined') var playlists_more = action.playlists_more - else if (core.search_results && core.search_results.playlists_more) var playlists_more = core.search_results.playlists_more - else var playlists_more = null + // more tracks + if (typeof (action.playlists_more) !== 'undefined') var { playlists_more } = action; + else if (core.search_results && core.search_results.playlists_more) var { playlists_more } = core.search_results; + else var playlists_more = null; - // tracks - if (core.search_results && core.search_results.tracks){ - var tracks = core.search_results.tracks - } else { - var tracks = [] - } - if (action.tracks) tracks = [...tracks, ...helpers.formatTracks(action.tracks)] + // tracks + if (core.search_results && core.search_results.tracks) { + var { tracks } = core.search_results; + } else { + var tracks = []; + } + if (action.tracks) tracks = [...tracks, ...helpers.formatTracks(action.tracks)]; - // more tracks - if (typeof(action.tracks_more) !== 'undefined') var tracks_more = action.tracks_more - else if (core.search_results && core.search_results.tracks_more) var tracks_more = core.search_results.tracks_more - else var tracks_more = null + // more tracks + if (typeof (action.tracks_more) !== 'undefined') var { tracks_more } = action; + else if (core.search_results && core.search_results.tracks_more) var { tracks_more } = core.search_results; + else var tracks_more = null; - return Object.assign({}, core, { - search_results: { - artists_more: artists_more, - artists_uris: helpers.removeDuplicates(artists_uris), - albums_more: albums_more, - albums_uris: helpers.removeDuplicates(albums_uris), - playlists_more: playlists_more, - playlists_uris: helpers.removeDuplicates(playlists_uris), - tracks: tracks, - tracks_more: tracks_more - } - }); + return { + ...core, + search_results: { + artists_more, + artists_uris: helpers.removeDuplicates(artists_uris), + albums_more, + albums_uris: helpers.removeDuplicates(albums_uris), + playlists_more, + playlists_uris: helpers.removeDuplicates(playlists_uris), + tracks, + tracks_more, + }, + }; - default: - return core - } + default: + return core; + } } - - - diff --git a/src/js/services/discogs/actions.js b/src/js/services/discogs/actions.js index 27a078fb..284777ef 100755 --- a/src/js/services/discogs/actions.js +++ b/src/js/services/discogs/actions.js @@ -1,7 +1,7 @@ -var coreActions = require('../core/actions'); -var uiActions = require('../ui/actions'); -var helpers = require('../../helpers'); +const coreActions = require('../core/actions'); +const uiActions = require('../ui/actions'); +const helpers = require('../../helpers'); /** * Send an ajax request to the LastFM API @@ -9,100 +9,96 @@ var helpers = require('../../helpers'); * @param dispatch = obj * @param endpoint = String * @param params = String - **/ -var sendRequest = (dispatch, endpoint, params) => { - return new Promise((resolve, reject) => { - const loader_key = helpers.generateGuid(); + * */ +const sendRequest = (dispatch, endpoint, params) => new Promise((resolve, reject) => { + const loader_key = helpers.generateGuid(); - dispatch(uiActions.startLoading(loader_key, ',discogs_'+endpoint)); + dispatch(uiActions.startLoading(loader_key, `,discogs_${endpoint}`)); - let key = 'CXIwsVMAjrXIVitBWgqd'; - let secret = 'KiEUfwKpebxRnEHlKoXnYIftJxeuqjTK'; + const key = 'CXIwsVMAjrXIVitBWgqd'; + const secret = 'KiEUfwKpebxRnEHlKoXnYIftJxeuqjTK'; - var config = { - method: 'GET', - cache: true, - timeout: 30000, - url: `https://api.discogs.com/${endpoint}?key=${key}&secret=${secret}&${params}`, - headers: { - 'user-agent': 'Iris/1.0', - }, + const config = { + method: 'GET', + cache: true, + timeout: 30000, + url: `https://api.discogs.com/${endpoint}?key=${key}&secret=${secret}&${params}`, + headers: { + 'user-agent': 'Iris/1.0', + }, + }; + + $.ajax(config).then( + (response) => { + dispatch(uiActions.stopLoading(loader_key)); + if (response.error) { + reject({ + config, + error: response, + }); + } else { + resolve(response); + } + }, + (xhr, status, error) => { + dispatch(uiActions.stopLoading(loader_key)); + + // Snatch a more meaningful error + let description = null; + if (xhr && xhr.responseJSON && xhr.responseJSON.message) { + description = xhr.responseJSON.message; + } + + reject({ + config, + error, + description, + status, + xhr, + }); + }, + ); +}); + +export function findArtist(name) { + return sendRequest(dispatch, `artists/${artist.mbid}`, 'inc=url-rels') + .then( + (response) => { + if (response) { + const image_relations = response.relations.filter((rel) => rel.type == 'image'); + if (image_relations) { + const updated_artist = { + uri, + images: image_relations.map((relation) => relation.url.resource.replace('File:', 'Special:FilePath/')), + }; + dispatch(coreActions.artistLoaded(updated_artist)); + } } - - $.ajax(config).then( - response => { - dispatch(uiActions.stopLoading(loader_key)); - if (response.error){ - reject({ - config: config, - error: response - }); - } else { - resolve(response); - } - }, - (xhr, status, error) => { - dispatch(uiActions.stopLoading(loader_key)); - - // Snatch a more meaningful error - var description = null; - if (xhr && xhr.responseJSON && xhr.responseJSON.message){ - description = xhr.responseJSON.message; - } - - reject({ - config: config, - error: error, - description: description, - status: status, - xhr: xhr - }); - } - ) - }) + }, + (error) => { + console.log(`Musicbrainz: No results for ${artist.mbid}`); + }, + ); } -export function findArtist(name){ - return sendRequest(dispatch, 'artists/'+artist.mbid, 'inc=url-rels') - .then( - response => { - if (response){ - const image_relations = response.relations.filter(rel => rel.type == "image"); - if (image_relations) { - const updated_artist = { - uri: uri, - images: image_relations.map(relation => - relation.url.resource.replace("File:","Special:FilePath/") - ), - } - dispatch(coreActions.artistLoaded(updated_artist)); - } - } - }, - error => { - console.log(`Musicbrainz: No results for ${artist.mbid}`) +export function getArtistImages(uri, artist) { + return (dispatch, getState) => { + sendRequest(dispatch, 'database/search', `query=${artist.name}`) + .then( + (response) => { + if (response) { + if (response.results && response.results[0].cover_image) { + const updated_artist = { + uri, + images: [response.results[0].cover_image], + }; + dispatch(coreActions.artistLoaded(updated_artist)); } - ) -} - -export function getArtistImages(uri, artist){ - return (dispatch, getState) => { - sendRequest(dispatch, 'database/search', `query=${artist.name}`) - .then( - response => { - if (response){ - if (response.results && response.results[0].cover_image) { - const updated_artist = { - uri: uri, - images: [response.results[0].cover_image], - } - dispatch(coreActions.artistLoaded(updated_artist)); - } - } - }, - error => { - console.log(`Discogs: No results for ${artist.name}`) - } - ) - } + } + }, + (error) => { + console.log(`Discogs: No results for ${artist.name}`); + }, + ); + }; } diff --git a/src/js/services/genius/actions.js b/src/js/services/genius/actions.js index 8ac75ffa..dcb95fd5 100755 --- a/src/js/services/genius/actions.js +++ b/src/js/services/genius/actions.js @@ -1,13 +1,13 @@ -var coreActions = require('../core/actions'); -var uiActions = require('../ui/actions'); -var helpers = require('../../helpers'); +const coreActions = require('../core/actions'); +const uiActions = require('../ui/actions'); +const helpers = require('../../helpers'); -export function set(data){ - return { - type: 'GENIUS_SET', - data: data - } +export function set(data) { + return { + type: 'GENIUS_SET', + data, + }; } /** @@ -19,115 +19,112 @@ export function set(data){ * @param method string * @param data mixed = request payload * @return Promise - **/ -var sendRequest = (dispatch, getState, endpoint, method = 'GET', data = false) => { - return new Promise((resolve, reject) => { + * */ +const sendRequest = (dispatch, getState, endpoint, method = 'GET', data = false) => new Promise((resolve, reject) => { + if (endpoint.startsWith('https://') || endpoint.startsWith('http://')) { + var url = endpoint; + } else { + var url = `https://api.genius.com/${endpoint}`; + if (getState().genius.access_token) { + url += `?access_token=${getState().genius.access_token}`; + } + } - if (endpoint.startsWith('https://') || endpoint.startsWith('http://')){ - var url = endpoint; - } else { - var url = 'https://api.genius.com/'+endpoint; - if (getState().genius.access_token){ - url += '?access_token='+getState().genius.access_token; - } - } + // create our ajax request config + const config = { + method, + url, + timeout: 30000, + crossDomain: true, + }; - // create our ajax request config - var config = { - method: method, - url: url, - timeout: 30000, - crossDomain: true - } + // only if we've got data do we add it to the request (this prevents appending of "&false" to the URL) + if (data) { + if (typeof (data) === 'string') { + config.data = data; + } else { + config.data = JSON.stringify(data); + } + } - // only if we've got data do we add it to the request (this prevents appending of "&false" to the URL) - if (data){ - if (typeof(data) === 'string'){ - config.data = data - } else { - config.data = JSON.stringify(data) - } - } + // add reference to loader queue + const loader_key = helpers.generateGuid(); + dispatch(uiActions.startLoading(loader_key, `genius_${endpoint}`)); - // add reference to loader queue - var loader_key = helpers.generateGuid(); - dispatch(uiActions.startLoading(loader_key, 'genius_'+endpoint)); + $.ajax(config).then( + (response) => { + dispatch(uiActions.stopLoading(loader_key)); - $.ajax(config).then( - response => { - dispatch(uiActions.stopLoading(loader_key)); - - if (response.meta && response.meta.status >= 200 && response.meta.status < 300 && response.response){ - resolve(response.response); - } else { - reject({ - config: config, - xhr: xhr, - status: status, - error: error - }); - } - }, - (xhr, status, error) => { - dispatch(uiActions.stopLoading(loader_key)); - reject({ - config: config, - xhr: xhr, - status: status, - error: error - }); - } - ) - }); -} + if (response.meta && response.meta.status >= 200 && response.meta.status < 300 && response.response) { + resolve(response.response); + } else { + reject({ + config, + xhr, + status, + error, + }); + } + }, + (xhr, status, error) => { + dispatch(uiActions.stopLoading(loader_key)); + reject({ + config, + xhr, + status, + error, + }); + }, + ); +}); /** * Handle authorization process - **/ + * */ -export function authorizationGranted(data){ - return { - type: 'GENIUS_AUTHORIZATION_GRANTED', - data: data - } +export function authorizationGranted(data) { + return { + type: 'GENIUS_AUTHORIZATION_GRANTED', + data, + }; } -export function revokeAuthorization(){ - return { - type: 'GENIUS_AUTHORIZATION_REVOKED' - } +export function revokeAuthorization() { + return { + type: 'GENIUS_AUTHORIZATION_REVOKED', + }; } -export function importAuthorization(authorization){ - return { - type: 'GENIUS_IMPORT_AUTHORIZATION', - authorization: authorization - } +export function importAuthorization(authorization) { + return { + type: 'GENIUS_IMPORT_AUTHORIZATION', + authorization, + }; } /** * Get current user - **/ -export function getMe(){ - return (dispatch, getState) => { - sendRequest(dispatch, getState, 'account') - .then( - response => { - dispatch({ - type: 'GENIUS_ME_LOADED', - me: response.user - }); - }, - error => { - dispatch(coreActions.handleException( - 'Could not load your Genius profile', - error - )); - } - ); - } + * */ +export function getMe() { + return (dispatch, getState) => { + sendRequest(dispatch, getState, 'account') + .then( + (response) => { + dispatch({ + type: 'GENIUS_ME_LOADED', + me: response.user, + }); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load your Genius profile', + error, + )); + }, + ); + }; } /** @@ -136,108 +133,104 @@ export function getMe(){ * * @param uri = track uri * @param path = String, the relative API path for the HTML lyrics - **/ -export function getTrackLyrics(uri, path){ - return (dispatch, getState) => { + * */ +export function getTrackLyrics(uri, path) { + return (dispatch, getState) => { + dispatch(coreActions.trackLoaded({ + uri, + lyrics: null, + lyrics_path: null, + })); - dispatch(coreActions.trackLoaded({ - uri: uri, - lyrics: null, - lyrics_path: null - })); + const config = { + method: 'GET', + url: `//${getState().mopidy.host}:${getState().mopidy.port}/iris/http/get_lyrics?path=${path}&connection_id=${getState().pusher.connection_id}`, + timeout: 10000, + }; - var config = { - method: 'GET', - url: '//'+getState().mopidy.host+':'+getState().mopidy.port+'/iris/http/get_lyrics?path='+path+'&connection_id='+getState().pusher.connection_id, - timeout: 10000 - } + // add reference to loader queue + const loader_key = helpers.generateGuid(); + dispatch(uiActions.startLoading(loader_key, 'genius_get_lyrics')); - // add reference to loader queue - var loader_key = helpers.generateGuid(); - dispatch(uiActions.startLoading(loader_key, 'genius_get_lyrics')); - - $.ajax(config) - .then( - (response, status, xhr) => { - dispatch(uiActions.stopLoading(loader_key)); - - if (response && response.result){ - var html = $(response.result); - var lyrics = html.find('.lyrics'); - if (lyrics.length > 0){ + $.ajax(config) + .then( + (response, status, xhr) => { + dispatch(uiActions.stopLoading(loader_key)); + if (response && response.result) { + const html = $(response.result); + let lyrics = html.find('.lyrics'); + if (lyrics.length > 0) { lyrics = lyrics.first(); - lyrics.find('a').replaceWith(function(){ + lyrics.find('a').replaceWith(function () { return this.innerHTML; }); - var lyrics_html = lyrics.html(); + let lyrics_html = lyrics.html(); lyrics_html = lyrics_html.replace(/(\[)/g, '['); lyrics_html = lyrics_html.replace(/(\])/g, ']'); dispatch(coreActions.trackLoaded({ - uri: uri, - lyrics: lyrics_html, - lyrics_path: path - })); + uri, + lyrics: lyrics_html, + lyrics_path: path, + })); } - } else { + } else { dispatch(coreActions.handleException( 'Could not get track lyrics', - response.error + response.error, )); - } - - }, - (xhr, status, error) => { - dispatch(uiActions.stopLoading(loader_key)); - dispatch(coreActions.handleException( - 'Could not get track lyrics', - error - )); - } - ); - } + } + }, + (xhr, status, error) => { + dispatch(uiActions.stopLoading(loader_key)); + dispatch(coreActions.handleException( + 'Could not get track lyrics', + error, + )); + }, + ); + }; } -export function findTrackLyrics(track){ - return (dispatch, getState) => { - - var query = ''; - query += track.artists[0].name+' '; - query += track.name; - query = query.toLowerCase(); - query = query.replace(/\([^)]*\) */g, ''); // anything in circle-braces - query = query.replace(/\([^[]*\] */g, ''); // anything in square-braces +export function findTrackLyrics(track) { + return (dispatch, getState) => { + let query = ''; + query += `${track.artists[0].name} `; + query += track.name; + query = query.toLowerCase(); + query = query.replace(/\([^)]*\) */g, ''); // anything in circle-braces + query = query.replace(/\([^[]*\] */g, ''); // anything in square-braces - sendRequest(dispatch, getState, 'search', 'GET', 'q='+encodeURIComponent(query)) - .then( - response => { - if (response.hits && response.hits.length > 0){ - var lyrics_results = []; - for (var i = 0; i < response.hits.length; i++){ - lyrics_results.push({ - title: response.hits[i].result.full_title, - url: response.hits[i].result.url, - path: response.hits[i].result.path - }); - } - dispatch(coreActions.trackLoaded({ - uri: track.uri, - lyrics_results: lyrics_results - })); + sendRequest(dispatch, getState, 'search', 'GET', `q=${encodeURIComponent(query)}`) + .then( + (response) => { + if (response.hits && response.hits.length > 0) { + const lyrics_results = []; + for (let i = 0; i < response.hits.length; i++) { + lyrics_results.push({ + title: response.hits[i].result.full_title, + url: response.hits[i].result.url, + path: response.hits[i].result.path, + }); + } + dispatch(coreActions.trackLoaded({ + uri: track.uri, + lyrics_results, + })); - // Immediately go and get the first result's lyrics - var lyrics_result = lyrics_results[0]; - dispatch(getTrackLyrics(track.uri, lyrics_result.path)); - } - }, - error => { - dispatch(coreActions.handleException( - 'Could not search for track lyrics', - error - )); - } - ); - } + // Immediately go and get the first result's lyrics + const lyrics_result = lyrics_results[0]; + dispatch(getTrackLyrics(track.uri, lyrics_result.path)); + } + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not search for track lyrics', + error, + )); + }, + ); + }; } diff --git a/src/js/services/genius/middleware.js b/src/js/services/genius/middleware.js index 6fd3caed..7bf642f1 100755 --- a/src/js/services/genius/middleware.js +++ b/src/js/services/genius/middleware.js @@ -1,59 +1,58 @@ -var helpers = require('./../../helpers'); -var coreActions = require('../core/actions'); -var geniusActions = require('./actions'); +const helpers = require('./../../helpers'); +const coreActions = require('../core/actions'); +const geniusActions = require('./actions'); -const GeniusMiddleware = (function(){ - return store => next => action => { - var state = store.getState(); +const GeniusMiddleware = (function () { + return (store) => (next) => (action) => { + const state = store.getState(); - switch(action.type){ - - case 'GENIUS_ME_LOADED': - var me = helpers.formatUser(action.me); - Object.assign( - me, - { - uri: "genius:user:"+me.id - } - ); + switch (action.type) { + case 'GENIUS_ME_LOADED': + var me = helpers.formatUser(action.me); + Object.assign( + me, + { + uri: `genius:user:${me.id}`, + }, + ); store.dispatch({ type: 'GENIUS_USER_LOADED', - user: action.me + user: action.me, }); - action.me = me; + action.me = me; next(action); break; - case 'GENIUS_USER_LOADED': - var user = helpers.formatUser(action.user); - Object.assign( - user, - { - uri: "genius:user:"+user.id - } - ); - store.dispatch(coreActions.userLoaded(user)); - action.user = user; - next(action); - break; + case 'GENIUS_USER_LOADED': + var user = helpers.formatUser(action.user); + Object.assign( + user, + { + uri: `genius:user:${user.id}`, + }, + ); + store.dispatch(coreActions.userLoaded(user)); + action.user = user; + next(action); + break; - case 'GENIUS_IMPORT_AUTHORIZATION': + case 'GENIUS_IMPORT_AUTHORIZATION': - // Wait a few moments before we fetch, allowing the import to complete first - // TODO: Use callbacks for better code accuracy - setTimeout(() => {store.dispatch(geniusActions.getMe())}, 100); + // Wait a few moments before we fetch, allowing the import to complete first + // TODO: Use callbacks for better code accuracy + setTimeout(() => { store.dispatch(geniusActions.getMe()); }, 100); - next(action); - break; + next(action); + break; - // This action is irrelevant to us, pass it on to the next middleware - default: - return next(action); - } + // This action is irrelevant to us, pass it on to the next middleware + default: + return next(action); } -})(); + }; +}()); -export default GeniusMiddleware \ No newline at end of file +export default GeniusMiddleware; diff --git a/src/js/services/genius/reducer.js b/src/js/services/genius/reducer.js index 31c1ec64..87e2a7db 100755 --- a/src/js/services/genius/reducer.js +++ b/src/js/services/genius/reducer.js @@ -1,42 +1,42 @@ -export default function reducer(genius = {}, action){ - switch (action.type){ +export default function reducer(genius = {}, action) { + switch (action.type) { + case 'GENIUS_SET': + return { ...genius, ...action.data }; - case 'GENIUS_SET': - return Object.assign({}, genius, action.data) + case 'GENIUS_ME_LOADED': + return { ...genius, me: action.me }; - case 'GENIUS_ME_LOADED': - return Object.assign({}, genius, { - me: action.me - }) + case 'GENIUS_AUTHORIZATION_GRANTED': + return { + ...genius, + authorizing: false, + authorization: action.data, + authorization_code: action.data.authorization_code, + access_token: action.data.access_token, + }; - case 'GENIUS_AUTHORIZATION_GRANTED': - return Object.assign({}, genius, { - authorizing: false, - authorization: action.data, - authorization_code: action.data.authorization_code, - access_token: action.data.access_token - }) + case 'GENIUS_AUTHORIZATION_REVOKED': + return { + ...genius, + authorizing: false, + authorization: null, + authorization_code: null, + access_token: null, + me: null, + }; - case 'GENIUS_AUTHORIZATION_REVOKED': - return Object.assign({}, genius, { - authorizing: false, - authorization: null, - authorization_code: null, - access_token: null, - me: null - }); + case 'GENIUS_IMPORT_AUTHORIZATION': + return { + ...genius, + authorizing: false, + authorization: action.authorization, + authorization_code: action.authorization.authorization_code, + access_token: action.authorization.access_token, + me: null, + }; - case 'GENIUS_IMPORT_AUTHORIZATION': - return Object.assign({}, genius, { - authorizing: false, - authorization: action.authorization, - authorization_code: action.authorization.authorization_code, - access_token: action.authorization.access_token, - me: null - }) - - default: - return genius - } + default: + return genius; + } } diff --git a/src/js/services/google/actions.js b/src/js/services/google/actions.js index a8e7fd4d..d00ab52d 100755 --- a/src/js/services/google/actions.js +++ b/src/js/services/google/actions.js @@ -1,31 +1,31 @@ -export function set(data){ - return { - type: 'GOOGLE_SET', - data: data - } +export function set(data) { + return { + type: 'GOOGLE_SET', + data, + }; } -export function getLibraryArtists(){ - return { - type: 'GOOGLE_GET_LIBRARY_ARTISTS' - } +export function getLibraryArtists() { + return { + type: 'GOOGLE_GET_LIBRARY_ARTISTS', + }; } -export function clearLibraryArtists(){ - return { - type: 'GOOGLE_CLEAR_LIBRARY_ARTISTS' - } +export function clearLibraryArtists() { + return { + type: 'GOOGLE_CLEAR_LIBRARY_ARTISTS', + }; } -export function getLibraryAlbums(){ - return { - 'type': 'GOOGLE_GET_LIBRARY_ALBUMS' - } +export function getLibraryAlbums() { + return { + type: 'GOOGLE_GET_LIBRARY_ALBUMS', + }; } -export function clearLibraryAlbums(){ - return { - type: 'GOOGLE_CLEAR_LIBRARY_ALBUMS' - } -} \ No newline at end of file +export function clearLibraryAlbums() { + return { + type: 'GOOGLE_CLEAR_LIBRARY_ALBUMS', + }; +} diff --git a/src/js/services/google/middleware.js b/src/js/services/google/middleware.js index 10755c2a..42c02039 100755 --- a/src/js/services/google/middleware.js +++ b/src/js/services/google/middleware.js @@ -1,145 +1,140 @@ -import ReactGA from 'react-ga' +import ReactGA from 'react-ga'; -var helpers = require('../../helpers'); -var coreActions = require('../core/actions'); -var mopidyActions = require('../mopidy/actions'); -var uiActions = require('../ui/actions'); +const helpers = require('../../helpers'); +const coreActions = require('../core/actions'); +const mopidyActions = require('../mopidy/actions'); +const uiActions = require('../ui/actions'); -const GoogleMiddleware = (function(){ - - // A Google request is an alias of the Mopidy request - const request = (store, method, params = null, response_callback = null, error_callback = null) => { - store.dispatch( +const GoogleMiddleware = (function () { + // A Google request is an alias of the Mopidy request + const request = (store, method, params = null, response_callback = null, error_callback = null) => { + store.dispatch( mopidyActions.request( method, params, response_callback, - error_callback - ) - ); - return; - } + error_callback, + ), + ); + }; - return store => next => action => { - var google = store.getState().google; + return (store) => (next) => (action) => { + const { google } = store.getState(); - switch(action.type){ - case 'GOOGLE_GET_LIBRARY_ALBUMS': - var last_run = store.getState().ui.processes.GOOGLE_LIBRARY_ALBUMS_PROCESSOR + switch (action.type) { + case 'GOOGLE_GET_LIBRARY_ALBUMS': + var last_run = store.getState().ui.processes.GOOGLE_LIBRARY_ALBUMS_PROCESSOR; - if (!last_run){ - request( - store, - 'library.browse', - { - uri: 'gmusic:album' - }, - response => { - if (response.length <= 0){ - return; - } + if (!last_run) { + request( + store, + 'library.browse', + { + uri: 'gmusic:album', + }, + (response) => { + if (response.length <= 0) { + return; + } - var uris = helpers.arrayOf('uri',response); - store.dispatch({ - type: 'GOOGLE_LIBRARY_ALBUMS_LOADED', - uris: uris - }); + const uris = helpers.arrayOf('uri', response); + store.dispatch({ + type: 'GOOGLE_LIBRARY_ALBUMS_LOADED', + uris, + }); - // Start our process to load the full album objects - store.dispatch(uiActions.startProcess( - 'GOOGLE_LIBRARY_ALBUMS_PROCESSOR', - 'Loading '+uris.length+' Google albums', - { - uris: uris, - total: uris.length, - remaining: uris.length - } - )); - } - ); - - } else if (last_run.status == 'cancelled'){ - store.dispatch(uiActions.resumeProcess('GOOGLE_LIBRARY_ALBUMS_PROCESSOR')) - } else if (last_run.status == 'finished'){ - // TODO: do we want to force a refresh? - } - - break; - - case 'GOOGLE_LIBRARY_ALBUMS_PROCESSOR': - if (store.getState().ui.processes['GOOGLE_LIBRARY_ALBUMS_PROCESSOR'] !== undefined){ - var processor = store.getState().ui.processes['GOOGLE_LIBRARY_ALBUMS_PROCESSOR'] - - if (processor.status == 'cancelling'){ - store.dispatch(uiActions.processCancelled('GOOGLE_LIBRARY_ALBUMS_PROCESSOR')) - return false - } - } - - var uris = Object.assign([], action.data.uris); - var uris_to_load = uris.splice(0,50); - - if (uris_to_load.length > 0){ - store.dispatch(uiActions.updateProcess( - 'GOOGLE_LIBRARY_ALBUMS_PROCESSOR', - 'Loading '+uris.length+' Google albums', - { - uris: uris, - remaining: uris.length - } - )); - store.dispatch(mopidyActions.getAlbums(uris_to_load, {name: 'GOOGLE_LIBRARY_ALBUMS_PROCESSOR', data: {uris: uris}})) - } else { - store.dispatch(uiActions.processFinishing('GOOGLE_LIBRARY_ALBUMS_PROCESSOR')); - } - - break - - case 'GOOGLE_GET_LIBRARY_ARTISTS': - request( - store, - 'library.browse', - { - uri: 'gmusic:artist' - }, - response => { - if (response.length <= 0){ - return - } - - var uris = []; - for (var i = 0; i < response.length; i++){ - - // Convert local URI to actual artist URI - // See https://github.com/mopidy/mopidy-local-sqlite/issues/39 - response[i].uri = response[i].uri.replace('local:directory?albumartist=',''); - uris.push(response[i].uri); - } - - store.dispatch(coreActions.artistsLoaded(response)); - - store.dispatch({ - type: 'GOOGLE_LIBRARY_ARTISTS_LOADED', - uris: uris - }); - }, - error => { - store.dispatch(coreActions.handleException( - 'Could not load Google artists', - error - )); - } - ); - break; - - - // This action is irrelevant to us, pass it on to the next middleware - default: - return next(action); + // Start our process to load the full album objects + store.dispatch(uiActions.startProcess( + 'GOOGLE_LIBRARY_ALBUMS_PROCESSOR', + `Loading ${uris.length} Google albums`, + { + uris, + total: uris.length, + remaining: uris.length, + }, + )); + }, + ); + } else if (last_run.status == 'cancelled') { + store.dispatch(uiActions.resumeProcess('GOOGLE_LIBRARY_ALBUMS_PROCESSOR')); + } else if (last_run.status == 'finished') { + // TODO: do we want to force a refresh? } + + break; + + case 'GOOGLE_LIBRARY_ALBUMS_PROCESSOR': + if (store.getState().ui.processes.GOOGLE_LIBRARY_ALBUMS_PROCESSOR !== undefined) { + const processor = store.getState().ui.processes.GOOGLE_LIBRARY_ALBUMS_PROCESSOR; + + if (processor.status == 'cancelling') { + store.dispatch(uiActions.processCancelled('GOOGLE_LIBRARY_ALBUMS_PROCESSOR')); + return false; + } + } + + var uris = Object.assign([], action.data.uris); + var uris_to_load = uris.splice(0, 50); + + if (uris_to_load.length > 0) { + store.dispatch(uiActions.updateProcess( + 'GOOGLE_LIBRARY_ALBUMS_PROCESSOR', + `Loading ${uris.length} Google albums`, + { + uris, + remaining: uris.length, + }, + )); + store.dispatch(mopidyActions.getAlbums(uris_to_load, { name: 'GOOGLE_LIBRARY_ALBUMS_PROCESSOR', data: { uris } })); + } else { + store.dispatch(uiActions.processFinishing('GOOGLE_LIBRARY_ALBUMS_PROCESSOR')); + } + + break; + + case 'GOOGLE_GET_LIBRARY_ARTISTS': + request( + store, + 'library.browse', + { + uri: 'gmusic:artist', + }, + (response) => { + if (response.length <= 0) { + return; + } + + const uris = []; + for (let i = 0; i < response.length; i++) { + // Convert local URI to actual artist URI + // See https://github.com/mopidy/mopidy-local-sqlite/issues/39 + response[i].uri = response[i].uri.replace('local:directory?albumartist=', ''); + uris.push(response[i].uri); + } + + store.dispatch(coreActions.artistsLoaded(response)); + + store.dispatch({ + type: 'GOOGLE_LIBRARY_ARTISTS_LOADED', + uris, + }); + }, + (error) => { + store.dispatch(coreActions.handleException( + 'Could not load Google artists', + error, + )); + }, + ); + break; + + + // This action is irrelevant to us, pass it on to the next middleware + default: + return next(action); } + }; +}()); -})(); - -export default GoogleMiddleware +export default GoogleMiddleware; diff --git a/src/js/services/google/reducer.js b/src/js/services/google/reducer.js index d4bd6aee..c83a079b 100755 --- a/src/js/services/google/reducer.js +++ b/src/js/services/google/reducer.js @@ -1,38 +1,34 @@ import * as helpers from '../../helpers'; -export default function reducer(google = {}, action){ - switch (action.type){ +export default function reducer(google = {}, action) { + switch (action.type) { + case 'GOOGLE_SET': + return { ...google, ...action.data }; - case 'GOOGLE_SET': - return Object.assign({}, google, action.data); + case 'GOOGLE_LIBRARY_ARTISTS_LOADED': + if (google.library_artists) { + var uris = [...google.library_artists, ...action.uris]; + } else { + var { uris } = action; + } + return { ...google, library_artists: helpers.removeDuplicates(uris) }; - case 'GOOGLE_LIBRARY_ARTISTS_LOADED': - if (google.library_artists){ - var uris = [...google.library_artists,...action.uris]; - } else { - var uris = action.uris; - } - return Object.assign({}, google, { library_artists: helpers.removeDuplicates(uris) }); + case 'GOOGLE_CLEAR_LIBRARY_ARTISTS': + return { ...google, library_artists: null }; - case 'GOOGLE_CLEAR_LIBRARY_ARTISTS': - return Object.assign({}, google, { library_artists: null }); + case 'GOOGLE_LIBRARY_ALBUMS_LOADED': + if (google.library_albums) { + var uris = [...google.library_albums, ...action.uris]; + } else { + var { uris } = action; + } + return { ...google, library_albums: helpers.removeDuplicates(uris) }; - case 'GOOGLE_LIBRARY_ALBUMS_LOADED': - if (google.library_albums){ - var uris = [...google.library_albums,...action.uris]; - } else { - var uris = action.uris; - } - return Object.assign({}, google, { library_albums: helpers.removeDuplicates(uris) }); + case 'GOOGLE_CLEAR_LIBRARY_ALBUMS': + return { ...google, library_albums: null }; - case 'GOOGLE_CLEAR_LIBRARY_ALBUMS': - return Object.assign({}, google, { library_albums: null }); - - default: - return google - } + default: + return google; + } } - - - diff --git a/src/js/services/lastfm/actions.js b/src/js/services/lastfm/actions.js index 91b03bc3..d62622f7 100755 --- a/src/js/services/lastfm/actions.js +++ b/src/js/services/lastfm/actions.js @@ -1,13 +1,13 @@ -var coreActions = require('../core/actions'); -var uiActions = require('../ui/actions'); -var helpers = require('../../helpers'); +const coreActions = require('../core/actions'); +const uiActions = require('../ui/actions'); +const helpers = require('../../helpers'); -export function set(data){ - return { - type: 'LASTFM_SET', - data: data - } +export function set(data) { + return { + type: 'LASTFM_SET', + data, + }; } /** @@ -17,62 +17,59 @@ export function set(data){ * @param getState = obj * @param params = string, the url params to send * @params signed = boolean, whether we've got a signed request with baked-in api_key - **/ -var sendRequest = (dispatch, getState, params, signed = false) => { - return new Promise((resolve, reject) => { + * */ +const sendRequest = (dispatch, getState, params, signed = false) => new Promise((resolve, reject) => { + const loader_key = helpers.generateGuid(); + let method = params.substring(params.indexOf('method=') + 7, params.length); + method = method.substring(0, method.indexOf('&')); - var loader_key = helpers.generateGuid(); - var method = params.substring(params.indexOf("method=")+7, params.length); - method = method.substring(0, method.indexOf("&")); + dispatch(uiActions.startLoading(loader_key, `lastfm_${method}`)); - dispatch(uiActions.startLoading(loader_key, 'lastfm_'+method)); + const config = { + method: 'GET', + cache: true, + timeout: 30000, + url: `https://ws.audioscrobbler.com/2.0/?format=json&${params}`, + }; - var config = { - method: 'GET', - cache: true, - timeout: 30000, - url: 'https://ws.audioscrobbler.com/2.0/?format=json&'+params - } + // Signed requests don't need our api_key as the proxy has it's own + if (!signed) { + config.url += '&api_key=4320a3ef51c9b3d69de552ac083c55e3'; + } else { + config.method = 'POST'; + } - // Signed requests don't need our api_key as the proxy has it's own - if (!signed){ - config.url += '&api_key=4320a3ef51c9b3d69de552ac083c55e3'; - } else { - config.method = 'POST'; - } + $.ajax(config).then( + (response) => { + dispatch(uiActions.stopLoading(loader_key)); + if (response.error) { + reject({ + config, + error: response, + }); + } else { + resolve(response); + } + }, + (xhr, status, error) => { + dispatch(uiActions.stopLoading(loader_key)); - $.ajax(config).then( - response => { - dispatch(uiActions.stopLoading(loader_key)); - if (response.error){ - reject({ - config: config, - error: response - }); - } else { - resolve(response); - } - }, - (xhr, status, error) => { - dispatch(uiActions.stopLoading(loader_key)); + // Snatch a more meaningful error + let description = null; + if (xhr && xhr.responseJSON && xhr.responseJSON.message) { + description = xhr.responseJSON.message; + } - // Snatch a more meaningful error - var description = null; - if (xhr && xhr.responseJSON && xhr.responseJSON.message){ - description = xhr.responseJSON.message; - } - - reject({ - config: config, - error: error, - description: description, - status: status, - xhr: xhr - }); - } - ) - }) -} + reject({ + config, + error, + description, + status, + xhr, + }); + }, + ); +}); /** * Send a SIGNED ajax request to the LastFM API @@ -81,419 +78,400 @@ var sendRequest = (dispatch, getState, params, signed = false) => { * @param getState = obj * @param params = string, the url params to send * @param signed = boolean - **/ -const sendSignedRequest = (dispatch, getState, params) => { - return new Promise((resolve, reject) => { + * */ +const sendSignedRequest = (dispatch, getState, params) => new Promise((resolve, reject) => { + // Not authorized + if (!getState().lastfm.authorization) { + reject({ + params, + error: 'No active LastFM authorization (session)', + }); + } - // Not authorized - if (!getState().lastfm.authorization){ - reject({ - params: params, - error: "No active LastFM authorization (session)" - }); - } + const loader_key = helpers.generateGuid(); + let method = params.substring(params.indexOf('method=') + 7, params.length); + method = method.substring(0, method.indexOf('&')); - var loader_key = helpers.generateGuid(); - var method = params.substring(params.indexOf("method=")+7, params.length); - method = method.substring(0, method.indexOf("&")); + dispatch(uiActions.startLoading(loader_key, `lastfm_${method}`)); - dispatch(uiActions.startLoading(loader_key, 'lastfm_'+method)); + params += `&sk=${getState().lastfm.authorization.key}`; - params += "&sk="+getState().lastfm.authorization.key; + const config = { + method: 'GET', + cache: false, + timeout: 30000, + url: `${getState().lastfm.authorization_url}?action=sign_request&${params}`, + }; - var config = { - method: 'GET', - cache: false, - timeout: 30000, - url: getState().lastfm.authorization_url+"?action=sign_request&"+params - } + // Get our server proxy to sign our request + $.ajax(config).then( + (response) => { + dispatch(uiActions.stopLoading(loader_key)); - // Get our server proxy to sign our request - $.ajax(config).then( - response => { - dispatch(uiActions.stopLoading(loader_key)); - - // Now we have signed params, we can make the actual request - sendRequest(dispatch, getState, response.params, true) - .then( - response => { - resolve(response); - }, - error => { - reject(error); - } - ); - }, - (xhr, status, error) => { - dispatch(uiActions.stopLoading(loader_key)); - reject(error) - } + // Now we have signed params, we can make the actual request + sendRequest(dispatch, getState, response.params, true) + .then( + (response) => { + resolve(response); + }, + (error) => { + reject(error); + }, ); - }) -} + }, + (xhr, status, error) => { + dispatch(uiActions.stopLoading(loader_key)); + reject(error); + }, + ); +}); /** * Handle authorization process - **/ + * */ -export function authorizationGranted(data){ - data.session.expiry = new Date().getTime() + 3600; - return { - type: 'LASTFM_AUTHORIZATION_GRANTED', - data: data - } +export function authorizationGranted(data) { + data.session.expiry = new Date().getTime() + 3600; + return { + type: 'LASTFM_AUTHORIZATION_GRANTED', + data, + }; } -export function revokeAuthorization(){ - return { - type: 'LASTFM_AUTHORIZATION_REVOKED' - } +export function revokeAuthorization() { + return { + type: 'LASTFM_AUTHORIZATION_REVOKED', + }; } -export function importAuthorization(authorization){ - return { - type: 'LASTFM_IMPORT_AUTHORIZATION', - authorization: authorization - } +export function importAuthorization(authorization) { + return { + type: 'LASTFM_IMPORT_AUTHORIZATION', + authorization, + }; } - /** * Non-signed requests - **/ + * */ -export function getMe(){ - return (dispatch, getState) => { - var params = 'method=user.getInfo&user='+getState().lastfm.authorization.name - sendRequest(dispatch, getState, params) - .then( - response => { - if (response.user){ - dispatch({ - type: 'LASTFM_ME_LOADED', - me: response.user - }); - } - }, - error => { - dispatch(coreActions.handleException( - "Could not get your LastFM profile", - error - )); - } - ) - } +export function getMe() { + return (dispatch, getState) => { + const params = `method=user.getInfo&user=${getState().lastfm.authorization.name}`; + sendRequest(dispatch, getState, params) + .then( + (response) => { + if (response.user) { + dispatch({ + type: 'LASTFM_ME_LOADED', + me: response.user, + }); + } + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not get your LastFM profile', + error, + )); + }, + ); + }; } -export function getTrack(uri){ - return (dispatch, getState) => { - if (getState().core.tracks[uri] !== undefined){ - var track = getState().core.tracks[uri]; - if (!track.artists){ - dispatch(coreActions.handleException( - "Could not get LastFM track", - {}, - "Track has no artists" - )); - return; +export function getTrack(uri) { + return (dispatch, getState) => { + if (getState().core.tracks[uri] !== undefined) { + var track = getState().core.tracks[uri]; + if (!track.artists) { + dispatch(coreActions.handleException( + 'Could not get LastFM track', + {}, + 'Track has no artists', + )); + return; + } + } else { + dispatch(coreActions.handleException( + 'Could not get LastFM track', + {}, + 'Could not find track in index', + )); + return; + } + + const track_name = track.name; + const artist_name = encodeURIComponent(track.artists[0].name); + let params = `method=track.getInfo&track=${track_name}&artist=${artist_name}`; + if (getState().lastfm.authorization) { + params += `&username=${getState().lastfm.authorization.name}`; + } + sendRequest(dispatch, getState, params) + .then( + (response) => { + if (response.track) { + const merged_track = { + + uri: track.uri, + ...response.track, + ...track, + }; + dispatch(coreActions.trackLoaded(merged_track)); + } + }, + (error) => { + console.info(`LastFM: No results for track '${track_name}' by '${artist_name}'`); + }, + ); + }; +} + +export function getArtist(uri, artist, mbid = false) { + return (dispatch, getState) => { + if (mbid) { + var params = `method=artist.getInfo&mbid=${mbid}`; + } else { + artist = artist.replace('&', 'and'); + artist = encodeURIComponent(artist); + var params = `method=artist.getInfo&artist=${artist}`; + } + sendRequest(dispatch, getState, params) + .then( + (response) => { + if (response.artist) { + const artist = { + uri, + mbid: response.artist.mbid, + biography: response.artist.bio.content, + biography_publish_date: response.artist.bio.published, + biography_link: response.artist.bio.links.link.href, + listeners: parseInt(response.artist.stats.listeners), + }; + + dispatch(coreActions.artistLoaded(artist)); + } + }, + (error) => { + console.info(`LastFM: No results for artist '${artist}'`); + }, + ); + }; +} + +export function getAlbum(uri, artist, album, mbid = false) { + return (dispatch, getState) => { + if (mbid) { + var params = `method=album.getInfo&mbid=${mbid}`; + } else { + artist = encodeURIComponent(artist); + album = encodeURIComponent(album); + var params = `method=album.getInfo&album=${album}&artist=${artist}`; + } + sendRequest(dispatch, getState, params) + .then( + (response) => { + if (response.album) { + const existing_album = getState().core.albums[uri]; + + const album = { + uri, + images: response.album.image, + listeners: parseInt(response.album.listeners), + play_count: parseInt(response.album.playcount), + mbid: response.album.mbid, + wiki: (response.album.wiki ? response.album.wiki.content : null), + wiki_publish_date: (response.album.wiki ? response.album.wiki.published : null), + }; + + // If we've already got some of this album and it has images aready, don't use our ones. + // In *most* cases this existing image will be perfectly suffice. This prevents an ugly + // flicker when the existing image is replaced by the LastFM one + if (existing_album && existing_album.images) { + delete album.images; } - } else { - dispatch(coreActions.handleException( - "Could not get LastFM track", - {}, - "Could not find track in index" - )); - return; - } - var track_name = track.name; - var artist_name = encodeURIComponent(track.artists[0].name); - var params = 'method=track.getInfo&track='+track_name+'&artist='+artist_name; - if (getState().lastfm.authorization){ - params += '&username='+getState().lastfm.authorization.name; - } - sendRequest(dispatch, getState, params) - .then( - response => { - if (response.track){ - var merged_track = Object.assign( - {}, - { - uri: track.uri - }, - response.track, - track - ); - dispatch(coreActions.trackLoaded(merged_track)); - } + dispatch(coreActions.albumLoaded(album)); + } + }, + (error) => { + console.info(`LastFM: No results for album '${album}'`); + }, + ); + }; +} + + +export function getImages(context, uri) { + return (dispatch, getState) => { + let record = getState().core[context][uri]; + if (record) { + switch (context) { + case 'tracks': + + if (record.mbid) { + var params = `method=album.getInfo&mbid=${record.mbid}`; + } else if (record.artists && record.artists.length > 0 && record.album) { + var artist = encodeURIComponent(record.artists[0].name); + var album = encodeURIComponent(record.album.name); + var params = `method=album.getInfo&album=${album}&artist=${artist}`; + } + + if (params) { + sendRequest(dispatch, getState, params) + .then( + (response) => { + if (response.album) { + const images = helpers.formatImages(response.album.image); + dispatch(coreActions.trackLoaded({ uri, images })); + } }, - error => { - console.info("LastFM: No results for track '"+track_name+"' by '"+artist_name+"'"); - } - ) - } -} + ); + } + break; -export function getArtist(uri, artist, mbid = false){ + case 'albums': - return (dispatch, getState) => { - if (mbid){ - var params = 'method=artist.getInfo&mbid='+mbid - } else { - artist = artist.replace("&","and"); - artist = encodeURIComponent(artist); - var params = 'method=artist.getInfo&artist='+artist - } - sendRequest(dispatch, getState, params) - .then( - response => { - if (response.artist){ - var artist = { - uri: uri, - mbid: response.artist.mbid, - biography: response.artist.bio.content, - biography_publish_date: response.artist.bio.published, - biography_link: response.artist.bio.links.link.href, - listeners: parseInt(response.artist.stats.listeners) - }; + if (record.mbid) { + var params = `method=album.getInfo&mbid=${record.mbid}`; + } else if (record.artists && record.artists.length > 0) { + var artist = encodeURIComponent(record.artists[0].name); + var album = encodeURIComponent(record.name); + var params = `method=album.getInfo&album=${album}&artist=${artist}`; + } - dispatch(coreActions.artistLoaded(artist)); - } + if (params) { + sendRequest(dispatch, getState, params) + .then( + (response) => { + if (response.album) { + record = { ...record, images: response.album.image }; + dispatch(coreActions.albumLoaded(record)); + } }, - error => { - console.info("LastFM: No results for artist '"+artist+"'"); - } - ) + ); + } + break; + } } + }; } -export function getAlbum(uri, artist, album, mbid = false){ - return (dispatch, getState) => { - if (mbid){ - var params = 'method=album.getInfo&mbid='+mbid; - } else { - artist = encodeURIComponent(artist); - album = encodeURIComponent(album); - var params = 'method=album.getInfo&album='+album+'&artist='+artist; - } - sendRequest(dispatch, getState, params) - .then( - response => { - if (response.album){ - - var existing_album = getState().core.albums[uri]; - - var album = { - uri: uri, - images: response.album.image, - listeners: parseInt(response.album.listeners), - play_count: parseInt(response.album.playcount), - mbid: response.album.mbid, - wiki: (response.album.wiki ? response.album.wiki.content : null), - wiki_publish_date: (response.album.wiki ? response.album.wiki.published : null), - }; - - // If we've already got some of this album and it has images aready, don't use our ones. - // In *most* cases this existing image will be perfectly suffice. This prevents an ugly - // flicker when the existing image is replaced by the LastFM one - if (existing_album && existing_album.images){ - delete album.images; - } - - dispatch(coreActions.albumLoaded(album)); - } - }, - error => { - console.info("LastFM: No results for album '"+album+"'"); - } - ); - } -} - - -export function getImages(context, uri){ - return (dispatch, getState) => { - - var record = getState().core[context][uri]; - if (record){ - switch (context){ - - case "tracks": - - if (record.mbid){ - var params = 'method=album.getInfo&mbid='+record.mbid; - } else if (record.artists && record.artists.length > 0 && record.album){ - var artist = encodeURIComponent(record.artists[0].name); - var album = encodeURIComponent(record.album.name); - var params = 'method=album.getInfo&album='+album+'&artist='+artist; - } - - if (params){ - sendRequest(dispatch, getState, params) - .then( - response => { - if (response.album){ - let images = helpers.formatImages(response.album.image); - dispatch(coreActions.trackLoaded({uri: uri, images: images})); - } - } - ); - } - break; - - case "albums": - - if (record.mbid){ - var params = 'method=album.getInfo&mbid='+record.mbid; - } else if (record.artists && record.artists.length > 0){ - var artist = encodeURIComponent(record.artists[0].name); - var album = encodeURIComponent(record.name); - var params = 'method=album.getInfo&album='+album+'&artist='+artist; - } - - if (params){ - sendRequest(dispatch, getState, params) - .then( - response => { - if (response.album){ - record = Object.assign({}, record, {images: response.album.image}); - dispatch(coreActions.albumLoaded(record)); - } - } - ); - } - break; - } - } - } -} - - - - - - /** * Signed requests - **/ + * */ -export function loveTrack(uri){ - return (dispatch, getState) => { - if (getState().core.tracks[uri] !== undefined){ - var track = getState().core.tracks[uri]; - if (!track.artists){ - dispatch(coreActions.handleException( - "Could not love LastFM track", - track, - "Track has no artists" - )); - return; - } - } else { - dispatch(coreActions.handleException( - "Could not love LastFM track", - track, - "Could not find track in index" - )); - return; - } - - var artist = encodeURIComponent(track.artists[0].name); - var params = 'method=track.love&track='+track.name+'&artist='+artist; - sendSignedRequest(dispatch, getState, params) - .then( - response => { - track = Object.assign( - {}, - track, - { - userloved: true - } - ); - dispatch({ - type: 'TRACKS_LOADED', - tracks: [track] - }); - } - ); +export function loveTrack(uri) { + return (dispatch, getState) => { + if (getState().core.tracks[uri] !== undefined) { + var track = getState().core.tracks[uri]; + if (!track.artists) { + dispatch(coreActions.handleException( + 'Could not love LastFM track', + track, + 'Track has no artists', + )); + return; + } + } else { + dispatch(coreActions.handleException( + 'Could not love LastFM track', + track, + 'Could not find track in index', + )); + return; } + + const artist = encodeURIComponent(track.artists[0].name); + const params = `method=track.love&track=${track.name}&artist=${artist}`; + sendSignedRequest(dispatch, getState, params) + .then( + (response) => { + track = { + + ...track, + userloved: true, + }; + dispatch({ + type: 'TRACKS_LOADED', + tracks: [track], + }); + }, + ); + }; } -export function unloveTrack(uri){ - return (dispatch, getState) => { - if (getState().core.tracks[uri] !== undefined){ - var track = getState().core.tracks[uri]; - if (!track.artists){ - dispatch(coreActions.handleException( - "Could not unlove LastFM track", - track, - "Track has no artists" - )); - return; - } - } else { - dispatch(coreActions.handleException( - "Could not unlove LastFM track", - track, - "Could not find track in index" - )); - return; - } - - var artist = encodeURIComponent(track.artists[0].name); - var params = 'method=track.unlove&track='+track.name+'&artist='+artist; - sendSignedRequest(dispatch, getState, params) - .then( - response => { - track = Object.assign( - {}, - track, - { - userloved: false - } - ); - dispatch({ - type: 'TRACKS_LOADED', - tracks: [track] - }); - } - ); +export function unloveTrack(uri) { + return (dispatch, getState) => { + if (getState().core.tracks[uri] !== undefined) { + var track = getState().core.tracks[uri]; + if (!track.artists) { + dispatch(coreActions.handleException( + 'Could not unlove LastFM track', + track, + 'Track has no artists', + )); + return; + } + } else { + dispatch(coreActions.handleException( + 'Could not unlove LastFM track', + track, + 'Could not find track in index', + )); + return; } + + const artist = encodeURIComponent(track.artists[0].name); + const params = `method=track.unlove&track=${track.name}&artist=${artist}`; + sendSignedRequest(dispatch, getState, params) + .then( + (response) => { + track = { + + ...track, + userloved: false, + }; + dispatch({ + type: 'TRACKS_LOADED', + tracks: [track], + }); + }, + ); + }; } /** * TODO: Currently scrobbling client-side would result in duplicated scrobbles * if the user was authorized across multiple connections. Ideally this would * be handled server-side. Mopidy-Scrobbler currently achieves this. - **/ -export function scrobble(track){ - return (dispatch, getState) => { - var track_name = track.name; - var artist_name = "Unknown"; - if (track.artists){ - artist_name = track.artists[0].name; - } - var artist_name = encodeURIComponent(artist_name); - - var params = 'method=track.scrobble'; - params += '&track='+track_name+'&artist='+artist_name; - params += '×tamp='+Math.floor(Date.now() / 1000); - - sendSignedRequest(dispatch, getState, params) - .then( - response => { - console.log("Scrobbled", response); - }, - error => { - dispatch(coreActions.handleException( - 'Could not scrobble LastFM track', - error, - (error.description ? error.description : null) - )); - } - ) + * */ +export function scrobble(track) { + return (dispatch, getState) => { + const track_name = track.name; + var artist_name = 'Unknown'; + if (track.artists) { + artist_name = track.artists[0].name; } + var artist_name = encodeURIComponent(artist_name); + + let params = 'method=track.scrobble'; + params += `&track=${track_name}&artist=${artist_name}`; + params += `×tamp=${Math.floor(Date.now() / 1000)}`; + + sendSignedRequest(dispatch, getState, params) + .then( + (response) => { + console.log('Scrobbled', response); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not scrobble LastFM track', + error, + (error.description ? error.description : null), + )); + }, + ); + }; } diff --git a/src/js/services/lastfm/middleware.js b/src/js/services/lastfm/middleware.js index 62a04134..34d5d3e6 100755 --- a/src/js/services/lastfm/middleware.js +++ b/src/js/services/lastfm/middleware.js @@ -1,39 +1,37 @@ -var helpers = require('./../../helpers'); -var coreActions = require('../core/actions'); -var lastfmActions = require('./actions'); +const helpers = require('./../../helpers'); +const coreActions = require('../core/actions'); +const lastfmActions = require('./actions'); -const LastfmMiddleware = (function(){ +const LastfmMiddleware = (function () { + return (store) => (next) => (action) => { + switch (action.type) { + case 'LASTFM_ME_LOADED': + var me = helpers.formatUser(action.me); + Object.assign( + me, + { + uri: `lastfm:user:${me.name}`, + }, + ); + store.dispatch(coreActions.userLoaded(me)); + action.me = me; + next(action); + break; - return store => next => action => { - switch(action.type){ + case 'LASTFM_IMPORT_AUTHORIZATION': - case 'LASTFM_ME_LOADED': - var me = helpers.formatUser(action.me); - Object.assign( - me, - { - uri: "lastfm:user:"+me.name - } - ); - store.dispatch(coreActions.userLoaded(me)); - action.me = me; - next(action); - break; + // Wait a few moments before we fetch, allowing the import to complete first + // TODO: Use callbacks for better code accuracy + setTimeout(() => { store.dispatch(lastfmActions.getMe()); }, 100); - case 'LASTFM_IMPORT_AUTHORIZATION': + next(action); + break; - // Wait a few moments before we fetch, allowing the import to complete first - // TODO: Use callbacks for better code accuracy - setTimeout(() => {store.dispatch(lastfmActions.getMe())}, 100); - - next(action); - break; - - default: - return next(action); - } + default: + return next(action); } -})(); + }; +}()); -export default LastfmMiddleware \ No newline at end of file +export default LastfmMiddleware; diff --git a/src/js/services/lastfm/reducer.js b/src/js/services/lastfm/reducer.js index 0c32c6f0..12e452b2 100755 --- a/src/js/services/lastfm/reducer.js +++ b/src/js/services/lastfm/reducer.js @@ -1,46 +1,43 @@ -export default function reducer(lastfm = {}, action){ - switch (action.type){ +export default function reducer(lastfm = {}, action) { + switch (action.type) { + case 'LASTFM_CONNECT': + case 'LASTFM_CONNECTING': + return { ...lastfm, connected: false, connecting: true }; - case 'LASTFM_CONNECT': - case 'LASTFM_CONNECTING': - return Object.assign({}, lastfm, { connected: false, connecting: true }); + case 'LASTFM_CONNECTED': + return { ...lastfm, connected: true, connecting: false }; - case 'LASTFM_CONNECTED': - return Object.assign({}, lastfm, { connected: true, connecting: false }); + case 'LASTFM_SET': + return { ...lastfm, ...action.data }; - case 'LASTFM_SET': - return Object.assign({}, lastfm, action.data) + case 'LASTFM_ME_LOADED': + return { ...lastfm, me: action.me }; - case 'LASTFM_ME_LOADED': - return Object.assign({}, lastfm, { - me: action.me - }) + case 'LASTFM_AUTHORIZATION_GRANTED': + return { + ...lastfm, + authorizing: false, + authorization: action.data.session, + }; - case 'LASTFM_AUTHORIZATION_GRANTED': - return Object.assign({}, lastfm, { - authorizing: false, - authorization: action.data.session - }) + case 'LASTFM_AUTHORIZATION_REVOKED': + return { + ...lastfm, + authorizing: false, + authorization: false, + me: null, + }; - case 'LASTFM_AUTHORIZATION_REVOKED': - return Object.assign({}, lastfm, { - authorizing: false, - authorization: false, - me: null - }); + case 'LASTFM_IMPORT_AUTHORIZATION': + return { + ...lastfm, + authorizing: false, + authorization: action.authorization, + me: null, + }; - case 'LASTFM_IMPORT_AUTHORIZATION': - return Object.assign({}, lastfm, { - authorizing: false, - authorization: action.authorization, - me: null - }); - - default: - return lastfm - } + default: + return lastfm; + } } - - - diff --git a/src/js/services/localstorage/middleware.js b/src/js/services/localstorage/middleware.js index 6028b8e9..f2221cac 100755 --- a/src/js/services/localstorage/middleware.js +++ b/src/js/services/localstorage/middleware.js @@ -1,271 +1,267 @@ -var helpers = require('../../helpers.js') +const helpers = require('../../helpers.js'); -const localstorageMiddleware = (function(){ - - /** +const localstorageMiddleware = (function () { + /** * The actual middleware inteceptor - **/ - return store => next => action => { + * */ + return (store) => (next) => (action) => { + // proceed as normal first + // this way, any reducers and middleware do their thing BEFORE we store our new state + next(action); - // proceed as normal first - // this way, any reducers and middleware do their thing BEFORE we store our new state - next(action); + // append our state to a global variable. This gives us access to debug the store at any point + window._store = store; - // append our state to a global variable. This gives us access to debug the store at any point - window._store = store + // if debug enabled + if (store.getState().ui.log_actions) { + const ignored_actions = [ + 'START_LOADING', + 'STOP_LOADING', + ]; - // if debug enabled - if (store.getState().ui.log_actions){ + // Show non-ignored actions + if (!ignored_actions.includes(action.type)) { + console.log(action); + } + } - var ignored_actions = [ - 'START_LOADING', - 'STOP_LOADING' - ] + switch (action.type) { + case 'PUSHER_CONNECTED': + helpers.setStorage( + 'pusher', + { + connection_id: action.connection_id, + }, + ); + break; - // Show non-ignored actions - if (!ignored_actions.includes(action.type)){ - console.log(action) - } + case 'PUSHER_SET_PORT': + helpers.setStorage( + 'pusher', + { + port: action.port, + }, + ); + break; + + case 'PUSHER_SET_USERNAME': + helpers.setStorage( + 'pusher', + { + username: action.username, + }, + ); + break; + + case 'MOPIDY_URISCHEMES_FILTERED': + helpers.setStorage( + 'mopidy', + { + uri_schemes: action.data, + }, + ); + break; + + case 'SPOTIFY_AUTHORIZATION_GRANTED': + if (action.authorization !== undefined) { + var { authorization } = action; + } else if (action.data) { + var authorization = action.data; + } + helpers.setStorage( + 'spotify', + { + authorization, + access_token: authorization.access_token, + refresh_token: authorization.refresh_token, + token_expiry: authorization.token_expiry, + }, + ); + break; + + case 'SPOTIFY_IMPORT_AUTHORIZATION': + helpers.setStorage( + 'spotify', + { + authorization: action.authorization, + access_token: action.authorization.access_token, + refresh_token: action.authorization.refresh_token, + token_expiry: action.authorization.token_expiry, + me: null, + }, + ); + break; + + case 'SPOTIFY_AUTHORIZATION_REVOKED': + helpers.setStorage( + 'spotify', + { + authorization: null, + access_token: null, + refresh_token: null, + token_expiry: null, + me: null, + }, + ); + break; + + case 'SPOTIFY_TOKEN_REFRESHED': + helpers.setStorage( + 'spotify', + { + access_token: action.data.access_token, + token_expiry: action.data.token_expiry, + provider: action.provider, + }, + ); + break; + + case 'SPOTIFY_ME_LOADED': + helpers.setStorage( + 'spotify', + { + me: action.me, + }, + ); + break; + + case 'LASTFM_ME_LOADED': + helpers.setStorage( + 'lastfm', + { + me: action.me, + }, + ); + break; + + case 'LASTFM_AUTHORIZATION_GRANTED': + helpers.setStorage( + 'lastfm', + { + authorization: action.data.session, + }, + ); + break; + + case 'LASTFM_AUTHORIZATION_REVOKED': + helpers.setStorage( + 'lastfm', + { + authorization: null, + me: null, + }, + ); + break; + + case 'LASTFM_IMPORT_AUTHORIZATION': + helpers.setStorage( + 'lastfm', + { + authorization: action.authorization, + me: null, + }, + ); + break; + + case 'GENIUS_ME_LOADED': + helpers.setStorage( + 'genius', + { + me: action.me, + }, + ); + break; + + case 'GENIUS_AUTHORIZATION_GRANTED': + helpers.setStorage( + 'genius', + { + authorization: action.data, + authorization_code: action.data.authorization_code, + access_token: action.data.access_token, + }, + ); + break; + + case 'GENIUS_AUTHORIZATION_REVOKED': + helpers.setStorage( + 'genius', + { + me: null, + authorization: null, + authorization_code: null, + access_token: null, + }, + ); + break; + + case 'GENIUS_IMPORT_AUTHORIZATION': + helpers.setStorage( + 'genius', + { + authorization: action.authorization, + authorization_code: action.authorization.authorization_code, + access_token: action.authorization.access_token, + me: null, + }, + ); + break; + + case 'CORE_SET': + helpers.setStorage( + 'core', + action.data, + ); + break; + + case 'UI_SET': + helpers.setStorage( + 'ui', + action.data, + ); + break; + + case 'MOPIDY_SET': + helpers.setStorage( + 'mopidy', + action.data, + ); + break; + + case 'SPOTIFY_SET': + helpers.setStorage( + 'spotify', + action.data, + ); + break; + + case 'SNAPCAST_COMMANDS_UPDATED': + helpers.setStorage( + 'snapcast', + { + commands: action.commands, + }, + ); + break; + + case 'SUPPRESS_BROADCAST': + var ui = helpers.getStorage('ui'); + if (ui.suppressed_broadcasts !== undefined) { + var { suppressed_broadcasts } = ui; + } else { + var suppressed_broadcasts = []; } - switch(action.type){ + suppressed_broadcasts.push(action.key); - case 'PUSHER_CONNECTED': - helpers.setStorage( - 'pusher', - { - connection_id: action.connection_id - } - ); - break; - - case 'PUSHER_SET_PORT': - helpers.setStorage( - 'pusher', - { - port: action.port - } - ); - break; - - case 'PUSHER_SET_USERNAME': - helpers.setStorage( - 'pusher', - { - username: action.username - } - ); - break; - - case 'MOPIDY_URISCHEMES_FILTERED': - helpers.setStorage( - 'mopidy', - { - uri_schemes: action.data - } - ); - break; - - case 'SPOTIFY_AUTHORIZATION_GRANTED': - if (action.authorization !== undefined){ - var authorization = action.authorization; - } else if (action.data){ - var authorization = action.data; - } - helpers.setStorage( - 'spotify', - { - authorization: authorization, - access_token: authorization.access_token, - refresh_token: authorization.refresh_token, - token_expiry: authorization.token_expiry - } - ); - break; - - case 'SPOTIFY_IMPORT_AUTHORIZATION': - helpers.setStorage( - 'spotify', - { - authorization: action.authorization, - access_token: action.authorization.access_token, - refresh_token: action.authorization.refresh_token, - token_expiry: action.authorization.token_expiry, - me: null - } - ); - break; - - case 'SPOTIFY_AUTHORIZATION_REVOKED': - helpers.setStorage( - 'spotify', - { - authorization: null, - access_token: null, - refresh_token: null, - token_expiry: null, - me: null - } - ); - break; - - case 'SPOTIFY_TOKEN_REFRESHED': - helpers.setStorage( - 'spotify', - { - access_token: action.data.access_token, - token_expiry: action.data.token_expiry, - provider: action.provider - } - ); - break; - - case 'SPOTIFY_ME_LOADED': - helpers.setStorage( - 'spotify', - { - me: action.me - } - ); - break; - - case 'LASTFM_ME_LOADED': - helpers.setStorage( - 'lastfm', - { - me: action.me - } - ); - break; - - case 'LASTFM_AUTHORIZATION_GRANTED': - helpers.setStorage( - 'lastfm', - { - authorization: action.data.session - } - ); - break; - - case 'LASTFM_AUTHORIZATION_REVOKED': - helpers.setStorage( - 'lastfm', - { - authorization: null, - me: null - } - ); - break; - - case 'LASTFM_IMPORT_AUTHORIZATION': - helpers.setStorage( - 'lastfm', - { - authorization: action.authorization, - me: null - } - ); - break; - - case 'GENIUS_ME_LOADED': - helpers.setStorage( - 'genius', - { - me: action.me - } - ); - break; - - case 'GENIUS_AUTHORIZATION_GRANTED': - helpers.setStorage( - 'genius', - { - authorization: action.data, - authorization_code: action.data.authorization_code, - access_token: action.data.access_token - } - ); - break; - - case 'GENIUS_AUTHORIZATION_REVOKED': - helpers.setStorage( - 'genius', - { - me: null, - authorization: null, - authorization_code: null, - access_token: null - } - ); - break; - - case 'GENIUS_IMPORT_AUTHORIZATION': - helpers.setStorage( - 'genius', - { - authorization: action.authorization, - authorization_code: action.authorization.authorization_code, - access_token: action.authorization.access_token, - me: null - } - ); - break; - - case 'CORE_SET': - helpers.setStorage( - 'core', - action.data - ); - break - - case 'UI_SET': - helpers.setStorage( - 'ui', - action.data - ); - break - - case 'MOPIDY_SET': - helpers.setStorage( - 'mopidy', - action.data - ); - break; - - case 'SPOTIFY_SET': - helpers.setStorage( - 'spotify', - action.data - ); - break; - - case 'SNAPCAST_COMMANDS_UPDATED': - helpers.setStorage( - 'snapcast', - { - commands: action.commands - } - ); - break; - - case 'SUPPRESS_BROADCAST': - var ui = helpers.getStorage('ui'); - if (ui.suppressed_broadcasts !== undefined){ - var suppressed_broadcasts = ui.suppressed_broadcasts; - } else { - var suppressed_broadcasts = []; - } - - suppressed_broadcasts.push(action.key); - - helpers.setStorage( - 'ui', - { - suppressed_broadcasts: suppressed_broadcasts - } - ); - break + helpers.setStorage( + 'ui', + { + suppressed_broadcasts, + }, + ); + break; /** * Experimental saving of stores to localStorage @@ -293,9 +289,8 @@ const localstorageMiddleware = (function(){ next(action); break; */ - } } + }; +}()); -})(); - -export default localstorageMiddleware +export default localstorageMiddleware; diff --git a/src/js/services/mopidy/actions.js b/src/js/services/mopidy/actions.js index 9b3c2a68..f5dc5c9e 100755 --- a/src/js/services/mopidy/actions.js +++ b/src/js/services/mopidy/actions.js @@ -1,509 +1,506 @@ -import * as helpers from '../../helpers' +import * as helpers from '../../helpers'; -export function set(data){ - return { - type: 'MOPIDY_SET', - data: data - } +export function set(data) { + return { + type: 'MOPIDY_SET', + data, + }; } -export function request(method, params = {}, response_callback = null, error_callback = null){ - return { - type: 'MOPIDY_REQUEST', - method: method, - params: params, - response_callback: response_callback, - error_callback: error_callback - } +export function request(method, params = {}, response_callback = null, error_callback = null) { + return { + type: 'MOPIDY_REQUEST', + method, + params, + response_callback, + error_callback, + }; } -export function connect(){ - return { - type: 'MOPIDY_CONNECT' - } +export function connect() { + return { + type: 'MOPIDY_CONNECT', + }; } -export function connecting(){ - return { - type: 'MOPIDY_CONNECTING' - } +export function connecting() { + return { + type: 'MOPIDY_CONNECTING', + }; } -export function disconnect(){ - return { - type: 'MOPIDY_DISCONNECT' - } +export function disconnect() { + return { + type: 'MOPIDY_DISCONNECT', + }; } -export function debug(call, value){ - return { - type: 'MOPIDY_DEBUG', - call: call, - value: value - } +export function debug(call, value) { + return { + type: 'MOPIDY_DEBUG', + call, + value, + }; } -export function restartStarted(){ - return { - type: 'MOPIDY_RESTART_STARTED' - } +export function restartStarted() { + return { + type: 'MOPIDY_RESTART_STARTED', + }; } -export function restartFinished(){ - return { - type: 'MOPIDY_RESTART_FINISHED' - } +export function restartFinished() { + return { + type: 'MOPIDY_RESTART_FINISHED', + }; } -export function upgradeStarted(){ - return { - type: 'MOPIDY_UPGRADE_STARTED' - } +export function upgradeStarted() { + return { + type: 'MOPIDY_UPGRADE_STARTED', + }; } -export function upgradeFinished(){ - return { - type: 'MOPIDY_UPGRADE_FINISHED' - } +export function upgradeFinished() { + return { + type: 'MOPIDY_UPGRADE_FINISHED', + }; } -export function localScanStarted(){ - return { - type: 'MOPIDY_LOCAL_SCAN_STARTED' - } +export function localScanStarted() { + return { + type: 'MOPIDY_LOCAL_SCAN_STARTED', + }; } -export function localScanFinished(){ - return { - type: 'MOPIDY_LOCAL_SCAN_FINISHED' - } +export function localScanFinished() { + return { + type: 'MOPIDY_LOCAL_SCAN_FINISHED', + }; } /** * Core play actions - **/ + * */ -export function getPlayState(){ - return { - type: 'MOPIDY_GET_PLAY_STATE' - } +export function getPlayState() { + return { + type: 'MOPIDY_GET_PLAY_STATE', + }; } -export function play(){ - return { - type: 'MOPIDY_PLAY' - } +export function play() { + return { + type: 'MOPIDY_PLAY', + }; } -export function pause(){ - return { - type: 'MOPIDY_PAUSE' - } +export function pause() { + return { + type: 'MOPIDY_PAUSE', + }; } -export function stop(){ - return { - type: 'MOPIDY_STOP' - } +export function stop() { + return { + type: 'MOPIDY_STOP', + }; } -export function next(){ - return { - type: 'MOPIDY_NEXT' - } +export function next() { + return { + type: 'MOPIDY_NEXT', + }; } -export function previous(){ - return { - type: 'MOPIDY_PREVIOUS' - } +export function previous() { + return { + type: 'MOPIDY_PREVIOUS', + }; } -export function getMute(){ - return { - type: 'MOPIDY_GET_MUTE' - } +export function getMute() { + return { + type: 'MOPIDY_GET_MUTE', + }; } -export function setMute(mute){ - return { - type: 'MOPIDY_SET_MUTE', - mute: mute - } +export function setMute(mute) { + return { + type: 'MOPIDY_SET_MUTE', + mute, + }; } -export function getVolume(){ - return { - type: 'MOPIDY_GET_VOLUME' - } +export function getVolume() { + return { + type: 'MOPIDY_GET_VOLUME', + }; } -export function setVolume(volume){ - return { - type: 'MOPIDY_SET_VOLUME', - volume: volume - } +export function setVolume(volume) { + return { + type: 'MOPIDY_SET_VOLUME', + volume, + }; } -export function getConsume(){ - return { - type: 'MOPIDY_GET_CONSUME' - } +export function getConsume() { + return { + type: 'MOPIDY_GET_CONSUME', + }; } -export function setConsume(consume){ - return { - type: 'MOPIDY_SET_CONSUME', - consume: consume - } +export function setConsume(consume) { + return { + type: 'MOPIDY_SET_CONSUME', + consume, + }; } -export function getRepeat(){ - return { - type: 'MOPIDY_GET_REPEAT' - } +export function getRepeat() { + return { + type: 'MOPIDY_GET_REPEAT', + }; } -export function setRepeat(repeat){ - return { - type: 'MOPIDY_SET_REPEAT', - repeat: repeat - } +export function setRepeat(repeat) { + return { + type: 'MOPIDY_SET_REPEAT', + repeat, + }; } -export function getRandom(){ - return { - type: 'MOPIDY_GET_RANDOM' - } +export function getRandom() { + return { + type: 'MOPIDY_GET_RANDOM', + }; } -export function setRandom(random){ - return { - type: 'MOPIDY_SET_RANDOM', - random: random - } +export function setRandom(random) { + return { + type: 'MOPIDY_SET_RANDOM', + random, + }; } -export function getTimePosition(){ - return { - type: 'MOPIDY_GET_TIME_POSITION' - } +export function getTimePosition() { + return { + type: 'MOPIDY_GET_TIME_POSITION', + }; } -export function setTimePosition(time_position){ - return { - type: 'MOPIDY_SET_TIME_POSITION', - time_position: parseInt(time_position) - } +export function setTimePosition(time_position) { + return { + type: 'MOPIDY_SET_TIME_POSITION', + time_position: parseInt(time_position), + }; } -export function timePosition(time_position){ - return { - type: 'MOPIDY_TIME_POSITION', - time_position: parseInt(time_position) - } +export function timePosition(time_position) { + return { + type: 'MOPIDY_TIME_POSITION', + time_position: parseInt(time_position), + }; } -export function getUriSchemes(){ - return { - type: 'MOPIDY_GET_URI_SCHEMES' - } +export function getUriSchemes() { + return { + type: 'MOPIDY_GET_URI_SCHEMES', + }; } - /** * Advanced playback actions - **/ + * */ -export function getCurrentTrack(){ - return { - type: 'MOPIDY_GET_CURRENT_TRACK' - } +export function getCurrentTrack() { + return { + type: 'MOPIDY_GET_CURRENT_TRACK', + }; } -export function currentTrackLoaded(tl_track){ - return { - type: 'MOPIDY_CURRENT_TRACK_LOADED', - tl_track: tl_track - } +export function currentTrackLoaded(tl_track) { + return { + type: 'MOPIDY_CURRENT_TRACK_LOADED', + tl_track, + }; } -export function getNextTrack(){ - return { - type: 'MOPIDY_GET_NEXT_TRACK' - } +export function getNextTrack() { + return { + type: 'MOPIDY_GET_NEXT_TRACK', + }; } -export function clearCurrentTrack(){ - return { - type: 'CLEAR_CURRENT_TRACK' - } +export function clearCurrentTrack() { + return { + type: 'CLEAR_CURRENT_TRACK', + }; } -export function getQueue(){ - return { - type: 'MOPIDY_GET_QUEUE' - } +export function getQueue() { + return { + type: 'MOPIDY_GET_QUEUE', + }; } -export function changeTrack(tlid){ - return { - type: 'MOPIDY_CHANGE_TRACK', - tlid: tlid - } +export function changeTrack(tlid) { + return { + type: 'MOPIDY_CHANGE_TRACK', + tlid, + }; } /** * Playing assets - **/ + * */ -export function playURIs(uris = [], from_uri = null, shuffle = false){ - return { - type: 'MOPIDY_PLAY_URIS', - uris: uris, - from_uri: from_uri - } +export function playURIs(uris = [], from_uri = null, shuffle = false) { + return { + type: 'MOPIDY_PLAY_URIS', + uris, + from_uri, + }; } -export function playAlbum(uri){ - return { - type: 'MOPIDY_PLAY_ALBUM', - uri: uri - } +export function playAlbum(uri) { + return { + type: 'MOPIDY_PLAY_ALBUM', + uri, + }; } -export function playPlaylist(uri, shuffle = false){ - return { - type: 'MOPIDY_PLAY_PLAYLIST', - uri: uri, - shuffle: shuffle - } +export function playPlaylist(uri, shuffle = false) { + return { + type: 'MOPIDY_PLAY_PLAYLIST', + uri, + shuffle, + }; } -export function enqueueURIs(uris = [], from_uri = null, play_next = false, at_position = null, offset = 0){ - return { - type: 'MOPIDY_ENQUEUE_URIS', - uris: uris, - from_uri: from_uri, - at_position: at_position, - play_next: play_next, - offset: offset - } +export function enqueueURIs(uris = [], from_uri = null, play_next = false, at_position = null, offset = 0) { + return { + type: 'MOPIDY_ENQUEUE_URIS', + uris, + from_uri, + at_position, + play_next, + offset, + }; } -export function enqueueAlbum(uri, next = false, at_position = null){ - return { - type: 'MOPIDY_ENQUEUE_ALBUM', - uri: uri, - next: next, - at_position: at_position - } +export function enqueueAlbum(uri, next = false, at_position = null) { + return { + type: 'MOPIDY_ENQUEUE_ALBUM', + uri, + next, + at_position, + }; } -export function enqueuePlaylist(uri, play_next = false, at_position = null){ - return { - type: 'MOPIDY_ENQUEUE_PLAYLIST', - uri: uri, - play_next: play_next, - at_position: at_position - } +export function enqueuePlaylist(uri, play_next = false, at_position = null) { + return { + type: 'MOPIDY_ENQUEUE_PLAYLIST', + uri, + play_next, + at_position, + }; } -export function enqueueURIsBatchDone(){ - return { - type: 'MOPIDY_ENQUEUE_URIS_BATCH_DONE' - } +export function enqueueURIsBatchDone() { + return { + type: 'MOPIDY_ENQUEUE_URIS_BATCH_DONE', + }; } /** * Playlist maniopulation - **/ + * */ -export function removeTracks(tlids){ - return { - type: 'MOPIDY_REMOVE_TRACKS', - tlids: tlids - } +export function removeTracks(tlids) { + return { + type: 'MOPIDY_REMOVE_TRACKS', + tlids, + }; } -export function reorderTracklist(indexes, insert_before){ - var range = helpers.createRange(indexes ); - if (insert_before > range.start ) insert_before = insert_before - range.length - return { - type: 'MOPIDY_REORDER_TRACKLIST', - range_start: range.start, - range_length: range.length, - insert_before: insert_before - } +export function reorderTracklist(indexes, insert_before) { + const range = helpers.createRange(indexes); + if (insert_before > range.start) insert_before -= range.length; + return { + type: 'MOPIDY_REORDER_TRACKLIST', + range_start: range.start, + range_length: range.length, + insert_before, + }; } -export function clearTracklist(){ - return { - type: 'MOPIDY_CLEAR_TRACKLIST' - } +export function clearTracklist() { + return { + type: 'MOPIDY_CLEAR_TRACKLIST', + }; } - /** * Asset-oriented actions - **/ + * */ -export function getImages(context, uris){ - return { - type: 'MOPIDY_GET_IMAGES', - context: context, - uris: uris - } +export function getImages(context, uris) { + return { + type: 'MOPIDY_GET_IMAGES', + context, + uris, + }; } -export function createPlaylist(name, scheme){ - return { - type: 'MOPIDY_CREATE_PLAYLIST', - name: name, - scheme: scheme - } +export function createPlaylist(name, scheme) { + return { + type: 'MOPIDY_CREATE_PLAYLIST', + name, + scheme, + }; } -export function deletePlaylist(uri){ - return { - type: 'MOPIDY_DELETE_PLAYLIST', - uri: uri - } +export function deletePlaylist(uri) { + return { + type: 'MOPIDY_DELETE_PLAYLIST', + uri, + }; } -export function getLibraryPlaylists(){ - return { type: 'MOPIDY_GET_LIBRARY_PLAYLISTS' } +export function getLibraryPlaylists() { + return { type: 'MOPIDY_GET_LIBRARY_PLAYLISTS' }; } -export function getPlaylist(uri){ - return { - type: 'MOPIDY_GET_PLAYLIST', - data: { uri: uri } - } +export function getPlaylist(uri) { + return { + type: 'MOPIDY_GET_PLAYLIST', + data: { uri }, + }; } -export function getPlaylists(uris, processor = null){ - return { - type: 'MOPIDY_GET_PLAYLISTS', - uris: uris, - processor: processor - } +export function getPlaylists(uris, processor = null) { + return { + type: 'MOPIDY_GET_PLAYLISTS', + uris, + processor, + }; } -export function getDirectory(uri){ - return { - type: 'MOPIDY_GET_DIRECTORY', - data: { uri: uri } - } +export function getDirectory(uri) { + return { + type: 'MOPIDY_GET_DIRECTORY', + data: { uri }, + }; } -export function getTrack(uri){ - return { - type: 'MOPIDY_GET_TRACK', - data: { uri: uri } - } +export function getTrack(uri) { + return { + type: 'MOPIDY_GET_TRACK', + data: { uri }, + }; } -export function getLibraryArtists(uri = null){ - return { - type: 'MOPIDY_GET_LIBRARY_ARTISTS', - uri: uri - } +export function getLibraryArtists(uri = null) { + return { + type: 'MOPIDY_GET_LIBRARY_ARTISTS', + uri, + }; } -export function clearLibraryArtists(){ - return { - type: 'MOPIDY_CLEAR_LIBRARY_ARTISTS' - } +export function clearLibraryArtists() { + return { + type: 'MOPIDY_CLEAR_LIBRARY_ARTISTS', + }; } -export function getArtist(uri){ - return { - type: 'MOPIDY_GET_ARTIST', - data: { uri: uri } - } +export function getArtist(uri) { + return { + type: 'MOPIDY_GET_ARTIST', + data: { uri }, + }; } -export function getArtists(uris, processor = null){ - return { - type: 'MOPIDY_GET_ARTISTS', - uris: uris, - processor: processor - } +export function getArtists(uris, processor = null) { + return { + type: 'MOPIDY_GET_ARTISTS', + uris, + processor, + }; } -export function getAlbum(uri){ - return { - type: 'MOPIDY_GET_ALBUM', - data: { uri: uri } - } +export function getAlbum(uri) { + return { + type: 'MOPIDY_GET_ALBUM', + data: { uri }, + }; } -export function getAlbums(uris, processor = null){ - return { - type: 'MOPIDY_GET_ALBUMS', - uris: uris, - processor: processor - } +export function getAlbums(uris, processor = null) { + return { + type: 'MOPIDY_GET_ALBUMS', + uris, + processor, + }; } -export function getLibraryAlbums(uri = null){ - return { - type: 'MOPIDY_GET_LIBRARY_ALBUMS', - uri: uri - } +export function getLibraryAlbums(uri = null) { + return { + type: 'MOPIDY_GET_LIBRARY_ALBUMS', + uri, + }; } -export function clearLibraryAlbums(){ - return { - type: 'MOPIDY_CLEAR_LIBRARY_ALBUMS' - } +export function clearLibraryAlbums() { + return { + type: 'MOPIDY_CLEAR_LIBRARY_ALBUMS', + }; } -export function runProcessor(processor){ - return { - type: processor - } +export function runProcessor(processor) { + return { + type: processor, + }; } -export function cancelProcessor(processor){ - return { - type: processor+'_CANCEL' - } +export function cancelProcessor(processor) { + return { + type: `${processor}_CANCEL`, + }; } /** * Searching - **/ + * */ -export function clearSearchResults(){ - return { - type: 'MOPIDY_CLEAR_SEARCH_RESULTS' - } +export function clearSearchResults() { + return { + type: 'MOPIDY_CLEAR_SEARCH_RESULTS', + }; } -export function getSearchResults(context, query, limit = 100){ - return { - type: 'MOPIDY_GET_SEARCH_RESULTS', - context: context, - query: query, - limit: limit - } +export function getSearchResults(context, query, limit = 100) { + return { + type: 'MOPIDY_GET_SEARCH_RESULTS', + context, + query, + limit, + }; } /** * Other general actions - **/ + * */ -export function getQueueHistory(){ - return { - type: 'MOPIDY_GET_QUEUE_HISTORY' - } +export function getQueueHistory() { + return { + type: 'MOPIDY_GET_QUEUE_HISTORY', + }; } - \ No newline at end of file diff --git a/src/js/services/mopidy/middleware.js b/src/js/services/mopidy/middleware.js index 13b1db01..9fb22313 100755 --- a/src/js/services/mopidy/middleware.js +++ b/src/js/services/mopidy/middleware.js @@ -5,146 +5,142 @@ import { sha256 } from 'js-sha256'; import * as helpers from '../../helpers'; -var mopidyActions = require('./actions.js'); -var coreActions = require('../core/actions.js'); -var uiActions = require('../ui/actions.js'); -var spotifyActions = require('../spotify/actions.js'); -var pusherActions = require('../pusher/actions.js'); -var googleActions = require('../google/actions.js'); -var lastfmActions = require('../lastfm/actions.js'); -var discogsActions = require('../discogs/actions.js'); +const mopidyActions = require('./actions.js'); +const coreActions = require('../core/actions.js'); +const uiActions = require('../ui/actions.js'); +const spotifyActions = require('../spotify/actions.js'); +const pusherActions = require('../pusher/actions.js'); +const googleActions = require('../google/actions.js'); +const lastfmActions = require('../lastfm/actions.js'); +const discogsActions = require('../discogs/actions.js'); -const MopidyMiddleware = (function(){ +const MopidyMiddleware = (function () { + // container for the actual Mopidy socket + let socket = null; - // container for the actual Mopidy socket - var socket = null; + // play position timer + let progress_interval = null; + let progress_interval_counter = 0; - // play position timer - var progress_interval = null; - var progress_interval_counter = 0; - - // handle all manner of socket messages - const handleMessage = (ws, store, type, data) => { - - // if debug enabled - if (store.getState().ui.log_mopidy){ - console.log('Mopidy', type, data); - } - - switch(type){ - - case 'state:online': - store.dispatch({ type: 'MOPIDY_CONNECTED' }); - - store.dispatch(mopidyActions.getPlayState()); - store.dispatch(mopidyActions.getVolume()); - store.dispatch(mopidyActions.getMute()); - store.dispatch(mopidyActions.getConsume()); - store.dispatch(mopidyActions.getRandom()); - store.dispatch(mopidyActions.getRepeat()); - store.dispatch(mopidyActions.getQueue()); - store.dispatch(mopidyActions.getCurrentTrack()); - store.dispatch(mopidyActions.getTimePosition()); - store.dispatch(mopidyActions.getUriSchemes()); - - // Every 1s update our play position (when playing) - progress_interval = setInterval(() => { - if (store.getState().mopidy.play_state == 'playing'){ - - // Every 10s get real position from server, provided we're in-focus - if (progress_interval_counter % 5 == 0 && store.getState().ui.window_focus === true){ - store.dispatch(mopidyActions.getTimePosition()); - - // Otherwise we just assume to add 1000ms every 1000ms of play time - } else { - store.dispatch(mopidyActions.timePosition(store.getState().mopidy.time_position + 1000)); - } - - progress_interval_counter++ - } - }, 1000); - - break; - - case 'state:offline': - store.dispatch({ type: 'MOPIDY_DISCONNECTED' }); - store.dispatch(mopidyActions.clearCurrentTrack()); - - // reset our playback interval timer - clearInterval(progress_interval) - progress_interval_counter = 0 - break; - - case 'event:tracklistChanged': - store.dispatch(mopidyActions.getQueue()); - - // Wait a jiffy before we get the next track - // We don't want to impede snappyness for this luxury request - setTimeout( - () => { - store.dispatch(mopidyActions.getNextTrack()); - }, - 1000 - ); - break; - - case 'event:playbackStateChanged': - store.dispatch({ - type: 'MOPIDY_PLAY_STATE', - play_state: data.new_state - }); - store.dispatch(mopidyActions.getTimePosition()); - break; - - case 'event:seeked': - store.dispatch({ - type: 'MOPIDY_TIME_POSITION', - time_position: data.time_position - }); - break; - - case 'event:trackPlaybackEnded': - store.dispatch(mopidyActions.clearCurrentTrack()); - store.dispatch({ - type: 'MOPIDY_TIME_POSITION', - time_position: 0 - }); - break; - - case 'event:trackPlaybackStarted': - store.dispatch(mopidyActions.currentTrackLoaded(data.tl_track)); - - // Wait a jiffy before we get the next track - // We don't want to impede snappyness for this luxury request - setTimeout( - () => { - store.dispatch(mopidyActions.getNextTrack()); - }, - 1000 - ); - break; - - case 'event:volumeChanged': - store.dispatch({type: 'MOPIDY_VOLUME', volume: data.volume}); - break; - - case 'event:muteChanged': - store.dispatch({type: 'MOPIDY_MUTE', mute: data.mute}); - break; - - case 'event:optionsChanged': - store.dispatch(mopidyActions.getConsume()); - store.dispatch(mopidyActions.getRandom()); - store.dispatch(mopidyActions.getRepeat()); - break; - - default: - //console.log('MopidyService: Unhandled message', type, message ); - } + // handle all manner of socket messages + const handleMessage = (ws, store, type, data) => { + // if debug enabled + if (store.getState().ui.log_mopidy) { + console.log('Mopidy', type, data); } + switch (type) { + case 'state:online': + store.dispatch({ type: 'MOPIDY_CONNECTED' }); - /** + store.dispatch(mopidyActions.getPlayState()); + store.dispatch(mopidyActions.getVolume()); + store.dispatch(mopidyActions.getMute()); + store.dispatch(mopidyActions.getConsume()); + store.dispatch(mopidyActions.getRandom()); + store.dispatch(mopidyActions.getRepeat()); + store.dispatch(mopidyActions.getQueue()); + store.dispatch(mopidyActions.getCurrentTrack()); + store.dispatch(mopidyActions.getTimePosition()); + store.dispatch(mopidyActions.getUriSchemes()); + + // Every 1s update our play position (when playing) + progress_interval = setInterval(() => { + if (store.getState().mopidy.play_state == 'playing') { + // Every 10s get real position from server, provided we're in-focus + if (progress_interval_counter % 5 == 0 && store.getState().ui.window_focus === true) { + store.dispatch(mopidyActions.getTimePosition()); + + // Otherwise we just assume to add 1000ms every 1000ms of play time + } else { + store.dispatch(mopidyActions.timePosition(store.getState().mopidy.time_position + 1000)); + } + + progress_interval_counter++; + } + }, 1000); + + break; + + case 'state:offline': + store.dispatch({ type: 'MOPIDY_DISCONNECTED' }); + store.dispatch(mopidyActions.clearCurrentTrack()); + + // reset our playback interval timer + clearInterval(progress_interval); + progress_interval_counter = 0; + break; + + case 'event:tracklistChanged': + store.dispatch(mopidyActions.getQueue()); + + // Wait a jiffy before we get the next track + // We don't want to impede snappyness for this luxury request + setTimeout( + () => { + store.dispatch(mopidyActions.getNextTrack()); + }, + 1000, + ); + break; + + case 'event:playbackStateChanged': + store.dispatch({ + type: 'MOPIDY_PLAY_STATE', + play_state: data.new_state, + }); + store.dispatch(mopidyActions.getTimePosition()); + break; + + case 'event:seeked': + store.dispatch({ + type: 'MOPIDY_TIME_POSITION', + time_position: data.time_position, + }); + break; + + case 'event:trackPlaybackEnded': + store.dispatch(mopidyActions.clearCurrentTrack()); + store.dispatch({ + type: 'MOPIDY_TIME_POSITION', + time_position: 0, + }); + break; + + case 'event:trackPlaybackStarted': + store.dispatch(mopidyActions.currentTrackLoaded(data.tl_track)); + + // Wait a jiffy before we get the next track + // We don't want to impede snappyness for this luxury request + setTimeout( + () => { + store.dispatch(mopidyActions.getNextTrack()); + }, + 1000, + ); + break; + + case 'event:volumeChanged': + store.dispatch({ type: 'MOPIDY_VOLUME', volume: data.volume }); + break; + + case 'event:muteChanged': + store.dispatch({ type: 'MOPIDY_MUTE', mute: data.mute }); + break; + + case 'event:optionsChanged': + store.dispatch(mopidyActions.getConsume()); + store.dispatch(mopidyActions.getRandom()); + store.dispatch(mopidyActions.getRepeat()); + break; + + default: + // console.log('MopidyService: Unhandled message', type, message ); + } + }; + + + /** * Call something with Mopidy * * Sends request to Mopidy server, and updates our local storage on return @@ -153,1893 +149,1858 @@ const MopidyMiddleware = (function(){ * @param string property = TlTracks, Consume, etc * @param string value (optional) = value of the property to pass * @return promise - **/ - const request = (ws, store, call, value = {}) => { - if (!store.getState().mopidy.connected){ - return false; - } - - var callParts = call.split('.'); - var model = callParts[0]; - var method = callParts[1]; - - return new Promise((resolve, reject) => { - if (method in ws[model]){ - var mopidyObject = ws[model][method]; - var property = method; - } else { - var mopidyObject = ws[model]; - var property = model; - } - - // Detect invalid model.method calls, which result in an empty mopidyObject - if (!mopidyObject || typeof(mopidyObject) !== 'function'){ - var error = { - message: 'Call to an invalid object. Check you are calling a valid Mopidy object.', - call: call, - value: value - } - - store.dispatch(coreActions.handleException( - 'Mopidy: '+error.message, - error - )); - - reject(error); - } - - var loader_key = helpers.generateGuid(); - store.dispatch(uiActions.startLoading(loader_key, 'mopidy_'+property)); - - // Start our 15 second timeout - var timeout = setTimeout( - function(){ - store.dispatch(uiActions.stopLoading(loader_key)); - reject({message: "Request timed out", call: call, value: value}); - }, - 30000 - ); - - mopidyObject(value) - .then( - response => { - clearTimeout(timeout); - store.dispatch(uiActions.stopLoading(loader_key)); - resolve(response); - }, - error => { - clearTimeout(timeout); - store.dispatch(uiActions.stopLoading(loader_key)); - reject(error); - } - ); - }); + * */ + const request = (ws, store, call, value = {}) => { + if (!store.getState().mopidy.connected) { + return false; } + const callParts = call.split('.'); + const model = callParts[0]; + const method = callParts[1]; - /** + return new Promise((resolve, reject) => { + if (method in ws[model]) { + var mopidyObject = ws[model][method]; + var property = method; + } else { + var mopidyObject = ws[model]; + var property = model; + } + + // Detect invalid model.method calls, which result in an empty mopidyObject + if (!mopidyObject || typeof (mopidyObject) !== 'function') { + const error = { + message: 'Call to an invalid object. Check you are calling a valid Mopidy object.', + call, + value, + }; + + store.dispatch(coreActions.handleException( + `Mopidy: ${error.message}`, + error, + )); + + reject(error); + } + + const loader_key = helpers.generateGuid(); + store.dispatch(uiActions.startLoading(loader_key, `mopidy_${property}`)); + + // Start our 15 second timeout + const timeout = setTimeout( + () => { + store.dispatch(uiActions.stopLoading(loader_key)); + reject({ message: 'Request timed out', call, value }); + }, + 30000, + ); + + mopidyObject(value) + .then( + (response) => { + clearTimeout(timeout); + store.dispatch(uiActions.stopLoading(loader_key)); + resolve(response); + }, + (error) => { + clearTimeout(timeout); + store.dispatch(uiActions.stopLoading(loader_key)); + reject(error); + }, + ); + }); + }; + + + /** * Middleware * * This behaves like an action interceptor. We listen for specific actions * and handle special functionality. If the action is not in our switch, then * it just proceeds to the next middleware, or default functionality - **/ - return store => next => action => { - switch(action.type){ - - case 'MOPIDY_CONNECT': - if (socket != null){ + * */ + return (store) => (next) => (action) => { + switch (action.type) { + case 'MOPIDY_CONNECT': + if (socket != null) { socket.close(); - } + } - store.dispatch({ type: 'MOPIDY_CONNECTING'}); - var state = store.getState(); + store.dispatch({ type: 'MOPIDY_CONNECTING' }); + var state = store.getState(); - socket = new Mopidy({ - webSocketUrl: 'ws'+(window.location.protocol === 'https:' ? 's' : '')+'://'+state.mopidy.host+':'+state.mopidy.port+'/mopidy/ws/', - callingConvention: 'by-position-or-by-name' - }); + socket = new Mopidy({ + webSocketUrl: `ws${window.location.protocol === 'https:' ? 's' : ''}://${state.mopidy.host}:${state.mopidy.port}/mopidy/ws/`, + callingConvention: 'by-position-or-by-name', + }); - socket.on((type, data) => handleMessage(socket, store, type, data)); - break; + socket.on((type, data) => handleMessage(socket, store, type, data)); + break; - case 'MOPIDY_CONNECTED': - if (store.getState().ui.allow_reporting){ - var hashed_hostname = sha256(window.location.hostname); + case 'MOPIDY_CONNECTED': + if (store.getState().ui.allow_reporting) { + const hashed_hostname = sha256(window.location.hostname); ReactGA.event({ category: 'Mopidy', action: 'Connected', label: hashed_hostname }); } - store.dispatch(uiActions.createNotification({content: 'Mopidy connected'})); - next(action); - break; + store.dispatch(uiActions.createNotification({ content: 'Mopidy connected' })); + next(action); + break; - case 'MOPIDY_DISCONNECT': - if (socket != null) socket.close() - socket = null - store.dispatch({ type: 'MOPIDY_DISCONNECTED' }) - break; + case 'MOPIDY_DISCONNECT': + if (socket != null) socket.close(); + socket = null; + store.dispatch({ type: 'MOPIDY_DISCONNECTED' }); + break; - case 'MOPIDY_DISCONNECTED': - store.dispatch(uiActions.createNotification({type: 'bad', content: 'Mopidy disconnected'})); - helpers.setFavicon('favicon_error.png'); - break; + case 'MOPIDY_DISCONNECTED': + store.dispatch(uiActions.createNotification({ type: 'bad', content: 'Mopidy disconnected' })); + helpers.setFavicon('favicon_error.png'); + break; - case 'MOPIDY_DEBUG': - request(socket, store, action.call, action.value ) - .then(response => { - store.dispatch({type: 'DEBUG', response: response}); - }) - break; + case 'MOPIDY_DEBUG': + request(socket, store, action.call, action.value) + .then((response) => { + store.dispatch({ type: 'DEBUG', response }); + }); + break; - case 'SET_WINDOW_FOCUS': + case 'SET_WINDOW_FOCUS': - // Focus has just been regained - if (action.window_focus === true){ - store.dispatch(mopidyActions.getPlayState()); - store.dispatch(mopidyActions.getVolume()); - store.dispatch(mopidyActions.getMute()); - store.dispatch(mopidyActions.getConsume()); - store.dispatch(mopidyActions.getRandom()); - store.dispatch(mopidyActions.getRepeat()); - store.dispatch(mopidyActions.getCurrentTrack()); - store.dispatch(mopidyActions.getTimePosition()); - } - break; + // Focus has just been regained + if (action.window_focus === true) { + store.dispatch(mopidyActions.getPlayState()); + store.dispatch(mopidyActions.getVolume()); + store.dispatch(mopidyActions.getMute()); + store.dispatch(mopidyActions.getConsume()); + store.dispatch(mopidyActions.getRandom()); + store.dispatch(mopidyActions.getRepeat()); + store.dispatch(mopidyActions.getCurrentTrack()); + store.dispatch(mopidyActions.getTimePosition()); + } + break; - case 'MOPIDY_REQUEST': - request(socket, store, action.method, action.params) - .then( - response => { - if (action.response_callback){ + case 'MOPIDY_REQUEST': + request(socket, store, action.method, action.params) + .then( + (response) => { + if (action.response_callback) { store.dispatch(action.response_callback.call(this, response)); } - }, - error => { - if (action.error_callback){ + }, + (error) => { + if (action.error_callback) { store.dispatch(action.error_callback.call(this, error)); } else { store.dispatch(coreActions.handleException( 'Mopidy request failed', error, action.method, - action + action, )); } - } - ); - break + }, + ); + break; - /** + /** * General playback - **/ + * */ - case 'MOPIDY_PLAY_STATE': - store.dispatch(uiActions.setWindowTitle(null, action.play_state)); + case 'MOPIDY_PLAY_STATE': + store.dispatch(uiActions.setWindowTitle(null, action.play_state)); - if (action.play_state == 'playing'){ - helpers.setFavicon('favicon.png'); - } else { - helpers.setFavicon('favicon_paused.png'); - } - next(action); - break + if (action.play_state == 'playing') { + helpers.setFavicon('favicon.png'); + } else { + helpers.setFavicon('favicon_paused.png'); + } + next(action); + break; - case 'MOPIDY_GET_PLAY_STATE': - request(socket, store, 'playback.getState') - .then( - response => { - store.dispatch({ - type: 'MOPIDY_PLAY_STATE', - play_state: response - }) - } - ) - break + case 'MOPIDY_GET_PLAY_STATE': + request(socket, store, 'playback.getState') + .then( + (response) => { + store.dispatch({ + type: 'MOPIDY_PLAY_STATE', + play_state: response, + }); + }, + ); + break; - case 'MOPIDY_PLAY': - request(socket, store, 'playback.play') - .then( - response => { - store.dispatch({ - type: 'MOPIDY_PLAY_STATE', - play_state: 'playing' - }) - } - ); + case 'MOPIDY_PLAY': + request(socket, store, 'playback.play') + .then( + (response) => { + store.dispatch({ + type: 'MOPIDY_PLAY_STATE', + play_state: 'playing', + }); + }, + ); - store.dispatch(pusherActions.deliverBroadcast( - 'notification', - { - notification: { - content: store.getState().pusher.username +(store.getState().mopidy.play_state == 'paused' ? ' resumed' : ' started')+' playback', - icon: (store.getState().core.current_track ? helpers.getTrackIcon(store.getState().core.current_track, store.getState().core) : false) - } - } - )); - break; + store.dispatch(pusherActions.deliverBroadcast( + 'notification', + { + notification: { + content: `${store.getState().pusher.username + (store.getState().mopidy.play_state == 'paused' ? ' resumed' : ' started')} playback`, + icon: (store.getState().core.current_track ? helpers.getTrackIcon(store.getState().core.current_track, store.getState().core) : false), + }, + }, + )); + break; - case 'MOPIDY_PAUSE': - request(socket, store, 'playback.pause') - .then(response => { - store.dispatch({ - type: 'MOPIDY_PLAY_STATE', - play_state: 'paused' - }) - ; - store.dispatch(pusherActions.deliverBroadcast( - 'notification', - { - notification: { - content: store.getState().pusher.username +' paused playback', - icon: (store.getState().core.current_track ? helpers.getTrackIcon(store.getState().core.current_track, store.getState().core) : false) - } - } - )); - } - ); + case 'MOPIDY_PAUSE': + request(socket, store, 'playback.pause') + .then((response) => { + store.dispatch({ + type: 'MOPIDY_PLAY_STATE', + play_state: 'paused', + }); + store.dispatch(pusherActions.deliverBroadcast( + 'notification', + { + notification: { + content: `${store.getState().pusher.username} paused playback`, + icon: (store.getState().core.current_track ? helpers.getTrackIcon(store.getState().core.current_track, store.getState().core) : false), + }, + }, + )); + }); - break + break; - case 'MOPIDY_PREVIOUS': + case 'MOPIDY_PREVIOUS': - // Let the UI know we're in transition - store.dispatch(uiActions.setCurrentTrackTransition('previous')); + // Let the UI know we're in transition + store.dispatch(uiActions.setCurrentTrackTransition('previous')); - request(socket, store, 'playback.previous'); - break + request(socket, store, 'playback.previous'); + break; - case 'MOPIDY_NEXT': + case 'MOPIDY_NEXT': - // Let the UI know we're in transition - store.dispatch(uiActions.setCurrentTrackTransition('next')); + // Let the UI know we're in transition + store.dispatch(uiActions.setCurrentTrackTransition('next')); - request(socket, store, 'playback.next') - .then(response => { - store.dispatch(pusherActions.deliverBroadcast( - 'notification', - { - notification: { - content: store.getState().pusher.username +' skipped "'+store.getState().core.current_track.name+'"', - icon: (store.getState().core.current_track ? helpers.getTrackIcon(store.getState().core.current_track, store.getState().core) : false) - } - } - )); - }); + request(socket, store, 'playback.next') + .then((response) => { + store.dispatch(pusherActions.deliverBroadcast( + 'notification', + { + notification: { + content: `${store.getState().pusher.username} skipped "${store.getState().core.current_track.name}"`, + icon: (store.getState().core.current_track ? helpers.getTrackIcon(store.getState().core.current_track, store.getState().core) : false), + }, + }, + )); + }); - break + break; - case 'MOPIDY_STOP': - request(socket, store, 'playback.stop') - .then(response => { - store.dispatch(mopidyActions.clearCurrentTrack()); + case 'MOPIDY_STOP': + request(socket, store, 'playback.stop') + .then((response) => { + store.dispatch(mopidyActions.clearCurrentTrack()); - store.dispatch(pusherActions.deliverBroadcast( - 'notification', - { - notification: { - content: store.getState().pusher.username +' stopped playback', - icon: (store.getState().core.current_track ? helpers.getTrackIcon(store.getState().core.current_track, store.getState().core) : false) - } - } - )); - }); - break + store.dispatch(pusherActions.deliverBroadcast( + 'notification', + { + notification: { + content: `${store.getState().pusher.username} stopped playback`, + icon: (store.getState().core.current_track ? helpers.getTrackIcon(store.getState().core.current_track, store.getState().core) : false), + }, + }, + )); + }); + break; - case 'MOPIDY_CHANGE_TRACK': - request(socket, store, 'playback.play', {tlid: action.tlid}) - .then(response => { - store.dispatch(pusherActions.deliverBroadcast( - 'notification', - { - notification: { - content: store.getState().pusher.username +' changed track' - } - } - )); - }); - break; + case 'MOPIDY_CHANGE_TRACK': + request(socket, store, 'playback.play', { tlid: action.tlid }) + .then((response) => { + store.dispatch(pusherActions.deliverBroadcast( + 'notification', + { + notification: { + content: `${store.getState().pusher.username} changed track`, + }, + }, + )); + }); + break; - case 'MOPIDY_REMOVE_TRACKS': - request(socket, store, 'tracklist.remove', {tlid: action.tlids}) - .then(response => { - store.dispatch(pusherActions.deliverBroadcast( - 'notification', - { - notification: { - content: store.getState().pusher.username +' removed '+action.tlids.length+' tracks' - } - } - )); - }); - break; + case 'MOPIDY_REMOVE_TRACKS': + request(socket, store, 'tracklist.remove', { tlid: action.tlids }) + .then((response) => { + store.dispatch(pusherActions.deliverBroadcast( + 'notification', + { + notification: { + content: `${store.getState().pusher.username} removed ${action.tlids.length} tracks`, + }, + }, + )); + }); + break; - case 'MOPIDY_GET_REPEAT': - request(socket, store, 'tracklist.getRepeat') - .then(response => { - store.dispatch({ - type: 'MOPIDY_REPEAT', - repeat: response - }); - }); - break; + case 'MOPIDY_GET_REPEAT': + request(socket, store, 'tracklist.getRepeat') + .then((response) => { + store.dispatch({ + type: 'MOPIDY_REPEAT', + repeat: response, + }); + }); + break; - case 'MOPIDY_SET_REPEAT': - request(socket, store, 'tracklist.setRepeat', [action.repeat]); - break; + case 'MOPIDY_SET_REPEAT': + request(socket, store, 'tracklist.setRepeat', [action.repeat]); + break; - case 'MOPIDY_GET_RANDOM': - request(socket, store, 'tracklist.getRandom') - .then(response => { - store.dispatch({ - type: 'MOPIDY_RANDOM', - random: response - }); - }); - break; + case 'MOPIDY_GET_RANDOM': + request(socket, store, 'tracklist.getRandom') + .then((response) => { + store.dispatch({ + type: 'MOPIDY_RANDOM', + random: response, + }); + }); + break; - case 'MOPIDY_SET_RANDOM': - request(socket, store, 'tracklist.setRandom', [action.random]); - break; + case 'MOPIDY_SET_RANDOM': + request(socket, store, 'tracklist.setRandom', [action.random]); + break; - case 'MOPIDY_GET_CONSUME': - request(socket, store, 'tracklist.getConsume') - .then(response => { - store.dispatch({ - type: 'MOPIDY_CONSUME', - consume: response - }); - }); - break; + case 'MOPIDY_GET_CONSUME': + request(socket, store, 'tracklist.getConsume') + .then((response) => { + store.dispatch({ + type: 'MOPIDY_CONSUME', + consume: response, + }); + }); + break; - case 'MOPIDY_SET_CONSUME': - request(socket, store, 'tracklist.setConsume', [action.consume]); - break; + case 'MOPIDY_SET_CONSUME': + request(socket, store, 'tracklist.setConsume', [action.consume]); + break; - case 'MOPIDY_GET_MUTE': - request(socket, store, 'mixer.getMute') - .then(response => { - store.dispatch({ - type: 'MOPIDY_MUTE', - mute: response - }); - }); - break; + case 'MOPIDY_GET_MUTE': + request(socket, store, 'mixer.getMute') + .then((response) => { + store.dispatch({ + type: 'MOPIDY_MUTE', + mute: response, + }); + }); + break; - case 'MOPIDY_SET_MUTE': - request(socket, store, 'mixer.setMute', [action.mute]) - .then(response => { - store.dispatch(pusherActions.deliverBroadcast( - 'notification', - { - notification: { - content: store.getState().pusher.username +(action.mute ? ' muted' : ' unmuted')+' playback' - } - } - )); - }); - break; + case 'MOPIDY_SET_MUTE': + request(socket, store, 'mixer.setMute', [action.mute]) + .then((response) => { + store.dispatch(pusherActions.deliverBroadcast( + 'notification', + { + notification: { + content: `${store.getState().pusher.username + (action.mute ? ' muted' : ' unmuted')} playback`, + }, + }, + )); + }); + break; - case 'MOPIDY_GET_VOLUME': - request(socket, store, 'playback.getVolume') - .then( - response => { - store.dispatch({ - type: 'MOPIDY_VOLUME', - volume: response - }); - } - ); - break; + case 'MOPIDY_GET_VOLUME': + request(socket, store, 'playback.getVolume') + .then( + (response) => { + store.dispatch({ + type: 'MOPIDY_VOLUME', + volume: response, + }); + }, + ); + break; - case 'MOPIDY_SET_VOLUME': - request(socket, store, 'playback.setVolume', {volume: action.volume}) - .then( - response => { - store.dispatch({ - type: 'MOPIDY_VOLUME', - volume: action.volume - }); - } - ); - break; + case 'MOPIDY_SET_VOLUME': + request(socket, store, 'playback.setVolume', { volume: action.volume }) + .then( + (response) => { + store.dispatch({ + type: 'MOPIDY_VOLUME', + volume: action.volume, + }); + }, + ); + break; - case 'MOPIDY_SET_TIME_POSITION': - request(socket, store, 'playback.seek', {time_position: action.time_position}) - .then( - response => { - store.dispatch({ - type: 'MOPIDY_TIME_POSITION', - time_position: action.time_position - }); - } - ); - break; + case 'MOPIDY_SET_TIME_POSITION': + request(socket, store, 'playback.seek', { time_position: action.time_position }) + .then( + (response) => { + store.dispatch({ + type: 'MOPIDY_TIME_POSITION', + time_position: action.time_position, + }); + }, + ); + break; - case 'MOPIDY_GET_TIME_POSITION': - request(socket, store, 'playback.getTimePosition') - .then( - response => { - store.dispatch({ - type: 'MOPIDY_TIME_POSITION', - time_position: response - }); - } - ); - break; + case 'MOPIDY_GET_TIME_POSITION': + request(socket, store, 'playback.getTimePosition') + .then( + (response) => { + store.dispatch({ + type: 'MOPIDY_TIME_POSITION', + time_position: response, + }); + }, + ); + break; - case 'MOPIDY_GET_URI_SCHEMES': - request(socket, store, 'getUriSchemes') - .then( - response => { - var uri_schemes = response; - var remove = ['http','https','mms','rtmp','rtmps','rtsp','sc','yt']; + case 'MOPIDY_GET_URI_SCHEMES': + request(socket, store, 'getUriSchemes') + .then( + (response) => { + const uri_schemes = response; + const remove = ['http', 'https', 'mms', 'rtmp', 'rtmps', 'rtsp', 'sc', 'yt']; - // remove all our ignored types - for(var i = 0; i < remove.length; i++){ - var index = uri_schemes.indexOf(remove[i]); - if (index > -1 ) uri_schemes.splice(index, 1); - } + // remove all our ignored types + for (var i = 0; i < remove.length; i++) { + const index = uri_schemes.indexOf(remove[i]); + if (index > -1) uri_schemes.splice(index, 1); + } - // append with ':' to make them a mopidy URI - for(var i = 0; i < uri_schemes.length; i++){ - uri_schemes[i] = uri_schemes[i] +':'; - } + // append with ':' to make them a mopidy URI + for (var i = 0; i < uri_schemes.length; i++) { + uri_schemes[i] = `${uri_schemes[i]}:`; + } - // Enable Iris providers when the backend is available - store.dispatch(spotifyActions.set({enabled: uri_schemes.includes('spotify:')})); - store.dispatch(googleActions.set({enabled: uri_schemes.includes('gmusic:')})); + // Enable Iris providers when the backend is available + store.dispatch(spotifyActions.set({ enabled: uri_schemes.includes('spotify:') })); + store.dispatch(googleActions.set({ enabled: uri_schemes.includes('gmusic:') })); - // If we haven't customised our search schemes, add all to search - if (store.getState().ui.uri_schemes_search_enabled === undefined){ - store.dispatch(uiActions.set({uri_schemes_search_enabled: uri_schemes})); - } + // If we haven't customised our search schemes, add all to search + if (store.getState().ui.uri_schemes_search_enabled === undefined) { + store.dispatch(uiActions.set({ uri_schemes_search_enabled: uri_schemes })); + } - store.dispatch({type: 'MOPIDY_URI_SCHEMES', uri_schemes: uri_schemes}); - } - ); - break; + store.dispatch({ type: 'MOPIDY_URI_SCHEMES', uri_schemes }); + }, + ); + break; - /** + /** * Advanced playback events - **/ + * */ - case 'MOPIDY_PLAY_PLAYLIST': - var playlist = store.getState().core.playlists[action.uri]; + case 'MOPIDY_PLAY_PLAYLIST': + var playlist = store.getState().core.playlists[action.uri]; - // We have the playlist loaded already, and we've got at least 1 track to start playing - if (playlist && playlist.tracks_uris && playlist.tracks_uris.length > 0){ + // We have the playlist loaded already, and we've got at least 1 track to start playing + if (playlist && playlist.tracks_uris && playlist.tracks_uris.length > 0) { + // We've got all of the tracks, so just play those; no further action required + if (playlist.tracks_total == playlist.tracks_uris.length) { + store.dispatch(mopidyActions.playURIs(playlist.tracks_uris, action.uri, action.shuffle)); + break; + } - // We've got all of the tracks, so just play those; no further action required - if (playlist.tracks_total == playlist.tracks_uris.length){ - store.dispatch(mopidyActions.playURIs(playlist.tracks_uris, action.uri, action.shuffle)); - break; - } + // Spotify-provided playlists need to be handled by the Spotify service + if (playlist.provider == 'spotify') { + store.dispatch(spotifyActions.getAllPlaylistTracks(action.uri, action.shuffle, 'play')); + break; + } - // Spotify-provided playlists need to be handled by the Spotify service - if (playlist.provider == 'spotify'){ - store.dispatch(spotifyActions.getAllPlaylistTracks(action.uri, action.shuffle, 'play')); - break; - } + // It's a Spotify playlist that we haven't loaded yet, so Spotify HTTP API needs to go get it + } else if (helpers.uriSource(action.uri) == 'spotify' && store.getState().spotify.enabled) { + store.dispatch(spotifyActions.getAllPlaylistTracks(action.uri, action.shuffle, 'play')); + break; + } - // It's a Spotify playlist that we haven't loaded yet, so Spotify HTTP API needs to go get it - } else if (helpers.uriSource(action.uri) == 'spotify' && store.getState().spotify.enabled){ - store.dispatch(spotifyActions.getAllPlaylistTracks(action.uri, action.shuffle, 'play')); - break - } - - // Not in index, and Spotify HTTP not enabled, so just play it as-is - // Fetch the playlist tracks via backend and add each track by URI - request(socket, store, 'playlists.lookup', {uri: action.uri}) - .then( - response => { - if (!response || response.tracks === undefined || !response.tracks){ - store.dispatch(uiActions.createNotification({content: 'Failed to load playlist tracks', type: 'bad'})); - } else { - var tracks_uris = helpers.arrayOf('uri',response.tracks); - if (action.shuffle){ + // Not in index, and Spotify HTTP not enabled, so just play it as-is + // Fetch the playlist tracks via backend and add each track by URI + request(socket, store, 'playlists.lookup', { uri: action.uri }) + .then( + (response) => { + if (!response || response.tracks === undefined || !response.tracks) { + store.dispatch(uiActions.createNotification({ content: 'Failed to load playlist tracks', type: 'bad' })); + } else { + let tracks_uris = helpers.arrayOf('uri', response.tracks); + if (action.shuffle) { tracks_uris = helpers.shuffle(tracks_uris); - } - store.dispatch(mopidyActions.playURIs(tracks_uris, action.uri)) - } - }, - error => { - store.dispatch(coreActions.handleException( - "Mopidy: "+(error.message ? error.message : "Lookup failed"), - error - )); - } - ); - break; - - case 'MOPIDY_ENQUEUE_PLAYLIST': - - var playlist = store.getState().core.playlists[action.uri]; - - // We have the playlist loaded already, and we've got at least 1 track to start playing - if (playlist && playlist.tracks_uris && playlist.tracks_uris.length > 0){ - - // We've got all of the tracks, so just play those; no further action required - if (playlist.tracks_total == playlist.tracks_uris.length){ - var tracks_uris = Object.assign([], playlist.tracks_uris); - if (action.shuffle){ - tracks_uris = helpers.shuffle(tracks_uris); - } - store.dispatch(mopidyActions.enqueueURIs(tracks_uris, action.uri, action.play_next, action.at_position, action.offset)) - break; - } - - // Spotify-provided playlists need to be handled by the Spotify service - if (playlist.provider == 'spotify'){ - store.dispatch(spotifyActions.getAllPlaylistTracks(action.uri, action.shuffle, 'enqueue', action.play_next)); - break; - } - - // It's a Spotify playlist that we haven't loaded yet, so Spotify HTTP API needs to go get it - } else if (helpers.uriSource(action.uri) == 'spotify' && store.getState().spotify.enabled){ - store.dispatch(spotifyActions.getAllPlaylistTracks(action.uri, action.shuffle, 'enqueue', action.play_next)); - break } + store.dispatch(mopidyActions.playURIs(tracks_uris, action.uri)); + } + }, + (error) => { + store.dispatch(coreActions.handleException( + `Mopidy: ${error.message ? error.message : 'Lookup failed'}`, + error, + )); + }, + ); + break; - // Not in index, and Spotify HTTP not enabled, so just play it as-is - // Fetch the playlist tracks via backend and add each track by URI - request(socket, store, 'playlists.lookup', {uri: action.uri}) - .then( - response => { - if (response.tracks === undefined){ - store.dispatch(uiActions.createNotification({content: 'Failed to load playlist tracks', type: 'bad'})); - } else { - var tracks_uris = helpers.arrayOf('uri',response.tracks); - if (action.shuffle){ + case 'MOPIDY_ENQUEUE_PLAYLIST': + + var playlist = store.getState().core.playlists[action.uri]; + + // We have the playlist loaded already, and we've got at least 1 track to start playing + if (playlist && playlist.tracks_uris && playlist.tracks_uris.length > 0) { + // We've got all of the tracks, so just play those; no further action required + if (playlist.tracks_total == playlist.tracks_uris.length) { + let tracks_uris = Object.assign([], playlist.tracks_uris); + if (action.shuffle) { + tracks_uris = helpers.shuffle(tracks_uris); + } + store.dispatch(mopidyActions.enqueueURIs(tracks_uris, action.uri, action.play_next, action.at_position, action.offset)); + break; + } + + // Spotify-provided playlists need to be handled by the Spotify service + if (playlist.provider == 'spotify') { + store.dispatch(spotifyActions.getAllPlaylistTracks(action.uri, action.shuffle, 'enqueue', action.play_next)); + break; + } + + // It's a Spotify playlist that we haven't loaded yet, so Spotify HTTP API needs to go get it + } else if (helpers.uriSource(action.uri) == 'spotify' && store.getState().spotify.enabled) { + store.dispatch(spotifyActions.getAllPlaylistTracks(action.uri, action.shuffle, 'enqueue', action.play_next)); + break; + } + + // Not in index, and Spotify HTTP not enabled, so just play it as-is + // Fetch the playlist tracks via backend and add each track by URI + request(socket, store, 'playlists.lookup', { uri: action.uri }) + .then( + (response) => { + if (response.tracks === undefined) { + store.dispatch(uiActions.createNotification({ content: 'Failed to load playlist tracks', type: 'bad' })); + } else { + let tracks_uris = helpers.arrayOf('uri', response.tracks); + if (action.shuffle) { tracks_uris = helpers.shuffle(tracks_uris); - } - store.dispatch(mopidyActions.enqueueURIs(tracks_uris, action.uri, action.play_next, action.at_position, action.offset)) - } - }, - error => { - store.dispatch(coreActions.handleException( - "Mopidy: "+(error.message ? error.message : "Lookup failed"), - error - )); - } - ); - break; - - case 'MOPIDY_ENQUEUE_URIS': - - if (!action.uris || action.uris.length <= 0){ - this.props.uiActions.createNotification({content: "No URIs to enqueue", type: "warning"}); - break; } + store.dispatch(mopidyActions.enqueueURIs(tracks_uris, action.uri, action.play_next, action.at_position, action.offset)); + } + }, + (error) => { + store.dispatch(coreActions.handleException( + `Mopidy: ${error.message ? error.message : 'Lookup failed'}`, + error, + )); + }, + ); + break; - store.dispatch(pusherActions.deliverBroadcast( - 'notification', - { - notification: { - content: store.getState().pusher.username +' is adding '+action.uris.length+' URIs to queue', - icon: (store.getState().core.current_track ? helpers.getTrackIcon(store.getState().core.current_track, store.getState().core) : false) - } - } - )); + case 'MOPIDY_ENQUEUE_URIS': - // split into batches - var uris = Object.assign([], action.uris) - var batches = [] - var batch_size = 5 - while (uris.length > 0){ - batches.push({ - uris: uris.splice(0,batch_size), - at_position: action.at_position, - play_next: action.play_next, - offset: action.offset + (batch_size * batches.length), - from_uri: action.from_uri - }) - } + if (!action.uris || action.uris.length <= 0) { + this.props.uiActions.createNotification({ content: 'No URIs to enqueue', type: 'warning' }); + break; + } - // pass this modified action to the reducer (and other middleware) - action.batches = batches; - next(action); + store.dispatch(pusherActions.deliverBroadcast( + 'notification', + { + notification: { + content: `${store.getState().pusher.username} is adding ${action.uris.length} URIs to queue`, + icon: (store.getState().core.current_track ? helpers.getTrackIcon(store.getState().core.current_track, store.getState().core) : false), + }, + }, + )); - // start our processor - store.dispatch(uiActions.startProcess( - 'MOPIDY_ENQUEUE_URIS_PROCESSOR', - 'Adding '+action.uris.length+' URI(s)', - { - batches: batches, - remaining: action.uris.length, - total: action.uris.length - } - )) - break + // split into batches + var uris = Object.assign([], action.uris); + var batches = []; + var batch_size = 5; + while (uris.length > 0) { + batches.push({ + uris: uris.splice(0, batch_size), + at_position: action.at_position, + play_next: action.play_next, + offset: action.offset + (batch_size * batches.length), + from_uri: action.from_uri, + }); + } - case 'MOPIDY_ENQUEUE_URIS_PROCESSOR': + // pass this modified action to the reducer (and other middleware) + action.batches = batches; + next(action); - var last_run = store.getState().ui.processes.MOPIDY_ENQUEUE_URIS_PROCESSOR + // start our processor + store.dispatch(uiActions.startProcess( + 'MOPIDY_ENQUEUE_URIS_PROCESSOR', + `Adding ${action.uris.length} URI(s)`, + { + batches, + remaining: action.uris.length, + total: action.uris.length, + }, + )); + break; - // Cancelling - if (last_run && last_run.status == 'cancelling'){ - store.dispatch(uiActions.processCancelled('MOPIDY_ENQUEUE_URIS_PROCESSOR')) - return + case 'MOPIDY_ENQUEUE_URIS_PROCESSOR': - // make sure we have some uris in the queue - } else if (action.data.batches && action.data.batches.length > 0){ + var last_run = store.getState().ui.processes.MOPIDY_ENQUEUE_URIS_PROCESSOR; - var batches = Object.assign([], action.data.batches) - var batch = batches[0] - var total_uris = 0 - for (var i = 0; i < batches.length; i++){ - total_uris += batches[i].uris.length - } - batches.shift() - store.dispatch(uiActions.updateProcess( - 'MOPIDY_ENQUEUE_URIS_PROCESSOR', - 'Adding '+total_uris+' URI(s)', - { - remaining: total_uris - } - )) + // Cancelling + if (last_run && last_run.status == 'cancelling') { + store.dispatch(uiActions.processCancelled('MOPIDY_ENQUEUE_URIS_PROCESSOR')); + return; - // no batches means we're done here - } else { - store.dispatch(uiActions.processFinishing('MOPIDY_ENQUEUE_URIS_PROCESSOR')) - break - } + // make sure we have some uris in the queue + } if (action.data.batches && action.data.batches.length > 0) { + var batches = Object.assign([], action.data.batches); + var batch = batches[0]; + let total_uris = 0; + for (var i = 0; i < batches.length; i++) { + total_uris += batches[i].uris.length; + } + batches.shift(); + store.dispatch(uiActions.updateProcess( + 'MOPIDY_ENQUEUE_URIS_PROCESSOR', + `Adding ${total_uris} URI(s)`, + { + remaining: total_uris, + }, + )); - var current_track = store.getState().core.current_track - var queue = store.getState().core.queue - var current_track_index = -1 + // no batches means we're done here + } else { + store.dispatch(uiActions.processFinishing('MOPIDY_ENQUEUE_URIS_PROCESSOR')); + break; + } - if (current_track){ - for (var i = 0; i < queue.length; i++){ - if (queue[i].tlid == current_track.tlid){ - current_track_index = i - break - } - } - } + var { current_track } = store.getState().core; + var { queue } = store.getState().core; + var current_track_index = -1; - var params = {uris: batch.uris} + if (current_track) { + for (var i = 0; i < queue.length; i++) { + if (queue[i].tlid == current_track.tlid) { + current_track_index = i; + break; + } + } + } - // Play this batch next - if (batch.play_next){ + var params = { uris: batch.uris }; - // Make sure we're playing something first - if (current_track_index > -1){ - params.at_position = current_track_index + batch.offset + 1 + // Play this batch next + if (batch.play_next) { + // Make sure we're playing something first + if (current_track_index > -1) { + params.at_position = current_track_index + batch.offset + 1; - // Default to top of queue if we're not playing - } else { - params.at_position = 0 + batch.offset - } + // Default to top of queue if we're not playing + } else { + params.at_position = 0 + batch.offset; + } - // A specific position has been defined - // NOTE: This is likely to be wrong as the original action is unaware of batches or other client requests - } else if (batch.at_position){ - params.at_position = batch.at_position + batch.offset - } + // A specific position has been defined + // NOTE: This is likely to be wrong as the original action is unaware of batches or other client requests + } else if (batch.at_position) { + params.at_position = batch.at_position + batch.offset; + } - request(socket, store, 'tracklist.add', params) - .then( - response => { - // add metadata to queue - var tlids = [] - for (var i = 0; i < response.length; i++){ - tlids.push(response[i].tlid) - } - store.dispatch(pusherActions.addQueueMetadata(tlids, batch.from_uri)) + request(socket, store, 'tracklist.add', params) + .then( + (response) => { + // add metadata to queue + const tlids = []; + for (let i = 0; i < response.length; i++) { + tlids.push(response[i].tlid); + } + store.dispatch(pusherActions.addQueueMetadata(tlids, batch.from_uri)); - // Re-run the batch checker in 100ms. This allows a small window for other - // server requests before our next batch. It's a little crude but it means the server isn't - // locked until we're completely done. - setTimeout( - function(){ - store.dispatch(uiActions.runProcess(action.type, {batches: batches})) - }, - 100 - ) - }, - error => { - store.dispatch(coreActions.handleException( - "Mopidy: "+(error.message ? error.message : "Adding tracks failed"), - error - )); - } - ) + // Re-run the batch checker in 100ms. This allows a small window for other + // server requests before our next batch. It's a little crude but it means the server isn't + // locked until we're completely done. + setTimeout( + () => { + store.dispatch(uiActions.runProcess(action.type, { batches })); + }, + 100, + ); + }, + (error) => { + store.dispatch(coreActions.handleException( + `Mopidy: ${error.message ? error.message : 'Adding tracks failed'}`, + error, + )); + }, + ); - break + break; - case 'MOPIDY_PLAY_URIS': + case 'MOPIDY_PLAY_URIS': - if (!action.uris || action.uris.length <= 0){ - this.props.uiActions.createNotification({content: "No URIs to play", type: "warning"}); - break; - } + if (!action.uris || action.uris.length <= 0) { + this.props.uiActions.createNotification({ content: 'No URIs to play', type: 'warning' }); + break; + } - // Stop the radio - if (store.getState().core.radio && store.getState().core.radio.enabled){ - store.dispatch(pusherActions.stopRadio()); - } + // Stop the radio + if (store.getState().core.radio && store.getState().core.radio.enabled) { + store.dispatch(pusherActions.stopRadio()); + } - // Clear tracklist (if set) - if (store.getState().ui.clear_tracklist_on_play){ - store.dispatch(mopidyActions.clearTracklist()); - } + // Clear tracklist (if set) + if (store.getState().ui.clear_tracklist_on_play) { + store.dispatch(mopidyActions.clearTracklist()); + } - // Shuffle/random mode - if (store.getState().mopidy.random){ + // Shuffle/random mode + if (store.getState().mopidy.random) { var first_uri_index = Math.floor(Math.random() * action.uris.length); } else { var first_uri_index = 0; } var first_uri = action.uris[first_uri_index]; - // add our first track - request(socket, store, 'tracklist.add', { uri: first_uri, at_position: 0 }) - .then( - response => { + // add our first track + request(socket, store, 'tracklist.add', { uri: first_uri, at_position: 0 }) + .then( + (response) => { + // play it (only if we got a successful lookup) + if (response.length > 0) { + store.dispatch(mopidyActions.changeTrack(response[0].tlid)); - // play it (only if we got a successful lookup) - if (response.length > 0){ - store.dispatch(mopidyActions.changeTrack(response[0].tlid)); + const tlids = []; + for (let i = 0; i < response.length; i++) { + tlids.push(response[i].tlid); + } + store.dispatch(pusherActions.addQueueMetadata(tlids, action.from_uri)); + } else { + store.dispatch(coreActions.handleException( + 'Mopidy: Failed to add some tracks', + response, + )); + } - var tlids = [] - for (var i = 0; i < response.length; i++){ - tlids.push(response[i].tlid) - } - store.dispatch(pusherActions.addQueueMetadata(tlids, action.from_uri)) - } else { - store.dispatch(coreActions.handleException( - "Mopidy: Failed to add some tracks", - response - )); - } + // Remove our first_uri as we've already added it + action.uris.splice(first_uri_index, 1); - // Remove our first_uri as we've already added it - action.uris.splice(first_uri_index, 1); + // And add the rest of our uris (if any) + if (action.uris.length > 0) { + // Wait a moment so the server can trigger track_changed etc + // this means our UI feels snappier as the first track shows up quickly + setTimeout( + () => { + store.dispatch(mopidyActions.enqueueURIs(action.uris, action.from_uri, null, 1)); + }, + 100, + ); + } + }, + (error) => { + store.dispatch(coreActions.handleException( + `Mopidy: ${error.message ? error.message : 'Adding tracks failed'}`, + error, + )); + }, + ); + break; - // And add the rest of our uris (if any) - if (action.uris.length > 0){ + case 'MOPIDY_REORDER_TRACKLIST': - // Wait a moment so the server can trigger track_changed etc - // this means our UI feels snappier as the first track shows up quickly - setTimeout( - function(){ - store.dispatch(mopidyActions.enqueueURIs(action.uris, action.from_uri, null, 1)) - }, - 100 - ) - } - }, - error => { - store.dispatch(coreActions.handleException( - "Mopidy: "+(error.message ? error.message : "Adding tracks failed"), - error - )); - } - ) - break; + // add our first track + request(socket, store, 'tracklist.move', { start: action.range_start, end: action.range_start + action.range_length, to_position: action.insert_before }) + .then( + (response) => { + // TODO: when complete, send event to confirm success/failure + }, + (error) => { + store.dispatch(coreActions.handleException( + `Mopidy: ${error.message ? error.message : 'Reorder failed'}`, + error, + )); + }, + ); + break; - case 'MOPIDY_REORDER_TRACKLIST': - - // add our first track - request(socket, store, 'tracklist.move', { start: action.range_start, end: action.range_start + action.range_length, to_position: action.insert_before } ) - .then( - response => { - // TODO: when complete, send event to confirm success/failure - }, - error => { - store.dispatch(coreActions.handleException( - "Mopidy: "+(error.message ? error.message : "Reorder failed"), - error - )); - } - ) - break; - - case 'MOPIDY_CLEAR_TRACKLIST': - request(socket, store, 'tracklist.clear') + case 'MOPIDY_CLEAR_TRACKLIST': + request(socket, store, 'tracklist.clear') .then( - response => { + (response) => { store.dispatch(coreActions.clearCurrentTrack()); store.dispatch(pusherActions.deliverBroadcast( 'notification', { notification: { - content: store.getState().pusher.username +' cleared queue' - } - } + content: `${store.getState().pusher.username} cleared queue`, + }, + }, )); - } + }, ); - break; + break; - /** + /** * =============================================================== SEARCHING ============ * ====================================================================================== - **/ + * */ - case 'MOPIDY_GET_SEARCH_RESULTS': + case 'MOPIDY_GET_SEARCH_RESULTS': - // Flush out our previous results - store.dispatch({type: 'MOPIDY_CLEAR_SEARCH_RESULTS'}); + // Flush out our previous results + store.dispatch({ type: 'MOPIDY_CLEAR_SEARCH_RESULTS' }); - var uri_schemes_to_ignore = ['spotify:']; - var uri_schemes = Object.assign([], store.getState().ui.uri_schemes_search_enabled); - for (var i = 0; i < uri_schemes.length; i++){ - if (uri_schemes_to_ignore.includes(uri_schemes[i])){ - uri_schemes.splice(i,1); + var uri_schemes_to_ignore = ['spotify:']; + var uri_schemes = Object.assign([], store.getState().ui.uri_schemes_search_enabled); + for (var i = 0; i < uri_schemes.length; i++) { + if (uri_schemes_to_ignore.includes(uri_schemes[i])) { + uri_schemes.splice(i, 1); + } + } + var uri_schemes_total = uri_schemes.length; + var uri_scheme = uri_schemes.shift(); + + if (uri_schemes_total <= 0) { + store.dispatch(uiActions.createNotification({ content: 'No sources selected', type: 'warning' })); + } else { + store.dispatch(uiActions.startProcess( + 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', + `Searching ${uri_schemes_total} Mopidy providers`, + { + context: action.context, + query: action.query, + limit: action.limit, + total: uri_schemes_total, + remaining: uri_schemes.length, + uri_scheme, + uri_schemes, + }, + )); + } + + break; + + + case 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR': + var last_run = store.getState().ui.processes.MOPIDY_GET_SEARCH_RESULTS_PROCESSOR; + + // Cancelling + if (last_run && last_run.status == 'cancelling') { + store.dispatch(uiActions.processCancelled('MOPIDY_GET_SEARCH_RESULTS_PROCESSOR')); + return; + + // No more schemes, so we're done! + } if (!action.data.uri_scheme) { + store.dispatch(uiActions.processFinishing('MOPIDY_GET_SEARCH_RESULTS_PROCESSOR')); + return; + } + + // Construct our next batch's task + var next_uri_schemes = Object.assign([], action.data.uri_schemes); + var next_uri_scheme = next_uri_schemes.shift(); + + // Update UI for this round + store.dispatch(uiActions.updateProcess( + 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', + `Searching ${action.data.uri_scheme.replace(':', '')}`, + { + remaining: action.data.uri_schemes.length, + }, + )); + + switch (action.data.context) { + // Albums + case 'albums': + + // Quick check to see if we should be cancelling + var last_run = store.getState().ui.processes.MOPIDY_GET_SEARCH_RESULTS_PROCESSOR; + if (last_run && last_run.status == 'cancelling') { + store.dispatch(uiActions.processCancelled('MOPIDY_GET_SEARCH_RESULTS_PROCESSOR')); + return; + } + + store.dispatch(uiActions.updateProcess( + 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', + `Searching ${action.data.uri_scheme.replace(':', '')}`, + )); + + var continue_process = () => { + store.dispatch(uiActions.runProcess( + 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', + { + context: action.data.context, + query: action.data.query, + limit: action.data.limit, + uri_scheme: next_uri_scheme, + uri_schemes: next_uri_schemes, + }, + )); + }; + + request(socket, store, 'library.search', { query: { album: [action.data.query] }, uris: [action.data.uri_scheme] }) + .then( + (response) => { + if (response.length > 0) { + let albums = []; + + // Merge our proper album response container + if (response[0].albums) { + albums = [...response[0].albums, ...albums]; } - } - var uri_schemes_total = uri_schemes.length; - var uri_scheme = uri_schemes.shift(); - if (uri_schemes_total <= 0){ - store.dispatch(uiActions.createNotification({content: 'No sources selected', type: 'warning'})); - } else { - store.dispatch(uiActions.startProcess( - 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', - 'Searching '+uri_schemes_total+' Mopidy providers', - { - context: action.context, - query: action.query, - limit: action.limit, - total: uri_schemes_total, - remaining: uri_schemes.length, - uri_scheme: uri_scheme, - uri_schemes: uri_schemes + // Pull the Album objects from our track responses + if (response[0].tracks) { + for (let i = 0; i < response[0].tracks.length; i++) { + if (response[0].tracks[i].album !== undefined && response[0].tracks[i].album.uri !== undefined) { + albums.push(response[0].tracks[i].album); } + } + } + + let albums_uris = helpers.arrayOf('uri', albums); + albums_uris = helpers.removeDuplicates(albums_uris); + + store.dispatch(coreActions.albumsLoaded(albums)); + + // and plug in their URIs + store.dispatch({ + type: 'MOPIDY_SEARCH_RESULTS_LOADED', + query: action.data.query, + context: action.data.context, + results: albums_uris, + }); + } + + continue_process(); + }, + (error) => { + store.dispatch(coreActions.handleException( + `Mopidy: ${error.message ? error.message : 'Search failed'}`, + error, + )); + continue_process(); + }, + ); + break; + + // Artists + case 'artists': + + // Quick check to see if we should be cancelling + var last_run = store.getState().ui.processes.MOPIDY_GET_SEARCH_RESULTS_PROCESSOR; + if (last_run && last_run.status == 'cancelling') { + store.dispatch(uiActions.processCancelled('MOPIDY_GET_SEARCH_RESULTS_PROCESSOR')); + return; + } + + store.dispatch(uiActions.updateProcess( + 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', + `Searching ${action.data.uri_scheme.replace(':', '')}`, + )); + + var continue_process = () => { + store.dispatch(uiActions.runProcess( + 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', + { + context: action.data.context, + query: action.data.query, + limit: action.data.limit, + uri_scheme: next_uri_scheme, + uri_schemes: next_uri_schemes, + }, + )); + }; + + request(socket, store, 'library.search', { query: { artist: [action.data.query] }, uris: [action.data.uri_scheme] }) + .then( + (response) => { + if (response.length > 0) { + let artists_uris = []; + + // Pull actual artist objects + if (response[0].artists) { + for (var i = 0; i < response[0].artists.length; i++) { + artists_uris.push(response[0].artists.uri); + } + } + + // Digest track artists into actual artist results + if (response[0].tracks) { + for (var i = 0; i < response[0].tracks.length; i++) { + if (response[0].tracks[i].artists) { + for (let j = 0; j < response[0].tracks[i].artists.length; j++) { + const artist = response[0].tracks[i].artists[j]; + if (artist.uri) { + artists_uris.push(artist.uri); + } + } + } + } + } + + artists_uris = helpers.removeDuplicates(artists_uris); + + // load each artist + for (var i = 0; i < artists_uris.length; i++) { + store.dispatch(mopidyActions.getArtist(artists_uris[i])); + } + + // and plug in their URIs + store.dispatch({ + type: 'MOPIDY_SEARCH_RESULTS_LOADED', + query: action.data.query, + context: action.data.context, + results: artists_uris, + }); + } + + continue_process(); + }, + (error) => { + store.dispatch(coreActions.handleException( + `Mopidy: ${error.message ? error.message : 'Search failed'}`, + error, + )); + continue_process(); + }, + ); + break; + + // Playlists + case 'playlists': + + // Quick check to see if we should be cancelling + var last_run = store.getState().ui.processes.MOPIDY_GET_SEARCH_RESULTS_PROCESSOR; + if (last_run && last_run.status == 'cancelling') { + store.dispatch(uiActions.processCancelled('MOPIDY_GET_SEARCH_RESULTS_PROCESSOR')); + return; + } + + store.dispatch(uiActions.updateProcess( + 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', + 'Searching playlists', + )); + + var continue_process = () => { + store.dispatch(uiActions.processFinishing('MOPIDY_GET_SEARCH_RESULTS_PROCESSOR')); + }; + + request(socket, store, 'playlists.asList') + .then( + (response) => { + if (response.length > 0) { + const playlists_uris = []; + + for (var i = 0; i < response.length; i++) { + const playlist = response[i]; + if (playlist.name.includes(action.data.query) && action.data.uri_schemes.includes(`${helpers.uriSource(playlist.uri)}:`)) { + playlists_uris.push(playlist.uri); + } + } + + // load each playlist + for (var i = 0; i < playlists_uris.length; i++) { + store.dispatch(mopidyActions.getPlaylist(playlists_uris[i])); + } + + // and plug in their URIs + store.dispatch({ + type: 'MOPIDY_SEARCH_RESULTS_LOADED', + query: action.data.query, + context: action.data.context, + results: playlists_uris, + }); + } + continue_process(); + }, + (error) => { + store.dispatch(coreActions.handleException( + `Mopidy: ${error.message ? error.message : 'Search failed'}`, + error, + )); + continue_process(); + }, + ); + break; + + // Tracks + case 'tracks': + + // Quick check to see if we should be cancelling + var last_run = store.getState().ui.processes.MOPIDY_GET_SEARCH_RESULTS_PROCESSOR; + if (last_run && last_run.status == 'cancelling') { + store.dispatch(uiActions.processCancelled('MOPIDY_GET_SEARCH_RESULTS_PROCESSOR')); + return; + } + + store.dispatch(uiActions.updateProcess( + 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', + `Searching ${action.data.uri_scheme.replace(':', '')}`, + )); + + var continue_process = () => { + store.dispatch(uiActions.runProcess( + 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', + { + context: action.data.context, + query: action.data.query, + limit: action.data.limit, + uri_scheme: next_uri_scheme, + uri_schemes: next_uri_schemes, + }, + )); + }; + + request(socket, store, 'library.search', { query: { any: [action.data.query] }, uris: [action.data.uri_scheme] }) + .then( + (response) => { + if (response.length > 0 && response[0].tracks !== undefined) { + const { tracks } = response[0]; + + store.dispatch({ + type: 'MOPIDY_SEARCH_RESULTS_LOADED', + query: action.data.query, + context: action.data.context, + results: helpers.formatTracks(tracks), + }); + } + continue_process(); + }, + (error) => { + store.dispatch(coreActions.handleException( + `Mopidy: ${error.message ? error.message : 'Search failed'}`, + error, + )); + continue_process(); + }, + ); + + break; + + // Search for all types + case 'all': + default: + + var process_tracks = () => { + // Quick check to see if we should be cancelling + const last_run = store.getState().ui.processes.MOPIDY_GET_SEARCH_RESULTS_PROCESSOR; + if (last_run && last_run.status == 'cancelling') { + store.dispatch(uiActions.processCancelled('MOPIDY_GET_SEARCH_RESULTS_PROCESSOR')); + return; + } + + store.dispatch(uiActions.updateProcess( + 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', + `Searching ${action.data.uri_scheme.replace(':', '')} tracks`, + { + remaining: (action.data.uri_schemes.length) + 1, + }, + )); + request(socket, store, 'library.search', { query: { any: [action.data.query] }, uris: [action.data.uri_scheme] }) + .then( + (response) => { + if (response.length > 0 && response[0].tracks !== undefined) { + const { tracks } = response[0]; + + store.dispatch({ + type: 'MOPIDY_SEARCH_RESULTS_LOADED', + query: action.data.query, + context: 'tracks', + results: helpers.formatTracks(tracks), + }); + } + + process_albums(); + }, + (error) => { + store.dispatch(coreActions.handleException( + `Mopidy: ${error.message ? error.message : 'Search failed'}`, + error, )); - } + process_albums(); + }, + ); + }; - break + var process_albums = () => { + // Quick check to see if we should be cancelling + const last_run = store.getState().ui.processes.MOPIDY_GET_SEARCH_RESULTS_PROCESSOR; + if (last_run && last_run.status == 'cancelling') { + store.dispatch(uiActions.processCancelled('MOPIDY_GET_SEARCH_RESULTS_PROCESSOR')); + return; + } + store.dispatch(uiActions.updateProcess( + 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', + `Searching ${action.data.uri_scheme.replace(':', '')} albums`, + { + remaining: (action.data.uri_schemes.length) + 0.75, + }, + )); + request(socket, store, 'library.search', { query: { album: [action.data.query] }, uris: [action.data.uri_scheme] }) + .then( + (response) => { + if (response.length > 0) { + let albums = []; - case 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR': - var last_run = store.getState().ui.processes.MOPIDY_GET_SEARCH_RESULTS_PROCESSOR; + // Merge actual album responses first + if (response[0].albums) { + albums = [...response[0].albums, ...albums]; + } - // Cancelling - if (last_run && last_run.status == 'cancelling'){ - store.dispatch(uiActions.processCancelled('MOPIDY_GET_SEARCH_RESULTS_PROCESSOR')); - return + // Then digest tracks albums + if (response[0].tracks) { + for (let i = 0; i < response[0].tracks.length; i++) { + if (response[0].tracks[i].album !== undefined && response[0].tracks[i].album.uri !== undefined) { + albums.push(response[0].tracks[i].album); + } + } + } - // No more schemes, so we're done! - } else if (!action.data.uri_scheme){ - store.dispatch(uiActions.processFinishing('MOPIDY_GET_SEARCH_RESULTS_PROCESSOR')) - return - } + let albums_uris = helpers.arrayOf('uri', albums); + albums_uris = helpers.removeDuplicates(albums_uris); - // Construct our next batch's task - var next_uri_schemes = Object.assign([], action.data.uri_schemes) - var next_uri_scheme = next_uri_schemes.shift() + store.dispatch(coreActions.albumsLoaded(albums)); - // Update UI for this round - store.dispatch(uiActions.updateProcess( - 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', - 'Searching '+action.data.uri_scheme.replace(':',''), - { - remaining: action.data.uri_schemes.length + // and plug in their URIs + store.dispatch({ + type: 'MOPIDY_SEARCH_RESULTS_LOADED', + query: action.data.query, + context: 'albums', + results: albums_uris, + }); } + + process_artists(); + }, + (error) => { + store.dispatch(coreActions.handleException( + `Mopidy: ${error.message ? error.message : 'Search failed'}`, + error, + )); + process_artists(); + }, + ); + }; + + var process_artists = () => { + // Quick check to see if we should be cancelling + const last_run = store.getState().ui.processes.MOPIDY_GET_SEARCH_RESULTS_PROCESSOR; + if (last_run && last_run.status == 'cancelling') { + store.dispatch(uiActions.processCancelled('MOPIDY_GET_SEARCH_RESULTS_PROCESSOR')); + return; + } + + store.dispatch(uiActions.updateProcess( + 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', + `Searching ${action.data.uri_scheme.replace(':', '')} artists`, + { + remaining: (action.data.uri_schemes.length) + 0.5, + }, + )); + request(socket, store, 'library.search', { query: { artist: [action.data.query] }, uris: [action.data.uri_scheme] }) + .then( + (response) => { + if (response.length > 0) { + let artists_uris = []; + + // Pull our actual artists objects + if (response[0].artists) { + for (var i = 0; i < response[0].artists.length; i++) { + artists_uris.push(response[0].artists.uri); + } + } + + // Digest tracks artists + if (response[0].tracks) { + for (var i = 0; i < response[0].tracks.length; i++) { + if (response[0].tracks[i].artists) { + for (let j = 0; j < response[0].tracks[i].artists.length; j++) { + const artist = response[0].tracks[i].artists[j]; + if (artist.uri) { + artists_uris.push(artist.uri); + } + } + } + } + } + + artists_uris = helpers.removeDuplicates(artists_uris); + + // load each artist + for (var i = 0; i < artists_uris.length; i++) { + store.dispatch(mopidyActions.getArtist(artists_uris[i])); + } + + // and plug in their URIs + store.dispatch({ + type: 'MOPIDY_SEARCH_RESULTS_LOADED', + query: action.data.query, + context: 'artists', + results: artists_uris, + }); + } + + process_playlists(); + }, + (error) => { + store.dispatch(coreActions.handleException( + `Mopidy: ${error.message ? error.message : 'Search failed'}`, + error, + )); + process_playlists(); + }, + ); + }; + var process_playlists = () => { + // Quick check to see if we should be cancelling + const last_run = store.getState().ui.processes.MOPIDY_GET_SEARCH_RESULTS_PROCESSOR; + if (last_run && last_run.status == 'cancelling') { + store.dispatch(uiActions.processCancelled('MOPIDY_GET_SEARCH_RESULTS_PROCESSOR')); + return; + } + + if (action.data.uri_scheme == 'm3u:') { + store.dispatch(uiActions.updateProcess( + 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', + `Searching ${action.data.uri_scheme.replace(':', '')} playlists`, + { + remaining: (action.data.uri_schemes.length) + 0.25, + }, )); - - switch (action.data.context){ - - // Albums - case 'albums': - - // Quick check to see if we should be cancelling - var last_run = store.getState().ui.processes.MOPIDY_GET_SEARCH_RESULTS_PROCESSOR; - if (last_run && last_run.status == 'cancelling'){ - store.dispatch(uiActions.processCancelled('MOPIDY_GET_SEARCH_RESULTS_PROCESSOR')); - return; + request(socket, store, 'playlists.asList') + .then( + (response) => { + if (response.length > 0) { + let playlists_uris = []; + for (var i = 0; i < response.length; i++) { + const playlist = response[i]; + if (playlist.name.includes(action.data.query) && action.data.uri_schemes.includes(`${helpers.uriSource(playlist.uri)}:`)) { + playlists_uris.push(playlist.uri); + } } - store.dispatch(uiActions.updateProcess( - 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', - 'Searching '+action.data.uri_scheme.replace(':','') - )) + playlists_uris = playlists_uris; - var continue_process = () => { - store.dispatch(uiActions.runProcess( - 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', - { - context: action.data.context, - query: action.data.query, - limit: action.data.limit, - uri_scheme: next_uri_scheme, - uri_schemes: next_uri_schemes - } - )) + // load each playlist + for (var i = 0; i < playlists_uris.length; i++) { + store.dispatch(mopidyActions.getPlaylist(playlists_uris[i])); } - request(socket, store, 'library.search', {query: {album: [action.data.query]}, uris: [action.data.uri_scheme]}) - .then( - response => { - if (response.length > 0){ - var albums = []; + // and plug in their URIs + store.dispatch({ + type: 'MOPIDY_SEARCH_RESULTS_LOADED', + query: action.data.query, + context: 'playlists', + results: playlists_uris, + }); + } - // Merge our proper album response container - if (response[0].albums){ - albums = [...response[0].albums, ...albums]; - } + finished(); + }, + (error) => { + store.dispatch(coreActions.handleException( + `Mopidy: ${error.message ? error.message : 'Search failed'}`, + error, + )); + finished(); + }, + ); + } else { + finished(); + } + }; - // Pull the Album objects from our track responses - if (response[0].tracks){ - for (var i = 0; i < response[0].tracks.length; i++){ - if (response[0].tracks[i].album !== undefined && response[0].tracks[i].album.uri !== undefined){ - albums.push(response[0].tracks[i].album) - } - } - } + var finished = () => { + // We're finally done searching for types on this provider + // On to the next scheme! + store.dispatch(uiActions.runProcess( + 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', + { + context: action.data.context, + query: action.data.query, + limit: action.data.limit, + uri_scheme: next_uri_scheme, + uri_schemes: next_uri_schemes, + remaining: action.data.uri_schemes.length, + }, + )); + }; - var albums_uris = helpers.arrayOf('uri',albums) - albums_uris = helpers.removeDuplicates(albums_uris) + // Kick things off with the tracks + process_tracks(); + } - store.dispatch(coreActions.albumsLoaded(albums)); - - // and plug in their URIs - store.dispatch({ - type: 'MOPIDY_SEARCH_RESULTS_LOADED', - query: action.data.query, - context: action.data.context, - results: albums_uris - }) - } - - continue_process(); - }, - error => { - store.dispatch(coreActions.handleException( - "Mopidy: "+(error.message ? error.message : "Search failed"), - error - )); - continue_process(); - } - ) - break - - // Artists - case 'artists': - - // Quick check to see if we should be cancelling - var last_run = store.getState().ui.processes.MOPIDY_GET_SEARCH_RESULTS_PROCESSOR; - if (last_run && last_run.status == 'cancelling'){ - store.dispatch(uiActions.processCancelled('MOPIDY_GET_SEARCH_RESULTS_PROCESSOR')); - return; - } - - store.dispatch(uiActions.updateProcess( - 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', - 'Searching '+action.data.uri_scheme.replace(':','') - )) - - var continue_process = () => { - store.dispatch(uiActions.runProcess( - 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', - { - context: action.data.context, - query: action.data.query, - limit: action.data.limit, - uri_scheme: next_uri_scheme, - uri_schemes: next_uri_schemes - } - )) - } - - request(socket, store, 'library.search', {query: {artist: [action.data.query]}, uris: [action.data.uri_scheme]}) - .then( - response => { - if (response.length > 0){ - var artists_uris = []; - - // Pull actual artist objects - if (response[0].artists){ - for (var i = 0; i < response[0].artists.length; i++){ - artists_uris.push(response[0].artists.uri); - } - } - - // Digest track artists into actual artist results - if (response[0].tracks){ - for (var i = 0; i < response[0].tracks.length; i++){ - if (response[0].tracks[i].artists){ - for (var j = 0; j < response[0].tracks[i].artists.length; j++){ - var artist = response[0].tracks[i].artists[j] - if (artist.uri){ - artists_uris.push(artist.uri) - } - } - } - } - } - - artists_uris = helpers.removeDuplicates(artists_uris); - - // load each artist - for (var i = 0; i < artists_uris.length; i++){ - store.dispatch(mopidyActions.getArtist(artists_uris[i])); - } - - // and plug in their URIs - store.dispatch({ - type: 'MOPIDY_SEARCH_RESULTS_LOADED', - query: action.data.query, - context: action.data.context, - results: artists_uris - }) - } - - continue_process(); - }, - error => { - store.dispatch(coreActions.handleException( - "Mopidy: "+(error.message ? error.message : "Search failed"), - error - )); - continue_process(); - } - ); - break; - - // Playlists - case 'playlists': - - // Quick check to see if we should be cancelling - var last_run = store.getState().ui.processes.MOPIDY_GET_SEARCH_RESULTS_PROCESSOR; - if (last_run && last_run.status == 'cancelling'){ - store.dispatch(uiActions.processCancelled('MOPIDY_GET_SEARCH_RESULTS_PROCESSOR')); - return; - } - - store.dispatch(uiActions.updateProcess( - 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', - 'Searching playlists' - )) - - var continue_process = () => { - store.dispatch(uiActions.processFinishing('MOPIDY_GET_SEARCH_RESULTS_PROCESSOR')) - } - - request(socket, store, 'playlists.asList') - .then( - response => { - if (response.length > 0){ - var playlists_uris = []; - - for (var i = 0; i < response.length; i++){ - var playlist = response[i] - if (playlist.name.includes(action.data.query) && action.data.uri_schemes.includes(helpers.uriSource(playlist.uri)+':')){ - playlists_uris.push(playlist.uri) - } - } - - // load each playlist - for (var i = 0; i < playlists_uris.length; i++){ - store.dispatch(mopidyActions.getPlaylist(playlists_uris[i])) - } - - // and plug in their URIs - store.dispatch({ - type: 'MOPIDY_SEARCH_RESULTS_LOADED', - query: action.data.query, - context: action.data.context, - results: playlists_uris - }) - } - continue_process(); - }, - error => { - store.dispatch(coreActions.handleException( - "Mopidy: "+(error.message ? error.message : "Search failed"), - error - )); - continue_process(); - } - ) - break - - // Tracks - case 'tracks': - - // Quick check to see if we should be cancelling - var last_run = store.getState().ui.processes.MOPIDY_GET_SEARCH_RESULTS_PROCESSOR; - if (last_run && last_run.status == 'cancelling'){ - store.dispatch(uiActions.processCancelled('MOPIDY_GET_SEARCH_RESULTS_PROCESSOR')); - return; - } - - store.dispatch(uiActions.updateProcess( - 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', - 'Searching '+action.data.uri_scheme.replace(':','') - )); - - var continue_process = () => { - store.dispatch(uiActions.runProcess( - 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', - { - context: action.data.context, - query: action.data.query, - limit: action.data.limit, - uri_scheme: next_uri_scheme, - uri_schemes: next_uri_schemes - } - )) - } - - request(socket, store, 'library.search', {query: {any: [action.data.query]}, uris: [action.data.uri_scheme]}) - .then( - response => { - if (response.length > 0 && response[0].tracks !== undefined){ - var tracks = response[0].tracks; - - store.dispatch({ - type: 'MOPIDY_SEARCH_RESULTS_LOADED', - query: action.data.query, - context: action.data.context, - results: helpers.formatTracks(tracks) - }); - } - continue_process(); - }, - error => { - store.dispatch(coreActions.handleException( - "Mopidy: "+(error.message ? error.message : "Search failed"), - error - )); - continue_process(); - } - ) - - break - - // Search for all types - case 'all': - default: - - var process_tracks = () => { - - // Quick check to see if we should be cancelling - var last_run = store.getState().ui.processes.MOPIDY_GET_SEARCH_RESULTS_PROCESSOR; - if (last_run && last_run.status == 'cancelling'){ - store.dispatch(uiActions.processCancelled('MOPIDY_GET_SEARCH_RESULTS_PROCESSOR')); - return; - } - - store.dispatch(uiActions.updateProcess( - 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', - 'Searching '+action.data.uri_scheme.replace(':','')+' tracks', - { - remaining: (action.data.uri_schemes.length) + 1 - } - )); - request(socket, store, 'library.search', {query: {any: [action.data.query]}, uris: [action.data.uri_scheme]}) - .then( - response => { - if (response.length > 0 && response[0].tracks !== undefined){ - var tracks = response[0].tracks; - - store.dispatch({ - type: 'MOPIDY_SEARCH_RESULTS_LOADED', - query: action.data.query, - context: 'tracks', - results: helpers.formatTracks(tracks) - }); - } - - process_albums(); - }, - error => { - store.dispatch(coreActions.handleException( - "Mopidy: "+(error.message ? error.message : "Search failed"), - error - )); - process_albums(); - } - ); - } - - var process_albums = () => { - - // Quick check to see if we should be cancelling - var last_run = store.getState().ui.processes.MOPIDY_GET_SEARCH_RESULTS_PROCESSOR; - if (last_run && last_run.status == 'cancelling'){ - store.dispatch(uiActions.processCancelled('MOPIDY_GET_SEARCH_RESULTS_PROCESSOR')); - return; - } - - store.dispatch(uiActions.updateProcess( - 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', - 'Searching '+action.data.uri_scheme.replace(':','')+' albums', - { - remaining: (action.data.uri_schemes.length) + 0.75 - } - )); - request(socket, store, 'library.search', {query: {album: [action.data.query]}, uris: [action.data.uri_scheme]}) - .then( - response => { - if (response.length > 0){ - var albums = []; - - // Merge actual album responses first - if (response[0].albums){ - albums = [...response[0].albums, ...albums]; - } - - // Then digest tracks albums - if (response[0].tracks){ - for (var i = 0; i < response[0].tracks.length; i++){ - if (response[0].tracks[i].album !== undefined && response[0].tracks[i].album.uri !== undefined){ - albums.push(response[0].tracks[i].album) - } - } - } - - var albums_uris = helpers.arrayOf('uri',albums) - albums_uris = helpers.removeDuplicates(albums_uris) - - store.dispatch(coreActions.albumsLoaded(albums)); - - // and plug in their URIs - store.dispatch({ - type: 'MOPIDY_SEARCH_RESULTS_LOADED', - query: action.data.query, - context: 'albums', - results: albums_uris - }) - } - - process_artists(); - }, - error => { - store.dispatch(coreActions.handleException( - "Mopidy: "+(error.message ? error.message : "Search failed"), - error - )); - process_artists(); - } - ); - } - - var process_artists = () => { - - // Quick check to see if we should be cancelling - var last_run = store.getState().ui.processes.MOPIDY_GET_SEARCH_RESULTS_PROCESSOR; - if (last_run && last_run.status == 'cancelling'){ - store.dispatch(uiActions.processCancelled('MOPIDY_GET_SEARCH_RESULTS_PROCESSOR')); - return; - } - - store.dispatch(uiActions.updateProcess( - 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', - 'Searching '+action.data.uri_scheme.replace(':','')+' artists', - { - remaining: (action.data.uri_schemes.length) + 0.5 - } - )); - request(socket, store, 'library.search', {query: {artist: [action.data.query]}, uris: [action.data.uri_scheme]}) - .then( - response => { - if (response.length > 0){ - var artists_uris = []; - - // Pull our actual artists objects - if (response[0].artists){ - for (var i = 0; i < response[0].artists.length; i++){ - artists_uris.push(response[0].artists.uri); - } - } - - // Digest tracks artists - if (response[0].tracks){ - for (var i = 0; i < response[0].tracks.length; i++){ - if (response[0].tracks[i].artists){ - for (var j = 0; j < response[0].tracks[i].artists.length; j++){ - var artist = response[0].tracks[i].artists[j] - if (artist.uri){ - artists_uris.push(artist.uri) - } - } - } - } - } - - artists_uris = helpers.removeDuplicates(artists_uris) - - // load each artist - for (var i = 0; i < artists_uris.length; i++){ - store.dispatch(mopidyActions.getArtist(artists_uris[i])) - } - - // and plug in their URIs - store.dispatch({ - type: 'MOPIDY_SEARCH_RESULTS_LOADED', - query: action.data.query, - context: 'artists', - results: artists_uris - }) - } - - process_playlists(); - }, - error => { - store.dispatch(coreActions.handleException( - "Mopidy: "+(error.message ? error.message : "Search failed"), - error - )); - process_playlists(); - } - ); - } - var process_playlists = () => { - - // Quick check to see if we should be cancelling - var last_run = store.getState().ui.processes.MOPIDY_GET_SEARCH_RESULTS_PROCESSOR; - if (last_run && last_run.status == 'cancelling'){ - store.dispatch(uiActions.processCancelled('MOPIDY_GET_SEARCH_RESULTS_PROCESSOR')); - return; - } - - if (action.data.uri_scheme == 'm3u:'){ - store.dispatch(uiActions.updateProcess( - 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', - 'Searching '+action.data.uri_scheme.replace(':','')+' playlists', - { - remaining: (action.data.uri_schemes.length) + 0.25 - } - )) - request(socket, store, 'playlists.asList') - .then( - response => { - if (response.length > 0){ - - var playlists_uris = [] - for (var i = 0; i < response.length; i++){ - var playlist = response[i] - if (playlist.name.includes(action.data.query) && action.data.uri_schemes.includes(helpers.uriSource(playlist.uri)+':')){ - playlists_uris.push(playlist.uri) - } - } - - playlists_uris = playlists_uris - - // load each playlist - for (var i = 0; i < playlists_uris.length; i++){ - store.dispatch(mopidyActions.getPlaylist(playlists_uris[i])) - } - - // and plug in their URIs - store.dispatch({ - type: 'MOPIDY_SEARCH_RESULTS_LOADED', - query: action.data.query, - context: 'playlists', - results: playlists_uris - }) - } - - finished(); - }, - error => { - store.dispatch(coreActions.handleException( - "Mopidy: "+(error.message ? error.message : "Search failed"), - error - )); - finished(); - } - ); - } else { - finished(); - } - } - - var finished = () => { - // We're finally done searching for types on this provider - // On to the next scheme! - store.dispatch(uiActions.runProcess( - 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', - { - context: action.data.context, - query: action.data.query, - limit: action.data.limit, - uri_scheme: next_uri_scheme, - uri_schemes: next_uri_schemes, - remaining: action.data.uri_schemes.length - } - )) - } - - // Kick things off with the tracks - process_tracks(); - } - - break + break; - /** + /** * =============================================================== PLAYLIST(S) ========== * ====================================================================================== - **/ + * */ - case 'MOPIDY_GET_LIBRARY_PLAYLISTS': - request(socket, store, 'playlists.asList') - .then(response => { + case 'MOPIDY_GET_LIBRARY_PLAYLISTS': + request(socket, store, 'playlists.asList') + .then((response) => { + // drop in our URI list + const playlist_uris = helpers.arrayOf('uri', response); + const playlist_uris_filtered = []; - // drop in our URI list - var playlist_uris = helpers.arrayOf('uri',response) - var playlist_uris_filtered = [] + // Remove any Spotify playlists. These will be handled by our Spotify API + for (var i = 0; i < playlist_uris.length; i++) { + if (helpers.uriSource(playlist_uris[i]) != 'spotify') { + playlist_uris_filtered.push(playlist_uris[i]); + } + } - // Remove any Spotify playlists. These will be handled by our Spotify API - for (var i = 0; i < playlist_uris.length; i++){ - if (helpers.uriSource(playlist_uris[i]) != 'spotify'){ - playlist_uris_filtered.push(playlist_uris[i]) - } - } + store.dispatch({ type: 'MOPIDY_LIBRARY_PLAYLISTS_LOADED', uris: playlist_uris_filtered }); + store.dispatch({ type: 'MOPIDY_LIBRARY_PLAYLISTS_LOADED_ALL' }); - store.dispatch({ type: 'MOPIDY_LIBRARY_PLAYLISTS_LOADED', uris: playlist_uris_filtered }); - store.dispatch({ type: 'MOPIDY_LIBRARY_PLAYLISTS_LOADED_ALL' }); + // get the full playlist objects + for (var i = 0; i < playlist_uris_filtered.length; i++) { + request(socket, store, 'playlists.lookup', { uri: playlist_uris_filtered[i] }) + .then((response) => { + const source = helpers.uriSource(response.uri); + const playlist = { - // get the full playlist objects - for (var i = 0; i < playlist_uris_filtered.length; i++){ - request(socket, store, 'playlists.lookup', { uri: playlist_uris_filtered[i] }) - .then(response => { - var source = helpers.uriSource(response.uri); - var playlist = Object.assign( - {}, - { - type: 'playlist', - name: response.name, - uri: response.uri, - source: source, + type: 'playlist', + name: response.name, + uri: response.uri, + source, provider: 'mopidy', - last_modified: response.last_modified, - tracks_total: (response.tracks ? response.tracks.length : 0 ) - } - ) + last_modified: response.last_modified, + tracks_total: (response.tracks ? response.tracks.length : 0), + }; - store.dispatch(coreActions.playlistLoaded(playlist)); - }) - } - }) - break; + store.dispatch(coreActions.playlistLoaded(playlist)); + }); + } + }); + break; - case 'MOPIDY_GET_PLAYLIST': - request(socket, store, 'playlists.lookup', action.data ) - .then(response => { - var playlist = Object.assign( - {}, - response, - { - uri: response.uri, - type: 'playlist', - is_completely_loaded: true, - provider: 'mopidy', - tracks: (response.tracks ? response.tracks : []), - tracks_total: (response.tracks ? response.tracks.length : 0) - } - ) + case 'MOPIDY_GET_PLAYLIST': + request(socket, store, 'playlists.lookup', action.data) + .then((response) => { + const playlist = { - // tracks? get the full track objects - if (playlist.tracks.length > 0){ - store.dispatch({ - type: 'MOPIDY_RESOLVE_PLAYLIST_TRACKS', - tracks: playlist.tracks, - key: playlist.uri - }); - } + ...response, + uri: response.uri, + type: 'playlist', + is_completely_loaded: true, + provider: 'mopidy', + tracks: (response.tracks ? response.tracks : []), + tracks_total: (response.tracks ? response.tracks.length : 0), + }; - store.dispatch(coreActions.playlistLoaded(playlist)); - }) - break; + // tracks? get the full track objects + if (playlist.tracks.length > 0) { + store.dispatch({ + type: 'MOPIDY_RESOLVE_PLAYLIST_TRACKS', + tracks: playlist.tracks, + key: playlist.uri, + }); + } - case 'MOPIDY_RESOLVE_PLAYLIST_TRACKS': - var tracks = Object.assign([], action.tracks) - var uris = helpers.arrayOf('uri',tracks) + store.dispatch(coreActions.playlistLoaded(playlist)); + }); + break; - request(socket, store, 'library.lookup', { uris: uris }) - .then(response => { - for (var uri in response){ - if (response.hasOwnProperty(uri)){ + case 'MOPIDY_RESOLVE_PLAYLIST_TRACKS': + var tracks = Object.assign([], action.tracks); + var uris = helpers.arrayOf('uri', tracks); - var track = response[uri][0] - if (track){ + request(socket, store, 'library.lookup', { uris }) + .then((response) => { + for (const uri in response) { + if (response.hasOwnProperty(uri)) { + var track = response[uri][0]; + if (track) { + // find the track reference, and drop in the full track data + function getByURI(trackReference) { + return track.uri == trackReference.uri; + } + const trackReferences = tracks.filter(getByURI); - // find the track reference, and drop in the full track data - function getByURI(trackReference){ - return track.uri == trackReference.uri - } - var trackReferences = tracks.filter(getByURI); - - // there could be multiple instances of this track, so accommodate this - for (var j = 0; j < trackReferences.length; j++){ - var key = tracks.indexOf(trackReferences[j]); - tracks[key] = track; - } - } - } - } - - store.dispatch({ - type: 'PLAYLIST_TRACKS', - tracks: tracks, - key: action.key - }); - }) - break - - case 'MOPIDY_ADD_PLAYLIST_TRACKS': - - request(socket, store, 'playlists.lookup', { uri: action.key }) - .then(response => { - var tracks = []; - for(var i = 0; i < action.tracks_uris.length; i++){ - tracks.push({ - __model__: "Track", - uri: action.tracks_uris[i] - }); - } - - var playlist = Object.assign({}, response) - if (playlist.tracks){ - playlist.tracks = [...playlist.tracks, ...tracks] - } else { - playlist.tracks = tracks - } - - request(socket, store, 'playlists.save', { playlist: playlist } ) - .then(response => { - store.dispatch({ - type: 'PLAYLIST_TRACKS_ADDED', - key: action.key, - tracks_uris: action.tracks_uris - }); - }) - }); - break - - case 'MOPIDY_REMOVE_PLAYLIST_TRACKS': - - // reverse order our indexes (otherwise removing from top will affect the keys following) - function descending(a,b){ - return b-a; + // there could be multiple instances of this track, so accommodate this + for (let j = 0; j < trackReferences.length; j++) { + const key = tracks.indexOf(trackReferences[j]); + tracks[key] = track; + } } - var indexes = Object.assign([], action.tracks_indexes) - indexes.sort(descending); + } + } - request(socket, store, 'playlists.lookup', { uri: action.key }) - .then(response => { - var playlist = Object.assign({}, response) - for(var i = 0; i < indexes.length; i++){ - playlist.tracks.splice(indexes[i], 1); - } - request(socket, store, 'playlists.save', { playlist: playlist } ) - .then(response => { - store.dispatch({ - type: 'PLAYLIST_TRACKS_REMOVED', - key: action.key, - tracks_indexes: action.tracks_indexes - }); - }) - }); - break + store.dispatch({ + type: 'PLAYLIST_TRACKS', + tracks, + key: action.key, + }); + }); + break; - case 'MOPIDY_SAVE_PLAYLIST': + case 'MOPIDY_ADD_PLAYLIST_TRACKS': - // Even though we have the full playlist in our index, our "playlists.save" request - // requires a Mopidy playlist object (with updates) - request(socket, store, 'playlists.lookup', { uri: action.key }) - .then(response => { + request(socket, store, 'playlists.lookup', { uri: action.key }) + .then((response) => { + const tracks = []; + for (let i = 0; i < action.tracks_uris.length; i++) { + tracks.push({ + __model__: 'Track', + uri: action.tracks_uris[i], + }); + } - var mopidy_playlist = Object.assign({}, response, { name: action.name }); + const playlist = { ...response }; + if (playlist.tracks) { + playlist.tracks = [...playlist.tracks, ...tracks]; + } else { + playlist.tracks = tracks; + } - request(socket, store, 'playlists.save', { playlist: mopidy_playlist }) - .then(response => { + request(socket, store, 'playlists.save', { playlist }) + .then((response) => { + store.dispatch({ + type: 'PLAYLIST_TRACKS_ADDED', + key: action.key, + tracks_uris: action.tracks_uris, + }); + }); + }); + break; + case 'MOPIDY_REMOVE_PLAYLIST_TRACKS': + + // reverse order our indexes (otherwise removing from top will affect the keys following) + function descending(a, b) { + return b - a; + } + var indexes = Object.assign([], action.tracks_indexes); + indexes.sort(descending); + + request(socket, store, 'playlists.lookup', { uri: action.key }) + .then((response) => { + const playlist = { ...response }; + for (let i = 0; i < indexes.length; i++) { + playlist.tracks.splice(indexes[i], 1); + } + request(socket, store, 'playlists.save', { playlist }) + .then((response) => { + store.dispatch({ + type: 'PLAYLIST_TRACKS_REMOVED', + key: action.key, + tracks_indexes: action.tracks_indexes, + }); + }); + }); + break; + + case 'MOPIDY_SAVE_PLAYLIST': + + // Even though we have the full playlist in our index, our "playlists.save" request + // requires a Mopidy playlist object (with updates) + request(socket, store, 'playlists.lookup', { uri: action.key }) + .then((response) => { + const mopidy_playlist = { ...response, name: action.name }; + + request(socket, store, 'playlists.save', { playlist: mopidy_playlist }) + .then((response) => { // Overwrite our playlist with the response to our save // This is essential to get the updated URI from Mopidy - var playlist = Object.assign( - {}, - store.getState().core.playlists[action.key], - { - uri: response.uri, - name: response.name - } - ); + const playlist = { - // When we rename a playlist, the URI also changes to reflect the name change. - // We need to update our index, as well as redirect our current page URL. - if (action.key !== playlist.uri){ + ...store.getState().core.playlists[action.key], + uri: response.uri, + name: response.name, + }; + // When we rename a playlist, the URI also changes to reflect the name change. + // We need to update our index, as well as redirect our current page URL. + if (action.key !== playlist.uri) { // Remove old playlist (by old key/uri) from index // By providing the new key, the old playlist gets replaced with a redirector object - store.dispatch(coreActions.removeFromIndex('playlists', action.key, playlist.uri)); - } + store.dispatch(coreActions.removeFromIndex('playlists', action.key, playlist.uri)); + } - store.dispatch(coreActions.playlistLoaded(playlist)); + store.dispatch(coreActions.playlistLoaded(playlist)); - store.dispatch(uiActions.createNotification({type: 'info', content: 'Playlist saved'})); - }) - }); - break + store.dispatch(uiActions.createNotification({ type: 'info', content: 'Playlist saved' })); + }); + }); + break; - case 'MOPIDY_REORDER_PLAYLIST_TRACKS': - request(socket, store, 'playlists.lookup', { uri: action.key }) - .then(response => { + case 'MOPIDY_REORDER_PLAYLIST_TRACKS': + request(socket, store, 'playlists.lookup', { uri: action.key }) + .then((response) => { + let playlist = { ...response }; + const tracks = Object.assign([], playlist.tracks); + const tracks_to_move = []; - var playlist = Object.assign({}, response) - var tracks = Object.assign([], playlist.tracks) - var tracks_to_move = [] + // calculate destination index: if dragging down, accommodate the offset created by the tracks we're moving + const { range_start } = action; + const { range_length } = action; + let { insert_before } = action; + if (insert_before > range_start) insert_before -= range_length; - // calculate destination index: if dragging down, accommodate the offset created by the tracks we're moving - var range_start = action.range_start - var range_length = action.range_length - var insert_before = action.insert_before - if (insert_before > range_start ) insert_before = insert_before - range_length + // collate our tracks to be moved + for (var i = 0; i < range_length; i++) { + // add to FRONT: we work backwards to avoid screwing up our indexes + tracks_to_move.unshift(tracks[range_start + i]); + } - // collate our tracks to be moved - for(var i = 0; i < range_length; i++){ + // remove tracks from their old location + tracks.splice(range_start, range_length); - // add to FRONT: we work backwards to avoid screwing up our indexes - tracks_to_move.unshift(tracks[range_start + i] ) - } + // now plug them back in, in their new location + for (var i = 0; i < tracks_to_move.length; i++) { + tracks.splice(insert_before, 0, tracks_to_move[i]); + } - // remove tracks from their old location - tracks.splice(range_start, range_length ) + // update playlist + playlist = { ...playlist, tracks }; + request(socket, store, 'playlists.save', { playlist }) + .then((response) => { + store.dispatch({ + type: 'MOPIDY_RESOLVE_PLAYLIST_TRACKS', + tracks: playlist.tracks, + key: playlist.uri, + }); + }); + }); + break; - // now plug them back in, in their new location - for(var i = 0; i < tracks_to_move.length; i++){ - tracks.splice(insert_before, 0, tracks_to_move[i] ) - } + case 'MOPIDY_CREATE_PLAYLIST': + request(socket, store, 'playlists.create', { name: action.name, uri_scheme: action.scheme }) + .then((response) => { + store.dispatch(uiActions.createNotification({ type: 'info', content: 'Created playlist' })); + store.dispatch(coreActions.playlistLoaded(response)); + store.dispatch({ + type: 'MOPIDY_LIBRARY_PLAYLIST_CREATED', + key: response.uri, + }); + }); + break; - // update playlist - playlist = Object.assign({}, playlist, { tracks: tracks }) - request(socket, store, 'playlists.save', { playlist: playlist } ) - .then(response => { - store.dispatch({ - type: 'MOPIDY_RESOLVE_PLAYLIST_TRACKS', - tracks: playlist.tracks, - key: playlist.uri - }); - }); - }); - break - - case 'MOPIDY_CREATE_PLAYLIST': - request(socket, store, 'playlists.create', { name: action.name, uri_scheme: action.scheme }) - .then(response => { - store.dispatch(uiActions.createNotification({type: 'info', content: 'Created playlist'})); - store.dispatch(coreActions.playlistLoaded(response)); - store.dispatch({ - type: 'MOPIDY_LIBRARY_PLAYLIST_CREATED', - key: response.uri - }) - }); - break - - case 'MOPIDY_DELETE_PLAYLIST': - request(socket, store, 'playlists.delete', { uri: action.uri }) - .then(response => { - store.dispatch(uiActions.createNotification({content: 'Deleted playlist'})); - store.dispatch(coreActions.removeFromIndex('playlists', action.uri)); - store.dispatch({ - type: 'MOPIDY_LIBRARY_PLAYLIST_DELETED', - key: action.uri - }); - }); - break + case 'MOPIDY_DELETE_PLAYLIST': + request(socket, store, 'playlists.delete', { uri: action.uri }) + .then((response) => { + store.dispatch(uiActions.createNotification({ content: 'Deleted playlist' })); + store.dispatch(coreActions.removeFromIndex('playlists', action.uri)); + store.dispatch({ + type: 'MOPIDY_LIBRARY_PLAYLIST_DELETED', + key: action.uri, + }); + }); + break; - /** + /** * =============================================================== ALBUM(S) ============= * ====================================================================================== - **/ + * */ - case 'MOPIDY_GET_LIBRARY_ALBUMS': - var last_run = store.getState().ui.processes.MOPIDY_LIBRARY_ALBUMS_PROCESSOR + case 'MOPIDY_GET_LIBRARY_ALBUMS': + var last_run = store.getState().ui.processes.MOPIDY_LIBRARY_ALBUMS_PROCESSOR; - if (!last_run){ - request(socket, store, 'library.browse', { uri: store.getState().mopidy.library_albums_uri }) - .then(response => { - if (response.length <= 0) return + if (!last_run) { + request(socket, store, 'library.browse', { uri: store.getState().mopidy.library_albums_uri }) + .then((response) => { + if (response.length <= 0) return; - var uris = helpers.arrayOf('uri',response) - store.dispatch({ - type: 'MOPIDY_LIBRARY_ALBUMS_LOADED', - uris: uris - }); + const uris = helpers.arrayOf('uri', response); + store.dispatch({ + type: 'MOPIDY_LIBRARY_ALBUMS_LOADED', + uris, + }); - // Start our process to load the full album objects - store.dispatch(uiActions.startProcess( - 'MOPIDY_LIBRARY_ALBUMS_PROCESSOR', - 'Loading '+uris.length+' local albums', - { - uris: uris, - total: uris.length, - remaining: uris.length - } - )) - }) + // Start our process to load the full album objects + store.dispatch(uiActions.startProcess( + 'MOPIDY_LIBRARY_ALBUMS_PROCESSOR', + `Loading ${uris.length} local albums`, + { + uris, + total: uris.length, + remaining: uris.length, + }, + )); + }); + } else if (last_run.status == 'cancelled') { + store.dispatch(uiActions.resumeProcess('MOPIDY_LIBRARY_ALBUMS_PROCESSOR')); + } else if (last_run.status == 'finished') { + // TODO: do we want to force a refresh? + } - } else if (last_run.status == 'cancelled'){ - store.dispatch(uiActions.resumeProcess('MOPIDY_LIBRARY_ALBUMS_PROCESSOR')) - } else if (last_run.status == 'finished'){ - // TODO: do we want to force a refresh? - } + break; - break; + case 'MOPIDY_LIBRARY_ALBUMS_PROCESSOR': + if (store.getState().ui.processes.MOPIDY_LIBRARY_ALBUMS_PROCESSOR !== undefined) { + const processor = store.getState().ui.processes.MOPIDY_LIBRARY_ALBUMS_PROCESSOR; - case 'MOPIDY_LIBRARY_ALBUMS_PROCESSOR': - if (store.getState().ui.processes['MOPIDY_LIBRARY_ALBUMS_PROCESSOR'] !== undefined){ - var processor = store.getState().ui.processes['MOPIDY_LIBRARY_ALBUMS_PROCESSOR'] + if (processor.status == 'cancelling') { + store.dispatch(uiActions.processCancelled('MOPIDY_LIBRARY_ALBUMS_PROCESSOR')); + return false; + } + } - if (processor.status == 'cancelling'){ - store.dispatch(uiActions.processCancelled('MOPIDY_LIBRARY_ALBUMS_PROCESSOR')) - return false - } - } + var uris = Object.assign([], action.data.uris); + var uris_to_load = uris.splice(0, 50); - var uris = Object.assign([], action.data.uris); - var uris_to_load = uris.splice(0,50); + if (uris_to_load.length > 0) { + store.dispatch(uiActions.updateProcess( + 'MOPIDY_LIBRARY_ALBUMS_PROCESSOR', + `Loading ${uris.length} local albums`, + { + uris, + remaining: uris.length, + }, + )); + store.dispatch(mopidyActions.getAlbums(uris_to_load, { name: 'MOPIDY_LIBRARY_ALBUMS_PROCESSOR', data: { uris } })); + } else { + store.dispatch(uiActions.processFinishing('MOPIDY_LIBRARY_ALBUMS_PROCESSOR')); + } - if (uris_to_load.length > 0){ - store.dispatch(uiActions.updateProcess( - 'MOPIDY_LIBRARY_ALBUMS_PROCESSOR', - 'Loading '+uris.length+' local albums', - { - uris: uris, - remaining: uris.length - } - )); - store.dispatch(mopidyActions.getAlbums(uris_to_load, {name: 'MOPIDY_LIBRARY_ALBUMS_PROCESSOR', data: {uris: uris}})) - } else { - store.dispatch(uiActions.processFinishing('MOPIDY_LIBRARY_ALBUMS_PROCESSOR')) - } + break; - break - - case 'MOPIDY_GET_ALBUMS': - request(socket, store, 'library.lookup', { uris: action.uris } ) - .then(response => { - if (response.length <= 0){ + case 'MOPIDY_GET_ALBUMS': + request(socket, store, 'library.lookup', { uris: action.uris }) + .then((response) => { + if (response.length <= 0) { return; - } + } - var albums_loaded = []; - var artists_loaded = []; - var tracks_loaded = []; + const albums_loaded = []; + const artists_loaded = []; + const tracks_loaded = []; - for (var uri in response){ - if (response.hasOwnProperty(uri) && response[uri].length > 0 && response[uri][0].album){ - var tracks = response[uri]; + for (const uri in response) { + if (response.hasOwnProperty(uri) && response[uri].length > 0 && response[uri][0].album) { + const tracks = response[uri]; - var artists_uris = []; - if (tracks[0].artists){ - for (var artist of response[uri][0].artists){ - artists_uris.push(artist.uri); + const artists_uris = []; + if (tracks[0].artists) { + for (const artist of response[uri][0].artists) { + artists_uris.push(artist.uri); artists_loaded.push(artist); - } + } } - var tracks_uris = []; - for (var track of tracks){ - tracks_uris.push(track.uri); + const tracks_uris = []; + for (const track of tracks) { + tracks_uris.push(track.uri); tracks_loaded.push(track); } - var album = Object.assign( - {}, - { - source: 'local', - artists_uris: artists_uris, - tracks_uris: tracks_uris, - tracks_total: tracks_uris.length - }, - tracks[0].album - ) + const album = { - albums_loaded.push(album); - } - } + source: 'local', + artists_uris, + tracks_uris, + tracks_total: tracks_uris.length, + ...tracks[0].album, + }; - store.dispatch(coreActions.albumsLoaded(albums_loaded)); - store.dispatch(coreActions.artistsLoaded(artists_loaded)); - store.dispatch(coreActions.tracksLoaded(tracks_loaded)); + albums_loaded.push(album); + } + } - // Re-run any consequential processes in a few ms. This allows a small window for other - // server requests before our next batch. It's a little crude but it means the server isn't - // locked until we're completely done. - if (action.processor){ - setTimeout( - function(){ - store.dispatch(uiActions.runProcess(action.processor.name, action.processor.data)) - }, - 10 - ) - } - }) - break; + store.dispatch(coreActions.albumsLoaded(albums_loaded)); + store.dispatch(coreActions.artistsLoaded(artists_loaded)); + store.dispatch(coreActions.tracksLoaded(tracks_loaded)); - case 'MOPIDY_GET_ALBUM': - request(socket, store, 'library.lookup', action.data) - .then(response => { - if (!response || response.length <= 0){ - return; - } + // Re-run any consequential processes in a few ms. This allows a small window for other + // server requests before our next batch. It's a little crude but it means the server isn't + // locked until we're completely done. + if (action.processor) { + setTimeout( + () => { + store.dispatch(uiActions.runProcess(action.processor.name, action.processor.data)); + }, + 10, + ); + } + }); + break; - var artists = []; - if (response[0].artists){ - for (var artist of response[0].artists){ + case 'MOPIDY_GET_ALBUM': + request(socket, store, 'library.lookup', action.data) + .then((response) => { + if (!response || response.length <= 0) { + return; + } + + const artists = []; + if (response[0].artists) { + for (const artist of response[0].artists) { artists.push(artist); - } - } + } + } - var album = Object.assign( - {}, - response[0].album, - { - source: 'local', - artists_uris: helpers.arrayOf('uri', artists), - tracks_uris: helpers.arrayOf('uri', response), - tracks_total: response.length - } - ); - - store.dispatch(coreActions.albumLoaded(album)); - store.dispatch(coreActions.artistsLoaded(artists)); + const album = { - // load artwork from LastFM - if (!response[0].album.images){ - var mbid = helpers.getFromUri('mbid',album.uri); - if (mbid){ - store.dispatch(lastfmActions.getAlbum(album.uri, false, false, mbid)); - } else if (artists && artists.length > 0){ - store.dispatch(lastfmActions.getAlbum(album.uri, artists[0].name, album.name)); - } - } + ...response[0].album, + source: 'local', + artists_uris: helpers.arrayOf('uri', artists), + tracks_uris: helpers.arrayOf('uri', response), + tracks_total: response.length, + }; - request(socket, store, 'library.lookup', { uris: album.tracks_uris } ) - .then(response => { - var tracks_loaded = []; + store.dispatch(coreActions.albumLoaded(album)); + store.dispatch(coreActions.artistsLoaded(artists)); - for (var uri in response){ - if (response.hasOwnProperty(uri)){ - tracks_loaded.push(response[uri][0]); - } - } + // load artwork from LastFM + if (!response[0].album.images) { + const mbid = helpers.getFromUri('mbid', album.uri); + if (mbid) { + store.dispatch(lastfmActions.getAlbum(album.uri, false, false, mbid)); + } else if (artists && artists.length > 0) { + store.dispatch(lastfmActions.getAlbum(album.uri, artists[0].name, album.name)); + } + } - store.dispatch(coreActions.tracksLoaded(tracks_loaded)); - }) - }) - break; + request(socket, store, 'library.lookup', { uris: album.tracks_uris }) + .then((response) => { + const tracks_loaded = []; + + for (const uri in response) { + if (response.hasOwnProperty(uri)) { + tracks_loaded.push(response[uri][0]); + } + } + + store.dispatch(coreActions.tracksLoaded(tracks_loaded)); + }); + }); + break; - /** + /** * =============================================================== ARTIST(S) ============ * ====================================================================================== - **/ - case 'MOPIDY_GET_LIBRARY_ARTISTS': - request(socket, store, 'library.browse', { uri: store.getState().mopidy.library_artists_uri } ) - .then(response => { - if (response.length <= 0) return + * */ + case 'MOPIDY_GET_LIBRARY_ARTISTS': + request(socket, store, 'library.browse', { uri: store.getState().mopidy.library_artists_uri }) + .then((response) => { + if (response.length <= 0) return; - var uris = []; - for (var i = 0; i < response.length; i++){ + const uris = []; + for (let i = 0; i < response.length; i++) { + // Convert local URI to actual artist URI + // See https://github.com/mopidy/mopidy-local-sqlite/issues/39 + response[i].uri = response[i].uri.replace('local:directory?albumartist=', ''); + uris.push(response[i].uri); + } - // Convert local URI to actual artist URI - // See https://github.com/mopidy/mopidy-local-sqlite/issues/39 - response[i].uri = response[i].uri.replace('local:directory?albumartist=',''); - uris.push(response[i].uri); - } + store.dispatch(coreActions.artistsLoaded(response)); - store.dispatch(coreActions.artistsLoaded(response)); + store.dispatch({ + type: 'MOPIDY_LIBRARY_ARTISTS_LOADED', + uris, + }); + }); + break; - store.dispatch({ - type: 'MOPIDY_LIBRARY_ARTISTS_LOADED', - uris: uris - }); - }); - break; - - /** + /** * TODO: Fetch and process library artists * * We can't get specific artist artwork from Mopidy. Perhaps we fetch additional @@ -2093,356 +2054,341 @@ const MopidyMiddleware = (function(){ } break; - **/ + * */ - case 'MOPIDY_GET_ARTIST': - request(socket, store, 'library.lookup', action.data) - .then(response => { - - if (response.length <= 0){ + case 'MOPIDY_GET_ARTIST': + request(socket, store, 'library.lookup', action.data) + .then((response) => { + if (response.length <= 0) { return; - } + } - var albums = []; - for (var i = 0; i < response.length; i++){ - if (response[i].album){ - var album = Object.assign( - {}, - response[i].album, - { - uri: response[i].album.uri, - } - ); - if (album){ - function getByURI(albumToCheck){ - return album.uri == albumToCheck.uri - } - var existingAlbum = albums.find(getByURI); - if (!existingAlbum){ - albums.push(album); - } - } - } - } - if (albums){ - store.dispatch(coreActions.albumsLoaded(albums)); - } + const albums = []; + for (let i = 0; i < response.length; i++) { + if (response[i].album) { + var album = { - // Start with an empty artist object - var artist = { + ...response[i].album, + uri: response[i].album.uri, + }; + if (album) { + function getByURI(albumToCheck) { + return album.uri == albumToCheck.uri; + } + const existingAlbum = albums.find(getByURI); + if (!existingAlbum) { + albums.push(album); + } + } + } + } + if (albums) { + store.dispatch(coreActions.albumsLoaded(albums)); + } + + // Start with an empty artist object + let artist = { uri: action.data.uri, - provider: 'mopidy' - }; - - // Get the artist object from the track. This is a bit ugly because it's a simplified - // (Mopidy) artist object but gives us enough to fetch their name and artwork. - for (var raw_artist of response[0].artists){ + provider: 'mopidy', + }; + // Get the artist object from the track. This is a bit ugly because it's a simplified + // (Mopidy) artist object but gives us enough to fetch their name and artwork. + for (const raw_artist of response[0].artists) { // We're only interested in the artist we asked for - if (raw_artist.uri === artist.uri){ - artist = Object.assign({}, raw_artist); + if (raw_artist.uri === artist.uri) { + artist = { ...raw_artist }; } - } + } - // Add our tracks and albums - artist.albums_uris = helpers.arrayOf('uri',albums), - artist.tracks = response; + // Add our tracks and albums + artist.albums_uris = helpers.arrayOf('uri', albums), + artist.tracks = response; - store.dispatch(coreActions.artistLoaded(artist)); + store.dispatch(coreActions.artistLoaded(artist)); - // Load supprting information from LastFM and Discogs - var existing_artist = store.getState().core.artists[artist.uri]; - if (existing_artist) { + // Load supprting information from LastFM and Discogs + const existing_artist = store.getState().core.artists[artist.uri]; + if (existing_artist) { + if (!existing_artist.images) { + store.dispatch(discogsActions.getArtistImages(artist.uri, artist)); + } - if (!existing_artist.images) { - store.dispatch(discogsActions.getArtistImages(artist.uri, artist)); - } + // Get biography and other stats from LastFM + if (!existing_artist.biography) { + // TODO: Move this condition into lastfmActions + if (artist.musicbrainz_id) { + store.dispatch(lastfmActions.getArtist(artist.uri, false, artist.musicbrainz_id)); + } else { + store.dispatch(lastfmActions.getArtist(artist.uri, artist.name)); + } + } + } + }); + break; - // Get biography and other stats from LastFM - if (!existing_artist.biography){ - // TODO: Move this condition into lastfmActions - if (artist.musicbrainz_id){ - store.dispatch(lastfmActions.getArtist(artist.uri, false, artist.musicbrainz_id)); - } else { - store.dispatch(lastfmActions.getArtist(artist.uri, artist.name)); - } - } - } - }) - break; + case 'MOPIDY_GET_ARTISTS': + request(socket, store, 'library.lookup', { uris: action.uris }) + .then((response) => { + if (response.length <= 0) return; - case 'MOPIDY_GET_ARTISTS': - request(socket, store, 'library.lookup', {uris: action.uris}) - .then(response => { - if (response.length <= 0) return + const artists = []; - var artists = [] + for (const uri in response) { + if (response.hasOwnProperty(uri) && response[uri].length > 0 && response[uri][0].artists) { + const artist = { - for (var uri in response){ - if (response.hasOwnProperty(uri) && response[uri].length > 0 && response[uri][0].artists){ - var artist = Object.assign( - {}, - (response ? response[uri][0].artists[0] : {}), - { - provider: 'mopidy' - } - ); - artists.push(artist); - } - } + ...(response ? response[uri][0].artists[0] : {}), + provider: 'mopidy', + }; + artists.push(artist); + } + } - store.dispatch(coreActions.artistsLoaded(artists)); + store.dispatch(coreActions.artistsLoaded(artists)); - // Re-run any consequential processes in 100ms. This allows a small window for other - // server requests before our next batch. It's a little crude but it means the server isn't - // locked until we're completely done. - if (action.processor){ - setTimeout( - function(){ - store.dispatch(uiActions.runProcess(action.processor.name, action.processor.data)) - }, - 100 - ) - } - }) - break; + // Re-run any consequential processes in 100ms. This allows a small window for other + // server requests before our next batch. It's a little crude but it means the server isn't + // locked until we're completely done. + if (action.processor) { + setTimeout( + () => { + store.dispatch(uiActions.runProcess(action.processor.name, action.processor.data)); + }, + 100, + ); + } + }); + break; - /** + /** * =============================================================== TRACKS ================ * ====================================================================================== - **/ + * */ - case 'MOPIDY_GET_QUEUE': - request(socket, store, 'tracklist.getTlTracks') - .then( - response => { - store.dispatch({ - type: 'QUEUE_LOADED', - tracks: response - }); - } - ); - break; + case 'MOPIDY_GET_QUEUE': + request(socket, store, 'tracklist.getTlTracks') + .then( + (response) => { + store.dispatch({ + type: 'QUEUE_LOADED', + tracks: response, + }); + }, + ); + break; - case 'MOPIDY_GET_QUEUE_HISTORY': - request(socket, store, 'history.getHistory') - .then( - response => { - store.dispatch({ - type: 'MOPIDY_QUEUE_HISTORY', - tracks: response - }); - } - ); - break; + case 'MOPIDY_GET_QUEUE_HISTORY': + request(socket, store, 'history.getHistory') + .then( + (response) => { + store.dispatch({ + type: 'MOPIDY_QUEUE_HISTORY', + tracks: response, + }); + }, + ); + break; - case 'MOPIDY_GET_CURRENT_TRACK': - request(socket, store, 'playback.getCurrentTlTrack') - .then( - response => { - if (response && response.track){ - store.dispatch(mopidyActions.currentTrackLoaded(response)); - } - } - ); - break; + case 'MOPIDY_GET_CURRENT_TRACK': + request(socket, store, 'playback.getCurrentTlTrack') + .then( + (response) => { + if (response && response.track) { + store.dispatch(mopidyActions.currentTrackLoaded(response)); + } + }, + ); + break; - case 'MOPIDY_CURRENT_TRACK_LOADED': + case 'MOPIDY_CURRENT_TRACK_LOADED': - // Let the UI know we're finished transition - store.dispatch(uiActions.setCurrentTrackTransition(false)); + // Let the UI know we're finished transition + store.dispatch(uiActions.setCurrentTrackTransition(false)); - var track = helpers.formatTrack(action.tl_track); - if (track.uri){ - - // Deliver the data we've got already - store.dispatch({ - type: 'CURRENT_TRACK_LOADED', - track: track, - uri: track.uri - }); - - // Now attempt to get supporting images - if (store.getState().core.tracks[track.uri] === undefined || store.getState().core.tracks[track.uri].images === undefined){ + var track = helpers.formatTrack(action.tl_track); + if (track.uri) { + // Deliver the data we've got already + store.dispatch({ + type: 'CURRENT_TRACK_LOADED', + track, + uri: track.uri, + }); + // Now attempt to get supporting images + if (store.getState().core.tracks[track.uri] === undefined || store.getState().core.tracks[track.uri].images === undefined) { // We've got Spotify running, and it's a spotify track - go straight to the source! - if (store.getState().spotify.enabled && helpers.uriSource(track.uri) == 'spotify'){ + if (store.getState().spotify.enabled && helpers.uriSource(track.uri) == 'spotify') { store.dispatch(spotifyActions.getTrack(track.uri)); // Some other source, rely on Mopidy backends to do their work } else { - store.dispatch(mopidyActions.getImages('tracks',[track.uri])); + store.dispatch(mopidyActions.getImages('tracks', [track.uri])); } } } - break; + break; - case 'MOPIDY_GET_NEXT_TRACK': - request(socket, store, 'tracklist.getNextTlid') - .then( - response => { - if (response && response >= 0){ + case 'MOPIDY_GET_NEXT_TRACK': + request(socket, store, 'tracklist.getNextTlid') + .then( + (response) => { + if (response && response >= 0) { + // Get the full track object from our tracklist + // We know it will be here, as the tlid refers to an item in this list + const track = helpers.applyFilter('tlid', response, store.getState().core.queue, true); - // Get the full track object from our tracklist - // We know it will be here, as the tlid refers to an item in this list - var track = helpers.applyFilter('tlid', response, store.getState().core.queue, true); + if (track && track.uri) { + store.dispatch({ + type: 'NEXT_TRACK_LOADED', + uri: track.uri, + }); - if (track && track.uri){ - store.dispatch({ - type: 'NEXT_TRACK_LOADED', - uri: track.uri - }); + // We don't have the track (including images) already in our index + if (store.getState().core.tracks[track.uri] === undefined || store.getState().core.tracks[track.uri].images === undefined) { + // We've got Spotify running, and it's a spotify track - go straight to the source! + if (store.getState().spotify.enabled && helpers.uriSource(track.uri) == 'spotify') { + store.dispatch(spotifyActions.getTrack(track.uri)); - // We don't have the track (including images) already in our index - if (store.getState().core.tracks[track.uri] === undefined || store.getState().core.tracks[track.uri].images === undefined){ + // Some other source, rely on Mopidy backends to do their work + } else { + store.dispatch(mopidyActions.getImages('tracks', [track.uri])); + } + } + } + } + }, + ); + break; - // We've got Spotify running, and it's a spotify track - go straight to the source! - if (store.getState().spotify.enabled && helpers.uriSource(track.uri) == 'spotify'){ - store.dispatch(spotifyActions.getTrack(track.uri)); - - // Some other source, rely on Mopidy backends to do their work - } else { - store.dispatch(mopidyActions.getImages('tracks',[track.uri])); - } - } - } - } - } - ); - break; - - case 'MOPIDY_GET_TRACK': - request(socket, store, 'library.lookup', action.data ) - .then( - response => { - if (response.length > 0){ - var track = Object.assign({}, response[0]); - store.dispatch(coreActions.trackLoaded(track)); - } - }, - error => { - store.dispatch(coreActions.handleException( - "Mopidy: "+(error.message ? error.message : "Could not get track"), - error - )); - } - ) - break + case 'MOPIDY_GET_TRACK': + request(socket, store, 'library.lookup', action.data) + .then( + (response) => { + if (response.length > 0) { + const track = { ...response[0] }; + store.dispatch(coreActions.trackLoaded(track)); + } + }, + (error) => { + store.dispatch(coreActions.handleException( + `Mopidy: ${error.message ? error.message : 'Could not get track'}`, + error, + )); + }, + ); + break; - /** + /** * =============================================================== IMAGES =============== * ====================================================================================== - **/ + * */ - case 'MOPIDY_GET_IMAGES': - if (action.uris){ - request(socket, store, 'library.getImages', {uris: action.uris}) - .then(response => { - var records = []; - for (var uri in response){ - if (response.hasOwnProperty(uri)){ - var images = response[uri]; + case 'MOPIDY_GET_IMAGES': + if (action.uris) { + request(socket, store, 'library.getImages', { uris: action.uris }) + .then((response) => { + const records = []; + for (const uri in response) { + if (response.hasOwnProperty(uri)) { + let images = response[uri]; images = helpers.digestMopidyImages(store.getState().mopidy, images); - if (images && images.length > 0){ + if (images && images.length > 0) { records.push({ - uri: uri, - images: images - }); - + uri, + images, + }); } else { store.dispatch(lastfmActions.getImages(action.context, uri)); } } } - var action_data = { - type: (action.context+'_LOADED').toUpperCase() - } + const action_data = { + type: (`${action.context}_LOADED`).toUpperCase(), + }; action_data[action.context] = records; store.dispatch(action_data); }); - } + } - next(action); - break; + next(action); + break; - /** + /** * =============================================================== LOCAL ================ * ====================================================================================== - **/ + * */ - case 'MOPIDY_GET_DIRECTORY': - store.dispatch({ - type: 'MOPIDY_DIRECTORY_FLUSH' + case 'MOPIDY_GET_DIRECTORY': + store.dispatch({ + type: 'MOPIDY_DIRECTORY_FLUSH', + }); + + request(socket, store, 'library.browse', action.data) + .then((response) => { + const tracks_uris = []; + const subdirectories = []; + + for (const item of response) { + if (item.type === 'track') { + tracks_uris.push(item.uri); + } else { + subdirectories.push(item); + } + } + + if (tracks_uris.length > 0) { + request(socket, store, 'library.lookup', { uris: tracks_uris }) + .then((response) => { + if (response.length <= 0) { + return; + } + + const tracks = []; + + for (const uri in response) { + if (response.hasOwnProperty(uri) && response[uri].length > 0) { + tracks.push(helpers.formatTrack(response[uri][0])); + } + } + + store.dispatch({ + type: 'MOPIDY_DIRECTORY_LOADED', + directory: { + tracks, + subdirectories, + }, + }); }); + } else { + store.dispatch({ + type: 'MOPIDY_DIRECTORY_LOADED', + directory: { + tracks, + subdirectories, + }, + }); + } + }); + break; - request(socket, store, 'library.browse', action.data) - .then(response => { - - var tracks_uris = []; - var subdirectories = []; - - for (var item of response){ - if (item.type === "track"){ - tracks_uris.push(item.uri); - } else { - subdirectories.push(item); - } - } - - if (tracks_uris.length > 0){ - request(socket, store, 'library.lookup', {uris: tracks_uris}) - .then(response => { - if (response.length <= 0){ - return; - } - - var tracks = []; - - for (var uri in response){ - if (response.hasOwnProperty(uri) && response[uri].length > 0){ - tracks.push(helpers.formatTrack(response[uri][0])); - } - } - - store.dispatch({ - type: 'MOPIDY_DIRECTORY_LOADED', - directory: { - tracks: tracks, - subdirectories: subdirectories - } - }); - } - ); - } else { - store.dispatch({ - type: 'MOPIDY_DIRECTORY_LOADED', - directory: { - tracks: tracks, - subdirectories: subdirectories - } - }); - } - }); - break; - - case 'MOPIDY_DIRECTORY': - if (store.getState().ui.allow_reporting && action.data){ + case 'MOPIDY_DIRECTORY': + if (store.getState().ui.allow_reporting && action.data) { ReactGA.event({ category: 'Directory', action: 'Load', label: action.data.uri }); - } - next(action) - break - - // This action is irrelevant to us, pass it on to the next middleware - default: - return next(action) } + next(action); + break; + + // This action is irrelevant to us, pass it on to the next middleware + default: + return next(action); } + }; +}()); -})(); - -export default MopidyMiddleware +export default MopidyMiddleware; diff --git a/src/js/services/mopidy/reducer.js b/src/js/services/mopidy/reducer.js index 486e6349..d2ea594e 100755 --- a/src/js/services/mopidy/reducer.js +++ b/src/js/services/mopidy/reducer.js @@ -1,217 +1,190 @@ import * as helpers from '../../helpers'; -export default function reducer(mopidy = {}, action){ - switch (action.type){ +export default function reducer(mopidy = {}, action) { + switch (action.type) { + case 'MOPIDY_SET': + return { ...mopidy, ...action.data }; - case 'MOPIDY_SET': - return Object.assign({}, mopidy, action.data); + case 'MOPIDY_CONNECT': + case 'MOPIDY_CONNECTING': + return { + ...mopidy, + connected: false, + connecting: true, + }; - case 'MOPIDY_CONNECT': - case 'MOPIDY_CONNECTING': - return Object.assign({}, mopidy, { - connected: false, - connecting: true - }); - - case 'MOPIDY_CONNECTED': - return Object.assign({}, mopidy, { - connected: true, + case 'MOPIDY_CONNECTED': + return { + ...mopidy, + connected: true, connecting: false, restart_running: false, - upgrade_running: false - }); + upgrade_running: false, + }; - case 'MOPIDY_DISCONNECTED': - return Object.assign({}, mopidy, { - connected: false, - connecting: false - }); + case 'MOPIDY_DISCONNECTED': + return { + ...mopidy, + connected: false, + connecting: false, + }; - case 'MOPIDY_CHANGE_TRACK': - return Object.assign({}, mopidy, { - tlid: action.tlid - }); + case 'MOPIDY_CHANGE_TRACK': + return { ...mopidy, tlid: action.tlid }; - case 'MOPIDY_URI_SCHEMES': - return Object.assign({}, mopidy, { - uri_schemes: action.uri_schemes - }); + case 'MOPIDY_URI_SCHEMES': + return { ...mopidy, uri_schemes: action.uri_schemes }; - case 'MOPIDY_RESTART_STARTED': - return Object.assign({}, mopidy, { restart_running: true }); + case 'MOPIDY_RESTART_STARTED': + return { ...mopidy, restart_running: true }; - case 'MOPIDY_RESTART_FINISHED': - return Object.assign({}, mopidy, { restart_running: false }); + case 'MOPIDY_RESTART_FINISHED': + return { ...mopidy, restart_running: false }; - case 'MOPIDY_UPGRADE_STARTED': - return Object.assign({}, mopidy, { upgrade_running: true }); + case 'MOPIDY_UPGRADE_STARTED': + return { ...mopidy, upgrade_running: true }; - case 'MOPIDY_UPGRADE_FINISHED': - return Object.assign({}, mopidy, { upgrade_running: false }); + case 'MOPIDY_UPGRADE_FINISHED': + return { ...mopidy, upgrade_running: false }; - case 'MOPIDY_LOCAL_SCAN_STARTED': - return Object.assign({}, mopidy, { local_scan_running: true }); + case 'MOPIDY_LOCAL_SCAN_STARTED': + return { ...mopidy, local_scan_running: true }; - case 'MOPIDY_LOCAL_SCAN_FINISHED': - return Object.assign({}, mopidy, { local_scan_running: false }); + case 'MOPIDY_LOCAL_SCAN_FINISHED': + return { ...mopidy, local_scan_running: false }; - /** + /** * State-oriented actions - **/ - case 'MOPIDY_PLAY_STATE': - return Object.assign({}, mopidy, { - play_state: action.play_state - }); + * */ + case 'MOPIDY_PLAY_STATE': + return { ...mopidy, play_state: action.play_state }; - case 'MOPIDY_CONSUME': - return Object.assign({}, mopidy, { - consume: action.consume - }); + case 'MOPIDY_CONSUME': + return { ...mopidy, consume: action.consume }; - case 'MOPIDY_RANDOM': - return Object.assign({}, mopidy, { - random: action.random - }); + case 'MOPIDY_RANDOM': + return { ...mopidy, random: action.random }; - case 'MOPIDY_REPEAT': - return Object.assign({}, mopidy, { - repeat: action.repeat - }); + case 'MOPIDY_REPEAT': + return { ...mopidy, repeat: action.repeat }; - case 'MOPIDY_VOLUME': - return Object.assign({}, mopidy, { - volume: action.volume - }); + case 'MOPIDY_VOLUME': + return { ...mopidy, volume: action.volume }; - case 'MOPIDY_MUTE': - return Object.assign({}, mopidy, { - mute: action.mute - }); + case 'MOPIDY_MUTE': + return { ...mopidy, mute: action.mute }; - case 'MOPIDY_TIME_POSITION': - return Object.assign({}, mopidy, { - time_position: action.time_position - }); + case 'MOPIDY_TIME_POSITION': + return { ...mopidy, time_position: action.time_position }; - case 'MOPIDY_QUEUE_HISTORY': - var history = []; - for (var i = 0; i < action.tracks.length; i++){ - history.push(Object.assign( - {}, - action.tracks[i][1], - { - played_at: action.tracks[i][0], - type: 'history' - } - )) - } - return Object.assign({}, mopidy, { - queue_history: history - }); + case 'MOPIDY_QUEUE_HISTORY': + var history = []; + for (let i = 0; i < action.tracks.length; i++) { + history.push({ + + ...action.tracks[i][1], + played_at: action.tracks[i][0], + type: 'history', + }); + } + return { ...mopidy, queue_history: history }; - /** + /** * Directories * This also facilitates all backend-only music providers (SoundCloud, Dirble, etc) - **/ + * */ - case 'MOPIDY_DIRECTORY_FLUSH': - return Object.assign({}, mopidy, { - directory: null - }); + case 'MOPIDY_DIRECTORY_FLUSH': + return { ...mopidy, directory: null }; - case 'MOPIDY_DIRECTORY_LOADED': - return Object.assign({}, mopidy, { - directory: action.directory - }); + case 'MOPIDY_DIRECTORY_LOADED': + return { ...mopidy, directory: action.directory }; - /** + /** * Library - **/ + * */ - case 'MOPIDY_LIBRARY_PLAYLISTS_LOADED': - if (mopidy.library_playlists){ - var uris = [...mopidy.library_playlists,...action.uris] - } else { - var uris = action.uris - } - return Object.assign({}, mopidy, { library_playlists: helpers.removeDuplicates(uris) }) + case 'MOPIDY_LIBRARY_PLAYLISTS_LOADED': + if (mopidy.library_playlists) { + var uris = [...mopidy.library_playlists, ...action.uris]; + } else { + var { uris } = action; + } + return { ...mopidy, library_playlists: helpers.removeDuplicates(uris) }; - case 'MOPIDY_LIBRARY_PLAYLISTS_LOADED_ALL': - return Object.assign({}, mopidy, { library_playlists_loaded_all: true }) + case 'MOPIDY_LIBRARY_PLAYLISTS_LOADED_ALL': + return { ...mopidy, library_playlists_loaded_all: true }; - case 'MOPIDY_LIBRARY_PLAYLIST_CREATED': - var library_playlists = []; - if (mopidy.library_playlists){ - library_playlists = Object.assign([], mopidy.library_playlists); - } - library_playlists.push(action.key); - return Object.assign({}, mopidy, { library_playlists: library_playlists }) + case 'MOPIDY_LIBRARY_PLAYLIST_CREATED': + var library_playlists = []; + if (mopidy.library_playlists) { + library_playlists = Object.assign([], mopidy.library_playlists); + } + library_playlists.push(action.key); + return { ...mopidy, library_playlists }; - case 'MOPIDY_LIBRARY_PLAYLIST_DELETED': - var library_playlists = [] - if (mopidy.library_playlists){ - library_playlists = Object.assign([], mopidy.library_playlists); - library_playlists.splice(library_playlists.indexOf(action.uri), 1); - } - return Object.assign({}, mopidy, { library_playlists: library_playlists }) + case 'MOPIDY_LIBRARY_PLAYLIST_DELETED': + var library_playlists = []; + if (mopidy.library_playlists) { + library_playlists = Object.assign([], mopidy.library_playlists); + library_playlists.splice(library_playlists.indexOf(action.uri), 1); + } + return { ...mopidy, library_playlists }; - case 'MOPIDY_LIBRARY_ARTISTS_LOADED': - if (mopidy.library_artists){ - var uris = [...mopidy.library_artists,...action.uris] - } else { - var uris = action.uris - } - return Object.assign({}, mopidy, { library_artists: helpers.removeDuplicates(uris) }) + case 'MOPIDY_LIBRARY_ARTISTS_LOADED': + if (mopidy.library_artists) { + var uris = [...mopidy.library_artists, ...action.uris]; + } else { + var { uris } = action; + } + return { ...mopidy, library_artists: helpers.removeDuplicates(uris) }; - case 'MOPIDY_CLEAR_LIBRARY_ARTISTS': - return Object.assign({}, mopidy, { library_artists: null }) + case 'MOPIDY_CLEAR_LIBRARY_ARTISTS': + return { ...mopidy, library_artists: null }; - case 'MOPIDY_LIBRARY_ALBUMS_LOADED': - if (mopidy.library_albums){ - var uris = [...mopidy.library_albums,...action.uris] - } else { - var uris = action.uris - } - return Object.assign({}, mopidy, { library_albums: helpers.removeDuplicates(uris) }) + case 'MOPIDY_LIBRARY_ALBUMS_LOADED': + if (mopidy.library_albums) { + var uris = [...mopidy.library_albums, ...action.uris]; + } else { + var { uris } = action; + } + return { ...mopidy, library_albums: helpers.removeDuplicates(uris) }; - case 'MOPIDY_CLEAR_LIBRARY_ALBUMS': - return Object.assign({}, mopidy, { library_albums: null }) + case 'MOPIDY_CLEAR_LIBRARY_ALBUMS': + return { ...mopidy, library_albums: null }; - /** + /** * Searching - **/ + * */ - case 'MOPIDY_CLEAR_SEARCH_RESULTS': - return Object.assign({}, mopidy, { search_results: {} }); + case 'MOPIDY_CLEAR_SEARCH_RESULTS': + return { ...mopidy, search_results: {} }; - case 'MOPIDY_SEARCH_RESULTS_LOADED': + case 'MOPIDY_SEARCH_RESULTS_LOADED': - // Fetch or create our container - if (mopidy.search_results){ - var search_results = Object.assign({}, mopidy.search_results); - } else { - var search_results = {}; - } + // Fetch or create our container + if (mopidy.search_results) { + var search_results = { ...mopidy.search_results }; + } else { + var search_results = {}; + } - search_results.query = action.query; + search_results.query = action.query; - if (search_results[action.context]){ - search_results[action.context] = [...search_results[action.context], ...action.results]; - } else { - search_results[action.context] = action.results; - } + if (search_results[action.context]) { + search_results[action.context] = [...search_results[action.context], ...action.results]; + } else { + search_results[action.context] = action.results; + } - return Object.assign({}, mopidy, { search_results: search_results }); + return { ...mopidy, search_results }; - default: - return mopidy - } + default: + return mopidy; + } } - - - diff --git a/src/js/services/pusher/actions.js b/src/js/services/pusher/actions.js index 9343c738..4b7f9929 100755 --- a/src/js/services/pusher/actions.js +++ b/src/js/services/pusher/actions.js @@ -1,249 +1,249 @@ -export function setPort(port){ - return { - type: 'PUSHER_SET_PORT', - port: port - } +export function setPort(port) { + return { + type: 'PUSHER_SET_PORT', + port, + }; } -export function setUsername(username, force = false){ - return { - type: 'PUSHER_SET_USERNAME', - username: username.replace(/[\W_]+/g,''), - force: force - } +export function setUsername(username, force = false) { + return { + type: 'PUSHER_SET_USERNAME', + username: username.replace(/[\W_]+/g, ''), + force, + }; } -export function connect(){ - return { - type: 'PUSHER_CONNECT' - } +export function connect() { + return { + type: 'PUSHER_CONNECT', + }; } -export function disconnect(){ - return { - type: 'PUSHER_DISCONNECT' - } +export function disconnect() { + return { + type: 'PUSHER_DISCONNECT', + }; } -export function upgrade(){ - return { - type: 'PUSHER_UPGRADE' - } +export function upgrade() { + return { + type: 'PUSHER_UPGRADE', + }; } -export function reload(){ - return { - type: 'PUSHER_RELOAD' - } +export function reload() { + return { + type: 'PUSHER_RELOAD', + }; } -export function restart(){ - return { - type: 'PUSHER_RESTART' - } +export function restart() { + return { + type: 'PUSHER_RESTART', + }; } -export function localScan(){ - return { - type: 'PUSHER_LOCAL_SCAN' - } +export function localScan() { + return { + type: 'PUSHER_LOCAL_SCAN', + }; } -export function getConnections(){ - return { - type: 'PUSHER_GET_CONNECTIONS' - } +export function getConnections() { + return { + type: 'PUSHER_GET_CONNECTIONS', + }; } -export function connectionAdded(connection){ - return { - type: 'PUSHER_CONNECTION_ADDED', - connection: connection - } +export function connectionAdded(connection) { + return { + type: 'PUSHER_CONNECTION_ADDED', + connection, + }; } -export function updateConnection(connection){ - return { - type: 'PUSHER_UPDATE_CONNECTION', - connection: connection - } +export function updateConnection(connection) { + return { + type: 'PUSHER_UPDATE_CONNECTION', + connection, + }; } -export function connectionChanged(connection){ - return { - type: 'PUSHER_CONNECTION_CHANGED', - connection: connection - } +export function connectionChanged(connection) { + return { + type: 'PUSHER_CONNECTION_CHANGED', + connection, + }; } -export function connectionRemoved(connection){ - return { - type: 'PUSHER_CONNECTION_REMOVED', - connection: connection - } +export function connectionRemoved(connection) { + return { + type: 'PUSHER_CONNECTION_REMOVED', + connection, + }; } -export function request(method, params = null, response_callback = null, error_callback = null){ - return { - type: 'PUSHER_REQUEST', - method: method, - params: params, - response_callback: response_callback, - error_callback: error_callback - } +export function request(method, params = null, response_callback = null, error_callback = null) { + return { + type: 'PUSHER_REQUEST', + method, + params, + response_callback, + error_callback, + }; } -export function getConfig(){ - return { - type: 'PUSHER_GET_CONFIG' - } +export function getConfig() { + return { + type: 'PUSHER_GET_CONFIG', + }; } -export function getVersion(){ - return { - type: 'PUSHER_GET_VERSION' - } +export function getVersion() { + return { + type: 'PUSHER_GET_VERSION', + }; } -export function deliverBroadcast(method, params){ - return { - type: 'PUSHER_DELIVER_BROADCAST', - data: { - method: method, - params: params - } - } +export function deliverBroadcast(method, params) { + return { + type: 'PUSHER_DELIVER_BROADCAST', + data: { + method, + params, + }, + }; } -export function deliverMessage(recipient, method, params){ - return { - type: 'PUSHER_DELIVER_MESSAGE', - data: { - recipient: recipient, - method: method, - params: params - } - } +export function deliverMessage(recipient, method, params) { + return { + type: 'PUSHER_DELIVER_MESSAGE', + data: { + recipient, + method, + params, + }, + }; } -export function getRadio(){ - return { - type: 'PUSHER_GET_RADIO' - } +export function getRadio() { + return { + type: 'PUSHER_GET_RADIO', + }; } -export function startRadio(uris){ - return { - type: 'PUSHER_START_RADIO', - uris: uris - } +export function startRadio(uris) { + return { + type: 'PUSHER_START_RADIO', + uris, + }; } -export function updateRadio(uris){ - return { - type: 'PUSHER_UPDATE_RADIO', - uris: uris - } +export function updateRadio(uris) { + return { + type: 'PUSHER_UPDATE_RADIO', + uris, + }; } -export function stopRadio(){ - return { - type: 'PUSHER_STOP_RADIO' - } +export function stopRadio() { + return { + type: 'PUSHER_STOP_RADIO', + }; } -export function radioStarted(radio){ - return { - type: 'PUSHER_RADIO_STARTED', - radio: radio - } +export function radioStarted(radio) { + return { + type: 'PUSHER_RADIO_STARTED', + radio, + }; } -export function radioChanged(radio){ - return { - type: 'PUSHER_RADIO_CHANGED', - radio: radio - } +export function radioChanged(radio) { + return { + type: 'PUSHER_RADIO_CHANGED', + radio, + }; } -export function radioStopped(){ - return { - type: 'PUSHER_RADIO_STOPPED' - } +export function radioStopped() { + return { + type: 'PUSHER_RADIO_STOPPED', + }; } -export function debug(message = null){ - return { - type: 'PUSHER_DEBUG', - message: message - } +export function debug(message = null) { + return { + type: 'PUSHER_DEBUG', + message, + }; } -export function getQueueMetadata(){ - return { - type: 'PUSHER_GET_QUEUE_METADATA' - } +export function getQueueMetadata() { + return { + type: 'PUSHER_GET_QUEUE_METADATA', + }; } -export function queueMetadataChanged(queue_metadata){ - return { - type: 'PUSHER_QUEUE_METADATA_CHANGED', - queue_metadata: queue_metadata - } +export function queueMetadataChanged(queue_metadata) { + return { + type: 'PUSHER_QUEUE_METADATA_CHANGED', + queue_metadata, + }; } -export function addQueueMetadata(tlids = [], from_uri = null){ - return { - type: 'PUSHER_ADD_QUEUE_METADATA', - tlids: tlids, - from_uri: from_uri - } +export function addQueueMetadata(tlids = [], from_uri = null) { + return { + type: 'PUSHER_ADD_QUEUE_METADATA', + tlids, + from_uri, + }; } /** * Commands (buttons) - **/ + * */ -export function getCommands(){ - return { - type: 'PUSHER_GET_COMMANDS' - } +export function getCommands() { + return { + type: 'PUSHER_GET_COMMANDS', + }; } -export function setCommand(command){ - return { - type: 'PUSHER_SET_COMMAND', - command: command - } +export function setCommand(command) { + return { + type: 'PUSHER_SET_COMMAND', + command, + }; } -export function setCommands(commands){ - return { - type: 'PUSHER_SET_COMMANDS', - commands: commands - } +export function setCommands(commands) { + return { + type: 'PUSHER_SET_COMMANDS', + commands, + }; } -export function removeCommand(id){ - return { - type: 'PUSHER_REMOVE_COMMAND', - id: id - } +export function removeCommand(id) { + return { + type: 'PUSHER_REMOVE_COMMAND', + id, + }; } -export function runCommand(id, notify = false){ - return { - type: 'PUSHER_RUN_COMMAND', - id: id, - notify: notify - } +export function runCommand(id, notify = false) { + return { + type: 'PUSHER_RUN_COMMAND', + id, + notify, + }; } -export function commandsUpdated(commands){ - return { - type: 'PUSHER_COMMANDS_UPDATED', - commands: commands - } -} \ No newline at end of file +export function commandsUpdated(commands) { + return { + type: 'PUSHER_COMMANDS_UPDATED', + commands, + }; +} diff --git a/src/js/services/pusher/middleware.js b/src/js/services/pusher/middleware.js index de6edeef..c17f2223 100755 --- a/src/js/services/pusher/middleware.js +++ b/src/js/services/pusher/middleware.js @@ -1,794 +1,782 @@ import ReactGA from 'react-ga'; -var helpers = require('../../helpers'); -var coreActions = require('../core/actions'); -var uiActions = require('../ui/actions'); -var mopidyActions = require('../mopidy/actions'); -var pusherActions = require('./actions'); -var lastfmActions = require('../lastfm/actions'); -var geniusActions = require('../genius/actions'); -var spotifyActions = require('../spotify/actions'); -var snapcastActions = require('../snapcast/actions'); +const helpers = require('../../helpers'); +const coreActions = require('../core/actions'); +const uiActions = require('../ui/actions'); +const mopidyActions = require('../mopidy/actions'); +const pusherActions = require('./actions'); +const lastfmActions = require('../lastfm/actions'); +const geniusActions = require('../genius/actions'); +const spotifyActions = require('../spotify/actions'); +const snapcastActions = require('../snapcast/actions'); -const PusherMiddleware = (function(){ +const PusherMiddleware = (function () { + // container for the actual websocket + let socket = null; - // container for the actual websocket - var socket = null + // requests pending + const deferredRequests = []; - // requests pending - var deferredRequests = [] + // handle all manner of socket messages + const handleMessage = (ws, store, message) => { + if (store.getState().ui.log_pusher) { + console.log('Pusher log (incoming)', message); + } - // handle all manner of socket messages - const handleMessage = (ws, store, message) => { + // Pull our ID. JSON-RPC nests the ID under the error object, + // so make sure we handle that. + // TODO: Use this as our measure of a successful response vs error + let id = null; + if (message.id) { + id = message.id; + } else if (message.error && message.error.id) { + id = message.error.id; + } - if (store.getState().ui.log_pusher){ - console.log('Pusher log (incoming)', message); - } + // Response with request_id + if (id) { + // Response matches a pending request + if (deferredRequests[id] !== undefined) { + store.dispatch(uiActions.stopLoading(id)); - // Pull our ID. JSON-RPC nests the ID under the error object, - // so make sure we handle that. - // TODO: Use this as our measure of a successful response vs error - var id = null; - if (message.id){ - id = message.id; - } else if (message.error && message.error.id){ - id = message.error.id; - } + // Response is an error + if (message.error !== undefined) { + deferredRequests[id].reject(message.error); - // Response with request_id - if (id){ - - // Response matches a pending request - if (deferredRequests[id] !== undefined){ - - store.dispatch(uiActions.stopLoading(id)); - - // Response is an error - if (message.error !== undefined){ - deferredRequests[id].reject(message.error); - - // Successful response - } else { - deferredRequests[id].resolve(message.result); - } - - // Hmm, the response doesn't appear to be for us? - } else { - store.dispatch(coreActions.handleException( - 'Pusher: Response received with no matching request', - message - )); - } - - // General broadcast received + // Successful response } else { - - // Broadcast of an error - if (message.error !== undefined){ - store.dispatch(coreActions.handleException( - 'Pusher: '+message.error.message, - message, - (message.error.data !== undefined && message.error.data.description !== undefined ? message.error.data.description : null) - )); - - } else { - - switch (message.method){ - case 'connection_added': - store.dispatch(pusherActions.connectionAdded(message.params.connection)); - break; - case 'connection_changed': - store.dispatch(pusherActions.connectionChanged(message.params.connection)); - break; - case 'connection_removed': - store.dispatch(pusherActions.connectionRemoved(message.params.connection)); - break; - case 'queue_metadata_changed': - store.dispatch(pusherActions.queueMetadataChanged(message.params.queue_metadata)); - break; - case 'spotify_token_changed': - store.dispatch(spotifyActions.tokenChanged(message.params.spotify_token)); - break; - case 'share_configuration_received': - store.dispatch(uiActions.createNotification({ - type: 'share-configuration-received', - configuration: message.params, - sticky: true - })); - break; - case 'notification': - store.dispatch(uiActions.createNotification(message.params.notification)); - break; - case 'radio_started': - store.dispatch(pusherActions.radioStarted(message.params.radio)); - break; - case 'radio_changed': - store.dispatch(pusherActions.radioChanged(message.params.radio)); - break; - case 'radio_stopped': - store.dispatch(pusherActions.radioStopped()); - break; - case 'commands_changed': - store.dispatch(pusherActions.commandsUpdated(message.params.commands)); - break; - case 'reload': - window.location.reload(true); - break; - - // Local scan - case 'local_scan_started': - store.dispatch(uiActions.updateProcess('local_scan', 'Scanning local library')); - break; - case 'local_scan_finished': - store.dispatch(uiActions.processFinished('local_scan')); - store.dispatch(uiActions.createNotification({key: 'local_scan', type: 'info', content: 'Local scan finished', description: message.params.output})); - break; - case 'local_scan_error': - store.dispatch(uiActions.processFinished('local_scan')); - store.dispatch(uiActions.createNotification({type: 'bad', content: message.params.message, description: message.params.description})); - break; - - // Upgrade - case 'upgrade_started': - store.dispatch(uiActions.updateProcess('upgrade', 'Upgrading')); - break; - case 'upgrade_finished': - store.dispatch(uiActions.updateProcess('upgrade', 'Restarting to complete upgrade')); - break; - case 'upgrade_error': - store.dispatch(uiActions.processFinished('upgrade')); - store.dispatch(uiActions.createNotification({type: 'bad', content: message.params.message, description: message.params.description})); - break; - - // Restart - case 'restart_started': - store.dispatch(uiActions.processFinished('upgrade')); - store.dispatch(uiActions.createNotification({type: 'info', content: 'Restarting server...'})); - break; - case 'restart_error': - store.dispatch(uiActions.processFinished('upgrade')); - store.dispatch(uiActions.createNotification({type: 'bad', content: message.params.message, description: message.params.description})); - break; - - // Test - case 'test_started': - store.dispatch(uiActions.updateProcess('test', 'Running test')); - break; - case 'test_finished': - store.dispatch(uiActions.processFinished('test')); - store.dispatch(uiActions.createNotification({type: 'info', content: 'Test finished', description: message.params.output})); - break; - case 'test_error': - store.dispatch(uiActions.processFinished('test')); - store.dispatch(uiActions.createNotification({type: 'bad', content: message.params.message, description: message.params.description})); - break; - } - - // Pass snapcast events to the Snapcast service - if (message.method.startsWith('snapcast_')){ - store.dispatch(snapcastActions.eventReceived(message)); - } - } + deferredRequests[id].resolve(message.result); } + + // Hmm, the response doesn't appear to be for us? + } else { + store.dispatch(coreActions.handleException( + 'Pusher: Response received with no matching request', + message, + )); + } + + // General broadcast received + } else { + // Broadcast of an error + if (message.error !== undefined) { + store.dispatch(coreActions.handleException( + `Pusher: ${message.error.message}`, + message, + (message.error.data !== undefined && message.error.data.description !== undefined ? message.error.data.description : null), + )); + } else { + switch (message.method) { + case 'connection_added': + store.dispatch(pusherActions.connectionAdded(message.params.connection)); + break; + case 'connection_changed': + store.dispatch(pusherActions.connectionChanged(message.params.connection)); + break; + case 'connection_removed': + store.dispatch(pusherActions.connectionRemoved(message.params.connection)); + break; + case 'queue_metadata_changed': + store.dispatch(pusherActions.queueMetadataChanged(message.params.queue_metadata)); + break; + case 'spotify_token_changed': + store.dispatch(spotifyActions.tokenChanged(message.params.spotify_token)); + break; + case 'share_configuration_received': + store.dispatch(uiActions.createNotification({ + type: 'share-configuration-received', + configuration: message.params, + sticky: true, + })); + break; + case 'notification': + store.dispatch(uiActions.createNotification(message.params.notification)); + break; + case 'radio_started': + store.dispatch(pusherActions.radioStarted(message.params.radio)); + break; + case 'radio_changed': + store.dispatch(pusherActions.radioChanged(message.params.radio)); + break; + case 'radio_stopped': + store.dispatch(pusherActions.radioStopped()); + break; + case 'commands_changed': + store.dispatch(pusherActions.commandsUpdated(message.params.commands)); + break; + case 'reload': + window.location.reload(true); + break; + + // Local scan + case 'local_scan_started': + store.dispatch(uiActions.updateProcess('local_scan', 'Scanning local library')); + break; + case 'local_scan_finished': + store.dispatch(uiActions.processFinished('local_scan')); + store.dispatch(uiActions.createNotification({ + key: 'local_scan', type: 'info', content: 'Local scan finished', description: message.params.output, + })); + break; + case 'local_scan_error': + store.dispatch(uiActions.processFinished('local_scan')); + store.dispatch(uiActions.createNotification({ type: 'bad', content: message.params.message, description: message.params.description })); + break; + + // Upgrade + case 'upgrade_started': + store.dispatch(uiActions.updateProcess('upgrade', 'Upgrading')); + break; + case 'upgrade_finished': + store.dispatch(uiActions.updateProcess('upgrade', 'Restarting to complete upgrade')); + break; + case 'upgrade_error': + store.dispatch(uiActions.processFinished('upgrade')); + store.dispatch(uiActions.createNotification({ type: 'bad', content: message.params.message, description: message.params.description })); + break; + + // Restart + case 'restart_started': + store.dispatch(uiActions.processFinished('upgrade')); + store.dispatch(uiActions.createNotification({ type: 'info', content: 'Restarting server...' })); + break; + case 'restart_error': + store.dispatch(uiActions.processFinished('upgrade')); + store.dispatch(uiActions.createNotification({ type: 'bad', content: message.params.message, description: message.params.description })); + break; + + // Test + case 'test_started': + store.dispatch(uiActions.updateProcess('test', 'Running test')); + break; + case 'test_finished': + store.dispatch(uiActions.processFinished('test')); + store.dispatch(uiActions.createNotification({ type: 'info', content: 'Test finished', description: message.params.output })); + break; + case 'test_error': + store.dispatch(uiActions.processFinished('test')); + store.dispatch(uiActions.createNotification({ type: 'bad', content: message.params.message, description: message.params.description })); + break; + } + + // Pass snapcast events to the Snapcast service + if (message.method.startsWith('snapcast_')) { + store.dispatch(snapcastActions.eventReceived(message)); + } + } + } + }; + + const request = (store, method, params = null) => new Promise((resolve, reject) => { + const id = helpers.generateGuid(); + const message = { + jsonrpc: '2.0', + id, + method, + }; + if (params) { + message.params = params; } - const request = (store, method, params = null) => { - return new Promise((resolve, reject) => { - - var id = helpers.generateGuid(); - var message = { - jsonrpc: '2.0', - id: id, - method: method - } - if (params){ - message.params = params; - } - - if (store.getState().ui.log_pusher){ - console.log('Pusher log (outgoing)', message); - } - - socket.send(JSON.stringify(message)); - - store.dispatch(uiActions.startLoading(id, 'pusher_'+method)); - - // Start our 30 second timeout - var timeout = setTimeout( - function(){ - store.dispatch(uiActions.stopLoading(id)); - reject({ - id: id, - code: 32300, - message: "Request timed out" - }); - }, - 30000 - ); - - // add query to our deferred responses - deferredRequests[id] = { - resolve: resolve, - reject: reject - }; - }) + if (store.getState().ui.log_pusher) { + console.log('Pusher log (outgoing)', message); } - return store => next => action => { - var pusher = store.getState().pusher; - - switch(action.type){ + socket.send(JSON.stringify(message)); - case 'PUSHER_CONNECT': - if (socket != null){ - socket.close(); - } + store.dispatch(uiActions.startLoading(id, `pusher_${method}`)); - store.dispatch({type: 'PUSHER_CONNECTING'}); + // Start our 30 second timeout + const timeout = setTimeout( + () => { + store.dispatch(uiActions.stopLoading(id)); + reject({ + id, + code: 32300, + message: 'Request timed out', + }); + }, + 30000, + ); - var state = store.getState(); - - socket = new WebSocket( - 'ws'+(window.location.protocol === 'https:' ? 's' : '')+'://'+state.mopidy.host+':'+state.mopidy.port+'/iris/ws/' - ); + // add query to our deferred responses + deferredRequests[id] = { + resolve, + reject, + }; + }); - socket.onopen = () => { - store.dispatch({ - type: 'PUSHER_CONNECTED' - }); - }; + return (store) => (next) => (action) => { + const { pusher } = store.getState(); - socket.onclose = (e) => { - store.dispatch({ - type: 'PUSHER_DISCONNECTED' - }) + switch (action.type) { + case 'PUSHER_CONNECT': + if (socket != null) { + socket.close(); + } - // attempt to reconnect every 5 seconds - setTimeout(() => { - store.dispatch(pusherActions.connect()) - }, 5000); - }; + store.dispatch({ type: 'PUSHER_CONNECTING' }); - socket.onerror = (e) => { - if (socket.readyState == 1) { - store.dispatch(coreActions.handleException( - 'Pusher websocket error', - e, - e.type - )); - } - }; + var state = store.getState(); - socket.onmessage = (message) => { - var message = JSON.parse(message.data); - handleMessage(socket, store, message); - }; + socket = new WebSocket( + `ws${window.location.protocol === 'https:' ? 's' : ''}://${state.mopidy.host}:${state.mopidy.port}/iris/ws/`, + ); - break; + socket.onopen = () => { + store.dispatch({ + type: 'PUSHER_CONNECTED', + }); + }; - case 'PUSHER_CONNECTED': - if (store.getState().ui.allow_reporting){ - ReactGA.event({ category: 'Pusher', action: 'Connected', label: action.username}); + socket.onclose = (e) => { + store.dispatch({ + type: 'PUSHER_DISCONNECTED', + }); + + // attempt to reconnect every 5 seconds + setTimeout(() => { + store.dispatch(pusherActions.connect()); + }, 5000); + }; + + socket.onerror = (e) => { + if (socket.readyState == 1) { + store.dispatch(coreActions.handleException( + 'Pusher websocket error', + e, + e.type, + )); + } + }; + + socket.onmessage = (message) => { + var message = JSON.parse(message.data); + handleMessage(socket, store, message); + }; + + break; + + case 'PUSHER_CONNECTED': + if (store.getState().ui.allow_reporting) { + ReactGA.event({ category: 'Pusher', action: 'Connected', label: action.username }); } - store.dispatch(pusherActions.updateConnection()); - store.dispatch(pusherActions.getConfig()); - store.dispatch(pusherActions.getRadio()); - store.dispatch(pusherActions.getCommands()); - store.dispatch(pusherActions.getQueueMetadata()); + store.dispatch(pusherActions.updateConnection()); + store.dispatch(pusherActions.getConfig()); + store.dispatch(pusherActions.getRadio()); + store.dispatch(pusherActions.getCommands()); + store.dispatch(pusherActions.getQueueMetadata()); - // Give things a few moments to setup before we check for version. - // This is because the server makes a GitHub request, which creates a [very] small delay - // in subsequent requests. - setTimeout( - function(){ - store.dispatch(pusherActions.getVersion()); - }, - 500 - ); - next(action); - break; + // Give things a few moments to setup before we check for version. + // This is because the server makes a GitHub request, which creates a [very] small delay + // in subsequent requests. + setTimeout( + () => { + store.dispatch(pusherActions.getVersion()); + }, + 500, + ); + next(action); + break; - case 'PUSHER_REQUEST': - request(store, action.method, action.params) - .then( - response => { - if (action.response_callback){ + case 'PUSHER_REQUEST': + request(store, action.method, action.params) + .then( + (response) => { + if (action.response_callback) { action.response_callback.call(this, response); } - }, - error => { - if (action.error_callback){ + }, + (error) => { + if (action.error_callback) { action.error_callback.call(this, error); } else { store.dispatch(coreActions.handleException( 'Pusher request failed', error, action.method, - action + action, )); } - } - ); - break + }, + ); + break; - case 'PUSHER_DELIVER_MESSAGE': - request(store, 'send_message', action.data) - .then( - response => { - store.dispatch(uiActions.createNotification({type: 'info', content: 'Message delivered'})); - }, - error => { - store.dispatch(coreActions.handleException( - 'Could not deliver message', - error - )); - } - ); - break + case 'PUSHER_DELIVER_MESSAGE': + request(store, 'send_message', action.data) + .then( + (response) => { + store.dispatch(uiActions.createNotification({ type: 'info', content: 'Message delivered' })); + }, + (error) => { + store.dispatch(coreActions.handleException( + 'Could not deliver message', + error, + )); + }, + ); + break; - case 'PUSHER_DELIVER_BROADCAST': - request(store, 'broadcast', action.data) - break + case 'PUSHER_DELIVER_BROADCAST': + request(store, 'broadcast', action.data); + break; - case 'PUSHER_SET_USERNAME': - store.dispatch(pusherActions.updateConnection({username: action.username})); - next(action); - break; + case 'PUSHER_SET_USERNAME': + store.dispatch(pusherActions.updateConnection({ username: action.username })); + next(action); + break; - case 'PUSHER_UPDATE_CONNECTION': + case 'PUSHER_UPDATE_CONNECTION': - // Our action can provide new values during a state update (eg the field was just changed) - // but by default we refer to our existing state - let connection = { - username: store.getState().pusher.username, - client_id: store.getState().pusher.client_id, - ...(action.connection ? action.connection : {}) - } - - request(store, 'update_connection', connection) - .then( - response => { - response.type = 'PUSHER_CONNECTION_UPDATED' - store.dispatch(response) - }, - error => { - store.dispatch(coreActions.handleException( - 'Could not update connection', - error - )); - } - ); - next(action); - break; + // Our action can provide new values during a state update (eg the field was just changed) + // but by default we refer to our existing state + const connection = { + username: store.getState().pusher.username, + client_id: store.getState().pusher.client_id, + ...(action.connection ? action.connection : {}), + }; - case 'PUSHER_GET_QUEUE_METADATA': - request(store, 'get_queue_metadata') - .then( - response => { - response.type = 'PUSHER_QUEUE_METADATA' - store.dispatch(response) - }, - error => { - store.dispatch(coreActions.handleException( - 'Could not load queue metadata', - error - )); - } - ); - break; + request(store, 'update_connection', connection) + .then( + (response) => { + response.type = 'PUSHER_CONNECTION_UPDATED'; + store.dispatch(response); + }, + (error) => { + store.dispatch(coreActions.handleException( + 'Could not update connection', + error, + )); + }, + ); + next(action); + break; - case 'PUSHER_ADD_QUEUE_METADATA': - request(store, 'add_queue_metadata', { - tlids: action.tlids, - added_from: action.from_uri, - added_by: pusher.username - }) - break; + case 'PUSHER_GET_QUEUE_METADATA': + request(store, 'get_queue_metadata') + .then( + (response) => { + response.type = 'PUSHER_QUEUE_METADATA'; + store.dispatch(response); + }, + (error) => { + store.dispatch(coreActions.handleException( + 'Could not load queue metadata', + error, + )); + }, + ); + break; - case 'PUSHER_GET_VERSION': - request(store, 'get_version') - .then( - response => { - store.dispatch({ - type: 'PUSHER_VERSION', - version: response.version - }) - }, - error => { - store.dispatch(coreActions.handleException( - 'Could not load version', - error - )); - } - ); - break; + case 'PUSHER_ADD_QUEUE_METADATA': + request(store, 'add_queue_metadata', { + tlids: action.tlids, + added_from: action.from_uri, + added_by: pusher.username, + }); + break; - case 'PUSHER_GET_CONFIG': - request(store, 'get_config') - .then( - response => { - store.dispatch({ - type: 'PUSHER_CONFIG', - config: response.config - }); - }, - error => { - store.dispatch(coreActions.handleException( - 'Could not load config', - error - )); - } - ); - break; + case 'PUSHER_GET_VERSION': + request(store, 'get_version') + .then( + (response) => { + store.dispatch({ + type: 'PUSHER_VERSION', + version: response.version, + }); + }, + (error) => { + store.dispatch(coreActions.handleException( + 'Could not load version', + error, + )); + }, + ); + break; - case 'PUSHER_GET_CONNECTIONS': - request(store, 'get_connections') - .then( - response => { - store.dispatch({ - type: 'PUSHER_CONNECTIONS', - connections: response.connections - }) - }, - error => { - store.dispatch(coreActions.handleException( - 'Could not load connections', - error - )); - } - ); - return next(action); - break; + case 'PUSHER_GET_CONFIG': + request(store, 'get_config') + .then( + (response) => { + store.dispatch({ + type: 'PUSHER_CONFIG', + config: response.config, + }); + }, + (error) => { + store.dispatch(coreActions.handleException( + 'Could not load config', + error, + )); + }, + ); + break; + + case 'PUSHER_GET_CONNECTIONS': + request(store, 'get_connections') + .then( + (response) => { + store.dispatch({ + type: 'PUSHER_CONNECTIONS', + connections: response.connections, + }); + }, + (error) => { + store.dispatch(coreActions.handleException( + 'Could not load connections', + error, + )); + }, + ); + return next(action); + break; - /** + /** * Commands - **/ + * */ - case 'PUSHER_GET_COMMANDS': - request(store, 'get_commands') - .then( - response => { + case 'PUSHER_GET_COMMANDS': + request(store, 'get_commands') + .then( + (response) => { store.dispatch(pusherActions.commandsUpdated(response.commands)); - }, - error => { - // We're not too worried about capturing errors here - // It's also likely to fail where UI has been updated but - // server hasn't been restarted yet. - } - ); - next(action); - break + }, + (error) => { + // We're not too worried about capturing errors here + // It's also likely to fail where UI has been updated but + // server hasn't been restarted yet. + }, + ); + next(action); + break; - case 'PUSHER_SET_COMMAND': - var commands = Object.assign({}, pusher.commands); + case 'PUSHER_SET_COMMAND': + var commands = { ...pusher.commands }; - if (commands[action.command.id]){ - var command = Object.assign({}, commands[action.command.id], action.command); - } else { - var command = action.command; - } - commands[action.command.id] = command; + if (commands[action.command.id]) { + var command = { ...commands[action.command.id], ...action.command }; + } else { + var { command } = action; + } + commands[action.command.id] = command; - store.dispatch(pusherActions.setCommands(commands)); - break + store.dispatch(pusherActions.setCommands(commands)); + break; - case 'PUSHER_SET_COMMANDS': - request(store, 'set_commands', {commands: action.commands}) - .then( - response => { - // No action required, the change will be broadcast - }, - error => { - store.dispatch(coreActions.handleException( - 'Could not set commands', - error - )); - } - ); - - next(action); - break + case 'PUSHER_SET_COMMANDS': + request(store, 'set_commands', { commands: action.commands }) + .then( + (response) => { + // No action required, the change will be broadcast + }, + (error) => { + store.dispatch(coreActions.handleException( + 'Could not set commands', + error, + )); + }, + ); - case 'PUSHER_REMOVE_COMMAND': - var commands_index = Object.assign({}, pusher.commands); - delete commands_index[action.id]; + next(action); + break; - request(store, 'set_commands', {commands: commands_index}) - .then( - response => { - // No action required, the change will be broadcast - }, - error => { - store.dispatch(coreActions.handleException( - 'Could not remove command', - error - )); - } - ); - - next(action); - break + case 'PUSHER_REMOVE_COMMAND': + var commands_index = { ...pusher.commands }; + delete commands_index[action.id]; - case 'PUSHER_RUN_COMMAND': - var command = Object.assign({}, pusher.commands[action.id]); - var notification_key = 'command_'+action.id; - - if (action.notify){ - store.dispatch(uiActions.startProcess(notification_key, 'Running command')); - } - - request(store, 'run_command', {id: action.id}) - .then(response => { - console.log("Command response",response); + request(store, 'set_commands', { commands: commands_index }) + .then( + (response) => { + // No action required, the change will be broadcast + }, + (error) => { + store.dispatch(coreActions.handleException( + 'Could not remove command', + error, + )); + }, + ); + + next(action); + break; + + case 'PUSHER_RUN_COMMAND': + var command = { ...pusher.commands[action.id] }; + var notification_key = `command_${action.id}`; + + if (action.notify) { + store.dispatch(uiActions.startProcess(notification_key, 'Running command')); + } + + request(store, 'run_command', { id: action.id }) + .then((response) => { + console.log('Command response', response); store.dispatch(uiActions.processFinished(notification_key)); - if (action.notify){ - store.dispatch(uiActions.createNotification({key: notification_key, type: 'info', content: 'Command sent'})); - } - }, - error => { + if (action.notify) { + store.dispatch(uiActions.createNotification({ key: notification_key, type: 'info', content: 'Command sent' })); + } + }, + (error) => { store.dispatch(uiActions.processFinished(notification_key)); - store.dispatch(coreActions.handleException( - 'Could not run command', - error - )); - }) + store.dispatch(coreActions.handleException( + 'Could not run command', + error, + )); + }); - break + break; - /** + /** * Radio - **/ + * */ - case 'PUSHER_GET_RADIO': - request(store, 'get_radio') - .then( - response => { - store.dispatch({ - type: 'PUSHER_RADIO_LOADED', - radio: response.radio - }); - }, - error => { - store.dispatch(coreActions.handleException( - 'Could not load radio', - error - )); - } - ); - break; + case 'PUSHER_GET_RADIO': + request(store, 'get_radio') + .then( + (response) => { + store.dispatch({ + type: 'PUSHER_RADIO_LOADED', + radio: response.radio, + }); + }, + (error) => { + store.dispatch(coreActions.handleException( + 'Could not load radio', + error, + )); + }, + ); + break; - case 'PUSHER_START_RADIO': - case 'PUSHER_UPDATE_RADIO': - if (store.getState().ui.allow_reporting){ + case 'PUSHER_START_RADIO': + case 'PUSHER_UPDATE_RADIO': + if (store.getState().ui.allow_reporting) { ReactGA.event({ category: 'Pusher', action: 'Start radio', label: action.uris.join() }); } - // start our UI process notification - if (action.type == 'PUSHER_UPDATE_RADIO'){ - store.dispatch(uiActions.startProcess('PUSHER_RADIO_PROCESS', 'Updating radio')) - } else { - store.dispatch(uiActions.startProcess('PUSHER_RADIO_PROCESS', 'Starting radio')) - } + // start our UI process notification + if (action.type == 'PUSHER_UPDATE_RADIO') { + store.dispatch(uiActions.startProcess('PUSHER_RADIO_PROCESS', 'Updating radio')); + } else { + store.dispatch(uiActions.startProcess('PUSHER_RADIO_PROCESS', 'Starting radio')); + } - var data = { - reset: (action.type == 'PUSHER_START_RADIO'), - seed_artists: [], - seed_genres: [], - seed_tracks: [] - } - - for(var i = 0; i < action.uris.length; i++){ - switch(helpers.uriType(action.uris[i] )){ - case 'artist': - data.seed_artists.push(action.uris[i] ); - break; - case 'track': - data.seed_tracks.push(action.uris[i] ); - break; - case 'genre': - data.seed_genres.push(action.uris[i] ); - break; - } - } + var data = { + reset: (action.type == 'PUSHER_START_RADIO'), + seed_artists: [], + seed_genres: [], + seed_tracks: [], + }; - if (action.type == 'PUSHER_START_RADIO'){ - store.dispatch(pusherActions.deliverBroadcast( - 'notification', - { - notification: { - type: 'info', - content: pusher.username + ' is starting radio mode' - } - } - )); - } + for (let i = 0; i < action.uris.length; i++) { + switch (helpers.uriType(action.uris[i])) { + case 'artist': + data.seed_artists.push(action.uris[i]); + break; + case 'track': + data.seed_tracks.push(action.uris[i]); + break; + case 'genre': + data.seed_genres.push(action.uris[i]); + break; + } + } - request(store, 'change_radio', data) - .then( - response => { - store.dispatch(uiActions.processFinishing('PUSHER_RADIO_PROCESS')); - if (response.status == 0){ - store.dispatch(uiActions.createNotification({content: response.message, type: 'bad'})); - } - store.dispatch(pusherActions.radioChanged(response.radio)); - }, - error => { - store.dispatch(uiActions.processFinishing('PUSHER_RADIO_PROCESS')); - store.dispatch(coreActions.handleException( - 'Could not change radio', - error - )); - } - ) - break + if (action.type == 'PUSHER_START_RADIO') { + store.dispatch(pusherActions.deliverBroadcast( + 'notification', + { + notification: { + type: 'info', + content: `${pusher.username} is starting radio mode`, + }, + }, + )); + } - case 'PUSHER_STOP_RADIO': - store.dispatch(uiActions.createNotification({content: 'Stopping radio'})); + request(store, 'change_radio', data) + .then( + (response) => { + store.dispatch(uiActions.processFinishing('PUSHER_RADIO_PROCESS')); + if (response.status == 0) { + store.dispatch(uiActions.createNotification({ content: response.message, type: 'bad' })); + } + store.dispatch(pusherActions.radioChanged(response.radio)); + }, + (error) => { + store.dispatch(uiActions.processFinishing('PUSHER_RADIO_PROCESS')); + store.dispatch(coreActions.handleException( + 'Could not change radio', + error, + )); + }, + ); + break; - if (store.getState().ui.allow_reporting){ + case 'PUSHER_STOP_RADIO': + store.dispatch(uiActions.createNotification({ content: 'Stopping radio' })); + + if (store.getState().ui.allow_reporting) { ReactGA.event({ category: 'Pusher', action: 'Stop radio' }); } - store.dispatch(pusherActions.deliverBroadcast( - 'notification', - { - notification: { - type: 'info', - content: pusher.username + ' stopped radio mode' - } - } - )); + store.dispatch(pusherActions.deliverBroadcast( + 'notification', + { + notification: { + type: 'info', + content: `${pusher.username} stopped radio mode`, + }, + }, + )); - var data = { - seed_artists: [], - seed_genres: [], - seed_tracks: [] - } + var data = { + seed_artists: [], + seed_genres: [], + seed_tracks: [], + }; - request(store, 'stop_radio', data) - .then( - response => { - store.dispatch(pusherActions.radioStopped()); - }, error => { - store.dispatch(coreActions.handleException( - 'Could not stop radio', - error - )); - } - ); - break + request(store, 'stop_radio', data) + .then( + (response) => { + store.dispatch(pusherActions.radioStopped()); + }, (error) => { + store.dispatch(coreActions.handleException( + 'Could not stop radio', + error, + )); + }, + ); + break; - /** + /** * Notifications and alerts - **/ + * */ - case 'PUSHER_BROWSER_NOTIFICATION': - store.dispatch(uiActions.createBrowserNotification(action)) - break + case 'PUSHER_BROWSER_NOTIFICATION': + store.dispatch(uiActions.createBrowserNotification(action)); + break; - case 'PUSHER_NOTIFICATION': - var data = Object.assign( - {}, - action, { - type: action.notification_type - } - ); - store.dispatch(uiActions.createNotification(data)); - break + case 'PUSHER_NOTIFICATION': + var data = { + + ...action, type: action.notification_type, + }; + store.dispatch(uiActions.createNotification(data)); + break; - /** + /** * Server actions - **/ + * */ - case 'PUSHER_RELOAD': - // Hard reload. This doesn't strictly clear the cache, but our compiler's - // cache buster should handle that - window.location.reload(true); - break + case 'PUSHER_RELOAD': + // Hard reload. This doesn't strictly clear the cache, but our compiler's + // cache buster should handle that + window.location.reload(true); + break; - case 'PUSHER_RESTART': - request(store, 'restart'); - next(action); - break + case 'PUSHER_RESTART': + request(store, 'restart'); + next(action); + break; - case 'PUSHER_UPGRADE': - if (store.getState().ui.allow_reporting){ + case 'PUSHER_UPGRADE': + if (store.getState().ui.allow_reporting) { ReactGA.event({ category: 'Pusher', action: 'Upgrade', label: '' }); } - request(store, 'upgrade'); - break; + request(store, 'upgrade'); + break; - case 'PUSHER_LOCAL_SCAN': - if (store.getState().ui.allow_reporting){ + case 'PUSHER_LOCAL_SCAN': + if (store.getState().ui.allow_reporting) { ReactGA.event({ category: 'Pusher', action: 'Local scan', label: '' }); } - request(store, 'local_scan'); - break; + request(store, 'local_scan'); + break; - case 'PUSHER_TEST': - request(store, 'test'); - break; + case 'PUSHER_TEST': + request(store, 'test'); + break; - case 'PUSHER_VERSION': - if (store.getState().ui.allow_reporting){ + case 'PUSHER_VERSION': + if (store.getState().ui.allow_reporting) { ReactGA.event({ category: 'Pusher', action: 'Version', label: action.version.current }); } - next(action); - break + next(action); + break; - case 'PUSHER_CONFIG': + case 'PUSHER_CONFIG': - // Set default country/locale (unless we've already been configured) - var spotify = store.getState().spotify; - var spotify_updated = false; - var spotify_updates = {}; + // Set default country/locale (unless we've already been configured) + var { spotify } = store.getState(); + var spotify_updated = false; + var spotify_updates = {}; - if (!spotify.country && action.config.country){ - spotify_updates.country = action.config.country; - spotify_updated = true; - } + if (!spotify.country && action.config.country) { + spotify_updates.country = action.config.country; + spotify_updated = true; + } - if (!spotify.locale && action.config.locale){ - spotify_updates.locale = action.config.locale; - spotify_updated = true; - } + if (!spotify.locale && action.config.locale) { + spotify_updates.locale = action.config.locale; + spotify_updated = true; + } - if (action.config.spotify_authorization_url){ - spotify_updates.authorization_url = action.config.spotify_authorization_url; - spotify_updated = true; - } + if (action.config.spotify_authorization_url) { + spotify_updates.authorization_url = action.config.spotify_authorization_url; + spotify_updated = true; + } - if (spotify_updated){ - store.dispatch(spotifyActions.set(spotify_updates)); - } - - store.dispatch(lastfmActions.set({ - authorization_url: (action.config.lastfm_authorization_url ? action.config.lastfm_authorization_url : null) - })); - - store.dispatch(geniusActions.set({ - authorization_url: (action.config.genius_authorization_url ? action.config.genius_authorization_url : null) - })); + if (spotify_updated) { + store.dispatch(spotifyActions.set(spotify_updates)); + } - next(action); - break + store.dispatch(lastfmActions.set({ + authorization_url: (action.config.lastfm_authorization_url ? action.config.lastfm_authorization_url : null), + })); - case 'PUSHER_DEBUG': - request(store, action.message.method, action.message.data ) - .then( - response => { - store.dispatch({type: 'DEBUG', response: response}) - }, - error => { - store.dispatch(coreActions.handleException( - 'Could not debug', - error, - error.message - )); - } - ); - break; + store.dispatch(geniusActions.set({ + authorization_url: (action.config.genius_authorization_url ? action.config.genius_authorization_url : null), + })); - case 'PUSHER_ERROR': - store.dispatch(uiActions.createNotification(action.message, 'bad')); - if (store.getState().ui.allow_reporting){ + next(action); + break; + + case 'PUSHER_DEBUG': + request(store, action.message.method, action.message.data) + .then( + (response) => { + store.dispatch({ type: 'DEBUG', response }); + }, + (error) => { + store.dispatch(coreActions.handleException( + 'Could not debug', + error, + error.message, + )); + }, + ); + break; + + case 'PUSHER_ERROR': + store.dispatch(uiActions.createNotification(action.message, 'bad')); + if (store.getState().ui.allow_reporting) { ReactGA.event({ category: 'Pusher', action: 'Error', label: action.message }); } - break + break; - // This action is irrelevant to us, pass it on to the next middleware - default: - return next(action); - } + // This action is irrelevant to us, pass it on to the next middleware + default: + return next(action); } + }; +}()); -})(); - -export default PusherMiddleware +export default PusherMiddleware; diff --git a/src/js/services/pusher/reducer.js b/src/js/services/pusher/reducer.js index eddc7f6c..1ee35172 100755 --- a/src/js/services/pusher/reducer.js +++ b/src/js/services/pusher/reducer.js @@ -1,73 +1,72 @@ -export default function reducer(pusher = {}, action){ - switch (action.type){ +export default function reducer(pusher = {}, action) { + switch (action.type) { + case 'PUSHER_CONNECT': + case 'PUSHER_CONNECTING': + return { ...pusher, connected: false, connecting: true }; - case 'PUSHER_CONNECT': - case 'PUSHER_CONNECTING': - return Object.assign({}, pusher, { connected: false, connecting: true }); + case 'PUSHER_CONNECTED': + return { + ...pusher, + connected: true, + connecting: false, + connection_id: action.connection_id, + client_id: action.client_id, + username: action.username, + }; - case 'PUSHER_CONNECTED': - return Object.assign({}, pusher, { - connected: true, - connecting: false, - connection_id: action.connection_id, - client_id: action.client_id, - username: action.username - }); + case 'PUSHER_DISCONNECTED': + return { ...pusher, connected: false, connecting: false }; - case 'PUSHER_DISCONNECTED': - return Object.assign({}, pusher, { connected: false, connecting: false }); + case 'PUSHER_SET_PORT': + return { ...pusher, port: action.port }; - case 'PUSHER_SET_PORT': - return Object.assign({}, pusher, { port: action.port }); + case 'PUSHER_SET_USERNAME': + return { ...pusher, username: action.username }; - case 'PUSHER_SET_USERNAME': - return Object.assign({}, pusher, { username: action.username }); + case 'PUSHER_CONNECTIONS': + var connections = {}; + for (let i = 0; i < action.connections.length; i++) { + connections[action.connections[i].connection_id] = action.connections[i]; + } + return { ...pusher, connections }; - case 'PUSHER_CONNECTIONS': - var connections = {} - for (var i = 0; i < action.connections.length; i++){ - connections[action.connections[i].connection_id] = action.connections[i] - } - return Object.assign({}, pusher, { connections: connections }); + case 'PUSHER_CONNECTION_ADDED': + case 'PUSHER_CONNECTION_CHANGED': + var connections = { ...pusher.connections }; + connections[action.connection.connection_id] = action.connection; + return { ...pusher, connections }; - case 'PUSHER_CONNECTION_ADDED': - case 'PUSHER_CONNECTION_CHANGED': - var connections = Object.assign({}, pusher.connections) - connections[action.connection.connection_id] = action.connection; - return Object.assign({}, pusher, { connections: connections }); + case 'PUSHER_CONNECTION_UPDATED': + return { + ...pusher, + username: action.connection.username, + client_id: action.connection.client_id, + connection_id: action.connection.connection_id, + }; - case 'PUSHER_CONNECTION_UPDATED': - return Object.assign({}, pusher, { - username: action.connection.username, - client_id: action.connection.client_id, - connection_id: action.connection.connection_id - }); + case 'PUSHER_CONNECTION_REMOVED': + var connections = { ...pusher.connections }; + delete connections[action.connection.connection_id]; + return { ...pusher, connections }; - case 'PUSHER_CONNECTION_REMOVED': - var connections = Object.assign({}, pusher.connections) - delete connections[action.connection.connection_id] - return Object.assign({}, pusher, { connections: connections }); + case 'PUSHER_VERSION': + return { + ...pusher, + version: action.version, + upgrading: false, + }; - case 'PUSHER_VERSION': - return Object.assign({}, pusher, { - version: action.version, - upgrading: false - }); + case 'PUSHER_START_UPGRADE': + return { ...pusher, upgrading: true }; - case 'PUSHER_START_UPGRADE': - return Object.assign({}, pusher, { upgrading: true }); + case 'PUSHER_CONFIG': + return { ...pusher, config: action.config }; - case 'PUSHER_CONFIG': - return Object.assign({}, pusher, { config: action.config }); + case 'PUSHER_COMMANDS_UPDATED': + return { ...pusher, commands: action.commands }; - case 'PUSHER_COMMANDS_UPDATED': - return Object.assign({}, pusher, { commands: action.commands }); - - default: - return pusher - } + default: + return pusher; + } } - - - diff --git a/src/js/services/snapcast/actions.js b/src/js/services/snapcast/actions.js index a3e5579b..40c2aaae 100755 --- a/src/js/services/snapcast/actions.js +++ b/src/js/services/snapcast/actions.js @@ -1,135 +1,133 @@ -export function getServer(){ - return { - type: 'SNAPCAST_GET_SERVER' - } +export function getServer() { + return { + type: 'SNAPCAST_GET_SERVER', + }; } -export function setClientName(id, name){ - return { - type: 'SNAPCAST_SET_CLIENT_NAME', - id: id, - name: name - } +export function setClientName(id, name) { + return { + type: 'SNAPCAST_SET_CLIENT_NAME', + id, + name, + }; } -export function setClientMute(id, mute){ - return { - type: 'SNAPCAST_SET_CLIENT_MUTE', - id: id, - mute: mute - } +export function setClientMute(id, mute) { + return { + type: 'SNAPCAST_SET_CLIENT_MUTE', + id, + mute, + }; } -export function setClientVolume(id, volume){ - return { - type: 'SNAPCAST_SET_CLIENT_VOLUME', - id: id, - volume: volume - } +export function setClientVolume(id, volume) { + return { + type: 'SNAPCAST_SET_CLIENT_VOLUME', + id, + volume, + }; } -export function setClientLatency(id, latency){ - return { - type: 'SNAPCAST_SET_CLIENT_LATENCY', - id: id, - latency: latency - } +export function setClientLatency(id, latency) { + return { + type: 'SNAPCAST_SET_CLIENT_LATENCY', + id, + latency, + }; } -export function setClientGroup(id, group_id){ - return { - type: 'SNAPCAST_SET_CLIENT_GROUP', - id: id, - group_id: group_id - } +export function setClientGroup(id, group_id) { + return { + type: 'SNAPCAST_SET_CLIENT_GROUP', + id, + group_id, + }; } -export function deleteClient(id){ - return { - type: 'SNAPCAST_DELETE_CLIENT', - id: id - } +export function deleteClient(id) { + return { + type: 'SNAPCAST_DELETE_CLIENT', + id, + }; } -export function setGroupStream(id, stream_id){ - return { - type: 'SNAPCAST_SET_GROUP_STREAM', - id: id, - stream_id: stream_id - } +export function setGroupStream(id, stream_id) { + return { + type: 'SNAPCAST_SET_GROUP_STREAM', + id, + stream_id, + }; } -export function setGroupMute(id, mute){ - return { - type: 'SNAPCAST_SET_GROUP_MUTE', - id: id, - mute: mute - } +export function setGroupMute(id, mute) { + return { + type: 'SNAPCAST_SET_GROUP_MUTE', + id, + mute, + }; } -export function setGroupVolume(id, percent, old_percent = 0){ - return { - type: 'SNAPCAST_SET_GROUP_VOLUME', - id: id, - percent: percent, - old_percent: old_percent - } +export function setGroupVolume(id, percent, old_percent = 0) { + return { + type: 'SNAPCAST_SET_GROUP_VOLUME', + id, + percent, + old_percent, + }; } -export function eventReceived(message){ - return { - type: 'SNAPCAST_EVENT_RECEIVED', - method: message.method, - params: message.params - } +export function eventReceived(message) { + return { + type: 'SNAPCAST_EVENT_RECEIVED', + method: message.method, + params: message.params, + }; } - - /** * Record loaders * * We've got a loaded record, now we just need to plug it in to our state and stores. - **/ + * */ -export function serverLoaded(server){ - return { - type: 'SNAPCAST_SERVER_LOADED', - server: server - } +export function serverLoaded(server) { + return { + type: 'SNAPCAST_SERVER_LOADED', + server, + }; } -export function clientLoaded(client){ - return clientsLoaded([client]); +export function clientLoaded(client) { + return clientsLoaded([client]); } -export function clientsLoaded(clients, flush = false){ - return { - type: 'SNAPCAST_CLIENTS_LOADED', - clients: clients, - flush: flush - } +export function clientsLoaded(clients, flush = false) { + return { + type: 'SNAPCAST_CLIENTS_LOADED', + clients, + flush, + }; } -export function groupLoaded(group){ - return groupsLoaded([group]); +export function groupLoaded(group) { + return groupsLoaded([group]); } -export function groupsLoaded(groups, flush = false){ - return { - type: 'SNAPCAST_GROUPS_LOADED', - groups: groups, - flush: flush - } +export function groupsLoaded(groups, flush = false) { + return { + type: 'SNAPCAST_GROUPS_LOADED', + groups, + flush, + }; } -export function streamLoaded(stream){ - return streamsLoaded([stream]); +export function streamLoaded(stream) { + return streamsLoaded([stream]); } -export function streamsLoaded(streams, flush = false){ - return { - type: 'SNAPCAST_STREAMS_LOADED', - streams: streams, - flush: flush - } -} \ No newline at end of file +export function streamsLoaded(streams, flush = false) { + return { + type: 'SNAPCAST_STREAMS_LOADED', + streams, + flush, + }; +} diff --git a/src/js/services/snapcast/middleware.js b/src/js/services/snapcast/middleware.js index bc252d99..73796941 100755 --- a/src/js/services/snapcast/middleware.js +++ b/src/js/services/snapcast/middleware.js @@ -1,474 +1,468 @@ -import ReactGA from 'react-ga' +import ReactGA from 'react-ga'; -var helpers = require('../../helpers'); -var coreActions = require('../core/actions'); -var uiActions = require('../ui/actions'); -var pusherActions = require('../pusher/actions'); -var snapcastActions = require('./actions'); +const helpers = require('../../helpers'); +const coreActions = require('../core/actions'); +const uiActions = require('../ui/actions'); +const pusherActions = require('../pusher/actions'); +const snapcastActions = require('./actions'); -const SnapcastMiddleware = (function(){ - - // A snapcast request is an alias of the Pusher request - const request = (store, params = null, response_callback = null, error_callback = null) => { - store.dispatch( +const SnapcastMiddleware = (function () { + // A snapcast request is an alias of the Pusher request + const request = (store, params = null, response_callback = null, error_callback = null) => { + store.dispatch( pusherActions.request( 'snapcast', params, response_callback, - error_callback - ) - ); - return; - } + error_callback, + ), + ); + }; - return store => next => action => { - var snapcast = store.getState().snapcast; + return (store) => (next) => (action) => { + const { snapcast } = store.getState(); - switch(action.type){ - - case 'SNAPCAST_GET_SERVER': - request( - store, + switch (action.type) { + case 'SNAPCAST_GET_SERVER': + request( + store, { - method: 'Server.GetStatus' + method: 'Server.GetStatus', }, - response => { - store.dispatch(snapcastActions.serverLoaded(response.server)); + (response) => { + store.dispatch(snapcastActions.serverLoaded(response.server)); + }, + (error) => { + store.dispatch(coreActions.handleException( + 'Could not get server', + error, + )); }, - error => { - store.dispatch(coreActions.handleException( - 'Could not get server', - error - )); - } ); - break + break; - case 'SNAPCAST_SERVER_LOADED': - store.dispatch(snapcastActions.groupsLoaded(action.server.groups, true)); - store.dispatch(snapcastActions.streamsLoaded(action.server.streams, true)); + case 'SNAPCAST_SERVER_LOADED': + store.dispatch(snapcastActions.groupsLoaded(action.server.groups, true)); + store.dispatch(snapcastActions.streamsLoaded(action.server.streams, true)); - // Snapcast double-nests the server object - action.server = action.server.server; + // Snapcast double-nests the server object + action.server = action.server.server; - next(action); - break + next(action); + break; - case 'SNAPCAST_GROUPS_LOADED': - var groups_index = Object.assign({}, snapcast.groups); - var groups_loaded = []; - var clients_loaded = []; + case 'SNAPCAST_GROUPS_LOADED': + var groups_index = { ...snapcast.groups }; + var groups_loaded = []; + var clients_loaded = []; - for (var raw_group of action.groups){ - var group = helpers.formatGroup(raw_group); + for (const raw_group of action.groups) { + var group = helpers.formatGroup(raw_group); - if (groups_index[group.id]){ - group = Object.assign({}, groups_index[group.id], group); - } + if (groups_index[group.id]) { + group = { ...groups_index[group.id], ...group }; + } - if (raw_group.clients){ - group.clients_ids = helpers.arrayOf('id', raw_group.clients); - clients_loaded = [...clients_loaded, ...raw_group.clients]; - } + if (raw_group.clients) { + group.clients_ids = helpers.arrayOf('id', raw_group.clients); + clients_loaded = [...clients_loaded, ...raw_group.clients]; + } - groups_loaded.push(group); - }; - - action.groups = groups_loaded; - - if (clients_loaded.length > 0){ - store.dispatch(snapcastActions.clientsLoaded(clients_loaded, action.flush)); - } - - next(action); - break - - case 'SNAPCAST_CLIENTS_LOADED': - var clients_index = Object.assign({}, snapcast.clients); - var clients_loaded = []; - - for (var raw_client of action.clients){ - var client = helpers.formatClient(raw_client); - - if (clients_index[client.id]){ - client = Object.assign({}, clients_index[client.id], client); - } - - clients_loaded.push(client); - } - - action.clients = clients_loaded; - - next(action); - break; - - case 'SNAPCAST_SET_CLIENT_NAME': - var client = snapcast.clients[action.id]; - var data = { - method: 'Client.SetName', - params: { - id: action.id, - name: action.name - } - } - - request( - store, - data, - response => { - store.dispatch(snapcastActions.clientLoaded( - { - id: action.id, - name: response.name - } - )); - } - ); - break - - case 'SNAPCAST_SET_CLIENT_MUTE': - var client = store.getState().snapcast.clients[action.id]; - var data = { - method: 'Client.SetVolume', - params: { - id: action.id, - volume: { - muted: action.mute, - percent: client.volume, - } - } - } - - request( - store, - data, - response => { - store.dispatch(snapcastActions.clientLoaded( - { - id: action.id, - volume: response.volume.percent, - mute: response.volume.muted - } - )); - }, - error => { - store.dispatch(coreActions.handleException( - 'Error', - error, - error.message - )); - } - ); - break - - case 'SNAPCAST_SET_CLIENT_VOLUME': - var client = snapcast.clients[action.id]; - - var data = { - method: 'Client.SetVolume', - params: { - id: action.id, - volume: { - muted: client.mute, - percent: action.volume - } - } - } - - request( - store, - data, - response => { - store.dispatch(snapcastActions.clientLoaded( - { - id: action.id, - volume: response.volume.percent - } - )); - }, - error => { - store.dispatch(coreActions.handleException( - 'Error', - error, - error.message - )); - } - ); - break - - case 'SNAPCAST_SET_CLIENT_LATENCY': - var client = store.getState().snapcast.clients[action.id]; - var data = { - method: 'Client.SetLatency', - params: { - id: action.id, - latency: action.latency - } - } - - request( - store, - data, - response => { - store.dispatch(snapcastActions.clientLoaded( - { - id: action.id, - latency: response.latency - } - )); - }, - error => { - store.dispatch(coreActions.handleException( - 'Error', - error, - error.message - )); - } - ); - break - - case 'SNAPCAST_SET_CLIENT_GROUP': - - var group = snapcast.groups[action.group_id]; - var clients_ids = group.clients_ids; - var clients_ids_index = clients_ids.indexOf(action.id); - - // Not in group (yet), so add it - if (clients_ids_index <= -1){ - clients_ids.push(action.id); - - // Already there, so remove it - } else { - clients_ids.splice(clients_ids_index, 1); - } - - var data = { - method: 'Group.SetClients', - params: { - id: action.group_id, - clients: clients_ids - } - } - - request( - store, - data, - response => { - store.dispatch(snapcastActions.serverLoaded(response.server)); - }, - error => { - store.dispatch(coreActions.handleException( - 'Error', - error, - error.message - )); - } - ); - break - - case 'SNAPCAST_DELETE_CLIENT': - var data = { - method: 'Server.DeleteClient', - params: { - id: action.id - } - } - - request( - store, - data, - response => { - store.dispatch({ - type: 'SNAPCAST_CLIENT_REMOVED', - key: action.data.params.id - }) - }, - error => { - store.dispatch(coreActions.handleException( - 'Error', - error, - error.message - )); - } - ); - break - - case 'SNAPCAST_SET_GROUP_STREAM': - var group = store.getState().snapcast.groups[action.id]; - var data = { - method: 'Group.SetStream', - params: { - id: action.id, - stream_id: action.stream_id - } - } - - request( - store, - data, - response => { - store.dispatch(snapcastActions.groupLoaded( - { - id: action.id, - stream_id: action.stream_id - } - )); - }, - error => { - store.dispatch(coreActions.handleException( - 'Could not change stream', - error, - error.message - )); - } - ); - break - - case 'SNAPCAST_SET_GROUP_MUTE': - var group = store.getState().snapcast.groups[action.id]; - var data = { - method: 'Group.SetMute', - params: { - id: action.id, - mute: action.mute - } - } - - request( - store, - data, - response => { - store.dispatch(snapcastActions.groupLoaded( - { - id: action.id, - muted: response.mute - } - )); - }, - error => { - store.dispatch(coreActions.handleException( - 'Could not toggle mute', - error, - error.message - )); - } - ); - break; - - case 'SNAPCAST_SET_GROUP_VOLUME': - var clients_to_update = []; - var group = snapcast.groups[action.id]; - var change = action.percent - action.old_percent; - - for (var client_id of group.clients_ids){ - - // Apply the change proportionately to each client - var client = snapcast.clients[client_id]; - var current_volume = client.volume; - var new_volume = current_volume + change; - - // Only change if the client is within min/max limits - if ((change > 0 && current_volume < 100) || (change < 0 && current_volume > 0)){ - clients_to_update.push({ - id: client.id, - volume: new_volume - }); - } - } - - // Loop our required changes, and post each to Snapcast - for (var client_to_update of clients_to_update){ - var volume = client_to_update.volume + ((group.clients_ids.length - clients_to_update.length) * change); - - // Make sure we're not creating an impossible percent - if (volume < 0){ - volume = 0; - } else if (volume > 100){ - volume = 100; - } - - store.dispatch(snapcastActions.setClientVolume(client_to_update.id, volume)); - } - break; - - case 'SNAPCAST_EVENT_RECEIVED': - - // Drop the prefix - action.method = action.method.replace('snapcast_',''); - - switch (action.method){ - - case 'Client.OnConnect': - store.dispatch(snapcastActions.clientLoaded( - { - id: action.params.client.id, - name: action.params.client.name, - volume: action.params.client.volume.percent, - mute: action.params.client.volume.muted, - connected: action.params.client.connected - } - )); - break; - - case 'Client.OnDisconnect': - store.dispatch(snapcastActions.clientLoaded( - { - id: action.params.client.id, - connected: action.params.client.connected - } - )); - break; - - case 'Client.OnVolumeChanged': - store.dispatch(snapcastActions.clientLoaded( - { - id: action.params.id, - mute: action.params.volume.muted, - volume: action.params.volume.percent - } - )); - break; - - case 'Client.OnLatencyChanged': - store.dispatch(snapcastActions.clientLoaded( - { - id: action.params.id, - latency: action.params.latency - } - )); - break; - - case 'Client.OnNameChanged': - store.dispatch(snapcastActions.clientLoaded( - { - id: action.params.id, - name: action.params.name - } - )); - break; - - case 'Group.OnMute': - store.dispatch(snapcastActions.groupLoaded( - { - id: action.params.id, - mute: action.params.mute - } - )); - break; - - case 'Server.OnUpdate': - store.dispatch(snapcastActions.serverLoaded(action.params)); - break; - } - break; - - // This action is irrelevant to us, pass it on to the next middleware - default: - return next(action); + groups_loaded.push(group); } + + action.groups = groups_loaded; + + if (clients_loaded.length > 0) { + store.dispatch(snapcastActions.clientsLoaded(clients_loaded, action.flush)); + } + + next(action); + break; + + case 'SNAPCAST_CLIENTS_LOADED': + var clients_index = { ...snapcast.clients }; + var clients_loaded = []; + + for (const raw_client of action.clients) { + var client = helpers.formatClient(raw_client); + + if (clients_index[client.id]) { + client = { ...clients_index[client.id], ...client }; + } + + clients_loaded.push(client); + } + + action.clients = clients_loaded; + + next(action); + break; + + case 'SNAPCAST_SET_CLIENT_NAME': + var client = snapcast.clients[action.id]; + var data = { + method: 'Client.SetName', + params: { + id: action.id, + name: action.name, + }, + }; + + request( + store, + data, + (response) => { + store.dispatch(snapcastActions.clientLoaded( + { + id: action.id, + name: response.name, + }, + )); + }, + ); + break; + + case 'SNAPCAST_SET_CLIENT_MUTE': + var client = store.getState().snapcast.clients[action.id]; + var data = { + method: 'Client.SetVolume', + params: { + id: action.id, + volume: { + muted: action.mute, + percent: client.volume, + }, + }, + }; + + request( + store, + data, + (response) => { + store.dispatch(snapcastActions.clientLoaded( + { + id: action.id, + volume: response.volume.percent, + mute: response.volume.muted, + }, + )); + }, + (error) => { + store.dispatch(coreActions.handleException( + 'Error', + error, + error.message, + )); + }, + ); + break; + + case 'SNAPCAST_SET_CLIENT_VOLUME': + var client = snapcast.clients[action.id]; + + var data = { + method: 'Client.SetVolume', + params: { + id: action.id, + volume: { + muted: client.mute, + percent: action.volume, + }, + }, + }; + + request( + store, + data, + (response) => { + store.dispatch(snapcastActions.clientLoaded( + { + id: action.id, + volume: response.volume.percent, + }, + )); + }, + (error) => { + store.dispatch(coreActions.handleException( + 'Error', + error, + error.message, + )); + }, + ); + break; + + case 'SNAPCAST_SET_CLIENT_LATENCY': + var client = store.getState().snapcast.clients[action.id]; + var data = { + method: 'Client.SetLatency', + params: { + id: action.id, + latency: action.latency, + }, + }; + + request( + store, + data, + (response) => { + store.dispatch(snapcastActions.clientLoaded( + { + id: action.id, + latency: response.latency, + }, + )); + }, + (error) => { + store.dispatch(coreActions.handleException( + 'Error', + error, + error.message, + )); + }, + ); + break; + + case 'SNAPCAST_SET_CLIENT_GROUP': + + var group = snapcast.groups[action.group_id]; + var { clients_ids } = group; + var clients_ids_index = clients_ids.indexOf(action.id); + + // Not in group (yet), so add it + if (clients_ids_index <= -1) { + clients_ids.push(action.id); + + // Already there, so remove it + } else { + clients_ids.splice(clients_ids_index, 1); + } + + var data = { + method: 'Group.SetClients', + params: { + id: action.group_id, + clients: clients_ids, + }, + }; + + request( + store, + data, + (response) => { + store.dispatch(snapcastActions.serverLoaded(response.server)); + }, + (error) => { + store.dispatch(coreActions.handleException( + 'Error', + error, + error.message, + )); + }, + ); + break; + + case 'SNAPCAST_DELETE_CLIENT': + var data = { + method: 'Server.DeleteClient', + params: { + id: action.id, + }, + }; + + request( + store, + data, + (response) => { + store.dispatch({ + type: 'SNAPCAST_CLIENT_REMOVED', + key: action.data.params.id, + }); + }, + (error) => { + store.dispatch(coreActions.handleException( + 'Error', + error, + error.message, + )); + }, + ); + break; + + case 'SNAPCAST_SET_GROUP_STREAM': + var group = store.getState().snapcast.groups[action.id]; + var data = { + method: 'Group.SetStream', + params: { + id: action.id, + stream_id: action.stream_id, + }, + }; + + request( + store, + data, + (response) => { + store.dispatch(snapcastActions.groupLoaded( + { + id: action.id, + stream_id: action.stream_id, + }, + )); + }, + (error) => { + store.dispatch(coreActions.handleException( + 'Could not change stream', + error, + error.message, + )); + }, + ); + break; + + case 'SNAPCAST_SET_GROUP_MUTE': + var group = store.getState().snapcast.groups[action.id]; + var data = { + method: 'Group.SetMute', + params: { + id: action.id, + mute: action.mute, + }, + }; + + request( + store, + data, + (response) => { + store.dispatch(snapcastActions.groupLoaded( + { + id: action.id, + muted: response.mute, + }, + )); + }, + (error) => { + store.dispatch(coreActions.handleException( + 'Could not toggle mute', + error, + error.message, + )); + }, + ); + break; + + case 'SNAPCAST_SET_GROUP_VOLUME': + var clients_to_update = []; + var group = snapcast.groups[action.id]; + var change = action.percent - action.old_percent; + + for (const client_id of group.clients_ids) { + // Apply the change proportionately to each client + var client = snapcast.clients[client_id]; + const current_volume = client.volume; + const new_volume = current_volume + change; + + // Only change if the client is within min/max limits + if ((change > 0 && current_volume < 100) || (change < 0 && current_volume > 0)) { + clients_to_update.push({ + id: client.id, + volume: new_volume, + }); + } + } + + // Loop our required changes, and post each to Snapcast + for (const client_to_update of clients_to_update) { + let volume = client_to_update.volume + ((group.clients_ids.length - clients_to_update.length) * change); + + // Make sure we're not creating an impossible percent + if (volume < 0) { + volume = 0; + } else if (volume > 100) { + volume = 100; + } + + store.dispatch(snapcastActions.setClientVolume(client_to_update.id, volume)); + } + break; + + case 'SNAPCAST_EVENT_RECEIVED': + + // Drop the prefix + action.method = action.method.replace('snapcast_', ''); + + switch (action.method) { + case 'Client.OnConnect': + store.dispatch(snapcastActions.clientLoaded( + { + id: action.params.client.id, + name: action.params.client.name, + volume: action.params.client.volume.percent, + mute: action.params.client.volume.muted, + connected: action.params.client.connected, + }, + )); + break; + + case 'Client.OnDisconnect': + store.dispatch(snapcastActions.clientLoaded( + { + id: action.params.client.id, + connected: action.params.client.connected, + }, + )); + break; + + case 'Client.OnVolumeChanged': + store.dispatch(snapcastActions.clientLoaded( + { + id: action.params.id, + mute: action.params.volume.muted, + volume: action.params.volume.percent, + }, + )); + break; + + case 'Client.OnLatencyChanged': + store.dispatch(snapcastActions.clientLoaded( + { + id: action.params.id, + latency: action.params.latency, + }, + )); + break; + + case 'Client.OnNameChanged': + store.dispatch(snapcastActions.clientLoaded( + { + id: action.params.id, + name: action.params.name, + }, + )); + break; + + case 'Group.OnMute': + store.dispatch(snapcastActions.groupLoaded( + { + id: action.params.id, + mute: action.params.mute, + }, + )); + break; + + case 'Server.OnUpdate': + store.dispatch(snapcastActions.serverLoaded(action.params)); + break; + } + break; + + // This action is irrelevant to us, pass it on to the next middleware + default: + return next(action); } + }; +}()); -})(); - -export default SnapcastMiddleware +export default SnapcastMiddleware; diff --git a/src/js/services/snapcast/reducer.js b/src/js/services/snapcast/reducer.js index 2166f5ed..ef3c2265 100755 --- a/src/js/services/snapcast/reducer.js +++ b/src/js/services/snapcast/reducer.js @@ -1,51 +1,47 @@ -export default function reducer(snapcast = {}, action){ - switch (action.type){ +export default function reducer(snapcast = {}, action) { + switch (action.type) { + case 'SNAPCAST_SERVER_LOADED': + var server = { ...action.server }; + return { ...snapcast, server }; - case 'SNAPCAST_SERVER_LOADED': - var server = Object.assign({}, action.server); - return Object.assign({}, snapcast, { server: server }); + case 'SNAPCAST_CLIENTS_LOADED': + if (action.flush) { + var clients = {}; + } else { + var clients = { ...snapcast.clients }; + } - case 'SNAPCAST_CLIENTS_LOADED': - if (action.flush){ - var clients = {}; - } else { - var clients = Object.assign({}, snapcast.clients); - } + for (const client of action.clients) { + clients[client.id] = client; + } + return { ...snapcast, clients }; - for (var client of action.clients){ - clients[client.id] = client; - } - return Object.assign({}, snapcast, { clients: clients }); + case 'SNAPCAST_GROUPS_LOADED': + if (action.flush) { + var groups = {}; + } else { + var groups = { ...snapcast.groups }; + } - case 'SNAPCAST_GROUPS_LOADED': - if (action.flush){ - var groups = {}; - } else { - var groups = Object.assign({}, snapcast.groups); - } + for (const group of action.groups) { + groups[group.id] = group; + } + return { ...snapcast, groups }; - for (var group of action.groups){ - groups[group.id] = group; - } - return Object.assign({}, snapcast, { groups: groups }); + case 'SNAPCAST_STREAMS_LOADED': + if (action.flush) { + var streams = {}; + } else { + var streams = { ...snapcast.streams }; + } - case 'SNAPCAST_STREAMS_LOADED': - if (action.flush){ - var streams = {}; - } else { - var streams = Object.assign({}, snapcast.streams); - } + for (const stream of action.streams) { + streams[stream.id] = stream; + } + return { ...snapcast, streams }; - for (var stream of action.streams){ - streams[stream.id] = stream; - } - return Object.assign({}, snapcast, { streams: streams }); - - default: - return snapcast - } + default: + return snapcast; + } } - - - diff --git a/src/js/services/spotify/actions.js b/src/js/services/spotify/actions.js index 84b77673..a2f38a21 100755 --- a/src/js/services/spotify/actions.js +++ b/src/js/services/spotify/actions.js @@ -1,9 +1,9 @@ -var coreActions = require('../../services/core/actions') -var uiActions = require('../../services/ui/actions') -var mopidyActions = require('../../services/mopidy/actions') -var lastfmActions = require('../../services/lastfm/actions') -var helpers = require('../../helpers') +const coreActions = require('../../services/core/actions'); +const uiActions = require('../../services/ui/actions'); +const mopidyActions = require('../../services/mopidy/actions'); +const lastfmActions = require('../../services/lastfm/actions'); +const helpers = require('../../helpers'); /** * Send an ajax request to the Spotify API @@ -14,300 +14,288 @@ var helpers = require('../../helpers') * @param method string * @param data mixed = request payload * @return Promise - **/ -const request = (dispatch, getState, endpoint, method = 'GET', data = false) => { + * */ +const request = (dispatch, getState, endpoint, method = 'GET', data = false) => { + // Add reference to loader queue + // We do this straight away so that even if we're refreshing the token, it still registers as + // loading said endpoint + const loader_key = helpers.generateGuid(); + dispatch(uiActions.startLoading(loader_key, `spotify_${endpoint}`)); - // Add reference to loader queue - // We do this straight away so that even if we're refreshing the token, it still registers as - // loading said endpoint - var loader_key = helpers.generateGuid() - dispatch(uiActions.startLoading(loader_key, 'spotify_'+endpoint)); + return new Promise((resolve, reject) => { + getToken(dispatch, getState) + .then( + (response) => { + // prepend the API baseurl, unless the endpoint already has it (ie pagination requests) + let url = `https://api.spotify.com/v1/${endpoint}`; + if (endpoint.startsWith('https://api.spotify.com/')) { + url = endpoint; + } - return new Promise((resolve, reject) => { - getToken(dispatch, getState) - .then( - response => { + // create our ajax request config + const config = { + method, + url, + cached: true, + timeout: 30000, + headers: { + Authorization: `Bearer ${response}`, + Accept: 'application/json', + }, + }; - // prepend the API baseurl, unless the endpoint already has it (ie pagination requests) - var url = 'https://api.spotify.com/v1/'+endpoint - if (endpoint.startsWith('https://api.spotify.com/')){ - url = endpoint; - } + // only if we've got data do we add it to the request (this prevents appending of "&false" to the URL) + if (data) { + if (typeof (data) === 'string') { + config.data = data; + } else { + config.data = JSON.stringify(data); + } + } - // create our ajax request config - var config = { - method: method, - url: url, - cached: true, - timeout: 30000, - headers: { - Authorization: 'Bearer '+ response, - Accept: 'application/json' - } - } + $.ajax(config).then( + (response) => { + dispatch(uiActions.stopLoading(loader_key)); + resolve(response); + }, + (xhr, status, error) => { + dispatch(uiActions.stopLoading(loader_key)); - // only if we've got data do we add it to the request (this prevents appending of "&false" to the URL) - if (data){ - if (typeof(data) === 'string'){ - config.data = data - } else { - config.data = JSON.stringify(data) - } - } + // TODO: Rate limiting + if (xhr.status == 429) { + alert('You hit the Spotify API rate limiter'); + } - $.ajax(config).then( - response => { - dispatch(uiActions.stopLoading(loader_key)) - resolve(response) - }, - (xhr, status, error) => { - dispatch(uiActions.stopLoading(loader_key)); + // TODO: Instead of allowing request to fail before renewing the token, once refreshed + // we should retry the original request(s) + if (xhr.responseJSON && xhr.responseJSON.error && xhr.responseJSON.error.message == 'The access token expired') { + dispatch(refreshToken(dispatch, getState)); + } - // TODO: Rate limiting - if (xhr.status == 429){ - alert("You hit the Spotify API rate limiter"); - } + reject({ + config, + xhr, + status, + error, + }); + }, + ); + }, + (error) => { + dispatch(coreActions.handleException( + error.error, + )); - // TODO: Instead of allowing request to fail before renewing the token, once refreshed - // we should retry the original request(s) - if (xhr.responseJSON && xhr.responseJSON.error && xhr.responseJSON.error.message == 'The access token expired'){ - dispatch(refreshToken(dispatch, getState)); - } - - reject({ - config: config, - xhr: xhr, - status: status, - error: error - }); - } - ) - }, - error => { - dispatch(coreActions.handleException( - error.error - )); - - reject(error); - } - ); - } - ); -} + reject(error); + }, + ); + }); +}; /** * Check an access token validity * * @return Promise -**/ -function getToken(dispatch, getState){ - return new Promise((resolve, reject) => { +* */ +function getToken(dispatch, getState) { + return new Promise((resolve, reject) => { + // token is okay for now, so just resolve with the current token + if (getState().spotify.token_expiry && new Date().getTime() < getState().spotify.token_expiry) { + resolve(getState().spotify.access_token); + return; + } - // token is okay for now, so just resolve with the current token - if (getState().spotify.token_expiry && new Date().getTime() < getState().spotify.token_expiry){ - resolve(getState().spotify.access_token) - return - } - - // token is expiring/expired, so go get a new one and resolve that - // TODO: Detect whether we already have a pending refresh, in which case we - // need to wait until it's done, and then return that - - // We've already got a refresh in progress - if (getState().ui.load_queue.spotify_refresh_token !== undefined){ + // token is expiring/expired, so go get a new one and resolve that + // TODO: Detect whether we already have a pending refresh, in which case we + // need to wait until it's done, and then return that + // We've already got a refresh in progress + if (getState().ui.load_queue.spotify_refresh_token !== undefined) { console.log("Already refreshing token, we'll wait 1000ms and try again"); // Re-check the queue periodically to see if it's finished yet // TODO: Look at properly hooking up with the ajax finish event setTimeout( - () => { + () => // Return myself for a re-check - return getToken(dispatch, getState); - }, - 1000 + getToken(dispatch, getState), + 1000, ); - } else { + } else { refreshToken(dispatch, getState) .then( - response => { - resolve(response.access_token) + (response) => { + resolve(response.access_token); + }, + (error) => { + reject(error); }, - error => { - reject(error) - } ); } - }); + }); } -function refreshToken(dispatch, getState){ - return new Promise((resolve, reject) => { +function refreshToken(dispatch, getState) { + return new Promise((resolve, reject) => { + // add reference to loader queue + const loader_key = helpers.generateGuid(); + dispatch(uiActions.startLoading(loader_key, 'spotify_refresh_token')); - // add reference to loader queue - var loader_key = helpers.generateGuid(); - dispatch(uiActions.startLoading(loader_key, 'spotify_refresh_token')); + // Fully-authorized, so we can use the local Spotify credentials + if (getState().spotify.authorization) { + var config = { + method: 'GET', + url: `${getState().spotify.authorization_url}?action=refresh&refresh_token=${getState().spotify.refresh_token}`, + dataType: 'json', + timeout: 10000, + }; - // Fully-authorized, so we can use the local Spotify credentials - if (getState().spotify.authorization){ + $.ajax(config) + .then( + (response) => { + dispatch(uiActions.stopLoading(loader_key)); - var config = { - method: 'GET', - url: getState().spotify.authorization_url+'?action=refresh&refresh_token='+getState().spotify.refresh_token, - dataType: "json", - timeout: 10000 - }; + response.token_expiry = new Date().getTime() + (response.expires_in * 1000); + response.source = 'spotify'; + dispatch({ + type: 'SPOTIFY_TOKEN_REFRESHED', + data: response, + }); + resolve(response); + }, + (xhr, status, error) => { + dispatch(uiActions.stopLoading(loader_key)); - $.ajax(config) - .then( - response => { - dispatch(uiActions.stopLoading(loader_key)); + reject({ + config, + xhr, + status, + error, + }); + }, + ); - response.token_expiry = new Date().getTime() + (response.expires_in * 1000 ) - response.source = 'spotify' - dispatch({ - type: 'SPOTIFY_TOKEN_REFRESHED', - data: response - }) - resolve(response); - }, - (xhr, status, error) => { - dispatch(uiActions.stopLoading(loader_key)); - - reject({ - config: config, - xhr: xhr, - status: status, - error: error - }); - } - ); - - // Server-side authorized (with limited scope) so we need to refresh - // using the Mopidy-Spotify credentials - } else { + // Server-side authorized (with limited scope) so we need to refresh + // using the Mopidy-Spotify credentials + } else { + var config = { + method: 'GET', + url: `//${getState().mopidy.host}:${getState().mopidy.port}/iris/http/refresh_spotify_token`, + dataType: 'json', + timeout: 10000, + }; - var config = { - method: 'GET', - url: '//'+getState().mopidy.host+':'+getState().mopidy.port+'/iris/http/refresh_spotify_token', - dataType: "json", - timeout: 10000 - } + $.ajax(config) + .then( + (response, status, xhr) => { + dispatch(uiActions.stopLoading(loader_key)); - $.ajax(config) - .then( - (response, status, xhr) => { - dispatch(uiActions.stopLoading(loader_key)); + if (response.error) { + reject({ + config, + xhr, + status, + error: response.error, + }); + } else { + const token = response.result.spotify_token; + token.token_expiry = new Date().getTime() + (token.expires_in * 1000); + token.source = 'mopidy'; + dispatch({ + type: 'SPOTIFY_TOKEN_REFRESHED', + access_token_provider: 'backend', + data: token, + }); + resolve(token); + } + }, + (xhr, status, error) => { + dispatch(uiActions.stopLoading(loader_key)); - if (response.error){ - reject({ - config: config, - xhr: xhr, - status: status, - error: response.error - }); - - } else { - var token = response.result.spotify_token; - token.token_expiry = new Date().getTime() + (token.expires_in * 1000 ); - token.source = 'mopidy'; - dispatch({ - type: 'SPOTIFY_TOKEN_REFRESHED', - access_token_provider: 'backend', - data: token - }); - resolve(token); - } - - }, - (xhr, status, error) => { - dispatch(uiActions.stopLoading(loader_key)); - - reject({ - config: config, - xhr: xhr, - status: status, - error: error - }); - } - ); - } - - }) + reject({ + config, + xhr, + status, + error, + }); + }, + ); + } + }); } -export function set(data){ - return { - type: 'SPOTIFY_SET', - data: data - } +export function set(data) { + return { + type: 'SPOTIFY_SET', + data, + }; } -export function connect(){ - return (dispatch, getState) => { - dispatch({ type: 'SPOTIFY_CONNECTING' }); - dispatch(getMe()); - } +export function connect() { + return (dispatch, getState) => { + dispatch({ type: 'SPOTIFY_CONNECTING' }); + dispatch(getMe()); + }; } /** * Handle authorization process - **/ + * */ -export function authorizationGranted(data){ - data.token_expiry = new Date().getTime() + data.expires_in; - return { type: 'SPOTIFY_AUTHORIZATION_GRANTED', data: data } +export function authorizationGranted(data) { + data.token_expiry = new Date().getTime() + data.expires_in; + return { type: 'SPOTIFY_AUTHORIZATION_GRANTED', data }; } -export function revokeAuthorization(){ - return { type: 'SPOTIFY_AUTHORIZATION_REVOKED' } +export function revokeAuthorization() { + return { type: 'SPOTIFY_AUTHORIZATION_REVOKED' }; } -export function refreshingToken(){ - return (dispatch, getState) => { - dispatch({ type: 'SPOTIFY_TOKEN_REFRESHING' }); - refreshToken(dispatch, getState ); - } +export function refreshingToken() { + return (dispatch, getState) => { + dispatch({ type: 'SPOTIFY_TOKEN_REFRESHING' }); + refreshToken(dispatch, getState); + }; } -export function tokenChanged(spotify_token){ - return { - type: 'SPOTIFY_TOKEN_CHANGED', - spotify_token: spotify_token - } +export function tokenChanged(spotify_token) { + return { + type: 'SPOTIFY_TOKEN_CHANGED', + spotify_token, + }; } -export function importAuthorization(authorization){ - return { - type: 'SPOTIFY_IMPORT_AUTHORIZATION', - authorization: authorization - } +export function importAuthorization(authorization) { + return { + type: 'SPOTIFY_IMPORT_AUTHORIZATION', + authorization, + }; } /** * Get current user - **/ -export function getMe(){ - return (dispatch, getState) => { - request(dispatch, getState, 'me' ) - .then( - response => { - dispatch({ - type: 'SPOTIFY_ME_LOADED', - me: response - }); - }, - error => { - dispatch(coreActions.handleException( - 'Could not load your profile', - error - )); - } - ); - } + * */ +export function getMe() { + return (dispatch, getState) => { + request(dispatch, getState, 'me') + .then( + (response) => { + dispatch({ + type: 'SPOTIFY_ME_LOADED', + me: response, + }); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load your profile', + error, + )); + }, + ); + }; } @@ -315,592 +303,581 @@ export function getMe(){ * Get a single track * * @param uri string - **/ -export function getTrack(uri){ - return (dispatch, getState) => { - request(dispatch, getState, 'tracks/'+ helpers.getFromUri('trackid', uri)) - .then( - response => { - dispatch(coreActions.trackLoaded(response)); - }, - error => { - dispatch(coreActions.handleException( - 'Could not load track', - error - )); + * */ +export function getTrack(uri) { + return (dispatch, getState) => { + request(dispatch, getState, `tracks/${helpers.getFromUri('trackid', uri)}`) + .then( + (response) => { + dispatch(coreActions.trackLoaded(response)); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load track', + error, + )); + }, + ); + }; +} + +export function getLibraryTracks() { + return (dispatch, getState) => { + request(dispatch, getState, 'me/tracks?limit=50') + .then( + (response) => { + dispatch({ + type: 'SPOTIFY_LIBRARY_TRACKS_LOADED', + data: response, + }); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not get library tracks', + error, + )); + }, + ); + }; +} + +export function getFeaturedPlaylists() { + return (dispatch, getState) => { + dispatch({ type: 'SPOTIFY_FEATURED_PLAYLISTS_LOADED', data: false }); + + const date = new Date(); + date.setHours(date.getHours()); + const year = date.getFullYear(); + let month = date.getMonth(); + if (month < 10) month = `0${month}`; + let day = date.getDay(); + if (day < 10) day = `0${day}`; + let hour = date.getHours(); + if (hour < 10) hour = `0${hour}`; + let min = date.getMinutes(); + if (min < 10) min = `0${min}`; + let sec = date.getSeconds(); + if (sec < 10) sec = `0${sec}`; + + const timestamp = `${year}-${month}-${day}T${hour}:${min}:${sec}`; + + request(dispatch, getState, `browse/featured-playlists?limit=50&country=${getState().spotify.country}&locale=${getState().spotify.locale}timestamp=${timestamp}`) + .then( + (response) => { + const playlists = []; + for (let i = 0; i < response.playlists.items.length; i++) { + playlists.push({ + + ...response.playlists.items[i], + is_completely_loaded: false, + can_edit: (getState().spotify.me && response.playlists.items[i].owner.id == getState().spotify.me.id), + tracks_total: response.playlists.items[i].tracks.total, + }); + } + + dispatch({ + type: 'PLAYLISTS_LOADED', + playlists, + }); + + dispatch({ + type: 'SPOTIFY_FEATURED_PLAYLISTS_LOADED', + data: { + message: response.message, + playlists: helpers.upgradeSpotifyPlaylistUris(helpers.arrayOf('uri', response.playlists.items)), + }, + }); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load featured playlists', + error, + )); + }, + ); + }; +} + +export function getCategories() { + return (dispatch, getState) => { + request(dispatch, getState, `browse/categories?limit=50&country=${getState().spotify.country}&locale=${getState().spotify.locale}`) + .then( + (response) => { + dispatch({ + type: 'SPOTIFY_CATEGORIES_LOADED', + categories: response.categories.items, + }); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load categories', + error, + )); + }, + ); + }; +} + +export function getCategory(id) { + return (dispatch, getState) => { + request(dispatch, getState, `browse/categories/${id}?country=${getState().spotify.country}&locale=${getState().spotify.locale}`) + .then( + (response) => { + dispatch({ + type: 'SPOTIFY_CATEGORY_LOADED', + category: { + uri: `category:${response.id}`, + playlist_uris: [], + ...response, + }, + }); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load category', + error, + )); + }, + ); + }; +} + +export function getCategoryPlaylists(id) { + return (dispatch, getState) => { + request(dispatch, getState, `browse/categories/${id}/playlists?limit=50&country=${getState().spotify.country}&locale=${getState().spotify.locale}`) + .then( + (response) => { + dispatch({ + type: 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED', + uri: `category:${id}`, + playlists: response.playlists, + }); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load category playlists', + error, + )); + }, + ); + }; +} + +export function getNewReleases() { + return (dispatch, getState) => { + request(dispatch, getState, `browse/new-releases?country=${getState().spotify.country}&limit=50`) + .then( + (response) => { + dispatch({ + type: 'SPOTIFY_NEW_RELEASES_LOADED', + data: response, + }); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load new releases', + error, + )); + }, + ); + }; +} + +export function getURL(url, action_name, key = false) { + return (dispatch, getState) => { + request(dispatch, getState, url) + .then( + (response) => { + dispatch({ + type: action_name, + key, + data: response, + }); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load URL', + error, + )); + }, + ); + }; +} + +export function getMore(url, core_action = null, custom_action = null) { + return (dispatch, getState) => { + request(dispatch, getState, url) + .then( + (response) => { + if (core_action) { + dispatch(coreActions.loadedMore( + core_action.parent_type, + core_action.parent_key, + core_action.records_type, + response, + )); + } else if (custom_action) { + custom_action.data = response; + dispatch(custom_action); + } else { + dispatch(coreActions.handleException( + 'No callback handler for loading more items', + )); + } + }, + (error) => { + dispatch(coreActions.handleException( + `Could not load more ${callback_action.parent_type} ${callback_action.records_type}s`, + error, + )); + }, + ); + }; +} + +export function clearSearchResults() { + return { + type: 'SPOTIFY_CLEAR_SEARCH_RESULTS', + }; +} + +export function getSearchResults(type, query, limit = 50, offset = 0) { + return (dispatch, getState) => { + dispatch(uiActions.startProcess('SPOTIFY_GET_SEARCH_RESULTS_PROCESSOR', 'Searching Spotify')); + + type = type.replace(/s+$/, ''); + if (type == 'all') { + type = 'album,artist,playlist,track'; + } + + let url = `search?q=${query}`; + url += `&type=${type}`; + url += `&country=${getState().spotify.country}`; + url += `&limit=${limit}`; + url += `&offset=${offset}`; + + request(dispatch, getState, url) + .then( + (response) => { + if (response.tracks !== undefined) { + dispatch({ + type: 'SPOTIFY_SEARCH_RESULTS_LOADED', + context: 'tracks', + query, + results: helpers.formatTracks(response.tracks.items), + more: response.tracks.next, + }); + } + + if (response.artists !== undefined) { + dispatch({ + type: 'ARTISTS_LOADED', + artists: response.artists.items, + }); + dispatch({ + type: 'SPOTIFY_SEARCH_RESULTS_LOADED', + context: 'artists', + query, + results: helpers.arrayOf('uri', response.artists.items), + more: response.artists.next, + }); + } + + if (response.albums !== undefined) { + dispatch({ + type: 'ALBUMS_LOADED', + albums: response.albums.items, + }); + dispatch({ + type: 'SPOTIFY_SEARCH_RESULTS_LOADED', + context: 'albums', + query, + results: helpers.arrayOf('uri', response.albums.items), + more: response.albums.next, + }); + } + + if (response.playlists !== undefined) { + const playlists = []; + for (const playlist of response.playlists.items) { + playlists.push({ + + ...helpers.formatPlaylist(playlist), + can_edit: (getState().spotify.me && playlist.owner.id == getState().spotify.me.id), + tracks_total: playlist.tracks.total, + }); + } + dispatch({ + type: 'PLAYLISTS_LOADED', + playlists, + }); + + dispatch({ + type: 'SPOTIFY_SEARCH_RESULTS_LOADED', + context: 'playlists', + query, + results: helpers.arrayOf('uri', playlists), + more: response.playlists.next, + }); + } + + dispatch(uiActions.processFinishing('SPOTIFY_GET_SEARCH_RESULTS_PROCESSOR')); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load search results', + error, + )); + }, + ); + }; +} + +export function getAutocompleteResults(field_id, query, types = ['album', 'artist', 'playlist', 'track']) { + return (dispatch, getState) => { + dispatch({ type: 'SPOTIFY_AUTOCOMPLETE_LOADING', field_id }); + + const genre_included = types.includes('genre'); + if (genre_included) { + const index = types.indexOf('genre'); + types.splice(index, 1); + } + + let endpoint = `search?q=${query}`; + endpoint += `&type=${types.join(',')}`; + endpoint += `&country=${getState().spotify.country}`; + + request(dispatch, getState, endpoint) + .then( + (response) => { + const genres = []; + if (genre_included) { + const available_genres = getState().spotify.genres; + if (available_genres) { + for (let i = 0; i < available_genres.length; i++) { + if (available_genres[i].includes(query)) { + const genre = available_genres[i]; + genres.push({ + name: (genre.charAt(0).toUpperCase() + genre.slice(1)).replace('-', ' '), + uri: `spotify:genre:${genre}`, + }); } - ); - } + } + } + } + + if (response.artists && response.artists.items) { + dispatch(coreActions.artistsLoaded(response.artists.items)); + } + + if (response.albums && response.albums.items) { + dispatch(coreActions.albumsLoaded(response.albums.items)); + } + + if (response.playlists && response.playlists.items) { + dispatch(coreActions.playlistsLoaded(response.playlists.items)); + } + + if (response.tracks && response.tracks.items) { + dispatch(coreActions.tracksLoaded(response.tracks.items)); + } + + dispatch({ + type: 'SPOTIFY_AUTOCOMPLETE_LOADED', + field_id, + results: { + artists: (response.artists ? response.artists.items : []), + albums: (response.albums ? response.albums.items : []), + playlists: (response.playlists ? response.playlists.items : []), + tracks: (response.tracks ? response.tracks.items : []), + genres, + }, + }); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load autocomplete results', + error, + )); + }, + ); + }; } -export function getLibraryTracks(){ - return (dispatch, getState) => { - request(dispatch, getState, 'me/tracks?limit=50') - .then( - response => { - dispatch({ - type: 'SPOTIFY_LIBRARY_TRACKS_LOADED', - data: response - }); - }, - error => { - dispatch(coreActions.handleException( - 'Could not get library tracks', - error - )); - } - ); - } +export function clearAutocompleteResults(field_id = null) { + return { + type: 'SPOTIFY_AUTOCOMPLETE_CLEAR', + field_id, + }; } -export function getFeaturedPlaylists(){ - return (dispatch, getState) => { +export function following(uri, method = 'GET') { + return (dispatch, getState) => { + if (method == 'PUT') var is_following = true; + if (method == 'DELETE') var is_following = false; - dispatch({ type: 'SPOTIFY_FEATURED_PLAYLISTS_LOADED', data: false }); - - var date = new Date(); - date.setHours(date.getHours()); - var year = date.getFullYear(); - var month = date.getMonth(); - if (month < 10 ) month = '0'+month; - var day = date.getDay(); - if (day < 10 ) day = '0'+day; - var hour = date.getHours(); - if (hour < 10 ) hour = '0'+hour; - var min = date.getMinutes(); - if (min < 10 ) min = '0'+min; - var sec = date.getSeconds(); - if (sec < 10 ) sec = '0'+sec; - - var timestamp = year+'-'+month+'-'+day+'T'+hour+':'+min+':'+sec; - - request(dispatch, getState, 'browse/featured-playlists?limit=50&country='+getState().spotify.country+'&locale='+getState().spotify.locale+'timestamp='+timestamp) - .then( - response => { - var playlists = [] - for (var i = 0; i < response.playlists.items.length; i++){ - playlists.push(Object.assign( - {}, - response.playlists.items[i], - { - is_completely_loaded: false, - can_edit: (getState().spotify.me && response.playlists.items[i].owner.id == getState().spotify.me.id), - tracks_total: response.playlists.items[i].tracks.total - } - )) - } - - dispatch({ - type: 'PLAYLISTS_LOADED', - playlists: playlists - }); - - dispatch({ - type: 'SPOTIFY_FEATURED_PLAYLISTS_LOADED', - data: { - message: response.message, - playlists: helpers.upgradeSpotifyPlaylistUris(helpers.arrayOf('uri',response.playlists.items)) - } - }); - }, - error => { - dispatch(coreActions.handleException( - 'Could not load featured playlists', - error - )); - } - ); - } -} - -export function getCategories(){ - return (dispatch, getState) => { - request(dispatch, getState, 'browse/categories?limit=50&country='+getState().spotify.country+'&locale='+getState().spotify.locale ) - .then( - response => { - dispatch({ - type: 'SPOTIFY_CATEGORIES_LOADED', - categories: response.categories.items - }); - }, - error => { - dispatch(coreActions.handleException( - 'Could not load categories', - error - )); - } - ); - } -} - -export function getCategory(id){ - return (dispatch, getState) => { - request(dispatch, getState, 'browse/categories/'+id+'?country='+getState().spotify.country+'&locale='+getState().spotify.locale ) - .then( - response => { - dispatch({ - type: 'SPOTIFY_CATEGORY_LOADED', - category: Object.assign( - { - uri: 'category:'+response.id, - playlist_uris: [] - }, - response - ) - }); - }, - error => { - dispatch(coreActions.handleException( - 'Could not load category', - error - )); - } - ) - } -} - -export function getCategoryPlaylists(id){ - return (dispatch, getState) => { - request(dispatch, getState, 'browse/categories/'+id+'/playlists?limit=50&country='+getState().spotify.country+'&locale='+getState().spotify.locale ) - .then( - response => { - dispatch({ - type: 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED', - uri: 'category:'+id, - playlists: response.playlists - }); - }, - error => { - dispatch(coreActions.handleException( - 'Could not load category playlists', - error - )); - } - ) - } -} - -export function getNewReleases(){ - return (dispatch, getState) => { - request(dispatch, getState, 'browse/new-releases?country='+getState().spotify.country+'&limit=50' ) - .then( - response => { - dispatch({ - type: 'SPOTIFY_NEW_RELEASES_LOADED', - data: response - }); - }, - error => { - dispatch(coreActions.handleException( - 'Could not load new releases', - error - )); - } - ); - } -} - -export function getURL(url, action_name, key = false){ - return (dispatch, getState) => { - request(dispatch, getState, url ) - .then( - response => { - dispatch({ - type: action_name, - key: key, - data: response - }); - }, - error => { - dispatch(coreActions.handleException( - 'Could not load URL', - error - )); - } - ); - } -} - -export function getMore(url, core_action = null, custom_action = null){ - return (dispatch, getState) => { - request(dispatch, getState, url) - .then( - response => { - if (core_action){ - dispatch(coreActions.loadedMore( - core_action.parent_type, - core_action.parent_key, - core_action.records_type, - response - )); - } else if (custom_action){ - custom_action.data = response; - dispatch(custom_action); - } else { - dispatch(coreActions.handleException( - 'No callback handler for loading more items' - )); - } - }, - error => { - dispatch(coreActions.handleException( - 'Could not load more '+callback_action.parent_type+' '+callback_action.records_type+'s', - error - )); - } - ); - } -} - -export function clearSearchResults(){ - return { - type: 'SPOTIFY_CLEAR_SEARCH_RESULTS' - } -} - -export function getSearchResults(type, query, limit = 50, offset = 0){ - return (dispatch, getState) => { - - dispatch(uiActions.startProcess('SPOTIFY_GET_SEARCH_RESULTS_PROCESSOR','Searching Spotify')) - - type = type.replace(/s+$/, "") - if (type == 'all'){ - type = 'album,artist,playlist,track' + const asset_name = helpers.uriType(uri); + let endpoint; let + data; + switch (asset_name) { + case 'track': + if (method == 'GET') { + endpoint = `me/tracks/contains/?ids=${helpers.getFromUri('trackid', uri)}`; + } else { + endpoint = `me/tracks/?ids=${helpers.getFromUri('trackid', uri)}`; } - - var url = 'search?q='+query - url += '&type='+type - url += '&country='+getState().spotify.country - url += '&limit='+limit - url += '&offset='+offset - - request(dispatch, getState, url ) - .then( - response => { - if (response.tracks !== undefined){ - dispatch({ - type: 'SPOTIFY_SEARCH_RESULTS_LOADED', - context: 'tracks', - query: query, - results: helpers.formatTracks(response.tracks.items), - more: response.tracks.next, - }); - } - - if (response.artists !== undefined){ - dispatch({ - type: 'ARTISTS_LOADED', - artists: response.artists.items - }); - dispatch({ - type: 'SPOTIFY_SEARCH_RESULTS_LOADED', - context: 'artists', - query: query, - results: helpers.arrayOf('uri',response.artists.items), - more: response.artists.next, - }); - } - - if (response.albums !== undefined){ - dispatch({ - type: 'ALBUMS_LOADED', - albums: response.albums.items - }); - dispatch({ - type: 'SPOTIFY_SEARCH_RESULTS_LOADED', - context: 'albums', - query: query, - results: helpers.arrayOf('uri',response.albums.items), - more: response.albums.next, - }); - } - - if (response.playlists !== undefined){ - var playlists = []; - for (var playlist of response.playlists.items){ - playlists.push(Object.assign( - {}, - helpers.formatPlaylist(playlist), - { - can_edit: (getState().spotify.me && playlist.owner.id == getState().spotify.me.id), - tracks_total: playlist.tracks.total - } - )) - } - dispatch({ - type: 'PLAYLISTS_LOADED', - playlists: playlists - }); - - dispatch({ - type: 'SPOTIFY_SEARCH_RESULTS_LOADED', - context: 'playlists', - query: query, - results: helpers.arrayOf('uri',playlists), - more: response.playlists.next - }); - } - - dispatch(uiActions.processFinishing('SPOTIFY_GET_SEARCH_RESULTS_PROCESSOR')) - }, - error => { - dispatch(coreActions.handleException( - 'Could not load search results', - error - )); - } - ); - } -} - -export function getAutocompleteResults(field_id, query, types = ['album','artist','playlist','track']){ - return (dispatch, getState) => { - - dispatch({type: 'SPOTIFY_AUTOCOMPLETE_LOADING', field_id: field_id}) - - var genre_included = types.includes('genre') - if (genre_included){ - var index = types.indexOf('genre') - types.splice(index,1) + break; + case 'album': + if (method == 'GET') { + endpoint = `me/albums/contains/?ids=${helpers.getFromUri('albumid', uri)}`; + } else { + endpoint = `me/albums/?ids=${helpers.getFromUri('albumid', uri)}`; } - - var endpoint = 'search?q='+query - endpoint += '&type='+types.join(',') - endpoint += '&country='+getState().spotify.country - - request(dispatch, getState, endpoint) - .then( - response => { - var genres = [] - if (genre_included){ - var available_genres = getState().spotify.genres - if (available_genres){ - for (var i = 0; i < available_genres.length; i++){ - if (available_genres[i].includes(query)){ - var genre = available_genres[i] - genres.push({ - name: (genre.charAt(0).toUpperCase()+genre.slice(1)).replace('-',' '), - uri: 'spotify:genre:'+genre - }) - } - } - } - } - - if (response.artists && response.artists.items){ - dispatch(coreActions.artistsLoaded(response.artists.items)); - } - - if (response.albums && response.albums.items){ - dispatch(coreActions.albumsLoaded(response.albums.items)); - } - - if (response.playlists && response.playlists.items){ - dispatch(coreActions.playlistsLoaded(response.playlists.items)); - } - - if (response.tracks && response.tracks.items){ - dispatch(coreActions.tracksLoaded(response.tracks.items)); - } - - dispatch({ - type: 'SPOTIFY_AUTOCOMPLETE_LOADED', - field_id: field_id, - results: { - artists: (response.artists ? response.artists.items : []), - albums: (response.albums ? response.albums.items : []), - playlists: (response.playlists ? response.playlists.items : []), - tracks: (response.tracks ? response.tracks.items : []), - genres: genres - } - }); - }, - error => { - dispatch(coreActions.handleException( - 'Could not load autocomplete results', - error - )); - } - ); - } -} - -export function clearAutocompleteResults(field_id = null){ - return { - type: 'SPOTIFY_AUTOCOMPLETE_CLEAR', - field_id: field_id - } -} - -export function following(uri, method = 'GET'){ - return (dispatch, getState) => { - - if (method == 'PUT' ) var is_following = true - if (method == 'DELETE' ) var is_following = false - - var asset_name = helpers.uriType(uri); - var endpoint, data - switch(asset_name){ - case 'track': - if (method == 'GET'){ - endpoint = 'me/tracks/contains/?ids='+ helpers.getFromUri('trackid', uri) - } else { - endpoint = 'me/tracks/?ids='+ helpers.getFromUri('trackid', uri) - } - break - case 'album': - if (method == 'GET'){ - endpoint = 'me/albums/contains/?ids='+ helpers.getFromUri('albumid', uri) - } else { - endpoint = 'me/albums/?ids='+ helpers.getFromUri('albumid', uri) - } - break - case 'artist': - if (method == 'GET'){ - endpoint = 'me/following/contains?type=artist&ids='+ helpers.getFromUri('artistid', uri) - } else { - endpoint = 'me/following?type=artist&ids='+ helpers.getFromUri('artistid', uri) - data = {} - } - break - case 'user': - if (method == 'GET'){ - endpoint = 'me/following/contains?type=user&ids='+ helpers.getFromUri('userid', uri) - } else { - endpoint = 'me/following?type=user&ids='+ helpers.getFromUri('userid', uri) - data = {} - } - break - case 'playlist': - if (method == 'GET'){ - endpoint = 'playlists/'+ helpers.getFromUri('playlistid',uri) +'/followers/contains?ids='+ getState().spotify.me.id - } else { - endpoint = 'playlists/'+ helpers.getFromUri('playlistid',uri) +'/followers' - } - break; + break; + case 'artist': + if (method == 'GET') { + endpoint = `me/following/contains?type=artist&ids=${helpers.getFromUri('artistid', uri)}`; + } else { + endpoint = `me/following?type=artist&ids=${helpers.getFromUri('artistid', uri)}`; + data = {}; } - - request(dispatch, getState, endpoint, method, data) - .then( - response => { - if (response ) is_following = response - if (typeof(is_following) === 'object' ) is_following = is_following[0] - - dispatch({ - type: 'SPOTIFY_LIBRARY_'+asset_name.toUpperCase()+'_CHECK', - key: uri, - in_library: is_following - }) - }, - error => { - dispatch(coreActions.handleException( - 'Could not follow/unfollow', - error - )); - } - ); + break; + case 'user': + if (method == 'GET') { + endpoint = `me/following/contains?type=user&ids=${helpers.getFromUri('userid', uri)}`; + } else { + endpoint = `me/following?type=user&ids=${helpers.getFromUri('userid', uri)}`; + data = {}; + } + break; + case 'playlist': + if (method == 'GET') { + endpoint = `playlists/${helpers.getFromUri('playlistid', uri)}/followers/contains?ids=${getState().spotify.me.id}`; + } else { + endpoint = `playlists/${helpers.getFromUri('playlistid', uri)}/followers`; + } + break; } + + request(dispatch, getState, endpoint, method, data) + .then( + (response) => { + if (response) is_following = response; + if (typeof (is_following) === 'object') is_following = is_following[0]; + + dispatch({ + type: `SPOTIFY_LIBRARY_${asset_name.toUpperCase()}_CHECK`, + key: uri, + in_library: is_following, + }); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not follow/unfollow', + error, + )); + }, + ); + }; } /** * Resolve radio seeds into full objects * * @param radio object - **/ -export function resolveRadioSeeds(radio){ - return (dispatch, getState) => { + * */ +export function resolveRadioSeeds(radio) { + return (dispatch, getState) => { + if (radio.seed_artists.length > 0) { + let artist_ids = ''; + for (var i = 0; i < radio.seed_artists.length; i++) { + if (i > 0) artist_ids += ','; + artist_ids += helpers.getFromUri('artistid', radio.seed_artists[i]); + } - if (radio.seed_artists.length > 0){ - var artist_ids = ''; - for (var i = 0; i < radio.seed_artists.length; i++){ - if (i > 0) artist_ids += ',' - artist_ids += helpers.getFromUri('artistid', radio.seed_artists[i]) + request(dispatch, getState, `artists?ids=${artist_ids}`) + .then( + (response) => { + if (response && response.artists) { + dispatch({ + type: 'ARTISTS_LOADED', + artists: response.artists, + }); + } else { + console.error('No Spotify artists returned', artist_ids); } - - request(dispatch, getState, 'artists?ids='+ artist_ids ) - .then( - response => { - if (response && response.artists){ - dispatch({ - type: 'ARTISTS_LOADED', - artists: response.artists - }) - } else { - console.error('No Spotify artists returned', artist_ids) - } - }, - error => { - dispatch(coreActions.handleException( - 'Could not resolve radio artist seeds', - error - )); - } - ) - } - - if (radio.seed_tracks.length > 0){ - var track_ids = ''; - for (var i = 0; i < radio.seed_tracks.length; i++){ - if (i > 0) track_ids += ',' - track_ids += helpers.getFromUri('trackid', radio.seed_tracks[i]) - } - - request(dispatch, getState, 'tracks?ids='+ track_ids ) - .then( - response => { - dispatch({ - type: 'TRACKS_LOADED', - tracks: response.tracks - }) - }, - error => { - dispatch(coreActions.handleException( - 'Could not load radio track seeds', - error - )); - } - ) - } + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not resolve radio artist seeds', + error, + )); + }, + ); } + + if (radio.seed_tracks.length > 0) { + let track_ids = ''; + for (var i = 0; i < radio.seed_tracks.length; i++) { + if (i > 0) track_ids += ','; + track_ids += helpers.getFromUri('trackid', radio.seed_tracks[i]); + } + + request(dispatch, getState, `tracks?ids=${track_ids}`) + .then( + (response) => { + dispatch({ + type: 'TRACKS_LOADED', + tracks: response.tracks, + }); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load radio track seeds', + error, + )); + }, + ); + } + }; } /** * =============================================================== DISCOVER ============= * ====================================================================================== - **/ + * */ /** * Get my favorites * * @param uri string - **/ -export function getFavorites(limit = 50, term = 'long_term'){ - return (dispatch, getState) => { + * */ +export function getFavorites(limit = 50, term = 'long_term') { + return (dispatch, getState) => { + dispatch({ type: 'SPOTIFY_FAVORITES_LOADED', artists: [], tracks: [] }); - dispatch({type: 'SPOTIFY_FAVORITES_LOADED', artists: [], tracks: []}) + $.when( + request(dispatch, getState, `me/top/artists?limit=${limit}&time_range=${term}`), + request(dispatch, getState, `me/top/tracks?limit=${limit}&time_range=${term}`), - $.when( - request(dispatch, getState, 'me/top/artists?limit='+limit+'&time_range='+term), - request(dispatch, getState, 'me/top/tracks?limit='+limit+'&time_range='+term) - - ).then( - (artists_response, tracks_response) => { - dispatch({ - type: 'SPOTIFY_FAVORITES_LOADED', - artists: artists_response.items, - tracks: tracks_response.items - }); - }, - (artists_error, tracks_error) => { - dispatch(coreActions.handleException( - 'Could not load favorites', - Object.assign({},artists_error,tracks_error) - )); - } - ) - } + ).then( + (artists_response, tracks_response) => { + dispatch({ + type: 'SPOTIFY_FAVORITES_LOADED', + artists: artists_response.items, + tracks: tracks_response.items, + }); + }, + (artists_error, tracks_error) => { + dispatch(coreActions.handleException( + 'Could not load favorites', + { ...artists_error, ...tracks_error }, + )); + }, + ); + }; } @@ -909,118 +886,116 @@ export function getFavorites(limit = 50, term = 'long_term'){ * This is based off our 'favorites' and then we use those as seeds * * @param uris = array of artist or track URIs or a genre string - **/ -export function getRecommendations(uris = [], limit = 20, tunabilities = null){ - return (dispatch, getState) => { + * */ +export function getRecommendations(uris = [], limit = 20, tunabilities = null) { + return (dispatch, getState) => { + dispatch({ type: 'CLEAR_SPOTIFY_RECOMMENDATIONS' }); - dispatch({type: 'CLEAR_SPOTIFY_RECOMMENDATIONS'}); + // build our starting point + const artists_ids = []; + const tracks_ids = []; + const genres = []; - // build our starting point - var artists_ids = [] - var tracks_ids = [] - var genres = [] + for (let i = 0; i < uris.length; i++) { + const uri = uris[i]; - for (var i = 0; i < uris.length; i++){ - var uri = uris[i] + switch (helpers.uriType(uri)) { + case 'artist': + artists_ids.push(helpers.getFromUri('artistid', uri)); + break; - switch (helpers.uriType(uri)){ - - case 'artist': - artists_ids.push(helpers.getFromUri('artistid',uri)) - break + case 'track': + tracks_ids.push(helpers.getFromUri('trackid', uri)); + break; - case 'track': - tracks_ids.push(helpers.getFromUri('trackid',uri)) - break + case 'genre': + genres.push(helpers.getFromUri('genreid', uri)); + break; - case 'genre': - genres.push(helpers.getFromUri('genreid',uri)) - break - - case 'default': - genres.push(uri) - break - } - } - - // construct our endpoint URL with all the appropriate arguments - var endpoint = 'recommendations'; - endpoint += '?seed_artists='+artists_ids.join(','); - endpoint += '&seed_tracks='+tracks_ids.join(','); - endpoint += '&seed_genres='+genres.join(','); - endpoint += '&limit='+limit; - - if (tunabilities){ - for (var key in tunabilities){ - if (tunabilities.hasOwnProperty(key)){ - endpoint += '&'+key+'='+tunabilities[key]; - } - } - } - - request(dispatch, getState, endpoint) - .then( - response => { - var tracks = Object.assign([], response.tracks); - - // We only get simple artist objects, so we need to - // get the full object. We'll add URIs to our recommendations - // anyway so we can proceed in the meantime - var artists_uris = [] - if (tracks.length > artists_ids.length && tracks.length > 10){ - while (artists_uris.length < 6){ - var random_index = Math.round(Math.random() * (tracks.length - 1)) - var artist = tracks[random_index].artists[0] - - // Make sure this artist is not already in our sample, and - // is not one of the seeds - if (!artists_uris.includes(artist.uri) && !artists_ids.includes(artist.id)){ - artists_uris.push(artist.uri) - dispatch(getArtist(artist.uri)) - } - } - } - - // Copy already loaded albums into array - var albums = [] - var albums_uris = [] - if (tracks.length > 10){ - while (albums.length < 6){ - var random_index = Math.round(Math.random() * (tracks.length - 1)) - var album = tracks[random_index].album - - // Make sure this album is not already in our sample - if (!albums_uris.includes(album.uri)){ - albums_uris.push(album.uri) - albums.push(album) - } - } - } - - if (albums.length > 0){ - dispatch(coreActions.albumsLoaded(albums)); - } - - if (tracks.length > 0){ - dispatch(coreActions.tracksLoaded(tracks)); - } - - dispatch({ - type: 'SPOTIFY_RECOMMENDATIONS_LOADED', - seeds_uris: uris, - tracks_uris: helpers.arrayOf('uri',tracks), - artists_uris: artists_uris, - albums_uris: helpers.arrayOf('uri',albums) - }); - }, - error => { - dispatch(coreActions.handleException( - 'Could not load recommendations', - error - )); - } - ) + case 'default': + genres.push(uri); + break; + } } + + // construct our endpoint URL with all the appropriate arguments + let endpoint = 'recommendations'; + endpoint += `?seed_artists=${artists_ids.join(',')}`; + endpoint += `&seed_tracks=${tracks_ids.join(',')}`; + endpoint += `&seed_genres=${genres.join(',')}`; + endpoint += `&limit=${limit}`; + + if (tunabilities) { + for (const key in tunabilities) { + if (tunabilities.hasOwnProperty(key)) { + endpoint += `&${key}=${tunabilities[key]}`; + } + } + } + + request(dispatch, getState, endpoint) + .then( + (response) => { + const tracks = Object.assign([], response.tracks); + + // We only get simple artist objects, so we need to + // get the full object. We'll add URIs to our recommendations + // anyway so we can proceed in the meantime + const artists_uris = []; + if (tracks.length > artists_ids.length && tracks.length > 10) { + while (artists_uris.length < 6) { + var random_index = Math.round(Math.random() * (tracks.length - 1)); + const artist = tracks[random_index].artists[0]; + + // Make sure this artist is not already in our sample, and + // is not one of the seeds + if (!artists_uris.includes(artist.uri) && !artists_ids.includes(artist.id)) { + artists_uris.push(artist.uri); + dispatch(getArtist(artist.uri)); + } + } + } + + // Copy already loaded albums into array + const albums = []; + const albums_uris = []; + if (tracks.length > 10) { + while (albums.length < 6) { + var random_index = Math.round(Math.random() * (tracks.length - 1)); + const { album } = tracks[random_index]; + + // Make sure this album is not already in our sample + if (!albums_uris.includes(album.uri)) { + albums_uris.push(album.uri); + albums.push(album); + } + } + } + + if (albums.length > 0) { + dispatch(coreActions.albumsLoaded(albums)); + } + + if (tracks.length > 0) { + dispatch(coreActions.tracksLoaded(tracks)); + } + + dispatch({ + type: 'SPOTIFY_RECOMMENDATIONS_LOADED', + seeds_uris: uris, + tracks_uris: helpers.arrayOf('uri', tracks), + artists_uris, + albums_uris: helpers.arrayOf('uri', albums), + }); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load recommendations', + error, + )); + }, + ); + }; } @@ -1028,1030 +1003,997 @@ export function getRecommendations(uris = [], limit = 20, tunabilities = null){ * Get all the available genres * * @param uri string - **/ -export function getGenres(){ - return (dispatch, getState) => { - request(dispatch, getState, 'recommendations/available-genre-seeds') - .then( - response => { - dispatch({ - type: 'SPOTIFY_GENRES_LOADED', - genres: response.genres - }); - }, - error => { - dispatch(coreActions.handleException( - 'Could not load genres', - error - )); - } - ) - } + * */ +export function getGenres() { + return (dispatch, getState) => { + request(dispatch, getState, 'recommendations/available-genre-seeds') + .then( + (response) => { + dispatch({ + type: 'SPOTIFY_GENRES_LOADED', + genres: response.genres, + }); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load genres', + error, + )); + }, + ); + }; } - - /** * =============================================================== ARTIST(S) ============ * ====================================================================================== - **/ + * */ /** * Get a single artist * * @param uri string * @param full boolean (whether we want a full artist object) - **/ -export function getArtist(uri, full = false){ - return (dispatch, getState) => { + * */ +export function getArtist(uri, full = false) { + return (dispatch, getState) => { + // Start with an empty object + // As each requests completes, they'll add to this object + const artist = {}; - // Start with an empty object - // As each requests completes, they'll add to this object - var artist = {} + // We need our artist, obviously + const requests = [ + request(dispatch, getState, `artists/${helpers.getFromUri('artistid', uri)}`) + .then( + (response) => { + Object.assign(artist, response); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load artist', + error, + )); + }, + ), + ]; - // We need our artist, obviously - var requests = [ - request(dispatch, getState, 'artists/'+ helpers.getFromUri('artistid', uri) ) - .then( - response => { - Object.assign(artist, response); - }, - error => { - dispatch(coreActions.handleException( - 'Could not load artist', - error - )); - } - ) - ]; + // Do we want a full artist, with all supporting material? + if (full) { + requests.push( + request(dispatch, getState, `artists/${helpers.getFromUri('artistid', uri)}/top-tracks?country=${getState().spotify.country}`) + .then( + (response) => { + Object.assign(artist, response); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load artist\'s top tracks', + error, + )); + }, + ), + ); - // Do we want a full artist, with all supporting material? - if (full){ + requests.push( + request(dispatch, getState, `artists/${helpers.getFromUri('artistid', uri)}/related-artists`) + .then( + (response) => { + dispatch(coreActions.artistsLoaded(response.artists)); + Object.assign(artist, { related_artists_uris: helpers.arrayOf('uri', response.artists) }); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load artist\'s related artists', + error, + )); + }, + ), + ); + } - requests.push( - request(dispatch, getState, 'artists/'+ helpers.getFromUri('artistid', uri) +'/top-tracks?country='+getState().spotify.country ) - .then( - response => { - Object.assign(artist, response); - }, - error => { - dispatch(coreActions.handleException( - 'Could not load artist\'s top tracks', - error - )); - } - ) - ); + // Run our requests + $.when.apply($, requests).then(() => { + if (artist.musicbrainz_id) { + dispatch(lastfmActions.getArtist(artist.uri, false, artist.musicbrainz_id)); + } else { + dispatch(lastfmActions.getArtist(artist.uri, artist.name.replace('&', 'and'))); + } - requests.push( - request(dispatch, getState, 'artists/'+ helpers.getFromUri('artistid', uri) +'/related-artists' ) - .then( - response => { - dispatch(coreActions.artistsLoaded(response.artists)); - Object.assign(artist, { related_artists_uris: helpers.arrayOf('uri',response.artists) }); - }, - error => { - dispatch(coreActions.handleException( - 'Could not load artist\'s related artists', - error - )); - } - ) - ); - } + dispatch(coreActions.artistLoaded(artist)); - // Run our requests - $.when.apply($, requests).then(() => { + // Now go get our artist albums + if (full) { + request(dispatch, getState, `artists/${helpers.getFromUri('artistid', uri)}/albums?market=${getState().spotify.country}`) + .then( + (response) => { + dispatch({ + type: 'SPOTIFY_ARTIST_ALBUMS_LOADED', + artist_uri: uri, + data: response, + }); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load artist\'s albums', + error, + )); + }, + ); + } + }); + }; +} - if (artist.musicbrainz_id){ - dispatch(lastfmActions.getArtist(artist.uri, false, artist.musicbrainz_id)) - } else { - dispatch(lastfmActions.getArtist(artist.uri, artist.name.replace('&','and'))) +export function getArtists(uris) { + return (dispatch, getState) => { + // now get all the artists for this album (full objects) + let ids = ''; + for (let i = 0; i < uris.length; i++) { + if (ids != '') ids += ','; + ids += helpers.getFromUri('artistid', uris[i]); + } + + request(dispatch, getState, `artists/?ids=${ids}`) + .then( + (response) => { + for (var i = i; i < response.length; i++) { + const artist = response; + for (var i = 0; i < artist.albums.length; i++) { + dispatch({ + type: 'ALBUM_LOADED', + album: artist.albums[i], + }); } - + artist.albums_uris = helpers.arrayOf('uri', artist.albums); + artist.albums_more = artist.albums.next; dispatch(coreActions.artistLoaded(artist)); - - // Now go get our artist albums - if (full){ - request(dispatch, getState, 'artists/'+ helpers.getFromUri('artistid', uri) +'/albums?market='+getState().spotify.country ) - .then( - response => { - dispatch({ - type: 'SPOTIFY_ARTIST_ALBUMS_LOADED', - artist_uri: uri, - data: response - }) - }, - error => { - dispatch(coreActions.handleException( - 'Could not load artist\'s albums', - error - )); - } - ); - } - }) - } -} - -export function getArtists(uris){ - return (dispatch, getState) => { - - // now get all the artists for this album (full objects) - var ids = ''; - for(var i = 0; i < uris.length; i++){ - if (ids != '' ) ids += ',' - ids += helpers.getFromUri('artistid', uris[i] ); - } - - request(dispatch, getState, 'artists/?ids='+ids ) - .then( - response => { - for (var i = i; i < response.length; i++){ - var artist = response - for (var i = 0; i < artist.albums.length; i++){ - dispatch({ - type: 'ALBUM_LOADED', - album: artist.albums[i] - }); - } - artist.albums_uris = helpers.arrayOf('uri',artist.albums) - artist.albums_more = artist.albums.next - dispatch(coreActions.artistLoaded(artist)); - } - }, - error => { - dispatch(coreActions.handleException( - 'Could not load artists', - error - )); - } - ); - } + } + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load artists', + error, + )); + }, + ); + }; } -export function playArtistTopTracks(uri){ - return (dispatch, getState) => { - const artists = getState().core.artists +export function playArtistTopTracks(uri) { + return (dispatch, getState) => { + const { artists } = getState().core; - // Do we have this artist (and their tracks) in our index already? - if (typeof(artists[uri]) !== 'undefined' && typeof(artists[uri].tracks) !== 'undefined'){ - const uris = helpers.arrayOf('uri',artists[uri].tracks) - dispatch(mopidyActions.playURIs(uris, uri)) + // Do we have this artist (and their tracks) in our index already? + if (typeof (artists[uri]) !== 'undefined' && typeof (artists[uri].tracks) !== 'undefined') { + const uris = helpers.arrayOf('uri', artists[uri].tracks); + dispatch(mopidyActions.playURIs(uris, uri)); - // We need to load the artist's top tracks first - } else { - request(dispatch, getState, 'artists/'+ helpers.getFromUri('artistid', uri) +'/top-tracks?country='+getState().spotify.country ) - .then( - response => { - const uris = helpers.arrayOf('uri',response.tracks) - dispatch(mopidyActions.playURIs(uris, uri)) - }, - error => { - dispatch(coreActions.handleException( - 'Could not play artist\'s top tracks', - error - )); - } - ) - } + // We need to load the artist's top tracks first + } else { + request(dispatch, getState, `artists/${helpers.getFromUri('artistid', uri)}/top-tracks?country=${getState().spotify.country}`) + .then( + (response) => { + const uris = helpers.arrayOf('uri', response.tracks); + dispatch(mopidyActions.playURIs(uris, uri)); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not play artist\'s top tracks', + error, + )); + }, + ); } + }; } - /** * =============================================================== USER(S) ============== * ====================================================================================== - **/ + * */ -export function getUser(uri){ - return (dispatch, getState) => { - request(dispatch, getState, 'users/'+ helpers.getFromUri('userid',uri)) - .then( - response => { - dispatch(coreActions.userLoaded(helpers.formatUser(response))); - }, - error => { - dispatch(coreActions.handleException( - 'Could not load user', - error - )); - } - ); - } +export function getUser(uri) { + return (dispatch, getState) => { + request(dispatch, getState, `users/${helpers.getFromUri('userid', uri)}`) + .then( + (response) => { + dispatch(coreActions.userLoaded(helpers.formatUser(response))); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load user', + error, + )); + }, + ); + }; } -export function getUserPlaylists(uri){ - return (dispatch, getState) => { +export function getUserPlaylists(uri) { + return (dispatch, getState) => { + // get the first page of playlists + request(dispatch, getState, `users/${helpers.getFromUri('userid', uri)}/playlists?limit=40`) + .then( + (response) => { + const playlists = []; + for (const raw_playlist of response.items) { + let can_edit = false; + if (getState().spotify.me && raw_playlist.owner.id == getState().spotify.me.id) { + can_edit = true; + } - // get the first page of playlists - request(dispatch, getState, 'users/'+ helpers.getFromUri('userid', uri) +'/playlists?limit=40' ) - .then( - response => { - var playlists = []; - for (var raw_playlist of response.items){ + const playlist = { - var can_edit = false; - if (getState().spotify.me && raw_playlist.owner.id == getState().spotify.me.id){ - can_edit = true; - } + ...helpers.formatPlaylist(raw_playlist), + can_edit, + tracks_total: raw_playlist.tracks.total, + }; - var playlist = Object.assign( - {}, - helpers.formatPlaylist(raw_playlist), - { - can_edit: can_edit, - tracks_total: raw_playlist.tracks.total - } - ); + playlists.push(playlist); + } - playlists.push(playlist); - } - - dispatch(coreActions.userPlaylistsLoaded(uri, playlists, response.next, response.total)); - }, - error => { - dispatch(coreActions.handleException( - 'Could not load user\'s playlists', - error - )); - } - ); - } + dispatch(coreActions.userPlaylistsLoaded(uri, playlists, response.next, response.total)); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load user\'s playlists', + error, + )); + }, + ); + }; } - - /** * =============================================================== ALBUM(S) ============= * ====================================================================================== - **/ + * */ /** * Single album * * @oaram uri string - **/ -export function getAlbum(uri){ - return (dispatch, getState) => { + * */ +export function getAlbum(uri) { + return (dispatch, getState) => { + // get the album + request(dispatch, getState, `albums/${helpers.getFromUri('albumid', uri)}`) + .then( + (response) => { + // dispatch our loaded artists (simple objects) + dispatch(coreActions.artistsLoaded(response.artists)); - // get the album - request(dispatch, getState, 'albums/'+ helpers.getFromUri('albumid', uri) ) + const tracks = Object.assign([], response.tracks.items); + + const album = { + + ...helpers.formatAlbum(response), + artists_uris: helpers.arrayOf('uri', response.artists), + tracks_uris: helpers.arrayOf('uri', tracks), + tracks_more: response.tracks.next, + tracks_total: response.tracks.total, + }; + + // add our album to all the tracks + for (var i = 0; i < tracks.length; i++) { + tracks[i].album = { + name: album.name, + uri: album.uri, + }; + } + + dispatch(coreActions.albumLoaded(album)); + dispatch(coreActions.tracksLoaded(tracks)); + + // now get all the artists for this album (full objects) + // we do this to get the artist artwork + const artist_ids = []; + for (var i = 0; i < response.artists.length; i++) { + artist_ids.push(helpers.getFromUri('artistid', response.artists[i].uri)); + } + + // get all album artists as full objects + request(dispatch, getState, `artists/?ids=${artist_ids}`) .then( - response => { - - // dispatch our loaded artists (simple objects) - dispatch(coreActions.artistsLoaded(response.artists)); - - var tracks = Object.assign([], response.tracks.items); - - var album = Object.assign( - {}, - helpers.formatAlbum(response), - { - artists_uris: helpers.arrayOf('uri',response.artists), - tracks_uris: helpers.arrayOf('uri', tracks), - tracks_more: response.tracks.next, - tracks_total: response.tracks.total - } - ); - - // add our album to all the tracks - for (var i = 0; i < tracks.length; i++){ - tracks[i].album = { - name: album.name, - uri: album.uri - } - } - - dispatch(coreActions.albumLoaded(album)); - dispatch(coreActions.tracksLoaded(tracks)); - - // now get all the artists for this album (full objects) - // we do this to get the artist artwork - var artist_ids = []; - for(var i = 0; i < response.artists.length; i++){ - artist_ids.push(helpers.getFromUri('artistid', response.artists[i].uri ) ) - } - - // get all album artists as full objects - request(dispatch, getState, 'artists/?ids='+artist_ids ) - .then( - response => { - dispatch(coreActions.artistsLoaded(response.artists)); - }, - error => { - dispatch(coreActions.handleException( - 'Could not load album\'s artists', - error - )); - } - ); - - }, - error => { - dispatch(coreActions.handleException( - 'Could not load album', - error - )); - } - ) - } + (response) => { + dispatch(coreActions.artistsLoaded(response.artists)); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load album\'s artists', + error, + )); + }, + ); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load album', + error, + )); + }, + ); + }; } -export function toggleAlbumInLibrary(uri, method){ - if (method == 'PUT' ) var new_state = 1 - if (method == 'DELETE' ) var new_state = 0 +export function toggleAlbumInLibrary(uri, method) { + if (method == 'PUT') var new_state = 1; + if (method == 'DELETE') var new_state = 0; - return (dispatch, getState) => { - request(dispatch, getState, 'me/albums?ids='+ helpers.getFromUri('albumid',uri), method ) - .then( - response => { - dispatch({ - type: 'SPOTIFY_ALBUM_FOLLOWING', - key: uri, - data: new_state - }); - }, - error => { - dispatch(coreActions.handleException( - 'Could not add/remove library album', - error - )); - } - ); - } + return (dispatch, getState) => { + request(dispatch, getState, `me/albums?ids=${helpers.getFromUri('albumid', uri)}`, method) + .then( + (response) => { + dispatch({ + type: 'SPOTIFY_ALBUM_FOLLOWING', + key: uri, + data: new_state, + }); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not add/remove library album', + error, + )); + }, + ); + }; } - - - /** * =============================================================== PLAYLIST(S) ========== * ====================================================================================== - **/ + * */ -export function createPlaylist(name, description, is_public, is_collaborative){ - return (dispatch, getState) => { +export function createPlaylist(name, description, is_public, is_collaborative) { + return (dispatch, getState) => { + const data = { + name, + description, + public: is_public, + collaborative: is_collaborative, + }; - var data = { - name: name, - description: description, - public: is_public, - collaborative: is_collaborative - } + request(dispatch, getState, `users/${getState().spotify.me.id}/playlists/`, 'POST', data) + .then( + (response) => { + dispatch({ + type: 'PLAYLIST_LOADED', + key: response.uri, + playlist: { - request(dispatch, getState, 'users/'+ getState().spotify.me.id +'/playlists/', 'POST', data) - .then( - response => { - dispatch({ - type: 'PLAYLIST_LOADED', - key: response.uri, - playlist: Object.assign( - {}, - response, - { - can_edit: true, - tracks: [], - tracks_more: null, - tracks_total: 0 - }) - }); - - dispatch({ - type: 'LIBRARY_PLAYLISTS_LOADED', - uris: [response.uri] - }) - - dispatch(uiActions.createNotification({type: 'info', content: 'Created playlist'})); + ...response, + can_edit: true, + tracks: [], + tracks_more: null, + tracks_total: 0, }, - error => { - dispatch(coreActions.handleException( - 'Could not create playlist', - error - )); - } - ) - } + }); + + dispatch({ + type: 'LIBRARY_PLAYLISTS_LOADED', + uris: [response.uri], + }); + + dispatch(uiActions.createNotification({ type: 'info', content: 'Created playlist' })); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not create playlist', + error, + )); + }, + ); + }; } -export function savePlaylist(uri, name, description, is_public, is_collaborative, image){ - return (dispatch, getState) => { +export function savePlaylist(uri, name, description, is_public, is_collaborative, image) { + return (dispatch, getState) => { + const data = { + name, + description, + public: is_public, + collaborative: is_collaborative, + }; - var data = { - name: name, - description: description, - public: is_public, - collaborative: is_collaborative - } + // Update the playlist fields + request( + dispatch, getState, `users/${getState().spotify.me.id}/playlists/${helpers.getFromUri('playlistid', uri)}`, 'PUT', data, + ) + .then( + (response) => { + dispatch(uiActions.createNotification({ type: 'info', content: 'Playlist saved' })); - // Update the playlist fields - request( - dispatch, getState, 'users/'+ getState().spotify.me.id +'/playlists/'+ helpers.getFromUri('playlistid',uri), 'PUT', data) - .then( - response => { - dispatch(uiActions.createNotification({type: 'info', content: 'Playlist saved'})); + // Save the image + if (image) { + request(dispatch, getState, `users/${getState().spotify.me.id}/playlists/${helpers.getFromUri('playlistid', uri)}/images`, 'PUT', image) + .then( - // Save the image - if (image){ - request(dispatch, getState, 'users/'+ getState().spotify.me.id +'/playlists/'+ helpers.getFromUri('playlistid',uri)+'/images', 'PUT', image) - .then( + (response) => { + dispatch({ + type: 'PLAYLIST_UPDATED', + key: uri, + playlist: { + name, + public: is_public, + collaborative: is_collaborative, + description, + }, + }); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not save image', + error, + )); + }, + ); - response => { - dispatch({ - type: 'PLAYLIST_UPDATED', - key: uri, - playlist: { - name: name, - public: is_public, - collaborative: is_collaborative, - description: description - } - }); - }, - error => { - dispatch(coreActions.handleException( - 'Could not save image', - error - )); - } - ); - - // No image, so we're done here - } else { - dispatch({ - type: 'PLAYLIST_UPDATED', - key: uri, - playlist: { - name: name, - public: is_public, - collaborative: is_collaborative, - description: description - } - }); - } - }, - error => { - dispatch(coreActions.handleException( - 'Could not save playlist', - error - )); - } - ); - } + // No image, so we're done here + } else { + dispatch({ + type: 'PLAYLIST_UPDATED', + key: uri, + playlist: { + name, + public: is_public, + collaborative: is_collaborative, + description, + }, + }); + } + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not save playlist', + error, + )); + }, + ); + }; } -export function getPlaylist(uri){ - return (dispatch, getState) => { +export function getPlaylist(uri) { + return (dispatch, getState) => { + // get the main playlist object + request(dispatch, getState, `playlists/${helpers.getFromUri('playlistid', uri)}?market=${getState().spotify.country}`) + .then( + (response) => { + // convert links in description + let description = null; + if (response.description) { + description = response.description; + description = description.split(' { - dispatch(coreActions.handleException( - 'Could not load playlist', - error - )); - } - ) - } + dispatch(coreActions.userLoaded(helpers.formatUser(response.owner))); + dispatch(coreActions.tracksLoaded(tracks)); + dispatch(coreActions.playlistLoaded(playlist)); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load playlist', + error, + )); + }, + ); + }; } /** * Get all library tracks * * Recursively get .next until we have all tracks - **/ + * */ -export function getLibraryTracksAndPlay(uri){ - return (dispatch, getState) => { - dispatch(uiActions.startProcess( - 'SPOTIFY_GET_LIBRARY_TRACKS_AND_PLAY_PROCESSOR', - 'Loading library tracks', - { - uri: uri, - next: 'me/tracks' - } - )) - } +export function getLibraryTracksAndPlay(uri) { + return (dispatch, getState) => { + dispatch(uiActions.startProcess( + 'SPOTIFY_GET_LIBRARY_TRACKS_AND_PLAY_PROCESSOR', + 'Loading library tracks', + { + uri, + next: 'me/tracks', + }, + )); + }; } -export function getLibraryTracksAndPlayProcessor(data){ - return (dispatch, getState) => { - request(dispatch, getState, data.next) - .then( - response => { +export function getLibraryTracksAndPlayProcessor(data) { + return (dispatch, getState) => { + request(dispatch, getState, data.next) + .then( + (response) => { + // Check to see if we've been cancelled + if (getState().ui.processes.SPOTIFY_GET_LIBRARY_TRACKS_AND_PLAY_PROCESSOR !== undefined) { + const processor = getState().ui.processes.SPOTIFY_GET_LIBRARY_TRACKS_AND_PLAY_PROCESSOR; - // Check to see if we've been cancelled - if (getState().ui.processes['SPOTIFY_GET_LIBRARY_TRACKS_AND_PLAY_PROCESSOR'] !== undefined){ - var processor = getState().ui.processes['SPOTIFY_GET_LIBRARY_TRACKS_AND_PLAY_PROCESSOR'] + if (processor.status == 'cancelling') { + dispatch(uiActions.processCancelled('SPOTIFY_GET_LIBRARY_TRACKS_AND_PLAY_PROCESSOR')); + return false; + } + } - if (processor.status == 'cancelling'){ - dispatch(uiActions.processCancelled('SPOTIFY_GET_LIBRARY_TRACKS_AND_PLAY_PROCESSOR')) - return false - } - } + // Add on our new batch of loaded tracks + let uris = []; + const new_uris = []; + for (let i = 0; i < response.items.length; i++) { + new_uris.push(response.items[i].track.uri); + } + if (data.uris) { + uris = [...data.uris, ...new_uris]; + } else { + uris = new_uris; + } - // Add on our new batch of loaded tracks - var uris = []; - var new_uris = []; - for (var i = 0; i < response.items.length; i++){ - new_uris.push(response.items[i].track.uri) - } - if (data.uris){ - uris = [...data.uris, ...new_uris]; - } else { - uris = new_uris; - } - - // We got a next link, so we've got more work to be done - if (response.next){ - dispatch(uiActions.updateProcess( - 'SPOTIFY_GET_LIBRARY_TRACKS_AND_PLAY_PROCESSOR', - 'Loading '+(response.total-uris.length)+' library tracks', - { - next: response.next, - total: response.total, - remaining: response.total - uris.length - } - )) - dispatch(uiActions.runProcess( - 'SPOTIFY_GET_LIBRARY_TRACKS_AND_PLAY_PROCESSOR', - { - next: response.next, - uris: uris - } - )) - } else { - dispatch(mopidyActions.playURIs(uris, data.uri)); - dispatch(uiActions.processFinishing('SPOTIFY_GET_LIBRARY_TRACKS_AND_PLAY_PROCESSOR')) - } - }, - error => { - dispatch(coreActions.handleException( - 'Could not load library tracks', - error - )); - } - ); - } + // We got a next link, so we've got more work to be done + if (response.next) { + dispatch(uiActions.updateProcess( + 'SPOTIFY_GET_LIBRARY_TRACKS_AND_PLAY_PROCESSOR', + `Loading ${response.total - uris.length} library tracks`, + { + next: response.next, + total: response.total, + remaining: response.total - uris.length, + }, + )); + dispatch(uiActions.runProcess( + 'SPOTIFY_GET_LIBRARY_TRACKS_AND_PLAY_PROCESSOR', + { + next: response.next, + uris, + }, + )); + } else { + dispatch(mopidyActions.playURIs(uris, data.uri)); + dispatch(uiActions.processFinishing('SPOTIFY_GET_LIBRARY_TRACKS_AND_PLAY_PROCESSOR')); + } + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load library tracks', + error, + )); + }, + ); + }; } /** * Get all tracks for a playlist * * Recursively get .next until we have all tracks - **/ + * */ -export function getAllPlaylistTracks(uri, shuffle = false, callback_action = null, play_next = false, at_position = null, offset = 0){ - return (dispatch, getState) => { - dispatch(uiActions.startProcess( - 'SPOTIFY_GET_ALL_PLAYLIST_TRACKS_PROCESSOR', - 'Loading playlist tracks', - { - uri: uri, - next: 'playlists/'+ helpers.getFromUri('playlistid',uri) +'/tracks?market='+getState().spotify.country, - shuffle: shuffle, - play_next: play_next, - at_position: at_position, - offset: offset, - callback_action: callback_action - } - )) - } +export function getAllPlaylistTracks(uri, shuffle = false, callback_action = null, play_next = false, at_position = null, offset = 0) { + return (dispatch, getState) => { + dispatch(uiActions.startProcess( + 'SPOTIFY_GET_ALL_PLAYLIST_TRACKS_PROCESSOR', + 'Loading playlist tracks', + { + uri, + next: `playlists/${helpers.getFromUri('playlistid', uri)}/tracks?market=${getState().spotify.country}`, + shuffle, + play_next, + at_position, + offset, + callback_action, + }, + )); + }; } -export function getAllPlaylistTracksProcessor(data){ - return (dispatch, getState) => { - request(dispatch, getState, data.next) - .then( - response => { +export function getAllPlaylistTracksProcessor(data) { + return (dispatch, getState) => { + request(dispatch, getState, data.next) + .then( + (response) => { + // Check to see if we've been cancelled + if (getState().ui.processes.SPOTIFY_GET_ALL_PLAYLIST_TRACKS_PROCESSOR !== undefined) { + const processor = getState().ui.processes.SPOTIFY_GET_ALL_PLAYLIST_TRACKS_PROCESSOR; - // Check to see if we've been cancelled - if (getState().ui.processes['SPOTIFY_GET_ALL_PLAYLIST_TRACKS_PROCESSOR'] !== undefined){ - var processor = getState().ui.processes['SPOTIFY_GET_ALL_PLAYLIST_TRACKS_PROCESSOR'] + if (processor.status == 'cancelling') { + dispatch(uiActions.processCancelled('SPOTIFY_GET_ALL_PLAYLIST_TRACKS_PROCESSOR')); + return false; + } + } - if (processor.status == 'cancelling'){ - dispatch(uiActions.processCancelled('SPOTIFY_GET_ALL_PLAYLIST_TRACKS_PROCESSOR')) - return false - } - } - - // Add on our new batch of loaded tracks - var uris = [] - var new_uris = [] - for (var item of response.items){ - if (item.track){ + // Add on our new batch of loaded tracks + let uris = []; + const new_uris = []; + for (const item of response.items) { + if (item.track) { new_uris.push(item.track.uri); } - } - if (data.uris){ - uris = [...data.uris, ...new_uris]; - } else { - uris = new_uris; - } + } + if (data.uris) { + uris = [...data.uris, ...new_uris]; + } else { + uris = new_uris; + } - // We got a next link, so we've got more work to be done - if (response.next){ - dispatch(uiActions.updateProcess( - 'SPOTIFY_GET_ALL_PLAYLIST_TRACKS_PROCESSOR', - 'Loading '+(response.total-uris.length)+' playlist tracks', - Object.assign( - {}, - data, - { - next: response.next, + // We got a next link, so we've got more work to be done + if (response.next) { + dispatch(uiActions.updateProcess( + 'SPOTIFY_GET_ALL_PLAYLIST_TRACKS_PROCESSOR', + `Loading ${response.total - uris.length} playlist tracks`, + { + + ...data, + next: response.next, total: response.total, - remaining: response.total - uris.length - } - ) - )); - dispatch(uiActions.runProcess( - 'SPOTIFY_GET_ALL_PLAYLIST_TRACKS_PROCESSOR', - Object.assign( - {}, - data, - { - next: response.next, - uris: uris, - } - ) - )); - } else { + remaining: response.total - uris.length, + }, + )); + dispatch(uiActions.runProcess( + 'SPOTIFY_GET_ALL_PLAYLIST_TRACKS_PROCESSOR', + { - if (data.shuffle){ + ...data, + next: response.next, + uris, + }, + )); + } else { + if (data.shuffle) { uris = helpers.shuffle(uris); } // We don't bother "finishing", we just want it "finished" immediately // This bypasses the fade transition for a more smooth transition between two // processes that flow together - dispatch(uiActions.processFinished('SPOTIFY_GET_ALL_PLAYLIST_TRACKS_PROCESSOR')); + dispatch(uiActions.processFinished('SPOTIFY_GET_ALL_PLAYLIST_TRACKS_PROCESSOR')); - if (data.callback_action == 'enqueue'){ + if (data.callback_action == 'enqueue') { dispatch(mopidyActions.enqueueURIs(uris, data.uri, data.play_next, data.at_position, data.offset)); } else { dispatch(mopidyActions.playURIs(uris, data.uri)); } - } - }, - error => { - dispatch(coreActions.handleException( - 'Could not load tracks to play playlist', - error - )); - } - ); - } + } + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load tracks to play playlist', + error, + )); + }, + ); + }; } -export function toggleFollowingPlaylist(uri, method){ - if (method == 'PUT' ) var new_state = 1 - if (method == 'DELETE' ) var new_state = 0 +export function toggleFollowingPlaylist(uri, method) { + if (method == 'PUT') var new_state = 1; + if (method == 'DELETE') var new_state = 0; - return (dispatch, getState) => { - request(dispatch, getState, 'playlists/'+ helpers.getFromUri('playlistid',uri) + '/followers', method ) - .then( - response => { - dispatch({ - type: 'SPOTIFY_PLAYLIST_FOLLOWING_LOADED', - key: uri, - is_following: new_state - }); - }, - error => { - dispatch(coreActions.handleException( - 'Could not add/remove library playlist', - error - )); - } - ); - } + return (dispatch, getState) => { + request(dispatch, getState, `playlists/${helpers.getFromUri('playlistid', uri)}/followers`, method) + .then( + (response) => { + dispatch({ + type: 'SPOTIFY_PLAYLIST_FOLLOWING_LOADED', + key: uri, + is_following: new_state, + }); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not add/remove library playlist', + error, + )); + }, + ); + }; } -export function addTracksToPlaylist(uri, tracks_uris){ - return (dispatch, getState) => { - request(dispatch, getState, 'playlists/'+ helpers.getFromUri('playlistid',uri) + '/tracks', 'POST', { uris: tracks_uris } ) - .then( - response => { - dispatch({ - type: 'PLAYLIST_TRACKS_ADDED', - key: uri, - tracks_uris: tracks_uris, - snapshot_id: response.snapshot_id - }); - }, - error => { - dispatch(coreActions.handleException( - 'Could not add tracks to playlist', - error - )); - } - ); - } +export function addTracksToPlaylist(uri, tracks_uris) { + return (dispatch, getState) => { + request(dispatch, getState, `playlists/${helpers.getFromUri('playlistid', uri)}/tracks`, 'POST', { uris: tracks_uris }) + .then( + (response) => { + dispatch({ + type: 'PLAYLIST_TRACKS_ADDED', + key: uri, + tracks_uris, + snapshot_id: response.snapshot_id, + }); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not add tracks to playlist', + error, + )); + }, + ); + }; } -export function deleteTracksFromPlaylist(uri, snapshot_id, tracks_indexes){ - return (dispatch, getState) => { - request(dispatch, getState, 'playlists/'+ helpers.getFromUri('playlistid',uri) + '/tracks', 'DELETE', { snapshot_id: snapshot_id, positions: tracks_indexes } ) - .then( - response => { - dispatch({ - type: 'PLAYLIST_TRACKS_REMOVED', - key: uri, - tracks_indexes: tracks_indexes, - snapshot_id: response.snapshot_id - }); - }, - error => { - dispatch(coreActions.handleException( - 'Could not remove tracks from playlist', - error - )); - } - ); - } +export function deleteTracksFromPlaylist(uri, snapshot_id, tracks_indexes) { + return (dispatch, getState) => { + request(dispatch, getState, `playlists/${helpers.getFromUri('playlistid', uri)}/tracks`, 'DELETE', { snapshot_id, positions: tracks_indexes }) + .then( + (response) => { + dispatch({ + type: 'PLAYLIST_TRACKS_REMOVED', + key: uri, + tracks_indexes, + snapshot_id: response.snapshot_id, + }); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not remove tracks from playlist', + error, + )); + }, + ); + }; } -export function reorderPlaylistTracks(uri, range_start, range_length, insert_before, snapshot_id){ - return (dispatch, getState) => { - request(dispatch, getState, 'playlists/'+ helpers.getFromUri('playlistid',uri) + '/tracks', 'PUT', { uri: uri, range_start: range_start, range_length: range_length, insert_before: insert_before, snapshot_id: snapshot_id } ) - .then( - response => { - dispatch({ - type: 'PLAYLIST_TRACKS_REORDERED', - key: uri, - range_start: range_start, - range_length: range_length, - insert_before: insert_before, - snapshot_id: response.snapshot_id - }); - }, - error => { - dispatch(coreActions.handleException( - 'Could not reorder playlist tracks', - error - )); - } - ); - } +export function reorderPlaylistTracks(uri, range_start, range_length, insert_before, snapshot_id) { + return (dispatch, getState) => { + request(dispatch, getState, `playlists/${helpers.getFromUri('playlistid', uri)}/tracks`, 'PUT', { + uri, range_start, range_length, insert_before, snapshot_id, + }) + .then( + (response) => { + dispatch({ + type: 'PLAYLIST_TRACKS_REORDERED', + key: uri, + range_start, + range_length, + insert_before, + snapshot_id: response.snapshot_id, + }); + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not reorder playlist tracks', + error, + )); + }, + ); + }; } - /** * =============================================================== LIBRARY ============== * ====================================================================================== - **/ + * */ -export function flushLibrary(){ - return { - type: "SPOTIFY_FLUSH_LIBRARY" - } +export function flushLibrary() { + return { + type: 'SPOTIFY_FLUSH_LIBRARY', + }; } - + /** * Playlists - **/ + * */ -export function getLibraryPlaylists(){ - return (dispatch, getState) => { - var last_run = getState().ui.processes.SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR +export function getLibraryPlaylists() { + return (dispatch, getState) => { + const last_run = getState().ui.processes.SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR; - if (!last_run){ - dispatch(uiActions.startProcess('SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR','Loading Spotify playlists', {next: 'me/playlists?limit=50'})) - } else if (last_run.status == 'cancelled'){ - dispatch(uiActions.resumeProcess('SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR')) + if (!last_run) { + dispatch(uiActions.startProcess('SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR', 'Loading Spotify playlists', { next: 'me/playlists?limit=50' })); + } else if (last_run.status == 'cancelled') { + dispatch(uiActions.resumeProcess('SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR')); - // We've already finished, but the status has been flushed - } else if (last_run.status == 'finished' && !getState().spotify.library_playlists_loaded_all){ - dispatch(uiActions.startProcess('SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR','Loading Spotify playlists', {next: 'me/playlists?limit=50'})) - } + // We've already finished, but the status has been flushed + } else if (last_run.status == 'finished' && !getState().spotify.library_playlists_loaded_all) { + dispatch(uiActions.startProcess('SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR', 'Loading Spotify playlists', { next: 'me/playlists?limit=50' })); } + }; } -export function getLibraryPlaylistsProcessor(data){ - return (dispatch, getState) => { - request(dispatch, getState, data.next) - .then( - response => { - dispatch({ - type: 'SPOTIFY_LIBRARY_PLAYLISTS_LOADED', - playlists: response.items - }) +export function getLibraryPlaylistsProcessor(data) { + return (dispatch, getState) => { + request(dispatch, getState, data.next) + .then( + (response) => { + dispatch({ + type: 'SPOTIFY_LIBRARY_PLAYLISTS_LOADED', + playlists: response.items, + }); - // Check to see if we've been cancelled - if (getState().ui.processes['SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR'] !== undefined){ - var processor = getState().ui.processes['SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR'] + // Check to see if we've been cancelled + if (getState().ui.processes.SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR !== undefined) { + const processor = getState().ui.processes.SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR; - if (processor.status == 'cancelling'){ - dispatch(uiActions.processCancelled('SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR')) - return false - } - } + if (processor.status == 'cancelling') { + dispatch(uiActions.processCancelled('SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR')); + return false; + } + } - // We got a next link, so we've got more work to be done - if (response.next){ - var total = response.total - var loaded = getState().spotify.library_playlists.length - var remaining = total - loaded - dispatch(uiActions.updateProcess( - 'SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR', - 'Loading '+remaining+' Spotify playlists', - { - next: response.next, - total: response.total, - remaining: remaining - } - )) - dispatch(uiActions.runProcess('SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR', {next: response.next})) - } else { - dispatch(uiActions.processFinishing('SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR')) - dispatch({type: 'SPOTIFY_LIBRARY_PLAYLISTS_LOADED_ALL'}) - } - }, - error => { - dispatch(coreActions.handleException( - 'Could not load library playlists', - error - )); - } - ); - } + // We got a next link, so we've got more work to be done + if (response.next) { + const { total } = response; + const loaded = getState().spotify.library_playlists.length; + const remaining = total - loaded; + dispatch(uiActions.updateProcess( + 'SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR', + `Loading ${remaining} Spotify playlists`, + { + next: response.next, + total: response.total, + remaining, + }, + )); + dispatch(uiActions.runProcess('SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR', { next: response.next })); + } else { + dispatch(uiActions.processFinishing('SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR')); + dispatch({ type: 'SPOTIFY_LIBRARY_PLAYLISTS_LOADED_ALL' }); + } + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load library playlists', + error, + )); + }, + ); + }; } - + /** * Artists - **/ + * */ -export function getLibraryArtists(){ - return (dispatch, getState) => { - var last_run = getState().ui.processes.SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR +export function getLibraryArtists() { + return (dispatch, getState) => { + const last_run = getState().ui.processes.SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR; - if (!last_run){ - dispatch(uiActions.startProcess('SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR','Loading Spotify artists', {next: 'me/following?type=artist&limit=50'})) - } else if (last_run.status == 'cancelled'){ - dispatch(uiActions.resumeProcess('SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR')) + if (!last_run) { + dispatch(uiActions.startProcess('SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR', 'Loading Spotify artists', { next: 'me/following?type=artist&limit=50' })); + } else if (last_run.status == 'cancelled') { + dispatch(uiActions.resumeProcess('SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR')); - // We've already finished, but the status has been flushed - } else if (last_run.status == 'finished' && !getState().spotify.library_artists_loaded_all){ - dispatch(uiActions.startProcess('SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR','Loading Spotify artists', {next: 'me/following?type=artist&limit=50'})) - } + // We've already finished, but the status has been flushed + } else if (last_run.status == 'finished' && !getState().spotify.library_artists_loaded_all) { + dispatch(uiActions.startProcess('SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR', 'Loading Spotify artists', { next: 'me/following?type=artist&limit=50' })); } + }; } -export function getLibraryArtistsProcessor(data){ - return (dispatch, getState) => { - request(dispatch, getState, data.next) - .then( - response => { - dispatch({ - type: 'SPOTIFY_LIBRARY_ARTISTS_LOADED', - artists: response.artists.items - }) +export function getLibraryArtistsProcessor(data) { + return (dispatch, getState) => { + request(dispatch, getState, data.next) + .then( + (response) => { + dispatch({ + type: 'SPOTIFY_LIBRARY_ARTISTS_LOADED', + artists: response.artists.items, + }); - // Check to see if we've been cancelled - if (getState().ui.processes['SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR'] !== undefined){ - var processor = getState().ui.processes['SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR'] + // Check to see if we've been cancelled + if (getState().ui.processes.SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR !== undefined) { + const processor = getState().ui.processes.SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR; - if (processor.status == 'cancelling'){ - dispatch(uiActions.processCancelled('SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR')) - return false - } - } + if (processor.status == 'cancelling') { + dispatch(uiActions.processCancelled('SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR')); + return false; + } + } - // We got a next link, so we've got more work to be done - if (response.artists.next){ - var total = response.artists.total - var loaded = getState().spotify.library_artists.length - var remaining = total - loaded - dispatch(uiActions.updateProcess( - 'SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR', - 'Loading '+remaining+' Spotify artists', - { - next: response.artists.next, - total: response.artists.total, - remaining: remaining - } - )) - dispatch(uiActions.runProcess('SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR', {next: response.artists.next})) - } else { - dispatch(uiActions.processFinishing('SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR')) - } - }, - error => { - dispatch(coreActions.handleException( - 'Could not load library artists', - error - )); - } - ); - } + // We got a next link, so we've got more work to be done + if (response.artists.next) { + const { total } = response.artists; + const loaded = getState().spotify.library_artists.length; + const remaining = total - loaded; + dispatch(uiActions.updateProcess( + 'SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR', + `Loading ${remaining} Spotify artists`, + { + next: response.artists.next, + total: response.artists.total, + remaining, + }, + )); + dispatch(uiActions.runProcess('SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR', { next: response.artists.next })); + } else { + dispatch(uiActions.processFinishing('SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR')); + } + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load library artists', + error, + )); + }, + ); + }; } - + /** * ALbums - **/ + * */ -export function getLibraryAlbums(){ - return (dispatch, getState) => { - var last_run = getState().ui.processes.SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR +export function getLibraryAlbums() { + return (dispatch, getState) => { + const last_run = getState().ui.processes.SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR; - if (!last_run){ - dispatch(uiActions.startProcess('SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR','Loading Spotify albums', {next: 'me/albums?limit=50'})) - } else if (last_run.status == 'cancelled'){ - dispatch(uiActions.updateProcess('SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR','Loading Spotify albums', {next: 'me/albums?limit=50'})) + if (!last_run) { + dispatch(uiActions.startProcess('SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR', 'Loading Spotify albums', { next: 'me/albums?limit=50' })); + } else if (last_run.status == 'cancelled') { + dispatch(uiActions.updateProcess('SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR', 'Loading Spotify albums', { next: 'me/albums?limit=50' })); - // We've already finished, but the status has been flushed - } else if (last_run.status == 'finished' && !getState().spotify.library_albums_loaded_all){ - dispatch(uiActions.startProcess('SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR','Loading Spotify albums', {next: 'me/albums?limit=50'})) - } + // We've already finished, but the status has been flushed + } else if (last_run.status == 'finished' && !getState().spotify.library_albums_loaded_all) { + dispatch(uiActions.startProcess('SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR', 'Loading Spotify albums', { next: 'me/albums?limit=50' })); } + }; } -export function getLibraryAlbumsProcessor(data){ - return (dispatch, getState) => { - request(dispatch, getState, data.next) - .then( - response => { - dispatch({ - type: 'SPOTIFY_LIBRARY_ALBUMS_LOADED', - albums: response.items - }) +export function getLibraryAlbumsProcessor(data) { + return (dispatch, getState) => { + request(dispatch, getState, data.next) + .then( + (response) => { + dispatch({ + type: 'SPOTIFY_LIBRARY_ALBUMS_LOADED', + albums: response.items, + }); - // Check to see if we've been cancelled - if (getState().ui.processes['SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR'] !== undefined){ - var processor = getState().ui.processes['SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR'] + // Check to see if we've been cancelled + if (getState().ui.processes.SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR !== undefined) { + const processor = getState().ui.processes.SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR; - if (processor.status == 'cancelling'){ - dispatch(uiActions.processCancelled('SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR')) - return false - } - } + if (processor.status == 'cancelling') { + dispatch(uiActions.processCancelled('SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR')); + return false; + } + } - // We got a next link, so we've got more work to be done - if (response.next){ - var total = response.total - var loaded = getState().spotify.library_albums.length - var remaining = total - loaded - dispatch(uiActions.updateProcess( - 'SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR', - 'Loading '+remaining+' Spotify albums', - { - next: response.next, - total: response.total, - remaining: remaining - } - )) - dispatch(uiActions.runProcess('SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR', {next: response.next})) - } else { - dispatch(uiActions.processFinishing('SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR')) - } - }, - error => { - dispatch(coreActions.handleException( - 'Could not load library albums', - error - )); - } - ); - } -} \ No newline at end of file + // We got a next link, so we've got more work to be done + if (response.next) { + const { total } = response; + const loaded = getState().spotify.library_albums.length; + const remaining = total - loaded; + dispatch(uiActions.updateProcess( + 'SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR', + `Loading ${remaining} Spotify albums`, + { + next: response.next, + total: response.total, + remaining, + }, + )); + dispatch(uiActions.runProcess('SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR', { next: response.next })); + } else { + dispatch(uiActions.processFinishing('SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR')); + } + }, + (error) => { + dispatch(coreActions.handleException( + 'Could not load library albums', + error, + )); + }, + ); + }; +} diff --git a/src/js/services/spotify/middleware.js b/src/js/services/spotify/middleware.js index 06f22e19..3e0ab753 100755 --- a/src/js/services/spotify/middleware.js +++ b/src/js/services/spotify/middleware.js @@ -2,150 +2,147 @@ import ReactGA from 'react-ga'; import { sha256 } from 'js-sha256'; -var helpers = require('./../../helpers'); -var coreActions = require('../core/actions'); -var spotifyActions = require('./actions'); -var uiActions = require('../ui/actions'); -var pusherActions = require('../pusher/actions'); +const helpers = require('./../../helpers'); +const coreActions = require('../core/actions'); +const spotifyActions = require('./actions'); +const uiActions = require('../ui/actions'); +const pusherActions = require('../pusher/actions'); -const SpotifyMiddleware = (function(){ - return store => next => action => { - var spotify = store.getState().spotify; +const SpotifyMiddleware = (function () { + return (store) => (next) => (action) => { + const { spotify } = store.getState(); - switch(action.type){ - - case 'SPOTIFY_AUTHORIZATION_GRANTED': - if (store.getState().ui.allow_reporting){ - ReactGA.event({category: 'Spotify', action: 'Authorization granted'}); + switch (action.type) { + case 'SPOTIFY_AUTHORIZATION_GRANTED': + if (store.getState().ui.allow_reporting) { + ReactGA.event({ category: 'Spotify', action: 'Authorization granted' }); } - // Flush out the previous user's library - store.dispatch(spotifyActions.flushLibrary()); + // Flush out the previous user's library + store.dispatch(spotifyActions.flushLibrary()); - next(action); - break; + next(action); + break; - case 'SPOTIFY_AUTHORIZATION_REVOKED': - if (store.getState().ui.allow_reporting){ - ReactGA.event({ category: 'Spotify', action: 'Authorization revoked'}); + case 'SPOTIFY_AUTHORIZATION_REVOKED': + if (store.getState().ui.allow_reporting) { + ReactGA.event({ category: 'Spotify', action: 'Authorization revoked' }); } - next(action); + next(action); - // Now dispatch a getMe to get the backend-provided user - store.dispatch(spotifyActions.getMe()); + // Now dispatch a getMe to get the backend-provided user + store.dispatch(spotifyActions.getMe()); - // Flush out the previous user's library - store.dispatch(spotifyActions.flushLibrary()); + // Flush out the previous user's library + store.dispatch(spotifyActions.flushLibrary()); - break; + break; - case 'SPOTIFY_IMPORT_AUTHORIZATION': + case 'SPOTIFY_IMPORT_AUTHORIZATION': - // Flush out the previous user's library - store.dispatch(spotifyActions.flushLibrary()); + // Flush out the previous user's library + store.dispatch(spotifyActions.flushLibrary()); - // Wait a few moments before we fetch, allowing the import to complete first - // TODO: Use callbacks for better code accuracy - setTimeout(() => {store.dispatch(spotifyActions.getMe())}, 100); + // Wait a few moments before we fetch, allowing the import to complete first + // TODO: Use callbacks for better code accuracy + setTimeout(() => { store.dispatch(spotifyActions.getMe()); }, 100); - next(action); - break; + next(action); + break; - case 'SPOTIFY_RECOMMENDATIONS_LOADED': - if (store.getState().ui.allow_reporting && action.seeds_uris){ - ReactGA.event({ category: 'Spotify', action: 'Recommendations', label: action.seeds_uris.join(',') }) - } - next(action) - break; + case 'SPOTIFY_RECOMMENDATIONS_LOADED': + if (store.getState().ui.allow_reporting && action.seeds_uris) { + ReactGA.event({ category: 'Spotify', action: 'Recommendations', label: action.seeds_uris.join(',') }); + } + next(action); + break; - case 'SPOTIFY_USER_LOADED': - if (store.getState().ui.allow_reporting && action.data){ + case 'SPOTIFY_USER_LOADED': + if (store.getState().ui.allow_reporting && action.data) { ReactGA.event({ category: 'User', action: 'Load', label: action.data.uri }); - } - next(action) - break; + } + next(action); + break; - case 'SPOTIFY_CREATE_PLAYLIST': - store.dispatch(spotifyActions.createPlaylist(action.name, action.description, action.is_private, action.is_collaborative )) - break; + case 'SPOTIFY_CREATE_PLAYLIST': + store.dispatch(spotifyActions.createPlaylist(action.name, action.description, action.is_private, action.is_collaborative)); + break; - case 'SPOTIFY_REMOVE_PLAYLIST_TRACKS': - var playlist = Object.assign({}, store.getState().core.playlists[action.key]); - store.dispatch(spotifyActions.deleteTracksFromPlaylist(playlist.uri, playlist.snapshot_id, action.tracks_indexes )) - break; + case 'SPOTIFY_REMOVE_PLAYLIST_TRACKS': + var playlist = { ...store.getState().core.playlists[action.key] }; + store.dispatch(spotifyActions.deleteTracksFromPlaylist(playlist.uri, playlist.snapshot_id, action.tracks_indexes)); + break; - case 'SPOTIFY_ADD_PLAYLIST_TRACKS': - store.dispatch(spotifyActions.addTracksToPlaylist(action.key, action.tracks_uris )) - break; + case 'SPOTIFY_ADD_PLAYLIST_TRACKS': + store.dispatch(spotifyActions.addTracksToPlaylist(action.key, action.tracks_uris)); + break; - case 'SPOTIFY_REORDER_PLAYLIST_TRACKS': - store.dispatch(spotifyActions.reorderPlaylistTracks(action.key, action.range_start, action.range_length, action.insert_before, action.snapshot_id )) - break; + case 'SPOTIFY_REORDER_PLAYLIST_TRACKS': + store.dispatch(spotifyActions.reorderPlaylistTracks(action.key, action.range_start, action.range_length, action.insert_before, action.snapshot_id)); + break; - case 'SPOTIFY_SAVE_PLAYLIST': - store.dispatch(spotifyActions.savePlaylist(action.key, action.name, action.description, action.is_public, action.is_collaborative, action.image)) - break; + case 'SPOTIFY_SAVE_PLAYLIST': + store.dispatch(spotifyActions.savePlaylist(action.key, action.name, action.description, action.is_public, action.is_collaborative, action.image)); + break; - case 'SPOTIFY_NEW_RELEASES_LOADED': - store.dispatch({ - type: 'ALBUMS_LOADED', - albums: action.data.albums.items - }); + case 'SPOTIFY_NEW_RELEASES_LOADED': + store.dispatch({ + type: 'ALBUMS_LOADED', + albums: action.data.albums.items, + }); - // Collate result into the three key values we want - action.uris = helpers.arrayOf('uri', action.data.albums.items); - action.more = action.data.albums.next; - action.total = action.data.albums.total; + // Collate result into the three key values we want + action.uris = helpers.arrayOf('uri', action.data.albums.items); + action.more = action.data.albums.next; + action.total = action.data.albums.total; - // And pass on to our reducer - next(action); - break + // And pass on to our reducer + next(action); + break; - case 'SPOTIFY_ARTIST_ALBUMS_LOADED': - store.dispatch(coreActions.albumsLoaded(action.data.items)); - store.dispatch({ - type: 'ARTIST_ALBUMS_LOADED', - artist_uri: action.artist_uri, - albums_uris: helpers.arrayOf('uri',action.data.items), - more: action.data.next, - total: action.data.total - }); - break + case 'SPOTIFY_ARTIST_ALBUMS_LOADED': + store.dispatch(coreActions.albumsLoaded(action.data.items)); + store.dispatch({ + type: 'ARTIST_ALBUMS_LOADED', + artist_uri: action.artist_uri, + albums_uris: helpers.arrayOf('uri', action.data.items), + more: action.data.next, + total: action.data.total, + }); + break; - case 'SPOTIFY_USER_PLAYLISTS_LOADED': - var playlists = [] - for(var i = 0; i < action.data.items.length; i++){ - var playlist = Object.assign( - {}, - action.data.items[i], - { - tracks_total: action.data.items[i].tracks.total - } - ) + case 'SPOTIFY_USER_PLAYLISTS_LOADED': + var playlists = []; + for (var i = 0; i < action.data.items.length; i++) { + var playlist = { - // remove our tracklist. It'll overwrite any full records otherwise - delete playlist.tracks + ...action.data.items[i], + tracks_total: action.data.items[i].tracks.total, + }; - playlists.push(playlist) - } + // remove our tracklist. It'll overwrite any full records otherwise + delete playlist.tracks; - store.dispatch({ - type: 'PLAYLISTS_LOADED', - playlists: playlists - }); + playlists.push(playlist); + } - store.dispatch({ - type: 'USER_PLAYLISTS_LOADED', - key: action.key, - uris: helpers.arrayOf('uri',playlists), - more: action.data.next, - total: action.data.total - }); - break + store.dispatch({ + type: 'PLAYLISTS_LOADED', + playlists, + }); -/* + store.dispatch({ + type: 'USER_PLAYLISTS_LOADED', + key: action.key, + uris: helpers.arrayOf('uri', playlists), + more: action.data.next, + total: action.data.total, + }); + break; + + /* case 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED': var playlists = [] @@ -179,305 +176,299 @@ const SpotifyMiddleware = (function(){ break */ - case 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED': - store.dispatch(coreActions.playlistsLoaded(action.playlists.items)); - - action.uris = helpers.arrayOf('uri', action.playlists.items); - action.more = action.playlists.next; - action.total = action.playlists.total; - delete action.playlists; + case 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED': + store.dispatch(coreActions.playlistsLoaded(action.playlists.items)); - // Upgrade our URIs - action.uris = helpers.upgradeSpotifyPlaylistUris(action.uris); + action.uris = helpers.arrayOf('uri', action.playlists.items); + action.more = action.playlists.next; + action.total = action.playlists.total; + delete action.playlists; - next(action); - break; + // Upgrade our URIs + action.uris = helpers.upgradeSpotifyPlaylistUris(action.uris); - case 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED_MORE': - store.dispatch({ - type: 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED', - uri: action.uri, - playlists: action.data.playlists - }); - break; + next(action); + break; - case 'SPOTIFY_CATEGORY_LOADED': - store.dispatch({ - type: 'SPOTIFY_CATEGORIES_LOADED', - categories: [action.category] - }); - break; + case 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED_MORE': + store.dispatch({ + type: 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED', + uri: action.uri, + playlists: action.data.playlists, + }); + break; - case 'SPOTIFY_CATEGORIES_LOADED': - var categories_index = Object.assign({}, spotify.categories); - var categories_loaded = []; + case 'SPOTIFY_CATEGORY_LOADED': + store.dispatch({ + type: 'SPOTIFY_CATEGORIES_LOADED', + categories: [action.category], + }); + break; - for (var raw_category of action.categories){ - var category = Object.assign({}, raw_category); + case 'SPOTIFY_CATEGORIES_LOADED': + var categories_index = { ...spotify.categories }; + var categories_loaded = []; - if (!category.uri){ - category.uri = "category:"+category.id; - } + for (const raw_category of action.categories) { + let category = { ...raw_category }; - if (categories_index[category.uri] !== undefined){ - category = Object.assign({}, categories_index[category.uri], category); - } + if (!category.uri) { + category.uri = `category:${category.id}`; + } - if (category.icons){ - category.icons = helpers.formatImages(category.icons); - } + if (categories_index[category.uri] !== undefined) { + category = { ...categories_index[category.uri], ...category }; + } - categories_loaded.push(category); - } + if (category.icons) { + category.icons = helpers.formatImages(category.icons); + } - action.categories = categories_loaded; - next(action); - break; + categories_loaded.push(category); + } - case 'SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR': - store.dispatch(spotifyActions.getLibraryPlaylistsProcessor(action.data)) - break + action.categories = categories_loaded; + next(action); + break; - case 'SPOTIFY_LIBRARY_PLAYLISTS_LOADED': - var playlists = [] - for (var playlist of action.playlists){ - Object.assign( - playlist, - { - uri: playlist.uri.replace(/spotify:user:([^:]*?):/i, "spotify:"), - can_edit: (store.getState().spotify.me && store.getState().spotify.me.id == playlist.owner.id), - source: 'spotify', - in_library: true, // assumed because we asked for library items - tracks_total: playlist.tracks.total - } - ); + case 'SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR': + store.dispatch(spotifyActions.getLibraryPlaylistsProcessor(action.data)); + break; - // remove our tracklist. It'll overwrite any full records otherwise - delete playlist.tracks + case 'SPOTIFY_LIBRARY_PLAYLISTS_LOADED': + var playlists = []; + for (var playlist of action.playlists) { + Object.assign( + playlist, + { + uri: playlist.uri.replace(/spotify:user:([^:]*?):/i, 'spotify:'), + can_edit: (store.getState().spotify.me && store.getState().spotify.me.id == playlist.owner.id), + source: 'spotify', + in_library: true, // assumed because we asked for library items + tracks_total: playlist.tracks.total, + }, + ); - playlists.push(playlist) - } + // remove our tracklist. It'll overwrite any full records otherwise + delete playlist.tracks; - store.dispatch({ - type: 'PLAYLISTS_LOADED', - playlists: playlists - }); + playlists.push(playlist); + } - // Append our action with the uris. This gets handed down to subsequent middleware and our reducer. - action.uris = helpers.arrayOf('uri',playlists) - next(action) - break + store.dispatch({ + type: 'PLAYLISTS_LOADED', + playlists, + }); - case 'SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR': - store.dispatch(spotifyActions.getLibraryArtistsProcessor(action.data)) - break + // Append our action with the uris. This gets handed down to subsequent middleware and our reducer. + action.uris = helpers.arrayOf('uri', playlists); + next(action); + break; - case 'SPOTIFY_LIBRARY_ARTISTS_LOADED': - var artists = [] - for (var i = 0; i < action.artists.length; i++){ - artists.push( - Object.assign( - {}, - action.artists[i], - { - source: 'spotify', - in_library: true // assumed because we asked for library items - } - ) - ) - } - store.dispatch({ - type: 'ARTISTS_LOADED', - artists: artists - }); + case 'SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR': + store.dispatch(spotifyActions.getLibraryArtistsProcessor(action.data)); + break; - // Append our action with the uris. This gets handed down to subsequent middleware and our reducer. - action.uris = helpers.arrayOf('uri',artists) - next(action) - break + case 'SPOTIFY_LIBRARY_ARTISTS_LOADED': + var artists = []; + for (var i = 0; i < action.artists.length; i++) { + artists.push( + { - case 'SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR': - store.dispatch(spotifyActions.getLibraryAlbumsProcessor(action.data)) - break + ...action.artists[i], + source: 'spotify', + in_library: true, // assumed because we asked for library items - case 'SPOTIFY_GET_LIBRARY_TRACKS_AND_PLAY_PROCESSOR': - store.dispatch(spotifyActions.getLibraryTracksAndPlayProcessor(action.data)) - break + }, + ); + } + store.dispatch({ + type: 'ARTISTS_LOADED', + artists, + }); - case 'SPOTIFY_GET_ALL_PLAYLIST_TRACKS_PROCESSOR': - store.dispatch(spotifyActions.getAllPlaylistTracksProcessor(action.data)) - break + // Append our action with the uris. This gets handed down to subsequent middleware and our reducer. + action.uris = helpers.arrayOf('uri', artists); + next(action); + break; - case 'SPOTIFY_LIBRARY_ALBUMS_LOADED': - var albums = [] - for (var i = 0; i < action.albums.length; i++){ - albums.push( - Object.assign( - {}, - action.albums[i].album, - { - in_library: true, // assumed because we asked for library items - source: 'spotify', - added_at: action.albums[i].added_at, - tracks: action.albums[i].album.tracks.items, - tracks_more: action.albums[i].album.tracks.next, - tracks_total: action.albums[i].album.tracks.total - } - ) - ) - } + case 'SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR': + store.dispatch(spotifyActions.getLibraryAlbumsProcessor(action.data)); + break; - store.dispatch({ - type: 'ALBUMS_LOADED', - albums: albums - }); + case 'SPOTIFY_GET_LIBRARY_TRACKS_AND_PLAY_PROCESSOR': + store.dispatch(spotifyActions.getLibraryTracksAndPlayProcessor(action.data)); + break; - // Append our action with the uris. This gets handed down to subsequent middleware and our reducer. - action.uris = helpers.arrayOf('uri',albums) - next(action) - break + case 'SPOTIFY_GET_ALL_PLAYLIST_TRACKS_PROCESSOR': + store.dispatch(spotifyActions.getAllPlaylistTracksProcessor(action.data)); + break; - case 'SPOTIFY_FAVORITES_LOADED': - if (action.artists.length > 0){ - store.dispatch({ - type: 'ARTISTS_LOADED', - artists: action.artists - }) - action.artists_uris = helpers.arrayOf('uri',action.artists) - } - if (action.tracks.length > 0){ - store.dispatch({ - type: 'TRACKS_LOADED', - tracks: action.tracks - }) - action.tracks_uris = helpers.arrayOf('uri',action.tracks) - } - next(action) - break + case 'SPOTIFY_LIBRARY_ALBUMS_LOADED': + var albums = []; + for (var i = 0; i < action.albums.length; i++) { + albums.push( + { - case 'SPOTIFY_LIBRARY_TRACKS_LOADED': - case 'SPOTIFY_LIBRARY_TRACKS_LOADED_MORE': - if (action.data){ - store.dispatch({ - type: 'TRACKS_LOADED', - tracks: action.data.items - }); - } - next(action); - break; + ...action.albums[i].album, + in_library: true, // assumed because we asked for library items + source: 'spotify', + added_at: action.albums[i].added_at, + tracks: action.albums[i].album.tracks.items, + tracks_more: action.albums[i].album.tracks.next, + tracks_total: action.albums[i].album.tracks.total, + }, + ); + } - case 'SPOTIFY_TRACK_LOADED': - store.dispatch({ - type: 'TRACKS_LOADED', - tracks: [action.data] - }); - next(action); - break + store.dispatch({ + type: 'ALBUMS_LOADED', + albums, + }); + + // Append our action with the uris. This gets handed down to subsequent middleware and our reducer. + action.uris = helpers.arrayOf('uri', albums); + next(action); + break; + + case 'SPOTIFY_FAVORITES_LOADED': + if (action.artists.length > 0) { + store.dispatch({ + type: 'ARTISTS_LOADED', + artists: action.artists, + }); + action.artists_uris = helpers.arrayOf('uri', action.artists); + } + if (action.tracks.length > 0) { + store.dispatch({ + type: 'TRACKS_LOADED', + tracks: action.tracks, + }); + action.tracks_uris = helpers.arrayOf('uri', action.tracks); + } + next(action); + break; + + case 'SPOTIFY_LIBRARY_TRACKS_LOADED': + case 'SPOTIFY_LIBRARY_TRACKS_LOADED_MORE': + if (action.data) { + store.dispatch({ + type: 'TRACKS_LOADED', + tracks: action.data.items, + }); + } + next(action); + break; + + case 'SPOTIFY_TRACK_LOADED': + store.dispatch({ + type: 'TRACKS_LOADED', + tracks: [action.data], + }); + next(action); + break; - /** + /** * Searching * More results are lazy-loaded on demand, based on the _more URL - **/ + * */ - case 'SEARCH_STARTED': - store.dispatch({ - type: 'SPOTIFY_CLEAR_SEARCH_RESULTS' - }); - next(action); - break + case 'SEARCH_STARTED': + store.dispatch({ + type: 'SPOTIFY_CLEAR_SEARCH_RESULTS', + }); + next(action); + break; - case 'SPOTIFY_SEARCH_RESULTS_LOADED_MORE_TRACKS': - store.dispatch({ - type: 'SPOTIFY_SEARCH_RESULTS_LOADED', - context: 'tracks', - results: helpers.formatTracks(action.data.tracks.items), - more: action.data.tracks.next - }); - break + case 'SPOTIFY_SEARCH_RESULTS_LOADED_MORE_TRACKS': + store.dispatch({ + type: 'SPOTIFY_SEARCH_RESULTS_LOADED', + context: 'tracks', + results: helpers.formatTracks(action.data.tracks.items), + more: action.data.tracks.next, + }); + break; - case 'SPOTIFY_SEARCH_RESULTS_LOADED_MORE_ARTISTS': - - store.dispatch({ - type: 'ARTISTS_LOADED', - artists: action.data.artists.items - }); + case 'SPOTIFY_SEARCH_RESULTS_LOADED_MORE_ARTISTS': - store.dispatch({ - type: 'SPOTIFY_SEARCH_RESULTS_LOADED', - context: 'artists', - results: helpers.arrayOf('uri',action.data.playlists.items), - more: action.data.playlists.next - }); - break + store.dispatch({ + type: 'ARTISTS_LOADED', + artists: action.data.artists.items, + }); - case 'SPOTIFY_SEARCH_RESULTS_LOADED_MORE_ALBUMS': + store.dispatch({ + type: 'SPOTIFY_SEARCH_RESULTS_LOADED', + context: 'artists', + results: helpers.arrayOf('uri', action.data.playlists.items), + more: action.data.playlists.next, + }); + break; - store.dispatch({ - type: 'ALBUMS_LOADED', - albums: action.data.albums.items - }); + case 'SPOTIFY_SEARCH_RESULTS_LOADED_MORE_ALBUMS': - store.dispatch({ - type: 'SPOTIFY_SEARCH_RESULTS_LOADED', - context: 'playlists', - results: helpers.arrayOf('uri',action.data.albums.items), - more: action.data.albums.next - }); - break + store.dispatch({ + type: 'ALBUMS_LOADED', + albums: action.data.albums.items, + }); - case 'SPOTIFY_SEARCH_RESULTS_LOADED_MORE_PLAYLISTS': + store.dispatch({ + type: 'SPOTIFY_SEARCH_RESULTS_LOADED', + context: 'playlists', + results: helpers.arrayOf('uri', action.data.albums.items), + more: action.data.albums.next, + }); + break; - var playlists = [] - for (var i = 0; i < action.data.playlists.items.length; i++){ - playlists.push(Object.assign( - {}, - action.data.playlists.items[i], - { - tracks_total: action.data.playlists.items[i].tracks.total - } - )) - } + case 'SPOTIFY_SEARCH_RESULTS_LOADED_MORE_PLAYLISTS': - store.dispatch({ - type: 'PLAYLISTS_LOADED', - playlists: playlists - }); + var playlists = []; + for (var i = 0; i < action.data.playlists.items.length; i++) { + playlists.push({ - store.dispatch({ - type: 'SPOTIFY_SEARCH_RESULTS_LOADED', - context: 'playlists', - results: helpers.arrayOf('uri',action.data.playlists.items), - more: action.data.playlists.next - }); - break + ...action.data.playlists.items[i], + tracks_total: action.data.playlists.items[i].tracks.total, + }); + } + + store.dispatch({ + type: 'PLAYLISTS_LOADED', + playlists, + }); + + store.dispatch({ + type: 'SPOTIFY_SEARCH_RESULTS_LOADED', + context: 'playlists', + results: helpers.arrayOf('uri', action.data.playlists.items), + more: action.data.playlists.next, + }); + break; - case 'SPOTIFY_ME_LOADED': - var me = Object.assign({}, helpers.formatUser(action.me)); + case 'SPOTIFY_ME_LOADED': + var me = { ...helpers.formatUser(action.me) }; - // We are Anonymous currently so use 'me' name as my Pusher username - if (store.getState().pusher.username == 'Anonymous'){ - store.dispatch(pusherActions.setUsername(me.name)); - } + // We are Anonymous currently so use 'me' name as my Pusher username + if (store.getState().pusher.username == 'Anonymous') { + store.dispatch(pusherActions.setUsername(me.name)); + } - if (store.getState().ui.allow_reporting){ - var hashed_username = sha256(me.id); - ReactGA.set({userId: hashed_username}); - ReactGA.event({category: 'Spotify', action: 'Authorization verified', label: hashed_username}); + if (store.getState().ui.allow_reporting) { + const hashed_username = sha256(me.id); + ReactGA.set({ userId: hashed_username }); + ReactGA.event({ category: 'Spotify', action: 'Authorization verified', label: hashed_username }); } - store.dispatch(coreActions.userLoaded(me)); - action.me = me; - next(action); - break; + store.dispatch(coreActions.userLoaded(me)); + action.me = me; + next(action); + break; - // This action is irrelevant to us, pass it on to the next middleware - default: - return next(action); - } + // This action is irrelevant to us, pass it on to the next middleware + default: + return next(action); } + }; +}()); -})(); - -export default SpotifyMiddleware \ No newline at end of file +export default SpotifyMiddleware; diff --git a/src/js/services/spotify/reducer.js b/src/js/services/spotify/reducer.js index 5bb80693..15900886 100755 --- a/src/js/services/spotify/reducer.js +++ b/src/js/services/spotify/reducer.js @@ -1,358 +1,347 @@ -import * as helpers from '../../helpers' +import * as helpers from '../../helpers'; -export default function reducer(spotify = {}, action){ - switch (action.type){ +export default function reducer(spotify = {}, action) { + switch (action.type) { + case 'SPOTIFY_SET': + return { ...spotify, ...action.data }; - case 'SPOTIFY_SET': - return Object.assign({},spotify,action.data) + case 'PUSHER_SPOTIFY_TOKEN': + if (spotify.authorization) return spotify; + return { + ...spotify, + authorizing: false, + authorization: false, + access_token: action.data.access_token, + token_expiry: action.data.token_expiry, + }; - case 'PUSHER_SPOTIFY_TOKEN': - if (spotify.authorization) return spotify; - return Object.assign({}, spotify, { - authorizing: false, - authorization: false, - access_token: action.data.access_token, - token_expiry: action.data.token_expiry - }) + case 'SPOTIFY_AUTHORIZATION_GRANTED': + return { + ...spotify, + enabled: true, + authorizing: false, + authorization: action.data, + access_token: action.data.access_token, + refresh_token: action.data.refresh_token, + token_expiry: action.data.token_expiry, + }; - case 'SPOTIFY_AUTHORIZATION_GRANTED': - return Object.assign({}, spotify, { - enabled: true, - authorizing: false, - authorization: action.data, - access_token: action.data.access_token, - refresh_token: action.data.refresh_token, - token_expiry: action.data.token_expiry - }) + case 'SPOTIFY_AUTHORIZATION_REVOKED': + return { + ...spotify, + authorizing: false, + authorization: false, + access_token: false, + refresh_token: false, + token_expiry: 0, + me: false, + }; - case 'SPOTIFY_AUTHORIZATION_REVOKED': - return Object.assign({}, spotify, { - authorizing: false, - authorization: false, - access_token: false, - refresh_token: false, - token_expiry: 0, - me: false - }) + case 'SPOTIFY_IMPORT_AUTHORIZATION': + return { + ...spotify, + authorizing: false, + authorization: action.authorization, + access_token: action.authorization.access_token, + refresh_token: action.authorization.refresh_token, + token_expiry: action.authorization.token_expiry, + me: action.me, + }; - case 'SPOTIFY_IMPORT_AUTHORIZATION': - return Object.assign({}, spotify, { - authorizing: false, - authorization: action.authorization, - access_token: action.authorization.access_token, - refresh_token: action.authorization.refresh_token, - token_expiry: action.authorization.token_expiry, - me: action.me - }); + case 'SPOTIFY_TOKEN_REFRESHING': + return { ...spotify, refreshing_token: true }; - case 'SPOTIFY_TOKEN_REFRESHING': - return Object.assign({}, spotify, { refreshing_token: true }) + case 'SPOTIFY_TOKEN_REFRESHED': + return { + ...spotify, + refreshing_token: false, + access_token: action.data.access_token, + token_expiry: action.data.token_expiry, + }; - case 'SPOTIFY_TOKEN_REFRESHED': - return Object.assign({}, spotify, { - refreshing_token: false, - access_token: action.data.access_token, - token_expiry: action.data.token_expiry - }); + case 'SPOTIFY_TOKEN_CHANGED': + return { + ...spotify, + access_token: action.spotify_token.access_token, + token_expiry: action.spotify_token.token_expiry, + }; - case 'SPOTIFY_TOKEN_CHANGED': - return Object.assign({}, spotify, { - access_token: action.spotify_token.access_token, - token_expiry: action.spotify_token.token_expiry - }); + case 'SPOTIFY_ME_LOADED': + return { ...spotify, me: action.me }; - case 'SPOTIFY_ME_LOADED': - return Object.assign({}, spotify, { me: action.me }) - - case 'SPOTIFY_FEATURED_PLAYLISTS_LOADED': - return Object.assign({}, spotify, { featured_playlists: action.data }) + case 'SPOTIFY_FEATURED_PLAYLISTS_LOADED': + return { ...spotify, featured_playlists: action.data }; - case 'SPOTIFY_NEW_RELEASES_LOADED': - var new_releases = []; - if (spotify.new_releases){ + case 'SPOTIFY_NEW_RELEASES_LOADED': + var new_releases = []; + if (spotify.new_releases) { new_releases = Object.assign([], spotify.new_releases); - } - return Object.assign({}, spotify, { - new_releases: helpers.removeDuplicates([...new_releases, ...action.uris]), - new_releases_more: action.more, - new_releases_total: action.total - }); + } + return { + ...spotify, + new_releases: helpers.removeDuplicates([...new_releases, ...action.uris]), + new_releases_more: action.more, + new_releases_total: action.total, + }; - case 'SPOTIFY_DISCOVER_LOADED': - if (!action.data ){ - return Object.assign( - {}, - spotify, - { - discover: [] - } - ); - } - return Object.assign( - {}, - spotify, - { - discover: [...spotify.discover, ...[action.data]] - } - ); + case 'SPOTIFY_DISCOVER_LOADED': + if (!action.data) { + return { - case 'CLEAR_SPOTIFY_RECOMMENDATIONS': - return Object.assign({}, spotify, {recommendations: {artists_uris: [], albums_uris: [], tracks_uris: []}}); + ...spotify, + discover: [], + }; + } + return { - case 'SPOTIFY_RECOMMENDATIONS_LOADED': - return Object.assign( - {}, - spotify, - { - recommendations: { - artists_uris: action.artists_uris, - albums_uris: action.albums_uris, - tracks_uris: action.tracks_uris - } - }); + ...spotify, + discover: [...spotify.discover, ...[action.data]], + }; - case 'SPOTIFY_FAVORITES_LOADED': - return Object.assign( - {}, - spotify, - { - favorite_artists: action.artists_uris, - favorite_tracks: action.tracks_uris - }) + case 'CLEAR_SPOTIFY_RECOMMENDATIONS': + return { ...spotify, recommendations: { artists_uris: [], albums_uris: [], tracks_uris: [] } }; - case 'SPOTIFY_AUTOCOMPLETE_LOADING': - var autocomplete_results = spotify.autocomplete_results - autocomplete_results[action.field_id] = {loading: true} - return Object.assign( - {}, - spotify, - { - autocomplete_results: autocomplete_results - }) + case 'SPOTIFY_RECOMMENDATIONS_LOADED': + return { - case 'SPOTIFY_AUTOCOMPLETE_LOADED': - var autocomplete_results = spotify.autocomplete_results - autocomplete_results[action.field_id] = action.results - autocomplete_results[action.field_id].loading = false - return Object.assign( - {}, - spotify, - { - autocomplete_results: autocomplete_results - }) + ...spotify, + recommendations: { + artists_uris: action.artists_uris, + albums_uris: action.albums_uris, + tracks_uris: action.tracks_uris, + }, + }; - case 'SPOTIFY_AUTOCOMPLETE_CLEAR': - var autocomplete_results = spotify.autocomplete_results - if (typeof(autocomplete_results[action.field_id]) !== 'undefined'){ - delete autocomplete_results[action.field_id] - } - return Object.assign( - {}, - spotify, - { - autocomplete_results: autocomplete_results - }) + case 'SPOTIFY_FAVORITES_LOADED': + return { - case 'SPOTIFY_GENRES_LOADED': - return Object.assign( - {}, - spotify, - { - genres: action.genres - }) + ...spotify, + favorite_artists: action.artists_uris, + favorite_tracks: action.tracks_uris, + }; + + case 'SPOTIFY_AUTOCOMPLETE_LOADING': + var { autocomplete_results } = spotify; + autocomplete_results[action.field_id] = { loading: true }; + return { + + ...spotify, + autocomplete_results, + }; + + case 'SPOTIFY_AUTOCOMPLETE_LOADED': + var { autocomplete_results } = spotify; + autocomplete_results[action.field_id] = action.results; + autocomplete_results[action.field_id].loading = false; + return { + + ...spotify, + autocomplete_results, + }; + + case 'SPOTIFY_AUTOCOMPLETE_CLEAR': + var { autocomplete_results } = spotify; + if (typeof (autocomplete_results[action.field_id]) !== 'undefined') { + delete autocomplete_results[action.field_id]; + } + return { + + ...spotify, + autocomplete_results, + }; + + case 'SPOTIFY_GENRES_LOADED': + return { + + ...spotify, + genres: action.genres, + }; - /** + /** * Categories - **/ + * */ - case 'SPOTIFY_CATEGORIES_LOADED': - var categories = Object.assign({}, spotify.categories); - for (var category of action.categories){ - categories[category.uri] = category - } - return Object.assign({}, spotify, { categories: categories }); + case 'SPOTIFY_CATEGORIES_LOADED': + var categories = { ...spotify.categories }; + for (var category of action.categories) { + categories[category.uri] = category; + } + return { ...spotify, categories }; - case 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED': - var categories = Object.assign({}, spotify.categories) - var playlists_uris = []; + case 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED': + var categories = { ...spotify.categories }; + var playlists_uris = []; - if (categories[action.uri].playlists_uris){ - playlists_uris = categories[action.uri].playlists_uris; - } + if (categories[action.uri].playlists_uris) { + playlists_uris = categories[action.uri].playlists_uris; + } - var category = Object.assign( - {}, - categories[action.uri], - { - playlists_uris: [...playlists_uris, ...action.uris], - playlists_more: action.more, - playlists_total: action.total - } - ) - categories[action.uri] = category - return Object.assign({}, spotify, { categories: categories }); + var category = { + + ...categories[action.uri], + playlists_uris: [...playlists_uris, ...action.uris], + playlists_more: action.more, + playlists_total: action.total, + }; + categories[action.uri] = category; + return { ...spotify, categories }; - - /** + /** * Library - **/ + * */ - case 'SPOTIFY_FLUSH_LIBRARY': - return Object.assign( - {}, - spotify, - { - library_playlists: null, - library_playlists_loaded_all: null, - library_playlists_status: null, - library_albums: null, - library_albums_status: null, - library_albums_loaded_all: null, - library_artists: null, - library_artists_status: null, - library_artists_loaded_all: null, - library_tracks: null, - library_tracks_status: null, - library_tracks_loaded_all: null - } - ) + case 'SPOTIFY_FLUSH_LIBRARY': + return { - case 'SPOTIFY_LIBRARY_PLAYLISTS_LOADED': - if (spotify.library_playlists){ - var uris = [...spotify.library_playlists,...action.uris] - } else { - var uris = action.uris - } - return Object.assign({}, spotify, { library_playlists: helpers.removeDuplicates(uris) }) + ...spotify, + library_playlists: null, + library_playlists_loaded_all: null, + library_playlists_status: null, + library_albums: null, + library_albums_status: null, + library_albums_loaded_all: null, + library_artists: null, + library_artists_status: null, + library_artists_loaded_all: null, + library_tracks: null, + library_tracks_status: null, + library_tracks_loaded_all: null, + }; - case 'SPOTIFY_LIBRARY_ARTISTS_LOADED': - if (spotify.library_artists){ - var uris = [...spotify.library_artists,...action.uris] - } else { - var uris = action.uris - } - return Object.assign({}, spotify, { library_artists: helpers.removeDuplicates(uris) }) + case 'SPOTIFY_LIBRARY_PLAYLISTS_LOADED': + if (spotify.library_playlists) { + var uris = [...spotify.library_playlists, ...action.uris]; + } else { + var { uris } = action; + } + return { ...spotify, library_playlists: helpers.removeDuplicates(uris) }; - case 'SPOTIFY_LIBRARY_ALBUMS_LOADED': - if (spotify.library_albums){ - var uris = [...spotify.library_albums,...action.uris] - } else { - var uris = action.uris - } - return Object.assign({}, spotify, { library_albums: helpers.removeDuplicates(uris) }) + case 'SPOTIFY_LIBRARY_ARTISTS_LOADED': + if (spotify.library_artists) { + var uris = [...spotify.library_artists, ...action.uris]; + } else { + var { uris } = action; + } + return { ...spotify, library_artists: helpers.removeDuplicates(uris) }; - case 'SPOTIFY_LIBRARY_TRACKS_LOADED': - case 'SPOTIFY_LIBRARY_TRACKS_LOADED_MORE': - var tracks = action.data.items; - var uris = []; + case 'SPOTIFY_LIBRARY_ALBUMS_LOADED': + if (spotify.library_albums) { + var uris = [...spotify.library_albums, ...action.uris]; + } else { + var { uris } = action; + } + return { ...spotify, library_albums: helpers.removeDuplicates(uris) }; - if (tracks){ - tracks = helpers.formatTracks(tracks); - uris = helpers.arrayOf('uri', tracks); - if (spotify.library_tracks){ - uris = [...spotify.library_tracks, ...uris] - } - } + case 'SPOTIFY_LIBRARY_TRACKS_LOADED': + case 'SPOTIFY_LIBRARY_TRACKS_LOADED_MORE': + var tracks = action.data.items; + var uris = []; - return Object.assign({}, spotify, { - library_tracks: helpers.removeDuplicates(uris), - library_tracks_more: action.data.next - }) + if (tracks) { + tracks = helpers.formatTracks(tracks); + uris = helpers.arrayOf('uri', tracks); + if (spotify.library_tracks) { + uris = [...spotify.library_tracks, ...uris]; + } + } + + return { + ...spotify, + library_tracks: helpers.removeDuplicates(uris), + library_tracks_more: action.data.next, + }; - case 'SPOTIFY_LIBRARY_PLAYLISTS_LOADED_ALL': - return Object.assign({}, spotify, { library_playlists_loaded_all: true }) + case 'SPOTIFY_LIBRARY_PLAYLISTS_LOADED_ALL': + return { ...spotify, library_playlists_loaded_all: true }; - case 'SPOTIFY_LIBRARY_PLAYLISTS_CLEAR': - return Object.assign({}, spotify, { library_playlists: [] }) + case 'SPOTIFY_LIBRARY_PLAYLISTS_CLEAR': + return { ...spotify, library_playlists: [] }; - case 'SPOTIFY_LIBRARY_ARTISTS_CLEAR': - return Object.assign({}, spotify, { library_artists: [] }) + case 'SPOTIFY_LIBRARY_ARTISTS_CLEAR': + return { ...spotify, library_artists: [] }; - case 'SPOTIFY_LIBRARY_ALBUMS_CLEAR': - return Object.assign({}, spotify, { library_albums: [] }) + case 'SPOTIFY_LIBRARY_ALBUMS_CLEAR': + return { ...spotify, library_albums: [] }; - case 'SPOTIFY_LIBRARY_ALBUM_CHECK': - var items = Object.assign([], spotify.library_albums) - var index = items.indexOf(action.key) - if (index > -1 && !action.in_library){ - items.splice(index, 1) - } else if (index < 0 && action.in_library){ - items.push(action.key) - } - return Object.assign({}, spotify, { library_albums: items }); + case 'SPOTIFY_LIBRARY_ALBUM_CHECK': + var items = Object.assign([], spotify.library_albums); + var index = items.indexOf(action.key); + if (index > -1 && !action.in_library) { + items.splice(index, 1); + } else if (index < 0 && action.in_library) { + items.push(action.key); + } + return { ...spotify, library_albums: items }; - case 'SPOTIFY_LIBRARY_ARTIST_CHECK': - var items = Object.assign([], spotify.library_artists) - var index = items.indexOf(action.key) - if (index > -1 && !action.in_library){ - items.splice(index, 1) - } else if (index < 0 && action.in_library){ - items.push(action.key) - } - return Object.assign({}, spotify, { library_artists: items }); + case 'SPOTIFY_LIBRARY_ARTIST_CHECK': + var items = Object.assign([], spotify.library_artists); + var index = items.indexOf(action.key); + if (index > -1 && !action.in_library) { + items.splice(index, 1); + } else if (index < 0 && action.in_library) { + items.push(action.key); + } + return { ...spotify, library_artists: items }; - case 'SPOTIFY_LIBRARY_PLAYLIST_CHECK': - var items = Object.assign([], spotify.library_playlists) - var index = items.indexOf(action.key) - if (index > -1 && !action.in_library){ - items.splice(index, 1) - } else if (index < 0 && action.in_library){ - items.push(action.key) - } - return Object.assign({}, spotify, { library_playlists: items }); + case 'SPOTIFY_LIBRARY_PLAYLIST_CHECK': + var items = Object.assign([], spotify.library_playlists); + var index = items.indexOf(action.key); + if (index > -1 && !action.in_library) { + items.splice(index, 1); + } else if (index < 0 && action.in_library) { + items.push(action.key); + } + return { ...spotify, library_playlists: items }; - case 'SPOTIFY_LIBRARY_TRACK_CHECK': - var items = Object.assign([], spotify.library_tracks) - var index = items.indexOf(action.key) - if (index > -1 && !action.in_library) { - items.splice(index, 1) - } else if (index < 0 && action.in_library){ - items.push(action.key) - } - return Object.assign({}, spotify, { library_tracks: items }); + case 'SPOTIFY_LIBRARY_TRACK_CHECK': + var items = Object.assign([], spotify.library_tracks); + var index = items.indexOf(action.key); + if (index > -1 && !action.in_library) { + items.splice(index, 1); + } else if (index < 0 && action.in_library) { + items.push(action.key); + } + return { ...spotify, library_tracks: items }; - /** + /** * Searching - **/ + * */ - case 'SPOTIFY_CLEAR_SEARCH_RESULTS': - return Object.assign({}, spotify, { search_results: {} }); + case 'SPOTIFY_CLEAR_SEARCH_RESULTS': + return { ...spotify, search_results: {} }; - case 'SPOTIFY_SEARCH_RESULTS_LOADED': + case 'SPOTIFY_SEARCH_RESULTS_LOADED': - // Fetch or create our container - if (spotify.search_results){ - var search_results = Object.assign({}, spotify.search_results) - } else { - var search_results = {} - } + // Fetch or create our container + if (spotify.search_results) { + var search_results = { ...spotify.search_results }; + } else { + var search_results = {}; + } - search_results.query = action.query; + search_results.query = action.query; - if (search_results.results){ - search_results[action.context] = [...search_results[action.context], ...action.results] - } else { - search_results[action.context] = action.results - } + if (search_results.results) { + search_results[action.context] = [...search_results[action.context], ...action.results]; + } else { + search_results[action.context] = action.results; + } - if (action.more){ - search_results[action.context+'_more'] = action.more - } else { - search_results[action.context+'_more'] = null - } - return Object.assign({}, spotify, { search_results: search_results }); + if (action.more) { + search_results[`${action.context}_more`] = action.more; + } else { + search_results[`${action.context}_more`] = null; + } + return { ...spotify, search_results }; - default: - return spotify - } + default: + return spotify; + } } - - - diff --git a/src/js/services/ui/actions.js b/src/js/services/ui/actions.js index 8b81cb7f..0c2ca288 100755 --- a/src/js/services/ui/actions.js +++ b/src/js/services/ui/actions.js @@ -1,12 +1,12 @@ -import * as helpers from '../../helpers' +import * as helpers from '../../helpers'; -export function setWindowTitle(title = null, play_state = null){ - return { - type: 'SET_WINDOW_TITLE', - title: title, - play_state: play_state - } +export function setWindowTitle(title = null, play_state = null) { + return { + type: 'SET_WINDOW_TITLE', + title, + play_state, + }; } /** @@ -14,282 +14,276 @@ export function setWindowTitle(title = null, play_state = null){ * we're pending transition to a new/next track * * @param transitioning Boolean - **/ -export function setCurrentTrackTransition(current_track_transition){ - return { - type: 'SET_CURRENT_TRACK_TRANSITION', - current_track_transition: current_track_transition - } + * */ +export function setCurrentTrackTransition(current_track_transition) { + return { + type: 'SET_CURRENT_TRACK_TRANSITION', + current_track_transition, + }; } -export function setSelectedTracks(keys = []){ - if (typeof(keys) === 'string'){ - keys = [keys] - } - return { - type: 'SET_SELECTED_TRACKS', - keys: keys - } +export function setSelectedTracks(keys = []) { + if (typeof (keys) === 'string') { + keys = [keys]; + } + return { + type: 'SET_SELECTED_TRACKS', + keys, + }; } -export function showContextMenu(data){ - - // Touchend - if (data.e.changedTouches){ - data.position_x = data.e.changedTouches[0].clientX; - data.position_y = data.e.changedTouches[0].clientY; +export function showContextMenu(data) { + // Touchend + if (data.e.changedTouches) { + data.position_x = data.e.changedTouches[0].clientX; + data.position_y = data.e.changedTouches[0].clientY; // Touchstart - } else if (data.e.touches){ - data.position_x = data.e.touches[0].clientX; - data.position_y = data.e.touches[0].clientY; + } else if (data.e.touches) { + data.position_x = data.e.touches[0].clientX; + data.position_y = data.e.touches[0].clientY; // Click/mousedown/mouseup/etc - } else { - data.position_x = data.e.clientX; - data.position_y = data.e.clientY; - } + } else { + data.position_x = data.e.clientX; + data.position_y = data.e.clientY; + } - return { - type: 'SHOW_CONTEXT_MENU', - data: data - } + return { + type: 'SHOW_CONTEXT_MENU', + data, + }; } -export function setSlimMode(slim_mode){ - return { - type: 'SET_SLIM_MODE', - slim_mode: slim_mode - } +export function setSlimMode(slim_mode) { + return { + type: 'SET_SLIM_MODE', + slim_mode, + }; } -export function setWindowFocus(window_focus){ - return { - type: 'SET_WINDOW_FOCUS', - window_focus: window_focus - } +export function setWindowFocus(window_focus) { + return { + type: 'SET_WINDOW_FOCUS', + window_focus, + }; } -export function hideContextMenu(){ - return { - type: 'HIDE_CONTEXT_MENU' - } +export function hideContextMenu() { + return { + type: 'HIDE_CONTEXT_MENU', + }; } -export function removeContextMenu(){ - return { - type: 'REMOVE_CONTEXT_MENU' - } +export function removeContextMenu() { + return { + type: 'REMOVE_CONTEXT_MENU', + }; } -export function showTouchContextMenu(data){ - return { - type: 'SHOW_TOUCH_CONTEXT_MENU', - data: data - } +export function showTouchContextMenu(data) { + return { + type: 'SHOW_TOUCH_CONTEXT_MENU', + data, + }; } -export function hideTouchContextMenu(){ - return { - type: 'HIDE_TOUCH_CONTEXT_MENU' - } +export function hideTouchContextMenu() { + return { + type: 'HIDE_TOUCH_CONTEXT_MENU', + }; } -export function removeTouchContextMenu(){ - return { - type: 'REMOVE_TOUCH_CONTEXT_MENU' - } +export function removeTouchContextMenu() { + return { + type: 'REMOVE_TOUCH_CONTEXT_MENU', + }; } -export function lazyLoading(start){ - return { - type: 'LAZY_LOADING', - start: start - } +export function lazyLoading(start) { + return { + type: 'LAZY_LOADING', + start, + }; } -export function toggleSidebar(new_state = 'toggle'){ - var action = { - type: 'TOGGLE_SIDEBAR' - } - if (new_state != 'toggle'){ - action.new_state = new_state - } - return action +export function toggleSidebar(new_state = 'toggle') { + const action = { + type: 'TOGGLE_SIDEBAR', + }; + if (new_state != 'toggle') { + action.new_state = new_state; + } + return action; } -export function dragStart(e, context, from_uri = null, victims, victims_indexes = null){ - return { - type: 'DRAG_START', - context: context, - from_uri: from_uri, - victims: victims, - victims_indexes: victims_indexes, - start_x: e.clientX, - start_y: e.clientY - } +export function dragStart(e, context, from_uri = null, victims, victims_indexes = null) { + return { + type: 'DRAG_START', + context, + from_uri, + victims, + victims_indexes, + start_x: e.clientX, + start_y: e.clientY, + }; } -export function dragActive(){ - return { type: 'DRAG_ACTIVE' } +export function dragActive() { + return { type: 'DRAG_ACTIVE' }; } -export function dragEnd(){ - return { type: 'DRAG_END' } +export function dragEnd() { + return { type: 'DRAG_END' }; } -export function set(data){ - return { - type: 'UI_SET', - data: data - } +export function set(data) { + return { + type: 'UI_SET', + data, + }; } -export function installPrompt(event){ - return { - type: 'INSTALL_PROMPT', - event: event - } +export function installPrompt(event) { + return { + type: 'INSTALL_PROMPT', + event, + }; } - /** * Notifications * * Subtle info/tooltip messages - **/ + * */ -export function createBrowserNotification(data){ - return { - type: 'BROWSER_NOTIFICATION', - data: data - } +export function createBrowserNotification(data) { + return { + type: 'BROWSER_NOTIFICATION', + data, + }; } -export function createNotification(data){ +export function createNotification(data) { + // Allow 'message' as an alias of our content + if (data.message) { + data.content = data.message; + } - // Allow 'message' as an alias of our content - if (data.message){ - data.content = data.message; - } - - // Shortcut notifications are short and sweet - if (data.type == 'shortcut'){ - data.duration = 0.4; - } + // Shortcut notifications are short and sweet + if (data.type == 'shortcut') { + data.duration = 0.4; + } - return { - type: 'CREATE_NOTIFICATION', - notification: Object.assign( - { - key: helpers.generateGuid(), - duration: 5, - type: 'default', - title: null, - content: null, - description: null, - sticky: false, - closing: false, - render_content: false - }, - data - ) - } + return { + type: 'CREATE_NOTIFICATION', + notification: { + key: helpers.generateGuid(), + duration: 5, + type: 'default', + title: null, + content: null, + description: null, + sticky: false, + closing: false, + render_content: false, + ...data, + }, + }; } -export function closeNotification(key){ - return { - type: 'CLOSE_NOTIFICATION', - key: key - } +export function closeNotification(key) { + return { + type: 'CLOSE_NOTIFICATION', + key, + }; } -export function removeNotification(key, manual = false){ - return { - type: 'REMOVE_NOTIFICATION', - key: key, - manual: manual - } +export function removeNotification(key, manual = false) { + return { + type: 'REMOVE_NOTIFICATION', + key, + manual, + }; } - /** * Loaders - **/ + * */ -export function startLoading(key,source){ - return { - type: 'START_LOADING', - source: source, - key: key - } +export function startLoading(key, source) { + return { + type: 'START_LOADING', + source, + key, + }; } -export function stopLoading(key){ - return { - type: 'STOP_LOADING', - key: key - } +export function stopLoading(key) { + return { + type: 'STOP_LOADING', + key, + }; } -export function startProcess(key,message,data = {}){ - return { - type: 'START_PROCESS', - key: key, - message: message, - data: data - } +export function startProcess(key, message, data = {}) { + return { + type: 'START_PROCESS', + key, + message, + data, + }; } -export function resumeProcess(key,message,data = {}){ - return { - type: 'RESUME_PROCESS', - key: key - } +export function resumeProcess(key, message, data = {}) { + return { + type: 'RESUME_PROCESS', + key, + }; } -export function updateProcess(key,message,data = {}){ - return { - type: 'UPDATE_PROCESS', - key: key, - message: message, - data: data - } +export function updateProcess(key, message, data = {}) { + return { + type: 'UPDATE_PROCESS', + key, + message, + data, + }; } -export function runProcess(key,data = {}){ - return { - type: key, - data: data - } +export function runProcess(key, data = {}) { + return { + type: key, + data, + }; } -export function cancelProcess(key){ - return { - type: 'CANCEL_PROCESS', - key: key - } +export function cancelProcess(key) { + return { + type: 'CANCEL_PROCESS', + key, + }; } -export function processCancelled(key){ - return { - type: 'PROCESS_CANCELLED', - key: key - } +export function processCancelled(key) { + return { + type: 'PROCESS_CANCELLED', + key, + }; } -export function processFinishing(key){ - return { - type: 'PROCESS_FINISHING', - key: key - } +export function processFinishing(key) { + return { + type: 'PROCESS_FINISHING', + key, + }; } -export function processFinished(key){ - return { - type: 'PROCESS_FINISHED', - key: key - } +export function processFinished(key) { + return { + type: 'PROCESS_FINISHED', + key, + }; } diff --git a/src/js/services/ui/middleware.js b/src/js/services/ui/middleware.js index e9ad8cc7..17a03ee7 100755 --- a/src/js/services/ui/middleware.js +++ b/src/js/services/ui/middleware.js @@ -2,247 +2,244 @@ import ReactGA from 'react-ga'; import * as helpers from '../../helpers'; -var uiActions = require('./actions.js') -var mopidyActions = require('../mopidy/actions.js') +const uiActions = require('./actions.js'); +const mopidyActions = require('../mopidy/actions.js'); -const UIMiddleware = (function(){ - - /** +const UIMiddleware = (function () { + /** * The actual middleware inteceptor - **/ - return store => next => action => { - switch(action.type){ + * */ + return (store) => (next) => (action) => { + switch (action.type) { + case 'MOPIDY_STATE': - case 'MOPIDY_STATE': + // Get the current track from our index + var { tracks } = store.getState().core; + var current_track_uri = store.getState().core.current_track; + var current_track = null; + if (tracks[current_track_uri] !== undefined) { + current_track = tracks[current_track_uri]; + } + next(action); + break; - // Get the current track from our index - var tracks = store.getState().core.tracks; - var current_track_uri = store.getState().core.current_track; - var current_track = null; - if (tracks[current_track_uri] !== undefined){ - current_track = tracks[current_track_uri]; - } - next(action); - break + case 'SET_WINDOW_TITLE': - case 'SET_WINDOW_TITLE': + var window_title = ''; - var window_title = ""; + if (action.play_state) { + var { play_state } = action; + } else { + var { play_state } = store.getState().mopidy; + } - if (action.play_state){ - var play_state = action.play_state; - } else { - var play_state = store.getState().mopidy.play_state; - } + if (action.title) { + var { title } = action; + } else { + var title = store.getState().ui.window_title; + } - if (action.title){ - var title = action.title; - } else { - var title = store.getState().ui.window_title; - } - - document.title = window_title+' '+title; + document.title = `${window_title} ${title}`; - next(action); - break; + next(action); + break; - case 'OPEN_MODAL': - if (store.getState().ui.allow_reporting){ + case 'OPEN_MODAL': + if (store.getState().ui.allow_reporting) { ReactGA.event({ category: 'Modal', action: 'Opened', label: action.modal.name }); } - $('body').addClass('modal-open') - store.dispatch(uiActions.hideContextMenu()) - store.dispatch(uiActions.hideTouchContextMenu()) - next(action) - break + $('body').addClass('modal-open'); + store.dispatch(uiActions.hideContextMenu()); + store.dispatch(uiActions.hideTouchContextMenu()); + next(action); + break; - case 'CLOSE_MODAL': - if (store.getState().ui.allow_reporting){ - ReactGA.event({ category: 'Modal', action: 'Closed', label: null }); - } - $('body').removeClass('modal-open') - next(action) - break - - case 'HIDE_CONTEXT_MENU': - if (store.getState().ui.context_menu){ - setTimeout( - () => { - store.dispatch(uiActions.removeContextMenu()) - }, - 200 - ); - next(action); - } - break; - - case 'HIDE_TOUCH_CONTEXT_MENU': - if (store.getState().ui.context_menu){ - setTimeout( - () => { - store.dispatch(uiActions.removeTouchContextMenu()) - }, - 200 - ); - next(action); - } - break; - - case 'BROWSER_NOTIFICATION': - - var notification = window.Notification || window.mozNotification || window.webkitNotification; - if ('undefined' === typeof notification) return false; - if ('undefined' !== typeof notification) notification.requestPermission(function(permission){}); - - // handle nested data objects - var data = {} - if (typeof(action.data) ) data = action.data - if (typeof(data.data) ) data = Object.assign({}, data, data.data) - - // construct our browser notification - var title = ''; - var options = { - body: '', - dir: 'auto', - lang: 'EN', - tag: 'iris' - }; - if (data.title ) title = data.title; - if (data.body ) options.body = data.body; - if (data.icon ) options.icon = data.icon; - - // make it so - var notification = new notification(title, options ); - break - - case 'CREATE_NOTIFICATION': - - // start a timeout to close this notification - if (!action.notification.sticky){ - var timeout = setTimeout( - function(){ - store.dispatch(uiActions.closeNotification(action.notification.key)) - }, - action.notification.duration * 1000 - ) - } - - next(action); - break; - - case 'CLOSE_NOTIFICATION': - var notifications = Object.assign({}, store.getState().ui.notifications); - - // start a timeout to remove this notification - // This gives us time to animate out the notification before we remove the data - var timeout = setTimeout( - function(){ - store.dispatch(uiActions.removeNotification(action.key)) - }, - 200 - ) - - next(action); - break; - - case 'REMOVE_NOTIFICATION': - - // Manual removal - if (action.manual){ - var notifications = Object.assign({}, store.getState().ui.notifications); - - // If a broadcast, add to suppressed_broadcasts - if (notifications[action.key] && notifications[action.key].type == 'broadcast'){ - store.dispatch({ - type: 'SUPPRESS_BROADCAST', - key: action.key - }) - } - } - - next(action); - break; - - case 'BROADCASTS_LOADED': - var suppressed_broadcasts = [] - if (store.getState().ui.suppressed_broadcasts !== undefined){ - suppressed_broadcasts = store.getState().ui.suppressed_broadcasts - } - - for (var i = 0; i < action.broadcasts.length; i++){ - var broadcast = action.broadcasts[i] - - if (!suppressed_broadcasts.includes(broadcast.key)){ - if (broadcast.message){ - var data = { - key: (broadcast.key ? broadcast.key : null), - title: (broadcast.title ? broadcast.title : null), - content: (broadcast.message ? broadcast.message : null), - links: (broadcast.links ? broadcast.links : null), - type: 'broadcast', - sticky: true - } - store.dispatch(uiActions.createNotification(data)); - } - } - } - - next(action) - break - - case 'START_PROCESS': - store.dispatch({ - type: action.key, - data: action.data - }) - store.dispatch({ - type: action.key+'_STARTED' - }) - next(action) - break - - case 'RESUME_PROCESS': - store.dispatch({ - type: action.key, - data: store.getState().ui.processes[action.key].data - }) - next(action) - break - - case 'PROCESS_CANCELLED': - store.dispatch({ - type: action.key+'_CANCELLED' - }) - next(action) - break - - case 'PROCESS_FINISHING': - - // start a timeout to remove this notification - // This gives us time to animate out the notification before we remove the data - var timeout = setTimeout( - function(){ - store.dispatch(uiActions.processFinished(action.key)) - }, - 200 - ) - - next(action); - break; - - case 'PROCESS_FINISHED': - store.dispatch({ - type: action.key+'_FINISHED' - }); - next(action); - break; - - // This action is irrelevant to us, pass it on to the next middleware - default: - return next(action) + case 'CLOSE_MODAL': + if (store.getState().ui.allow_reporting) { + ReactGA.event({ category: 'Modal', action: 'Closed', label: null }); } + $('body').removeClass('modal-open'); + next(action); + break; + + case 'HIDE_CONTEXT_MENU': + if (store.getState().ui.context_menu) { + setTimeout( + () => { + store.dispatch(uiActions.removeContextMenu()); + }, + 200, + ); + next(action); + } + break; + + case 'HIDE_TOUCH_CONTEXT_MENU': + if (store.getState().ui.context_menu) { + setTimeout( + () => { + store.dispatch(uiActions.removeTouchContextMenu()); + }, + 200, + ); + next(action); + } + break; + + case 'BROWSER_NOTIFICATION': + + var notification = window.Notification || window.mozNotification || window.webkitNotification; + if (typeof notification === 'undefined') return false; + if (typeof notification !== 'undefined') notification.requestPermission((permission) => {}); + + // handle nested data objects + var data = {}; + if (typeof (action.data)) data = action.data; + if (typeof (data.data)) data = { ...data, ...data.data }; + + // construct our browser notification + var title = ''; + var options = { + body: '', + dir: 'auto', + lang: 'EN', + tag: 'iris', + }; + if (data.title) title = data.title; + if (data.body) options.body = data.body; + if (data.icon) options.icon = data.icon; + + // make it so + var notification = new notification(title, options); + break; + + case 'CREATE_NOTIFICATION': + + // start a timeout to close this notification + if (!action.notification.sticky) { + var timeout = setTimeout( + () => { + store.dispatch(uiActions.closeNotification(action.notification.key)); + }, + action.notification.duration * 1000, + ); + } + + next(action); + break; + + case 'CLOSE_NOTIFICATION': + var notifications = { ...store.getState().ui.notifications }; + + // start a timeout to remove this notification + // This gives us time to animate out the notification before we remove the data + var timeout = setTimeout( + () => { + store.dispatch(uiActions.removeNotification(action.key)); + }, + 200, + ); + + next(action); + break; + + case 'REMOVE_NOTIFICATION': + + // Manual removal + if (action.manual) { + var notifications = { ...store.getState().ui.notifications }; + + // If a broadcast, add to suppressed_broadcasts + if (notifications[action.key] && notifications[action.key].type == 'broadcast') { + store.dispatch({ + type: 'SUPPRESS_BROADCAST', + key: action.key, + }); + } + } + + next(action); + break; + + case 'BROADCASTS_LOADED': + var suppressed_broadcasts = []; + if (store.getState().ui.suppressed_broadcasts !== undefined) { + suppressed_broadcasts = store.getState().ui.suppressed_broadcasts; + } + + for (let i = 0; i < action.broadcasts.length; i++) { + const broadcast = action.broadcasts[i]; + + if (!suppressed_broadcasts.includes(broadcast.key)) { + if (broadcast.message) { + var data = { + key: (broadcast.key ? broadcast.key : null), + title: (broadcast.title ? broadcast.title : null), + content: (broadcast.message ? broadcast.message : null), + links: (broadcast.links ? broadcast.links : null), + type: 'broadcast', + sticky: true, + }; + store.dispatch(uiActions.createNotification(data)); + } + } + } + + next(action); + break; + + case 'START_PROCESS': + store.dispatch({ + type: action.key, + data: action.data, + }); + store.dispatch({ + type: `${action.key}_STARTED`, + }); + next(action); + break; + + case 'RESUME_PROCESS': + store.dispatch({ + type: action.key, + data: store.getState().ui.processes[action.key].data, + }); + next(action); + break; + + case 'PROCESS_CANCELLED': + store.dispatch({ + type: `${action.key}_CANCELLED`, + }); + next(action); + break; + + case 'PROCESS_FINISHING': + + // start a timeout to remove this notification + // This gives us time to animate out the notification before we remove the data + var timeout = setTimeout( + () => { + store.dispatch(uiActions.processFinished(action.key)); + }, + 200, + ); + + next(action); + break; + + case 'PROCESS_FINISHED': + store.dispatch({ + type: `${action.key}_FINISHED`, + }); + next(action); + break; + + // This action is irrelevant to us, pass it on to the next middleware + default: + return next(action); } + }; +}()); -})(); - -export default UIMiddleware \ No newline at end of file +export default UIMiddleware; diff --git a/src/js/services/ui/reducer.js b/src/js/services/ui/reducer.js index 6f46b151..1467f208 100755 --- a/src/js/services/ui/reducer.js +++ b/src/js/services/ui/reducer.js @@ -1,228 +1,210 @@ -import * as helpers from '../../helpers' +import * as helpers from '../../helpers'; -export default function reducer(ui = {}, action){ - switch (action.type){ +export default function reducer(ui = {}, action) { + switch (action.type) { + case 'LAZY_LOADING': + return { ...ui, lazy_loading: action.start }; - case 'LAZY_LOADING': - return Object.assign({}, ui, { lazy_loading: action.start }); + case 'SET_SLIM_MODE': + return { ...ui, slim_mode: action.slim_mode }; - case 'SET_SLIM_MODE': - return Object.assign({}, ui, { slim_mode: action.slim_mode }); + case 'SET_WINDOW_FOCUS': + return { ...ui, window_focus: action.window_focus }; - case 'SET_WINDOW_FOCUS': - return Object.assign({}, ui, { window_focus: action.window_focus }); + case 'DEBUG': + return { ...ui, debug_response: action.response }; - case 'DEBUG': - return Object.assign({}, ui, { debug_response: action.response }) + case 'UI_SET': + return { ...ui, ...action.data }; - case 'UI_SET': - return Object.assign({}, ui, action.data); - - case 'SET_WINDOW_TITLE': - if (action.title){ - return Object.assign({}, ui, { window_title: action.title }); - } else { - return ui; - } - - case 'TOGGLE_SIDEBAR': - var new_state = !ui.sidebar_open - if (typeof(action.new_state) !== 'undefined' ) new_state = action.new_state - return Object.assign({}, ui, { sidebar_open : new_state }) - - case 'SET_CURRENT_TRACK_TRANSITION': - return Object.assign({}, ui, { current_track_transition : action.current_track_transition }) - - case 'SET_SELECTED_TRACKS': - return Object.assign({}, ui, { selected_tracks : Object.assign([],action.keys) }) - - case 'ICON_LOADED': - var icons = Object.assign({}, ui.icons); - icons[action.key] = action.icon; - return Object.assign({}, ui, {icons : icons}); - - case 'INSTALL_PROMPT': - return Object.assign({}, ui, {install_prompt : action.event}); + case 'SET_WINDOW_TITLE': + if (action.title) { + return { ...ui, window_title: action.title }; + } + return ui; - /** + case 'TOGGLE_SIDEBAR': + var new_state = !ui.sidebar_open; + if (typeof (action.new_state) !== 'undefined') new_state = action.new_state; + return { ...ui, sidebar_open: new_state }; + + case 'SET_CURRENT_TRACK_TRANSITION': + return { ...ui, current_track_transition: action.current_track_transition }; + + case 'SET_SELECTED_TRACKS': + return { ...ui, selected_tracks: Object.assign([], action.keys) }; + + case 'ICON_LOADED': + var icons = { ...ui.icons }; + icons[action.key] = action.icon; + return { ...ui, icons }; + + case 'INSTALL_PROMPT': + return { ...ui, install_prompt: action.event }; + + + /** * Context menu - **/ + * */ - case 'SHOW_CONTEXT_MENU': - return Object.assign({}, ui, { - context_menu: action.data - }); + case 'SHOW_CONTEXT_MENU': + return { ...ui, context_menu: action.data }; - case 'HIDE_CONTEXT_MENU': - return Object.assign( - {}, - ui, - { - context_menu: Object.assign({}, ui.context_menu, {closing: true}) - } - ); + case 'HIDE_CONTEXT_MENU': + return { - case 'REMOVE_CONTEXT_MENU': - return Object.assign({}, ui, {context_menu: null}); + ...ui, + context_menu: { ...ui.context_menu, closing: true }, + }; - case 'SHOW_TOUCH_CONTEXT_MENU': - return Object.assign({}, ui, { - touch_context_menu: action.data - }); + case 'REMOVE_CONTEXT_MENU': + return { ...ui, context_menu: null }; - case 'HIDE_TOUCH_CONTEXT_MENU': - return Object.assign( - {}, - ui, - { - touch_context_menu: Object.assign({}, ui.touch_context_menu, {closing: true}) - } - ); + case 'SHOW_TOUCH_CONTEXT_MENU': + return { ...ui, touch_context_menu: action.data }; - case 'REMOVE_TOUCH_CONTEXT_MENU': - return Object.assign({}, ui, {touch_context_menu: null}); + case 'HIDE_TOUCH_CONTEXT_MENU': + return { + + ...ui, + touch_context_menu: { ...ui.touch_context_menu, closing: true }, + }; + + case 'REMOVE_TOUCH_CONTEXT_MENU': + return { ...ui, touch_context_menu: null }; - - /** + /** * Dragging - **/ + * */ - case 'DRAG_START': - return Object.assign({}, ui, { - dragger: { - dragging: true, - active: false, - context: action.context, - from_uri: action.from_uri, - victims: action.victims, - victims_indexes: action.victims_indexes, - start_x: action.start_x, - start_y: action.start_y - } - }); + case 'DRAG_START': + return { + ...ui, + dragger: { + dragging: true, + active: false, + context: action.context, + from_uri: action.from_uri, + victims: action.victims, + victims_indexes: action.victims_indexes, + start_x: action.start_x, + start_y: action.start_y, + }, + }; - case 'DRAG_ACTIVE': - var dragger = Object.assign({}, ui.dragger, { active: true }) - return Object.assign({}, ui, { dragger: dragger }); + case 'DRAG_ACTIVE': + var dragger = { ...ui.dragger, active: true }; + return { ...ui, dragger }; - case 'DRAG_END': - return Object.assign({}, ui, { - dragger: false - }); + case 'DRAG_END': + return { ...ui, dragger: false }; - - - /** + /** * Modals - **/ + * */ - case 'OPEN_MODAL': - return Object.assign({}, ui, { modal: action.modal }) + case 'OPEN_MODAL': + return { ...ui, modal: action.modal }; - case 'CLOSE_MODAL': - return Object.assign({}, ui, { modal: false }) + case 'CLOSE_MODAL': + return { ...ui, modal: false }; - /** + /** * Notifications - **/ + * */ - case 'CREATE_NOTIFICATION': - var notifications = Object.assign({}, ui.notifications); - notifications[action.notification.key] = action.notification; - return Object.assign({}, ui, { notifications: notifications }); + case 'CREATE_NOTIFICATION': + var notifications = { ...ui.notifications }; + notifications[action.notification.key] = action.notification; + return { ...ui, notifications }; - case 'CLOSE_NOTIFICATION': - var notifications = Object.assign({}, ui.notifications); - if (notifications[action.key]){ - notifications[action.key].closing = true; - } - return Object.assign({}, ui, { notifications: notifications }); + case 'CLOSE_NOTIFICATION': + var notifications = { ...ui.notifications }; + if (notifications[action.key]) { + notifications[action.key].closing = true; + } + return { ...ui, notifications }; - case 'REMOVE_NOTIFICATION': - var notifications = Object.assign({}, ui.notifications); - delete notifications[action.key]; - return Object.assign({}, ui, {notifications: notifications}); + case 'REMOVE_NOTIFICATION': + var notifications = { ...ui.notifications }; + delete notifications[action.key]; + return { ...ui, notifications }; - - - /** + /** * Loading and processes - **/ + * */ - case 'START_LOADING': - var load_queue = Object.assign({}, (ui.load_queue ? ui.load_queue : {})) - load_queue[action.key] = action.source - return Object.assign({}, ui, {load_queue: load_queue}) + case 'START_LOADING': + var load_queue = { ...(ui.load_queue ? ui.load_queue : {}) }; + load_queue[action.key] = action.source; + return { ...ui, load_queue }; - case 'STOP_LOADING': - var load_queue = Object.assign({}, (ui.load_queue ? ui.load_queue : {})) - if (load_queue[action.key]){ - delete load_queue[action.key] - } - return Object.assign({}, ui, {load_queue: load_queue}) + case 'STOP_LOADING': + var load_queue = { ...(ui.load_queue ? ui.load_queue : {}) }; + if (load_queue[action.key]) { + delete load_queue[action.key]; + } + return { ...ui, load_queue }; - case 'START_PROCESS': - case 'UPDATE_PROCESS': - var processes = Object.assign({}, (ui.processes ? ui.processes : [])) - if (processes[action.key]){ - var data = Object.assign({}, processes[action.key].data, action.data) - } else { - var data = action.data - } - processes[action.key] = { - key: action.key, - message: action.message, - status: 'running', - data: data - } - return Object.assign({}, ui, {processes: processes}) + case 'START_PROCESS': + case 'UPDATE_PROCESS': + var processes = { ...(ui.processes ? ui.processes : []) }; + if (processes[action.key]) { + var data = { ...processes[action.key].data, ...action.data }; + } else { + var { data } = action; + } + processes[action.key] = { + key: action.key, + message: action.message, + status: 'running', + data, + }; + return { ...ui, processes }; - case 'RESUME_PROCESS': - var processes = Object.assign({}, (ui.processes ? ui.processes : {})) - if (processes[action.key]){ - processes[action.key] = Object.assign({}, processes[action.key], {status: 'running'}) - } - return Object.assign({}, ui, {processes: processes}) + case 'RESUME_PROCESS': + var processes = { ...(ui.processes ? ui.processes : {}) }; + if (processes[action.key]) { + processes[action.key] = { ...processes[action.key], status: 'running' }; + } + return { ...ui, processes }; - case 'CANCEL_PROCESS': - var processes = Object.assign({}, (ui.processes ? ui.processes : {})) - if (processes[action.key]){ - processes[action.key] = Object.assign({}, processes[action.key], {status: 'cancelling'}) - } - return Object.assign({}, ui, {processes: processes}) + case 'CANCEL_PROCESS': + var processes = { ...(ui.processes ? ui.processes : {}) }; + if (processes[action.key]) { + processes[action.key] = { ...processes[action.key], status: 'cancelling' }; + } + return { ...ui, processes }; - case 'PROCESS_CANCELLED': - var processes = Object.assign({}, (ui.processes ? ui.processes : {})) - if (processes[action.key]){ - processes[action.key] = Object.assign({}, processes[action.key], {status: 'cancelled'}) - } - return Object.assign({}, ui, {processes: processes}) + case 'PROCESS_CANCELLED': + var processes = { ...(ui.processes ? ui.processes : {}) }; + if (processes[action.key]) { + processes[action.key] = { ...processes[action.key], status: 'cancelled' }; + } + return { ...ui, processes }; - case 'PROCESS_FINISHING': - var processes = Object.assign({}, (ui.processes ? ui.processes : {})) - if (processes[action.key]){ - processes[action.key] = Object.assign({}, processes[action.key], {closing: true}) - } - return Object.assign({}, ui, {processes: processes}) + case 'PROCESS_FINISHING': + var processes = { ...(ui.processes ? ui.processes : {}) }; + if (processes[action.key]) { + processes[action.key] = { ...processes[action.key], closing: true }; + } + return { ...ui, processes }; - case 'PROCESS_FINISHED': - var processes = Object.assign({}, (ui.processes ? ui.processes : {})) - if (processes[action.key]){ - processes[action.key] = Object.assign({}, processes[action.key], {status: 'finished', closing: false}) - } - return Object.assign({}, ui, {processes: processes}) + case 'PROCESS_FINISHED': + var processes = { ...(ui.processes ? ui.processes : {}) }; + if (processes[action.key]) { + processes[action.key] = { ...processes[action.key], status: 'finished', closing: false }; + } + return { ...ui, processes }; - default: - return ui - } + default: + return ui; + } } - - - diff --git a/src/js/store/index.js b/src/js/store/index.js index 3fd854b0..bd146426 100755 --- a/src/js/store/index.js +++ b/src/js/store/index.js @@ -1,6 +1,7 @@ import { createStore, applyMiddleware, combineReducers } from 'redux'; +import thunk from 'redux-thunk'; import * as helpers from '../helpers'; import core from '../services/core/reducer'; @@ -15,7 +16,6 @@ import genius from '../services/genius/reducer'; import migration from './migration'; -import thunk from 'redux-thunk'; import coreMiddleware from '../services/core/middleware'; import uiMiddleware from '../services/ui/middleware'; import pusherMiddleware from '../services/pusher/middleware'; @@ -28,121 +28,121 @@ import snapcastMiddleware from '../services/snapcast/middleware'; import localstorageMiddleware from '../services/localstorage/middleware'; let state = { - core: { - outputs: [], - queue: [], - queue_metadata: {}, - current_track: null, - albums: {}, - artists: {}, - playlists: {}, - users: {}, - tracks: {}, - http_streaming_enabled: false, - http_streaming_cachebuster: null, - http_streaming_url: "http://"+window.location.hostname+":8000/mopidy" - }, - ui: { - theme: 'dark', - smooth_scrolling_enabled: true, - hotkeys_enabled: true, - playback_controls_touch_enabled: true, - allow_reporting: true, - wide_scrollbar_enabled: false, - window_focus: true, - slim_mode: false, - selected_tracks: [], - notifications: {}, - processes: {} - }, - mopidy: { - connected: false, - host: window.location.hostname, - port: (window.location.port ? window.location.port : (window.location.protocol === 'https:' ? '443' : '80')), - ssl: (window.location.protocol === 'https:' ? true : false), - mute: false, - volume: 0, - progress: 0, - play_state: null, - uri_schemes: [], - library_albums_uri: 'local:directory?type=album', - library_artists_uri: 'local:directory?type=artist' - }, - pusher: { - connected: false, - username: helpers.generateGuid(), - client_id: helpers.generateGuid(), - connections: {}, - version: { - current: null - }, - config: {} - }, - lastfm: { - me: null, - authorization_url: 'https://jamesbarnsley.co.nz/iris/auth_lastfm.php' - }, - genius: { - me: null, - authorization_url: 'https://jamesbarnsley.co.nz/iris/auth_genius.php' - }, - spotify: { - me: null, - autocomplete_results: {}, - authorization_url: 'https://jamesbarnsley.co.nz/iris/auth_spotify.php' - }, - google: { - enabled: false - }, - snapcast: { - streams: {}, - groups: {}, - clients: {}, - server: null, - commands: {} - } + core: { + outputs: [], + queue: [], + queue_metadata: {}, + current_track: null, + albums: {}, + artists: {}, + playlists: {}, + users: {}, + tracks: {}, + http_streaming_enabled: false, + http_streaming_cachebuster: null, + http_streaming_url: `http://${window.location.hostname}:8000/mopidy`, + }, + ui: { + theme: 'dark', + smooth_scrolling_enabled: true, + hotkeys_enabled: true, + playback_controls_touch_enabled: true, + allow_reporting: true, + wide_scrollbar_enabled: false, + window_focus: true, + slim_mode: false, + selected_tracks: [], + notifications: {}, + processes: {}, + }, + mopidy: { + connected: false, + host: window.location.hostname, + port: (window.location.port ? window.location.port : (window.location.protocol === 'https:' ? '443' : '80')), + ssl: (window.location.protocol === 'https:'), + mute: false, + volume: 0, + progress: 0, + play_state: null, + uri_schemes: [], + library_albums_uri: 'local:directory?type=album', + library_artists_uri: 'local:directory?type=artist', + }, + pusher: { + connected: false, + username: helpers.generateGuid(), + client_id: helpers.generateGuid(), + connections: {}, + version: { + current: null, + }, + config: {}, + }, + lastfm: { + me: null, + authorization_url: 'https://jamesbarnsley.co.nz/iris/auth_lastfm.php', + }, + genius: { + me: null, + authorization_url: 'https://jamesbarnsley.co.nz/iris/auth_genius.php', + }, + spotify: { + me: null, + autocomplete_results: {}, + authorization_url: 'https://jamesbarnsley.co.nz/iris/auth_spotify.php', + }, + google: { + enabled: false, + }, + snapcast: { + streams: {}, + groups: {}, + clients: {}, + server: null, + commands: {}, + }, }; // load all our stored values from LocalStorage -state.core = Object.assign({}, state.core, helpers.getStorage('core')); -state.ui = Object.assign({}, state.ui, helpers.getStorage('ui')); -state.mopidy = Object.assign({}, state.mopidy, helpers.getStorage('mopidy')); -state.pusher = Object.assign({}, state.pusher, helpers.getStorage('pusher')); -state.spotify = Object.assign({}, state.spotify, helpers.getStorage('spotify')); -state.lastfm = Object.assign({}, state.lastfm, helpers.getStorage('lastfm')); -state.genius = Object.assign({}, state.genius, helpers.getStorage('genius')); -state.google = Object.assign({}, state.google, helpers.getStorage('google')); -state.snapcast = Object.assign({}, state.snapcast, helpers.getStorage('snapcast')); +state.core = { ...state.core, ...helpers.getStorage('core') }; +state.ui = { ...state.ui, ...helpers.getStorage('ui') }; +state.mopidy = { ...state.mopidy, ...helpers.getStorage('mopidy') }; +state.pusher = { ...state.pusher, ...helpers.getStorage('pusher') }; +state.spotify = { ...state.spotify, ...helpers.getStorage('spotify') }; +state.lastfm = { ...state.lastfm, ...helpers.getStorage('lastfm') }; +state.genius = { ...state.genius, ...helpers.getStorage('genius') }; +state.google = { ...state.google, ...helpers.getStorage('google') }; +state.snapcast = { ...state.snapcast, ...helpers.getStorage('snapcast') }; // Run any migrations state = migration(state); const reducers = combineReducers({ - core, - ui, - pusher, - mopidy, - lastfm, - genius, - spotify, - google, - snapcast + core, + ui, + pusher, + mopidy, + lastfm, + genius, + spotify, + google, + snapcast, }); export default createStore( - reducers, - state, - applyMiddleware( - thunk, - localstorageMiddleware, - coreMiddleware, - uiMiddleware, - mopidyMiddleware, - pusherMiddleware, - spotifyMiddleware, - lastfmMiddleware, - geniusMiddleware, - googleMiddleware, - snapcastMiddleware - ) + reducers, + state, + applyMiddleware( + thunk, + localstorageMiddleware, + coreMiddleware, + uiMiddleware, + mopidyMiddleware, + pusherMiddleware, + spotifyMiddleware, + lastfmMiddleware, + geniusMiddleware, + googleMiddleware, + snapcastMiddleware, + ), ); diff --git a/src/js/store/migration.js b/src/js/store/migration.js index a7570e4c..b236d1e8 100755 --- a/src/js/store/migration.js +++ b/src/js/store/migration.js @@ -1,7 +1,7 @@ -export default state => { - if (state.ui.shortkeys_enabled !== undefined){ - state.ui.hotkeys_enabled = state.ui.shortkeys_enabled; - } - return state; -} \ No newline at end of file +export default (state) => { + if (state.ui.shortkeys_enabled !== undefined) { + state.ui.hotkeys_enabled = state.ui.shortkeys_enabled; + } + return state; +}; diff --git a/src/js/views/Album.js b/src/js/views/Album.js index 5d01cd4d..17ca4c6b 100755 --- a/src/js/views/Album.js +++ b/src/js/views/Album.js @@ -1,27 +1,27 @@ -import React from "react"; -import { connect } from "react-redux"; -import { bindActionCreators } from "redux"; +import React from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; -import ErrorMessage from "../components/ErrorMessage"; -import Header from "../components/Header"; -import TrackList from "../components/TrackList"; -import Thumbnail from "../components/Thumbnail"; -import Parallax from "../components/Parallax"; -import ArtistSentence from "../components/ArtistSentence"; -import ArtistGrid from "../components/ArtistGrid"; -import FollowButton from "../components/Fields/FollowButton"; -import NiceNumber from "../components/NiceNumber"; -import Dater from "../components/Dater"; -import LazyLoadListener from "../components/LazyLoadListener"; -import ContextMenuTrigger from "../components/ContextMenuTrigger"; -import Icon from "../components/Icon"; +import ErrorMessage from '../components/ErrorMessage'; +import Header from '../components/Header'; +import TrackList from '../components/TrackList'; +import Thumbnail from '../components/Thumbnail'; +import Parallax from '../components/Parallax'; +import ArtistSentence from '../components/ArtistSentence'; +import ArtistGrid from '../components/ArtistGrid'; +import FollowButton from '../components/Fields/FollowButton'; +import NiceNumber from '../components/NiceNumber'; +import Dater from '../components/Dater'; +import LazyLoadListener from '../components/LazyLoadListener'; +import ContextMenuTrigger from '../components/ContextMenuTrigger'; +import Icon from '../components/Icon'; -import * as helpers from "../helpers"; -import * as coreActions from "../services/core/actions"; -import * as uiActions from "../services/ui/actions"; -import * as mopidyActions from "../services/mopidy/actions"; -import * as spotifyActions from "../services/spotify/actions"; -import * as lastfmActions from "../services/lastfm/actions"; +import * as helpers from '../helpers'; +import * as coreActions from '../services/core/actions'; +import * as uiActions from '../services/ui/actions'; +import * as mopidyActions from '../services/mopidy/actions'; +import * as spotifyActions from '../services/spotify/actions'; +import * as lastfmActions from '../services/lastfm/actions'; export class Album extends React.Component { constructor(props) { @@ -38,7 +38,7 @@ export class Album extends React.Component { this.props.lastfmActions.getAlbum( this.props.album.uri, this.props.album.artists[0].name, - this.props.album.name + this.props.album.name, ); } } @@ -46,8 +46,8 @@ export class Album extends React.Component { handleContextMenu(e) { e.preventDefault(); - var data = { uris: [this.props.uri] }; - this.props.uiActions.showContextMenu(e, data, "album", "click"); + const data = { uris: [this.props.uri] }; + this.props.uiActions.showContextMenu(e, data, 'album', 'click'); } componentWillReceiveProps(nextProps) { @@ -57,21 +57,21 @@ export class Album extends React.Component { // if mopidy has just connected AND we're a local album, go get } else if (!this.props.mopidy_connected && nextProps.mopidy_connected) { - if (helpers.uriSource(nextProps.uri) != "spotify") { + if (helpers.uriSource(nextProps.uri) != 'spotify') { this.props.coreActions.loadAlbum(nextProps.uri); } } // We have just received our full album or our album artists if ( - (!this.props.album && nextProps.album) || - (!this.props.album.artists && nextProps.album.artists) + (!this.props.album && nextProps.album) + || (!this.props.album.artists && nextProps.album.artists) ) { if (nextProps.album.wiki === undefined && nextProps.artists.length > 0) { this.props.lastfmActions.getAlbum( nextProps.album.uri, nextProps.album.artists[0].name, - nextProps.album.name + nextProps.album.name, ); } } @@ -83,41 +83,41 @@ export class Album extends React.Component { setWindowTitle(album = this.props.album) { if (album) { - var artists = ""; + let artists = ''; if (album.artists_uris && this.props.artists) { - for (var i = 0; i < album.artists_uris.length; i++) { - var uri = album.artists_uris[i]; + for (let i = 0; i < album.artists_uris.length; i++) { + const uri = album.artists_uris[i]; if (this.props.artists.hasOwnProperty(uri)) { - if (artists != "") { - artists += ", "; + if (artists != '') { + artists += ', '; } artists += this.props.artists[uri].name; } } } this.props.uiActions.setWindowTitle( - album.name + " by " + artists + " (album)" + `${album.name} by ${artists} (album)`, ); } else { - this.props.uiActions.setWindowTitle("Album"); + this.props.uiActions.setWindowTitle('Album'); } } handleContextMenu(e) { - var data = { - e: e, - context: "album", + const data = { + e, + context: 'album', items: [this.props.album], - uris: [this.props.uri] + uris: [this.props.uri], }; this.props.uiActions.showContextMenu(data); } loadMore() { this.props.spotifyActions.getMore(this.props.album.tracks_more, { - parent_type: "album", + parent_type: 'album', parent_key: this.props.album.uri, - records_type: "track" + records_type: 'track', }); } @@ -126,7 +126,7 @@ export class Album extends React.Component { } inLibrary() { - var library = helpers.uriSource(this.props.uri) + "_library_albums"; + const library = `${helpers.uriSource(this.props.uri)}_library_albums`; return ( this.props[library] && this.props[library].indexOf(this.props.uri) > -1 ); @@ -136,35 +136,35 @@ export class Album extends React.Component { if (!this.props.album) { if ( helpers.isLoading(this.props.load_queue, [ - "spotify_albums/" + helpers.getFromUri("albumid", this.props.uri) + `spotify_albums/${helpers.getFromUri('albumid', this.props.uri)}`, ]) ) { return (
    -
    +
    ); - } else { - return ( - -

    - Could not find album with URI " - {encodeURIComponent(this.props.uri)}" -

    -
    - ); } + return ( + +

    + Could not find album with URI " + {encodeURIComponent(this.props.uri)} +" +

    +
    + ); } - var album = helpers.collate(this.props.album, { + const album = helpers.collate(this.props.album, { tracks: this.props.tracks, - artists: this.props.artists + artists: this.props.artists, }); if ( - !album.tracks_uris || - (album.tracks_uris && !album.tracks) || - album.tracks_uris.length !== album.tracks.length + !album.tracks_uris + || (album.tracks_uris && !album.tracks) + || album.tracks_uris.length !== album.tracks.length ) { var is_loading_tracks = true; } else { @@ -198,7 +198,13 @@ export class Album extends React.Component { ) : null} - {album.tracks ?
  • {album.tracks.length} tracks
  • : null} + {album.tracks ? ( +
  • + {album.tracks.length} + {' '} +tracks +
  • + ) : null} {!this.props.slim_mode && album.tracks ? (
  • @@ -206,22 +212,26 @@ export class Album extends React.Component { ) : null} {!this.props.slim_mode && album.play_count ? (
  • - plays + + {' '} +plays
  • ) : null} {!this.props.slim_mode && album.listeners ? (
  • - listeners + + {' '} +listeners
  • ) : null}
    - - {helpers.uriSource(this.props.uri) == "spotify" ? ( + {helpers.uriSource(this.props.uri) == 'spotify' ? ( ) : null} - this.handleContextMenu(e)} /> + this.handleContextMenu(e)} />
    @@ -253,7 +263,9 @@ export class Album extends React.Component {

    {album.wiki}


    - Published: {album.wiki_publish_date} + Published: + {' '} + {album.wiki_publish_date}
    @@ -264,9 +276,9 @@ export class Album extends React.Component { } const mapStateToProps = (state, ownProps) => { - var uri = decodeURIComponent(ownProps.match.params.uri); + const uri = decodeURIComponent(ownProps.match.params.uri); return { - uri: uri, + uri, slim_mode: state.ui.slim_mode, theme: state.ui.theme, load_queue: state.ui.load_queue, @@ -280,21 +292,19 @@ const mapStateToProps = (state, ownProps) => { spotify_library_albums: state.spotify.library_albums, local_library_albums: state.mopidy.library_albums, spotify_authorized: state.spotify.authorization, - mopidy_connected: state.mopidy.connected + mopidy_connected: state.mopidy.connected, }; }; -const mapDispatchToProps = dispatch => { - return { - coreActions: bindActionCreators(coreActions, dispatch), - uiActions: bindActionCreators(uiActions, dispatch), - mopidyActions: bindActionCreators(mopidyActions, dispatch), - spotifyActions: bindActionCreators(spotifyActions, dispatch), - lastfmActions: bindActionCreators(lastfmActions, dispatch) - }; -}; +const mapDispatchToProps = (dispatch) => ({ + coreActions: bindActionCreators(coreActions, dispatch), + uiActions: bindActionCreators(uiActions, dispatch), + mopidyActions: bindActionCreators(mopidyActions, dispatch), + spotifyActions: bindActionCreators(spotifyActions, dispatch), + lastfmActions: bindActionCreators(lastfmActions, dispatch), +}); export default connect( mapStateToProps, - mapDispatchToProps + mapDispatchToProps, )(Album); diff --git a/src/js/views/Artist.js b/src/js/views/Artist.js index 1fe01da1..44d221a7 100755 --- a/src/js/views/Artist.js +++ b/src/js/views/Artist.js @@ -7,455 +7,508 @@ import { Route, Switch } from 'react-router-dom'; import ErrorMessage from '../components/ErrorMessage'; import Link from '../components/Link'; -import LazyLoadListener from '../components/LazyLoadListener' -import TrackList from '../components/TrackList' -import AlbumGrid from '../components/AlbumGrid' -import Thumbnail from '../components/Thumbnail' -import Parallax from '../components/Parallax' -import ArtistGrid from '../components/ArtistGrid' -import RelatedArtists from '../components/RelatedArtists' -import FollowButton from '../components/Fields/FollowButton' -import ContextMenuTrigger from '../components/ContextMenuTrigger' -import DropdownField from '../components/Fields/DropdownField' -import Icon from '../components/Icon' +import LazyLoadListener from '../components/LazyLoadListener'; +import TrackList from '../components/TrackList'; +import AlbumGrid from '../components/AlbumGrid'; +import Thumbnail from '../components/Thumbnail'; +import Parallax from '../components/Parallax'; +import ArtistGrid from '../components/ArtistGrid'; +import RelatedArtists from '../components/RelatedArtists'; +import FollowButton from '../components/Fields/FollowButton'; +import ContextMenuTrigger from '../components/ContextMenuTrigger'; +import DropdownField from '../components/Fields/DropdownField'; +import Icon from '../components/Icon'; -import * as helpers from '../helpers' -import * as coreActions from '../services/core/actions' -import * as uiActions from '../services/ui/actions' -import * as mopidyActions from '../services/mopidy/actions' -import * as pusherActions from '../services/pusher/actions' -import * as lastfmActions from '../services/lastfm/actions' -import * as spotifyActions from '../services/spotify/actions' +import * as helpers from '../helpers'; +import * as coreActions from '../services/core/actions'; +import * as uiActions from '../services/ui/actions'; +import * as mopidyActions from '../services/mopidy/actions'; +import * as pusherActions from '../services/pusher/actions'; +import * as lastfmActions from '../services/lastfm/actions'; +import * as spotifyActions from '../services/spotify/actions'; -class Artist extends React.Component{ +class Artist extends React.Component { + constructor(props) { + super(props); + } - constructor(props){ - super(props); - } + componentDidMount() { + this.setWindowTitle(); + this.props.coreActions.loadArtist(this.props.uri); + } - componentDidMount(){ - this.setWindowTitle(); - this.props.coreActions.loadArtist(this.props.uri); - } + componentWillReceiveProps(nextProps) { + if (nextProps.uri != this.props.uri) { + this.props.coreActions.loadArtist(nextProps.uri); + } else if (!this.props.mopidy_connected && nextProps.mopidy_connected) { + if (helpers.uriSource(this.props.uri) != 'spotify') { + this.props.coreActions.loadArtist(nextProps.uri); + } + } - componentWillReceiveProps(nextProps){ + if (!this.props.artist && nextProps.artist) { + this.setWindowTitle(nextProps.artist); + } - if (nextProps.uri != this.props.uri){ - this.props.coreActions.loadArtist(nextProps.uri); + if (this.props.uri !== nextProps.uri && nextProps.artist) { + this.setWindowTitle(nextProps.artist); + } + } - } else if (!this.props.mopidy_connected && nextProps.mopidy_connected){ - if (helpers.uriSource(this.props.uri ) != 'spotify'){ - this.props.coreActions.loadArtist(nextProps.uri); - } - } + setWindowTitle(artist = this.props.artist) { + if (artist) { + this.props.uiActions.setWindowTitle(`${artist.name} (artist)`); + } else { + this.props.uiActions.setWindowTitle('Artist'); + } + } - if (!this.props.artist && nextProps.artist){ - this.setWindowTitle(nextProps.artist); - } + handleContextMenu(e) { + const data = { + e, + context: 'artist', + items: [this.props.artist], + uris: [this.props.uri], + }; + this.props.uiActions.showContextMenu(data); + } - if (this.props.uri !== nextProps.uri && nextProps.artist){ - this.setWindowTitle(nextProps.artist); - } - } + loadMore() { + this.props.spotifyActions.getMore( + this.props.artist.albums_more, + { + parent_type: 'artist', + parent_key: this.props.uri, + records_type: 'album', + }, + ); + } - setWindowTitle(artist = this.props.artist){ - if (artist){ - this.props.uiActions.setWindowTitle(artist.name+" (artist)"); - } else{ - this.props.uiActions.setWindowTitle("Artist"); - } - } + inLibrary() { + const library = `${helpers.uriSource(this.props.uri)}_library_artists`; + return (this.props[library] && this.props[library].indexOf(this.props.uri) > -1); + } - handleContextMenu(e){ - var data = { - e: e, - context: 'artist', - items: [this.props.artist], - uris: [this.props.uri] - } - this.props.uiActions.showContextMenu(data); - } + setSort(value) { + let reverse = false; + if (value !== null && this.props.sort == value) { + reverse = !this.props.sort_reverse; + } - loadMore(){ - this.props.spotifyActions.getMore( - this.props.artist.albums_more, - { - parent_type: 'artist', - parent_key: this.props.uri, - records_type: 'album' - } - ); - } + const data = { + artist_albums_sort_reverse: reverse, + artist_albums_sort: value, + }; + this.props.uiActions.set(data); + } - inLibrary(){ - var library = helpers.uriSource(this.props.uri)+'_library_artists' - return (this.props[library] && this.props[library].indexOf(this.props.uri) > -1) - } + setFilter(value) { + this.props.uiActions.set({ artist_albums_filter: value }); + } - setSort(value){ - var reverse = false; - if (value !== null && this.props.sort == value){ - reverse = !this.props.sort_reverse; - } + renderOverview() { + const artist = helpers.collate( + this.props.artist, + { + artists: this.props.artists, + albums: this.props.albums, + tracks: this.props.tracks, + }, + ); - var data = { - artist_albums_sort_reverse: reverse, - artist_albums_sort: value - } - this.props.uiActions.set(data); - } + if (this.props.sort && artist.albums) { + artist.albums = helpers.sortItems(artist.albums, this.props.sort, this.props.sort_reverse); + } - setFilter(value){ - this.props.uiActions.set({artist_albums_filter: value}); - } + if (this.props.filter && artist.albums) { + artist.albums = helpers.applyFilter('type', this.props.filter, artist.albums); + } - renderOverview(){ - var artist = helpers.collate( - this.props.artist, - { - artists: this.props.artists, - albums: this.props.albums, - tracks: this.props.tracks - } - ); + const sort_options = [ + { + value: null, + label: 'Default', + }, + { + value: 'name', + label: 'Name', + }, + { + value: 'release_date', + label: 'Date', + }, + { + value: 'tracks_uris.length', + label: 'Tracks', + }, + ]; - if (this.props.sort && artist.albums){ - artist.albums = helpers.sortItems(artist.albums, this.props.sort, this.props.sort_reverse); - } + const filter_options = [ + { + value: null, + label: 'All', + }, + { + value: 'album', + label: 'Albums', + }, + { + value: 'single', + label: 'Singles', + }, + { + value: 'compilation', + label: 'Compilations', + }, + ]; - if (this.props.filter && artist.albums){ - artist.albums = helpers.applyFilter('type', this.props.filter, artist.albums); - } + 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; + } - var sort_options = [ - { - value: null, - label: 'Default' - }, - { - value: 'name', - label: 'Name' - }, - { - value: 'release_date', - label: 'Date' - }, - { - value: 'tracks_uris.length', - label: 'Tracks' - } - ]; + return ( +
    +
    0 ? '70' : '100'}`}> + {artist.tracks ?

    Top tracks

    : null} +
    + + +
    +
    - var filter_options = [ - { - value: null, - label: 'All' - }, - { - value: 'album', - label: 'Albums' - }, - { - value: 'single', - label: 'Singles' - }, - { - value: 'compilation', - label: 'Compilations' - } - ]; +
    - 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; - } + {artist.related_artists && artist.related_artists.length > 0 ? ( +
    +

    Related artists

    +
    + All related artists +
    + ) : null} - return ( -
    -
    0 ? "70" : "100")}> - {artist.tracks ?

    Top tracks

    : null} -
    - - -
    -
    +
    - ); - } +
    + + this.loadMore()} + /> +
    +
    + ) : null} +
    + ); + } - renderTracks(){ - var artist = helpers.collate( - this.props.artist, - { - artists: this.props.artists, - tracks: this.props.tracks - } - ); + renderTracks() { + const artist = helpers.collate( + this.props.artist, + { + artists: this.props.artists, + tracks: this.props.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; - } + 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; + } - return ( -
    -
    - - -
    -
    - ) - } + return ( +
    +
    + + +
    +
    + ); + } - renderRelatedArtists(){ - var artist = helpers.collate( - this.props.artist, - { - artists: this.props.artists - } - ); + renderRelatedArtists() { + const artist = helpers.collate( + this.props.artist, + { + artists: this.props.artists, + }, + ); - return ( -
    -
    - -
    -
    - ) - } + return ( +
    +
    + +
    +
    + ); + } - renderAbout(){ - var artist = helpers.collate( - this.props.artist, - { - artists: this.props.artists - } - ); + renderAbout() { + const artist = helpers.collate( + this.props.artist, + { + artists: this.props.artists, + }, + ); - var thumbnails = []; - if (artist.images && artist.images.length > 0){ - for (var images of artist.images){ + 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( +
    + +
    , + ); + } + } + } - // Capture potential 'null' images - // TODO: Prevent these from existing in the first place - if (images.huge){ - thumbnails.push( -
    - -
    - ); - } - } - } + return ( +
    - return ( -
    +
    + {thumbnails} + {artist.followers ? ( +
    + + + {artist.followers.toLocaleString()} + {' '} +followers + +
    + ) : null} + {artist.popularity ? ( +
    + + + {artist.popularity } +% popularity + +
    + ) : null} + {artist.listeners ? ( +
    + + + { artist.listeners.toLocaleString() } + {' '} +listeners + +
    + ) : null } +
    -
    - {thumbnails} - {artist.followers ?
    {artist.followers.toLocaleString()} followers
    : null} - {artist.popularity ?
    {artist.popularity }% popularity
    : null} - {artist.listeners ?
    { artist.listeners.toLocaleString() } listeners
    : null } -
    +
    +
    + { artist.biography ? ( +
    +

    {artist.biography}

    +
    +
    +Published: + { artist.biography_publish_date } +
    + +
    + ) : null } +
    +
    +
    + ); + } -
    -
    - { artist.biography ?

    {artist.biography}


    -
    Published: { artist.biography_publish_date }
    -
    : null } -
    -
    -
    - ); - } + render() { + const scheme = helpers.uriSource(this.props.uri); - render(){ - var scheme = helpers.uriSource(this.props.uri); + if (!this.props.artist) { + if (helpers.isLoading(this.props.load_queue, [`spotify_artists/${helpers.getFromUri('artistid', this.props.uri)}`, 'lastfm_method=artist.getInfo'])) { + return ( +
    +
    +
    + ); + } + return ( + +

    +Could not find artist with URI " + {encodeURIComponent(this.props.uri)} +" +

    +
    + ); + } - if (!this.props.artist){ - if (helpers.isLoading(this.props.load_queue,['spotify_artists/'+helpers.getFromUri('artistid',this.props.uri), 'lastfm_method=artist.getInfo'])){ - return ( -
    -
    -
    - ) - } else { - return ( - -

    Could not find artist with URI "{encodeURIComponent(this.props.uri)}"

    -
    - ); - } - } + 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; + } - 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'); - var 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; + } - 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 ( -
    -
    + - +
    +
    -
    -
    +
    + +
    -
    - -
    - -
    -

    {this.props.artist ? this.props.artist.name : null}

    -
    - - {is_spotify ? : null} - this.handleContextMenu(e)} /> -
    -
    -
    -
    - -

    Overview

    - - {this.props.artist.tracks_uris && this.props.artist.tracks_uris.length > 10 ? -

    Tracks

    - : null} - {this.props.artist.related_artists_uris ? -

    Related artists

    - : null} - -

    About

    - -
    -
    -
    -
    - - - {this.renderRelatedArtists()} - - - {this.renderTracks()} - - - {this.renderAbout()} - - - {this.renderOverview()} - - -
    -
    - ); - } +
    +

    {this.props.artist ? this.props.artist.name : null}

    +
    + + {is_spotify ? : null} + this.handleContextMenu(e)} /> +
    +
    +
    +
    + +

    Overview

    + + {this.props.artist.tracks_uris && this.props.artist.tracks_uris.length > 10 ? ( + +

    Tracks

    + + ) : null} + {this.props.artist.related_artists_uris ? ( + +

    Related artists

    + + ) : null} + +

    About

    + +
    +
    +
    +
    + + + {this.renderRelatedArtists()} + + + {this.renderTracks()} + + + {this.renderAbout()} + + + {this.renderOverview()} + + +
    +
    + ); + } } const mapStateToProps = (state, ownProps) => { - var uri = decodeURIComponent(ownProps.match.params.uri); - return { - uri: uri, - theme: state.ui.theme, - slim_mode: state.ui.slim_mode, - load_queue: state.ui.load_queue, - artist: (state.core.artists[uri] !== undefined ? state.core.artists[uri] : false), - tracks: state.core.tracks, - artists: state.core.artists, - spotify_library_artists: state.spotify.library_artists, - local_library_artists: state.mopidy.library_artists, - albums: (state.core.albums ? state.core.albums : []), - filter: (state.ui.artist_albums_filter ? state.ui.artist_albums_filter : null), - sort: (state.ui.artist_albums_sort ? state.ui.artist_albums_sort : null), - sort_reverse: (state.ui.artist_albums_sort_reverse ? true : false), - spotify_authorized: state.spotify.authorization, - mopidy_connected: state.mopidy.connected - } -} + const uri = decodeURIComponent(ownProps.match.params.uri); + return { + uri, + theme: state.ui.theme, + slim_mode: state.ui.slim_mode, + load_queue: state.ui.load_queue, + artist: (state.core.artists[uri] !== undefined ? state.core.artists[uri] : false), + tracks: state.core.tracks, + artists: state.core.artists, + spotify_library_artists: state.spotify.library_artists, + local_library_artists: state.mopidy.library_artists, + albums: (state.core.albums ? state.core.albums : []), + filter: (state.ui.artist_albums_filter ? state.ui.artist_albums_filter : null), + sort: (state.ui.artist_albums_sort ? state.ui.artist_albums_sort : null), + sort_reverse: (!!state.ui.artist_albums_sort_reverse), + spotify_authorized: state.spotify.authorization, + mopidy_connected: state.mopidy.connected, + }; +}; -const mapDispatchToProps = (dispatch) => { - return { - coreActions: bindActionCreators(coreActions, dispatch), - uiActions: bindActionCreators(uiActions, dispatch), - mopidyActions: bindActionCreators(mopidyActions, dispatch), - pusherActions: bindActionCreators(pusherActions, dispatch), - lastfmActions: bindActionCreators(lastfmActions, dispatch), - spotifyActions: bindActionCreators(spotifyActions, dispatch) - } -} +const mapDispatchToProps = (dispatch) => ({ + coreActions: bindActionCreators(coreActions, dispatch), + uiActions: bindActionCreators(uiActions, dispatch), + mopidyActions: bindActionCreators(mopidyActions, dispatch), + pusherActions: bindActionCreators(pusherActions, dispatch), + lastfmActions: bindActionCreators(lastfmActions, dispatch), + spotifyActions: bindActionCreators(spotifyActions, dispatch), +}); -export default connect(mapStateToProps, mapDispatchToProps)(Artist) +export default connect(mapStateToProps, mapDispatchToProps)(Artist); diff --git a/src/js/views/Debug.js b/src/js/views/Debug.js index 5fa0756a..3be4cd3e 100755 --- a/src/js/views/Debug.js +++ b/src/js/views/Debug.js @@ -1,237 +1,238 @@ -import React from 'react' -import { connect } from 'react-redux' +import React from 'react'; +import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux' +import { bindActionCreators } from 'redux'; -import Header from '../components/Header' -import Thumbnail from '../components/Thumbnail' -import Icon from '../components/Icon' +import Header from '../components/Header'; +import Thumbnail from '../components/Thumbnail'; +import Icon from '../components/Icon'; import Link from '../components/Link'; -import * as uiActions from '../services/ui/actions' -import * as pusherActions from '../services/pusher/actions' -import * as mopidyActions from '../services/mopidy/actions' -import * as spotifyActions from '../services/spotify/actions' +import * as uiActions from '../services/ui/actions'; +import * as pusherActions from '../services/pusher/actions'; +import * as mopidyActions from '../services/mopidy/actions'; +import * as spotifyActions from '../services/spotify/actions'; -class Debug extends React.Component{ +class Debug extends React.Component { + constructor(props) { + super(props); + this.state = { + mopidy_call: 'playlists.asList', + mopidy_data: '{}', + pusher_data: '{"method":"get_config"}', + access_token: this.props.access_token, + toggling_test_mode: false, + }; + } - constructor(props){ - super(props); - this.state = { - mopidy_call: 'playlists.asList', - mopidy_data: '{}', - pusher_data: '{"method":"get_config"}', - access_token: this.props.access_token, - toggling_test_mode: false - } - } + componentDidMount() { + this.props.uiActions.setWindowTitle('Debug'); + } - componentDidMount(){ - this.props.uiActions.setWindowTitle("Debug"); - } + callMopidy(e) { + e.preventDefault(); + this.props.mopidyActions.debug(this.state.mopidy_call, JSON.parse(this.state.mopidy_data)); + } - callMopidy(e){ - e.preventDefault() - this.props.mopidyActions.debug(this.state.mopidy_call, JSON.parse(this.state.mopidy_data) ) - } + callPusher(e) { + e.preventDefault(); + this.props.pusherActions.debug(JSON.parse(this.state.pusher_data)); + } - callPusher(e){ - e.preventDefault() - this.props.pusherActions.debug(JSON.parse(this.state.pusher_data) ) - } + toggleTestMode(e) { + this.setState({ toggling_test_mode: true }); + this.props.uiActions.set({ test_mode: !this.props.test_mode }); - toggleTestMode(e){ - this.setState({toggling_test_mode: true}); - this.props.uiActions.set({ test_mode: !this.props.test_mode }); + // Wait a second to allow state to update, and then refresh + setTimeout(location.reload(), 1000); + } - // Wait a second to allow state to update, and then refresh - setTimeout(location.reload(), 1000); - } + render() { + const options = ( + this.props.history.push('/settings')}> + +Back + + ); - render(){ - - var options = ( - this.props.history.push('/settings')}> - Back - - ) - - return ( -
    -
    - + return ( +
    +
    + Debug -
    +
    -
    +
    -

    User interface

    -
    -
    -
    Test mode
    -
    - {this.state.toggling_test_mode ? Applying... : (this.props.test_mode ? this.toggleTestMode(e)}>Disable : this.toggleTestMode(e)}>Enable)} -
    -
    -
    -
    Debug
    -
    - -
    -
    -
    -
    Logging
    -
    - - - -
    -
    - -
    +

    User interface

    +
    +
    +
    Test mode
    +
    + {this.state.toggling_test_mode ? Applying... : (this.props.test_mode ? this.toggleTestMode(e)}>Disable : this.toggleTestMode(e)}>Enable)} +
    +
    +
    +
    Debug
    +
    + +
    +
    +
    +
    Logging
    +
    + + + +
    +
    +
    + + -

    Spotify

    - +

    Spotify

    + -

    Mopidy

    -
    this.callMopidy(e)}> - - -
    -
    -
    - -
    -
    -
    +

    Mopidy

    +
    this.callMopidy(e)}> + + -
    -
    -
    - -
    -
    -
    +

    Pusher

    +
    this.callPusher(e)}> + +
    } + return ( + +

    + {this.props.command ? 'Edit' : 'Create'} + {' '} +command +

    + this.handleSubmit(e)}>
    -
    +
    + Name +
    +
    + this.setState({ name: value })} + /> +
    +
    + +
    +
    + Colour +
    +
    + this.setState({ colour })} + /> +
    +
    + +
    +
    + Icon +
    +
    + this.setState({ icon })} + /> +
    +
    + +
    +
    + URL +
    +
    + this.setState({ url: value })} + /> +
    +
    + +
    +
    + Method +
    +
    + + +
    +
    + + {this.state.method == 'POST' && ( +
    +
    + Data +
    +
    + -
    -
    +
    +
    +