Skip to content

Commit

Permalink
Components: Display site icon by performing media lookup of icon ID
Browse files Browse the repository at this point in the history
  • Loading branch information
aduth committed Dec 23, 2016
1 parent 12fc2d8 commit 5ed530e
Show file tree
Hide file tree
Showing 10 changed files with 407 additions and 11 deletions.
43 changes: 32 additions & 11 deletions client/components/site-icon/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
*/
import React from 'react';
import { connect } from 'react-redux';
import { includes } from 'lodash';
import { get, includes } from 'lodash';
import { parse as parseUrl } from 'url';
import classNames from 'classnames';

/**
* Internal dependencies
*/
import QuerySites from 'components/data/query-sites';
import { getSite } from 'state/sites/selectors';
import { getSiteIconUrl } from 'state/selectors';
import resizeImageUrl from 'lib/resize-image-url';
import Gridicon from 'components/gridicon';

Expand All @@ -31,28 +33,33 @@ const SiteIcon = React.createClass( {
size: React.PropTypes.number
},

getIconSrcUrl( imageUrl ) {
const { host } = parseUrl( imageUrl, true, true );
getIconSrcUrl() {
const { iconUrl } = this.props;
if ( ! iconUrl ) {
return;
}

const { host } = parseUrl( iconUrl, true, true );
const sizeParam = includes( host, 'gravatar.com' ) ? 's' : 'w';

return resizeImageUrl( imageUrl, {
return resizeImageUrl( iconUrl, {
[ sizeParam ]: this.props.imgSize
} );
},

render() {
var iconSrc, iconClasses, style;
const { site, siteId } = this.props;

// Set the site icon path if it's available
iconSrc = ( this.props.site && this.props.site.icon ) ? this.getIconSrcUrl( this.props.site.icon.img ) : null;
const iconSrc = this.getIconSrcUrl();

iconClasses = classNames( {
const iconClasses = classNames( {
'site-icon': true,
'is-blank': ! iconSrc
} );

// Size inline styles
style = {
const style = {
height: this.props.size,
width: this.props.size,
lineHeight: this.props.size + 'px',
Expand All @@ -61,6 +68,7 @@ const SiteIcon = React.createClass( {

return (
<div className={ iconClasses } style={ style }>
{ ! site && siteId > 0 && <QuerySites siteId={ siteId } /> }
{ iconSrc
? <img className="site-icon__img" src={ iconSrc } />
: <Gridicon icon="globe" size={ Math.round( this.props.size / 1.3 ) } />
Expand All @@ -70,6 +78,19 @@ const SiteIcon = React.createClass( {
}
} );

export default connect( ( state, { siteId } ) => (
siteId ? { site: getSite( state, siteId ) } : {}
) )( SiteIcon );
export default connect( ( state, { site, siteId, imgSize } ) => {
// Until sites state is completely within Redux, we provide compatibility
// in cases where site object is passed to use the icon.img property as URL
if ( site ) {
return {
iconUrl: get( site, 'icon.img' )
};
}

// Otherwise, assume we want to perform the lookup in Redux state
// exclusively using the site ID
return {
site: getSite( state, siteId ),
iconUrl: getSiteIconUrl( state, siteId, imgSize )
};
} )( SiteIcon );
16 changes: 16 additions & 0 deletions client/state/selectors/get-media-item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* External dependencies
*/
import { get } from 'lodash';

/**
* Returns a media object by site ID, media ID, or null if not known
*
* @param {Object} state Global state tree
* @param {Number} siteId Site ID
* @param {Number} mediaId Media ID
* @return {?Object} Media object, if known
*/
export default function getMediaItem( state, siteId, mediaId ) {
return get( state.media.items, [ siteId, mediaId ], null );
}
22 changes: 22 additions & 0 deletions client/state/selectors/get-media-url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Internal dependencies
*/
import safeImageUrl from 'lib/safe-image-url';
import { getMediaItem } from './';

/**
* Returns the URL for a media item, or null if not known
*
* @param {Object} state Global state tree
* @param {Number} siteId Site ID
* @param {Number} mediaId Media ID
* @return {?String} Media URL, if known
*/
export default function getMediaUrl( state, siteId, mediaId ) {
const media = getMediaItem( state, siteId, mediaId );
if ( ! media ) {
return null;
}

return safeImageUrl( media.URL );
}
35 changes: 35 additions & 0 deletions client/state/selectors/get-site-icon-id.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* External dependencies
*/
import { get } from 'lodash';

/**
* Internal dependencies
*/
import { getRawSite } from 'state/sites/selectors';
import { getSiteSettings } from 'state/site-settings/selectors';

/**
* Returns a ID to the media associated with a site's current site icon, or
* null if not known or an icon is not assigned.
*
* @param {Object} state Global state tree
* @param {Number} siteId Site ID
* @return {?Number} Media ID of site icon, if known and exists
*/
export default function getSiteIconId( state, siteId ) {
// Treat site object as preferred source of truth of media ID
const site = getRawSite( state, siteId );
if ( site ) {
return get( site, 'icon.media_id', null );
}

// Fall back to site settings in case we know settings prior to having
// received the site itself
const settings = getSiteSettings( state, siteId );
if ( settings ) {
return settings.site_icon;
}

return null;
}
30 changes: 30 additions & 0 deletions client/state/selectors/get-site-icon-url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* External dependencies
*/
import { get } from 'lodash';

/**
* Internal dependencies
*/
import { getRawSite } from 'state/sites/selectors';
import { getSiteIconId, getMediaUrl } from './';

/**
* Returns a URL to the site's current site icon, or null if no icon exists or
* if site is not known
*
* @param {Object} state Global state tree
* @param {Number} siteId Site ID
* @return {?String} URL of site icon, if known and exists
*/
export default function getSiteIconUrl( state, siteId ) {
const iconId = getSiteIconId( state, siteId );
const url = getMediaUrl( state, siteId, iconId );
if ( url ) {
return url;
}

// If cannot find media by ID, use icon.img property if available,
// otherwise assume icon is not set
return get( getRawSite( state, siteId ), 'icon.img', null );
}
4 changes: 4 additions & 0 deletions client/state/selectors/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@
*/

export canCurrentUser from './can-current-user';
export getMediaItem from './get-media-item';
export getMediaUrl from './get-media-url';
export getSiteIconId from './get-site-icon-id';
export getSiteIconUrl from './get-site-icon-url';
47 changes: 47 additions & 0 deletions client/state/selectors/test/get-media-item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* External dependencies
*/
import { expect } from 'chai';

/**
* Internal dependencies
*/
import { getMediaItem } from '../';

describe( 'getMediaItem()', () => {
it( 'should return null if the site is not in state', () => {
const item = getMediaItem( {
media: {
items: {}
}
}, 2916284, 42 );

expect( item ).to.be.null;
} );

it( 'should return null if the media for the site is not in state', () => {
const item = getMediaItem( {
media: {
items: {
2916284: {}
}
}
}, 2916284, 42 );

expect( item ).to.be.null;
} );

it( 'should return the media item', () => {
const item = getMediaItem( {
media: {
items: {
2916284: {
42: { ID: 42, title: 'flowers' }
}
}
}
}, 2916284, 42 );

expect( item ).to.eql( { ID: 42, title: 'flowers' } );
} );
} );
55 changes: 55 additions & 0 deletions client/state/selectors/test/get-media-url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* External dependencies
*/
import { expect } from 'chai';

/**
* Internal dependencies
*/
import { getMediaUrl } from '../';

describe( 'getMediaUrl()', () => {
it( 'should return null if the item is not in state', () => {
const url = getMediaUrl( {
media: {
items: {
2916284: {}
}
}
}, 2916284, 42 );

expect( url ).to.be.null;
} );

it( 'should return null if the media item URL is invalid', () => {
const url = getMediaUrl( {
media: {
items: {
2916284: {
42: { ID: 42, title: 'flowers' }
}
}
}
}, 2916284, 42 );

expect( url ).to.be.null;
} );

it( 'should return a safe variation of the media URL', () => {
const url = getMediaUrl( {
media: {
items: {
2916284: {
42: {
ID: 42,
title: 'flowers',
URL: 'https://example.files.wordpress.com/2014/06/flower.gif'
}
}
}
}
}, 2916284, 42 );

expect( url ).to.equal( 'https://example.files.wordpress.com/2014/06/flower.gif' );
} );
} );
88 changes: 88 additions & 0 deletions client/state/selectors/test/get-site-icon-id.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* External dependencies
*/
import { expect } from 'chai';

/**
* Internal dependencies
*/
import { getSiteIconId } from '../';

describe( 'getSiteIconId()', () => {
it( 'should return null if neither the site nor settings are known', () => {
const id = getSiteIconId( {
sites: {
items: {}
},
siteSettings: {
items: {}
}
}, 2916284 );

expect( id ).to.be.null;
} );

it( 'should prefer site state', () => {
const id = getSiteIconId( {
sites: {
items: {
2916284: {
ID: 2916284,
name: 'WordPress.com Example Blog',
icon: {
media_id: 42
}
}
}
},
siteSettings: {
items: {
2916284: {
site_icon: 36
}
}
}
}, 2916284 );

expect( id ).to.equal( 42 );
} );

it( 'should prefer site state, even if unset', () => {
const id = getSiteIconId( {
sites: {
items: {
2916284: {
ID: 2916284,
name: 'WordPress.com Example Blog'
}
}
},
siteSettings: {
items: {
2916284: {
site_icon: 42
}
}
}
}, 2916284 );

expect( id ).to.be.null;
} );

it( 'should fall back to settings state', () => {
const id = getSiteIconId( {
sites: {
items: {}
},
siteSettings: {
items: {
2916284: {
site_icon: 42
}
}
}
}, 2916284 );

expect( id ).to.equal( 42 );
} );
} );
Loading

0 comments on commit 5ed530e

Please sign in to comment.