Grids; Parallax

This commit is contained in:
James Barnsley
2016-10-25 21:36:33 +13:00
parent ab9f317aed
commit dff33316ee
20 changed files with 261 additions and 103 deletions

View File

@ -9,11 +9,11 @@ export default class AlbumGrid extends React.Component{
}
render(){
if( this.props.items ){
if( this.props.albums ){
return (
<ul className="grid album-grid">
{
this.props.items.items.map(
this.props.albums.map(
(album, index) => {
var link = album.uri;
if( album.album ) link = album.album.uri;

View File

@ -1,21 +1,22 @@
import React, { PropTypes } from 'react'
import AlbumGridItem from './AlbumGridItem'
import GridItem from './GridItem'
export default class AlbumGrid extends React.Component{
export default class ArtistGrid extends React.Component{
constructor(props) {
super(props);
}
render(){
if( this.props.items ){
if( this.props.artists ){
return (
<ul className="grid album-grid">
<ul className="grid artist-grid">
{
this.props.items.items.map(
(album, index) => {
return <AlbumGridItem item={album} key={index} />
this.props.artists.map(
(artist, index) => {
var link = artist.uri;
return <GridItem item={artist} key={index} link={'/artist/'+link} />
}
)
}

View File

@ -1,7 +1,7 @@
import React, { PropTypes } from 'react'
import FontAwesome from 'react-fontawesome'
import { Link } from 'react-router'
import Thumbnail from './Thumbnail'
export default class ArtistList extends React.Component{
@ -14,17 +14,12 @@ export default class ArtistList extends React.Component{
<span className="artist-list">
{
this.props.artists.map( (artist, index) => {
var separator = null;
if( index == this.props.artists.length - 2 ){
separator = ' and ';
}else if( index < this.props.artists.length - 2 ){
separator = ', ';
}
var link = '/artist/' + artist.uri;
return (
<span className="artist" key={artist.uri}>
<Link to={ link }>{ artist.name }</Link>{ separator }
</span>
<Link to={ link } key={artist.uri} className="artist">
<Thumbnail circle={true} size="small" images={artist.images} />
<span className="name">{ artist.name }</span>
</Link>
);
})
}

View File

@ -0,0 +1,34 @@
import React, { PropTypes } from 'react'
import FontAwesome from 'react-fontawesome'
import { Link } from 'react-router'
export default class ArtistSentence extends React.Component{
constructor(props) {
super(props);
}
render(){
return (
<span className="artist-sentence">
{
this.props.artists.map( (artist, index) => {
var separator = null;
if( index == this.props.artists.length - 2 ){
separator = ' and ';
}else if( index < this.props.artists.length - 2 ){
separator = ', ';
}
var link = '/artist/' + artist.uri;
return (
<span className="artist" key={artist.uri}>
<Link to={ link }>{ artist.name }</Link>{ separator }
</span>
);
})
}
</span>
);
}
}

View File

@ -3,7 +3,7 @@ import React, { PropTypes } from 'react'
import { Link, browserHistory } from 'react-router'
import Thumbnail from './Thumbnail'
import ArtistList from './ArtistList'
import ArtistSentence from './ArtistSentence'
export default class GridItem extends React.Component{
@ -28,7 +28,7 @@ export default class GridItem extends React.Component{
{ item.images ? <Thumbnail size="medium" images={item.images} /> : item.name }
<div className="name">{ item.name }</div>
<div className="secondary">
{ item.artists ? <ArtistList artists={ item.artists } /> : null }
{ item.artists ? <ArtistSentence artists={ item.artists } /> : null }
</div>
</div>
);

View File

@ -8,25 +8,139 @@ export default class Parallax extends React.Component{
super(props);
this.state = {
image: false
scrollTop: 0,
windowWidth: 0,
windowHeight: 0,
source: {},
canvas: {
width: 0,
height: 0
},
image: {}
}
}
componentDidMount(){
this.setState({
image: helpers.SizedImages( this.props.images )
this.loadImage( helpers.SizedImages( this.props.images ).huge )
.then(
response => this.setState({ image: response, source: {} })
)
this.updateCanvas()
$(window).resize(() => this.updateCanvas());
}
loadImage( url ){
return new Promise( (resolve, reject) => {
var imageObject = new Image();
imageObject.src = url;
imageObject.onload = function(){
resolve( imageObject )
}
})
this.update();
}
updateCanvas(){
var canvasWidth = $('.parallax').outerWidth();
var canvasHeight = $('.parallax').outerHeight();
if( this.state.canvas.width != canvasWidth || this.state.canvas.height != canvasHeight ){
this.setState({
canvas: {
width: canvasWidth,
height: canvasHeight
}
})
}
console.log( this.state );
}
update(){
// TODO: copy canvas rules in here from Spotmop
let self = this;
var parallax = $('.parallax');
var canvasDOM = document.getElementById('parallax-canvas');
var context = canvasDOM.getContext('2d');
var image = {};
Object.assign(image, self.state.source);
// set our canvas dimensions (if necessary)
var canvasWidth = $('#parallax-canvas').outerWidth();
var canvasHeight = $('#parallax-canvas').outerHeight();
if( context.canvas.width != canvasWidth || context.canvas.height != canvasHeight ){
context.canvas.width = canvasWidth;
context.canvas.height = canvasHeight;
}
// zoom image to fill canvas, widthwise
if( image.width < canvasWidth || image.width > canvasWidth ){
var scale = canvasWidth / image.width;
image.width = image.width * scale;
image.height = image.height * scale;
}
// now check for fill heightwise, and zoom in if necessary
if( image.height < canvasHeight ){
var scale = canvasHeight / image.height;
image.width = image.width * scale;
image.height = image.height * scale;
}
// figure out where we want the image to be, based on scroll position
var percent = Math.round( self.state.scrollTop / canvasHeight * 100 );
var position = Math.round( (canvasHeight / 2) * (percent/100) ) - 100;
image.x = ( canvasWidth / 2 ) - ( image.width / 2 );
image.y = ( ( canvasHeight / 2 ) - ( image.height / 2 ) ) + ( ( percent / 100 ) * 100);
// actually draw the image on the canvas
context.drawImage(imageObject, image.x, image.y, image.width, image.height);
self.setState({ image: image });
// poll for scroll changes
/*
var animateInterval = $interval(
function(){
window.requestAnimationFrame(function( event ){
var bannerPanel = $(document).find('.intro');
// if we've scrolled or resized
if(
state.scrollTop != $(document).scrollTop() ||
state.windowWidth != $(window).width() ||
state.windowHeight != $(window).height() ){
// update our state
state.scrollTop = $(document).scrollTop();
state.windowWidth = $(window).width();
state.windowHeight = $(window).height();
var bannerHeight = bannerPanel.outerHeight();
// and if we're within the bounds of our document
// this helps prevent us animating when the objects in question are off-screen
if( state.scrollTop < bannerHeight ){
positionArtistBackground( image );
}
}
});
},
10
);*/
}
render(){
return (
<div className="parallax">
{ this.state.image ? <img src={this.state.image.huge} /> : null }
<canvas id="parallax-canvas" width={this.state.canvas.width} height={this.state.canvas.height}></canvas>
</div>
);
}

View File

@ -5,7 +5,7 @@ import { bindActionCreators } from 'redux'
import FontAwesome from 'react-fontawesome'
import VolumeSlider from './VolumeSlider'
import ArtistList from './ArtistList'
import ArtistSentence from './ArtistSentence'
import Thumbnail from './Thumbnail'
import * as mopidyActions from '../services/mopidy/actions'
@ -22,7 +22,7 @@ class Player extends React.Component{
<div className="track-in-focus">
{ this.props.spotify.track && !this.props.mini ? <Thumbnail size="large" images={this.props.spotify.track.album.images} /> : null }
<div className="title">{ this.props.mopidy.currentTlTrack.track.name }</div>
<ArtistList artists={ this.props.mopidy.currentTlTrack.track.artists } />
<ArtistSentence artists={ this.props.mopidy.currentTlTrack.track.artists } />
</div>
);
}

View File

@ -1,29 +0,0 @@
import React, { PropTypes } from 'react'
import { Link } from 'react-router'
import Thumbnail from './Thumbnail'
export default class RelatedArtistList extends React.Component{
constructor(props) {
super(props);
}
render(){
return (
<span className="related-artist-list">
{
this.props.artists.map( (artist, index) => {
var link = '/artist/' + artist.uri;
return (
<Link to={ link } key={artist.uri} className="artist">
<Thumbnail circle={true} size="small" images={artist.images} />
{ artist.name }
</Link>
);
})
}
</span>
);
}
}

View File

@ -1,7 +1,7 @@
import React, { PropTypes } from 'react'
import FontAwesome from 'react-fontawesome'
import ArtistList from './ArtistList'
import ArtistSentence from './ArtistSentence'
import AlbumLink from './AlbumLink'
export default class Track extends React.Component{
@ -58,7 +58,7 @@ export default class Track extends React.Component{
{this.props.track.name}
</span>
<span className="col artists">
{ this.props.track.artists ? <ArtistList artists={this.props.track.artists} /> : null }
{ this.props.track.artists ? <ArtistSentence artists={this.props.track.artists} /> : null }
</span>
<span className="col album">
{ this.props.track.album ? <AlbumLink album={this.props.track.album} /> : null }

View File

@ -8,7 +8,7 @@ import TrackList from '../components/TrackList'
import AlbumGrid from '../components/AlbumGrid'
import Thumbnail from '../components/Thumbnail'
import Parallax from '../components/Parallax'
import RelatedArtistList from '../components/RelatedArtistList'
import ArtistList from '../components/ArtistList'
import * as spotifyActions from '../services/spotify/actions'
@ -34,13 +34,21 @@ class Artist extends React.Component{
if( this.props.spotify.artist ){
return (
<div>
<Header icon="mic" title={ this.props.spotify.artist.name } />
{ this.props.spotify.artist.images ? <Parallax images={ this.props.spotify.artist.images } /> : null }
{ this.props.spotify.artist.images ? <Thumbnail size="huge" images={ this.props.spotify.artist.images } /> : null }
<p>{ this.props.spotify.artist.followers.total.toLocaleString() } followers</p>
{ this.props.spotify.artist.related_artists ? <RelatedArtistList artists={ this.props.spotify.artist.related_artists } /> : null }
{ this.props.spotify.artist.tracks ? <TrackList tracks={ this.props.spotify.artist.tracks } /> : null }
{ this.props.spotify.artist_albums ? <AlbumGrid items={ this.props.spotify.artist_albums } /> : null }
<Parallax images={ this.props.spotify.artist.images } />
<div className="intro">
<Thumbnail size="huge" images={ this.props.spotify.artist.images } />
<h1>{ this.props.spotify.artist.name }</h1>
<p>{ this.props.spotify.artist.followers.total.toLocaleString() } followers</p>
</div>
<div className="col c3w2">
<h3>Top tracks</h3>
{ this.props.spotify.artist.tracks ? <TrackList tracks={ this.props.spotify.artist.tracks } /> : null }
</div>
<div className="col c3w1 cf">
<h3>Related artists</h3>
{ this.props.spotify.artist.related_artists ? <ArtistList artists={ this.props.spotify.artist.related_artists.slice(0,6) } /> : null }
</div>
{ this.props.spotify.artist_albums ? <AlbumGrid albums={ this.props.spotify.artist_albums.items } /> : null }
</div>
);
}

View File

@ -7,7 +7,7 @@ import FontAwesome from 'react-fontawesome'
import TrackList from '../components/TrackList'
import Track from '../components/Track'
import Player from '../components/Player'
import ArtistList from '../components/ArtistList'
import ArtistSentence from '../components/ArtistSentence'
import AlbumLink from '../components/AlbumLink'
import Header from '../components/Header'
@ -24,7 +24,7 @@ class Queue extends React.Component{
return (
<div>
<div>{ this.props.mopidy.trackInFocus.track.name }</div>
<div><ArtistList artists={ this.props.mopidy.trackInFocus.track.artists } /></div>
<div><ArtistSentence artists={ this.props.mopidy.trackInFocus.track.artists } /></div>
<div><AlbumLink album={ this.props.mopidy.trackInFocus.track.album } /></div>
</div>
);

View File

@ -29,7 +29,7 @@ class LibraryAlbums extends React.Component{
icon="cd"
title="My albums"
/>
<AlbumGrid items={this.props.spotify.libraryAlbums} />
<AlbumGrid albums={this.props.spotify.libraryAlbums.items} />
</div>
);
}

View File

@ -5,6 +5,7 @@ import { bindActionCreators } from 'redux'
import { Link } from 'react-router'
import Header from '../../components/Header'
import ArtistGrid from '../../components/ArtistGrid'
import * as mopidyActions from '../../services/mopidy/actions'
import * as spotifyActions from '../../services/spotify/actions'
@ -28,14 +29,7 @@ class LibraryArtists extends React.Component{
icon="mic"
title="My artists"
/>
<ul>
{
this.props.spotify.libraryArtists.artists.items.map( (artist, index) => {
var link = '/artist/' + artist.uri;
return <li key={index}><Link to={link}>{ artist.name }</Link></li>
})
}
</ul>
<ArtistGrid artists={this.props.spotify.libraryArtists.artists.items} />
</div>
);
}

View File

@ -25,8 +25,8 @@
color: $secondary_grey;
}
&:nth-child(5)::after {
@include clear;
&:nth-child(5) {
@include clearfix;
}
&:hover{

View File

@ -10,7 +10,7 @@
padding-bottom: 100%;
position: relative;
background-repeat: no-repeat;
background-color: #DDDDDD;
background-color: $light_grey;
background-position: 50% 50%;
background-size: cover;
}
@ -21,9 +21,13 @@
}
.parallax {
height: 300px;
height: 50vh;
width: 100%;
overflow: hidden;
.thumbnail {
max-width: none;
background: $light_grey;
canvas {
width: 100%;
height: 100%;
}
}

View File

@ -14,6 +14,7 @@
border-top: 1px solid #EEEEEE;
border-bottom: 1px solid #EEEEEE;
margin-bottom: -1px;
@include clearfix;
.col {
width: 10%;
@ -21,10 +22,6 @@
float: left;
}
&::after {
@include clear;
}
&.selected {
background: #FFF39C !important;
}
@ -79,3 +76,30 @@
}
}
.artist-list {
.artist {
display: block;
border: 0;
margin-bottom: 15px;
@include clearfix;
.thumbnail {
width: 50px;
margin-right: 20px;
float: left;
border: 3px solid transparent;
}
.name {
display: inline-block;
padding-top: 15px;
}
&:hover {
.thumbnail {
border-color: $turquoise;
opacity: 0.8;
}
}
}
}

View File

@ -87,7 +87,7 @@ aside{
@include one_line_text;
}
.artist-list {
.artist-sentence {
opacity: 0.5;
display: block;
@include one_line_text;

View File

@ -41,4 +41,23 @@ main {
border-bottom: 1px solid #000000;
}
}
}
.col {
min-height: 1px;
float: left;
&.c2w1 { width: 50%; }
&.c3w1 { width: 33.3334%; }
&.c3w2 { width: 66.6667%; }
&.c4w1 { width: 25%; }
&.c4w3 { width: 75%; }
&.c5w1 { width: 20%; }
&.c5w2 { width: 40%; }
&.c5w3 { width: 60%; }
&.c5w4 { width: 80%; }
}
.cf{
@include clearfix;
}

View File

@ -1,5 +1,6 @@
$turquoise: #08d58f;
$light_grey: #DDDDDD;
$secondary_grey: #888888;
@mixin one_line_text {
@ -8,8 +9,10 @@ $secondary_grey: #888888;
text-overflow: ellipsis;
}
@mixin clear {
content: '';
clear: both;
display: block;
@mixin clearfix {
&::after {
content: '';
clear: both;
display: block;
}
}

View File

@ -1,10 +1 @@
.tracks {
width: 65%;
float: left;
}
.related-artist-list {
width: 30%;
float: right;
}