Playlist and album views

This commit is contained in:
James Barnsley
2016-10-31 16:49:42 +13:00
parent 869d73231b
commit bab6c66852
20 changed files with 581 additions and 143 deletions

View File

@ -2919,18 +2919,29 @@ main h1 {
font-size: 40px;
}
main h2 {
font-weight: 500;
font-size: 30px;
}
main h3 {
font-weight: 100;
font-style: italic;
font-size: 20px;
}
main h4 {
font-size: 14px;
margin-bottom: 14px;
text-transform: uppercase;
}
main h3.underline {
main h4.underline {
padding-bottom: 4px;
border-bottom: 1px solid #000000;
}
main h3:not(:first-child) {
main h4:not(:first-child) {
padding-top: 40px;
}
@ -3665,7 +3676,7 @@ main header h1 {
left: 0;
bottom: 0;
width: 300px;
background: #DDDDDD;
background: #EEEEEE;
}
.album-view .intro .thumbnail {
@ -3673,12 +3684,132 @@ main header h1 {
margin: 0 auto;
}
.album-view .intro h1 {
padding-top: 30px;
.album-view .intro .details {
padding: 30px;
color: #AAAAAA;
text-transform: uppercase;
}
.album-view .intro .artist-grid {
padding: 0;
margin: 0;
}
.album-view .intro .artist-grid .grid-item {
margin: 0;
width: 50%;
position: relative;
}
.album-view .intro .artist-grid .grid-item .thumbnail .image {
background-color: #888888;
}
.album-view .intro .artist-grid .grid-item .name {
-webkit-transition: all 0.2s ease-in-out;
-moz-transition: all 0.2s ease-in-out;
-o-transition: all 0.2s ease-in-out;
transition: all 0.2s ease-in-out;
position: absolute;
top: 35%;
left: 0;
right: 0;
text-align: center;
color: #FFFFFF;
opacity: 0;
z-index: 2;
font-weight: bold;
}
.album-view .intro .artist-grid .grid-item:hover .name {
opacity: 1;
}
.album-view .main {
padding-left: 300px;
}
.album-view .main .title {
padding: 30px 40px;
}
.album-view .main .title h3 {
color: #AAAAAA;
}
.album-view .main .title h3 a:hover {
color: #777777;
}
.playlist-view .intro {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 300px;
background: #EEEEEE;
}
.playlist-view .intro .thumbnail {
float: none;
margin: 0 auto;
}
.playlist-view .intro .details {
padding: 30px;
color: #AAAAAA;
text-transform: uppercase;
}
.playlist-view .intro .artist-grid {
padding: 0;
margin: 0;
}
.playlist-view .intro .artist-grid .grid-item {
margin: 0;
width: 50%;
position: relative;
}
.playlist-view .intro .artist-grid .grid-item .thumbnail .image {
background-color: #888888;
}
.playlist-view .intro .artist-grid .grid-item .name {
-webkit-transition: all 0.2s ease-in-out;
-moz-transition: all 0.2s ease-in-out;
-o-transition: all 0.2s ease-in-out;
transition: all 0.2s ease-in-out;
position: absolute;
top: 35%;
left: 0;
right: 0;
text-align: center;
color: #FFFFFF;
opacity: 0;
z-index: 2;
font-weight: bold;
}
.playlist-view .intro .artist-grid .grid-item:hover .name {
opacity: 1;
}
.playlist-view .main {
padding-left: 300px;
}
.playlist-view .main .title {
padding: 30px 40px;
}
.playlist-view .main .title h3 {
color: #AAAAAA;
}
.playlist-view .main .title h3 a:hover {
color: #777777;
}
/*# sourceMappingURL=app.css.map*/

File diff suppressed because one or more lines are too long

View File

@ -5958,6 +5958,13 @@
_reactRouter.browserHistory.push(this.props.link);
}
}
}, {
key: 'renderThumbnail',
value: function renderThumbnail() {
if (this.props.item.images) return _react2.default.createElement(_Thumbnail2.default, { size: 'medium', images: this.props.item.images });
if (this.props.item.icons) return _react2.default.createElement(_Thumbnail2.default, { size: 'medium', images: this.props.item.icons });
return _react2.default.createElement(_Thumbnail2.default, { size: 'medium', images: [] });
}
}, {
key: 'render',
value: function render() {
@ -5973,8 +5980,7 @@
{ className: 'grid-item', onClick: function onClick(e) {
return _this2.handleClick(e);
} },
item.images ? _react2.default.createElement(_Thumbnail2.default, { size: 'medium', images: item.images }) : null,
item.icons ? _react2.default.createElement(_Thumbnail2.default, { size: 'medium', images: item.icons }) : null,
this.renderThumbnail(),
_react2.default.createElement(
'div',
{ className: 'name' },
@ -7514,9 +7520,15 @@
'div',
{ className: className },
this.props.albums.map(function (album, index) {
var link = album.uri;
if (album.album) link = album.album.uri;
return _react2.default.createElement(_GridItem2.default, { item: album, key: index, link: '/album/' + link });
// handle nested album objects (as in Album Library)
if (album.album) {
var flatAlbum = album.album;
flatAlbum.added_at = album.added_at;
album = flatAlbum;
}
return _react2.default.createElement(_GridItem2.default, { item: album, key: index, link: '/album/' + album.uri });
})
);
}
@ -10581,7 +10593,7 @@
/* 95 */
/***/ function(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_RESULT__;var require;/* WEBPACK VAR INJECTION */(function(process) {/** @license MIT License (c) copyright 2010-2014 original author or authors */
var require;var __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(process) {/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
@ -11288,6 +11300,10 @@
var _AlbumLink2 = _interopRequireDefault(_AlbumLink);
var _Dater = __webpack_require__(379);
var _Dater2 = _interopRequireDefault(_Dater);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
@ -11329,36 +11345,16 @@
// notify our tracklist
this.props.handleContextMenu(e);
}
}, {
key: 'formatDuration',
value: function formatDuration() {
if (typeof this.props.track.duration_ms !== 'undefined') {
var ms = this.props.track.duration_ms;
} else if (typeof this.props.track.length !== 'undefined') {
var ms = this.props.track.length;
} else {
return null;
}
var time = new Date(ms);
var min = time.getMinutes();
var sec = time.getSeconds();
if (sec < 10) sec = '0' + sec;
return min + ':' + sec;
}
}, {
key: 'render',
value: function render() {
var _this2 = this;
var className = 'list-item track';
if (typeof this.props.track.selected !== 'undefined' && this.props.track.selected) {
className += ' selected';
}
var track = this.props.track;
if (this.props.track.playing) {
className += ' playing';
}
var className = 'list-item track';
if (typeof track.selected !== 'undefined' && track.selected) className += ' selected';
if (track.playing) className += ' playing';
return _react2.default.createElement(
'div',
@ -11377,22 +11373,23 @@
_react2.default.createElement(
'span',
{ className: 'col name' },
this.props.track.name
track.name
),
_react2.default.createElement(
'span',
{ className: 'col artists' },
this.props.track.artists ? _react2.default.createElement(_ArtistSentence2.default, { artists: this.props.track.artists }) : null
track.artists ? _react2.default.createElement(_ArtistSentence2.default, { artists: track.artists }) : null
),
_react2.default.createElement(
'span',
{ className: 'col album' },
this.props.track.album ? _react2.default.createElement(_AlbumLink2.default, { album: this.props.track.album }) : null
track.album ? _react2.default.createElement(_AlbumLink2.default, { album: track.album }) : null
),
_react2.default.createElement(
'span',
{ className: 'col duration' },
this.formatDuration()
track.duration_ms ? _react2.default.createElement(_Dater2.default, { type: 'length', data: track.duration_ms }) : null,
track.length ? _react2.default.createElement(_Dater2.default, { type: 'length', data: track.length }) : null
)
);
}
@ -19117,6 +19114,8 @@
});
case 'MOPIDY_CURRENTTLTRACK':
if (!action.data) return mopidy;
var tracks = [];
Object.assign(tracks, mopidy.tracks);
@ -19566,6 +19565,14 @@
var _ArtistSentence2 = _interopRequireDefault(_ArtistSentence);
var _ArtistGrid = __webpack_require__(155);
var _ArtistGrid2 = _interopRequireDefault(_ArtistGrid);
var _Dater = __webpack_require__(379);
var _Dater2 = _interopRequireDefault(_Dater);
var _actions = __webpack_require__(11);
var spotifyActions = _interopRequireWildcard(_actions);
@ -19611,32 +19618,66 @@
this.props.spotifyActions.getAlbum(nextProps.params.uri);
}
}
}, {
key: 'totalTime',
value: function totalTime() {
if (!this.props.spotify.album) return null;
var tracks = this.props.spotify.tracks.items;
var duration = 0;
for (var i = 0; i < tracks.length; i++) {
duration += tracks[i].duration_ms;
}
return duration;
}
}, {
key: 'render',
value: function render() {
if (this.props.spotify.album) {
var album = this.props.spotify.album;
return _react2.default.createElement(
'div',
{ className: 'view album-view' },
_react2.default.createElement(
'div',
{ className: 'intro' },
_react2.default.createElement(_Thumbnail2.default, { size: 'large', images: this.props.spotify.album.images })
_react2.default.createElement(_Thumbnail2.default, { size: 'large', images: album.images }),
_react2.default.createElement(_ArtistGrid2.default, { artists: album.artists }),
_react2.default.createElement(
'div',
{ className: 'details' },
_react2.default.createElement(
'div',
null,
album.tracks.total,
' tracks, ',
this.totalTime
),
_react2.default.createElement(
'div',
null,
'Released ',
_react2.default.createElement(_Dater2.default, { type: 'date', data: album.release_date })
)
)
),
_react2.default.createElement(
'div',
{ className: 'main' },
_react2.default.createElement(
'h1',
null,
this.props.spotify.album.name
'div',
{ className: 'title' },
_react2.default.createElement(
'h1',
null,
album.name
),
_react2.default.createElement(
'h3',
null,
_react2.default.createElement(_ArtistSentence2.default, { artists: album.artists })
)
),
_react2.default.createElement(
'h3',
null,
_react2.default.createElement(_ArtistSentence2.default, { artists: this.props.spotify.album.artists })
),
_react2.default.createElement(_TrackList2.default, { tracks: this.props.spotify.album.tracks.items })
_react2.default.createElement(_TrackList2.default, { tracks: album.tracks.items })
)
);
}
@ -19805,12 +19846,6 @@
null,
this.props.children
),
_react2.default.createElement(
'footer',
null,
'Iris by James Barnsley \xA0|\xA0 v',
this.props.pusher.version.current
),
this.props.ui.context_menu.test,
_react2.default.createElement(_ContextMenu2.default, { state: this.props.ui.context_menu })
);
@ -19954,7 +19989,7 @@
'div',
{ className: 'col w70' },
_react2.default.createElement(
'h3',
'h4',
{ className: 'left-padding' },
'Top tracks'
),
@ -19965,7 +20000,7 @@
'div',
{ className: 'col w25' },
_react2.default.createElement(
'h3',
'h4',
null,
'Related artists'
),
@ -19973,7 +20008,7 @@
),
_react2.default.createElement('div', { className: 'cf' }),
_react2.default.createElement(
'h3',
'h4',
{ className: 'left-padding' },
'Albums'
),
@ -20029,9 +20064,13 @@
var _TrackList2 = _interopRequireDefault(_TrackList);
var _Header = __webpack_require__(18);
var _Thumbnail = __webpack_require__(31);
var _Header2 = _interopRequireDefault(_Header);
var _Thumbnail2 = _interopRequireDefault(_Thumbnail);
var _Dater = __webpack_require__(379);
var _Dater2 = _interopRequireDefault(_Dater);
var _actions = __webpack_require__(11);
@ -20083,14 +20122,42 @@
value: function render() {
if (this.props.spotify.playlist) {
var playlist = this.props.spotify.playlist;
var context = null;
if (this.props.spotify.playlist.owner.id == this.props.spotify.me.id) context = 'editable-playlist';
if (playlist.owner.id == this.props.spotify.me.id) context = 'editable-playlist';
return _react2.default.createElement(
'div',
{ className: 'view playlist-view' },
_react2.default.createElement(_Header2.default, { icon: 'playlist', title: this.props.spotify.playlist.name }),
_react2.default.createElement(_TrackList2.default, { context: context, tracks: this.props.spotify.playlist.tracks.items })
_react2.default.createElement(
'div',
{ className: 'intro' },
_react2.default.createElement(_Thumbnail2.default, { size: 'large', images: playlist.images }),
_react2.default.createElement(
'div',
{ className: 'details' },
_react2.default.createElement(
'div',
null,
playlist.tracks.total,
' tracks'
)
)
),
_react2.default.createElement(
'div',
{ className: 'main' },
_react2.default.createElement(
'div',
{ className: 'title' },
_react2.default.createElement(
'h1',
null,
playlist.name
)
),
_react2.default.createElement(_TrackList2.default, { context: context, tracks: playlist.tracks.items })
)
);
}
return null;
@ -20426,7 +20493,7 @@
'section',
null,
_react2.default.createElement(
'h3',
'h4',
{ className: 'underline' },
'Mopidy'
),
@ -20480,7 +20547,7 @@
)
),
_react2.default.createElement(
'h3',
'h4',
{ className: 'underline' },
'Pusher'
),
@ -20533,7 +20600,7 @@
)
),
_react2.default.createElement(
'h3',
'h4',
{ className: 'underline' },
'Spotify'
),
@ -20589,7 +20656,7 @@
),
_react2.default.createElement(_SpotifyAuthenticationFrame2.default, null),
_react2.default.createElement(
'h3',
'h4',
{ className: 'underline' },
'Advanced'
),
@ -48675,6 +48742,85 @@
module.exports = isPlainObject;
/***/ },
/* 371 */,
/* 372 */,
/* 373 */,
/* 374 */,
/* 375 */,
/* 376 */,
/* 377 */,
/* 378 */,
/* 379 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _react = __webpack_require__(2);
var _react2 = _interopRequireDefault(_react);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var Dater = function (_React$Component) {
_inherits(Dater, _React$Component);
function Dater(props) {
_classCallCheck(this, Dater);
return _possibleConstructorReturn(this, (Dater.__proto__ || Object.getPrototypeOf(Dater)).call(this, props));
}
_createClass(Dater, [{
key: 'calculate',
value: function calculate() {
switch (this.props.type) {
case 'length':
var time = new Date(this.props.data);
var min = time.getMinutes();
var sec = time.getSeconds();
if (sec < 10) sec = '0' + sec;
return min + ':' + sec;
break;
case 'date':
// TODO: nice date formatting
return this.props.data;
break;
default:
return null;
}
}
}, {
key: 'render',
value: function render() {
return _react2.default.createElement(
'span',
{ className: 'dater' },
this.calculate()
);
}
}]);
return Dater;
}(_react2.default.Component);
exports.default = Dater;
/***/ }
/******/ ])));
//# sourceMappingURL=app.js.map

File diff suppressed because one or more lines are too long

View File

@ -17,9 +17,15 @@ export default class AlbumGrid extends React.Component{
{
this.props.albums.map(
(album, index) => {
var link = album.uri;
if( album.album ) link = album.album.uri;
return <GridItem item={album} key={index} link={'/album/'+link} />
// handle nested album objects (as in Album Library)
if( album.album ){
var flatAlbum = album.album;
flatAlbum.added_at = album.added_at;
album = flatAlbum;
}
return <GridItem item={album} key={index} link={'/album/'+album.uri} />
}
)
}

View File

@ -28,7 +28,7 @@ class Artist extends React.Component{
if( this.props.spotify.artist ){
return (
<div>
<h3>{ this.props.spotify.artist.name }</h3>
<h4>{ this.props.spotify.artist.name }</h4>
</div>
);
}

34
src/js/components/Dater.js Executable file
View File

@ -0,0 +1,34 @@
import React, { PropTypes } from 'react'
export default class Dater extends React.Component{
constructor(props) {
super(props);
}
calculate(){
switch(this.props.type){
case 'length':
var time = new Date( this.props.data );
var min = time.getMinutes();
var sec = time.getSeconds();
if( sec < 10 ) sec = '0'+sec;
return min+':'+sec;
break
case 'date':
// TODO: nice date formatting
return this.props.data
break
default:
return null
}
}
render(){
return <span className="dater">{ this.calculate() }</span>
}
}

View File

@ -17,6 +17,12 @@ export default class GridItem extends React.Component{
}
}
renderThumbnail(){
if( this.props.item.images ) return <Thumbnail size="medium" images={this.props.item.images} />
if( this.props.item.icons ) return <Thumbnail size="medium" images={this.props.item.icons} />
return <Thumbnail size="medium" images={[]} />
}
render(){
var item = this.props.item;
if( typeof(item.album) !== 'undefined' ){
@ -25,8 +31,7 @@ export default class GridItem extends React.Component{
}
return (
<div className="grid-item" onClick={ (e) => this.handleClick(e) }>
{ item.images ? <Thumbnail size="medium" images={item.images} /> : null }
{ item.icons ? <Thumbnail size="medium" images={item.icons} /> : null }
{ this.renderThumbnail() }
<div className="name">{ item.name }</div>
<div className="secondary">
{ item.artists ? <ArtistSentence artists={ item.artists } /> : null }

View File

@ -3,6 +3,7 @@ import React, { PropTypes } from 'react'
import FontAwesome from 'react-fontawesome'
import ArtistSentence from './ArtistSentence'
import AlbumLink from './AlbumLink'
import Dater from './Dater'
export default class Track extends React.Component{
@ -31,32 +32,12 @@ export default class Track extends React.Component{
this.props.handleContextMenu(e);
}
formatDuration(){
if( typeof(this.props.track.duration_ms) !== 'undefined' ){
var ms = this.props.track.duration_ms;
}else if( typeof(this.props.track.length) !== 'undefined' ){
var ms = this.props.track.length;
}else{
return null;
}
var time = new Date(ms);
var min = time.getMinutes();
var sec = time.getSeconds();
if( sec < 10 ) sec = '0'+sec;
return min+':'+sec;
}
render(){
var track = this.props.track;
var className = 'list-item track';
if( typeof(this.props.track.selected) !== 'undefined' && this.props.track.selected ){
className += ' selected';
}
if( this.props.track.playing ){
className += ' playing';
}
if( typeof(track.selected) !== 'undefined' && track.selected ) className += ' selected';
if( track.playing ) className += ' playing';
return (
<div
@ -66,16 +47,17 @@ export default class Track extends React.Component{
onContextMenu={ (e) => this.handleContextMenu(e) }>
{ this.props.track.selected ? <FontAwesome name="check" className="select-state" fixedWidth /> : null }
<span className="col name">
{this.props.track.name}
{track.name}
</span>
<span className="col artists">
{ this.props.track.artists ? <ArtistSentence artists={this.props.track.artists} /> : null }
{ track.artists ? <ArtistSentence artists={track.artists} /> : null }
</span>
<span className="col album">
{ this.props.track.album ? <AlbumLink album={this.props.track.album} /> : null }
{ track.album ? <AlbumLink album={track.album} /> : null }
</span>
<span className="col duration">
{ this.formatDuration() }
{ track.duration_ms ? <Dater type="length" data={track.duration_ms} /> : null }
{ track.length ? <Dater type="length" data={track.length} /> : null }
</span>
</div>
);

View File

@ -53,6 +53,8 @@ export default function reducer(mopidy = {}, action){
});
case 'MOPIDY_CURRENTTLTRACK':
if( !action.data ) return mopidy;
var tracks = [];
Object.assign(tracks, mopidy.tracks);

View File

@ -7,6 +7,8 @@ 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 Dater from '../components/Dater'
import * as spotifyActions from '../services/spotify/actions'
import * as mopidyActions from '../services/mopidy/actions'
@ -29,17 +31,35 @@ class Album extends React.Component{
}
}
totalTime(){
if( !this.props.spotify.album ) return null
var tracks = this.props.spotify.tracks.items;
var duration = 0;
for( var i = 0; i < tracks.length; i++ ){
duration += tracks[i].duration_ms;
}
return duration;
}
render(){
if( this.props.spotify.album ){
var album = this.props.spotify.album;
return (
<div className="view album-view">
<div className="intro">
<Thumbnail size="large" images={ this.props.spotify.album.images } />
<Thumbnail size="large" images={ album.images } />
<ArtistGrid artists={ album.artists } />
<div className="details">
<div>{ album.tracks.total } tracks, { this.totalTime }</div>
<div>Released <Dater type="date" data={ album.release_date } /></div>
</div>
</div>
<div className="main">
<h1>{ this.props.spotify.album.name }</h1>
<h3><ArtistSentence artists={ this.props.spotify.album.artists } /></h3>
<TrackList tracks={this.props.spotify.album.tracks.items} />
<div className="title">
<h1>{ album.name }</h1>
<h3><ArtistSentence artists={ album.artists } /></h3>
</div>
<TrackList tracks={ album.tracks.items } />
</div>
</div>
);

View File

@ -77,9 +77,6 @@ class App extends React.Component{
<main>
{this.props.children}
</main>
<footer>
Iris by James Barnsley &nbsp;|&nbsp; v{ this.props.pusher.version.current }
</footer>
{ this.props.ui.context_menu.test }
<ContextMenu state={this.props.ui.context_menu} />
</div>

View File

@ -43,20 +43,20 @@ class Artist extends React.Component{
</div>
<div className="col w70">
<h3 className="left-padding">Top tracks</h3>
<h4 className="left-padding">Top tracks</h4>
{ this.props.spotify.artist.tracks ? <TrackList tracks={ this.props.spotify.artist.tracks } /> : null }
</div>
<div className="col w5"></div>
<div className="col w25">
<h3>Related artists</h3>
<h4>Related artists</h4>
{ this.props.spotify.artist.related_artists ? <ArtistList artists={ this.props.spotify.artist.related_artists.slice(0,6) } /> : null }
</div>
<div className="cf"></div>
<h3 className="left-padding">Albums</h3>
<h4 className="left-padding">Albums</h4>
{ this.props.spotify.artist_albums ? <AlbumGrid className="no-top-padding" albums={ this.props.spotify.artist_albums.items } /> : null }
</div>
);

View File

@ -4,7 +4,8 @@ import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import TrackList from '../components/TrackList'
import Header from '../components/Header'
import Thumbnail from '../components/Thumbnail'
import Dater from '../components/Dater'
import * as spotifyActions from '../services/spotify/actions'
import * as mopidyActions from '../services/mopidy/actions'
@ -30,13 +31,24 @@ class Playlist extends React.Component{
render(){
if( this.props.spotify.playlist ){
var playlist = this.props.spotify.playlist;
var context = null;
if( this.props.spotify.playlist.owner.id == this.props.spotify.me.id ) context = 'editable-playlist'
if( playlist.owner.id == this.props.spotify.me.id ) context = 'editable-playlist'
return (
<div className="view playlist-view">
<Header icon="playlist" title={ this.props.spotify.playlist.name } />
<TrackList context={context} tracks={this.props.spotify.playlist.tracks.items} />
<div className="intro">
<Thumbnail size="large" images={ playlist.images } />
<div className="details">
<div>{ playlist.tracks.total } tracks</div>
</div>
</div>
<div className="main">
<div className="title">
<h1>{ playlist.name }</h1>
</div>
<TrackList context={context} tracks={ playlist.tracks.items } />
</div>
</div>
);
}

View File

@ -65,7 +65,7 @@ class Settings extends React.Component{
<section>
<h3 className="underline">Mopidy</h3>
<h4 className="underline">Mopidy</h4>
<form onSubmit={(e) => this.setMopidyConfig(e)}>
<label>
<div className="label">Host</div>
@ -88,7 +88,7 @@ class Settings extends React.Component{
<button type="submit" className="secondary">Apply</button>
</form>
<h3 className="underline">Pusher</h3>
<h4 className="underline">Pusher</h4>
<form>
<label>
<div className="label">Username</div>
@ -112,7 +112,7 @@ class Settings extends React.Component{
</label>
</form>
<h3 className="underline">Spotify</h3>
<h4 className="underline">Spotify</h4>
<form onSubmit={(e) => this.setSpotifyConfig(e)}>
<label>
<div className="label">Country</div>
@ -138,7 +138,7 @@ class Settings extends React.Component{
<SpotifyAuthenticationFrame />
<h3 className="underline">Advanced</h3>
<h4 className="underline">Advanced</h4>
<label>
<div className="label">Version</div>
<div className="input">

View File

@ -17,4 +17,5 @@
@import 'views/artist';
@import 'views/album';
@import 'views/playlist';

View File

@ -41,7 +41,18 @@ main {
font-size: 40px;
}
h2 {
font-weight: 500;
font-size: 30px;
}
h3 {
font-weight: 100;
font-style: italic;
font-size: 20px;
}
h4 {
font-size: 14px;
margin-bottom: 14px;
text-transform: uppercase;

View File

@ -1,5 +1,6 @@
$turquoise: #08d58f;
$faint_grey: #EEEEEE;
$light_grey: #DDDDDD;
$mid_grey: #AAAAAA;
$dark_grey: #333333;

View File

@ -6,19 +6,67 @@
left: 0;
bottom: 0;
width: 300px;
background: $light_grey;
background: $faint_grey;
.thumbnail {
float: none;
margin: 0 auto;
}
h1 {
padding-top: 30px;
.details {
padding: 30px;
color: $mid_grey;
text-transform: uppercase;
}
.artist-grid {
padding: 0;
margin: 0;
.grid-item {
margin: 0;
width: 50%;
position: relative;
.thumbnail .image {
background-color: $secondary_grey;
}
.name {
@include animate();
position: absolute;
top: 35%;
left: 0;
right: 0;
text-align: center;
color: #FFFFFF;
opacity: 0;
z-index: 2;
font-weight: bold;
}
&:hover {
.name {
opacity: 1;
}
}
}
}
}
.main {
.title {
padding: 30px 40px;
h3 {
color: $mid_grey;
a:hover {
color: darken($mid_grey, 20%);
}
}
}
padding-left: 300px;
}
}

View File

@ -1,30 +1,72 @@
.album-view {
.playlist-view {
.intro {
@include clearfix;
position: relative;
text-align: center;
color: #FFFFFF;
padding: 20px;
height: 40vh;
z-index: 2;
.parallax {
position: absolute;
top: 0;
left: 0;
height: 100%;
z-index: -1;
}
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 300px;
background: $faint_grey;
.thumbnail {
float: none;
margin: 0 auto;
max-width: 160px;
}
h1 {
padding-top: 30px;
.details {
padding: 30px;
color: $mid_grey;
text-transform: uppercase;
}
.artist-grid {
padding: 0;
margin: 0;
.grid-item {
margin: 0;
width: 50%;
position: relative;
.thumbnail .image {
background-color: $secondary_grey;
}
.name {
@include animate();
position: absolute;
top: 35%;
left: 0;
right: 0;
text-align: center;
color: #FFFFFF;
opacity: 0;
z-index: 2;
font-weight: bold;
}
&:hover {
.name {
opacity: 1;
}
}
}
}
}
.main {
.title {
padding: 30px 40px;
h3 {
color: $mid_grey;
a:hover {
color: darken($mid_grey, 20%);
}
}
}
padding-left: 300px;
}
}