mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-29 05:00:43 +10:00
Extract ImageFromFile component away from AddImages
This commit is contained in:
parent
d4310d4a0f
commit
02da6a5de9
5 changed files with 131 additions and 82 deletions
|
|
@ -7,12 +7,10 @@
|
|||
* <input type="file" accept="images/*" onChange={handleChange} />
|
||||
* </AddImages>
|
||||
*/
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import React, { PropTypes } from 'react';
|
||||
import { SortableContainer } from 'react-sortable-hoc';
|
||||
import classNames from 'classnames';
|
||||
import { Promised, ResponsiveImage } from '../../components';
|
||||
import { uuid } from '../../util/propTypes';
|
||||
import { ImageFromFile, ResponsiveImage, SpinnerIcon } from '../../components';
|
||||
import css from './AddImages.css';
|
||||
|
||||
const RemoveImageButton = props => {
|
||||
|
|
@ -41,89 +39,38 @@ const RemoveImageButton = props => {
|
|||
);
|
||||
};
|
||||
|
||||
const { any, array, func, node, string, object } = PropTypes;
|
||||
const { array, func, node, string, object } = PropTypes;
|
||||
|
||||
RemoveImageButton.propTypes = { onClick: func.isRequired };
|
||||
|
||||
// readImage returns a promise which is resolved
|
||||
// when FileReader has loaded given file as dataURL
|
||||
const readImage = file =>
|
||||
new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = e => resolve(e.target.result);
|
||||
reader.onerror = e => {
|
||||
// eslint-disable-next-line
|
||||
console.error('Error (', e, `) happened while reading ${file.name}: ${e.target.result}`);
|
||||
reject(new Error(`Error reading ${file.name}: ${e.target.result}`));
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
// Create sortable elments out of given thumbnail file
|
||||
class Thumbnail extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
promisedImage: readImage(this.props.file),
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, file, id, imageId, onRemoveImage } = this.props;
|
||||
|
||||
const handleRemoveClick = e => {
|
||||
e.preventDefault();
|
||||
onRemoveImage(id);
|
||||
};
|
||||
|
||||
// While image is uploading we show overlay on top of thumbnail
|
||||
const uploadingOverlay = !imageId
|
||||
? <div className={css.thumbnailLoading}><FormattedMessage id="AddImages.upload" /></div>
|
||||
: null;
|
||||
const removeButton = imageId ? <RemoveImageButton onClick={handleRemoveClick} /> : null;
|
||||
const classes = classNames(css.thumbnail, className);
|
||||
return (
|
||||
<Promised
|
||||
key={id}
|
||||
promise={this.state.promisedImage}
|
||||
renderFulfilled={dataURL => {
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div className={css.aspectWrapper}>
|
||||
<img src={dataURL} alt={file.name} className={css.rootForImage} />
|
||||
</div>
|
||||
{removeButton}
|
||||
{uploadingOverlay}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
renderRejected={() => (
|
||||
<div className={css.thumbnail}><FormattedMessage id="AddImages.couldNotReadFile" /></div>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Thumbnail.defaultProps = { className: null, imageId: null };
|
||||
|
||||
Thumbnail.propTypes = {
|
||||
className: string,
|
||||
file: any.isRequired,
|
||||
id: string.isRequired,
|
||||
imageId: uuid,
|
||||
onRemoveImage: func.isRequired,
|
||||
};
|
||||
|
||||
const ThumbnailWrapper = props => {
|
||||
const { className, image, savedImageAltText, onRemoveImage } = props;
|
||||
const handleRemoveClick = e => {
|
||||
e.preventDefault();
|
||||
onRemoveImage(image.id);
|
||||
};
|
||||
|
||||
if (image.file) {
|
||||
return <Thumbnail className={className} onRemoveImage={onRemoveImage} {...image} />;
|
||||
// Remove this image if file has been uploaded
|
||||
const removeButton = image.imageId ? <RemoveImageButton onClick={handleRemoveClick} /> : null;
|
||||
|
||||
// While image is uploading we show overlay on top of thumbnail
|
||||
const uploadingOverlay = !image.imageId
|
||||
? <div className={css.thumbnailLoading}><SpinnerIcon /></div>
|
||||
: null;
|
||||
|
||||
return (
|
||||
<ImageFromFile
|
||||
id={image.id}
|
||||
className={className}
|
||||
rootClassName={css.thumbnail}
|
||||
file={image.file}
|
||||
>
|
||||
{removeButton}
|
||||
{uploadingOverlay}
|
||||
</ImageFromFile>
|
||||
);
|
||||
} else {
|
||||
const handleRemoveClick = e => {
|
||||
e.preventDefault();
|
||||
onRemoveImage(image.id);
|
||||
};
|
||||
const classes = classNames(css.thumbnail, className);
|
||||
return (
|
||||
<div className={classes}>
|
||||
|
|
|
|||
31
src/components/ImageFromFile/ImageFromFile.css
Normal file
31
src/components/ImageFromFile/ImageFromFile.css
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
@import '../../marketplace.css';
|
||||
|
||||
.root {
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
background-color: var(--matterColorNegative); /* Loading BG color */
|
||||
}
|
||||
|
||||
.threeToTwoWrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.aspectWrapper {
|
||||
padding-bottom: calc(100% * (2 / 3));
|
||||
}
|
||||
|
||||
.rootForImage {
|
||||
/* Layout - image will take space defined by aspect ratio wrapper */
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: var(--borderRadius);
|
||||
}
|
||||
70
src/components/ImageFromFile/ImageFromFile.js
Normal file
70
src/components/ImageFromFile/ImageFromFile.js
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import { Promised } from '../../components';
|
||||
|
||||
import css from './ImageFromFile.css';
|
||||
|
||||
// readImage returns a promise which is resolved
|
||||
// when FileReader has loaded given file as dataURL
|
||||
const readImage = file =>
|
||||
new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = e => resolve(e.target.result);
|
||||
reader.onerror = e => {
|
||||
// eslint-disable-next-line
|
||||
console.error('Error (', e, `) happened while reading ${file.name}: ${e.target.result}`);
|
||||
reject(new Error(`Error reading ${file.name}: ${e.target.result}`));
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
// Create elements out of given thumbnail file
|
||||
class ImageFromFile extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
promisedImage: readImage(this.props.file),
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, rootClassName, file, id, children } = this.props;
|
||||
const classes = classNames(rootClassName || css.root, className);
|
||||
return (
|
||||
<Promised
|
||||
key={id}
|
||||
promise={this.state.promisedImage}
|
||||
renderFulfilled={dataURL => {
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div className={css.threeToTwoWrapper}>
|
||||
<div className={css.aspectWrapper}>
|
||||
<img src={dataURL} alt={file.name} className={css.rootForImage} />
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
renderRejected={() => (
|
||||
<div className={classes}><FormattedMessage id="ImageFromFile.couldNotReadFile" /></div>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ImageFromFile.defaultProps = { className: null, children: null, rootClassName: null };
|
||||
|
||||
const { any, node, string } = PropTypes;
|
||||
|
||||
ImageFromFile.propTypes = {
|
||||
className: string,
|
||||
rootClassName: string,
|
||||
file: any.isRequired,
|
||||
id: string.isRequired,
|
||||
children: node,
|
||||
};
|
||||
|
||||
export default ImageFromFile;
|
||||
|
|
@ -18,6 +18,7 @@ import ExpandingTextarea from './ExpandingTextarea/ExpandingTextarea';
|
|||
import FilterPanel from './FilterPanel/FilterPanel';
|
||||
import HeroSection from './HeroSection/HeroSection';
|
||||
import ImageCarousel from './ImageCarousel/ImageCarousel';
|
||||
import ImageFromFile from './ImageFromFile/ImageFromFile';
|
||||
import ListingCard from './ListingCard/ListingCard';
|
||||
import LocationAutocompleteInput, {
|
||||
LocationAutocompleteInputField,
|
||||
|
|
@ -85,6 +86,7 @@ export {
|
|||
FilterPanel,
|
||||
HeroSection,
|
||||
ImageCarousel,
|
||||
ImageFromFile,
|
||||
InlineTextButton,
|
||||
ListingCard,
|
||||
LocationAutocompleteInput,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
{
|
||||
"AddImages.couldNotReadFile": "Could not read file",
|
||||
"AddImages.upload": "Uploading",
|
||||
"AuthenticationPage.emailAlreadyInUse": "An account already exists with this email address. Try logging in instead.",
|
||||
"AuthenticationPage.loginFailed": "The email and password you entered did not match our records. Please double-check and try again.",
|
||||
"AuthenticationPage.loginLinkText": "Login",
|
||||
|
|
@ -125,6 +123,7 @@
|
|||
"HeroSection.subTitle": "The largest online community to rent saunas in Finland.",
|
||||
"HeroSection.title": "Book saunas everywhere.",
|
||||
"ImageCarousel.imageAltText": "Image {index}/{count}",
|
||||
"ImageFromFile.couldNotReadFile": "Could not read file",
|
||||
"InboxPage.fetchFailed": "Could not load all messages. Please try again.",
|
||||
"InboxPage.noOrdersFound": "You haven't made any bookings.",
|
||||
"InboxPage.noSalesFound": "Nobody has booked anything from you yet.",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue