Merge branch 'feature/mobile'

This commit is contained in:
James Barnsley
2017-07-21 21:19:56 +12:00
26 changed files with 320 additions and 131 deletions

View File

@ -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",

View File

@ -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
View File

@ -53,6 +53,7 @@ var initialState = {
autocomplete_results: {}
},
ui: {
slim_mode: false,
current_tracklist: [],
current_tltrack: false,
notifications: [],

View File

@ -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}

View File

@ -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>

View File

@ -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>

View File

@ -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
}

View File

@ -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>
);

View File

@ -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
}

View File

@ -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) ==============

View File

@ -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'

View File

@ -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

View File

@ -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 })

View File

@ -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,&nbsp;
@ -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 ),

View File

@ -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 : []),

View File

@ -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" />&nbsp;
Back
</button>

View File

@ -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,

View File

@ -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" />&nbsp;
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">

View File

@ -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" />&nbsp;
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" />&nbsp;
Help
</a>

View File

@ -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,

View File

@ -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;
}

View File

@ -64,6 +64,12 @@ main {
-moz-filter: invert(1);
filter: invert(1);
}
.context-menu-trigger {
.dot {
background: $white;
}
}
}
@include responsive( $bp_medium ){

View File

@ -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;
}
}
}

View File

@ -14,6 +14,7 @@ body {
overflow-y: scroll;
overflow-x: hidden;
&.touch-dragging,
&.modal-open {
overflow-y: hidden;
}

View File

@ -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;

View File

@ -139,8 +139,6 @@
}
&.about {
padding: 0 20px 20px 20px;
.col.w40,
.col.w60 {
@include clearfix();