mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-29 05:00:43 +10:00
Separate images container to a component
This commit is contained in:
parent
8931a701a1
commit
c0e3113ce6
9 changed files with 230 additions and 70 deletions
33
src/components/AddImages/AddImages.css
Normal file
33
src/components/AddImages/AddImages.css
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
.imagesContainer {
|
||||
width: 100%;
|
||||
min-height: 120px;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
display: block;
|
||||
float: left;
|
||||
position: relative;
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
margin: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.thumbnailImage {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.thumbnailLoading {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: rgba(211, 211, 211, 0.8);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
29
src/components/AddImages/AddImages.example.css
Normal file
29
src/components/AddImages/AddImages.example.css
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
.addImageWrapper {
|
||||
float: left;
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
margin: 5px;
|
||||
|
||||
&::after {
|
||||
content: ".";
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
height: 0;
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
.addImage {
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
background-color: lightgrey;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.addImageInput {
|
||||
display: none;
|
||||
}
|
||||
83
src/components/AddImages/AddImages.example.js
Normal file
83
src/components/AddImages/AddImages.example.js
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
import React, { Component } from 'react';
|
||||
import { findIndex } from 'lodash';
|
||||
import { arrayMove } from 'react-sortable-hoc';
|
||||
import AddImages from './AddImages';
|
||||
import css from './AddImages.example.css';
|
||||
|
||||
let id = 0;
|
||||
const getId = () => {
|
||||
id += 1;
|
||||
return id;
|
||||
};
|
||||
|
||||
class AddImagesTest extends Component {
|
||||
constructor(props, state) {
|
||||
super(props, state);
|
||||
this.state = {
|
||||
images: [],
|
||||
};
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.onSortEnd = this.onSortEnd.bind(this);
|
||||
}
|
||||
|
||||
onChange(event) {
|
||||
const file = event.target.files[0];
|
||||
const fileId = getId();
|
||||
const imageData = { file, id: fileId, imageId: null };
|
||||
|
||||
// Show loading overlay
|
||||
this.setState({
|
||||
images: this.state.images.concat([imageData]),
|
||||
});
|
||||
|
||||
// Fake image uploaded state: show image thumbnail
|
||||
setTimeout(
|
||||
() => {
|
||||
|
||||
this.setState((prevState, props) => {
|
||||
const images = prevState.images;
|
||||
const imageIndex = findIndex(images, i => i.id === fileId);
|
||||
const updatedImage = { ...imageData, imageId: fileId };
|
||||
const updatedImages = [
|
||||
...images.slice(0, imageIndex),
|
||||
updatedImage,
|
||||
...images.slice(imageIndex + 1),
|
||||
];
|
||||
return {
|
||||
images: updatedImages,
|
||||
};
|
||||
});
|
||||
},
|
||||
1000);
|
||||
}
|
||||
|
||||
onSortEnd({ oldIndex, newIndex }) {
|
||||
const { images } = this.state;
|
||||
this.setState({
|
||||
images: arrayMove(images, oldIndex, newIndex),
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<AddImages images={this.state.images} onSortEnd={this.onSortEnd}>
|
||||
<div className={css.addImageWrapper}>
|
||||
<label className={css.addImage} htmlFor="addImageExampleInput">+ Add image</label>
|
||||
<input
|
||||
id="addImageExampleInput"
|
||||
type="file"
|
||||
accept="images/*"
|
||||
onChange={this.onChange}
|
||||
className={css.addImageInput}
|
||||
/>
|
||||
</div>
|
||||
</AddImages>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const Empty = {
|
||||
component: AddImagesTest,
|
||||
};
|
||||
70
src/components/AddImages/AddImages.js
Normal file
70
src/components/AddImages/AddImages.js
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/**
|
||||
* Creates a sortable image grid with children added to the end of the created grid.
|
||||
*
|
||||
* Example:
|
||||
* // images = [{ id: 'tempId', imageId: 'realIdFromAPI', file: File }];
|
||||
* <AddImages images={images}>
|
||||
* <input type="file" accept="images/*" onChange={handleChange} />
|
||||
* </AddImages>
|
||||
*/
|
||||
import React, { PropTypes } from 'react';
|
||||
import { Promised } from '../../components';
|
||||
import css from './AddImages.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);
|
||||
});
|
||||
|
||||
const AddImages = props => {
|
||||
const { children, images } = props;
|
||||
return (
|
||||
<div className={css.imagesContainer}>
|
||||
{images.map(i => {
|
||||
// While image is uploading we show overlay on top of thumbnail
|
||||
const uploadingOverlay = !i.imageId
|
||||
? <div className={css.thumbnailLoading}>Uploading</div>
|
||||
: null;
|
||||
return (
|
||||
<Promised
|
||||
key={i.id}
|
||||
promise={readImage(i.file)}
|
||||
renderFulfilled={dataURL => {
|
||||
return (
|
||||
<div className={css.thumbnail}>
|
||||
<img
|
||||
src={dataURL}
|
||||
alt={encodeURIComponent(i.file.name)}
|
||||
className={css.thumbnailImage}
|
||||
/>
|
||||
{uploadingOverlay}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
renderRejected={() => <div className={css.thumbnail}>Could not read file</div>}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
AddImages.defaultProps = { images: [] };
|
||||
|
||||
const { array, node } = PropTypes;
|
||||
|
||||
AddImages.propTypes = {
|
||||
images: array,
|
||||
children: node.isRequired,
|
||||
};
|
||||
|
||||
export default AddImages;
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import AddImages from './AddImages/AddImages';
|
||||
import BookingInfo from './BookingInfo/BookingInfo';
|
||||
import Discussion from './Discussion/Discussion';
|
||||
import FilterPanel from './FilterPanel/FilterPanel';
|
||||
|
|
@ -16,6 +17,7 @@ import RoutesProvider from './RoutesProvider/RoutesProvider';
|
|||
import SearchResultsPanel from './SearchResultsPanel/SearchResultsPanel';
|
||||
|
||||
export {
|
||||
AddImages,
|
||||
BookingInfo,
|
||||
Discussion,
|
||||
FilterPanel,
|
||||
|
|
|
|||
|
|
@ -2,11 +2,6 @@
|
|||
color: red;
|
||||
}
|
||||
|
||||
.imagesContainer {
|
||||
width: 100%;
|
||||
min-height: 110px;
|
||||
}
|
||||
|
||||
.addImageWrapper {
|
||||
float: left;
|
||||
width: 110px;
|
||||
|
|
@ -33,31 +28,8 @@
|
|||
margin-top: 0;
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
float: left;
|
||||
position: relative;
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
margin: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.thumbnailImage {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.thumbnailLoading {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: rgba(211, 211, 211, 0.8);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.addImageInput {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.imageRequiredWrapper {
|
||||
|
|
|
|||
|
|
@ -5,11 +5,14 @@ export const Empty = {
|
|||
component: EditListingForm,
|
||||
props: {
|
||||
images: [],
|
||||
onImageUpload(values) {
|
||||
onImageUpload: values => {
|
||||
console.log(`onImageUpload with id (${values.id}) and file name (${values.file.name})`);
|
||||
},
|
||||
onSubmit(values) {
|
||||
onSubmit: values => {
|
||||
console.log('Submit EditListingForm with (unformatted) values:', values);
|
||||
},
|
||||
onUpdateImageOrder: imageOrder => {
|
||||
console.log('onUpdateImageOrder with new imageOrder:', imageOrder);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,25 +3,12 @@ import { Field, reduxForm, propTypes as formPropTypes } from 'redux-form';
|
|||
import { intlShape, injectIntl } from 'react-intl';
|
||||
import { isEqual } from 'lodash';
|
||||
import { noEmptyArray, maxLength, required } from '../../util/validators';
|
||||
import { Promised } from '../../components';
|
||||
import { AddImages } from '../../components';
|
||||
import css from './EditListingForm.css';
|
||||
|
||||
const ACCEPT_IMAGES = 'image/*';
|
||||
const TITLE_MAX_LENGTH = 60;
|
||||
|
||||
// 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);
|
||||
});
|
||||
|
||||
// Custom inputs with validator messages
|
||||
const RenderField = ({ input, label, type, meta }) => {
|
||||
const { touched, error } = meta;
|
||||
|
|
@ -61,7 +48,7 @@ const RenderAddImage = props => {
|
|||
const inputProps = { accept, id: name, name, onChange, type };
|
||||
return (
|
||||
<div className={css.addImageWrapper}>
|
||||
<input {...inputProps} style={{ display: 'none' }} />
|
||||
<input {...inputProps} className={css.addImageInput} />
|
||||
<label htmlFor={name} className={css.addImage}>{label}</label>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -135,28 +122,7 @@ class EditListingForm extends Component {
|
|||
/>
|
||||
|
||||
<h3>Images</h3>
|
||||
<div className={css.imagesContainer}>
|
||||
{images.map(i => {
|
||||
// While image is uploading we show overlay on top of thumbnail
|
||||
const uploadingOverlay = !i.imageId
|
||||
? <div className={css.thumbnailLoading}>Uploading</div>
|
||||
: null;
|
||||
return (
|
||||
<Promised
|
||||
key={i.id}
|
||||
promise={readImage(i.file)}
|
||||
renderFulfilled={dataURL => {
|
||||
return (
|
||||
<div className={css.thumbnail}>
|
||||
<img src={dataURL} alt={i.file.name} className={css.thumbnailImage} />
|
||||
{uploadingOverlay}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
renderRejected={() => <div className={css.thumbnail}>Could not read file</div>}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<AddImages images={images}>
|
||||
<Field
|
||||
accept={ACCEPT_IMAGES}
|
||||
component={RenderAddImage}
|
||||
|
|
@ -182,7 +148,7 @@ class EditListingForm extends Component {
|
|||
type="hidden"
|
||||
validate={[noEmptyArray(imageRequiredStr)]}
|
||||
/>
|
||||
</div>
|
||||
</AddImages>
|
||||
|
||||
<Field
|
||||
name="description"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// components
|
||||
import * as AddImages from './components/AddImages/AddImages.example';
|
||||
import * as BookingInfo from './components/BookingInfo/BookingInfo.example';
|
||||
import * as ListingCard from './components/ListingCard/ListingCard.example';
|
||||
import * as NamedLink from './components/NamedLink/NamedLink.example';
|
||||
|
|
@ -17,6 +18,7 @@ import * as PasswordForgottenForm
|
|||
import * as SignUpForm from './containers/SignUpForm/SignUpForm.example';
|
||||
|
||||
export {
|
||||
AddImages,
|
||||
BookingInfo,
|
||||
ChangeAccountPasswordForm,
|
||||
ChangePasswordForm,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue