Merge branch 'feature/mobile'
This commit is contained in:
47
package.json
47
package.json
@ -10,39 +10,34 @@
|
||||
},
|
||||
"main": "app.js",
|
||||
"dependencies": {
|
||||
"webpack": "2.2.1",
|
||||
"webpack-strip": "*",
|
||||
"sass-loader": "6.0.2",
|
||||
"style-loader": "0.13.2",
|
||||
"css-loader": "0.26.2",
|
||||
"url-loader": "0.5.8",
|
||||
"file-loader": "0.10.1",
|
||||
"expose-loader": "0.7.3",
|
||||
|
||||
"node-sass": "4.5.0",
|
||||
"extract-text-webpack-plugin": "2.0.0",
|
||||
|
||||
"babel-core": "^6.23.1",
|
||||
"babel-loader": "^6.3.0",
|
||||
"babel-loader": "^7.1.1",
|
||||
"babel-preset-es2015": "^6.22.0",
|
||||
"babel-preset-react": "^6.22.0",
|
||||
"babel-preset-stage-0": "^6.22.0",
|
||||
"babel-preset-stage-2": "^6.22.0",
|
||||
|
||||
"flux": "^2.1.1",
|
||||
"redux": "*",
|
||||
"react": "*",
|
||||
"react-dom": "*",
|
||||
"react-router": "3.0.2",
|
||||
"redux": "*",
|
||||
"react-redux": "*",
|
||||
"redux-devtools": "*",
|
||||
"redux-thunk": "*",
|
||||
|
||||
"css-loader": "0.28.4",
|
||||
"expose-loader": "0.7.3",
|
||||
"extract-text-webpack-plugin": "3.0.0",
|
||||
"file-loader": "^0.11.2",
|
||||
"flux": "^3.1.2",
|
||||
"jquery": "3.1.1",
|
||||
"mopidy": "^0.5.0",
|
||||
"mopidy": "^0.5.0",
|
||||
"node-sass": "4.5.0",
|
||||
"react": "15.6.1",
|
||||
"react-dom": "15.6.1",
|
||||
"react-fontawesome": "*",
|
||||
"react-ga": "2.1.2"
|
||||
"react-ga": "2.1.2",
|
||||
"react-redux": "5.0.5",
|
||||
"react-router": "^3.0.5",
|
||||
"redux": "3.7.2",
|
||||
"redux-devtools": "3.4.0",
|
||||
"redux-thunk": "2.2.0",
|
||||
"sass-loader": "6.0.2",
|
||||
"style-loader": "0.13.2",
|
||||
"url-loader": "0.5.8",
|
||||
"webpack": "3.3.0",
|
||||
"webpack-strip": "0.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "./build.sh && NODE_ENV=development webpack --watch",
|
||||
|
||||
@ -30,11 +30,19 @@ class App extends React.Component{
|
||||
super(props);
|
||||
this.handleKeyUp = this.handleKeyUp.bind(this);
|
||||
this.handleKeyDown = this.handleKeyDown.bind(this);
|
||||
this.handleWindowResize = this.handleWindowResize.bind(this);
|
||||
}
|
||||
|
||||
componentWillMount(){
|
||||
window.addEventListener("keyup", this.handleKeyUp, false);
|
||||
window.addEventListener("keydown", this.handleKeyDown, false);
|
||||
window.addEventListener("resize", this.handleWindowResize, false);
|
||||
}
|
||||
|
||||
componentWillUnmount(){
|
||||
window.removeEventListener("keyup", this.handleKeyUp, false);
|
||||
window.removeEventListener("keydown", this.handleKeyDown, false);
|
||||
window.removeEventListener("resize", this.handleWindowResize, false);
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
@ -56,6 +64,9 @@ class App extends React.Component{
|
||||
// hide our sidebar
|
||||
this.props.uiActions.toggleSidebar( false )
|
||||
});
|
||||
|
||||
// Check our slim_mode
|
||||
this.handleWindowResize(null)
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps){
|
||||
@ -76,11 +87,6 @@ class App extends React.Component{
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount(){
|
||||
window.removeEventListener("keyup", this.handleKeyUp, false);
|
||||
window.removeEventListener("keydown", this.handleKeyDown, false);
|
||||
}
|
||||
|
||||
shouldTriggerShortcut(e){
|
||||
var ignoreNodes = ['INPUT', 'TEXTAREA']
|
||||
var keyCodes = [32,27,191,37,38,39,40,70]
|
||||
@ -95,6 +101,21 @@ class App extends React.Component{
|
||||
}
|
||||
}
|
||||
|
||||
handleWindowResize(e){
|
||||
var width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0)
|
||||
var height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0)
|
||||
|
||||
if (width <= 800){
|
||||
if (!this.props.slim_mode){
|
||||
this.props.uiActions.setSlimMode(true)
|
||||
}
|
||||
} else {
|
||||
if (this.props.slim_mode){
|
||||
this.props.uiActions.setSlimMode(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyDown(e){
|
||||
this.shouldTriggerShortcut(e)
|
||||
}
|
||||
@ -197,6 +218,8 @@ class App extends React.Component{
|
||||
if (this.props.dragger && this.props.dragger.active) className += ' dragging'
|
||||
if (this.props.sidebar_open) className += ' sidebar-open'
|
||||
if (this.props.modal) className += ' modal-open'
|
||||
if (this.props.touch_dragging) className += ' touch-dragging'
|
||||
if (this.props.slim_mode) className += ' slim-mode'
|
||||
if (helpers.isTouchDevice()){
|
||||
className += ' touch'
|
||||
} else {
|
||||
@ -236,6 +259,8 @@ class App extends React.Component{
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {
|
||||
touch_dragging: state.ui.touch_dragging,
|
||||
slim_mode: state.ui.slim_mode,
|
||||
broadcasts: (state.ui.broadcasts ? state.ui.broadcasts : []),
|
||||
volume: (state.mopidy.volume ? state.mopidy.volume : false),
|
||||
notifications: (state.ui.notifications ? state.ui.notifications : []),
|
||||
|
||||
1
src/js/bootstrap.js
vendored
1
src/js/bootstrap.js
vendored
@ -53,6 +53,7 @@ var initialState = {
|
||||
autocomplete_results: {}
|
||||
},
|
||||
ui: {
|
||||
slim_mode: false,
|
||||
current_tracklist: [],
|
||||
current_tltrack: false,
|
||||
notifications: [],
|
||||
|
||||
@ -174,6 +174,11 @@ class ContextMenu extends React.Component{
|
||||
this.props.mopidyActions.playPlaylist(this.props.menu.uris[0])
|
||||
}
|
||||
|
||||
playArtistTopTracks(e){
|
||||
this.props.uiActions.hideContextMenu()
|
||||
this.props.spotifyActions.playArtistTopTracks(this.props.menu.uris[0])
|
||||
}
|
||||
|
||||
addToQueue(e, next = false){
|
||||
this.props.uiActions.hideContextMenu()
|
||||
this.props.mopidyActions.enqueueURIs(this.props.menu.uris, this.props.menu.tracklist_uri, next)
|
||||
@ -361,6 +366,14 @@ class ContextMenu extends React.Component{
|
||||
</span>
|
||||
)
|
||||
|
||||
var play_artist_top_tracks = (
|
||||
<span className="menu-item-wrapper">
|
||||
<a className="menu-item" onClick={e => this.playArtistTopTracks(e)}>
|
||||
<span className="label">Play top tracks</span>
|
||||
</a>
|
||||
</span>
|
||||
)
|
||||
|
||||
var add_to_queue = (
|
||||
<span className="menu-item-wrapper">
|
||||
<a className="menu-item" onClick={e => this.addToQueue(e)}>
|
||||
@ -463,6 +476,7 @@ class ContextMenu extends React.Component{
|
||||
case 'artist':
|
||||
return (
|
||||
<div>
|
||||
{context.source == 'spotify' ? play_artist_top_tracks : null}
|
||||
{start_radio}
|
||||
{context.source == 'spotify' ? go_to_recommendations : null}
|
||||
{copy_uris}
|
||||
|
||||
@ -81,6 +81,9 @@ class DebugInfo extends React.Component{
|
||||
Enqueue batches: {this.props.mopidy.enqueue_uris_batches ? this.props.mopidy.enqueue_uris_batches.length : '0'}
|
||||
</div>
|
||||
<br />
|
||||
<div className="item">
|
||||
Slim mode: {this.props.ui.slim_mode ? 'on' : 'off'}
|
||||
</div>
|
||||
<div className="item">
|
||||
_testMode: {window._testMode ? 'on' : 'off'}
|
||||
</div>
|
||||
|
||||
@ -16,26 +16,32 @@ export default class Header extends React.Component{
|
||||
}
|
||||
}
|
||||
|
||||
handleContextMenuTrigger(e,options){
|
||||
e.preventDefault()
|
||||
var data = {
|
||||
e: e,
|
||||
context: 'custom',
|
||||
title: this.props.title,
|
||||
options: options
|
||||
handleContextMenuTrigger(e,options){
|
||||
|
||||
// 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)
|
||||
}
|
||||
this.props.uiActions.showContextMenu(data)
|
||||
}
|
||||
|
||||
renderOptions(){
|
||||
if (!this.props.options) return null
|
||||
if (!this.props.options && !this.props.handleContextMenuTrigger) return null
|
||||
|
||||
return (
|
||||
<div className='options'>
|
||||
<ContextMenuTrigger onTrigger={e => this.handleContextMenuTrigger(e,this.props.options)} />
|
||||
<span className="items">
|
||||
<span className="liner">
|
||||
{ this.props.options }
|
||||
{this.props.options ? this.props.options : null}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -62,7 +62,7 @@ class PlaybackControls extends React.Component{
|
||||
}
|
||||
|
||||
render(){
|
||||
var images = []
|
||||
var images = false
|
||||
if (this.props.current_track && this.props.current_track.album && this.props.current_track.album.images){
|
||||
images = this.props.current_track.album.images
|
||||
}
|
||||
|
||||
@ -59,6 +59,9 @@ export default class Track extends React.Component{
|
||||
|
||||
var track_columns = (
|
||||
<span>
|
||||
<span className="select-zone">
|
||||
{this.props.track.selected ? <FontAwesome name="check" className="selected" fixedWidth /> : null}
|
||||
</span>
|
||||
<span className="col name">
|
||||
{track.name ? track.name : <span className="grey-text">{track.uri}</span>}
|
||||
</span>
|
||||
@ -89,8 +92,16 @@ export default class Track extends React.Component{
|
||||
var added = '-'
|
||||
}
|
||||
|
||||
className+= ' has-drag-zone'
|
||||
|
||||
var track_columns = (
|
||||
<span>
|
||||
<span className="select-zone">
|
||||
{this.props.track.selected ? <FontAwesome name="check" className="selected" fixedWidth /> : null}
|
||||
</span>
|
||||
<span className="drag-zone">
|
||||
<FontAwesome name="bars" />
|
||||
</span>
|
||||
<span className="col name">
|
||||
{track.name ? track.name : <span className="grey-text">{track.uri}</span>}
|
||||
{track.explicit ? <span className="flag dark">EXPLICIT</span> : null}
|
||||
@ -116,6 +127,9 @@ export default class Track extends React.Component{
|
||||
|
||||
var track_columns = (
|
||||
<span>
|
||||
<span className="select-zone">
|
||||
{this.props.track.selected ? <FontAwesome name="check" className="selected" fixedWidth /> : null}
|
||||
</span>
|
||||
<span className="col name">
|
||||
{track.name ? track.name : <span className="grey-text">{track.uri}</span>}
|
||||
{track.explicit ? <span className="flag dark">EXPLICIT</span> : null}
|
||||
@ -146,9 +160,6 @@ export default class Track extends React.Component{
|
||||
onMouseUp={ e => this.props.handleMouseUp(e) }
|
||||
onDoubleClick={ e => this.props.handleDoubleClick(e) }
|
||||
onContextMenu={ e => this.handleContextMenu(e) }>
|
||||
<span className="state-icon">
|
||||
{this.props.track.selected ? <FontAwesome name="check" className="selected" fixedWidth /> : null}
|
||||
</span>
|
||||
{ track_columns }
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -21,16 +21,19 @@ class TrackList extends React.Component{
|
||||
last_selected_track: false
|
||||
}
|
||||
|
||||
this._touching = false
|
||||
this.touch_dragging = false
|
||||
this.handleKeyUp = this.handleKeyUp.bind(this)
|
||||
this.handleTouchMove = this.handleTouchMove.bind(this)
|
||||
}
|
||||
|
||||
componentWillMount(){
|
||||
window.addEventListener("keyup", this.handleKeyUp, false);
|
||||
window.addEventListener("keyup", this.handleKeyUp, false)
|
||||
window.addEventListener("touchmove", this.handleTouchMove, false)
|
||||
}
|
||||
|
||||
componentWillUnmount(){
|
||||
window.removeEventListener("keyup", this.handleKeyUp, false);
|
||||
window.removeEventListener("keyup", this.handleKeyUp, false)
|
||||
window.removeEventListener("touchmove", this.handleTouchMove, false)
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
@ -51,9 +54,9 @@ class TrackList extends React.Component{
|
||||
var target = $(e.target)
|
||||
|
||||
// Wide screen, so no worries
|
||||
if ($(window).width() > 800){
|
||||
if (!this.props.slim_mode){
|
||||
return 'default'
|
||||
} else if (target.is('.state-icon') || target.closest('.state-icon').length > 0){
|
||||
} else if (target.is('.select-zone') || target.closest('.select-zone').length > 0){
|
||||
return 'mobile'
|
||||
}
|
||||
|
||||
@ -76,12 +79,52 @@ class TrackList extends React.Component{
|
||||
}
|
||||
|
||||
handleTouchStart(e,index){
|
||||
this._touching = true
|
||||
var target = $(e.target)
|
||||
if (target.hasClass('drag-zone')){
|
||||
$('body').addClass('touch-dragging')
|
||||
this.touch_dragging = true
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
handleTouchMove(e){
|
||||
if (this.touch_dragging){
|
||||
e.preventDefault()
|
||||
let touch = e.touches[0]
|
||||
let over = $(document.elementFromPoint(touch.clientX, touch.clientY))
|
||||
if (!over.is('.track')){
|
||||
over = over.closest('.track')
|
||||
}
|
||||
$(document).find('.touch-drag-hover').removeClass('touch-drag-hover')
|
||||
if (over.length > 0){
|
||||
over.addClass('touch-drag-hover')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleTouchEnd(e,index){
|
||||
this._touching = false
|
||||
this.handleMouseDown(e,index)
|
||||
if (this.touch_dragging){
|
||||
let touch = e.changedTouches[0]
|
||||
let over = $(document.elementFromPoint(touch.clientX, touch.clientY))
|
||||
if (!over.is('.track')){
|
||||
over = over.closest('.track')
|
||||
}
|
||||
if (over.length > 0){
|
||||
let siblings = over.parent().children('.track')
|
||||
let dropped_at = siblings.index(over) - 1
|
||||
|
||||
if (typeof(this.props.reorderTracks) !== 'undefined'){
|
||||
this.props.reorderTracks([index],dropped_at)
|
||||
this.render()
|
||||
}
|
||||
}
|
||||
|
||||
$(document).find('.touch-drag-hover').removeClass('touch-drag-hover')
|
||||
$('body').removeClass('touch-dragging')
|
||||
this.touch_dragging = false
|
||||
} else {
|
||||
this.handleMouseDown(e,index)
|
||||
}
|
||||
|
||||
// Prevent any event bubbling. This prevents clicks and mouse events
|
||||
// from also being fired
|
||||
@ -348,7 +391,8 @@ class TrackList extends React.Component{
|
||||
{
|
||||
this.state.tracks.map(
|
||||
(track, index) => {
|
||||
return <Track
|
||||
return (
|
||||
<Track
|
||||
show_source_icon={this.props.show_source_icon}
|
||||
key={track.key}
|
||||
track={track}
|
||||
@ -358,7 +402,9 @@ class TrackList extends React.Component{
|
||||
handleMouseDown={e => self.handleMouseDown(e, index)}
|
||||
handleTouchStart={e => self.handleTouchStart(e, index)}
|
||||
handleTouchEnd={e => self.handleTouchEnd(e, index)}
|
||||
handleContextMenu={e => self.handleContextMenu(e)} />
|
||||
handleContextMenu={e => self.handleContextMenu(e)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -377,8 +423,8 @@ class TrackList extends React.Component{
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {
|
||||
slim_mode: state.ui.slim_mode,
|
||||
dragger: state.ui.dragger,
|
||||
emulate_touch: state.ui.emulate_touch,
|
||||
current_track: state.ui.current_track,
|
||||
context_menu: state.ui.context_menu
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
|
||||
var uiActions = require('../../services/ui/actions')
|
||||
var mopidyActions = require('../../services/mopidy/actions')
|
||||
var lastfmActions = require('../../services/lastfm/actions')
|
||||
var helpers = require('../../helpers')
|
||||
|
||||
@ -912,6 +913,27 @@ export function getLibraryArtists(){
|
||||
}
|
||||
|
||||
|
||||
export function playArtistTopTracks(uri){
|
||||
return (dispatch, getState) => {
|
||||
const artists = getState().ui.artists
|
||||
|
||||
// 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.asURIs(artists[uri].tracks)
|
||||
dispatch(mopidyActions.playURIs(uris, uri))
|
||||
|
||||
// We need to load the artist's top tracks first
|
||||
} else {
|
||||
sendRequest( dispatch, getState, 'artists/'+ helpers.getFromUri('artistid', uri) +'/top-tracks?country='+getState().spotify.country )
|
||||
.then( response => {
|
||||
const uris = helpers.asURIs(response.tracks)
|
||||
dispatch(mopidyActions.playURIs(uris, uri))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* =============================================================== USER(S) ==============
|
||||
|
||||
@ -30,6 +30,13 @@ export function showContextMenu(data){
|
||||
}
|
||||
}
|
||||
|
||||
export function setSlimMode(slim_mode){
|
||||
return {
|
||||
type: 'SET_SLIM_MODE',
|
||||
slim_mode: slim_mode
|
||||
}
|
||||
}
|
||||
|
||||
export function hideContextMenu(){
|
||||
return {
|
||||
type: 'HIDE_CONTEXT_MENU'
|
||||
|
||||
@ -263,6 +263,8 @@ const UIMiddleware = (function(){
|
||||
case 'OPEN_MODAL':
|
||||
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
|
||||
|
||||
|
||||
@ -7,6 +7,9 @@ export default function reducer(ui = {}, action){
|
||||
case 'LAZY_LOADING':
|
||||
return Object.assign({}, ui, { lazy_loading: action.start });
|
||||
|
||||
case 'SET_SLIM_MODE':
|
||||
return Object.assign({}, ui, { slim_mode: action.slim_mode });
|
||||
|
||||
case 'DEBUG':
|
||||
return Object.assign({}, ui, { debug_response: action.response })
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import FontAwesome from 'react-fontawesome'
|
||||
|
||||
import Header from '../components/Header'
|
||||
import TrackList from '../components/TrackList'
|
||||
import Thumbnail from '../components/Thumbnail'
|
||||
import Parallax from '../components/Parallax'
|
||||
@ -12,7 +13,6 @@ import ArtistGrid from '../components/ArtistGrid'
|
||||
import FollowButton from '../components/FollowButton'
|
||||
import Dater from '../components/Dater'
|
||||
import LazyLoadListener from '../components/LazyLoadListener'
|
||||
import SidebarToggleButton from '../components/SidebarToggleButton'
|
||||
import ContextMenuTrigger from '../components/ContextMenuTrigger'
|
||||
|
||||
import * as helpers from '../helpers'
|
||||
@ -121,7 +121,7 @@ class Album extends React.Component{
|
||||
return (
|
||||
<div className="view album-view content-wrapper">
|
||||
|
||||
<SidebarToggleButton />
|
||||
{this.props.slim_mode ? <Header icon="cd" title="Album" handleContextMenuTrigger={e => this.handleContextMenu(e)} uiActions={this.props.uiActions} /> : null}
|
||||
|
||||
<div className="thumbnail-wrapper">
|
||||
<Thumbnail size="large" canZoom images={ this.props.album.images } />
|
||||
@ -132,13 +132,8 @@ class Album extends React.Component{
|
||||
<h1>{ this.props.album.name }</h1>
|
||||
|
||||
<ul className="details">
|
||||
<li className="has-tooltip">
|
||||
<FontAwesome name={helpers.sourceIcon( this.props.params.uri )} />
|
||||
<span className="tooltip">
|
||||
{helpers.uriSource( this.props.params.uri )} {this.props.album.album_type ? this.props.album.album_type : 'album'}
|
||||
</span>
|
||||
</li>
|
||||
{ artists.length > 0 ? <li><ArtistSentence artists={artists} /></li> : null }
|
||||
{ !this.props.slim_mode ? <li className="has-tooltip"><FontAwesome name={helpers.sourceIcon( this.props.params.uri )} /><span className="tooltip">{helpers.uriSource( this.props.params.uri )} {this.props.album.album_type ? this.props.album.album_type : 'album'}</span></li> : null }
|
||||
{ !this.props.slim_mode && artists.length > 0 ? <li><ArtistSentence artists={artists} /></li> : null }
|
||||
{ this.props.album.release_date ? <li><Dater type="date" data={ this.props.album.release_date } /></li> : null }
|
||||
<li>
|
||||
{ this.props.album.tracks_total ? this.props.album.tracks_total : '0' } tracks,
|
||||
@ -150,7 +145,7 @@ class Album extends React.Component{
|
||||
<div className="actions">
|
||||
<button className="primary" onClick={e => this.play()}>Play</button>
|
||||
{ helpers.uriSource(this.props.params.uri) == 'spotify' ? <FollowButton className="secondary" uri={this.props.params.uri} addText="Add to library" removeText="Remove from library" is_following={this.inLibrary()} /> : null }
|
||||
<ContextMenuTrigger onTrigger={e => this.handleContextMenu(e)} />
|
||||
{this.props.slim_mode ? null : <ContextMenuTrigger onTrigger={e => this.handleContextMenu(e)} />}
|
||||
</div>
|
||||
|
||||
<section className="list-wrapper">
|
||||
@ -172,6 +167,7 @@ class Album extends React.Component{
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {
|
||||
slim_mode: state.ui.slim_mode,
|
||||
load_queue: state.ui.load_queue,
|
||||
artists: state.ui.artists,
|
||||
album: (state.ui.albums && typeof(state.ui.albums[ownProps.params.uri]) !== 'undefined' ? state.ui.albums[ownProps.params.uri] : false ),
|
||||
|
||||
@ -31,7 +31,7 @@ class Artist extends React.Component{
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
this.loadArtist();
|
||||
this.loadArtist()
|
||||
}
|
||||
|
||||
componentWillReceiveProps( nextProps ){
|
||||
@ -201,7 +201,7 @@ class Artist extends React.Component{
|
||||
return (
|
||||
<div className="view artist-view">
|
||||
|
||||
<SidebarToggleButton />
|
||||
{this.props.slim_mode ? <Header className="overlay" icon="mic" title="Artist" handleContextMenuTrigger={e => this.handleContextMenu(e)} uiActions={this.props.uiActions} /> : null}
|
||||
|
||||
<div className="intro">
|
||||
|
||||
@ -213,7 +213,7 @@ class Artist extends React.Component{
|
||||
<div className="actions">
|
||||
{ can_play_radio ? <button className="primary" onClick={e => this.props.pusherActions.startRadio([this.props.artist.uri])}>Start radio</button> : <button className="primary" onClick={e => this.props.mopidyActions.playURIs(this.props.artist.albums_uris, this.props.artist.uri)}>Play all</button>}
|
||||
{ can_follow ? <FollowButton className="white" uri={this.props.params.uri} removeText="Remove from library" addText="Add to library" is_following={this.inLibrary()} /> : null}
|
||||
<ContextMenuTrigger className="white" onTrigger={e => this.handleContextMenu(e)} />
|
||||
{this.props.slim_mode ? null : <ContextMenuTrigger className="white" onTrigger={e => this.handleContextMenu(e)} />}
|
||||
</div>
|
||||
{ this.renderSubViewMenu() }
|
||||
</div>
|
||||
@ -260,6 +260,7 @@ class Artist extends React.Component{
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {
|
||||
slim_mode: state.ui.slim_mode,
|
||||
load_queue: state.ui.load_queue,
|
||||
artist: (state.ui.artists && typeof(state.ui.artists[ownProps.params.uri]) !== 'undefined' ? state.ui.artists[ownProps.params.uri] : false ),
|
||||
artists: (state.ui.artists ? state.ui.artists : []),
|
||||
|
||||
@ -43,7 +43,7 @@ class Debug extends React.Component{
|
||||
|
||||
var options = (
|
||||
<span>
|
||||
<button onClick={e => hashHistory.push(global.baseURL+'settings')}>
|
||||
<button className="no-hover" onClick={e => hashHistory.push(global.baseURL+'settings')}>
|
||||
<FontAwesome name="reply" />
|
||||
Back
|
||||
</button>
|
||||
|
||||
@ -12,7 +12,7 @@ import Dater from '../components/Dater'
|
||||
import ConfirmationButton from '../components/ConfirmationButton'
|
||||
import LazyLoadListener from '../components/LazyLoadListener'
|
||||
import FollowButton from '../components/FollowButton'
|
||||
import SidebarToggleButton from '../components/SidebarToggleButton'
|
||||
import Header from '../components/Header'
|
||||
import ContextMenuTrigger from '../components/ContextMenuTrigger'
|
||||
|
||||
import * as helpers from '../helpers'
|
||||
@ -115,7 +115,7 @@ class Playlist extends React.Component{
|
||||
<div className="actions">
|
||||
<button className="primary" onClick={ e => this.play() }>Play</button>
|
||||
<button className="secondary" onClick={ e => this.props.uiActions.openModal('edit_playlist', { uri: this.props.playlist.uri, name: this.props.playlist.name }) }>Edit</button>
|
||||
<ContextMenuTrigger onTrigger={e => this.handleContextMenu(e)} />
|
||||
{this.props.slim_mode ? null : <ContextMenuTrigger onTrigger={e => this.handleContextMenu(e)} />}
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -125,7 +125,7 @@ class Playlist extends React.Component{
|
||||
<div className="actions">
|
||||
<button className="primary" onClick={ e => this.play() }>Play</button>
|
||||
<button className="secondary" onClick={ e => this.props.uiActions.openModal('edit_playlist', { uri: this.props.playlist.uri, name: this.props.playlist.name, is_public: this.props.playlist.public, description: this.props.playlist.description }) }>Edit</button>
|
||||
<ContextMenuTrigger onTrigger={e => this.handleContextMenu(e)} />
|
||||
{this.props.slim_mode ? null : <ContextMenuTrigger onTrigger={e => this.handleContextMenu(e)} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -133,7 +133,7 @@ class Playlist extends React.Component{
|
||||
<div className="actions">
|
||||
<button className="primary" onClick={ e => this.play() }>Play</button>
|
||||
<FollowButton className="secondary" uri={this.props.playlist.uri} addText="Add to library" removeText="Remove from library" is_following={this.inLibrary()} />
|
||||
<ContextMenuTrigger onTrigger={e => this.handleContextMenu(e)} />
|
||||
{this.props.slim_mode ? null : <ContextMenuTrigger onTrigger={e => this.handleContextMenu(e)} />}
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -141,7 +141,7 @@ class Playlist extends React.Component{
|
||||
return (
|
||||
<div className="actions">
|
||||
<button className="primary" onClick={ e => this.play() }>Play</button>
|
||||
<ContextMenuTrigger onTrigger={e => this.handleContextMenu(e)} />
|
||||
{this.props.slim_mode ? null : <ContextMenuTrigger onTrigger={e => this.handleContextMenu(e)} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -166,8 +166,8 @@ class Playlist extends React.Component{
|
||||
|
||||
return (
|
||||
<div className="view playlist-view content-wrapper">
|
||||
|
||||
<SidebarToggleButton />
|
||||
|
||||
{this.props.slim_mode ? <Header icon="playlist" title="Playlist" handleContextMenuTrigger={e => this.handleContextMenu(e)} uiActions={this.props.uiActions} /> : null}
|
||||
|
||||
<div className="thumbnail-wrapper">
|
||||
<Thumbnail size="large" canZoom images={ this.props.playlist.images } />
|
||||
@ -178,13 +178,8 @@ class Playlist extends React.Component{
|
||||
{ this.props.playlist.description ? <h2 className="description grey-text" dangerouslySetInnerHTML={{__html: this.props.playlist.description}}></h2> : null }
|
||||
|
||||
<ul className="details">
|
||||
<li className="has-tooltip">
|
||||
<FontAwesome name={helpers.sourceIcon( this.props.params.uri )} />
|
||||
<span className="tooltip">
|
||||
{helpers.uriSource( this.props.params.uri )} playlist
|
||||
</span>
|
||||
</li>
|
||||
{ this.props.playlist.owner ? <li><Link to={'/user/'+this.props.playlist.owner.uri}>{this.props.playlist.owner.id}</Link></li> : null }
|
||||
{ !this.props.slim_mode ? <li className="has-tooltip"><FontAwesome name={helpers.sourceIcon( this.props.params.uri )} /><span className="tooltip">{helpers.uriSource( this.props.params.uri )} playlist</span></li> : null }
|
||||
{ this.props.playlist.owner && !this.props.slim_mode ? <li><Link to={'/user/'+this.props.playlist.owner.uri}>{this.props.playlist.owner.id}</Link></li> : null }
|
||||
{ this.props.playlist.followers ? <li>{this.props.playlist.followers.total.toLocaleString()} followers</li> : null }
|
||||
{ this.props.playlist.last_modified ? <li><Dater type="ago" data={this.props.playlist.last_modified} /></li> : null }
|
||||
<li>
|
||||
@ -216,6 +211,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
var uri = ownProps.params.uri
|
||||
uri = uri.replace(' ','%20')
|
||||
return {
|
||||
slim_mode: state.ui.slim_mode,
|
||||
load_queue: state.ui.load_queue,
|
||||
playlist: (state.ui.playlists && typeof(state.ui.playlists[uri]) !== 'undefined' ? state.ui.playlists[uri] : false ),
|
||||
library_playlists: state.ui.library_playlists,
|
||||
|
||||
@ -39,7 +39,7 @@ class QueueHistory extends React.Component{
|
||||
render(){
|
||||
var options = (
|
||||
<span>
|
||||
<button onClick={e => hashHistory.push(global.baseURL+'queue')}>
|
||||
<button className="no-hover" onClick={e => hashHistory.push(global.baseURL+'queue')}>
|
||||
<FontAwesome name="reply" />
|
||||
Back
|
||||
</button>
|
||||
@ -47,7 +47,7 @@ class QueueHistory extends React.Component{
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="view queue-history-view">
|
||||
<div className="view queue-history-view">
|
||||
<Header icon="play" title="Playback history" options={options} uiActions={this.props.uiActions} />
|
||||
|
||||
<section className="content-wrapper list-wrapper">
|
||||
|
||||
@ -213,11 +213,11 @@ class Settings extends React.Component{
|
||||
|
||||
var options = (
|
||||
<span>
|
||||
<button onClick={e => hashHistory.push(global.baseURL+'settings/debug')}>
|
||||
<button className="no-hover" onClick={e => hashHistory.push(global.baseURL+'settings/debug')}>
|
||||
<FontAwesome name="flask" />
|
||||
Debug
|
||||
</button>
|
||||
<a className="button" href="https://github.com/jaedb/Iris/wiki" target="_blank">
|
||||
<a className="no-hover button" href="https://github.com/jaedb/Iris/wiki" target="_blank">
|
||||
<FontAwesome name="question" />
|
||||
Help
|
||||
</a>
|
||||
|
||||
@ -88,28 +88,44 @@ class LibraryPlaylists extends React.Component{
|
||||
}
|
||||
|
||||
if( this.props.view == 'list' ){
|
||||
var columns = [
|
||||
{
|
||||
label: 'Name',
|
||||
name: 'name'
|
||||
},
|
||||
{
|
||||
label: 'Owner',
|
||||
name: 'owner'
|
||||
},
|
||||
{
|
||||
label: 'Source',
|
||||
name: 'source'
|
||||
},
|
||||
{
|
||||
label: 'Tracks',
|
||||
name: 'tracks_total'
|
||||
},
|
||||
{
|
||||
label: 'Editable',
|
||||
name: 'can_edit'
|
||||
}
|
||||
]
|
||||
if (this.props.slim_mode){
|
||||
var columns = [
|
||||
{
|
||||
label: 'Name',
|
||||
name: 'name'
|
||||
},
|
||||
{
|
||||
label: 'Tracks',
|
||||
name: 'tracks_total'
|
||||
}
|
||||
]
|
||||
} else {
|
||||
var columns = [
|
||||
{
|
||||
label: 'Name',
|
||||
name: 'name'
|
||||
},
|
||||
{
|
||||
label: 'Owner',
|
||||
name: 'owner'
|
||||
},
|
||||
{
|
||||
label: 'Source',
|
||||
name: 'source'
|
||||
},
|
||||
{
|
||||
label: 'Tracks',
|
||||
name: 'tracks_total'
|
||||
},
|
||||
{
|
||||
label: 'Editable',
|
||||
name: 'can_edit'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
console.log(columns)
|
||||
|
||||
return (
|
||||
<section className="content-wrapper">
|
||||
<List
|
||||
@ -212,6 +228,7 @@ class LibraryPlaylists extends React.Component{
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {
|
||||
slim_mode: state.ui.slim_mode,
|
||||
load_queue: state.ui.load_queue,
|
||||
me_id: (state.spotify.me ? state.spotify.me.id : (state.ui.config && state.ui.config.spotify_username ? state.ui.config.spotify_username : false)),
|
||||
view: state.ui.library_playlists_view,
|
||||
|
||||
@ -1,4 +1,12 @@
|
||||
|
||||
@include responsive( $bp_medium ){
|
||||
body.context-menu-open {
|
||||
.body {
|
||||
@include blur(10px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#context-menu {
|
||||
position: fixed;
|
||||
z-index: 99;
|
||||
@ -228,7 +236,7 @@
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: $overlay_dark;
|
||||
background: $overlay_light;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
||||
@ -64,6 +64,12 @@ main {
|
||||
-moz-filter: invert(1);
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
.context-menu-trigger {
|
||||
.dot {
|
||||
background: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include responsive( $bp_medium ){
|
||||
|
||||
@ -22,13 +22,21 @@
|
||||
&.selected {
|
||||
background: $yellow !important;
|
||||
border-color: darken($yellow,10%);
|
||||
|
||||
.select-zone {
|
||||
&::after {
|
||||
background: $dark_grey;
|
||||
border-color: $dark_grey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.playing {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.state-icon {
|
||||
.select-zone,
|
||||
.drag-zone {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -79,7 +87,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
:root .dragging &.hover {
|
||||
:root .dragging &.hover,
|
||||
&.touch-drag-hover {
|
||||
border-top: 3px solid $blue;
|
||||
margin-top: -3px;
|
||||
background: transparent;
|
||||
@ -211,30 +220,51 @@
|
||||
float: none;
|
||||
}
|
||||
|
||||
.state-icon {
|
||||
.select-zone {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 48px;
|
||||
font-size: 10px;
|
||||
width: 38px;
|
||||
font-size: 8px;
|
||||
display: block;
|
||||
|
||||
.fa {
|
||||
position: absolute;
|
||||
top: 19px;
|
||||
left: 17px;
|
||||
top: 20px;
|
||||
left: 13px;
|
||||
pointer-events: none;
|
||||
color: $white;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&:after {
|
||||
display: block;
|
||||
content: '';
|
||||
border: 1px solid $darkest_grey;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
border-radius: 50%;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
left: 15px;
|
||||
top: 16px;
|
||||
left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.drag-zone {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 38px;
|
||||
height: 100%;
|
||||
width: 38px;
|
||||
font-size: 14px;
|
||||
display: block;
|
||||
|
||||
.fa {
|
||||
position: absolute;
|
||||
top: 17px;
|
||||
left: 8px;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
@ -253,7 +283,7 @@
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&:not(.name):not(.duration){
|
||||
&:not(.name):not(.duration):not(.album){
|
||||
color: $mid_grey;
|
||||
display: inline;
|
||||
width: auto !important;
|
||||
@ -261,10 +291,6 @@
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
&:not(.name):not(.duration):not(:nth-child(2)){
|
||||
@include bullet();
|
||||
}
|
||||
|
||||
&.followers_total:after {
|
||||
content: ' followers';
|
||||
}
|
||||
@ -277,6 +303,7 @@
|
||||
content: '% popularity';
|
||||
}
|
||||
|
||||
&.album,
|
||||
&.can_edit,
|
||||
&.release_date {
|
||||
display: none;
|
||||
@ -306,8 +333,11 @@
|
||||
}
|
||||
|
||||
&.track-list {
|
||||
.list-item {
|
||||
padding: 7px 30px 7px 48px !important;
|
||||
.list-item.has-drag-zone {
|
||||
padding: 8px 30px 8px 78px !important;
|
||||
}
|
||||
.list-item:not(.has-drag-zone){
|
||||
padding: 8px 30px 8px 38px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ body {
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
|
||||
&.touch-dragging,
|
||||
&.modal-open {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ $blue: #32b5f2;
|
||||
$yellow: #FFF39C;
|
||||
$orange: #f16f19;
|
||||
$overlay_dark: rgba(0, 0, 0, 0.88);
|
||||
$overlay_light: rgba(255, 255, 255, 0.70);
|
||||
|
||||
$bp_wide: 1000px;
|
||||
$bp_medium: 800px;
|
||||
|
||||
@ -139,8 +139,6 @@
|
||||
}
|
||||
|
||||
&.about {
|
||||
padding: 0 20px 20px 20px;
|
||||
|
||||
.col.w40,
|
||||
.col.w60 {
|
||||
@include clearfix();
|
||||
|
||||
Reference in New Issue
Block a user