mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-29 13:06:03 +10:00
Merge pull request #66 from sharetribe/sortable-images-container
Sortable images container
This commit is contained in:
commit
7fac0a2df1
17 changed files with 326 additions and 157 deletions
|
|
@ -17,6 +17,7 @@
|
|||
"react-intl": "^2.2.3",
|
||||
"react-redux": "^5.0.3",
|
||||
"react-router-dom": "4.0.0-beta.6",
|
||||
"react-sortable-hoc": "^0.6.1",
|
||||
"redux": "^3.6.0",
|
||||
"redux-form": "^6.5.0",
|
||||
"redux-saga": "^0.14.3",
|
||||
|
|
|
|||
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;
|
||||
}
|
||||
81
src/components/AddImages/AddImages.example.js
Normal file
81
src/components/AddImages/AddImages.example.js
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import React, { Component } from 'react';
|
||||
import { findIndex, uniqueId } from 'lodash';
|
||||
import { arrayMove } from 'react-sortable-hoc';
|
||||
import AddImages from './AddImages';
|
||||
import css from './AddImages.example.css';
|
||||
|
||||
const getId = () => {
|
||||
return uniqueId();
|
||||
};
|
||||
|
||||
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) => {
|
||||
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,
|
||||
};
|
||||
97
src/components/AddImages/AddImages.js
Normal file
97
src/components/AddImages/AddImages.js
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
/**
|
||||
* 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, { Component, PropTypes } from 'react';
|
||||
import { SortableContainer, SortableElement } from 'react-sortable-hoc';
|
||||
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);
|
||||
});
|
||||
|
||||
// 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 { file, id, imageId } = this.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={this.state.promisedImage}
|
||||
renderFulfilled={dataURL => {
|
||||
return (
|
||||
<li className={css.thumbnail}>
|
||||
<img src={dataURL} alt={file.name} className={css.thumbnailImage} />
|
||||
{uploadingOverlay}
|
||||
</li>
|
||||
);
|
||||
}}
|
||||
renderRejected={() => <li className={css.thumbnail}>Could not read file</li>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Thumbnail.defaultProps = { imageId: null };
|
||||
|
||||
const { any, array, func, node, string } = PropTypes;
|
||||
|
||||
Thumbnail.propTypes = {
|
||||
file: any.isRequired,
|
||||
id: string.isRequired,
|
||||
imageId: string,
|
||||
};
|
||||
|
||||
const SortableImage = SortableElement(Thumbnail);
|
||||
|
||||
// Create container where there are sortable images and passed children like "Add image" input etc.
|
||||
const SortableImages = SortableContainer(props => {
|
||||
const { children, images } = props;
|
||||
return (
|
||||
<ol className={css.imagesContainer}>
|
||||
{images.map((image, index) => <SortableImage {...image} index={index} key={image.id} />)}
|
||||
{children}
|
||||
</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: [] };
|
||||
|
||||
AddImages.propTypes = {
|
||||
images: array,
|
||||
children: node.isRequired,
|
||||
onSortEnd: func.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);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,26 +2,14 @@ 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 { 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 +49,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>
|
||||
);
|
||||
|
|
@ -83,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() {
|
||||
|
|
@ -102,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);
|
||||
|
|
@ -135,28 +129,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} onSortEnd={this.onSortEnd}>
|
||||
<Field
|
||||
accept={ACCEPT_IMAGES}
|
||||
component={RenderAddImage}
|
||||
|
|
@ -182,7 +155,7 @@ class EditListingForm extends Component {
|
|||
type="hidden"
|
||||
validate={[noEmptyArray(imageRequiredStr)]}
|
||||
/>
|
||||
</div>
|
||||
</AddImages>
|
||||
|
||||
<Field
|
||||
name="description"
|
||||
|
|
@ -203,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));
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
// TODO: renderdeep doesn't work due to
|
||||
// "Invariant Violation: getNodeFromInstance: Invalid argument."
|
||||
// refs and findDOMNode are not supported by react-test-renderer
|
||||
// (react-sortable-hoc uses them)
|
||||
import React from 'react';
|
||||
import { renderDeep } from '../../util/test-helpers';
|
||||
import { renderShallow } from '../../util/test-helpers';
|
||||
import EditListingForm from './EditListingForm';
|
||||
|
||||
describe('EditListingForm', () => {
|
||||
it('matches snapshot', () => {
|
||||
const tree = renderDeep(<EditListingForm images={[]} />);
|
||||
const tree = renderShallow(<EditListingForm images={[]} onImageUpload={v => v} onSubmit={v => v} onUpdateImageOrder={v => v} />);
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,82 +1,19 @@
|
|||
exports[`EditListingForm matches snapshot 1`] = `
|
||||
<form
|
||||
onSubmit={[Function]}>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="title">
|
||||
Title
|
||||
</label>
|
||||
<div>
|
||||
<input
|
||||
name="title"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onDragStart={[Function]}
|
||||
onDrop={[Function]}
|
||||
onFocus={[Function]}
|
||||
placeholder="Title"
|
||||
type="text"
|
||||
value="" />
|
||||
</div>
|
||||
</div>
|
||||
<h3>
|
||||
Images
|
||||
</h3>
|
||||
<div
|
||||
className={undefined}>
|
||||
<div
|
||||
className={undefined}>
|
||||
<input
|
||||
accept="image/*"
|
||||
id="addImage"
|
||||
name="addImage"
|
||||
onChange={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"display": "none",
|
||||
}
|
||||
}
|
||||
type="file" />
|
||||
<label
|
||||
className={undefined}
|
||||
htmlFor="addImage">
|
||||
+ Add image
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className={undefined}>
|
||||
<input
|
||||
name="images"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onDragStart={[Function]}
|
||||
onDrop={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="hidden"
|
||||
value="" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="description">
|
||||
Description
|
||||
</label>
|
||||
<div>
|
||||
<textarea
|
||||
name="description"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onDragStart={[Function]}
|
||||
onDrop={[Function]}
|
||||
onFocus={[Function]}
|
||||
placeholder="Description"
|
||||
value="" />
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
disabled={true}
|
||||
type="submit">
|
||||
Save listing
|
||||
</button>
|
||||
</form>
|
||||
<Connect(Form(InjectIntl(EditListingForm)))
|
||||
destroyOnUnmount={true}
|
||||
enableReinitialize={false}
|
||||
forceUnregisterOnUnmount={false}
|
||||
form="EditListingForm"
|
||||
getFormState={[Function]}
|
||||
images={Array []}
|
||||
keepDirtyOnReinitialize={false}
|
||||
onImageUpload={[Function]}
|
||||
onSubmit={[Function]}
|
||||
onUpdateImageOrder={[Function]}
|
||||
persistentSubmitErrors={false}
|
||||
pure={true}
|
||||
shouldAsyncValidate={[Function]}
|
||||
shouldValidate={[Function]}
|
||||
touchOnBlur={true}
|
||||
touchOnChange={false} />
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -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))>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
12
yarn.lock
12
yarn.lock
|
|
@ -932,7 +932,7 @@ babel-runtime@6.22.0:
|
|||
core-js "^2.4.0"
|
||||
regenerator-runtime "^0.10.0"
|
||||
|
||||
babel-runtime@^6.18.0, babel-runtime@^6.20.0, babel-runtime@^6.22.0:
|
||||
babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.20.0, babel-runtime@^6.22.0:
|
||||
version "6.23.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b"
|
||||
dependencies:
|
||||
|
|
@ -4033,7 +4033,7 @@ lodash.uniq@^4.3.0:
|
|||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
||||
|
||||
"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0:
|
||||
"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.12.0, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0:
|
||||
version "4.17.4"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
|
||||
|
||||
|
|
@ -5421,6 +5421,14 @@ react-side-effect@^1.1.0:
|
|||
exenv "^1.2.1"
|
||||
shallowequal "^0.2.2"
|
||||
|
||||
react-sortable-hoc@^0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/react-sortable-hoc/-/react-sortable-hoc-0.6.1.tgz#921ffdfab7b3918164e02db61db3bd309a2b5df3"
|
||||
dependencies:
|
||||
babel-runtime "^6.11.6"
|
||||
invariant "^2.2.1"
|
||||
lodash "^4.12.0"
|
||||
|
||||
react-test-renderer@^15.4.2:
|
||||
version "15.4.2"
|
||||
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-15.4.2.tgz#27e1dff5d26d0e830f99614c487622bc831416f3"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue