mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-26 06:47:17 +10:00
Use SortableContainer
This commit is contained in:
parent
b9a421834a
commit
3ff30f4720
6 changed files with 75 additions and 33 deletions
|
|
@ -8,6 +8,7 @@
|
|||
* </AddImages>
|
||||
*/
|
||||
import React, { PropTypes } from 'react';
|
||||
import { SortableContainer, SortableElement } from 'react-sortable-hoc';
|
||||
import { Promised } from '../../components';
|
||||
import css from './AddImages.css';
|
||||
|
||||
|
|
@ -24,47 +25,54 @@ const readImage = file => new Promise((resolve, reject) => {
|
|||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
const AddImages = props => {
|
||||
// Create sortable elments out of given thumbnail file
|
||||
const SortableImage = SortableElement(props => {
|
||||
const { file, id, imageId } = props;
|
||||
// While image is uploading we show overlay on top of thumbnail
|
||||
const uploadingOverlay = !imageId ? <div className={css.thumbnailLoading}>Uploading</div> : null;
|
||||
return (
|
||||
<Promised
|
||||
key={id}
|
||||
promise={readImage(file)}
|
||||
renderFulfilled={dataURL => {
|
||||
return (
|
||||
<li className={css.thumbnail}>
|
||||
<img src={dataURL} alt={encodeURIComponent(file.name)} className={css.thumbnailImage} />
|
||||
{uploadingOverlay}
|
||||
</li>
|
||||
);
|
||||
}}
|
||||
renderRejected={() => <li className={css.thumbnail}>Could not read file</li>}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
// Create container where there are sortable images and passed children like "Add image" input etc.
|
||||
const SortableImages = SortableContainer(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>}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<ol className={css.imagesContainer}>
|
||||
{images.map((image, index) => <SortableImage {...image} index={index} key={image.id} />)}
|
||||
{children}
|
||||
</div>
|
||||
</ol>
|
||||
);
|
||||
});
|
||||
|
||||
// Configure sortable container see. https://github.com/clauderic/react-sortable-hoc
|
||||
// Items can be sorted horizontally, vertically or in a grid.
|
||||
// axis="xy" means grid like sorting
|
||||
const AddImages = props => {
|
||||
return <SortableImages axis="xy" {...props} />;
|
||||
};
|
||||
|
||||
AddImages.defaultProps = { images: [] };
|
||||
|
||||
const { array, node } = PropTypes;
|
||||
const { array, func, node } = PropTypes;
|
||||
|
||||
AddImages.propTypes = {
|
||||
images: array,
|
||||
children: node.isRequired,
|
||||
onSortEnd: func.isRequired,
|
||||
};
|
||||
|
||||
export default AddImages;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React, { Component, PropTypes } from 'react';
|
|||
import { Field, reduxForm, propTypes as formPropTypes } from 'redux-form';
|
||||
import { intlShape, injectIntl } from 'react-intl';
|
||||
import { isEqual } from 'lodash';
|
||||
import { arrayMove } from 'react-sortable-hoc';
|
||||
import { noEmptyArray, maxLength, required } from '../../util/validators';
|
||||
import { AddImages } from '../../components';
|
||||
import css from './EditListingForm.css';
|
||||
|
|
@ -70,6 +71,7 @@ class EditListingForm extends Component {
|
|||
super(props);
|
||||
this.handleInitialize = this.handleInitialize.bind(this);
|
||||
this.onImageUploadHandler = this.onImageUploadHandler.bind(this);
|
||||
this.onSortEnd = this.onSortEnd.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
|
@ -89,6 +91,11 @@ class EditListingForm extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
onSortEnd({ oldIndex, newIndex }) {
|
||||
const images = arrayMove(this.props.images, oldIndex, newIndex);
|
||||
this.props.onUpdateImageOrder(images.map(i => i.id));
|
||||
}
|
||||
|
||||
handleInitialize() {
|
||||
const { initData = {}, initialize } = this.props;
|
||||
initialize(initData);
|
||||
|
|
@ -122,7 +129,7 @@ class EditListingForm extends Component {
|
|||
/>
|
||||
|
||||
<h3>Images</h3>
|
||||
<AddImages images={images}>
|
||||
<AddImages images={images} onSortEnd={this.onSortEnd}>
|
||||
<Field
|
||||
accept={ACCEPT_IMAGES}
|
||||
component={RenderAddImage}
|
||||
|
|
@ -169,6 +176,9 @@ EditListingForm.propTypes = {
|
|||
...formPropTypes,
|
||||
initData: shape({ title: string, description: string }),
|
||||
intl: intlShape.isRequired,
|
||||
onImageUpload: func.isRequired,
|
||||
onUpdateImageOrder: func.isRequired,
|
||||
onSubmit: func.isRequired,
|
||||
};
|
||||
|
||||
export default reduxForm({ form: 'EditListingForm' })(injectIntl(EditListingForm));
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ export const UPLOAD_IMAGE_REQUEST = 'app/EditListingPage/UPLOAD_IMAGE_REQUEST';
|
|||
export const UPLOAD_IMAGE_SUCCESS = 'app/EditListingPage/UPLOAD_IMAGE_SUCCESS';
|
||||
export const UPLOAD_IMAGE_ERROR = 'app/EditListingPage/UPLOAD_IMAGE_ERROR';
|
||||
|
||||
export const UPDATE_IMAGE_ORDER = 'app/EditListingPage/UPDATE_IMAGE_ORDER';
|
||||
|
||||
// ================ Reducer ================ //
|
||||
|
||||
const initialState = {
|
||||
|
|
@ -82,6 +84,8 @@ export default function reducer(state = initialState, action = {}) {
|
|||
};
|
||||
return { ...state, images };
|
||||
}
|
||||
case UPDATE_IMAGE_ORDER:
|
||||
return { ...state, imageOrder: payload.imageOrder };
|
||||
|
||||
default:
|
||||
return state;
|
||||
|
|
@ -92,6 +96,11 @@ export default function reducer(state = initialState, action = {}) {
|
|||
|
||||
// ================ Action creators ================ //
|
||||
|
||||
export const updateImageOrder = imageOrder => ({
|
||||
type: UPDATE_IMAGE_ORDER,
|
||||
payload: { imageOrder },
|
||||
});
|
||||
|
||||
// All the action creators that don't have the {Success, Error} suffix
|
||||
// take the params object that the corresponding SDK endpoint method
|
||||
// expects.
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
requestCreateListing,
|
||||
requestShowListing,
|
||||
requestImageUpload,
|
||||
updateImageOrder,
|
||||
} from './EditListingPage.duck';
|
||||
|
||||
const formatRequestData = values => {
|
||||
|
|
@ -39,7 +40,16 @@ export class EditListingPageComponent extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { data, intl, onCreateListing, onImageUpload, page, params, type } = this.props;
|
||||
const {
|
||||
data,
|
||||
intl,
|
||||
onCreateListing,
|
||||
onImageUpload,
|
||||
onUpdateImageOrder,
|
||||
page,
|
||||
params,
|
||||
type,
|
||||
} = this.props;
|
||||
const isNew = type === 'new';
|
||||
const id = page.submittedListingId || (params && new types.UUID(params.id));
|
||||
const listingsById = getListingsById(data, [id]);
|
||||
|
|
@ -77,6 +87,7 @@ export class EditListingPageComponent extends Component {
|
|||
initData={initData}
|
||||
onImageUpload={onImageUpload}
|
||||
onSubmit={onSubmit(onCreateListing)}
|
||||
onUpdateImageOrder={onUpdateImageOrder}
|
||||
saveActionMsg={saveActionMsg}
|
||||
/>
|
||||
</PageLayout>
|
||||
|
|
@ -103,9 +114,10 @@ const { func, object, shape, string } = PropTypes;
|
|||
EditListingPageComponent.propTypes = {
|
||||
data: object.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
onImageUpload: func.isRequired,
|
||||
onLoadListing: func.isRequired,
|
||||
onCreateListing: func.isRequired,
|
||||
onLoadListing: func.isRequired,
|
||||
onImageUpload: func.isRequired,
|
||||
onUpdateImageOrder: func.isRequired,
|
||||
page: object.isRequired,
|
||||
params: shape({
|
||||
id: string,
|
||||
|
|
@ -124,6 +136,7 @@ const mapDispatchToProps = dispatch => {
|
|||
onLoadListing: id => dispatch(requestShowListing({ id, include: ['author', 'images'] })),
|
||||
onCreateListing: values => dispatch(requestCreateListing(formatRequestData(values))),
|
||||
onImageUpload: data => dispatch(requestImageUpload(data)),
|
||||
onUpdateImageOrder: imageOrder => dispatch(updateImageOrder(imageOrder)),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ describe('EditListingPageComponent', () => {
|
|||
onCreateListing={v => v}
|
||||
onLoadListing={v => v}
|
||||
onImageUpload={v => v}
|
||||
onUpdateImageOrder={v => v}
|
||||
page={{ imageOrder: [], images: {} }}
|
||||
type="new"
|
||||
/>,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ exports[`EditListingPageComponent matches snapshot 1`] = `
|
|||
initData={Object {}}
|
||||
onImageUpload={[Function]}
|
||||
onSubmit={[Function]}
|
||||
onUpdateImageOrder={[Function]}
|
||||
saveActionMsg="Create listing" />
|
||||
</Connect(withRouter(PageLayout))>
|
||||
`;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue