Use Image variants instead of sizes

This commit is contained in:
Kimmo Puputti 2018-04-13 16:26:44 +03:00
parent f7cc195ecc
commit 946cf7f53c
32 changed files with 454 additions and 654 deletions

View file

@ -55,11 +55,7 @@ const ThumbnailWrapper = props => {
rootClassName={css.rootForImage}
image={image}
alt={savedImageAltText}
nameSet={[
{ name: 'landscape-crop', size: '400w' },
{ name: 'landscape-crop2x', size: '800w' },
]}
sizes="100%"
variants={['landscape-crop', 'landscape-crop2x']}
/>
</div>
<RemoveImageButton onClick={handleRemoveClick} />

View file

@ -38,20 +38,20 @@ const userWithProfileImage = {
id: new UUID('profile-image'),
type: 'image',
attributes: {
sizes: [
{
name: 'square-xlarge2x',
variants: {
'square-small': {
name: 'square-small',
width: 240,
height: 240,
url: 'https://lorempixel.com/240/240/people/',
},
{
name: 'square-xlarge4x',
'square-small2x': {
name: 'square-small2x',
width: 480,
height: 480,
url: 'https://lorempixel.com/480/480/people/',
},
],
},
},
},
};

View file

@ -1,5 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import { string, oneOfType, bool } from 'prop-types';
import { injectIntl, intlShape } from 'react-intl';
import classNames from 'classnames';
import { propTypes } from '../../util/types';
@ -13,8 +13,21 @@ import { ResponsiveImage, IconBannedUser, NamedLink } from '../../components/';
import css from './Avatar.css';
// Responsive image sizes hint
const AVATAR_SIZES = '40px';
const AVATAR_SIZES_MEDIUM = '60px';
const AVATAR_SIZES_LARGE = '96px';
const AVATAR_IMAGE_VARIANTS = [
// 240x240
'square-small',
// 480x480
'square-small2x',
];
export const AvatarComponent = props => {
const { rootClassName, className, user, disableProfileLink, intl } = props;
const { rootClassName, className, user, renderSizes, disableProfileLink, intl } = props;
const classes = classNames(rootClassName || css.root, className);
const userIsCurrentUser = user && user.type === 'currentUser';
@ -49,10 +62,8 @@ export const AvatarComponent = props => {
rootClassName={css.avatarImage}
alt={displayName}
image={avatarUser.profileImage}
nameSet={[
{ name: 'square-xlarge2x', size: '1x' },
{ name: 'square-xlarge4x', size: '2x' },
]}
variants={AVATAR_IMAGE_VARIANTS}
sizes={renderSizes}
/>
</NamedLink>
);
@ -63,10 +74,8 @@ export const AvatarComponent = props => {
rootClassName={css.avatarImage}
alt={displayName}
image={avatarUser.profileImage}
nameSet={[
{ name: 'square-xlarge2x', size: '1x' },
{ name: 'square-xlarge4x', size: '2x' },
]}
variants={AVATAR_IMAGE_VARIANTS}
sizes={renderSizes}
/>
</div>
);
@ -91,16 +100,16 @@ AvatarComponent.defaultProps = {
className: null,
rootClassName: null,
user: null,
renderSizes: AVATAR_SIZES,
disableProfileLink: false,
};
const { string, oneOfType, bool } = PropTypes;
AvatarComponent.propTypes = {
rootClassName: string,
className: string,
user: oneOfType([propTypes.user, propTypes.currentUser]),
renderSizes: string,
disableProfileLink: bool,
// from injectIntl
@ -111,8 +120,12 @@ const Avatar = injectIntl(AvatarComponent);
export default Avatar;
export const AvatarMedium = props => <Avatar rootClassName={css.mediumAvatar} {...props} />;
export const AvatarMedium = props => (
<Avatar rootClassName={css.mediumAvatar} renderSizes={AVATAR_SIZES_MEDIUM} {...props} />
);
AvatarMedium.displayName = 'AvatarMedium';
export const AvatarLarge = props => <Avatar rootClassName={css.largeAvatar} {...props} />;
export const AvatarLarge = props => (
<Avatar rootClassName={css.largeAvatar} renderSizes={AVATAR_SIZES_LARGE} {...props} />
);
AvatarLarge.displayName = 'AvatarLarge';

View file

@ -13,32 +13,32 @@ const imageSquare = {
id: new UUID('image-square'),
type: 'image',
attributes: {
sizes: [
{
variants: {
[imageName]: {
name: imageName,
width: 400,
height: 400,
url: 'https://via.placeholder.com/400x400',
},
{
[imageName2x]: {
name: imageName2x,
width: 800,
height: 800,
url: 'https://via.placeholder.com/800x800',
},
{
[imageName4x]: {
name: imageName4x,
width: 1600,
height: 1600,
url: 'https://via.placeholder.com/1600x1600',
},
{
[imageName6x]: {
name: imageName6x,
width: 2400,
height: 2400,
url: 'https://via.placeholder.com/2400x2400',
},
],
},
},
};
@ -46,64 +46,64 @@ const imagePortrait = {
id: new UUID('image-portrait'),
type: 'image',
attributes: {
sizes: [
{
variants: {
[imageName]: {
name: imageName,
width: 400,
height: 800,
url: 'https://via.placeholder.com/400x800',
},
{
[imageName2x]: {
name: imageName2x,
width: 800,
height: 1600,
url: 'https://via.placeholder.com/800x1600',
},
{
[imageName4x]: {
name: imageName4x,
width: 800,
height: 1600,
url: 'https://via.placeholder.com/800x1600',
},
{
[imageName6x]: {
name: imageName6x,
width: 1200,
height: 2400,
url: 'https://via.placeholder.com/1200x2400',
},
],
},
},
};
const imageLandscape = {
id: new UUID('image-landscape'),
type: 'image',
attributes: {
sizes: [
{
variants: {
[imageName]: {
name: imageName,
width: 400,
height: 200,
url: 'https://via.placeholder.com/400x200',
},
{
[imageName2x]: {
name: imageName2x,
width: 800,
height: 400,
url: 'https://via.placeholder.com/800x400',
},
{
[imageName4x]: {
name: imageName4x,
width: 1600,
height: 800,
url: 'https://via.placeholder.com/1600x800',
},
{
[imageName6x]: {
name: imageName6x,
width: 2400,
height: 1200,
url: 'https://via.placeholder.com/2400x1200',
},
],
},
},
};

View file

@ -10,11 +10,6 @@ import css from './ImageCarousel.css';
const KEY_CODE_LEFT_ARROW = 37;
const KEY_CODE_RIGHT_ARROW = 39;
const imageScaledSmall = 'scaled-small'; // width 320
const imageScaledMedium = 'scaled-medium'; // width 750
const imageScaledLarge = 'scaled-large'; // width 1024
const imageScaledXLarge = 'scaled-xlarge'; // width 2400
class ImageCarousel extends Component {
constructor(props) {
super(props);
@ -112,12 +107,7 @@ class ImageCarousel extends Component {
image={images[this.state.selectedImageIndex]}
onLoad={markImageLoaded(this.state.selectedImageIndex)}
onError={markImageLoaded(this.state.selectedImageIndex)}
nameSet={[
{ name: imageScaledSmall, size: '320w' },
{ name: imageScaledMedium, size: '750w' },
{ name: imageScaledLarge, size: '1024w' },
{ name: imageScaledXLarge, size: '2400w' },
]}
variants={['scaled-small', 'scaled-medium', 'scaled-large', 'scaled-xlarge']}
sizes="(max-width: 767px) 100vw, 80vw"
/>
</div>

View file

@ -34,7 +34,7 @@ const priceData = (price, intl) => {
};
export const ListingCardComponent = props => {
const { className, rootClassName, intl, listing, setActiveListing } = props;
const { className, rootClassName, intl, listing, renderSizes, setActiveListing } = props;
const classes = classNames(rootClassName || css.root, className);
const currentListing = ensureListing(listing);
const id = currentListing.id.uuid;
@ -59,10 +59,8 @@ export const ListingCardComponent = props => {
rootClassName={css.rootForImage}
alt={title}
image={firstImage}
nameSet={[
{ name: 'landscape-crop', size: '1x' },
{ name: 'landscape-crop2x', size: '2x' },
]}
variants={['landscape-crop', 'landscape-crop2x']}
sizes={renderSizes}
/>
</div>
</div>
@ -98,6 +96,7 @@ export const ListingCardComponent = props => {
ListingCardComponent.defaultProps = {
className: null,
rootClassName: null,
renderSizes: null,
setActiveListing: () => null,
};
@ -106,6 +105,10 @@ ListingCardComponent.propTypes = {
rootClassName: string,
intl: intlShape.isRequired,
listing: propTypes.listing.isRequired,
// Responsive image sizes hint
renderSizes: string,
setActiveListing: func,
};

View file

@ -20,21 +20,15 @@ exports[`ListingCard matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "1x",
},
Object {
"name": "landscape-crop2x",
"size": "2x",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes={null}
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>

View file

@ -107,6 +107,7 @@ export const ManageListingCardComponent = props => {
onCloseListing,
onOpenListing,
onToggleMenu,
renderSizes,
} = props;
const classes = classNames(rootClassName || css.root, className);
const currentListing = ensureOwnListing(listing);
@ -237,10 +238,8 @@ export const ManageListingCardComponent = props => {
rootClassName={css.rootForImage}
alt={title}
image={firstImage}
nameSet={[
{ name: 'landscape-crop', size: '1x' },
{ name: 'landscape-crop2x', size: '2x' },
]}
variants={['landscape-crop', 'landscape-crop2x']}
sizes={renderSizes}
/>
</div>
<div className={classNames(css.menuOverlayWrapper, { [css.menuOverlayOpen]: isMenuOpen })}>
@ -323,6 +322,7 @@ ManageListingCardComponent.defaultProps = {
className: null,
rootClassName: null,
actionsInProgressListingId: null,
renderSizes: null,
};
const { bool, func, shape, string } = PropTypes;
@ -340,6 +340,9 @@ ManageListingCardComponent.propTypes = {
onOpenListing: func.isRequired,
onToggleMenu: func.isRequired,
// Responsive image sizes hint
renderSizes: string,
// from withRouter
history: shape({
push: func.isRequired,

View file

@ -12,21 +12,15 @@ exports[`ManageListingCard matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "1x",
},
Object {
"name": "landscape-crop2x",
"size": "2x",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes={null}
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
<div

View file

@ -33,32 +33,23 @@ export const Image2X = {
id: new UUID('empty'),
type: 'image',
attributes: {
sizes: [
{
variants: {
crop: {
name: 'crop',
width: 200,
height: 133,
url: 'https://via.placeholder.com/200x133',
},
{
crop2x: {
name: 'crop2x',
width: 400,
height: 266,
url: 'https://via.placeholder.com/400x266',
},
],
},
},
},
nameSet: [
{
name: 'crop',
size: '1x',
},
{
name: 'crop2x',
size: '2x',
},
],
variants: ['crop', 'crop2x'],
},
};
@ -71,32 +62,23 @@ export const Image2XAspect = {
id: new UUID('empty'),
type: 'image',
attributes: {
sizes: [
{
variants: {
crop: {
name: 'crop',
width: 200,
height: 133,
url: 'https://via.placeholder.com/200x133',
},
{
crop2x: {
name: 'crop2x',
width: 400,
height: 266,
url: 'https://via.placeholder.com/400x266',
},
],
},
},
},
nameSet: [
{
name: 'crop',
size: '1x',
},
{
name: 'crop2x',
size: '2x',
},
],
variants: ['crop', 'crop2x'],
},
};
@ -109,32 +91,23 @@ export const Image2XWrongAspect = {
id: new UUID('empty'),
type: 'image',
attributes: {
sizes: [
{
variants: {
crop: {
name: 'crop',
width: 200,
height: 133,
url: 'https://via.placeholder.com/200x200',
url: 'https://via.placeholder.com/200x133',
},
{
crop2x: {
name: 'crop2x',
width: 400,
height: 266,
url: 'https://via.placeholder.com/400x400',
url: 'https://via.placeholder.com/400x266',
},
],
},
},
},
nameSet: [
{
name: 'crop',
size: '1x',
},
{
name: 'crop2x',
size: '2x',
},
],
variants: ['crop', 'crop2x'],
},
};
@ -146,32 +119,23 @@ export const Image2XWrongAspectNoWrapper = {
id: new UUID('empty'),
type: 'image',
attributes: {
sizes: [
{
variants: {
crop: {
name: 'crop',
width: 200,
height: 133,
url: 'https://via.placeholder.com/200x200',
url: 'https://via.placeholder.com/200x133',
},
{
crop2x: {
name: 'crop2x',
width: 400,
height: 266,
url: 'https://via.placeholder.com/400x400',
url: 'https://via.placeholder.com/400x266',
},
],
},
},
},
nameSet: [
{
name: 'crop',
size: '1x',
},
{
name: 'crop2x',
size: '2x',
},
],
variants: ['crop', 'crop2x'],
},
};
@ -181,6 +145,7 @@ export const ImageEmpty = {
props: {
alt: 'img',
image: null,
variants: ['crop', 'crop2x'],
},
};
@ -190,6 +155,7 @@ export const ImageEmptyWithAspect = {
props: {
alt: 'img',
image: null,
variants: ['crop', 'crop2x'],
},
};
@ -210,32 +176,23 @@ export const ImageWithSizes = {
id: new UUID('empty'),
type: 'image',
attributes: {
sizes: [
{
variants: {
crop: {
name: 'crop',
width: 200,
height: 133,
url: 'https://via.placeholder.com/200x133',
},
{
crop2x: {
name: 'crop2x',
width: 400,
height: 266,
url: 'https://via.placeholder.com/400x266',
},
],
},
},
},
nameSet: [
{
name: 'crop',
size: '200w',
},
{
name: 'crop2x',
size: '400w',
},
],
variants: ['crop', 'crop2x'],
sizes: '(max-width: 600px) 200px, 400px',
},
};

View file

@ -3,19 +3,19 @@
* <ResponsiveImage
* alt="ListingX"
* image={imageDataFromSDK}
* nameSet={[{ name: 'landscape-crop', size: '1x'}, { name: 'landscape-crop2x', size: '2x'}]}
* variants={['landscape-crop', 'landscape-crop2x']}
* />
* // produces:
* <img
* alt="ListingX"
* src="url/to/landscape-crop.jpg"
* srcSet="url/to/landscape-crop.jpg 1x, url/to/landscape-crop2x.jpg 2x" />
* srcSet="url/to/landscape-crop.jpg 400w, url/to/landscape-crop2x.jpg 800w" />
*
* Usage with sizes:
* <ResponsiveImage
* alt="ListingX"
* image={imageDataFromSDK}
* nameSet={[{ name: 'landscape-crop', size: '400w'}, { name: 'landscape-crop2x', size: '800w'}]}
* variants={['landscape-crop', 'landscape-crop2x']}
* sizes="(max-width: 600px) 100vw, 50vw"
* />
* // produces:
@ -28,10 +28,13 @@
* // This means that below 600px image will take as many pixels there are available on current
* // viewport width (100vw) - and above that image will only take 50% of the page width.
* // Browser decides which image it will fetch based on current screen size.
*
* NOTE: for all the possible image variant names and their respective
* sizes, see the API documentation.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { arrayOf, string } from 'prop-types';
import classNames from 'classnames';
import { FormattedMessage } from 'react-intl';
import { propTypes } from '../../util/types';
@ -40,10 +43,10 @@ import NoImageIcon from './NoImageIcon';
import css from './ResponsiveImage.css';
const ResponsiveImage = props => {
const { className, rootClassName, alt, noImageMessage, image, nameSet, sizes, ...rest } = props;
const { className, rootClassName, alt, noImageMessage, image, variants, ...rest } = props;
const classes = classNames(rootClassName || css.root, className);
if (image == null || nameSet.length === 0) {
if (image == null || variants.length === 0) {
const noImageClasses = classNames(rootClassName || css.root, css.noImageContainer, className);
// NoImageMessage is needed for listing images on top the map (those component lose context)
@ -61,43 +64,37 @@ const ResponsiveImage = props => {
/* eslint-enable jsx-a11y/img-redundant-alt */
}
const imageSizes = image.attributes.sizes;
const imageVariants = image.attributes.variants;
const srcSet = nameSet
.map(v => {
const variant = imageVariants ? imageVariants[v.name] : null;
const srcSet = variants
.map(variantName => {
const variant = imageVariants[variantName];
// deprecated
// for backwards compatibility only
const size = imageSizes ? imageSizes.find(i => i.name === v.name) : null;
if (variant || size) {
const url = (variant || size).url;
return `${url} ${v.size}`;
} else {
// Handle case where the requested variant doesn't exist, for
// example because it hasn't been loaded yet.
// Return null, which will be filtered out.
if (!variant) {
// Variant not available (most like just not loaded yet)
return null;
}
return `${variant.url} ${variant.width}w`;
})
.filter(v => v != null)
.join(', ');
const sizesProp = sizes ? { sizes } : {};
const imgProps = {
className: classes,
alt,
srcSet,
...rest,
};
return <img alt={alt} className={classes} srcSet={srcSet} {...rest} {...sizesProp} />;
// alt prop already defined above
// eslint-disable-next-line jsx-a11y/alt-text
return <img {...imgProps} />;
};
const { arrayOf, shape, string } = PropTypes;
ResponsiveImage.defaultProps = {
className: null,
rootClassName: null,
image: null,
nameSet: [],
sizes: null,
noImageMessage: null,
};
@ -106,13 +103,7 @@ ResponsiveImage.propTypes = {
rootClassName: string,
alt: string.isRequired,
image: propTypes.image,
nameSet: arrayOf(
shape({
name: string.isRequired,
size: string.isRequired,
})
),
sizes: string,
variants: arrayOf(string).isRequired,
noImageMessage: string,
};

View file

@ -85,10 +85,8 @@ const ListingCard = props => {
alt={title}
noImageMessage={intl.formatMessage({ id: 'SearchMapInfoCard.noImage' })}
image={firstImage}
nameSet={[
{ name: 'landscape-crop', size: '1x' },
{ name: 'landscape-crop2x', size: '2x' },
]}
variants={['landscape-crop', 'landscape-crop2x']}
sizes="250px"
/>
</div>
</div>

View file

@ -19,6 +19,16 @@ const SearchResultsPanel = props => {
/>
) : null;
// Panel width relative to the viewport
const panelMediumWidth = 50;
const panelLargeWidth = 62.5;
const cardRenderSizes = [
'(max-width: 767px) 100vw',
`(max-width: 1023px) ${panelMediumWidth}vw`,
`(max-width: 1920px) ${panelLargeWidth / 2}vw`,
`${panelLargeWidth / 3}vw`,
].join(', ');
return (
<div className={classes}>
<div className={css.listingCards}>
@ -27,6 +37,7 @@ const SearchResultsPanel = props => {
className={css.listingCard}
key={l.id.uuid}
listing={l}
renderSizes={cardRenderSizes}
setActiveListing={setActiveListing}
/>
))}

View file

@ -220,11 +220,7 @@ export class TransactionPanelComponent extends Component {
rootClassName={css.rootForImage}
alt={listingTitle}
image={firstImage}
nameSet={[
{ name: 'landscape-crop', size: '400w' },
{ name: 'landscape-crop2x', size: '800w' },
]}
sizes="100vw"
variants={['landscape-crop', 'landscape-crop2x']}
/>
</div>
</div>
@ -301,11 +297,7 @@ export class TransactionPanelComponent extends Component {
rootClassName={css.rootForImage}
alt={listingTitle}
image={firstImage}
nameSet={[
{ name: 'landscape-crop', size: '400w' },
{ name: 'landscape-crop2x', size: '800w' },
]}
sizes="100%"
variants={['landscape-crop', 'landscape-crop2x']}
/>
</div>
</div>

View file

@ -12,21 +12,14 @@ exports[`TransactionPanel - Order accepted matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100vw"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>
@ -845,21 +838,14 @@ exports[`TransactionPanel - Order accepted matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100%"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>
@ -1172,21 +1158,14 @@ exports[`TransactionPanel - Order autodeclined matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100vw"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>
@ -2005,21 +1984,14 @@ exports[`TransactionPanel - Order autodeclined matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100%"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>
@ -2332,21 +2304,14 @@ exports[`TransactionPanel - Order canceled matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100vw"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>
@ -3165,21 +3130,14 @@ exports[`TransactionPanel - Order canceled matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100%"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>
@ -3492,21 +3450,14 @@ exports[`TransactionPanel - Order declined matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100vw"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>
@ -4325,21 +4276,14 @@ exports[`TransactionPanel - Order declined matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100%"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>
@ -4652,21 +4596,14 @@ exports[`TransactionPanel - Order delivered matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100vw"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>
@ -5485,21 +5422,14 @@ exports[`TransactionPanel - Order delivered matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100%"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>
@ -5812,21 +5742,14 @@ exports[`TransactionPanel - Order enquired matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100vw"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>
@ -6645,21 +6568,14 @@ exports[`TransactionPanel - Order enquired matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100%"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>
@ -6972,21 +6888,14 @@ exports[`TransactionPanel - Order preauthorized matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100vw"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>
@ -7805,21 +7714,14 @@ exports[`TransactionPanel - Order preauthorized matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100%"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>
@ -8132,21 +8034,14 @@ exports[`TransactionPanel - Sale accepted matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100vw"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>
@ -8898,21 +8793,14 @@ exports[`TransactionPanel - Sale accepted matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100%"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>
@ -9200,21 +9088,14 @@ exports[`TransactionPanel - Sale autodeclined matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100vw"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>
@ -9966,21 +9847,14 @@ exports[`TransactionPanel - Sale autodeclined matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100%"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>
@ -10268,21 +10142,14 @@ exports[`TransactionPanel - Sale canceled matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100vw"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>
@ -11034,21 +10901,14 @@ exports[`TransactionPanel - Sale canceled matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100%"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>
@ -11336,21 +11196,14 @@ exports[`TransactionPanel - Sale declined matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100vw"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>
@ -12102,21 +11955,14 @@ exports[`TransactionPanel - Sale declined matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100%"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>
@ -12404,21 +12250,14 @@ exports[`TransactionPanel - Sale delivered matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100vw"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>
@ -13170,21 +13009,14 @@ exports[`TransactionPanel - Sale delivered matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100%"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>
@ -13472,21 +13304,14 @@ exports[`TransactionPanel - Sale enquired matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100vw"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>
@ -14238,21 +14063,14 @@ exports[`TransactionPanel - Sale enquired matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100%"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>
@ -14540,21 +14358,14 @@ exports[`TransactionPanel - Sale preauthorized matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100vw"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>
@ -15306,21 +15117,14 @@ exports[`TransactionPanel - Sale preauthorized matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100%"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
</div>

View file

@ -50,20 +50,20 @@ export const WithProfileImageAndBio = {
id: new UUID('profile-image'),
type: 'image',
attributes: {
sizes: [
{
name: 'square-xlarge2x',
variants: {
'square-small': {
name: 'square-small',
width: 240,
height: 240,
url: 'https://lorempixel.com/240/240/people/',
},
{
name: 'square-xlarge4x',
'square-small2x': {
name: 'square-small2x',
width: 480,
height: 480,
url: 'https://lorempixel.com/480/480/people/',
},
],
},
},
},
},

View file

@ -340,11 +340,7 @@ export class CheckoutPageComponent extends Component {
rootClassName={css.rootForImage}
alt={listingTitle}
image={firstImage}
nameSet={[
{ name: 'landscape-crop', size: '400w' },
{ name: 'landscape-crop2x', size: '800w' },
]}
sizes="100vw"
variants={['landscape-crop', 'landscape-crop2x']}
/>
</div>
<div className={classNames(css.avatarWrapper, css.avatarMobile)}>
@ -394,11 +390,7 @@ export class CheckoutPageComponent extends Component {
rootClassName={css.rootForImage}
alt={listingTitle}
image={firstImage}
nameSet={[
{ name: 'landscape-crop', size: '400w' },
{ name: 'landscape-crop2x', size: '800w' },
]}
sizes="100%"
variants={['landscape-crop', 'landscape-crop2x']}
/>
</div>
<div className={css.avatarWrapper}>

View file

@ -28,21 +28,14 @@ exports[`CheckoutPage matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100vw"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
<div
@ -109,21 +102,14 @@ exports[`CheckoutPage matches snapshot 1`] = `
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
noImageMessage={null}
rootClassName={null}
sizes="100%"
variants={
Array [
"landscape-crop",
"landscape-crop2x",
]
}
/>
</div>
<div>

View file

@ -275,7 +275,13 @@ export function requestCreateListing(data) {
// Modify store to understand that we have created listing and can redirect away
dispatch(createListingSuccess(response));
// Fetch listing data so that redirection is smooth
dispatch(requestShowListing({ id, include: ['author', 'images'] }));
dispatch(
requestShowListing({
id,
include: ['author', 'images'],
'fields.image': ['variants.landscape-crop', 'variants.landscape-crop2x'],
})
);
return response;
})
.then(response => {
@ -316,7 +322,11 @@ export function requestUpdateListing(tab, data) {
.update(data)
.then(response => {
updateResponse = response;
const payload = { id, include: ['author', 'images'] };
const payload = {
id,
include: ['author', 'images'],
'fields.image': ['variants.landscape-crop', 'variants.landscape-crop2x'],
};
return dispatch(requestShowListing(payload));
})
.then(() => {
@ -343,6 +353,7 @@ export function loadData(params) {
const payload = {
id: new UUID(id),
include: ['author', 'images'],
'fields.image': ['variants.landscape-crop', 'variants.landscape-crop2x'],
};
return dispatch(requestShowListing(payload));
};

View file

@ -91,6 +91,7 @@ export const loadData = (params, search) => (dispatch, getState, sdk) => {
const apiQueryParams = {
only: onlyFilter,
include: ['provider', 'provider.profileImage', 'customer', 'customer.profileImage', 'booking'],
'fields.image': ['variants.square-small', 'variants.square-small2x'],
page,
per_page: INBOX_PAGE_SIZE,
};

View file

@ -124,8 +124,8 @@ export const showListing = (listingId, isOwn = false) => (dispatch, getState, sd
'variants.scaled-xlarge',
// Avatars
'variants.square-xlarge2x',
'variants.square-xlarge4x',
'variants.square-small',
'variants.square-small2x',
],
};
@ -143,7 +143,12 @@ export const showListing = (listingId, isOwn = false) => (dispatch, getState, sd
export const fetchReviews = listingId => (dispatch, getState, sdk) => {
return sdk.reviews
.query({ listing_id: listingId, state: 'public', include: ['author', 'author.profileImage'] })
.query({
listing_id: listingId,
state: 'public',
include: ['author', 'author.profileImage'],
'fields.image': ['variants.square-small', 'variants.square-small2x'],
})
.then(response => {
const reviews = denormalisedResponseEntities(response);
dispatch(fetchReviewsSuccess(reviews));

View file

@ -116,8 +116,8 @@ describe('ListingPage', () => {
'variants.scaled-xlarge',
// Avatars
'variants.square-xlarge2x',
'variants.square-xlarge4x',
'variants.square-small',
'variants.square-small2x',
],
},
],
@ -163,8 +163,8 @@ describe('ListingPage', () => {
'variants.scaled-xlarge',
// Avatars
'variants.square-xlarge2x',
'variants.square-xlarge4x',
'variants.square-small',
'variants.square-small2x',
],
},
],

View file

@ -46,13 +46,12 @@ const SectionImages = props => {
rootClassName={css.rootForImage}
alt={title}
image={firstImage}
nameSet={[
{ name: 'landscape-crop', size: '400w' },
{ name: 'landscape-crop2x', size: '800w' },
{ name: 'landscape-crop4x', size: '1600w' },
{ name: 'landscape-crop6x', size: '2400w' },
variants={[
'landscape-crop',
'landscape-crop2x',
'landscape-crop4x',
'landscape-crop6x',
]}
sizes="100vw"
/>
{viewPhotosButton}
</div>

View file

@ -216,10 +216,11 @@ export const queryListingsError = e => ({
export const queryOwnListings = queryParams => (dispatch, getState, sdk) => {
dispatch(queryListingsRequest(queryParams));
const { include = [], page, perPage } = queryParams;
const { perPage, ...rest } = queryParams;
const params = { ...rest, per_page: perPage };
return sdk.ownListings
.query({ include, page, per_page: perPage })
.query(params)
.then(response => {
dispatch(addOwnEntities(response));
dispatch(queryListingsSuccess(response));

View file

@ -112,6 +112,14 @@ export class ManageListingsPageComponent extends Component {
const title = intl.formatMessage({ id: 'ManageListingsPage.title' });
const panelWidth = 62.5;
// Render hints for responsive image
const renderSizes = [
`(max-width: 767px) 100vw`,
`(max-width: 1920px) ${panelWidth / 2}vw`,
`${panelWidth / 3}vw`,
].join(', ');
return (
<Page title={title} scrollingDisabled={scrollingDisabled}>
<LayoutSingleColumn>
@ -137,6 +145,7 @@ export class ManageListingsPageComponent extends Component {
onOpenListing={onOpenListing}
hasOpeningError={openingErrorListingId.uuid === l.id.uuid}
hasClosingError={closingErrorListingId.uuid === l.id.uuid}
renderSizes={renderSizes}
/>
))}
</div>
@ -234,6 +243,8 @@ ManageListingsPage.loadData = (params, search) => {
page,
perPage: RESULT_PAGE_SIZE,
include: ['images'],
'fields.image': ['variants.landscape-crop', 'variants.landscape-crop2x'],
'limit.images': 1,
});
};

View file

@ -124,7 +124,11 @@ export const queryReviewsError = e => ({
export const queryUserListings = userId => (dispatch, getState, sdk) => {
dispatch(queryListingsRequest(userId));
return sdk.listings
.query({ author_id: userId, include: ['author', 'images'] })
.query({
author_id: userId,
include: ['author', 'images'],
'fields.image': ['variants.landscape-crop', 'variants.landscape-crop2x'],
})
.then(response => {
// Pick only the id and type properties from the response listings
const listingRefs = response.data.data.map(({ id, type }) => ({ id, type }));
@ -137,7 +141,12 @@ export const queryUserListings = userId => (dispatch, getState, sdk) => {
export const queryUserReviews = userId => (dispatch, getState, sdk) => {
sdk.reviews
.query({ subject_id: userId, state: 'public', include: ['author', 'author.profileImage'] })
.query({
subject_id: userId,
state: 'public',
include: ['author', 'author.profileImage'],
'fields.image': ['variants.square-small', 'variants.square-small2x'],
})
.then(response => {
const reviews = denormalisedResponseEntities(response);
dispatch(queryReviewsSuccess(reviews));
@ -148,7 +157,11 @@ export const queryUserReviews = userId => (dispatch, getState, sdk) => {
export const showUser = userId => (dispatch, getState, sdk) => {
dispatch(showUserRequest(userId));
return sdk.users
.show({ id: userId, include: ['profileImage'] })
.show({
id: userId,
include: ['profileImage'],
'fields.image': ['variants.square-small', 'variants.square-small2x'],
})
.then(response => {
dispatch(addMarketplaceEntities(response));
dispatch(showUserSuccess());

View file

@ -191,7 +191,12 @@ class ProfileSettingsFormComponent extends Component {
});
const avatarComponent =
!fileUploadInProgress && profileImage.imageId ? (
<Avatar className={avatarClasses} user={transientUser} disableProfileLink />
<Avatar
className={avatarClasses}
renderSizes="(max-width: 767px) 96px, 240px"
user={transientUser}
disableProfileLink
/>
) : null;
const chooseAvatarLabel =

View file

@ -115,8 +115,16 @@ export function uploadImage(actionPayload) {
const id = actionPayload.id;
dispatch(uploadImageRequest(actionPayload));
const bodyParams = {
image: actionPayload.file,
};
const queryParams = {
expand: true,
'fields.image': ['variants.square-small', 'variants.square-small2x'],
};
return sdk.images
.uploadProfileImage({ image: actionPayload.file }, { expand: true })
.uploadProfileImage(bodyParams, queryParams)
.then(resp => {
const uploadedImage = resp.data.data;
dispatch(uploadImageSuccess({ data: { id, uploadedImage } }));
@ -129,8 +137,14 @@ export const updateProfile = actionPayload => {
return (dispatch, getState, sdk) => {
dispatch(updateProfileRequest());
const queryParams = {
expand: true,
include: ['profileImage'],
'fields.image': ['variants.square-small', 'variants.square-small2x'],
};
return sdk.currentUser
.updateProfile(actionPayload, { expand: true, include: ['profileImage'] })
.updateProfile(actionPayload, queryParams)
.then(response => {
dispatch(updateProfileSuccess(response));

View file

@ -218,6 +218,7 @@ export const fetchTransaction = id => (dispatch, getState, sdk) => {
'reviews.author',
'reviews.subject',
],
...IMAGE_VARIANTS,
},
{ expand: true }
)
@ -235,6 +236,7 @@ export const fetchTransaction = id => (dispatch, getState, sdk) => {
return sdk.listings.show({
id: listingId,
include: ['author', 'author.profileImage', 'images'],
...IMAGE_VARIANTS,
});
} else {
return response;
@ -305,7 +307,12 @@ const fetchMessages = (txId, page) => (dispatch, getState, sdk) => {
dispatch(fetchMessagesRequest());
return sdk.messages
.query({ transaction_id: txId, include: ['sender', 'sender.profileImage'], ...paging })
.query({
transaction_id: txId,
include: ['sender', 'sender.profileImage'],
...IMAGE_VARIANTS,
...paging,
})
.then(response => {
const messages = denormalisedResponseEntities(response);
const { totalItems, totalPages, page: fetchedPage } = response.data.meta;
@ -375,6 +382,17 @@ export const sendMessage = (txId, message) => (dispatch, getState, sdk) => {
};
const REVIEW_TX_INCLUDES = ['reviews', 'reviews.author', 'reviews.subject'];
const IMAGE_VARIANTS = {
'fields.image': [
// Profile images
'variants.square-small',
'variants.square-small2x',
// Listing images:
'variants.landscape-crop',
'variants.landscape-crop2x',
],
};
// If other party has already sent a review, we need to make transition to
// TRANSITION_REVIEW_2_BY_<CUSTOMER/PROVIDER>
@ -385,7 +403,7 @@ const sendReviewAsSecond = (id, params, role, dispatch, sdk) => {
const include = REVIEW_TX_INCLUDES;
return sdk.transactions
.transition({ id, transition, params }, { expand: true, include })
.transition({ id, transition, params }, { expand: true, include, ...IMAGE_VARIANTS })
.then(response => {
dispatch(addMarketplaceEntities(response));
dispatch(sendReviewSuccess());
@ -411,7 +429,7 @@ const sendReviewAsFirst = (id, params, role, dispatch, sdk) => {
const include = REVIEW_TX_INCLUDES;
return sdk.transactions
.transition({ id, transition, params }, { expand: true, include })
.transition({ id, transition, params }, { expand: true, include, ...IMAGE_VARIANTS })
.then(response => {
dispatch(addMarketplaceEntities(response));
dispatch(sendReviewSuccess());

View file

@ -332,8 +332,13 @@ export const fetchCurrentUser = () => (dispatch, getState, sdk) => {
return Promise.resolve({});
}
const params = {
include: ['profileImage'],
'fields.image': ['variants.square-small', 'variants.square-small2x'],
};
return sdk.currentUser
.show({ include: ['profileImage'] })
.show(params)
.then(response => {
const entities = denormalisedResponseEntities(response);
if (entities.length !== 1) {

View file

@ -60,20 +60,21 @@ export const createImage = id => ({
id: new UUID(id),
type: 'image',
attributes: {
sizes: [
{
variants: {
// TODO: add all possible variants here
square: {
name: 'square',
height: 408,
width: 408,
url: 'https://via.placeholder.com/408x408',
},
{
square2x: {
name: 'square2x',
height: 816,
width: 816,
url: 'https://via.placeholder.com/816x816',
},
],
},
},
});

View file

@ -80,14 +80,6 @@ propTypes.image = shape({
id: propTypes.uuid.isRequired,
type: propTypes.value('image').isRequired,
attributes: shape({
sizes: arrayOf(
shape({
width: number.isRequired,
height: number.isRequired,
name: string.isRequired,
url: string.isRequired,
})
),
variants: objectOf(
shape({
width: number.isRequired,